@dealdeploy/skl 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/lib.test.ts +62 -0
  2. package/lib.ts +15 -1
  3. package/package.json +1 -1
  4. package/tui.ts +9 -1
package/lib.test.ts CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  findSkillEntries,
17
17
  parseRepoArg,
18
18
  buildAddArgs,
19
+ readSkillFrontmatterName,
19
20
  detectProjectAgents,
20
21
  parseSkillsListOutput,
21
22
  planUpdates,
@@ -326,6 +327,67 @@ describe("buildAddArgs", () => {
326
327
  const args = buildAddArgs("/my/catalog", "custom-skill", true);
327
328
  expect(args).toEqual(["add", "/my/catalog/custom-skill", "-y", "-g"]);
328
329
  });
330
+
331
+ test("uses SKILL.md frontmatter name for --skill flag when it differs from dir name", () => {
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
+
339
+ const entry: LockEntry = {
340
+ source: "owner/repo",
341
+ sourceUrl: "https://github.com/owner/repo",
342
+ skillPath: "skills/code-review",
343
+ treeSHA: "abc",
344
+ addedAt: "2026-01-01",
345
+ };
346
+ addToLock("code-review", entry);
347
+
348
+ const args = buildAddArgs(catalog, "code-review", true);
349
+ expect(args).toContain("--skill");
350
+ expect(args).toContain("code-review:code-review");
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();
390
+ });
329
391
  });
330
392
 
331
393
  // ── detectProjectAgents ─────────────────────────────────────────────
package/lib.ts CHANGED
@@ -160,6 +160,19 @@ export function detectProjectAgents(cwd: string): string[] {
160
160
  return agents;
161
161
  }
162
162
 
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
+
163
176
  /** Build npx skills add args for a skill. */
164
177
  export function buildAddArgs(
165
178
  catalogPath: string,
@@ -172,7 +185,8 @@ export function buildAddArgs(
172
185
  ? ["--agent", ...projectAgents]
173
186
  : [];
174
187
  if (lockEntry) {
175
- const args = ["add", lockEntry.sourceUrl, "--skill", name, "-y", ...agentArgs];
188
+ const skillName = readSkillFrontmatterName(catalogPath, name) ?? name;
189
+ const args = ["add", lockEntry.sourceUrl, "--skill", skillName, "-y", ...agentArgs];
176
190
  if (isGlobal) args.push("-g");
177
191
  return args;
178
192
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dealdeploy/skl",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "TUI skill manager for Claude Code agents",
5
5
  "module": "index.ts",
6
6
  "bin": {
package/tui.ts CHANGED
@@ -239,6 +239,14 @@ 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
+ );
242
250
 
243
251
  for (let di = 0; di < displayItems.length; di++) {
244
252
  const item = displayItems[di]!;
@@ -490,7 +498,7 @@ export function createTui(renderer: CliRenderer, deps: TuiDeps) {
490
498
  if (rows[item.skillIndex]!.row.visible) visibleBefore++;
491
499
  }
492
500
  }
493
- scrollBox.scrollTo(Math.max(0, visibleBefore + 1 - 2));
501
+ scrollBox.scrollTo(Math.max(0, visibleBefore + 2 - 2));
494
502
  }
495
503
  }
496
504