@wbern/claude-instructions 1.21.0 → 2.0.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 (79) hide show
  1. package/README.md +4 -2
  2. package/bin/cli.js +329 -192
  3. package/package.json +2 -3
  4. package/src/README.md +279 -0
  5. package/src/fragments/aaa-pattern.md +7 -0
  6. package/src/fragments/beads-awareness.md +1 -0
  7. package/src/fragments/beads-integration.md +8 -0
  8. package/{downloads/without-beads/commit.md → src/fragments/commit-process.md} +0 -17
  9. package/src/fragments/consistency-check.md +1 -0
  10. package/src/fragments/discovery-phase.md +22 -0
  11. package/src/fragments/fallback-arguments-beads.md +3 -0
  12. package/src/fragments/fallback-arguments.md +1 -0
  13. package/src/fragments/fullwidth-dollar-note.md +1 -0
  14. package/src/fragments/gap-beads.md +1 -0
  15. package/src/fragments/git-host-detection.md +19 -0
  16. package/src/fragments/github-issue-fetch.md +10 -0
  17. package/src/fragments/peeping-tom-warning.md +9 -0
  18. package/src/fragments/plan-beads-context-hint.md +1 -0
  19. package/src/fragments/plan-beads-details.md +49 -0
  20. package/src/fragments/plan-beads-integration.md +2 -0
  21. package/src/fragments/summarize-beads.md +8 -0
  22. package/{downloads/without-beads/summarize.md → src/fragments/summarize-structure.md} +0 -20
  23. package/{downloads/without-beads/tdd.md → src/fragments/tdd-fundamentals.md} +0 -21
  24. package/src/fragments/test-quality-criteria.md +24 -0
  25. package/src/fragments/universal-guidelines.md +6 -0
  26. package/{downloads/without-beads → src/sources}/add-command.md +11 -25
  27. package/{downloads/without-beads → src/sources}/ask.md +11 -6
  28. package/{downloads/without-beads → src/sources}/beepboop.md +7 -6
  29. package/{downloads/without-beads → src/sources}/busycommit.md +9 -36
  30. package/{downloads/without-beads → src/sources}/code-review.md +16 -30
  31. package/src/sources/commit.md +20 -0
  32. package/src/sources/cycle.md +23 -0
  33. package/{downloads/without-beads → src/sources}/gap.md +11 -8
  34. package/src/sources/green.md +23 -0
  35. package/src/sources/issue.md +42 -0
  36. package/{downloads/without-beads → src/sources}/kata.md +10 -9
  37. package/{downloads/without-beads → src/sources}/plan.md +18 -39
  38. package/{downloads/without-beads → src/sources}/pr.md +10 -6
  39. package/src/sources/red.md +26 -0
  40. package/src/sources/refactor.md +27 -0
  41. package/{downloads/without-beads → src/sources}/ship.md +11 -6
  42. package/{downloads/without-beads → src/sources}/show.md +11 -6
  43. package/src/sources/spike.md +23 -0
  44. package/src/sources/summarize.md +23 -0
  45. package/{downloads/without-beads → src/sources}/tdd-review.md +11 -31
  46. package/src/sources/tdd.md +24 -0
  47. package/{downloads/without-beads → src/sources}/worktree-add.md +13 -31
  48. package/{downloads/without-beads → src/sources}/worktree-cleanup.md +9 -27
  49. package/downloads/with-beads/add-command.md +0 -159
  50. package/downloads/with-beads/ask.md +0 -144
  51. package/downloads/with-beads/beepboop.md +0 -47
  52. package/downloads/with-beads/busycommit.md +0 -78
  53. package/downloads/with-beads/code-review.md +0 -263
  54. package/downloads/with-beads/commands-metadata.json +0 -155
  55. package/downloads/with-beads/commit.md +0 -49
  56. package/downloads/with-beads/cycle.md +0 -95
  57. package/downloads/with-beads/gap.md +0 -38
  58. package/downloads/with-beads/green.md +0 -95
  59. package/downloads/with-beads/issue.md +0 -152
  60. package/downloads/with-beads/kata.md +0 -444
  61. package/downloads/with-beads/plan.md +0 -186
  62. package/downloads/with-beads/pr.md +0 -82
  63. package/downloads/with-beads/red.md +0 -103
  64. package/downloads/with-beads/refactor.md +0 -105
  65. package/downloads/with-beads/ship.md +0 -98
  66. package/downloads/with-beads/show.md +0 -114
  67. package/downloads/with-beads/spike.md +0 -95
  68. package/downloads/with-beads/summarize.md +0 -54
  69. package/downloads/with-beads/tdd-review.md +0 -102
  70. package/downloads/with-beads/tdd.md +0 -94
  71. package/downloads/with-beads/worktree-add.md +0 -357
  72. package/downloads/with-beads/worktree-cleanup.md +0 -250
  73. package/downloads/without-beads/commands-metadata.json +0 -155
  74. package/downloads/without-beads/cycle.md +0 -90
  75. package/downloads/without-beads/green.md +0 -90
  76. package/downloads/without-beads/issue.md +0 -140
  77. package/downloads/without-beads/red.md +0 -98
  78. package/downloads/without-beads/refactor.md +0 -100
  79. package/downloads/without-beads/spike.md +0 -90
