@pellux/goodvibes-agent 0.1.114 → 0.1.117

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 (168) hide show
  1. package/CHANGELOG.md +41 -650
  2. package/README.md +40 -50
  3. package/dist/package/main.js +38224 -35673
  4. package/docs/README.md +5 -4
  5. package/docs/{connected-services.md → connected-host.md} +18 -10
  6. package/docs/getting-started.md +43 -42
  7. package/docs/release-and-publishing.md +6 -4
  8. package/package.json +3 -3
  9. package/src/agent/behavior-discovery-summary.ts +165 -0
  10. package/src/agent/persona-discovery.ts +1 -3
  11. package/src/agent/persona-registry.ts +5 -3
  12. package/src/agent/reminder-schedule-format.ts +6 -6
  13. package/src/agent/reminder-schedule.ts +1 -1
  14. package/src/agent/routine-registry.ts +43 -4
  15. package/src/agent/routine-schedule-format.ts +6 -6
  16. package/src/agent/routine-schedule-promotion.ts +1 -1
  17. package/src/agent/routine-schedule-receipts.ts +1 -1
  18. package/src/agent/runtime-profile-starters.ts +331 -0
  19. package/src/agent/runtime-profile.ts +228 -330
  20. package/src/agent/skill-registry.ts +190 -13
  21. package/src/cli/agent-knowledge-command.ts +2 -2
  22. package/src/cli/agent-knowledge-format.ts +2 -2
  23. package/src/cli/agent-knowledge-runtime.ts +1 -1
  24. package/src/cli/bundle-command.ts +1 -1
  25. package/src/cli/entrypoint.ts +16 -7
  26. package/src/cli/help.ts +26 -19
  27. package/src/cli/local-library-command.ts +75 -21
  28. package/src/cli/management-commands.ts +7 -6
  29. package/src/cli/management.ts +6 -51
  30. package/src/cli/parser.ts +23 -2
  31. package/src/cli/profiles-command.ts +247 -1
  32. package/src/cli/routines-command.ts +139 -11
  33. package/src/cli/service-posture.ts +11 -11
  34. package/src/cli/status.ts +21 -25
  35. package/src/input/agent-workspace-access-command-editor-submission.ts +149 -0
  36. package/src/input/agent-workspace-access-command-editors.ts +170 -0
  37. package/src/input/agent-workspace-activation.ts +2 -1
  38. package/src/input/agent-workspace-basic-command-editor-submission.ts +787 -0
  39. package/src/input/agent-workspace-basic-command-editors.ts +524 -459
  40. package/src/input/agent-workspace-categories.ts +177 -14
  41. package/src/input/agent-workspace-channel-command-editor-submission.ts +49 -0
  42. package/src/input/agent-workspace-channel-command-editors.ts +47 -0
  43. package/src/input/agent-workspace-command-editor.ts +25 -1
  44. package/src/input/agent-workspace-delegation-editor-submission.ts +55 -0
  45. package/src/input/agent-workspace-editors.ts +27 -0
  46. package/src/input/agent-workspace-knowledge-command-editor-submission.ts +132 -0
  47. package/src/input/agent-workspace-knowledge-command-editors.ts +110 -0
  48. package/src/input/agent-workspace-learned-behavior.ts +67 -0
  49. package/src/input/agent-workspace-library-command-editor-submission.ts +50 -0
  50. package/src/input/agent-workspace-library-command-editors.ts +35 -0
  51. package/src/input/agent-workspace-mcp-command-editor-submission.ts +95 -0
  52. package/src/input/agent-workspace-memory-command-editor-submission.ts +232 -0
  53. package/src/input/agent-workspace-memory-command-editors.ts +180 -0
  54. package/src/input/agent-workspace-navigation.ts +45 -0
  55. package/src/input/agent-workspace-notify-editor-submission.ts +107 -0
  56. package/src/input/agent-workspace-operations-command-editor-submission.ts +146 -0
  57. package/src/input/agent-workspace-operations-command-editors.ts +119 -0
  58. package/src/input/agent-workspace-provider-command-editor-submission.ts +155 -0
  59. package/src/input/agent-workspace-provider-command-editors.ts +93 -0
  60. package/src/input/agent-workspace-requirements.ts +11 -0
  61. package/src/input/agent-workspace-secret-editor-submission.ts +153 -0
  62. package/src/input/agent-workspace-session-command-editor-submission.ts +199 -0
  63. package/src/input/agent-workspace-session-command-editors.ts +249 -0
  64. package/src/input/agent-workspace-setup.ts +49 -19
  65. package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +101 -0
  66. package/src/input/agent-workspace-skill-bundle-command-editors.ts +110 -0
  67. package/src/input/agent-workspace-snapshot.ts +56 -8
  68. package/src/input/agent-workspace-task-command-editor-submission.ts +61 -0
  69. package/src/input/agent-workspace-task-command-editors.ts +47 -0
  70. package/src/input/agent-workspace-types.ts +124 -0
  71. package/src/input/agent-workspace-voice-media.ts +1 -1
  72. package/src/input/agent-workspace-workplan-editor-submission.ts +153 -0
  73. package/src/input/agent-workspace.ts +47 -37
  74. package/src/input/command-registry.ts +1 -1
  75. package/src/input/commands/agent-runtime-profile-runtime.ts +160 -6
  76. package/src/input/commands/agent-skills-runtime.ts +76 -12
  77. package/src/input/commands/agent-workspace-runtime.ts +4 -3
  78. package/src/input/commands/brief-runtime.ts +38 -9
  79. package/src/input/commands/channels-runtime.ts +3 -3
  80. package/src/input/commands/compat-runtime.ts +32 -0
  81. package/src/input/commands/health-runtime.ts +22 -24
  82. package/src/input/commands/knowledge.ts +26 -5
  83. package/src/input/commands/local-provider-runtime.ts +7 -3
  84. package/src/input/commands/local-setup-review.ts +4 -14
  85. package/src/input/commands/mcp-runtime.ts +8 -12
  86. package/src/input/commands/operator-runtime.ts +8 -67
  87. package/src/input/commands/personas-runtime.ts +1 -1
  88. package/src/input/commands/platform-access-runtime.ts +7 -70
  89. package/src/input/commands/policy-dispatch.ts +1 -1
  90. package/src/input/commands/policy.ts +1 -5
  91. package/src/input/commands/product-runtime.ts +2 -2
  92. package/src/input/commands/qrcode-runtime.ts +6 -4
  93. package/src/input/commands/routines-runtime.ts +44 -6
  94. package/src/input/commands/runtime-services.ts +1 -14
  95. package/src/input/commands/security-runtime.ts +3 -8
  96. package/src/input/commands/session-content.ts +1 -1
  97. package/src/input/commands/session-workflow.ts +5 -14
  98. package/src/input/commands/shell-core.ts +8 -2
  99. package/src/input/commands/subscription-runtime.ts +1 -6
  100. package/src/input/commands/support-bundle-runtime.ts +53 -0
  101. package/src/input/commands/tasks-runtime.ts +2 -2
  102. package/src/input/commands.ts +4 -0
  103. package/src/input/feed-context-factory.ts +0 -1
  104. package/src/input/handler-content-actions.ts +0 -25
  105. package/src/input/handler-feed.ts +0 -2
  106. package/src/input/handler-interactions.ts +2 -31
  107. package/src/input/handler-onboarding.ts +29 -12
  108. package/src/input/handler-shortcuts.ts +1 -3
  109. package/src/input/handler.ts +4 -7
  110. package/src/input/keybindings.ts +9 -9
  111. package/src/input/mcp-workspace.ts +90 -26
  112. package/src/input/onboarding/handler-onboarding-routes.ts +3 -1
  113. package/src/input/onboarding/onboarding-wizard-apply.ts +49 -0
  114. package/src/input/onboarding/onboarding-wizard-helpers.ts +1 -1
  115. package/src/input/onboarding/onboarding-wizard-operator-steps.ts +126 -11
  116. package/src/input/onboarding/onboarding-wizard-steps.ts +15 -8
  117. package/src/input/onboarding/onboarding-wizard-types.ts +1 -0
  118. package/src/input/onboarding/onboarding-wizard.ts +19 -2
  119. package/src/input/profile-picker-modal.ts +2 -2
  120. package/src/input/settings-modal-agent-policy.ts +6 -6
  121. package/src/input/settings-modal-subscriptions.ts +3 -3
  122. package/src/input/settings-modal.ts +14 -14
  123. package/src/main.ts +0 -2
  124. package/src/panels/automation-control-panel.ts +3 -3
  125. package/src/panels/builtin/agent.ts +1 -1
  126. package/src/panels/builtin/operations.ts +2 -2
  127. package/src/panels/builtin/session.ts +26 -18
  128. package/src/panels/builtin/shared.ts +6 -6
  129. package/src/panels/project-planning-panel.ts +1 -1
  130. package/src/panels/provider-health-domains.ts +3 -3
  131. package/src/panels/qr-panel.ts +44 -20
  132. package/src/panels/subscription-panel.ts +3 -3
  133. package/src/panels/system-messages-panel.ts +2 -2
  134. package/src/panels/tasks-panel.ts +4 -4
  135. package/src/planning/project-planning-coordinator.ts +2 -2
  136. package/src/renderer/agent-workspace.ts +52 -5
  137. package/src/renderer/block-actions.ts +1 -3
  138. package/src/renderer/help-overlay.ts +13 -8
  139. package/src/renderer/mcp-workspace.ts +32 -13
  140. package/src/renderer/model-picker-overlay.ts +0 -1
  141. package/src/renderer/model-workspace.ts +0 -2
  142. package/src/renderer/onboarding/onboarding-wizard.ts +20 -4
  143. package/src/renderer/panel-picker-overlay.ts +1 -1
  144. package/src/renderer/profile-picker-modal.ts +2 -2
  145. package/src/renderer/settings-modal-helpers.ts +23 -23
  146. package/src/renderer/settings-modal.ts +18 -18
  147. package/src/renderer/system-message.ts +1 -1
  148. package/src/runtime/agent-runtime-events.ts +129 -0
  149. package/src/runtime/bootstrap-command-parts.ts +3 -2
  150. package/src/runtime/bootstrap-core.ts +4 -64
  151. package/src/runtime/bootstrap-shell.ts +42 -4
  152. package/src/runtime/bootstrap.ts +8 -27
  153. package/src/runtime/connected-host-auth.ts +42 -0
  154. package/src/runtime/diagnostics/panels/panel-resources.ts +1 -1
  155. package/src/runtime/index.ts +1 -5
  156. package/src/runtime/onboarding/apply.ts +213 -0
  157. package/src/runtime/onboarding/derivation.ts +25 -6
  158. package/src/runtime/onboarding/snapshot.ts +4 -2
  159. package/src/runtime/onboarding/types.ts +28 -1
  160. package/src/runtime/onboarding/verify.ts +81 -2
  161. package/src/runtime/operator-token-cleanup.ts +2 -2
  162. package/src/runtime/services.ts +27 -12
  163. package/src/shell/service-settings-sync.ts +7 -7
  164. package/src/shell/ui-openers.ts +27 -38
  165. package/src/tools/agent-local-registry-tool.ts +10 -4
  166. package/src/tools/agent-tool-policy-guard.ts +5 -7
  167. package/src/version.ts +1 -1
  168. package/src/cli/package-verification.ts +0 -274
