@tacuchi/agent-workflow-cli 16.2.0 → 17.1.0
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/README.md +13 -0
- package/dist/adapters/node-file-system.d.ts +3 -1
- package/dist/adapters/node-file-system.d.ts.map +1 -1
- package/dist/adapters/node-file-system.js +18 -1
- package/dist/adapters/node-file-system.js.map +1 -1
- package/dist/application/mcp-doctor-service.d.ts.map +1 -1
- package/dist/application/mcp-doctor-service.js +3 -2
- package/dist/application/mcp-doctor-service.js.map +1 -1
- package/dist/application/mcp-host-writer.d.ts +1 -1
- package/dist/application/mcp-host-writer.d.ts.map +1 -1
- package/dist/application/mcp-host-writer.js +40 -10
- package/dist/application/mcp-host-writer.js.map +1 -1
- package/dist/application/mcp-remove-service.d.ts.map +1 -1
- package/dist/application/mcp-remove-service.js +3 -2
- package/dist/application/mcp-remove-service.js.map +1 -1
- package/dist/application/mcp-setup-service.d.ts.map +1 -1
- package/dist/application/mcp-setup-service.js +3 -2
- package/dist/application/mcp-setup-service.js.map +1 -1
- package/dist/application/self/install-plugin-skills-git.d.ts +1 -0
- package/dist/application/self/install-plugin-skills-git.d.ts.map +1 -1
- package/dist/application/self/install-plugin-skills-git.js +11 -2
- package/dist/application/self/install-plugin-skills-git.js.map +1 -1
- package/dist/application/self/install-plugin-skills.d.ts +6 -0
- package/dist/application/self/install-plugin-skills.d.ts.map +1 -1
- package/dist/application/self/install-plugin-skills.js +12 -3
- package/dist/application/self/install-plugin-skills.js.map +1 -1
- package/dist/application/self/install-skill.d.ts +1 -0
- package/dist/application/self/install-skill.d.ts.map +1 -1
- package/dist/application/self/install-skill.js +3 -1
- package/dist/application/self/install-skill.js.map +1 -1
- package/dist/application/self/mcp-config.d.ts +2 -0
- package/dist/application/self/mcp-config.d.ts.map +1 -1
- package/dist/application/self/mcp-config.js +29 -12
- package/dist/application/self/mcp-config.js.map +1 -1
- package/dist/application/self/skills-manager.d.ts +105 -0
- package/dist/application/self/skills-manager.d.ts.map +1 -0
- package/dist/application/self/skills-manager.js +594 -0
- package/dist/application/self/skills-manager.js.map +1 -0
- package/dist/application/self/skills-registry.d.ts +35 -0
- package/dist/application/self/skills-registry.d.ts.map +1 -0
- package/dist/application/self/skills-registry.js +87 -0
- package/dist/application/self/skills-registry.js.map +1 -0
- package/dist/cli/tui/app.js +5 -2
- package/dist/cli/tui/app.js.map +1 -1
- package/dist/cli/tui/components/focus-row.d.ts +1 -1
- package/dist/cli/tui/components/focus-row.js +1 -1
- package/dist/cli/tui/components/host-admin-section.d.ts +22 -0
- package/dist/cli/tui/components/host-admin-section.d.ts.map +1 -0
- package/dist/cli/tui/components/host-admin-section.js +321 -0
- package/dist/cli/tui/components/host-admin-section.js.map +1 -0
- package/dist/cli/tui/components/tabs-config.js +1 -1
- package/dist/cli/tui/components/tabs-config.js.map +1 -1
- package/dist/cli/tui/data/recommended-skills.d.ts +3 -0
- package/dist/cli/tui/data/recommended-skills.d.ts.map +1 -0
- package/dist/cli/tui/data/recommended-skills.js +90 -0
- package/dist/cli/tui/data/recommended-skills.js.map +1 -0
- package/dist/cli/tui/data/workflow-content.d.ts +5 -4
- package/dist/cli/tui/data/workflow-content.d.ts.map +1 -1
- package/dist/cli/tui/data/workflow-content.js +14 -134
- package/dist/cli/tui/data/workflow-content.js.map +1 -1
- package/dist/cli/tui/tabs/mcp-tab-helpers.d.ts +14 -7
- package/dist/cli/tui/tabs/mcp-tab-helpers.d.ts.map +1 -1
- package/dist/cli/tui/tabs/mcp-tab-helpers.js +16 -5
- package/dist/cli/tui/tabs/mcp-tab-helpers.js.map +1 -1
- package/dist/cli/tui/tabs/mcp-tab.d.ts.map +1 -1
- package/dist/cli/tui/tabs/mcp-tab.js +15 -13
- package/dist/cli/tui/tabs/mcp-tab.js.map +1 -1
- package/dist/cli/tui/tabs/skills-tab.d.ts +0 -1
- package/dist/cli/tui/tabs/skills-tab.d.ts.map +1 -1
- package/dist/cli/tui/tabs/skills-tab.js +292 -229
- package/dist/cli/tui/tabs/skills-tab.js.map +1 -1
- package/dist/cli/tui/tabs/status-tab.d.ts +1 -1
- package/dist/cli/tui/tabs/status-tab.d.ts.map +1 -1
- package/dist/cli/tui/tabs/status-tab.js +3 -2
- package/dist/cli/tui/tabs/status-tab.js.map +1 -1
- package/dist/cli/tui/tabs/workflow-tab.d.ts +6 -1
- package/dist/cli/tui/tabs/workflow-tab.d.ts.map +1 -1
- package/dist/cli/tui/tabs/workflow-tab.js +15 -28
- package/dist/cli/tui/tabs/workflow-tab.js.map +1 -1
- package/dist/domain/mcp-entry.d.ts +11 -0
- package/dist/domain/mcp-entry.d.ts.map +1 -1
- package/dist/domain/mcp-entry.js +13 -0
- package/dist/domain/mcp-entry.js.map +1 -1
- package/dist/ports/file-system.d.ts +13 -0
- package/dist/ports/file-system.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,160 +1,210 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// [Skills] — administrador de skills sueltas (modelo skills.sh): lista única
|
|
3
|
+
// con badges (installed → unmanaged → registered → recommended), detail con
|
|
4
|
+
// acciones por estado (unmanaged = informativa) y wizard [a] fuente → picker
|
|
5
|
+
// → warning de terceros → registrar.
|
|
6
|
+
// Respaldado por skills-manager; la administración del bundle `w` vive en
|
|
7
|
+
// [Workflows] (HostAdminSection).
|
|
2
8
|
import { Box, Text, useInput, useStdout } from "ink";
|
|
3
9
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
10
|
import { formatTuiEvent } from "../../../application/logging/log-events.js";
|
|
5
|
-
import {
|
|
6
|
-
import { SKILL_DIR_NAME, TARGET_ROOTS, selfInstallSkill, } from "../../../application/self/install-skill.js";
|
|
7
|
-
import { selfClearPluginCache } from "../../../application/self/plugin-cache-clear.js";
|
|
8
|
-
import { selfUninstall } from "../../../application/self/uninstall.js";
|
|
11
|
+
import { canonicalSkillsRoot, installSkill, listSkills, probeSkillSource, registerSkill, reinstallSkill, removeSkill, resolveSkillSource, uninstallSkill, updateSkill, } from "../../../application/self/skills-manager.js";
|
|
9
12
|
import { ConfirmBanner } from "../components/confirm-banner.js";
|
|
10
13
|
import { DetailPanel } from "../components/detail-panel.js";
|
|
14
|
+
import { InputPrompt } from "../components/input-prompt.js";
|
|
11
15
|
import { ListRow } from "../components/list-row.js";
|
|
12
16
|
import { PageHead } from "../components/page-head.js";
|
|
13
17
|
import { QuickActions } from "../components/quick-actions.js";
|
|
14
18
|
import { SectionHead } from "../components/section-head.js";
|
|
15
|
-
import {
|
|
16
|
-
import { HOSTS } from "../hosts.js";
|
|
19
|
+
import { RECOMMENDED_SKILLS } from "../data/recommended-skills.js";
|
|
17
20
|
import { useInputLock } from "../input-lock.js";
|
|
18
21
|
import { rowWidth } from "../row-width.js";
|
|
19
22
|
import { colors, icons } from "../theme.js";
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
const STATUS_GLYPH = {
|
|
24
|
+
installed: { glyph: "◆", active: true },
|
|
25
|
+
unmanaged: { glyph: "◈", active: true },
|
|
26
|
+
registered: { glyph: "◇", active: false },
|
|
27
|
+
recommended: { glyph: "·", active: false },
|
|
28
|
+
};
|
|
24
29
|
export function SkillsTab({ ctx, isActive, onToast }) {
|
|
25
|
-
const [
|
|
30
|
+
const [items, setItems] = useState([]);
|
|
26
31
|
const [cursor, setCursor] = useState(0);
|
|
27
32
|
const [actionCursor, setActionCursor] = useState(0);
|
|
28
33
|
const [mode, setMode] = useState({ kind: "list" });
|
|
29
|
-
|
|
34
|
+
// Espejo de items para preservar la selección POR NOMBRE en refresh: la lista
|
|
35
|
+
// se re-ordena tras cada operación (installed→registered→recommended) y un
|
|
36
|
+
// cursor numérico saltaría a otra skill.
|
|
37
|
+
const itemsRef = useRef([]);
|
|
30
38
|
const startedRef = useRef(false);
|
|
31
39
|
const { lock, unlock } = useInputLock();
|
|
32
40
|
const { stdout } = useStdout();
|
|
33
41
|
useEffect(() => {
|
|
34
|
-
if (
|
|
35
|
-
lock();
|
|
36
|
-
else
|
|
42
|
+
if (mode.kind === "list" || mode.kind === "detail")
|
|
37
43
|
unlock();
|
|
38
|
-
|
|
44
|
+
else
|
|
45
|
+
lock();
|
|
46
|
+
}, [mode, lock, unlock]);
|
|
39
47
|
useEffect(() => () => unlock(), [unlock]);
|
|
40
48
|
const refresh = useCallback(async () => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
hooksInstalled =
|
|
48
|
-
typeof parsed === "object" &&
|
|
49
|
-
parsed !== null &&
|
|
50
|
-
"hooks" in parsed &&
|
|
51
|
-
typeof parsed.hooks === "object" &&
|
|
52
|
-
Object.keys(parsed.hooks).length > 0;
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
hooksInstalled = false;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
const next = [];
|
|
59
|
-
for (const host of HOSTS) {
|
|
60
|
-
const path = pathForHost(host, home);
|
|
61
|
-
const installed = host.backed && path ? await ctx.fs.exists(path) : false;
|
|
62
|
-
next.push({
|
|
63
|
-
host,
|
|
64
|
-
installed,
|
|
65
|
-
hooks_installed: host.id === "claude" ? hooksInstalled : false,
|
|
66
|
-
path: installed
|
|
67
|
-
? friendlyPath(host, home)
|
|
68
|
-
: host.backed
|
|
69
|
-
? "not installed"
|
|
70
|
-
: "(not wired yet)",
|
|
49
|
+
try {
|
|
50
|
+
const next = await listSkills(ctx, RECOMMENDED_SKILLS);
|
|
51
|
+
setCursor((c) => {
|
|
52
|
+
const prevName = itemsRef.current[c]?.name;
|
|
53
|
+
const idx = prevName === undefined ? -1 : next.findIndex((s) => s.name === prevName);
|
|
54
|
+
return idx >= 0 ? idx : Math.min(Math.max(0, c), Math.max(0, next.length - 1));
|
|
71
55
|
});
|
|
56
|
+
itemsRef.current = next;
|
|
57
|
+
setItems(next);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
onToast?.({ tone: "err", title: "Error loading skills", body: err.message });
|
|
72
61
|
}
|
|
73
|
-
|
|
74
|
-
setCursor((c) => Math.min(Math.max(0, c), Math.max(0, next.length - 1)));
|
|
75
|
-
}, [ctx]);
|
|
62
|
+
}, [ctx, onToast]);
|
|
76
63
|
useEffect(() => {
|
|
77
64
|
if (startedRef.current)
|
|
78
65
|
return;
|
|
79
66
|
startedRef.current = true;
|
|
80
67
|
void refresh();
|
|
81
68
|
}, [refresh]);
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
69
|
+
const current = items[cursor] ?? null;
|
|
70
|
+
const installedCount = items.filter((s) => s.status === "installed").length;
|
|
71
|
+
const unmanagedCount = items.filter((s) => s.status === "unmanaged").length;
|
|
72
|
+
const registeredCount = items.filter((s) => s.status === "registered").length;
|
|
73
|
+
const recommendedCount = items.filter((s) => s.status === "recommended").length;
|
|
74
|
+
// Acciones del detail según estado (SPEC 003): recommended/registered →
|
|
75
|
+
// Install; installed → Update (solo git) / Reinstall / Uninstall; Remove para
|
|
76
|
+
// todo lo registrado (una recomendada vuelve a `recommended`). `unmanaged`
|
|
77
|
+
// (fuera del registro) no es operable: el guard de ownership del motor
|
|
78
|
+
// rechaza register/uninstall sobre dirs ajenos — fila informativa.
|
|
89
79
|
const detailActions = useMemo(() => {
|
|
90
|
-
if (!
|
|
80
|
+
if (!current || current.status === "unmanaged")
|
|
91
81
|
return [];
|
|
92
|
-
|
|
93
|
-
name: isInstalled ? "Reinstall" : "Install",
|
|
94
|
-
description: isInstalled ? "Overwrite files (--force)." : "Copy files (--force).",
|
|
95
|
-
};
|
|
96
|
-
if (isInstalled) {
|
|
82
|
+
if (current.status === "recommended") {
|
|
97
83
|
return [
|
|
98
|
-
reinstall,
|
|
99
84
|
{
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
85
|
+
id: "install",
|
|
86
|
+
action: {
|
|
87
|
+
name: "Install",
|
|
88
|
+
description: "Register + install (canonical + Claude replica).",
|
|
89
|
+
},
|
|
103
90
|
},
|
|
104
91
|
];
|
|
105
92
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
93
|
+
if (current.status === "registered") {
|
|
94
|
+
return [
|
|
95
|
+
{
|
|
96
|
+
id: "install",
|
|
97
|
+
action: { name: "Install", description: "Materialize canonical + Claude replica." },
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: "remove",
|
|
101
|
+
action: { name: "Remove", description: "Drop from the registry.", danger: true },
|
|
102
|
+
},
|
|
103
|
+
];
|
|
116
104
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
105
|
+
// El clasificador canónico del motor (isAbsolute cubre paths Windows como
|
|
106
|
+
// C:\… que un startsWith("/") clasificaría mal como git).
|
|
107
|
+
const resolved = resolveSkillSource(current.source, current.ref);
|
|
108
|
+
const gitSource = !("error" in resolved) && resolved.kind === "git";
|
|
109
|
+
return [
|
|
110
|
+
...(gitSource
|
|
111
|
+
? [
|
|
112
|
+
{
|
|
113
|
+
id: "update",
|
|
114
|
+
action: {
|
|
115
|
+
name: "Update",
|
|
116
|
+
description: "Re-fetch the registered ref (staging + swap).",
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
]
|
|
120
|
+
: []),
|
|
121
|
+
{
|
|
122
|
+
id: "reinstall",
|
|
123
|
+
action: { name: "Reinstall", description: "Repair the replica from the canonical." },
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: "uninstall",
|
|
127
|
+
action: {
|
|
128
|
+
name: "Uninstall",
|
|
129
|
+
description: "Delete canonical + replica; keeps the registration.",
|
|
130
|
+
danger: true,
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: "remove",
|
|
135
|
+
action: {
|
|
136
|
+
name: "Remove",
|
|
137
|
+
description: "Uninstall + drop from the registry.",
|
|
138
|
+
danger: true,
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
}, [current]);
|
|
143
|
+
// Superficie TUI en EN (SPEC 007); los summaries del motor (ES) van al body.
|
|
144
|
+
const runAction = useCallback(async (label, successTitle, op) => {
|
|
145
|
+
setMode({ kind: "busy", label });
|
|
123
146
|
try {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
onToast?.(failMsg !== undefined
|
|
130
|
-
? { tone: "err", title: `Step ${step} failed`, body: failMsg }
|
|
131
|
-
: { tone: "err", title: `Step ${step} failed` });
|
|
132
|
-
// The err toast is mirrored to the log by the notification-center
|
|
133
|
-
// safety net; nothing more to log here.
|
|
134
|
-
await refresh();
|
|
135
|
-
return;
|
|
147
|
+
const result = await op();
|
|
148
|
+
if (result.ok) {
|
|
149
|
+
onToast?.({ tone: "ok", title: successTitle, body: result.data?.summary ?? "" });
|
|
150
|
+
if (result.data?.warning) {
|
|
151
|
+
onToast?.({ tone: "info", title: "Notice", body: result.data.warning });
|
|
136
152
|
}
|
|
153
|
+
void ctx.logger?.info(formatTuiEvent(`skill-manager ${label}`, "ok"));
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
onToast?.({
|
|
157
|
+
tone: "err",
|
|
158
|
+
title: "Operation refused",
|
|
159
|
+
body: result.error?.message ?? "",
|
|
160
|
+
});
|
|
137
161
|
}
|
|
138
|
-
const finalAction = kind === "install" ? "install-full" : "uninstall-full";
|
|
139
|
-
onToast?.({ tone: "ok", title: buildSuccessMessage(finalAction, host.name) });
|
|
140
|
-
void ctx.logger?.info(formatTuiEvent(`skill ${kind} ${host.name}`, "ok"));
|
|
141
|
-
await refresh();
|
|
142
162
|
}
|
|
143
163
|
catch (err) {
|
|
144
164
|
onToast?.({ tone: "err", title: "Error", body: err.message });
|
|
145
165
|
}
|
|
146
|
-
|
|
147
|
-
|
|
166
|
+
await refresh();
|
|
167
|
+
setMode({ kind: "list" });
|
|
168
|
+
}, [ctx, onToast, refresh]);
|
|
169
|
+
const triggerAction = useCallback((id) => {
|
|
170
|
+
if (!current)
|
|
171
|
+
return;
|
|
172
|
+
const name = current.name;
|
|
173
|
+
switch (id) {
|
|
174
|
+
case "install":
|
|
175
|
+
if (current.status === "recommended") {
|
|
176
|
+
// Registrar + instalar en un paso (la semilla ya trae la fuente).
|
|
177
|
+
void runAction(`installing ${name}…`, `Installed · ${name}`, async () => {
|
|
178
|
+
const reg = await registerSkill(ctx, { source: current.source, pick: name });
|
|
179
|
+
if (!reg.ok)
|
|
180
|
+
return reg;
|
|
181
|
+
return installSkill(ctx, name);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
void runAction(`installing ${name}…`, `Installed · ${name}`, () => installSkill(ctx, name));
|
|
186
|
+
}
|
|
187
|
+
return;
|
|
188
|
+
case "update":
|
|
189
|
+
void runAction(`updating ${name}…`, `Updated · ${name}`, () => updateSkill(ctx, name));
|
|
190
|
+
return;
|
|
191
|
+
case "reinstall":
|
|
192
|
+
void runAction(`reinstalling ${name}…`, `Reinstalled · ${name}`, () => reinstallSkill(ctx, name));
|
|
193
|
+
return;
|
|
194
|
+
case "uninstall":
|
|
195
|
+
setMode({ kind: "confirm", action: "uninstall" });
|
|
196
|
+
return;
|
|
197
|
+
case "remove":
|
|
198
|
+
setMode({ kind: "confirm", action: "remove" });
|
|
199
|
+
return;
|
|
148
200
|
}
|
|
149
|
-
}, [ctx,
|
|
150
|
-
// input — list
|
|
201
|
+
}, [ctx, current, runAction]);
|
|
202
|
+
// input — list (↑↓ navega · ⏎ detail · a wizard)
|
|
151
203
|
useInput((input, key) => {
|
|
152
|
-
if (!isActive ||
|
|
204
|
+
if (!isActive || mode.kind !== "list")
|
|
153
205
|
return;
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
if (claude)
|
|
157
|
-
void runComposite("install", claude);
|
|
206
|
+
if (input === "a" || input === "A") {
|
|
207
|
+
setMode({ kind: "wizard-source" });
|
|
158
208
|
return;
|
|
159
209
|
}
|
|
160
210
|
if (key.upArrow) {
|
|
@@ -162,32 +212,24 @@ export function SkillsTab({ ctx, isActive, onToast }) {
|
|
|
162
212
|
return;
|
|
163
213
|
}
|
|
164
214
|
if (key.downArrow) {
|
|
165
|
-
setCursor((c) => (
|
|
215
|
+
setCursor((c) => (items.length === 0 ? 0 : Math.min(items.length - 1, c + 1)));
|
|
166
216
|
return;
|
|
167
217
|
}
|
|
168
|
-
if (key.return &&
|
|
169
|
-
if (!BACKED_INSTALL_TARGETS.has(focused.host.id)) {
|
|
170
|
-
onToast?.({
|
|
171
|
-
tone: "info",
|
|
172
|
-
title: `Host '${focused.host.name}'`,
|
|
173
|
-
body: "pending — backend without path mapping yet",
|
|
174
|
-
});
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
218
|
+
if (key.return && current) {
|
|
177
219
|
setActionCursor(0);
|
|
178
220
|
setMode({ kind: "detail" });
|
|
179
221
|
}
|
|
180
222
|
}, { isActive });
|
|
181
|
-
// input — detail
|
|
223
|
+
// input — detail (↑↓ acciones · ⏎ ejecuta · esc cierra)
|
|
182
224
|
useInput((_input, key) => {
|
|
183
|
-
if (!isActive ||
|
|
225
|
+
if (!isActive || mode.kind !== "detail" || !current)
|
|
184
226
|
return;
|
|
185
227
|
if (key.upArrow) {
|
|
186
228
|
setActionCursor((c) => Math.max(0, c - 1));
|
|
187
229
|
return;
|
|
188
230
|
}
|
|
189
231
|
if (key.downArrow) {
|
|
190
|
-
setActionCursor((c) => Math.min(detailActions.length - 1, c + 1));
|
|
232
|
+
setActionCursor((c) => Math.min(Math.max(0, detailActions.length - 1), c + 1));
|
|
191
233
|
return;
|
|
192
234
|
}
|
|
193
235
|
if (key.escape) {
|
|
@@ -195,129 +237,150 @@ export function SkillsTab({ ctx, isActive, onToast }) {
|
|
|
195
237
|
return;
|
|
196
238
|
}
|
|
197
239
|
if (key.return) {
|
|
198
|
-
const
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
240
|
+
const entry = detailActions[actionCursor];
|
|
241
|
+
if (entry)
|
|
242
|
+
triggerAction(entry.id);
|
|
243
|
+
}
|
|
244
|
+
}, { isActive });
|
|
245
|
+
// input — confirm (y confirma · n/esc vuelve al detail)
|
|
246
|
+
useInput((input, key) => {
|
|
247
|
+
if (!isActive || mode.kind !== "confirm" || !current)
|
|
248
|
+
return;
|
|
249
|
+
if (input === "y" || input === "Y") {
|
|
250
|
+
const name = current.name;
|
|
251
|
+
if (mode.action === "uninstall") {
|
|
252
|
+
void runAction(`uninstalling ${name}…`, `Uninstalled · ${name}`, () => uninstallSkill(ctx, name));
|
|
203
253
|
}
|
|
204
254
|
else {
|
|
205
|
-
void
|
|
206
|
-
setMode({ kind: "list" });
|
|
255
|
+
void runAction(`removing ${name}…`, `Removed · ${name}`, () => removeSkill(ctx, name));
|
|
207
256
|
}
|
|
208
257
|
}
|
|
258
|
+
else if (key.escape || input === "n" || input === "N") {
|
|
259
|
+
setMode({ kind: "detail" });
|
|
260
|
+
}
|
|
209
261
|
}, { isActive });
|
|
210
|
-
// input —
|
|
262
|
+
// input — wizard-source esc
|
|
263
|
+
useInput((_input, key) => {
|
|
264
|
+
if (!isActive || mode.kind !== "wizard-source")
|
|
265
|
+
return;
|
|
266
|
+
if (key.escape)
|
|
267
|
+
setMode({ kind: "list" });
|
|
268
|
+
}, { isActive });
|
|
269
|
+
// input — wizard-pick (↑↓ · ⏎ elige · esc cancela)
|
|
270
|
+
useInput((_input, key) => {
|
|
271
|
+
if (!isActive || mode.kind !== "wizard-pick")
|
|
272
|
+
return;
|
|
273
|
+
if (key.escape) {
|
|
274
|
+
setMode({ kind: "list" });
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (key.upArrow) {
|
|
278
|
+
setMode({ ...mode, cursor: Math.max(0, mode.cursor - 1) });
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (key.downArrow) {
|
|
282
|
+
setMode({ ...mode, cursor: Math.min(mode.candidates.length - 1, mode.cursor + 1) });
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (key.return) {
|
|
286
|
+
const pick = mode.candidates[mode.cursor];
|
|
287
|
+
if (pick)
|
|
288
|
+
setMode({ kind: "wizard-warning", source: mode.source, pick });
|
|
289
|
+
}
|
|
290
|
+
}, { isActive });
|
|
291
|
+
// input — wizard-warning (r registra · ⏎ registra+instala · esc cancela)
|
|
211
292
|
useInput((input, key) => {
|
|
212
|
-
if (!isActive || mode.kind !== "
|
|
293
|
+
if (!isActive || mode.kind !== "wizard-warning")
|
|
213
294
|
return;
|
|
214
|
-
if (
|
|
215
|
-
void runComposite("uninstall", mode.host);
|
|
295
|
+
if (key.escape) {
|
|
216
296
|
setMode({ kind: "list" });
|
|
297
|
+
return;
|
|
217
298
|
}
|
|
218
|
-
|
|
219
|
-
|
|
299
|
+
const { source, pick } = mode;
|
|
300
|
+
if (input === "r" || input === "R") {
|
|
301
|
+
void runAction(`registering ${pick}…`, `Registered · ${pick}`, () => registerSkill(ctx, { source, pick }));
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (key.return) {
|
|
305
|
+
void runAction(`installing ${pick}…`, `Installed · ${pick}`, async () => {
|
|
306
|
+
const reg = await registerSkill(ctx, { source, pick });
|
|
307
|
+
if (!reg.ok)
|
|
308
|
+
return reg;
|
|
309
|
+
return installSkill(ctx, pick);
|
|
310
|
+
});
|
|
220
311
|
}
|
|
221
312
|
}, { isActive });
|
|
222
|
-
const
|
|
313
|
+
const probeSource = useCallback(async (source) => {
|
|
314
|
+
setMode({ kind: "busy", label: "inspecting source…" });
|
|
315
|
+
try {
|
|
316
|
+
const probe = await probeSkillSource(ctx, { source });
|
|
317
|
+
if (!probe.ok || !probe.data) {
|
|
318
|
+
onToast?.({ tone: "err", title: "Invalid source", body: probe.error?.message ?? "" });
|
|
319
|
+
setMode({ kind: "list" });
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const candidates = probe.data.candidates;
|
|
323
|
+
const single = candidates.length === 1 ? candidates[0] : undefined;
|
|
324
|
+
setMode(single !== undefined
|
|
325
|
+
? { kind: "wizard-warning", source, pick: single }
|
|
326
|
+
: { kind: "wizard-pick", source, candidates, cursor: 0 });
|
|
327
|
+
}
|
|
328
|
+
catch (err) {
|
|
329
|
+
onToast?.({ tone: "err", title: "Error", body: err.message });
|
|
330
|
+
setMode({ kind: "list" });
|
|
331
|
+
}
|
|
332
|
+
}, [ctx, onToast]);
|
|
333
|
+
const overlayVisible = mode.kind !== "list";
|
|
334
|
+
const home = ctx.env.homeDir();
|
|
223
335
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(PageHead, { title: "Skills", count: {
|
|
224
|
-
label: `${installedCount}
|
|
225
|
-
tone: installedCount
|
|
226
|
-
}, action: _jsx(Text, { color: colors.mute, children: "
|
|
227
|
-
|
|
228
|
-
:
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
336
|
+
label: `${installedCount} installed${unmanagedCount > 0 ? ` · ${unmanagedCount} unmanaged` : ""} · ${registeredCount} registered · ${recommendedCount} recommended`,
|
|
337
|
+
tone: installedCount > 0 ? "accent" : "warn",
|
|
338
|
+
}, action: _jsx(Text, { color: colors.mute, children: "~/.agents/skills + host replicas" }) }), _jsx(SectionHead, { label: "Skills", count: items.length, hint: "installed \u2192 unmanaged \u2192 registered \u2192 recommended", ...(overlayVisible ? { rightAction: "esc to close" } : {}), marginTop: 0 }), _jsxs(Box, { flexDirection: "row", children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, paddingRight: 2, children: [items.map((s, i) => {
|
|
339
|
+
const glyph = STATUS_GLYPH[s.status];
|
|
340
|
+
return (_jsx(ListRow, { icon: glyph.glyph, iconActive: glyph.active, title: s.name, subtitle: s.status === "unmanaged" && s.source === ""
|
|
341
|
+
? "outside the registry"
|
|
342
|
+
: `${s.source}${s.ref ? ` #${s.ref}` : ""}`, meta: s.mode === "copy" ? [{ label: "copy", tone: "warn" }] : [], state: {
|
|
343
|
+
label: s.status,
|
|
344
|
+
tone: s.status === "installed"
|
|
345
|
+
? "ok"
|
|
346
|
+
: s.status === "unmanaged"
|
|
347
|
+
? "warn"
|
|
348
|
+
: s.status === "registered"
|
|
349
|
+
? "dim"
|
|
350
|
+
: "info",
|
|
351
|
+
}, chevron: true, active: cursor === i, dimmed: mode.kind.startsWith("wizard"), widthHint: rowWidth(stdout?.columns, overlayVisible) }, s.name));
|
|
352
|
+
}), mode.kind === "wizard-source" ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(SectionHead, { label: "Add skill", hint: "Step 1 \u00B7 Source", rightAction: "\u23CE inspect \u00B7 esc cancel" }), _jsx(Box, { marginLeft: 2, children: _jsx(InputPrompt, { message: "source (owner/repo \u00B7 git URL \u00B7 absolute path):", onSubmit: (value) => {
|
|
353
|
+
const source = value.trim();
|
|
354
|
+
if (!source) {
|
|
355
|
+
setMode({ kind: "list" });
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
void probeSource(source);
|
|
359
|
+
}, isActive: isActive }) })] })) : null, mode.kind === "wizard-pick" ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(SectionHead, { label: "Add skill", hint: `Step 2 · Pick one of ${mode.candidates.length}`, rightAction: "\u23CE choose \u00B7 esc cancel" }), _jsx(Box, { marginTop: 0, flexDirection: "column", children: mode.candidates.map((name, i) => (_jsx(ListRow, { icon: "\u00B7", title: name, active: mode.cursor === i, widthHint: rowWidth(stdout?.columns, true) }, name))) })] })) : null, mode.kind === "wizard-warning" ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(SectionHead, { label: `Add skill · ${mode.pick}`, hint: "Step 3 \u00B7 Review", rightAction: "esc cancel" }), _jsxs(Box, { marginLeft: 2, marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: colors.warn, children: "\u26A0 A third-party skill runs with your host's permissions \u2014 review it before installing." }), _jsx(Text, { color: colors.dim, wrap: "truncate-end", children: mode.source }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.faint, children: "[\u23CE] register + install \u00B7 [r] register only" }) })] })] })) : null, mode.kind === "busy" ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.warn, children: [icons.spinner, " ", mode.label] }) })) : null] }), current && (mode.kind === "detail" || mode.kind === "confirm") ? (_jsx(DetailPanel, { bordered: true, header: {
|
|
360
|
+
name: current.name,
|
|
361
|
+
meta: detailMeta(current, home),
|
|
236
362
|
}, statePill: {
|
|
237
|
-
label:
|
|
238
|
-
tone:
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (!root)
|
|
249
|
-
return null;
|
|
250
|
-
return `${home}/${root.join("/")}/${SKILL_DIR_NAME}`;
|
|
251
|
-
}
|
|
252
|
-
function friendlyPath(host, _home) {
|
|
253
|
-
const root = TARGET_ROOTS[host.id];
|
|
254
|
-
if (!root)
|
|
255
|
-
return "(not wired yet)";
|
|
256
|
-
return `~/${root.join("/")}/${SKILL_DIR_NAME}/`;
|
|
257
|
-
}
|
|
258
|
-
function buildArgsFor(action, target) {
|
|
259
|
-
const flags = new Set();
|
|
260
|
-
const values = new Map();
|
|
261
|
-
values.set("target", target);
|
|
262
|
-
if (action === "install-full" || action === "uninstall-full")
|
|
263
|
-
flags.add("--force");
|
|
264
|
-
if (action === "clean-cache")
|
|
265
|
-
values.set("plugin", SKILL_DIR_NAME);
|
|
266
|
-
return {
|
|
267
|
-
rest: [actionToSubcommand(action)],
|
|
268
|
-
plugin: {},
|
|
269
|
-
flags,
|
|
270
|
-
values,
|
|
271
|
-
valuesMulti: new Map(),
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
function actionToSubcommand(action) {
|
|
275
|
-
switch (action) {
|
|
276
|
-
case "install-full":
|
|
277
|
-
return "install-skill";
|
|
278
|
-
case "uninstall-full":
|
|
279
|
-
return "uninstall";
|
|
280
|
-
case "clean-cache":
|
|
281
|
-
return "clean-cache";
|
|
282
|
-
case "clean-legacy":
|
|
283
|
-
return "clean-legacy";
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
async function dispatchAction(action, target, ctx) {
|
|
287
|
-
const args = buildArgsFor(action, target);
|
|
288
|
-
switch (action) {
|
|
289
|
-
case "install-full":
|
|
290
|
-
return selfInstallSkill(args, ctx);
|
|
291
|
-
case "uninstall-full":
|
|
292
|
-
return selfUninstall(args, ctx);
|
|
293
|
-
case "clean-cache":
|
|
294
|
-
return selfClearPluginCache(args, ctx);
|
|
295
|
-
case "clean-legacy":
|
|
296
|
-
return selfCleanLegacy(args, ctx);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
function buildBusyLabel(action, label) {
|
|
300
|
-
switch (action) {
|
|
301
|
-
case "install-full":
|
|
302
|
-
return `installing on ${label}…`;
|
|
303
|
-
case "uninstall-full":
|
|
304
|
-
return `uninstalling from ${label}…`;
|
|
305
|
-
case "clean-cache":
|
|
306
|
-
return `cleaning cache on ${label}…`;
|
|
307
|
-
case "clean-legacy":
|
|
308
|
-
return `removing legacy skills from ${label}…`;
|
|
309
|
-
}
|
|
363
|
+
label: current.status,
|
|
364
|
+
tone: current.status === "installed"
|
|
365
|
+
? "ok"
|
|
366
|
+
: current.status === "unmanaged"
|
|
367
|
+
? "warn"
|
|
368
|
+
: "dim",
|
|
369
|
+
}, actions: detailActions.map((a) => a.action), focusedAction: actionCursor, banner: mode.kind === "confirm" ? (_jsx(ConfirmBanner, { title: `× ${mode.action === "uninstall" ? "Uninstall" : "Remove"} ${current.name}?`, body: mode.action === "uninstall"
|
|
370
|
+
? "Deletes canonical + replica; the registration stays."
|
|
371
|
+
: current.status === "installed"
|
|
372
|
+
? "Uninstalls and drops the registration. A recommended skill returns to the recommended list."
|
|
373
|
+
: "Drops the registration. A recommended skill returns to the recommended list." })) : null })) : null] }), _jsx(Box, { marginTop: 1, children: _jsx(QuickActions, { actions: [{ key: "a", label: "add skill" }] }) })] }));
|
|
310
374
|
}
|
|
311
|
-
function
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
case "clean-legacy":
|
|
320
|
-
return `Legacy skills removed from ${label}.`;
|
|
375
|
+
function detailMeta(item, home) {
|
|
376
|
+
const source = `${item.source}${item.ref ? ` #${item.ref}` : ""}`;
|
|
377
|
+
if (item.status === "recommended")
|
|
378
|
+
return `${source}\n${item.description ?? ""}`;
|
|
379
|
+
const canonical = `${canonicalSkillsRoot(home)}/${item.name}`.replace(home, "~");
|
|
380
|
+
const replicas = `agents ${item.replicas.agents ? "✓" : "·"} · claude ${item.replicas.claude ? "✓" : "·"}${item.mode === "copy" ? " (copy)" : ""}`;
|
|
381
|
+
if (item.status === "unmanaged") {
|
|
382
|
+
return `${source === "" ? "unknown source" : source}\n${canonical}\n${replicas}\nInstalled outside the registry (e.g. skills.sh) — not operable from here.`;
|
|
321
383
|
}
|
|
384
|
+
return `${source}\n${canonical}\n${replicas}`;
|
|
322
385
|
}
|
|
323
386
|
//# sourceMappingURL=skills-tab.js.map
|