@dealdeploy/skl 1.4.0 → 1.6.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/add-tui.ts +3 -1
- package/index.ts +6 -2
- package/lib.test.ts +7 -53
- package/lib.ts +19 -15
- package/package.json +1 -1
- package/tui.ts +22 -25
package/add-tui.ts
CHANGED
|
@@ -202,7 +202,9 @@ export function createAddTui(renderer: CliRenderer, deps: AddTuiDeps) {
|
|
|
202
202
|
renderer.keyInput.on("keypress", (key: KeyEvent) => {
|
|
203
203
|
const prevCursor = cursor;
|
|
204
204
|
|
|
205
|
-
|
|
205
|
+
const navKey = (key.ctrl && { h: "left", j: "down", k: "up", l: "right" }[key.name]) || key.name;
|
|
206
|
+
|
|
207
|
+
switch (navKey) {
|
|
206
208
|
case "j":
|
|
207
209
|
case "down":
|
|
208
210
|
if (cursor < skills.length - 1) cursor++;
|
package/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { createCliRenderer } from "@opentui/core";
|
|
|
4
4
|
import { existsSync, rmSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
5
5
|
import { join } from "path";
|
|
6
6
|
import { homedir } from "os";
|
|
7
|
-
import { getCatalogSkills, removeFromLock, catalogDir, buildAddArgs, readLock, detectProjectAgents } from "./lib.ts";
|
|
7
|
+
import { getCatalogSkills, removeFromLock, removeFromProjectLock, catalogDir, buildAddArgs, readLock, detectProjectAgents } from "./lib.ts";
|
|
8
8
|
import { createTui, type ColId } from "./tui.ts";
|
|
9
9
|
import { checkForUpdate } from "./update-check.ts";
|
|
10
10
|
|
|
@@ -122,7 +122,10 @@ const tui = createTui(renderer, {
|
|
|
122
122
|
new Response(proc.stdout).text(),
|
|
123
123
|
new Response(proc.stderr).text(),
|
|
124
124
|
]);
|
|
125
|
-
if (code === 0)
|
|
125
|
+
if (code === 0) {
|
|
126
|
+
if (!enable && !isGlobal) removeFromProjectLock(name, process.cwd());
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
126
129
|
if (debug) {
|
|
127
130
|
return (stderr || stdout).trim() || `exit code ${code}`;
|
|
128
131
|
}
|
|
@@ -148,6 +151,7 @@ const tui = createTui(renderer, {
|
|
|
148
151
|
for (const p of procs) await p.exited;
|
|
149
152
|
rmSync(join(CATALOG, name), { recursive: true, force: true });
|
|
150
153
|
removeFromLock(name);
|
|
154
|
+
removeFromProjectLock(name, process.cwd());
|
|
151
155
|
},
|
|
152
156
|
|
|
153
157
|
async onEdit(name: string) {
|
package/lib.test.ts
CHANGED
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
findSkillEntries,
|
|
17
17
|
parseRepoArg,
|
|
18
18
|
buildAddArgs,
|
|
19
|
-
readSkillFrontmatterName,
|
|
20
19
|
detectProjectAgents,
|
|
21
20
|
parseSkillsListOutput,
|
|
22
21
|
planUpdates,
|
|
@@ -289,7 +288,7 @@ describe("parseRepoArg", () => {
|
|
|
289
288
|
// ── buildAddArgs ────────────────────────────────────────────────────
|
|
290
289
|
|
|
291
290
|
describe("buildAddArgs", () => {
|
|
292
|
-
test("uses
|
|
291
|
+
test("uses owner/repo/skillPath shorthand for remote skill with project agents", () => {
|
|
293
292
|
const entry: LockEntry = {
|
|
294
293
|
source: "owner/repo",
|
|
295
294
|
sourceUrl: "https://github.com/owner/repo",
|
|
@@ -300,7 +299,7 @@ describe("buildAddArgs", () => {
|
|
|
300
299
|
addToLock("myskill", entry);
|
|
301
300
|
|
|
302
301
|
const args = buildAddArgs("/catalog", "myskill", false, ["claude-code", "roo"]);
|
|
303
|
-
expect(args).toEqual(["add", "
|
|
302
|
+
expect(args).toEqual(["add", "owner/repo/skills/myskill", "-y", "--agent", "claude-code", "roo"]);
|
|
304
303
|
});
|
|
305
304
|
|
|
306
305
|
test("global install does not pass --agent", () => {
|
|
@@ -316,6 +315,7 @@ describe("buildAddArgs", () => {
|
|
|
316
315
|
const args = buildAddArgs("/catalog", "myskill", true, ["claude-code"]);
|
|
317
316
|
expect(args).toContain("-g");
|
|
318
317
|
expect(args).not.toContain("--agent");
|
|
318
|
+
expect(args).toContain("owner/repo/skills/myskill");
|
|
319
319
|
});
|
|
320
320
|
|
|
321
321
|
test("uses catalog path for hand-authored skill with project agents", () => {
|
|
@@ -328,14 +328,7 @@ describe("buildAddArgs", () => {
|
|
|
328
328
|
expect(args).toEqual(["add", "/my/catalog/custom-skill", "-y", "-g"]);
|
|
329
329
|
});
|
|
330
330
|
|
|
331
|
-
test("uses
|
|
332
|
-
const catalog = catalogDir();
|
|
333
|
-
mkdirSync(join(catalog, "code-review"), { recursive: true });
|
|
334
|
-
writeFileSync(
|
|
335
|
-
join(catalog, "code-review", "SKILL.md"),
|
|
336
|
-
"---\nname: code-review:code-review\ndescription: Review PRs\n---\n# Code Review"
|
|
337
|
-
);
|
|
338
|
-
|
|
331
|
+
test("uses subpath for nested skill paths", () => {
|
|
339
332
|
const entry: LockEntry = {
|
|
340
333
|
source: "owner/repo",
|
|
341
334
|
sourceUrl: "https://github.com/owner/repo",
|
|
@@ -345,48 +338,9 @@ describe("buildAddArgs", () => {
|
|
|
345
338
|
};
|
|
346
339
|
addToLock("code-review", entry);
|
|
347
340
|
|
|
348
|
-
const args = buildAddArgs(catalog, "code-review", true);
|
|
349
|
-
expect(args).
|
|
350
|
-
expect(args).toContain("
|
|
351
|
-
expect(args).not.toContain("--agent");
|
|
352
|
-
expect(args).toContain("-g");
|
|
353
|
-
});
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
// ── readSkillFrontmatterName ────────────────────────────────────────
|
|
357
|
-
|
|
358
|
-
describe("readSkillFrontmatterName", () => {
|
|
359
|
-
test("reads name from valid frontmatter", () => {
|
|
360
|
-
const catalog = catalogDir();
|
|
361
|
-
mkdirSync(join(catalog, "my-skill"), { recursive: true });
|
|
362
|
-
writeFileSync(
|
|
363
|
-
join(catalog, "my-skill", "SKILL.md"),
|
|
364
|
-
"---\nname: my-skill:variant\ndescription: test\n---\n# Content"
|
|
365
|
-
);
|
|
366
|
-
expect(readSkillFrontmatterName(catalog, "my-skill")).toBe("my-skill:variant");
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
test("returns null when no frontmatter", () => {
|
|
370
|
-
const catalog = catalogDir();
|
|
371
|
-
mkdirSync(join(catalog, "no-fm"), { recursive: true });
|
|
372
|
-
writeFileSync(join(catalog, "no-fm", "SKILL.md"), "# Just markdown");
|
|
373
|
-
expect(readSkillFrontmatterName(catalog, "no-fm")).toBeNull();
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
test("returns null when SKILL.md missing", () => {
|
|
377
|
-
const catalog = catalogDir();
|
|
378
|
-
mkdirSync(join(catalog, "empty"), { recursive: true });
|
|
379
|
-
expect(readSkillFrontmatterName(catalog, "empty")).toBeNull();
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
test("returns null when name field missing from frontmatter", () => {
|
|
383
|
-
const catalog = catalogDir();
|
|
384
|
-
mkdirSync(join(catalog, "no-name"), { recursive: true });
|
|
385
|
-
writeFileSync(
|
|
386
|
-
join(catalog, "no-name", "SKILL.md"),
|
|
387
|
-
"---\ndescription: has no name\n---\n# Content"
|
|
388
|
-
);
|
|
389
|
-
expect(readSkillFrontmatterName(catalog, "no-name")).toBeNull();
|
|
341
|
+
const args = buildAddArgs("/catalog", "code-review", true);
|
|
342
|
+
expect(args).toEqual(["add", "owner/repo/skills/code-review", "-y", "-g"]);
|
|
343
|
+
expect(args).not.toContain("--skill");
|
|
390
344
|
});
|
|
391
345
|
});
|
|
392
346
|
|
package/lib.ts
CHANGED
|
@@ -52,6 +52,22 @@ export function removeFromLock(name: string): void {
|
|
|
52
52
|
writeLock(lock);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
export function removeFromProjectLock(name: string, cwd: string): void {
|
|
56
|
+
const lockFile = join(cwd, "skills-lock.json");
|
|
57
|
+
try {
|
|
58
|
+
const data = JSON.parse(readFileSync(lockFile, "utf-8"));
|
|
59
|
+
if (data?.skills?.[name]) {
|
|
60
|
+
delete data.skills[name];
|
|
61
|
+
const sorted: Record<string, unknown> = {};
|
|
62
|
+
for (const key of Object.keys(data.skills).sort()) {
|
|
63
|
+
sorted[key] = data.skills[key];
|
|
64
|
+
}
|
|
65
|
+
data.skills = sorted;
|
|
66
|
+
writeFileSync(lockFile, JSON.stringify(data, null, 2) + "\n");
|
|
67
|
+
}
|
|
68
|
+
} catch {}
|
|
69
|
+
}
|
|
70
|
+
|
|
55
71
|
export function getLockEntry(name: string): LockEntry | null {
|
|
56
72
|
const lock = readLock();
|
|
57
73
|
return lock.skills[name] ?? null;
|
|
@@ -160,19 +176,6 @@ export function detectProjectAgents(cwd: string): string[] {
|
|
|
160
176
|
return agents;
|
|
161
177
|
}
|
|
162
178
|
|
|
163
|
-
/** Read the frontmatter `name` from a skill's SKILL.md in the catalog. */
|
|
164
|
-
export function readSkillFrontmatterName(catalogPath: string, dirName: string): string | null {
|
|
165
|
-
try {
|
|
166
|
-
const content = readFileSync(join(catalogPath, dirName, "SKILL.md"), "utf-8");
|
|
167
|
-
const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
168
|
-
if (!match) return null;
|
|
169
|
-
const nameMatch = match[1]!.match(/^name:\s*(.+)$/m);
|
|
170
|
-
return nameMatch ? nameMatch[1]!.trim() : null;
|
|
171
|
-
} catch {
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
179
|
/** Build npx skills add args for a skill. */
|
|
177
180
|
export function buildAddArgs(
|
|
178
181
|
catalogPath: string,
|
|
@@ -185,8 +188,9 @@ export function buildAddArgs(
|
|
|
185
188
|
? ["--agent", ...projectAgents]
|
|
186
189
|
: [];
|
|
187
190
|
if (lockEntry) {
|
|
188
|
-
|
|
189
|
-
const
|
|
191
|
+
// Use owner/repo/skillPath shorthand so the CLI narrows via subpath
|
|
192
|
+
const source = `${lockEntry.source}/${lockEntry.skillPath}`;
|
|
193
|
+
const args = ["add", source, "-y", ...agentArgs];
|
|
190
194
|
if (isGlobal) args.push("-g");
|
|
191
195
|
return args;
|
|
192
196
|
}
|
package/package.json
CHANGED
package/tui.ts
CHANGED
|
@@ -227,7 +227,7 @@ export function createTui(renderer: CliRenderer, deps: TuiDeps) {
|
|
|
227
227
|
width: "100%",
|
|
228
228
|
});
|
|
229
229
|
|
|
230
|
-
type HeaderRef = { spacer: BoxRenderable
|
|
230
|
+
type HeaderRef = { spacer: BoxRenderable; row: BoxRenderable; text: TextRenderable };
|
|
231
231
|
const headerRefs = new Map<number, HeaderRef>();
|
|
232
232
|
|
|
233
233
|
type RowRefs = {
|
|
@@ -239,28 +239,17 @@ export function createTui(renderer: CliRenderer, deps: TuiDeps) {
|
|
|
239
239
|
const rows: RowRefs[] = new Array(allSkills.length);
|
|
240
240
|
|
|
241
241
|
scrollBox.add(searchRow);
|
|
242
|
-
scrollBox.add(
|
|
243
|
-
new BoxRenderable(renderer, {
|
|
244
|
-
id: "spacer-search",
|
|
245
|
-
height: 1,
|
|
246
|
-
width: "100%",
|
|
247
|
-
backgroundColor: C.rowBg,
|
|
248
|
-
})
|
|
249
|
-
);
|
|
250
242
|
|
|
251
243
|
for (let di = 0; di < displayItems.length; di++) {
|
|
252
244
|
const item = displayItems[di]!;
|
|
253
245
|
if (item.type === "header") {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
});
|
|
262
|
-
scrollBox.add(spacer);
|
|
263
|
-
}
|
|
246
|
+
const spacer = new BoxRenderable(renderer, {
|
|
247
|
+
id: `spacer-${di}`,
|
|
248
|
+
height: 1,
|
|
249
|
+
width: "100%",
|
|
250
|
+
backgroundColor: C.rowBg,
|
|
251
|
+
});
|
|
252
|
+
scrollBox.add(spacer);
|
|
264
253
|
const hRow = new BoxRenderable(renderer, {
|
|
265
254
|
id: `header-${di}`,
|
|
266
255
|
flexDirection: "row",
|
|
@@ -376,7 +365,7 @@ export function createTui(renderer: CliRenderer, deps: TuiDeps) {
|
|
|
376
365
|
const visible = visibleRepos.has(item.repo);
|
|
377
366
|
const ref = headerRefs.get(di)!;
|
|
378
367
|
ref.row.visible = visible;
|
|
379
|
-
|
|
368
|
+
ref.spacer.visible = visible;
|
|
380
369
|
} else {
|
|
381
370
|
const visible = matchingSkills.has(item.skillIndex);
|
|
382
371
|
rows[item.skillIndex]!.row.visible = visible;
|
|
@@ -447,7 +436,7 @@ export function createTui(renderer: CliRenderer, deps: TuiDeps) {
|
|
|
447
436
|
statusLine.fg = color;
|
|
448
437
|
if (statusTimeout) clearTimeout(statusTimeout);
|
|
449
438
|
statusTimeout = setTimeout(() => {
|
|
450
|
-
statusLine.content = "";
|
|
439
|
+
try { statusLine.content = ""; } catch {}
|
|
451
440
|
}, 3000);
|
|
452
441
|
}
|
|
453
442
|
|
|
@@ -492,7 +481,7 @@ export function createTui(renderer: CliRenderer, deps: TuiDeps) {
|
|
|
492
481
|
const ref = headerRefs.get(di)!;
|
|
493
482
|
if (ref.row.visible) {
|
|
494
483
|
visibleBefore++;
|
|
495
|
-
if (ref.spacer
|
|
484
|
+
if (ref.spacer.visible) visibleBefore++;
|
|
496
485
|
}
|
|
497
486
|
} else {
|
|
498
487
|
if (rows[item.skillIndex]!.row.visible) visibleBefore++;
|
|
@@ -622,7 +611,7 @@ export function createTui(renderer: CliRenderer, deps: TuiDeps) {
|
|
|
622
611
|
}
|
|
623
612
|
return;
|
|
624
613
|
}
|
|
625
|
-
if (key.name === "down" || key.name === "return") {
|
|
614
|
+
if (key.name === "down" || key.name === "return" || (key.ctrl && key.name === "j")) {
|
|
626
615
|
focusArea = "grid";
|
|
627
616
|
refreshAll();
|
|
628
617
|
ensureVisible();
|
|
@@ -642,7 +631,15 @@ export function createTui(renderer: CliRenderer, deps: TuiDeps) {
|
|
|
642
631
|
// ── Grid-focused input ──
|
|
643
632
|
|
|
644
633
|
if (key.name === "backspace") return;
|
|
645
|
-
|
|
634
|
+
const ctrlNav: Record<string, string> = { h: "left", j: "down", k: "up", l: "right" };
|
|
635
|
+
let navKey = key.name;
|
|
636
|
+
if (key.ctrl) {
|
|
637
|
+
if (ctrlNav[key.name]) {
|
|
638
|
+
navKey = ctrlNav[key.name]!;
|
|
639
|
+
} else {
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
646
643
|
|
|
647
644
|
if (key.sequence === "q") {
|
|
648
645
|
deps.onQuit();
|
|
@@ -672,7 +669,7 @@ export function createTui(renderer: CliRenderer, deps: TuiDeps) {
|
|
|
672
669
|
}
|
|
673
670
|
|
|
674
671
|
// ── Navigation & actions ──
|
|
675
|
-
switch (
|
|
672
|
+
switch (navKey) {
|
|
676
673
|
case "down":
|
|
677
674
|
if (cursor < filteredDisplayIndices.length - 1) cursor++;
|
|
678
675
|
break;
|