@pellux/goodvibes-agent 1.8.1 → 1.9.0

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.
Files changed (89) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +1 -1
  3. package/dist/package/{ast-grep-napi.linux-x64-gnu-mkk8xwww.node → ast-grep-napi.linux-x64-gnu-44ar68xs.node} +0 -0
  4. package/dist/package/{ast-grep-napi.linux-x64-musl-ryqtgdv6.node → ast-grep-napi.linux-x64-musl-asvc2tv1.node} +0 -0
  5. package/dist/package/main.js +135337 -118733
  6. package/dist/package/{web-tree-sitter-vmqc5d26.wasm → web-tree-sitter-jbz042ba.wasm} +0 -0
  7. package/docs/README.md +1 -1
  8. package/docs/tools-and-commands.md +9 -0
  9. package/package.json +5 -3
  10. package/src/agent/capability-registry.ts +300 -0
  11. package/src/agent/email/style-reply-lane.ts +13 -4
  12. package/src/agent/memory-consolidation-receipts.ts +79 -0
  13. package/src/agent/memory-prompt.ts +3 -1
  14. package/src/agent/operator-gateway-call.ts +134 -0
  15. package/src/agent/principal-attribution.ts +97 -0
  16. package/src/agent/prompt-context-receipts.ts +5 -1
  17. package/src/agent/unified-inbox.ts +17 -1
  18. package/src/cli/bundle-command.ts +2 -1
  19. package/src/cli/channel-profiles-command.ts +168 -0
  20. package/src/cli/ci-command.ts +214 -0
  21. package/src/cli/completion.ts +5 -0
  22. package/src/cli/connected-host-metrics.ts +165 -0
  23. package/src/cli/entrypoint.ts +25 -1
  24. package/src/cli/fleet-command.ts +178 -0
  25. package/src/cli/help.ts +115 -0
  26. package/src/cli/import-command.ts +208 -0
  27. package/src/cli/index.ts +1 -0
  28. package/src/cli/management-commands.ts +9 -8
  29. package/src/cli/management.ts +42 -0
  30. package/src/cli/memory-command.ts +4 -3
  31. package/src/cli/openclaw-import.ts +382 -0
  32. package/src/cli/operator-command-args.ts +88 -0
  33. package/src/cli/parser.ts +12 -0
  34. package/src/cli/personas-command.ts +3 -2
  35. package/src/cli/principals-command.ts +230 -0
  36. package/src/cli/profiles-command.ts +2 -1
  37. package/src/cli/relay-command.ts +100 -0
  38. package/src/cli/routines-command.ts +4 -3
  39. package/src/cli/skill-bundle-command.ts +3 -2
  40. package/src/cli/skills-command.ts +3 -2
  41. package/src/cli/status.ts +114 -2
  42. package/src/cli/temporal-label.ts +89 -0
  43. package/src/cli/types.ts +7 -0
  44. package/src/cli/workspaces-command.ts +135 -0
  45. package/src/config/agent-settings-policy.ts +8 -0
  46. package/src/config/checkpoint-settings.ts +113 -0
  47. package/src/config/workspace-registration.ts +299 -0
  48. package/src/core/rewind-turn-anchors.ts +82 -0
  49. package/src/input/agent-workspace-categories.ts +1 -0
  50. package/src/input/agent-workspace-channel-triage.ts +27 -8
  51. package/src/input/agent-workspace-local-library-snapshot.ts +5 -1
  52. package/src/input/agent-workspace-onboarding-categories.ts +17 -1
  53. package/src/input/agent-workspace-snapshot.ts +25 -3
  54. package/src/input/agent-workspace-types.ts +5 -5
  55. package/src/input/commands/connected-host-admin-runtime.ts +75 -0
  56. package/src/input/commands.ts +2 -0
  57. package/src/input/settings-modal-types.ts +16 -5
  58. package/src/main.ts +17 -12
  59. package/src/permissions/approval-posture.ts +40 -5
  60. package/src/permissions/prompt.ts +35 -1
  61. package/src/renderer/settings-modal-helpers.ts +11 -0
  62. package/src/renderer/settings-modal.ts +21 -0
  63. package/src/renderer/terminal-escapes.ts +65 -12
  64. package/src/runtime/agent-runtime-events.ts +102 -3
  65. package/src/runtime/bootstrap-core.ts +111 -10
  66. package/src/runtime/bootstrap-external-services.ts +156 -0
  67. package/src/runtime/bootstrap.ts +73 -69
  68. package/src/runtime/context-accounting-source.ts +114 -0
  69. package/src/runtime/conversation-rewind-port.ts +133 -0
  70. package/src/runtime/index.ts +8 -28
  71. package/src/runtime/memory-consolidation-scheduler.ts +67 -0
  72. package/src/runtime/memory-consolidation-wiring.ts +56 -0
  73. package/src/runtime/memory-usage-wiring.ts +75 -0
  74. package/src/runtime/services.ts +250 -14
  75. package/src/runtime/tool-permission-safety.ts +18 -1
  76. package/src/shell/blocking-input.ts +38 -8
  77. package/src/shell/startup-wiring.ts +26 -1
  78. package/src/tools/agent-context-policy.ts +1 -1
  79. package/src/tools/agent-harness-cli-command-policy.ts +29 -3
  80. package/src/tools/agent-harness-learning-auto-promote.ts +7 -3
  81. package/src/tools/agent-harness-memory-posture.ts +57 -2
  82. package/src/tools/agent-harness-metadata.ts +12 -0
  83. package/src/tools/agent-harness-mode-catalog.ts +1 -1
  84. package/src/tools/agent-harness-personal-ops-lanes.ts +8 -6
  85. package/src/tools/agent-harness-prompt-context.ts +3 -1
  86. package/src/tools/agent-harness-tool.ts +3 -2
  87. package/src/tools/agent-policy-explanation.ts +40 -0
  88. package/src/tools/tool-definition-compaction.ts +2 -0
  89. package/src/version.ts +1 -1
