@pellux/goodvibes-agent 0.1.117 → 1.0.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 (253) hide show
  1. package/CHANGELOG.md +13 -4
  2. package/README.md +35 -14
  3. package/bin/goodvibes-agent.ts +16 -2
  4. package/dist/package/main.js +176073 -170980
  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 +134 -68
  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 +44 -36
  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-learned-behavior.ts +2 -2
  66. package/src/input/agent-workspace-library-command-editor-submission.ts +1 -1
  67. package/src/input/agent-workspace-library-command-editors.ts +2 -2
  68. package/src/input/agent-workspace-local-operations.ts +218 -0
  69. package/src/input/agent-workspace-local-selection.ts +75 -0
  70. package/src/input/agent-workspace-media-command-editor-submission.ts +62 -0
  71. package/src/input/agent-workspace-media-command-editors.ts +27 -0
  72. package/src/input/agent-workspace-memory-command-editors.ts +1 -1
  73. package/src/input/agent-workspace-memory-editor.ts +2 -2
  74. package/src/input/agent-workspace-navigation.ts +38 -2
  75. package/src/input/agent-workspace-notify-editor-submission.ts +16 -2
  76. package/src/input/agent-workspace-operations-command-editor-submission.ts +63 -1
  77. package/src/input/agent-workspace-operations-command-editors.ts +80 -3
  78. package/src/input/agent-workspace-panel-route.ts +43 -0
  79. package/src/input/agent-workspace-reminder-schedule-editor.ts +1 -1
  80. package/src/input/agent-workspace-routine-schedule-editor.ts +2 -2
  81. package/src/input/agent-workspace-search.ts +169 -0
  82. package/src/input/agent-workspace-setup.ts +22 -11
  83. package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +6 -6
  84. package/src/input/agent-workspace-skill-bundle-command-editors.ts +10 -10
  85. package/src/input/agent-workspace-snapshot.ts +112 -13
  86. package/src/input/agent-workspace-task-command-editors.ts +4 -4
  87. package/src/input/agent-workspace-token.ts +18 -2
  88. package/src/input/agent-workspace-types.ts +92 -4
  89. package/src/input/agent-workspace-voice-media.ts +3 -6
  90. package/src/input/agent-workspace-web-research-editor.ts +104 -0
  91. package/src/input/agent-workspace.ts +136 -208
  92. package/src/input/command-registry.ts +4 -1
  93. package/src/input/commands/agent-runtime-profile-runtime.ts +45 -41
  94. package/src/input/commands/agent-skills-runtime.ts +83 -70
  95. package/src/input/commands/agent-workspace-runtime.ts +23 -7
  96. package/src/input/commands/brief-runtime.ts +25 -24
  97. package/src/input/commands/channels-runtime.ts +145 -31
  98. package/src/input/commands/delegation-runtime.ts +29 -23
  99. package/src/input/commands/experience-runtime.ts +6 -4
  100. package/src/input/commands/guidance-runtime.ts +4 -4
  101. package/src/input/commands/health-runtime.ts +140 -115
  102. package/src/input/commands/knowledge.ts +57 -56
  103. package/src/input/commands/local-provider-runtime.ts +36 -18
  104. package/src/input/commands/local-runtime.ts +138 -16
  105. package/src/input/commands/local-setup-review.ts +7 -7
  106. package/src/input/commands/mcp-runtime.ts +56 -35
  107. package/src/input/commands/notify-runtime.ts +38 -9
  108. package/src/input/commands/operator-actions-runtime.ts +138 -0
  109. package/src/input/commands/operator-runtime.ts +6 -17
  110. package/src/input/commands/personas-runtime.ts +45 -30
  111. package/src/input/commands/planning-runtime.ts +1 -1
  112. package/src/input/commands/platform-access-runtime.ts +29 -29
  113. package/src/input/commands/provider-accounts-runtime.ts +60 -39
  114. package/src/input/commands/qrcode-runtime.ts +45 -6
  115. package/src/input/commands/recall-bundle.ts +11 -11
  116. package/src/input/commands/recall-capture.ts +13 -11
  117. package/src/input/commands/recall-query.ts +29 -21
  118. package/src/input/commands/recall-review.ts +2 -1
  119. package/src/input/commands/routines-runtime.ts +59 -43
  120. package/src/input/commands/schedule-runtime.ts +33 -20
  121. package/src/input/commands/security-runtime.ts +4 -4
  122. package/src/input/commands/session-content.ts +80 -78
  123. package/src/input/commands/session-workflow.ts +132 -93
  124. package/src/input/commands/session.ts +3 -174
  125. package/src/input/commands/shell-core.ts +32 -17
  126. package/src/input/commands/subscription-runtime.ts +53 -179
  127. package/src/input/commands/tasks-runtime.ts +20 -20
  128. package/src/input/commands/work-plan-runtime.ts +33 -8
  129. package/src/input/commands.ts +2 -2
  130. package/src/input/feed-context-factory.ts +2 -1
  131. package/src/input/file-picker.ts +3 -2
  132. package/src/input/handler-command-route.ts +4 -7
  133. package/src/input/handler-content-actions.ts +89 -1
  134. package/src/input/handler-feed-routes.ts +19 -12
  135. package/src/input/handler-feed.ts +6 -3
  136. package/src/input/handler-interactions.ts +7 -5
  137. package/src/input/handler-modal-stack.ts +3 -3
  138. package/src/input/handler-onboarding.ts +24 -80
  139. package/src/input/handler-shortcuts.ts +15 -4
  140. package/src/input/handler.ts +47 -17
  141. package/src/input/input-history.ts +5 -6
  142. package/src/input/keybindings.ts +22 -11
  143. package/src/input/onboarding/onboarding-wizard-apply.ts +13 -0
  144. package/src/input/onboarding/onboarding-wizard-constants.ts +1 -0
  145. package/src/input/onboarding/onboarding-wizard-operator-steps.ts +147 -57
  146. package/src/input/onboarding/onboarding-wizard-steps.ts +46 -29
  147. package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
  148. package/src/input/onboarding/onboarding-wizard.ts +68 -0
  149. package/src/input/profile-picker-modal.ts +31 -12
  150. package/src/input/session-picker-modal.ts +21 -4
  151. package/src/input/settings-modal-behavior.ts +0 -3
  152. package/src/input/settings-modal-subscriptions.ts +3 -3
  153. package/src/input/settings-modal-types.ts +2 -13
  154. package/src/input/settings-modal.ts +6 -52
  155. package/src/input/submission-intent.ts +0 -1
  156. package/src/input/submission-router.ts +3 -3
  157. package/src/main.ts +18 -8
  158. package/src/panels/approval-panel.ts +8 -8
  159. package/src/panels/automation-control-panel.ts +2 -2
  160. package/src/panels/base-panel.ts +1 -1
  161. package/src/panels/builtin/agent.ts +1 -1
  162. package/src/panels/builtin/operations.ts +1 -10
  163. package/src/panels/builtin/session.ts +9 -9
  164. package/src/panels/builtin/shared.ts +2 -2
  165. package/src/panels/cost-tracker-panel.ts +1 -1
  166. package/src/panels/docs-panel.ts +5 -3
  167. package/src/panels/index.ts +0 -1
  168. package/src/panels/knowledge-panel.ts +6 -5
  169. package/src/panels/memory-panel.ts +7 -6
  170. package/src/panels/panel-list-panel.ts +36 -80
  171. package/src/panels/project-planning-panel.ts +18 -11
  172. package/src/panels/provider-account-snapshot.ts +51 -25
  173. package/src/panels/provider-accounts-panel.ts +33 -18
  174. package/src/panels/provider-health-domains.ts +45 -4
  175. package/src/panels/provider-health-panel.ts +5 -4
  176. package/src/panels/qr-panel.ts +1 -1
  177. package/src/panels/schedule-panel.ts +9 -17
  178. package/src/panels/security-panel.ts +8 -8
  179. package/src/panels/session-browser-panel.ts +10 -10
  180. package/src/panels/subscription-panel.ts +3 -3
  181. package/src/panels/system-messages-panel.ts +1 -1
  182. package/src/panels/tasks-panel.ts +20 -13
  183. package/src/panels/tool-inspector-panel.ts +19 -12
  184. package/src/panels/work-plan-panel.ts +5 -5
  185. package/src/planning/project-planning-coordinator.ts +1 -1
  186. package/src/provider-auth-route-display.ts +9 -0
  187. package/src/renderer/agent-workspace-style.ts +34 -0
  188. package/src/renderer/agent-workspace.ts +228 -52
  189. package/src/renderer/autocomplete-overlay.ts +25 -6
  190. package/src/renderer/bookmark-modal.ts +19 -4
  191. package/src/renderer/buffer.ts +4 -2
  192. package/src/renderer/context-inspector.ts +50 -13
  193. package/src/renderer/diff.ts +1 -1
  194. package/src/renderer/file-picker-overlay.ts +19 -6
  195. package/src/renderer/help-overlay.ts +106 -33
  196. package/src/renderer/history-search-overlay.ts +19 -4
  197. package/src/renderer/live-tail-modal.ts +27 -5
  198. package/src/renderer/mcp-workspace.ts +164 -50
  199. package/src/renderer/model-picker-overlay.ts +58 -2
  200. package/src/renderer/model-workspace.ts +104 -20
  201. package/src/renderer/process-modal.ts +27 -6
  202. package/src/renderer/profile-picker-modal.ts +20 -4
  203. package/src/renderer/search-overlay.ts +25 -5
  204. package/src/renderer/selection-modal-overlay.ts +46 -14
  205. package/src/renderer/session-picker-modal.ts +18 -1
  206. package/src/renderer/settings-modal-helpers.ts +2 -40
  207. package/src/renderer/settings-modal.ts +83 -50
  208. package/src/renderer/tool-call.ts +20 -11
  209. package/src/runtime/agent-runtime-events.ts +6 -6
  210. package/src/runtime/bootstrap-command-context.ts +7 -1
  211. package/src/runtime/bootstrap-command-parts.ts +9 -7
  212. package/src/runtime/bootstrap-core.ts +19 -1
  213. package/src/runtime/bootstrap-hook-bridge.ts +7 -18
  214. package/src/runtime/bootstrap-shell.ts +4 -4
  215. package/src/runtime/bootstrap.ts +31 -22
  216. package/src/runtime/connected-host-auth.ts +7 -2
  217. package/src/runtime/diagnostics/panels/index.ts +2 -2
  218. package/src/runtime/diagnostics/panels/policy.ts +7 -7
  219. package/src/runtime/index.ts +2 -1
  220. package/src/runtime/onboarding/apply-file-helpers.ts +66 -0
  221. package/src/runtime/onboarding/apply.ts +80 -79
  222. package/src/runtime/onboarding/derivation.ts +5 -8
  223. package/src/runtime/onboarding/snapshot.ts +0 -15
  224. package/src/runtime/onboarding/types.ts +8 -19
  225. package/src/runtime/onboarding/verify.ts +32 -10
  226. package/src/runtime/operator-token-cleanup.ts +1 -1
  227. package/src/runtime/services.ts +91 -22
  228. package/src/runtime/store/selectors/index.ts +3 -3
  229. package/src/runtime/store/state.ts +1 -4
  230. package/src/runtime/surface-feature-flags.ts +41 -6
  231. package/src/runtime/ui-read-models.ts +3 -4
  232. package/src/runtime/ui-services.ts +6 -6
  233. package/src/shell/blocking-input.ts +2 -1
  234. package/src/shell/ui-openers.ts +3 -13
  235. package/src/tools/agent-analysis-registry-policy.ts +0 -1
  236. package/src/tools/agent-channel-send-tool.ts +133 -0
  237. package/src/tools/agent-context-policy.ts +1 -1
  238. package/src/tools/agent-knowledge-ingest-tool.ts +405 -0
  239. package/src/tools/agent-knowledge-tool.ts +170 -0
  240. package/src/tools/agent-local-registry-tool.ts +269 -69
  241. package/src/tools/agent-media-generate-tool.ts +133 -0
  242. package/src/tools/agent-notify-tool.ts +143 -0
  243. package/src/tools/agent-operator-action-tool.ts +137 -0
  244. package/src/tools/agent-operator-briefing-tool.ts +217 -0
  245. package/src/tools/agent-reminder-schedule-tool.ts +237 -0
  246. package/src/tools/agent-tool-policy-guard.ts +8 -8
  247. package/src/tools/agent-work-plan-tool.ts +256 -0
  248. package/src/version.ts +1 -1
  249. package/src/input/commands/policy-dispatch.ts +0 -339
  250. package/src/input/commands/policy.ts +0 -13
  251. package/src/panels/panel-picker.ts +0 -105
  252. package/src/panels/policy-panel.ts +0 -308
  253. package/src/renderer/panel-picker-overlay.ts +0 -202
