@pellux/goodvibes-agent 0.1.110 → 0.1.112

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,6 @@ import { buildProviderAccountSnapshot } from '@/runtime/index.ts';
7
7
  import { getSettingsControlPlaneSnapshot } from '@/runtime/index.ts';
8
8
  import { checkRecoveryFile, readLastSessionPointer } from '@/runtime/index.ts';
9
9
  import {
10
- openCommandPanel,
11
10
  requireOperatorClient,
12
11
  requireProviderApi,
13
12
  requireReadModels,
@@ -22,16 +21,37 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
22
21
  name: 'health',
23
22
  aliases: ['doctor'],
24
23
  description: 'Health workspace for startup posture, service readiness, provider health, and Agent continuity',
25
- usage: '[open|review|setup|services|provider|accounts|auth|settings|remote|mcp|continuity|maintenance|repair [domain]]',
24
+ usage: '[review|setup|services|provider|accounts|auth|settings|remote|mcp|continuity|maintenance|repair [domain]]',
26
25
  async handler(args, ctx) {
27
26
  const sub = (args[0] ?? 'review').toLowerCase();
28
- const readModels = requireReadModels(ctx);
29
27
 
30
- if (sub === 'open' || sub === 'panel' || sub === 'provider') {
31
- openCommandPanel(ctx, 'provider-health');
28
+ if (sub === 'open' || sub === 'panel') {
29
+ ctx.print('Health panels are not part of the Agent workspace. Use /health review.');
32
30
  return;
33
31
  }
34
32
 
33
+ if (sub === 'provider') {
34
+ const providerApi = requireProviderApi(ctx);
35
+ const currentModel = await providerApi.getCurrentModel().catch(() => null);
36
+ const accounts = await requireOperatorClient(ctx).providers.accountSnapshot();
37
+ ctx.print([
38
+ 'Health Review: Provider',
39
+ ` selected model: ${currentModel?.registryKey ?? 'unknown'}`,
40
+ ` providers: ${providerApi.listProviderIds().length}`,
41
+ ` configured accounts: ${accounts.configuredCount}`,
42
+ ` account issues: ${accounts.issueCount}`,
43
+ ...(accounts.issueCount > 0
44
+ ? accounts.providers.flatMap((provider) => provider.issues.map((issue) => ` ${provider.providerId}: ${issue}`))
45
+ : [' no provider account issues detected']),
46
+ ' next: /model',
47
+ ' next: /provider',
48
+ ' next: /accounts review',
49
+ ].join('\n'));
50
+ return;
51
+ }
52
+
53
+ const readModels = requireReadModels(ctx);
54
+
35
55
  if (sub === 'services') {
36
56
  const registry = requireServiceRegistry(ctx);
37
57
  const all = registry.getAll();
@@ -318,7 +338,7 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
318
338
  ...(snapshot.serviceIssues.length > 0 ? ['', ...snapshot.serviceIssues.map((issue) => ` service: ${issue}`)] : []),
319
339
  '',
320
340
  'Next steps:',
321
- ' /health open',
341
+ ' /health review',
322
342
  ' /health services',
323
343
  ' /health accounts',
324
344
  ' /health auth',
@@ -137,8 +137,8 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
137
137
  name: 'mcp',
138
138
  aliases: [],
139
139
  description: 'Manage MCP servers and their tools',
140
- usage: '[add|remove|reload|config|review|tools [<server>]|auth-review|repair [server]]',
141
- argsHint: '[review|tools|config|add --yes|remove --yes]',
140
+ usage: '[servers|review|tools [<server>]|config|add|remove|reload|auth-review|repair [server]]',
141
+ argsHint: '[servers|review|tools|config|add --yes|remove --yes]',
142
142
  async handler(args, ctx) {
143
143
  const mcpApi = requireMcpApi(ctx);
144
144
  const listServerSecurity = () => mcpApi.listServerSecurity();
@@ -163,6 +163,25 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
163
163
  ].join('\n'));
164
164
  return;
165
165
  }
166
+ if (subcommand === 'servers') {
167
+ const servers = listServerSecurity();
168
+ if (servers.length === 0) {
169
+ ctx.print(
170
+ 'No MCP servers configured.\n'
171
+ + 'Add servers to one of these locations (scanned in order):\n'
172
+ + ' ~/.config/mcp/mcp.json (global XDG)\n'
173
+ + ' ~/.mcp/mcp.json (global dotdir)\n'
174
+ + ' ~/.config/claude/claude_desktop_config.json (Claude Desktop)\n'
175
+ + ' .mcp/mcp.json (project-local)\n'
176
+ + ' .goodvibes/mcp.json (goodvibes project)\n'
177
+ + '\nAdd one from inside Agent with explicit confirmation:\n'
178
+ + ' /mcp add filesystem npx -y @modelcontextprotocol/server-filesystem . --scope project --role filesystem --yes'
179
+ );
180
+ return;
181
+ }
182
+ ctx.print(formatMcpServerList(servers));
183
+ return;
184
+ }
166
185
  if (subcommand === 'tools') {
167
186
  const filterServer = commandArgs[1];
168
187
  ctx.print('Fetching MCP tool list...');
@@ -434,30 +453,36 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
434
453
  return;
435
454
  }
436
455
 
437
- const connected = servers.filter(s => s.connected);
438
- const disconnected = servers.filter(s => !s.connected);
439
- const lines: string[] = [`MCP Servers (${connected.length}/${servers.length} connected):`];
440
- for (const s of servers) {
441
- const pathScope = s.allowedPaths.length > 0 ? ` paths=${s.allowedPaths.length}` : '';
442
- const hostScope = s.allowedHosts.length > 0 ? ` hosts=${s.allowedHosts.length}` : '';
443
- const freshness = ` freshness=${s.schemaFreshness}`;
444
- const quarantine = s.schemaFreshness === 'quarantined' ? ` quarantine=${s.quarantineReason ?? 'unknown'}` : '';
445
- lines.push(` ${s.connected ? '[connected] ' : '[disconnected]'} ${s.name} trust=${s.trustMode} role=${s.role}${freshness}${quarantine}${pathScope}${hostScope}`);
446
- }
447
- if (connected.length > 0) {
448
- lines.push('');
449
- lines.push('Run "/mcp tools" to list all tools, or "/mcp tools <server>" for a specific server.');
450
- lines.push('Run "/mcp" to open the fullscreen MCP workspace, or "/mcp add <name> <command> [args...] [--scope project|global] --yes" to add/update.');
451
- lines.push('Run "/mcp reload --yes" after editing MCP config outside Agent.');
452
- lines.push('Run "/mcp trust <server> <mode> --yes" to change trust mode, or "/mcp role <server> <role> --yes" to change its coherence role.');
453
- lines.push('Run "/mcp quarantine <server> [detail] --yes" to block a server, or "/mcp quarantine <server> approve [operatorId] --yes" to approve a temporary override.');
454
- lines.push('Use /settings → MCP to explicitly enable allow-all for a server.');
455
- }
456
- if (disconnected.length > 0) {
457
- lines.push('');
458
- lines.push(`${disconnected.length} server(s) failed to connect. Check server command and args in your config.`);
459
- }
460
- ctx.print(lines.join('\n'));
456
+ ctx.print(formatMcpServerList(servers));
461
457
  },
462
458
  });
