@pellux/goodvibes-agent 0.1.80 → 0.1.82
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 +11 -0
- package/package.json +3 -1
- package/src/agent/skill-registry.ts +255 -5
- package/src/cli/help.ts +4 -4
- package/src/cli/management.ts +54 -136
- package/src/cli/status.ts +10 -10
- package/src/input/agent-workspace-categories.ts +2 -0
- package/src/input/agent-workspace-setup.ts +6 -4
- package/src/input/agent-workspace-snapshot.ts +26 -3
- package/src/input/agent-workspace-types.ts +4 -0
- package/src/input/commands/agent-skills-runtime.ts +151 -3
- package/src/input/commands/health-runtime.ts +12 -14
- package/src/input/commands/platform-access-runtime.ts +8 -4
- package/src/input/commands.ts +0 -2
- package/src/input/onboarding/onboarding-wizard-helpers.ts +1 -1
- package/src/panels/builtin/operations.ts +0 -10
- package/src/panels/provider-health-domains.ts +9 -8
- package/src/renderer/agent-workspace.ts +9 -6
- package/src/runtime/onboarding/apply.ts +6 -154
- package/src/runtime/onboarding/derivation.ts +4 -4
- package/src/runtime/onboarding/types.ts +1 -11
- package/src/runtime/onboarding/verify.ts +3 -25
- package/src/version.ts +1 -1
- package/src/input/commands/local-auth-runtime.ts +0 -128
- package/src/panels/local-auth-panel.ts +0 -130
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GoodVibes Agent will be recorded here.
|
|
4
4
|
|
|
5
|
+
## 0.1.82 - 2026-06-01
|
|
6
|
+
|
|
7
|
+
- 3185531 Remove Agent local auth ownership paths
|
|
8
|
+
|
|
9
|
+
## 0.1.81 - 2026-06-01
|
|
10
|
+
|
|
11
|
+
- Added local Agent skill bundles so users can group related local skills, enable or disable the bundle, and inject the bundle's member procedures into the same serial assistant conversation.
|
|
12
|
+
- Added `/agent-skills bundle ...` commands plus Agent workspace visibility for skill bundle counts, enabled bundle state, active skill count, and bundle membership.
|
|
13
|
+
- Restored the GitHub CI eval gate script and replaced the copied TUI/daemon release workflow with an Agent package release workflow that validates Bun global install, package contents, compiled binary launch, and optional npm publish.
|
|
14
|
+
- Kept bundles Agent-local and reviewable with no daemon lifecycle behavior, hidden background agents, or non-Agent knowledge fallback.
|
|
15
|
+
|
|
5
16
|
## 0.1.80 - 2026-06-01
|
|
6
17
|
|
|
7
18
|
- Added an Agent day-one readiness checklist to the onboarding review step covering runtime connection, default model route, profile setup, isolated Agent Knowledge, local behavior, channels, routines/schedules, and explicit build delegation.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.82",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "GoodVibes personal operator assistant TUI with a proactive Agent product brain, isolated Agent Knowledge, local profiles, routines, skills, personas, and explicit build delegation.",
|
|
6
6
|
"type": "module",
|
|
@@ -92,6 +92,8 @@
|
|
|
92
92
|
"publish:dry-run": "bun run scripts/publish-package.ts --dry-run",
|
|
93
93
|
"publish:check": "bun run scripts/publish-check.ts",
|
|
94
94
|
"package:install-check": "bun run scripts/package-install-check.ts",
|
|
95
|
+
"eval:gate": "bun test src/test/runtime/eval/runner.test.ts",
|
|
96
|
+
"ci:gate": "bun run typecheck && bun run test && bun run architecture:check && bun run perf:check && bun run eval:gate && bun run build && bun run publish:check && bun run package:install-check && bun run verification:ledger",
|
|
95
97
|
"build:prod": "bun run scripts/build.ts",
|
|
96
98
|
"build:all": "bun run scripts/build.ts --all",
|
|
97
99
|
"perf:check": "bun run scripts/perf-check.ts",
|
|
@@ -24,6 +24,21 @@ export interface AgentSkillRecord {
|
|
|
24
24
|
readonly reviewedAt?: string;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export interface AgentSkillBundleRecord {
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly name: string;
|
|
30
|
+
readonly description: string;
|
|
31
|
+
readonly skillIds: readonly string[];
|
|
32
|
+
readonly enabled: boolean;
|
|
33
|
+
readonly source: AgentSkillSource;
|
|
34
|
+
readonly provenance: string;
|
|
35
|
+
readonly reviewState: AgentSkillReviewState;
|
|
36
|
+
readonly staleReason?: string;
|
|
37
|
+
readonly createdAt: string;
|
|
38
|
+
readonly updatedAt: string;
|
|
39
|
+
readonly reviewedAt?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
export interface AgentSkillCreateInput {
|
|
28
43
|
readonly name: string;
|
|
29
44
|
readonly description: string;
|
|
@@ -35,6 +50,15 @@ export interface AgentSkillCreateInput {
|
|
|
35
50
|
readonly provenance?: string;
|
|
36
51
|
}
|
|
37
52
|
|
|
53
|
+
export interface AgentSkillBundleCreateInput {
|
|
54
|
+
readonly name: string;
|
|
55
|
+
readonly description: string;
|
|
56
|
+
readonly skillIds: readonly string[];
|
|
57
|
+
readonly enabled?: boolean;
|
|
58
|
+
readonly source?: AgentSkillSource;
|
|
59
|
+
readonly provenance?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
38
62
|
export interface AgentSkillUpdateInput {
|
|
39
63
|
readonly name?: string;
|
|
40
64
|
readonly description?: string;
|
|
@@ -44,15 +68,26 @@ export interface AgentSkillUpdateInput {
|
|
|
44
68
|
readonly provenance?: string;
|
|
45
69
|
}
|
|
46
70
|
|
|
71
|
+
export interface AgentSkillBundleUpdateInput {
|
|
72
|
+
readonly name?: string;
|
|
73
|
+
readonly description?: string;
|
|
74
|
+
readonly skillIds?: readonly string[];
|
|
75
|
+
readonly provenance?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
47
78
|
export interface AgentSkillSnapshot {
|
|
48
79
|
readonly path: string;
|
|
49
80
|
readonly skills: readonly AgentSkillRecord[];
|
|
50
81
|
readonly enabledSkills: readonly AgentSkillRecord[];
|
|
82
|
+
readonly bundles: readonly AgentSkillBundleRecord[];
|
|
83
|
+
readonly enabledBundles: readonly AgentSkillBundleRecord[];
|
|
84
|
+
readonly activeSkills: readonly AgentSkillRecord[];
|
|
51
85
|
}
|
|
52
86
|
|
|
53
87
|
interface SkillStoreFile {
|
|
54
88
|
readonly version: 1;
|
|
55
89
|
readonly skills: readonly AgentSkillRecord[];
|
|
90
|
+
readonly bundles: readonly AgentSkillBundleRecord[];
|
|
56
91
|
}
|
|
57
92
|
|
|
58
93
|
const STORE_VERSION = 1;
|
|
@@ -126,14 +161,44 @@ function parseSkill(value: unknown): AgentSkillRecord | null {
|
|
|
126
161
|
};
|
|
127
162
|
}
|
|
128
163
|
|
|
164
|
+
function parseBundle(value: unknown): AgentSkillBundleRecord | null {
|
|
165
|
+
if (!isRecord(value)) return null;
|
|
166
|
+
const id = readString(value.id).trim();
|
|
167
|
+
const name = normalizeName(readString(value.name));
|
|
168
|
+
const description = readString(value.description).trim();
|
|
169
|
+
const skillIds = readStringArray(value.skillIds).map(slugify).filter(Boolean);
|
|
170
|
+
if (!id || !name || !description || skillIds.length === 0) return null;
|
|
171
|
+
const reviewState = value.reviewState === 'reviewed' || value.reviewState === 'stale' ? value.reviewState : 'fresh';
|
|
172
|
+
const source = value.source === 'agent' || value.source === 'imported' || value.source === 'system' ? value.source : 'user';
|
|
173
|
+
const staleReason = readString(value.staleReason).trim();
|
|
174
|
+
const reviewedAt = readString(value.reviewedAt).trim();
|
|
175
|
+
return {
|
|
176
|
+
id,
|
|
177
|
+
name,
|
|
178
|
+
description,
|
|
179
|
+
skillIds: normalizeList(skillIds).map(slugify),
|
|
180
|
+
enabled: value.enabled === true,
|
|
181
|
+
source,
|
|
182
|
+
provenance: readString(value.provenance, source).trim() || source,
|
|
183
|
+
reviewState,
|
|
184
|
+
staleReason: staleReason || undefined,
|
|
185
|
+
createdAt: readString(value.createdAt, nowIso()),
|
|
186
|
+
updatedAt: readString(value.updatedAt, nowIso()),
|
|
187
|
+
reviewedAt: reviewedAt || undefined,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
129
191
|
function parseStore(raw: string): SkillStoreFile {
|
|
130
192
|
const parsed: unknown = JSON.parse(raw);
|
|
131
|
-
if (!isRecord(parsed)) return { version: STORE_VERSION, skills: [] };
|
|
193
|
+
if (!isRecord(parsed)) return { version: STORE_VERSION, skills: [], bundles: [] };
|
|
132
194
|
return {
|
|
133
195
|
version: STORE_VERSION,
|
|
134
196
|
skills: Array.isArray(parsed.skills)
|
|
135
197
|
? parsed.skills.map(parseSkill).filter((entry): entry is AgentSkillRecord => entry !== null)
|
|
136
198
|
: [],
|
|
199
|
+
bundles: Array.isArray(parsed.bundles)
|
|
200
|
+
? parsed.bundles.map(parseBundle).filter((entry): entry is AgentSkillBundleRecord => entry !== null)
|
|
201
|
+
: [],
|
|
137
202
|
};
|
|
138
203
|
}
|
|
139
204
|
|
|
@@ -154,10 +219,18 @@ export class AgentSkillRegistry {
|
|
|
154
219
|
|
|
155
220
|
public snapshot(): AgentSkillSnapshot {
|
|
156
221
|
const store = this.readStore();
|
|
222
|
+
const enabledBundles = store.bundles.filter((bundle) => bundle.enabled);
|
|
223
|
+
const activeSkillIds = new Set<string>(store.skills.filter((skill) => skill.enabled).map((skill) => skill.id));
|
|
224
|
+
for (const bundle of enabledBundles) {
|
|
225
|
+
for (const skillId of bundle.skillIds) activeSkillIds.add(skillId);
|
|
226
|
+
}
|
|
157
227
|
return {
|
|
158
228
|
path: this.storePath,
|
|
159
229
|
skills: [...store.skills],
|
|
160
230
|
enabledSkills: store.skills.filter((skill) => skill.enabled),
|
|
231
|
+
bundles: [...store.bundles],
|
|
232
|
+
enabledBundles,
|
|
233
|
+
activeSkills: store.skills.filter((skill) => activeSkillIds.has(skill.id)),
|
|
161
234
|
};
|
|
162
235
|
}
|
|
163
236
|
|
|
@@ -178,12 +251,33 @@ export class AgentSkillRegistry {
|
|
|
178
251
|
].some((field) => field.toLowerCase().includes(normalized)));
|
|
179
252
|
}
|
|
180
253
|
|
|
254
|
+
public listBundles(): readonly AgentSkillBundleRecord[] {
|
|
255
|
+
return this.snapshot().bundles;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
public searchBundles(query: string): readonly AgentSkillBundleRecord[] {
|
|
259
|
+
const normalized = query.trim().toLowerCase();
|
|
260
|
+
if (!normalized) return this.listBundles();
|
|
261
|
+
return this.listBundles().filter((bundle) => [
|
|
262
|
+
bundle.id,
|
|
263
|
+
bundle.name,
|
|
264
|
+
bundle.description,
|
|
265
|
+
...bundle.skillIds,
|
|
266
|
+
].some((field) => field.toLowerCase().includes(normalized)));
|
|
267
|
+
}
|
|
268
|
+
|
|
181
269
|
public get(idOrName: string): AgentSkillRecord | null {
|
|
182
270
|
const lookup = idOrName.trim().toLowerCase();
|
|
183
271
|
if (!lookup) return null;
|
|
184
272
|
return this.list().find((skill) => skill.id.toLowerCase() === lookup || skill.name.toLowerCase() === lookup) ?? null;
|
|
185
273
|
}
|
|
186
274
|
|
|
275
|
+
public getBundle(idOrName: string): AgentSkillBundleRecord | null {
|
|
276
|
+
const lookup = idOrName.trim().toLowerCase();
|
|
277
|
+
if (!lookup) return null;
|
|
278
|
+
return this.listBundles().find((bundle) => bundle.id.toLowerCase() === lookup || bundle.name.toLowerCase() === lookup) ?? null;
|
|
279
|
+
}
|
|
280
|
+
|
|
187
281
|
public create(input: AgentSkillCreateInput): AgentSkillRecord {
|
|
188
282
|
const store = this.readStore();
|
|
189
283
|
const name = normalizeName(input.name);
|
|
@@ -212,6 +306,32 @@ export class AgentSkillRegistry {
|
|
|
212
306
|
return skill;
|
|
213
307
|
}
|
|
214
308
|
|
|
309
|
+
public createBundle(input: AgentSkillBundleCreateInput): AgentSkillBundleRecord {
|
|
310
|
+
const store = this.readStore();
|
|
311
|
+
const name = normalizeName(input.name);
|
|
312
|
+
const description = input.description.trim();
|
|
313
|
+
const skillIds = this.normalizeExistingSkillIds(store, input.skillIds);
|
|
314
|
+
this.validateBundleRequired(name, description, skillIds);
|
|
315
|
+
assertNoSecretLikeText([name, description, ...skillIds]);
|
|
316
|
+
const duplicate = store.bundles.find((bundle) => bundle.name.toLowerCase() === name.toLowerCase());
|
|
317
|
+
if (duplicate) throw new Error(`Skill bundle already exists: ${duplicate.id}`);
|
|
318
|
+
const timestamp = nowIso();
|
|
319
|
+
const bundle: AgentSkillBundleRecord = {
|
|
320
|
+
id: this.nextBundleId(name, store.bundles),
|
|
321
|
+
name,
|
|
322
|
+
description,
|
|
323
|
+
skillIds,
|
|
324
|
+
enabled: input.enabled === true,
|
|
325
|
+
source: input.source ?? 'user',
|
|
326
|
+
provenance: input.provenance?.trim() || input.source || 'user',
|
|
327
|
+
reviewState: 'fresh',
|
|
328
|
+
createdAt: timestamp,
|
|
329
|
+
updatedAt: timestamp,
|
|
330
|
+
};
|
|
331
|
+
this.writeStore({ ...store, bundles: [...store.bundles, bundle] });
|
|
332
|
+
return bundle;
|
|
333
|
+
}
|
|
334
|
+
|
|
215
335
|
public update(idOrName: string, input: AgentSkillUpdateInput): AgentSkillRecord {
|
|
216
336
|
const store = this.readStore();
|
|
217
337
|
const existing = this.findInStore(store, idOrName);
|
|
@@ -243,6 +363,35 @@ export class AgentSkillRegistry {
|
|
|
243
363
|
return updated;
|
|
244
364
|
}
|
|
245
365
|
|
|
366
|
+
public updateBundle(idOrName: string, input: AgentSkillBundleUpdateInput): AgentSkillBundleRecord {
|
|
367
|
+
const store = this.readStore();
|
|
368
|
+
const existing = this.findBundleInStore(store, idOrName);
|
|
369
|
+
if (!existing) throw new Error(`Unknown skill bundle: ${idOrName}`);
|
|
370
|
+
const name = input.name === undefined ? existing.name : normalizeName(input.name);
|
|
371
|
+
const description = input.description === undefined ? existing.description : input.description.trim();
|
|
372
|
+
const skillIds = input.skillIds === undefined ? existing.skillIds : this.normalizeExistingSkillIds(store, input.skillIds);
|
|
373
|
+
this.validateBundleRequired(name, description, skillIds);
|
|
374
|
+
assertNoSecretLikeText([name, description, ...skillIds]);
|
|
375
|
+
const duplicate = store.bundles.find((bundle) => bundle.id !== existing.id && bundle.name.toLowerCase() === name.toLowerCase());
|
|
376
|
+
if (duplicate) throw new Error(`Skill bundle already exists: ${duplicate.id}`);
|
|
377
|
+
const updated: AgentSkillBundleRecord = {
|
|
378
|
+
...existing,
|
|
379
|
+
name,
|
|
380
|
+
description,
|
|
381
|
+
skillIds,
|
|
382
|
+
provenance: input.provenance === undefined ? existing.provenance : input.provenance.trim() || existing.provenance,
|
|
383
|
+
reviewState: 'fresh',
|
|
384
|
+
staleReason: undefined,
|
|
385
|
+
reviewedAt: undefined,
|
|
386
|
+
updatedAt: nowIso(),
|
|
387
|
+
};
|
|
388
|
+
this.writeStore({
|
|
389
|
+
...store,
|
|
390
|
+
bundles: store.bundles.map((bundle) => bundle.id === existing.id ? updated : bundle),
|
|
391
|
+
});
|
|
392
|
+
return updated;
|
|
393
|
+
}
|
|
394
|
+
|
|
246
395
|
public setEnabled(idOrName: string, enabled: boolean): AgentSkillRecord {
|
|
247
396
|
const store = this.readStore();
|
|
248
397
|
const existing = this.findInStore(store, idOrName);
|
|
@@ -255,6 +404,18 @@ export class AgentSkillRegistry {
|
|
|
255
404
|
return updated;
|
|
256
405
|
}
|
|
257
406
|
|
|
407
|
+
public setBundleEnabled(idOrName: string, enabled: boolean): AgentSkillBundleRecord {
|
|
408
|
+
const store = this.readStore();
|
|
409
|
+
const existing = this.findBundleInStore(store, idOrName);
|
|
410
|
+
if (!existing) throw new Error(`Unknown skill bundle: ${idOrName}`);
|
|
411
|
+
const updated: AgentSkillBundleRecord = { ...existing, enabled, updatedAt: nowIso() };
|
|
412
|
+
this.writeStore({
|
|
413
|
+
...store,
|
|
414
|
+
bundles: store.bundles.map((bundle) => bundle.id === existing.id ? updated : bundle),
|
|
415
|
+
});
|
|
416
|
+
return updated;
|
|
417
|
+
}
|
|
418
|
+
|
|
258
419
|
public markReviewed(idOrName: string): AgentSkillRecord {
|
|
259
420
|
const store = this.readStore();
|
|
260
421
|
const existing = this.findInStore(store, idOrName);
|
|
@@ -273,6 +434,24 @@ export class AgentSkillRegistry {
|
|
|
273
434
|
return updated;
|
|
274
435
|
}
|
|
275
436
|
|
|
437
|
+
public markBundleReviewed(idOrName: string): AgentSkillBundleRecord {
|
|
438
|
+
const store = this.readStore();
|
|
439
|
+
const existing = this.findBundleInStore(store, idOrName);
|
|
440
|
+
if (!existing) throw new Error(`Unknown skill bundle: ${idOrName}`);
|
|
441
|
+
const updated: AgentSkillBundleRecord = {
|
|
442
|
+
...existing,
|
|
443
|
+
reviewState: 'reviewed',
|
|
444
|
+
staleReason: undefined,
|
|
445
|
+
reviewedAt: nowIso(),
|
|
446
|
+
updatedAt: nowIso(),
|
|
447
|
+
};
|
|
448
|
+
this.writeStore({
|
|
449
|
+
...store,
|
|
450
|
+
bundles: store.bundles.map((bundle) => bundle.id === existing.id ? updated : bundle),
|
|
451
|
+
});
|
|
452
|
+
return updated;
|
|
453
|
+
}
|
|
454
|
+
|
|
276
455
|
public markStale(idOrName: string, reason: string): AgentSkillRecord {
|
|
277
456
|
const store = this.readStore();
|
|
278
457
|
const existing = this.findInStore(store, idOrName);
|
|
@@ -290,6 +469,23 @@ export class AgentSkillRegistry {
|
|
|
290
469
|
return updated;
|
|
291
470
|
}
|
|
292
471
|
|
|
472
|
+
public markBundleStale(idOrName: string, reason: string): AgentSkillBundleRecord {
|
|
473
|
+
const store = this.readStore();
|
|
474
|
+
const existing = this.findBundleInStore(store, idOrName);
|
|
475
|
+
if (!existing) throw new Error(`Unknown skill bundle: ${idOrName}`);
|
|
476
|
+
const updated: AgentSkillBundleRecord = {
|
|
477
|
+
...existing,
|
|
478
|
+
reviewState: 'stale',
|
|
479
|
+
staleReason: reason.trim() || 'Marked stale by user.',
|
|
480
|
+
updatedAt: nowIso(),
|
|
481
|
+
};
|
|
482
|
+
this.writeStore({
|
|
483
|
+
...store,
|
|
484
|
+
bundles: store.bundles.map((bundle) => bundle.id === existing.id ? updated : bundle),
|
|
485
|
+
});
|
|
486
|
+
return updated;
|
|
487
|
+
}
|
|
488
|
+
|
|
293
489
|
public deleteSkill(idOrName: string): AgentSkillRecord {
|
|
294
490
|
const store = this.readStore();
|
|
295
491
|
const existing = this.findInStore(store, idOrName);
|
|
@@ -297,6 +493,20 @@ export class AgentSkillRegistry {
|
|
|
297
493
|
this.writeStore({
|
|
298
494
|
...store,
|
|
299
495
|
skills: store.skills.filter((skill) => skill.id !== existing.id),
|
|
496
|
+
bundles: store.bundles
|
|
497
|
+
.map((bundle) => ({ ...bundle, skillIds: bundle.skillIds.filter((skillId) => skillId !== existing.id) }))
|
|
498
|
+
.filter((bundle) => bundle.skillIds.length > 0),
|
|
499
|
+
});
|
|
500
|
+
return existing;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
public deleteBundle(idOrName: string): AgentSkillBundleRecord {
|
|
504
|
+
const store = this.readStore();
|
|
505
|
+
const existing = this.findBundleInStore(store, idOrName);
|
|
506
|
+
if (!existing) throw new Error(`Unknown skill bundle: ${idOrName}`);
|
|
507
|
+
this.writeStore({
|
|
508
|
+
...store,
|
|
509
|
+
bundles: store.bundles.filter((bundle) => bundle.id !== existing.id),
|
|
300
510
|
});
|
|
301
511
|
return existing;
|
|
302
512
|
}
|
|
@@ -307,6 +517,12 @@ export class AgentSkillRegistry {
|
|
|
307
517
|
if (!procedure) throw new Error('Skill procedure is required.');
|
|
308
518
|
}
|
|
309
519
|
|
|
520
|
+
private validateBundleRequired(name: string, description: string, skillIds: readonly string[]): void {
|
|
521
|
+
if (!name) throw new Error('Skill bundle name is required.');
|
|
522
|
+
if (!description) throw new Error('Skill bundle description is required.');
|
|
523
|
+
if (skillIds.length === 0) throw new Error('Skill bundle must include at least one existing skill.');
|
|
524
|
+
}
|
|
525
|
+
|
|
310
526
|
private nextId(name: string, skills: readonly AgentSkillRecord[]): string {
|
|
311
527
|
const base = slugify(name);
|
|
312
528
|
const ids = new Set(skills.map((skill) => skill.id));
|
|
@@ -318,14 +534,40 @@ export class AgentSkillRegistry {
|
|
|
318
534
|
throw new Error(`Could not allocate skill id for ${name}.`);
|
|
319
535
|
}
|
|
320
536
|
|
|
537
|
+
private nextBundleId(name: string, bundles: readonly AgentSkillBundleRecord[]): string {
|
|
538
|
+
const base = slugify(name);
|
|
539
|
+
const ids = new Set(bundles.map((bundle) => bundle.id));
|
|
540
|
+
if (!ids.has(base)) return base;
|
|
541
|
+
for (let index = 2; index < 1000; index += 1) {
|
|
542
|
+
const candidate = `${base}-${index}`;
|
|
543
|
+
if (!ids.has(candidate)) return candidate;
|
|
544
|
+
}
|
|
545
|
+
throw new Error(`Could not allocate skill bundle id for ${name}.`);
|
|
546
|
+
}
|
|
547
|
+
|
|
321
548
|
private findInStore(store: SkillStoreFile, idOrName: string): AgentSkillRecord | null {
|
|
322
549
|
const lookup = idOrName.trim().toLowerCase();
|
|
323
550
|
if (!lookup) return null;
|
|
324
551
|
return store.skills.find((skill) => skill.id.toLowerCase() === lookup || skill.name.toLowerCase() === lookup) ?? null;
|
|
325
552
|
}
|
|
326
553
|
|
|
554
|
+
private findBundleInStore(store: SkillStoreFile, idOrName: string): AgentSkillBundleRecord | null {
|
|
555
|
+
const lookup = idOrName.trim().toLowerCase();
|
|
556
|
+
if (!lookup) return null;
|
|
557
|
+
return store.bundles.find((bundle) => bundle.id.toLowerCase() === lookup || bundle.name.toLowerCase() === lookup) ?? null;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
private normalizeExistingSkillIds(store: SkillStoreFile, skillIds: readonly string[]): readonly string[] {
|
|
561
|
+
const normalized = normalizeList(skillIds).map(slugify);
|
|
562
|
+
const known = new Set(store.skills.map((skill) => skill.id));
|
|
563
|
+
for (const skillId of normalized) {
|
|
564
|
+
if (!known.has(skillId)) throw new Error(`Unknown skill for bundle: ${skillId}`);
|
|
565
|
+
}
|
|
566
|
+
return normalized;
|
|
567
|
+
}
|
|
568
|
+
|
|
327
569
|
private readStore(): SkillStoreFile {
|
|
328
|
-
if (!existsSync(this.storePath)) return { version: STORE_VERSION, skills: [] };
|
|
570
|
+
if (!existsSync(this.storePath)) return { version: STORE_VERSION, skills: [], bundles: [] };
|
|
329
571
|
try {
|
|
330
572
|
return parseStore(readFileSync(this.storePath, 'utf-8'));
|
|
331
573
|
} catch (error) {
|
|
@@ -342,13 +584,21 @@ export class AgentSkillRegistry {
|
|
|
342
584
|
}
|
|
343
585
|
|
|
344
586
|
export function buildEnabledSkillsPrompt(shellPaths: ShellPathService): string | null {
|
|
345
|
-
const
|
|
346
|
-
|
|
587
|
+
const snapshot = AgentSkillRegistry.fromShellPaths(shellPaths).snapshot();
|
|
588
|
+
const active = snapshot.activeSkills;
|
|
589
|
+
if (active.length === 0 && snapshot.enabledBundles.length === 0) return null;
|
|
347
590
|
return [
|
|
348
591
|
'## Enabled GoodVibes Agent Skills',
|
|
349
592
|
'Use these local reusable procedures inside the same serial assistant conversation when they fit the user request.',
|
|
350
593
|
'',
|
|
351
|
-
...
|
|
594
|
+
...snapshot.enabledBundles.slice(0, 4).flatMap((bundle) => [
|
|
595
|
+
`### Skill Bundle: ${bundle.name}`,
|
|
596
|
+
`Description: ${bundle.description}`,
|
|
597
|
+
`Review state: ${bundle.reviewState}`,
|
|
598
|
+
`Included skills: ${bundle.skillIds.join(', ')}`,
|
|
599
|
+
'',
|
|
600
|
+
]),
|
|
601
|
+
...active.slice(0, 8).flatMap((skill) => [
|
|
352
602
|
`### ${skill.name}`,
|
|
353
603
|
`Description: ${skill.description}`,
|
|
354
604
|
`Review state: ${skill.reviewState}`,
|
package/src/cli/help.ts
CHANGED
|
@@ -38,7 +38,7 @@ export function renderGoodVibesHelp(binary = 'goodvibes-agent'): string {
|
|
|
38
38
|
' providers List/inspect/use provider config/auth posture',
|
|
39
39
|
' profiles Manage isolated Agent profile homes',
|
|
40
40
|
' routines Inspect local routines and explicitly promote one to an external schedule',
|
|
41
|
-
' auth Inspect
|
|
41
|
+
' auth Inspect Agent auth posture and external runtime token state',
|
|
42
42
|
' compat Inspect Agent SDK pin, runtime version, and Agent knowledge route readiness',
|
|
43
43
|
' knowledge Use isolated Agent Knowledge/Wiki routes',
|
|
44
44
|
' ask|search Shortcuts for isolated Agent Knowledge ask/search',
|
|
@@ -170,9 +170,9 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
170
170
|
examples: ['models current', 'models openai', 'models use openai:gpt-5.4'],
|
|
171
171
|
},
|
|
172
172
|
auth: {
|
|
173
|
-
usage: ['auth
|
|
174
|
-
summary: 'Inspect and
|
|
175
|
-
examples: ['auth', 'auth
|
|
173
|
+
usage: ['auth', 'auth status', 'auth review', 'auth users', 'auth sessions'],
|
|
174
|
+
summary: 'Inspect Agent auth posture and external runtime token state. Runtime user/session administration belongs to the runtime-owning TUI or host tooling.',
|
|
175
|
+
examples: ['auth', 'auth status', 'auth users'],
|
|
176
176
|
},
|
|
177
177
|
compat: {
|
|
178
178
|
usage: ['compat', 'compat --json'],
|