@pellux/goodvibes-agent 0.1.117 → 1.0.1

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 (255) hide show
  1. package/CHANGELOG.md +20 -4
  2. package/README.md +35 -14
  3. package/bin/goodvibes-agent.ts +16 -2
  4. package/dist/package/main.js +176093 -171065
  5. package/docs/README.md +11 -5
  6. package/docs/channels-remote-and-api.md +50 -0
  7. package/docs/connected-host.md +3 -3
  8. package/docs/getting-started.md +29 -15
  9. package/docs/knowledge-artifacts-and-multimodal.md +91 -0
  10. package/docs/project-planning.md +79 -0
  11. package/docs/providers-and-routing.md +46 -0
  12. package/docs/release-and-publishing.md +44 -9
  13. package/docs/tools-and-commands.md +123 -0
  14. package/docs/voice-and-live-tts.md +51 -0
  15. package/package.json +2 -5
  16. package/src/agent/channel-delivery.ts +201 -0
  17. package/src/agent/media-generation.ts +159 -0
  18. package/src/agent/memory-prompt.ts +0 -1
  19. package/src/agent/note-registry.ts +329 -0
  20. package/src/agent/operator-actions.ts +343 -0
  21. package/src/agent/persona-registry.ts +15 -14
  22. package/src/agent/record-labels.ts +107 -0
  23. package/src/agent/reminder-schedule-format.ts +33 -24
  24. package/src/agent/reminder-schedule.ts +26 -25
  25. package/src/agent/routine-registry.ts +13 -12
  26. package/src/agent/routine-schedule-args.ts +2 -1
  27. package/src/agent/routine-schedule-format.ts +77 -53
  28. package/src/agent/routine-schedule-promotion.ts +34 -32
  29. package/src/agent/routine-schedule-receipts.ts +28 -26
  30. package/src/agent/runtime-profile-starters.ts +2 -2
  31. package/src/agent/runtime-profile.ts +18 -17
  32. package/src/agent/skill-registry.ts +25 -24
  33. package/src/cli/agent-knowledge-args.ts +5 -1
  34. package/src/cli/agent-knowledge-command.ts +39 -33
  35. package/src/cli/agent-knowledge-format.ts +80 -67
  36. package/src/cli/agent-knowledge-methods.ts +1 -1
  37. package/src/cli/agent-knowledge-runtime.ts +32 -26
  38. package/src/cli/bundle-command.ts +13 -8
  39. package/src/cli/config-overrides.ts +37 -36
  40. package/src/cli/external-runtime.ts +10 -4
  41. package/src/cli/help.ts +29 -11
  42. package/src/cli/local-library-command.ts +159 -73
  43. package/src/cli/management-commands.ts +98 -62
  44. package/src/cli/management.ts +52 -26
  45. package/src/cli/memory-command.ts +66 -32
  46. package/src/cli/parser.ts +37 -24
  47. package/src/cli/profiles-command.ts +52 -35
  48. package/src/cli/provider-auth-routes.ts +2 -1
  49. package/src/cli/routines-command.ts +59 -39
  50. package/src/cli/service-posture.ts +6 -6
  51. package/src/cli/status.ts +46 -121
  52. package/src/core/conversation-message-snapshot.ts +131 -0
  53. package/src/input/agent-workspace-activation.ts +33 -7
  54. package/src/input/agent-workspace-basic-command-editor-submission.ts +7 -3
  55. package/src/input/agent-workspace-basic-command-editors.ts +30 -10
  56. package/src/input/agent-workspace-categories.ts +276 -64
  57. package/src/input/agent-workspace-channel-command-editor-submission.ts +34 -0
  58. package/src/input/agent-workspace-channel-command-editors.ts +23 -5
  59. package/src/input/agent-workspace-channels.ts +35 -2
  60. package/src/input/agent-workspace-command-editor.ts +18 -2
  61. package/src/input/agent-workspace-config-reader.ts +16 -0
  62. package/src/input/agent-workspace-delegation-editor-submission.ts +1 -1
  63. package/src/input/agent-workspace-editors.ts +140 -2
  64. package/src/input/agent-workspace-knowledge-query-editor.ts +1 -1
  65. package/src/input/agent-workspace-knowledge-url-editor.ts +4 -11
  66. package/src/input/agent-workspace-learned-behavior.ts +2 -2
  67. package/src/input/agent-workspace-library-command-editor-submission.ts +1 -1
  68. package/src/input/agent-workspace-library-command-editors.ts +2 -2
  69. package/src/input/agent-workspace-local-operations.ts +218 -0
  70. package/src/input/agent-workspace-local-selection.ts +75 -0
  71. package/src/input/agent-workspace-media-command-editor-submission.ts +62 -0
  72. package/src/input/agent-workspace-media-command-editors.ts +27 -0
  73. package/src/input/agent-workspace-memory-command-editors.ts +1 -1
  74. package/src/input/agent-workspace-memory-editor.ts +2 -2
  75. package/src/input/agent-workspace-navigation.ts +38 -2
  76. package/src/input/agent-workspace-notify-editor-submission.ts +16 -2
  77. package/src/input/agent-workspace-operations-command-editor-submission.ts +63 -1
  78. package/src/input/agent-workspace-operations-command-editors.ts +80 -3
  79. package/src/input/agent-workspace-panel-route.ts +43 -0
  80. package/src/input/agent-workspace-reminder-schedule-editor.ts +1 -1
  81. package/src/input/agent-workspace-routine-schedule-editor.ts +2 -2
  82. package/src/input/agent-workspace-search.ts +169 -0
  83. package/src/input/agent-workspace-setup.ts +22 -11
  84. package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +6 -6
  85. package/src/input/agent-workspace-skill-bundle-command-editors.ts +10 -10
  86. package/src/input/agent-workspace-snapshot.ts +112 -13
  87. package/src/input/agent-workspace-task-command-editors.ts +4 -4
  88. package/src/input/agent-workspace-token.ts +18 -2
  89. package/src/input/agent-workspace-types.ts +92 -4
  90. package/src/input/agent-workspace-voice-media.ts +3 -6
  91. package/src/input/agent-workspace-web-research-editor.ts +104 -0
  92. package/src/input/agent-workspace.ts +136 -208
  93. package/src/input/command-registry.ts +4 -1
  94. package/src/input/commands/agent-local-library-args.ts +52 -0
  95. package/src/input/commands/agent-runtime-profile-runtime.ts +45 -41
  96. package/src/input/commands/agent-skills-runtime.ts +87 -99
  97. package/src/input/commands/agent-workspace-runtime.ts +23 -7
  98. package/src/input/commands/brief-runtime.ts +25 -24
  99. package/src/input/commands/channels-runtime.ts +145 -31
  100. package/src/input/commands/delegation-runtime.ts +29 -23
  101. package/src/input/commands/experience-runtime.ts +6 -4
  102. package/src/input/commands/guidance-runtime.ts +4 -4
  103. package/src/input/commands/health-runtime.ts +140 -115
  104. package/src/input/commands/knowledge.ts +57 -56
  105. package/src/input/commands/local-provider-runtime.ts +36 -18
  106. package/src/input/commands/local-runtime.ts +138 -16
  107. package/src/input/commands/local-setup-review.ts +7 -7
  108. package/src/input/commands/mcp-runtime.ts +118 -50
  109. package/src/input/commands/notify-runtime.ts +38 -9
  110. package/src/input/commands/operator-actions-runtime.ts +138 -0
  111. package/src/input/commands/operator-runtime.ts +6 -17
  112. package/src/input/commands/personas-runtime.ts +49 -59
  113. package/src/input/commands/planning-runtime.ts +1 -1
  114. package/src/input/commands/platform-access-runtime.ts +29 -29
  115. package/src/input/commands/provider-accounts-runtime.ts +60 -39
  116. package/src/input/commands/qrcode-runtime.ts +45 -6
  117. package/src/input/commands/recall-bundle.ts +11 -11
  118. package/src/input/commands/recall-capture.ts +13 -11
  119. package/src/input/commands/recall-query.ts +29 -21
  120. package/src/input/commands/recall-review.ts +2 -1
  121. package/src/input/commands/routines-runtime.ts +63 -72
  122. package/src/input/commands/schedule-runtime.ts +33 -20
  123. package/src/input/commands/security-runtime.ts +4 -4
  124. package/src/input/commands/session-content.ts +80 -78
  125. package/src/input/commands/session-workflow.ts +132 -93
  126. package/src/input/commands/session.ts +3 -174
  127. package/src/input/commands/shell-core.ts +32 -17
  128. package/src/input/commands/subscription-runtime.ts +53 -179
  129. package/src/input/commands/tasks-runtime.ts +20 -20
  130. package/src/input/commands/work-plan-runtime.ts +33 -8
  131. package/src/input/commands.ts +2 -2
  132. package/src/input/feed-context-factory.ts +2 -1
  133. package/src/input/file-picker.ts +3 -2
  134. package/src/input/handler-command-route.ts +4 -7
  135. package/src/input/handler-content-actions.ts +89 -1
  136. package/src/input/handler-feed-routes.ts +19 -12
  137. package/src/input/handler-feed.ts +6 -3
  138. package/src/input/handler-interactions.ts +7 -5
  139. package/src/input/handler-modal-stack.ts +3 -3
  140. package/src/input/handler-onboarding.ts +24 -80
  141. package/src/input/handler-shortcuts.ts +15 -4
  142. package/src/input/handler.ts +47 -17
  143. package/src/input/input-history.ts +5 -6
  144. package/src/input/keybindings.ts +22 -11
  145. package/src/input/onboarding/onboarding-wizard-apply.ts +13 -0
  146. package/src/input/onboarding/onboarding-wizard-constants.ts +1 -0
  147. package/src/input/onboarding/onboarding-wizard-operator-steps.ts +147 -57
  148. package/src/input/onboarding/onboarding-wizard-steps.ts +46 -29
  149. package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
  150. package/src/input/onboarding/onboarding-wizard.ts +68 -0
  151. package/src/input/profile-picker-modal.ts +31 -12
  152. package/src/input/session-picker-modal.ts +22 -4
  153. package/src/input/settings-modal-behavior.ts +0 -3
  154. package/src/input/settings-modal-subscriptions.ts +3 -3
  155. package/src/input/settings-modal-types.ts +2 -13
  156. package/src/input/settings-modal.ts +6 -52
  157. package/src/input/submission-intent.ts +0 -1
  158. package/src/input/submission-router.ts +3 -3
  159. package/src/main.ts +18 -8
  160. package/src/panels/approval-panel.ts +8 -8
  161. package/src/panels/automation-control-panel.ts +2 -2
  162. package/src/panels/base-panel.ts +1 -1
  163. package/src/panels/builtin/agent.ts +1 -1
  164. package/src/panels/builtin/operations.ts +1 -10
  165. package/src/panels/builtin/session.ts +9 -9
  166. package/src/panels/builtin/shared.ts +2 -2
  167. package/src/panels/cost-tracker-panel.ts +1 -1
  168. package/src/panels/docs-panel.ts +5 -3
  169. package/src/panels/index.ts +0 -1
  170. package/src/panels/knowledge-panel.ts +6 -5
  171. package/src/panels/memory-panel.ts +7 -6
  172. package/src/panels/panel-list-panel.ts +36 -80
  173. package/src/panels/project-planning-panel.ts +18 -11
  174. package/src/panels/provider-account-snapshot.ts +51 -25
  175. package/src/panels/provider-accounts-panel.ts +33 -18
  176. package/src/panels/provider-health-domains.ts +45 -4
  177. package/src/panels/provider-health-panel.ts +5 -4
  178. package/src/panels/qr-panel.ts +1 -1
  179. package/src/panels/schedule-panel.ts +9 -17
  180. package/src/panels/security-panel.ts +8 -8
  181. package/src/panels/session-browser-panel.ts +10 -10
  182. package/src/panels/subscription-panel.ts +3 -3
  183. package/src/panels/system-messages-panel.ts +1 -1
  184. package/src/panels/tasks-panel.ts +20 -13
  185. package/src/panels/tool-inspector-panel.ts +19 -12
  186. package/src/panels/work-plan-panel.ts +5 -5
  187. package/src/planning/project-planning-coordinator.ts +1 -1
  188. package/src/provider-auth-route-display.ts +9 -0
  189. package/src/renderer/agent-workspace-style.ts +34 -0
  190. package/src/renderer/agent-workspace.ts +228 -52
  191. package/src/renderer/autocomplete-overlay.ts +25 -6
  192. package/src/renderer/bookmark-modal.ts +19 -4
  193. package/src/renderer/buffer.ts +4 -2
  194. package/src/renderer/context-inspector.ts +50 -13
  195. package/src/renderer/diff.ts +1 -1
  196. package/src/renderer/file-picker-overlay.ts +19 -6
  197. package/src/renderer/help-overlay.ts +106 -33
  198. package/src/renderer/history-search-overlay.ts +19 -4
  199. package/src/renderer/live-tail-modal.ts +27 -5
  200. package/src/renderer/mcp-workspace.ts +164 -50
  201. package/src/renderer/model-picker-overlay.ts +58 -2
  202. package/src/renderer/model-workspace.ts +104 -20
  203. package/src/renderer/process-modal.ts +27 -6
  204. package/src/renderer/profile-picker-modal.ts +20 -4
  205. package/src/renderer/search-overlay.ts +25 -5
  206. package/src/renderer/selection-modal-overlay.ts +46 -14
  207. package/src/renderer/session-picker-modal.ts +18 -1
  208. package/src/renderer/settings-modal-helpers.ts +2 -40
  209. package/src/renderer/settings-modal.ts +83 -50
  210. package/src/renderer/tool-call.ts +20 -11
  211. package/src/runtime/agent-runtime-events.ts +6 -6
  212. package/src/runtime/bootstrap-command-context.ts +7 -1
  213. package/src/runtime/bootstrap-command-parts.ts +9 -7
  214. package/src/runtime/bootstrap-core.ts +19 -1
  215. package/src/runtime/bootstrap-hook-bridge.ts +7 -18
  216. package/src/runtime/bootstrap-shell.ts +4 -4
  217. package/src/runtime/bootstrap.ts +31 -22
  218. package/src/runtime/connected-host-auth.ts +7 -2
  219. package/src/runtime/diagnostics/panels/index.ts +2 -2
  220. package/src/runtime/diagnostics/panels/policy.ts +7 -7
  221. package/src/runtime/index.ts +2 -1
  222. package/src/runtime/onboarding/apply-file-helpers.ts +66 -0
  223. package/src/runtime/onboarding/apply.ts +80 -79
  224. package/src/runtime/onboarding/derivation.ts +5 -8
  225. package/src/runtime/onboarding/snapshot.ts +0 -15
  226. package/src/runtime/onboarding/types.ts +8 -19
  227. package/src/runtime/onboarding/verify.ts +32 -10
  228. package/src/runtime/operator-token-cleanup.ts +1 -1
  229. package/src/runtime/services.ts +91 -22
  230. package/src/runtime/store/selectors/index.ts +3 -3
  231. package/src/runtime/store/state.ts +1 -4
  232. package/src/runtime/surface-feature-flags.ts +41 -6
  233. package/src/runtime/ui-read-models.ts +3 -4
  234. package/src/runtime/ui-services.ts +6 -6
  235. package/src/shell/blocking-input.ts +2 -1
  236. package/src/shell/ui-openers.ts +3 -13
  237. package/src/tools/agent-analysis-registry-policy.ts +0 -1
  238. package/src/tools/agent-channel-send-tool.ts +133 -0
  239. package/src/tools/agent-context-policy.ts +1 -1
  240. package/src/tools/agent-knowledge-ingest-tool.ts +405 -0
  241. package/src/tools/agent-knowledge-tool.ts +170 -0
  242. package/src/tools/agent-local-registry-tool.ts +269 -69
  243. package/src/tools/agent-media-generate-tool.ts +133 -0
  244. package/src/tools/agent-notify-tool.ts +143 -0
  245. package/src/tools/agent-operator-action-tool.ts +137 -0
  246. package/src/tools/agent-operator-briefing-tool.ts +217 -0
  247. package/src/tools/agent-reminder-schedule-tool.ts +237 -0
  248. package/src/tools/agent-tool-policy-guard.ts +8 -8
  249. package/src/tools/agent-work-plan-tool.ts +256 -0
  250. package/src/version.ts +1 -1
  251. package/src/input/commands/policy-dispatch.ts +0 -339
  252. package/src/input/commands/policy.ts +0 -13
  253. package/src/panels/panel-picker.ts +0 -105
  254. package/src/panels/policy-panel.ts +0 -308
  255. package/src/renderer/panel-picker-overlay.ts +0 -202
