@wbern/claude-instructions 1.12.0 → 1.14.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/bin/cli.js +144 -97
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -119,7 +119,6 @@ init_esm_shims();
|
|
|
119
119
|
import {
|
|
120
120
|
select,
|
|
121
121
|
text,
|
|
122
|
-
multiselect,
|
|
123
122
|
groupMultiselect,
|
|
124
123
|
isCancel,
|
|
125
124
|
intro,
|
|
@@ -469,16 +468,39 @@ async function checkExistingFiles(outputPath, variant, scope, options) {
|
|
|
469
468
|
if (!destinationPath) {
|
|
470
469
|
return [];
|
|
471
470
|
}
|
|
472
|
-
const
|
|
471
|
+
const allFiles = await fs.readdir(sourcePath);
|
|
472
|
+
const files = options?.commands ? allFiles.filter((f) => options.commands.includes(f)) : allFiles;
|
|
473
473
|
const existingFiles = [];
|
|
474
474
|
const prefix = options?.commandPrefix || "";
|
|
475
|
+
let metadata = null;
|
|
476
|
+
let allowedToolsSet = null;
|
|
477
|
+
if (options?.allowedTools && options.allowedTools.length > 0) {
|
|
478
|
+
metadata = await loadCommandsMetadata(variant || VARIANTS.WITH_BEADS);
|
|
479
|
+
allowedToolsSet = new Set(options.allowedTools);
|
|
480
|
+
}
|
|
475
481
|
for (const file of files) {
|
|
476
482
|
const destFileName = prefix + file;
|
|
477
483
|
const destFilePath = path2.join(destinationPath, destFileName);
|
|
478
484
|
const sourceFilePath = path2.join(sourcePath, file);
|
|
479
485
|
if (await fs.pathExists(destFilePath)) {
|
|
480
486
|
const existingContent = await fs.readFile(destFilePath, "utf-8");
|
|
481
|
-
|
|
487
|
+
let newContent = await fs.readFile(sourceFilePath, "utf-8");
|
|
488
|
+
if (metadata && allowedToolsSet) {
|
|
489
|
+
const commandMetadata = metadata[file];
|
|
490
|
+
const requestedTools = commandMetadata?.["_requested-tools"] || [];
|
|
491
|
+
const toolsForCommand = requestedTools.filter(
|
|
492
|
+
(tool) => allowedToolsSet.has(tool)
|
|
493
|
+
);
|
|
494
|
+
if (toolsForCommand.length > 0) {
|
|
495
|
+
const allowedToolsYaml = `allowed-tools: ${toolsForCommand.join(", ")}`;
|
|
496
|
+
newContent = newContent.replace(
|
|
497
|
+
/^---\n/,
|
|
498
|
+
`---
|
|
499
|
+
${allowedToolsYaml}
|
|
500
|
+
`
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
482
504
|
existingFiles.push({
|
|
483
505
|
filename: destFileName,
|
|
484
506
|
existingContent,
|
|
@@ -618,38 +640,42 @@ async function generateToDirectory(outputPath, variant, scope, options) {
|
|
|
618
640
|
const allFiles = await fs.readdir(sourcePath);
|
|
619
641
|
let files = options?.commands ? allFiles.filter((f) => options.commands.includes(f)) : allFiles;
|
|
620
642
|
if (options?.skipFiles) {
|
|
621
|
-
|
|
643
|
+
const prefix2 = options?.commandPrefix || "";
|
|
644
|
+
files = files.filter((f) => !options.skipFiles.includes(prefix2 + f));
|
|
622
645
|
}
|
|
623
|
-
|
|
646
|
+
const prefix = options?.commandPrefix || "";
|
|
647
|
+
if (options?.commands || options?.skipFiles || options?.commandPrefix) {
|
|
624
648
|
await fs.ensureDir(destinationPath);
|
|
625
649
|
for (const file of files) {
|
|
626
650
|
await fs.copy(
|
|
627
651
|
path2.join(sourcePath, file),
|
|
628
|
-
path2.join(destinationPath, file)
|
|
652
|
+
path2.join(destinationPath, prefix + file)
|
|
629
653
|
);
|
|
630
654
|
}
|
|
631
655
|
} else {
|
|
632
656
|
await fs.copy(sourcePath, destinationPath, {});
|
|
633
657
|
}
|
|
634
658
|
if (options?.allowedTools && options.allowedTools.length > 0) {
|
|
659
|
+
const metadata = await loadCommandsMetadata(variant || VARIANTS.WITH_BEADS);
|
|
660
|
+
const allowedToolsSet = new Set(options.allowedTools);
|
|
635
661
|
for (const file of files) {
|
|
636
|
-
const
|
|
637
|
-
const
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
662
|
+
const commandMetadata = metadata[file];
|
|
663
|
+
const requestedTools = commandMetadata?.["_requested-tools"] || [];
|
|
664
|
+
const toolsForCommand = requestedTools.filter(
|
|
665
|
+
(tool) => allowedToolsSet.has(tool)
|
|
666
|
+
);
|
|
667
|
+
if (toolsForCommand.length > 0) {
|
|
668
|
+
const filePath = path2.join(destinationPath, prefix + file);
|
|
669
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
670
|
+
const allowedToolsYaml = `allowed-tools: ${toolsForCommand.join(", ")}`;
|
|
671
|
+
const modifiedContent = content.replace(
|
|
672
|
+
/^---\n/,
|
|
673
|
+
`---
|
|
642
674
|
${allowedToolsYaml}
|
|
643
675
|
`
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
}
|
|
648
|
-
if (options?.commandPrefix) {
|
|
649
|
-
for (const file of files) {
|
|
650
|
-
const oldPath = path2.join(destinationPath, file);
|
|
651
|
-
const newPath = path2.join(destinationPath, options.commandPrefix + file);
|
|
652
|
-
await fs.rename(oldPath, newPath);
|
|
676
|
+
);
|
|
677
|
+
await fs.writeFile(filePath, modifiedContent);
|
|
678
|
+
}
|
|
653
679
|
}
|
|
654
680
|
}
|
|
655
681
|
let templateInjected = false;
|
|
@@ -696,6 +722,88 @@ ${allowedToolsYaml}
|
|
|
696
722
|
};
|
|
697
723
|
}
|
|
698
724
|
|
|
725
|
+
// scripts/cli-options.ts
|
|
726
|
+
init_esm_shims();
|
|
727
|
+
var CLI_OPTIONS = [
|
|
728
|
+
{
|
|
729
|
+
flag: "--variant",
|
|
730
|
+
key: "variant",
|
|
731
|
+
type: "string",
|
|
732
|
+
description: "Command variant (with-beads, without-beads)",
|
|
733
|
+
example: "--variant=with-beads",
|
|
734
|
+
requiredForNonInteractive: true
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
flag: "--scope",
|
|
738
|
+
key: "scope",
|
|
739
|
+
type: "string",
|
|
740
|
+
description: "Installation scope (project, user)",
|
|
741
|
+
example: "--scope=project",
|
|
742
|
+
requiredForNonInteractive: true
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
flag: "--prefix",
|
|
746
|
+
key: "prefix",
|
|
747
|
+
type: "string",
|
|
748
|
+
description: "Add prefix to command names",
|
|
749
|
+
example: "--prefix=my-",
|
|
750
|
+
requiredForNonInteractive: true
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
flag: "--commands",
|
|
754
|
+
key: "commands",
|
|
755
|
+
type: "array",
|
|
756
|
+
description: "Install only specific commands",
|
|
757
|
+
example: "--commands=commit,red,green"
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
flag: "--skip-template-injection",
|
|
761
|
+
key: "skipTemplateInjection",
|
|
762
|
+
type: "boolean",
|
|
763
|
+
description: "Skip injecting project CLAUDE.md customizations"
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
flag: "--update-existing",
|
|
767
|
+
key: "updateExisting",
|
|
768
|
+
type: "boolean",
|
|
769
|
+
description: "Only update already-installed commands"
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
flag: "--overwrite",
|
|
773
|
+
key: "overwrite",
|
|
774
|
+
type: "boolean",
|
|
775
|
+
description: "Overwrite conflicting files without prompting"
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
flag: "--skip-on-conflict",
|
|
779
|
+
key: "skipOnConflict",
|
|
780
|
+
type: "boolean",
|
|
781
|
+
description: "Skip conflicting files without prompting"
|
|
782
|
+
}
|
|
783
|
+
];
|
|
784
|
+
function generateHelpText() {
|
|
785
|
+
const lines = [
|
|
786
|
+
"Usage: npx @wbern/claude-instructions [options]",
|
|
787
|
+
"",
|
|
788
|
+
"Options:"
|
|
789
|
+
];
|
|
790
|
+
for (const opt of CLI_OPTIONS) {
|
|
791
|
+
const suffix = opt.type === "string" ? "=<value>" : opt.type === "array" ? "=<list>" : "";
|
|
792
|
+
const padding = 28 - (opt.flag.length + suffix.length);
|
|
793
|
+
lines.push(
|
|
794
|
+
` ${opt.flag}${suffix}${" ".repeat(Math.max(1, padding))}${opt.description}`
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
lines.push(" --help, -h Show this help message");
|
|
798
|
+
return lines.join("\n");
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// scripts/tty.ts
|
|
802
|
+
init_esm_shims();
|
|
803
|
+
function isInteractiveTTY() {
|
|
804
|
+
return process.stdout.isTTY === true;
|
|
805
|
+
}
|
|
806
|
+
|
|
699
807
|
// scripts/cli.ts
|
|
700
808
|
var pc = process.env.FORCE_COLOR ? import_picocolors.default.createColors(true) : import_picocolors.default;
|
|
701
809
|
function splitChangeIntoLines(value) {
|
|
@@ -840,6 +948,13 @@ async function main(args) {
|
|
|
840
948
|
}
|
|
841
949
|
}
|
|
842
950
|
} else {
|
|
951
|
+
if (!isInteractiveTTY()) {
|
|
952
|
+
const requiredFlags = CLI_OPTIONS.filter(
|
|
953
|
+
(opt) => opt.requiredForNonInteractive
|
|
954
|
+
).map((opt) => opt.flag).join(", ");
|
|
955
|
+
log.warn(`Non-interactive mode requires ${requiredFlags} arguments`);
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
843
958
|
variant = await select({
|
|
844
959
|
message: "Select variant",
|
|
845
960
|
options: [...VARIANT_OPTIONS]
|
|
@@ -904,9 +1019,11 @@ async function main(args) {
|
|
|
904
1019
|
variant
|
|
905
1020
|
);
|
|
906
1021
|
if (requestedToolsOptions.length > 0) {
|
|
907
|
-
selectedAllowedTools = await
|
|
1022
|
+
selectedAllowedTools = await groupMultiselect({
|
|
908
1023
|
message: "Select allowed tools for commands (optional)",
|
|
909
|
-
options:
|
|
1024
|
+
options: {
|
|
1025
|
+
"All tools": requestedToolsOptions
|
|
1026
|
+
},
|
|
910
1027
|
required: false
|
|
911
1028
|
});
|
|
912
1029
|
if (isCancel(selectedAllowedTools)) {
|
|
@@ -916,7 +1033,8 @@ async function main(args) {
|
|
|
916
1033
|
}
|
|
917
1034
|
const existingFiles = cachedExistingFiles ?? await checkExistingFiles(void 0, variant, scope, {
|
|
918
1035
|
commandPrefix,
|
|
919
|
-
commands: selectedCommands
|
|
1036
|
+
commands: selectedCommands,
|
|
1037
|
+
allowedTools: selectedAllowedTools
|
|
920
1038
|
});
|
|
921
1039
|
const skipFiles = [];
|
|
922
1040
|
if (!args?.overwrite && !args?.skipOnConflict) {
|
|
@@ -999,83 +1117,12 @@ async function main(args) {
|
|
|
999
1117
|
|
|
1000
1118
|
If Claude Code is already running, restart it to pick up the new commands.
|
|
1001
1119
|
|
|
1120
|
+
Try it out: /red clicking submit with empty email shows validation error
|
|
1121
|
+
|
|
1002
1122
|
Happy TDD'ing!`
|
|
1003
1123
|
);
|
|
1004
1124
|
}
|
|
1005
1125
|
|
|
1006
|
-
// scripts/cli-options.ts
|
|
1007
|
-
init_esm_shims();
|
|
1008
|
-
var CLI_OPTIONS = [
|
|
1009
|
-
{
|
|
1010
|
-
flag: "--variant",
|
|
1011
|
-
key: "variant",
|
|
1012
|
-
type: "string",
|
|
1013
|
-
description: "Command variant (with-beads, without-beads)",
|
|
1014
|
-
example: "--variant=with-beads"
|
|
1015
|
-
},
|
|
1016
|
-
{
|
|
1017
|
-
flag: "--scope",
|
|
1018
|
-
key: "scope",
|
|
1019
|
-
type: "string",
|
|
1020
|
-
description: "Installation scope (project, user)",
|
|
1021
|
-
example: "--scope=project"
|
|
1022
|
-
},
|
|
1023
|
-
{
|
|
1024
|
-
flag: "--prefix",
|
|
1025
|
-
key: "prefix",
|
|
1026
|
-
type: "string",
|
|
1027
|
-
description: "Add prefix to command names",
|
|
1028
|
-
example: "--prefix=my-"
|
|
1029
|
-
},
|
|
1030
|
-
{
|
|
1031
|
-
flag: "--commands",
|
|
1032
|
-
key: "commands",
|
|
1033
|
-
type: "array",
|
|
1034
|
-
description: "Install only specific commands",
|
|
1035
|
-
example: "--commands=commit,red,green"
|
|
1036
|
-
},
|
|
1037
|
-
{
|
|
1038
|
-
flag: "--skip-template-injection",
|
|
1039
|
-
key: "skipTemplateInjection",
|
|
1040
|
-
type: "boolean",
|
|
1041
|
-
description: "Skip injecting project CLAUDE.md customizations"
|
|
1042
|
-
},
|
|
1043
|
-
{
|
|
1044
|
-
flag: "--update-existing",
|
|
1045
|
-
key: "updateExisting",
|
|
1046
|
-
type: "boolean",
|
|
1047
|
-
description: "Only update already-installed commands"
|
|
1048
|
-
},
|
|
1049
|
-
{
|
|
1050
|
-
flag: "--overwrite",
|
|
1051
|
-
key: "overwrite",
|
|
1052
|
-
type: "boolean",
|
|
1053
|
-
description: "Overwrite conflicting files without prompting"
|
|
1054
|
-
},
|
|
1055
|
-
{
|
|
1056
|
-
flag: "--skip-on-conflict",
|
|
1057
|
-
key: "skipOnConflict",
|
|
1058
|
-
type: "boolean",
|
|
1059
|
-
description: "Skip conflicting files without prompting"
|
|
1060
|
-
}
|
|
1061
|
-
];
|
|
1062
|
-
function generateHelpText() {
|
|
1063
|
-
const lines = [
|
|
1064
|
-
"Usage: npx @wbern/claude-instructions [options]",
|
|
1065
|
-
"",
|
|
1066
|
-
"Options:"
|
|
1067
|
-
];
|
|
1068
|
-
for (const opt of CLI_OPTIONS) {
|
|
1069
|
-
const suffix = opt.type === "string" ? "=<value>" : opt.type === "array" ? "=<list>" : "";
|
|
1070
|
-
const padding = 28 - (opt.flag.length + suffix.length);
|
|
1071
|
-
lines.push(
|
|
1072
|
-
` ${opt.flag}${suffix}${" ".repeat(Math.max(1, padding))}${opt.description}`
|
|
1073
|
-
);
|
|
1074
|
-
}
|
|
1075
|
-
lines.push(" --help, -h Show this help message");
|
|
1076
|
-
return lines.join("\n");
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
1126
|
// scripts/bin.ts
|
|
1080
1127
|
function parseArgs(argv) {
|
|
1081
1128
|
const args = {};
|