@@ -0,0 +1,208 @@
1
+ import { isAbsolute, resolve } from 'node:path';
2
+ import {
3
+ MemoryEmbeddingProviderRegistry,
4
+ MemoryRegistry,
5
+ MemoryStore,
6
+ resolveCanonicalMemoryDbPath,
7
+ } from '@pellux/goodvibes-sdk/platform/state';
8
+ import { assertNoSecretLikeMemoryText } from '../agent/memory-safety.ts';
9
+ import { buildAgentSkillRequirements } from '../agent/skill-registry.ts';
10
+ import type { CliCommandOutput } from './types.ts';
11
+ import type { CliCommandRuntime } from './management.ts';
12
+ import { failure, personaRegistry, skillRegistry, success } from './local-library-command-shared.ts';
13
+ import {
14
+ OPENCLAW_LAYOUT_VERSION,
15
+ scanOpenClawWorkspace,
16
+ type OpenClawImportPlan,
17
+ } from './openclaw-import.ts';
18
+
19
+ /**
20
+ * `goodvibes-agent import openclaw [path]` — migrate an OpenClaw workspace into
21
+ * the Agent's existing registries. Dry-run is the DEFAULT (prints what would be
22
+ * created); --apply performs the writes through the persona registry, the
23
+ * canonical memory store, the skill registry, and the permission settings. No
24
+ * parallel storage is invented. See openclaw-import.ts for the targeted layout.
25
+ */
26
+
27
+ interface ApplyCounts {
28
+ created: number;
29
+ skipped: number;
30
+ }
31
+
32
+ function resolveSourcePath(runtime: CliCommandRuntime, arg: string | undefined): string {
33
+ // Home is taken from the runtime's owned home root (the composition root passes
34
+ // it in), never discovered implicitly. Default workspace is <home>/.openclaw.
35
+ if (!arg) return resolve(runtime.homeDirectory, '.openclaw');
36
+ if (arg.startsWith('~')) return resolve(runtime.homeDirectory, arg.slice(1).replace(/^[/\\]/, ''));
37
+ if (isAbsolute(arg)) return arg;
38
+ return resolve(runtime.workingDirectory, arg);
39
+ }
40
+
41
+ function planSummaryLines(plan: OpenClawImportPlan): string[] {
42
+ const lines: string[] = [];
43
+ lines.push(` personas: ${plan.personas.length}`);
44
+ for (const persona of plan.personas) lines.push(` - ${persona.name} (${persona.sourcePath})`);
45
+ lines.push(` memory records: ${plan.memories.length}`);
46
+ for (const memory of plan.memories) lines.push(` - ${memory.scope}/${memory.cls} ${memory.summary}`);
47
+ lines.push(` skills: ${plan.skills.length}`);
48
+ for (const skill of plan.skills) lines.push(` - ${skill.name} (${skill.sourcePath})`);
49
+ const perm = plan.permissions;
50
+ lines.push(` permission allowlist: ${perm.categories.length} categor${perm.categories.length === 1 ? 'y' : 'ies'}${perm.sourcePath ? ` (${perm.sourcePath})` : ''}`);
51
+ if (perm.categories.length > 0) lines.push(` - allow: ${perm.categories.join(', ')} (mode -> custom)`);
52
+ lines.push(` skipped: ${plan.skipped.length}`);
53
+ for (const entry of plan.skipped) lines.push(` - ${entry.path}: ${entry.reason}`);
54
+ return lines;
55
+ }
56
+
57
+ function renderDryRun(plan: OpenClawImportPlan): string {
58
+ return [
59
+ `OpenClaw import (dry run) — layout ${OPENCLAW_LAYOUT_VERSION}`,
60
+ ` source ${plan.sourcePath}`,
61
+ ...planSummaryLines(plan),
62
+ '',
63
+ ' This is a preview. Re-run with --apply to write these into the Agent registries.',
64
+ ].join('\n');
65
+ }
66
+
67
+ async function applyMemories(
68
+ runtime: CliCommandRuntime,
69
+ plan: OpenClawImportPlan,
70
+ notes: string[],
71
+ ): Promise<ApplyCounts> {
72
+ const counts: ApplyCounts = { created: 0, skipped: 0 };
73
+ if (plan.memories.length === 0) return counts;
74
+ const path = resolveCanonicalMemoryDbPath(runtime.homeDirectory);
75
+ const embeddingRegistry = new MemoryEmbeddingProviderRegistry({ configManager: runtime.configManager });
76
+ const store = new MemoryStore(path, { embeddingRegistry });
77
+ await store.init();
78
+ const registry = new MemoryRegistry(store);
79
+ try {
80
+ for (const memory of plan.memories) {
81
+ try {
82
+ assertNoSecretLikeMemoryText([memory.summary, memory.detail ?? '', ...memory.tags]);
83
+ await registry.add({
84
+ scope: memory.scope as 'session' | 'project' | 'team',
85
+ cls: memory.cls as 'fact',
86
+ summary: memory.summary,
87
+ detail: memory.detail,
88
+ tags: [...memory.tags],
89
+ provenance: [{ kind: 'event', ref: `openclaw-import:${memory.sourcePath}` }],
90
+ });
91
+ counts.created += 1;
92
+ } catch (error) {
93
+ counts.skipped += 1;
94
+ notes.push(` memory skipped (${memory.summary}): ${error instanceof Error ? error.message : String(error)}`);
95
+ }
96
+ }
97
+ } finally {
98
+ await store.save();
99
+ store.close();
100
+ }
101
+ return counts;
102
+ }
103
+
104
+ function applyPersonas(runtime: CliCommandRuntime, plan: OpenClawImportPlan, notes: string[]): ApplyCounts {
105
+ const counts: ApplyCounts = { created: 0, skipped: 0 };
106
+ const registry = personaRegistry(runtime);
107
+ for (const persona of plan.personas) {
108
+ try {
109
+ registry.create({
110
+ name: persona.name,
111
+ description: persona.description,
112
+ body: persona.body,
113
+ tags: [...persona.tags],
114
+ source: 'imported',
115
+ provenance: `openclaw-import:${persona.sourcePath}`,
116
+ });
117
+ counts.created += 1;
118
+ } catch (error) {
119
+ counts.skipped += 1;
120
+ notes.push(` persona skipped (${persona.name}): ${error instanceof Error ? error.message : String(error)}`);
121
+ }
122
+ }
123
+ return counts;
124
+ }
125
+
126
+ function applySkills(runtime: CliCommandRuntime, plan: OpenClawImportPlan, notes: string[]): ApplyCounts {
127
+ const counts: ApplyCounts = { created: 0, skipped: 0 };
128
+ const registry = skillRegistry(runtime);
129
+ for (const skill of plan.skills) {
130
+ try {
131
+ registry.create({
132
+ name: skill.name,
133
+ description: skill.description,
134
+ procedure: skill.procedure,
135
+ tags: [...skill.tags],
136
+ requirements: buildAgentSkillRequirements({ env: skill.requiresEnv, commands: skill.requiresCommand }),
137
+ source: 'imported',
138
+ provenance: `openclaw-import:${skill.sourcePath}`,
139
+ });
140
+ counts.created += 1;
141
+ } catch (error) {
142
+ counts.skipped += 1;
143
+ notes.push(` skill skipped (${skill.name}): ${error instanceof Error ? error.message : String(error)}`);
144
+ }
145
+ }
146
+ return counts;
147
+ }
148
+
149
+ function applyPermissions(runtime: CliCommandRuntime, plan: OpenClawImportPlan): number {
150
+ const categories = plan.permissions.categories;
151
+ if (categories.length === 0) return 0;
152
+ runtime.configManager.setDynamic('permissions.mode', 'custom');
153
+ for (const category of categories) {
154
+ runtime.configManager.setDynamic(`permissions.tools.${category}` as never, 'allow');
155
+ }
156
+ return categories.length;
157
+ }
158
+
159
+ async function renderApply(runtime: CliCommandRuntime, plan: OpenClawImportPlan): Promise<string> {
160
+ const notes: string[] = [];
161
+ const personas = applyPersonas(runtime, plan, notes);
162
+ const memories = await applyMemories(runtime, plan, notes);
163
+ const skills = applySkills(runtime, plan, notes);
164
+ const permissionCategories = applyPermissions(runtime, plan);
165
+ return [
166
+ `OpenClaw import (applied) — layout ${OPENCLAW_LAYOUT_VERSION}`,
167
+ ` source ${plan.sourcePath}`,
168
+ ` personas created ${personas.created}${personas.skipped ? ` (skipped ${personas.skipped})` : ''}`,
169
+ ` memory records created ${memories.created}${memories.skipped ? ` (skipped ${memories.skipped})` : ''}`,
170
+ ` skills created ${skills.created}${skills.skipped ? ` (skipped ${skills.skipped})` : ''}`,
171
+ ` permission categories allowed ${permissionCategories}${permissionCategories ? ` (${plan.permissions.categories.join(', ')}; mode -> custom)` : ''}`,
172
+ ` input files skipped ${plan.skipped.length}`,
173
+ ...notes,
174
+ ].join('\n');
175
+ }
176
+
177
+ function usageImport(): string {
178
+ return 'Usage: goodvibes-agent import openclaw [path] [--apply]';
179
+ }
180
+
181
+ export async function handleImportCommand(runtime: CliCommandRuntime): Promise<CliCommandOutput> {
182
+ try {
183
+ const [sourceRaw, ...rest] = runtime.cli.commandArgs;
184
+ const source = (sourceRaw ?? '').toLowerCase();
185
+ if (!source) return failure(runtime, 'invalid_import_command', usageImport(), 2);
186
+ if (source !== 'openclaw') {
187
+ return failure(runtime, 'unknown_import_source', `Unknown import source "${sourceRaw}". Supported sources: openclaw.\n${usageImport()}`, 2);
188
+ }
189
+ const apply = rest.includes('--apply');
190
+ const pathArg = rest.find((arg) => !arg.startsWith('--'));
191
+ const sourcePath = resolveSourcePath(runtime, pathArg);
192
+ const plan = scanOpenClawWorkspace(sourcePath);
193
+ if (!plan.exists) {
194
+ const message = pathArg
195
+ ? `OpenClaw workspace not found at ${sourcePath}.`
196
+ : `No OpenClaw workspace found at the default location ${sourcePath}. Pass a path: goodvibes-agent import openclaw <path>.`;
197
+ return failure(runtime, 'openclaw_workspace_missing', message, 1);
198
+ }
199
+ if (apply) {
200
+ const output = await renderApply(runtime, plan);
201
+ return success(runtime, 'agent.import.openclaw.apply', plan, output);
202
+ }
203
+ return success(runtime, 'agent.import.openclaw.dryRun', plan, renderDryRun(plan));
204
+ } catch (error) {
205
+ const message = error instanceof Error ? error.message : String(error);
206
+ return failure(runtime, 'agent.import.error', message, 1);
207
+ }
208
+ }
package/src/cli/index.ts CHANGED
@@ -7,4 +7,5 @@ export * from './completion.ts';
7
7
  export * from './config-overrides.ts';