package/bin/cli.js CHANGED
@@ -234,16 +234,16 @@ var Diff = class {
234
234
  }
235
235
  }
236
236
  }
237
- addToPath(path3, added, removed, oldPosInc, options) {
238
- const last = path3.lastComponent;
237
+ addToPath(path5, added, removed, oldPosInc, options) {
238
+ const last = path5.lastComponent;
239
239
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
240
240
  return {
241
- oldPos: path3.oldPos + oldPosInc,
241
+ oldPos: path5.oldPos + oldPosInc,
242
242
  lastComponent: { count: last.count + 1, added, removed, previousComponent: last.previousComponent }
243
243
  };
244
244
  } else {
245
245
  return {
246
- oldPos: path3.oldPos + oldPosInc,
246
+ oldPos: path5.oldPos + oldPosInc,
247
247
  lastComponent: { count: 1, added, removed, previousComponent: last }
248
248
  };
249
249
  }
@@ -390,16 +390,232 @@ var import_picocolors = __toESM(require_picocolors(), 1);
390
390
 
391
391
  // scripts/cli-generator.ts
392
392
  init_esm_shims();
393
+ import fs4 from "fs-extra";
394
+ import path4 from "path";
395
+ import { fileURLToPath as fileURLToPath3 } from "url";
396
+ import os from "os";
397
+
398
+ // scripts/fragment-expander.ts
399
+ init_esm_shims();
393
400
  import fs from "fs-extra";
394
401
  import path2 from "path";
402
+ var TRANSFORM_BLOCK_REGEX = /<!--\s*docs\s+(\w+)([^>]*)-->([\s\S]*?)<!--\s*\/docs\s*-->/g;
403
+ function parseOptions(attrString) {
404
+ const options = {};
405
+ const attrRegex = /(\w+)=['"]([^'"]*)['"]/g;
406
+ let match;
407
+ while ((match = attrRegex.exec(attrString)) !== null) {
408
+ options[match[1]] = match[2];
409
+ }
410
+ return options;
411
+ }
412
+ function expandContent(content, options) {
413
+ const { baseDir, flags } = options;
414
+ return content.replace(
415
+ TRANSFORM_BLOCK_REGEX,
416
+ (_match, transformName, attrString) => {
417
+ if (transformName !== "INCLUDE") {
418
+ return "";
419
+ }
420
+ const attrs = parseOptions(attrString);
421
+ const { path: includePath, featureFlag, elsePath } = attrs;
422
+ if (featureFlag && !flags.includes(featureFlag)) {
423
+ if (elsePath) {
424
+ const fullElsePath = path2.join(baseDir, elsePath);
425
+ try {
426
+ return fs.readFileSync(fullElsePath, "utf8");
427
+ } catch (err) {
428
+ throw new Error(
429
+ `Failed to read elsePath '${elsePath}': ${err instanceof Error ? err.message : String(err)}`
430
+ );
431
+ }
432
+ }
433
+ return "";
434
+ }
435
+ if (!includePath) {
436
+ return "";
437
+ }
438
+ const fullPath = path2.join(baseDir, includePath);
439
+ try {
440
+ return fs.readFileSync(fullPath, "utf8");
441
+ } catch (err) {
442
+ throw new Error(
443
+ `Failed to read '${includePath}': ${err instanceof Error ? err.message : String(err)}`
444
+ );
445
+ }
446
+ }
447
+ );
448
+ }
449
+
450
+ // scripts/generate-readme.ts
451
+ init_esm_shims();
452
+ import fs3 from "fs";
453
+ import path3 from "path";
395
454
  import { fileURLToPath as fileURLToPath2 } from "url";
396
- import os from "os";
455
+
456
+ // scripts/cli-options.ts
457
+ init_esm_shims();
458
+ var CLI_OPTIONS = [
459
+ {
460
+ flag: "--scope",
461
+ key: "scope",
462
+ type: "string",
463
+ description: "Installation scope (project, user)",
464
+ example: "--scope=project",
465
+ requiredForNonInteractive: true
466
+ },
467
+ {
468
+ flag: "--prefix",
469
+ key: "prefix",
470
+ type: "string",
471
+ description: "Add prefix to command names",
472
+ example: "--prefix=my-"
473
+ },
474
+ {
475
+ flag: "--commands",
476
+ key: "commands",
477
+ type: "array",
478
+ description: "Install only specific commands",
479
+ example: "--commands=commit,red,green"
480
+ },
481
+ {
482
+ flag: "--skip-template-injection",
483
+ key: "skipTemplateInjection",
484
+ type: "boolean",
485
+ description: "Skip injecting project CLAUDE.md customizations"
486
+ },
487
+ {
488
+ flag: "--update-existing",
489
+ key: "updateExisting",
490
+ type: "boolean",
491
+ description: "Only update already-installed commands"
492
+ },
493
+ {
494
+ flag: "--overwrite",
495
+ key: "overwrite",
496
+ type: "boolean",
497
+ description: "Overwrite conflicting files without prompting"
498
+ },
499
+ {
500
+ flag: "--skip-on-conflict",
501
+ key: "skipOnConflict",
502
+ type: "boolean",
503
+ description: "Skip conflicting files without prompting"
504
+ },
505
+ {
506
+ flag: "--flags",
507
+ key: "flags",
508
+ type: "array",
509
+ description: "Enable feature flags (beads, github, gitlab, etc.)",
510
+ example: "--flags=beads,github"
511
+ }
512
+ ];
513
+ function generateHelpText() {
514
+ const lines = [
515
+ "Usage: npx @wbern/claude-instructions [options]",
516
+ "",
517
+ "Options:"
518
+ ];
519
+ for (const opt of CLI_OPTIONS) {
520
+ const suffix = opt.type === "string" ? "=<value>" : opt.type === "array" ? "=<list>" : "";
521
+ const padding = 28 - (opt.flag.length + suffix.length);
522
+ lines.push(
523
+ ` ${opt.flag}${suffix}${" ".repeat(Math.max(1, padding))}${opt.description}`
524
+ );
525
+ }
526
+ lines.push(" --help, -h Show this help message");
527
+ lines.push(" --version, -v Show version number");
528
+ return lines.join("\n");
529
+ }
530
+
531
+ // scripts/utils.ts
532
+ init_esm_shims();
533
+ import fs2 from "fs";
534
+ function getMarkdownFiles(dir) {
535
+ return fs2.readdirSync(dir).filter((f) => f.endsWith(".md"));
536
+ }
537
+
538
+ // scripts/generate-readme.ts
397
539
  var __filename2 = fileURLToPath2(import.meta.url);
398
- var __dirname2 = path2.dirname(__filename2);
399
- var VARIANTS = {
400
- WITH_BEADS: "with-beads",
401
- WITHOUT_BEADS: "without-beads"
540
+ var __dirname2 = path3.dirname(__filename2);
541
+ var PROJECT_ROOT = path3.resolve(__dirname2, "..");
542
+ var SOURCES_DIR = "src/sources";
543
+ var FRONTMATTER_REGEX = /^---\s*\n([\s\S]*?)\n---/;
544
+ var CATEGORIES = {
545
+ PLANNING: "Planning",
546
+ TDD: "Test-Driven Development",
547
+ WORKFLOW: "Workflow",
548
+ SHIP_SHOW_ASK: "Ship / Show / Ask",
549
+ WORKTREE: "Worktree Management",
550
+ UTILITIES: "Utilities"
402
551
  };
552
+ function parseFrontmatter(content) {
553
+ const match = content.match(FRONTMATTER_REGEX);
554
+ if (!match) return {};
555
+ const frontmatter = {};
556
+ const lines = match[1].split("\n");
557
+ let currentKey = null;
558
+ let currentArray = null;
559
+ for (const line of lines) {
560
+ if (line.match(/^\s+-\s+/) && currentKey && currentArray) {
561
+ const value2 = line.replace(/^\s+-\s+/, "").trim();
562
+ currentArray.push(value2);
563
+ continue;
564
+ }
565
+ if (currentKey && currentArray) {
566
+ frontmatter[currentKey] = currentArray;
567
+ currentKey = null;
568
+ currentArray = null;
569
+ }
570
+ const colonIndex = line.indexOf(":");
571
+ if (colonIndex === -1) continue;
572
+ const key = line.slice(0, colonIndex).trim();
573
+ let value = line.slice(colonIndex + 1).trim();
574
+ if (value === "") {
575
+ currentKey = key;
576
+ currentArray = [];
577
+ continue;
578
+ }
579
+ if (key === "_order" && !isNaN(Number(value))) {
580
+ value = parseInt(value, 10);
581
+ }
582
+ if (key === "_selectedByDefault") {
583
+ frontmatter[key] = value === "true";
584
+ continue;
585
+ }
586
+ frontmatter[key] = value;
587
+ }
588
+ if (currentKey && currentArray) {
589
+ frontmatter[currentKey] = currentArray;
590
+ }
591
+ return frontmatter;
592
+ }
593
+ function getCategory(frontmatter) {
594
+ return frontmatter._category || CATEGORIES.UTILITIES;
595
+ }
596
+ function generateCommandsMetadata() {
597
+ const sourcesDir = path3.join(PROJECT_ROOT, SOURCES_DIR);
598
+ const files = getMarkdownFiles(sourcesDir);
599
+ const metadata = {};
600
+ for (const file of files) {
601
+ const content = fs3.readFileSync(path3.join(sourcesDir, file), "utf8");
602
+ const frontmatter = parseFrontmatter(content);
603
+ const requestedTools = frontmatter[REQUESTED_TOOLS_KEY];
604
+ metadata[file] = {
605
+ description: frontmatter.description || "No description",
606
+ hint: frontmatter._hint,
607
+ category: getCategory(frontmatter),
608
+ order: typeof frontmatter._order === "number" ? frontmatter._order : 999,
609
+ ...frontmatter._selectedByDefault === false ? { selectedByDefault: false } : {},
610
+ ...requestedTools ? { [REQUESTED_TOOLS_KEY]: requestedTools } : {}
611
+ };
612
+ }
613
+ return metadata;
614
+ }
615
+
616
+ // scripts/cli-generator.ts
617
+ var __filename3 = fileURLToPath3(import.meta.url);
618
+ var __dirname3 = path4.dirname(__filename3);
403
619
  var SCOPES = {
404
620
  PROJECT: "project",
405
621
  USER: "user"
@@ -407,7 +623,7 @@ var SCOPES = {
407
623
  var DIRECTORIES = {
408
624
  CLAUDE: ".claude",
409
625
  COMMANDS: "commands",
410
- DOWNLOADS: "downloads"
626
+ SOURCES: "src/sources"
411
627
  };
412
628
  var TEMPLATE_SOURCE_FILES = ["CLAUDE.md", "AGENTS.md"];
413
629
  var REQUESTED_TOOLS_KEY = "_requested-tools";
@@ -423,25 +639,32 @@ function truncatePathFromLeft(pathStr, maxLength) {
423
639
  }
424
640
  return ELLIPSIS + truncated;
425
641
  }
426
- var VARIANT_OPTIONS = [
427
- {
428
- value: VARIANTS.WITH_BEADS,
429
- label: "With Beads",
430
- hint: "Includes Beads task tracking"
431
- },
642
+ var FLAG_OPTIONS = [
432
643
  {
433
- value: VARIANTS.WITHOUT_BEADS,
434
- label: "Without Beads",
435
- hint: "Standard commands only"
644
+ value: "beads",
645
+ label: "Beads MCP",
646
+ hint: "Local issue tracking",
647
+ category: "Feature Flags"
436
648
  }
437
649
  ];
650
+ function getFlagsGroupedByCategory() {
651
+ const grouped = {};
652
+ for (const flag of FLAG_OPTIONS) {
653
+ const { category, ...option } = flag;
654
+ if (!grouped[category]) {
655
+ grouped[category] = [];
656
+ }
657
+ grouped[category].push(option);
658
+ }
659
+ return grouped;
660
+ }
438
661
  function getScopeOptions(terminalWidth = 80) {
439
- const projectPath = path2.join(
662
+ const projectPath = path4.join(
440
663
  process.cwd(),
441
664
  DIRECTORIES.CLAUDE,
442
665
  DIRECTORIES.COMMANDS
443
666
  );
444
- const userPath = path2.join(
667
+ const userPath = path4.join(
445
668
  os.homedir(),
446
669
  DIRECTORIES.CLAUDE,
447
670
  DIRECTORIES.COMMANDS
@@ -459,31 +682,32 @@ function getScopeOptions(terminalWidth = 80) {
459
682
  }
460
683
  ];
461
684
  }
462
- async function checkExistingFiles(outputPath, variant, scope, options) {
463
- const sourcePath = path2.join(
464
- __dirname2,
465
- "..",
466
- DIRECTORIES.DOWNLOADS,
467
- variant || VARIANTS.WITH_BEADS
468
- );
685
+ async function checkExistingFiles(outputPath, scope, options) {
686
+ const sourcePath = path4.join(__dirname3, "..", DIRECTORIES.SOURCES);
469
687
  const destinationPath = outputPath || getDestinationPath(outputPath, scope);
470
- const allFiles = await fs.readdir(sourcePath);
688
+ const flags = options?.flags ?? [];
689
+ const allFiles = await fs4.readdir(sourcePath);
471
690
  const files = options?.commands ? allFiles.filter((f) => options.commands.includes(f)) : allFiles;
472
691
  const existingFiles = [];
473
692
  const prefix = options?.commandPrefix || "";
474
693
  let metadata = null;
475
694
  let allowedToolsSet = null;
476
695
  if (options?.allowedTools && options.allowedTools.length > 0) {
477
- metadata = await loadCommandsMetadata(variant || VARIANTS.WITH_BEADS);
696
+ metadata = await loadCommandsMetadata();
478
697
  allowedToolsSet = new Set(options.allowedTools);
479
698
  }
699
+ const baseDir = path4.join(__dirname3, "..");
480
700
  for (const file of files) {
481
701
  const destFileName = prefix + file;
482
- const destFilePath = path2.join(destinationPath, destFileName);
483
- const sourceFilePath = path2.join(sourcePath, file);
484
- if (await fs.pathExists(destFilePath)) {
485
- const existingContent = await fs.readFile(destFilePath, "utf-8");
486
- let newContent = await fs.readFile(sourceFilePath, "utf-8");
702
+ const destFilePath = path4.join(destinationPath, destFileName);
703
+ const sourceFilePath = path4.join(sourcePath, file);
704
+ if (await fs4.pathExists(destFilePath)) {
705
+ const existingContent = await fs4.readFile(destFilePath, "utf-8");
706
+ const sourceContent = await fs4.readFile(sourceFilePath, "utf-8");
707
+ let newContent = expandContent(sourceContent, {
708
+ flags,
709
+ baseDir
710
+ });
487
711
  if (metadata && allowedToolsSet) {
488
712
  const commandMetadata = metadata[file];
489
713
  const requestedTools = commandMetadata?.[REQUESTED_TOOLS_KEY] || [];
@@ -518,19 +742,11 @@ var CATEGORY_ORDER = [
518
742
  "Utilities",
519
743
  "Ship / Show / Ask"
520
744
  ];
521
- async function loadCommandsMetadata(variant) {
522
- const sourcePath = path2.join(
523
- __dirname2,
524
- "..",
525
- DIRECTORIES.DOWNLOADS,
526
- variant || VARIANTS.WITH_BEADS
527
- );
528
- const metadataPath = path2.join(sourcePath, "commands-metadata.json");
529
- const metadataContent = await fs.readFile(metadataPath, "utf-8");
530
- return JSON.parse(metadataContent);
745
+ async function loadCommandsMetadata() {
746
+ return generateCommandsMetadata();
531
747
  }
532
- async function getCommandsGroupedByCategory(variant) {
533
- const metadata = await loadCommandsMetadata(variant);
748
+ async function getCommandsGroupedByCategory() {
749
+ const metadata = await loadCommandsMetadata();
534
750
  const grouped = {};
535
751
  for (const [filename, data] of Object.entries(metadata)) {
536
752
  const category = data.category;
@@ -577,8 +793,8 @@ function formatCommandsHint(commands) {
577
793
  const remaining = commands.length - 2;
578
794
  return `${first.join(", ")}, and ${remaining} ${remaining === 1 ? "other" : "others"}`;
579
795
  }
580
- async function getRequestedToolsOptions(variant) {
581
- const metadata = await loadCommandsMetadata(variant);
796
+ async function getRequestedToolsOptions() {
797
+ const metadata = await loadCommandsMetadata();
582
798
  const toolToCommands = /* @__PURE__ */ new Map();
583
799
  for (const [filename, data] of Object.entries(metadata)) {
584
800
  if (data[REQUESTED_TOOLS_KEY]) {
@@ -601,10 +817,10 @@ function getDestinationPath(outputPath, scope) {
601
817
  return outputPath;
602
818
  }
603
819
  if (scope === SCOPES.PROJECT) {
604
- return path2.join(process.cwd(), DIRECTORIES.CLAUDE, DIRECTORIES.COMMANDS);
820
+ return path4.join(process.cwd(), DIRECTORIES.CLAUDE, DIRECTORIES.COMMANDS);
605
821
  }
606
822
  if (scope === SCOPES.USER) {
607
- return path2.join(os.homedir(), DIRECTORIES.CLAUDE, DIRECTORIES.COMMANDS);
823
+ return path4.join(os.homedir(), DIRECTORIES.CLAUDE, DIRECTORIES.COMMANDS);
608
824
  }
609
825
  throw new Error("Either outputPath or scope must be provided");
610
826
  }
@@ -625,34 +841,35 @@ function extractTemplateBlocks(content) {
625
841
  }
626
842
  return blocks;
627
843
  }
628
- async function generateToDirectory(outputPath, variant, scope, options) {
629
- const sourcePath = path2.join(
630
- __dirname2,
631
- "..",
632
- DIRECTORIES.DOWNLOADS,
633
- variant || VARIANTS.WITH_BEADS
634
- );
844
+ async function generateToDirectory(outputPath, scope, options) {
635
845
  const destinationPath = getDestinationPath(outputPath, scope);
636
- const allFiles = await fs.readdir(sourcePath);
846
+ const sourcePath = path4.join(__dirname3, "..", DIRECTORIES.SOURCES);
847
+ const flags = options?.flags ?? [];
848
+ const allFiles = (await fs4.readdir(sourcePath)).filter(
849
+ (f) => f.endsWith(".md")
850
+ );
637
851
  let files = options?.commands ? allFiles.filter((f) => options.commands.includes(f)) : allFiles;
638
852
  if (options?.skipFiles) {
639
853
  const prefix2 = options?.commandPrefix || "";
640
854
  files = files.filter((f) => !options.skipFiles.includes(prefix2 + f));
641
855
  }
642
856
  const prefix = options?.commandPrefix || "";
643
- if (options?.commands || options?.skipFiles || options?.commandPrefix) {
644
- await fs.ensureDir(destinationPath);
645
- for (const file of files) {
646
- await fs.copy(
647
- path2.join(sourcePath, file),
648
- path2.join(destinationPath, prefix + file)
649
- );
650
- }
651
- } else {
652
- await fs.copy(sourcePath, destinationPath, {});
857
+ await fs4.ensureDir(destinationPath);
858
+ const baseDir = path4.join(__dirname3, "..");
859
+ for (const file of files) {
860
+ const sourceFilePath = path4.join(sourcePath, file);
861
+ const sourceContent = await fs4.readFile(sourceFilePath, "utf-8");
862
+ const expandedContent = expandContent(sourceContent, {
863
+ flags,
864
+ baseDir
865
+ });
866
+ await fs4.writeFile(
867
+ path4.join(destinationPath, prefix + file),
868
+ expandedContent
869
+ );
653
870
  }
654
871
  if (options?.allowedTools && options.allowedTools.length > 0) {
655
- const metadata = await loadCommandsMetadata(variant || VARIANTS.WITH_BEADS);
872
+ const metadata = await loadCommandsMetadata();
656
873
  const allowedToolsSet = new Set(options.allowedTools);
657
874
  for (const file of files) {
658
875
  const commandMetadata = metadata[file];
@@ -661,8 +878,8 @@ async function generateToDirectory(outputPath, variant, scope, options) {
661
878
  (tool) => allowedToolsSet.has(tool)
662
879
  );
663
880
  if (toolsForCommand.length > 0) {
664
- const filePath = path2.join(destinationPath, prefix + file);
665
- const content = await fs.readFile(filePath, "utf-8");
881
+ const filePath = path4.join(destinationPath, prefix + file);
882
+ const content = await fs4.readFile(filePath, "utf-8");
666
883
  const allowedToolsYaml = `allowed-tools: ${toolsForCommand.join(", ")}`;
667
884
  const modifiedContent = content.replace(
668
885
  /^---\n/,
@@ -670,7 +887,7 @@ async function generateToDirectory(outputPath, variant, scope, options) {
670
887
  ${allowedToolsYaml}
671
888
  `
672
889
  );
673
- await fs.writeFile(filePath, modifiedContent);
890
+ await fs4.writeFile(filePath, modifiedContent);
674
891
  }
675
892
  }
676
893
  }
@@ -678,21 +895,21 @@ ${allowedToolsYaml}
678
895
  if (!options?.skipTemplateInjection) {
679
896
  let templateSourcePath = null;
680
897
  for (const filename of TEMPLATE_SOURCE_FILES) {
681
- const candidatePath = path2.join(process.cwd(), filename);
682
- if (await fs.pathExists(candidatePath)) {
898
+ const candidatePath = path4.join(process.cwd(), filename);
899
+ if (await fs4.pathExists(candidatePath)) {
683
900
  templateSourcePath = candidatePath;
684
901
  break;
685
902
  }
686
903
  }
687
904
  if (templateSourcePath) {
688
- const sourceContent = await fs.readFile(templateSourcePath, "utf-8");
905
+ const sourceContent = await fs4.readFile(templateSourcePath, "utf-8");
689
906
  const templates = extractTemplateBlocks(sourceContent);
690
907
  if (templates.length > 0) {
691
908
  for (const file of files) {
692
- const commandName = path2.basename(file, ".md");
909
+ const commandName = path4.basename(file, ".md");
693
910
  const actualFileName = options?.commandPrefix ? options.commandPrefix + file : file;
694
- const filePath = path2.join(destinationPath, actualFileName);
695
- let content = await fs.readFile(filePath, "utf-8");
911
+ const filePath = path4.join(destinationPath, actualFileName);
912
+ let content = await fs4.readFile(filePath, "utf-8");
696
913
  let modified = false;
697
914
  for (const template of templates) {
698
915
  if (template.commands && !template.commands.includes(commandName)) {
@@ -702,7 +919,7 @@ ${allowedToolsYaml}
702
919
  modified = true;
703
920
  }
704
921
  if (modified) {
705
- await fs.writeFile(filePath, content);
922
+ await fs4.writeFile(filePath, content);
706
923
  }
707
924
  }
708
925
  templateInjected = true;
@@ -712,88 +929,12 @@ ${allowedToolsYaml}
712
929
  return {
713
930
  success: true,
714
931
  filesGenerated: files.length,
715
- variant,
716
932
  templateInjectionSkipped: options?.skipTemplateInjection,
717
- templateInjected
933
+ templateInjected,
934
+ flags: options?.flags
718
935
  };
719
936
  }
720
937
 
721
- // scripts/cli-options.ts
722
- init_esm_shims();
723
- var CLI_OPTIONS = [
724
- {
725
- flag: "--variant",
726
- key: "variant",
727
- type: "string",
728
- description: "Command variant (with-beads, without-beads)",
729
- example: "--variant=with-beads",
730
- requiredForNonInteractive: true
731
- },
732
- {
733
- flag: "--scope",
734
- key: "scope",
735
- type: "string",
736
- description: "Installation scope (project, user)",
737
- example: "--scope=project",
738
- requiredForNonInteractive: true
739
- },
740
- {
741
- flag: "--prefix",
742
- key: "prefix",
743
- type: "string",
744
- description: "Add prefix to command names",
745
- example: "--prefix=my-"
746
- },
747
- {
748
- flag: "--commands",
749
- key: "commands",
750
- type: "array",
751
- description: "Install only specific commands",
752
- example: "--commands=commit,red,green"
753
- },
754
- {
755
- flag: "--skip-template-injection",
756
- key: "skipTemplateInjection",
757
- type: "boolean",
758
- description: "Skip injecting project CLAUDE.md customizations"
759
- },
760
- {
761
- flag: "--update-existing",
762
- key: "updateExisting",
763
- type: "boolean",
764
- description: "Only update already-installed commands"
765
- },
766
- {
767
- flag: "--overwrite",
768
- key: "overwrite",
769
- type: "boolean",
770
- description: "Overwrite conflicting files without prompting"
771
- },
772
- {
773
- flag: "--skip-on-conflict",
774
- key: "skipOnConflict",
775
- type: "boolean",
776
- description: "Skip conflicting files without prompting"
777
- }
778
- ];
779
- function generateHelpText() {
780
- const lines = [
781
- "Usage: npx @wbern/claude-instructions [options]",
782
- "",
783
- "Options:"
784
- ];
785
- for (const opt of CLI_OPTIONS) {
786
- const suffix = opt.type === "string" ? "=<value>" : opt.type === "array" ? "=<list>" : "";
787
- const padding = 28 - (opt.flag.length + suffix.length);
788
- lines.push(
789
- ` ${opt.flag}${suffix}${" ".repeat(Math.max(1, padding))}${opt.description}`
790
- );
791
- }
792
- lines.push(" --help, -h Show this help message");
793
- lines.push(" --version, -v Show version number");
794
- return lines.join("\n");
795
- }
796
-
797
938
  // scripts/tty.ts
798
939
  init_esm_shims();
799
940
  function isInteractiveTTY() {
@@ -919,23 +1060,22 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
919
1060
  `;
920
1061
  async function main(args) {
921
1062
  intro(BATMAN_LOGO);
922
- let variant;
923
1063
  let scope;
924
1064
  let commandPrefix;
925
1065
  let selectedCommands;
926
1066
  let selectedAllowedTools;
1067
+ let selectedFlags;
927
1068
  let cachedExistingFiles;
928
- if (args?.variant && args?.scope) {
929
- variant = args.variant;
1069
+ if (args?.scope) {
930
1070
  scope = args.scope;
931
1071
  commandPrefix = args.prefix ?? "";
932
1072
  selectedCommands = args.commands;
1073
+ selectedFlags = args.flags;
933
1074
  if (args.updateExisting) {
934
1075
  cachedExistingFiles = await checkExistingFiles(
935
1076
  void 0,
936
- variant,
937
1077
  scope,
938
- { commandPrefix: commandPrefix || "" }
1078
+ { commandPrefix: commandPrefix || "", flags: selectedFlags }
939
1079
  );
940
1080
  selectedCommands = cachedExistingFiles.map((f) => f.filename);
941
1081
  if (selectedCommands.length === 0) {
@@ -951,13 +1091,6 @@ async function main(args) {
951
1091
  log.warn(`Non-interactive mode requires ${requiredFlags} arguments`);
952
1092
  return;
953
1093
  }
954
- variant = await select({
955
- message: "Select variant",
956
- options: [...VARIANT_OPTIONS]
957
- });
958
- if (isCancel(variant)) {
959
- return;
960
- }
961
1094
  const terminalWidth = process.stdout.columns || 80;
962
1095
  const uiOverhead = 25;
963
1096
  scope = await select({
@@ -974,15 +1107,24 @@ async function main(args) {
974
1107
  if (isCancel(commandPrefix)) {
975
1108
  return;
976
1109
  }
977
- let groupedCommands = await getCommandsGroupedByCategory(
978
- variant
979
- );
1110
+ const flagOptions = getFlagsGroupedByCategory();
1111
+ selectedFlags = await groupMultiselect({
1112
+ message: "Select feature flags (optional)",
1113
+ options: flagOptions,
1114
+ required: false
1115
+ });
1116
+ if (isCancel(selectedFlags)) {
1117
+ return;
1118
+ }
1119
+ let groupedCommands = await getCommandsGroupedByCategory();
980
1120
  if (args?.updateExisting) {
981
1121
  cachedExistingFiles = await checkExistingFiles(
982
1122
  void 0,
983
- variant,
984
1123
  scope,
985
- { commandPrefix: commandPrefix || "" }
1124
+ {
1125
+ commandPrefix: commandPrefix || "",
1126
+ flags: selectedFlags
1127
+ }
986
1128
  );
987
1129
  const existingFilenames = new Set(
988
1130
  cachedExistingFiles.map((f) => f.filename)
@@ -1011,9 +1153,7 @@ async function main(args) {
1011
1153
  if (isCancel(selectedCommands)) {
1012
1154
  return;
1013
1155
  }
1014
- const requestedToolsOptions = await getRequestedToolsOptions(
1015
- variant
1016
- );
1156
+ const requestedToolsOptions = await getRequestedToolsOptions();
1017
1157
  if (requestedToolsOptions.length > 0) {
1018
1158
  selectedAllowedTools = await groupMultiselect({
1019
1159
  message: "Select allowed tools for commands (optional)",
@@ -1027,10 +1167,11 @@ async function main(args) {
1027
1167
  }
1028
1168
  }
1029
1169
  }
1030
- const existingFiles = cachedExistingFiles ?? await checkExistingFiles(void 0, variant, scope, {
1170
+ const existingFiles = cachedExistingFiles ?? await checkExistingFiles(void 0, scope, {
1031
1171
  commandPrefix,
1032
1172
  commands: selectedCommands,
1033
- allowedTools: selectedAllowedTools
1173
+ allowedTools: selectedAllowedTools,
1174
+ flags: selectedFlags
1034
1175
  });
1035
1176
  const skipFiles = [];
1036
1177
  const shouldSkipConflicts = args?.skipOnConflict || !isInteractiveTTY();
@@ -1108,18 +1249,14 @@ async function main(args) {
1108
1249
  );
1109
1250
  }
1110
1251
  }
1111
- const result = await generateToDirectory(
1112
- void 0,
1113
- variant,
1114
- scope,
1115
- {
1116
- commandPrefix,
1117
- skipTemplateInjection: args?.skipTemplateInjection,
1118
- commands: selectedCommands,
1119
- skipFiles,
1120
- allowedTools: selectedAllowedTools
1121
- }
1122
- );
1252
+ const result = await generateToDirectory(void 0, scope, {
1253
+ commandPrefix,
1254
+ skipTemplateInjection: args?.skipTemplateInjection,
1255
+ commands: selectedCommands,
1256
+ skipFiles,
1257
+ allowedTools: selectedAllowedTools,
1258
+ flags: selectedFlags
1259
+ });
1123
1260
  const fullPath = scope === "project" ? `${process.cwd()}/.claude/commands` : `${os2.homedir()}/.claude/commands`;
1124
1261
  outro(
1125
1262
  `Installed ${result.filesGenerated} commands to ${fullPath}