@@ -3,6 +3,7 @@ import { dirname } from 'node:path';
3
3
  import { SecretsManager } from '../config/secrets.ts';
4
4
  import { BUILTIN_SECRET_PROVIDER_SOURCES, describeSecretRef, isSecretRefInput, resolveSecretRef } from '@pellux/goodvibes-sdk/platform/config';
5
5
  import { getSubscriptionProviderConfig, listAvailableSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config';
6
+ import type { OAuthProviderConfig } from '@pellux/goodvibes-sdk/platform/config';
6
7
  import { beginOpenAICodexLogin, exchangeOpenAICodexCode } from '@pellux/goodvibes-sdk/platform/config';
7
8
  import { inspectProviderAuth } from '@/runtime/index.ts';
8
9
  import { buildCompanionConnectionInfo, encodeConnectionPayload, formatConnectionBlock } from '@pellux/goodvibes-sdk/platform/pairing';
@@ -12,6 +13,13 @@ import type { CliCommandRuntime } from './management.ts';
12
13
  import { extractAuthorizationCode, formatJsonOrText, hasCommandFlag, openBrowser, urlHostForBindHost, withRuntimeServices, yesNo } from './management.ts';
13
14
  import { GOODVIBES_AGENT_PAIRING_SURFACE } from '../config/surface.ts';
14
15
  import { connectedHostTokenRequiredMessage, readConnectedHostOperatorToken } from '../runtime/connected-host-auth.ts';
16
+ import { formatAgentRecordSource } from '../agent/record-labels.ts';
17
+
18
+ function resolveManualSubscriptionConfig(config: OAuthProviderConfig): OAuthProviderConfig {
19
+ return config.manualRedirectUri
20
+ ? { ...config, redirectUri: config.manualRedirectUri }
21
+ : config;
22
+ }
15
23
 
16
24
  export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<string> {
17
25
  return await withRuntimeServices(runtime, async (services) => {
@@ -23,7 +31,7 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
23
31
  if (sub === 'providers') {
24
32
  return formatJsonOrText(runtime.cli)(available, [
25
33
  'GoodVibes subscription providers',
26
- ...available.map((provider) => ` ${provider.provider} source=${provider.source} redirect=${provider.oauth.redirectUri}`),
34
+ ...available.map((provider) => ` ${provider.provider} origin ${provider.source} redirect ${provider.oauth.redirectUri}`),
27
35
  ].join('\n'));
28
36
  }
29
37
  if (sub === 'inspect' || sub === 'show') {
@@ -41,21 +49,21 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
41
49
  const stored = services.subscriptionManager.get(provider);
42
50
  return formatJsonOrText(runtime.cli)({ provider, resolved, inspection, stored }, [
43
51
  `GoodVibes subscription ${provider}`,
44
- ` configured: ${yesNo(inspection.configured)}`,
45
- ` freshness: ${inspection.freshness}`,
46
- ` callbackMode: ${inspection.callbackMode}`,
52
+ ` configured ${yesNo(inspection.configured)}`,
53
+ ` freshness ${inspection.freshness}`,
54
+ ' finish mode explicit manual',
47
55
  ...(resolved ? [
48
- ` source: ${resolved.source}`,
49
- ` redirectUri: ${resolved.oauth.redirectUri}`,
56
+ ` origin ${formatAgentRecordSource(resolved.source)}`,
57
+ ` redirect URI ${resolved.oauth.redirectUri}`,
50
58
  ] : []),
51
59
  ...(stored ? [
52
- ` tokenType: ${stored.tokenType}`,
53
- ` expiresAt: ${stored.expiresAt ? new Date(stored.expiresAt).toISOString() : 'n/a'}`,
54
- ` refreshToken: ${stored.refreshToken ? 'present' : 'absent'}`,
55
- ` overrideAmbient: ${yesNo(stored.overrideAmbientApiKeys)}`,
56
- ] : [' stored: no']),
57
- ...inspection.issues.map((issue) => ` issue: ${issue}`),
58
- ...inspection.nextActions.map((action) => ` next: ${action}`),
60
+ ` token type ${stored.tokenType}`,
61
+ ` expires ${stored.expiresAt ? new Date(stored.expiresAt).toISOString() : 'n/a'}`,
62
+ ` refresh token ${stored.refreshToken ? 'present' : 'absent'}`,
63
+ ` override ambient ${yesNo(stored.overrideAmbientApiKeys)}`,
64
+ ] : [' stored no']),
65
+ ...inspection.issues.map((issue) => ` issue ${issue}`),
66
+ ...inspection.nextActions.map((action) => ` next ${action}`),
59
67
  ].join('\n'));
60
68
  }
61
69
  if (sub === 'login' || sub === 'start') {
@@ -63,7 +71,7 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
63
71
  const mode = sub === 'start' ? 'start' : rest[1]?.toLowerCase();
64
72
  if (!provider || mode !== 'start') return `Usage: ${binary} subscription login <provider> start [--open]`;
65
73
  const resolved = getSubscriptionProviderConfig(provider, services.serviceRegistry.get(provider));
66
- if (!resolved) return `No subscription provider found: ${provider}`;
74
+ if (!resolved) return `No subscription provider found ${provider}`;
67
75
  if (provider === 'openai' && resolved.source === 'builtin') {
68
76
  const started = await beginOpenAICodexLogin();
69
77
  services.subscriptionManager.savePending({
@@ -75,26 +83,29 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
75
83
  });
76
84
  const openResult = runtime.cli.flags.open || hasCommandFlag(rest, '--open') ? openBrowser(started.authorizationUrl) : null;
77
85
  return [
78
- `Subscription OAuth started: ${provider}`,
79
- ` source: ${resolved.source}`,
80
- ` state: ${started.state}`,
81
- ` redirectUri: ${started.redirectUri}`,
82
- ...(openResult ? [` open: ${openResult}`] : []),
83
- ` next: ${binary} subscription login ${provider} finish <code-or-url>`,
84
- ' authorizationUrl:',
86
+ `Subscription OAuth started ${provider}`,
87
+ ` origin ${formatAgentRecordSource(resolved.source)}`,
88
+ ` state ${started.state}`,
89
+ ` redirect URI ${started.redirectUri}`,
90
+ ...(openResult ? [` open ${openResult}`] : []),
91
+ ' completion paste the callback code or redirect URL into the finish command',
92
+ ` next ${binary} subscription login ${provider} finish <code-or-url>`,
93
+ ' authorization URL',
85
94
  ` ${started.authorizationUrl}`,
86
95
  ].join('\n');
87
96
  }
88
- const started = await services.subscriptionManager.beginOAuthLogin(provider, resolved.oauth);
97
+ const activeConfig = resolveManualSubscriptionConfig(resolved.oauth);
98
+ const started = await services.subscriptionManager.beginOAuthLogin(provider, activeConfig);
89
99
  const openResult = runtime.cli.flags.open || hasCommandFlag(rest, '--open') ? openBrowser(started.authorizationUrl) : null;
90
100
  return [
91
- `Subscription OAuth started: ${provider}`,
92
- ` source: ${resolved.source}`,
93
- ` state: ${started.pending.state}`,
94
- ` redirectUri: ${started.pending.redirectUri}`,
95
- ...(openResult ? [` open: ${openResult}`] : []),
96
- ` next: ${binary} subscription login ${provider} finish <code-or-url>`,
97
- ' authorizationUrl:',
101
+ `Subscription OAuth started ${provider}`,
102
+ ` origin ${formatAgentRecordSource(resolved.source)}`,
103
+ ` state ${started.pending.state}`,
104
+ ` redirect URI ${started.pending.redirectUri}`,
105
+ ...(openResult ? [` open ${openResult}`] : []),
106
+ ' completion paste the callback code or redirect URL into the finish command',
107
+ ` next ${binary} subscription login ${provider} finish <code-or-url>`,
108
+ ' authorization URL',
98
109
  ` ${started.authorizationUrl}`,
99
110
  ].join('\n');
100
111
  }
@@ -103,7 +114,7 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
103
114
  const codeInput = sub === 'finish' ? rest[1] : rest[2];
104
115
  if (!provider || !codeInput) return `Usage: ${binary} subscription login <provider> finish <code-or-url>`;
105
116
  const resolved = getSubscriptionProviderConfig(provider, services.serviceRegistry.get(provider));
106
- if (!resolved) return `No subscription provider found: ${provider}`;
117
+ if (!resolved) return `No subscription provider found ${provider}`;
107
118
  const code = extractAuthorizationCode(codeInput);
108
119
  if (provider === 'openai' && resolved.source === 'builtin') {
109
120
  const pendingLogin = services.subscriptionManager.getPending(provider);
@@ -122,24 +133,36 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
122
133
  createdAt: services.subscriptionManager.get(provider)?.createdAt ?? now,
123
134
  updatedAt: now,
124
135
  });
125
- return `Subscription stored: ${provider} token=${record.tokenType} expires=${record.expiresAt ? new Date(record.expiresAt).toISOString() : 'n/a'}`;
136
+ return [
137
+ `Subscription stored ${provider}`,
138
+ ` token type ${record.tokenType}`,
139
+ ` expires ${record.expiresAt ? new Date(record.expiresAt).toISOString() : 'n/a'}`,
140
+ ].join('\n');
126
141
  }
