@tacuchi/agent-workflow-cli 16.1.0 → 17.0.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/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 +101 -0
- package/dist/application/self/skills-manager.d.ts.map +1 -0
- package/dist/application/self/skills-manager.js +543 -0
- package/dist/application/self/skills-manager.js.map +1 -0
- package/dist/application/self/skills-registry.d.ts +31 -0
- package/dist/application/self/skills-registry.d.ts.map +1 -0
- package/dist/application/self/skills-registry.js +64 -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 +52 -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 +272 -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
- package/skills/w/commands/fix-git.md +1 -1
- package/skills/w/commands/plan-exec.md +7 -0
- package/skills/w/commands/plan-new.md +7 -0
- package/skills/w/commands/plan-refine.md +7 -0
- package/skills/w/commands/quick.md +7 -0
- package/skills/w/commands/spec-new.md +1 -0
- package/skills/w/commands/spec-refine.md +7 -0
|
@@ -1,160 +1,205 @@
|
|
|
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 → registered → recommended), detail con acciones por
|
|
4
|
+
// estado y wizard [a] fuente → picker → warning de terceros → registrar.
|
|
5
|
+
// Respaldado por skills-manager; la administración del bundle `w` vive en
|
|
6
|
+
// [Workflows] (HostAdminSection).
|
|
2
7
|
import { Box, Text, useInput, useStdout } from "ink";
|
|
3
8
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
9
|
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";
|
|
10
|
+
import { canonicalSkillsRoot, installSkill, listSkills, probeSkillSource, registerSkill, reinstallSkill, removeSkill, resolveSkillSource, uninstallSkill, updateSkill, } from "../../../application/self/skills-manager.js";
|
|
9
11
|
import { ConfirmBanner } from "../components/confirm-banner.js";
|
|
10
12
|
import { DetailPanel } from "../components/detail-panel.js";
|
|
13
|
+
import { InputPrompt } from "../components/input-prompt.js";
|
|
11
14
|
import { ListRow } from "../components/list-row.js";
|
|
12
15
|
import { PageHead } from "../components/page-head.js";
|
|
13
16
|
import { QuickActions } from "../components/quick-actions.js";
|
|
14
17
|
import { SectionHead } from "../components/section-head.js";
|
|
15
|
-
import {
|
|
16
|
-
import { HOSTS } from "../hosts.js";
|
|
18
|
+
import { RECOMMENDED_SKILLS } from "../data/recommended-skills.js";
|
|
17
19
|
import { useInputLock } from "../input-lock.js";
|
|
18
20
|
import { rowWidth } from "../row-width.js";
|
|
19
21
|
import { colors, icons } from "../theme.js";
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
const STATUS_GLYPH = {
|
|
23
|
+
installed: { glyph: "◆", active: true },
|
|
24
|
+
registered: { glyph: "◇", active: false },
|
|
25
|
+
recommended: { glyph: "·", active: false },
|
|
26
|
+
};
|
|
24
27
|
export function SkillsTab({ ctx, isActive, onToast }) {
|
|
25
|
-
const [
|
|
28
|
+
const [items, setItems] = useState([]);
|
|
26
29
|
const [cursor, setCursor] = useState(0);
|
|
27
30
|
const [actionCursor, setActionCursor] = useState(0);
|
|
28
31
|
const [mode, setMode] = useState({ kind: "list" });
|
|
29
|
-
|
|
32
|
+
// Espejo de items para preservar la selección POR NOMBRE en refresh: la lista
|
|
33
|
+
// se re-ordena tras cada operación (installed→registered→recommended) y un
|
|
34
|
+
// cursor numérico saltaría a otra skill.
|
|
35
|
+
const itemsRef = useRef([]);
|
|
30
36
|
const startedRef = useRef(false);
|
|
31
37
|
const { lock, unlock } = useInputLock();
|
|
32
38
|
const { stdout } = useStdout();
|
|
33
39
|
useEffect(() => {
|
|
34
|
-
if (
|
|
35
|
-
lock();
|
|
36
|
-
else
|
|
40
|
+
if (mode.kind === "list" || mode.kind === "detail")
|
|
37
41
|
unlock();
|
|
38
|
-
|
|
42
|
+
else
|
|
43
|
+
lock();
|
|
44
|
+
}, [mode, lock, unlock]);
|
|
39
45
|
useEffect(() => () => unlock(), [unlock]);
|
|
40
46
|
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)",
|
|
47
|
+
try {
|
|
48
|
+
const next = await listSkills(ctx, RECOMMENDED_SKILLS);
|
|
49
|
+
setCursor((c) => {
|
|
50
|
+
const prevName = itemsRef.current[c]?.name;
|
|
51
|
+
const idx = prevName === undefined ? -1 : next.findIndex((s) => s.name === prevName);
|
|
52
|
+
return idx >= 0 ? idx : Math.min(Math.max(0, c), Math.max(0, next.length - 1));
|
|
71
53
|
});
|
|
54
|
+
itemsRef.current = next;
|
|
55
|
+
setItems(next);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
onToast?.({ tone: "err", title: "Error loading skills", body: err.message });
|
|
72
59
|
}
|
|
73
|
-
|
|
74
|
-
setCursor((c) => Math.min(Math.max(0, c), Math.max(0, next.length - 1)));
|
|
75
|
-
}, [ctx]);
|
|
60
|
+
}, [ctx, onToast]);
|
|
76
61
|
useEffect(() => {
|
|
77
62
|
if (startedRef.current)
|
|
78
63
|
return;
|
|
79
64
|
startedRef.current = true;
|
|
80
65
|
void refresh();
|
|
81
66
|
}, [refresh]);
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
67
|
+
const current = items[cursor] ?? null;
|
|
68
|
+
const installedCount = items.filter((s) => s.status === "installed").length;
|
|
69
|
+
const registeredCount = items.filter((s) => s.status === "registered").length;
|
|
70
|
+
const recommendedCount = items.filter((s) => s.status === "recommended").length;
|
|
71
|
+
// Acciones del detail según estado (SPEC 003): recommended/registered →
|
|
72
|
+
// Install; installed → Update (solo git) / Reinstall / Uninstall; Remove para
|
|
73
|
+
// todo lo registrado (una recomendada vuelve a `recommended`).
|
|
89
74
|
const detailActions = useMemo(() => {
|
|
90
|
-
if (!
|
|
75
|
+
if (!current)
|
|
91
76
|
return [];
|
|
92
|
-
|
|
93
|
-
name: isInstalled ? "Reinstall" : "Install",
|
|
94
|
-
description: isInstalled ? "Overwrite files (--force)." : "Copy files (--force).",
|
|
95
|
-
};
|
|
96
|
-
if (isInstalled) {
|
|
77
|
+
if (current.status === "recommended") {
|
|
97
78
|
return [
|
|
98
|
-
reinstall,
|
|
99
79
|
{
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
80
|
+
id: "install",
|
|
81
|
+
action: {
|
|
82
|
+
name: "Install",
|
|
83
|
+
description: "Register + install (canonical + Claude replica).",
|
|
84
|
+
},
|
|
103
85
|
},
|
|
104
86
|
];
|
|
105
87
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
88
|
+
if (current.status === "registered") {
|
|
89
|
+
return [
|
|
90
|
+
{
|
|
91
|
+
id: "install",
|
|
92
|
+
action: { name: "Install", description: "Materialize canonical + Claude replica." },
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "remove",
|
|
96
|
+
action: { name: "Remove", description: "Drop from the registry.", danger: true },
|
|
97
|
+
},
|
|
98
|
+
];
|
|
116
99
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
100
|
+
// El clasificador canónico del motor (isAbsolute cubre paths Windows como
|
|
101
|
+
// C:\… que un startsWith("/") clasificaría mal como git).
|
|
102
|
+
const resolved = resolveSkillSource(current.source, current.ref);
|
|
103
|
+
const gitSource = !("error" in resolved) && resolved.kind === "git";
|
|
104
|
+
return [
|
|
105
|
+
...(gitSource
|
|
106
|
+
? [
|
|
107
|
+
{
|
|
108
|
+
id: "update",
|
|
109
|
+
action: {
|
|
110
|
+
name: "Update",
|
|
111
|
+
description: "Re-fetch the registered ref (staging + swap).",
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
]
|
|
115
|
+
: []),
|
|
116
|
+
{
|
|
117
|
+
id: "reinstall",
|
|
118
|
+
action: { name: "Reinstall", description: "Repair the replica from the canonical." },
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: "uninstall",
|
|
122
|
+
action: {
|
|
123
|
+
name: "Uninstall",
|
|
124
|
+
description: "Delete canonical + replica; keeps the registration.",
|
|
125
|
+
danger: true,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: "remove",
|
|
130
|
+
action: {
|
|
131
|
+
name: "Remove",
|
|
132
|
+
description: "Uninstall + drop from the registry.",
|
|
133
|
+
danger: true,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
];
|
|
137
|
+
}, [current]);
|
|
138
|
+
// Superficie TUI en EN (SPEC 007); los summaries del motor (ES) van al body.
|
|
139
|
+
const runAction = useCallback(async (label, successTitle, op) => {
|
|
140
|
+
setMode({ kind: "busy", label });
|
|
123
141
|
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;
|
|
142
|
+
const result = await op();
|
|
143
|
+
if (result.ok) {
|
|
144
|
+
onToast?.({ tone: "ok", title: successTitle, body: result.data?.summary ?? "" });
|
|
145
|
+
if (result.data?.warning) {
|
|
146
|
+
onToast?.({ tone: "info", title: "Notice", body: result.data.warning });
|
|
136
147
|
}
|
|
148
|
+
void ctx.logger?.info(formatTuiEvent(`skill-manager ${label}`, "ok"));
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
onToast?.({
|
|
152
|
+
tone: "err",
|
|
153
|
+
title: "Operation refused",
|
|
154
|
+
body: result.error?.message ?? "",
|
|
155
|
+
});
|
|
137
156
|
}
|
|
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
157
|
}
|
|
143
158
|
catch (err) {
|
|
144
159
|
onToast?.({ tone: "err", title: "Error", body: err.message });
|
|
145
160
|
}
|
|
146
|
-
|
|
147
|
-
|
|
161
|
+
await refresh();
|
|
162
|
+
setMode({ kind: "list" });
|
|
163
|
+
}, [ctx, onToast, refresh]);
|
|
164
|
+
const triggerAction = useCallback((id) => {
|
|
165
|
+
if (!current)
|
|
166
|
+
return;
|
|
167
|
+
const name = current.name;
|
|
168
|
+
switch (id) {
|
|
169
|
+
case "install":
|
|
170
|
+
if (current.status === "recommended") {
|
|
171
|
+
// Registrar + instalar en un paso (la semilla ya trae la fuente).
|
|
172
|
+
void runAction(`installing ${name}…`, `Installed · ${name}`, async () => {
|
|
173
|
+
const reg = await registerSkill(ctx, { source: current.source, pick: name });
|
|
174
|
+
if (!reg.ok)
|
|
175
|
+
return reg;
|
|
176
|
+
return installSkill(ctx, name);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
void runAction(`installing ${name}…`, `Installed · ${name}`, () => installSkill(ctx, name));
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
case "update":
|
|
184
|
+
void runAction(`updating ${name}…`, `Updated · ${name}`, () => updateSkill(ctx, name));
|
|
185
|
+
return;
|
|
186
|
+
case "reinstall":
|
|
187
|
+
void runAction(`reinstalling ${name}…`, `Reinstalled · ${name}`, () => reinstallSkill(ctx, name));
|
|
188
|
+
return;
|
|
189
|
+
case "uninstall":
|
|
190
|
+
setMode({ kind: "confirm", action: "uninstall" });
|
|
191
|
+
return;
|
|
192
|
+
case "remove":
|
|
193
|
+
setMode({ kind: "confirm", action: "remove" });
|
|
194
|
+
return;
|
|
148
195
|
}
|
|
149
|
-
}, [ctx,
|
|
150
|
-
// input — list
|
|
196
|
+
}, [ctx, current, runAction]);
|
|
197
|
+
// input — list (↑↓ navega · ⏎ detail · a wizard)
|
|
151
198
|
useInput((input, key) => {
|
|
152
|
-
if (!isActive ||
|
|
199
|
+
if (!isActive || mode.kind !== "list")
|
|
153
200
|
return;
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
if (claude)
|
|
157
|
-
void runComposite("install", claude);
|
|
201
|
+
if (input === "a" || input === "A") {
|
|
202
|
+
setMode({ kind: "wizard-source" });
|
|
158
203
|
return;
|
|
159
204
|
}
|
|
160
205
|
if (key.upArrow) {
|
|
@@ -162,25 +207,17 @@ export function SkillsTab({ ctx, isActive, onToast }) {
|
|
|
162
207
|
return;
|
|
163
208
|
}
|
|
164
209
|
if (key.downArrow) {
|
|
165
|
-
setCursor((c) => (
|
|
210
|
+
setCursor((c) => (items.length === 0 ? 0 : Math.min(items.length - 1, c + 1)));
|
|
166
211
|
return;
|
|
167
212
|
}
|
|
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
|
-
}
|
|
213
|
+
if (key.return && current) {
|
|
177
214
|
setActionCursor(0);
|
|
178
215
|
setMode({ kind: "detail" });
|
|
179
216
|
}
|
|
180
217
|
}, { isActive });
|
|
181
|
-
// input — detail
|
|
218
|
+
// input — detail (↑↓ acciones · ⏎ ejecuta · esc cierra)
|
|
182
219
|
useInput((_input, key) => {
|
|
183
|
-
if (!isActive ||
|
|
220
|
+
if (!isActive || mode.kind !== "detail" || !current)
|
|
184
221
|
return;
|
|
185
222
|
if (key.upArrow) {
|
|
186
223
|
setActionCursor((c) => Math.max(0, c - 1));
|
|
@@ -195,129 +232,135 @@ export function SkillsTab({ ctx, isActive, onToast }) {
|
|
|
195
232
|
return;
|
|
196
233
|
}
|
|
197
234
|
if (key.return) {
|
|
198
|
-
const
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
235
|
+
const entry = detailActions[actionCursor];
|
|
236
|
+
if (entry)
|
|
237
|
+
triggerAction(entry.id);
|
|
238
|
+
}
|
|
239
|
+
}, { isActive });
|
|
240
|
+
// input — confirm (y confirma · n/esc vuelve al detail)
|
|
241
|
+
useInput((input, key) => {
|
|
242
|
+
if (!isActive || mode.kind !== "confirm" || !current)
|
|
243
|
+
return;
|
|
244
|
+
if (input === "y" || input === "Y") {
|
|
245
|
+
const name = current.name;
|
|
246
|
+
if (mode.action === "uninstall") {
|
|
247
|
+
void runAction(`uninstalling ${name}…`, `Uninstalled · ${name}`, () => uninstallSkill(ctx, name));
|
|
203
248
|
}
|
|
204
249
|
else {
|
|
205
|
-
void
|
|
206
|
-
setMode({ kind: "list" });
|
|
250
|
+
void runAction(`removing ${name}…`, `Removed · ${name}`, () => removeSkill(ctx, name));
|
|
207
251
|
}
|
|
208
252
|
}
|
|
253
|
+
else if (key.escape || input === "n" || input === "N") {
|
|
254
|
+
setMode({ kind: "detail" });
|
|
255
|
+
}
|
|
256
|
+
}, { isActive });
|
|
257
|
+
// input — wizard-source esc
|
|
258
|
+
useInput((_input, key) => {
|
|
259
|
+
if (!isActive || mode.kind !== "wizard-source")
|
|
260
|
+
return;
|
|
261
|
+
if (key.escape)
|
|
262
|
+
setMode({ kind: "list" });
|
|
263
|
+
}, { isActive });
|
|
264
|
+
// input — wizard-pick (↑↓ · ⏎ elige · esc cancela)
|
|
265
|
+
useInput((_input, key) => {
|
|
266
|
+
if (!isActive || mode.kind !== "wizard-pick")
|
|
267
|
+
return;
|
|
268
|
+
if (key.escape) {
|
|
269
|
+
setMode({ kind: "list" });
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (key.upArrow) {
|
|
273
|
+
setMode({ ...mode, cursor: Math.max(0, mode.cursor - 1) });
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (key.downArrow) {
|
|
277
|
+
setMode({ ...mode, cursor: Math.min(mode.candidates.length - 1, mode.cursor + 1) });
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (key.return) {
|
|
281
|
+
const pick = mode.candidates[mode.cursor];
|
|
282
|
+
if (pick)
|
|
283
|
+
setMode({ kind: "wizard-warning", source: mode.source, pick });
|
|
284
|
+
}
|
|
209
285
|
}, { isActive });
|
|
210
|
-
// input —
|
|
286
|
+
// input — wizard-warning (r registra · ⏎ registra+instala · esc cancela)
|
|
211
287
|
useInput((input, key) => {
|
|
212
|
-
if (!isActive || mode.kind !== "
|
|
288
|
+
if (!isActive || mode.kind !== "wizard-warning")
|
|
213
289
|
return;
|
|
214
|
-
if (
|
|
215
|
-
void runComposite("uninstall", mode.host);
|
|
290
|
+
if (key.escape) {
|
|
216
291
|
setMode({ kind: "list" });
|
|
292
|
+
return;
|
|
217
293
|
}
|
|
218
|
-
|
|
219
|
-
|
|
294
|
+
const { source, pick } = mode;
|
|
295
|
+
if (input === "r" || input === "R") {
|
|
296
|
+
void runAction(`registering ${pick}…`, `Registered · ${pick}`, () => registerSkill(ctx, { source, pick }));
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (key.return) {
|
|
300
|
+
void runAction(`installing ${pick}…`, `Installed · ${pick}`, async () => {
|
|
301
|
+
const reg = await registerSkill(ctx, { source, pick });
|
|
302
|
+
if (!reg.ok)
|
|
303
|
+
return reg;
|
|
304
|
+
return installSkill(ctx, pick);
|
|
305
|
+
});
|
|
220
306
|
}
|
|
221
307
|
}, { isActive });
|
|
222
|
-
const
|
|
308
|
+
const probeSource = useCallback(async (source) => {
|
|
309
|
+
setMode({ kind: "busy", label: "inspecting source…" });
|
|
310
|
+
try {
|
|
311
|
+
const probe = await probeSkillSource(ctx, { source });
|
|
312
|
+
if (!probe.ok || !probe.data) {
|
|
313
|
+
onToast?.({ tone: "err", title: "Invalid source", body: probe.error?.message ?? "" });
|
|
314
|
+
setMode({ kind: "list" });
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const candidates = probe.data.candidates;
|
|
318
|
+
const single = candidates.length === 1 ? candidates[0] : undefined;
|
|
319
|
+
setMode(single !== undefined
|
|
320
|
+
? { kind: "wizard-warning", source, pick: single }
|
|
321
|
+
: { kind: "wizard-pick", source, candidates, cursor: 0 });
|
|
322
|
+
}
|
|
323
|
+
catch (err) {
|
|
324
|
+
onToast?.({ tone: "err", title: "Error", body: err.message });
|
|
325
|
+
setMode({ kind: "list" });
|
|
326
|
+
}
|
|
327
|
+
}, [ctx, onToast]);
|
|
328
|
+
const overlayVisible = mode.kind !== "list";
|
|
329
|
+
const home = ctx.env.homeDir();
|
|
223
330
|
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
|
-
: [], state: {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
331
|
+
label: `${installedCount} installed · ${registeredCount} registered · ${recommendedCount} recommended`,
|
|
332
|
+
tone: installedCount > 0 ? "accent" : "warn",
|
|
333
|
+
}, action: _jsx(Text, { color: colors.mute, children: "~/.agents/skills + host replicas" }) }), _jsx(SectionHead, { label: "Skills", count: items.length, hint: "installed \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) => {
|
|
334
|
+
const glyph = STATUS_GLYPH[s.status];
|
|
335
|
+
return (_jsx(ListRow, { icon: glyph.glyph, iconActive: glyph.active, title: s.name, subtitle: `${s.source}${s.ref ? ` #${s.ref}` : ""}`, meta: s.mode === "copy" ? [{ label: "copy", tone: "warn" }] : [], state: {
|
|
336
|
+
label: s.status,
|
|
337
|
+
tone: s.status === "installed" ? "ok" : s.status === "registered" ? "dim" : "info",
|
|
338
|
+
}, chevron: true, active: cursor === i, dimmed: mode.kind.startsWith("wizard"), widthHint: rowWidth(stdout?.columns, overlayVisible) }, s.name));
|
|
339
|
+
}), 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) => {
|
|
340
|
+
const source = value.trim();
|
|
341
|
+
if (!source) {
|
|
342
|
+
setMode({ kind: "list" });
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
void probeSource(source);
|
|
346
|
+
}, 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: {
|
|
347
|
+
name: current.name,
|
|
348
|
+
meta: detailMeta(current, home),
|
|
236
349
|
}, statePill: {
|
|
237
|
-
label:
|
|
238
|
-
tone:
|
|
239
|
-
}, actions: detailActions, focusedAction: actionCursor, banner: mode.kind === "confirm
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
function pathForHost(host, home) {
|
|
245
|
-
if (!BACKED_INSTALL_TARGETS.has(host.id))
|
|
246
|
-
return null;
|
|
247
|
-
const root = TARGET_ROOTS[host.id];
|
|
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
|
-
}
|
|
350
|
+
label: current.status,
|
|
351
|
+
tone: current.status === "installed" ? "ok" : "dim",
|
|
352
|
+
}, 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"
|
|
353
|
+
? "Deletes canonical + replica; the registration stays."
|
|
354
|
+
: current.status === "installed"
|
|
355
|
+
? "Uninstalls and drops the registration. A recommended skill returns to the recommended list."
|
|
356
|
+
: "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
357
|
}
|
|
311
|
-
function
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
return `Cache cleaned on ${label}.`;
|
|
319
|
-
case "clean-legacy":
|
|
320
|
-
return `Legacy skills removed from ${label}.`;
|
|
321
|
-
}
|
|
358
|
+
function detailMeta(item, home) {
|
|
359
|
+
const source = `${item.source}${item.ref ? ` #${item.ref}` : ""}`;
|
|
360
|
+
if (item.status === "recommended")
|
|
361
|
+
return `${source}\n${item.description ?? ""}`;
|
|
362
|
+
const canonical = `${canonicalSkillsRoot(home)}/${item.name}`.replace(home, "~");
|
|
363
|
+
const replicas = `agents ${item.replicas.agents ? "✓" : "·"} · claude ${item.replicas.claude ? "✓" : "·"}${item.mode === "copy" ? " (copy)" : ""}`;
|
|
364
|
+
return `${source}\n${canonical}\n${replicas}`;
|
|
322
365
|
}
|
|
323
366
|
//# sourceMappingURL=skills-tab.js.map
|