8
8
  export * from './endpoints.ts';
9
9
  export * from './bundle-command.ts';
10
+ export * from './import-command.ts';
10
11
  export * from './management.ts';
@@ -14,6 +14,7 @@ import { extractAuthorizationCode, formatJsonOrText, hasCommandFlag, openBrowser
14
14
  import { GOODVIBES_AGENT_PAIRING_SURFACE } from '../config/surface.ts';
15
15
  import { connectedHostTokenRequiredMessage, readConnectedHostOperatorToken } from '../runtime/connected-host-auth.ts';
16
16
  import { formatAgentRecordSource } from '../agent/record-labels.ts';
17
+ import { appendTemporalLabel } from './temporal-label.ts';
17
18
 
18
19
  function resolveManualSubscriptionConfig(config: OAuthProviderConfig): OAuthProviderConfig {
19
20
  return config.manualRedirectUri
@@ -58,7 +59,7 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
58
59
  ] : []),
59
60
  ...(stored ? [
60
61
  ` token type ${stored.tokenType}`,
61
- ` expires ${stored.expiresAt ? new Date(stored.expiresAt).toISOString() : 'n/a'}`,
62
+ ` expires ${stored.expiresAt ? appendTemporalLabel(new Date(stored.expiresAt).toISOString(), stored.expiresAt) : 'n/a'}`,
62
63
  ` refresh token ${stored.refreshToken ? 'present' : 'absent'}`,
63
64
  ` override ambient ${yesNo(stored.overrideAmbientApiKeys)}`,
64
65
  ] : [' stored no']),