463
459
  }
460
+
461
+ type McpSecurityServer = ReturnType<NonNullable<NonNullable<CommandContext['clients']>['mcpApi']>['listServerSecurity']>[number];
462
+
463
+ function formatMcpServerList(servers: readonly McpSecurityServer[]): string {
464
+ const connected = servers.filter(s => s.connected);
465
+ const disconnected = servers.filter(s => !s.connected);
466
+ const lines: string[] = [`MCP Servers (${connected.length}/${servers.length} connected):`];
467
+ for (const s of servers) {
468
+ const pathScope = s.allowedPaths.length > 0 ? ` paths=${s.allowedPaths.length}` : '';
469
+ const hostScope = s.allowedHosts.length > 0 ? ` hosts=${s.allowedHosts.length}` : '';
470
+ const freshness = ` freshness=${s.schemaFreshness}`;
471
+ const quarantine = s.schemaFreshness === 'quarantined' ? ` quarantine=${s.quarantineReason ?? 'unknown'}` : '';
472
+ lines.push(` ${s.connected ? '[connected] ' : '[disconnected]'} ${s.name} trust=${s.trustMode} role=${s.role}${freshness}${quarantine}${pathScope}${hostScope}`);
473
+ }
474
+ if (connected.length > 0) {
475
+ lines.push('');
476
+ lines.push('Run "/mcp tools" to list all tools, or "/mcp tools <server>" for a specific server.');
477
+ lines.push('Run "/mcp" to open the fullscreen MCP workspace, or "/mcp add <name> <command> [args...] [--scope project|global] --yes" to add/update.');
478
+ lines.push('Run "/mcp reload --yes" after editing MCP config outside Agent.');
479
+ lines.push('Run "/mcp trust <server> <mode> --yes" to change trust mode, or "/mcp role <server> <role> --yes" to change its coherence role.');
480
+ lines.push('Run "/mcp quarantine <server> [detail] --yes" to block a server, or "/mcp quarantine <server> approve [operatorId] --yes" to approve a temporary override.');
481
+ lines.push('Use /settings -> MCP to explicitly enable allow-all for a server.');
482
+ }
483
+ if (disconnected.length > 0) {
484
+ lines.push('');
485
+ lines.push(`${disconnected.length} server(s) failed to connect. Check server command and args in your config.`);
486
+ }
487
+ return lines.join('\n');
488
+ }
@@ -1,16 +1,16 @@
1
1
  /**
2
- * /recall command handler.
2
+ * /memory command handler.
3
3
  *
4
4
  * Implements the Project Memory Substrate commands:
5
5
  *
6
- * /recall add <class> <summary> — Add a new memory record
7
- * /recall add <class> <summary> --detail <text> --tags <tag,tag>
8
- * /recall search [query] — Search memory records
9
- * /recall search --cls <class> — Filter by class
10
- * /recall link <fromId> <toId> <relation> --yes — Link two records
11
- * /recall get <id> — Show a single record with provenance
12
- * /recall list [class] — List all records (optionally by class)
13
- * /recall remove <id> --yes — Delete a record
6
+ * /memory add <class> <summary> — Add a new memory record
7
+ * /memory add <class> <summary> --detail <text> --tags <tag,tag>
8
+ * /memory search [query] — Search memory records
9
+ * /memory search --cls <class> — Filter by class
10
+ * /memory link <fromId> <toId> <relation> --yes — Link two records
11
+ * /memory get <id> — Show a single record with provenance
12
+ * /memory list [class] — List all records (optionally by class)
13
+ * /memory remove <id> --yes — Delete a record
14
14
  */