@@ -1,12 +1,17 @@
1
1
  import {
2
2
  createAgentRuntimeProfile,
3
+ createAgentRuntimeProfileFromDiscovered,
4
+ createAgentRuntimeProfileTemplateFromDiscovered,
5
+ clearAgentRuntimeProfileSelection,
3
6
  deleteAgentRuntimeProfile,
4
7
  exportAgentRuntimeProfileTemplate,
5
8
  importAgentRuntimeProfileTemplate,
6
9
  isAgentRuntimeProfileTemplateId,
7
10
  listAgentRuntimeProfiles,
8
11
  listAgentRuntimeProfileTemplates,
12
+ readAgentRuntimeProfileSelection,
9
13
  resolveAgentRuntimeProfileHome,
14
+ setAgentRuntimeProfileSelection,
10
15
  type AgentRuntimeProfileTemplateId,
11
16
  type AgentRuntimeProfileCommandResult,
12
17
  type AgentRuntimeProfileInfo,
@@ -16,6 +21,7 @@ import type { CliCommandOutput, GoodVibesCliParseResult } from './types.ts';
16
21
  interface ProfilesCommandRuntime {
17
22
  readonly cli: GoodVibesCliParseResult;
18
23
  readonly homeDirectory: string;
24
+ readonly workingDirectory: string;
19
25
  }
20
26
 
21
27
  function hasYes(args: readonly string[]): boolean {
@@ -53,6 +59,12 @@ function parseTemplate(args: readonly string[]): AgentRuntimeProfileTemplateId |
53
59
  throw new Error(`Unknown Agent starter profile template: ${raw}. Use profiles templates to list starters.`);
54
60
  }
55
61
 
62
+ function parseCsvFlag(args: readonly string[], names: readonly string[]): readonly string[] | undefined {
63
+ const raw = flagValue(args, names);
64
+ if (!raw) return undefined;
65
+ return raw.split(',').map((entry) => entry.trim()).filter(Boolean);
66
+ }
67
+
56
68
  function profileLine(profile: AgentRuntimeProfileInfo): string {
57
69
  const created = profile.createdAt ? ` created=${profile.createdAt}` : '';
58
70
  const starter = profile.starterTemplateId ? ` starter=${profile.starterTemplateId}` : '';
@@ -99,6 +111,41 @@ function renderProfilesResult(result: AgentRuntimeProfileCommandResult): string
99
111
  ` use: goodvibes-agent profiles create <name> --template ${result.data.template.id} --yes`,
100
112
  ].join('\n');
101
113
  }
