skissue 0.1.18 → 0.1.20
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/README.md +1 -2
- package/dist/entry.js +138 -69
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
<a href="https://www.npmjs.com/package/skissue"><img src="https://img.shields.io/npm/v/skissue?style=flat-square&logo=npm&color=CB3837" alt="npm version"></a>
|
|
19
19
|
<a href="https://www.npmjs.com/package/skissue"><img src="https://img.shields.io/npm/dm/skissue?style=flat-square&logo=npm&color=CB3837" alt="npm downloads"></a>
|
|
20
20
|
<a href="https://github.com/midyan/skill-issue/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow?style=flat-square" alt="License MIT"></a>
|
|
21
|
-
<a href="https://github.com/midyan/skill-issue"><img src="https://img.shields.io/github/stars/midyan/skill-issue?style=flat-square&logo=github&color=8B5CF6" alt="GitHub stars"></a>
|
|
22
21
|
<img src="https://img.shields.io/badge/node-%3E%3D24-brightgreen?style=flat-square&logo=nodedotjs" alt="Node 24+">
|
|
23
22
|
<img src="https://img.shields.io/badge/TypeScript-5.7-blue?style=flat-square&logo=typescript" alt="TypeScript">
|
|
24
23
|
</p>
|
|
@@ -136,7 +135,7 @@ Skills are in `.agents/skills/`. Your agents can find them. Commit the folder. S
|
|
|
136
135
|
|
|
137
136
|
A skill registry is just a Git repo with a specific layout. Here's how to build one from scratch.
|
|
138
137
|
|
|
139
|
-
**Shortcut:** run `npx skissue init-registry` to create `registry.json`, `registry/<
|
|
138
|
+
**Shortcut:** run `npx skissue init-registry` to create `registry.json`, `registry/<skill-id>/SKILL.md` (default skill id `validate-links`), and optionally `git init` in the current directory or a path you choose.
|
|
140
139
|
|
|
141
140
|
### Registry structure
|
|
142
141
|
|
package/dist/entry.js
CHANGED
|
@@ -9,7 +9,7 @@ import { resolve as resolve4 } from "node:path";
|
|
|
9
9
|
// src/commands/init.ts
|
|
10
10
|
import * as p2 from "@clack/prompts";
|
|
11
11
|
import chalk2 from "chalk";
|
|
12
|
-
import { existsSync as existsSync4 } from "node:fs";
|
|
12
|
+
import { existsSync as existsSync4, statSync as statSync3 } from "node:fs";
|
|
13
13
|
import { mkdir as mkdir4 } from "node:fs/promises";
|
|
14
14
|
import { relative, resolve as resolve3 } from "node:path";
|
|
15
15
|
|
|
@@ -357,6 +357,7 @@ function requiredTrimmed(v) {
|
|
|
357
357
|
}
|
|
358
358
|
|
|
359
359
|
// src/commands/init-registry.ts
|
|
360
|
+
var DEFAULT_REGISTRY_SKILL_ID = "validate-links";
|
|
360
361
|
function validateSkillId(raw) {
|
|
361
362
|
const id = raw.trim();
|
|
362
363
|
if (!id) return "Skill id is required";
|
|
@@ -367,6 +368,15 @@ function validateSkillId(raw) {
|
|
|
367
368
|
function validateSkillIdPrompt(v) {
|
|
368
369
|
return validateSkillId(String(v ?? ""));
|
|
369
370
|
}
|
|
371
|
+
function registryDirectoryExists(root) {
|
|
372
|
+
const regDir = join3(root, "registry");
|
|
373
|
+
if (!existsSync3(regDir)) return false;
|
|
374
|
+
try {
|
|
375
|
+
return statSync2(regDir).isDirectory();
|
|
376
|
+
} catch {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
370
380
|
function registryLayoutExists(root) {
|
|
371
381
|
const jsonPath = join3(root, "registry.json");
|
|
372
382
|
const regDir = join3(root, "registry");
|
|
@@ -379,10 +389,23 @@ function registryLayoutExists(root) {
|
|
|
379
389
|
}
|
|
380
390
|
}
|
|
381
391
|
function skillMarkdown(skillId) {
|
|
392
|
+
if (skillId === DEFAULT_REGISTRY_SKILL_ID) {
|
|
393
|
+
return `---
|
|
394
|
+
name: validate-links
|
|
395
|
+
description: Scan markdown for broken relative links across the repo.
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
# validate-links (soft)
|
|
399
|
+
|
|
400
|
+
Use before large doc moves or when CI reports broken links. A **hard** implementation walks \`*.md\` files and checks that \`[text](relative)\` targets resolve.
|
|
401
|
+
|
|
402
|
+
Add a \`hard/\` directory when you want automated checks (for example a small script or \`npm run check:links\` wired to the same rules).
|
|
403
|
+
`;
|
|
404
|
+
}
|
|
382
405
|
const title = skillId.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
383
406
|
return `---
|
|
384
407
|
name: ${skillId}
|
|
385
|
-
description:
|
|
408
|
+
description: Starter skill scaffolded by skissue init-registry. Replace this description.
|
|
386
409
|
---
|
|
387
410
|
|
|
388
411
|
# ${title}
|
|
@@ -391,7 +414,7 @@ A minimal starter skill for your registry.
|
|
|
391
414
|
|
|
392
415
|
## When to use
|
|
393
416
|
|
|
394
|
-
|
|
417
|
+
Describe when an agent should apply this skill (customize this section).
|
|
395
418
|
|
|
396
419
|
## Instructions
|
|
397
420
|
|
|
@@ -448,8 +471,8 @@ async function promptMinimalRegistryScaffold(root) {
|
|
|
448
471
|
}
|
|
449
472
|
}
|
|
450
473
|
const idRaw = await p.text({
|
|
451
|
-
message: "
|
|
452
|
-
initialValue:
|
|
474
|
+
message: "Skill id (folder name under registry/)",
|
|
475
|
+
initialValue: DEFAULT_REGISTRY_SKILL_ID,
|
|
453
476
|
validate: validateSkillIdPrompt
|
|
454
477
|
});
|
|
455
478
|
if (p.isCancel(idRaw)) {
|
|
@@ -574,6 +597,59 @@ Transport: ${mode}
|
|
|
574
597
|
Branch: ${r.branch}
|
|
575
598
|
skillsRoot: ${cfg.skillsRoot}`;
|
|
576
599
|
}
|
|
600
|
+
async function runLocalBootstrapFlow(cwd, resolvedRoot) {
|
|
601
|
+
const atConsumerRoot = resolvedRoot === resolve3(cwd);
|
|
602
|
+
if (atConsumerRoot && !registryLayoutExists(resolvedRoot)) {
|
|
603
|
+
const okRoot = await p2.confirm({
|
|
604
|
+
message: "This adds registry.json and registry/ at your project root. Continue?",
|
|
605
|
+
initialValue: true
|
|
606
|
+
});
|
|
607
|
+
if (p2.isCancel(okRoot) || !okRoot) {
|
|
608
|
+
p2.cancel("Aborted.");
|
|
609
|
+
return { ok: false, exitCode: 0 };
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
const prompted = await promptMinimalRegistryScaffold(resolvedRoot);
|
|
613
|
+
if (!prompted) {
|
|
614
|
+
p2.cancel("Aborted. No files were changed.");
|
|
615
|
+
return { ok: false, exitCode: 0 };
|
|
616
|
+
}
|
|
617
|
+
let result;
|
|
618
|
+
try {
|
|
619
|
+
result = await scaffoldMinimalRegistry({
|
|
620
|
+
root: resolvedRoot,
|
|
621
|
+
skillId: prompted.skillId,
|
|
622
|
+
runGitInit: prompted.runGitInit
|
|
623
|
+
});
|
|
624
|
+
} catch (e) {
|
|
625
|
+
p2.cancel(e instanceof Error ? e.message : String(e));
|
|
626
|
+
return { ok: false, exitCode: 1 };
|
|
627
|
+
}
|
|
628
|
+
if (!prompted.hadGit && !result.gitInitRan) {
|
|
629
|
+
p2.note(
|
|
630
|
+
"This folder is not a git repository. Run `git init` before using it as a local skissue registry.",
|
|
631
|
+
chalk2.yellow("Heads up")
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
const branchDefault = await gitBranchShowCurrent(resolvedRoot) ?? "main";
|
|
635
|
+
const branch = await p2.text({
|
|
636
|
+
message: "Branch label (stored in config; installs use git HEAD from that directory)",
|
|
637
|
+
initialValue: branchDefault,
|
|
638
|
+
validate: requiredTrimmed
|
|
639
|
+
});
|
|
640
|
+
if (p2.isCancel(branch)) {
|
|
641
|
+
p2.cancel("Aborted.");
|
|
642
|
+
return { ok: false, exitCode: 0 };
|
|
643
|
+
}
|
|
644
|
+
const cfg = ConfigSchema.parse({
|
|
645
|
+
registry: {
|
|
646
|
+
path: localRegistryPathForConfig(cwd, resolvedRoot),
|
|
647
|
+
branch: String(branch).trim()
|
|
648
|
+
},
|
|
649
|
+
skillsRoot: ".agents/skills"
|
|
650
|
+
});
|
|
651
|
+
return { ok: true, cfg, bootstrappedSkillId: result.skillId };
|
|
652
|
+
}
|
|
577
653
|
async function runInit(cwd) {
|
|
578
654
|
p2.intro(chalk2.bold("skissue init"));
|
|
579
655
|
const existingPath = configPath(cwd);
|
|
@@ -646,23 +722,57 @@ async function runInit(cwd) {
|
|
|
646
722
|
process.exit(0);
|
|
647
723
|
}
|
|
648
724
|
const resolvedExisting = resolve3(cwd, String(regPath).trim());
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
p2.
|
|
657
|
-
|
|
725
|
+
if (!existsSync4(resolvedExisting) || !statSync3(resolvedExisting).isDirectory()) {
|
|
726
|
+
p2.cancel(`Path does not exist or is not a directory: ${resolvedExisting}`);
|
|
727
|
+
process.exitCode = 1;
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
if (!registryDirectoryExists(resolvedExisting)) {
|
|
731
|
+
const pathLabel = String(regPath).trim() || ".";
|
|
732
|
+
const bootstrapHere = await p2.confirm({
|
|
733
|
+
message: `${chalk2.yellow("No registry/ directory")} at ${chalk2.cyan(pathLabel)}. Bootstrap a minimal sample registry (registry.json + registry/<skill>) here?`,
|
|
734
|
+
initialValue: true
|
|
735
|
+
});
|
|
736
|
+
if (p2.isCancel(bootstrapHere)) {
|
|
737
|
+
p2.cancel("Aborted.");
|
|
738
|
+
process.exit(0);
|
|
739
|
+
}
|
|
740
|
+
if (!bootstrapHere) {
|
|
741
|
+
p2.cancel(
|
|
742
|
+
"Aborted. Create a registry/ folder in that directory, or run skissue init and choose Bootstrap a minimal sample registry."
|
|
743
|
+
);
|
|
744
|
+
process.exit(0);
|
|
745
|
+
}
|
|
746
|
+
const outcome = await runLocalBootstrapFlow(cwd, resolvedExisting);
|
|
747
|
+
if (!outcome.ok) {
|
|
748
|
+
if (outcome.exitCode === 1) {
|
|
749
|
+
process.exitCode = 1;
|
|
750
|
+
} else {
|
|
751
|
+
process.exit(0);
|
|
752
|
+
}
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
cfg = outcome.cfg;
|
|
756
|
+
bootstrappedSkillId = outcome.bootstrappedSkillId;
|
|
757
|
+
} else {
|
|
758
|
+
const branchDefault = await gitBranchShowCurrent(resolvedExisting) ?? "main";
|
|
759
|
+
const branch = await p2.text({
|
|
760
|
+
message: "Branch label (stored in config; installs use git HEAD from that directory)",
|
|
761
|
+
initialValue: branchDefault,
|
|
762
|
+
validate: requiredTrimmed
|
|
763
|
+
});
|
|
764
|
+
if (p2.isCancel(branch)) {
|
|
765
|
+
p2.cancel("Aborted.");
|
|
766
|
+
process.exit(0);
|
|
767
|
+
}
|
|
768
|
+
cfg = ConfigSchema.parse({
|
|
769
|
+
registry: {
|
|
770
|
+
path: String(regPath).trim(),
|
|
771
|
+
branch: String(branch).trim()
|
|
772
|
+
},
|
|
773
|
+
skillsRoot: ".agents/skills"
|
|
774
|
+
});
|
|
658
775
|
}
|
|
659
|
-
cfg = ConfigSchema.parse({
|
|
660
|
-
registry: {
|
|
661
|
-
path: String(regPath).trim(),
|
|
662
|
-
branch: String(branch).trim()
|
|
663
|
-
},
|
|
664
|
-
skillsRoot: ".agents/skills"
|
|
665
|
-
});
|
|
666
776
|
} else {
|
|
667
777
|
const pathRaw = await p2.text({
|
|
668
778
|
message: "Path for the new registry root (relative to this project or absolute)",
|
|
@@ -675,58 +785,17 @@ async function runInit(cwd) {
|
|
|
675
785
|
process.exit(0);
|
|
676
786
|
}
|
|
677
787
|
const resolvedRoot = resolve3(cwd, String(pathRaw).trim());
|
|
678
|
-
const
|
|
679
|
-
if (
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
});
|
|
684
|
-
if (p2.isCancel(okRoot) || !okRoot) {
|
|
685
|
-
p2.cancel("Aborted.");
|
|
788
|
+
const outcome = await runLocalBootstrapFlow(cwd, resolvedRoot);
|
|
789
|
+
if (!outcome.ok) {
|
|
790
|
+
if (outcome.exitCode === 1) {
|
|
791
|
+
process.exitCode = 1;
|
|
792
|
+
} else {
|
|
686
793
|
process.exit(0);
|
|
687
794
|
}
|
|
688
|
-
}
|
|
689
|
-
const prompted = await promptMinimalRegistryScaffold(resolvedRoot);
|
|
690
|
-
if (!prompted) {
|
|
691
|
-
p2.cancel("Aborted. No files were changed.");
|
|
692
|
-
process.exit(0);
|
|
693
|
-
}
|
|
694
|
-
let result;
|
|
695
|
-
try {
|
|
696
|
-
result = await scaffoldMinimalRegistry({
|
|
697
|
-
root: resolvedRoot,
|
|
698
|
-
skillId: prompted.skillId,
|
|
699
|
-
runGitInit: prompted.runGitInit
|
|
700
|
-
});
|
|
701
|
-
} catch (e) {
|
|
702
|
-
p2.cancel(e instanceof Error ? e.message : String(e));
|
|
703
|
-
process.exitCode = 1;
|
|
704
795
|
return;
|
|
705
796
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
p2.note(
|
|
709
|
-
"This folder is not a git repository. Run `git init` before using it as a local skissue registry.",
|
|
710
|
-
chalk2.yellow("Heads up")
|
|
711
|
-
);
|
|
712
|
-
}
|
|
713
|
-
const branchDefault = await gitBranchShowCurrent(resolvedRoot) ?? "main";
|
|
714
|
-
const branch = await p2.text({
|
|
715
|
-
message: "Branch label (stored in config; installs use git HEAD from that directory)",
|
|
716
|
-
initialValue: branchDefault,
|
|
717
|
-
validate: requiredTrimmed
|
|
718
|
-
});
|
|
719
|
-
if (p2.isCancel(branch)) {
|
|
720
|
-
p2.cancel("Aborted.");
|
|
721
|
-
process.exit(0);
|
|
722
|
-
}
|
|
723
|
-
cfg = ConfigSchema.parse({
|
|
724
|
-
registry: {
|
|
725
|
-
path: localRegistryPathForConfig(cwd, resolvedRoot),
|
|
726
|
-
branch: String(branch).trim()
|
|
727
|
-
},
|
|
728
|
-
skillsRoot: ".agents/skills"
|
|
729
|
-
});
|
|
797
|
+
cfg = outcome.cfg;
|
|
798
|
+
bootstrappedSkillId = outcome.bootstrappedSkillId;
|
|
730
799
|
}
|
|
731
800
|
} else {
|
|
732
801
|
const owner = await p2.text({
|