UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

201 lines (198 loc) 7.64 kB
/** * Generic subsystem CLI runner. * * Provides path/list/get/put/delete/append-log subcommands for any * storage subsystem. Used by `aiwg memory` (#966), `aiwg reflections` * (#967), and any future per-subsystem CLI wrappers — keeping the * shared logic in one place rather than duplicating it. * * Each subsystem-specific CLI is a 5-line wrapper: * * import { runSubsystemCli } from '../storage/subsystem-cli.js'; * export async function main(args: string[]): Promise<void> { * await runSubsystemCli('memory', args); * } * * @design @.aiwg/architecture/storage-design.md (§4) * @issue #934 * @issue #966 * @issue #967 */ import { getLoadedConfig, resolveStorage } from './index.js'; import { resolveSubsystemRoot } from './config.js'; export async function runSubsystemCli(subsystem, args, opts = {}) { const subcommand = args[0]; const subArgs = args.slice(1); const display = opts.displayName ?? subsystem; switch (subcommand) { case 'path': await handlePath(subsystem, display, subArgs); break; case 'list': await handleList(subsystem, display, subArgs); break; case 'get': await handleGet(subsystem, display, subArgs); break; case 'put': await handlePut(subsystem, display, subArgs); break; case 'delete': await handleDelete(subsystem, display, subArgs); break; case 'append-log': await handleAppendLog(subsystem, display, subArgs); break; default: printUsage(subsystem, display); if (subcommand) { throw new Error(`Unknown ${display} subcommand: ${subcommand}`); } } } async function handlePath(subsystem, display, args) { const subpath = args.find((a) => !a.startsWith('--')); const json = args.includes('--json'); const projectRoot = process.cwd(); const config = await getLoadedConfig(projectRoot); const backend = config?.backends?.[subsystem]?.type ?? 'fs'; if (backend !== 'fs') { if (json) { console.log(JSON.stringify({ backend, note: `${display} subsystem uses backend "${backend}" — physical filesystem path is not applicable. Use \`aiwg ${display} get/list\` instead.`, }, null, 2)); } else { console.log(`${display} backend: ${backend}`); console.log(` (filesystem path not applicable for this backend; use \`aiwg ${display} get/list\`)`); } return; } const root = resolveSubsystemRoot(subsystem, projectRoot, config); const fullPath = subpath ? `${root}/${subpath}` : root; if (json) { console.log(JSON.stringify({ backend, root, path: fullPath }, null, 2)); } else { console.log(fullPath); } } async function handleList(subsystem, display, args) { let prefix = ''; let json = false; for (let i = 0; i < args.length; i++) { if (args[i] === '--prefix') prefix = args[++i] ?? ''; else if (args[i] === '--json') json = true; } const adapter = await resolveStorage(subsystem); const entries = await adapter.list(prefix); if (json) { console.log(JSON.stringify(entries.map((e) => ({ path: e.path, size: e.size, modifiedAt: e.modifiedAt?.toISOString(), })), null, 2)); return; } if (entries.length === 0) { console.log(`No ${display} entries${prefix ? ` matching prefix "${prefix}"` : ''}.`); return; } for (const e of entries) console.log(e.path); } async function handleGet(subsystem, display, args) { const path = args[0]; if (!path) throw new Error(`Usage: aiwg ${display} get <path>`); const adapter = await resolveStorage(subsystem); const content = await adapter.read(path); if (content === null) throw new Error(`${display} entry not found: ${path}`); process.stdout.write(content); } async function handlePut(subsystem, display, args) { const path = args[0]; if (!path) { throw new Error(`Usage: aiwg ${display} put <path>\n` + ` Reads content from stdin. Creates parent directories. Overwrites existing.`); } const content = await readStdin(); const adapter = await resolveStorage(subsystem); await adapter.write(path, content); console.log(`Wrote ${content.length} bytes to ${display}:${path}`); } async function handleDelete(subsystem, display, args) { const path = args[0]; if (!path) throw new Error(`Usage: aiwg ${display} delete <path>`); const adapter = await resolveStorage(subsystem); await adapter.delete(path); console.log(`Deleted ${display}:${path} (no-op if missing)`); } async function handleAppendLog(subsystem, display, args) { const logPath = args[0]; if (!logPath) { throw new Error(`Usage: aiwg ${display} append-log <log-path>\n` + ` Reads a single JSON object from stdin and appends it as one JSONL line.`); } const stdinContent = (await readStdin()).trim(); if (stdinContent.length === 0) { throw new Error('append-log: stdin must contain a single JSON object (got empty input)'); } let parsed; try { parsed = JSON.parse(stdinContent); } catch (err) { throw new Error(`append-log: stdin must be valid JSON: ${err.message}`); } if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) { throw new Error('append-log: stdin must be a single JSON object (not an array, not a primitive)'); } const line = JSON.stringify(parsed) + '\n'; const adapter = await resolveStorage(subsystem); if (typeof adapter.append === 'function') { const existing = (await adapter.read(logPath)) ?? ''; if (existing.length > 0 && !existing.endsWith('\n')) { await adapter.append(logPath, '\n'); } await adapter.append(logPath, line); } else { const existing = (await adapter.read(logPath)) ?? ''; const trailing = existing.length === 0 || existing.endsWith('\n') ? '' : '\n'; await adapter.write(logPath, existing + trailing + line); } console.log(`Appended JSONL event to ${display}:${logPath}`); } async function readStdin() { const chunks = []; for await (const chunk of process.stdin) { chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk); } return Buffer.concat(chunks).toString('utf-8'); } function printUsage(subsystem, display) { console.log(`Usage: aiwg ${display} <subcommand> Subcommands: path [<subpath>] [--json] Resolved physical path (fs backend) list [--prefix <p>] [--json] List entries through the adapter get <path> Read entry to stdout put <path> Write stdin to entry delete <path> Delete entry (no-op if missing) append-log <log-path> Append a JSON object from stdin as JSONL (atomic when the backend supports adapter.append) Examples: aiwg ${display} path aiwg ${display} list --prefix some/ aiwg ${display} get some/page.md echo "# page" | aiwg ${display} put some/page.md echo '{"event":"x"}' | aiwg ${display} append-log some/log.jsonl The ${display} subsystem persists at .aiwg/${subsystem}/ on the default fs backend. Configure .aiwg/storage.config to redirect (#934).`); } //# sourceMappingURL=subsystem-cli.js.map