#!/usr/bin/env -S npx --yes tsx // ============================================================================= // set.ts — Store a value in the vault // // Part of: .opencode/skills/templating-vault/scripts/ // // Stores a value at a dot-separated key. Enforces: // - Literal keys can only be set via extract.ts (rejected here) // - Write-once keys cannot be overwritten // - Type and regex validation // - Owner-based access control // // USAGE: // set.ts --key --value [--owner ] [--write-once] // [--literal] [--force-literal] [--owner-overwrite] // [--type ] [--description ] // [--config ] [--store-dir ] // // OUTPUT (stdout): JSON result // ============================================================================= import { fileURLToPath } from 'node:url'; import { parseCommonFlags, getFlag, hasFlag, output, log, loadConfig, resolveStore, loadKeyMeta, saveKeyMeta, readValue, writeValue, hasValue, validateValue, type KeyMeta, } from './api.ts'; async function main(): Promise { const { config: cfgPath, storeDirs, rest } = parseCommonFlags(process.argv); const config = loadConfig(cfgPath, storeDirs.length > 0 ? storeDirs : undefined); const key = getFlag(rest, '--key'); const value = getFlag(rest, '--value'); const owner = getFlag(rest, '--owner'); const writeOnce = hasFlag(rest, '--write-once'); const forceLiteral = hasFlag(rest, '--force-literal'); // internal: used by extract.ts const setLiteralFlag = hasFlag(rest, '--literal'); // set literal in metadata const ownerOverwrite = hasFlag(rest, '--owner-overwrite'); const typeFlag = getFlag(rest, '--type'); const description = getFlag(rest, '--description'); if (!key) { output({ stored: false, error: '--key is required' }); process.exit(1); } if (value === undefined) { output({ stored: false, error: '--value is required' }); process.exit(1); } // Check access level const resolved = resolveStore(config, key); if (resolved.mapping.access === 'readonly') { output({ stored: false, error: `Store for prefix "${resolved.mapping.prefix}" is readonly` }); process.exit(1); } // Load existing metadata const meta = loadKeyMeta(config, key); // Literal enforcement: reject direct set on literal keys unless: // - --force-literal is set (internal flag used by extract.ts) // - Store access is 'admin' (bootstrap/setup mode) if (meta.literal && !forceLiteral && resolved.mapping.access !== 'admin') { output({ stored: false, error: `Cannot directly set literal variable "${key}". Use extract.ts with a regex instead, or use admin access to bootstrap.`, key, literal: true, }); process.exit(1); } // Write-once enforcement if (meta.write_once && hasValue(config, key)) { if (ownerOverwrite && meta.owner && meta.owner === owner) { // Owner can overwrite write-once keys log(`Owner-overwrite: updating write-once key "${key}"`); } else { output({ stored: false, error: `Key "${key}" is write-once and already has a value`, key, write_once: true, }); process.exit(1); } } // Readonly enforcement if (meta.readonly && hasValue(config, key) && resolved.mapping.access !== 'admin') { output({ stored: false, error: `Key "${key}" is readonly`, key, readonly: true, }); process.exit(1); } // Validation const validationError = validateValue(value, meta); if (validationError) { output({ stored: false, error: `Validation failed for "${key}": ${validationError}`, key }); process.exit(1); } // Write value writeValue(config, key, value); // Update metadata if flags provided const updatedMeta: KeyMeta = { ...meta }; if (writeOnce) updatedMeta.write_once = true; if (setLiteralFlag) updatedMeta.literal = true; if (owner) updatedMeta.owner = owner; if (typeFlag) updatedMeta.type = typeFlag; if (description) updatedMeta.description = description; // Only save meta if something changed from defaults or explicitly provided if (writeOnce || setLiteralFlag || owner || typeFlag || description) { saveKeyMeta(config, key, updatedMeta); } output({ stored: true, key, write_once: updatedMeta.write_once ?? false }); } if (process.argv[1] === fileURLToPath(import.meta.url)) { main().catch((err: unknown) => { output({ stored: false, error: String(err instanceof Error ? err.message : err) }); process.exit(1); }); }