127
- const record = await services.subscriptionManager.completeOAuthLogin(provider, resolved.oauth, code);
128
- return `Subscription stored: ${provider} token=${record.tokenType} expires=${record.expiresAt ? new Date(record.expiresAt).toISOString() : 'n/a'}`;
142
+ const activeConfig = resolveManualSubscriptionConfig(resolved.oauth);
143
+ const record = await services.subscriptionManager.completeOAuthLogin(provider, activeConfig, code);
144
+ return [
145
+ `Subscription stored ${provider}`,
146
+ ` token type ${record.tokenType}`,
147
+ ` expires ${record.expiresAt ? new Date(record.expiresAt).toISOString() : 'n/a'}`,
148
+ ].join('\n');
129
149
  }
130
150
  if (sub === 'refresh') {
131
151
  const provider = rest[0];
132
152
  if (!provider) return `Usage: ${binary} subscription refresh <provider>`;
133
153
  const resolved = getSubscriptionProviderConfig(provider, services.serviceRegistry.get(provider));
134
- if (!resolved) return `No subscription provider found: ${provider}`;
154
+ if (!resolved) return `No subscription provider found ${provider}`;
135
155
  const record = await services.subscriptionManager.refreshOAuthToken(provider, resolved.oauth);
136
- return `Subscription refreshed: ${provider} expires=${record.expiresAt ? new Date(record.expiresAt).toISOString() : 'n/a'}`;
156
+ return [
157
+ `Subscription refreshed ${provider}`,
158
+ ` expires ${record.expiresAt ? new Date(record.expiresAt).toISOString() : 'n/a'}`,
159
+ ].join('\n');
137
160
  }
