@skillkit/tui 1.3.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/LICENSE +190 -0
- package/dist/index.d.ts +189 -0
- package/dist/index.js +2122 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2122 @@
|
|
|
1
|
+
// src/index.tsx
|
|
2
|
+
import { render } from "ink";
|
|
3
|
+
|
|
4
|
+
// src/App.tsx
|
|
5
|
+
import { useState as useState12 } from "react";
|
|
6
|
+
import { Box as Box10, Text as Text10, useInput as useInput8, useApp, useStdout } from "ink";
|
|
7
|
+
|
|
8
|
+
// src/components/Sidebar.tsx
|
|
9
|
+
import { Box, Text } from "ink";
|
|
10
|
+
|
|
11
|
+
// src/theme.ts
|
|
12
|
+
import chalk from "chalk";
|
|
13
|
+
var colors = {
|
|
14
|
+
primary: "white",
|
|
15
|
+
secondary: "white",
|
|
16
|
+
secondaryDim: "gray",
|
|
17
|
+
success: "white",
|
|
18
|
+
danger: "white",
|
|
19
|
+
warning: "white",
|
|
20
|
+
background: "bgBlack",
|
|
21
|
+
borderDim: "gray"
|
|
22
|
+
};
|
|
23
|
+
var symbols = {
|
|
24
|
+
pointer: chalk.white("\u276F"),
|
|
25
|
+
bullet: "\u25CF",
|
|
26
|
+
checkboxOn: chalk.white("\u2714"),
|
|
27
|
+
checkboxOff: chalk.dim("\u2716"),
|
|
28
|
+
check: chalk.white("\u2713"),
|
|
29
|
+
star: "\u2605",
|
|
30
|
+
spinner: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"]
|
|
31
|
+
};
|
|
32
|
+
var logo = `
|
|
33
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
34
|
+
\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u255D\u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D
|
|
35
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551
|
|
36
|
+
\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551
|
|
37
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
38
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
// src/components/Sidebar.tsx
|
|
42
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
43
|
+
var NAV = [
|
|
44
|
+
{ id: "home", label: "Home" },
|
|
45
|
+
{ id: "browse", label: "Browse" },
|
|
46
|
+
{ id: "recommend", label: "Recommend" },
|
|
47
|
+
{ id: "translate", label: "Translate" },
|
|
48
|
+
{ id: "context", label: "Context" },
|
|
49
|
+
{ id: "installed", label: "List" },
|
|
50
|
+
{ id: "sync", label: "Sync" },
|
|
51
|
+
{ id: "settings", label: "Config" }
|
|
52
|
+
];
|
|
53
|
+
function Sidebar({ screen }) {
|
|
54
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: 14, borderStyle: "single", paddingX: 1, children: [
|
|
55
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: colors.primary, children: "SkillKit" }),
|
|
56
|
+
NAV.slice(0, 3).map((item) => /* @__PURE__ */ jsxs(Text, { inverse: screen === item.id, children: [
|
|
57
|
+
screen === item.id ? symbols.bullet : " ",
|
|
58
|
+
item.label
|
|
59
|
+
] }, item.id)),
|
|
60
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
61
|
+
NAV.slice(3, 5).map((item) => /* @__PURE__ */ jsxs(Text, { inverse: screen === item.id, children: [
|
|
62
|
+
screen === item.id ? symbols.bullet : " ",
|
|
63
|
+
item.label
|
|
64
|
+
] }, item.id)),
|
|
65
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
66
|
+
NAV.slice(5).map((item) => /* @__PURE__ */ jsxs(Text, { inverse: screen === item.id, children: [
|
|
67
|
+
screen === item.id ? symbols.bullet : " ",
|
|
68
|
+
item.label
|
|
69
|
+
] }, item.id)),
|
|
70
|
+
/* @__PURE__ */ jsx(Box, { flexGrow: 1 }),
|
|
71
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "? Help" }),
|
|
72
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "q Quit" })
|
|
73
|
+
] });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/screens/Home.tsx
|
|
77
|
+
import { useState, useEffect } from "react";
|
|
78
|
+
import { readFileSync } from "fs";
|
|
79
|
+
import { fileURLToPath } from "url";
|
|
80
|
+
import { dirname, join } from "path";
|
|
81
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
82
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
83
|
+
var CHARS = "01\u2588\u2593\u2592\u2591\u2554\u2557\u255A\u255D\u2551\u2550";
|
|
84
|
+
var logoSmall = `
|
|
85
|
+
\u2554\u2550\u2557\u2566\u2554\u2550\u2566\u2566 \u2566 \u2566\u2554\u2550\u2566\u2554\u2566\u2557
|
|
86
|
+
\u255A\u2550\u2557\u2560\u2569\u2557\u2551\u2551 \u2551 \u2560\u2569\u2557\u2551 \u2551
|
|
87
|
+
\u255A\u2550\u255D\u2569 \u2569\u2569\u2569\u2550\u255D\u2569\u2550\u255D\u2569 \u2569\u2569 \u2569
|
|
88
|
+
`.trim();
|
|
89
|
+
function getVersion() {
|
|
90
|
+
try {
|
|
91
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
92
|
+
const __dirname = dirname(__filename);
|
|
93
|
+
const packageJsonPath = join(__dirname, "../../../package.json");
|
|
94
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
95
|
+
return packageJson.version || "1.2.0";
|
|
96
|
+
} catch {
|
|
97
|
+
return "1.2.0";
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function scramble(target, progress) {
|
|
101
|
+
return target.split("\n").map((line) => {
|
|
102
|
+
return line.split("").map((char, i) => {
|
|
103
|
+
if (char === " ") return char;
|
|
104
|
+
if (progress > i / line.length * 100) return char;
|
|
105
|
+
return CHARS[Math.floor(Math.random() * CHARS.length)];
|
|
106
|
+
}).join("");
|
|
107
|
+
}).join("\n");
|
|
108
|
+
}
|
|
109
|
+
function Home({ cols = 80, rows = 24 }) {
|
|
110
|
+
const [progress, setProgress] = useState(0);
|
|
111
|
+
const [display, setDisplay] = useState("");
|
|
112
|
+
const version = getVersion();
|
|
113
|
+
const useLogo = cols < 80 ? logoSmall : logo;
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
if (progress >= 100) {
|
|
116
|
+
setDisplay(useLogo);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const t = setInterval(() => {
|
|
120
|
+
setProgress((p) => {
|
|
121
|
+
const next = p + 12;
|
|
122
|
+
setDisplay(scramble(useLogo, next));
|
|
123
|
+
return next;
|
|
124
|
+
});
|
|
125
|
+
}, 35);
|
|
126
|
+
return () => clearInterval(t);
|
|
127
|
+
}, [progress, useLogo]);
|
|
128
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
129
|
+
/* @__PURE__ */ jsx2(Text2, { color: colors.primary, children: display || useLogo }),
|
|
130
|
+
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { children: "Manage AI agent skills from your terminal." }) }),
|
|
131
|
+
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
132
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, children: "Quick Actions:" }),
|
|
133
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " [b] Browse skills marketplace" }),
|
|
134
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " [r] Get smart recommendations" }),
|
|
135
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " [t] Translate skills between agents" }),
|
|
136
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " [c] Manage project context" }),
|
|
137
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " [l] View installed skills" }),
|
|
138
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " [s] Sync skills across agents" })
|
|
139
|
+
] }),
|
|
140
|
+
rows >= 18 && /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
|
|
141
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, children: "Navigation:" }),
|
|
142
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " \u2191\u2193 Navigate lists" }),
|
|
143
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " Enter Select / Confirm" }),
|
|
144
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " Esc Go back / Home" }),
|
|
145
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " q Quit" })
|
|
146
|
+
] }),
|
|
147
|
+
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
148
|
+
"v",
|
|
149
|
+
version,
|
|
150
|
+
" - Works with 17 AI agents"
|
|
151
|
+
] }) })
|
|
152
|
+
] });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/screens/Browse.tsx
|
|
156
|
+
import { useState as useState3 } from "react";
|
|
157
|
+
import { existsSync, mkdirSync, cpSync, rmSync } from "fs";
|
|
158
|
+
import { join as join2 } from "path";
|
|
159
|
+
import { Box as Box3, Text as Text3, useInput } from "ink";
|
|
160
|
+
|
|
161
|
+
// src/hooks/useMarketplace.ts
|
|
162
|
+
import { useState as useState2, useCallback, useEffect as useEffect2 } from "react";
|
|
163
|
+
import { detectProvider } from "@skillkit/core";
|
|
164
|
+
var POPULAR_REPOS = [
|
|
165
|
+
{ source: "anthropics/skills", name: "Anthropic Official" },
|
|
166
|
+
{ source: "vercel-labs/agent-skills", name: "Vercel Labs" },
|
|
167
|
+
{ source: "expo/skills", name: "Expo / React Native" },
|
|
168
|
+
{ source: "remotion-dev/skills", name: "Remotion Video" },
|
|
169
|
+
{ source: "ComposioHQ/awesome-claude-skills", name: "Composio Awesome" },
|
|
170
|
+
{ source: "travisvn/awesome-claude-skills", name: "Travis Awesome" },
|
|
171
|
+
{ source: "mhattingpete/claude-skills-marketplace", name: "Skills Marketplace" },
|
|
172
|
+
{ source: "coreyhaines31/marketingskills", name: "Marketing Skills" },
|
|
173
|
+
{ source: "obra/superpowers", name: "Superpowers TDD" },
|
|
174
|
+
{ source: "softaworks/agent-toolkit", name: "Softaworks Toolkit" },
|
|
175
|
+
{ source: "wshobson/agents", name: "Dev Patterns" },
|
|
176
|
+
{ source: "langgenius/dify", name: "Dify Frontend" },
|
|
177
|
+
{ source: "trailofbits/skills", name: "Trail of Bits Security" },
|
|
178
|
+
{ source: "better-auth/skills", name: "Better Auth" },
|
|
179
|
+
{ source: "onmax/nuxt-skills", name: "Nuxt / Vue" },
|
|
180
|
+
{ source: "hyf0/vue-skills", name: "Vue Best Practices" },
|
|
181
|
+
{ source: "jezweb/claude-skills", name: "Cloudflare / TanStack" },
|
|
182
|
+
{ source: "elysiajs/skills", name: "ElysiaJS / Bun" },
|
|
183
|
+
{ source: "kadajett/agent-nestjs-skills", name: "NestJS" },
|
|
184
|
+
{ source: "callstackincubator/agent-skills", name: "React Native" },
|
|
185
|
+
{ source: "cloudai-x/threejs-skills", name: "Three.js" },
|
|
186
|
+
{ source: "emalorenzo/three-agent-skills", name: "Three.js Advanced" },
|
|
187
|
+
{ source: "dimillian/skills", name: "SwiftUI iOS" },
|
|
188
|
+
{ source: "stripe/ai", name: "Stripe Payments" },
|
|
189
|
+
{ source: "waynesutton/convexskills", name: "Convex Backend" },
|
|
190
|
+
{ source: "kepano/obsidian-skills", name: "Obsidian Notes" },
|
|
191
|
+
{ source: "jimliu/baoyu-skills", name: "Baoyu Tools" },
|
|
192
|
+
{ source: "giuseppe-trisciuoglio/developer-kit", name: "Shadcn / Radix" },
|
|
193
|
+
{ source: "openrouterteam/agent-skills", name: "OpenRouter SDK" },
|
|
194
|
+
{ source: "intellectronica/agent-skills", name: "Context7" }
|
|
195
|
+
];
|
|
196
|
+
function useMarketplace() {
|
|
197
|
+
const [allSkills, setAllSkills] = useState2([]);
|
|
198
|
+
const [filteredSkills, setFilteredSkills] = useState2([]);
|
|
199
|
+
const [loading, setLoading] = useState2(false);
|
|
200
|
+
const [error, setError] = useState2(null);
|
|
201
|
+
const [currentRepo, setCurrentRepo] = useState2(null);
|
|
202
|
+
const [fetchedRepos, setFetchedRepos] = useState2(/* @__PURE__ */ new Set());
|
|
203
|
+
const fetchRepo = useCallback(async (source) => {
|
|
204
|
+
if (fetchedRepos.has(source)) return;
|
|
205
|
+
setLoading(true);
|
|
206
|
+
setError(null);
|
|
207
|
+
setCurrentRepo(source);
|
|
208
|
+
try {
|
|
209
|
+
const provider = detectProvider(source);
|
|
210
|
+
if (!provider) {
|
|
211
|
+
throw new Error(`Could not detect provider for: ${source}`);
|
|
212
|
+
}
|
|
213
|
+
const result = await provider.clone(source, "", { depth: 1 });
|
|
214
|
+
if (!result.success || !result.discoveredSkills) {
|
|
215
|
+
throw new Error(result.error || "Failed to fetch skills");
|
|
216
|
+
}
|
|
217
|
+
const repoName = POPULAR_REPOS.find((r) => r.source === source)?.name || source;
|
|
218
|
+
const newSkills = result.discoveredSkills.map((skill) => ({
|
|
219
|
+
name: skill.name,
|
|
220
|
+
source,
|
|
221
|
+
repoName,
|
|
222
|
+
description: void 0
|
|
223
|
+
}));
|
|
224
|
+
setAllSkills((prev) => {
|
|
225
|
+
const updated = [...prev, ...newSkills];
|
|
226
|
+
return updated.sort((a, b) => a.name.localeCompare(b.name));
|
|
227
|
+
});
|
|
228
|
+
setFetchedRepos((prev) => /* @__PURE__ */ new Set([...prev, source]));
|
|
229
|
+
if (result.tempRoot) {
|
|
230
|
+
const { rmSync: rmSync3 } = await import("fs");
|
|
231
|
+
rmSync3(result.tempRoot, { recursive: true, force: true });
|
|
232
|
+
}
|
|
233
|
+
} catch (err) {
|
|
234
|
+
setError(err instanceof Error ? err.message : "Failed to fetch repository");
|
|
235
|
+
} finally {
|
|
236
|
+
setLoading(false);
|
|
237
|
+
setCurrentRepo(null);
|
|
238
|
+
}
|
|
239
|
+
}, [fetchedRepos]);
|
|
240
|
+
const fetchAllRepos = useCallback(async () => {
|
|
241
|
+
setLoading(true);
|
|
242
|
+
setError(null);
|
|
243
|
+
for (const repo of POPULAR_REPOS) {
|
|
244
|
+
if (!fetchedRepos.has(repo.source)) {
|
|
245
|
+
setCurrentRepo(repo.source);
|
|
246
|
+
try {
|
|
247
|
+
await fetchRepo(repo.source);
|
|
248
|
+
} catch {
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
setLoading(false);
|
|
253
|
+
setCurrentRepo(null);
|
|
254
|
+
}, [fetchRepo, fetchedRepos]);
|
|
255
|
+
const search = useCallback((query) => {
|
|
256
|
+
if (!query.trim()) {
|
|
257
|
+
setFilteredSkills(allSkills);
|
|
258
|
+
} else {
|
|
259
|
+
const lowerQuery = query.toLowerCase();
|
|
260
|
+
setFilteredSkills(
|
|
261
|
+
allSkills.filter(
|
|
262
|
+
(s) => s.name.toLowerCase().includes(lowerQuery) || s.source.toLowerCase().includes(lowerQuery) || s.repoName.toLowerCase().includes(lowerQuery) || s.description?.toLowerCase().includes(lowerQuery)
|
|
263
|
+
)
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
}, [allSkills]);
|
|
267
|
+
const refresh = useCallback(() => {
|
|
268
|
+
setFetchedRepos(/* @__PURE__ */ new Set());
|
|
269
|
+
setAllSkills([]);
|
|
270
|
+
setFilteredSkills([]);
|
|
271
|
+
}, []);
|
|
272
|
+
useEffect2(() => {
|
|
273
|
+
setFilteredSkills(allSkills);
|
|
274
|
+
}, [allSkills]);
|
|
275
|
+
const skills = filteredSkills.map((s) => ({
|
|
276
|
+
name: s.name,
|
|
277
|
+
description: s.description || s.repoName,
|
|
278
|
+
source: s.source
|
|
279
|
+
}));
|
|
280
|
+
return {
|
|
281
|
+
skills,
|
|
282
|
+
loading,
|
|
283
|
+
error,
|
|
284
|
+
totalCount: allSkills.length,
|
|
285
|
+
repos: POPULAR_REPOS,
|
|
286
|
+
currentRepo,
|
|
287
|
+
refresh,
|
|
288
|
+
search,
|
|
289
|
+
fetchRepo,
|
|
290
|
+
fetchAllRepos
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// src/screens/Browse.tsx
|
|
295
|
+
import { detectProvider as detectProvider2 } from "@skillkit/core";
|
|
296
|
+
import { detectAgent, getAdapter as getAdapter2, getAllAdapters } from "@skillkit/agents";
|
|
297
|
+
|
|
298
|
+
// src/helpers.ts
|
|
299
|
+
import {
|
|
300
|
+
loadConfig,
|
|
301
|
+
getSearchDirs as coreGetSearchDirs,
|
|
302
|
+
getInstallDir as coreGetInstallDir,
|
|
303
|
+
saveSkillMetadata as coreSaveSkillMetadata
|
|
304
|
+
} from "@skillkit/core";
|
|
305
|
+
import { getAdapter } from "@skillkit/agents";
|
|
306
|
+
function getSearchDirs(agentType) {
|
|
307
|
+
const type = agentType || loadConfig().agent;
|
|
308
|
+
const adapter = getAdapter(type);
|
|
309
|
+
const adapterInfo = {
|
|
310
|
+
type: adapter.type,
|
|
311
|
+
name: adapter.name,
|
|
312
|
+
skillsDir: adapter.skillsDir,
|
|
313
|
+
configFile: adapter.configFile
|
|
314
|
+
};
|
|
315
|
+
return coreGetSearchDirs(adapterInfo);
|
|
316
|
+
}
|
|
317
|
+
function getInstallDir(global = false, agentType) {
|
|
318
|
+
const type = agentType || loadConfig().agent;
|
|
319
|
+
const adapter = getAdapter(type);
|
|
320
|
+
const adapterInfo = {
|
|
321
|
+
type: adapter.type,
|
|
322
|
+
name: adapter.name,
|
|
323
|
+
skillsDir: adapter.skillsDir,
|
|
324
|
+
configFile: adapter.configFile
|
|
325
|
+
};
|
|
326
|
+
return coreGetInstallDir(adapterInfo, global);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// src/screens/Browse.tsx
|
|
330
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
331
|
+
function Browse({ rows = 24 }) {
|
|
332
|
+
const { repos, skills, loading, currentRepo, fetchRepo, fetchAllRepos } = useMarketplace();
|
|
333
|
+
const [view, setView] = useState3("repos");
|
|
334
|
+
const [sel, setSel] = useState3(0);
|
|
335
|
+
const [installing, setInstalling] = useState3(null);
|
|
336
|
+
const [message, setMessage] = useState3(null);
|
|
337
|
+
const [selectedSkill, setSelectedSkill] = useState3(null);
|
|
338
|
+
const [agents, setAgents] = useState3([]);
|
|
339
|
+
const items = view === "repos" ? repos : view === "skills" ? skills : agents;
|
|
340
|
+
const maxVisible = Math.max(5, rows - 8);
|
|
341
|
+
const start = Math.max(0, Math.min(sel - Math.floor(maxVisible / 2), items.length - maxVisible));
|
|
342
|
+
const visible = items.slice(start, start + maxVisible);
|
|
343
|
+
const showAgentSelection = async (skillName, source) => {
|
|
344
|
+
setSelectedSkill({ name: skillName, source });
|
|
345
|
+
const adapters = getAllAdapters();
|
|
346
|
+
const agentList = [];
|
|
347
|
+
for (const a of adapters) {
|
|
348
|
+
agentList.push({
|
|
349
|
+
type: a.type,
|
|
350
|
+
name: a.name,
|
|
351
|
+
detected: await a.isDetected()
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
setAgents(agentList);
|
|
355
|
+
setView("agents");
|
|
356
|
+
setSel(0);
|
|
357
|
+
};
|
|
358
|
+
const installSkill = async (skillName, source, agentType) => {
|
|
359
|
+
setInstalling(skillName);
|
|
360
|
+
setMessage(null);
|
|
361
|
+
try {
|
|
362
|
+
const provider = detectProvider2(source);
|
|
363
|
+
if (!provider) {
|
|
364
|
+
setMessage(`Error: Unknown provider for ${source}`);
|
|
365
|
+
setInstalling(null);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const result = await provider.clone(source, "", { depth: 1 });
|
|
369
|
+
if (!result.success || !result.discoveredSkills) {
|
|
370
|
+
setMessage(`Error: ${result.error || "Failed to fetch"}`);
|
|
371
|
+
setInstalling(null);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const skill = result.discoveredSkills.find((s) => s.name === skillName);
|
|
375
|
+
if (!skill) {
|
|
376
|
+
setMessage(`Error: Skill ${skillName} not found`);
|
|
377
|
+
setInstalling(null);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const targetAgentType = agentType || await detectAgent();
|
|
381
|
+
const adapter = getAdapter2(targetAgentType);
|
|
382
|
+
const installDir = getInstallDir(false, targetAgentType);
|
|
383
|
+
if (!existsSync(installDir)) {
|
|
384
|
+
mkdirSync(installDir, { recursive: true });
|
|
385
|
+
}
|
|
386
|
+
const targetPath = join2(installDir, skillName);
|
|
387
|
+
if (existsSync(targetPath)) {
|
|
388
|
+
rmSync(targetPath, { recursive: true, force: true });
|
|
389
|
+
}
|
|
390
|
+
cpSync(skill.path, targetPath, { recursive: true, dereference: true });
|
|
391
|
+
const metadata = {
|
|
392
|
+
name: skillName,
|
|
393
|
+
description: "",
|
|
394
|
+
source,
|
|
395
|
+
sourceType: provider.type,
|
|
396
|
+
subpath: skillName,
|
|
397
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
398
|
+
enabled: true
|
|
399
|
+
};
|
|
400
|
+
coreSaveSkillMetadata(targetPath, metadata);
|
|
401
|
+
if (result.tempRoot) {
|
|
402
|
+
rmSync(result.tempRoot, { recursive: true, force: true });
|
|
403
|
+
}
|
|
404
|
+
setMessage(`\u2713 Installed ${skillName} to ${adapter.name}`);
|
|
405
|
+
if (!agentType) {
|
|
406
|
+
setView("skills");
|
|
407
|
+
}
|
|
408
|
+
} catch (err) {
|
|
409
|
+
setMessage(`Error: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
410
|
+
} finally {
|
|
411
|
+
setInstalling(null);
|
|
412
|
+
if (agentType) {
|
|
413
|
+
setSelectedSkill(null);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
useInput((input, key) => {
|
|
418
|
+
if (loading || installing) return;
|
|
419
|
+
if (key.upArrow) setSel((i) => Math.max(0, i - 1));
|
|
420
|
+
else if (key.downArrow) setSel((i) => Math.min(items.length - 1, i + 1));
|
|
421
|
+
else if (key.return) {
|
|
422
|
+
if (view === "repos" && repos[sel]) {
|
|
423
|
+
fetchRepo(repos[sel].source);
|
|
424
|
+
setView("skills");
|
|
425
|
+
setSel(0);
|
|
426
|
+
setMessage(null);
|
|
427
|
+
} else if (view === "skills" && skills[sel]?.source) {
|
|
428
|
+
installSkill(skills[sel].name, skills[sel].source);
|
|
429
|
+
} else if (view === "agents" && agents[sel]) {
|
|
430
|
+
installSkill(selectedSkill.name, selectedSkill.source, agents[sel].type);
|
|
431
|
+
}
|
|
432
|
+
} else if (input === "m" && view === "skills" && skills[sel]?.source) {
|
|
433
|
+
showAgentSelection(skills[sel].name, skills[sel].source);
|
|
434
|
+
} else if (input === "r") {
|
|
435
|
+
if (view === "skills") {
|
|
436
|
+
setView("repos");
|
|
437
|
+
setSel(0);
|
|
438
|
+
setMessage(null);
|
|
439
|
+
} else if (view === "agents") {
|
|
440
|
+
setView("skills");
|
|
441
|
+
setSel(0);
|
|
442
|
+
}
|
|
443
|
+
} else if (input === "a" && view === "repos") {
|
|
444
|
+
fetchAllRepos();
|
|
445
|
+
setView("skills");
|
|
446
|
+
setSel(0);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
if (view === "agents") {
|
|
450
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
451
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, color: colors.primary, children: "SELECT AGENT" }),
|
|
452
|
+
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
453
|
+
'Install "',
|
|
454
|
+
selectedSkill?.name,
|
|
455
|
+
'" to which agent?'
|
|
456
|
+
] }),
|
|
457
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "(All agents supported - directory created if needed)" }),
|
|
458
|
+
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, flexDirection: "column", children: visible.map((agent, i) => {
|
|
459
|
+
const idx = start + i;
|
|
460
|
+
const isSel = idx === sel;
|
|
461
|
+
const a = agent;
|
|
462
|
+
const status = a.detected ? "(ready)" : "(will create)";
|
|
463
|
+
return /* @__PURE__ */ jsxs3(Text3, { inverse: isSel, children: [
|
|
464
|
+
isSel ? symbols.pointer : " ",
|
|
465
|
+
" ",
|
|
466
|
+
a.name.padEnd(20),
|
|
467
|
+
" ",
|
|
468
|
+
/* @__PURE__ */ jsx3(Text3, { color: colors.secondaryDim, children: status })
|
|
469
|
+
] }, a.type);
|
|
470
|
+
}) }),
|
|
471
|
+
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Enter=install to selected agent r=back q=quit" }) })
|
|
472
|
+
] });
|
|
473
|
+
}
|
|
474
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
475
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, color: colors.primary, children: view === "repos" ? "REPOSITORIES" : "SKILLS" }),
|
|
476
|
+
loading && /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
477
|
+
"Loading ",
|
|
478
|
+
currentRepo,
|
|
479
|
+
"..."
|
|
480
|
+
] }),
|
|
481
|
+
installing && /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
482
|
+
"Installing ",
|
|
483
|
+
installing,
|
|
484
|
+
"..."
|
|
485
|
+
] }),
|
|
486
|
+
message && /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: message }),
|
|
487
|
+
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
488
|
+
items.length,
|
|
489
|
+
" items"
|
|
490
|
+
] }),
|
|
491
|
+
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
492
|
+
start > 0 && /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
493
|
+
" \u2191 ",
|
|
494
|
+
start,
|
|
495
|
+
" more"
|
|
496
|
+
] }),
|
|
497
|
+
visible.map((item, i) => {
|
|
498
|
+
const idx = start + i;
|
|
499
|
+
const isSel = idx === sel;
|
|
500
|
+
const name = view === "repos" ? item.name : item.name;
|
|
501
|
+
const src = view === "repos" ? item.source : item.source || "";
|
|
502
|
+
return /* @__PURE__ */ jsxs3(Text3, { inverse: isSel, children: [
|
|
503
|
+
isSel ? symbols.pointer : " ",
|
|
504
|
+
name.padEnd(25),
|
|
505
|
+
" ",
|
|
506
|
+
/* @__PURE__ */ jsx3(Text3, { color: colors.secondaryDim, children: src })
|
|
507
|
+
] }, src + name);
|
|
508
|
+
}),
|
|
509
|
+
start + maxVisible < items.length && /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
510
|
+
" \u2193 ",
|
|
511
|
+
items.length - start - maxVisible,
|
|
512
|
+
" more"
|
|
513
|
+
] })
|
|
514
|
+
] }),
|
|
515
|
+
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: view === "repos" ? "Enter=fetch a=all q=quit" : "Enter=quick install m=choose agent r=back q=quit" }) })
|
|
516
|
+
] });
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// src/screens/Installed.tsx
|
|
520
|
+
import { useState as useState5 } from "react";
|
|
521
|
+
import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
|
|
522
|
+
|
|
523
|
+
// src/hooks/useSkills.ts
|
|
524
|
+
import { useState as useState4, useEffect as useEffect3 } from "react";
|
|
525
|
+
import { findAllSkills } from "@skillkit/core";
|
|
526
|
+
function useSkills() {
|
|
527
|
+
const [skills, setSkills] = useState4([]);
|
|
528
|
+
const [loading, setLoading] = useState4(true);
|
|
529
|
+
const [error, setError] = useState4(null);
|
|
530
|
+
const refresh = () => {
|
|
531
|
+
setLoading(true);
|
|
532
|
+
setError(null);
|
|
533
|
+
try {
|
|
534
|
+
const searchDirs = getSearchDirs();
|
|
535
|
+
const foundSkills = findAllSkills(searchDirs);
|
|
536
|
+
const skillItems = foundSkills.map((s) => ({
|
|
537
|
+
name: s.name,
|
|
538
|
+
description: s.description,
|
|
539
|
+
source: s.metadata?.source,
|
|
540
|
+
enabled: s.enabled
|
|
541
|
+
}));
|
|
542
|
+
setSkills(skillItems);
|
|
543
|
+
} catch (err) {
|
|
544
|
+
setError(err instanceof Error ? err.message : "Failed to load skills");
|
|
545
|
+
} finally {
|
|
546
|
+
setLoading(false);
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
const remove = async (name) => {
|
|
550
|
+
const { rmSync: rmSync3 } = await import("fs");
|
|
551
|
+
const foundSkill = skills.find((s) => s.name === name);
|
|
552
|
+
if (foundSkill) {
|
|
553
|
+
const searchDirs = getSearchDirs();
|
|
554
|
+
const allSkills = findAllSkills(searchDirs);
|
|
555
|
+
const skill = allSkills.find((s) => s.name === name);
|
|
556
|
+
if (skill) {
|
|
557
|
+
rmSync3(skill.path, { recursive: true, force: true });
|
|
558
|
+
refresh();
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
useEffect3(() => {
|
|
563
|
+
refresh();
|
|
564
|
+
}, []);
|
|
565
|
+
return { skills, loading, error, refresh, remove };
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// src/screens/Installed.tsx
|
|
569
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
570
|
+
function Installed({ rows = 24 }) {
|
|
571
|
+
const { skills, loading, refresh, remove } = useSkills();
|
|
572
|
+
const [sel, setSel] = useState5(0);
|
|
573
|
+
const maxVisible = Math.max(5, rows - 6);
|
|
574
|
+
const start = Math.max(0, Math.min(sel - Math.floor(maxVisible / 2), skills.length - maxVisible));
|
|
575
|
+
const visible = skills.slice(start, start + maxVisible);
|
|
576
|
+
useInput2((input, key) => {
|
|
577
|
+
if (loading) return;
|
|
578
|
+
if (key.upArrow) setSel((i) => Math.max(0, i - 1));
|
|
579
|
+
else if (key.downArrow) setSel((i) => Math.min(skills.length - 1, i + 1));
|
|
580
|
+
else if (input === "r") refresh();
|
|
581
|
+
else if (input === "d" && skills[sel]) remove(skills[sel].name);
|
|
582
|
+
});
|
|
583
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
584
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, color: colors.primary, children: "INSTALLED SKILLS" }),
|
|
585
|
+
/* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
586
|
+
skills.length,
|
|
587
|
+
" skills"
|
|
588
|
+
] }),
|
|
589
|
+
loading && /* @__PURE__ */ jsx4(Text4, { children: "Loading..." }),
|
|
590
|
+
!loading && skills.length === 0 && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "No skills installed. Press b to browse." }),
|
|
591
|
+
!loading && skills.length > 0 && /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
|
|
592
|
+
start > 0 && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
593
|
+
" \u2191 ",
|
|
594
|
+
start,
|
|
595
|
+
" more"
|
|
596
|
+
] }),
|
|
597
|
+
visible.map((skill, i) => {
|
|
598
|
+
const idx = start + i;
|
|
599
|
+
const isSel = idx === sel;
|
|
600
|
+
return /* @__PURE__ */ jsxs4(Text4, { inverse: isSel, children: [
|
|
601
|
+
isSel ? symbols.pointer : " ",
|
|
602
|
+
skill.name.padEnd(30),
|
|
603
|
+
" ",
|
|
604
|
+
skill.source && /* @__PURE__ */ jsx4(Text4, { color: colors.secondaryDim, children: skill.source })
|
|
605
|
+
] }, skill.name);
|
|
606
|
+
}),
|
|
607
|
+
start + maxVisible < skills.length && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
608
|
+
" \u2193 ",
|
|
609
|
+
skills.length - start - maxVisible,
|
|
610
|
+
" more"
|
|
611
|
+
] })
|
|
612
|
+
] }),
|
|
613
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "r=refresh d=delete q=quit" }) })
|
|
614
|
+
] });
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// src/screens/Sync.tsx
|
|
618
|
+
import { useState as useState6, useEffect as useEffect4 } from "react";
|
|
619
|
+
import { Box as Box5, Text as Text5, useInput as useInput3 } from "ink";
|
|
620
|
+
import { getAllAdapters as getAllAdapters2 } from "@skillkit/agents";
|
|
621
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
622
|
+
function Sync({ rows = 24 }) {
|
|
623
|
+
const [agents, setAgents] = useState6([]);
|
|
624
|
+
const [loading, setLoading] = useState6(true);
|
|
625
|
+
const [sel, setSel] = useState6(0);
|
|
626
|
+
const [syncing, setSyncing] = useState6(false);
|
|
627
|
+
const maxVisible = Math.max(5, rows - 6);
|
|
628
|
+
const start = Math.max(0, Math.min(sel - Math.floor(maxVisible / 2), agents.length - maxVisible));
|
|
629
|
+
const visible = agents.slice(start, start + maxVisible);
|
|
630
|
+
useEffect4(() => {
|
|
631
|
+
(async () => {
|
|
632
|
+
const adapters = getAllAdapters2();
|
|
633
|
+
const s = [];
|
|
634
|
+
for (const a of adapters) {
|
|
635
|
+
s.push({ name: a.name, type: a.type, detected: await a.isDetected() });
|
|
636
|
+
}
|
|
637
|
+
setAgents(s);
|
|
638
|
+
setLoading(false);
|
|
639
|
+
})();
|
|
640
|
+
}, []);
|
|
641
|
+
useInput3((input, key) => {
|
|
642
|
+
if (loading || syncing) return;
|
|
643
|
+
if (key.upArrow) setSel((i) => Math.max(0, i - 1));
|
|
644
|
+
else if (key.downArrow) setSel((i) => Math.min(agents.length - 1, i + 1));
|
|
645
|
+
else if (input === "a") {
|
|
646
|
+
setSyncing(true);
|
|
647
|
+
setTimeout(() => setSyncing(false), 500);
|
|
648
|
+
} else if (key.return && agents[sel]?.detected) {
|
|
649
|
+
setSyncing(true);
|
|
650
|
+
setTimeout(() => setSyncing(false), 300);
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
const detected = agents.filter((a) => a.detected).length;
|
|
654
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
655
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, color: colors.primary, children: "SYNC SKILLS" }),
|
|
656
|
+
/* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
657
|
+
detected,
|
|
658
|
+
"/",
|
|
659
|
+
agents.length,
|
|
660
|
+
" agents detected"
|
|
661
|
+
] }),
|
|
662
|
+
loading && /* @__PURE__ */ jsx5(Text5, { children: "Detecting agents..." }),
|
|
663
|
+
syncing && /* @__PURE__ */ jsx5(Text5, { children: "Syncing..." }),
|
|
664
|
+
!loading && !syncing && /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
665
|
+
start > 0 && /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
666
|
+
" \u2191 ",
|
|
667
|
+
start,
|
|
668
|
+
" more"
|
|
669
|
+
] }),
|
|
670
|
+
visible.map((agent, i) => {
|
|
671
|
+
const idx = start + i;
|
|
672
|
+
const isSel = idx === sel;
|
|
673
|
+
return /* @__PURE__ */ jsxs5(Text5, { inverse: isSel, dimColor: !agent.detected, children: [
|
|
674
|
+
isSel ? symbols.pointer : " ",
|
|
675
|
+
agent.detected ? symbols.checkboxOn : symbols.checkboxOff,
|
|
676
|
+
" ",
|
|
677
|
+
agent.name.padEnd(20),
|
|
678
|
+
" ",
|
|
679
|
+
agent.detected ? "Ready" : "N/A"
|
|
680
|
+
] }, agent.type);
|
|
681
|
+
}),
|
|
682
|
+
start + maxVisible < agents.length && /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
683
|
+
" \u2193 ",
|
|
684
|
+
agents.length - start - maxVisible,
|
|
685
|
+
" more"
|
|
686
|
+
] })
|
|
687
|
+
] }),
|
|
688
|
+
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Enter=sync a=all q=quit" }) })
|
|
689
|
+
] });
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// src/screens/Settings.tsx
|
|
693
|
+
import { useState as useState7 } from "react";
|
|
694
|
+
import { Box as Box6, Text as Text6, useInput as useInput4 } from "ink";
|
|
695
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
696
|
+
var SETTINGS = [
|
|
697
|
+
{ id: "agent", label: "Default Agent", value: "auto-detect" },
|
|
698
|
+
{ id: "sync", label: "Auto Sync", value: "disabled" },
|
|
699
|
+
{ id: "cache", label: "Cache Dir", value: "~/.skillkit/cache" }
|
|
700
|
+
];
|
|
701
|
+
function Settings({}) {
|
|
702
|
+
const [sel, setSel] = useState7(0);
|
|
703
|
+
useInput4((_, key) => {
|
|
704
|
+
if (key.upArrow) setSel((i) => Math.max(0, i - 1));
|
|
705
|
+
else if (key.downArrow) setSel((i) => Math.min(SETTINGS.length - 1, i + 1));
|
|
706
|
+
});
|
|
707
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
708
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, color: colors.primary, children: "SETTINGS" }),
|
|
709
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Configure SkillKit" }),
|
|
710
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, flexDirection: "column", children: SETTINGS.map((s, i) => {
|
|
711
|
+
const isSel = i === sel;
|
|
712
|
+
return /* @__PURE__ */ jsxs6(Text6, { inverse: isSel, children: [
|
|
713
|
+
isSel ? symbols.pointer : " ",
|
|
714
|
+
s.label.padEnd(16),
|
|
715
|
+
" ",
|
|
716
|
+
/* @__PURE__ */ jsx6(Text6, { color: colors.secondaryDim, children: s.value })
|
|
717
|
+
] }, s.id);
|
|
718
|
+
}) }),
|
|
719
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Enter=edit q=quit" }) })
|
|
720
|
+
] });
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// src/screens/Recommend.tsx
|
|
724
|
+
import { useState as useState9 } from "react";
|
|
725
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, cpSync as cpSync2, rmSync as rmSync2 } from "fs";
|
|
726
|
+
import { join as join4 } from "path";
|
|
727
|
+
import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
|
|
728
|
+
|
|
729
|
+
// src/hooks/useRecommend.ts
|
|
730
|
+
import { useState as useState8, useCallback as useCallback2, useEffect as useEffect5 } from "react";
|
|
731
|
+
import {
|
|
732
|
+
RecommendationEngine,
|
|
733
|
+
ContextManager
|
|
734
|
+
} from "@skillkit/core";
|
|
735
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
736
|
+
import { join as join3 } from "path";
|
|
737
|
+
var INDEX_PATH = join3(process.env.HOME || "~", ".skillkit", "index.json");
|
|
738
|
+
var INDEX_CACHE_HOURS = 24;
|
|
739
|
+
function getSampleSkills() {
|
|
740
|
+
return [
|
|
741
|
+
{
|
|
742
|
+
name: "vercel-react-best-practices",
|
|
743
|
+
description: "Modern React patterns including Server Components, hooks best practices, and performance optimization",
|
|
744
|
+
source: "vercel-labs/agent-skills",
|
|
745
|
+
tags: ["react", "frontend", "typescript", "nextjs", "performance"],
|
|
746
|
+
compatibility: {
|
|
747
|
+
frameworks: ["react", "nextjs"],
|
|
748
|
+
languages: ["typescript", "javascript"],
|
|
749
|
+
libraries: []
|
|
750
|
+
},
|
|
751
|
+
popularity: 1500,
|
|
752
|
+
quality: 95,
|
|
753
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
754
|
+
verified: true
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
name: "tailwind-v4-patterns",
|
|
758
|
+
description: "Tailwind CSS v4 utility patterns, responsive design, and component styling best practices",
|
|
759
|
+
source: "vercel-labs/agent-skills",
|
|
760
|
+
tags: ["tailwind", "css", "styling", "frontend", "responsive"],
|
|
761
|
+
compatibility: {
|
|
762
|
+
frameworks: [],
|
|
763
|
+
languages: ["typescript", "javascript"],
|
|
764
|
+
libraries: ["tailwindcss"]
|
|
765
|
+
},
|
|
766
|
+
popularity: 1200,
|
|
767
|
+
quality: 92,
|
|
768
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
769
|
+
verified: true
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
name: "nextjs-app-router",
|
|
773
|
+
description: "Next.js App Router patterns including layouts, server actions, and data fetching",
|
|
774
|
+
source: "vercel-labs/agent-skills",
|
|
775
|
+
tags: ["nextjs", "react", "routing", "server-actions", "frontend"],
|
|
776
|
+
compatibility: {
|
|
777
|
+
frameworks: ["nextjs"],
|
|
778
|
+
languages: ["typescript", "javascript"],
|
|
779
|
+
libraries: []
|
|
780
|
+
},
|
|
781
|
+
popularity: 1100,
|
|
782
|
+
quality: 94,
|
|
783
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
784
|
+
verified: true
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
name: "typescript-strict-patterns",
|
|
788
|
+
description: "TypeScript strict mode patterns, type safety, and advanced type utilities",
|
|
789
|
+
source: "anthropics/skills",
|
|
790
|
+
tags: ["typescript", "types", "safety", "patterns"],
|
|
791
|
+
compatibility: {
|
|
792
|
+
frameworks: [],
|
|
793
|
+
languages: ["typescript"],
|
|
794
|
+
libraries: []
|
|
795
|
+
},
|
|
796
|
+
popularity: 900,
|
|
797
|
+
quality: 90,
|
|
798
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
799
|
+
verified: true
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
name: "supabase-best-practices",
|
|
803
|
+
description: "Supabase integration patterns including auth, database queries, and real-time subscriptions",
|
|
804
|
+
source: "anthropics/skills",
|
|
805
|
+
tags: ["supabase", "database", "auth", "backend", "postgresql"],
|
|
806
|
+
compatibility: {
|
|
807
|
+
frameworks: [],
|
|
808
|
+
languages: ["typescript", "javascript"],
|
|
809
|
+
libraries: ["@supabase/supabase-js"]
|
|
810
|
+
},
|
|
811
|
+
popularity: 800,
|
|
812
|
+
quality: 88,
|
|
813
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
814
|
+
verified: true
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
name: "vitest-testing-patterns",
|
|
818
|
+
description: "Testing patterns with Vitest including mocking, assertions, and test organization",
|
|
819
|
+
source: "anthropics/skills",
|
|
820
|
+
tags: ["vitest", "testing", "typescript", "mocking", "tdd"],
|
|
821
|
+
compatibility: {
|
|
822
|
+
frameworks: [],
|
|
823
|
+
languages: ["typescript", "javascript"],
|
|
824
|
+
libraries: ["vitest"]
|
|
825
|
+
},
|
|
826
|
+
popularity: 700,
|
|
827
|
+
quality: 86,
|
|
828
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
829
|
+
verified: false
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
name: "prisma-database-patterns",
|
|
833
|
+
description: "Prisma ORM patterns for schema design, migrations, and efficient queries",
|
|
834
|
+
source: "vercel-labs/agent-skills",
|
|
835
|
+
tags: ["prisma", "database", "orm", "postgresql", "backend"],
|
|
836
|
+
compatibility: {
|
|
837
|
+
frameworks: [],
|
|
838
|
+
languages: ["typescript"],
|
|
839
|
+
libraries: ["@prisma/client"]
|
|
840
|
+
},
|
|
841
|
+
popularity: 850,
|
|
842
|
+
quality: 89,
|
|
843
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
844
|
+
verified: true
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
name: "security-best-practices",
|
|
848
|
+
description: "Security patterns for web applications including XSS prevention, CSRF, and secure headers",
|
|
849
|
+
source: "trailofbits/skills",
|
|
850
|
+
tags: ["security", "xss", "csrf", "headers", "owasp"],
|
|
851
|
+
compatibility: {
|
|
852
|
+
frameworks: [],
|
|
853
|
+
languages: ["typescript", "javascript", "python"],
|
|
854
|
+
libraries: []
|
|
855
|
+
},
|
|
856
|
+
popularity: 600,
|
|
857
|
+
quality: 95,
|
|
858
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
859
|
+
verified: true
|
|
860
|
+
},
|
|
861
|
+
{
|
|
862
|
+
name: "python-fastapi-patterns",
|
|
863
|
+
description: "FastAPI best practices for building high-performance Python APIs",
|
|
864
|
+
source: "python-skills/fastapi",
|
|
865
|
+
tags: ["python", "fastapi", "backend", "api", "async"],
|
|
866
|
+
compatibility: {
|
|
867
|
+
frameworks: ["fastapi"],
|
|
868
|
+
languages: ["python"],
|
|
869
|
+
libraries: []
|
|
870
|
+
},
|
|
871
|
+
popularity: 550,
|
|
872
|
+
quality: 85,
|
|
873
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
874
|
+
verified: false
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
name: "zustand-state-management",
|
|
878
|
+
description: "Zustand state management patterns for React applications",
|
|
879
|
+
source: "react-skills/state",
|
|
880
|
+
tags: ["zustand", "react", "state-management", "frontend"],
|
|
881
|
+
compatibility: {
|
|
882
|
+
frameworks: ["react"],
|
|
883
|
+
languages: ["typescript", "javascript"],
|
|
884
|
+
libraries: ["zustand"]
|
|
885
|
+
},
|
|
886
|
+
popularity: 650,
|
|
887
|
+
quality: 84,
|
|
888
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
889
|
+
verified: false
|
|
890
|
+
}
|
|
891
|
+
];
|
|
892
|
+
}
|
|
893
|
+
function useRecommend(projectPath = process.cwd()) {
|
|
894
|
+
const [recommendations, setRecommendations] = useState8([]);
|
|
895
|
+
const [searchResults, setSearchResults] = useState8([]);
|
|
896
|
+
const [profile, setProfile] = useState8(null);
|
|
897
|
+
const [loading, setLoading] = useState8(true);
|
|
898
|
+
const [error, setError] = useState8(null);
|
|
899
|
+
const [totalScanned, setTotalScanned] = useState8(0);
|
|
900
|
+
const [indexStatus, setIndexStatus] = useState8("missing");
|
|
901
|
+
const [engine] = useState8(() => new RecommendationEngine());
|
|
902
|
+
const loadIndex = useCallback2(() => {
|
|
903
|
+
if (!existsSync2(INDEX_PATH)) {
|
|
904
|
+
setIndexStatus("missing");
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
907
|
+
try {
|
|
908
|
+
const content = readFileSync2(INDEX_PATH, "utf-8");
|
|
909
|
+
const index = JSON.parse(content);
|
|
910
|
+
const lastUpdated = new Date(index.lastUpdated);
|
|
911
|
+
const hoursSinceUpdate = (Date.now() - lastUpdated.getTime()) / (1e3 * 60 * 60);
|
|
912
|
+
if (hoursSinceUpdate > INDEX_CACHE_HOURS) {
|
|
913
|
+
setIndexStatus("stale");
|
|
914
|
+
} else {
|
|
915
|
+
setIndexStatus("fresh");
|
|
916
|
+
}
|
|
917
|
+
return index;
|
|
918
|
+
} catch {
|
|
919
|
+
setIndexStatus("missing");
|
|
920
|
+
return null;
|
|
921
|
+
}
|
|
922
|
+
}, []);
|
|
923
|
+
const getProjectProfile = useCallback2(() => {
|
|
924
|
+
try {
|
|
925
|
+
const manager = new ContextManager(projectPath);
|
|
926
|
+
let context = manager.get();
|
|
927
|
+
if (!context) {
|
|
928
|
+
context = manager.init();
|
|
929
|
+
}
|
|
930
|
+
if (!context) {
|
|
931
|
+
return null;
|
|
932
|
+
}
|
|
933
|
+
return {
|
|
934
|
+
name: context.project.name,
|
|
935
|
+
type: context.project.type,
|
|
936
|
+
stack: context.stack,
|
|
937
|
+
patterns: context.patterns,
|
|
938
|
+
installedSkills: context.skills?.installed || [],
|
|
939
|
+
excludedSkills: context.skills?.excluded || []
|
|
940
|
+
};
|
|
941
|
+
} catch {
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
944
|
+
}, [projectPath]);
|
|
945
|
+
const loadRecommendations = useCallback2(() => {
|
|
946
|
+
setLoading(true);
|
|
947
|
+
setError(null);
|
|
948
|
+
try {
|
|
949
|
+
const projectProfile = getProjectProfile();
|
|
950
|
+
if (!projectProfile) {
|
|
951
|
+
setError("Failed to analyze project");
|
|
952
|
+
setLoading(false);
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
setProfile(projectProfile);
|
|
956
|
+
const index = loadIndex();
|
|
957
|
+
if (!index || index.skills.length === 0) {
|
|
958
|
+
setRecommendations([]);
|
|
959
|
+
setTotalScanned(0);
|
|
960
|
+
setLoading(false);
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
engine.loadIndex(index);
|
|
964
|
+
const result = engine.recommend(projectProfile, {
|
|
965
|
+
limit: 20,
|
|
966
|
+
minScore: 20,
|
|
967
|
+
excludeInstalled: true,
|
|
968
|
+
includeReasons: true
|
|
969
|
+
});
|
|
970
|
+
setRecommendations(result.recommendations);
|
|
971
|
+
setTotalScanned(result.totalSkillsScanned);
|
|
972
|
+
} catch (err) {
|
|
973
|
+
setError(err instanceof Error ? err.message : "Failed to get recommendations");
|
|
974
|
+
} finally {
|
|
975
|
+
setLoading(false);
|
|
976
|
+
}
|
|
977
|
+
}, [engine, getProjectProfile, loadIndex]);
|
|
978
|
+
const updateIndex = useCallback2(() => {
|
|
979
|
+
setLoading(true);
|
|
980
|
+
setError(null);
|
|
981
|
+
try {
|
|
982
|
+
const sampleIndex = {
|
|
983
|
+
version: 1,
|
|
984
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
985
|
+
skills: getSampleSkills(),
|
|
986
|
+
sources: [
|
|
987
|
+
{
|
|
988
|
+
name: "vercel-labs",
|
|
989
|
+
url: "https://github.com/vercel-labs/agent-skills",
|
|
990
|
+
lastFetched: (/* @__PURE__ */ new Date()).toISOString(),
|
|
991
|
+
skillCount: 5
|
|
992
|
+
},
|
|
993
|
+
{
|
|
994
|
+
name: "anthropics",
|
|
995
|
+
url: "https://github.com/anthropics/skills",
|
|
996
|
+
lastFetched: (/* @__PURE__ */ new Date()).toISOString(),
|
|
997
|
+
skillCount: 3
|
|
998
|
+
}
|
|
999
|
+
]
|
|
1000
|
+
};
|
|
1001
|
+
const indexDir = join3(process.env.HOME || "~", ".skillkit");
|
|
1002
|
+
if (!existsSync2(indexDir)) {
|
|
1003
|
+
mkdirSync2(indexDir, { recursive: true });
|
|
1004
|
+
}
|
|
1005
|
+
writeFileSync(INDEX_PATH, JSON.stringify(sampleIndex, null, 2));
|
|
1006
|
+
setIndexStatus("fresh");
|
|
1007
|
+
loadRecommendations();
|
|
1008
|
+
} catch (err) {
|
|
1009
|
+
setError(err instanceof Error ? err.message : "Failed to update index");
|
|
1010
|
+
setLoading(false);
|
|
1011
|
+
}
|
|
1012
|
+
}, [loadRecommendations]);
|
|
1013
|
+
const search = useCallback2((query) => {
|
|
1014
|
+
if (!query.trim()) {
|
|
1015
|
+
setSearchResults([]);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
const results = engine.search({
|
|
1019
|
+
query,
|
|
1020
|
+
limit: 10,
|
|
1021
|
+
semantic: true
|
|
1022
|
+
});
|
|
1023
|
+
const scoredResults = results.map((r) => ({
|
|
1024
|
+
skill: r.skill,
|
|
1025
|
+
score: r.relevance,
|
|
1026
|
+
reasons: r.matchedTerms.map((term) => ({
|
|
1027
|
+
category: "tag",
|
|
1028
|
+
description: `Matched: ${term}`,
|
|
1029
|
+
weight: 0,
|
|
1030
|
+
matched: [term]
|
|
1031
|
+
})),
|
|
1032
|
+
warnings: []
|
|
1033
|
+
}));
|
|
1034
|
+
setSearchResults(scoredResults);
|
|
1035
|
+
}, [engine]);
|
|
1036
|
+
const refresh = useCallback2(() => {
|
|
1037
|
+
loadRecommendations();
|
|
1038
|
+
}, [loadRecommendations]);
|
|
1039
|
+
useEffect5(() => {
|
|
1040
|
+
loadRecommendations();
|
|
1041
|
+
}, [loadRecommendations]);
|
|
1042
|
+
return {
|
|
1043
|
+
recommendations,
|
|
1044
|
+
profile,
|
|
1045
|
+
loading,
|
|
1046
|
+
error,
|
|
1047
|
+
totalScanned,
|
|
1048
|
+
indexStatus,
|
|
1049
|
+
refresh,
|
|
1050
|
+
updateIndex,
|
|
1051
|
+
search,
|
|
1052
|
+
searchResults
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// src/screens/Recommend.tsx
|
|
1057
|
+
import { detectProvider as detectProvider3 } from "@skillkit/core";
|
|
1058
|
+
import { detectAgent as detectAgent2, getAdapter as getAdapter3, getAllAdapters as getAllAdapters3 } from "@skillkit/agents";
|
|
1059
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1060
|
+
function Recommend({ rows = 24 }) {
|
|
1061
|
+
const {
|
|
1062
|
+
recommendations,
|
|
1063
|
+
profile,
|
|
1064
|
+
loading,
|
|
1065
|
+
error,
|
|
1066
|
+
totalScanned,
|
|
1067
|
+
indexStatus,
|
|
1068
|
+
refresh,
|
|
1069
|
+
updateIndex,
|
|
1070
|
+
search,
|
|
1071
|
+
searchResults
|
|
1072
|
+
} = useRecommend();
|
|
1073
|
+
const [view, setView] = useState9("recommendations");
|
|
1074
|
+
const [sel, setSel] = useState9(0);
|
|
1075
|
+
const [searchQuery, setSearchQuery] = useState9("");
|
|
1076
|
+
const [searchMode, setSearchMode] = useState9(false);
|
|
1077
|
+
const [installing, setInstalling] = useState9(null);
|
|
1078
|
+
const [message, setMessage] = useState9(null);
|
|
1079
|
+
const [selectedSkill, setSelectedSkill] = useState9(null);
|
|
1080
|
+
const [agents, setAgents] = useState9([]);
|
|
1081
|
+
const items = view === "recommendations" ? recommendations : view === "search" ? searchResults : agents;
|
|
1082
|
+
const maxVisible = Math.max(5, rows - 10);
|
|
1083
|
+
const start = Math.max(0, Math.min(sel - Math.floor(maxVisible / 2), items.length - maxVisible));
|
|
1084
|
+
const visible = items.slice(start, start + maxVisible);
|
|
1085
|
+
const showAgentSelection = async (skillName, source) => {
|
|
1086
|
+
setSelectedSkill({ name: skillName, source });
|
|
1087
|
+
const adapters = getAllAdapters3();
|
|
1088
|
+
const agentList = [];
|
|
1089
|
+
for (const a of adapters) {
|
|
1090
|
+
agentList.push({
|
|
1091
|
+
type: a.type,
|
|
1092
|
+
name: a.name,
|
|
1093
|
+
detected: await a.isDetected()
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
setAgents(agentList);
|
|
1097
|
+
setView("agents");
|
|
1098
|
+
setSel(0);
|
|
1099
|
+
};
|
|
1100
|
+
const installSkill = async (skillName, source, agentType) => {
|
|
1101
|
+
if (!source) {
|
|
1102
|
+
setMessage("Error: No source available for this skill");
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
setInstalling(skillName);
|
|
1106
|
+
setMessage(null);
|
|
1107
|
+
try {
|
|
1108
|
+
const provider = detectProvider3(source);
|
|
1109
|
+
if (!provider) {
|
|
1110
|
+
setMessage(`Error: Unknown provider for ${source}`);
|
|
1111
|
+
setInstalling(null);
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
const result = await provider.clone(source, "", { depth: 1 });
|
|
1115
|
+
if (!result.success || !result.discoveredSkills) {
|
|
1116
|
+
setMessage(`Error: ${result.error || "Failed to fetch"}`);
|
|
1117
|
+
setInstalling(null);
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
const skill = result.discoveredSkills.find((s) => s.name === skillName);
|
|
1121
|
+
if (!skill) {
|
|
1122
|
+
setMessage(`Error: Skill ${skillName} not found in repo`);
|
|
1123
|
+
setInstalling(null);
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
const targetAgentType = agentType || await detectAgent2();
|
|
1127
|
+
const adapter = getAdapter3(targetAgentType);
|
|
1128
|
+
const installDir = getInstallDir(false, targetAgentType);
|
|
1129
|
+
if (!existsSync3(installDir)) {
|
|
1130
|
+
mkdirSync3(installDir, { recursive: true });
|
|
1131
|
+
}
|
|
1132
|
+
const targetPath = join4(installDir, skillName);
|
|
1133
|
+
if (existsSync3(targetPath)) {
|
|
1134
|
+
rmSync2(targetPath, { recursive: true, force: true });
|
|
1135
|
+
}
|
|
1136
|
+
cpSync2(skill.path, targetPath, { recursive: true, dereference: true });
|
|
1137
|
+
const metadata = {
|
|
1138
|
+
name: skillName,
|
|
1139
|
+
description: "",
|
|
1140
|
+
source,
|
|
1141
|
+
sourceType: provider.type,
|
|
1142
|
+
subpath: skillName,
|
|
1143
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1144
|
+
enabled: true
|
|
1145
|
+
};
|
|
1146
|
+
coreSaveSkillMetadata(targetPath, metadata);
|
|
1147
|
+
if (result.tempRoot) {
|
|
1148
|
+
rmSync2(result.tempRoot, { recursive: true, force: true });
|
|
1149
|
+
}
|
|
1150
|
+
setMessage(`\u2713 Installed ${skillName} to ${adapter.name}`);
|
|
1151
|
+
} catch (err) {
|
|
1152
|
+
setMessage(`Error: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
1153
|
+
} finally {
|
|
1154
|
+
setInstalling(null);
|
|
1155
|
+
if (agentType) {
|
|
1156
|
+
setSelectedSkill(null);
|
|
1157
|
+
setView("recommendations");
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
};
|
|
1161
|
+
useInput5((input, key) => {
|
|
1162
|
+
if (loading || installing) return;
|
|
1163
|
+
if (searchMode) {
|
|
1164
|
+
if (key.escape) {
|
|
1165
|
+
setSearchMode(false);
|
|
1166
|
+
setSearchQuery("");
|
|
1167
|
+
setView("recommendations");
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
if (key.return) {
|
|
1171
|
+
setSearchMode(false);
|
|
1172
|
+
if (searchQuery.trim()) {
|
|
1173
|
+
search(searchQuery);
|
|
1174
|
+
setView("search");
|
|
1175
|
+
setSel(0);
|
|
1176
|
+
}
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
if (key.backspace || key.delete) {
|
|
1180
|
+
setSearchQuery((q) => q.slice(0, -1));
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
if (input && !key.ctrl && !key.meta) {
|
|
1184
|
+
setSearchQuery((q) => q + input);
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
if (key.upArrow) setSel((i) => Math.max(0, i - 1));
|
|
1190
|
+
else if (key.downArrow) setSel((i) => Math.min(items.length - 1, i + 1));
|
|
1191
|
+
else if (key.return) {
|
|
1192
|
+
if (view === "agents" && agents[sel]) {
|
|
1193
|
+
installSkill(selectedSkill.name, selectedSkill.source, agents[sel].type);
|
|
1194
|
+
} else if ((view === "recommendations" || view === "search") && items[sel]) {
|
|
1195
|
+
const skill = items[sel];
|
|
1196
|
+
if (skill.skill.source) {
|
|
1197
|
+
installSkill(skill.skill.name, skill.skill.source);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
} else if (input === "/") {
|
|
1201
|
+
setSearchMode(true);
|
|
1202
|
+
setSearchQuery("");
|
|
1203
|
+
} else if (input === "m" && (view === "recommendations" || view === "search") && items[sel]) {
|
|
1204
|
+
const skill = items[sel];
|
|
1205
|
+
if (skill.skill.source) {
|
|
1206
|
+
showAgentSelection(skill.skill.name, skill.skill.source);
|
|
1207
|
+
}
|
|
1208
|
+
} else if (input === "u") {
|
|
1209
|
+
updateIndex();
|
|
1210
|
+
} else if (input === "r" && view !== "recommendations") {
|
|
1211
|
+
if (view === "agents") {
|
|
1212
|
+
setView("recommendations");
|
|
1213
|
+
} else {
|
|
1214
|
+
setView("recommendations");
|
|
1215
|
+
setSearchQuery("");
|
|
1216
|
+
}
|
|
1217
|
+
setSel(0);
|
|
1218
|
+
} else if (input === "R") {
|
|
1219
|
+
refresh();
|
|
1220
|
+
}
|
|
1221
|
+
});
|
|
1222
|
+
if (view === "agents") {
|
|
1223
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
1224
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, color: colors.primary, children: "SELECT AGENT" }),
|
|
1225
|
+
/* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
1226
|
+
'Install "',
|
|
1227
|
+
selectedSkill?.name,
|
|
1228
|
+
'" to which agent?'
|
|
1229
|
+
] }),
|
|
1230
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, flexDirection: "column", children: visible.map((agent, i) => {
|
|
1231
|
+
const idx = start + i;
|
|
1232
|
+
const isSel = idx === sel;
|
|
1233
|
+
const a = agent;
|
|
1234
|
+
const status = a.detected ? "(ready)" : "(will create)";
|
|
1235
|
+
return /* @__PURE__ */ jsxs7(Text7, { inverse: isSel, children: [
|
|
1236
|
+
isSel ? symbols.pointer : " ",
|
|
1237
|
+
" ",
|
|
1238
|
+
a.name.padEnd(20),
|
|
1239
|
+
" ",
|
|
1240
|
+
/* @__PURE__ */ jsx7(Text7, { color: colors.secondaryDim, children: status })
|
|
1241
|
+
] }, a.type);
|
|
1242
|
+
}) }),
|
|
1243
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Enter=install r=back q=quit" }) })
|
|
1244
|
+
] });
|
|
1245
|
+
}
|
|
1246
|
+
const getScoreBar = (score) => {
|
|
1247
|
+
const filled = Math.round(score / 10);
|
|
1248
|
+
const empty = 10 - filled;
|
|
1249
|
+
return "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
1250
|
+
};
|
|
1251
|
+
const getScoreColor = (score) => {
|
|
1252
|
+
if (score >= 70) return "green";
|
|
1253
|
+
if (score >= 50) return "yellow";
|
|
1254
|
+
return "gray";
|
|
1255
|
+
};
|
|
1256
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
1257
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, color: colors.primary, children: view === "search" ? `SEARCH: "${searchQuery}"` : "RECOMMENDATIONS" }),
|
|
1258
|
+
loading && /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Loading..." }),
|
|
1259
|
+
installing && /* @__PURE__ */ jsxs7(Text7, { children: [
|
|
1260
|
+
"Installing ",
|
|
1261
|
+
installing,
|
|
1262
|
+
"..."
|
|
1263
|
+
] }),
|
|
1264
|
+
message && /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: message }),
|
|
1265
|
+
error && /* @__PURE__ */ jsx7(Text7, { color: "red", children: error }),
|
|
1266
|
+
searchMode && /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
|
|
1267
|
+
/* @__PURE__ */ jsxs7(Text7, { children: [
|
|
1268
|
+
"Search: ",
|
|
1269
|
+
searchQuery
|
|
1270
|
+
] }),
|
|
1271
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\u2588" })
|
|
1272
|
+
] }),
|
|
1273
|
+
indexStatus === "missing" && !loading && /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
1274
|
+
/* @__PURE__ */ jsx7(Text7, { color: "yellow", children: "No skill index found." }),
|
|
1275
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Press 'u' to update index from known sources." })
|
|
1276
|
+
] }),
|
|
1277
|
+
indexStatus === "stale" && !loading && /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Index may be outdated. Press 'u' to update." }),
|
|
1278
|
+
profile && !searchMode && /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
1279
|
+
/* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
1280
|
+
"Project: ",
|
|
1281
|
+
profile.name,
|
|
1282
|
+
profile.type ? ` (${profile.type})` : ""
|
|
1283
|
+
] }),
|
|
1284
|
+
profile.stack.languages.length > 0 && /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
1285
|
+
"Stack: ",
|
|
1286
|
+
profile.stack.languages.map((l) => l.name).join(", "),
|
|
1287
|
+
profile.stack.frameworks.length > 0 && `, ${profile.stack.frameworks.map((f) => f.name).join(", ")}`
|
|
1288
|
+
] })
|
|
1289
|
+
] }),
|
|
1290
|
+
!searchMode && /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: view === "search" ? `${searchResults.length} results` : `${recommendations.length} of ${totalScanned} skills matched` }),
|
|
1291
|
+
/* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
|
|
1292
|
+
start > 0 && /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
1293
|
+
" \u2191 ",
|
|
1294
|
+
start,
|
|
1295
|
+
" more"
|
|
1296
|
+
] }),
|
|
1297
|
+
visible.map((item, i) => {
|
|
1298
|
+
const idx = start + i;
|
|
1299
|
+
const isSel = idx === sel;
|
|
1300
|
+
const skill = item.skill;
|
|
1301
|
+
const score = item.score;
|
|
1302
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
1303
|
+
/* @__PURE__ */ jsxs7(Text7, { inverse: isSel, children: [
|
|
1304
|
+
isSel ? symbols.pointer : " ",
|
|
1305
|
+
/* @__PURE__ */ jsxs7(Text7, { color: getScoreColor(score), children: [
|
|
1306
|
+
score.toString().padStart(3),
|
|
1307
|
+
"%"
|
|
1308
|
+
] }),
|
|
1309
|
+
" ",
|
|
1310
|
+
/* @__PURE__ */ jsx7(Text7, { color: getScoreColor(score), children: getScoreBar(score) }),
|
|
1311
|
+
" ",
|
|
1312
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, children: skill.name })
|
|
1313
|
+
] }),
|
|
1314
|
+
isSel && skill.description && /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
1315
|
+
" ",
|
|
1316
|
+
skill.description.slice(0, 60),
|
|
1317
|
+
skill.description.length > 60 ? "..." : ""
|
|
1318
|
+
] }),
|
|
1319
|
+
isSel && skill.source && /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
1320
|
+
" Source: ",
|
|
1321
|
+
skill.source
|
|
1322
|
+
] })
|
|
1323
|
+
] }, skill.name);
|
|
1324
|
+
}),
|
|
1325
|
+
start + maxVisible < items.length && /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
1326
|
+
" \u2193 ",
|
|
1327
|
+
items.length - start - maxVisible,
|
|
1328
|
+
" more"
|
|
1329
|
+
] })
|
|
1330
|
+
] }),
|
|
1331
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: view === "search" ? "Enter=install m=choose agent /=search r=back u=update q=quit" : "Enter=install m=choose agent /=search u=update R=refresh q=quit" }) })
|
|
1332
|
+
] });
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
// src/screens/Translate.tsx
|
|
1336
|
+
import { useState as useState10, useEffect as useEffect6 } from "react";
|
|
1337
|
+
import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync4 } from "fs";
|
|
1338
|
+
import { join as join5 } from "path";
|
|
1339
|
+
import { Box as Box8, Text as Text8, useInput as useInput6 } from "ink";
|
|
1340
|
+
import {
|
|
1341
|
+
translateSkill,
|
|
1342
|
+
getSupportedTranslationAgents
|
|
1343
|
+
} from "@skillkit/core";
|
|
1344
|
+
import { getAllAdapters as getAllAdapters4, getAdapter as getAdapter4 } from "@skillkit/agents";
|
|
1345
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1346
|
+
function Translate({ rows = 24 }) {
|
|
1347
|
+
const [view, setView] = useState10("skills");
|
|
1348
|
+
const [skills, setSkills] = useState10([]);
|
|
1349
|
+
const [agents, setAgents] = useState10([]);
|
|
1350
|
+
const [sel, setSel] = useState10(0);
|
|
1351
|
+
const [selectedSkill, setSelectedSkill] = useState10(null);
|
|
1352
|
+
const [selectedAgent, setSelectedAgent] = useState10(null);
|
|
1353
|
+
const [preview, setPreview] = useState10("");
|
|
1354
|
+
const [result, setResult] = useState10(null);
|
|
1355
|
+
const [loading, setLoading] = useState10(false);
|
|
1356
|
+
useEffect6(() => {
|
|
1357
|
+
const loadSkills = () => {
|
|
1358
|
+
const installDir = getInstallDir(false);
|
|
1359
|
+
const foundSkills = [];
|
|
1360
|
+
if (existsSync4(installDir)) {
|
|
1361
|
+
const dirs = readdirSync(installDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
1362
|
+
for (const dir of dirs) {
|
|
1363
|
+
const skillPath = join5(installDir, dir);
|
|
1364
|
+
const skillMdPath = join5(skillPath, "SKILL.md");
|
|
1365
|
+
if (existsSync4(skillMdPath)) {
|
|
1366
|
+
const content = readFileSync3(skillMdPath, "utf-8");
|
|
1367
|
+
foundSkills.push({
|
|
1368
|
+
name: dir,
|
|
1369
|
+
path: skillPath,
|
|
1370
|
+
content
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
setSkills(foundSkills);
|
|
1376
|
+
};
|
|
1377
|
+
loadSkills();
|
|
1378
|
+
}, []);
|
|
1379
|
+
useEffect6(() => {
|
|
1380
|
+
const adapters = getAllAdapters4();
|
|
1381
|
+
const supportedAgents = getSupportedTranslationAgents();
|
|
1382
|
+
const agentList = adapters.filter((a) => supportedAgents.includes(a.type)).map((a) => ({
|
|
1383
|
+
type: a.type,
|
|
1384
|
+
name: a.name
|
|
1385
|
+
}));
|
|
1386
|
+
setAgents(agentList);
|
|
1387
|
+
}, []);
|
|
1388
|
+
const currentItems = view === "skills" ? skills : agents;
|
|
1389
|
+
const maxVisible = Math.max(5, rows - 12);
|
|
1390
|
+
const start = Math.max(0, Math.min(sel - Math.floor(maxVisible / 2), currentItems.length - maxVisible));
|
|
1391
|
+
const visible = currentItems.slice(start, start + maxVisible);
|
|
1392
|
+
const generatePreview = (skill, agent) => {
|
|
1393
|
+
try {
|
|
1394
|
+
const result2 = translateSkill(skill.content, agent.type, { sourceFilename: "SKILL.md" });
|
|
1395
|
+
return result2.content;
|
|
1396
|
+
} catch (err) {
|
|
1397
|
+
return `Error: ${err instanceof Error ? err.message : "Translation failed"}`;
|
|
1398
|
+
}
|
|
1399
|
+
};
|
|
1400
|
+
const executeTranslation = () => {
|
|
1401
|
+
if (!selectedSkill || !selectedAgent) return;
|
|
1402
|
+
setLoading(true);
|
|
1403
|
+
try {
|
|
1404
|
+
const translationResult = translateSkill(selectedSkill.content, selectedAgent.type, {
|
|
1405
|
+
sourceFilename: "SKILL.md"
|
|
1406
|
+
});
|
|
1407
|
+
if (!translationResult.success) {
|
|
1408
|
+
setResult({
|
|
1409
|
+
success: false,
|
|
1410
|
+
message: `Translation failed: ${translationResult.warnings.join(", ")}`
|
|
1411
|
+
});
|
|
1412
|
+
setLoading(false);
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
const adapter = getAdapter4(selectedAgent.type);
|
|
1416
|
+
const targetDir = adapter?.skillsDir ? join5(process.cwd(), adapter.skillsDir) : join5(process.cwd(), `.${selectedAgent.type}/skills/`);
|
|
1417
|
+
if (!existsSync4(targetDir)) {
|
|
1418
|
+
mkdirSync4(targetDir, { recursive: true });
|
|
1419
|
+
}
|
|
1420
|
+
const filename = translationResult.filename || `${selectedSkill.name}.md`;
|
|
1421
|
+
const targetPath = join5(targetDir, filename);
|
|
1422
|
+
writeFileSync2(targetPath, translationResult.content, "utf-8");
|
|
1423
|
+
setResult({
|
|
1424
|
+
success: true,
|
|
1425
|
+
message: `Translated ${selectedSkill.name} to ${selectedAgent.name} format`,
|
|
1426
|
+
path: targetPath
|
|
1427
|
+
});
|
|
1428
|
+
} catch (err) {
|
|
1429
|
+
setResult({
|
|
1430
|
+
success: false,
|
|
1431
|
+
message: `Error: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
1432
|
+
});
|
|
1433
|
+
} finally {
|
|
1434
|
+
setLoading(false);
|
|
1435
|
+
setView("result");
|
|
1436
|
+
}
|
|
1437
|
+
};
|
|
1438
|
+
useInput6((_input, key) => {
|
|
1439
|
+
if (loading) return;
|
|
1440
|
+
if (key.upArrow) setSel((i) => Math.max(0, i - 1));
|
|
1441
|
+
else if (key.downArrow) setSel((i) => Math.min(currentItems.length - 1, i + 1));
|
|
1442
|
+
else if (key.escape) {
|
|
1443
|
+
if (view === "result") {
|
|
1444
|
+
setView("skills");
|
|
1445
|
+
setSelectedSkill(null);
|
|
1446
|
+
setSelectedAgent(null);
|
|
1447
|
+
setResult(null);
|
|
1448
|
+
setSel(0);
|
|
1449
|
+
} else if (view === "preview") {
|
|
1450
|
+
setView("agents");
|
|
1451
|
+
} else if (view === "agents") {
|
|
1452
|
+
setView("skills");
|
|
1453
|
+
setSelectedSkill(null);
|
|
1454
|
+
}
|
|
1455
|
+
} else if (key.return) {
|
|
1456
|
+
if (view === "skills" && skills[sel]) {
|
|
1457
|
+
setSelectedSkill(skills[sel]);
|
|
1458
|
+
setView("agents");
|
|
1459
|
+
setSel(0);
|
|
1460
|
+
} else if (view === "agents" && agents[sel]) {
|
|
1461
|
+
setSelectedAgent(agents[sel]);
|
|
1462
|
+
const previewContent = generatePreview(selectedSkill, agents[sel]);
|
|
1463
|
+
setPreview(previewContent);
|
|
1464
|
+
setView("preview");
|
|
1465
|
+
} else if (view === "preview") {
|
|
1466
|
+
executeTranslation();
|
|
1467
|
+
} else if (view === "result") {
|
|
1468
|
+
setView("skills");
|
|
1469
|
+
setSelectedSkill(null);
|
|
1470
|
+
setSelectedAgent(null);
|
|
1471
|
+
setResult(null);
|
|
1472
|
+
setSel(0);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
1476
|
+
if (view === "result" && result) {
|
|
1477
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
1478
|
+
/* @__PURE__ */ jsxs8(Text8, { bold: true, color: colors.primary, children: [
|
|
1479
|
+
"TRANSLATION ",
|
|
1480
|
+
result.success ? "COMPLETE" : "FAILED"
|
|
1481
|
+
] }),
|
|
1482
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
1483
|
+
/* @__PURE__ */ jsxs8(Text8, { color: result.success ? "green" : "red", children: [
|
|
1484
|
+
result.success ? "\u2713" : "\u2717",
|
|
1485
|
+
" ",
|
|
1486
|
+
result.message
|
|
1487
|
+
] }),
|
|
1488
|
+
result.path && /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
1489
|
+
"Saved to: ",
|
|
1490
|
+
result.path
|
|
1491
|
+
] })
|
|
1492
|
+
] }),
|
|
1493
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 2, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Press Enter or Esc to continue" }) })
|
|
1494
|
+
] });
|
|
1495
|
+
}
|
|
1496
|
+
if (view === "preview") {
|
|
1497
|
+
const previewLines = preview.split("\n").slice(0, maxVisible);
|
|
1498
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
1499
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: colors.primary, children: "TRANSLATION PREVIEW" }),
|
|
1500
|
+
/* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
1501
|
+
selectedSkill?.name,
|
|
1502
|
+
" \u2192 ",
|
|
1503
|
+
selectedAgent?.name
|
|
1504
|
+
] }),
|
|
1505
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", borderStyle: "single", paddingX: 1, children: [
|
|
1506
|
+
previewLines.map((line, i) => /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: line.slice(0, 70) }, i)),
|
|
1507
|
+
preview.split("\n").length > maxVisible && /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
1508
|
+
"... (",
|
|
1509
|
+
preview.split("\n").length - maxVisible,
|
|
1510
|
+
" more lines)"
|
|
1511
|
+
] })
|
|
1512
|
+
] }),
|
|
1513
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Enter=confirm translation Esc=back" }) })
|
|
1514
|
+
] });
|
|
1515
|
+
}
|
|
1516
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
1517
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: colors.primary, children: view === "skills" ? "TRANSLATE SKILL" : "SELECT TARGET AGENT" }),
|
|
1518
|
+
view === "skills" && /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Select a skill to translate to another agent format" }),
|
|
1519
|
+
view === "agents" && selectedSkill && /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
1520
|
+
'Translate "',
|
|
1521
|
+
selectedSkill.name,
|
|
1522
|
+
'" to which agent format?'
|
|
1523
|
+
] }),
|
|
1524
|
+
skills.length === 0 && view === "skills" && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { color: "yellow", children: "No skills installed. Install some skills first with 'skillkit install'" }) }),
|
|
1525
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
1526
|
+
start > 0 && /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
1527
|
+
" \u2191 ",
|
|
1528
|
+
start,
|
|
1529
|
+
" more"
|
|
1530
|
+
] }),
|
|
1531
|
+
visible.map((item, i) => {
|
|
1532
|
+
const idx = start + i;
|
|
1533
|
+
const isSel = idx === sel;
|
|
1534
|
+
if (view === "skills") {
|
|
1535
|
+
const skill = item;
|
|
1536
|
+
return /* @__PURE__ */ jsxs8(Text8, { inverse: isSel, children: [
|
|
1537
|
+
isSel ? symbols.pointer : " ",
|
|
1538
|
+
" ",
|
|
1539
|
+
skill.name
|
|
1540
|
+
] }, skill.name);
|
|
1541
|
+
} else {
|
|
1542
|
+
const agent = item;
|
|
1543
|
+
return /* @__PURE__ */ jsxs8(Text8, { inverse: isSel, children: [
|
|
1544
|
+
isSel ? symbols.pointer : " ",
|
|
1545
|
+
" ",
|
|
1546
|
+
agent.name.padEnd(20),
|
|
1547
|
+
" ",
|
|
1548
|
+
/* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
1549
|
+
"(",
|
|
1550
|
+
agent.type,
|
|
1551
|
+
")"
|
|
1552
|
+
] })
|
|
1553
|
+
] }, agent.type);
|
|
1554
|
+
}
|
|
1555
|
+
}),
|
|
1556
|
+
start + maxVisible < currentItems.length && /* @__PURE__ */ jsxs8(Text8, { dimColor: true, children: [
|
|
1557
|
+
" \u2193 ",
|
|
1558
|
+
currentItems.length - start - maxVisible,
|
|
1559
|
+
" more"
|
|
1560
|
+
] })
|
|
1561
|
+
] }),
|
|
1562
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: view === "skills" ? "Enter=select skill q=quit" : "Enter=select agent Esc=back q=quit" }) })
|
|
1563
|
+
] });
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
// src/screens/Context.tsx
|
|
1567
|
+
import { useState as useState11, useEffect as useEffect7 } from "react";
|
|
1568
|
+
import { Box as Box9, Text as Text9, useInput as useInput7 } from "ink";
|
|
1569
|
+
import {
|
|
1570
|
+
loadContext,
|
|
1571
|
+
initContext,
|
|
1572
|
+
syncToAgent
|
|
1573
|
+
} from "@skillkit/core";
|
|
1574
|
+
import { getAllAdapters as getAllAdapters5 } from "@skillkit/agents";
|
|
1575
|
+
import { Fragment, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1576
|
+
function Context({ rows = 24 }) {
|
|
1577
|
+
const [view, setView] = useState11("overview");
|
|
1578
|
+
const [context, setContext] = useState11(null);
|
|
1579
|
+
const [loading, setLoading] = useState11(true);
|
|
1580
|
+
const [initializing, setInitializing] = useState11(false);
|
|
1581
|
+
const [syncing, setSyncing] = useState11(false);
|
|
1582
|
+
const [agents, setAgents] = useState11([]);
|
|
1583
|
+
const [sel, setSel] = useState11(0);
|
|
1584
|
+
const [message, setMessage] = useState11(null);
|
|
1585
|
+
const [error, setError] = useState11(null);
|
|
1586
|
+
const projectPath = process.cwd();
|
|
1587
|
+
useEffect7(() => {
|
|
1588
|
+
const load = async () => {
|
|
1589
|
+
setLoading(true);
|
|
1590
|
+
try {
|
|
1591
|
+
const ctx = loadContext(projectPath);
|
|
1592
|
+
setContext(ctx);
|
|
1593
|
+
const adapters = getAllAdapters5();
|
|
1594
|
+
const agentList = [];
|
|
1595
|
+
for (const a of adapters) {
|
|
1596
|
+
const detected = await a.isDetected();
|
|
1597
|
+
agentList.push({
|
|
1598
|
+
type: a.type,
|
|
1599
|
+
name: a.name,
|
|
1600
|
+
detected,
|
|
1601
|
+
synced: ctx?.agents?.synced?.includes(a.type)
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
setAgents(agentList);
|
|
1605
|
+
} catch (err) {
|
|
1606
|
+
setError(err instanceof Error ? err.message : "Failed to load context");
|
|
1607
|
+
} finally {
|
|
1608
|
+
setLoading(false);
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
load();
|
|
1612
|
+
}, [projectPath]);
|
|
1613
|
+
const maxVisible = Math.max(5, rows - 14);
|
|
1614
|
+
const start = Math.max(0, Math.min(sel - Math.floor(maxVisible / 2), agents.length - maxVisible));
|
|
1615
|
+
const visible = agents.slice(start, start + maxVisible);
|
|
1616
|
+
const handleInit = () => {
|
|
1617
|
+
setInitializing(true);
|
|
1618
|
+
setMessage(null);
|
|
1619
|
+
setError(null);
|
|
1620
|
+
try {
|
|
1621
|
+
const newContext = initContext(projectPath);
|
|
1622
|
+
setContext(newContext);
|
|
1623
|
+
setMessage("Context initialized successfully");
|
|
1624
|
+
} catch (err) {
|
|
1625
|
+
setError(err instanceof Error ? err.message : "Failed to initialize context");
|
|
1626
|
+
} finally {
|
|
1627
|
+
setInitializing(false);
|
|
1628
|
+
}
|
|
1629
|
+
};
|
|
1630
|
+
const handleSync = async (agentType) => {
|
|
1631
|
+
setSyncing(true);
|
|
1632
|
+
setMessage(null);
|
|
1633
|
+
setError(null);
|
|
1634
|
+
try {
|
|
1635
|
+
await syncToAgent(agentType, projectPath);
|
|
1636
|
+
setAgents(
|
|
1637
|
+
(prev) => prev.map(
|
|
1638
|
+
(a) => a.type === agentType ? { ...a, synced: true } : a
|
|
1639
|
+
)
|
|
1640
|
+
);
|
|
1641
|
+
setMessage(`Synced to ${agentType} successfully`);
|
|
1642
|
+
} catch (err) {
|
|
1643
|
+
setError(err instanceof Error ? err.message : "Sync failed");
|
|
1644
|
+
} finally {
|
|
1645
|
+
setSyncing(false);
|
|
1646
|
+
}
|
|
1647
|
+
};
|
|
1648
|
+
const handleSyncAll = async () => {
|
|
1649
|
+
setSyncing(true);
|
|
1650
|
+
setMessage(null);
|
|
1651
|
+
setError(null);
|
|
1652
|
+
try {
|
|
1653
|
+
const detectedAgents = agents.filter((a) => a.detected);
|
|
1654
|
+
for (const agent of detectedAgents) {
|
|
1655
|
+
await syncToAgent(agent.type, projectPath);
|
|
1656
|
+
}
|
|
1657
|
+
setAgents(
|
|
1658
|
+
(prev) => prev.map(
|
|
1659
|
+
(a) => a.detected ? { ...a, synced: true } : a
|
|
1660
|
+
)
|
|
1661
|
+
);
|
|
1662
|
+
setMessage(`Synced to ${detectedAgents.length} agents`);
|
|
1663
|
+
} catch (err) {
|
|
1664
|
+
setError(err instanceof Error ? err.message : "Sync failed");
|
|
1665
|
+
} finally {
|
|
1666
|
+
setSyncing(false);
|
|
1667
|
+
}
|
|
1668
|
+
};
|
|
1669
|
+
useInput7((input, key) => {
|
|
1670
|
+
if (loading || initializing || syncing) return;
|
|
1671
|
+
if (view === "sync") {
|
|
1672
|
+
if (key.upArrow) setSel((i) => Math.max(0, i - 1));
|
|
1673
|
+
else if (key.downArrow) setSel((i) => Math.min(agents.length - 1, i + 1));
|
|
1674
|
+
else if (key.return && agents[sel]) {
|
|
1675
|
+
handleSync(agents[sel].type);
|
|
1676
|
+
} else if (key.escape) {
|
|
1677
|
+
setView("overview");
|
|
1678
|
+
setSel(0);
|
|
1679
|
+
} else if (input === "a") {
|
|
1680
|
+
handleSyncAll();
|
|
1681
|
+
}
|
|
1682
|
+
} else if (view === "overview") {
|
|
1683
|
+
if (input === "i") handleInit();
|
|
1684
|
+
else if (input === "s") {
|
|
1685
|
+
setView("sync");
|
|
1686
|
+
setSel(0);
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
});
|
|
1690
|
+
if (loading) {
|
|
1691
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
1692
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: colors.primary, children: "PROJECT CONTEXT" }),
|
|
1693
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Loading..." })
|
|
1694
|
+
] });
|
|
1695
|
+
}
|
|
1696
|
+
if (view === "sync") {
|
|
1697
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
1698
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: colors.primary, children: "SYNC TO AGENTS" }),
|
|
1699
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Select an agent to sync your project context" }),
|
|
1700
|
+
message && /* @__PURE__ */ jsxs9(Text9, { color: "green", children: [
|
|
1701
|
+
"\u2713",
|
|
1702
|
+
" ",
|
|
1703
|
+
message
|
|
1704
|
+
] }),
|
|
1705
|
+
error && /* @__PURE__ */ jsxs9(Text9, { color: "red", children: [
|
|
1706
|
+
"\u2717",
|
|
1707
|
+
" ",
|
|
1708
|
+
error
|
|
1709
|
+
] }),
|
|
1710
|
+
syncing && /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Syncing..." }),
|
|
1711
|
+
/* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
1712
|
+
start > 0 && /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
1713
|
+
" \u2191 ",
|
|
1714
|
+
start,
|
|
1715
|
+
" more"
|
|
1716
|
+
] }),
|
|
1717
|
+
visible.map((agent, i) => {
|
|
1718
|
+
const idx = start + i;
|
|
1719
|
+
const isSel = idx === sel;
|
|
1720
|
+
const status = agent.synced ? "(synced)" : agent.detected ? "(ready)" : "(not detected)";
|
|
1721
|
+
const statusColor = agent.synced ? "green" : agent.detected ? "yellow" : "gray";
|
|
1722
|
+
return /* @__PURE__ */ jsxs9(Text9, { inverse: isSel, children: [
|
|
1723
|
+
isSel ? symbols.pointer : " ",
|
|
1724
|
+
" ",
|
|
1725
|
+
agent.name.padEnd(20),
|
|
1726
|
+
/* @__PURE__ */ jsx9(Text9, { color: statusColor, children: status })
|
|
1727
|
+
] }, agent.type);
|
|
1728
|
+
}),
|
|
1729
|
+
start + maxVisible < agents.length && /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
1730
|
+
" \u2193 ",
|
|
1731
|
+
agents.length - start - maxVisible,
|
|
1732
|
+
" more"
|
|
1733
|
+
] })
|
|
1734
|
+
] }),
|
|
1735
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Enter=sync to agent a=sync all detected Esc=back q=quit" }) })
|
|
1736
|
+
] });
|
|
1737
|
+
}
|
|
1738
|
+
const stack = context?.stack;
|
|
1739
|
+
const detectedCount = agents.filter((a) => a.detected).length;
|
|
1740
|
+
const syncedCount = agents.filter((a) => a.synced).length;
|
|
1741
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
1742
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: colors.primary, children: "PROJECT CONTEXT" }),
|
|
1743
|
+
message && /* @__PURE__ */ jsxs9(Text9, { color: "green", children: [
|
|
1744
|
+
"\u2713",
|
|
1745
|
+
" ",
|
|
1746
|
+
message
|
|
1747
|
+
] }),
|
|
1748
|
+
error && /* @__PURE__ */ jsxs9(Text9, { color: "red", children: [
|
|
1749
|
+
"\u2717",
|
|
1750
|
+
" ",
|
|
1751
|
+
error
|
|
1752
|
+
] }),
|
|
1753
|
+
initializing && /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Initializing..." }),
|
|
1754
|
+
!context && /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
1755
|
+
/* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "No context found." }),
|
|
1756
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Press 'i' to initialize and analyze your project." })
|
|
1757
|
+
] }),
|
|
1758
|
+
context && /* @__PURE__ */ jsxs9(Fragment, { children: [
|
|
1759
|
+
/* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
1760
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: "Project:" }),
|
|
1761
|
+
/* @__PURE__ */ jsxs9(Text9, { children: [
|
|
1762
|
+
" Name: ",
|
|
1763
|
+
context.project?.name || "Unknown"
|
|
1764
|
+
] }),
|
|
1765
|
+
/* @__PURE__ */ jsxs9(Text9, { children: [
|
|
1766
|
+
" Type: ",
|
|
1767
|
+
context.project?.type || "Not detected"
|
|
1768
|
+
] })
|
|
1769
|
+
] }),
|
|
1770
|
+
/* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
1771
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: "Stack:" }),
|
|
1772
|
+
stack?.languages && stack.languages.length > 0 && /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
1773
|
+
" Languages: ",
|
|
1774
|
+
stack.languages.map((l) => l.name).join(", ")
|
|
1775
|
+
] }),
|
|
1776
|
+
stack?.frameworks && stack.frameworks.length > 0 && /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
1777
|
+
" Frameworks: ",
|
|
1778
|
+
stack.frameworks.map((f) => f.name).join(", ")
|
|
1779
|
+
] }),
|
|
1780
|
+
stack?.libraries && stack.libraries.length > 0 && /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
1781
|
+
" Libraries: ",
|
|
1782
|
+
stack.libraries.slice(0, 5).map((l) => l.name).join(", "),
|
|
1783
|
+
stack.libraries.length > 5 ? "..." : ""
|
|
1784
|
+
] }),
|
|
1785
|
+
!stack?.languages?.length && !stack?.frameworks?.length && /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " No stack detected. Press 'i' to re-analyze." })
|
|
1786
|
+
] }),
|
|
1787
|
+
/* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
1788
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: "Agents:" }),
|
|
1789
|
+
/* @__PURE__ */ jsxs9(Text9, { children: [
|
|
1790
|
+
" Detected: ",
|
|
1791
|
+
detectedCount,
|
|
1792
|
+
" agents"
|
|
1793
|
+
] }),
|
|
1794
|
+
/* @__PURE__ */ jsxs9(Text9, { children: [
|
|
1795
|
+
" Synced: ",
|
|
1796
|
+
syncedCount,
|
|
1797
|
+
" agents"
|
|
1798
|
+
] }),
|
|
1799
|
+
detectedCount > 0 && /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
1800
|
+
" (",
|
|
1801
|
+
agents.filter((a) => a.detected).map((a) => a.name).join(", "),
|
|
1802
|
+
")"
|
|
1803
|
+
] })
|
|
1804
|
+
] })
|
|
1805
|
+
] }),
|
|
1806
|
+
/* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
|
|
1807
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: "Actions:" }),
|
|
1808
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " [i] Initialize/refresh context" }),
|
|
1809
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " [s] Sync to agents" })
|
|
1810
|
+
] }),
|
|
1811
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "i=init context s=sync to agents q=quit" }) })
|
|
1812
|
+
] });
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
// src/App.tsx
|
|
1816
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1817
|
+
function App() {
|
|
1818
|
+
const [screen, setScreen] = useState12("home");
|
|
1819
|
+
const { exit } = useApp();
|
|
1820
|
+
const { stdout } = useStdout();
|
|
1821
|
+
const cols = stdout?.columns || 80;
|
|
1822
|
+
const rows = stdout?.rows || 24;
|
|
1823
|
+
const showSidebar = cols >= 70;
|
|
1824
|
+
useInput8((input, key) => {
|
|
1825
|
+
if (input === "q") {
|
|
1826
|
+
exit();
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
if (key.escape) {
|
|
1830
|
+
setScreen("home");
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1833
|
+
if (input === "h") setScreen("home");
|
|
1834
|
+
if (input === "b") setScreen("browse");
|
|
1835
|
+
if (input === "l") setScreen("installed");
|
|
1836
|
+
if (input === "s") setScreen("sync");
|
|
1837
|
+
if (input === ",") setScreen("settings");
|
|
1838
|
+
if (input === "r") setScreen("recommend");
|
|
1839
|
+
if (input === "t") setScreen("translate");
|
|
1840
|
+
if (input === "c") setScreen("context");
|
|
1841
|
+
});
|
|
1842
|
+
const renderScreen = () => {
|
|
1843
|
+
switch (screen) {
|
|
1844
|
+
case "home":
|
|
1845
|
+
return /* @__PURE__ */ jsx10(Home, { onNavigate: setScreen, cols, rows });
|
|
1846
|
+
case "browse":
|
|
1847
|
+
return /* @__PURE__ */ jsx10(Browse, { cols, rows });
|
|
1848
|
+
case "installed":
|
|
1849
|
+
return /* @__PURE__ */ jsx10(Installed, { cols, rows });
|
|
1850
|
+
case "sync":
|
|
1851
|
+
return /* @__PURE__ */ jsx10(Sync, { cols, rows });
|
|
1852
|
+
case "settings":
|
|
1853
|
+
return /* @__PURE__ */ jsx10(Settings, { cols, rows });
|
|
1854
|
+
case "recommend":
|
|
1855
|
+
return /* @__PURE__ */ jsx10(Recommend, { cols, rows });
|
|
1856
|
+
case "translate":
|
|
1857
|
+
return /* @__PURE__ */ jsx10(Translate, { cols, rows });
|
|
1858
|
+
case "context":
|
|
1859
|
+
return /* @__PURE__ */ jsx10(Context, { cols, rows });
|
|
1860
|
+
}
|
|
1861
|
+
};
|
|
1862
|
+
const contentHeight = rows - 2;
|
|
1863
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", height: rows, children: [
|
|
1864
|
+
/* @__PURE__ */ jsxs10(Box10, { flexDirection: "row", height: contentHeight, children: [
|
|
1865
|
+
showSidebar && /* @__PURE__ */ jsx10(Sidebar, { screen, onNavigate: setScreen }),
|
|
1866
|
+
/* @__PURE__ */ jsx10(Box10, { flexDirection: "column", flexGrow: 1, marginLeft: 1, children: renderScreen() })
|
|
1867
|
+
] }),
|
|
1868
|
+
/* @__PURE__ */ jsx10(Box10, { children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "h Home b Browse r Rec t Trans c Ctx l List s Sync , Config q Quit" }) })
|
|
1869
|
+
] });
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
// src/components/Header.tsx
|
|
1873
|
+
import { Box as Box11, Text as Text11 } from "ink";
|
|
1874
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1875
|
+
function Header({ title, subtitle, count }) {
|
|
1876
|
+
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", marginBottom: 1, children: [
|
|
1877
|
+
/* @__PURE__ */ jsxs11(Box11, { justifyContent: "space-between", children: [
|
|
1878
|
+
/* @__PURE__ */ jsx11(Text11, { color: colors.primary, bold: true, children: title.toUpperCase() }),
|
|
1879
|
+
count !== void 0 && /* @__PURE__ */ jsxs11(Text11, { color: colors.secondaryDim, children: [
|
|
1880
|
+
symbols.star,
|
|
1881
|
+
" ",
|
|
1882
|
+
count
|
|
1883
|
+
] })
|
|
1884
|
+
] }),
|
|
1885
|
+
subtitle && /* @__PURE__ */ jsx11(Text11, { color: colors.secondaryDim, dimColor: true, children: subtitle })
|
|
1886
|
+
] });
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
// src/components/SkillList.tsx
|
|
1890
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
1891
|
+
import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1892
|
+
function formatInstalls(count) {
|
|
1893
|
+
if (count >= 1e3) {
|
|
1894
|
+
return `${(count / 1e3).toFixed(1)}K`;
|
|
1895
|
+
}
|
|
1896
|
+
return String(count);
|
|
1897
|
+
}
|
|
1898
|
+
function SkillList({
|
|
1899
|
+
skills,
|
|
1900
|
+
selectedIndex,
|
|
1901
|
+
showInstalls = false,
|
|
1902
|
+
showRank = false,
|
|
1903
|
+
showSource = true,
|
|
1904
|
+
maxVisible = 10
|
|
1905
|
+
}) {
|
|
1906
|
+
if (skills.length === 0) {
|
|
1907
|
+
return /* @__PURE__ */ jsx12(Box12, { children: /* @__PURE__ */ jsx12(Text12, { color: colors.secondaryDim, dimColor: true, children: "No skills found" }) });
|
|
1908
|
+
}
|
|
1909
|
+
const startIndex = Math.max(0, selectedIndex - Math.floor(maxVisible / 2));
|
|
1910
|
+
const visibleSkills = skills.slice(startIndex, startIndex + maxVisible);
|
|
1911
|
+
const actualStartIndex = startIndex;
|
|
1912
|
+
return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
|
|
1913
|
+
showRank && /* @__PURE__ */ jsx12(Box12, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Text12, { color: colors.secondaryDim, children: [
|
|
1914
|
+
" # SKILL",
|
|
1915
|
+
showSource && " SOURCE",
|
|
1916
|
+
showInstalls && " INSTALLS"
|
|
1917
|
+
] }) }),
|
|
1918
|
+
visibleSkills.map((skill, idx) => {
|
|
1919
|
+
const realIndex = actualStartIndex + idx;
|
|
1920
|
+
const isSelected = realIndex === selectedIndex;
|
|
1921
|
+
const skillName = skill.name.padEnd(28).slice(0, 28);
|
|
1922
|
+
const sourceName = skill.source ? skill.source.slice(0, 25) : "";
|
|
1923
|
+
return /* @__PURE__ */ jsxs12(Box12, { children: [
|
|
1924
|
+
/* @__PURE__ */ jsxs12(
|
|
1925
|
+
Text12,
|
|
1926
|
+
{
|
|
1927
|
+
color: isSelected ? colors.primary : colors.secondary,
|
|
1928
|
+
bold: isSelected,
|
|
1929
|
+
inverse: isSelected,
|
|
1930
|
+
children: [
|
|
1931
|
+
isSelected ? symbols.pointer : " ",
|
|
1932
|
+
showRank ? String(realIndex + 1).padStart(2, " ") : "",
|
|
1933
|
+
" ",
|
|
1934
|
+
skillName
|
|
1935
|
+
]
|
|
1936
|
+
}
|
|
1937
|
+
),
|
|
1938
|
+
showSource && /* @__PURE__ */ jsxs12(Text12, { color: colors.secondaryDim, dimColor: !isSelected, children: [
|
|
1939
|
+
" ",
|
|
1940
|
+
sourceName
|
|
1941
|
+
] }),
|
|
1942
|
+
showInstalls && skill.installs !== void 0 && /* @__PURE__ */ jsx12(Text12, { color: colors.secondaryDim, children: formatInstalls(skill.installs).padStart(8) })
|
|
1943
|
+
] }, `${skill.source}-${skill.name}`);
|
|
1944
|
+
}),
|
|
1945
|
+
skills.length > maxVisible && /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text12, { color: colors.secondaryDim, dimColor: true, children: [
|
|
1946
|
+
"Showing ",
|
|
1947
|
+
startIndex + 1,
|
|
1948
|
+
"-",
|
|
1949
|
+
Math.min(startIndex + maxVisible, skills.length),
|
|
1950
|
+
" of ",
|
|
1951
|
+
skills.length
|
|
1952
|
+
] }) })
|
|
1953
|
+
] });
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
// src/components/StatusBar.tsx
|
|
1957
|
+
import { Box as Box13, Text as Text13 } from "ink";
|
|
1958
|
+
import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1959
|
+
function StatusBar({ shortcuts, message }) {
|
|
1960
|
+
return /* @__PURE__ */ jsxs13(
|
|
1961
|
+
Box13,
|
|
1962
|
+
{
|
|
1963
|
+
borderStyle: "single",
|
|
1964
|
+
borderColor: colors.borderDim,
|
|
1965
|
+
borderTop: true,
|
|
1966
|
+
borderBottom: false,
|
|
1967
|
+
borderLeft: false,
|
|
1968
|
+
borderRight: false,
|
|
1969
|
+
paddingX: 1,
|
|
1970
|
+
justifyContent: "space-between",
|
|
1971
|
+
children: [
|
|
1972
|
+
/* @__PURE__ */ jsx13(Box13, { gap: 2, children: shortcuts.map((shortcut, idx) => /* @__PURE__ */ jsxs13(Box13, { gap: 1, children: [
|
|
1973
|
+
/* @__PURE__ */ jsx13(Text13, { color: colors.primary, bold: true, children: shortcut.key }),
|
|
1974
|
+
/* @__PURE__ */ jsx13(Text13, { color: colors.secondaryDim, children: shortcut.label })
|
|
1975
|
+
] }, idx)) }),
|
|
1976
|
+
message && /* @__PURE__ */ jsxs13(Text13, { color: colors.success, children: [
|
|
1977
|
+
symbols.check,
|
|
1978
|
+
" ",
|
|
1979
|
+
message
|
|
1980
|
+
] })
|
|
1981
|
+
]
|
|
1982
|
+
}
|
|
1983
|
+
);
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
// src/components/SearchInput.tsx
|
|
1987
|
+
import { Box as Box14, Text as Text14 } from "ink";
|
|
1988
|
+
import TextInput from "ink-text-input";
|
|
1989
|
+
import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1990
|
+
function SearchInput({
|
|
1991
|
+
value,
|
|
1992
|
+
onChange,
|
|
1993
|
+
placeholder = "Search skills...",
|
|
1994
|
+
isFocused = false
|
|
1995
|
+
}) {
|
|
1996
|
+
return /* @__PURE__ */ jsxs14(
|
|
1997
|
+
Box14,
|
|
1998
|
+
{
|
|
1999
|
+
borderStyle: "single",
|
|
2000
|
+
borderColor: isFocused ? colors.primary : colors.borderDim,
|
|
2001
|
+
paddingX: 1,
|
|
2002
|
+
children: [
|
|
2003
|
+
/* @__PURE__ */ jsx14(Text14, { color: colors.secondaryDim, children: "/ " }),
|
|
2004
|
+
isFocused ? /* @__PURE__ */ jsx14(
|
|
2005
|
+
TextInput,
|
|
2006
|
+
{
|
|
2007
|
+
value,
|
|
2008
|
+
onChange,
|
|
2009
|
+
placeholder
|
|
2010
|
+
}
|
|
2011
|
+
) : /* @__PURE__ */ jsx14(Text14, { color: value ? colors.secondary : colors.secondaryDim, children: value || placeholder }),
|
|
2012
|
+
/* @__PURE__ */ jsx14(Box14, { flexGrow: 1 }),
|
|
2013
|
+
/* @__PURE__ */ jsx14(Text14, { color: colors.secondaryDim, children: "/" })
|
|
2014
|
+
]
|
|
2015
|
+
}
|
|
2016
|
+
);
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
// src/hooks/useKeyboard.ts
|
|
2020
|
+
import { useState as useState13, useCallback as useCallback3, useEffect as useEffect8 } from "react";
|
|
2021
|
+
import { useInput as useInput9, useApp as useApp2 } from "ink";
|
|
2022
|
+
function useKeyboard(options = {}) {
|
|
2023
|
+
const { exit } = useApp2();
|
|
2024
|
+
useInput9((input, key) => {
|
|
2025
|
+
if (options.disabled) return;
|
|
2026
|
+
if (input === "q" || key.ctrl && input === "c") {
|
|
2027
|
+
exit();
|
|
2028
|
+
return;
|
|
2029
|
+
}
|
|
2030
|
+
if (key.escape && options.onBack) {
|
|
2031
|
+
options.onBack();
|
|
2032
|
+
return;
|
|
2033
|
+
}
|
|
2034
|
+
if (key.return && options.onSelect) {
|
|
2035
|
+
options.onSelect();
|
|
2036
|
+
return;
|
|
2037
|
+
}
|
|
2038
|
+
if (key.upArrow && options.onUp) {
|
|
2039
|
+
options.onUp();
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
if (key.downArrow && options.onDown) {
|
|
2043
|
+
options.onDown();
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2046
|
+
if (input === "/" && options.onSearch) {
|
|
2047
|
+
options.onSearch();
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
if (input === "i" && options.onInstall) {
|
|
2051
|
+
options.onInstall();
|
|
2052
|
+
return;
|
|
2053
|
+
}
|
|
2054
|
+
if (input === "h" && options.onNavigate) {
|
|
2055
|
+
options.onNavigate("home");
|
|
2056
|
+
} else if (input === "b" && options.onNavigate) {
|
|
2057
|
+
options.onNavigate("browse");
|
|
2058
|
+
} else if (input === "l" && options.onNavigate) {
|
|
2059
|
+
options.onNavigate("installed");
|
|
2060
|
+
} else if (input === "s" && options.onNavigate) {
|
|
2061
|
+
options.onNavigate("sync");
|
|
2062
|
+
} else if (input === "," && options.onNavigate) {
|
|
2063
|
+
options.onNavigate("settings");
|
|
2064
|
+
}
|
|
2065
|
+
});
|
|
2066
|
+
}
|
|
2067
|
+
function useListNavigation(listLength, initialIndex = 0) {
|
|
2068
|
+
const [selectedIndex, setSelectedIndex] = useState13(initialIndex);
|
|
2069
|
+
useEffect8(() => {
|
|
2070
|
+
if (selectedIndex >= listLength && listLength > 0) {
|
|
2071
|
+
setSelectedIndex(listLength - 1);
|
|
2072
|
+
}
|
|
2073
|
+
}, [listLength, selectedIndex]);
|
|
2074
|
+
const moveUp = useCallback3(() => {
|
|
2075
|
+
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
|
2076
|
+
}, []);
|
|
2077
|
+
const moveDown = useCallback3(() => {
|
|
2078
|
+
setSelectedIndex((prev) => Math.min(listLength - 1, prev + 1));
|
|
2079
|
+
}, [listLength]);
|
|
2080
|
+
const reset = useCallback3(() => {
|
|
2081
|
+
setSelectedIndex(0);
|
|
2082
|
+
}, []);
|
|
2083
|
+
return { selectedIndex, setSelectedIndex, moveUp, moveDown, reset };
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
// src/index.tsx
|
|
2087
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
2088
|
+
function startTUI() {
|
|
2089
|
+
process.stdout.write("\x1B[2J\x1B[0f");
|
|
2090
|
+
const { waitUntilExit, clear } = render(/* @__PURE__ */ jsx15(App, {}), {
|
|
2091
|
+
exitOnCtrlC: true
|
|
2092
|
+
});
|
|
2093
|
+
return waitUntilExit().then(() => {
|
|
2094
|
+
clear();
|
|
2095
|
+
});
|
|
2096
|
+
}
|
|
2097
|
+
export {
|
|
2098
|
+
App,
|
|
2099
|
+
Browse,
|
|
2100
|
+
Context,
|
|
2101
|
+
Header,
|
|
2102
|
+
Home,
|
|
2103
|
+
Installed,
|
|
2104
|
+
Recommend,
|
|
2105
|
+
SearchInput,
|
|
2106
|
+
Settings,
|
|
2107
|
+
Sidebar,
|
|
2108
|
+
SkillList,
|
|
2109
|
+
StatusBar,
|
|
2110
|
+
Sync,
|
|
2111
|
+
Translate,
|
|
2112
|
+
colors,
|
|
2113
|
+
logo,
|
|
2114
|
+
startTUI,
|
|
2115
|
+
symbols,
|
|
2116
|
+
useKeyboard,
|
|
2117
|
+
useListNavigation,
|
|
2118
|
+
useMarketplace,
|
|
2119
|
+
useRecommend,
|
|
2120
|
+
useSkills
|
|
2121
|
+
};
|
|
2122
|
+
//# sourceMappingURL=index.js.map
|