15
15
 
16
16
  import type { SlashCommand, CommandContext } from '../command-registry.ts';
@@ -118,7 +118,7 @@ export const recallCommand: SlashCommand = {
118
118
 
119
119
  default: {
120
120
  const usage = [
121
- 'Usage: /recall <subcommand>',
121
+ 'Usage: /memory <subcommand>',
122
122
  ' add <class> <summary> [--scope <session|project|team>] [--detail <text>] [--tags <t,t>] [--session <id>] [--task <id>] [--file <path>]',
123
123
  ` classes: ${VALID_CLASSES.join(', ')}`,
124
124
  ' capture incident <id|latest> — Capture a forensics incident as durable memory',
@@ -44,7 +44,7 @@ function formatNextQuestion(question: ProjectPlanningQuestion | undefined): stri
44
44
  if (!question) return 'No next question recorded.';
45
45
  const lines = [`Next question: ${question.prompt}`];
46
46
  if (question.recommendedAnswer) lines.push(`Recommended answer: ${question.recommendedAnswer}`);
47
- lines.push('Answer in the prompt, or focus the Planning workspace to choose/type an answer.');
47
+ lines.push('Answer in the main prompt or review planning state with /plan status.');
48
48
  return lines.join('\n');
49
49
  }
50
50
 
@@ -52,8 +52,8 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
52
52
  registry.register({
53
53
  name: 'plan',
54
54
  description: 'Inspect or seed Agent workspace planning state',
55
- usage: '[panel | approve --yes | list | show <id> | mode | explain | override <strategy> --yes | status | clear --yes | <planning goal>]',
56
- argsHint: '[panel|approve|status|<goal>]',
55
+ usage: '[approve --yes | list | show <id> | mode | explain | override <strategy> --yes | status | clear --yes | <planning goal>]',
56
+ argsHint: '[approve|status|list|<goal>]',
57
57
  async handler(args, ctx) {
58
58
  const planManager = requirePlanManager(ctx);
59
59
  const sessionLineageTracker = requireSessionLineageTracker(ctx);
@@ -74,7 +74,6 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
74
74
 
75
75
  const projectPlanningService = ctx.workspace.projectPlanningService;
76
76
  const projectId = ctx.workspace.projectPlanningProjectId;
77
- const openProjectPlanningPanel = () => ctx.showPanel?.('project-planning');
78
77
 
79
78
  if (args.length === 0) {
80
79
  if (projectPlanningService && projectId) {
@@ -92,7 +91,6 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
92
91
  const planningNamespace = String(
93
92
  status[['knowledge', 'SpaceId'].join('') as keyof typeof status] ?? `project:${status.projectId}`,
94
93
  );
95
- openProjectPlanningPanel();
96
94
  ctx.print(
97
95
  `Project planning: ${evaluation.readiness}\n` +
98
96
  `Project: ${status.projectId}\n` +
@@ -113,8 +111,7 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
113
111
  }
114
112
 
115
113
  if (args[0] === 'panel') {
116
- openProjectPlanningPanel();
117
- ctx.print('Opened project planning panel.');
114
+ ctx.print('Planning panels are not part of the Agent workspace. Use /plan status or /plan list.');
118
115
  return;
119
116
  }
120
117
 
@@ -146,7 +143,6 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
146
143
  },
147
144
  });
148
145
  const evaluation = await projectPlanningService.evaluate({ projectId });
149
- openProjectPlanningPanel();
150
146
  ctx.print(`Project planning approved. Readiness: ${evaluation.readiness}. State: ${result.state?.id ?? 'current'}.`);
151
147
  return;
152
148
  }
@@ -208,7 +204,6 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
208
204
  ? await persistEvaluatedNextQuestion(projectPlanningService, projectId, result.state, initialEvaluation)
209
205
  : { state: result.state, evaluation: initialEvaluation };
210
206
  sessionLineageTracker.setOriginalTask(taskDescription.slice(0, 200));
211
- openProjectPlanningPanel();
212
207
 
213
208
  ctx.print(
214
209
  `Project planning seeded: "${state?.goal ?? taskDescription}"\n` +
@@ -4,7 +4,6 @@ import type {
4
4
  ProviderAccountSnapshot,
5
5
  } from '@/runtime/index.ts';
6
6
  import {
7
- openCommandPanel,
8
7
  requireOperatorClient,
9
8
  } from './runtime-services.ts';
10
9
 
@@ -25,11 +24,11 @@ export function registerProviderAccountsRuntimeCommands(registry: CommandRegistr
25
24
  name: 'accounts',
26
25
  aliases: ['account'],
27
26
  description: 'Review provider auth routes, subscription windows, and billing-path safety',
28
- usage: '[review|panel|show <provider>|routes <provider>|repair <provider>]',
27
+ usage: '[review|show <provider>|routes <provider>|repair <provider>]',
29
28
  async handler(args, ctx) {
30
29
  const sub = (args[0] ?? 'review').toLowerCase();
31
30
  if (sub === 'panel' || sub === 'open') {
32
- openCommandPanel(ctx, 'accounts');
31
+ ctx.print('Provider account panels are not part of the Agent workspace. Use /accounts review.');
33
32
  return;
34
33
  }
35
34
  const snapshot = await loadProviderAccountSnapshot(ctx);
@@ -1,20 +1,59 @@
1
1
  import type { CommandRegistry } from '../command-registry.ts';
2
- import { openCommandPanel } from './runtime-services.ts';
2
+ import { join } from 'node:path';
3
+ import { networkInterfaces } from 'node:os';
4
+ import {
5
+ buildCompanionConnectionInfo,
6
+ encodeConnectionPayload,
7
+ formatConnectionBlock,
8
+ generateQrMatrix,
9
+ getOrCreateCompanionToken,
10
+ renderQrToString,
11
+ } from '@pellux/goodvibes-sdk/platform/pairing';
12
+ import { GOODVIBES_AGENT_PAIRING_SURFACE } from '../../config/surface.ts';
13
+ import { resolveRuntimeEndpointBinding } from '../../cli/endpoints.ts';
14
+ import { requirePlatform, requireShellPaths } from './runtime-services.ts';
15
+
16
+ function getLocalNetworkIp(): string {
17
+ try {
18
+ const nets = networkInterfaces();
19
+ for (const name of Object.keys(nets)) {
20
+ for (const netInfo of nets[name] ?? []) {
21
+ if (netInfo.family === 'IPv4' && !netInfo.internal) return netInfo.address;
22
+ }
23
+ }
24
+ } catch {
25
+ return '127.0.0.1';
26
+ }
27
+ return '127.0.0.1';
28
+ }
29
+
30
+ function urlHostForBindHost(host: string): string {
31
+ if (host === '0.0.0.0' || host === '::') return getLocalNetworkIp();
32
+ return host || '127.0.0.1';
33
+ }
3
34
 
4
- /**
5
- * Register the /qrcode command.
6
- *
7
- * Opens the QR Code panel which displays a scannable QR code for
8
- * companion app pairing, along with connection URL, token, and username.
9
- */
10
35
  export function registerQrcodeRuntimeCommands(registry: CommandRegistry): void {
11
36
  registry.register({
12
37
  name: 'qrcode',
13
38
  aliases: ['qr', 'pair'],
14
- description: 'Open the QR code panel for companion app pairing',
39
+ description: 'Print companion pairing details and a QR code',
15
40
  usage: '',
16
41
  handler(_args, ctx) {
17
- openCommandPanel(ctx, 'qr-code');
42
+ const shellPaths = requireShellPaths(ctx);
43
+ const configManager = requirePlatform(ctx).configManager;
44
+ const daemonHomeDir = join(shellPaths.homeDirectory, '.goodvibes', 'daemon');
45
+ const tokenRecord = getOrCreateCompanionToken(GOODVIBES_AGENT_PAIRING_SURFACE, { daemonHomeDir });
46
+ const binding = resolveRuntimeEndpointBinding(configManager, 'controlPlane');
47
+ const daemonUrl = `http://${urlHostForBindHost(binding.host)}:${binding.port}`;
48
+ const info = buildCompanionConnectionInfo({
49
+ daemonUrl,
50
+ token: tokenRecord.token,
51
+ username: 'admin',
52
+ surface: GOODVIBES_AGENT_PAIRING_SURFACE,
53
+ });
54
+ const payload = encodeConnectionPayload(info);
55
+ const qr = renderQrToString(generateQrMatrix(payload));
56
+ ctx.print([formatConnectionBlock(info, payload), '', qr].join('\n'));
18
57
  },
19
58
  });
20
59
  }
@@ -18,11 +18,11 @@ export function handleRecallExport(args: string[], context: CommandContext): voi
18
18
 
19
19
  const pathArg = commandArgs[0];
20
20
  if (!pathArg) {
21
- context.print('[recall] Usage: /recall export <path> [--scope <scope>] [--cls <class>] --yes');
21
+ context.print('[memory] Usage: /memory export <path> [--scope <scope>] [--cls <class>] --yes');
22
22
  return;
23
23
  }
24
24
  if (!parsed.yes) {
25
- requireYesFlag(context, `export durable memory bundle to ${pathArg}`, '/recall export <path> [--scope <scope>] [--cls <class>] --yes');
25
+ requireYesFlag(context, `export durable memory bundle to ${pathArg}`, '/memory export <path> [--scope <scope>] [--cls <class>] --yes');
26
26
  return;
27
27
  }
28
28
 
@@ -31,7 +31,7 @@ export function handleRecallExport(args: string[], context: CommandContext): voi
31
31
  if (scopeIdx !== -1 && commandArgs[scopeIdx + 1]) {
32
32
  const scope = commandArgs[scopeIdx + 1];
33
33
  if (!isValidScope(scope)) {
34
- context.print(`[recall] Unknown scope "${scope}". Valid: ${VALID_SCOPES.join(', ')}`);
34
+ context.print(`[memory] Unknown scope "${scope}". Valid: ${VALID_SCOPES.join(', ')}`);
35
35
  return;
36
36
  }
37
37
  filter.scope = scope;
@@ -41,7 +41,7 @@ export function handleRecallExport(args: string[], context: CommandContext): voi
41
41
  if (clsIdx !== -1 && commandArgs[clsIdx + 1]) {
42
42
  const cls = commandArgs[clsIdx + 1];
43
43
  if (!isValidClass(cls)) {
44
- context.print(`[recall] Unknown class "${cls}". Valid: ${VALID_CLASSES.join(', ')}`);
44
+ context.print(`[memory] Unknown class "${cls}". Valid: ${VALID_CLASSES.join(', ')}`);
45
45
  return;
46
46
  }
47
47
  filter.cls = cls;
@@ -51,7 +51,7 @@ export function handleRecallExport(args: string[], context: CommandContext): voi
51
51
  const targetPath = resolveBundlePath(pathArg, requireShellPaths(context));
52
52
  mkdirSync(dirname(targetPath), { recursive: true });
53
53
  writeFileSync(targetPath, JSON.stringify(bundle, null, 2) + '\n', 'utf-8');
54
- context.print(`[recall] Exported ${bundle.recordCount} record(s) and ${bundle.linkCount} link(s) to ${targetPath}`);
54
+ context.print(`[memory] Exported ${bundle.recordCount} record(s) and ${bundle.linkCount} link(s) to ${targetPath}`);
55
55
  }
56
56
 
57
57
  export async function handleRecallImport(args: string[], context: CommandContext): Promise<void> {
@@ -64,11 +64,11 @@ export async function handleRecallImport(args: string[], context: CommandContext
64
64
 
65
65
  const pathArg = commandArgs[0];
66
66
  if (!pathArg) {
67
- context.print('[recall] Usage: /recall import <path> --yes');
67
+ context.print('[memory] Usage: /memory import <path> --yes');
68
68
  return;
69
69
  }
70
70
  if (!parsed.yes) {
71
- requireYesFlag(context, `import durable memory bundle from ${pathArg}`, '/recall import <path> --yes');
71
+ requireYesFlag(context, `import durable memory bundle from ${pathArg}`, '/memory import <path> --yes');
72
72
  return;
73
73
  }
74
74
 
@@ -77,12 +77,12 @@ export async function handleRecallImport(args: string[], context: CommandContext
77
77
  try {
78
78
  bundle = JSON.parse(readFileSync(targetPath, 'utf-8')) as MemoryBundle;
79
79
  } catch (error) {
80
- context.print(`[recall] Failed to read memory bundle: ${summarizeError(error)}`);
80
+ context.print(`[memory] Failed to read memory bundle: ${summarizeError(error)}`);
81
81
  return;
82
82
  }
83
83
 
84
84
  const result = await memory.importBundle(bundle);
85
- context.print(`[recall] Imported bundle from ${targetPath}`);
85
+ context.print(`[memory] Imported bundle from ${targetPath}`);
86
86
  context.print(` Records: imported=${result.importedRecords} skipped=${result.skippedRecords}`);
87
87
  context.print(` Links: imported=${result.importedLinks}`);
88
88
  }
@@ -106,30 +106,30 @@ export function handleRecallHandoffExport(args: string[], context: CommandContex
106
106
  }
107
107
  const pathArg = commandArgs[0];
108
108
  if (!pathArg) {
109
- context.print('[recall] Usage: /recall handoff-export <path> [--scope <scope>] --yes');
109
+ context.print('[memory] Usage: /memory handoff-export <path> [--scope <scope>] --yes');
110
110
  return;
111
111
  }
112
112
  if (!parsed.yes) {
113
- requireYesFlag(context, `export memory handoff bundle to ${pathArg}`, '/recall handoff-export <path> [--scope <scope>] --yes');
113
+ requireYesFlag(context, `export memory handoff bundle to ${pathArg}`, '/memory handoff-export <path> [--scope <scope>] --yes');
114
114
  return;
115
115
  }
116
116
  const scopeIdx = commandArgs.indexOf('--scope');
117
117
  const scopeRaw = scopeIdx !== -1 ? commandArgs[scopeIdx + 1] : 'team';
118
118
  if (!scopeRaw || !isValidScope(scopeRaw)) {
119
- context.print(`[recall] Unknown scope "${scopeRaw ?? ''}". Valid: ${VALID_SCOPES.join(', ')}`);
119
+ context.print(`[memory] Unknown scope "${scopeRaw ?? ''}". Valid: ${VALID_SCOPES.join(', ')}`);
120
120
  return;
121
121
  }
122
122
  const bundle = memory.exportBundle({ scope: scopeRaw });
123
123
  const targetPath = resolveBundlePath(pathArg, requireShellPaths(context));
124
124
  mkdirSync(dirname(targetPath), { recursive: true });
125
125
  writeFileSync(targetPath, JSON.stringify(bundle, null, 2) + '\n', 'utf-8');
126
- context.print(`[recall] Exported ${scopeRaw} handoff bundle to ${targetPath}`);
126
+ context.print(`[memory] Exported ${scopeRaw} handoff bundle to ${targetPath}`);
127
127
  }
128
128
 
129
129
  export function handleRecallHandoffInspect(args: string[], context: CommandContext): void {
130
130
  const pathArg = args[0];
131
131
  if (!pathArg) {
132
- context.print('[recall] Usage: /recall handoff-inspect <path>');
132
+ context.print('[memory] Usage: /memory handoff-inspect <path>');
133
133
  return;
134
134
  }
135
135
  const targetPath = resolveBundlePath(pathArg, requireShellPaths(context));
@@ -137,7 +137,7 @@ export function handleRecallHandoffInspect(args: string[], context: CommandConte
137
137
  const bundle = JSON.parse(readFileSync(targetPath, 'utf-8')) as MemoryBundle;
138
138
  context.print(inspectBundle(bundle));
139
139
  } catch (error) {
140
- context.print(`[recall] Failed to inspect handoff bundle: ${summarizeError(error)}`);
140
+ context.print(`[memory] Failed to inspect handoff bundle: ${summarizeError(error)}`);
141
141
  }
142
142
  }
143
143
 
@@ -146,7 +146,7 @@ export async function handleRecallHandoffImport(args: string[], context: Command
146
146
  const commandArgs = [...parsed.rest];
147
147
  const pathArg = commandArgs[0];
148
148
  if (!pathArg) {
149
- context.print('[recall] Usage: /recall handoff-import <path> --yes');
149
+ context.print('[memory] Usage: /memory handoff-import <path> --yes');
150
150
  return;
151
151
  }
152
152
  await handleRecallImport([pathArg, ...(parsed.yes ? ['--yes'] : [])], context);
@@ -17,7 +17,7 @@ export async function handleRecallAdd(args: string[], context: CommandContext):
17
17
 
18
18
  const cls = args[0];
19
19
  if (!cls || !isValidClass(cls)) {
20
- context.print(`[recall] Invalid class "${cls ?? ''}". Valid: ${VALID_CLASSES.join(', ')}`);
20
+ context.print(`[memory] Invalid class "${cls ?? ''}". Valid: ${VALID_CLASSES.join(', ')}`);
21
21
  return;
22
22
  }
23
23
 
@@ -36,7 +36,7 @@ export async function handleRecallAdd(args: string[], context: CommandContext):
36
36
  const scope = scopeRaw && isValidScope(scopeRaw) ? scopeRaw : 'project';
37
37
 
38
38
  if (scopeRaw && !isValidScope(scopeRaw)) {
39
- context.print(`[recall] Invalid scope "${scopeRaw}". Valid: ${VALID_SCOPES.join(', ')}`);
39
+ context.print(`[memory] Invalid scope "${scopeRaw}". Valid: ${VALID_SCOPES.join(', ')}`);
40
40
  return;
41
41
  }
42
42
 
@@ -55,12 +55,12 @@ export async function handleRecallAdd(args: string[], context: CommandContext):
55
55
  }
56
56
  const summary = summaryTokens.join(' ');
57
57
  if (!summary.trim()) {
58
- context.print('[recall] Usage: /recall add <class> <summary> [--detail <text>] [--tags <t1,t2>]');
58
+ context.print('[memory] Usage: /memory add <class> <summary> [--detail <text>] [--tags <t1,t2>]');
59
59
  return;
60
60
  }
61
61
 
62
62
  const record = await memory.add({ scope, cls, summary, detail, tags, provenance });
63
- context.print(`[recall] Added ${cls}: ${record.id}`);
63
+ context.print(`[memory] Added ${cls}: ${record.id}`);
64
64
  context.print(` Scope: ${record.scope}`);
65
65
  context.print(` Summary: ${record.summary}`);
66
66
  if (record.tags.length) context.print(` Tags: ${record.tags.join(', ')}`);
@@ -76,7 +76,7 @@ export async function handleRecallCapture(args: string[], context: CommandContex
76
76
  return;
77
77
  }
78
78
  if (!context.extensions.forensicsRegistry) {
79
- context.print('[recall] Forensics registry not available.');
79
+ context.print('[memory] Forensics registry not available.');
80
80
  return;
81
81
  }
82
82
 
@@ -87,66 +87,66 @@ export async function handleRecallCapture(args: string[], context: CommandContex
87
87
  ? context.extensions.forensicsRegistry.latest()
88
88
  : context.extensions.forensicsRegistry.getById(requestedId);
89
89
  if (!report) {
90
- context.print(`[recall] Incident not found: ${requestedId ?? 'latest'}`);
90
+ context.print(`[memory] Incident not found: ${requestedId ?? 'latest'}`);
91
91
  return;
92
92
  }
93
93
  const bundle = context.extensions.forensicsRegistry.buildBundle(report.id);
94
94
  if (!bundle) {
95
- context.print(`[recall] Failed to build incident bundle: ${report.id}`);
95
+ context.print(`[memory] Failed to build incident bundle: ${report.id}`);
96
96
  return;
97
97
  }
98
98
  const record = await memory.add(buildIncidentMemoryAddOptions(bundle));
99
- context.print(`[recall] Captured incident ${report.id} into memory as ${record.id}`);
99
+ context.print(`[memory] Captured incident ${report.id} into memory as ${record.id}`);
100
100
  return;
101
101
  }
102
102
 
103
103
  if (target === 'policy') {
104
104
  const review = context.extensions.policyRuntimeState?.getSnapshot().lastPreflightReview;
105
105
  if (!review) {
106
- context.print('[recall] No policy preflight review is available to capture.');
106
+ context.print('[memory] No policy preflight review is available to capture.');
107
107
  return;
108
108
  }
109
109
  const record = await memory.add(buildPolicyPreflightMemoryAddOptions(review));
110
- context.print(`[recall] Captured policy preflight into memory as ${record.id}`);
110
+ context.print(`[memory] Captured policy preflight into memory as ${record.id}`);
111
111
  return;
112
112
  }
113
113
 
114
114
  if (target === 'mcp') {
115
115
  const serverName = args[1];
116
116
  if (!serverName) {
117
- context.print('[recall] Usage: /recall capture mcp <server>');
117
+ context.print('[memory] Usage: /memory capture mcp <server>');
118
118
  return;
119
119
  }
120
120
  const server = context.extensions.mcpRegistry.listServerSecurity().find((entry) => entry.name === serverName);
121
121
  if (!server) {
122
- context.print(`[recall] MCP server not found: ${serverName}`);
122
+ context.print(`[memory] MCP server not found: ${serverName}`);
123
123
  return;
124
124
  }
125
125
  const record = await memory.add(buildMcpSecurityMemoryAddOptions(server));
126
- context.print(`[recall] Captured MCP server ${server.name} into memory as ${record.id}`);
126
+ context.print(`[memory] Captured MCP server ${server.name} into memory as ${record.id}`);
127
127
  return;
128
128
  }
129
129
 
130
130
  if (target === 'plugin') {
131
131
  if (!pluginManager) {
132
- context.print('[recall] Plugin manager not available.');
132
+ context.print('[memory] Plugin manager not available.');
133
133
  return;
134
134
  }
135
135
  const pluginName = args[1];
136
136
  if (!pluginName) {
137
- context.print('[recall] Usage: /recall capture plugin <name>');
137
+ context.print('[memory] Usage: /memory capture plugin <name>');
138
138
  return;
139
139
  }
140
140
  const plugin = pluginManager.list().find((entry) => entry.name === pluginName);
141
141
  if (!plugin) {
142
- context.print(`[recall] Plugin not found: ${pluginName}`);
142
+ context.print(`[memory] Plugin not found: ${pluginName}`);
143
143
  return;
144
144
  }
145
145
  const quarantineReason = pluginManager.getQuarantineRecord(plugin.name)?.reason;
146
146
  const record = await memory.add(buildPluginSecurityMemoryAddOptions(plugin, quarantineReason));
147
- context.print(`[recall] Captured plugin ${plugin.name} into memory as ${record.id}`);
147
+ context.print(`[memory] Captured plugin ${plugin.name} into memory as ${record.id}`);
148
148
  return;
149
149
  }
150
150
 
151
- context.print('[recall] Usage: /recall capture incident <id|latest> | /recall capture policy | /recall capture mcp <server> | /recall capture plugin <name>');
151
+ context.print('[memory] Usage: /memory capture incident <id|latest> | /memory capture policy | /memory capture mcp <server> | /memory capture plugin <name>');
152
152
  }