@dealdeploy/skl 0.1.7 → 0.2.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/.agents/skills/opentui/SKILL.md +198 -0
- package/.agents/skills/opentui/references/animation/REFERENCE.md +431 -0
- package/.agents/skills/opentui/references/components/REFERENCE.md +143 -0
- package/.agents/skills/opentui/references/components/code-diff.md +496 -0
- package/.agents/skills/opentui/references/components/containers.md +412 -0
- package/.agents/skills/opentui/references/components/inputs.md +531 -0
- package/.agents/skills/opentui/references/components/text-display.md +384 -0
- package/.agents/skills/opentui/references/core/REFERENCE.md +145 -0
- package/.agents/skills/opentui/references/core/api.md +506 -0
- package/.agents/skills/opentui/references/core/configuration.md +166 -0
- package/.agents/skills/opentui/references/core/gotchas.md +393 -0
- package/.agents/skills/opentui/references/core/patterns.md +448 -0
- package/.agents/skills/opentui/references/keyboard/REFERENCE.md +511 -0
- package/.agents/skills/opentui/references/layout/REFERENCE.md +337 -0
- package/.agents/skills/opentui/references/layout/patterns.md +444 -0
- package/.agents/skills/opentui/references/react/REFERENCE.md +174 -0
- package/.agents/skills/opentui/references/react/api.md +435 -0
- package/.agents/skills/opentui/references/react/configuration.md +301 -0
- package/.agents/skills/opentui/references/react/gotchas.md +443 -0
- package/.agents/skills/opentui/references/react/patterns.md +501 -0
- package/.agents/skills/opentui/references/solid/REFERENCE.md +201 -0
- package/.agents/skills/opentui/references/solid/api.md +543 -0
- package/.agents/skills/opentui/references/solid/configuration.md +315 -0
- package/.agents/skills/opentui/references/solid/gotchas.md +415 -0
- package/.agents/skills/opentui/references/solid/patterns.md +558 -0
- package/.agents/skills/opentui/references/testing/REFERENCE.md +614 -0
- package/.claude/skills/opentui/SKILL.md +198 -0
- package/.claude/skills/opentui/references/animation/REFERENCE.md +431 -0
- package/.claude/skills/opentui/references/components/REFERENCE.md +143 -0
- package/.claude/skills/opentui/references/components/code-diff.md +496 -0
- package/.claude/skills/opentui/references/components/containers.md +412 -0
- package/.claude/skills/opentui/references/components/inputs.md +531 -0
- package/.claude/skills/opentui/references/components/text-display.md +384 -0
- package/.claude/skills/opentui/references/core/REFERENCE.md +145 -0
- package/.claude/skills/opentui/references/core/api.md +506 -0
- package/.claude/skills/opentui/references/core/configuration.md +166 -0
- package/.claude/skills/opentui/references/core/gotchas.md +393 -0
- package/.claude/skills/opentui/references/core/patterns.md +448 -0
- package/.claude/skills/opentui/references/keyboard/REFERENCE.md +511 -0
- package/.claude/skills/opentui/references/layout/REFERENCE.md +337 -0
- package/.claude/skills/opentui/references/layout/patterns.md +444 -0
- package/.claude/skills/opentui/references/react/REFERENCE.md +174 -0
- package/.claude/skills/opentui/references/react/api.md +435 -0
- package/.claude/skills/opentui/references/react/configuration.md +301 -0
- package/.claude/skills/opentui/references/react/gotchas.md +443 -0
- package/.claude/skills/opentui/references/react/patterns.md +501 -0
- package/.claude/skills/opentui/references/solid/REFERENCE.md +201 -0
- package/.claude/skills/opentui/references/solid/api.md +543 -0
- package/.claude/skills/opentui/references/solid/configuration.md +315 -0
- package/.claude/skills/opentui/references/solid/gotchas.md +415 -0
- package/.claude/skills/opentui/references/solid/patterns.md +558 -0
- package/.claude/skills/opentui/references/testing/REFERENCE.md +614 -0
- package/index.ts +169 -38
- package/package.json +1 -1
- package/update.ts +87 -0
package/index.ts
CHANGED
|
@@ -39,6 +39,10 @@ if (arg === "add") {
|
|
|
39
39
|
await import("./add.ts");
|
|
40
40
|
process.exit(0);
|
|
41
41
|
}
|
|
42
|
+
if (arg === "update") {
|
|
43
|
+
await import("./update.ts");
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
42
46
|
|
|
43
47
|
try {
|
|
44
48
|
|
|
@@ -95,8 +99,35 @@ function nonLibraryExists(dir: string, name: string): boolean {
|
|
|
95
99
|
}
|
|
96
100
|
}
|
|
97
101
|
|
|
102
|
+
function isLocalDir(dir: string): boolean {
|
|
103
|
+
return dir !== GLOBAL_DIR && dir !== GLOBAL_CLAUDE_DIR;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isEnabled(dir: string, name: string): boolean {
|
|
107
|
+
if (isLocalDir(dir)) return existsSync(join(dir, name));
|
|
108
|
+
return isLibraryLink(dir, name);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function isBlocked(dir: string, name: string): boolean {
|
|
112
|
+
if (isLocalDir(dir)) return false;
|
|
113
|
+
return nonLibraryExists(dir, name);
|
|
114
|
+
}
|
|
115
|
+
|
|
98
116
|
function toggle(dir: string, name: string): boolean {
|
|
99
117
|
const full = join(dir, name);
|
|
118
|
+
if (isLocalDir(dir)) {
|
|
119
|
+
// Copy-based toggle for project-local dirs
|
|
120
|
+
if (existsSync(full)) {
|
|
121
|
+
rmSync(full, { recursive: true, force: true });
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
// Remove any broken symlink left from old behavior
|
|
125
|
+
try { rmSync(full, { force: true }); } catch {}
|
|
126
|
+
mkdirSync(dir, { recursive: true });
|
|
127
|
+
cpSync(join(LIBRARY, name), full, { recursive: true });
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
// Symlink-based toggle for global dirs
|
|
100
131
|
if (isLibraryLink(dir, name)) {
|
|
101
132
|
unlinkSync(full);
|
|
102
133
|
return true;
|
|
@@ -145,14 +176,11 @@ function ensureLocalClaudeDir(): string {
|
|
|
145
176
|
|
|
146
177
|
// ── Migration check ──────────────────────────────────────────────────
|
|
147
178
|
{
|
|
179
|
+
// Only migrate global dirs — project-local dirs use copies, not symlinks
|
|
148
180
|
const dirsToCheck: [string, string][] = [
|
|
149
181
|
[GLOBAL_DIR, "~/.agents/skills"],
|
|
150
182
|
[GLOBAL_CLAUDE_DIR, "~/.claude/skills"],
|
|
151
183
|
];
|
|
152
|
-
const ld = findLocalDir();
|
|
153
|
-
if (ld) dirsToCheck.push([ld, ld.replace(homedir(), "~")]);
|
|
154
|
-
const lcd = findLocalClaudeDir();
|
|
155
|
-
if (lcd) dirsToCheck.push([lcd, lcd.replace(homedir(), "~")]);
|
|
156
184
|
|
|
157
185
|
const migrations: { dir: string; label: string; name: string; conflict: boolean }[] = [];
|
|
158
186
|
|
|
@@ -171,7 +199,7 @@ function ensureLocalClaudeDir(): string {
|
|
|
171
199
|
if (migrations.length > 0) {
|
|
172
200
|
console.log("\nFound skills not managed by library:");
|
|
173
201
|
for (const m of migrations) {
|
|
174
|
-
console.log(` ${m.label}/${m.name}${m.conflict ? " (
|
|
202
|
+
console.log(` ${m.label}/${m.name}${m.conflict ? " (already in library)" : ""}`);
|
|
175
203
|
}
|
|
176
204
|
process.stdout.write(`\nMigrate to ${LIBRARY.replace(homedir(), "~")} and replace with symlinks? [y/N] `);
|
|
177
205
|
|
|
@@ -181,33 +209,89 @@ function ensureLocalClaudeDir(): string {
|
|
|
181
209
|
|
|
182
210
|
if (answer === "y" || answer === "yes") {
|
|
183
211
|
let migrated = 0;
|
|
184
|
-
let skipped = 0;
|
|
185
212
|
for (const m of migrations) {
|
|
186
213
|
const src = join(m.dir, m.name);
|
|
187
214
|
const dest = join(LIBRARY, m.name);
|
|
188
215
|
if (m.conflict) {
|
|
189
|
-
|
|
190
|
-
skipped++;
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
try {
|
|
194
|
-
renameSync(src, dest);
|
|
195
|
-
} catch {
|
|
196
|
-
// cross-device fallback
|
|
197
|
-
cpSync(src, dest, { recursive: true });
|
|
216
|
+
// Library already has this skill — remove local and symlink to library
|
|
198
217
|
rmSync(src, { recursive: true, force: true });
|
|
218
|
+
symlinkSync(dest, src);
|
|
219
|
+
console.log(` replaced ${m.name} (library version kept)`);
|
|
220
|
+
} else {
|
|
221
|
+
try {
|
|
222
|
+
renameSync(src, dest);
|
|
223
|
+
} catch {
|
|
224
|
+
// cross-device fallback
|
|
225
|
+
cpSync(src, dest, { recursive: true });
|
|
226
|
+
rmSync(src, { recursive: true, force: true });
|
|
227
|
+
}
|
|
228
|
+
symlinkSync(dest, src);
|
|
229
|
+
console.log(` migrated ${m.name}`);
|
|
199
230
|
}
|
|
200
|
-
symlinkSync(dest, src);
|
|
201
|
-
console.log(` migrated ${m.name}`);
|
|
202
231
|
migrated++;
|
|
203
232
|
}
|
|
204
|
-
console.log(`\n${migrated} migrated
|
|
233
|
+
console.log(`\n${migrated} migrated\n`);
|
|
205
234
|
} else {
|
|
206
235
|
console.log("Skipping migration.\n");
|
|
207
236
|
}
|
|
208
237
|
}
|
|
209
238
|
}
|
|
210
239
|
|
|
240
|
+
// ── Local symlink → copy conversion ─────────────────────────────────
|
|
241
|
+
{
|
|
242
|
+
const localDirsToCheck: [string, string][] = [];
|
|
243
|
+
const ld = findLocalDir();
|
|
244
|
+
if (ld) localDirsToCheck.push([ld, ld.replace(homedir(), "~")]);
|
|
245
|
+
const lcd = findLocalClaudeDir();
|
|
246
|
+
if (lcd) localDirsToCheck.push([lcd, lcd.replace(homedir(), "~")]);
|
|
247
|
+
|
|
248
|
+
const localSymlinks: { dir: string; label: string; name: string; target: string }[] = [];
|
|
249
|
+
|
|
250
|
+
for (const [dir, label] of localDirsToCheck) {
|
|
251
|
+
try {
|
|
252
|
+
for (const name of readdirSync(dir)) {
|
|
253
|
+
const full = join(dir, name);
|
|
254
|
+
try {
|
|
255
|
+
if (lstatSync(full).isSymbolicLink()) {
|
|
256
|
+
localSymlinks.push({ dir, label, name, target: readlinkSync(full) });
|
|
257
|
+
}
|
|
258
|
+
} catch {}
|
|
259
|
+
}
|
|
260
|
+
} catch {}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (localSymlinks.length > 0) {
|
|
264
|
+
console.log("\nFound symlinked skills in project-local dirs:");
|
|
265
|
+
for (const s of localSymlinks) {
|
|
266
|
+
console.log(` ${s.label}/${s.name} → ${s.target}`);
|
|
267
|
+
}
|
|
268
|
+
process.stdout.write("\nConvert to local copies? (recommended for portability) [y/N] ");
|
|
269
|
+
|
|
270
|
+
const buf = new Uint8Array(100);
|
|
271
|
+
const n = readSync(0, buf);
|
|
272
|
+
const answer = new TextDecoder().decode(buf.subarray(0, n)).trim().toLowerCase();
|
|
273
|
+
|
|
274
|
+
if (answer === "y" || answer === "yes") {
|
|
275
|
+
let converted = 0;
|
|
276
|
+
for (const s of localSymlinks) {
|
|
277
|
+
const full = join(s.dir, s.name);
|
|
278
|
+
const libPath = join(LIBRARY, s.name);
|
|
279
|
+
unlinkSync(full);
|
|
280
|
+
if (existsSync(libPath)) {
|
|
281
|
+
cpSync(libPath, full, { recursive: true });
|
|
282
|
+
console.log(` converted ${s.name}`);
|
|
283
|
+
converted++;
|
|
284
|
+
} else {
|
|
285
|
+
console.log(` removed ${s.name} (not in library)`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
console.log(`\n${converted} converted\n`);
|
|
289
|
+
} else {
|
|
290
|
+
console.log("Skipping conversion.\n");
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
211
295
|
const allSkills = readdirSync(LIBRARY).sort();
|
|
212
296
|
|
|
213
297
|
// ── State ───────────────────────────────────────────────────────────
|
|
@@ -297,14 +381,23 @@ const colHeaderRow = new BoxRenderable(renderer, {
|
|
|
297
381
|
paddingLeft: 1,
|
|
298
382
|
});
|
|
299
383
|
|
|
384
|
+
let COL_W = 14;
|
|
385
|
+
let NAME_W = 34;
|
|
386
|
+
|
|
387
|
+
function calcWidths() {
|
|
388
|
+
const w = process.stdout.columns || 80;
|
|
389
|
+
COL_W = Math.max(5, Math.min(14, Math.floor((w - 20) / 4)));
|
|
390
|
+
NAME_W = Math.min(34, Math.max(15, w - COL_W * 4 - 1));
|
|
391
|
+
}
|
|
392
|
+
calcWidths();
|
|
393
|
+
|
|
300
394
|
const colName = new TextRenderable(renderer, {
|
|
301
395
|
id: "col-name",
|
|
302
396
|
content: "Skill",
|
|
303
397
|
fg: C.fgDim,
|
|
304
398
|
attributes: TextAttributes.BOLD,
|
|
305
|
-
width:
|
|
399
|
+
width: NAME_W,
|
|
306
400
|
});
|
|
307
|
-
const COL_W = 14;
|
|
308
401
|
const colGlobal = new TextRenderable(renderer, {
|
|
309
402
|
id: "col-global",
|
|
310
403
|
content: "~/.agents",
|
|
@@ -382,9 +475,9 @@ for (let i = 0; i < allSkills.length; i++) {
|
|
|
382
475
|
|
|
383
476
|
const nameText = new TextRenderable(renderer, {
|
|
384
477
|
id: `name-${i}`,
|
|
385
|
-
content: ` ${skill}`,
|
|
478
|
+
content: ` ${ellipsize(skill, NAME_W - 3)}`,
|
|
386
479
|
fg: C.fg,
|
|
387
|
-
width:
|
|
480
|
+
width: NAME_W,
|
|
388
481
|
});
|
|
389
482
|
|
|
390
483
|
const gChecked = isLibraryLink(GLOBAL_DIR, skill);
|
|
@@ -405,8 +498,8 @@ for (let i = 0; i < allSkills.length; i++) {
|
|
|
405
498
|
width: COL_W,
|
|
406
499
|
});
|
|
407
500
|
|
|
408
|
-
const lChecked = localDir ?
|
|
409
|
-
const lBlocked = localDir ?
|
|
501
|
+
const lChecked = localDir ? isEnabled(localDir, skill) : false;
|
|
502
|
+
const lBlocked = localDir ? isBlocked(localDir, skill) : false;
|
|
410
503
|
const localText = new TextRenderable(renderer, {
|
|
411
504
|
id: `local-${i}`,
|
|
412
505
|
content: localDir ? checkboxStr(lChecked, lBlocked) : " -",
|
|
@@ -414,8 +507,8 @@ for (let i = 0; i < allSkills.length; i++) {
|
|
|
414
507
|
width: COL_W,
|
|
415
508
|
});
|
|
416
509
|
|
|
417
|
-
const lcChecked = localClaudeDir ?
|
|
418
|
-
const lcBlocked = localClaudeDir ?
|
|
510
|
+
const lcChecked = localClaudeDir ? isEnabled(localClaudeDir, skill) : false;
|
|
511
|
+
const lcBlocked = localClaudeDir ? isBlocked(localClaudeDir, skill) : false;
|
|
419
512
|
const localClaudeText = new TextRenderable(renderer, {
|
|
420
513
|
id: `local-claude-${i}`,
|
|
421
514
|
content: localClaudeDir ? checkboxStr(lcChecked, lcBlocked) : " -",
|
|
@@ -442,7 +535,7 @@ const footerSep = new TextRenderable(renderer, {
|
|
|
442
535
|
|
|
443
536
|
const footer = new TextRenderable(renderer, {
|
|
444
537
|
id: "footer",
|
|
445
|
-
content: " ↑↓ move ←→/tab col enter toggle ^a all ^e edit ^d del
|
|
538
|
+
content: " ↑↓ move ←→/tab col enter toggle ^a all ^e edit ^d del / search q/esc quit",
|
|
446
539
|
fg: C.footer,
|
|
447
540
|
height: 1,
|
|
448
541
|
});
|
|
@@ -513,7 +606,7 @@ function updateRow(i: number) {
|
|
|
513
606
|
r.row.backgroundColor = isCursor ? C.cursorBg : baseBg;
|
|
514
607
|
|
|
515
608
|
const pointer = isCursor ? "▸" : " ";
|
|
516
|
-
r.nameText.content = `${pointer} ${skill}`;
|
|
609
|
+
r.nameText.content = `${pointer} ${ellipsize(skill, NAME_W - 3)}`;
|
|
517
610
|
r.nameText.fg = isCursor ? "#ffffff" : C.fg;
|
|
518
611
|
r.nameText.attributes = isCursor ? TextAttributes.BOLD : TextAttributes.NONE;
|
|
519
612
|
|
|
@@ -533,8 +626,8 @@ function updateRow(i: number) {
|
|
|
533
626
|
r.globalClaudeText.bg = gcActive ? C.accentBg : undefined;
|
|
534
627
|
r.globalClaudeText.attributes = gcActive ? TextAttributes.BOLD : TextAttributes.NONE;
|
|
535
628
|
|
|
536
|
-
const lChecked = localDir ?
|
|
537
|
-
const lBlocked = localDir ?
|
|
629
|
+
const lChecked = localDir ? isEnabled(localDir, skill) : false;
|
|
630
|
+
const lBlocked = localDir ? isBlocked(localDir, skill) : false;
|
|
538
631
|
const lActive = isCursor && cursorCol === "local";
|
|
539
632
|
if (localDir) {
|
|
540
633
|
r.localText.content = checkboxStr(lChecked, lBlocked);
|
|
@@ -547,8 +640,8 @@ function updateRow(i: number) {
|
|
|
547
640
|
r.localText.bg = lActive ? C.accentBg : undefined;
|
|
548
641
|
}
|
|
549
642
|
|
|
550
|
-
const lcChecked = localClaudeDir ?
|
|
551
|
-
const lcBlocked = localClaudeDir ?
|
|
643
|
+
const lcChecked = localClaudeDir ? isEnabled(localClaudeDir, skill) : false;
|
|
644
|
+
const lcBlocked = localClaudeDir ? isBlocked(localClaudeDir, skill) : false;
|
|
552
645
|
const lcActive = isCursor && cursorCol === "localClaude";
|
|
553
646
|
if (localClaudeDir) {
|
|
554
647
|
r.localClaudeText.content = checkboxStr(lcChecked, lcBlocked);
|
|
@@ -571,6 +664,30 @@ function setStatus(msg: string, color: string) {
|
|
|
571
664
|
}, 3000);
|
|
572
665
|
}
|
|
573
666
|
|
|
667
|
+
function relayout() {
|
|
668
|
+
calcWidths();
|
|
669
|
+
colName.width = NAME_W;
|
|
670
|
+
colGlobal.width = COL_W;
|
|
671
|
+
colGlobalClaude.width = COL_W;
|
|
672
|
+
colLocal.width = COL_W;
|
|
673
|
+
colLocal.content = ellipsize(localLabel, COL_W - 1);
|
|
674
|
+
colLocalClaude.width = COL_W;
|
|
675
|
+
colLocalClaude.content = ellipsize(localClaudeLabel, COL_W - 1);
|
|
676
|
+
for (let i = 0; i < allSkills.length; i++) {
|
|
677
|
+
const r = rows[i];
|
|
678
|
+
r.nameText.width = NAME_W;
|
|
679
|
+
r.globalText.width = COL_W;
|
|
680
|
+
r.globalClaudeText.width = COL_W;
|
|
681
|
+
r.localText.width = COL_W;
|
|
682
|
+
r.localClaudeText.width = COL_W;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
process.stdout.on("resize", () => {
|
|
687
|
+
relayout();
|
|
688
|
+
refreshAll();
|
|
689
|
+
});
|
|
690
|
+
|
|
574
691
|
function refreshAll() {
|
|
575
692
|
for (const i of filteredIndices) updateRow(i);
|
|
576
693
|
updateSearchBar();
|
|
@@ -604,7 +721,7 @@ function toggleAllColumn(col: ColId) {
|
|
|
604
721
|
// Determine intent: if majority of visible skills are linked, unlink all; otherwise link all
|
|
605
722
|
let linkedCount = 0;
|
|
606
723
|
for (const i of filteredIndices) {
|
|
607
|
-
if (
|
|
724
|
+
if (isEnabled(dir, allSkills[i])) linkedCount++;
|
|
608
725
|
}
|
|
609
726
|
const shouldLink = linkedCount <= filteredIndices.length / 2;
|
|
610
727
|
let changed = 0;
|
|
@@ -612,7 +729,7 @@ function toggleAllColumn(col: ColId) {
|
|
|
612
729
|
|
|
613
730
|
for (const i of filteredIndices) {
|
|
614
731
|
const skill = allSkills[i];
|
|
615
|
-
const isLinked =
|
|
732
|
+
const isLinked = isEnabled(dir, skill);
|
|
616
733
|
if (shouldLink && !isLinked) {
|
|
617
734
|
if (toggle(dir, skill)) changed++;
|
|
618
735
|
else skipped++;
|
|
@@ -658,11 +775,11 @@ function cancelPendingDelete() {
|
|
|
658
775
|
function deleteSkill(idx: number) {
|
|
659
776
|
const skill = allSkills[idx];
|
|
660
777
|
|
|
661
|
-
// Remove symlinks
|
|
778
|
+
// Remove from all skill dirs (symlinks for global, copies for local)
|
|
662
779
|
if (isLibraryLink(GLOBAL_DIR, skill)) unlinkSync(join(GLOBAL_DIR, skill));
|
|
663
780
|
if (isLibraryLink(GLOBAL_CLAUDE_DIR, skill)) unlinkSync(join(GLOBAL_CLAUDE_DIR, skill));
|
|
664
|
-
if (localDir &&
|
|
665
|
-
if (localClaudeDir &&
|
|
781
|
+
if (localDir && existsSync(join(localDir, skill))) rmSync(join(localDir, skill), { recursive: true, force: true });
|
|
782
|
+
if (localClaudeDir && existsSync(join(localClaudeDir, skill))) rmSync(join(localClaudeDir, skill), { recursive: true, force: true });
|
|
666
783
|
|
|
667
784
|
// Remove from library
|
|
668
785
|
rmSync(join(LIBRARY, skill), { recursive: true, force: true });
|
|
@@ -791,6 +908,20 @@ renderer.keyInput.on("keypress", (key: KeyEvent) => {
|
|
|
791
908
|
return;
|
|
792
909
|
}
|
|
793
910
|
|
|
911
|
+
// ── "q" to quit ──
|
|
912
|
+
if (key.sequence === "q") {
|
|
913
|
+
renderer.destroy();
|
|
914
|
+
process.exit(0);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// ── "/" to focus search (vim-style) ──
|
|
918
|
+
if (key.sequence === "/") {
|
|
919
|
+
focusArea = "search";
|
|
920
|
+
refreshAll();
|
|
921
|
+
ensureVisible();
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
|
|
794
925
|
// ── Navigation & actions ──
|
|
795
926
|
switch (key.name) {
|
|
796
927
|
case "down":
|
|
@@ -835,7 +966,7 @@ renderer.keyInput.on("keypress", (key: KeyEvent) => {
|
|
|
835
966
|
const { dir, label } = dirForCol(cursorCol);
|
|
836
967
|
const ok = toggle(dir, skill);
|
|
837
968
|
if (ok) {
|
|
838
|
-
const linked =
|
|
969
|
+
const linked = isEnabled(dir, skill);
|
|
839
970
|
setStatus(
|
|
840
971
|
linked ? `✓ ${skill} → ${label}` : `✗ ${skill} removed from ${label}`,
|
|
841
972
|
linked ? C.statusOk : C.statusErr
|
package/package.json
CHANGED
package/update.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { readdirSync, existsSync, cpSync, rmSync, lstatSync, readlinkSync, readSync, unlinkSync } from "fs";
|
|
4
|
+
import { join, resolve } from "path";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
|
|
7
|
+
const LIBRARY = join(homedir(), "dotfiles/skills");
|
|
8
|
+
|
|
9
|
+
if (!existsSync(LIBRARY)) {
|
|
10
|
+
console.error(`skl: library not found at ${LIBRARY}`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const localDirs: [string, string][] = [];
|
|
15
|
+
const agentsDir = join(process.cwd(), ".agents/skills");
|
|
16
|
+
if (existsSync(agentsDir)) localDirs.push([agentsDir, ".agents/skills"]);
|
|
17
|
+
|
|
18
|
+
const claudeDir = join(process.cwd(), ".claude/skills");
|
|
19
|
+
if (existsSync(claudeDir)) localDirs.push([claudeDir, ".claude/skills"]);
|
|
20
|
+
|
|
21
|
+
if (localDirs.length === 0) {
|
|
22
|
+
console.log("No local skill directories found.");
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type Entry = { dir: string; label: string; name: string; kind: "symlink" | "copy" };
|
|
27
|
+
|
|
28
|
+
const symlinks: Entry[] = [];
|
|
29
|
+
const copies: Entry[] = [];
|
|
30
|
+
let skipped = 0;
|
|
31
|
+
|
|
32
|
+
for (const [dir, label] of localDirs) {
|
|
33
|
+
for (const name of readdirSync(dir)) {
|
|
34
|
+
const localPath = join(dir, name);
|
|
35
|
+
const libPath = join(LIBRARY, name);
|
|
36
|
+
if (!existsSync(libPath)) {
|
|
37
|
+
skipped++;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const isSym = lstatSync(localPath).isSymbolicLink();
|
|
41
|
+
const entry: Entry = { dir, label, name, kind: isSym ? "symlink" : "copy" };
|
|
42
|
+
if (isSym) symlinks.push(entry);
|
|
43
|
+
else copies.push(entry);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Handle symlinks → copies (requires confirmation)
|
|
48
|
+
if (symlinks.length > 0) {
|
|
49
|
+
console.log("\nSymlinks to convert to local copies:");
|
|
50
|
+
for (const s of symlinks) {
|
|
51
|
+
console.log(` ${s.label}/${s.name} → ${readlinkSync(join(s.dir, s.name))}`);
|
|
52
|
+
}
|
|
53
|
+
process.stdout.write(`\nReplace ${symlinks.length} symlink(s) with copies from library? [y/N] `);
|
|
54
|
+
|
|
55
|
+
const buf = new Uint8Array(100);
|
|
56
|
+
const n = readSync(0, buf);
|
|
57
|
+
const answer = new TextDecoder().decode(buf.subarray(0, n)).trim().toLowerCase();
|
|
58
|
+
|
|
59
|
+
if (answer === "y" || answer === "yes") {
|
|
60
|
+
for (const s of symlinks) {
|
|
61
|
+
const localPath = join(s.dir, s.name);
|
|
62
|
+
unlinkSync(localPath);
|
|
63
|
+
cpSync(join(LIBRARY, s.name), localPath, { recursive: true });
|
|
64
|
+
console.log(` converted ${s.label}/${s.name}`);
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
console.log("Skipping symlink conversion.");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Update existing copies
|
|
72
|
+
if (copies.length > 0) {
|
|
73
|
+
console.log(`\nUpdating ${copies.length} local copies from library...`);
|
|
74
|
+
for (const c of copies) {
|
|
75
|
+
const localPath = join(c.dir, c.name);
|
|
76
|
+
rmSync(localPath, { recursive: true, force: true });
|
|
77
|
+
cpSync(join(LIBRARY, c.name), localPath, { recursive: true });
|
|
78
|
+
console.log(` updated ${c.label}/${c.name}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (symlinks.length === 0 && copies.length === 0) {
|
|
83
|
+
console.log("Nothing to update.");
|
|
84
|
+
}
|
|
85
|
+
if (skipped > 0) {
|
|
86
|
+
console.log(`${skipped} skipped (not in library)`);
|
|
87
|
+
}
|