@@ -1,38 +1,14 @@
1
1
  import { discoverPersonas, type DiscoveredPersonaRecord } from '../../agent/persona-discovery.ts';
2
2
  import { AgentPersonaRegistry, type AgentPersonaRecord } from '../../agent/persona-registry.ts';
3
+ import { formatAgentRecordOrigin, formatAgentRecordReviewState } from '../../agent/record-labels.ts';
3
4
  import type { CommandContext, CommandRegistry } from '../command-registry.ts';
5
+ import { parseAgentLocalLibraryArgs, type ParsedAgentLocalLibraryArgs } from './agent-local-library-args.ts';
4
6
  import { requireShellPaths } from './runtime-services.ts';
5
7
 
6
- interface ParsedPersonaArgs {
7
- readonly rest: readonly string[];
8
- readonly flags: ReadonlyMap<string, string>;
9
- readonly yes: boolean;
10
- }
8
+ const PERSONA_VALUE_FLAGS = ['name', 'description', 'body', 'tags', 'triggers'] as const;
11
9
 
12
- function parsePersonaArgs(args: readonly string[]): ParsedPersonaArgs {
13
- const flags = new Map<string, string>();
14
- const rest: string[] = [];
15
- let yes = false;
16
- for (let index = 0; index < args.length; index += 1) {
17
- const token = args[index] ?? '';
18
- if (token === '--yes') {
19
- yes = true;
20
- continue;
21
- }
22
- if (token.startsWith('--')) {
23
- const key = token.slice(2);
24
- const next = args[index + 1];
25
- if (next !== undefined && !next.startsWith('--')) {
26
- flags.set(key, next);
27
- index += 1;
28
- } else {
29
- flags.set(key, 'true');
30
- }
31
- continue;
32
- }
33
- rest.push(token);
34
- }
35
- return { rest, flags, yes };
10
+ function parsePersonaArgs(args: readonly string[]): ParsedAgentLocalLibraryArgs {
11
+ return parseAgentLocalLibraryArgs(args, { valueFlags: PERSONA_VALUE_FLAGS });
36
12
  }
37
13
 
38
14
  function splitList(value: string | undefined): readonly string[] {
@@ -44,21 +20,33 @@ function registryFromContext(ctx: CommandContext): AgentPersonaRegistry {
44
20
  return AgentPersonaRegistry.fromShellPaths(requireShellPaths(ctx));
45
21
  }
46
22
 
23
+ function formatPersonaReceipt(title: string, persona: Pick<AgentPersonaRecord, 'id' | 'name'>, extra: readonly string[] = []): string {
24
+ const activeSuffix = extra.some((line) => line.includes('active yes') || line.includes('active: yes')) ? ' (active)' : '';
25
+ const normalizedExtra = extra.map((line) => line.replace(/^ active /, ' active: '));
26
+ return [
27
+ `${title} ${persona.id}: ${persona.name}${activeSuffix}`,
28
+ ` id ${persona.id}`,
29
+ ` name ${persona.name}`,
30
+ ...normalizedExtra,
31
+ ].join('\n');
32
+ }
33
+
47
34
  function summarizePersona(persona: AgentPersonaRecord, activePersonaId: string | null): string {
48
35
  const active = persona.id === activePersonaId ? 'active' : 'inactive';
49
- const tags = persona.tags.length > 0 ? ` tags=${persona.tags.join(',')}` : '';
50
- return ` ${persona.id} ${active} ${persona.reviewState} ${persona.name} - ${persona.description}${tags}`;
36
+ const review = formatAgentRecordReviewState(persona.reviewState);
37
+ const tags = persona.tags.length > 0 ? ` tags ${persona.tags.join(', ')}` : '';
38
+ return ` ${persona.id} ${active} ${review} ${persona.name} - ${persona.description}${tags}`;
51
39
  }
52
40
 
53
41
  function renderList(title: string, registry: AgentPersonaRegistry, personas: readonly AgentPersonaRecord[]): string {
54
42
  const snapshot = registry.snapshot();
55
43
  if (personas.length === 0) {
56
- return `${title}\n No local Agent personas yet. Create one with /personas create --name <name> --description <summary> --body <instructions>.`;
44
+ return `${title}\n No local Agent personas yet.\n No Agent-local personas yet. Create one with /personas create --name <name> --description <summary> --body <instructions>.`;
57
45
  }
58
46
  return [
59
47
  `${title} (${personas.length})`,
60
- ` store: ${snapshot.path}`,
61
- ` active: ${snapshot.activePersona?.name ?? '(none)'}`,
48
+ ` store ${snapshot.path}`,
49
+ ` active ${snapshot.activePersona?.name ?? '(none)'}`,
62
50
  ...personas.map((persona) => summarizePersona(persona, snapshot.activePersonaId)),
63
51
  ].join('\n');
64
52
  }
@@ -66,11 +54,10 @@ function renderList(title: string, registry: AgentPersonaRegistry, personas: rea
66
54
  function renderPersona(persona: AgentPersonaRecord, activePersonaId: string | null): string {
67
55
  return [
68
56
  `Persona ${persona.name}`,
69
- ` id: ${persona.id}`,
57
+ ` id ${persona.id}`,
70
58
  ` active: ${persona.id === activePersonaId ? 'yes' : 'no'}`,
71
- ` review: ${persona.reviewState}`,
72
- ` source: ${persona.source}`,
73
- ` provenance: ${persona.provenance}`,
59
+ ` review: ${formatAgentRecordReviewState(persona.reviewState)}`,
60
+ ` origin: ${formatAgentRecordOrigin(persona.source, persona.provenance)}`,
74
61
  ` tags: ${persona.tags.join(', ') || '(none)'}`,
75
62
  ` triggers: ${persona.triggers.join(', ') || '(none)'}`,
76
63
  ` created: ${persona.createdAt}`,
@@ -85,7 +72,7 @@ function renderPersona(persona: AgentPersonaRecord, activePersonaId: string | nu
85
72
 
86
73
  function summarizeDiscoveredPersona(persona: DiscoveredPersonaRecord): string {
87
74
  const description = persona.description ? ` - ${persona.description}` : '';
88
- return ` ${persona.name} ${persona.origin}${description}\n path: ${persona.path}`;
75
+ return ` ${persona.name} ${persona.origin}${description}\n path ${persona.path}`;
89
76
  }
90
77
 
91
78
  function renderDiscoveredPersonas(personas: readonly DiscoveredPersonaRecord[]): string {
@@ -131,18 +118,18 @@ async function importDiscoveredPersona(args: readonly string[], ctx: CommandCont
131
118
  }
132
119
  const discovered = findDiscoveredPersona(await discoverPersonas(requireShellPaths(ctx)), name);
133
120
  if (!discovered) {
134
- ctx.print(`Unknown discovered Agent persona: ${name}\nRun /personas discover to inspect available persona files.`);
121
+ ctx.print(`Unknown discovered Agent persona ${name}\nRun /personas discover to inspect available persona files.`);
135
122
  return;
136
123
  }
137
124
  if (!parsed.yes) {
138
125
  ctx.print([
139
126
  'Agent persona import preview',
140
- ` name: ${discovered.name}`,
141
- ` origin: ${discovered.origin}`,
142
- ` path: ${discovered.path}`,
143
- ` description: ${discovered.description || '(none)'}`,
144
- ` body characters: ${discovered.body.length}`,
145
- ' next: rerun with --yes to import into the Agent-local persona registry',
127
+ ` name ${discovered.name}`,
128
+ ` origin ${discovered.origin}`,
129
+ ` path ${discovered.path}`,
130
+ ` description ${discovered.description || '(none)'}`,
131
+ ` body characters ${discovered.body.length}`,
132
+ ' next rerun with --yes to import into the Agent-local persona registry',
146
133
  ].join('\n'));
147
134
  return;
148
135
  }
@@ -153,10 +140,10 @@ async function importDiscoveredPersona(args: readonly string[], ctx: CommandCont
153
140
  tags: frontmatterList(discovered, 'tags'),
154
141
  triggers: frontmatterList(discovered, 'triggers'),
155
142
  source: 'imported',
156
- provenance: `discovered:${discovered.origin}:${discovered.path}`,
143
+ provenance: `Imported file (${discovered.origin}): ${discovered.path}`,
157
144
  });
158
145
  if (parsed.flags.get('use') === 'true') personaRegistry.setActive(persona.id);
159
- ctx.print(`Imported Agent persona ${persona.id}: ${persona.name}${parsed.flags.get('use') === 'true' ? ' (active)' : ''}`);
146
+ ctx.print(formatPersonaReceipt('Imported Agent persona', persona, [` active ${parsed.flags.get('use') === 'true' ? 'yes' : 'no'}`]));
160
147
  }
161
148
 
162
149
  function requiredFlag(flags: ReadonlyMap<string, string>, key: string): string {
@@ -166,14 +153,17 @@ function requiredFlag(flags: ReadonlyMap<string, string>, key: string): string {
166
153
  }
167
154
 
168
155
  function printError(ctx: CommandContext, error: unknown): void {
169
- ctx.print(`Error: ${error instanceof Error ? error.message : String(error)}`);
156
+ ctx.print([
157
+ 'Error',
158
+ ` message ${error instanceof Error ? error.message : String(error)}`,
159
+ ].join('\n'));
170
160
  }
171
161
 
172
162
  export function registerPersonasRuntimeCommands(registry: CommandRegistry): void {
173
163
  registry.register({
174
164
  name: 'personas',
175
165
  aliases: ['persona'],
176
- description: 'Manage local GoodVibes Agent personas',
166
+ description: 'Manage Agent-local personas',
177
167
  usage: '[list|discover|import-discovered <name> --yes|search <query>|show <id>|create --name <name> --description <summary> --body <instructions>|update <id> [--name ...] [--description ...] [--body ...]|use <id>|active|clear|review <id>|stale <id> <reason...>|delete <id> --yes]',
178
168
  async handler(args, ctx) {
179
169
  const sub = (args[0] ?? 'list').toLowerCase();
@@ -204,7 +194,7 @@ export function registerPersonasRuntimeCommands(registry: CommandRegistry): void
204
194
  }
205
195
  const snapshot = registryStore.snapshot();
206
196
  const persona = registryStore.get(id);
207
- ctx.print(persona ? renderPersona(persona, snapshot.activePersonaId) : `Unknown persona: ${id}`);
197
+ ctx.print(persona ? renderPersona(persona, snapshot.activePersonaId) : `Unknown Agent persona ${id}`);
208
198
  return;
209
199
  }
210
200
  if (sub === 'create') {
@@ -217,9 +207,9 @@ export function registerPersonasRuntimeCommands(registry: CommandRegistry): void
217
207
  tags: splitList(parsed.flags.get('tags')),
218
208
  triggers: splitList(parsed.flags.get('triggers')),
219
209
  source: 'user',
220
- provenance: 'slash-command',
210
+ provenance: 'Command',
221
211
  });
222
- ctx.print(`Created Agent persona ${persona.id}: ${persona.name}`);
212
+ ctx.print(formatPersonaReceipt('Created Agent persona', persona));
223
213
  return;
224
214
  }
225
215
  if (sub === 'update') {
@@ -235,9 +225,9 @@ export function registerPersonasRuntimeCommands(registry: CommandRegistry): void
235
225
  body: parsed.flags.get('body'),
236
226
  tags: parsed.flags.has('tags') ? splitList(parsed.flags.get('tags')) : undefined,
237
227
  triggers: parsed.flags.has('triggers') ? splitList(parsed.flags.get('triggers')) : undefined,
238
- provenance: 'slash-command',
228
+ provenance: 'Command',
239
229
  });
240
- ctx.print(`Updated Agent persona ${updated.id}: ${updated.name}`);
230
+ ctx.print(formatPersonaReceipt('Updated Agent persona', updated));
241
231
  return;
242
232
  }
243
233
  if (sub === 'use') {
@@ -247,7 +237,7 @@ export function registerPersonasRuntimeCommands(registry: CommandRegistry): void
247
237
  return;
248
238
  }
249
239
  const persona = registryStore.setActive(id);
250
- ctx.print(`Active Agent persona: ${persona.name} (${persona.id})`);
240
+ ctx.print(`Active Agent persona: ${persona.name}\n${formatPersonaReceipt('Active Agent persona', persona)}`);
251
241
  return;
252
242
  }
253
243
  if (sub === 'active') {
@@ -267,7 +257,7 @@ export function registerPersonasRuntimeCommands(registry: CommandRegistry): void
267
257
  return;
268
258
  }
269
259
  const persona = registryStore.markReviewed(id);
270
- ctx.print(`Reviewed Agent persona ${persona.id}.`);
260
+ ctx.print(formatPersonaReceipt('Reviewed Agent persona', persona));
271
261
  return;
272
262
  }
273
263
  if (sub === 'stale') {
@@ -277,7 +267,7 @@ export function registerPersonasRuntimeCommands(registry: CommandRegistry): void
277
267
  return;
278
268
  }
279
269
  const persona = registryStore.markStale(id, args.slice(2).join(' '));
280
- ctx.print(`Marked Agent persona ${persona.id} stale.`);
270
+ ctx.print(formatPersonaReceipt('Marked Agent persona stale', persona));
281
271
  return;
282
272
  }
283
273
  if (sub === 'delete' || sub === 'remove') {
@@ -292,7 +282,7 @@ export function registerPersonasRuntimeCommands(registry: CommandRegistry): void
292
282
  return;
293
283
  }
294
284
  const removed = registryStore.deletePersona(id);
295
- ctx.print(`Deleted Agent persona ${removed.id}: ${removed.name}`);
285
+ ctx.print(formatPersonaReceipt('Deleted Agent persona', removed));
296
286
  return;
297
287
  }
