@pellux/goodvibes-agent 0.1.33 → 0.1.35

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to GoodVibes Agent will be recorded here.
4
4
 
5
+ ## 0.1.35 - 2026-05-31
6
+
7
+ - 2c25d5e Add local starter profile import export
8
+
9
+ ## 0.1.34 - 2026-05-31
10
+
11
+ - 28838cc Add curated Agent profile starters
12
+
5
13
  ## 0.1.33 - 2026-05-31
6
14
 
7
15
  - bfe0127 Add profile portability workspace coverage
package/README.md CHANGED
@@ -49,12 +49,15 @@ Use `goodvibes-agent capabilities` or `/capabilities` to inspect the OpenClaw/He
49
49
  Use isolated Agent runtime profiles when one machine needs separate operator identities or local state:
50
50
 
51
51
  ```sh
52
- goodvibes-agent profiles create household --yes
52
+ goodvibes-agent profiles templates
53
+ goodvibes-agent profiles create household --template household --yes
54
+ goodvibes-agent profiles templates export research ./research-starter.json --yes
55
+ goodvibes-agent profiles templates import ./research-starter.json --yes
53
56
  goodvibes-agent --agent-profile household status
54
57
  GOODVIBES_AGENT_HOME=/path/to/agent-home goodvibes-agent status
55
58
  ```
56
59
 
57
- Profiles isolate Agent-local config, sessions, local memory, personas, skills, routines, and setup state. The daemon is still external and shared unless your daemon host is separately configured otherwise.
60
+ Profiles isolate Agent-local config, sessions, local memory, personas, skills, routines, and setup state. Starter templates seed local personas, skills, and routines for household, research, travel, operations, and personal productivity profiles; exported starter JSON can be edited and re-imported as a local starter. The daemon is still external and shared unless your daemon host is separately configured otherwise.
58
61
 
59
62
  Local Agent behavior is editable from the TUI:
60
63
 
package/docs/README.md CHANGED
@@ -20,7 +20,8 @@ Important baseline constraints:
20
20
  - Agent does not start, stop, restart, install, uninstall, or own daemon/listener/web/service lifecycle.
21
21
  - Agent Knowledge/Wiki uses only `/api/goodvibes-agent/knowledge/*`; there is no default Knowledge/Wiki, HomeGraph, or Home Assistant fallback.
22
22
  - Agent exposes `goodvibes-agent capabilities` and `/capabilities` to compare OpenClaw/Hermes capability targets against current Agent readiness and configuration paths.
23
- - Agent supports isolated runtime homes with `GOODVIBES_AGENT_HOME=<path>` and named profile homes with `goodvibes-agent profiles create <name> --yes` plus `--agent-profile <name>`.
23
+ - Agent supports isolated runtime homes with `GOODVIBES_AGENT_HOME=<path>` and named profile homes with `goodvibes-agent profiles create <name> --template <starter> --yes` plus `--agent-profile <name>`.
24
+ - Agent ships starter profile templates for household, research, travel, operations, and personal productivity local state; `profiles templates export/import` supports local custom starters.
24
25
  - Local personas, routines, and Agent skills are stored under the Agent surface root and are injected only into the serial Agent conversation.
25
26
  - Normal assistant chat is not coding-session delegation.
26
27
  - Build/fix/review delegation to GoodVibes TUI must be explicit; WRFC is not the default Agent behavior.
@@ -48,12 +48,15 @@ GOODVIBES_AGENT_HOME=/path/to/agent-home goodvibes-agent status
48
48
  Use named runtime profiles for repeatable local identities:
49
49
 
50
50
  ```sh
51
- goodvibes-agent profiles create household --yes
51
+ goodvibes-agent profiles templates
52
+ goodvibes-agent profiles create household --template household --yes
53
+ goodvibes-agent profiles templates export research ./research-starter.json --yes
54
+ goodvibes-agent profiles templates import ./research-starter.json --yes
52
55
  goodvibes-agent --agent-profile household status
53
56
  goodvibes-agent --agent-profile household
54
57
  ```
55
58
 
56
- Named profiles isolate Agent-local config, sessions, memory, personas, skills, routines, and setup state under a profile-specific home. They do not start or isolate the external daemon by themselves.
59
+ Named profiles isolate Agent-local config, sessions, memory, personas, skills, routines, and setup state under a profile-specific home. Starter templates seed local personas, skills, and routines for household, research, travel, operations, and personal productivity profiles; exported starter JSON can be edited and re-imported as a local starter. They do not start or isolate the external daemon by themselves.
57
60
 
58
61
  ## Local Personas, Routines, And Skills
59
62
 
@@ -57,7 +57,7 @@ Primary sources used for the benchmark:
57
57
  | Tools/MCP | Broad toolsets, MCP, browser, media, terminal, files | GoodVibes SDK tools with Agent policy guards and MCP/provider integrations |
58
58
  | Voice/media/canvas/nodes | Voice, TTS, mobile nodes, live canvas, browser automation | GoodVibes media/voice/browser/node primitives with an Agent workspace for setup, image input, browser posture, MCP, and remote/node inspection |
59
59
  | Build/code work | Direct terminal/file/code tools and subagents | Explicit delegation to GoodVibes TUI; local WRFC/spawn fanout blocked |
60
- | Profiles | Independent profiles with own config/memory/skills/gateway | `GOODVIBES_AGENT_HOME` and named `--agent-profile` homes isolate Agent-local state; the Agent workspace exposes profile and portability flows; daemon remains external |
60
+ | Profiles | Independent profiles with own config/memory/skills/gateway | `GOODVIBES_AGENT_HOME` and named `--agent-profile` homes isolate Agent-local state; starter templates seed local personas/skills/routines; starter JSON can be exported/imported for local custom lanes; the Agent workspace exposes profile and portability flows; daemon remains external |
61
61
  | Security | DM pairing, approvals, sandboxing, allowlists | Daemon approvals, auth diagnostics, secret refs, confirmation gates, model-tool policy |
62
62
 
63
63
  ## Exceed Targets
