@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.
Files changed (89) hide show
  1. package/README.md +13 -0
  2. package/dist/adapters/node-file-system.d.ts +3 -1
  3. package/dist/adapters/node-file-system.d.ts.map +1 -1
  4. package/dist/adapters/node-file-system.js +18 -1
  5. package/dist/adapters/node-file-system.js.map +1 -1
  6. package/dist/application/mcp-doctor-service.d.ts.map +1 -1
  7. package/dist/application/mcp-doctor-service.js +3 -2
  8. package/dist/application/mcp-doctor-service.js.map +1 -1
  9. package/dist/application/mcp-host-writer.d.ts +1 -1
  10. package/dist/application/mcp-host-writer.d.ts.map +1 -1
  11. package/dist/application/mcp-host-writer.js +40 -10
  12. package/dist/application/mcp-host-writer.js.map +1 -1
  13. package/dist/application/mcp-remove-service.d.ts.map +1 -1
  14. package/dist/application/mcp-remove-service.js +3 -2
  15. package/dist/application/mcp-remove-service.js.map +1 -1
  16. package/dist/application/mcp-setup-service.d.ts.map +1 -1
  17. package/dist/application/mcp-setup-service.js +3 -2
  18. package/dist/application/mcp-setup-service.js.map +1 -1
  19. package/dist/application/self/install-plugin-skills-git.d.ts +1 -0
  20. package/dist/application/self/install-plugin-skills-git.d.ts.map +1 -1
  21. package/dist/application/self/install-plugin-skills-git.js +11 -2
  22. package/dist/application/self/install-plugin-skills-git.js.map +1 -1
  23. package/dist/application/self/install-plugin-skills.d.ts +6 -0
  24. package/dist/application/self/install-plugin-skills.d.ts.map +1 -1
  25. package/dist/application/self/install-plugin-skills.js +12 -3
  26. package/dist/application/self/install-plugin-skills.js.map +1 -1
  27. package/dist/application/self/mcp-config.d.ts +2 -0
  28. package/dist/application/self/mcp-config.d.ts.map +1 -1
  29. package/dist/application/self/mcp-config.js +29 -12
  30. package/dist/application/self/mcp-config.js.map +1 -1
  31. package/dist/application/self/skills-manager.d.ts +101 -0
  32. package/dist/application/self/skills-manager.d.ts.map +1 -0
  33. package/dist/application/self/skills-manager.js +543 -0
  34. package/dist/application/self/skills-manager.js.map +1 -0
  35. package/dist/application/self/skills-registry.d.ts +31 -0
  36. package/dist/application/self/skills-registry.d.ts.map +1 -0
  37. package/dist/application/self/skills-registry.js +64 -0
  38. package/dist/application/self/skills-registry.js.map +1 -0
  39. package/dist/cli/tui/app.js +5 -2
  40. package/dist/cli/tui/app.js.map +1 -1
  41. package/dist/cli/tui/components/focus-row.d.ts +1 -1
  42. package/dist/cli/tui/components/focus-row.js +1 -1
  43. package/dist/cli/tui/components/host-admin-section.d.ts +22 -0
  44. package/dist/cli/tui/components/host-admin-section.d.ts.map +1 -0
  45. package/dist/cli/tui/components/host-admin-section.js +321 -0
  46. package/dist/cli/tui/components/host-admin-section.js.map +1 -0
  47. package/dist/cli/tui/components/tabs-config.js +1 -1
  48. package/dist/cli/tui/components/tabs-config.js.map +1 -1
  49. package/dist/cli/tui/data/recommended-skills.d.ts +3 -0
  50. package/dist/cli/tui/data/recommended-skills.d.ts.map +1 -0
  51. package/dist/cli/tui/data/recommended-skills.js +52 -0
  52. package/dist/cli/tui/data/recommended-skills.js.map +1 -0
  53. package/dist/cli/tui/data/workflow-content.d.ts +5 -4
  54. package/dist/cli/tui/data/workflow-content.d.ts.map +1 -1
  55. package/dist/cli/tui/data/workflow-content.js +14 -134
  56. package/dist/cli/tui/data/workflow-content.js.map +1 -1
  57. package/dist/cli/tui/tabs/mcp-tab-helpers.d.ts +14 -7
  58. package/dist/cli/tui/tabs/mcp-tab-helpers.d.ts.map +1 -1
  59. package/dist/cli/tui/tabs/mcp-tab-helpers.js +16 -5
  60. package/dist/cli/tui/tabs/mcp-tab-helpers.js.map +1 -1
  61. package/dist/cli/tui/tabs/mcp-tab.d.ts.map +1 -1
  62. package/dist/cli/tui/tabs/mcp-tab.js +15 -13
  63. package/dist/cli/tui/tabs/mcp-tab.js.map +1 -1
  64. package/dist/cli/tui/tabs/skills-tab.d.ts +0 -1
  65. package/dist/cli/tui/tabs/skills-tab.d.ts.map +1 -1
  66. package/dist/cli/tui/tabs/skills-tab.js +272 -229
  67. package/dist/cli/tui/tabs/skills-tab.js.map +1 -1
  68. package/dist/cli/tui/tabs/status-tab.d.ts +1 -1
  69. package/dist/cli/tui/tabs/status-tab.d.ts.map +1 -1
  70. package/dist/cli/tui/tabs/status-tab.js +3 -2
  71. package/dist/cli/tui/tabs/status-tab.js.map +1 -1
  72. package/dist/cli/tui/tabs/workflow-tab.d.ts +6 -1
  73. package/dist/cli/tui/tabs/workflow-tab.d.ts.map +1 -1
  74. package/dist/cli/tui/tabs/workflow-tab.js +15 -28
  75. package/dist/cli/tui/tabs/workflow-tab.js.map +1 -1
  76. package/dist/domain/mcp-entry.d.ts +11 -0
  77. package/dist/domain/mcp-entry.d.ts.map +1 -1
  78. package/dist/domain/mcp-entry.js +13 -0
  79. package/dist/domain/mcp-entry.js.map +1 -1
  80. package/dist/ports/file-system.d.ts +13 -0
  81. package/dist/ports/file-system.d.ts.map +1 -1
  82. package/package.json +1 -1
  83. package/skills/w/commands/fix-git.md +1 -1
  84. package/skills/w/commands/plan-exec.md +7 -0
  85. package/skills/w/commands/plan-new.md +7 -0
  86. package/skills/w/commands/plan-refine.md +7 -0
  87. package/skills/w/commands/quick.md +7 -0
  88. package/skills/w/commands/spec-new.md +1 -0
  89. 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 { selfCleanLegacy } from "../../../application/self/clean-legacy.js";
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 { WORKFLOW_CONTENT } from "../data/workflow-content.js";
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 HOOKS_SUPPORTED_TARGETS = new Set(["claude"]);
21
- // Derived from the backend's own target map so the tab can't drift from what
22
- // `self install/uninstall-skill` actually supports (clean-legacy v14.5.1 lesson).
23
- const BACKED_INSTALL_TARGETS = new Set(Object.keys(TARGET_ROOTS));
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 [skills, setSkills] = useState([]);
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
- const [busy, setBusy] = useState(null);
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 (busy)
35
- lock();
36
- else
40
+ if (mode.kind === "list" || mode.kind === "detail")
37
41
  unlock();