298
288
  ctx.print('Usage: /personas [list|discover|import-discovered|search <query>|show <id>|create|update|use|active|clear|review|stale|delete]');
@@ -111,7 +111,7 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
111
111
  }
112
112
 
113
113
  if (args[0] === 'panel') {
114
- ctx.print('Planning panels are not part of the Agent workspace. Use /plan status or /plan list.');
114
+ ctx.print('Use /plan status or /plan list for compact command output. Open Agent Workspace -> Work -> Planning status or Saved plans for the workspace view.');
115
115
  return;
116
116
  }
117
117
 
@@ -3,6 +3,7 @@ import { dirname } from 'node:path';
3
3
  import type { CommandRegistry } from '../command-registry.ts';
4
4
  import { listBuiltinSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config';
5
5
  import { buildAuthInspectionSnapshot, inspectProviderAuth } from '@/runtime/index.ts';
6
+ import { formatAgentRecordSource } from '../../agent/record-labels.ts';
6
7
  import { requireSecretsManager, requireServiceRegistry, requireShellPaths, requireSubscriptionManager } from './runtime-services.ts';
7
8
  import { requireYesFlag, stripYesFlag } from './confirmation.ts';
8
9
 
@@ -18,11 +19,11 @@ interface AuthReviewBundle {
18
19
  function inspectAuthBundle(bundle: AuthReviewBundle): string {
19
20
  return [
20
21
  'Auth Review Bundle',
21
- ` exportedAt: ${new Date(bundle.exportedAt).toISOString()}`,
22
- ` externalRuntimeAuth: ${bundle.externalRuntimeAuth}`,
23
- ` stored secrets: ${bundle.secretKeys.length}`,
24
- ` active subscriptions: ${bundle.activeSubscriptions.length}`,
25
- ` pending subscriptions: ${bundle.pendingSubscriptions.length}`,
22
+ ` exported at ${new Date(bundle.exportedAt).toISOString()}`,
23
+ ` connected host auth ${bundle.externalRuntimeAuth}`,
24
+ ` stored secrets ${bundle.secretKeys.length}`,
25
+ ` active subscriptions ${bundle.activeSubscriptions.length}`,
26
+ ` pending subscriptions ${bundle.pendingSubscriptions.length}`,
26
27
  ].join('\n');
27
28
  }
28
29
 
@@ -66,12 +67,12 @@ export function registerPlatformAccessRuntimeCommands(registry: CommandRegistry)
66
67
  const builtinProviders = listBuiltinSubscriptionProviders().map((entry) => entry.provider);
67
68
  ctx.print([
68
69
  'Auth Review',
69
- ' connected-host auth: managed outside goodvibes-agent',
70
- ` stored secrets: ${snapshot.secretKeyCount}`,
71
- ` built-in providers: ${builtinProviders.length}${builtinProviders.length > 0 ? ` (${builtinProviders.join(', ')})` : ''}`,
72
- ` active subscriptions: ${snapshot.activeSubscriptions}${snapshot.activeSubscriptions > 0 ? ` (${snapshot.providers.filter((provider) => provider.activeSubscription).map((provider) => provider.provider).join(', ')})` : ''}`,
73
- ` pending subscriptions: ${snapshot.pendingSubscriptions}${snapshot.pendingSubscriptions > 0 ? ` (${snapshot.providers.filter((provider) => provider.pendingLogin).map((provider) => provider.provider).join(', ')})` : ''}`,
74
- ...snapshot.providers.map((provider) => ` ${provider.provider} freshness=${provider.freshness} mode=${provider.callbackMode} configured=${provider.configured ? 'yes' : 'no'}`),
70
+ ' connected host auth managed outside goodvibes-agent',
71
+ ` stored secrets ${snapshot.secretKeyCount}`,
72
+ ` built-in providers ${builtinProviders.length}${builtinProviders.length > 0 ? ` (${builtinProviders.join(', ')})` : ''}`,
73
+ ` active subscriptions ${snapshot.activeSubscriptions}${snapshot.activeSubscriptions > 0 ? ` (${snapshot.providers.filter((provider) => provider.activeSubscription).map((provider) => provider.provider).join(', ')})` : ''}`,
74
+ ` pending subscriptions ${snapshot.pendingSubscriptions}${snapshot.pendingSubscriptions > 0 ? ` (${snapshot.providers.filter((provider) => provider.pendingLogin).map((provider) => provider.provider).join(', ')})` : ''}`,
75
+ ...snapshot.providers.map((provider) => ` ${provider.provider} auth ${provider.freshness} finish manual configured ${provider.configured ? 'yes' : 'no'}`),
75
76
  ].join('\n'));
76
77
  return;
77
78
  }
@@ -89,19 +90,18 @@ export function registerPlatformAccessRuntimeCommands(registry: CommandRegistry)
89
90
  });