@@ -75,7 +75,7 @@ GoodVibes Agent should exceed OpenClaw/Hermes by making these properties true fr
75
75
 
76
76
  - Live daemon account health and last delivery errors in the Channels workspace once a stable read-only route is available.
77
77
  - Artifact and multimodal Agent Knowledge ingest affordances once Agent-specific routes are stable.
78
- - Curated profile starter templates for household, research, travel, operations, and personal productivity profiles.
78
+ - Guided starter profile authoring inside the fullscreen Agent workspace.
79
79
  - Artifact and multimodal Agent Knowledge ingestion when the isolated Agent route accepts artifact-backed media.
80
80
  - Delegation receipts and artifact review inside the operator workspace.
81
81
  - Approval center with route risk labels and saved policy presets.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-agent",
3
- "version": "0.1.33",
3
+ "version": "0.1.35",
4
4
  "private": false,
5
5
  "description": "Near-fork GoodVibes operator assistant with the GoodVibes TUI shell, renderer, input, fullscreen workspace, and daemon-connected Agent product brain.",
6
6
  "type": "module",
@@ -1,5 +1,23 @@
1
1
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
2
- import { join } from 'node:path';
2
+ import { dirname, join } from 'node:path';
3
+ import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
4
+ import { AgentPersonaRegistry } from './persona-registry.ts';
5
+ import { AgentRoutineRegistry } from './routine-registry.ts';
6
+ import { AgentSkillRegistry } from './skill-registry.ts';
7
+
8
+ export type AgentRuntimeProfileTemplateId = string;
9
+ export type AgentRuntimeProfileTemplateSource = 'builtin' | 'local';
10
+
11
+ export interface AgentRuntimeProfileTemplateSummary {
12
+ readonly id: AgentRuntimeProfileTemplateId;
13
+ readonly name: string;
14
+ readonly description: string;
15
+ readonly personaName: string;
16
+ readonly skillNames: readonly string[];
17
+ readonly routineNames: readonly string[];
18
+ readonly source: AgentRuntimeProfileTemplateSource;
19
+ readonly path?: string;
20
+ }
3
21
 