38
- }, [busy, lock, unlock]);
42
+ else
43
+ lock();
44
+ }, [mode, lock, unlock]);
39
45
  useEffect(() => () => unlock(), [unlock]);
40
46
  const refresh = useCallback(async () => {
41
- const home = ctx.env.homeDir();
42
- const settingsPath = `${home}/.claude/settings.json`;
43
- let hooksInstalled = false;
44
- if (await ctx.fs.exists(settingsPath)) {
45
- try {
46
- const parsed = JSON.parse(await ctx.fs.readText(settingsPath));
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
- setSkills(next);
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 installedCount = skills.filter((s) => s.installed).length;
83
- const totalCount = skills.length;
84
- const backedHosts = HOSTS.filter((h) => h.backed).length;
85
- const pendingHosts = HOSTS.length - backedHosts;
86
- const focused = skills[cursor] ?? null;
87
- const isInstalled = focused?.installed === true;
88
- const isBackedFocused = focused ? BACKED_INSTALL_TARGETS.has(focused.host.id) : false;
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 (!focused)
75
+ if (!current)
91
76
  return [];
92
- const reinstall = {
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
- name: "Uninstall",
101
- description: "Remove files. Reversible.",
102
- danger: true,
80
+ id: "install",
81
+ action: {
82
+ name: "Install",
83
+ description: "Register + install (canonical + Claude replica).",
84
+ },
103
85
  },
104
86
  ];
105
87
  }
106
- return [reinstall];
107
- }, [focused, isInstalled]);
108
- const runComposite = useCallback(async (kind, host) => {
109
- if (!BACKED_INSTALL_TARGETS.has(host.id)) {
110
- onToast?.({
111
- tone: "info",
112
- title: `Host '${host.name}' not supported yet`,
113
- body: "Install/uninstall backend without path mapping.",
114
- });
115
- return;
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
- const target = host.id;
118
- const steps = kind === "install"
119
- ? ["clean-legacy", "clean-cache", "install-full"]
120
- : ["uninstall-full", "clean-cache"];
121
- const startLabel = kind === "install" ? `installing on ${host.name}…` : `uninstalling from ${host.name}…`;
122
- setBusy(startLabel);
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
- for (const step of steps) {
125
- setBusy(buildBusyLabel(step, host.name));
126
- const result = await dispatchAction(step, target, ctx);
127
- if (!result.ok) {
128
- const failMsg = result.error?.message;
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
- finally {
147
- setBusy(null);
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, refresh, onToast]);
150
- // input — list mode (↑↓ navega · ⏎ abre detail · Esc no-op · 'i' empty-state install)
196
+ }, [ctx, current, runAction]);
197
+ // input — list (↑↓ navega · ⏎ detail · a wizard)
151
198
  useInput((input, key) => {
152
- if (!isActive || busy || mode.kind !== "list")
199
+ if (!isActive || mode.kind !== "list")
153
200
  return;
154
- if ((input === "i" || input === "I") && installedCount === 0) {
155
- const claude = HOSTS.find((h) => h.id === "claude");
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) => (skills.length === 0 ? 0 : Math.min(skills.length - 1, c + 1)));
210
+ setCursor((c) => (items.length === 0 ? 0 : Math.min(items.length - 1, c + 1)));
166
211
  return;
167
212
  }
168
- if (key.return && focused) {
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 mode (↑↓ navega actions · ⏎ ejecuta focused · Esc cierra)
218
+ // input — detail (↑↓ acciones · ⏎ ejecuta · esc cierra)
182
219
  useInput((_input, key) => {
183
- if (!isActive || busy || mode.kind !== "detail" || !focused)
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 action = detailActions[actionCursor];
199
- if (!action)
200
- return;
201
- if (action.danger) {
202
- setMode({ kind: "confirm-uninstall", host: focused.host });
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 runComposite("install", focused.host);
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 — confirm-uninstall (y confirma · n/esc cancela)
286
+ // input — wizard-warning (r registra · ⏎ registra+instala · esc cancela)
211
287
  useInput((input, key) => {
212
- if (!isActive || mode.kind !== "confirm-uninstall")
288
+ if (!isActive || mode.kind !== "wizard-warning")
213
289
  return;
214
- if (input === "y" || input === "Y") {
215
- void runComposite("uninstall", mode.host);
290
+ if (key.escape) {
216
291
  setMode({ kind: "list" });
292
+ return;
217
293
  }
218
- else if (key.escape || input === "n" || input === "N") {
219
- setMode({ kind: "detail" });
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 detailVisible = mode.kind === "detail" || mode.kind === "confirm-uninstall";
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}/${totalCount} hosts · ${subSkillsTotal()} sub-skills · ${WORKFLOW_CONTENT.slashCommands.length} slash commands`,
225
- tone: installedCount === 0 ? "warn" : "accent",
226
- }, action: _jsx(Text, { color: colors.mute, children: "one universal SKILL \u00B7 agent-workflow" }) }), _jsx(SectionHead, { label: "Hosts", count: totalCount, hint: `backed ${backedHosts} · pending ${pendingHosts}`, ...(detailVisible ? { rightAction: "esc to close detail" } : {}), marginTop: 0 }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { flexDirection: "column", flexGrow: 1, paddingRight: 2, children: skills.map((s, i) => (_jsx(ListRow, { icon: icons.diamond, iconActive: s.installed, title: s.host.name, subtitle: friendlyPath(s.host, ctx.env.homeDir()), meta: HOOKS_SUPPORTED_TARGETS.has(s.host.id) && s.hooks_installed
227
- ? [{ label: "hooks armed", tone: "ok" }]
228
- : [], state: {
229
- label: s.installed ? "installed" : s.host.backed ? "backed" : "pending",
230
- tone: s.installed ? "ok" : s.host.backed ? "dim" : "warn",
231
- }, chevron: true, active: cursor === i, widthHint: rowWidth(stdout?.columns, detailVisible) }, s.host.id))) }), focused && isBackedFocused && detailVisible ? (_jsx(DetailPanel, { bordered: true, header: {
232
- name: focused.host.name,
233
- meta: `${focused.path}${focused.hooks_installed
234
- ? `\nhooks armed · SKILL + ${WORKFLOW_CONTENT.slashCommands.length} slash + ${WORKFLOW_CONTENT.hooks.length} hooks`
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: isInstalled ? "installed" : "missing",
238
- tone: isInstalled ? "ok" : "dim",
239
- }, actions: detailActions, focusedAction: actionCursor, banner: mode.kind === "confirm-uninstall" ? (_jsx(ConfirmBanner, { title: `× Uninstall ${mode.host.name}?`, body: `Removes SKILL + commands + hooks from ${friendlyPath(mode.host, ctx.env.homeDir())}. Reversible with Reinstall.` })) : null })) : null] }), busy ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: colors.warn, children: [icons.spinner, " ", busy] }) })) : null, installedCount === 0 ? (_jsx(Box, { marginTop: 1, children: _jsx(QuickActions, { actions: [{ key: "i", label: "install on Claude" }] }) })) : null] }));
240
- }
241
- function subSkillsTotal() {
242
- return WORKFLOW_CONTENT.commandFamilies.reduce((n, f) => n + f.items.length, 0);
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 buildSuccessMessage(action, label) {
312
- switch (action) {
313
- case "install-full":
314
- return `Install complete OK on ${label}.`;
315
- case "uninstall-full":
316
- return `Uninstall complete OK on ${label}.`;
317
- case "clean-cache":
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