90
91
  ctx.print([
91
92
  `Auth Provider ${provider}`,
92
- ` configured: ${inspection.configured ? 'yes' : 'no'}`,
93
- ...(inspection.source ? [` source: ${inspection.source}`] : []),
94
- ` freshness: ${inspection.freshness}`,
95
- ` callbackMode: ${inspection.callbackMode}`,
96
- ...(inspection.redirectUri ? [` redirectUri: ${inspection.redirectUri}`] : []),
97
- ...(inspection.localCallback ? [` localCallback: ${inspection.localCallback}`] : []),
98
- ` activeSubscription: ${inspection.activeSubscription ? 'yes' : 'no'}`,
99
- ` pendingLogin: ${inspection.pendingLogin ? 'yes' : 'no'}`,
100
- ` overrideAmbientApiKeys: ${inspection.overrideAmbientApiKeys ? 'yes' : 'no'}`,
101
- ...(inspection.tokenType ? [` tokenType: ${inspection.tokenType}`] : []),
102
- ...(inspection.expiresAt ? [` expiresAt: ${new Date(inspection.expiresAt).toISOString()}`] : []),
103
- ...inspection.issues.map((issue) => ` issue: ${issue}`),
104
- ...inspection.nextActions.map((action) => ` next: ${action}`),
93
+ ` configured ${inspection.configured ? 'yes' : 'no'}`,
94
+ ...(inspection.source ? [` origin ${formatAgentRecordSource(inspection.source)}`] : []),
95
+ ` freshness ${inspection.freshness}`,
96
+ ' finish mode explicit manual',
97
+ ...(inspection.redirectUri ? [` redirect URI ${inspection.redirectUri}`] : []),
98
+ ` active subscription ${inspection.activeSubscription ? 'yes' : 'no'}`,
99
+ ` pending login ${inspection.pendingLogin ? 'yes' : 'no'}`,
100
+ ` ambient API key override ${inspection.overrideAmbientApiKeys ? 'yes' : 'no'}`,
101
+ ...(inspection.tokenType ? [` token type ${inspection.tokenType}`] : []),
102
+ ...(inspection.expiresAt ? [` expires at ${new Date(inspection.expiresAt).toISOString()}`] : []),
103
+ ...inspection.issues.map((issue) => ` issue ${issue}`),
104
+ ...inspection.nextActions.map((action) => ` next ${action}`),
105
105
  ].join('\n'));
