@pellux/goodvibes-agent 0.1.34 → 0.1.36
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 +8 -0
- package/README.md +5 -1
- package/docs/README.md +1 -1
- package/docs/getting-started.md +5 -1
- package/docs/operator-capability-benchmark.md +2 -2
- package/package.json +1 -1
- package/src/agent/runtime-profile.ts +177 -19
- package/src/cli/help.ts +3 -3
- package/src/cli/profiles-command.ts +89 -2
- package/src/input/agent-workspace.ts +16 -2
- package/src/input/commands/agent-runtime-profile-runtime.ts +223 -0
- package/src/input/commands.ts +2 -0
- package/src/operator/capability-benchmark.ts +9 -9
- package/src/renderer/agent-workspace.ts +2 -1
- package/src/version.ts +1 -1
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.36 - 2026-05-31
|
|
6
|
+
|
|
7
|
+
- 9de2f5e Add guided Agent starter authoring
|
|
8
|
+
|
|
9
|
+
## 0.1.35 - 2026-05-31
|
|
10
|
+
|
|
11
|
+
- 2c25d5e Add local starter profile import export
|
|
12
|
+
|
|
5
13
|
## 0.1.34 - 2026-05-31
|
|
6
14
|
|
|
7
15
|
- 28838cc Add curated Agent profile starters
|
package/README.md
CHANGED
|
@@ -46,16 +46,20 @@ Inside the Agent TUI, use `/agent`, `/home`, or `/operator` to open the operator
|
|
|
46
46
|
|
|
47
47
|
Use `goodvibes-agent capabilities` or `/capabilities` to inspect the OpenClaw/Hermes benchmark, current Agent posture, configuration commands, usage paths, and remaining gaps.
|
|
48
48
|
|
|
49
|
+
Inside the workspace, use `/agent-profile guide` to author custom profile starters without leaving the Agent TUI. The guided flow lists starters, exports starter JSON, imports edited local starters, and creates isolated runtime profiles from them.
|
|
50
|
+
|
|
49
51
|
Use isolated Agent runtime profiles when one machine needs separate operator identities or local state:
|
|
50
52
|
|
|
51
53
|
```sh
|
|
52
54
|
goodvibes-agent profiles templates
|
|
53
55
|
goodvibes-agent profiles create household --template household --yes
|
|
56
|
+
goodvibes-agent profiles templates export research ./research-starter.json --yes
|
|
57
|
+
goodvibes-agent profiles templates import ./research-starter.json --yes
|
|
54
58
|
goodvibes-agent --agent-profile household status
|
|
55
59
|
GOODVIBES_AGENT_HOME=/path/to/agent-home goodvibes-agent status
|
|
56
60
|
```
|
|
57
61
|
|
|
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.
|
|
62
|
+
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
63
|
|
|
60
64
|
Local Agent behavior is editable from the TUI:
|
|
61
65
|
|
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` and `/agent-profile guide` support 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.
|
package/docs/getting-started.md
CHANGED
|
@@ -37,6 +37,8 @@ bun run dev
|
|
|
37
37
|
|
|
38
38
|
Once the TUI opens, run `/agent`, `/home`, or `/operator` to open the Agent operator workspace. That fullscreen workspace is the current front door for setup/config, knowledge status, local memory and skills, read-only work/approval/automation views, and explicit GoodVibes TUI build delegation.
|
|
39
39
|
|
|
40
|
+
Use `/agent-profile guide` inside that workspace to walk through starter-profile authoring. It lists built-in and local starters, exports a JSON starter for editing, imports the edited starter back into this Agent home, and creates isolated profiles from the result.
|
|
41
|
+
|
|
40
42
|
## Isolated Agent Profiles
|
|
41
43
|
|
|
42
44
|
Use a separate Agent home when you want isolated local state:
|
|
@@ -50,11 +52,13 @@ Use named runtime profiles for repeatable local identities:
|
|
|
50
52
|
```sh
|
|
51
53
|
goodvibes-agent profiles templates
|
|
52
54
|
goodvibes-agent profiles create household --template household --yes
|
|
55
|
+
goodvibes-agent profiles templates export research ./research-starter.json --yes
|
|
56
|
+
goodvibes-agent profiles templates import ./research-starter.json --yes
|
|
53
57
|
goodvibes-agent --agent-profile household status
|
|
54
58
|
goodvibes-agent --agent-profile household
|
|
55
59
|
```
|
|
56
60
|
|
|
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.
|
|
61
|
+
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
62
|
|
|
59
63
|
## Local Personas, Routines, And Skills
|
|
60
64
|
|
|
@@ -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;
|
|
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; `/agent-profile guide` brings starter authoring into the Agent workspace; 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
|
-
-
|
|
78
|
+
- Visual starter-template editing inside the fullscreen Agent workspace after the command-guided authoring path.
|
|
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.
|
|
3
|
+
"version": "0.1.36",
|
|
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 =
|
|
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,33 +487,169 @@ function profileStorePath(homeDirectory: string, folder: string, file: string):
|
|
|
465
487
|
}
|
|
466
488
|
|
|
467
489
|
export function isAgentRuntimeProfileTemplateId(value: unknown): value is AgentRuntimeProfileTemplateId {
|
|
468
|
-
|
|
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
|
-
|
|
472
|
-
return
|
|
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
|
-
|
|
483
|
-
const
|
|
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
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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,
|
|
492
577
|
};
|
|
493
578
|
}
|
|
494
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
|
+
export function getAgentRuntimeProfileTemplateFile(templateId: AgentRuntimeProfileTemplateId, baseHomeDirectory?: string): AgentRuntimeProfileStarterTemplateFile {
|
|
619
|
+
return templateFilePayload(resolveAgentRuntimeProfileTemplate(templateId, baseHomeDirectory));
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function templateFilePayload(template: AgentRuntimeProfileStarterTemplate): AgentRuntimeProfileStarterTemplateFile {
|
|
623
|
+
return {
|
|
624
|
+
version: 1,
|
|
625
|
+
template: {
|
|
626
|
+
...template,
|
|
627
|
+
source: 'local',
|
|
628
|
+
path: undefined,
|
|
629
|
+
},
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
export function exportAgentRuntimeProfileTemplate(baseHomeDirectory: string, templateId: AgentRuntimeProfileTemplateId, outputPath: string): AgentRuntimeProfileTemplateSummary {
|
|
634
|
+
const template = resolveAgentRuntimeProfileTemplate(templateId, baseHomeDirectory);
|
|
635
|
+
const target = outputPath.trim();
|
|
636
|
+
if (!target) throw new Error('Template export path is required.');
|
|
637
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
638
|
+
writeFileSync(target, `${JSON.stringify(templateFilePayload(template), null, 2)}\n`, 'utf-8');
|
|
639
|
+
return { ...summarizeTemplate(template), path: target };
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
export function importAgentRuntimeProfileTemplate(baseHomeDirectory: string, sourcePath: string): AgentRuntimeProfileTemplateSummary {
|
|
643
|
+
const source = sourcePath.trim();
|
|
644
|
+
if (!source) throw new Error('Template import path is required.');
|
|
645
|
+
const parsed = parseStarterTemplate(JSON.parse(readFileSync(source, 'utf-8')), 'local');
|
|
646
|
+
const root = getAgentRuntimeProfileTemplatesRoot(baseHomeDirectory);
|
|
647
|
+
mkdirSync(root, { recursive: true });
|
|
648
|
+
const target = join(root, `${parsed.id}.json`);
|
|
649
|
+
writeFileSync(target, `${JSON.stringify(templateFilePayload({ ...parsed, source: 'local', path: target }), null, 2)}\n`, 'utf-8');
|
|
650
|
+
return summarizeTemplate({ ...parsed, source: 'local', path: target });
|
|
651
|
+
}
|
|
652
|
+
|
|
495
653
|
function createMissingSkill(registry: AgentSkillRegistry, template: AgentRuntimeProfileStarterTemplate['skills'][number]): string {
|
|
496
654
|
const existing = registry.get(template.name);
|
|
497
655
|
if (existing) return existing.id;
|
|
@@ -522,9 +680,8 @@ function createMissingRoutine(registry: AgentRoutineRegistry, template: AgentRun
|
|
|
522
680
|
}).id;
|
|
523
681
|
}
|
|
524
682
|
|
|
525
|
-
export function applyAgentRuntimeProfileTemplate(homeDirectory: string, templateId: AgentRuntimeProfileTemplateId): AgentRuntimeProfileTemplateApplication {
|
|
526
|
-
const template =
|
|
527
|
-
if (!template) throw new Error(`Unknown Agent starter profile template: ${templateId}`);
|
|
683
|
+
export function applyAgentRuntimeProfileTemplate(homeDirectory: string, templateId: AgentRuntimeProfileTemplateId, baseHomeDirectory?: string): AgentRuntimeProfileTemplateApplication {
|
|
684
|
+
const template = resolveAgentRuntimeProfileTemplate(templateId, baseHomeDirectory);
|
|
528
685
|
const personaRegistry = new AgentPersonaRegistry(profileStorePath(homeDirectory, 'personas', 'personas.json'));
|
|
529
686
|
const skillRegistry = new AgentSkillRegistry(profileStorePath(homeDirectory, 'skills', 'skills.json'));
|
|
530
687
|
const routineRegistry = new AgentRoutineRegistry(profileStorePath(homeDirectory, 'routines', 'routines.json'));
|
|
@@ -544,6 +701,7 @@ export function applyAgentRuntimeProfileTemplate(homeDirectory: string, template
|
|
|
544
701
|
return {
|
|
545
702
|
id: template.id,
|
|
546
703
|
name: template.name,
|
|
704
|
+
source: template.source,
|
|
547
705
|
appliedAt: new Date().toISOString(),
|
|
548
706
|
personaIds: [persona.id],
|
|
549
707
|
skillIds,
|
|
@@ -572,7 +730,7 @@ export function createAgentRuntimeProfile(baseHomeDirectory: string, profileName
|
|
|
572
730
|
mkdirSync(resolution.homeDirectory, { recursive: true });
|
|
573
731
|
const createdAt = new Date().toISOString();
|
|
574
732
|
const appliedTemplate = options.templateId
|
|
575
|
-
? applyAgentRuntimeProfileTemplate(resolution.homeDirectory, options.templateId)
|
|
733
|
+
? applyAgentRuntimeProfileTemplate(resolution.homeDirectory, options.templateId, baseHomeDirectory)
|
|
576
734
|
: undefined;
|
|
577
735
|
writeFileSync(
|
|
578
736
|
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
|
|
159
|
-
examples: ['profiles templates', 'profiles
|
|
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),
|
|
@@ -4,7 +4,7 @@ import type { CommandContext } from './command-registry.ts';
|
|
|
4
4
|
import { AgentPersonaRegistry } from '../agent/persona-registry.ts';
|
|
5
5
|
import { AgentRoutineRegistry } from '../agent/routine-registry.ts';
|
|
6
6
|
import { AgentSkillRegistry } from '../agent/skill-registry.ts';
|
|
7
|
-
import { getAgentRuntimeProfilesRoot, listAgentRuntimeProfiles } from '../agent/runtime-profile.ts';
|
|
7
|
+
import { getAgentRuntimeProfilesRoot, listAgentRuntimeProfiles, listAgentRuntimeProfileTemplates } from '../agent/runtime-profile.ts';
|
|
8
8
|
|
|
9
9
|
export const AGENT_WORKSPACE_MODAL_NAME = 'agentWorkspace';
|
|
10
10
|
|
|
@@ -97,6 +97,8 @@ export interface AgentWorkspaceRuntimeSnapshot {
|
|
|
97
97
|
readonly activeRuntimeProfile: string;
|
|
98
98
|
readonly runtimeProfileCount: number;
|
|
99
99
|
readonly runtimeProfileRoot: string;
|
|
100
|
+
readonly runtimeStarterTemplateCount: number;
|
|
101
|
+
readonly localStarterTemplateCount: number;
|
|
100
102
|
readonly configProfileCount: number;
|
|
101
103
|
readonly warnings: readonly string[];
|
|
102
104
|
}
|
|
@@ -378,6 +380,13 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
|
|
|
378
380
|
return 0;
|
|
379
381
|
}
|
|
380
382
|
})();
|
|
383
|
+
const runtimeStarterTemplates = (() => {
|
|
384
|
+
try {
|
|
385
|
+
return listAgentRuntimeProfileTemplates(context.workspace?.shellPaths?.homeDirectory ?? '');
|
|
386
|
+
} catch {
|
|
387
|
+
return [];
|
|
388
|
+
}
|
|
389
|
+
})();
|
|
381
390
|
const voiceProviders = (() => {
|
|
382
391
|
try {
|
|
383
392
|
return context.platform?.voiceProviderRegistry?.list?.() ?? [];
|
|
@@ -437,6 +446,8 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
|
|
|
437
446
|
activeRuntimeProfile: inferActiveRuntimeProfile(context.workspace?.shellPaths?.homeDirectory ?? ''),
|
|
438
447
|
runtimeProfileCount: runtimeProfiles.length,
|
|
439
448
|
runtimeProfileRoot: getAgentRuntimeProfilesRoot(context.workspace?.shellPaths?.homeDirectory ?? ''),
|
|
449
|
+
runtimeStarterTemplateCount: runtimeStarterTemplates.length,
|
|
450
|
+
localStarterTemplateCount: runtimeStarterTemplates.filter((template) => template.source === 'local').length,
|
|
440
451
|
configProfileCount,
|
|
441
452
|
warnings,
|
|
442
453
|
};
|
|
@@ -522,10 +533,13 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
|
|
|
522
533
|
detail: 'Hermes profiles isolate agent state. GoodVibes Agent exposes named runtime homes, config profile pickers, profile-sync bundles, setup transfer bundles, and support bundles while keeping the daemon external.',
|
|
523
534
|
actions: [
|
|
524
535
|
{ id: 'profiles-open', label: 'Open config profiles', detail: 'Open the TUI-derived config profile picker for display/provider/behavior profile files.', command: '/profiles', kind: 'command', safety: 'safe' },
|
|
536
|
+
{ id: 'runtime-profile-guide', label: 'Starter authoring guide', detail: 'Open the Agent-local starter authoring flow inside the TUI command surface.', command: '/agent-profile guide', kind: 'command', safety: 'safe' },
|
|
537
|
+
{ id: 'runtime-profile-templates', label: 'Browse starter templates', detail: 'List built-in and local Agent starter templates with persona, skill, routine, and source details.', command: '/agent-profile templates', kind: 'command', safety: 'read-only' },
|
|
525
538
|
{ 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
539
|
{ 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
540
|
{ 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: '
|
|
541
|
+
{ id: 'runtime-profile-create', label: 'Create runtime profile', detail: 'Create an isolated Agent runtime profile from a built-in or local starter. Requires a real name and explicit --yes.', command: '/agent-profile create <name> --template <id> --yes', kind: 'command', safety: 'safe' },
|
|
542
|
+
{ id: 'runtime-profile-template-edit', label: 'Customize starter', detail: 'Export a starter JSON file, edit it, import it as a local starter, then create a profile from it.', command: '/agent-profile template export <id> <path> --yes', kind: 'command', safety: 'safe' },
|
|
529
543
|
{ 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
544
|
],
|
|
531
545
|
},
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { mkdirSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import {
|
|
4
|
+
createAgentRuntimeProfile,
|
|
5
|
+
deleteAgentRuntimeProfile,
|
|
6
|
+
exportAgentRuntimeProfileTemplate,
|
|
7
|
+
getAgentRuntimeProfileTemplateFile,
|
|
8
|
+
importAgentRuntimeProfileTemplate,
|
|
9
|
+
listAgentRuntimeProfiles,
|
|
10
|
+
listAgentRuntimeProfileTemplates,
|
|
11
|
+
type AgentRuntimeProfileInfo,
|
|
12
|
+
type AgentRuntimeProfileTemplateSummary,
|
|
13
|
+
} from '../../agent/runtime-profile.ts';
|
|
14
|
+
import type { CommandContext, CommandRegistry } from '../command-registry.ts';
|
|
15
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
16
|
+
import { requireShellPaths } from './runtime-services.ts';
|
|
17
|
+
|
|
18
|
+
function parseFlag(args: readonly string[], name: string): string | undefined {
|
|
19
|
+
const index = args.indexOf(name);
|
|
20
|
+
if (index < 0) return undefined;
|
|
21
|
+
const value = args[index + 1];
|
|
22
|
+
return value && !value.startsWith('--') ? value : undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function profileLine(profile: AgentRuntimeProfileInfo): string {
|
|
26
|
+
const created = profile.createdAt ? ` created=${profile.createdAt}` : '';
|
|
27
|
+
const starter = profile.starterTemplateId ? ` starter=${profile.starterTemplateId}` : '';
|
|
28
|
+
return ` ${profile.id} home=${profile.homeDirectory}${created}${starter}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function templateLine(template: AgentRuntimeProfileTemplateSummary): string {
|
|
32
|
+
const source = template.source === 'local' ? `local ${template.path ?? ''}`.trim() : 'builtin';
|
|
33
|
+
return [
|
|
34
|
+
` ${template.id} [${source}]`,
|
|
35
|
+
` ${template.name}: ${template.description}`,
|
|
36
|
+
` persona: ${template.personaName}`,
|
|
37
|
+
` skills: ${template.skillNames.join(', ')}`,
|
|
38
|
+
` routines: ${template.routineNames.join(', ')}`,
|
|
39
|
+
].join('\n');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function renderProfiles(homeDirectory: string): string {
|
|
43
|
+
const profiles = listAgentRuntimeProfiles(homeDirectory);
|
|
44
|
+
if (profiles.length === 0) {
|
|
45
|
+
return [
|
|
46
|
+
'Agent Runtime Profiles',
|
|
47
|
+
' No isolated Agent runtime profiles yet.',
|
|
48
|
+
' Create one with /agent-profile create <name> --template <id> --yes.',
|
|
49
|
+
].join('\n');
|
|
50
|
+
}
|
|
51
|
+
return ['Agent Runtime Profiles', ...profiles.map(profileLine)].join('\n');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function renderTemplates(homeDirectory: string): string {
|
|
55
|
+
const templates = listAgentRuntimeProfileTemplates(homeDirectory);
|
|
56
|
+
return [
|
|
57
|
+
`Agent Starter Templates (${templates.length})`,
|
|
58
|
+
...templates.map(templateLine),
|
|
59
|
+
'',
|
|
60
|
+
'Authoring flow:',
|
|
61
|
+
' /agent-profile template export research ./agent-starter.json --yes',
|
|
62
|
+
' edit the JSON file',
|
|
63
|
+
' /agent-profile template import ./agent-starter.json --yes',
|
|
64
|
+
' /agent-profile create <name> --template <imported-id> --yes',
|
|
65
|
+
].join('\n');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function renderGuide(homeDirectory: string): string {
|
|
69
|
+
const templates = listAgentRuntimeProfileTemplates(homeDirectory);
|
|
70
|
+
const localCount = templates.filter((template) => template.source === 'local').length;
|
|
71
|
+
return [
|
|
72
|
+
'Agent Starter Authoring Guide',
|
|
73
|
+
` built-in starters: ${templates.length - localCount}`,
|
|
74
|
+
` local starters: ${localCount}`,
|
|
75
|
+
'',
|
|
76
|
+
'1. Pick a base starter:',
|
|
77
|
+
' /agent-profile templates',
|
|
78
|
+
'2. Export a starter JSON file:',
|
|
79
|
+
' /agent-profile template export research ./agent-starter.json --yes',
|
|
80
|
+
'3. Edit id, name, description, persona, skills, and routines in that JSON file.',
|
|
81
|
+
'4. Import it into this Agent home:',
|
|
82
|
+
' /agent-profile template import ./agent-starter.json --yes',
|
|
83
|
+
'5. Create a runtime profile from the imported starter:',
|
|
84
|
+
' /agent-profile create <name> --template <imported-id> --yes',
|
|
85
|
+
'',
|
|
86
|
+
'This writes only Agent-local starter/profile state. It does not mutate the daemon, default wiki, or HomeGraph.',
|
|
87
|
+
].join('\n');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function renderTemplatePreview(homeDirectory: string, templateId: string): string {
|
|
91
|
+
const file = getAgentRuntimeProfileTemplateFile(templateId, homeDirectory);
|
|
92
|
+
return [
|
|
93
|
+
`Agent Starter Template: ${file.template.id}`,
|
|
94
|
+
` name: ${file.template.name}`,
|
|
95
|
+
` source: ${file.template.source}`,
|
|
96
|
+
` description: ${file.template.description}`,
|
|
97
|
+
` persona: ${file.template.persona.name}`,
|
|
98
|
+
` skills: ${file.template.skills.map((skill) => skill.name).join(', ')}`,
|
|
99
|
+
` routines: ${file.template.routines.map((routine) => routine.name).join(', ')}`,
|
|
100
|
+
'',
|
|
101
|
+
'Export/edit/import to customize this starter:',
|
|
102
|
+
` /agent-profile template export ${file.template.id} ./agent-starter.json --yes`,
|
|
103
|
+
].join('\n');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function registerAgentRuntimeProfileRuntimeCommands(registry: CommandRegistry): void {
|
|
107
|
+
registry.register({
|
|
108
|
+
name: 'agent-profile',
|
|
109
|
+
aliases: ['runtime-profile', 'agent-profiles'],
|
|
110
|
+
description: 'Manage isolated Agent runtime profiles and starter templates',
|
|
111
|
+
usage: '[list|templates|guide|template show|template export|template import|create|delete]',
|
|
112
|
+
handler(args, ctx) {
|
|
113
|
+
const shellPaths = requireShellPaths(ctx);
|
|
114
|
+
const homeDirectory = shellPaths.homeDirectory;
|
|
115
|
+
const parsed = stripYesFlag(args);
|
|
116
|
+
const commandArgs = [...parsed.rest];
|
|
117
|
+
const sub = (commandArgs[0] ?? 'list').toLowerCase();
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
if (sub === 'list' || sub === 'profiles') {
|
|
121
|
+
ctx.print(renderProfiles(homeDirectory));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (sub === 'templates' || sub === 'starters') {
|
|
126
|
+
ctx.print(renderTemplates(homeDirectory));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (sub === 'guide' || sub === 'author') {
|
|
131
|
+
ctx.print(renderGuide(homeDirectory));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (sub === 'template' || sub === 'starter') {
|
|
136
|
+
const mode = (commandArgs[1] ?? 'show').toLowerCase();
|
|
137
|
+
if (mode === 'show' || mode === 'preview') {
|
|
138
|
+
const templateId = commandArgs[2];
|
|
139
|
+
if (!templateId) {
|
|
140
|
+
ctx.print('Usage: /agent-profile template show <id>');
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
ctx.print(renderTemplatePreview(homeDirectory, templateId));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (mode === 'export') {
|
|
147
|
+
const templateId = commandArgs[2];
|
|
148
|
+
const pathArg = commandArgs[3];
|
|
149
|
+
if (!templateId || !pathArg) {
|
|
150
|
+
ctx.print('Usage: /agent-profile template export <id> <path> --yes');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (!parsed.yes) {
|
|
154
|
+
requireYesFlag(ctx, `export Agent starter template ${templateId}`, '/agent-profile template export <id> <path> --yes');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const targetPath = shellPaths.resolveWorkspacePath(pathArg);
|
|
158
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
159
|
+
const template = exportAgentRuntimeProfileTemplate(homeDirectory, templateId, targetPath);
|
|
160
|
+
ctx.print(`Agent starter template exported: ${template.id}\n path: ${template.path ?? targetPath}\n edit it, then import it with /agent-profile template import <path> --yes`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (mode === 'import') {
|
|
164
|
+
const pathArg = commandArgs[2];
|
|
165
|
+
if (!pathArg) {
|
|
166
|
+
ctx.print('Usage: /agent-profile template import <path> --yes');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (!parsed.yes) {
|
|
170
|
+
requireYesFlag(ctx, 'import Agent starter template', '/agent-profile template import <path> --yes');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const sourcePath = shellPaths.resolveWorkspacePath(pathArg);
|
|
174
|
+
const template = importAgentRuntimeProfileTemplate(homeDirectory, sourcePath);
|
|
175
|
+
ctx.print(`Agent starter template imported: ${template.id}\n source: ${template.source}\n use: /agent-profile create <name> --template ${template.id} --yes`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
ctx.print('Usage: /agent-profile template [show <id>|export <id> <path> --yes|import <path> --yes]');
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (sub === 'create') {
|
|
183
|
+
const name = commandArgs[1];
|
|
184
|
+
const templateId = parseFlag(commandArgs, '--template') ?? parseFlag(commandArgs, '--starter');
|
|
185
|
+
if (!name) {
|
|
186
|
+
ctx.print('Usage: /agent-profile create <name> [--template <id>] --yes');
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (!parsed.yes) {
|
|
190
|
+
requireYesFlag(ctx, `create Agent runtime profile ${name}`, '/agent-profile create <name> [--template <id>] --yes');
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const profile = createAgentRuntimeProfile(homeDirectory, name, { templateId });
|
|
194
|
+
ctx.print([
|
|
195
|
+
`Agent runtime profile created: ${profile.id}`,
|
|
196
|
+
` home: ${profile.homeDirectory}`,
|
|
197
|
+
profile.starterTemplateId ? ` starter: ${profile.starterTemplateId}` : '',
|
|
198
|
+
` launch: goodvibes-agent --agent-profile ${profile.id}`,
|
|
199
|
+
].filter(Boolean).join('\n'));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (sub === 'delete') {
|
|
204
|
+
const name = commandArgs[1];
|
|
205
|
+
if (!name) {
|
|
206
|
+
ctx.print('Usage: /agent-profile delete <name> --yes');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (!parsed.yes) {
|
|
210
|
+
requireYesFlag(ctx, `delete Agent runtime profile ${name}`, '/agent-profile delete <name> --yes');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
ctx.print(deleteAgentRuntimeProfile(homeDirectory, name) ? `Agent runtime profile deleted: ${name}` : `Agent runtime profile not found: ${name}`);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
ctx.print('Usage: /agent-profile [list|templates|guide|template show <id>|template export <id> <path> --yes|template import <path> --yes|create <name> [--template <id>] --yes|delete <name> --yes]');
|
|
218
|
+
} catch (error) {
|
|
219
|
+
ctx.print(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
}
|
package/src/input/commands.ts
CHANGED
|
@@ -53,6 +53,7 @@ import { registerTtsRuntimeCommands } from './commands/tts-runtime.ts';
|
|
|
53
53
|
import { registerCloudflareRuntimeCommands } from './commands/cloudflare-runtime.ts';
|
|
54
54
|
import { registerWorkPlanRuntimeCommands } from './commands/work-plan-runtime.ts';
|
|
55
55
|
import { registerAgentWorkspaceRuntimeCommands } from './commands/agent-workspace-runtime.ts';
|
|
56
|
+
import { registerAgentRuntimeProfileRuntimeCommands } from './commands/agent-runtime-profile-runtime.ts';
|
|
56
57
|
import { registerAgentExternalizedTuiCommands } from './commands/agent-externalized-tui.ts';
|
|
57
58
|
import { registerDelegationRuntimeCommands } from './commands/delegation-runtime.ts';
|
|
58
59
|
import { registerPersonasRuntimeCommands } from './commands/personas-runtime.ts';
|
|
@@ -67,6 +68,7 @@ import { registerCapabilitiesRuntimeCommands } from './commands/capabilities-run
|
|
|
67
68
|
export function registerBuiltinCommands(registry: CommandRegistry): void {
|
|
68
69
|
registerShellCoreCommands(registry);
|
|
69
70
|
registerAgentWorkspaceRuntimeCommands(registry);
|
|
71
|
+
registerAgentRuntimeProfileRuntimeCommands(registry);
|
|
70
72
|
registerCapabilitiesRuntimeCommands(registry);
|
|
71
73
|
registerPersonasRuntimeCommands(registry);
|
|
72
74
|
registerAgentSkillsRuntimeCommands(registry);
|
|
@@ -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
|
|
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,
|
|
164
|
-
configure: ['GOODVIBES_AGENT_HOME=<path> goodvibes-agent status', 'goodvibes-agent profiles create household --template household --yes', '/agent → Profiles & Portability'],
|
|
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
|
|
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 authoring.',
|
|
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-profile guide', '/agent → Profiles & Portability'],
|
|
165
|
+
use: ['goodvibes-agent --agent-profile household', '/agent-profile templates', '/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', 'editable starter templates', 'TUI-guided starter authoring'],
|
|
167
|
+
next: ['Add visual starter-template editing inside the fullscreen Agent workspace.'],
|
|
168
168
|
},
|
|
169
169
|
{
|
|
170
170
|
id: 'security-approvals',
|
|
@@ -132,9 +132,10 @@ function snapshotLines(category: AgentWorkspaceCategory, snapshot: AgentWorkspac
|
|
|
132
132
|
{ text: `Active runtime profile: ${snapshot.activeRuntimeProfile}`, fg: PALETTE.info },
|
|
133
133
|
{ text: `Runtime profiles under this home: ${snapshot.runtimeProfileCount}`, fg: PALETTE.info },
|
|
134
134
|
{ text: `Runtime profile root: ${snapshot.runtimeProfileRoot}`, fg: PALETTE.muted },
|
|
135
|
+
{ text: `Starter templates: ${snapshot.runtimeStarterTemplateCount}; local custom: ${snapshot.localStarterTemplateCount}`, fg: PALETTE.info },
|
|
135
136
|
{ text: `Config profiles: ${snapshot.configProfileCount}`, fg: PALETTE.info },
|
|
136
137
|
{ text: 'Named runtime profiles isolate Agent-local config, sessions, memory, personas, skills, routines, setup, and bundles.', fg: PALETTE.good },
|
|
137
|
-
{ text: 'Starter
|
|
138
|
+
{ text: 'Starter authoring: browse, export, edit, import, and create Agent profiles from inside this workspace via /agent-profile.', fg: PALETTE.info },
|
|
138
139
|
{ text: 'The external daemon remains shared unless the daemon host is configured separately.', fg: PALETTE.warn },
|
|
139
140
|
{ text: 'Portable bundles require explicit export/import commands with real paths and --yes.', fg: PALETTE.muted },
|
|
140
141
|
);
|
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.
|
|
9
|
+
let _version = '0.1.36';
|
|
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 {
|