@dealdeploy/skl 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,18 @@
1
+ name: Publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: oven-sh/setup-bun@v2
14
+ - run: bun install
15
+ - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc
16
+ env:
17
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
18
+ - run: npm publish --access public
package/add.ts ADDED
@@ -0,0 +1,411 @@
1
+ import { existsSync, lstatSync, mkdirSync, writeFileSync, unlinkSync } from "fs";
2
+ import { join } from "path";
3
+ import { homedir } from "os";
4
+ import {
5
+ createCliRenderer,
6
+ BoxRenderable,
7
+ TextRenderable,
8
+ ScrollBoxRenderable,
9
+ TextAttributes,
10
+ type KeyEvent,
11
+ } from "@opentui/core";
12
+
13
+ const LIBRARY = join(homedir(), "dotfiles/skills");
14
+
15
+ let repo = process.argv[3];
16
+ if (repo) {
17
+ // Accept GitHub URLs: https://github.com/owner/repo[/...]
18
+ const urlMatch = repo.match(/github\.com\/([^/]+\/[^/]+)/);
19
+ if (urlMatch) repo = urlMatch[1].replace(/\.git$/, "");
20
+ }
21
+ if (!repo || !/^[^/]+\/[^/]+$/.test(repo)) {
22
+ console.error("Usage: skl add owner/repo");
23
+ process.exit(1);
24
+ }
25
+
26
+ // ── Helpers ──────────────────────────────────────────────────────────
27
+
28
+ async function gh(args: string): Promise<string> {
29
+ const proc = Bun.spawn(["gh", "api", ...args.split(" ")], {
30
+ stdout: "pipe",
31
+ stderr: "pipe",
32
+ });
33
+ const out = await new Response(proc.stdout).text();
34
+ const code = await proc.exited;
35
+ if (code !== 0) {
36
+ const err = await new Response(proc.stderr).text();
37
+ throw new Error(`gh api ${args} failed: ${err.trim()}`);
38
+ }
39
+ return out.trim();
40
+ }
41
+
42
+ function skillExists(name: string): boolean {
43
+ const p = join(LIBRARY, name);
44
+ try {
45
+ const stat = lstatSync(p);
46
+ if (stat.isDirectory()) return true;
47
+ return false;
48
+ } catch {
49
+ return false;
50
+ }
51
+ }
52
+
53
+ // ── Colors (matching index.ts) ───────────────────────────────────────
54
+
55
+ const C = {
56
+ bg: "#1a1a2e",
57
+ rowBg: "#1a1a2e",
58
+ rowAltBg: "#1f1f38",
59
+ cursorBg: "#2a2a5a",
60
+ accentBg: "#3a3a7a",
61
+ border: "#444477",
62
+ fg: "#ccccdd",
63
+ fgDim: "#666688",
64
+ checked: "#66ff88",
65
+ unchecked: "#555566",
66
+ warning: "#ffaa44",
67
+ accent: "#8888ff",
68
+ title: "#aaaaff",
69
+ footer: "#888899",
70
+ statusOk: "#66ff88",
71
+ statusErr: "#ff6666",
72
+ };
73
+
74
+ // ── Fetch phase (plain stdout) ───────────────────────────────────────
75
+
76
+ console.log(`Fetching ${repo}...`);
77
+
78
+ let branch: string;
79
+ try {
80
+ const repoJson = JSON.parse(await gh(`repos/${repo}`));
81
+ branch = repoJson.default_branch;
82
+ } catch (e: any) {
83
+ console.error(`Failed to access ${repo}: ${e.message}`);
84
+ process.exit(1);
85
+ }
86
+
87
+ const treeJson = JSON.parse(
88
+ await gh(`repos/${repo}/git/trees/${branch}?recursive=1`)
89
+ );
90
+ const tree: { path: string; type: string }[] = treeJson.tree;
91
+
92
+ // Find SKILL.md at any depth (supports both flat "name/SKILL.md" and nested "skill/name/SKILL.md")
93
+ const skillEntries = tree
94
+ .filter((e) => e.type === "blob" && e.path.endsWith("/SKILL.md"))
95
+ .map((e) => {
96
+ const prefix = e.path.replace(/\/SKILL\.md$/, ""); // e.g. "skill/opentui" or "opentui"
97
+ const name = prefix.split("/").pop()!; // last segment is the skill name
98
+ return { prefix, name };
99
+ })
100
+ .sort((a, b) => a.name.localeCompare(b.name));
101
+
102
+ if (skillEntries.length === 0) {
103
+ console.error(`No skills found in ${repo} (no directories with SKILL.md)`);
104
+ process.exit(1);
105
+ }
106
+
107
+ // ── Classify skills ──────────────────────────────────────────────────
108
+
109
+ type SkillEntry = { name: string; prefix: string; exists: boolean };
110
+ const skills: SkillEntry[] = skillEntries.map(({ name, prefix }) => ({
111
+ name,
112
+ prefix,
113
+ exists: skillExists(name),
114
+ }));
115
+
116
+ const addableCount = skills.filter((s) => !s.exists).length;
117
+ if (addableCount === 0) {
118
+ console.log("All skills already exist in your library.");
119
+ process.exit(0);
120
+ }
121
+
122
+ // ── State ────────────────────────────────────────────────────────────
123
+
124
+ let cursor = 0;
125
+ const checked = new Set<number>();
126
+ let statusTimeout: ReturnType<typeof setTimeout> | null = null;
127
+ let exitResolve: () => void;
128
+
129
+ // Start cursor on first addable skill
130
+ for (let i = 0; i < skills.length; i++) {
131
+ if (!skills[i].exists) { cursor = i; break; }
132
+ }
133
+
134
+ // ── Build TUI ────────────────────────────────────────────────────────
135
+
136
+ const renderer = await createCliRenderer({ exitOnCtrlC: true });
137
+
138
+ const outer = new BoxRenderable(renderer, {
139
+ id: "outer",
140
+ width: "100%",
141
+ height: "100%",
142
+ flexDirection: "column",
143
+ backgroundColor: C.bg,
144
+ });
145
+
146
+ const header = new TextRenderable(renderer, {
147
+ id: "header",
148
+ content: ` Add skills from ${repo}`,
149
+ fg: C.title,
150
+ attributes: TextAttributes.BOLD,
151
+ height: 1,
152
+ });
153
+
154
+ const sep = new TextRenderable(renderer, {
155
+ id: "sep",
156
+ content: "─".repeat(60),
157
+ fg: C.border,
158
+ height: 1,
159
+ });
160
+
161
+ const scrollBox = new ScrollBoxRenderable(renderer, {
162
+ id: "skill-list",
163
+ flexGrow: 1,
164
+ width: "100%",
165
+ });
166
+
167
+ type RowRefs = {
168
+ row: BoxRenderable;
169
+ checkText: TextRenderable;
170
+ nameText: TextRenderable;
171
+ };
172
+ const rows: RowRefs[] = [];
173
+
174
+ for (let i = 0; i < skills.length; i++) {
175
+ const skill = skills[i];
176
+
177
+ const row = new BoxRenderable(renderer, {
178
+ id: `row-${i}`,
179
+ flexDirection: "row",
180
+ height: 1,
181
+ width: "100%",
182
+ paddingLeft: 1,
183
+ backgroundColor: i % 2 === 0 ? C.rowBg : C.rowAltBg,
184
+ });
185
+
186
+ const checkText = new TextRenderable(renderer, {
187
+ id: `check-${i}`,
188
+ content: skill.exists ? "[*]" : "[ ]",
189
+ fg: skill.exists ? C.fgDim : C.unchecked,
190
+ width: 4,
191
+ });
192
+
193
+ const label = skill.exists ? `${skill.name} (exists)` : skill.name;
194
+ const nameText = new TextRenderable(renderer, {
195
+ id: `name-${i}`,
196
+ content: ` ${label}`,
197
+ fg: skill.exists ? C.fgDim : C.fg,
198
+ width: 40,
199
+ });
200
+
201
+ row.add(checkText);
202
+ row.add(nameText);
203
+ scrollBox.add(row);
204
+ rows.push({ row, checkText, nameText });
205
+ }
206
+
207
+ const footerSep = new TextRenderable(renderer, {
208
+ id: "footer-sep",
209
+ content: "─".repeat(60),
210
+ fg: C.border,
211
+ height: 1,
212
+ });
213
+
214
+ const footer = new TextRenderable(renderer, {
215
+ id: "footer",
216
+ content: " j/k move space toggle a all enter confirm q cancel",
217
+ fg: C.footer,
218
+ height: 1,
219
+ });
220
+
221
+ const statusLine = new TextRenderable(renderer, {
222
+ id: "status",
223
+ content: "",
224
+ fg: C.statusOk,
225
+ height: 1,
226
+ });
227
+
228
+ outer.add(header);
229
+ outer.add(sep);
230
+ outer.add(scrollBox);
231
+ outer.add(footerSep);
232
+ outer.add(footer);
233
+ outer.add(statusLine);
234
+ renderer.root.add(outer);
235
+
236
+ // ── Display helpers ──────────────────────────────────────────────────
237
+
238
+ function updateRow(i: number) {
239
+ const skill = skills[i];
240
+ const r = rows[i];
241
+ const isCursor = cursor === i;
242
+
243
+ const baseBg = i % 2 === 0 ? C.rowBg : C.rowAltBg;
244
+ r.row.backgroundColor = isCursor ? C.cursorBg : baseBg;
245
+
246
+ if (skill.exists) {
247
+ r.checkText.content = "[*]";
248
+ r.checkText.fg = C.fgDim;
249
+ const pointer = isCursor ? "▸" : " ";
250
+ r.nameText.content = `${pointer} ${skill.name} (exists)`;
251
+ r.nameText.fg = C.fgDim;
252
+ r.nameText.attributes = isCursor ? TextAttributes.BOLD : TextAttributes.NONE;
253
+ } else {
254
+ const isChecked = checked.has(i);
255
+ r.checkText.content = isChecked ? "[x]" : "[ ]";
256
+ r.checkText.fg = isCursor ? C.accent : (isChecked ? C.checked : C.unchecked);
257
+ const pointer = isCursor ? "▸" : " ";
258
+ r.nameText.content = `${pointer} ${skill.name}`;
259
+ r.nameText.fg = isCursor ? "#ffffff" : C.fg;
260
+ r.nameText.attributes = isCursor ? TextAttributes.BOLD : TextAttributes.NONE;
261
+ }
262
+ }
263
+
264
+ function setStatus(msg: string, color: string) {
265
+ statusLine.content = ` ${msg}`;
266
+ statusLine.fg = color;
267
+ if (statusTimeout) clearTimeout(statusTimeout);
268
+ statusTimeout = setTimeout(() => {
269
+ statusLine.content = "";
270
+ }, 3000);
271
+ }
272
+
273
+ function selectedCount(): number {
274
+ return checked.size;
275
+ }
276
+
277
+ function updateHeader() {
278
+ header.content = ` Add skills from ${repo} (${selectedCount()} selected)`;
279
+ }
280
+
281
+ function refreshAll() {
282
+ for (let i = 0; i < skills.length; i++) updateRow(i);
283
+ updateHeader();
284
+ }
285
+
286
+ function ensureVisible() {
287
+ scrollBox.scrollTo(Math.max(0, cursor - 2));
288
+ }
289
+
290
+ refreshAll();
291
+
292
+ // ── Confirm & download ───────────────────────────────────────────────
293
+
294
+ async function confirmAndDownload() {
295
+ const selected = [...checked].map((i) => skills[i].name);
296
+ if (selected.length === 0) {
297
+ setStatus("Nothing selected — use space to toggle", C.warning);
298
+ return;
299
+ }
300
+
301
+ renderer.destroy();
302
+
303
+ // Download phase — plain stdout like before
304
+ const baseUrl = `https://raw.githubusercontent.com/${repo}/${branch}`;
305
+
306
+ for (const skillName of selected) {
307
+ const skill = skills.find((s) => s.name === skillName)!;
308
+ const skillDir = join(LIBRARY, skillName);
309
+
310
+ // Remove broken symlink if present
311
+ try {
312
+ if (lstatSync(skillDir).isSymbolicLink()) {
313
+ unlinkSync(skillDir);
314
+ }
315
+ } catch {}
316
+
317
+ mkdirSync(skillDir, { recursive: true });
318
+
319
+ const prefix = skill.prefix + "/";
320
+ const files = tree
321
+ .filter((e) => e.type === "blob" && e.path.startsWith(prefix))
322
+ .map((e) => e.path);
323
+
324
+ process.stdout.write(` ${skillName} (${files.length} files)...`);
325
+
326
+ for (const filePath of files) {
327
+ const url = `${baseUrl}/${filePath}`;
328
+ const resp = await fetch(url);
329
+ if (!resp.ok) {
330
+ console.error(`\n Failed to fetch ${filePath}: ${resp.status}`);
331
+ continue;
332
+ }
333
+ const content = await resp.arrayBuffer();
334
+ // Remap: strip the repo prefix and save under the skill name
335
+ const relativePath = filePath.substring(prefix.length);
336
+ const dest = join(LIBRARY, skillName, relativePath);
337
+ const dir = dest.substring(0, dest.lastIndexOf("/"));
338
+ mkdirSync(dir, { recursive: true });
339
+ writeFileSync(dest, Buffer.from(content));
340
+ }
341
+ console.log(" done");
342
+ }
343
+
344
+ console.log(`\nAdded ${selected.length} skill(s) to ~/dotfiles/skills/`);
345
+ exitResolve();
346
+ }
347
+
348
+ // ── Key handler ──────────────────────────────────────────────────────
349
+
350
+ renderer.keyInput.on("keypress", (key: KeyEvent) => {
351
+ const prevCursor = cursor;
352
+
353
+ switch (key.name) {
354
+ case "j":
355
+ case "down":
356
+ if (cursor < skills.length - 1) cursor++;
357
+ break;
358
+ case "k":
359
+ case "up":
360
+ if (cursor > 0) cursor--;
361
+ break;
362
+ case "space": {
363
+ if (skills[cursor].exists) {
364
+ setStatus(`${skills[cursor].name} already exists`, C.warning);
365
+ break;
366
+ }
367
+ if (checked.has(cursor)) {
368
+ checked.delete(cursor);
369
+ } else {
370
+ checked.add(cursor);
371
+ }
372
+ break;
373
+ }
374
+ case "a": {
375
+ // Toggle all addable skills
376
+ const addable = skills
377
+ .map((s, i) => (!s.exists ? i : -1))
378
+ .filter((i) => i >= 0);
379
+ const allChecked = addable.every((i) => checked.has(i));
380
+ if (allChecked) {
381
+ for (const i of addable) checked.delete(i);
382
+ } else {
383
+ for (const i of addable) checked.add(i);
384
+ }
385
+ for (const i of addable) updateRow(i);
386
+ updateHeader();
387
+ ensureVisible();
388
+ return;
389
+ }
390
+ case "return":
391
+ confirmAndDownload();
392
+ return;
393
+ case "q":
394
+ case "escape":
395
+ renderer.destroy();
396
+ exitResolve();
397
+ return;
398
+ default:
399
+ return;
400
+ }
401
+
402
+ if (prevCursor !== cursor) updateRow(prevCursor);
403
+ updateRow(cursor);
404
+ updateHeader();
405
+ ensureVisible();
406
+ });
407
+
408
+ // Block module from resolving until TUI exits
409
+ await new Promise<void>((resolve) => {
410
+ exitResolve = resolve;
411
+ });
package/bun.lock ADDED
@@ -0,0 +1,212 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "workspaces": {
4
+ "": {
5
+ "name": "skills-manager",
6
+ "dependencies": {
7
+ "@opentui/core": "^0.1.80",
8
+ },
9
+ "devDependencies": {
10
+ "@types/bun": "latest",
11
+ },
12
+ "peerDependencies": {
13
+ "typescript": "^5",
14
+ },
15
+ },
16
+ },
17
+ "packages": {
18
+ "@dimforge/rapier2d-simd-compat": ["@dimforge/rapier2d-simd-compat@0.17.3", "", {}, "sha512-bijvwWz6NHsNj5e5i1vtd3dU2pDhthSaTUZSh14DUGGKJfw8eMnlWZsxwHBxB/a3AXVNDjL9abuHw1k9FGR+jg=="],
19
+
20
+ "@jimp/core": ["@jimp/core@1.6.0", "", { "dependencies": { "@jimp/file-ops": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "await-to-js": "^3.0.0", "exif-parser": "^0.1.12", "file-type": "^16.0.0", "mime": "3" } }, "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w=="],
21
+
22
+ "@jimp/diff": ["@jimp/diff@1.6.0", "", { "dependencies": { "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "pixelmatch": "^5.3.0" } }, "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw=="],
23
+
24
+ "@jimp/file-ops": ["@jimp/file-ops@1.6.0", "", {}, "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ=="],
25
+
26
+ "@jimp/js-bmp": ["@jimp/js-bmp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "bmp-ts": "^1.0.9" } }, "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw=="],
27
+
28
+ "@jimp/js-gif": ["@jimp/js-gif@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "gifwrap": "^0.10.1", "omggif": "^1.0.10" } }, "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g=="],
29
+
30
+ "@jimp/js-jpeg": ["@jimp/js-jpeg@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "jpeg-js": "^0.4.4" } }, "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA=="],
31
+
32
+ "@jimp/js-png": ["@jimp/js-png@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "pngjs": "^7.0.0" } }, "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg=="],
33
+
34
+ "@jimp/js-tiff": ["@jimp/js-tiff@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "utif2": "^4.1.0" } }, "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw=="],
35
+
36
+ "@jimp/plugin-blit": ["@jimp/plugin-blit@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA=="],
37
+
38
+ "@jimp/plugin-blur": ["@jimp/plugin-blur@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw=="],
39
+
40
+ "@jimp/plugin-circle": ["@jimp/plugin-circle@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw=="],
41
+
42
+ "@jimp/plugin-color": ["@jimp/plugin-color@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "tinycolor2": "^1.6.0", "zod": "^3.23.8" } }, "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA=="],
43
+
44
+ "@jimp/plugin-contain": ["@jimp/plugin-contain@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ=="],
45
+
46
+ "@jimp/plugin-cover": ["@jimp/plugin-cover@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA=="],
47
+
48
+ "@jimp/plugin-crop": ["@jimp/plugin-crop@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang=="],
49
+
50
+ "@jimp/plugin-displace": ["@jimp/plugin-displace@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q=="],
51
+
52
+ "@jimp/plugin-dither": ["@jimp/plugin-dither@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0" } }, "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ=="],
53
+
54
+ "@jimp/plugin-fisheye": ["@jimp/plugin-fisheye@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA=="],
55
+
56
+ "@jimp/plugin-flip": ["@jimp/plugin-flip@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg=="],
57
+
58
+ "@jimp/plugin-hash": ["@jimp/plugin-hash@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "any-base": "^1.1.0" } }, "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q=="],
59
+
60
+ "@jimp/plugin-mask": ["@jimp/plugin-mask@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA=="],
61
+
62
+ "@jimp/plugin-print": ["@jimp/plugin-print@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/types": "1.6.0", "parse-bmfont-ascii": "^1.0.6", "parse-bmfont-binary": "^1.0.6", "parse-bmfont-xml": "^1.1.6", "simple-xml-to-json": "^1.2.2", "zod": "^3.23.8" } }, "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A=="],
63
+
64
+ "@jimp/plugin-quantize": ["@jimp/plugin-quantize@1.6.0", "", { "dependencies": { "image-q": "^4.0.0", "zod": "^3.23.8" } }, "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg=="],
65
+
66
+ "@jimp/plugin-resize": ["@jimp/plugin-resize@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA=="],
67
+
68
+ "@jimp/plugin-rotate": ["@jimp/plugin-rotate@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw=="],
69
+
70
+ "@jimp/plugin-threshold": ["@jimp/plugin-threshold@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w=="],
71
+
72
+ "@jimp/types": ["@jimp/types@1.6.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg=="],
73
+
74
+ "@jimp/utils": ["@jimp/utils@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "tinycolor2": "^1.6.0" } }, "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA=="],
75
+
76
+ "@opentui/core": ["@opentui/core@0.1.80", "", { "dependencies": { "bun-ffi-structs": "0.1.2", "diff": "8.0.2", "jimp": "1.6.0", "marked": "17.0.1", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.80", "@opentui/core-darwin-x64": "0.1.80", "@opentui/core-linux-arm64": "0.1.80", "@opentui/core-linux-x64": "0.1.80", "@opentui/core-win32-arm64": "0.1.80", "@opentui/core-win32-x64": "0.1.80", "bun-webgpu": "0.1.5", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-fkgk8XiO1pauTrWzh3uRL4JNYGPINegv/JoTa1POygV9Rdwu0i4KseicwtDtvwEp6q7p+OdspmNqi6/DqPYqEQ=="],
77
+
78
+ "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.80", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B1KXimgNzwXY9KS/fZqAnUOj1TSmMmfmm6BJKZwXM65S2hYyuHeZ/E7qN6e4Wqr3dVhzMxH06cR92qf30Qs2QA=="],
79
+
80
+ "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.80", "", { "os": "darwin", "cpu": "x64" }, "sha512-gthbIutmy4VmeOiMacUVTVkB0N/Uc4bLU1Yy2hM23RoJBmfiMgDuf+tpCTYKaJpg4UmWHGP4Q11CNSGCNeNW4g=="],
81
+
82
+ "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.80", "", { "os": "linux", "cpu": "arm64" }, "sha512-tfGq8BDF144CKKAZP97kXmZYBOKCz2Q1XDTpzz6doxHUy5/4pLc0TnJEOZj/Vhzq37wBoG6TGRrCXgHvDAps1Q=="],
83
+
84
+ "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.80", "", { "os": "linux", "cpu": "x64" }, "sha512-IrlHbzSUy/p6GWelToOkk/PG5qh7haLL+dr/vL83pdSGfh+eJIBHta+EjycvBGr5SxVzCVJFmXUi4QZ47S+o2w=="],
85
+
86
+ "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.80", "", { "os": "win32", "cpu": "arm64" }, "sha512-ZgXAClJSc7GgaTftWcmyO5psMYsO+S8XHnxwGm0rXhs/bAz52kH46wKO8wfPAfqUsIYpe1UGgaAejv104w7yEQ=="],
87
+
88
+ "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.80", "", { "os": "win32", "cpu": "x64" }, "sha512-8qGyKHfiU/VOYhfER7YWuIQDb2XBfpiyMkUjdwXYPYIPJ0A0vcAulg9CPNrRb+hzzAthH5nmX8gnu8yPsE/n4w=="],
89
+
90
+ "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
91
+
92
+ "@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
93
+
94
+ "@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="],
95
+
96
+ "@webgpu/types": ["@webgpu/types@0.1.69", "", {}, "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ=="],
97
+
98
+ "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
99
+
100
+ "any-base": ["any-base@1.1.0", "", {}, "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="],
101
+
102
+ "await-to-js": ["await-to-js@3.0.0", "", {}, "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g=="],
103
+
104
+ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
105
+
106
+ "bmp-ts": ["bmp-ts@1.0.9", "", {}, "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw=="],
107
+
108
+ "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
109
+
110
+ "bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="],
111
+
112
+ "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
113
+
114
+ "bun-webgpu": ["bun-webgpu@0.1.5", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.5", "bun-webgpu-darwin-x64": "^0.1.5", "bun-webgpu-linux-x64": "^0.1.5", "bun-webgpu-win32-x64": "^0.1.5" } }, "sha512-91/K6S5whZKX7CWAm9AylhyKrLGRz6BUiiPiM/kXadSnD4rffljCD/q9cNFftm5YXhx4MvLqw33yEilxogJvwA=="],
115
+
116
+ "bun-webgpu-darwin-arm64": ["bun-webgpu-darwin-arm64@0.1.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-qM7W5IaFpWYGPDcNiQ8DOng3noQ97gxpH2MFH1mGsdKwI0T4oy++egSh5Z7s6AQx8WKgc9GzAsTUM4KZkFdacw=="],
117
+
118
+ "bun-webgpu-darwin-x64": ["bun-webgpu-darwin-x64@0.1.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-oVoIsme27pcXB68YxnQSAgdNGCa4A3PGWYIBUewOh9VnJaoik4JenGb5Yy+svGE+ETFhQXV9nhHqgMPsDRrO6A=="],
119
+
120
+ "bun-webgpu-linux-x64": ["bun-webgpu-linux-x64@0.1.5", "", { "os": "linux", "cpu": "x64" }, "sha512-+SYt09k+xDEl/GfcU7L1zdNgm7IlvAFKV5Xl/auBwuprKG5UwXNhjRlRAWfhTMCUZWN+NDf8E+ZQx0cQi9K2/g=="],
121
+
122
+ "bun-webgpu-win32-x64": ["bun-webgpu-win32-x64@0.1.5", "", { "os": "win32", "cpu": "x64" }, "sha512-zvnUl4EAsQbKsmZVu+lEJcH8axQ7MiCfqg2OmnHd6uw1THABmHaX0GbpKiHshdgadNN2Nf+4zDyTJB5YMcAdrA=="],
123
+
124
+ "diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="],
125
+
126
+ "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
127
+
128
+ "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
129
+
130
+ "exif-parser": ["exif-parser@0.1.12", "", {}, "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="],
131
+
132
+ "file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
133
+
134
+ "gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="],
135
+
136
+ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
137
+
138
+ "image-q": ["image-q@4.0.0", "", { "dependencies": { "@types/node": "16.9.1" } }, "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw=="],
139
+
140
+ "jimp": ["jimp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/diff": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-gif": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-blur": "1.6.0", "@jimp/plugin-circle": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-contain": "1.6.0", "@jimp/plugin-cover": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-displace": "1.6.0", "@jimp/plugin-dither": "1.6.0", "@jimp/plugin-fisheye": "1.6.0", "@jimp/plugin-flip": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/plugin-mask": "1.6.0", "@jimp/plugin-print": "1.6.0", "@jimp/plugin-quantize": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/plugin-rotate": "1.6.0", "@jimp/plugin-threshold": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg=="],
141
+
142
+ "jpeg-js": ["jpeg-js@0.4.4", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="],
143
+
144
+ "marked": ["marked@17.0.1", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg=="],
145
+
146
+ "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
147
+
148
+ "omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="],
149
+
150
+ "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
151
+
152
+ "parse-bmfont-ascii": ["parse-bmfont-ascii@1.0.6", "", {}, "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA=="],
153
+
154
+ "parse-bmfont-binary": ["parse-bmfont-binary@1.0.6", "", {}, "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA=="],
155
+
156
+ "parse-bmfont-xml": ["parse-bmfont-xml@1.1.6", "", { "dependencies": { "xml-parse-from-string": "^1.0.0", "xml2js": "^0.5.0" } }, "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA=="],
157
+
158
+ "peek-readable": ["peek-readable@4.1.0", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="],
159
+
160
+ "pixelmatch": ["pixelmatch@5.3.0", "", { "dependencies": { "pngjs": "^6.0.0" }, "bin": { "pixelmatch": "bin/pixelmatch" } }, "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q=="],
161
+
162
+ "planck": ["planck@1.4.3", "", { "peerDependencies": { "stage-js": "^1.0.0-alpha.12" } }, "sha512-B+lHKhRSeg7vZOfEyEzyQVu7nx8JHcX3QgnAcHXrPW0j04XYKX5eXSiUrxH2Z5QR8OoqvjD6zKIaPMdMYAd0uA=="],
163
+
164
+ "pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="],
165
+
166
+ "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
167
+
168
+ "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
169
+
170
+ "readable-web-to-node-stream": ["readable-web-to-node-stream@3.0.4", "", { "dependencies": { "readable-stream": "^4.7.0" } }, "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw=="],
171
+
172
+ "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
173
+
174
+ "sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="],
175
+
176
+ "simple-xml-to-json": ["simple-xml-to-json@1.2.3", "", {}, "sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA=="],
177
+
178
+ "stage-js": ["stage-js@1.0.1", "", {}, "sha512-cz14aPp/wY0s3bkb/B93BPP5ZAEhgBbRmAT3CCDqert8eCAqIpQ0RB2zpK8Ksxf+Pisl5oTzvPHtL4CVzzeHcw=="],
179
+
180
+ "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
181
+
182
+ "strtok3": ["strtok3@6.3.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" } }, "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw=="],
183
+
184
+ "three": ["three@0.177.0", "", {}, "sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg=="],
185
+
186
+ "tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="],
187
+
188
+ "token-types": ["token-types@4.2.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ=="],
189
+
190
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
191
+
192
+ "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
193
+
194
+ "utif2": ["utif2@4.1.0", "", { "dependencies": { "pako": "^1.0.11" } }, "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w=="],
195
+
196
+ "web-tree-sitter": ["web-tree-sitter@0.25.10", "", { "peerDependencies": { "@types/emscripten": "^1.40.0" }, "optionalPeers": ["@types/emscripten"] }, "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA=="],
197
+
198
+ "xml-parse-from-string": ["xml-parse-from-string@1.0.1", "", {}, "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g=="],
199
+
200
+ "xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="],
201
+
202
+ "xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
203
+
204
+ "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
205
+
206
+ "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
207
+
208
+ "image-q/@types/node": ["@types/node@16.9.1", "", {}, "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g=="],
209
+
210
+ "pixelmatch/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="],
211
+ }
212
+ }
package/index.ts ADDED
@@ -0,0 +1,622 @@
1
+ // ── Subcommand routing ───────────────────────────────────────────────
2
+ const [subcommand] = process.argv.slice(2);
3
+ if (subcommand === "add") {
4
+ await import("./add.ts");
5
+ process.exit(0);
6
+ }
7
+
8
+ import {
9
+ createCliRenderer,
10
+ BoxRenderable,
11
+ TextRenderable,
12
+ ScrollBoxRenderable,
13
+ TextAttributes,
14
+ type KeyEvent,
15
+ } from "@opentui/core";
16
+ import {
17
+ readdirSync,
18
+ lstatSync,
19
+ readlinkSync,
20
+ symlinkSync,
21
+ unlinkSync,
22
+ mkdirSync,
23
+ existsSync,
24
+ rmSync,
25
+ } from "fs";
26
+ import { join, resolve } from "path";
27
+ import { homedir } from "os";
28
+
29
+ // ── Constants & helpers ─────────────────────────────────────────────
30
+
31
+ const LIBRARY = join(homedir(), "dotfiles/skills");
32
+ const GLOBAL_DIR = join(homedir(), ".agents/skills");
33
+
34
+ function ellipsize(text: string, max: number): string {
35
+ if (max <= 0) return "";
36
+ if (text.length <= max) return text;
37
+ if (max === 1) return "…";
38
+ return `${text.slice(0, max - 1)}…`;
39
+ }
40
+
41
+ // Safety: ensure GLOBAL_DIR is a real directory, not a symlink to the library
42
+ try {
43
+ if (lstatSync(GLOBAL_DIR).isSymbolicLink()) {
44
+ console.error(`ERROR: ${GLOBAL_DIR} is a symlink — it must be a real directory.`);
45
+ console.error("Remove it and create a real directory: rm ~/.agents/skills && mkdir ~/.agents/skills");
46
+ process.exit(1);
47
+ }
48
+ } catch {
49
+ // doesn't exist yet, that's fine — toggle will mkdir
50
+ }
51
+
52
+ function isLibraryLink(dir: string, name: string): boolean {
53
+ const full = join(dir, name);
54
+ try {
55
+ return (
56
+ lstatSync(full).isSymbolicLink() &&
57
+ resolve(readlinkSync(full)) === resolve(join(LIBRARY, name))
58
+ );
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+
64
+ function nonLibraryExists(dir: string, name: string): boolean {
65
+ const full = join(dir, name);
66
+ try {
67
+ return existsSync(full) && !isLibraryLink(dir, name);
68
+ } catch {
69
+ return false;
70
+ }
71
+ }
72
+
73
+ function toggle(dir: string, name: string): boolean {
74
+ const full = join(dir, name);
75
+ if (isLibraryLink(dir, name)) {
76
+ unlinkSync(full);
77
+ return true;
78
+ }
79
+ if (existsSync(full)) {
80
+ return false;
81
+ }
82
+ mkdirSync(dir, { recursive: true });
83
+ symlinkSync(join(LIBRARY, name), full);
84
+ return true;
85
+ }
86
+
87
+ function findLocalDir(): string | null {
88
+ const dir = join(process.cwd(), ".agents/skills");
89
+ if (existsSync(dir)) return dir;
90
+ return null;
91
+ }
92
+
93
+ function ensureLocalDir(): string {
94
+ if (localDir) return localDir;
95
+ const dir = join(process.cwd(), ".agents/skills");
96
+ mkdirSync(dir, { recursive: true });
97
+ localDir = dir;
98
+ localLabel = dir.replace(homedir(), "~");
99
+ colLocal.content = ellipsize(localLabel, 20);
100
+ refreshAll();
101
+ return dir;
102
+ }
103
+
104
+ const allSkills = readdirSync(LIBRARY).sort();
105
+
106
+ // ── State ───────────────────────────────────────────────────────────
107
+
108
+ let cursor = 0; // index into filteredIndices
109
+ let cursorCol: "global" | "local" = "global";
110
+ let localDir = findLocalDir();
111
+ let statusTimeout: ReturnType<typeof setTimeout> | null = null;
112
+ let pendingDelete: number | null = null; // allSkills index awaiting confirmation
113
+ const deletedSkills = new Set<number>();
114
+ let searchQuery = "";
115
+ let filteredIndices: number[] = allSkills.map((_, i) => i);
116
+
117
+ // ── Colors ──────────────────────────────────────────────────────────
118
+
119
+ const C = {
120
+ bg: "#1a1a2e",
121
+ rowBg: "#1a1a2e",
122
+ rowAltBg: "#1f1f38",
123
+ cursorBg: "#2a2a5a",
124
+ accentBg: "#3a3a7a",
125
+ border: "#444477",
126
+ fg: "#ccccdd",
127
+ fgDim: "#666688",
128
+ checked: "#66ff88",
129
+ unchecked: "#555566",
130
+ warning: "#ffaa44",
131
+ accent: "#8888ff",
132
+ title: "#aaaaff",
133
+ footer: "#888899",
134
+ statusOk: "#66ff88",
135
+ statusErr: "#ff6666",
136
+ search: "#ffdd55",
137
+ };
138
+
139
+ // ── Checkbox helpers ────────────────────────────────────────────────
140
+
141
+ function checkboxStr(checked: boolean, blocked: boolean): string {
142
+ if (blocked) return "[!]";
143
+ return checked ? "[x]" : "[ ]";
144
+ }
145
+
146
+ function checkboxColor(checked: boolean, blocked: boolean, active: boolean): string {
147
+ if (blocked) return C.warning;
148
+ if (active) return C.accent;
149
+ return checked ? C.checked : C.unchecked;
150
+ }
151
+
152
+ // ── Build TUI ───────────────────────────────────────────────────────
153
+
154
+ const renderer = await createCliRenderer({ exitOnCtrlC: true });
155
+
156
+ const outer = new BoxRenderable(renderer, {
157
+ id: "outer",
158
+ width: "100%",
159
+ height: "100%",
160
+ flexDirection: "column",
161
+ backgroundColor: C.bg,
162
+ });
163
+
164
+ // Search bar (always visible at top, type-to-filter)
165
+ const searchBar = new TextRenderable(renderer, {
166
+ id: "search-bar",
167
+ content: "",
168
+ fg: C.search,
169
+ width: "100%",
170
+ height: 1,
171
+ visible: true,
172
+ });
173
+
174
+ const colHeaderRow = new BoxRenderable(renderer, {
175
+ id: "col-header-row",
176
+ flexDirection: "row",
177
+ height: 1,
178
+ paddingLeft: 1,
179
+ });
180
+
181
+ const colName = new TextRenderable(renderer, {
182
+ id: "col-name",
183
+ content: "Skill",
184
+ fg: C.fgDim,
185
+ attributes: TextAttributes.BOLD,
186
+ width: 34,
187
+ });
188
+ const colGlobal = new TextRenderable(renderer, {
189
+ id: "col-global",
190
+ content: "~/.agents",
191
+ fg: C.fgDim,
192
+ attributes: TextAttributes.BOLD,
193
+ width: 12,
194
+ });
195
+ let localLabel = localDir ? localDir.replace(homedir(), "~") : ".agents (n/a)";
196
+ const localLabelHeader = ellipsize(localLabel, 20);
197
+ const colLocal = new TextRenderable(renderer, {
198
+ id: "col-local",
199
+ content: localLabelHeader,
200
+ fg: C.fgDim,
201
+ attributes: TextAttributes.BOLD,
202
+ width: 20,
203
+ });
204
+
205
+ colHeaderRow.add(colName);
206
+ colHeaderRow.add(colGlobal);
207
+ colHeaderRow.add(colLocal);
208
+
209
+ const sep = new TextRenderable(renderer, {
210
+ id: "sep",
211
+ content: "─".repeat(60),
212
+ fg: C.border,
213
+ height: 1,
214
+ });
215
+
216
+ const scrollBox = new ScrollBoxRenderable(renderer, {
217
+ id: "skill-list",
218
+ flexGrow: 1,
219
+ width: "100%",
220
+ });
221
+
222
+ type RowRefs = {
223
+ row: BoxRenderable;
224
+ nameText: TextRenderable;
225
+ globalText: TextRenderable;
226
+ localText: TextRenderable;
227
+ };
228
+ const rows: RowRefs[] = [];
229
+
230
+ for (let i = 0; i < allSkills.length; i++) {
231
+ const skill = allSkills[i];
232
+
233
+ const row = new BoxRenderable(renderer, {
234
+ id: `row-${i}`,
235
+ flexDirection: "row",
236
+ height: 1,
237
+ width: "100%",
238
+ paddingLeft: 1,
239
+ backgroundColor: i % 2 === 0 ? C.rowBg : C.rowAltBg,
240
+ });
241
+
242
+ const nameText = new TextRenderable(renderer, {
243
+ id: `name-${i}`,
244
+ content: ` ${skill}`,
245
+ fg: C.fg,
246
+ width: 34,
247
+ });
248
+
249
+ const gChecked = isLibraryLink(GLOBAL_DIR, skill);
250
+ const gBlocked = nonLibraryExists(GLOBAL_DIR, skill);
251
+ const globalText = new TextRenderable(renderer, {
252
+ id: `global-${i}`,
253
+ content: checkboxStr(gChecked, gBlocked),
254
+ fg: checkboxColor(gChecked, gBlocked, false),
255
+ width: 10,
256
+ });
257
+
258
+ const lChecked = localDir ? isLibraryLink(localDir, skill) : false;
259
+ const lBlocked = localDir ? nonLibraryExists(localDir, skill) : false;
260
+ const localText = new TextRenderable(renderer, {
261
+ id: `local-${i}`,
262
+ content: localDir ? checkboxStr(lChecked, lBlocked) : " -",
263
+ fg: localDir ? checkboxColor(lChecked, lBlocked, false) : C.fgDim,
264
+ width: 10,
265
+ });
266
+
267
+ row.add(nameText);
268
+ row.add(globalText);
269
+ row.add(localText);
270
+ scrollBox.add(row);
271
+ rows.push({ row, nameText, globalText, localText });
272
+ }
273
+
274
+ const footerSep = new TextRenderable(renderer, {
275
+ id: "footer-sep",
276
+ content: "─".repeat(60),
277
+ fg: C.border,
278
+ height: 1,
279
+ });
280
+
281
+ const footer = new TextRenderable(renderer, {
282
+ id: "footer",
283
+ content: " type to filter ↑↓ move ←→/tab col enter toggle ^a all ^e edit ^d del esc quit",
284
+ fg: C.footer,
285
+ height: 1,
286
+ });
287
+
288
+ const statusLine = new TextRenderable(renderer, {
289
+ id: "status",
290
+ content: "",
291
+ fg: C.statusOk,
292
+ height: 1,
293
+ });
294
+
295
+ outer.add(searchBar);
296
+ outer.add(colHeaderRow);
297
+ outer.add(sep);
298
+ outer.add(scrollBox);
299
+ outer.add(footerSep);
300
+ outer.add(footer);
301
+ outer.add(statusLine);
302
+ renderer.root.add(outer);
303
+
304
+ // ── Search / filter ─────────────────────────────────────────────────
305
+
306
+ function applyFilter() {
307
+ const term = searchQuery.toLowerCase();
308
+ filteredIndices = [];
309
+ for (let i = 0; i < allSkills.length; i++) {
310
+ if (deletedSkills.has(i)) {
311
+ rows[i].row.visible = false;
312
+ continue;
313
+ }
314
+ const match = !term || allSkills[i].toLowerCase().includes(term);
315
+ rows[i].row.visible = match;
316
+ if (match) filteredIndices.push(i);
317
+ }
318
+ // clamp cursor
319
+ if (filteredIndices.length === 0) {
320
+ cursor = 0;
321
+ } else if (cursor >= filteredIndices.length) {
322
+ cursor = filteredIndices.length - 1;
323
+ }
324
+ }
325
+
326
+ function updateSearchBar() {
327
+ searchBar.content = ` / ${searchQuery}█`;
328
+ }
329
+
330
+ // ── Update display ──────────────────────────────────────────────────
331
+
332
+ function currentSkillIndex(): number | null {
333
+ return filteredIndices.length > 0 ? filteredIndices[cursor] : null;
334
+ }
335
+
336
+ function updateRow(i: number) {
337
+ const skill = allSkills[i];
338
+ const r = rows[i];
339
+ const ci = currentSkillIndex();
340
+ const isCursor = ci === i;
341
+
342
+ const visPos = filteredIndices.indexOf(i);
343
+ const baseBg = visPos % 2 === 0 ? C.rowBg : C.rowAltBg;
344
+ r.row.backgroundColor = isCursor ? C.cursorBg : baseBg;
345
+
346
+ const pointer = isCursor ? "▸" : " ";
347
+ r.nameText.content = `${pointer} ${skill}`;
348
+ r.nameText.fg = isCursor ? "#ffffff" : C.fg;
349
+ r.nameText.attributes = isCursor ? TextAttributes.BOLD : TextAttributes.NONE;
350
+
351
+ const gChecked = isLibraryLink(GLOBAL_DIR, skill);
352
+ const gBlocked = nonLibraryExists(GLOBAL_DIR, skill);
353
+ const gActive = isCursor && cursorCol === "global";
354
+ r.globalText.content = checkboxStr(gChecked, gBlocked);
355
+ r.globalText.fg = checkboxColor(gChecked, gBlocked, gActive);
356
+ r.globalText.bg = gActive ? C.accentBg : undefined;
357
+ r.globalText.attributes = gActive ? TextAttributes.BOLD : TextAttributes.NONE;
358
+
359
+ const lChecked = localDir ? isLibraryLink(localDir, skill) : false;
360
+ const lBlocked = localDir ? nonLibraryExists(localDir, skill) : false;
361
+ const lActive = isCursor && cursorCol === "local";
362
+ if (localDir) {
363
+ r.localText.content = checkboxStr(lChecked, lBlocked);
364
+ r.localText.fg = checkboxColor(lChecked, lBlocked, lActive);
365
+ r.localText.bg = lActive ? C.accentBg : undefined;
366
+ r.localText.attributes = lActive ? TextAttributes.BOLD : TextAttributes.NONE;
367
+ } else {
368
+ r.localText.content = " -";
369
+ r.localText.fg = C.fgDim;
370
+ r.localText.bg = lActive ? C.accentBg : undefined;
371
+ }
372
+ }
373
+
374
+ function setStatus(msg: string, color: string) {
375
+ statusLine.content = ` ${msg}`;
376
+ statusLine.fg = color;
377
+ if (statusTimeout) clearTimeout(statusTimeout);
378
+ statusTimeout = setTimeout(() => {
379
+ statusLine.content = "";
380
+ }, 3000);
381
+ }
382
+
383
+ function refreshAll() {
384
+ for (const i of filteredIndices) updateRow(i);
385
+ updateSearchBar();
386
+ }
387
+
388
+ refreshAll();
389
+
390
+ // ── Scrolling helper ────────────────────────────────────────────────
391
+
392
+ function ensureVisible() {
393
+ scrollBox.scrollTo(Math.max(0, cursor - 2));
394
+ }
395
+
396
+ // ── Toggle all in column ────────────────────────────────────────────
397
+
398
+ function toggleAllColumn(col: "global" | "local") {
399
+ const dir = col === "global" ? GLOBAL_DIR : ensureLocalDir();
400
+
401
+ // Determine intent: if majority of visible skills are linked, unlink all; otherwise link all
402
+ let linkedCount = 0;
403
+ for (const i of filteredIndices) {
404
+ if (isLibraryLink(dir, allSkills[i])) linkedCount++;
405
+ }
406
+ const shouldLink = linkedCount <= filteredIndices.length / 2;
407
+ let changed = 0;
408
+ let skipped = 0;
409
+
410
+ for (const i of filteredIndices) {
411
+ const skill = allSkills[i];
412
+ const isLinked = isLibraryLink(dir, skill);
413
+ if (shouldLink && !isLinked) {
414
+ if (toggle(dir, skill)) changed++;
415
+ else skipped++;
416
+ } else if (!shouldLink && isLinked) {
417
+ if (toggle(dir, skill)) changed++;
418
+ }
419
+ }
420
+
421
+ const dirLabel = col === "global" ? "~/.agents" : localLabel;
422
+ const action = shouldLink ? "linked" : "unlinked";
423
+ let msg = `${action} ${changed} skills in ${dirLabel}`;
424
+ if (skipped) msg += ` (${skipped} skipped)`;
425
+ setStatus(msg, shouldLink ? C.statusOk : C.statusErr);
426
+ }
427
+
428
+ // ── Edit skill ──────────────────────────────────────────────────
429
+
430
+ async function editSkill(idx: number) {
431
+ const skill = allSkills[idx];
432
+ const skillPath = join(LIBRARY, skill);
433
+ renderer.destroy();
434
+ const editor = process.env.EDITOR || "nvim";
435
+ const proc = Bun.spawn([editor, skillPath], {
436
+ stdio: ["inherit", "inherit", "inherit"],
437
+ });
438
+ await proc.exited;
439
+ // Re-exec so the TUI picks up any changes
440
+ const self = Bun.spawn(["bun", ...process.argv.slice(1)], {
441
+ stdio: ["inherit", "inherit", "inherit"],
442
+ });
443
+ await self.exited;
444
+ process.exit(0);
445
+ }
446
+
447
+ // ── Delete skill ────────────────────────────────────────────────
448
+
449
+ function cancelPendingDelete() {
450
+ if (pendingDelete !== null) {
451
+ pendingDelete = null;
452
+ statusLine.content = "";
453
+ }
454
+ }
455
+
456
+ function deleteSkill(idx: number) {
457
+ const skill = allSkills[idx];
458
+
459
+ // Remove symlinks first
460
+ if (isLibraryLink(GLOBAL_DIR, skill)) unlinkSync(join(GLOBAL_DIR, skill));
461
+ if (localDir && isLibraryLink(localDir, skill)) unlinkSync(join(localDir, skill));
462
+
463
+ // Remove from library
464
+ rmSync(join(LIBRARY, skill), { recursive: true, force: true });
465
+
466
+ // Mark deleted and hide
467
+ deletedSkills.add(idx);
468
+ rows[idx].row.visible = false;
469
+ pendingDelete = null;
470
+
471
+ applyFilter();
472
+ setStatus(`🗑 ${skill} deleted`, C.statusErr);
473
+ refreshAll();
474
+ ensureVisible();
475
+ }
476
+
477
+ // ── Key handler ─────────────────────────────────────────────────────
478
+
479
+ renderer.keyInput.on("keypress", (key: KeyEvent) => {
480
+ // ── Delete confirmation mode ──
481
+ if (pendingDelete !== null) {
482
+ if (key.name === "y") {
483
+ deleteSkill(pendingDelete);
484
+ } else {
485
+ cancelPendingDelete();
486
+ setStatus("delete cancelled", C.fgDim);
487
+ }
488
+ return;
489
+ }
490
+
491
+ const prevIdx = currentSkillIndex();
492
+
493
+ // ── Escape: clear search or quit ──
494
+ if (key.name === "escape") {
495
+ if (searchQuery) {
496
+ searchQuery = "";
497
+ applyFilter();
498
+ refreshAll();
499
+ ensureVisible();
500
+ } else {
501
+ renderer.destroy();
502
+ process.exit(0);
503
+ }
504
+ return;
505
+ }
506
+
507
+ // ── Backspace: delete from search ──
508
+ if (key.name === "backspace") {
509
+ if (searchQuery) {
510
+ searchQuery = searchQuery.slice(0, -1);
511
+ applyFilter();
512
+ cursor = 0;
513
+ refreshAll();
514
+ ensureVisible();
515
+ }
516
+ return;
517
+ }
518
+
519
+ // ── Ctrl combos ──
520
+ if (key.ctrl) {
521
+ switch (key.name) {
522
+ case "a":
523
+ toggleAllColumn(cursorCol);
524
+ for (const i of filteredIndices) updateRow(i);
525
+ ensureVisible();
526
+ return;
527
+ case "e": {
528
+ const idx = currentSkillIndex();
529
+ if (idx !== null) editSkill(idx);
530
+ return;
531
+ }
532
+ case "d": {
533
+ const idx = currentSkillIndex();
534
+ if (idx !== null) {
535
+ pendingDelete = idx;
536
+ setStatus(`delete ${allSkills[idx]}? (y to confirm)`, C.warning);
537
+ }
538
+ return;
539
+ }
540
+ }
541
+ return;
542
+ }
543
+
544
+ // ── Navigation & actions (arrow keys, enter, tab) ──
545
+ switch (key.name) {
546
+ case "down":
547
+ if (cursor < filteredIndices.length - 1) cursor++;
548
+ break;
549
+ case "up":
550
+ if (cursor > 0) cursor--;
551
+ break;
552
+ case "left":
553
+ cursorCol = "global";
554
+ break;
555
+ case "right":
556
+ cursorCol = "local";
557
+ break;
558
+ case "tab":
559
+ cursorCol = cursorCol === "global" ? "local" : "global";
560
+ break;
561
+ case "pagedown":
562
+ cursor = Math.min(filteredIndices.length - 1, cursor + 10);
563
+ break;
564
+ case "pageup":
565
+ cursor = Math.max(0, cursor - 10);
566
+ break;
567
+ case "home":
568
+ cursor = 0;
569
+ break;
570
+ case "end":
571
+ cursor = Math.max(0, filteredIndices.length - 1);
572
+ break;
573
+ case "return": {
574
+ const idx = currentSkillIndex();
575
+ if (idx === null) break;
576
+ const skill = allSkills[idx];
577
+ if (cursorCol === "global") {
578
+ const ok = toggle(GLOBAL_DIR, skill);
579
+ if (ok) {
580
+ const linked = isLibraryLink(GLOBAL_DIR, skill);
581
+ setStatus(
582
+ linked ? `✓ ${skill} → ~/.agents/skills` : `✗ ${skill} removed from ~/.agents/skills`,
583
+ linked ? C.statusOk : C.statusErr
584
+ );
585
+ } else {
586
+ setStatus(`⚠ ${skill}: non-library file exists in ~/.agents/skills, skipped`, C.warning);
587
+ }
588
+ } else {
589
+ const dir = ensureLocalDir();
590
+ const ok = toggle(dir, skill);
591
+ if (ok) {
592
+ const linked = isLibraryLink(localDir, skill);
593
+ setStatus(
594
+ linked ? `✓ ${skill} → ${localLabel}` : `✗ ${skill} removed from ${localLabel}`,
595
+ linked ? C.statusOk : C.statusErr
596
+ );
597
+ } else {
598
+ setStatus(`⚠ ${skill}: non-library file exists in ${localLabel}, skipped`, C.warning);
599
+ }
600
+ }
601
+ break;
602
+ }
603
+ default:
604
+ // ── Printable character → search input ──
605
+ if (key.sequence && key.sequence.length === 1 && !key.meta) {
606
+ searchQuery += key.sequence;
607
+ applyFilter();
608
+ cursor = 0;
609
+ refreshAll();
610
+ ensureVisible();
611
+ return;
612
+ }
613
+ return;
614
+ }
615
+
616
+ if (prevIdx !== null && prevIdx !== currentSkillIndex()) {
617
+ updateRow(prevIdx);
618
+ }
619
+ const ci = currentSkillIndex();
620
+ if (ci !== null) updateRow(ci);
621
+ ensureVisible();
622
+ });
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@dealdeploy/skl",
3
+ "version": "0.1.0",
4
+ "description": "TUI skill manager for Claude Code agents",
5
+ "module": "index.ts",
6
+ "bin": {
7
+ "skl": "index.ts"
8
+ },
9
+ "type": "module",
10
+ "dependencies": {
11
+ "@opentui/core": "^0.1.80"
12
+ },
13
+ "devDependencies": {
14
+ "@types/bun": "latest"
15
+ },
16
+ "peerDependencies": {
17
+ "typescript": "^5"
18
+ }
19
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Environment setup & latest features
4
+ "lib": ["ESNext"],
5
+ "target": "ESNext",
6
+ "module": "Preserve",
7
+ "moduleDetection": "force",
8
+ "jsx": "react-jsx",
9
+ "allowJs": true,
10
+
11
+ // Bundler mode
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "noEmit": true,
16
+
17
+ // Best practices
18
+ "strict": true,
19
+ "skipLibCheck": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "noUncheckedIndexedAccess": true,
22
+ "noImplicitOverride": true,
23
+
24
+ // Some stricter flags (disabled by default)
25
+ "noUnusedLocals": false,
26
+ "noUnusedParameters": false,
27
+ "noPropertyAccessFromIndexSignature": false
28
+ }
29
+ }