@pellux/goodvibes-agent 0.1.114 → 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.
@@ -4,6 +4,11 @@ import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
4
4
  import { AgentPersonaRegistry } from './persona-registry.ts';
5
5
  import { AgentRoutineRegistry } from './routine-registry.ts';
6
6
  import { AgentSkillRegistry } from './skill-registry.ts';
7
+ import { discoverPersonas, type DiscoveredPersonaRecord } from './persona-discovery.ts';
8
+ import { discoverRoutines, type DiscoveredRoutineRecord } from './routine-discovery.ts';
9
+ import { discoverSkills, type SkillRecord } from './skill-discovery.ts';
10
+ import type { ShellPathService } from '@/runtime/index.ts';
11
+ import { STARTER_TEMPLATES, type AgentRuntimeProfileStarterTemplate, type AgentRuntimeProfileStarterTemplateFile } from './runtime-profile-starters.ts';
7
12
 
8
13
  export type AgentRuntimeProfileTemplateId = string;
9
14
  export type AgentRuntimeProfileTemplateSource = 'builtin' | 'local';
@@ -45,6 +50,21 @@ export interface CreateAgentRuntimeProfileOptions {
45
50
  readonly templateId?: AgentRuntimeProfileTemplateId;
46
51
  }
47
52
 
53
+ export interface CreateAgentRuntimeProfileTemplateFromDiscoveredOptions {
54
+ readonly id: AgentRuntimeProfileTemplateId;
55
+ readonly name?: string;
56
+ readonly description?: string;
57
+ readonly persona?: string;
58
+ readonly skills?: readonly string[];
59
+ readonly routines?: readonly string[];
60
+ readonly replace?: boolean;
61
+ }
62
+
63
+ export interface CreateAgentRuntimeProfileFromDiscoveredOptions extends Omit<CreateAgentRuntimeProfileTemplateFromDiscoveredOptions, 'id'> {
64
+ readonly profileName: string;
65
+ readonly templateId?: AgentRuntimeProfileTemplateId;
66
+ }
67
+
48
68
  export interface AgentRuntimeProfileCommandResult {
49
69
  readonly ok: boolean;
50
70
  readonly kind:
@@ -53,6 +73,8 @@ export interface AgentRuntimeProfileCommandResult {
53
73
  | 'agent.profiles.templates'
54
74
  | 'agent.profiles.template.export'
55
75
  | 'agent.profiles.template.import'
76
+ | 'agent.profiles.template.from_discovered'
77
+ | 'agent.profiles.create_from_discovered'
56
78
  | 'agent.profiles.create'
57
79
  | 'agent.profiles.delete'
58
80
  | 'agent.profiles.error';
@@ -71,336 +93,6 @@ export interface AgentRuntimeProfileCommandResult {
71
93
  const PROFILE_CREATED_FILE = 'profile.json';
72
94
  const PROFILE_ID_PATTERN = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/;
73
95
 
74
- interface AgentRuntimeProfileStarterTemplate extends AgentRuntimeProfileTemplateSummary {
75
- readonly persona: {
76
- readonly name: string;
77
- readonly description: string;
78
- readonly body: string;
79
- readonly tags: readonly string[];
80
- readonly triggers: readonly string[];
81
- };
82
- readonly skills: readonly {
83
- readonly name: string;
84
- readonly description: string;
85
- readonly procedure: string;
86
- readonly triggers: readonly string[];
87
- readonly tags: readonly string[];
88
- }[];
89
- readonly routines: readonly {
90
- readonly name: string;
91
- readonly description: string;
92
- readonly steps: string;
93
- readonly triggers: readonly string[];
94
- readonly tags: readonly string[];
95
- }[];
96
- }
97
-
98
- interface AgentRuntimeProfileStarterTemplateFile {
99
- readonly version: 1;
100
- readonly template: AgentRuntimeProfileStarterTemplate;
101
- }
102
-
103
- const STARTER_TEMPLATES: readonly AgentRuntimeProfileStarterTemplate[] = [
104
- {
105
- id: 'household',
106
- source: 'builtin',
107
- name: 'Household Operator',
108
- description: 'Coordinate household tasks, home service checks, shared routines, and family logistics.',
109
- personaName: 'Household Operator',
110
- skillNames: ['Household Triage', 'Home Service Check'],
111
- routineNames: ['Weekly Household Review'],
112
- persona: {
113
- name: 'Household Operator',
114
- description: 'Practical assistant for home operations, shared chores, services, and family logistics.',
115
- body: [
116
- 'Operate as a calm household coordinator.',
117
- 'Track preferences, routines, device/service notes, and recurring decisions in Agent-local memory after they are durable and non-secret.',
118
- 'Use read-only daemon/operator routes for status checks. Require explicit approval for purchases, messages to other people, service changes, deletions, or secret handling.',
119
- 'Keep replies concrete: next action, owner, date, and open question when one is needed.',
120
- ].join('\n'),
121
- tags: ['household', 'home', 'coordination'],
122
- triggers: ['home', 'household', 'family', 'chores', 'errands'],
123
- },
124
- skills: [
125
- {
126
- name: 'Household Triage',
127
- description: 'Turn a household request into owner, urgency, next step, and reminder/delegation posture.',
128
- procedure: [
129
- '1. Identify whether the task is information, coordination, purchase, repair, or reminder.',
130
- '2. Check Agent-local memory for known preferences or constraints before asking repeat questions.',
131
- '3. Propose the next non-destructive action. Ask before external messages, payments, account changes, or device/service changes.',
132
- '4. Record durable non-secret decisions locally with provenance.',
133
- ].join('\n'),
134
- triggers: ['plan household', 'home task', 'family logistics'],
135
- tags: ['household', 'triage'],
136
- },
137
- {
138
- name: 'Home Service Check',
139
- description: 'Review configured services and surface actionable household status without taking hidden action.',
140
- procedure: [
141
- '1. Inspect configured read-only status, approval, schedule, and knowledge routes.',
142
- '2. Summarize degraded services, pending approvals, and stale routines.',
143
- '3. Suggest explicit commands for approved follow-up instead of mutating services directly.',
144
- ].join('\n'),
145
- triggers: ['home status', 'service check', 'household check'],
146
- tags: ['household', 'ops'],
147
- },
148
- ],
149
- routines: [
150
- {
151
- name: 'Weekly Household Review',
152
- description: 'Review open household commitments, pending approvals, and stale home notes.',
153
- steps: [
154
- '1. List pending local routines, workplan tasks, and approvals.',
155
- '2. Summarize what changed since the last review from local Agent state.',
156
- '3. Ask for confirmation before scheduling, sending messages, purchasing, or changing services.',
157
- '4. Record reviewed preferences and stale notes locally.',
158
- ].join('\n'),
159
- triggers: ['weekly review', 'household review'],
160
- tags: ['household', 'review'],
161
- },
162
- ],
163
- },
164
- {
165
- id: 'research',
166
- source: 'builtin',
167
- name: 'Research Analyst',
168
- description: 'Source-grounded research, brief generation, question tracking, and evidence review.',
169
- personaName: 'Research Analyst',
170
- skillNames: ['Source-grounded Brief', 'Research Gap Tracker'],
171
- routineNames: ['Research Packet Review'],
172
- persona: {
173
- name: 'Research Analyst',
174
- description: 'Evidence-first analyst for research questions, summaries, and decision support.',
175
- body: [
176
- 'Prefer primary sources and cite provenance clearly.',
177
- 'Use Agent Knowledge only through the isolated Agent knowledge routes.',
178
- 'Separate findings, confidence, gaps, and recommendations.',
179
- 'Do not store secrets or unsupported claims as durable knowledge.',
180
- ].join('\n'),
181
- tags: ['research', 'analysis', 'evidence'],
182
- triggers: ['research', 'brief', 'compare', 'investigate'],
183
- },
184
- skills: [
185
- {
186
- name: 'Source-grounded Brief',
187
- description: 'Produce concise findings with source provenance and confidence.',
188
- procedure: [
189
- '1. Clarify the decision or question if the request is ambiguous.',
190
- '2. Search current sources when freshness matters.',
191
- '3. Return answer, evidence, confidence, gaps, and suggested follow-up.',
192
- '4. Save durable, reviewed, non-secret facts to Agent-local memory only when useful later.',
193
- ].join('\n'),
194
- triggers: ['brief me', 'research this', 'compare options'],
195
- tags: ['research', 'briefing'],
196
- },
197
- {
198
- name: 'Research Gap Tracker',
199
- description: 'Maintain open research questions and stale assumptions.',
200
- procedure: [
201
- '1. Extract unknowns, weak evidence, and stale facts from the current task.',
202
- '2. Convert actionable follow-up into workplan or local routine guidance only after explicit user direction.',
203
- '3. Mark resolved gaps with source and date.',
204
- ].join('\n'),
205
- triggers: ['research gaps', 'unknowns', 'follow up'],
206
- tags: ['research', 'quality'],
207
- },
208
- ],
209
- routines: [
210
- {
211
- name: 'Research Packet Review',
212
- description: 'Review recent research notes for stale assumptions and missing citations.',
213
- steps: [
214
- '1. Inspect local memory/skills/personas relevant to the current topic.',
215
- '2. Identify claims lacking provenance.',
216
- '3. Recommend refresh searches for time-sensitive claims.',
217
- '4. Mark stale local notes instead of deleting them without explicit command intent.',
218
- ].join('\n'),
219
- triggers: ['review research', 'research packet'],
220
- tags: ['research', 'review'],
221
- },
222
- ],
223
- },
224
- {
225
- id: 'travel',
226
- source: 'builtin',
227
- name: 'Travel Planner',
228
- description: 'Trip planning, itinerary decisions, packing, local constraints, and travel follow-through.',
229
- personaName: 'Travel Planner',
230
- skillNames: ['Trip Decision Matrix', 'Travel Checklist Builder'],
231
- routineNames: ['Pre-trip Readiness Review'],
232
- persona: {
233
- name: 'Travel Planner',
234
- description: 'Travel planning assistant focused on constraints, itinerary tradeoffs, and readiness.',
235
- body: [
236
- 'Track destinations, dates, preferences, constraints, and open decisions locally.',
237
- 'Search current details for prices, schedules, visa/rule changes, weather, and safety.',
238
- 'Ask before bookings, purchases, external messages, or account changes.',
239
- 'Keep recommendations practical and time-aware.',
240
- ].join('\n'),
241
- tags: ['travel', 'planning', 'logistics'],
242
- triggers: ['trip', 'travel', 'itinerary', 'flight', 'hotel'],
243
- },
244
- skills: [
245
- {
246
- name: 'Trip Decision Matrix',
247
- description: 'Compare travel options against user constraints and live facts.',
248
- procedure: [
249
- '1. List hard constraints: dates, budget, location, accessibility, work needs, and risk tolerance.',
250
- '2. Search current schedule/price/rule data when decisions depend on fresh details.',
251
- '3. Present ranked options with tradeoffs and next checks.',
252
- '4. Ask for confirmation before bookings or payments.',
253
- ].join('\n'),
254
- triggers: ['choose travel', 'compare hotels', 'compare flights'],
255
- tags: ['travel', 'decision'],
256
- },
257
- {
258
- name: 'Travel Checklist Builder',
259
- description: 'Create trip-specific prep lists and reminders from known constraints.',
260
- procedure: [
261
- '1. Infer trip type and constraints from the current conversation and local memory.',
262
- '2. Build checklist sections for documents, packing, transport, lodging, work, health, and communications.',
263
- '3. Keep tasks local until the user explicitly requests scheduling or external coordination.',
264
- ].join('\n'),
265
- triggers: ['packing list', 'trip checklist', 'travel prep'],
266
- tags: ['travel', 'checklist'],
267
- },
268
- ],
269
- routines: [
270
- {
271
- name: 'Pre-trip Readiness Review',
272
- description: 'Check documents, reservations, transport, packing, weather, and open decisions.',
273
- steps: [
274
- '1. Review trip dates, locations, reservations, and open decisions from local Agent state.',
275
- '2. Refresh current weather, schedule, and rule data when needed.',
276
- '3. Summarize blockers and next confirmed action.',
277
- '4. Ask before external sends, purchases, or reservation changes.',
278
- ].join('\n'),
279
- triggers: ['pre trip review', 'before travel'],
280
- tags: ['travel', 'review'],
281
- },
282
- ],
283
- },
284
- {
285
- id: 'operations',
286
- source: 'builtin',
287
- name: 'Operations Lead',
288
- description: 'Operational monitoring, incident triage, approvals, schedules, and service health.',
289
- personaName: 'Operations Lead',
290
- skillNames: ['Incident Intake', 'Approval Review'],
291
- routineNames: ['Daily Operations Sweep'],
292
- persona: {
293
- name: 'Operations Lead',
294
- description: 'Operator persona for systems, incidents, runbooks, approvals, and service posture.',
295
- body: [
296
- 'Favor explicit state, logs, health, approvals, and next action.',
297
- 'Use read-only daemon/operator routes by default.',
298
- 'Require explicit confirmation for run, pause, resume, cancel, retry, approve, deny, service changes, or writes.',
299
- 'Delegate code/build fixes to GoodVibes TUI when explicitly requested.',
300
- ].join('\n'),
301
- tags: ['operations', 'incident', 'runbook'],
302
- triggers: ['incident', 'ops', 'approval', 'automation', 'service'],
303
- },
304
- skills: [
305
- {
306
- name: 'Incident Intake',
307
- description: 'Convert a symptom into severity, suspected system, evidence, and next safe checks.',
308
- procedure: [
309
- '1. Identify symptom, affected surface, time window, severity, and user impact.',
310
- '2. Pull read-only status, approvals, schedules, runs, and workplan summaries.',
311
- '3. Separate evidence from hypothesis.',
312
- '4. Ask before mutating services, automation, schedules, or approvals.',
313
- ].join('\n'),
314
- triggers: ['incident', 'outage', 'broken', 'triage'],
315
- tags: ['operations', 'incident'],
316
- },
317
- {
318
- name: 'Approval Review',
319
- description: 'Summarize pending approvals with risk, route, and required decision.',
320
- procedure: [
321
- '1. List pending approvals and classify risk labels.',
322
- '2. Explain what each approval would allow.',
323
- '3. Only approve, deny, or cancel from exact user command with confirmation.',
324
- ].join('\n'),
325
- triggers: ['approvals', 'pending approval', 'review approvals'],
326
- tags: ['operations', 'approvals'],
327
- },
328
- ],
329
- routines: [
330
- {
331
- name: 'Daily Operations Sweep',
332
- description: 'Inspect service posture, pending approvals, failed runs, and stale tasks.',
333
- steps: [
334
- '1. Refresh daemon status, compat, approvals, workplan, automation snapshot, runs, schedules, and capacity.',
335
- '2. Summarize degraded items and blocked work.',
336
- '3. Recommend exact follow-up commands with confirmation gates for side effects.',
337
- ].join('\n'),
338
- triggers: ['daily ops', 'operations sweep'],
339
- tags: ['operations', 'review'],
340
- },
341
- ],
342
- },
343
- {
344
- id: 'personal-productivity',
345
- source: 'builtin',
346
- name: 'Personal Productivity',
347
- description: 'Task capture, weekly planning, focus blocks, reminders, and decision hygiene.',
348
- personaName: 'Personal Productivity Coach',
349
- skillNames: ['Inbox Zero Triage', 'Focus Block Planner'],
350
- routineNames: ['Weekly Personal Planning'],
351
- persona: {
352
- name: 'Personal Productivity Coach',
353
- description: 'Personal assistant for task capture, planning, prioritization, and follow-through.',
354
- body: [
355
- 'Keep planning lightweight and action-oriented.',
356
- 'Capture durable preferences, commitments, and constraints locally when they are useful later.',
357
- 'Prefer one clear next action over broad plans.',
358
- 'Ask before sending messages, spending money, changing services, or deleting records.',
359
- ].join('\n'),
360
- tags: ['productivity', 'planning', 'personal'],
361
- triggers: ['plan my day', 'prioritize', 'tasks', 'focus'],
362
- },
363
- skills: [
364
- {
365
- name: 'Inbox Zero Triage',
366
- description: 'Sort loose tasks into do, delegate, defer, drop, or ask-for-info.',
367
- procedure: [
368
- '1. Capture each item as an outcome and next action.',
369
- '2. Classify by urgency, effort, dependency, and consequence.',
370
- '3. Recommend a short ordered list for today and parking lot for later.',
371
- '4. Store durable commitments locally with source and date.',
372
- ].join('\n'),
373
- triggers: ['triage tasks', 'organize tasks', 'inbox'],
374
- tags: ['productivity', 'triage'],
375
- },
376
- {
377
- name: 'Focus Block Planner',
378
- description: 'Design realistic focus blocks around constraints and energy.',
379
- procedure: [
380
- '1. Identify available time, constraints, and high-value outcome.',
381
- '2. Split work into 25-90 minute blocks with breakpoints.',
382
- '3. Keep schedule changes local unless the user explicitly asks for calendar or external updates.',
383
- ].join('\n'),
384
- triggers: ['focus block', 'plan my day', 'deep work'],
385
- tags: ['productivity', 'focus'],
386
- },
387
- ],
388
- routines: [
389
- {
390
- name: 'Weekly Personal Planning',
391
- description: 'Review commitments, priorities, routines, and open decisions for the week.',
392
- steps: [
393
- '1. Review local tasks, routines, memory, and recent decisions.',
394
- '2. Identify top outcomes, blockers, and decisions needed from the user.',
395
- '3. Suggest a small weekly plan and ask before creating external reminders or sending messages.',
396
- ].join('\n'),
397
- triggers: ['weekly planning', 'plan my week'],
398
- tags: ['productivity', 'review'],
399
- },
400
- ],
401
- },
402
- ];
403
-
404
96
  export function normalizeAgentRuntimeProfileId(value: string): string {
405
97
  return value
406
98
  .trim()
@@ -514,6 +206,72 @@ function parseStringArray(value: unknown): readonly string[] {
514
206
  return value.filter((entry): entry is string => typeof entry === 'string').map((entry) => entry.trim()).filter(Boolean);
515
207
  }
516
208
 
209
+ function splitFrontmatterList(value: string | undefined): readonly string[] {
210
+ if (!value) return [];
211
+ return value.split(',').map((entry) => entry.trim()).filter(Boolean);
212
+ }
213
+
214
+ function normalizedLookupValues(value: string, path?: string): readonly string[] {
215
+ const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
216
+ const basename = path?.split(/[\\/]/).pop()?.replace(/\.md$/i, '') ?? '';
217
+ return [value, slug, path ?? '', basename].map((entry) => entry.trim().toLowerCase()).filter(Boolean);
218
+ }
219
+
220
+ function selectDiscoveredRecord<T extends { readonly name: string; readonly path: string }>(
221
+ records: readonly T[],
222
+ selector: string | undefined,
223
+ label: string,
224
+ ): T {
225
+ if (records.length === 0) throw new Error(`No discovered Agent ${label} files found.`);
226
+ if (!selector?.trim()) return records[0]!;
227
+ const lookup = selector.trim().toLowerCase();
228
+ const match = records.find((record) => normalizedLookupValues(record.name, record.path).includes(lookup));
229
+ if (!match) throw new Error(`Unknown discovered Agent ${label}: ${selector}.`);
230
+ return match;
231
+ }
232
+
233
+ function selectDiscoveredRecords<T extends { readonly name: string; readonly path: string }>(
234
+ records: readonly T[],
235
+ selectors: readonly string[] | undefined,
236
+ label: string,
237
+ ): readonly T[] {
238
+ if (records.length === 0) throw new Error(`No discovered Agent ${label} files found.`);
239
+ if (!selectors || selectors.length === 0 || selectors.includes('all')) return records;
240
+ return selectors.map((selector) => selectDiscoveredRecord(records, selector, label));
241
+ }
242
+
243
+ function discoveredSkillToTemplate(skill: SkillRecord): AgentRuntimeProfileStarterTemplate['skills'][number] {
244
+ if (!skill.body.trim()) throw new Error(`Discovered Agent skill ${skill.name} has no procedure body.`);
245
+ return {
246
+ name: skill.name,
247
+ description: skill.description || `Imported skill from ${skill.origin} skill file.`,
248
+ procedure: skill.body,
249
+ triggers: splitFrontmatterList(skill.frontmatter.triggers),
250
+ tags: splitFrontmatterList(skill.frontmatter.tags),
251
+ };
252
+ }
253
+
254
+ function discoveredRoutineToTemplate(routine: DiscoveredRoutineRecord): AgentRuntimeProfileStarterTemplate['routines'][number] {
255
+ if (!routine.steps.trim()) throw new Error(`Discovered Agent routine ${routine.name} has no steps.`);
256
+ return {
257
+ name: routine.name,
258
+ description: routine.description || `Imported routine from ${routine.origin} markdown file.`,
259
+ steps: routine.steps,
260
+ triggers: splitFrontmatterList(routine.frontmatter.triggers),
261
+ tags: splitFrontmatterList(routine.frontmatter.tags),
262
+ };
263
+ }
264
+
265
+ function discoveredPersonaToTemplate(persona: DiscoveredPersonaRecord): AgentRuntimeProfileStarterTemplate['persona'] {
266
+ return {
267
+ name: persona.name,
268
+ description: persona.description || `Imported persona from ${persona.origin} markdown file.`,
269
+ body: persona.body,
270
+ tags: splitFrontmatterList(persona.frontmatter.tags),
271
+ triggers: splitFrontmatterList(persona.frontmatter.triggers),
272
+ };
273
+ }
274
+
517
275
  function readTemplateTextBlock(value: unknown, field: string): string {
518
276
  if (typeof value !== 'string' || !value.trim()) throw new Error(`Starter template ${field} is required.`);
519
277
  return value.trim();
@@ -650,6 +408,68 @@ export function importAgentRuntimeProfileTemplate(baseHomeDirectory: string, sou
650
408
  return summarizeTemplate({ ...parsed, source: 'local', path: target });
651
409
  }
652
410
 
411
+ export async function createAgentRuntimeProfileTemplateFromDiscovered(
412
+ shellPaths: Pick<ShellPathService, 'homeDirectory' | 'workingDirectory'>,
413
+ options: CreateAgentRuntimeProfileTemplateFromDiscoveredOptions,
414
+ ): Promise<AgentRuntimeProfileTemplateSummary> {
415
+ const id = assertValidAgentRuntimeProfileId(options.id);
416
+ const [personas, skills, routines] = await Promise.all([
417
+ discoverPersonas(shellPaths),
418
+ discoverSkills(shellPaths),
419
+ discoverRoutines(shellPaths),
420
+ ]);
421
+ const selectedPersona = selectDiscoveredRecord(personas, options.persona, 'persona');
422
+ const selectedSkills = selectDiscoveredRecords(skills, options.skills, 'skill');
423
+ const selectedRoutines = selectDiscoveredRecords(routines, options.routines, 'routine');
424
+ const target = join(getAgentRuntimeProfileTemplatesRoot(shellPaths.homeDirectory), `${id}.json`);
425
+ if (existsSync(target) && options.replace !== true) {
426
+ throw new Error(`Agent starter template already exists: ${id}. Rerun with --replace to overwrite it.`);
427
+ }
428
+ const persona = discoveredPersonaToTemplate(selectedPersona);
429
+ const template: AgentRuntimeProfileStarterTemplate = {
430
+ id,
431
+ source: 'local',
432
+ path: target,
433
+ name: options.name?.trim() || `${persona.name} Starter`,
434
+ description: options.description?.trim() || 'Agent starter template assembled from discovered local persona, skill, and routine files.',
435
+ personaName: persona.name,
436
+ skillNames: selectedSkills.map((skill) => skill.name),
437
+ routineNames: selectedRoutines.map((routine) => routine.name),
438
+ persona,
439
+ skills: selectedSkills.map(discoveredSkillToTemplate),
440
+ routines: selectedRoutines.map(discoveredRoutineToTemplate),
441
+ };
442
+ mkdirSync(dirname(target), { recursive: true });
443
+ writeFileSync(target, `${JSON.stringify(templateFilePayload(template), null, 2)}\n`, 'utf-8');
444
+ return summarizeTemplate(template);
445
+ }
446
+
447
+ export async function createAgentRuntimeProfileFromDiscovered(
448
+ shellPaths: Pick<ShellPathService, 'homeDirectory' | 'workingDirectory'>,
449
+ options: CreateAgentRuntimeProfileFromDiscoveredOptions,
450
+ ): Promise<{
451
+ readonly profile: AgentRuntimeProfileInfo;
452
+ readonly template: AgentRuntimeProfileTemplateSummary;
453
+ }> {
454
+ const profileId = assertValidAgentRuntimeProfileId(options.profileName);
455
+ const resolution = resolveAgentRuntimeProfileHome(shellPaths.homeDirectory, profileId);
456
+ if (existsSync(resolution.homeDirectory)) {
457
+ throw new Error(`Agent profile already exists: ${resolution.id}`);
458
+ }
459
+ const templateId = assertValidAgentRuntimeProfileId(options.templateId ?? profileId);
460
+ const template = await createAgentRuntimeProfileTemplateFromDiscovered(shellPaths, {
461
+ id: templateId,
462
+ name: options.name,
463
+ description: options.description,
464
+ persona: options.persona,
465
+ skills: options.skills,
466
+ routines: options.routines,
467
+ replace: options.replace,
468
+ });
469
+ const profile = createAgentRuntimeProfile(shellPaths.homeDirectory, profileId, { templateId: template.id });
470
+ return { profile, template };
471
+ }
472
+
653
473
  function createMissingSkill(registry: AgentSkillRegistry, template: AgentRuntimeProfileStarterTemplate['skills'][number]): string {
654
474
  const existing = registry.get(template.name);
655
475
  if (existing) return existing.id;
package/src/cli/help.ts CHANGED
@@ -157,9 +157,9 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
157
157
  examples: ['providers', 'providers inspect openai-subscriber', 'providers use openai openai:gpt-5.4'],
158
158
  },
159
159
  profiles: {
160
- usage: ['profiles list', 'profiles templates', 'profiles templates export <id> <path> --yes', 'profiles templates import <path> --yes', 'profiles show <name>', 'profiles create <name> [--template <id>] --yes', 'profiles delete <name> --yes', '--agent-profile <name>'],
160
+ usage: ['profiles list', 'profiles templates', 'profiles templates from-discovered <id> --yes', 'profiles create-from-discovered <name> --yes', 'profiles templates export <id> <path> --yes', 'profiles templates import <path> --yes', 'profiles show <name>', 'profiles create <name> [--template <id>] --yes', 'profiles delete <name> --yes', '--agent-profile <name>'],
161
161
  summary: 'Create and inspect isolated Agent profile homes, with starter templates for household, research, travel, operations, personal productivity, and local imported starters. A profile changes Agent-local config, sessions, memory, personas, skills, routines, and setup paths without changing connected GoodVibes services.',
162
- examples: ['profiles templates', 'profiles templates export research ./research-starter.json --yes', 'profiles templates import ./research-starter.json --yes', 'profiles create household --template household --yes', '--agent-profile household status'],
162
+ examples: ['profiles templates', 'profiles create-from-discovered research-desk --yes', 'profiles templates from-discovered research-desk --yes', 'profiles templates export research ./research-starter.json --yes', 'profiles templates import ./research-starter.json --yes', 'profiles create household --template household --yes', '--agent-profile household status'],
163
163
  },
164
164
  personas: {
165
165
  usage: [