138
161
  if (sub === 'logout' || sub === 'remove') {
139
162
  const provider = rest[0];
140
163
  if (!provider) return `Usage: ${binary} subscription logout <provider>`;
141
164
  const removed = services.subscriptionManager.logout(provider);
142
- return removed ? `Subscription removed: ${provider}` : `No stored subscription session existed for ${provider}.`;
165
+ return removed ? `Subscription removed ${provider}` : `No stored subscription session existed for ${provider}.`;
143
166
  }
144
167
  if (sub !== 'list' && sub !== 'status' && sub !== 'review') {
145
168
  return `Usage: ${binary} subscription [list|providers|inspect <provider>|login <provider> start|finish <code-or-url>|refresh <provider>|logout <provider>]`;
@@ -155,10 +178,10 @@ export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<s
155
178
  };
156
179
  return formatJsonOrText(runtime.cli)(value, [
157
180
  'GoodVibes subscriptions',
158
- subscriptions.length === 0 ? ' active: none' : ' active:',
159
- ...subscriptions.map((sub) => ` ${sub.provider} token=${sub.tokenType} expires=${sub.expiresAt ? new Date(sub.expiresAt).toISOString() : 'n/a'} overrideAmbient=${yesNo(sub.overrideAmbientApiKeys)}`),
160
- pending.length === 0 ? ' pending: none' : ' pending:',
161
- ...pending.map((sub) => ` ${sub.provider} created=${new Date(sub.createdAt).toISOString()}`),
181
+ 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
+ pending.length === 0 ? ' pending none' : ' pending',
184
+ ...pending.map((sub) => ` ${sub.provider} created ${new Date(sub.createdAt).toISOString()}`),
162
185
  ].join('\n'));
