@pellux/goodvibes-agent 0.1.113 → 0.1.116

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 (31) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +4 -0
  3. package/dist/package/main.js +1659 -324
  4. package/docs/getting-started.md +4 -0
  5. package/package.json +1 -1
  6. package/src/agent/behavior-discovery-summary.ts +167 -0
  7. package/src/agent/persona-discovery.ts +117 -0
  8. package/src/agent/routine-discovery.ts +115 -0
  9. package/src/agent/runtime-profile-starters.ts +331 -0
  10. package/src/agent/runtime-profile.ts +150 -330
  11. package/src/cli/help.ts +11 -3
  12. package/src/cli/local-library-command.ts +81 -1
  13. package/src/cli/profiles-command.ts +128 -0
  14. package/src/cli/routines-command.ts +156 -1
  15. package/src/input/agent-workspace-basic-command-editor-submission.ts +534 -0
  16. package/src/input/agent-workspace-basic-command-editors.ts +77 -395
  17. package/src/input/agent-workspace-categories.ts +7 -0
  18. package/src/input/agent-workspace-command-editor.ts +4 -0
  19. package/src/input/agent-workspace-setup.ts +33 -13
  20. package/src/input/agent-workspace-snapshot.ts +6 -0
  21. package/src/input/agent-workspace-types.ts +6 -0
  22. package/src/input/commands/agent-runtime-profile-runtime.ts +73 -4
  23. package/src/input/commands/personas-runtime.ts +87 -2
  24. package/src/input/commands/routines-runtime.ts +87 -2
  25. package/src/input/onboarding/onboarding-wizard-operator-steps.ts +46 -9
  26. package/src/input/onboarding/onboarding-wizard-steps.ts +1 -1
  27. package/src/renderer/agent-workspace.ts +26 -0
  28. package/src/runtime/onboarding/derivation.ts +20 -1
  29. package/src/runtime/onboarding/snapshot.ts +2 -0
  30. package/src/runtime/onboarding/types.ts +2 -0
  31. package/src/version.ts +1 -1
@@ -1,4 +1,5 @@
1
1
  import { createShellPathService } from '@/runtime/index.ts';
2
+ import { discoverPersonas, type DiscoveredPersonaRecord } from '../agent/persona-discovery.ts';
2
3
  import { AgentPersonaRegistry, type AgentPersonaRecord } from '../agent/persona-registry.ts';
3
4
  import { discoverSkills, type SkillRecord } from '../agent/skill-discovery.ts';
4
5
  import {
@@ -156,6 +157,50 @@ function renderPersona(persona: AgentPersonaRecord, activePersonaId: string | nu
156
157
  ].filter((line): line is string => Boolean(line)).join('\n');
157
158
  }
158
159
 