114
+ if (result.kind === 'agent.profiles.template.from_discovered' && result.data?.template) {
115
+ return [
116
+ `Agent starter template created from discovered behavior: ${result.data.template.id}`,
117
+ ` name: ${result.data.template.name}`,
118
+ ` persona: ${result.data.template.personaName}`,
119
+ ` skills: ${result.data.template.skillNames.join(', ')}`,
120
+ ` routines: ${result.data.template.routineNames.join(', ')}`,
121
+ ` use: goodvibes-agent profiles create <name> --template ${result.data.template.id} --yes`,
122
+ ].join('\n');
123
+ }
124
+ if (result.kind === 'agent.profiles.default') {
125
+ const selected = result.data?.selectedProfile;
126
+ if (!selected) return 'No default Agent profile selected. Use: goodvibes-agent profiles use <name> --yes';
127
+ return [
128
+ `Default Agent profile: ${selected.id}${selected.exists ? '' : ' (missing)'}`,
129
+ ` home: ${selected.homeDirectory}`,
130
+ selected.selectedAt ? ` selectedAt: ${selected.selectedAt}` : '',
131
+ ` next launch: ${result.data?.nextCommand ?? 'goodvibes-agent'}`,
132
+ selected.exists ? '' : ' fix: create the profile again or clear it with goodvibes-agent profiles default clear --yes',
133
+ ].filter(Boolean).join('\n');
134
+ }
135
+ if (result.kind === 'agent.profiles.default.clear') {
136
+ return 'Default Agent profile cleared. Next launch uses the base Agent home unless --agent-profile is provided.';
137
+ }
138
+ if (result.kind === 'agent.profiles.create_from_discovered' && result.data?.profile && result.data.template) {
139
+ return [
140
+ `Agent profile created from discovered behavior: ${result.data.profile.id}`,
141
+ ` home: ${result.data.profile.homeDirectory}`,
142
+ ` starter: ${result.data.template.id}`,
143
+ ` persona: ${result.data.template.personaName}`,
144
+ ` skills: ${result.data.template.skillNames.join(', ')}`,
145
+ ` routines: ${result.data.template.routineNames.join(', ')}`,
146
+ ` launch: ${result.data.nextCommand ?? `goodvibes-agent --agent-profile ${result.data.profile.id}`}`,
147
+ ].join('\n');
148
+ }
102
149
  const profile = result.data?.profile;