163
186
  });
164
187
  }
@@ -186,7 +209,11 @@ export async function handleSecrets(runtime: CliCommandRuntime): Promise<string>
186
209
  }
187
210
  const resolved = await resolveSecretRef(ref, { resolveLocalSecret: (key) => secrets.get(key) });
188
211
  const value = { ref: describeSecretRef(ref), resolved: Boolean(resolved.value) };
189
- return formatJsonOrText(runtime.cli)(value, `[secrets] ${value.ref}: ${value.resolved ? 'resolved <redacted>' : 'missing'}`);
212
+ return formatJsonOrText(runtime.cli)(value, [
213
+ 'GoodVibes secret test',
214
+ ` ref ${value.ref}`,
215
+ ` result ${value.resolved ? 'resolved <redacted>' : 'missing'}`,
216
+ ].join('\n'));
190
217
  }
191
218
  if (sub === 'set' || sub === 'link') {
192
219
  const flags = new Set(rest.filter((arg) => arg.startsWith('--')));
@@ -201,7 +228,10 @@ export async function handleSecrets(runtime: CliCommandRuntime): Promise<string>
201
228
  scope: flags.has('--user') ? 'user' : 'project',
202
229
  medium: flags.has('--plaintext') ? 'plaintext' : 'secure',
203
230
  });
204
- return `[secrets] ${sub === 'link' ? 'Linked' : 'Stored'}: ${key}`;
231
+ return [
232
+ `Secret ${sub === 'link' ? 'linked' : 'stored'}`,
233
+ ` key ${key}`,
234
+ ].join('\n');
205
235
  }
206
236
  if (sub === 'delete') {
207
237
  const key = rest.find((arg) => !arg.startsWith('--'));
@@ -211,18 +241,21 @@ export async function handleSecrets(runtime: CliCommandRuntime): Promise<string>
211
241
  scope: flags.has('--user') ? 'user' : flags.has('--project') ? 'project' : undefined,
212
242
  medium: flags.has('--secure') ? 'secure' : flags.has('--plaintext') ? 'plaintext' : undefined,
213
243
  });
214
- return `[secrets] Deleted: ${key}`;
244
+ return [
245
+ 'Secret deleted',
246
+ ` key ${key}`,
247
+ ].join('\n');
215
248
  }
216
249
  const [records, review] = await Promise.all([secrets.listDetailed(), secrets.inspect()]);
217
250
  const stored = records.filter((record) => record.source !== 'env');
218
251
  const value = { policy: review.policy, records: stored, warnings: review.warnings };
219
252
  return formatJsonOrText(runtime.cli)(value, [
220
253
  'GoodVibes secrets',
221
- ` policy: ${review.policy}`,
222
- ` secure available: ${yesNo(review.secureAvailable)}`,
223
- ` stored keys: ${stored.length}`,
254
+ ` policy ${review.policy}`,
255
+ ` secure available ${yesNo(review.secureAvailable)}`,
256
+ ` stored keys ${stored.length}`,
224
257
  ...stored.map((record) => ` ${record.key} (${record.source}${record.refSource ? `, ref:${record.refSource}` : ''}${record.overriddenByEnv ? ', env override' : ''})`),
225
- ...review.warnings.map((warning) => ` warning: ${warning}`),
258
+ ...review.warnings.map((warning) => ` warning ${warning}`),
226
259
  ].join('\n'));
