@pellux/goodvibes-agent 0.1.34 → 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,10 @@
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
+
5
9
  ## 0.1.34 - 2026-05-31
6
10
 
7
11
  - 28838cc Add curated Agent profile starters
package/README.md CHANGED
@@ -51,11 +51,13 @@ Use isolated Agent runtime profiles when one machine needs separate operator ide
51
51
  ```sh
52
52
  goodvibes-agent profiles templates
53
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
54
56
  goodvibes-agent --agent-profile household status
55
57
  GOODVIBES_AGENT_HOME=/path/to/agent-home goodvibes-agent status
56
58
  ```
57
59
 
58
- 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. 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.
59
61
 
60
62
  Local Agent behavior is editable from the TUI:
61
63
 
package/docs/README.md CHANGED
@@ -21,7 +21,7 @@ Important baseline constraints:
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
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.
24
+ - Agent ships starter profile templates for household, research, travel, operations, and personal productivity local state; `profiles templates export/import` supports local custom starters.
25
25
  - Local personas, routines, and Agent skills are stored under the Agent surface root and are injected only into the serial Agent conversation.
26
26
  - Normal assistant chat is not coding-session delegation.
27
27
  - Build/fix/review delegation to GoodVibes TUI must be explicit; WRFC is not the default Agent behavior.
@@ -50,11 +50,13 @@ Use named runtime profiles for repeatable local identities:
50
50
  ```sh
51
51
  goodvibes-agent profiles templates
52
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
53
55
  goodvibes-agent --agent-profile household status
54
56
  goodvibes-agent --agent-profile household
55
57
  ```
56
58
 
57
- 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. 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.
58
60
 
59
61
  ## Local Personas, Routines, And Skills
60
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; starter templates seed local personas/skills/routines; 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
- - Custom starter profile authoring/import for teams and repeatable operator lanes.
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.34",
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,11 +1,12 @@
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
3
  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
7
 
8
- export type AgentRuntimeProfileTemplateId = 'household' | 'research' | 'travel' | 'operations' | 'personal-productivity';
8
+ export type AgentRuntimeProfileTemplateId = string;
9
+ export type AgentRuntimeProfileTemplateSource = 'builtin' | 'local';
9
10
 