@@ -136,7 +137,7 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
136
137
  return [
137
138
  `Subscription stored ${provider}`,
138
139
  ` token type ${record.tokenType}`,
139
- ` expires ${record.expiresAt ? new Date(record.expiresAt).toISOString() : 'n/a'}`,
140
+ ` expires ${record.expiresAt ? appendTemporalLabel(new Date(record.expiresAt).toISOString(), record.expiresAt) : 'n/a'}`,
140
141
  ].join('\n');
141
142
  }
142
143
  const activeConfig = resolveManualSubscriptionConfig(resolved.oauth);
@@ -144,7 +145,7 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
144
145
  return [
145
146
  `Subscription stored ${provider}`,
146
147
  ` token type ${record.tokenType}`,
147
- ` expires ${record.expiresAt ? new Date(record.expiresAt).toISOString() : 'n/a'}`,
148
+ ` expires ${record.expiresAt ? appendTemporalLabel(new Date(record.expiresAt).toISOString(), record.expiresAt) : 'n/a'}`,
148
149
  ].join('\n');
149
150
  }
150
151
  if (sub === 'refresh') {
@@ -155,7 +156,7 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
155
156
  const record = await services.subscriptionManager.refreshOAuthToken(provider, resolved.oauth);
156
157
  return [
157
158
  `Subscription refreshed ${provider}`,
158
- ` expires ${record.expiresAt ? new Date(record.expiresAt).toISOString() : 'n/a'}`,
159
+ ` expires ${record.expiresAt ? appendTemporalLabel(new Date(record.expiresAt).toISOString(), record.expiresAt) : 'n/a'}`,
159
160
  ].join('\n');
160
161
  }