160
+ function summarizeDiscoveredPersona(persona: DiscoveredPersonaRecord): string {
161
+ const description = persona.description ? ` - ${persona.description}` : '';
162
+ return [
163
+ ` ${persona.name} ${persona.origin}${description}`,
164
+ ` path: ${persona.path}`,
165
+ ].join('\n');
166
+ }
167
+
168
+ function renderDiscoveredPersonaList(personas: readonly DiscoveredPersonaRecord[]): string {
169
+ if (personas.length === 0) {
170
+ return [
171
+ 'Discovered Agent persona files',
172
+ ' No persona markdown files found in Agent persona folders.',
173
+ ' Search roots: .goodvibes/personas, .goodvibes/agent/personas, .goodvibes/agents, ~/.goodvibes/personas, ~/.goodvibes/agent/personas, ~/.goodvibes/agents',
174
+ ].join('\n');
175
+ }
176
+ return [
177
+ `Discovered Agent persona files (${personas.length})`,
178
+ ...personas.map(summarizeDiscoveredPersona),
179
+ '',
180
+ 'Import one with: goodvibes-agent personas import-discovered <name> --yes',
181
+ ].join('\n');
182
+ }
183
+
184
+ function discoveredPersonaLookupValues(persona: DiscoveredPersonaRecord): readonly string[] {
185
+ const slug = persona.name.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
186
+ const basename = persona.path.split(/[\\/]/).pop()?.replace(/\.md$/i, '') ?? '';
187
+ return [persona.name, slug, persona.path, basename]
188
+ .map((value) => value.trim().toLowerCase())
189
+ .filter(Boolean);
190
+ }
191
+
192
+ function findDiscoveredPersona(personas: readonly DiscoveredPersonaRecord[], idOrName: string): DiscoveredPersonaRecord | null {
193
+ const lookup = idOrName.trim().toLowerCase();
194
+ if (!lookup) return null;
195
+ return personas.find((persona) => discoveredPersonaLookupValues(persona).includes(lookup)) ?? null;
196
+ }
197
+
198
+ function discoveredPersonaFrontmatterList(persona: DiscoveredPersonaRecord, key: string): readonly string[] {
199
+ const value = persona.frontmatter[key];
200
+ if (!value) return [];
201
+ return value.split(',').map((entry) => entry.trim()).filter(Boolean);
202
+ }
203
+
159
204
  function summarizeSkill(skill: AgentSkillRecord): string {
160
205
  const enabled = skill.enabled ? 'enabled' : 'disabled';
161
206
  const tags = skill.tags.length > 0 ? ` tags=${skill.tags.join(',')}` : '';
@@ -281,7 +326,7 @@ function renderBundle(bundle: AgentSkillBundleRecord): string {
281
326
  }
282
327
 
283
328
  function usagePersonas(): string {
284
- return 'Usage: goodvibes-agent personas [list|active|search <query>|show <id>|create|update <id>|use <id>|clear|review <id>|stale <id> <reason>|delete <id> --yes]';
329
+ return 'Usage: goodvibes-agent personas [list|active|discover|import-discovered <name> --yes|search <query>|show <id>|create|update <id>|use <id>|clear|review <id>|stale <id> <reason>|delete <id> --yes]';
285
330
  }
286
331
 
287
332
  function usageSkills(): string {
@@ -312,6 +357,41 @@ export async function handlePersonasCommand(runtime: CliCommandRuntime): Promise
312
357
  if (!active) return failure(runtime, 'agent.personas.active_missing', 'No active Agent persona.', 1);
313
358
  return success(runtime, 'agent.personas.active', active, renderPersona(active, active.id));
314
359
  }
360
+ if (normalized === 'discover') {
361
+ const discovered = await discoverPersonas(shellPaths(runtime));
362
+ return success(runtime, 'agent.personas.discover', { personas: discovered }, renderDiscoveredPersonaList(discovered));
363
+ }
364
+ if (normalized === 'import-discovered' || normalized === 'import-persona') {
365
+ const options = parseOptions(rest);
366
+ const name = options.positionals.join(' ').trim();
367
+ if (!name) return failure(runtime, 'invalid_persona_command', 'Usage: goodvibes-agent personas import-discovered <name> [--use] --yes', 2);
368
+ const discovered = findDiscoveredPersona(await discoverPersonas(shellPaths(runtime)), name);
369
+ if (!discovered) {
370
+ return failure(runtime, 'persona_discovery_not_found', `Unknown discovered Agent persona: ${name}\nRun goodvibes-agent personas discover to inspect available persona files.`, 1);
371
+ }
372
+ if (!hasFlag(options, 'yes')) {
373
+ return success(runtime, 'agent.personas.import_discovered.preview', { persona: discovered }, [
374
+ 'Agent persona import preview',
375
+ ` name: ${discovered.name}`,
376
+ ` origin: ${discovered.origin}`,
377
+ ` path: ${discovered.path}`,
378
+ ` description: ${discovered.description || '(none)'}`,
379
+ ` body characters: ${discovered.body.length}`,
380
+ ' next: rerun with --yes to import into the Agent-local persona registry',
381
+ ].join('\n'));
382
+ }
383
+ const persona = registry.create({
384
+ name: discovered.name,
385
+ description: discovered.description || `Imported persona from ${discovered.origin} markdown file.`,
386
+ body: discovered.body,
387
+ tags: discoveredPersonaFrontmatterList(discovered, 'tags'),
388
+ triggers: discoveredPersonaFrontmatterList(discovered, 'triggers'),
389
+ source: 'imported',
390
+ provenance: `discovered:${discovered.origin}:${discovered.path}`,
391
+ });
392
+ if (hasFlag(options, 'use')) registry.setActive(persona.id);
393
+ return success(runtime, 'agent.personas.import_discovered', persona, `Imported Agent persona ${persona.id}: ${persona.name}${hasFlag(options, 'use') ? ' (active)' : ''}`);
394
+ }
315
395
  if (normalized === 'search' || normalized === 'find') {
316
396
  const query = rest.join(' ').trim();
317
397
  const results = registry.search(query);
@@ -1,5 +1,7 @@
1
1
  import {
2
2
  createAgentRuntimeProfile,
3
+ createAgentRuntimeProfileFromDiscovered,
4
+ createAgentRuntimeProfileTemplateFromDiscovered,
3
5
  deleteAgentRuntimeProfile,
4
6
  exportAgentRuntimeProfileTemplate,
5
7
  importAgentRuntimeProfileTemplate,
@@ -16,6 +18,7 @@ import type { CliCommandOutput, GoodVibesCliParseResult } from './types.ts';
16
18
  interface ProfilesCommandRuntime {
17
19
  readonly cli: GoodVibesCliParseResult;
18
20
  readonly homeDirectory: string;
21
+ readonly workingDirectory: string;
19
22
  }
20
23
 
21
24
  function hasYes(args: readonly string[]): boolean {
@@ -53,6 +56,12 @@ function parseTemplate(args: readonly string[]): AgentRuntimeProfileTemplateId |
53
56
  throw new Error(`Unknown Agent starter profile template: ${raw}. Use profiles templates to list starters.`);
54
57
  }
55
58
 
59
+ function parseCsvFlag(args: readonly string[], names: readonly string[]): readonly string[] | undefined {
60
+ const raw = flagValue(args, names);
61
+ if (!raw) return undefined;
62
+ return raw.split(',').map((entry) => entry.trim()).filter(Boolean);
63
+ }
64
+
56
65
  function profileLine(profile: AgentRuntimeProfileInfo): string {
57
66
  const created = profile.createdAt ? ` created=${profile.createdAt}` : '';
58
67
  const starter = profile.starterTemplateId ? ` starter=${profile.starterTemplateId}` : '';
@@ -99,6 +108,27 @@ function renderProfilesResult(result: AgentRuntimeProfileCommandResult): string
99
108
  ` use: goodvibes-agent profiles create <name> --template ${result.data.template.id} --yes`,
100
109
  ].join('\n');
101
110
  }
111
+ if (result.kind === 'agent.profiles.template.from_discovered' && result.data?.template) {
112
+ return [
113
+ `Agent starter template created from discovered behavior: ${result.data.template.id}`,
114
+ ` name: ${result.data.template.name}`,
115
+ ` persona: ${result.data.template.personaName}`,
116
+ ` skills: ${result.data.template.skillNames.join(', ')}`,
117
+ ` routines: ${result.data.template.routineNames.join(', ')}`,
118
+ ` use: goodvibes-agent profiles create <name> --template ${result.data.template.id} --yes`,
119
+ ].join('\n');
120
+ }
121
+ if (result.kind === 'agent.profiles.create_from_discovered' && result.data?.profile && result.data.template) {
122
+ return [
123
+ `Agent profile created from discovered behavior: ${result.data.profile.id}`,
124
+ ` home: ${result.data.profile.homeDirectory}`,
125
+ ` starter: ${result.data.template.id}`,
126
+ ` persona: ${result.data.template.personaName}`,
127
+ ` skills: ${result.data.template.skillNames.join(', ')}`,
128
+ ` routines: ${result.data.template.routineNames.join(', ')}`,
129
+ ` launch: ${result.data.nextCommand ?? `goodvibes-agent --agent-profile ${result.data.profile.id}`}`,
130
+ ].join('\n');
131
+ }
102
132
  const profile = result.data?.profile;
103
133
  if (result.kind === 'agent.profiles.create' && profile) {
104
134
  const template = result.data?.appliedTemplate;
@@ -142,6 +172,51 @@ export async function handleProfilesCommand(runtime: ProfilesCommandRuntime): Pr
142
172
 
143
173
  if (sub === 'templates' || sub === 'starters') {
144
174
  const [templateAction, templateId, templatePath] = values;
175
+ if (templateAction === 'from-discovered' || templateAction === 'import-discovered') {
176
+ if (!templateId) {
177
+ const result: AgentRuntimeProfileCommandResult = {
178
+ ok: false,
179
+ kind: 'agent.profiles.error',
180
+ 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',
181
+ };
182
+ return {
183
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
184
+ exitCode: 2,
185
+ };
186
+ }
187
+ if (!hasYes(rawRest)) {
188
+ const result: AgentRuntimeProfileCommandResult = {
189
+ ok: false,
190
+ kind: 'agent.profiles.error',
191
+ error: `Refusing to create Agent starter template ${templateId} from discovered behavior without --yes.`,
192
+ };
193
+ return {
194
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
195
+ exitCode: 2,
196
+ };
197
+ }
198
+ const template = await createAgentRuntimeProfileTemplateFromDiscovered({
199
+ homeDirectory: runtime.homeDirectory,
200
+ workingDirectory: runtime.workingDirectory,
201
+ }, {
202
+ id: templateId,
203
+ name: flagValue(rawRest, ['--name']) ?? undefined,
204
+ description: flagValue(rawRest, ['--description']) ?? undefined,
205
+ persona: flagValue(rawRest, ['--persona']) ?? undefined,
206
+ skills: parseCsvFlag(rawRest, ['--skills']),
207
+ routines: parseCsvFlag(rawRest, ['--routines']),
208
+ replace: hasYes(rawRest) && rawRest.includes('--replace'),
209
+ });
210
+ const result: AgentRuntimeProfileCommandResult = {
211
+ ok: true,
212
+ kind: 'agent.profiles.template.from_discovered',
213
+ data: { template },
214
+ };
215
+ return {
216
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
217
+ exitCode: 0,
218
+ };
219
+ }
145
220
  if (templateAction === 'export') {
146
221
  if (!templateId || !templatePath) {
147
222
  const result: AgentRuntimeProfileCommandResult = {
@@ -290,6 +365,59 @@ export async function handleProfilesCommand(runtime: ProfilesCommandRuntime): Pr
290
365
  };
291
366
  }
292
367
 
368
+ if (sub === 'create-from-discovered' || sub === 'create-discovered') {
369
+ const name = values[0];
370
+ if (!name) {
371
+ const result: AgentRuntimeProfileCommandResult = {
372
+ ok: false,
373
+ kind: 'agent.profiles.error',
374
+ 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',
375
+ };
376
+ return {
377
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
378
+ exitCode: 2,
379
+ };
380
+ }
381
+ if (!hasYes(rawRest)) {
382
+ const result: AgentRuntimeProfileCommandResult = {
383
+ ok: false,
384
+ kind: 'agent.profiles.error',
385
+ error: `Refusing to create Agent profile ${name} from discovered behavior without --yes.`,
386
+ };
387
+ return {
388
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
389
+ exitCode: 2,
390
+ };
391
+ }
392
+ const created = await createAgentRuntimeProfileFromDiscovered({
393
+ homeDirectory: runtime.homeDirectory,
394
+ workingDirectory: runtime.workingDirectory,
395
+ }, {
396
+ profileName: name,
397
+ templateId: flagValue(rawRest, ['--template-id', '--starter-id']) ?? undefined,
398
+ name: flagValue(rawRest, ['--profile-name', '--name']) ?? undefined,
399
+ description: flagValue(rawRest, ['--description']) ?? undefined,
400
+ persona: flagValue(rawRest, ['--persona']) ?? undefined,
401
+ skills: parseCsvFlag(rawRest, ['--skills']),
402
+ routines: parseCsvFlag(rawRest, ['--routines']),
403
+ replace: rawRest.includes('--replace'),
404
+ });
405
+ const result: AgentRuntimeProfileCommandResult = {
406
+ ok: true,
407
+ kind: 'agent.profiles.create_from_discovered',
408
+ data: {
409
+ profile: created.profile,
410
+ appliedTemplate: created.profile.starterTemplateApplication,
411
+ template: created.template,
412
+ nextCommand: `goodvibes-agent --agent-profile ${created.profile.id}`,
413
+ },
414
+ };
415
+ return {
416
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
417
+ exitCode: 0,
418
+ };
419
+ }
420
+
293
421
  if (sub === 'delete' || sub === 'remove') {
294
422
  const name = values[0];
295
423
  if (!name) {
@@ -1,4 +1,5 @@
1
1
  import { createShellPathService } from '@/runtime/index.ts';
2
+ import { discoverRoutines, type DiscoveredRoutineRecord } from '../agent/routine-discovery.ts';
2
3
  import { AgentRoutineRegistry, type AgentRoutineRecord } from '../agent/routine-registry.ts';
3
4
  import {
4
5
  buildRoutineSchedulePreview,
@@ -44,6 +45,13 @@ function routineRegistry(runtime: CliCommandRuntime): AgentRoutineRegistry {
44
45
  }));
45
46
  }
46
47
 
48
+ function shellPaths(runtime: CliCommandRuntime): ReturnType<typeof createShellPathService> {
49
+ return createShellPathService({
50
+ workingDirectory: runtime.workingDirectory,
51
+ homeDirectory: runtime.homeDirectory,
52
+ });
53
+ }
54
+
47
55
  function routineReceiptStore(runtime: CliCommandRuntime): RoutineScheduleReceiptStore {
48
56
  return RoutineScheduleReceiptStore.fromShellPaths(createShellPathService({
49
57
  workingDirectory: runtime.workingDirectory,
@@ -51,12 +59,83 @@ function routineReceiptStore(runtime: CliCommandRuntime): RoutineScheduleReceipt
51
59
  }));
52
60
  }
53
61
 
62
+ function splitList(value: string | undefined): readonly string[] {
63
+ if (!value) return [];
64
+ return value.split(',').map((entry) => entry.trim()).filter(Boolean);
65
+ }
66
+
67
+ function parseImportFlags(args: readonly string[]): {
68
+ readonly name: string;
69
+ readonly enabled: boolean;
70
+ readonly yes: boolean;
71
+ } {
72
+ const positionals: string[] = [];
73
+ let enabled = false;
74
+ let yes = false;
75
+ for (const arg of args) {
76
+ if (arg === '--enabled') {
77
+ enabled = true;
78
+ continue;
79
+ }
80
+ if (arg === '--yes') {
81
+ yes = true;
82
+ continue;
83
+ }
84
+ positionals.push(arg);
85
+ }
86
+ return { name: positionals.join(' ').trim(), enabled, yes };
87
+ }
88
+
54
89
  function summarizeRoutine(routine: AgentRoutineRecord): string {
55
90
  const enabled = routine.enabled ? 'enabled' : 'disabled';
56
91
  const tags = routine.tags.length > 0 ? ` tags=${routine.tags.join(',')}` : '';
57
92
  return ` ${routine.id} ${enabled} ${routine.reviewState} starts=${routine.startCount} ${routine.name} - ${routine.description}${tags}`;
58
93
  }
59
94
 
95
+ function summarizeDiscoveredRoutine(routine: DiscoveredRoutineRecord): string {
96
+ const description = routine.description ? ` - ${routine.description}` : '';
97
+ return [
98
+ ` ${routine.name} ${routine.origin}${description}`,
99
+ ` path: ${routine.path}`,
100
+ ].join('\n');
101
+ }
102
+
103
+ function renderDiscoveredRoutineList(routines: readonly DiscoveredRoutineRecord[]): string {
104
+ if (routines.length === 0) {
105
+ return [
106
+ 'Discovered Agent routine files',
107
+ ' No routine markdown files found in Agent routine folders.',
108
+ ' Search roots: .goodvibes/routines, .goodvibes/agent/routines, ~/.goodvibes/routines, ~/.goodvibes/agent/routines',
109
+ ].join('\n');
110
+ }
111
+ return [
112
+ `Discovered Agent routine files (${routines.length})`,
113
+ ...routines.map(summarizeDiscoveredRoutine),
114
+ '',
115
+ 'Import one with: goodvibes-agent routines import-discovered <name> --yes',
116
+ ].join('\n');
117
+ }
118
+
119
+ function discoveredRoutineLookupValues(routine: DiscoveredRoutineRecord): readonly string[] {
120
+ const slug = routine.name.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
121
+ const basename = routine.path.split(/[\\/]/).pop()?.replace(/\.md$/i, '') ?? '';
122
+ return [routine.name, slug, routine.path, basename]
123
+ .map((value) => value.trim().toLowerCase())
124
+ .filter(Boolean);
125
+ }
126
+
127
+ function findDiscoveredRoutine(routines: readonly DiscoveredRoutineRecord[], idOrName: string): DiscoveredRoutineRecord | null {
128
+ const lookup = idOrName.trim().toLowerCase();
129
+ if (!lookup) return null;
130
+ return routines.find((routine) => discoveredRoutineLookupValues(routine).includes(lookup)) ?? null;
131
+ }
132
+
133
+ function discoveredRoutineFrontmatterList(routine: DiscoveredRoutineRecord, key: string): readonly string[] {
134
+ const value = routine.frontmatter[key];
135
+ if (!value) return [];
136
+ return splitList(value);
137
+ }
138
+
60
139
  function renderRoutineList(title: string, path: string, routines: readonly AgentRoutineRecord[]): string {
61
140
  if (routines.length === 0) {
62
141
  return [
@@ -176,6 +255,82 @@ export async function handleRoutinesCommand(runtime: CliCommandRuntime): Promise
176
255
  exitCode: 0,
177
256
  };
178
257
  }
258
+ if (normalized === 'discover') {
259
+ const discovered = await discoverRoutines(shellPaths(runtime));
260
+ const value: RoutinesCommandSuccess<{ readonly routines: readonly DiscoveredRoutineRecord[] }> = {
261
+ ok: true,
262
+ kind: 'agent.routines.discover',
263
+ data: { routines: discovered },
264
+ };
265
+ return {
266
+ output: jsonOrText(runtime, value, renderDiscoveredRoutineList(discovered)),
267
+ exitCode: 0,
268
+ };
269
+ }
270
+ if (normalized === 'import-discovered' || normalized === 'import-routine') {
271
+ const parsed = parseImportFlags(rest);
272
+ if (!parsed.name) {
273
+ const failure: RoutinesCommandFailure = {
274
+ ok: false,
275
+ kind: 'invalid_routine_command',
276
+ error: 'Usage: goodvibes-agent routines import-discovered <name> [--enabled] --yes',
277
+ };
278
+ return {
279
+ output: runtime.cli.flags.outputFormat === 'json' ? JSON.stringify(failure, null, 2) : failure.error,
280
+ exitCode: 2,
281
+ };
282
+ }
283
+ const discovered = findDiscoveredRoutine(await discoverRoutines(shellPaths(runtime)), parsed.name);
284
+ if (!discovered) {
285
+ const failure: RoutinesCommandFailure = {
286
+ ok: false,
287
+ kind: 'routine_discovery_not_found',
288
+ error: `Unknown discovered Agent routine: ${parsed.name}\nRun goodvibes-agent routines discover to inspect available routine files.`,
289
+ };
290
+ return {
291
+ output: runtime.cli.flags.outputFormat === 'json' ? JSON.stringify(failure, null, 2) : failure.error,
292
+ exitCode: 1,
293
+ };
294
+ }
295
+ if (!parsed.yes) {
296
+ const value: RoutinesCommandSuccess<{ readonly routine: DiscoveredRoutineRecord }> = {
297
+ ok: true,
298
+ kind: 'agent.routines.import_discovered.preview',
299
+ data: { routine: discovered },
300
+ };
301
+ return {
302
+ output: jsonOrText(runtime, value, [
303
+ 'Agent routine import preview',
304
+ ` name: ${discovered.name}`,
305
+ ` origin: ${discovered.origin}`,
306
+ ` path: ${discovered.path}`,
307
+ ` description: ${discovered.description || '(none)'}`,
308
+ ` steps characters: ${discovered.steps.length}`,
309
+ ' next: rerun with --yes to import into the Agent-local routine registry',
310
+ ].join('\n')),
311
+ exitCode: 0,
312
+ };
313
+ }
314
+ const routine = registry.create({
315
+ name: discovered.name,
316
+ description: discovered.description || `Imported routine from ${discovered.origin} markdown file.`,
317
+ steps: discovered.steps,
318
+ tags: discoveredRoutineFrontmatterList(discovered, 'tags'),
319
+ triggers: discoveredRoutineFrontmatterList(discovered, 'triggers'),
320
+ enabled: parsed.enabled,
321
+ source: 'imported',
322
+ provenance: `discovered:${discovered.origin}:${discovered.path}`,
323
+ });
324
+ const value: RoutinesCommandSuccess<AgentRoutineRecord> = {
325
+ ok: true,
326
+ kind: 'agent.routines.import_discovered',
327
+ data: routine,
328
+ };
329
+ return {
330
+ output: jsonOrText(runtime, value, `Imported Agent routine ${routine.id}: ${routine.name}${routine.enabled ? ' (enabled)' : ''}`),
331
+ exitCode: 0,
332
+ };
333
+ }
179
334
  if (normalized === 'show') {
180
335
  const id = rest[0];
181
336
  if (!id) return { output: 'Usage: goodvibes-agent routines show <id>', exitCode: 2 };
@@ -241,7 +396,7 @@ export async function handleRoutinesCommand(runtime: CliCommandRuntime): Promise
241
396
  return handleRoutinePromotion(runtime, rest);
242
397
  }
243
398
  return {
244
- output: 'Usage: goodvibes-agent routines [list|enabled|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]',
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]',
245
400
  exitCode: 2,
246
401
  };
247
402
  }