227
260
  }
228
261
 
@@ -235,21 +268,21 @@ export async function handleSessions(runtime: CliCommandRuntime): Promise<string
235
268
  const value = sessions;
236
269
  return formatJsonOrText(runtime.cli)(value, [
237
270
  `GoodVibes sessions (${sessions.length})`,
238
- ...sessions.slice(0, 50).map((session) => ` ${session.name} messages=${session.messageCount} ${new Date(session.timestamp).toISOString()} ${session.title || '(untitled)'}`),
271
+ ...sessions.slice(0, 50).map((session) => ` ${session.name} messages ${session.messageCount} ${new Date(session.timestamp).toISOString()} ${session.title || '(untitled)'}`),
239
272
  ].join('\n'));
240
273
  }
241
274
  if (sub === 'show' || sub === 'info') {
242
275
  const target = rest.join(' ').trim();
243
276
  if (!target) return `Usage: ${binary} sessions show <id|name>`;
244
277
  const found = sessions.find((session) => session.name === target || session.name.startsWith(target) || session.title.toLowerCase() === target.toLowerCase());
245
- if (!found) return `Session not found: ${target}`;
278
+ if (!found) return `Session not found ${target}`;
246
279
  return formatJsonOrText(runtime.cli)(found, [
247
280
  `Session ${found.name}`,
248
- ` title: ${found.title || '(untitled)'}`,
249
- ` messages: ${found.messageCount}`,
250
- ` provider/model: ${found.provider}/${found.model}`,
251
- ` updated: ${new Date(found.timestamp).toISOString()}`,
252
- ` file: ${found.filePath}`,
281
+ ` title ${found.title || '(untitled)'}`,
282
+ ` messages ${found.messageCount}`,
283
+ ` provider/model ${found.provider}/${found.model}`,
284
+ ` updated ${new Date(found.timestamp).toISOString()}`,
285
+ ` file ${found.filePath}`,
253
286
  ].join('\n'));
254
287
  }
255
288
  if (sub === 'export') {
@@ -257,14 +290,17 @@ export async function handleSessions(runtime: CliCommandRuntime): Promise<string
257
290
  const outputPath = rest[1];
258
291
  if (!target) return `Usage: ${binary} sessions export <id|name> [path]`;
259
292
  const found = sessions.find((session) => session.name === target || session.name.startsWith(target) || session.title.toLowerCase() === target.toLowerCase());
260
- if (!found) return `Session not found: ${target}`;
293
+ if (!found) return `Session not found ${target}`;
261
294
  const data = services.sessionManager.load(found.name);
262
295
  const text = JSON.stringify({ name: found.name, ...data }, null, 2) + '\n';
263
296
  if (outputPath) {
264
297
  const targetPath = services.shellPaths.resolveWorkspacePath(outputPath);
265
298
  mkdirSync(dirname(targetPath), { recursive: true });
266
299
  writeFileSync(targetPath, text, 'utf-8');
267
- return `Session exported: ${targetPath}`;
300
+ return [
301
+ 'Session exported',
302
+ ` path ${targetPath}`,
303
+ ].join('\n');
268
304
  }
269
305
  return text.trimEnd();
270
306
  }