4
22
  export interface AgentRuntimeProfileResolution {
5
23
  readonly id: string;
@@ -8,6 +26,23 @@ export interface AgentRuntimeProfileResolution {
8
26
 
9
27
  export interface AgentRuntimeProfileInfo extends AgentRuntimeProfileResolution {
10
28
  readonly createdAt: string | null;
29
+ readonly starterTemplateId?: AgentRuntimeProfileTemplateId;
30
+ readonly starterTemplateName?: string;
31
+ readonly starterTemplateApplication?: AgentRuntimeProfileTemplateApplication;
32
+ }
33
+
34
+ export interface AgentRuntimeProfileTemplateApplication {
35
+ readonly id: AgentRuntimeProfileTemplateId;
36
+ readonly name: string;
37
+ readonly source: AgentRuntimeProfileTemplateSource;
38
+ readonly appliedAt: string;
39
+ readonly personaIds: readonly string[];
40
+ readonly skillIds: readonly string[];
41
+ readonly routineIds: readonly string[];
42
+ }
43
+
44
+ export interface CreateAgentRuntimeProfileOptions {
45
+ readonly templateId?: AgentRuntimeProfileTemplateId;
11
46
  }
12
47
 
13
48
  export interface AgentRuntimeProfileCommandResult {
@@ -15,12 +50,19 @@ export interface AgentRuntimeProfileCommandResult {
15
50
  readonly kind:
16
51
  | 'agent.profiles.list'
17
52
  | 'agent.profiles.show'
53
+ | 'agent.profiles.templates'
54
+ | 'agent.profiles.template.export'
55
+ | 'agent.profiles.template.import'
18
56
  | 'agent.profiles.create'
19
57
  | 'agent.profiles.delete'
20
58
  | 'agent.profiles.error';
21
59
  readonly data?: {
22
60
  readonly profiles?: readonly AgentRuntimeProfileInfo[];
23
61
  readonly profile?: AgentRuntimeProfileInfo;
62
+ readonly templates?: readonly AgentRuntimeProfileTemplateSummary[];
63
+ readonly appliedTemplate?: AgentRuntimeProfileTemplateApplication;
64
+ readonly template?: AgentRuntimeProfileTemplateSummary;
65
+ readonly path?: string;
24
66
  readonly nextCommand?: string;
25
67
  };
26
68
  readonly error?: string;
@@ -29,6 +71,336 @@ export interface AgentRuntimeProfileCommandResult {
29
71
  const PROFILE_CREATED_FILE = 'profile.json';
30
72
  const PROFILE_ID_PATTERN = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/;
31
73
 
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
+
32
404
  export function normalizeAgentRuntimeProfileId(value: string): string {
33
405
  return value
34
406
  .trim()
@@ -54,6 +426,10 @@ export function getAgentRuntimeProfilesRoot(baseHomeDirectory: string): string {
54
426
  return join(baseHomeDirectory, '.goodvibes', 'agent', 'profile-homes');
55
427
  }
56
428
 
429
+ export function getAgentRuntimeProfileTemplatesRoot(baseHomeDirectory: string): string {
430
+ return join(baseHomeDirectory, '.goodvibes', GOODVIBES_AGENT_SURFACE_ROOT, 'profile-starters');
431
+ }
432
+
57
433
  export function resolveAgentRuntimeProfileHome(baseHomeDirectory: string, profileName: string): AgentRuntimeProfileResolution {
58
434
  const id = assertValidAgentRuntimeProfileId(profileName);
59
435
  return {
@@ -74,12 +450,258 @@ function readProfileCreatedAt(homeDirectory: string): string | null {
74
450
  return null;
75
451
  }
76
452
 
453
+ function readProfileStarterTemplate(homeDirectory: string): Pick<AgentRuntimeProfileInfo, 'starterTemplateId' | 'starterTemplateName'> {
454
+ const path = join(homeDirectory, PROFILE_CREATED_FILE);
455
+ if (!existsSync(path)) return {};
456
+ try {
457
+ const raw: unknown = JSON.parse(readFileSync(path, 'utf-8'));
458
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return {};
459
+ const record = raw as Record<string, unknown>;
460
+ const starter = record.starterTemplate;
461
+ if (!starter || typeof starter !== 'object' || Array.isArray(starter)) return {};
462
+ const starterRecord = starter as Record<string, unknown>;
463
+ const id = starterRecord.id;
464
+ const name = starterRecord.name;
465
+ if (!isAgentRuntimeProfileTemplateId(id) || typeof name !== 'string') return {};
466
+ return {
467
+ starterTemplateId: id,
468
+ starterTemplateName: name,
469
+ };
470
+ } catch {
471
+ return {};
472
+ }
473
+ }
474
+
77
475
  function buildProfileInfo(baseHomeDirectory: string, id: string): AgentRuntimeProfileInfo {
78
476
  const { homeDirectory } = resolveAgentRuntimeProfileHome(baseHomeDirectory, id);
79
477
  return {
80
478
  id,
81
479
  homeDirectory,
82
480
  createdAt: readProfileCreatedAt(homeDirectory),
481
+ ...readProfileStarterTemplate(homeDirectory),
482
+ };
483
+ }
484
+
485
+ function profileStorePath(homeDirectory: string, folder: string, file: string): string {
486
+ return join(homeDirectory, '.goodvibes', GOODVIBES_AGENT_SURFACE_ROOT, folder, file);
487
+ }
488
+
489
+ export function isAgentRuntimeProfileTemplateId(value: unknown): value is AgentRuntimeProfileTemplateId {
490
+ if (typeof value !== 'string') return false;
491
+ try {
492
+ assertValidAgentRuntimeProfileId(value);
493
+ return true;
494
+ } catch {
495
+ return false;
496
+ }
497
+ }
498
+
499
+ function summarizeTemplate(template: AgentRuntimeProfileStarterTemplate): AgentRuntimeProfileTemplateSummary {
500
+ return {
501
+ id: template.id,
502
+ name: template.name,
503
+ description: template.description,
504
+ personaName: template.personaName,
505
+ skillNames: [...template.skillNames],
506
+ routineNames: [...template.routineNames],
507
+ source: template.source,
508
+ path: template.path,
509
+ };
510
+ }
511
+
512
+ function parseStringArray(value: unknown): readonly string[] {
513
+ if (!Array.isArray(value)) return [];
514
+ return value.filter((entry): entry is string => typeof entry === 'string').map((entry) => entry.trim()).filter(Boolean);
515
+ }
516
+
517
+ function readTemplateTextBlock(value: unknown, field: string): string {
518
+ if (typeof value !== 'string' || !value.trim()) throw new Error(`Starter template ${field} is required.`);
519
+ return value.trim();
520
+ }
521
+
522
+ function readTemplateObject(value: unknown, field: string): Record<string, unknown> {
523
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`Starter template ${field} must be an object.`);
524
+ return value as Record<string, unknown>;
525
+ }
526
+
527
+ function parseTemplateSkill(value: unknown): AgentRuntimeProfileStarterTemplate['skills'][number] {
528
+ const record = readTemplateObject(value, 'skill');
529
+ return {
530
+ name: readTemplateTextBlock(record.name, 'skill.name'),
531
+ description: readTemplateTextBlock(record.description, 'skill.description'),
532
+ procedure: readTemplateTextBlock(record.procedure, 'skill.procedure'),
533
+ triggers: parseStringArray(record.triggers),
534
+ tags: parseStringArray(record.tags),
535
+ };
536
+ }
537
+
538
+ function parseTemplateRoutine(value: unknown): AgentRuntimeProfileStarterTemplate['routines'][number] {
539
+ const record = readTemplateObject(value, 'routine');
540
+ return {
541
+ name: readTemplateTextBlock(record.name, 'routine.name'),
542
+ description: readTemplateTextBlock(record.description, 'routine.description'),
543
+ steps: readTemplateTextBlock(record.steps, 'routine.steps'),
544
+ triggers: parseStringArray(record.triggers),
545
+ tags: parseStringArray(record.tags),
546
+ };
547
+ }
548
+
549
+ function parseStarterTemplate(raw: unknown, source: AgentRuntimeProfileTemplateSource, path?: string): AgentRuntimeProfileStarterTemplate {
550
+ const file = readTemplateObject(raw, 'file');
551
+ const templateRecord = readTemplateObject(file.template ?? raw, 'template');
552
+ const id = assertValidAgentRuntimeProfileId(readTemplateTextBlock(templateRecord.id, 'id'));
553
+ const personaRecord = readTemplateObject(templateRecord.persona, 'persona');
554
+ const skills = Array.isArray(templateRecord.skills) ? templateRecord.skills.map(parseTemplateSkill) : [];
555
+ const routines = Array.isArray(templateRecord.routines) ? templateRecord.routines.map(parseTemplateRoutine) : [];
556
+ if (skills.length === 0) throw new Error(`Starter template ${id} must include at least one skill.`);
557
+ if (routines.length === 0) throw new Error(`Starter template ${id} must include at least one routine.`);
558
+ const persona = {
559
+ name: readTemplateTextBlock(personaRecord.name, 'persona.name'),
560
+ description: readTemplateTextBlock(personaRecord.description, 'persona.description'),
561
+ body: readTemplateTextBlock(personaRecord.body, 'persona.body'),
562
+ tags: parseStringArray(personaRecord.tags),
563
+ triggers: parseStringArray(personaRecord.triggers),
564
+ };
565
+ return {
566
+ id,
567
+ source,
568
+ path,
569
+ name: readTemplateTextBlock(templateRecord.name, 'name'),
570
+ description: readTemplateTextBlock(templateRecord.description, 'description'),
571
+ personaName: typeof templateRecord.personaName === 'string' && templateRecord.personaName.trim() ? templateRecord.personaName.trim() : persona.name,
572
+ skillNames: skills.map((skill) => skill.name),
573
+ routineNames: routines.map((routine) => routine.name),
574
+ persona,
575
+ skills,
576
+ routines,
577
+ };
578
+ }
579
+
580
+ function readLocalTemplate(path: string): AgentRuntimeProfileStarterTemplate | null {
581
+ try {
582
+ return parseStarterTemplate(JSON.parse(readFileSync(path, 'utf-8')), 'local', path);
583
+ } catch {
584
+ return null;
585
+ }
586
+ }
587
+
588
+ function listLocalTemplates(baseHomeDirectory: string): readonly AgentRuntimeProfileStarterTemplate[] {
589
+ const root = getAgentRuntimeProfileTemplatesRoot(baseHomeDirectory);
590
+ if (!existsSync(root)) return [];
591
+ return readdirSync(root)
592
+ .filter((entry) => entry.endsWith('.json'))
593
+ .map((entry) => readLocalTemplate(join(root, entry)))
594
+ .filter((entry): entry is AgentRuntimeProfileStarterTemplate => entry !== null)
595
+ .sort((left, right) => left.id.localeCompare(right.id));
596
+ }
597
+
598
+ function resolveAgentRuntimeProfileTemplate(templateId: AgentRuntimeProfileTemplateId, baseHomeDirectory?: string): AgentRuntimeProfileStarterTemplate {
599
+ const id = assertValidAgentRuntimeProfileId(templateId);
600
+ const builtin = STARTER_TEMPLATES.find((template) => template.id === id);
601
+ if (builtin) return builtin;
602
+ const local = baseHomeDirectory ? listLocalTemplates(baseHomeDirectory).find((template) => template.id === id) : undefined;
603
+ if (local) return local;
604
+ const suffix = baseHomeDirectory ? ' Use profiles templates to list starters.' : '';
605
+ throw new Error(`Unknown Agent starter profile template: ${templateId}.${suffix}`);
606
+ }
607
+
608
+ export function listAgentRuntimeProfileTemplates(baseHomeDirectory?: string): readonly AgentRuntimeProfileTemplateSummary[] {
609
+ const builtins = STARTER_TEMPLATES.map(summarizeTemplate);
610
+ const locals = baseHomeDirectory ? listLocalTemplates(baseHomeDirectory).map(summarizeTemplate) : [];
611
+ return [...builtins, ...locals];
612
+ }
613
+
614
+ export function getAgentRuntimeProfileTemplate(templateId: AgentRuntimeProfileTemplateId, baseHomeDirectory?: string): AgentRuntimeProfileTemplateSummary {
615
+ return summarizeTemplate(resolveAgentRuntimeProfileTemplate(templateId, baseHomeDirectory));
616
+ }
617
+
618
+ function templateFilePayload(template: AgentRuntimeProfileStarterTemplate): AgentRuntimeProfileStarterTemplateFile {
619
+ return {
620
+ version: 1,
621
+ template: {
622
+ ...template,
623
+ source: 'local',
624
+ path: undefined,
625
+ },
626
+ };
627
+ }
628
+
629
+ export function exportAgentRuntimeProfileTemplate(baseHomeDirectory: string, templateId: AgentRuntimeProfileTemplateId, outputPath: string): AgentRuntimeProfileTemplateSummary {
630
+ const template = resolveAgentRuntimeProfileTemplate(templateId, baseHomeDirectory);
631
+ const target = outputPath.trim();
632
+ if (!target) throw new Error('Template export path is required.');
633
+ mkdirSync(dirname(target), { recursive: true });
634
+ writeFileSync(target, `${JSON.stringify(templateFilePayload(template), null, 2)}\n`, 'utf-8');
635
+ return { ...summarizeTemplate(template), path: target };
636
+ }
637
+
638
+ export function importAgentRuntimeProfileTemplate(baseHomeDirectory: string, sourcePath: string): AgentRuntimeProfileTemplateSummary {
639
+ const source = sourcePath.trim();
640
+ if (!source) throw new Error('Template import path is required.');
641
+ const parsed = parseStarterTemplate(JSON.parse(readFileSync(source, 'utf-8')), 'local');
642
+ const root = getAgentRuntimeProfileTemplatesRoot(baseHomeDirectory);
643
+ mkdirSync(root, { recursive: true });
644
+ const target = join(root, `${parsed.id}.json`);
645
+ writeFileSync(target, `${JSON.stringify(templateFilePayload({ ...parsed, source: 'local', path: target }), null, 2)}\n`, 'utf-8');
646
+ return summarizeTemplate({ ...parsed, source: 'local', path: target });
647
+ }
648
+
649
+ function createMissingSkill(registry: AgentSkillRegistry, template: AgentRuntimeProfileStarterTemplate['skills'][number]): string {
650
+ const existing = registry.get(template.name);
651
+ if (existing) return existing.id;
652
+ return registry.create({
653
+ name: template.name,
654
+ description: template.description,
655
+ procedure: template.procedure,
656
+ triggers: template.triggers,
657
+ tags: template.tags,
658
+ enabled: true,
659
+ source: 'system',
660
+ provenance: `goodvibes-agent starter:${template.name}`,
661
+ }).id;
662
+ }
663
+
664
+ function createMissingRoutine(registry: AgentRoutineRegistry, template: AgentRuntimeProfileStarterTemplate['routines'][number]): string {
665
+ const existing = registry.get(template.name);
666
+ if (existing) return existing.id;
667
+ return registry.create({
668
+ name: template.name,
669
+ description: template.description,
670
+ steps: template.steps,
671
+ triggers: template.triggers,
672
+ tags: template.tags,
673
+ enabled: true,
674
+ source: 'system',
675
+ provenance: `goodvibes-agent starter:${template.name}`,
676
+ }).id;
677
+ }
678
+
679
+ export function applyAgentRuntimeProfileTemplate(homeDirectory: string, templateId: AgentRuntimeProfileTemplateId, baseHomeDirectory?: string): AgentRuntimeProfileTemplateApplication {
680
+ const template = resolveAgentRuntimeProfileTemplate(templateId, baseHomeDirectory);
681
+ const personaRegistry = new AgentPersonaRegistry(profileStorePath(homeDirectory, 'personas', 'personas.json'));
682
+ const skillRegistry = new AgentSkillRegistry(profileStorePath(homeDirectory, 'skills', 'skills.json'));
683
+ const routineRegistry = new AgentRoutineRegistry(profileStorePath(homeDirectory, 'routines', 'routines.json'));
684
+ const existingPersona = personaRegistry.get(template.persona.name);
685
+ const persona = existingPersona ?? personaRegistry.create({
686
+ name: template.persona.name,
687
+ description: template.persona.description,
688
+ body: template.persona.body,
689
+ tags: template.persona.tags,
690
+ triggers: template.persona.triggers,
691
+ source: 'system',
692
+ provenance: `goodvibes-agent starter:${template.name}`,
693
+ });
694
+ personaRegistry.setActive(persona.id);
695
+ const skillIds = template.skills.map((skill) => createMissingSkill(skillRegistry, skill));
696
+ const routineIds = template.routines.map((routine) => createMissingRoutine(routineRegistry, routine));
697
+ return {
698
+ id: template.id,
699
+ name: template.name,
700
+ source: template.source,
701
+ appliedAt: new Date().toISOString(),
702
+ personaIds: [persona.id],
703
+ skillIds,
704
+ routineIds,
83
705
  };
84
706
  }
85
707
 
@@ -99,16 +721,29 @@ export function listAgentRuntimeProfiles(baseHomeDirectory: string): readonly Ag
99
721
  .map((entry) => buildProfileInfo(baseHomeDirectory, entry));
100
722
  }
101
723
 
102
- export function createAgentRuntimeProfile(baseHomeDirectory: string, profileName: string): AgentRuntimeProfileInfo {
724
+ export function createAgentRuntimeProfile(baseHomeDirectory: string, profileName: string, options: CreateAgentRuntimeProfileOptions = {}): AgentRuntimeProfileInfo {
103
725
  const resolution = resolveAgentRuntimeProfileHome(baseHomeDirectory, profileName);
104
726
  mkdirSync(resolution.homeDirectory, { recursive: true });
105
727
  const createdAt = new Date().toISOString();
728
+ const appliedTemplate = options.templateId
729
+ ? applyAgentRuntimeProfileTemplate(resolution.homeDirectory, options.templateId, baseHomeDirectory)
730
+ : undefined;
106
731
  writeFileSync(
107
732
  join(resolution.homeDirectory, PROFILE_CREATED_FILE),
108
- `${JSON.stringify({ id: resolution.id, createdAt }, null, 2)}\n`,
733
+ `${JSON.stringify({
734
+ id: resolution.id,
735
+ createdAt,
736
+ starterTemplate: appliedTemplate,
737
+ }, null, 2)}\n`,
109
738
  'utf-8',
110
739
  );
111
- return { ...resolution, createdAt };
740
+ return {
741
+ ...resolution,
742
+ createdAt,
743
+ starterTemplateId: appliedTemplate?.id,
744
+ starterTemplateName: appliedTemplate?.name,
745
+ starterTemplateApplication: appliedTemplate,
746
+ };
112
747
  }
113
748
 
114
749
  export function deleteAgentRuntimeProfile(baseHomeDirectory: string, profileName: string): boolean {
package/src/cli/help.ts CHANGED
@@ -96,7 +96,7 @@ export function renderGoodVibesHelp(binary = 'goodvibes-agent'): string {
96
96
  ` ${binary} models current`,
97
97
  ` ${binary} models use openai:gpt-5.2`,
98
98
  ` ${binary} providers inspect openai`,
99
- ` ${binary} profiles create household --yes`,
99
+ ` ${binary} profiles create household --template household --yes`,
100
100
  ` ${binary} --agent-profile household`,
101
101
  ` ${binary} compat`,
102
102
  ` ${binary} capabilities`,
@@ -154,9 +154,9 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
154
154
  examples: ['providers', 'providers inspect openai-subscriber', 'providers use openai openai:gpt-5.4'],
155
155
  },
156
156
  profiles: {
157
- usage: ['profiles list', 'profiles show <name>', 'profiles create <name> --yes', 'profiles delete <name> --yes', '--agent-profile <name>'],
158
- summary: 'Create and inspect isolated Agent runtime profile homes. A profile changes Agent-local config, sessions, memory, personas, skills, routines, and setup paths without changing the externally owned daemon.',
159
- examples: ['profiles create household --yes', 'profiles list', '--agent-profile household status', '--agent-profile household'],
157
+ 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>'],
158
+ summary: 'Create and inspect isolated Agent runtime 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 the externally owned daemon.',
159
+ 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'],
160
160
  },
161
161
  models: {
162
162
  usage: ['models [provider]', 'models current', 'models use <registryKey>', 'models pin <registryKey>', 'models recent'],
@@ -1,8 +1,13 @@
1
1
  import {
2
2
  createAgentRuntimeProfile,
3
3
  deleteAgentRuntimeProfile,
4
+ exportAgentRuntimeProfileTemplate,
5
+ importAgentRuntimeProfileTemplate,
6
+ isAgentRuntimeProfileTemplateId,
4
7
  listAgentRuntimeProfiles,
8
+ listAgentRuntimeProfileTemplates,
5
9
  resolveAgentRuntimeProfileHome,
10
+ type AgentRuntimeProfileTemplateId,
6
11
  type AgentRuntimeProfileCommandResult,
7
12
  type AgentRuntimeProfileInfo,
8
13
  } from '../agent/runtime-profile.ts';
@@ -26,26 +31,84 @@ function commandValues(args: readonly string[]): readonly string[] {
26
31
  return values;
27
32
  }
28
33
 
34
+ function flagValue(args: readonly string[], names: readonly string[]): string | null {
35
+ for (let index = 0; index < args.length; index += 1) {
36
+ const token = args[index]!;
37
+ for (const name of names) {
38
+ if (token === name) {
39
+ const next = args[index + 1];
40
+ return next && !next.startsWith('--') ? next : null;
41
+ }
42
+ if (token.startsWith(`${name}=`)) return token.slice(name.length + 1);
43
+ }
44
+ }
45
+ return null;
46
+ }
47
+
48
+ function parseTemplate(args: readonly string[]): AgentRuntimeProfileTemplateId | undefined {
49
+ const raw = flagValue(args, ['--template', '--starter']);
50
+ if (!raw || raw === 'blank') return undefined;
51
+ const normalized = raw.trim().toLowerCase().replace(/_/g, '-');
52
+ if (isAgentRuntimeProfileTemplateId(normalized)) return normalized;
53
+ throw new Error(`Unknown Agent starter profile template: ${raw}. Use profiles templates to list starters.`);
54
+ }
55
+
29
56
  function profileLine(profile: AgentRuntimeProfileInfo): string {
30
57
  const created = profile.createdAt ? ` created=${profile.createdAt}` : '';
31
- return ` ${profile.id} home=${profile.homeDirectory}${created}`;
58
+ const starter = profile.starterTemplateId ? ` starter=${profile.starterTemplateId}` : '';
59
+ return ` ${profile.id} home=${profile.homeDirectory}${created}${starter}`;
32
60
  }
33
61
 
34
62
  function renderProfilesResult(result: AgentRuntimeProfileCommandResult): string {
35
63
  if (!result.ok) return result.error ?? 'Agent profile command failed.';
36
64
  if (result.kind === 'agent.profiles.list') {
37
65
  const profiles = result.data?.profiles ?? [];
38
- if (profiles.length === 0) return 'No Agent runtime profiles. Use: goodvibes-agent profiles create <name> --yes';
66
+ if (profiles.length === 0) return 'No Agent runtime profiles. Use: goodvibes-agent profiles create <name> --template <id> --yes';
39
67
  return [
40
68
  `Agent runtime profiles (${profiles.length})`,
41
69
  ...profiles.map(profileLine),
42
70
  ].join('\n');
43
71
  }
72
+ if (result.kind === 'agent.profiles.templates') {
73
+ const templates = result.data?.templates ?? [];
74
+ return [
75
+ `Agent starter profile templates (${templates.length})`,
76
+ ...templates.map((template) => [
77
+ ` ${template.id} ${template.name} [${template.source}]`,
78
+ ` ${template.description}`,
79
+ ` persona: ${template.personaName}`,
80
+ ` skills: ${template.skillNames.join(', ')}`,
81
+ ` routines: ${template.routineNames.join(', ')}`,
82
+ ].join('\n')),
83
+ 'Use: goodvibes-agent profiles create <name> --template <id> --yes',
84
+ 'Export/edit/import: goodvibes-agent profiles templates export <id> <path> --yes',
85
+ ].join('\n');
86
+ }
87
+ if (result.kind === 'agent.profiles.template.export' && result.data?.template && result.data.path) {
88
+ return [
89
+ `Agent starter template exported: ${result.data.template.id}`,
90
+ ` path: ${result.data.path}`,
91
+ ' edit the JSON, then import it with: goodvibes-agent profiles templates import <path> --yes',
92
+ ].join('\n');
93
+ }
94
+ if (result.kind === 'agent.profiles.template.import' && result.data?.template) {
95
+ return [
96
+ `Agent starter template imported: ${result.data.template.id}`,
97
+ ` name: ${result.data.template.name}`,
98
+ ` source: ${result.data.template.source}`,
99
+ ` use: goodvibes-agent profiles create <name> --template ${result.data.template.id} --yes`,
100
+ ].join('\n');
101
+ }
44
102
  const profile = result.data?.profile;
45
103
  if (result.kind === 'agent.profiles.create' && profile) {
104
+ const template = result.data?.appliedTemplate;
46
105
  return [
47
106
  `Agent runtime profile created: ${profile.id}`,
48
107
  ` home: ${profile.homeDirectory}`,
108
+ ...(template ? [
109
+ ` starter: ${template.id} (${template.name})`,
110
+ ` seeded: ${template.personaIds.length} persona, ${template.skillIds.length} skills, ${template.routineIds.length} routine`,
111
+ ] : []),
49
112
  ` use: ${result.data?.nextCommand ?? `goodvibes-agent --agent-profile ${profile.id}`}`,
50
113
  ].join('\n');
51
114
  }
@@ -77,6 +140,87 @@ export async function handleProfilesCommand(runtime: ProfilesCommandRuntime): Pr
77
140
  };
78
141
  }
79
142
 
143
+ if (sub === 'templates' || sub === 'starters') {
144
+ const [templateAction, templateId, templatePath] = values;
145
+ if (templateAction === 'export') {
146
+ if (!templateId || !templatePath) {
147
+ const result: AgentRuntimeProfileCommandResult = {
148
+ ok: false,
149
+ kind: 'agent.profiles.error',
150
+ error: 'Usage: goodvibes-agent profiles templates export <id> <path> --yes',
151
+ };
152
+ return {
153
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
154
+ exitCode: 2,
155
+ };
156
+ }
157
+ if (!hasYes(rawRest)) {
158
+ const result: AgentRuntimeProfileCommandResult = {
159
+ ok: false,
160
+ kind: 'agent.profiles.error',
161
+ error: `Refusing to export Agent starter template ${templateId} without --yes.`,
162
+ };
163
+ return {
164
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
165
+ exitCode: 2,
166
+ };
167
+ }
168
+ const template = exportAgentRuntimeProfileTemplate(runtime.homeDirectory, templateId, templatePath);
169
+ const result: AgentRuntimeProfileCommandResult = {
170
+ ok: true,
171
+ kind: 'agent.profiles.template.export',
172
+ data: { template, path: templatePath },
173
+ };
174
+ return {
175
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
176
+ exitCode: 0,
177
+ };
178
+ }
179
+ if (templateAction === 'import') {
180
+ if (!templateId) {
181
+ const result: AgentRuntimeProfileCommandResult = {
182
+ ok: false,
183
+ kind: 'agent.profiles.error',
184
+ error: 'Usage: goodvibes-agent profiles templates import <path> --yes',
185
+ };
186
+ return {
187
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
188
+ exitCode: 2,
189
+ };
190
+ }
191
+ if (!hasYes(rawRest)) {
192
+ const result: AgentRuntimeProfileCommandResult = {
193
+ ok: false,
194
+ kind: 'agent.profiles.error',
195
+ error: `Refusing to import Agent starter template ${templateId} without --yes.`,
196
+ };
197
+ return {
198
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
199
+ exitCode: 2,
200
+ };
201
+ }
202
+ const template = importAgentRuntimeProfileTemplate(runtime.homeDirectory, templateId);
203
+ const result: AgentRuntimeProfileCommandResult = {
204
+ ok: true,
205
+ kind: 'agent.profiles.template.import',
206
+ data: { template, path: templateId },
207
+ };
208
+ return {
209
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
210
+ exitCode: 0,
211
+ };
212
+ }
213
+ const result: AgentRuntimeProfileCommandResult = {
214
+ ok: true,
215
+ kind: 'agent.profiles.templates',
216
+ data: { templates: listAgentRuntimeProfileTemplates(runtime.homeDirectory) },
217
+ };
218
+ return {
219
+ output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
220
+ exitCode: 0,
221
+ };
222
+ }
223
+
80
224
  if (sub === 'show' || sub === 'path' || sub === 'home') {
81
225
  const name = values[0];
82
226
  if (!name) {
@@ -91,12 +235,14 @@ export async function handleProfilesCommand(runtime: ProfilesCommandRuntime): Pr
91
235
  };
92
236
  }
93
237
  const profile = resolveAgentRuntimeProfileHome(runtime.homeDirectory, name);
238
+ const info = listAgentRuntimeProfiles(runtime.homeDirectory).find((entry) => entry.id === profile.id) ?? { ...profile, createdAt: null };
94
239
  const result: AgentRuntimeProfileCommandResult = {
95
240
  ok: true,
96
241
  kind: 'agent.profiles.show',
97
- data: { profile: { ...profile, createdAt: null } },
242
+ data: { profile: info },
98
243
  };
99
- const text = [`Agent runtime profile: ${profile.id}`, ` home: ${profile.homeDirectory}`, ` use: goodvibes-agent --agent-profile ${profile.id}`].join('\n');
244
+ const starter = info.starterTemplateId ? [` starter: ${info.starterTemplateId} (${info.starterTemplateName ?? info.starterTemplateId})`] : [];
245
+ const text = [`Agent runtime profile: ${profile.id}`, ` home: ${profile.homeDirectory}`, ...starter, ` use: goodvibes-agent --agent-profile ${profile.id}`].join('\n');
100
246
  return {
101
247
  output: runtime.cli.flags.outputFormat === 'json' ? JSON.stringify(result, null, 2) : text,
102
248
  exitCode: 0,
@@ -109,7 +255,7 @@ export async function handleProfilesCommand(runtime: ProfilesCommandRuntime): Pr
109
255
  const result: AgentRuntimeProfileCommandResult = {
110
256
  ok: false,
111
257
  kind: 'agent.profiles.error',
112
- error: 'Usage: goodvibes-agent profiles create <name> --yes',
258
+ error: 'Usage: goodvibes-agent profiles create <name> [--template <id>] --yes',
113
259
  };
114
260
  return {
115
261
  output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
@@ -127,12 +273,14 @@ export async function handleProfilesCommand(runtime: ProfilesCommandRuntime): Pr
127
273
  exitCode: 2,
128
274
  };
129
275
  }
130
- const profile = createAgentRuntimeProfile(runtime.homeDirectory, name);
276
+ const templateId = parseTemplate(rawRest);
277
+ const profile = createAgentRuntimeProfile(runtime.homeDirectory, name, { templateId });
131
278
  const result: AgentRuntimeProfileCommandResult = {
132
279
  ok: true,
133
280
  kind: 'agent.profiles.create',
134
281
  data: {
135
282
  profile,
283
+ appliedTemplate: profile.starterTemplateApplication,
136
284
  nextCommand: `goodvibes-agent --agent-profile ${profile.id}`,
137
285
  },
138
286
  };
@@ -188,7 +336,7 @@ export async function handleProfilesCommand(runtime: ProfilesCommandRuntime): Pr
188
336
  const result: AgentRuntimeProfileCommandResult = {
189
337
  ok: false,
190
338
  kind: 'agent.profiles.error',
191
- error: 'Usage: goodvibes-agent profiles [list|show <name>|create <name> --yes|delete <name> --yes]',
339
+ error: 'Usage: goodvibes-agent profiles [list|templates|show <name>|create <name> [--template <id>] --yes|delete <name> --yes]',
192
340
  };
193
341
  return {
194
342
  output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
@@ -525,7 +525,8 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
525
525
  { id: 'profile-sync-list', label: 'Profile sync list', detail: 'Inspect saved config profiles available for export/import.', command: '/profilesync list', kind: 'command', safety: 'read-only' },
526
526
  { id: 'profile-sync-export', label: 'Export profile sync', detail: 'Export config profiles to a portable bundle. Requires a real path and explicit --yes.', command: '/profilesync export <path> --yes', kind: 'command', safety: 'safe' },
527
527
  { id: 'setup-transfer-export', label: 'Export setup transfer', detail: 'Export Agent setup transfer data from the current home. Requires a real path and explicit --yes.', command: '/setup transfer export <path> --yes', kind: 'command', safety: 'safe' },
528
- { id: 'runtime-profile-create', label: 'Create runtime profile', detail: 'Use the CLI command goodvibes-agent profiles create <name> --yes, then launch with --agent-profile <name>. Runtime profile homes isolate Agent-local state and do not start a daemon.', kind: 'guidance', safety: 'safe' },
528
+ { id: 'runtime-profile-create', label: 'Create runtime profile', detail: 'Use goodvibes-agent profiles templates, then goodvibes-agent profiles create <name> --template <id> --yes. Starters seed local personas, skills, and routines without touching the daemon.', kind: 'guidance', safety: 'safe' },
529
+ { id: 'runtime-profile-template-edit', label: 'Customize starter', detail: 'Use goodvibes-agent profiles templates export <id> <path> --yes, edit the JSON, then import it with profiles templates import <path> --yes.', kind: 'guidance', safety: 'safe' },
529
530
  { id: 'runtime-profile-switch', label: 'Switch runtime profile', detail: 'Launch goodvibes-agent --agent-profile <name> to use that isolated Agent home. This workspace cannot switch the current process home after startup.', kind: 'guidance', safety: 'safe' },
530
531
  ],
531
532
  },
@@ -100,11 +100,11 @@ export const OPERATOR_CAPABILITY_BENCHMARKS: readonly OperatorCapabilityBenchmar
100
100
  posture: 'ready',
101
101
  competitors: ['openclaw', 'hermes'],
102
102
  competitorBaseline: 'Skills/procedural memory, persona files, profile state, durable memory, and recurring workflows.',
103
- goodvibesAgent: 'Typed local registries for Agent personas, skills, routines, and local memory; active items are injected into the serial main conversation.',
104
- configure: ['/personas create ...', '/agent-skills create ...', '/routines create ...', '/memory add ...'],
105
- use: ['/personas use <id>', '/skills local list', '/routines start <id>', '/memory search <query>'],
106
- exceedsBy: ['Review/stale lifecycle fields', 'secret-value rejection', 'Agent-local state that does not contaminate wiki or HomeGraph'],
107
- next: ['Add CLI parity for local registry lifecycle and a curated starter pack of operator skills/routines.'],
103
+ goodvibesAgent: 'Typed local registries for Agent personas, skills, routines, and local memory; active items are injected into the serial main conversation. Runtime profile starters seed curated persona, skill, and routine bundles, and users can export/edit/import local starters.',
104
+ configure: ['/personas create ...', '/agent-skills create ...', '/routines create ...', '/memory add ...', 'goodvibes-agent profiles create research --template research --yes', 'goodvibes-agent profiles templates import ./starter.json --yes'],
105
+ use: ['/personas use <id>', '/skills local list', '/routines start <id>', '/memory search <query>', 'goodvibes-agent profiles templates'],
106
+ exceedsBy: ['Review/stale lifecycle fields', 'secret-value rejection', 'Agent-local state that does not contaminate wiki or HomeGraph', 'starter bundles that remain local and reviewable', 'editable starter JSON for repeatable operator lanes'],
107
+ next: ['Add guided starter authoring inside the fullscreen Agent workspace.'],
108
108
  },
109
109
  {
110
110
  id: 'automation-schedules',
@@ -160,11 +160,11 @@ export const OPERATOR_CAPABILITY_BENCHMARKS: readonly OperatorCapabilityBenchmar
160
160
  posture: 'ready',
161
161
  competitors: ['hermes'],
162
162
  competitorBaseline: 'Profiles run independent agents with isolated configs, sessions, skills, memory, cron jobs, and gateway state.',
163
- goodvibesAgent: 'Supports GOODVIBES_AGENT_HOME and named --agent-profile homes for isolated Agent-local config, sessions, memory, personas, skills, routines, setup, and bundles; daemon remains shared/external by design. The Agent workspace exposes runtime profile posture, config profiles, profile sync, and setup transfer shortcuts.',
164
- configure: ['GOODVIBES_AGENT_HOME=<path> goodvibes-agent status', 'goodvibes-agent profiles create household --yes', '/agent → Profiles & Portability'],
163
+ goodvibesAgent: 'Supports GOODVIBES_AGENT_HOME and named --agent-profile homes for isolated Agent-local config, sessions, memory, personas, skills, routines, setup, and bundles; daemon remains shared/external by design. The Agent workspace exposes runtime profile posture, config profiles, profile sync, setup transfer shortcuts, starter profile templates, and local starter import/export.',
164
+ configure: ['GOODVIBES_AGENT_HOME=<path> goodvibes-agent status', 'goodvibes-agent profiles create household --template household --yes', 'goodvibes-agent profiles templates export research ./starter.json --yes', '/agent → Profiles & Portability'],
165
165
  use: ['goodvibes-agent --agent-profile household', '/profiles', '/profilesync list', '/setup transfer export <path> --yes'],
166
- exceedsBy: ['Typed support bundles', 'explicit daemon boundary', 'no accidental cross-product knowledge fallback', 'profile isolation without hidden daemon lifecycle ownership', 'fullscreen profile and portability workflow discovery'],
167
- next: ['Add curated profile starter templates for household, research, travel, operations, and personal productivity profiles.'],
166
+ exceedsBy: ['Typed support bundles', 'explicit daemon boundary', 'no accidental cross-product knowledge fallback', 'profile isolation without hidden daemon lifecycle ownership', 'fullscreen profile and portability workflow discovery', 'curated local starter packs', 'editable starter templates'],
167
+ next: ['Add guided starter authoring inside the fullscreen Agent workspace.'],
168
168
  },
169
169
  {
170
170
  id: 'security-approvals',
@@ -134,6 +134,7 @@ function snapshotLines(category: AgentWorkspaceCategory, snapshot: AgentWorkspac
134
134
  { text: `Runtime profile root: ${snapshot.runtimeProfileRoot}`, fg: PALETTE.muted },
135
135
  { text: `Config profiles: ${snapshot.configProfileCount}`, fg: PALETTE.info },
136
136
  { text: 'Named runtime profiles isolate Agent-local config, sessions, memory, personas, skills, routines, setup, and bundles.', fg: PALETTE.good },
137
+ { text: 'Starter templates: household, research, travel, operations, personal-productivity.', fg: PALETTE.info },
137
138
  { text: 'The external daemon remains shared unless the daemon host is configured separately.', fg: PALETTE.warn },
138
139
  { text: 'Portable bundles require explicit export/import commands with real paths and --yes.', fg: PALETTE.muted },
139
140
  );
package/src/version.ts CHANGED
@@ -6,7 +6,7 @@ import { join } from 'node:path';
6
6
  // The prebuild script updates the fallback value before compilation.
7
7
  // Uses import.meta.dir (Bun) to locate package.json relative to this file,
8
8
  // which is correct regardless of the process working directory.
9
- let _version = '0.1.33';
9
+ let _version = '0.1.35';
10
10
  let _sdkVersion = '0.33.35';
11
11
  try {
12
12
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8')) as {