161
162
  if (sub === 'logout' || sub === 'remove') {
@@ -179,9 +180,9 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
179
180
  return formatJsonOrText(runtime.cli)(value, [
180
181
  'GoodVibes subscriptions',
181
182
  subscriptions.length === 0 ? ' active none' : ' active',
182
- ...subscriptions.map((sub) => ` ${sub.provider} token ${sub.tokenType} expires ${sub.expiresAt ? new Date(sub.expiresAt).toISOString() : 'n/a'} override ambient ${yesNo(sub.overrideAmbientApiKeys)}`),
183
+ ...subscriptions.map((sub) => ` ${sub.provider} token ${sub.tokenType} expires ${sub.expiresAt ? appendTemporalLabel(new Date(sub.expiresAt).toISOString(), sub.expiresAt) : 'n/a'} override ambient ${yesNo(sub.overrideAmbientApiKeys)}`),
183
184
  pending.length === 0 ? ' pending none' : ' pending',
184
- ...pending.map((sub) => ` ${sub.provider} created ${new Date(sub.createdAt).toISOString()}`),
185
+ ...pending.map((sub) => ` ${sub.provider} created ${appendTemporalLabel(new Date(sub.createdAt).toISOString(), sub.createdAt)}`),
185
186
  ].join('\n'));
186
187
  });