10
11
  export interface AgentRuntimeProfileTemplateSummary {
11
12
  readonly id: AgentRuntimeProfileTemplateId;
@@ -14,6 +15,8 @@ export interface AgentRuntimeProfileTemplateSummary {
14
15
  readonly personaName: string;
15
16
  readonly skillNames: readonly string[];
16
17
  readonly routineNames: readonly string[];
18
+ readonly source: AgentRuntimeProfileTemplateSource;
19
+ readonly path?: string;
17
20
  }
18
21
 
19
22
  export interface AgentRuntimeProfileResolution {
@@ -31,6 +34,7 @@ export interface AgentRuntimeProfileInfo extends AgentRuntimeProfileResolution {
31
34
  export interface AgentRuntimeProfileTemplateApplication {
32
35
  readonly id: AgentRuntimeProfileTemplateId;
33
36
  readonly name: string;
37
+ readonly source: AgentRuntimeProfileTemplateSource;
34
38
  readonly appliedAt: string;
35
39
  readonly personaIds: readonly string[];
36
40
  readonly skillIds: readonly string[];
@@ -47,6 +51,8 @@ export interface AgentRuntimeProfileCommandResult {
47
51
  | 'agent.profiles.list'
48
52
  | 'agent.profiles.show'
49
53
  | 'agent.profiles.templates'
54
+ | 'agent.profiles.template.export'
55
+ | 'agent.profiles.template.import'
50
56
  | 'agent.profiles.create'
51
57
  | 'agent.profiles.delete'
52
58
  | 'agent.profiles.error';
@@ -55,6 +61,8 @@ export interface AgentRuntimeProfileCommandResult {
55
61
  readonly profile?: AgentRuntimeProfileInfo;
56
62
  readonly templates?: readonly AgentRuntimeProfileTemplateSummary[];
57
63
  readonly appliedTemplate?: AgentRuntimeProfileTemplateApplication;
64
+ readonly template?: AgentRuntimeProfileTemplateSummary;
65
+ readonly path?: string;
58
66
  readonly nextCommand?: string;
59
67
  };
60
68
  readonly error?: string;
@@ -87,9 +95,15 @@ interface AgentRuntimeProfileStarterTemplate extends AgentRuntimeProfileTemplate
87
95
  }[];
88
96
  }
89
97
 
98
+ interface AgentRuntimeProfileStarterTemplateFile {
99
+ readonly version: 1;
100
+ readonly template: AgentRuntimeProfileStarterTemplate;
101
+ }
102
+
90
103
  const STARTER_TEMPLATES: readonly AgentRuntimeProfileStarterTemplate[] = [
91
104
  {
92
105
  id: 'household',
106
+ source: 'builtin',
93
107
  name: 'Household Operator',
94
108
  description: 'Coordinate household tasks, home service checks, shared routines, and family logistics.',
95
109
  personaName: 'Household Operator',
@@ -149,6 +163,7 @@ const STARTER_TEMPLATES: readonly AgentRuntimeProfileStarterTemplate[] = [
149
163
  },
150
164
  {
151
165
  id: 'research',
166
+ source: 'builtin',
152
167
  name: 'Research Analyst',
153
168
  description: 'Source-grounded research, brief generation, question tracking, and evidence review.',
154
169
  personaName: 'Research Analyst',
@@ -208,6 +223,7 @@ const STARTER_TEMPLATES: readonly AgentRuntimeProfileStarterTemplate[] = [
208
223
  },
209
224
  {
210
225
  id: 'travel',
226
+ source: 'builtin',
211
227
  name: 'Travel Planner',
212
228
  description: 'Trip planning, itinerary decisions, packing, local constraints, and travel follow-through.',
213
229
  personaName: 'Travel Planner',
@@ -267,6 +283,7 @@ const STARTER_TEMPLATES: readonly AgentRuntimeProfileStarterTemplate[] = [
267
283
  },
268
284
  {
269
285
  id: 'operations',
286
+ source: 'builtin',
270
287
  name: 'Operations Lead',
271
288
  description: 'Operational monitoring, incident triage, approvals, schedules, and service health.',
272
289
  personaName: 'Operations Lead',
@@ -325,6 +342,7 @@ const STARTER_TEMPLATES: readonly AgentRuntimeProfileStarterTemplate[] = [
325
342
  },
326
343
  {
327
344
  id: 'personal-productivity',
345
+ source: 'builtin',
328
346
  name: 'Personal Productivity',
329
347
  description: 'Task capture, weekly planning, focus blocks, reminders, and decision hygiene.',
330
348
  personaName: 'Personal Productivity Coach',
@@ -408,6 +426,10 @@ export function getAgentRuntimeProfilesRoot(baseHomeDirectory: string): string {
408
426
  return join(baseHomeDirectory, '.goodvibes', 'agent', 'profile-homes');
409
427
  }
410
428
 
429
+ export function getAgentRuntimeProfileTemplatesRoot(baseHomeDirectory: string): string {
430
+ return join(baseHomeDirectory, '.goodvibes', GOODVIBES_AGENT_SURFACE_ROOT, 'profile-starters');
431
+ }
432
+
411
433
  export function resolveAgentRuntimeProfileHome(baseHomeDirectory: string, profileName: string): AgentRuntimeProfileResolution {
412
434
  const id = assertValidAgentRuntimeProfileId(profileName);
413
435
  return {
@@ -465,31 +487,163 @@ function profileStorePath(homeDirectory: string, folder: string, file: string):
465
487
  }
466
488
 
467
489
  export function isAgentRuntimeProfileTemplateId(value: unknown): value is AgentRuntimeProfileTemplateId {
468
- return typeof value === 'string' && STARTER_TEMPLATES.some((template) => template.id === value);
490
+ if (typeof value !== 'string') return false;
491
+ try {
492
+ assertValidAgentRuntimeProfileId(value);
493
+ return true;
494
+ } catch {
495
+ return false;
496
+ }
469
497
  }
470
498
 
471
- export function listAgentRuntimeProfileTemplates(): readonly AgentRuntimeProfileTemplateSummary[] {
472
- return STARTER_TEMPLATES.map((template) => ({
499
+ function summarizeTemplate(template: AgentRuntimeProfileStarterTemplate): AgentRuntimeProfileTemplateSummary {
500
+ return {
473
501
  id: template.id,
474
502
  name: template.name,
475
503
  description: template.description,
476
504
  personaName: template.personaName,
477
505
  skillNames: [...template.skillNames],
478
506
  routineNames: [...template.routineNames],
479
- }));
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>;
480
525
  }
481
526
 
482
- export function getAgentRuntimeProfileTemplate(templateId: AgentRuntimeProfileTemplateId): AgentRuntimeProfileTemplateSummary {
483
- const template = STARTER_TEMPLATES.find((entry) => entry.id === templateId);
484
- if (!template) throw new Error(`Unknown Agent starter profile template: ${templateId}`);
527
+ function parseTemplateSkill(value: unknown): AgentRuntimeProfileStarterTemplate['skills'][number] {
528
+ const record = readTemplateObject(value, 'skill');
485
529
  return {
486
- id: template.id,
487
- name: template.name,
488
- description: template.description,
489
- personaName: template.personaName,
490
- skillNames: [...template.skillNames],
491
- routineNames: [...template.routineNames],
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),
492
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 });
493
647
  }
494
648
 
495
649
  function createMissingSkill(registry: AgentSkillRegistry, template: AgentRuntimeProfileStarterTemplate['skills'][number]): string {
@@ -522,9 +676,8 @@ function createMissingRoutine(registry: AgentRoutineRegistry, template: AgentRun
522
676
  }).id;
523
677
  }
524
678
 
525
- export function applyAgentRuntimeProfileTemplate(homeDirectory: string, templateId: AgentRuntimeProfileTemplateId): AgentRuntimeProfileTemplateApplication {
526
- const template = STARTER_TEMPLATES.find((entry) => entry.id === templateId);
527
- if (!template) throw new Error(`Unknown Agent starter profile template: ${templateId}`);
679
+ export function applyAgentRuntimeProfileTemplate(homeDirectory: string, templateId: AgentRuntimeProfileTemplateId, baseHomeDirectory?: string): AgentRuntimeProfileTemplateApplication {
680
+ const template = resolveAgentRuntimeProfileTemplate(templateId, baseHomeDirectory);
528
681
  const personaRegistry = new AgentPersonaRegistry(profileStorePath(homeDirectory, 'personas', 'personas.json'));
529
682
  const skillRegistry = new AgentSkillRegistry(profileStorePath(homeDirectory, 'skills', 'skills.json'));
530
683
  const routineRegistry = new AgentRoutineRegistry(profileStorePath(homeDirectory, 'routines', 'routines.json'));
@@ -544,6 +697,7 @@ export function applyAgentRuntimeProfileTemplate(homeDirectory: string, template
544
697
  return {
545
698
  id: template.id,
546
699
  name: template.name,
700
+ source: template.source,
547
701
  appliedAt: new Date().toISOString(),
548
702
  personaIds: [persona.id],
549
703
  skillIds,
@@ -572,7 +726,7 @@ export function createAgentRuntimeProfile(baseHomeDirectory: string, profileName
572
726
  mkdirSync(resolution.homeDirectory, { recursive: true });
573
727
  const createdAt = new Date().toISOString();
574
728
  const appliedTemplate = options.templateId
575
- ? applyAgentRuntimeProfileTemplate(resolution.homeDirectory, options.templateId)
729
+ ? applyAgentRuntimeProfileTemplate(resolution.homeDirectory, options.templateId, baseHomeDirectory)
576
730
  : undefined;
577
731
  writeFileSync(
578
732
  join(resolution.homeDirectory, PROFILE_CREATED_FILE),
package/src/cli/help.ts CHANGED
@@ -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 templates', '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, and personal productivity. 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 create household --template 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,6 +1,8 @@
1
1
  import {
2
2
  createAgentRuntimeProfile,
3
3
  deleteAgentRuntimeProfile,
4
+ exportAgentRuntimeProfileTemplate,
5
+ importAgentRuntimeProfileTemplate,
4
6
  isAgentRuntimeProfileTemplateId,
5
7
  listAgentRuntimeProfiles,
6
8
  listAgentRuntimeProfileTemplates,
@@ -72,13 +74,29 @@ function renderProfilesResult(result: AgentRuntimeProfileCommandResult): string
72
74
  return [
73
75
  `Agent starter profile templates (${templates.length})`,
74
76
  ...templates.map((template) => [
75
- ` ${template.id} ${template.name}`,
77
+ ` ${template.id} ${template.name} [${template.source}]`,
76
78
  ` ${template.description}`,
77
79
  ` persona: ${template.personaName}`,
78
80
  ` skills: ${template.skillNames.join(', ')}`,
79
81
  ` routines: ${template.routineNames.join(', ')}`,
80
82
  ].join('\n')),
81
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`,
82
100
  ].join('\n');
83
101
  }
84
102
  const profile = result.data?.profile;
@@ -123,10 +141,79 @@ export async function handleProfilesCommand(runtime: ProfilesCommandRuntime): Pr
123
141
  }
124
142
 
125
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
+ }
126
213
  const result: AgentRuntimeProfileCommandResult = {
127
214
  ok: true,
128
215
  kind: 'agent.profiles.templates',
129
- data: { templates: listAgentRuntimeProfileTemplates() },
216
+ data: { templates: listAgentRuntimeProfileTemplates(runtime.homeDirectory) },
130
217
  };
131
218
  return {
132
219
  output: renderProfilesOutput(result, runtime.cli.flags.outputFormat),
@@ -526,6 +526,7 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
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
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. Runtime profile starters seed curated persona, skill, and routine bundles.',
104
- configure: ['/personas create ...', '/agent-skills create ...', '/routines create ...', '/memory add ...', 'goodvibes-agent profiles create research --template research --yes'],
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
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'],
107
- next: ['Add import/export editing for custom starter 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, setup transfer shortcuts, and starter profile templates.',
164
- configure: ['GOODVIBES_AGENT_HOME=<path> goodvibes-agent status', 'goodvibes-agent profiles create household --template 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', 'curated local starter packs'],
167
- next: ['Add custom starter template authoring and import.'],
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',
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.34';
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 {