103
150
  if (result.kind === 'agent.profiles.create' && profile) {
104
151
  const template = result.data?.appliedTemplate;
@@ -142,6 +189,51 @@ export async function handleProfilesCommand(runtime: ProfilesCommandRuntime): Pr
142
189
 
143
190
  if (sub === 'templates' || sub === 'starters') {
144
191
  const [templateAction, templateId, templatePath] = values;
192
+ if (templateAction === 'from-discovered' || templateAction === 'import-discovered') {
193
+ if (!templateId) {
194
+ const result: AgentRuntimeProfileCommandResult = {
195
+ ok: false,
196
+ kind: 'agent.profiles.error',
197
+ error: 'Usage: goodvibes-agent profiles templates from-discovered <id> [--name <name>] [--description <summary>] [--persona <name>] [--skills all|name,name] [--routines all|name,name] [--replace] --yes',
198
+ };
199
+ return {
200
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
201
+ exitCode: 2,
202
+ };
203
+ }
204
+ if (!hasYes(rawRest)) {
205
+ const result: AgentRuntimeProfileCommandResult = {
206
+ ok: false,
207
+ kind: 'agent.profiles.error',
208
+ error: `Refusing to create Agent starter template ${templateId} from discovered behavior without --yes.`,
209
+ };
210
+ return {
211
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
212
+ exitCode: 2,
213
+ };
214
+ }
215
+ const template = await createAgentRuntimeProfileTemplateFromDiscovered({
216
+ homeDirectory: runtime.homeDirectory,
217
+ workingDirectory: runtime.workingDirectory,
218
+ }, {
219
+ id: templateId,
220
+ name: flagValue(rawRest, ['--name']) ?? undefined,
221
+ description: flagValue(rawRest, ['--description']) ?? undefined,
222
+ persona: flagValue(rawRest, ['--persona']) ?? undefined,
223
+ skills: parseCsvFlag(rawRest, ['--skills']),
224
+ routines: parseCsvFlag(rawRest, ['--routines']),
225
+ replace: hasYes(rawRest) && rawRest.includes('--replace'),
226
+ });
227
+ const result: AgentRuntimeProfileCommandResult = {
228
+ ok: true,
229
+ kind: 'agent.profiles.template.from_discovered',
230
+ data: { template },
231
+ };
232
+ return {
233
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
234
+ exitCode: 0,
235
+ };
236
+ }
145
237
  if (templateAction === 'export') {
146
238
  if (!templateId || !templatePath) {
147
239
  const result: AgentRuntimeProfileCommandResult = {
@@ -221,6 +313,107 @@ export async function handleProfilesCommand(runtime: ProfilesCommandRuntime): Pr
221
313
  };
222
314
  }
223
315
 
316
+ if (sub === 'default') {
317
+ const target = values[0];
318
+ if (target === 'clear' || target === 'none' || target === 'base') {
319
+ if (!hasYes(rawRest)) {
320
+ const result: AgentRuntimeProfileCommandResult = {
321
+ ok: false,
322
+ kind: 'agent.profiles.error',
323
+ error: 'Refusing to clear the default Agent profile without --yes.',
324
+ };
325
+ return {
326
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
327
+ exitCode: 2,
328
+ };
329
+ }
330
+ clearAgentRuntimeProfileSelection(runtime.homeDirectory);
331
+ const result: AgentRuntimeProfileCommandResult = {
332
+ ok: true,
333
+ kind: 'agent.profiles.default.clear',
334
+ };
335
+ return {
336
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
337
+ exitCode: 0,
338
+ };
339
+ }
340
+ if (target) {
341
+ if (!hasYes(rawRest)) {
342
+ const result: AgentRuntimeProfileCommandResult = {
343
+ ok: false,
344
+ kind: 'agent.profiles.error',
345
+ error: `Refusing to set default Agent profile ${target} without --yes.`,
346
+ };
347
+ return {
348
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
349
+ exitCode: 2,
350
+ };
351
+ }
352
+ const selected = setAgentRuntimeProfileSelection(runtime.homeDirectory, target);
353
+ const result: AgentRuntimeProfileCommandResult = {
354
+ ok: true,
355
+ kind: 'agent.profiles.default',
356
+ data: {
357
+ selectedProfile: selected,
358
+ nextCommand: 'goodvibes-agent',
359
+ },
360
+ };
361
+ return {
362
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
363
+ exitCode: 0,
364
+ };
365
+ }
366
+ const selected = readAgentRuntimeProfileSelection(runtime.homeDirectory);
367
+ const result: AgentRuntimeProfileCommandResult = {
368
+ ok: true,
369
+ kind: 'agent.profiles.default',
370
+ data: selected ? { selectedProfile: selected, nextCommand: 'goodvibes-agent' } : undefined,
371
+ };
372
+ return {
373
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
374
+ exitCode: 0,
375
+ };
376
+ }
377
+
378
+ if (sub === 'use' || sub === 'select' || sub === 'switch') {
379
+ const name = values[0];
380
+ if (!name) {
381
+ const result: AgentRuntimeProfileCommandResult = {
382
+ ok: false,
383
+ kind: 'agent.profiles.error',
384
+ error: 'Usage: goodvibes-agent profiles use <name> --yes',
385
+ };
386
+ return {
387
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
388
+ exitCode: 2,
389
+ };
390
+ }
391
+ if (!hasYes(rawRest)) {
392
+ const result: AgentRuntimeProfileCommandResult = {
393
+ ok: false,
394
+ kind: 'agent.profiles.error',
395
+ error: `Refusing to set default Agent profile ${name} without --yes.`,
396
+ };
397
+ return {
398
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
399
+ exitCode: 2,
400
+ };
401
+ }
402
+ const selected = setAgentRuntimeProfileSelection(runtime.homeDirectory, name);
403
+ const result: AgentRuntimeProfileCommandResult = {
404
+ ok: true,
405
+ kind: 'agent.profiles.default',
406
+ data: {
407
+ selectedProfile: selected,
408
+ nextCommand: 'goodvibes-agent',
409
+ },
410
+ };
411
+ return {
412
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
413
+ exitCode: 0,
414
+ };
415
+ }
416
+
224
417
  if (sub === 'show' || sub === 'path' || sub === 'home') {
225
418
  const name = values[0];
226
419
  if (!name) {
@@ -290,6 +483,59 @@ export async function handleProfilesCommand(runtime: ProfilesCommandRuntime): Pr
290
483
  };
291
484
  }
292
485
 
486
+ if (sub === 'create-from-discovered' || sub === 'create-discovered') {
487
+ const name = values[0];
488
+ if (!name) {
489
+ const result: AgentRuntimeProfileCommandResult = {
490
+ ok: false,
491
+ kind: 'agent.profiles.error',
492
+ error: 'Usage: goodvibes-agent profiles create-from-discovered <name> [--template-id <id>] [--profile-name <display>] [--description <summary>] [--persona <name>] [--skills all|name,name] [--routines all|name,name] [--replace] --yes',
493
+ };
494
+ return {
495
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
496
+ exitCode: 2,
497
+ };
498
+ }
499
+ if (!hasYes(rawRest)) {
500
+ const result: AgentRuntimeProfileCommandResult = {
501
+ ok: false,
502
+ kind: 'agent.profiles.error',
503
+ error: `Refusing to create Agent profile ${name} from discovered behavior without --yes.`,
504
+ };
505
+ return {
506
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
507
+ exitCode: 2,
508
+ };
509
+ }
510
+ const created = await createAgentRuntimeProfileFromDiscovered({
511
+ homeDirectory: runtime.homeDirectory,
512
+ workingDirectory: runtime.workingDirectory,
513
+ }, {
514
+ profileName: name,
515
+ templateId: flagValue(rawRest, ['--template-id', '--starter-id']) ?? undefined,
516
+ name: flagValue(rawRest, ['--profile-name', '--name']) ?? undefined,
517
+ description: flagValue(rawRest, ['--description']) ?? undefined,
518
+ persona: flagValue(rawRest, ['--persona']) ?? undefined,
519
+ skills: parseCsvFlag(rawRest, ['--skills']),
520
+ routines: parseCsvFlag(rawRest, ['--routines']),
521
+ replace: rawRest.includes('--replace'),
522
+ });
523
+ const result: AgentRuntimeProfileCommandResult = {
524
+ ok: true,
525
+ kind: 'agent.profiles.create_from_discovered',
526
+ data: {
527
+ profile: created.profile,
528
+ appliedTemplate: created.profile.starterTemplateApplication,
529
+ template: created.template,
530
+ nextCommand: `goodvibes-agent --agent-profile ${created.profile.id}`,
531
+ },
532
+ };
533
+ return {
534
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
535
+ exitCode: 0,
536
+ };
537
+ }
538
+
293
539
  if (sub === 'delete' || sub === 'remove') {
294
540
  const name = values[0];
295
541
  if (!name) {
@@ -336,7 +582,7 @@ export async function handleProfilesCommand(runtime: ProfilesCommandRuntime): Pr
336
582
  const result: AgentRuntimeProfileCommandResult = {
337
583
  ok: false,
338
584
  kind: 'agent.profiles.error',
339
- error: 'Usage: goodvibes-agent profiles [list|templates|show <name>|create <name> [--template <id>] --yes|delete <name> --yes]',
585
+ error: 'Usage: goodvibes-agent profiles [list|templates|default [<name>|clear] --yes|use <name> --yes|show <name>|create <name> [--template <id>] --yes|delete <name> --yes]',
340
586
  };
341
587
  return {
342
588
  output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
@@ -1,6 +1,7 @@
1
1
  import { createShellPathService } from '@/runtime/index.ts';
2
2
  import { discoverRoutines, type DiscoveredRoutineRecord } from '../agent/routine-discovery.ts';
3
- import { AgentRoutineRegistry, type AgentRoutineRecord } from '../agent/routine-registry.ts';
3
+ import { AgentRoutineRegistry, evaluateAgentRoutineReadiness, type AgentRoutineRecord } from '../agent/routine-registry.ts';
4
+ import { buildAgentSkillRequirements, formatAgentSkillRequirement } from '../agent/skill-registry.ts';
4
5
  import {
5
6
  buildRoutineSchedulePreview,
6
7
  promoteRoutineToDaemonSchedule,
@@ -34,10 +35,26 @@ interface RoutinesCommandFailure {
34
35
  readonly error: string;
35
36
  }
36
37
 
38
+ interface ParsedRoutineOptions {
39
+ readonly positionals: readonly string[];
40
+ readonly flags: ReadonlyMap<string, string>;
41
+ readonly yes: boolean;
42
+ }
43
+
44
+ const ROUTINE_CREATE_USAGE = 'Usage: goodvibes-agent routines create --name <name> --description <summary> --steps <steps> [--tags a,b] [--triggers a,b] [--requires-env A,B] [--requires-command gh,jq] [--enabled]';
45
+
37
46
  function jsonOrText(runtime: CliCommandRuntime, value: unknown, text: string): string {
38
47
  return runtime.cli.flags.outputFormat === 'json' ? JSON.stringify(value, null, 2) : text;
39
48
  }
40
49
 
50
+ function commandFailure(runtime: CliCommandRuntime, kind: string, error: string, exitCode: number): CliCommandOutput {
51
+ const failure: RoutinesCommandFailure = { ok: false, kind, error };
52
+ return {
53
+ output: runtime.cli.flags.outputFormat === 'json' ? JSON.stringify(failure, null, 2) : error,
54
+ exitCode,
55
+ };
56
+ }
57
+
41
58
  function routineRegistry(runtime: CliCommandRuntime): AgentRoutineRegistry {
42
59
  return AgentRoutineRegistry.fromShellPaths(createShellPathService({
43
60
  workingDirectory: runtime.workingDirectory,
@@ -64,6 +81,47 @@ function splitList(value: string | undefined): readonly string[] {
64
81
  return value.split(',').map((entry) => entry.trim()).filter(Boolean);
65
82
  }
66
83
 
84
+ function parseRoutineOptions(args: readonly string[]): ParsedRoutineOptions {
85
+ const flags = new Map<string, string>();
86
+ const positionals: string[] = [];
87
+ let yes = false;
88
+ for (let index = 0; index < args.length; index += 1) {
89
+ const token = args[index] ?? '';
90
+ if (token === '--yes') {
91
+ yes = true;
92
+ continue;
93
+ }
94
+ if (token.startsWith('--')) {
95
+ const key = token.slice(2);
96
+ const next = args[index + 1];
97
+ if (next !== undefined && !next.startsWith('--')) {
98
+ flags.set(key, next);
99
+ index += 1;
100
+ } else {
101
+ flags.set(key, 'true');
102
+ }
103
+ continue;
104
+ }
105
+ positionals.push(token);
106
+ }
107
+ return { positionals, flags, yes };
108
+ }
109
+
110
+ function optionValue(options: ParsedRoutineOptions, key: string): string | undefined {
111
+ const value = options.flags.get(key)?.trim();
112
+ return value ? value : undefined;
113
+ }
114
+
115
+ function hasFlag(options: ParsedRoutineOptions, key: string): boolean {
116
+ return options.flags.get(key) === 'true';
117
+ }
118
+
119
+ function requiredOption(options: ParsedRoutineOptions, key: string): string {
120
+ const value = optionValue(options, key);
121
+ if (!value) throw new Error(`${ROUTINE_CREATE_USAGE}\nMissing --${key}.`);
122
+ return value;
123
+ }
124
+
67
125
  function parseImportFlags(args: readonly string[]): {
68
126
  readonly name: string;
69
127
  readonly enabled: boolean;
@@ -89,7 +147,9 @@ function parseImportFlags(args: readonly string[]): {
89
147
  function summarizeRoutine(routine: AgentRoutineRecord): string {
90
148
  const enabled = routine.enabled ? 'enabled' : 'disabled';
91
149
  const tags = routine.tags.length > 0 ? ` tags=${routine.tags.join(',')}` : '';
92
- return ` ${routine.id} ${enabled} ${routine.reviewState} starts=${routine.startCount} ${routine.name} - ${routine.description}${tags}`;
150
+ const readiness = evaluateAgentRoutineReadiness(routine);
151
+ const ready = readiness.ready ? 'ready' : `needs ${readiness.missing.map(formatAgentSkillRequirement).join(',')}`;
152
+ return ` ${routine.id} ${enabled} ${routine.reviewState} ${ready} starts=${routine.startCount} ${routine.name} - ${routine.description}${tags}`;
93
153
  }
94
154
 
95
155
  function summarizeDiscoveredRoutine(routine: DiscoveredRoutineRecord): string {
@@ -136,13 +196,21 @@ function discoveredRoutineFrontmatterList(routine: DiscoveredRoutineRecord, key:
136
196
  return splitList(value);
137
197
  }
138
198
 
139
- function renderRoutineList(title: string, path: string, routines: readonly AgentRoutineRecord[]): string {
199
+ function discoveredRoutineFrontmatterAnyList(routine: DiscoveredRoutineRecord, keys: readonly string[]): readonly string[] {
200
+ for (const key of keys) {
201
+ const values = discoveredRoutineFrontmatterList(routine, key);
202
+ if (values.length > 0) return values;
203
+ }
204
+ return [];
205
+ }
206
+
207
+ function renderRoutineList(title: string, path: string, routines: readonly AgentRoutineRecord[], emptyMessage?: string): string {
140
208
  if (routines.length === 0) {
141
209
  return [
142
210
  title,
143
- ' No local Agent routines yet.',
144
- ' Create routines inside the Agent TUI with /routines create, or create an Agent profile from a starter template.',
145
- ].join('\n');
211
+ ` ${emptyMessage ?? 'No local Agent routines yet.'}`,
212
+ emptyMessage ? '' : ' Open /agent routines in the TUI, or use goodvibes-agent routines create for scripted setup.',
213
+ ].filter(Boolean).join('\n');
146
214
  }
147
215
  return [
148
216
  `${title} (${routines.length})`,
@@ -152,10 +220,14 @@ function renderRoutineList(title: string, path: string, routines: readonly Agent
152
220
  }
153
221
 
154
222
  function renderRoutine(routine: AgentRoutineRecord): string {
223
+ const readiness = evaluateAgentRoutineReadiness(routine);
155
224
  return [
156
225
  `Routine ${routine.name}`,
157
226
  ` id: ${routine.id}`,
158
227
  ` enabled: ${routine.enabled ? 'yes' : 'no'}`,
228
+ ` readiness: ${readiness.ready ? 'ready' : 'needs setup'}`,
229
+ ` requirements: ${routine.requirements.map(formatAgentSkillRequirement).join(', ') || '(none)'}`,
230
+ readiness.missing.length > 0 ? ` missing: ${readiness.missing.map(formatAgentSkillRequirement).join(', ')}` : '',
159
231
  ` review: ${routine.reviewState}`,
160
232
  ` source: ${routine.source}`,
161
233
  ` provenance: ${routine.provenance}`,
@@ -235,15 +307,23 @@ export async function handleRoutinesCommand(runtime: CliCommandRuntime): Promise
235
307
  const registry = routineRegistry(runtime);
236
308
  const snapshot = registry.snapshot();
237
309
  const normalized = sub.toLowerCase();
238
- if (normalized === 'list' || normalized === 'enabled') {
239
- const routines = normalized === 'enabled' ? snapshot.enabledRoutines : snapshot.routines;
310
+ if (normalized === 'list' || normalized === 'enabled' || normalized === 'attention' || normalized === 'needs-setup') {
311
+ const routines = normalized === 'enabled'
312
+ ? snapshot.enabledRoutines
313
+ : normalized === 'attention' || normalized === 'needs-setup'
314
+ ? snapshot.routines.filter((routine) => !evaluateAgentRoutineReadiness(routine).ready)
315
+ : snapshot.routines;
240
316
  const value: RoutinesCommandSuccess<{
241
317
  readonly path: string;
242
318
  readonly routines: readonly AgentRoutineRecord[];
243
319
  readonly enabledCount: number;
244
320
  }> = {
245
321
  ok: true,
246
- kind: normalized === 'enabled' ? 'agent.routines.enabled' : 'agent.routines.list',
322
+ kind: normalized === 'enabled'
323
+ ? 'agent.routines.enabled'
324
+ : normalized === 'attention' || normalized === 'needs-setup'
325
+ ? 'agent.routines.attention'
326
+ : 'agent.routines.list',
247
327
  data: {
248
328
  path: snapshot.path,
249
329
  routines,
@@ -251,7 +331,16 @@ export async function handleRoutinesCommand(runtime: CliCommandRuntime): Promise
251
331
  },
252
332
  };
253
333
  return {
254
- output: jsonOrText(runtime, value, renderRoutineList(normalized === 'enabled' ? 'Enabled Agent routines' : 'Agent routines', snapshot.path, routines)),
334
+ output: jsonOrText(runtime, value, renderRoutineList(
335
+ normalized === 'enabled'
336
+ ? 'Enabled Agent routines'
337
+ : normalized === 'attention' || normalized === 'needs-setup'
338
+ ? 'Agent routines needing setup'
339
+ : 'Agent routines',
340
+ snapshot.path,
341
+ routines,
342
+ normalized === 'attention' || normalized === 'needs-setup' ? 'No local Agent routines need setup.' : undefined,
343
+ )),
255
344
  exitCode: 0,
256
345
  };
257
346
  }
@@ -317,6 +406,10 @@ export async function handleRoutinesCommand(runtime: CliCommandRuntime): Promise
317
406
  steps: discovered.steps,
318
407
  tags: discoveredRoutineFrontmatterList(discovered, 'tags'),
319
408
  triggers: discoveredRoutineFrontmatterList(discovered, 'triggers'),
409
+ requirements: buildAgentSkillRequirements({
410
+ env: discoveredRoutineFrontmatterAnyList(discovered, ['requiresEnv', 'requires-env', 'requires_env']),
411
+ commands: discoveredRoutineFrontmatterAnyList(discovered, ['requiresCommands', 'requires-commands', 'requires_commands', 'commands']),
412
+ }),
320
413
  enabled: parsed.enabled,
321
414
  source: 'imported',
322
415
  provenance: `discovered:${discovered.origin}:${discovered.path}`,
@@ -331,6 +424,41 @@ export async function handleRoutinesCommand(runtime: CliCommandRuntime): Promise
331
424
  exitCode: 0,
332
425
  };
333
426
  }
427
+ if (normalized === 'create') {
428
+ const options = parseRoutineOptions(rest);
429
+ try {
430
+ const routine = registry.create({
431
+ name: requiredOption(options, 'name'),
432
+ description: requiredOption(options, 'description'),
433
+ steps: optionValue(options, 'steps') ?? options.positionals.join(' ').trim(),
434
+ tags: splitList(optionValue(options, 'tags')),
435
+ triggers: splitList(optionValue(options, 'triggers')),
436
+ requirements: buildAgentSkillRequirements({
437
+ env: splitList(optionValue(options, 'requires-env')),
438
+ commands: splitList(optionValue(options, 'requires-command') ?? optionValue(options, 'requires-commands')),
439
+ }),
440
+ enabled: hasFlag(options, 'enabled'),
441
+ source: 'user',
442
+ provenance: 'cli',
443
+ });
444
+ const value: RoutinesCommandSuccess<AgentRoutineRecord> = {
445
+ ok: true,
446
+ kind: 'agent.routines.create',
447
+ data: routine,
448
+ };
449
+ return {
450
+ output: jsonOrText(runtime, value, `Created Agent routine ${routine.id}: ${routine.name}${routine.enabled ? ' (enabled)' : ''}`),
451
+ exitCode: 0,
452
+ };
453
+ } catch (error) {
454
+ return commandFailure(
455
+ runtime,
456
+ error instanceof Error && error.message.startsWith('Usage:') ? 'invalid_routine_command' : 'agent.routines.create.error',
457
+ error instanceof Error ? error.message : String(error),
458
+ error instanceof Error && error.message.startsWith('Usage:') ? 2 : 1,
459
+ );
460
+ }
461
+ }
334
462
  if (normalized === 'show') {
335
463
  const id = rest[0];
336
464
  if (!id) return { output: 'Usage: goodvibes-agent routines show <id>', exitCode: 2 };
@@ -396,7 +524,7 @@ export async function handleRoutinesCommand(runtime: CliCommandRuntime): Promise
396
524
  return handleRoutinePromotion(runtime, rest);
397
525
  }
398
526
  return {
399
- output: 'Usage: goodvibes-agent routines [list|enabled|discover|import-discovered <name> --yes|show <id>|receipts|reconcile|receipt <id>|promote <id> (--cron <expr>|--every <interval>|--at <iso-time>) [--delivery-channel <channel>|--delivery-route <route>|--delivery-webhook <url>] --yes]',
527
+ output: `Usage: goodvibes-agent routines [list|enabled|attention|discover|import-discovered <name> --yes|create --name <name> --description <summary> --steps <steps>|show <id>|receipts|reconcile|receipt <id>|promote <id> (--cron <expr>|--every <interval>|--at <iso-time>) [--delivery-channel <channel>|--delivery-route <route>|--delivery-webhook <url>] --yes]`,
400
528
  exitCode: 2,
401
529
  };
402
530
  }
@@ -32,7 +32,7 @@ export interface CliServiceLogPosture {
32
32
  readonly readError?: string;
33
33
  }
34
34
 
35
- export interface CliExternalDaemonLifecyclePosture {
35
+ export interface CliExternalHostLifecyclePosture {
36
36
  readonly platform: 'manual';
37
37
  readonly path: string;
38
38
  readonly installed: false;
@@ -54,7 +54,7 @@ export interface CliServicePosture {
54
54
  readonly restartOnFailure: boolean;
55
55
  readonly daemonEnabled: boolean;
56
56
  };
57
- readonly managed: CliExternalDaemonLifecyclePosture;
57
+ readonly managed: CliExternalHostLifecyclePosture;
58
58
  readonly endpoints: readonly CliServiceEndpointPosture[];
59
59
  readonly log: CliServiceLogPosture;
60
60
  readonly issues: readonly string[];
@@ -140,10 +140,10 @@ function resolveConfiguredLogPath(runtime: CliServiceRuntime): string | undefine
140
140
  return isAbsolute(trimmed) ? trimmed : join(runtime.homeDirectory, trimmed);
141
141
  }
142
142
 
143
- function createExternalDaemonLifecycle(logPath: string | undefined): CliExternalDaemonLifecyclePosture {
143
+ function createExternalHostLifecycle(logPath: string | undefined): CliExternalHostLifecyclePosture {
144
144
  return {
145
145
  platform: 'manual',
146
- path: 'connected GoodVibes services',
146
+ path: 'connected GoodVibes host',
147
147
  installed: false,
148
148
  autostart: false,
149
149
  running: false,
@@ -151,7 +151,7 @@ function createExternalDaemonLifecycle(logPath: string | undefined): CliExternal
151
151
  commandPreview: 'managed outside goodvibes-agent',
152
152
  suggestedCommands: [],
153
153
  lastAction: 'status',
154
- pidPath: 'connected GoodVibes services',
154
+ pidPath: 'connected GoodVibes host',
155
155
  lastError: null,
156
156
  };
157
157
  }
@@ -184,7 +184,7 @@ export async function buildCliServicePosture(
184
184
  const issues: string[] = [];
185
185
 
186
186
  if (serverBackedEnabled && !config.enabled) {
187
- issues.push('Connected-service settings are present, but Agent service ownership is disabled by design.');
187
+ issues.push('Connected-host settings are present, but Agent host ownership is disabled by design.');
188
188
  }
189
189
  for (const endpoint of endpoints) {
190
190
  if (endpoint.enabled && options.probe && endpoint.reachable === false) {
@@ -209,7 +209,7 @@ export async function buildCliServicePosture(
209
209
 
210
210
  return {
211
211
  config,
212
- managed: createExternalDaemonLifecycle(configuredLogPath),
212
+ managed: createExternalHostLifecycle(configuredLogPath),
213
213
  endpoints,
214
214
  log,
215
215
  issues,
@@ -223,16 +223,16 @@ function yesNo(value: boolean): string {
223
223
  export function formatCliServicePosture(posture: CliServicePosture, json = false): string {
224
224
  if (json) return JSON.stringify(posture, null, 2);
225
225
  return [
226
- 'GoodVibes Agent runtime connection diagnostics',
227
- ' ownership: managed outside goodvibes-agent',
228
- ' Agent owns lifecycle: no',
226
+ 'GoodVibes Agent connected-host diagnostics',
227
+ ' lifecycle owner: outside goodvibes-agent',
228
+ ' Agent starts connected host: no',
229
229
  ` external host config present: ${yesNo(posture.config.enabled)}`,
230
230
  ' external host lifecycle config: ignored by Agent',
231
231
  ` legacy host switch present: ${yesNo(posture.config.daemonEnabled)}`,
232
232
  ` log: ${posture.log.path ?? 'n/a'} (${posture.log.exists ? 'present' : 'missing'})`,
233
233
  ...(posture.log.readError ? [` log read error: ${posture.log.readError}`] : []),
234
234
  '',
235
- 'Connected GoodVibes API checks:',
235
+ 'Connected API checks:',
236
236
  ...posture.endpoints.map((endpoint) =>
237
237
  ` ${endpoint.label}: enabled=${yesNo(endpoint.enabled)} ${endpoint.binding.hostMode} ${endpoint.binding.host}:${endpoint.binding.port} posture=${endpoint.bindPosture.label}${endpoint.reachable === undefined ? '' : ` reachable=${yesNo(endpoint.reachable)}`}`,
238
238
  ),