187
188
  }
@@ -268,7 +269,7 @@ export async function handleSessions(runtime: CliCommandRuntime): Promise<string
268
269
  const value = sessions;
269
270
  return formatJsonOrText(runtime.cli)(value, [
270
271
  `GoodVibes sessions (${sessions.length})`,
271
- ...sessions.slice(0, 50).map((session) => ` ${session.name} messages ${session.messageCount} ${new Date(session.timestamp).toISOString()} ${session.title || '(untitled)'}`),
272
+ ...sessions.slice(0, 50).map((session) => ` ${session.name} messages ${session.messageCount} ${appendTemporalLabel(new Date(session.timestamp).toISOString(), session.timestamp)} ${session.title || '(untitled)'}`),
272
273
  ].join('\n'));
273
274
  }
274
275
  if (sub === 'show' || sub === 'info') {
@@ -281,7 +282,7 @@ export async function handleSessions(runtime: CliCommandRuntime): Promise<string
281
282
  ` title ${found.title || '(untitled)'}`,
282
283
  ` messages ${found.messageCount}`,
283
284
  ` provider/model ${found.provider}/${found.model}`,
284
- ` updated ${new Date(found.timestamp).toISOString()}`,
285
+ ` updated ${appendTemporalLabel(new Date(found.timestamp).toISOString(), found.timestamp)}`,
285
286
  ` file ${found.filePath}`,
286
287
  ].join('\n'));
287
288
  }
@@ -27,6 +27,7 @@ import type { GoodVibesCliParseResult } from './types.ts';
27
27
  import { formatProviderAuthRoute, summarizeProviderAuthRoutes } from './provider-auth-routes.ts';
28
28
  import { classifyProviderSetup } from './provider-classification.ts';
29
29
  import { handleBundleCommand } from './bundle-command.ts';
30
+ import { handleImportCommand } from './import-command.ts';
30
31
  import { handleSecrets, handleSessions, handleTasks, renderPairing, renderSubscriptions } from './management-commands.ts';
31
32
  import { handleAgentKnowledgeCommand, handleAgentKnowledgeShortcutCommand, handleCompatCommand, handleDelegateCommand } from './agent-knowledge-command.ts';
32
33
  import { handlePersonasCommand } from './personas-command.ts';
@@ -34,6 +35,12 @@ import { handleSkillsCommand } from './skills-command.ts';
34
35
  import { handleMemoryCommand } from './memory-command.ts';
35
36
  import { handleProfilesCommand } from './profiles-command.ts';
36
37
  import { handleRoutinesCommand } from './routines-command.ts';
38
+ import { handleCiCommand } from './ci-command.ts';
39
+ import { handlePrincipalsCommand } from './principals-command.ts';
40
+ import { handleChannelProfilesCommand } from './channel-profiles-command.ts';
41
+ import { handleWorkspacesCommand } from './workspaces-command.ts';
42
+ import { handleRelayCommand } from './relay-command.ts';
43
+ import { handleFleetCommand } from './fleet-command.ts';
37
44
  import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