@@ -281,9 +317,9 @@ export async function handleTasks(runtime: CliCommandRuntime): Promise<string> {
281
317
  if (sub === 'submit') {
282
318
  return [
283
319
  'GoodVibes Agent blocks CLI task submission from the host-owned task workflow.',
284
- ' policy: do normal assistant work in the main Agent conversation or use `goodvibes-agent run <prompt>` for an explicit one-shot run.',
285
- ' build/fix/review: use `goodvibes-agent delegate <task>` for explicit GoodVibes TUI handoff.',
286
- ' result: no local task was started.',
320
+ ' policy do normal assistant work in the main Agent conversation or use `goodvibes-agent run <prompt>` for an explicit one-shot run.',
321
+ ' build/fix/review use `goodvibes-agent delegate <task>` for explicit GoodVibes TUI handoff.',
322
+ ' result no local task was started.',
287
323
  ].join('\n');
288
324
  }
289
325
  return await withRuntimeServices(runtime, (services) => {
@@ -296,7 +332,7 @@ export async function handleTasks(runtime: CliCommandRuntime): Promise<string> {
296
332
  if (sub === 'show') {
297
333
  if (!rest[0]) return `Usage: ${runtime.cli.binary} tasks show <taskId>`;
298
334
  const task = tasks.find((candidate) => candidate.id === rest[0]);
299
- return task ? JSON.stringify(task, null, 2) : `Unknown task: ${rest[0] ?? ''}`;
335
+ return task ? JSON.stringify(task, null, 2) : `Unknown task ${rest[0] ?? ''}`;
300
336
  }
301
337
  return `Usage: ${runtime.cli.binary} tasks list|show <taskId>`;
302
338
  });
@@ -306,9 +342,9 @@ export async function renderPairing(runtime: CliCommandRuntime): Promise<string>
306
342
  const tokenRecord = readConnectedHostOperatorToken(runtime.homeDirectory);
307
343
  if (!tokenRecord.token) return connectedHostTokenRequiredMessage(tokenRecord.path);
308
344
  const binding = resolveRuntimeEndpointBinding(runtime.configManager, 'controlPlane');
309
- const daemonUrl = `http://${urlHostForBindHost(binding.host)}:${binding.port}`;
345
+ const connectedHostUrl = `http://${urlHostForBindHost(binding.host)}:${binding.port}`;
310
346
  const info = buildCompanionConnectionInfo({
311
- daemonUrl,
347
+ daemonUrl: connectedHostUrl,
312
348
  token: tokenRecord.token,
313
349
  username: 'admin',
314
350
  surface: GOODVIBES_AGENT_PAIRING_SURFACE,
@@ -13,6 +13,9 @@ import type { RuntimeServices } from '../runtime/services.ts';
13
13
  import { SecretsManager } from '../config/secrets.ts';
14
14
  import { RuntimeEventBus, type TurnEvent } from '@/runtime/index.ts';
15
15
  import { createShellPathService } from '@/runtime/index.ts';
16
+ import { buildPersistedSessionContext } from '@/runtime/index.ts';
17
+ import type { SessionSnapshot } from '@/runtime/index.ts';
18
+ import { conversationMessagesAsSessionRecords } from '../core/conversation-message-snapshot.ts';
16
19
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
17
20
  import { listProviderRuntimeSnapshots } from '@pellux/goodvibes-sdk/platform/providers';
18
21
  import { BUILTIN_SECRET_PROVIDER_SOURCES, describeSecretRef, isSecretRefInput, resolveSecretRef } from '@pellux/goodvibes-sdk/platform/config';
@@ -56,8 +59,8 @@ export function formatJsonOrText(cli: GoodVibesCliParseResult): Formatter {
56
59
  }
57
60
 
58
61
  function exitCodeForText(output: string): number {
59
- if (output.startsWith('Usage:') || output.startsWith('Invalid ') || output.startsWith('Unsupported:')) return 2;
60
- if (output.startsWith('Session not found:') || output.startsWith('Unknown task:') || output.startsWith('Task submit failed ')) return 1;
62
+ if (output.startsWith('Usage:') || output.startsWith('Invalid ') || output.startsWith('Unsupported')) return 2;
63
+ if (output.startsWith('Session not found') || output.startsWith('Unknown task') || output.startsWith('Task submit failed ')) return 1;
61
64
  if (output.startsWith('No stored ') || output.startsWith('No pending ') || output.startsWith('No model ') || output.startsWith('No provider ') || output.startsWith('No auth ')) return 1;
62
65
  if (output.startsWith('Unknown ')) return 1;
63
66
  if (output === 'Bundle has no config object to import.') return 1;
@@ -98,7 +101,7 @@ export function getNestedValue(source: unknown, key: string): unknown {
98
101
  let cursor = source;
99
102
  for (const part of key.split('.')) {
100
103
  if (cursor == null || typeof cursor !== 'object') return undefined;
101
- cursor = (cursor as Record<string, unknown>)[part];
104
+ cursor = Reflect.get(cursor, part);
102
105
  }
103
106
  return cursor;
104
107
  }
@@ -278,7 +281,13 @@ export async function runNonInteractiveAgent(runtime: CliCommandRuntime): Promis
278
281
  }) + '\n');
279
282
  }
280
283
  } finally {
281
- const snapshot = ctx.conversation.toJSON() as Parameters<typeof ctx.shutdown>[0];
284
+ const messages = ctx.conversation.getMessageSnapshot();
285
+ const snapshot: SessionSnapshot = {
286
+ messages: conversationMessagesAsSessionRecords(messages),
287
+ timestamp: Date.now(),
288
+ title: ctx.conversation.title,
289
+ ...buildPersistedSessionContext(messages, ctx.conversation.getTitleSource()),
290
+ };
282
291
  await ctx.shutdown(snapshot);
283
292
  }
284
293
  return exitCode;
@@ -326,18 +335,25 @@ async function renderProviders(runtime: CliCommandRuntime): Promise<string> {
326
335
  } else {
327
336
  runtime.configManager.setDynamic('provider.model', formatProviderModel(provider, getModelIdFromProviderModel(runtime.configManager.get('provider.model'))));
328
337
  }
329
- return requestedModel
330
- ? `Provider selected: ${provider} (${requestedModel})\n warning: model catalog entry was not available locally; saved explicit selection.`
331
- : `Provider selected: ${provider}\n warning: model catalog entry was not available locally; model selection was left unchanged.`;
338
+ return [
339
+ 'Provider selected',
340
+ ` provider ${provider}`,
341
+ ...(requestedModel ? [` model ${requestedModel}`] : []),
342
+ ` warning model catalog entry was not available locally; ${requestedModel ? 'saved explicit selection' : 'model selection was left unchanged'}.`,
343
+ ].join('\n');
332
344
  }
333
345
  runtime.configManager.setDynamic('provider.model', selected.registryKey);
334
- return `Provider selected: ${selected.provider} (${selected.registryKey})`;
346
+ return [
347
+ 'Provider selected',
348
+ ` provider ${selected.provider}`,
349
+ ` model ${selected.registryKey}`,
350
+ ].join('\n');
335
351
  }
