@pellux/goodvibes-agent 0.1.112 → 0.1.113
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 +6 -0
- package/dist/package/main.js +105 -45
- package/package.json +1 -1
- package/src/cli/help.ts +4 -0
- package/src/cli/local-library-command.ts +90 -1
- package/src/cli/service-posture.ts +1 -8
- package/src/cli/status.ts +1 -30
- package/src/renderer/ui-factory.ts +1 -1
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GoodVibes Agent will be recorded here.
|
|
4
4
|
|
|
5
|
+
## 0.1.113 - 2026-06-02
|
|
6
|
+
|
|
7
|
+
- efe8b9c feat: expose skill discovery in agent cli
|
|
8
|
+
- 13ef595 fix: align agent docs and launch identity
|
|
9
|
+
- 26fb57b fix: keep agent diagnostics product scoped
|
|
10
|
+
|
|
5
11
|
## 0.1.112 - 2026-06-02
|
|
6
12
|
|
|
7
13
|
- 566995c test: align operator visibility gate
|
package/dist/package/main.js
CHANGED
|
@@ -816523,7 +816523,7 @@ var createStyledCell = (char, overrides = {}) => ({
|
|
|
816523
816523
|
// src/version.ts
|
|
816524
816524
|
import { readFileSync } from "fs";
|
|
816525
816525
|
import { join } from "path";
|
|
816526
|
-
var _version = "0.1.
|
|
816526
|
+
var _version = "0.1.113";
|
|
816527
816527
|
var _sdkVersion = "0.33.35";
|
|
816528
816528
|
try {
|
|
816529
816529
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, "..", "package.json"), "utf-8"));
|
|
@@ -816960,7 +816960,7 @@ class UIFactory {
|
|
|
816960
816960
|
const CYAN = "#00ffff";
|
|
816961
816961
|
const GREY = "244";
|
|
816962
816962
|
const TITLE_COLOR = "250";
|
|
816963
|
-
const brand = ` GoodVibes `;
|
|
816963
|
+
const brand = ` GoodVibes Agent `;
|
|
816964
816964
|
const ver = `v${VERSION} `;
|
|
816965
816965
|
const stats = ` ${model} `;
|
|
816966
816966
|
const prov = `(${provider}) `;
|
|
@@ -883605,6 +883605,8 @@ var COMMAND_HELP = {
|
|
|
883605
883605
|
"skills list",
|
|
883606
883606
|
"skills enabled",
|
|
883607
883607
|
"skills active",
|
|
883608
|
+
"skills discover",
|
|
883609
|
+
"skills import-discovered <name> [--enabled] --yes",
|
|
883608
883610
|
"skills search <query>",
|
|
883609
883611
|
"skills show <id>",
|
|
883610
883612
|
"skills create --name <name> --description <summary> --procedure <steps> [--tags a,b] [--triggers a,b] [--enabled]",
|
|
@@ -883619,6 +883621,8 @@ var COMMAND_HELP = {
|
|
|
883619
883621
|
summary: "Manage reusable Agent-local procedures and skill bundles for the main conversation.",
|
|
883620
883622
|
examples: [
|
|
883621
883623
|
"skills list",
|
|
883624
|
+
"skills discover",
|
|
883625
|
+
'skills import-discovered "Daily Brief" --enabled --yes',
|
|
883622
883626
|
'skills create --name "Daily Brief" --description "Summarize operator state" --procedure "Review Agent Knowledge, work plans, approvals, and routines" --enabled',
|
|
883623
883627
|
'skills bundle create --name "Ops Pack" --description "Daily operations bundle" --skills daily-brief --enabled',
|
|
883624
883628
|
"skills delete daily-brief --yes"
|
|
@@ -883882,8 +883886,6 @@ function bindLine(label, enabled, binding) {
|
|
|
883882
883886
|
function buildCliDoctorFindings(options) {
|
|
883883
883887
|
const config6 = options.configManager;
|
|
883884
883888
|
const serviceEnabled = config6.get("service.enabled") === true;
|
|
883885
|
-
const serviceAutostart = config6.get("service.autostart") === true;
|
|
883886
|
-
const restartOnFailure = config6.get("service.restartOnFailure") === true;
|
|
883887
883889
|
const daemonEnabled = config6.get("danger.daemon") === true;
|
|
883888
883890
|
const listenerEnabled = config6.get("danger.httpListener") === true;
|
|
883889
883891
|
const webEnabled = config6.get("web.enabled") === true;
|
|
@@ -883957,28 +883959,6 @@ function buildCliDoctorFindings(options) {
|
|
|
883957
883959
|
action: "Manage connected-service availability from GoodVibes TUI or the owning host, then use Agent for read-only diagnostics."
|
|
883958
883960
|
});
|
|
883959
883961
|
}
|
|
883960
|
-
if (serviceEnabled && !serviceAutostart) {
|
|
883961
|
-
findings.push({
|
|
883962
|
-
id: "runtime-autostart-disabled",
|
|
883963
|
-
area: "runtime",
|
|
883964
|
-
severity: "warning",
|
|
883965
|
-
summary: "Connected-service autostart is off.",
|
|
883966
|
-
cause: "service.enabled is true and service.autostart is false.",
|
|
883967
|
-
impact: "Connected GoodVibes services may not be available after login or reboot even though host-managed startup is selected.",
|
|
883968
|
-
action: "Configure autostart from GoodVibes TUI or the owning host; Agent will not mutate this setting."
|
|
883969
|
-
});
|
|
883970
|
-
}
|
|
883971
|
-
if (serviceEnabled && !restartOnFailure) {
|
|
883972
|
-
findings.push({
|
|
883973
|
-
id: "runtime-restart-disabled",
|
|
883974
|
-
area: "runtime",
|
|
883975
|
-
severity: "warning",
|
|
883976
|
-
summary: "Connected-service restart-on-failure is off.",
|
|
883977
|
-
cause: "service.enabled is true and service.restartOnFailure is false.",
|
|
883978
|
-
impact: "A crashed connected service or listener may stay down until manually restarted.",
|
|
883979
|
-
action: "Configure restart-on-failure from GoodVibes TUI or the owning host; Agent will not mutate this setting."
|
|
883980
|
-
});
|
|
883981
|
-
}
|
|
883982
883962
|
if (options.service) {
|
|
883983
883963
|
for (const issue2 of options.service.issues) {
|
|
883984
883964
|
if (findings.some((finding) => finding.summary === issue2))
|
|
@@ -884109,8 +884089,6 @@ function renderCliStatus(options) {
|
|
|
884109
884089
|
const config6 = options.configManager;
|
|
884110
884090
|
const snapshot2 = buildCliStatusSnapshot(options);
|
|
884111
884091
|
const serviceEnabled = snapshot2.runtimeConnection.enabled;
|
|
884112
|
-
const serviceAutostart = snapshot2.runtimeConnection.autostart;
|
|
884113
|
-
const restartOnFailure = snapshot2.runtimeConnection.restartOnFailure;
|
|
884114
884092
|
const controlPlaneEnabled = snapshot2.runtimeEndpoints.controlPlane.enabled;
|
|
884115
884093
|
const listenerEnabled = snapshot2.runtimeEndpoints.httpListener.enabled;
|
|
884116
884094
|
const webEnabled = snapshot2.runtimeEndpoints.web.enabled;
|
|
@@ -884169,7 +884147,7 @@ function renderCliStatus(options) {
|
|
|
884169
884147
|
` updatedAt: ${marker?.payload ? new Date(marker.payload.updatedAt).toISOString() : "n/a"}`
|
|
884170
884148
|
];
|
|
884171
884149
|
if (options.doctor) {
|
|
884172
|
-
lines.push("", "Connected-Service Config Signals:", ` host config present: ${yesNo(serviceEnabled)}`,
|
|
884150
|
+
lines.push("", "Connected-Service Config Signals:", ` host config present: ${yesNo(serviceEnabled)}`, " lifecycle config: external to Agent", "", "Endpoint Diagnostics:", bindLine("runtimeApi", controlPlaneEnabled, controlPlaneBinding), bindLine("incomingWebhook", listenerEnabled, httpListenerBinding), bindLine("browserCompanion", webEnabled, webBinding));
|
|
884173
884151
|
lines.push("", "Warnings:");
|
|
884174
884152
|
if (findings.length === 0) {
|
|
884175
884153
|
lines.push(" none");
|
|
@@ -884892,12 +884870,6 @@ async function buildCliServicePosture(runtime3, options = {}) {
|
|
|
884892
884870
|
if (serverBackedEnabled && !config6.enabled) {
|
|
884893
884871
|
issues.push("Connected-service settings are present, but Agent service ownership is disabled by design.");
|
|
884894
884872
|
}
|
|
884895
|
-
if (config6.enabled && !config6.autostart) {
|
|
884896
|
-
issues.push("Connected-service autostart is off.");
|
|
884897
|
-
}
|
|
884898
|
-
if (config6.enabled && !config6.restartOnFailure) {
|
|
884899
|
-
issues.push("Connected-service restart-on-failure is off.");
|
|
884900
|
-
}
|
|
884901
884873
|
for (const endpoint5 of endpoints5) {
|
|
884902
884874
|
if (endpoint5.enabled && options.probe && endpoint5.reachable === false) {
|
|
884903
884875
|
issues.push(`${endpoint5.label} is enabled but not reachable on ${endpoint5.binding.host}:${endpoint5.binding.port}.`);
|
|
@@ -887113,6 +887085,12 @@ function skillRegistry(runtime3) {
|
|
|
887113
887085
|
homeDirectory: runtime3.homeDirectory
|
|
887114
887086
|
}));
|
|
887115
887087
|
}
|
|
887088
|
+
function shellPaths(runtime3) {
|
|
887089
|
+
return createShellPathService2({
|
|
887090
|
+
workingDirectory: runtime3.workingDirectory,
|
|
887091
|
+
homeDirectory: runtime3.homeDirectory
|
|
887092
|
+
});
|
|
887093
|
+
}
|
|
887116
887094
|
function summarizePersona2(persona, activePersonaId) {
|
|
887117
887095
|
const active = persona.id === activePersonaId ? "active" : "available";
|
|
887118
887096
|
const tags = persona.tags.length > 0 ? ` tags=${persona.tags.join(",")}` : "";
|
|
@@ -887179,6 +887157,50 @@ function renderSkillList(title, path7, skills) {
|
|
|
887179
887157
|
].join(`
|
|
887180
887158
|
`);
|
|
887181
887159
|
}
|
|
887160
|
+
function summarizeDiscoveredSkill2(skill) {
|
|
887161
|
+
const description = skill.description ? ` - ${skill.description}` : "";
|
|
887162
|
+
const dependencies = skill.dependencies.length > 0 ? ` deps=${skill.dependencies.join(",")}` : "";
|
|
887163
|
+
const includes = skill.includes.length > 0 ? ` includes=${skill.includes.join(",")}` : "";
|
|
887164
|
+
return [
|
|
887165
|
+
` ${skill.name} ${skill.origin}${description}${dependencies}${includes}`,
|
|
887166
|
+
` path: ${skill.path}`
|
|
887167
|
+
].join(`
|
|
887168
|
+
`);
|
|
887169
|
+
}
|
|
887170
|
+
function renderDiscoveredSkillList(skills) {
|
|
887171
|
+
if (skills.length === 0) {
|
|
887172
|
+
return [
|
|
887173
|
+
"Discovered Agent skill files",
|
|
887174
|
+
" No SKILL.md or .md skill files found in Agent skill folders.",
|
|
887175
|
+
" Search roots: .goodvibes/skills, .goodvibes/agent/skills, ~/.goodvibes/skills, ~/.goodvibes/agent/skills"
|
|
887176
|
+
].join(`
|
|
887177
|
+
`);
|
|
887178
|
+
}
|
|
887179
|
+
return [
|
|
887180
|
+
`Discovered Agent skill files (${skills.length})`,
|
|
887181
|
+
...skills.map(summarizeDiscoveredSkill2),
|
|
887182
|
+
"",
|
|
887183
|
+
"Import one with: goodvibes-agent skills import-discovered <name> --yes"
|
|
887184
|
+
].join(`
|
|
887185
|
+
`);
|
|
887186
|
+
}
|
|
887187
|
+
function discoveredSkillLookupValues2(skill) {
|
|
887188
|
+
const slug = skill.name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
887189
|
+
const basename6 = skill.path.split(/[\\/]/).pop()?.replace(/\.md$/i, "") ?? "";
|
|
887190
|
+
return [skill.name, slug, skill.path, basename6].map((value) => value.trim().toLowerCase()).filter(Boolean);
|
|
887191
|
+
}
|
|
887192
|
+
function findDiscoveredSkill2(skills, idOrName) {
|
|
887193
|
+
const lookup = idOrName.trim().toLowerCase();
|
|
887194
|
+
if (!lookup)
|
|
887195
|
+
return null;
|
|
887196
|
+
return skills.find((skill) => discoveredSkillLookupValues2(skill).includes(lookup)) ?? null;
|
|
887197
|
+
}
|
|
887198
|
+
function discoveredFrontmatterList(skill, key) {
|
|
887199
|
+
const value = skill.frontmatter[key];
|
|
887200
|
+
if (!value)
|
|
887201
|
+
return [];
|
|
887202
|
+
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
887203
|
+
}
|
|
887182
887204
|
function renderBundleList2(title, path7, bundles) {
|
|
887183
887205
|
if (bundles.length === 0) {
|
|
887184
887206
|
return [
|
|
@@ -887236,7 +887258,7 @@ function usagePersonas() {
|
|
|
887236
887258
|
return "Usage: goodvibes-agent personas [list|active|search <query>|show <id>|create|update <id>|use <id>|clear|review <id>|stale <id> <reason>|delete <id> --yes]";
|
|
887237
887259
|
}
|
|
887238
887260
|
function usageSkills() {
|
|
887239
|
-
return "Usage: goodvibes-agent skills [list|enabled|active|search <query>|show <id>|create|update <id>|enable <id>|disable <id>|review <id>|stale <id> <reason>|delete <id> --yes|bundle ...]";
|
|
887261
|
+
return "Usage: goodvibes-agent skills [list|enabled|active|discover|import-discovered <name> --yes|search <query>|show <id>|create|update <id>|enable <id>|disable <id>|review <id>|stale <id> <reason>|delete <id> --yes|bundle ...]";
|
|
887240
887262
|
}
|
|
887241
887263
|
function usageBundles() {
|
|
887242
887264
|
return "Usage: goodvibes-agent skills bundle [list|enabled|search <query>|show <id>|create|update <id>|enable <id>|disable <id>|review <id>|stale <id> <reason>|delete <id> --yes]";
|
|
@@ -887466,6 +887488,44 @@ async function handleSkillsCommand(runtime3) {
|
|
|
887466
887488
|
|
|
887467
887489
|
`));
|
|
887468
887490
|
}
|
|
887491
|
+
if (normalized === "discover") {
|
|
887492
|
+
const discovered = await discoverSkills(shellPaths(runtime3));
|
|
887493
|
+
return success3(runtime3, "agent.skills.discover", { skills: discovered }, renderDiscoveredSkillList(discovered));
|
|
887494
|
+
}
|
|
887495
|
+
if (normalized === "import-discovered" || normalized === "import-skill") {
|
|
887496
|
+
const options = parseOptions(rest);
|
|
887497
|
+
const name51 = options.positionals.join(" ").trim();
|
|
887498
|
+
if (!name51)
|
|
887499
|
+
return failure(runtime3, "invalid_skill_command", "Usage: goodvibes-agent skills import-discovered <name> [--enabled] --yes", 2);
|
|
887500
|
+
const discovered = findDiscoveredSkill2(await discoverSkills(shellPaths(runtime3)), name51);
|
|
887501
|
+
if (!discovered) {
|
|
887502
|
+
return failure(runtime3, "skill_discovery_not_found", `Unknown discovered Agent skill: ${name51}
|
|
887503
|
+
Run goodvibes-agent skills discover to inspect available skill files.`, 1);
|
|
887504
|
+
}
|
|
887505
|
+
if (!hasFlag3(options, "yes")) {
|
|
887506
|
+
return success3(runtime3, "agent.skills.import_discovered.preview", { skill: discovered }, [
|
|
887507
|
+
"Agent skill import preview",
|
|
887508
|
+
` name: ${discovered.name}`,
|
|
887509
|
+
` origin: ${discovered.origin}`,
|
|
887510
|
+
` path: ${discovered.path}`,
|
|
887511
|
+
` description: ${discovered.description || "(none)"}`,
|
|
887512
|
+
` procedure characters: ${discovered.body.length}`,
|
|
887513
|
+
" next: rerun with --yes to import into the Agent-local skill registry"
|
|
887514
|
+
].join(`
|
|
887515
|
+
`));
|
|
887516
|
+
}
|
|
887517
|
+
const skill = registry5.create({
|
|
887518
|
+
name: discovered.name,
|
|
887519
|
+
description: discovered.description || `Imported skill from ${discovered.origin} skill file.`,
|
|
887520
|
+
procedure: discovered.body,
|
|
887521
|
+
tags: discoveredFrontmatterList(discovered, "tags"),
|
|
887522
|
+
triggers: discoveredFrontmatterList(discovered, "triggers"),
|
|
887523
|
+
enabled: hasFlag3(options, "enabled"),
|
|
887524
|
+
source: "imported",
|
|
887525
|
+
provenance: `discovered:${discovered.origin}:${discovered.path}`
|
|
887526
|
+
});
|
|
887527
|
+
return success3(runtime3, "agent.skills.import_discovered", skill, `Imported Agent skill ${skill.id}: ${skill.name}${skill.enabled ? " (enabled)" : ""}`);
|
|
887528
|
+
}
|
|
887469
887529
|
if (normalized === "search" || normalized === "find") {
|
|
887470
887530
|
const query2 = rest.join(" ").trim();
|
|
887471
887531
|
const results = registry5.search(query2);
|
|
@@ -888746,12 +888806,12 @@ async function withRuntimeServices(runtime3, fn) {
|
|
|
888746
888806
|
}
|
|
888747
888807
|
}
|
|
888748
888808
|
function readAuthPaths(runtime3) {
|
|
888749
|
-
const
|
|
888809
|
+
const shellPaths2 = createShellPathService2({
|
|
888750
888810
|
workingDirectory: runtime3.workingDirectory,
|
|
888751
888811
|
homeDirectory: runtime3.homeDirectory
|
|
888752
888812
|
});
|
|
888753
|
-
const userStorePath =
|
|
888754
|
-
const bootstrapCredentialPath =
|
|
888813
|
+
const userStorePath = shellPaths2.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, "auth-users.json");
|
|
888814
|
+
const bootstrapCredentialPath = shellPaths2.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, "auth-bootstrap.txt");
|
|
888755
888815
|
const operatorTokenPath = join98(runtime3.homeDirectory, ".goodvibes", "daemon", "operator-tokens.json");
|
|
888756
888816
|
return {
|
|
888757
888817
|
userStorePath,
|
|
@@ -889312,14 +889372,14 @@ async function prepareShellCliRuntime(argv, roots, binary2 = "goodvibes-agent")
|
|
|
889312
889372
|
process.exit(2);
|
|
889313
889373
|
}
|
|
889314
889374
|
if (cli.command === "status" || cli.command === "doctor" || cli.command === "onboarding" && cli.commandArgs[0] === "status") {
|
|
889315
|
-
const
|
|
889375
|
+
const shellPaths2 = createShellPathService2({
|
|
889316
889376
|
workingDirectory: bootstrapWorkingDir,
|
|
889317
889377
|
homeDirectory: bootstrapHomeDirectory
|
|
889318
889378
|
});
|
|
889319
|
-
const userStorePath =
|
|
889320
|
-
const bootstrapCredentialPath =
|
|
889379
|
+
const userStorePath = shellPaths2.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, "auth-users.json");
|
|
889380
|
+
const bootstrapCredentialPath = shellPaths2.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, "auth-bootstrap.txt");
|
|
889321
889381
|
const operatorTokenPath = join99(bootstrapHomeDirectory, ".goodvibes", "daemon", "operator-tokens.json");
|
|
889322
|
-
const onboardingMarkers = readOnboardingCheckMarkers(
|
|
889382
|
+
const onboardingMarkers = readOnboardingCheckMarkers(shellPaths2);
|
|
889323
889383
|
const service = await buildCliServicePosture({
|
|
889324
889384
|
configManager,
|
|
889325
889385
|
workingDirectory: bootstrapWorkingDir,
|
|
@@ -889433,8 +889493,8 @@ function getInteractiveTerminalLaunchError(input) {
|
|
|
889433
889493
|
`);
|
|
889434
889494
|
}
|
|
889435
889495
|
function applyInitialTuiCliState(options) {
|
|
889436
|
-
const { cli, input, commandRegistry, commandContext, shellPaths, render } = options;
|
|
889437
|
-
const globalOnboardingMarker = readOnboardingCheckMarker(
|
|
889496
|
+
const { cli, input, commandRegistry, commandContext, shellPaths: shellPaths2, render } = options;
|
|
889497
|
+
const globalOnboardingMarker = readOnboardingCheckMarker(shellPaths2, "user");
|
|
889438
889498
|
const seededPrompt = cli.flags.prompt ?? (cli.rawCommand === undefined && cli.positionals.length > 0 ? cli.positionals.join(" ") : undefined);
|
|
889439
889499
|
if (cli.command === "onboarding") {
|
|
889440
889500
|
input.openOnboardingWizard({ mode: "edit", reset: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.113",
|
|
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",
|
package/src/cli/help.ts
CHANGED
|
@@ -188,6 +188,8 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
188
188
|
'skills list',
|
|
189
189
|
'skills enabled',
|
|
190
190
|
'skills active',
|
|
191
|
+
'skills discover',
|
|
192
|
+
'skills import-discovered <name> [--enabled] --yes',
|
|
191
193
|
'skills search <query>',
|
|
192
194
|
'skills show <id>',
|
|
193
195
|
'skills create --name <name> --description <summary> --procedure <steps> [--tags a,b] [--triggers a,b] [--enabled]',
|
|
@@ -202,6 +204,8 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
202
204
|
summary: 'Manage reusable Agent-local procedures and skill bundles for the main conversation.',
|
|
203
205
|
examples: [
|
|
204
206
|
'skills list',
|
|
207
|
+
'skills discover',
|
|
208
|
+
'skills import-discovered "Daily Brief" --enabled --yes',
|
|
205
209
|
'skills create --name "Daily Brief" --description "Summarize operator state" --procedure "Review Agent Knowledge, work plans, approvals, and routines" --enabled',
|
|
206
210
|
'skills bundle create --name "Ops Pack" --description "Daily operations bundle" --skills daily-brief --enabled',
|
|
207
211
|
'skills delete daily-brief --yes',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createShellPathService } from '@/runtime/index.ts';
|
|
2
2
|
import { AgentPersonaRegistry, type AgentPersonaRecord } from '../agent/persona-registry.ts';
|
|
3
|
+
import { discoverSkills, type SkillRecord } from '../agent/skill-discovery.ts';
|
|
3
4
|
import {
|
|
4
5
|
AgentSkillRegistry,
|
|
5
6
|
type AgentSkillBundleRecord,
|
|
@@ -107,6 +108,13 @@ function skillRegistry(runtime: CliCommandRuntime): AgentSkillRegistry {
|
|
|
107
108
|
}));
|
|
108
109
|
}
|
|
109
110
|
|
|
111
|
+
function shellPaths(runtime: CliCommandRuntime): ReturnType<typeof createShellPathService> {
|
|
112
|
+
return createShellPathService({
|
|
113
|
+
workingDirectory: runtime.workingDirectory,
|
|
114
|
+
homeDirectory: runtime.homeDirectory,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
110
118
|
function summarizePersona(persona: AgentPersonaRecord, activePersonaId: string | null): string {
|
|
111
119
|
const active = persona.id === activePersonaId ? 'active' : 'available';
|
|
112
120
|
const tags = persona.tags.length > 0 ? ` tags=${persona.tags.join(',')}` : '';
|
|
@@ -174,6 +182,52 @@ function renderSkillList(title: string, path: string, skills: readonly AgentSkil
|
|
|
174
182
|
].join('\n');
|
|
175
183
|
}
|
|
176
184
|
|
|
185
|
+
function summarizeDiscoveredSkill(skill: SkillRecord): string {
|
|
186
|
+
const description = skill.description ? ` - ${skill.description}` : '';
|
|
187
|
+
const dependencies = skill.dependencies.length > 0 ? ` deps=${skill.dependencies.join(',')}` : '';
|
|
188
|
+
const includes = skill.includes.length > 0 ? ` includes=${skill.includes.join(',')}` : '';
|
|
189
|
+
return [
|
|
190
|
+
` ${skill.name} ${skill.origin}${description}${dependencies}${includes}`,
|
|
191
|
+
` path: ${skill.path}`,
|
|
192
|
+
].join('\n');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function renderDiscoveredSkillList(skills: readonly SkillRecord[]): string {
|
|
196
|
+
if (skills.length === 0) {
|
|
197
|
+
return [
|
|
198
|
+
'Discovered Agent skill files',
|
|
199
|
+
' No SKILL.md or .md skill files found in Agent skill folders.',
|
|
200
|
+
' Search roots: .goodvibes/skills, .goodvibes/agent/skills, ~/.goodvibes/skills, ~/.goodvibes/agent/skills',
|
|
201
|
+
].join('\n');
|
|
202
|
+
}
|
|
203
|
+
return [
|
|
204
|
+
`Discovered Agent skill files (${skills.length})`,
|
|
205
|
+
...skills.map(summarizeDiscoveredSkill),
|
|
206
|
+
'',
|
|
207
|
+
'Import one with: goodvibes-agent skills import-discovered <name> --yes',
|
|
208
|
+
].join('\n');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function discoveredSkillLookupValues(skill: SkillRecord): readonly string[] {
|
|
212
|
+
const slug = skill.name.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
213
|
+
const basename = skill.path.split(/[\\/]/).pop()?.replace(/\.md$/i, '') ?? '';
|
|
214
|
+
return [skill.name, slug, skill.path, basename]
|
|
215
|
+
.map((value) => value.trim().toLowerCase())
|
|
216
|
+
.filter(Boolean);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function findDiscoveredSkill(skills: readonly SkillRecord[], idOrName: string): SkillRecord | null {
|
|
220
|
+
const lookup = idOrName.trim().toLowerCase();
|
|
221
|
+
if (!lookup) return null;
|
|
222
|
+
return skills.find((skill) => discoveredSkillLookupValues(skill).includes(lookup)) ?? null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function discoveredFrontmatterList(skill: SkillRecord, key: string): readonly string[] {
|
|
226
|
+
const value = skill.frontmatter[key];
|
|
227
|
+
if (!value) return [];
|
|
228
|
+
return value.split(',').map((entry) => entry.trim()).filter(Boolean);
|
|
229
|
+
}
|
|
230
|
+
|
|
177
231
|
function renderBundleList(title: string, path: string, bundles: readonly AgentSkillBundleRecord[]): string {
|
|
178
232
|
if (bundles.length === 0) {
|
|
179
233
|
return [
|
|
@@ -231,7 +285,7 @@ function usagePersonas(): string {
|
|
|
231
285
|
}
|
|
232
286
|
|
|
233
287
|
function usageSkills(): string {
|
|
234
|
-
return 'Usage: goodvibes-agent skills [list|enabled|active|search <query>|show <id>|create|update <id>|enable <id>|disable <id>|review <id>|stale <id> <reason>|delete <id> --yes|bundle ...]';
|
|
288
|
+
return 'Usage: goodvibes-agent skills [list|enabled|active|discover|import-discovered <name> --yes|search <query>|show <id>|create|update <id>|enable <id>|disable <id>|review <id>|stale <id> <reason>|delete <id> --yes|bundle ...]';
|
|
235
289
|
}
|
|
236
290
|
|
|
237
291
|
function usageBundles(): string {
|
|
@@ -453,6 +507,41 @@ export async function handleSkillsCommand(runtime: CliCommandRuntime): Promise<C
|
|
|
453
507
|
snapshot.enabledBundles.length > 0 ? renderBundleList('Enabled Agent skill bundles', snapshot.path, snapshot.enabledBundles) : '',
|
|
454
508
|
].filter(Boolean).join('\n\n'));
|
|
455
509
|
}
|
|
510
|
+
if (normalized === 'discover') {
|
|
511
|
+
const discovered = await discoverSkills(shellPaths(runtime));
|
|
512
|
+
return success(runtime, 'agent.skills.discover', { skills: discovered }, renderDiscoveredSkillList(discovered));
|
|
513
|
+
}
|
|
514
|
+
if (normalized === 'import-discovered' || normalized === 'import-skill') {
|
|
515
|
+
const options = parseOptions(rest);
|
|
516
|
+
const name = options.positionals.join(' ').trim();
|
|
517
|
+
if (!name) return failure(runtime, 'invalid_skill_command', 'Usage: goodvibes-agent skills import-discovered <name> [--enabled] --yes', 2);
|
|
518
|
+
const discovered = findDiscoveredSkill(await discoverSkills(shellPaths(runtime)), name);
|
|
519
|
+
if (!discovered) {
|
|
520
|
+
return failure(runtime, 'skill_discovery_not_found', `Unknown discovered Agent skill: ${name}\nRun goodvibes-agent skills discover to inspect available skill files.`, 1);
|
|
521
|
+
}
|
|
522
|
+
if (!hasFlag(options, 'yes')) {
|
|
523
|
+
return success(runtime, 'agent.skills.import_discovered.preview', { skill: discovered }, [
|
|
524
|
+
'Agent skill import preview',
|
|
525
|
+
` name: ${discovered.name}`,
|
|
526
|
+
` origin: ${discovered.origin}`,
|
|
527
|
+
` path: ${discovered.path}`,
|
|
528
|
+
` description: ${discovered.description || '(none)'}`,
|
|
529
|
+
` procedure characters: ${discovered.body.length}`,
|
|
530
|
+
' next: rerun with --yes to import into the Agent-local skill registry',
|
|
531
|
+
].join('\n'));
|
|
532
|
+
}
|
|
533
|
+
const skill = registry.create({
|
|
534
|
+
name: discovered.name,
|
|
535
|
+
description: discovered.description || `Imported skill from ${discovered.origin} skill file.`,
|
|
536
|
+
procedure: discovered.body,
|
|
537
|
+
tags: discoveredFrontmatterList(discovered, 'tags'),
|
|
538
|
+
triggers: discoveredFrontmatterList(discovered, 'triggers'),
|
|
539
|
+
enabled: hasFlag(options, 'enabled'),
|
|
540
|
+
source: 'imported',
|
|
541
|
+
provenance: `discovered:${discovered.origin}:${discovered.path}`,
|
|
542
|
+
});
|
|
543
|
+
return success(runtime, 'agent.skills.import_discovered', skill, `Imported Agent skill ${skill.id}: ${skill.name}${skill.enabled ? ' (enabled)' : ''}`);
|
|
544
|
+
}
|
|
456
545
|
if (normalized === 'search' || normalized === 'find') {
|
|
457
546
|
const query = rest.join(' ').trim();
|
|
458
547
|
const results = registry.search(query);
|
|
@@ -186,12 +186,6 @@ export async function buildCliServicePosture(
|
|
|
186
186
|
if (serverBackedEnabled && !config.enabled) {
|
|
187
187
|
issues.push('Connected-service settings are present, but Agent service ownership is disabled by design.');
|
|
188
188
|
}
|
|
189
|
-
if (config.enabled && !config.autostart) {
|
|
190
|
-
issues.push('Connected-service autostart is off.');
|
|
191
|
-
}
|
|
192
|
-
if (config.enabled && !config.restartOnFailure) {
|
|
193
|
-
issues.push('Connected-service restart-on-failure is off.');
|
|
194
|
-
}
|
|
195
189
|
for (const endpoint of endpoints) {
|
|
196
190
|
if (endpoint.enabled && options.probe && endpoint.reachable === false) {
|
|
197
191
|
issues.push(`${endpoint.label} is enabled but not reachable on ${endpoint.binding.host}:${endpoint.binding.port}.`);
|
|
@@ -233,8 +227,7 @@ export function formatCliServicePosture(posture: CliServicePosture, json = false
|
|
|
233
227
|
' ownership: managed outside goodvibes-agent',
|
|
234
228
|
' Agent owns lifecycle: no',
|
|
235
229
|
` external host config present: ${yesNo(posture.config.enabled)}`,
|
|
236
|
-
|
|
237
|
-
` external host restart config: ${yesNo(posture.config.restartOnFailure)}`,
|
|
230
|
+
' external host lifecycle config: ignored by Agent',
|
|
238
231
|
` legacy host switch present: ${yesNo(posture.config.daemonEnabled)}`,
|
|
239
232
|
` log: ${posture.log.path ?? 'n/a'} (${posture.log.exists ? 'present' : 'missing'})`,
|
|
240
233
|
...(posture.log.readError ? [` log read error: ${posture.log.readError}`] : []),
|
package/src/cli/status.ts
CHANGED
|
@@ -99,8 +99,6 @@ function bindLine(label: string, enabled: unknown, binding: { readonly hostMode:
|
|
|
99
99
|
export function buildCliDoctorFindings(options: CliStatusOptions): readonly CliDoctorFinding[] {
|
|
100
100
|
const config = options.configManager;
|
|
101
101
|
const serviceEnabled = config.get('service.enabled') === true;
|
|
102
|
-
const serviceAutostart = config.get('service.autostart') === true;
|
|
103
|
-
const restartOnFailure = config.get('service.restartOnFailure') === true;
|
|
104
102
|
const daemonEnabled = config.get('danger.daemon') === true;
|
|
105
103
|
const listenerEnabled = config.get('danger.httpListener') === true;
|
|
106
104
|
const webEnabled = config.get('web.enabled') === true;
|
|
@@ -180,30 +178,6 @@ export function buildCliDoctorFindings(options: CliStatusOptions): readonly CliD
|
|
|
180
178
|
});
|
|
181
179
|
}
|
|
182
180
|
|
|
183
|
-
if (serviceEnabled && !serviceAutostart) {
|
|
184
|
-
findings.push({
|
|
185
|
-
id: 'runtime-autostart-disabled',
|
|
186
|
-
area: 'runtime',
|
|
187
|
-
severity: 'warning',
|
|
188
|
-
summary: 'Connected-service autostart is off.',
|
|
189
|
-
cause: 'service.enabled is true and service.autostart is false.',
|
|
190
|
-
impact: 'Connected GoodVibes services may not be available after login or reboot even though host-managed startup is selected.',
|
|
191
|
-
action: 'Configure autostart from GoodVibes TUI or the owning host; Agent will not mutate this setting.',
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (serviceEnabled && !restartOnFailure) {
|
|
196
|
-
findings.push({
|
|
197
|
-
id: 'runtime-restart-disabled',
|
|
198
|
-
area: 'runtime',
|
|
199
|
-
severity: 'warning',
|
|
200
|
-
summary: 'Connected-service restart-on-failure is off.',
|
|
201
|
-
cause: 'service.enabled is true and service.restartOnFailure is false.',
|
|
202
|
-
impact: 'A crashed connected service or listener may stay down until manually restarted.',
|
|
203
|
-
action: 'Configure restart-on-failure from GoodVibes TUI or the owning host; Agent will not mutate this setting.',
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
|
|
207
181
|
if (options.service) {
|
|
208
182
|
for (const issue of options.service.issues) {
|
|
209
183
|
if (findings.some((finding) => finding.summary === issue)) continue;
|
|
@@ -342,8 +316,6 @@ export function renderCliStatus(options: CliStatusOptions): string {
|
|
|
342
316
|
const config = options.configManager;
|
|
343
317
|
const snapshot = buildCliStatusSnapshot(options);
|
|
344
318
|
const serviceEnabled = snapshot.runtimeConnection.enabled;
|
|
345
|
-
const serviceAutostart = snapshot.runtimeConnection.autostart;
|
|
346
|
-
const restartOnFailure = snapshot.runtimeConnection.restartOnFailure;
|
|
347
319
|
const controlPlaneEnabled = snapshot.runtimeEndpoints.controlPlane.enabled;
|
|
348
320
|
const listenerEnabled = snapshot.runtimeEndpoints.httpListener.enabled;
|
|
349
321
|
const webEnabled = snapshot.runtimeEndpoints.web.enabled;
|
|
@@ -414,8 +386,7 @@ export function renderCliStatus(options: CliStatusOptions): string {
|
|
|
414
386
|
'',
|
|
415
387
|
'Connected-Service Config Signals:',
|
|
416
388
|
` host config present: ${yesNo(serviceEnabled)}`,
|
|
417
|
-
|
|
418
|
-
` host restart policy: ${yesNo(restartOnFailure)}`,
|
|
389
|
+
' lifecycle config: external to Agent',
|
|
419
390
|
'',
|
|
420
391
|
'Endpoint Diagnostics:',
|
|
421
392
|
bindLine('runtimeApi', controlPlaneEnabled, controlPlaneBinding),
|
|
@@ -28,7 +28,7 @@ export class UIFactory {
|
|
|
28
28
|
const CYAN = '#00ffff';
|
|
29
29
|
const GREY = '244';
|
|
30
30
|
const TITLE_COLOR = '250';
|
|
31
|
-
const brand = ` GoodVibes `;
|
|
31
|
+
const brand = ` GoodVibes Agent `;
|
|
32
32
|
const ver = `v${VERSION} `;
|
|
33
33
|
const stats = ` ${model} `;
|
|
34
34
|
const prov = `(${provider}) `;
|
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.113';
|
|
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 {
|