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

197 lines (195 loc) 7.38 kB
/** * Sandbox Command Handler * * Thin CLI wrappers over aiwg serve sandbox REST endpoints. * * Subcommands: * alias <sandbox-id> <agent-id> <name> Assign a logical name to an agent (#917) * resolve <ref> Resolve an agent by name/instanceId/agentId * identities [--json] List all known agent identities * * @issue #917 */ import * as ui from '../ui.js'; function getServeBase() { const port = process.env['AIWG_SERVE_PORT'] ?? '7337'; return `http://127.0.0.1:${port}`; } /** * Default timeout for CLI → local aiwg serve REST calls. A wedged serve must * not hang the CLI indefinitely. Overridable via AIWG_FETCH_TIMEOUT_MS for * slow local environments or integration tests. */ function fetchTimeoutMs() { const raw = process.env['AIWG_FETCH_TIMEOUT_MS']; const n = raw ? parseInt(raw, 10) : NaN; return Number.isFinite(n) && n > 0 ? n : 5_000; } /** * Format a single-line error message distinguishing fetch timeout from * other unreachable-serve conditions. Timeouts produced by * AbortSignal.timeout() surface as DOMException with `name === 'TimeoutError'` * under Node 20+. */ function reachabilityMessage(err) { const name = err instanceof Error ? err.name : ''; if (name === 'TimeoutError') { return `aiwg serve timed out after ${fetchTimeoutMs()}ms. Is it wedged? Try: curl ${getServeBase()}/api/health`; } return `Cannot reach aiwg serve. Start it with: aiwg serve`; } /** * Combine the handler's user-cancel signal (Ctrl-C) with a per-call * timeout so fetches abort on either. Node 20+ `AbortSignal.any` is * required; handlers without `ctx.signal` (older test fixtures) fall * back to the timeout alone. */ function combineSignal(ctxSignal, timeoutMs) { const timeoutSignal = AbortSignal.timeout(timeoutMs); if (!ctxSignal) return timeoutSignal; return AbortSignal.any([ctxSignal, timeoutSignal]); } async function sandboxAlias(ctx) { const [sandboxId, agentId, ...nameParts] = ctx.args; const name = nameParts.join(' '); if (!sandboxId || !agentId || !name) { ui.error('Usage: aiwg sandbox alias <sandbox-id> <agent-id> <name>'); ui.info('Example: aiwg sandbox alias sb-abc123 agent-01 security-01'); return { exitCode: 1 }; } const base = getServeBase(); try { const resp = await fetch(`${base}/api/sandboxes/${encodeURIComponent(sandboxId)}/agents/${encodeURIComponent(agentId)}/alias`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name }), signal: combineSignal(ctx.signal, fetchTimeoutMs()), }); if (!resp.ok) { const body = await resp.json().catch(() => ({ error: resp.statusText })); ui.error(`alias failed (${resp.status}): ${body.error ?? resp.statusText}`); return { exitCode: 1 }; } const result = await resp.json(); ui.success(`Agent ${agentId} aliased as "${result.logicalName}"`); return { exitCode: 0 }; } catch (err) { ui.error(reachabilityMessage(err)); return { exitCode: 1 }; } } async function sandboxResolve(ctx) { const ref = ctx.args[0]; if (!ref) { ui.error('Usage: aiwg sandbox resolve <name-or-instanceId-or-agentId>'); return { exitCode: 1 }; } const base = getServeBase(); try { const resp = await fetch(`${base}/api/agents/resolve/${encodeURIComponent(ref)}`, { signal: combineSignal(ctx.signal, fetchTimeoutMs()), }); if (!resp.ok) { const body = await resp.json().catch(() => ({ error: resp.statusText })); ui.error(`resolve failed (${resp.status}): ${body.error ?? resp.statusText}`); return { exitCode: 1 }; } const result = await resp.json(); const agent = result.agent; console.log(`sandbox: ${result.sandboxName} (${result.sandboxId})`); console.log(`agentId: ${agent['agentId'] ?? '?'}`); console.log(`instanceId: ${agent['instanceId'] ?? '(none)'}`); console.log(`name: ${agent['logicalName'] ?? '(none)'}`); console.log(`status: ${agent['status'] ?? '?'}`); return { exitCode: 0 }; } catch (err) { ui.error(reachabilityMessage(err)); return { exitCode: 1 }; } } async function sandboxIdentities(ctx) { const json = ctx.args.includes('--json'); const base = getServeBase(); try { const resp = await fetch(`${base}/api/agents/identities`, { signal: combineSignal(ctx.signal, fetchTimeoutMs()), }); if (!resp.ok) { ui.error(`identities failed (${resp.status}): ${resp.statusText}`); return { exitCode: 1 }; } const result = await resp.json(); if (json) { console.log(JSON.stringify(result, null, 2)); return { exitCode: 0 }; } if (!result.identities.length) { ui.info('No agent identities recorded yet.'); return { exitCode: 0 }; } ui.blank(); console.log(` ${ui.brandMark()} ${ui.bold('Agent Identities')}`); ui.rule(); for (const id of result.identities) { const name = id['logicalName']; const instanceId = id['instanceId']; const lastSeen = id['lastSeenAt']; console.log(` • ${ui.bold(name ?? instanceId)}`); if (name) console.log(` instanceId: ${instanceId}`); console.log(` lastSeen: ${lastSeen}`); } ui.blank(); return { exitCode: 0 }; } catch (err) { ui.error(reachabilityMessage(err)); return { exitCode: 1 }; } } const subcommands = { alias: sandboxAlias, resolve: sandboxResolve, identities: sandboxIdentities, }; function showSandboxHelp() { ui.blank(); console.log(` ${ui.brandMark()} ${ui.bold('Sandbox')} — CLI access to sandbox agent management`); ui.rule(); console.log(` ${ui.bold('Usage:')} aiwg sandbox <subcommand> [options] ${ui.bold('Subcommands:')} alias <sb> <agent> <name> Assign a stable logical name to an agent (#917) resolve <ref> Resolve agent by name / instanceId / agentId identities [--json] List all known persistent agent identities ${ui.bold('Examples:')} aiwg sandbox alias sb-abc123 agent-01 security-01 aiwg sandbox resolve security-01 aiwg sandbox identities `); } export const sandboxHandler = { id: 'sandbox', name: 'Sandbox', description: 'Sandbox agent management (alias, resolve, identities)', category: 'sandbox', aliases: [], async execute(ctx) { const subcmd = ctx.args[0]; if (!subcmd || subcmd === '--help' || subcmd === '-h') { showSandboxHelp(); return { exitCode: 0 }; } const handler = subcommands[subcmd]; if (!handler) { ui.error(`Unknown subcommand: ${subcmd}. Run 'aiwg sandbox --help' for usage.`); return { exitCode: 1 }; } return handler({ ...ctx, args: ctx.args.slice(1) }); }, }; export const sandboxHandlers = [sandboxHandler]; //# sourceMappingURL=sandbox.js.map