38
45
 
39
46
  export interface CliCommandRuntime {
@@ -620,6 +627,36 @@ export async function handleGoodVibesCliCommand(runtime: CliCommandRuntime): Pro
620
627
  console.log(result.output);
621
628
  return { handled: true, exitCode: result.exitCode };
622
629
  }
630
+ case 'ci': {
631
+ const result = await handleCiCommand(runtime);
632
+ console.log(result.output);
633
+ return { handled: true, exitCode: result.exitCode };
634
+ }
635
+ case 'principals': {
636
+ const result = await handlePrincipalsCommand(runtime);
637
+ console.log(result.output);
638
+ return { handled: true, exitCode: result.exitCode };
639
+ }
640
+ case 'channel-profiles': {
641
+ const result = await handleChannelProfilesCommand(runtime);
642
+ console.log(result.output);
643
+ return { handled: true, exitCode: result.exitCode };
644
+ }
645
+ case 'workspaces': {
646
+ const result = await handleWorkspacesCommand(runtime);
647
+ console.log(result.output);
648
+ return { handled: true, exitCode: result.exitCode };
649
+ }
650
+ case 'relay': {
651
+ const result = handleRelayCommand(runtime);
652
+ console.log(result.output);
653
+ return { handled: true, exitCode: result.exitCode };
654
+ }
655
+ case 'fleet': {
656
+ const result = await handleFleetCommand(runtime);
657
+ console.log(result.output);
658
+ return { handled: true, exitCode: result.exitCode };
659
+ }
623
660
  case 'knowledge': {
624
661
  const result = await handleAgentKnowledgeCommand(runtime);
625
662
  console.log(result.output);
@@ -665,6 +702,11 @@ export async function handleGoodVibesCliCommand(runtime: CliCommandRuntime): Pro
665
702
  console.log(result.output);
666
703
  return { handled: true, exitCode: result.exitCode };
667
704
  }
705
+ case 'import': {
706
+ const result = await handleImportCommand(runtime);
707
+ console.log(result.output);
708
+ return { handled: true, exitCode: result.exitCode };
709
+ }
668
710
  default:
669
711
  return { handled: false, exitCode: 0 };
670
712
  }
@@ -23,6 +23,7 @@ import type { CliCommandRuntime } from './management.ts';
23
23
  // Split out to stay under the architecture line cap; see its file header for the
24
24
  // CLI ruling on when a memory subcommand goes over the wire vs local-direct.
25
25
  import { tryWireMemoryCommand } from './memory-command-wire.ts';
26
+ import { appendTemporalLabel } from './temporal-label.ts';
26
27
 
27
28
  const VALID_CLASSES: readonly MemoryClass[] = ['decision', 'constraint', 'incident', 'pattern', 'fact', 'risk', 'runbook', 'architecture', 'ownership'];
28
29
  const VALID_SCOPES: readonly MemoryScope[] = ['session', 'project', 'team'];
@@ -253,9 +254,9 @@ export function renderRecord(record: MemoryRecord, links: readonly MemoryLink[])
253
254
  ` review: ${formatAgentRecordReviewState(record.reviewState)}`,
254
255
  ` confidence: ${record.confidence}`,
255
256
  ` tags: ${record.tags.join(', ') || '(none)'}`,
256
- ` created: ${timestamp(record.createdAt)}`,
257
- ` updated: ${timestamp(record.updatedAt)}`,
258
- record.reviewedAt ? ` reviewed: ${timestamp(record.reviewedAt)}${record.reviewedBy ? ` by ${record.reviewedBy}` : ''}` : '',
257
+ ` created: ${appendTemporalLabel(timestamp(record.createdAt), record.createdAt)}`,
258
+ ` updated: ${appendTemporalLabel(timestamp(record.updatedAt), record.updatedAt)}`,
259
+ record.reviewedAt ? ` reviewed: ${appendTemporalLabel(timestamp(record.reviewedAt), record.reviewedAt)}${record.reviewedBy ? ` by ${record.reviewedBy}` : ''}` : '',
259
260
  record.staleReason ? ` stale reason: ${record.staleReason}` : '',
260
261
  '',
261
262
  record.summary,