106
106
  return;
107
107
  }
@@ -119,12 +119,12 @@ export function registerPlatformAccessRuntimeCommands(registry: CommandRegistry)
119
119
  });
120
120
  ctx.print([
121
121
  `Auth Repair ${provider}`,
122
- ` configured: ${inspection.configured ? 'yes' : 'no'}`,
123
- ` freshness: ${inspection.freshness}`,
124
- ` callbackMode: ${inspection.callbackMode}`,
125
- ...inspection.issues.map((issue) => ` issue: ${issue}`),
122
+ ` configured ${inspection.configured ? 'yes' : 'no'}`,
123
+ ` freshness ${inspection.freshness}`,
124
+ ' finish mode explicit manual',
125
+ ...inspection.issues.map((issue) => ` issue ${issue}`),
126
126
  ...(inspection.nextActions.length > 0
127
- ? [' next:', ...inspection.nextActions.map((action) => ` ${action}`)]
127
+ ? [' next', ...inspection.nextActions.map((action) => ` ${action}`)]
128
128
  : [' No active repair actions suggested.']),
129
129
  ].join('\n'));
130
130
  return;
@@ -2,13 +2,34 @@ import type { CommandContext, CommandRegistry } from '../command-registry.ts';
2
2
  import type {
3
3
  ProviderAccountRecord,
4
4
  ProviderAccountSnapshot,
5
- } from '@/runtime/index.ts';
5
+ ProviderAuthRoute,
6
+ ProviderRouteRecord,
7
+ } from '../../panels/provider-account-snapshot.ts';
8
+ import { buildProviderAccountSnapshot } from '../../panels/provider-account-snapshot.ts';
6
9
  import {
7
- requireOperatorClient,
10
+ requireProvider,
11
+ requireServiceRegistry,
12
+ requireSubscriptionManager,
8
13
  } from './runtime-services.ts';