336
352
  if (sub === 'inspect' || sub === 'show') {
337
353
  const provider = rest[0];
338
354
  if (!provider) return `Usage: ${binary} providers inspect <provider>`;
339
355
  const snapshot = snapshots.find((candidate) => candidate.providerId === provider);
340
- if (!snapshot) return `No provider found: ${provider}`;
356
+ if (!snapshot) return `No provider found ${provider}`;
341
357
  const setup = classifyProviderSetup({
342
358
  providerId: snapshot.providerId,
343
359
  authMode: snapshot.runtime.auth?.mode,
@@ -383,7 +399,7 @@ async function renderProviders(runtime: CliCommandRuntime): Promise<string> {
383
399
  return formatJsonOrText(runtime.cli)(value, [
384
400
  'GoodVibes providers',
385
401
  ...value.map((provider) =>
386
- ` ${provider.current ? '*' : ' '} ${provider.provider.padEnd(18)} setup=${provider.setupClass} configured=${yesNo(provider.configured)} via=${provider.configuredVia ?? 'n/a'} models=${provider.models} routes=${provider.authRouteSummary} ${provider.detail ?? ''}`.trimEnd(),
402
+ ` ${provider.current ? '*' : ' '} ${provider.provider.padEnd(18)} setup ${provider.setupClass} configured ${yesNo(provider.configured)} via ${provider.configuredVia ?? 'n/a'} models ${provider.models} routes ${provider.authRouteSummary} ${provider.detail ?? ''}`.trimEnd(),
387
403
  ),
388
404
  ].join('\n'));
389
405
  });
@@ -436,18 +452,28 @@ async function renderModels(runtime: CliCommandRuntime): Promise<string> {
436
452
  const provider = inferProviderFromRegistryKey(modelKey);
437
453
  runtime.configManager.setDynamic('provider.model', formatProviderModel(provider, modelKey));
438
454
  await services.favoritesStore.recordUsage(modelKey);
439
- return `Model selected: ${modelKey}\n warning: model catalog entry was not available locally; saved explicit selection.`;
455
+ return [
456
+ 'Model selected',
457
+ ` model ${modelKey}`,
458
+ ' warning model catalog entry was not available locally; saved explicit selection.',
459
+ ].join('\n');
440
460
  }
441
461
  runtime.configManager.setDynamic('provider.model', model.registryKey);
442
462
  await services.favoritesStore.recordUsage(model.registryKey);
443
- return `Model selected: ${model.registryKey}`;
463
+ return [
464
+ 'Model selected',
465
+ ` model ${model.registryKey}`,
466
+ ].join('\n');
444
467
  }
445
468
  if (subOrFilter === 'pin' || subOrFilter === 'unpin') {
446
469
  const modelKey = rest[0];
447
470
  if (!modelKey) return `Usage: ${binary} models ${subOrFilter} <registryKey>`;
448
471
  if (subOrFilter === 'pin') await services.favoritesStore.pinModel(modelKey);
449
472
  else await services.favoritesStore.unpinModel(modelKey);
450
- return `Model ${subOrFilter === 'pin' ? 'pinned' : 'unpinned'}: ${modelKey}`;
473
+ return [
474
+ `Model ${subOrFilter === 'pin' ? 'pinned' : 'unpinned'}`,
475
+ ` model ${modelKey}`,
476
+ ].join('\n');
451
477
  }
452
478
  if (subOrFilter === 'pinned') {
453
479
  const pinned = await services.favoritesStore.getPinned();
@@ -479,7 +505,7 @@ async function renderModels(runtime: CliCommandRuntime): Promise<string> {
479
505
  }));
480
506
  return formatJsonOrText(runtime.cli)(value, [
481
507
  `GoodVibes models${filter ? ` (${filter})` : ''}`,
482
- ...value.map((model) => ` ${model.current ? '*' : ' '} ${model.registryKey.padEnd(42)} setup=${model.setupClass} ctx=${model.contextWindow.toLocaleString()} ${model.displayName}`),
508
+ ...value.map((model) => ` ${model.current ? '*' : ' '} ${model.registryKey.padEnd(42)} setup ${model.setupClass} context ${model.contextWindow.toLocaleString()} ${model.displayName}`),
483
509
  ].join('\n'));
484
510
  });
485
511
  }
@@ -521,23 +547,23 @@ async function renderAuth(runtime: CliCommandRuntime): Promise<string> {
521
547
  if (sub === 'users' || sub === 'sessions') {
522
548
  return formatJsonOrText(runtime.cli)(value, [
523
549
  `GoodVibes Agent auth ${sub}`,
524
- ' owner: connected GoodVibes host',
525
- ` operator token: ${paths.operatorTokenPresent ? 'present' : 'missing'}`,
526
- ` operator token path: ${paths.operatorTokenPath}`,
527
- ` ${sub}: managed outside Agent`,
550
+ ' owner connected GoodVibes host',
551
+ ` operator token ${paths.operatorTokenPresent ? 'present' : 'missing'}`,
552
+ ` operator token path ${paths.operatorTokenPath}`,
553
+ ` ${sub} managed outside Agent`,
528
554
  ' Agent does not enumerate or mutate connected-host users/sessions from the local CLI.',
529
555
  ].join('\n'));
530
556
  }
531
557
  return formatJsonOrText(runtime.cli)(value, [
532
558
  'GoodVibes Agent auth',
533
- ' owner: connected GoodVibes host',
534
- ` permission mode: ${String(value.permissionMode)}`,
535
- ` operator token: ${paths.operatorTokenPresent ? 'present' : 'missing'} (${paths.operatorTokenPath})`,
536
- ` compatibility user store: ${paths.userStorePresent ? 'present' : 'missing'} (${paths.userStorePath})`,
537
- ` compatibility bootstrap credential: ${paths.bootstrapCredentialPresent ? 'present' : 'missing'} (${paths.bootstrapCredentialPath})`,
538
- ' connected-host user/session administration: outside Agent',
539
- ' next: goodvibes-agent providers',
540
- ' next: goodvibes-agent subscription providers',
559
+ ' owner connected GoodVibes host',
560
+ ` permission mode ${String(value.permissionMode)}`,
561
+ ` operator token ${paths.operatorTokenPresent ? 'present' : 'missing'} (${paths.operatorTokenPath})`,
562
+ ` compatibility user store ${paths.userStorePresent ? 'present' : 'missing'} (${paths.userStorePath})`,
563
+ ` compatibility bootstrap credential ${paths.bootstrapCredentialPresent ? 'present' : 'missing'} (${paths.bootstrapCredentialPath})`,
564
+ ' connected-host user/session administration outside Agent',
565
+ ' next goodvibes-agent providers',
566
+ ' next goodvibes-agent subscription providers',
541
567
  ].join('\n'));
542
568
  }
543
569