skissue 0.1.17 → 0.1.19
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/dist/entry.js +147 -78
- package/package.json +1 -1
- package/scripts/ensure-local-bin.mjs +21 -15
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
|
|
|
@@ -350,6 +350,13 @@ import chalk from "chalk";
|
|
|
350
350
|
import { existsSync as existsSync3, statSync as statSync2 } from "node:fs";
|
|
351
351
|
import { mkdir as mkdir3, writeFile as writeFile2 } from "node:fs/promises";
|
|
352
352
|
import { join as join3, resolve as resolve2 } from "node:path";
|
|
353
|
+
|
|
354
|
+
// src/commands/prompt-validators.ts
|
|
355
|
+
function requiredTrimmed(v) {
|
|
356
|
+
return String(v ?? "").trim() ? void 0 : "Required";
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// src/commands/init-registry.ts
|
|
353
360
|
function validateSkillId(raw) {
|
|
354
361
|
const id = raw.trim();
|
|
355
362
|
if (!id) return "Skill id is required";
|
|
@@ -357,6 +364,18 @@ function validateSkillId(raw) {
|
|
|
357
364
|
if (id === "." || id === ".." || id.includes("..")) return "Invalid skill id";
|
|
358
365
|
return void 0;
|
|
359
366
|
}
|
|
367
|
+
function validateSkillIdPrompt(v) {
|
|
368
|
+
return validateSkillId(String(v ?? ""));
|
|
369
|
+
}
|
|
370
|
+
function registryDirectoryExists(root) {
|
|
371
|
+
const regDir = join3(root, "registry");
|
|
372
|
+
if (!existsSync3(regDir)) return false;
|
|
373
|
+
try {
|
|
374
|
+
return statSync2(regDir).isDirectory();
|
|
375
|
+
} catch {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
360
379
|
function registryLayoutExists(root) {
|
|
361
380
|
const jsonPath = join3(root, "registry.json");
|
|
362
381
|
const regDir = join3(root, "registry");
|
|
@@ -440,7 +459,7 @@ async function promptMinimalRegistryScaffold(root) {
|
|
|
440
459
|
const idRaw = await p.text({
|
|
441
460
|
message: "Sample skill id (folder name under registry/)",
|
|
442
461
|
initialValue: "sample-skill",
|
|
443
|
-
validate:
|
|
462
|
+
validate: validateSkillIdPrompt
|
|
444
463
|
});
|
|
445
464
|
if (p.isCancel(idRaw)) {
|
|
446
465
|
return null;
|
|
@@ -494,6 +513,7 @@ async function runInitRegistry(cwd) {
|
|
|
494
513
|
if (p.isCancel(where)) {
|
|
495
514
|
p.cancel("Aborted.");
|
|
496
515
|
process.exit(0);
|
|
516
|
+
return;
|
|
497
517
|
}
|
|
498
518
|
let root;
|
|
499
519
|
if (where === "here") {
|
|
@@ -502,11 +522,12 @@ async function runInitRegistry(cwd) {
|
|
|
502
522
|
const raw = await p.text({
|
|
503
523
|
message: "Path to registry root (absolute or relative to current directory)",
|
|
504
524
|
placeholder: "../my-skill-registry",
|
|
505
|
-
validate:
|
|
525
|
+
validate: requiredTrimmed
|
|
506
526
|
});
|
|
507
527
|
if (p.isCancel(raw)) {
|
|
508
528
|
p.cancel("Aborted.");
|
|
509
529
|
process.exit(0);
|
|
530
|
+
return;
|
|
510
531
|
}
|
|
511
532
|
root = resolve2(cwd, String(raw).trim());
|
|
512
533
|
}
|
|
@@ -514,6 +535,7 @@ async function runInitRegistry(cwd) {
|
|
|
514
535
|
if (!prompted) {
|
|
515
536
|
p.cancel("Aborted. No files were changed.");
|
|
516
537
|
process.exit(0);
|
|
538
|
+
return;
|
|
517
539
|
}
|
|
518
540
|
const { skillId, runGitInit, hadGit } = prompted;
|
|
519
541
|
try {
|
|
@@ -561,6 +583,59 @@ Transport: ${mode}
|
|
|
561
583
|
Branch: ${r.branch}
|
|
562
584
|
skillsRoot: ${cfg.skillsRoot}`;
|
|
563
585
|
}
|
|
586
|
+
async function runLocalBootstrapFlow(cwd, resolvedRoot) {
|
|
587
|
+
const atConsumerRoot = resolvedRoot === resolve3(cwd);
|
|
588
|
+
if (atConsumerRoot && !registryLayoutExists(resolvedRoot)) {
|
|
589
|
+
const okRoot = await p2.confirm({
|
|
590
|
+
message: "This adds registry.json and registry/ at your project root. Continue?",
|
|
591
|
+
initialValue: false
|
|
592
|
+
});
|
|
593
|
+
if (p2.isCancel(okRoot) || !okRoot) {
|
|
594
|
+
p2.cancel("Aborted.");
|
|
595
|
+
return { ok: false, exitCode: 0 };
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
const prompted = await promptMinimalRegistryScaffold(resolvedRoot);
|
|
599
|
+
if (!prompted) {
|
|
600
|
+
p2.cancel("Aborted. No files were changed.");
|
|
601
|
+
return { ok: false, exitCode: 0 };
|
|
602
|
+
}
|
|
603
|
+
let result;
|
|
604
|
+
try {
|
|
605
|
+
result = await scaffoldMinimalRegistry({
|
|
606
|
+
root: resolvedRoot,
|
|
607
|
+
skillId: prompted.skillId,
|
|
608
|
+
runGitInit: prompted.runGitInit
|
|
609
|
+
});
|
|
610
|
+
} catch (e) {
|
|
611
|
+
p2.cancel(e instanceof Error ? e.message : String(e));
|
|
612
|
+
return { ok: false, exitCode: 1 };
|
|
613
|
+
}
|
|
614
|
+
if (!prompted.hadGit && !result.gitInitRan) {
|
|
615
|
+
p2.note(
|
|
616
|
+
"This folder is not a git repository. Run `git init` before using it as a local skissue registry.",
|
|
617
|
+
chalk2.yellow("Heads up")
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
const branchDefault = await gitBranchShowCurrent(resolvedRoot) ?? "main";
|
|
621
|
+
const branch = await p2.text({
|
|
622
|
+
message: "Branch label (stored in config; installs use git HEAD from that directory)",
|
|
623
|
+
initialValue: branchDefault,
|
|
624
|
+
validate: requiredTrimmed
|
|
625
|
+
});
|
|
626
|
+
if (p2.isCancel(branch)) {
|
|
627
|
+
p2.cancel("Aborted.");
|
|
628
|
+
return { ok: false, exitCode: 0 };
|
|
629
|
+
}
|
|
630
|
+
const cfg = ConfigSchema.parse({
|
|
631
|
+
registry: {
|
|
632
|
+
path: localRegistryPathForConfig(cwd, resolvedRoot),
|
|
633
|
+
branch: String(branch).trim()
|
|
634
|
+
},
|
|
635
|
+
skillsRoot: ".agents/skills"
|
|
636
|
+
});
|
|
637
|
+
return { ok: true, cfg, bootstrappedSkillId: result.skillId };
|
|
638
|
+
}
|
|
564
639
|
async function runInit(cwd) {
|
|
565
640
|
p2.intro(chalk2.bold("skissue init"));
|
|
566
641
|
const existingPath = configPath(cwd);
|
|
@@ -626,100 +701,93 @@ async function runInit(cwd) {
|
|
|
626
701
|
message: "Path to registry repo root (relative to this project or absolute). Must contain registry/ and be a git repository.",
|
|
627
702
|
placeholder: ".",
|
|
628
703
|
initialValue: ".",
|
|
629
|
-
validate:
|
|
704
|
+
validate: requiredTrimmed
|
|
630
705
|
});
|
|
631
706
|
if (p2.isCancel(regPath)) {
|
|
632
707
|
p2.cancel("Aborted.");
|
|
633
708
|
process.exit(0);
|
|
634
709
|
}
|
|
635
710
|
const resolvedExisting = resolve3(cwd, String(regPath).trim());
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
p2.
|
|
644
|
-
|
|
711
|
+
if (!existsSync4(resolvedExisting) || !statSync3(resolvedExisting).isDirectory()) {
|
|
712
|
+
p2.cancel(`Path does not exist or is not a directory: ${resolvedExisting}`);
|
|
713
|
+
process.exitCode = 1;
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
if (!registryDirectoryExists(resolvedExisting)) {
|
|
717
|
+
const pathLabel = String(regPath).trim() || ".";
|
|
718
|
+
const bootstrapHere = await p2.confirm({
|
|
719
|
+
message: `${chalk2.yellow("No registry/ directory")} at ${chalk2.cyan(pathLabel)}. Bootstrap a minimal sample registry (registry.json + registry/<skill>) here?`,
|
|
720
|
+
initialValue: true
|
|
721
|
+
});
|
|
722
|
+
if (p2.isCancel(bootstrapHere)) {
|
|
723
|
+
p2.cancel("Aborted.");
|
|
724
|
+
process.exit(0);
|
|
725
|
+
}
|
|
726
|
+
if (!bootstrapHere) {
|
|
727
|
+
p2.cancel(
|
|
728
|
+
"Aborted. Create a registry/ folder in that directory, or run skissue init and choose Bootstrap a minimal sample registry."
|
|
729
|
+
);
|
|
730
|
+
process.exit(0);
|
|
731
|
+
}
|
|
732
|
+
const outcome = await runLocalBootstrapFlow(cwd, resolvedExisting);
|
|
733
|
+
if (!outcome.ok) {
|
|
734
|
+
if (outcome.exitCode === 1) {
|
|
735
|
+
process.exitCode = 1;
|
|
736
|
+
} else {
|
|
737
|
+
process.exit(0);
|
|
738
|
+
}
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
cfg = outcome.cfg;
|
|
742
|
+
bootstrappedSkillId = outcome.bootstrappedSkillId;
|
|
743
|
+
} else {
|
|
744
|
+
const branchDefault = await gitBranchShowCurrent(resolvedExisting) ?? "main";
|
|
745
|
+
const branch = await p2.text({
|
|
746
|
+
message: "Branch label (stored in config; installs use git HEAD from that directory)",
|
|
747
|
+
initialValue: branchDefault,
|
|
748
|
+
validate: requiredTrimmed
|
|
749
|
+
});
|
|
750
|
+
if (p2.isCancel(branch)) {
|
|
751
|
+
p2.cancel("Aborted.");
|
|
752
|
+
process.exit(0);
|
|
753
|
+
}
|
|
754
|
+
cfg = ConfigSchema.parse({
|
|
755
|
+
registry: {
|
|
756
|
+
path: String(regPath).trim(),
|
|
757
|
+
branch: String(branch).trim()
|
|
758
|
+
},
|
|
759
|
+
skillsRoot: ".agents/skills"
|
|
760
|
+
});
|
|
645
761
|
}
|
|
646
|
-
cfg = ConfigSchema.parse({
|
|
647
|
-
registry: {
|
|
648
|
-
path: String(regPath).trim(),
|
|
649
|
-
branch: String(branch).trim()
|
|
650
|
-
},
|
|
651
|
-
skillsRoot: ".agents/skills"
|
|
652
|
-
});
|
|
653
762
|
} else {
|
|
654
763
|
const pathRaw = await p2.text({
|
|
655
764
|
message: "Path for the new registry root (relative to this project or absolute)",
|
|
656
765
|
placeholder: "./skill-registry",
|
|
657
766
|
initialValue: "./skill-registry",
|
|
658
|
-
validate:
|
|
767
|
+
validate: requiredTrimmed
|
|
659
768
|
});
|
|
660
769
|
if (p2.isCancel(pathRaw)) {
|
|
661
770
|
p2.cancel("Aborted.");
|
|
662
771
|
process.exit(0);
|
|
663
772
|
}
|
|
664
773
|
const resolvedRoot = resolve3(cwd, String(pathRaw).trim());
|
|
665
|
-
const
|
|
666
|
-
if (
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
});
|
|
671
|
-
if (p2.isCancel(okRoot) || !okRoot) {
|
|
672
|
-
p2.cancel("Aborted.");
|
|
774
|
+
const outcome = await runLocalBootstrapFlow(cwd, resolvedRoot);
|
|
775
|
+
if (!outcome.ok) {
|
|
776
|
+
if (outcome.exitCode === 1) {
|
|
777
|
+
process.exitCode = 1;
|
|
778
|
+
} else {
|
|
673
779
|
process.exit(0);
|
|
674
780
|
}
|
|
675
|
-
}
|
|
676
|
-
const prompted = await promptMinimalRegistryScaffold(resolvedRoot);
|
|
677
|
-
if (!prompted) {
|
|
678
|
-
p2.cancel("Aborted. No files were changed.");
|
|
679
|
-
process.exit(0);
|
|
680
|
-
}
|
|
681
|
-
let result;
|
|
682
|
-
try {
|
|
683
|
-
result = await scaffoldMinimalRegistry({
|
|
684
|
-
root: resolvedRoot,
|
|
685
|
-
skillId: prompted.skillId,
|
|
686
|
-
runGitInit: prompted.runGitInit
|
|
687
|
-
});
|
|
688
|
-
} catch (e) {
|
|
689
|
-
p2.cancel(e instanceof Error ? e.message : String(e));
|
|
690
|
-
process.exitCode = 1;
|
|
691
781
|
return;
|
|
692
782
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
p2.note(
|
|
696
|
-
"This folder is not a git repository. Run `git init` before using it as a local skissue registry.",
|
|
697
|
-
chalk2.yellow("Heads up")
|
|
698
|
-
);
|
|
699
|
-
}
|
|
700
|
-
const branchDefault = await gitBranchShowCurrent(resolvedRoot) ?? "main";
|
|
701
|
-
const branch = await p2.text({
|
|
702
|
-
message: "Branch label (stored in config; installs use git HEAD from that directory)",
|
|
703
|
-
initialValue: branchDefault,
|
|
704
|
-
validate: (v) => v?.trim() ? void 0 : "Required"
|
|
705
|
-
});
|
|
706
|
-
if (p2.isCancel(branch)) {
|
|
707
|
-
p2.cancel("Aborted.");
|
|
708
|
-
process.exit(0);
|
|
709
|
-
}
|
|
710
|
-
cfg = ConfigSchema.parse({
|
|
711
|
-
registry: {
|
|
712
|
-
path: localRegistryPathForConfig(cwd, resolvedRoot),
|
|
713
|
-
branch: String(branch).trim()
|
|
714
|
-
},
|
|
715
|
-
skillsRoot: ".agents/skills"
|
|
716
|
-
});
|
|
783
|
+
cfg = outcome.cfg;
|
|
784
|
+
bootstrappedSkillId = outcome.bootstrappedSkillId;
|
|
717
785
|
}
|
|
718
786
|
} else {
|
|
719
787
|
const owner = await p2.text({
|
|
720
788
|
message: "GitHub registry owner (org or user)",
|
|
721
789
|
placeholder: "acme",
|
|
722
|
-
validate:
|
|
790
|
+
validate: requiredTrimmed
|
|
723
791
|
});
|
|
724
792
|
if (p2.isCancel(owner)) {
|
|
725
793
|
p2.cancel("Aborted.");
|
|
@@ -728,7 +796,7 @@ async function runInit(cwd) {
|
|
|
728
796
|
const repo = await p2.text({
|
|
729
797
|
message: "Registry repository name",
|
|
730
798
|
placeholder: "skill-registry",
|
|
731
|
-
validate:
|
|
799
|
+
validate: requiredTrimmed
|
|
732
800
|
});
|
|
733
801
|
if (p2.isCancel(repo)) {
|
|
734
802
|
p2.cancel("Aborted.");
|
|
@@ -737,7 +805,7 @@ async function runInit(cwd) {
|
|
|
737
805
|
const branch = await p2.text({
|
|
738
806
|
message: "Branch to track",
|
|
739
807
|
initialValue: "main",
|
|
740
|
-
validate:
|
|
808
|
+
validate: requiredTrimmed
|
|
741
809
|
});
|
|
742
810
|
if (p2.isCancel(branch)) {
|
|
743
811
|
p2.cancel("Aborted.");
|
|
@@ -755,7 +823,7 @@ async function runInit(cwd) {
|
|
|
755
823
|
const skillsRoot = await p2.text({
|
|
756
824
|
message: "Install skills under (relative to project root)",
|
|
757
825
|
initialValue: cfg.skillsRoot,
|
|
758
|
-
validate:
|
|
826
|
+
validate: requiredTrimmed
|
|
759
827
|
});
|
|
760
828
|
if (p2.isCancel(skillsRoot)) {
|
|
761
829
|
p2.cancel("Aborted.");
|
|
@@ -1054,7 +1122,7 @@ async function runUpdate(cwd, skillId) {
|
|
|
1054
1122
|
// src/commands/manage.ts
|
|
1055
1123
|
import * as p3 from "@clack/prompts";
|
|
1056
1124
|
import chalk9 from "chalk";
|
|
1057
|
-
import
|
|
1125
|
+
import { emitKeypressEvents } from "node:readline";
|
|
1058
1126
|
|
|
1059
1127
|
// src/commands/banner.ts
|
|
1060
1128
|
import chalk8 from "chalk";
|
|
@@ -1156,7 +1224,7 @@ async function promptMainMenuSelect(availableCount, installedCount) {
|
|
|
1156
1224
|
};
|
|
1157
1225
|
const { stdin } = process;
|
|
1158
1226
|
if (stdin.isTTY) {
|
|
1159
|
-
|
|
1227
|
+
emitKeypressEvents(stdin);
|
|
1160
1228
|
stdin.prependListener("keypress", onCtrlC);
|
|
1161
1229
|
}
|
|
1162
1230
|
try {
|
|
@@ -1461,8 +1529,9 @@ async function runDoctor(cwd) {
|
|
|
1461
1529
|
process.exitCode = 1;
|
|
1462
1530
|
return;
|
|
1463
1531
|
}
|
|
1464
|
-
|
|
1465
|
-
|
|
1532
|
+
const hint = authHint(config);
|
|
1533
|
+
if (hint) {
|
|
1534
|
+
console.log(chalk10.dim(`\u2022 ${hint}`));
|
|
1466
1535
|
}
|
|
1467
1536
|
try {
|
|
1468
1537
|
const { path: repoPath, head } = await ensureRegistryCheckout(cwd, config);
|
|
@@ -1545,7 +1614,7 @@ program.command("update").description("Re-fetch and overwrite installed skill(s)
|
|
|
1545
1614
|
process.exitCode = 1;
|
|
1546
1615
|
}
|
|
1547
1616
|
});
|
|
1548
|
-
program.command("doctor").description("Check Node, config, and sync registry checkout").option("-C, --cwd <path>", "Project root"
|
|
1617
|
+
program.command("doctor").description("Check Node, config, and sync registry checkout").option("-C, --cwd <path>", "Project root").action(async (opts) => {
|
|
1549
1618
|
const cwd = resolve4(opts.cwd ?? process.cwd());
|
|
1550
1619
|
try {
|
|
1551
1620
|
await runDoctor(cwd);
|
package/package.json
CHANGED
|
@@ -10,23 +10,29 @@ import { chmodSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
|
10
10
|
import { dirname, join, sep } from "node:path";
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} scriptPath Absolute path to this file (or a test fixture path with the same layout).
|
|
15
|
+
*/
|
|
16
|
+
export function runEnsureLocalBin(scriptPath) {
|
|
17
|
+
if (scriptPath.split(sep).includes("node_modules")) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
17
20
|
|
|
18
|
-
const root = dirname(dirname(scriptPath));
|
|
19
|
-
const dist = join(root, "dist", "entry.js");
|
|
20
|
-
const binDir = join(root, "node_modules", ".bin");
|
|
21
|
-
const out = join(binDir, "skissue");
|
|
21
|
+
const root = dirname(dirname(scriptPath));
|
|
22
|
+
const dist = join(root, "dist", "entry.js");
|
|
23
|
+
const binDir = join(root, "node_modules", ".bin");
|
|
24
|
+
const out = join(binDir, "skissue");
|
|
22
25
|
|
|
23
|
-
if (!existsSync(dist)) {
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
+
if (!existsSync(dist)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
26
29
|
|
|
27
|
-
mkdirSync(binDir, { recursive: true });
|
|
28
|
-
const content = `#!/usr/bin/env sh
|
|
30
|
+
mkdirSync(binDir, { recursive: true });
|
|
31
|
+
const content = `#!/usr/bin/env sh
|
|
29
32
|
exec node "${dist}" "$@"
|
|
30
33
|
`;
|
|
31
|
-
writeFileSync(out, content, { mode: 0o755 });
|
|
32
|
-
chmodSync(out, 0o755);
|
|
34
|
+
writeFileSync(out, content, { mode: 0o755 });
|
|
35
|
+
chmodSync(out, 0o755);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
runEnsureLocalBin(fileURLToPath(import.meta.url));
|