14
+ import { formatProviderAuthRouteId } from '../../provider-auth-route-display.ts';
15
+
16
+ function formatRouteList(routes: readonly ProviderAuthRoute[]): string {
17
+ return routes.map((route) => formatProviderAuthRouteId(route)).join(', ');
18
+ }
19
+
20
+ function formatRouteRecord(route: ProviderRouteRecord): string {
21
+ return `${formatProviderAuthRouteId(route.route)} ${route.usable ? 'usable' : 'blocked'} auth ${route.freshness} ${route.detail}`;
22
+ }
9
23
 
10
24
  async function loadProviderAccountSnapshot(context: CommandContext): Promise<ProviderAccountSnapshot> {
11
- return await requireOperatorClient(context).providers.accountSnapshot();
25
+ return await buildProviderAccountSnapshot({
26
+ providerModels: requireProvider(context).providerRegistry,
27
+ services: requireServiceRegistry(context),
28
+ subscriptions: requireSubscriptionManager(context),
29
+ environment: {
30
+ hasEnvironmentVariable: (name: string) => Boolean(process.env[name]),
31
+ },
32
+ });
12
33
  }
13
34
 
14
35
  function findProviderAccountRecord(
@@ -28,7 +49,7 @@ export function registerProviderAccountsRuntimeCommands(registry: CommandRegistr
28
49
  async handler(args, ctx) {
29
50
  const sub = (args[0] ?? 'review').toLowerCase();
30
51
  if (sub === 'panel' || sub === 'open') {
31
- ctx.print('Provider account panels are not part of the Agent workspace. Use /accounts review.');
52
+ ctx.print('Open Agent Workspace -> Setup -> Provider accounts for the workspace view, or run /accounts review for compact command output.');
32
53
  return;
33
54
  }
34
55
  const snapshot = await loadProviderAccountSnapshot(ctx);
@@ -36,16 +57,16 @@ export function registerProviderAccountsRuntimeCommands(registry: CommandRegistr
36
57
  const providerId = args[1];
37
58
  const record = findProviderAccountRecord(snapshot, providerId);
38
59
  if (!record) {
39
- ctx.print(providerId ? `Unknown provider account: ${providerId}` : 'Usage: /accounts routes <provider>');
60
+ ctx.print(providerId ? `Unknown provider account ${providerId}` : 'Usage: /accounts routes <provider>');
40
61
  return;
41
62
  }
42
63
  ctx.print([
43
64
  `Provider Routes ${record.providerId}`,
44
- ` preferred: ${record.preferredRoute}`,
45
- ` active: ${record.activeRoute}`,
46
- ` reason: ${record.activeRouteReason}`,
47
- ...record.routeRecords.map((route) => ` ${route.route} usable=${route.usable ? 'yes' : 'no'} freshness=${route.freshness} ${route.detail}`),
48
- ...record.routeRecords.flatMap((route) => route.issues.map((issue) => ` issue: ${issue}`)),
65
+ ` preferred route ${formatProviderAuthRouteId(record.preferredRoute)}`,
66
+ ` active route ${formatProviderAuthRouteId(record.activeRoute)}`,
67
+ ` reason ${record.activeRouteReason}`,
68
+ ...record.routeRecords.map((route) => ` ${formatRouteRecord(route)}`),
69
+ ...record.routeRecords.flatMap((route) => route.issues.map((issue) => ` issue ${issue}`)),
49
70
  ].join('\n'));
50
71
  return;
51
72
  }
@@ -53,17 +74,17 @@ export function registerProviderAccountsRuntimeCommands(registry: CommandRegistr
53
74
  const providerId = args[1];
54
75
  const record = findProviderAccountRecord(snapshot, providerId);
55
76
  if (!record) {
56
- ctx.print(providerId ? `Unknown provider account: ${providerId}` : 'Usage: /accounts repair <provider>');
77
+ ctx.print(providerId ? `Unknown provider account ${providerId}` : 'Usage: /accounts repair <provider>');
57
78
  return;
58
79
  }
59
80
  ctx.print([
60
81
  `Provider Account Repair ${record.providerId}`,
61
- ` active route: ${record.activeRoute}`,
62
- ` preferred route: ${record.preferredRoute}`,
63
- ...(record.fallbackRisk ? [` fallback: ${record.fallbackRisk}`] : []),
64
- ...(record.issues.map((issue) => ` issue: ${issue}`)),
82
+ ` active route ${formatProviderAuthRouteId(record.activeRoute)}`,
83
+ ` preferred route ${formatProviderAuthRouteId(record.preferredRoute)}`,
84
+ ...(record.fallbackRisk ? [` routing risk ${record.fallbackRisk}`] : []),
85
+ ...(record.issues.map((issue) => ` issue ${issue}`)),
65
86
  ...(record.recommendedActions.length > 0
66
- ? [' next:', ...record.recommendedActions.map((action) => ` ${action}`)]
87
+ ? [' next', ...record.recommendedActions.map((action) => ` ${action}`)]
67
88
  : [' No active repair actions suggested.']),
68
89
  ].join('\n'));
69
90
  return;
@@ -72,39 +93,39 @@ export function registerProviderAccountsRuntimeCommands(registry: CommandRegistr
72
93
  const providerId = args[1];
73
94
  const record = findProviderAccountRecord(snapshot, providerId);
74
95
  if (!record) {
75
- ctx.print(providerId ? `Unknown provider account: ${providerId}` : 'Usage: /accounts show <provider>');
96
+ ctx.print(providerId ? `Unknown provider account ${providerId}` : 'Usage: /accounts show <provider>');
76
97
  return;
77
98
  }
78
99
  ctx.print([
79
100
  `Provider Account ${record.providerId}`,
80
- ` preferredRoute: ${record.preferredRoute}`,
81
- ` activeRoute: ${record.activeRoute}`,
82
- ` authFreshness: ${record.authFreshness}`,
83
- ` configured: ${record.configured ? 'yes' : 'no'}`,
84
- ` oauthReady: ${record.oauthReady ? 'yes' : 'no'}`,
85
- ` pendingLogin: ${record.pendingLogin ? 'yes' : 'no'}`,
86
- ` availableRoutes: ${record.availableRoutes.join(', ')}`,
87
- ` modelCount: ${record.modelCount}`,
88
- ` routeReason: ${record.activeRouteReason}`,
89
- ...(record.fallbackRoute ? [` fallbackRoute: ${record.fallbackRoute}`] : []),
90
- ...(record.fallbackRisk ? [` fallbackRisk: ${record.fallbackRisk}`] : []),
91
- ...(record.expiresAt ? [` expiresAt: ${new Date(record.expiresAt).toISOString()}`] : []),
92
- ...record.routeRecords.map((route) => ` route ${route.route}: usable=${route.usable ? 'yes' : 'no'} freshness=${route.freshness} — ${route.detail}`),
93
- ...record.routeRecords.flatMap((route) => route.issues.map((issue) => ` issue: ${issue}`)),
94
- ...record.usageWindows.map((entry) => ` window: ${entry.label} — ${entry.detail}`),
95
- ...record.issues.map((issue) => ` issue: ${issue}`),
96
- ...record.notes.map((note) => ` note: ${note}`),
97
- ...record.recommendedActions.map((action) => ` next: ${action}`),
101
+ ` preferred route ${formatProviderAuthRouteId(record.preferredRoute)}`,
102
+ ` active route ${formatProviderAuthRouteId(record.activeRoute)}`,
103
+ ` auth freshness ${record.authFreshness}`,
104
+ ` configured ${record.configured ? 'yes' : 'no'}`,
105
+ ` OAuth ready ${record.oauthReady ? 'yes' : 'no'}`,
106
+ ` pending login ${record.pendingLogin ? 'yes' : 'no'}`,
107
+ ` available routes ${formatRouteList(record.availableRoutes)}`,
108
+ ` model count ${record.modelCount}`,
109
+ ` route reason ${record.activeRouteReason}`,
110
+ ...(record.fallbackRoute ? [` fallback route ${formatProviderAuthRouteId(record.fallbackRoute)}`] : []),
111
+ ...(record.fallbackRisk ? [` routing risk ${record.fallbackRisk}`] : []),
112
+ ...(record.expiresAt ? [` expires at ${new Date(record.expiresAt).toISOString()}`] : []),
113
+ ...record.routeRecords.map((route) => ` route ${formatRouteRecord(route)}`),
114
+ ...record.routeRecords.flatMap((route) => route.issues.map((issue) => ` issue ${issue}`)),
115
+ ...record.usageWindows.map((entry) => ` window ${entry.label} — ${entry.detail}`),
116
+ ...record.issues.map((issue) => ` issue ${issue}`),
117
+ ...record.notes.map((note) => ` note ${note}`),
118
+ ...record.recommendedActions.map((action) => ` next ${action}`),
98
119
  ].join('\n'));
99
120
  return;
100
121
  }
101
122
  ctx.print([
102
123
  'Provider Account Review',
103
- ` providers: ${snapshot.providers.length}`,
104
- ` configured: ${snapshot.configuredCount}`,
105
- ` issues: ${snapshot.issueCount}`,
124
+ ` providers ${snapshot.providers.length}`,
125
+ ` configured ${snapshot.configuredCount}`,
126
+ ` issues ${snapshot.issueCount}`,
106
127
  ...snapshot.providers.map((record) => (
107
- ` ${record.providerId} active=${record.activeRoute} preferred=${record.preferredRoute} freshness=${record.authFreshness} models=${record.modelCount} issues=${record.issues.length}`
128
+ ` ${record.providerId} active route ${formatProviderAuthRouteId(record.activeRoute)} preferred route ${formatProviderAuthRouteId(record.preferredRoute)} auth ${record.authFreshness} models ${record.modelCount} issues ${record.issues.length}`
108
129
  )),
109
130
  ].join('\n'));
110
131
  },
@@ -2,14 +2,14 @@ import type { CommandRegistry } from '../command-registry.ts';
2
2
  import { networkInterfaces } from 'node:os';
3
3
  import {
4
4
  buildCompanionConnectionInfo,
5
+ type CompanionConnectionInfo,
5
6
  encodeConnectionPayload,
6
- formatConnectionBlock,
7
7
  generateQrMatrix,
8
8
  renderQrToString,
9
9
  } from '@pellux/goodvibes-sdk/platform/pairing';
10
10
  import { GOODVIBES_AGENT_PAIRING_SURFACE } from '../../config/surface.ts';
11
11
  import { resolveRuntimeEndpointBinding } from '../../cli/endpoints.ts';
12
- import { connectedHostTokenRequiredMessage, readConnectedHostOperatorToken } from '../../runtime/connected-host-auth.ts';
12
+ import { connectedHostOperatorTokenFingerprint, connectedHostTokenRequiredMessage, readConnectedHostOperatorToken } from '../../runtime/connected-host-auth.ts';
13
13
  import { requirePlatform, requireShellPaths } from './runtime-services.ts';
14
14
 
15
15
  function getLocalNetworkIp(): string {
@@ -31,13 +31,42 @@ function urlHostForBindHost(host: string): string {
31
31
  return host || '127.0.0.1';
32
32
  }
33
33
 
34
+ function formatTokenLine(token: string, showToken: boolean): string {
35
+ if (showToken) return `Token: ${token}`;
36
+ return `Token: present sha256:${connectedHostOperatorTokenFingerprint(token)} (hidden in text; QR contains pairing payload)`;
37
+ }
38
+
39
+ function formatAgentPairingBlock(info: CompanionConnectionInfo, qr: string, showToken: boolean): string {
40
+ return [
41
+ `GoodVibes Agent companion pairing v${info.version}`,
42
+ '━━━━━━━━━━━━━━━━━━━━━━━━',
43
+ `Connected host: ${info.url}`,
44
+ `Surface: ${info.surface}`,
45
+ `User: ${info.username}`,
46
+ formatTokenLine(info.token, showToken),
47
+ '',
48
+ 'Scan to connect:',
49
+ '',
50
+ qr,
51
+ '',
52
+ showToken
53
+ ? 'Manual token display was explicitly confirmed for this command.'
54
+ : 'For manual setup, rerun /pair --show-token --yes to print the token once.',
55
+ 'Agent is waiting for the companion app to connect to the owning GoodVibes host.',
56
+ ].join('\n');
57
+ }
58
+
59
+ function shouldShowToken(args: readonly string[]): boolean {
60
+ return args.includes('--show-token') || args.includes('--manual-token');
61
+ }
62
+
34
63
  export function registerQrcodeRuntimeCommands(registry: CommandRegistry): void {
35
64
  registry.register({
36
65
  name: 'qrcode',
37
66
  aliases: ['qr', 'pair'],
38
67
  description: 'Print companion pairing details and a QR code',
39
68
  usage: '',
40
- handler(_args, ctx) {
69
+ handler(args, ctx) {
41
70
  const shellPaths = requireShellPaths(ctx);
42
71
  const configManager = requirePlatform(ctx).configManager;
43
72
  const tokenRecord = readConnectedHostOperatorToken(shellPaths.homeDirectory);
@@ -46,16 +75,26 @@ export function registerQrcodeRuntimeCommands(registry: CommandRegistry): void {
46
75
  return;
47
76
  }
48
77
  const binding = resolveRuntimeEndpointBinding(configManager, 'controlPlane');
49
- const daemonUrl = `http://${urlHostForBindHost(binding.host)}:${binding.port}`;
78
+ const connectedHostUrl = `http://${urlHostForBindHost(binding.host)}:${binding.port}`;
50
79
  const info = buildCompanionConnectionInfo({
51
- daemonUrl,
80
+ daemonUrl: connectedHostUrl,
52
81
  token: tokenRecord.token,
53
82
  username: 'admin',
54
83
  surface: GOODVIBES_AGENT_PAIRING_SURFACE,
55
84
  });
56
85
  const payload = encodeConnectionPayload(info);
57
86
  const qr = renderQrToString(generateQrMatrix(payload));
58
- ctx.print([formatConnectionBlock(info, payload), '', qr].join('\n'));
87
+ const showToken = shouldShowToken(args);
88
+ if (showToken && !args.includes('--yes')) {
89
+ ctx.print([
90
+ 'Manual companion token display requires confirmation.',
91
+ ' rerun: /pair --show-token --yes',
92
+ ` token fingerprint: sha256:${connectedHostOperatorTokenFingerprint(tokenRecord.token)}`,
93
+ ' QR pairing remains available without printing the raw token.',
94
+ ].join('\n'));
95
+ return;
96
+ }
97
+ ctx.print(formatAgentPairingBlock(info, qr, showToken));
59
98
  },
60
99
  });
61
100
  }