planmode 0.2.2 → 0.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.
package/dist/index.js CHANGED
@@ -1,28 +1,19 @@
1
1
  #!/usr/bin/env node
2
-
3
- // src/index.ts
4
- import { Command as Command16 } from "commander";
5
-
6
- // src/commands/install.ts
7
- import { Command } from "commander";
8
-
9
- // src/lib/installer.ts
10
- import fs7 from "fs";
11
- import path7 from "path";
12
- import crypto from "crypto";
13
-
14
- // src/lib/registry.ts
15
- import fs2 from "fs";
16
- import path2 from "path";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
17
11
 
18
12
  // src/lib/config.ts
19
13
  import fs from "fs";
20
14
  import path from "path";
21
15
  import os from "os";
22
16
  import { parse, stringify } from "yaml";
23
- var CONFIG_DIR = path.join(os.homedir(), ".planmode");
24
- var CONFIG_PATH = path.join(CONFIG_DIR, "config");
25
- var CACHE_DIR = path.join(CONFIG_DIR, "cache");
26
17
  function getCacheDir() {
27
18
  const config = readConfig();
28
19
  return config.cache?.dir ?? CACHE_DIR;
@@ -61,9 +52,19 @@ function getRegistries() {
61
52
  ...config.registries
62
53
  };
63
54
  }
55
+ var CONFIG_DIR, CONFIG_PATH, CACHE_DIR;
56
+ var init_config = __esm({
57
+ "src/lib/config.ts"() {
58
+ "use strict";
59
+ CONFIG_DIR = path.join(os.homedir(), ".planmode");
60
+ CONFIG_PATH = path.join(CONFIG_DIR, "config");
61
+ CACHE_DIR = path.join(CONFIG_DIR, "cache");
62
+ }
63
+ });
64
64
 
65
65
  // src/lib/registry.ts
66
- var INDEX_CACHE_FILE = "index.json";
66
+ import fs2 from "fs";
67
+ import path2 from "path";
67
68
  function getHeaders() {
68
69
  const headers = {
69
70
  "Accept": "application/vnd.github.v3.raw+json",
@@ -164,6 +165,14 @@ async function fetchVersionMetadata(packageName, version) {
164
165
  }
165
166
  return await response.json();
166
167
  }
168
+ var INDEX_CACHE_FILE;
169
+ var init_registry = __esm({
170
+ "src/lib/registry.ts"() {
171
+ "use strict";
172
+ init_config();
173
+ INDEX_CACHE_FILE = "index.json";
174
+ }
175
+ });
167
176
 
168
177
  // src/lib/resolver.ts
169
178
  function parseSemver(version) {
@@ -245,6 +254,12 @@ async function resolveVersion(packageName, versionRange) {
245
254
  const version = matching[matching.length - 1];
246
255
  return { version, metadata };
247
256
  }
257
+ var init_resolver = __esm({
258
+ "src/lib/resolver.ts"() {
259
+ "use strict";
260
+ init_registry();
261
+ }
262
+ });
248
263
 
249
264
  // src/lib/git.ts
250
265
  import fs3 from "fs";
@@ -316,15 +331,20 @@ async function getRemoteUrl(dir) {
316
331
  }
317
332
  async function getHeadSha(dir) {
318
333
  const git = simpleGit(dir);
319
- const log = await git.log({ n: 1 });
320
- return log.latest?.hash ?? "";
334
+ const log8 = await git.log({ n: 1 });
335
+ return log8.latest?.hash ?? "";
321
336
  }
337
+ var init_git = __esm({
338
+ "src/lib/git.ts"() {
339
+ "use strict";
340
+ init_config();
341
+ }
342
+ });
322
343
 
323
344
  // src/lib/lockfile.ts
324
345
  import fs4 from "fs";
325
346
  import path4 from "path";
326
347
  import { parse as parse2, stringify as stringify2 } from "yaml";
327
- var LOCKFILE_NAME = "planmode.lock";
328
348
  function getLockfilePath(projectDir = process.cwd()) {
329
349
  return path4.join(projectDir, LOCKFILE_NAME);
330
350
  }
@@ -356,12 +376,17 @@ function getLockedVersion(packageName, projectDir = process.cwd()) {
356
376
  const lockfile = readLockfile(projectDir);
357
377
  return lockfile.packages[packageName];
358
378
  }
379
+ var LOCKFILE_NAME;
380
+ var init_lockfile = __esm({
381
+ "src/lib/lockfile.ts"() {
382
+ "use strict";
383
+ LOCKFILE_NAME = "planmode.lock";
384
+ }
385
+ });
359
386
 
360
387
  // src/lib/claude-md.ts
361
388
  import fs5 from "fs";
362
389
  import path5 from "path";
363
- var CLAUDE_MD = "CLAUDE.md";
364
- var PLANMODE_SECTION = "# Planmode";
365
390
  function getClaudeMdPath(projectDir = process.cwd()) {
366
391
  return path5.join(projectDir, CLAUDE_MD);
367
392
  }
@@ -411,27 +436,19 @@ function listImports(projectDir = process.cwd()) {
411
436
  }
412
437
  return imports;
413
438
  }
439
+ var CLAUDE_MD, PLANMODE_SECTION;
440
+ var init_claude_md = __esm({
441
+ "src/lib/claude-md.ts"() {
442
+ "use strict";
443
+ CLAUDE_MD = "CLAUDE.md";
444
+ PLANMODE_SECTION = "# Planmode";
445
+ }
446
+ });
414
447
 
415
448
  // src/lib/manifest.ts
416
449
  import fs6 from "fs";
417
450
  import path6 from "path";
418
451
  import { parse as parse3 } from "yaml";
419
- var NAME_REGEX = /^(@[a-z0-9-]+\/)?[a-z0-9][a-z0-9-]*$/;
420
- var SEMVER_REGEX = /^\d+\.\d+\.\d+$/;
421
- var VALID_TYPES = ["prompt", "rule", "plan"];
422
- var VALID_VAR_TYPES = ["string", "number", "boolean", "enum", "resolved"];
423
- var VALID_CATEGORIES = [
424
- "frontend",
425
- "backend",
426
- "devops",
427
- "database",
428
- "testing",
429
- "mobile",
430
- "ai-ml",
431
- "design",
432
- "security",
433
- "other"
434
- ];
435
452
  function parseManifest(raw) {
436
453
  const data = parse3(raw);
437
454
  if (!data || typeof data !== "object") {
@@ -518,10 +535,31 @@ function readPackageContent(dir, manifest) {
518
535
  }
519
536
  throw new Error("Package must specify either content or content_file");
520
537
  }
538
+ var NAME_REGEX, SEMVER_REGEX, VALID_TYPES, VALID_VAR_TYPES, VALID_CATEGORIES;
539
+ var init_manifest = __esm({
540
+ "src/lib/manifest.ts"() {
541
+ "use strict";
542
+ NAME_REGEX = /^(@[a-z0-9-]+\/)?[a-z0-9][a-z0-9-]*$/;
543
+ SEMVER_REGEX = /^\d+\.\d+\.\d+$/;
544
+ VALID_TYPES = ["prompt", "rule", "plan"];
545
+ VALID_VAR_TYPES = ["string", "number", "boolean", "enum", "resolved"];
546
+ VALID_CATEGORIES = [
547
+ "frontend",
548
+ "backend",
549
+ "devops",
550
+ "database",
551
+ "testing",
552
+ "mobile",
553
+ "ai-ml",
554
+ "design",
555
+ "security",
556
+ "other"
557
+ ];
558
+ }
559
+ });
521
560
 
522
561
  // src/lib/template.ts
523
562
  import Handlebars from "handlebars";
524
- Handlebars.registerHelper("eq", (a, b) => a === b);
525
563
  function renderTemplate(content, variables) {
526
564
  const template = Handlebars.compile(content);
527
565
  return template(variables);
@@ -584,118 +622,256 @@ function extractPath(obj, pathStr) {
584
622
  }
585
623
  return String(current ?? "");
586
624
  }
625
+ var init_template = __esm({
626
+ "src/lib/template.ts"() {
627
+ "use strict";
628
+ Handlebars.registerHelper("eq", (a, b) => a === b);
629
+ }
630
+ });
587
631
 
588
632
  // src/lib/logger.ts
589
- var RESET = "\x1B[0m";
590
- var RED = "\x1B[31m";
591
- var GREEN = "\x1B[32m";
592
- var YELLOW = "\x1B[33m";
593
- var CYAN = "\x1B[36m";
594
- var DIM = "\x1B[2m";
595
- var BOLD = "\x1B[1m";
596
633
  function stripAnsi(str) {
597
634
  return str.replace(/\x1b\[[0-9;]*m/g, "");
598
635
  }
599
- var capturing = false;
600
- var captured = [];
601
- var logger = {
602
- capture() {
603
- capturing = true;
604
- captured = [];
605
- },
606
- flush() {
607
- const messages = captured;
608
- captured = [];
636
+ var RESET, RED, GREEN, YELLOW, CYAN, DIM, BOLD, capturing, captured, logger;
637
+ var init_logger = __esm({
638
+ "src/lib/logger.ts"() {
639
+ "use strict";
640
+ RESET = "\x1B[0m";
641
+ RED = "\x1B[31m";
642
+ GREEN = "\x1B[32m";
643
+ YELLOW = "\x1B[33m";
644
+ CYAN = "\x1B[36m";
645
+ DIM = "\x1B[2m";
646
+ BOLD = "\x1B[1m";
609
647
  capturing = false;
610
- return messages;
611
- },
612
- isCapturing() {
613
- return capturing;
614
- },
615
- info(msg) {
616
- const text = `info ${msg}`;
617
- if (capturing) {
618
- captured.push(stripAnsi(text));
619
- } else {
620
- console.log(`${CYAN}info${RESET} ${msg}`);
621
- }
622
- },
623
- success(msg) {
624
- const text = `\u2713 ${msg}`;
625
- if (capturing) {
626
- captured.push(stripAnsi(text));
627
- } else {
628
- console.log(`${GREEN}\u2713${RESET} ${msg}`);
629
- }
630
- },
631
- warn(msg) {
632
- const text = `warn ${msg}`;
633
- if (capturing) {
634
- captured.push(stripAnsi(text));
635
- } else {
636
- console.log(`${YELLOW}warn${RESET} ${msg}`);
637
- }
638
- },
639
- error(msg) {
640
- const text = `error ${msg}`;
641
- if (capturing) {
642
- captured.push(stripAnsi(text));
643
- } else {
644
- console.error(`${RED}error${RESET} ${msg}`);
645
- }
646
- },
647
- dim(msg) {
648
- if (capturing) {
649
- captured.push(msg);
650
- } else {
651
- console.log(`${DIM}${msg}${RESET}`);
652
- }
653
- },
654
- bold(msg) {
655
- if (capturing) {
656
- captured.push(msg);
657
- } else {
658
- console.log(`${BOLD}${msg}${RESET}`);
659
- }
660
- },
661
- table(headers, rows) {
662
- const colWidths = headers.map(
663
- (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
664
- );
665
- const header = headers.map((h, i) => h.toUpperCase().padEnd(colWidths[i])).join(" ");
666
- if (capturing) {
667
- captured.push(` ${header}`);
668
- for (const row of rows) {
669
- const line = row.map((cell, i) => cell.padEnd(colWidths[i])).join(" ");
670
- captured.push(` ${line}`);
671
- }
672
- } else {
673
- console.log(` ${DIM}${header}${RESET}`);
674
- for (const row of rows) {
675
- const line = row.map((cell, i) => cell.padEnd(colWidths[i])).join(" ");
676
- console.log(` ${line}`);
648
+ captured = [];
649
+ logger = {
650
+ capture() {
651
+ capturing = true;
652
+ captured = [];
653
+ },
654
+ flush() {
655
+ const messages = captured;
656
+ captured = [];
657
+ capturing = false;
658
+ return messages;
659
+ },
660
+ isCapturing() {
661
+ return capturing;
662
+ },
663
+ info(msg) {
664
+ const text4 = `info ${msg}`;
665
+ if (capturing) {
666
+ captured.push(stripAnsi(text4));
667
+ } else {
668
+ console.log(`${CYAN}info${RESET} ${msg}`);
669
+ }
670
+ },
671
+ success(msg) {
672
+ const text4 = `\u2713 ${msg}`;
673
+ if (capturing) {
674
+ captured.push(stripAnsi(text4));
675
+ } else {
676
+ console.log(`${GREEN}\u2713${RESET} ${msg}`);
677
+ }
678
+ },
679
+ warn(msg) {
680
+ const text4 = `warn ${msg}`;
681
+ if (capturing) {
682
+ captured.push(stripAnsi(text4));
683
+ } else {
684
+ console.log(`${YELLOW}warn${RESET} ${msg}`);
685
+ }
686
+ },
687
+ error(msg) {
688
+ const text4 = `error ${msg}`;
689
+ if (capturing) {
690
+ captured.push(stripAnsi(text4));
691
+ } else {
692
+ console.error(`${RED}error${RESET} ${msg}`);
693
+ }
694
+ },
695
+ dim(msg) {
696
+ if (capturing) {
697
+ captured.push(msg);
698
+ } else {
699
+ console.log(`${DIM}${msg}${RESET}`);
700
+ }
701
+ },
702
+ bold(msg) {
703
+ if (capturing) {
704
+ captured.push(msg);
705
+ } else {
706
+ console.log(`${BOLD}${msg}${RESET}`);
707
+ }
708
+ },
709
+ table(headers, rows) {
710
+ const colWidths = headers.map(
711
+ (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
712
+ );
713
+ const header = headers.map((h, i) => h.toUpperCase().padEnd(colWidths[i])).join(" ");
714
+ if (capturing) {
715
+ captured.push(` ${header}`);
716
+ for (const row of rows) {
717
+ const line = row.map((cell, i) => cell.padEnd(colWidths[i])).join(" ");
718
+ captured.push(` ${line}`);
719
+ }
720
+ } else {
721
+ console.log(` ${DIM}${header}${RESET}`);
722
+ for (const row of rows) {
723
+ const line = row.map((cell, i) => cell.padEnd(colWidths[i])).join(" ");
724
+ console.log(` ${line}`);
725
+ }
726
+ }
727
+ },
728
+ blank() {
729
+ if (capturing) {
730
+ captured.push("");
731
+ } else {
732
+ console.log();
733
+ }
677
734
  }
678
- }
679
- },
680
- blank() {
681
- if (capturing) {
682
- captured.push("");
683
- } else {
684
- console.log();
685
- }
735
+ };
686
736
  }
687
- };
737
+ });
688
738
 
689
739
  // src/lib/analytics.ts
690
- var API_BASE = "https://api.planmode.org";
691
740
  function trackDownload(packageName) {
692
741
  fetch(`${API_BASE}/downloads/${encodeURIComponent(packageName)}`, {
693
742
  method: "POST"
694
743
  }).catch(() => {
695
744
  });
696
745
  }
746
+ var API_BASE;
747
+ var init_analytics = __esm({
748
+ "src/lib/analytics.ts"() {
749
+ "use strict";
750
+ API_BASE = "https://api.planmode.org";
751
+ }
752
+ });
753
+
754
+ // src/lib/prompts.ts
755
+ import * as p from "@clack/prompts";
756
+ function isInteractive() {
757
+ return Boolean(process.stdin.isTTY) && !process.env.CI;
758
+ }
759
+ function handleCancel(value) {
760
+ if (p.isCancel(value)) {
761
+ p.cancel("Cancelled.");
762
+ process.exit(0);
763
+ }
764
+ return value;
765
+ }
766
+ async function promptForVariable(name, def) {
767
+ switch (def.type) {
768
+ case "enum": {
769
+ const value = await p.select({
770
+ message: def.description || name,
771
+ options: (def.options ?? []).map((opt) => ({
772
+ value: opt,
773
+ label: opt
774
+ })),
775
+ initialValue: def.default !== void 0 ? String(def.default) : void 0
776
+ });
777
+ return handleCancel(value);
778
+ }
779
+ case "boolean": {
780
+ const value = await p.confirm({
781
+ message: def.description || name,
782
+ initialValue: def.default !== void 0 ? Boolean(def.default) : false
783
+ });
784
+ return handleCancel(value);
785
+ }
786
+ case "number": {
787
+ const value = await p.text({
788
+ message: def.description || name,
789
+ placeholder: def.default !== void 0 ? String(def.default) : void 0,
790
+ defaultValue: def.default !== void 0 ? String(def.default) : void 0,
791
+ validate(input) {
792
+ if (isNaN(Number(input))) return "Must be a number";
793
+ }
794
+ });
795
+ return Number(handleCancel(value));
796
+ }
797
+ case "string":
798
+ default: {
799
+ const value = await p.text({
800
+ message: def.description || name,
801
+ placeholder: def.default !== void 0 ? String(def.default) : void 0,
802
+ defaultValue: def.default !== void 0 ? String(def.default) : void 0,
803
+ validate(input) {
804
+ if (def.required && !input) return `${name} is required`;
805
+ }
806
+ });
807
+ return handleCancel(value);
808
+ }
809
+ }
810
+ }
811
+ async function promptForVariables(variableDefs, provided, noInput = false) {
812
+ const values = {};
813
+ for (const [name, def] of Object.entries(variableDefs)) {
814
+ if (def.type === "resolved") continue;
815
+ if (provided[name] !== void 0) {
816
+ values[name] = coerceValue2(provided[name], def);
817
+ } else if (def.default !== void 0) {
818
+ if (isInteractive() && !noInput) {
819
+ values[name] = await promptForVariable(name, def);
820
+ } else {
821
+ values[name] = def.default;
822
+ }
823
+ } else if (def.required) {
824
+ if (isInteractive() && !noInput) {
825
+ values[name] = await promptForVariable(name, def);
826
+ } else {
827
+ throw new Error(`Missing required variable: ${name} -- ${def.description}`);
828
+ }
829
+ }
830
+ }
831
+ return values;
832
+ }
833
+ function coerceValue2(raw, def) {
834
+ switch (def.type) {
835
+ case "number":
836
+ return Number(raw);
837
+ case "boolean":
838
+ return raw === "true" || raw === "1" || raw === "yes";
839
+ case "enum":
840
+ if (def.options && !def.options.includes(raw)) {
841
+ throw new Error(
842
+ `Invalid value "${raw}" for enum variable. Options: ${def.options.join(", ")}`
843
+ );
844
+ }
845
+ return raw;
846
+ default:
847
+ return raw;
848
+ }
849
+ }
850
+ async function withSpinner(message, fn, successMessage) {
851
+ if (!isInteractive()) {
852
+ return fn();
853
+ }
854
+ const s = p.spinner();
855
+ s.start(message);
856
+ try {
857
+ const result = await fn();
858
+ s.stop(successMessage ?? message);
859
+ return result;
860
+ } catch (err) {
861
+ s.stop(`Failed: ${message}`);
862
+ throw err;
863
+ }
864
+ }
865
+ var init_prompts = __esm({
866
+ "src/lib/prompts.ts"() {
867
+ "use strict";
868
+ }
869
+ });
697
870
 
698
871
  // src/lib/installer.ts
872
+ import fs7 from "fs";
873
+ import path7 from "path";
874
+ import crypto from "crypto";
699
875
  function getInstallDir(type) {
700
876
  switch (type) {
701
877
  case "plan":
@@ -714,38 +890,60 @@ function contentHash(content) {
714
890
  }
715
891
  async function installPackage(packageName, options = {}) {
716
892
  const projectDir = options.projectDir ?? process.cwd();
893
+ const interactive = options.interactive ?? (isInteractive() && !options.noInput);
717
894
  const locked = getLockedVersion(packageName, projectDir);
718
895
  if (locked && !options.version) {
719
896
  logger.dim(`${packageName}@${locked.version} already installed`);
720
897
  return;
721
898
  }
722
- logger.info(`Resolving ${packageName}...`);
723
- const { version, metadata } = await resolveVersion(packageName, options.version);
724
- const versionMeta = await fetchVersionMetadata(packageName, version);
725
- logger.info(`Fetching ${packageName}@${version}...`);
726
- const basePath = versionMeta.source.path ? `${versionMeta.source.path}/` : "";
727
- const manifestRaw = await fetchFileAtTag(
728
- versionMeta.source.repository,
729
- versionMeta.source.tag,
730
- `${basePath}planmode.yaml`
731
- );
732
- const manifest = parseManifest(manifestRaw);
733
- let content;
734
- if (manifest.content) {
735
- content = manifest.content;
736
- } else if (manifest.content_file) {
737
- content = await fetchFileAtTag(
899
+ const resolveAndFetch = async () => {
900
+ const { version: version2, metadata: metadata2 } = await resolveVersion(packageName, options.version);
901
+ const versionMeta2 = await fetchVersionMetadata(packageName, version2);
902
+ return { version: version2, metadata: metadata2, versionMeta: versionMeta2 };
903
+ };
904
+ const { version, metadata, versionMeta } = interactive ? await withSpinner(
905
+ `Resolving ${packageName}...`,
906
+ resolveAndFetch,
907
+ `Resolved ${packageName}`
908
+ ) : await (async () => {
909
+ logger.info(`Resolving ${packageName}...`);
910
+ return resolveAndFetch();
911
+ })();
912
+ const fetchContent = async () => {
913
+ const basePath = versionMeta.source.path ? `${versionMeta.source.path}/` : "";
914
+ const manifestRaw = await fetchFileAtTag(
738
915
  versionMeta.source.repository,
739
916
  versionMeta.source.tag,
740
- `${basePath}${manifest.content_file}`
917
+ `${basePath}planmode.yaml`
741
918
  );
742
- } else {
743
- throw new Error("Package has no content or content_file");
744
- }
919
+ const manifest2 = parseManifest(manifestRaw);
920
+ let content2;
921
+ if (manifest2.content) {
922
+ content2 = manifest2.content;
923
+ } else if (manifest2.content_file) {
924
+ content2 = await fetchFileAtTag(
925
+ versionMeta.source.repository,
926
+ versionMeta.source.tag,
927
+ `${basePath}${manifest2.content_file}`
928
+ );
929
+ } else {
930
+ throw new Error("Package has no content or content_file");
931
+ }
932
+ return { manifest: manifest2, content: content2 };
933
+ };
934
+ const { manifest, content: rawContent } = interactive ? await withSpinner(
935
+ `Fetching ${packageName}@${version}...`,
936
+ fetchContent,
937
+ `Fetched ${packageName}@${version}`
938
+ ) : await (async () => {
939
+ logger.info(`Fetching ${packageName}@${version}...`);
940
+ return fetchContent();
941
+ })();
942
+ let content = rawContent;
745
943
  if (manifest.variables && Object.keys(manifest.variables).length > 0) {
746
944
  const provided = options.variables ?? {};
747
- if (options.noInput) {
748
- const values = collectVariableValues(manifest.variables, provided);
945
+ if (interactive) {
946
+ const values = await promptForVariables(manifest.variables, provided, false);
749
947
  content = renderTemplate(content, values);
750
948
  } else {
751
949
  const values = collectVariableValues(manifest.variables, provided);
@@ -807,7 +1005,8 @@ async function installPackage(packageName, options = {}) {
807
1005
  await installPackage(name, {
808
1006
  version: range === "*" ? void 0 : range,
809
1007
  projectDir,
810
- noInput: options.noInput
1008
+ noInput: options.noInput,
1009
+ interactive: options.interactive
811
1010
  });
812
1011
  }
813
1012
  }
@@ -844,78 +1043,1187 @@ async function updatePackage(packageName, projectDir = process.cwd()) {
844
1043
  await installPackage(packageName, { version, projectDir });
845
1044
  return true;
846
1045
  }
847
-
848
- // src/commands/install.ts
849
- function parseVariables(pairs) {
850
- const vars = {};
851
- for (const pair of pairs) {
852
- const eq = pair.indexOf("=");
853
- if (eq === -1) {
854
- throw new Error(`Invalid variable format: "${pair}". Use --set key=value`);
855
- }
856
- vars[pair.slice(0, eq)] = pair.slice(eq + 1);
1046
+ var init_installer = __esm({
1047
+ "src/lib/installer.ts"() {
1048
+ "use strict";
1049
+ init_resolver();
1050
+ init_registry();
1051
+ init_git();
1052
+ init_lockfile();
1053
+ init_claude_md();
1054
+ init_manifest();
1055
+ init_template();
1056
+ init_logger();
1057
+ init_analytics();
1058
+ init_prompts();
857
1059
  }
858
- return vars;
1060
+ });
1061
+
1062
+ // src/lib/templates.ts
1063
+ function getPlanTemplate(name) {
1064
+ return `# ${name}
1065
+
1066
+ ## Prerequisites
1067
+
1068
+ - List any tools, dependencies, or setup required before starting
1069
+
1070
+ ## Steps
1071
+
1072
+ 1. **Step one** \u2014 Description of what to do first
1073
+ 2. **Step two** \u2014 Description of what to do next
1074
+ 3. **Step three** \u2014 Description of the final step
1075
+
1076
+ ## Verification
1077
+
1078
+ - [ ] Verify step one completed successfully
1079
+ - [ ] Verify step two completed successfully
1080
+ - [ ] Verify the final result works as expected
1081
+ `;
859
1082
  }
860
- var installCommand = new Command("install").description("Install a package into the current project").argument("<package>", "Package name (e.g., nextjs-tailwind-starter)").option("-v, --version <version>", "Install specific version").option("--rule", "Force install as a rule to .claude/rules/").option("--no-input", "Fail if any required variable is missing").option("--set <key=value...>", "Set template variables (e.g., --set project_name=myapp)").action(
861
- async (packageName, options) => {
862
- try {
863
- logger.blank();
864
- const variables = options.set ? parseVariables(options.set) : void 0;
865
- await installPackage(packageName, {
866
- version: options.version,
867
- forceRule: options.rule,
868
- noInput: options.input === false,
869
- variables
870
- });
871
- logger.blank();
872
- } catch (err) {
873
- logger.error(err.message);
874
- process.exit(1);
875
- }
876
- }
877
- );
1083
+ function getRuleTemplate(name) {
1084
+ return `# ${name}
878
1085
 
879
- // src/commands/uninstall.ts
880
- import { Command as Command2 } from "commander";
881
- var uninstallCommand = new Command2("uninstall").description("Remove an installed package").argument("<package>", "Package name").action(async (packageName) => {
882
- try {
883
- logger.blank();
884
- await uninstallPackage(packageName);
885
- logger.blank();
886
- } catch (err) {
887
- logger.error(err.message);
888
- process.exit(1);
1086
+ ## Code Style
1087
+
1088
+ - Follow consistent naming conventions
1089
+ - Keep functions small and focused
1090
+
1091
+ ## Best Practices
1092
+
1093
+ - Prefer composition over inheritance
1094
+ - Write self-documenting code
1095
+
1096
+ ## Avoid
1097
+
1098
+ - Do not use deprecated APIs
1099
+ - Do not ignore error handling
1100
+ `;
1101
+ }
1102
+ function getPromptTemplate(name) {
1103
+ return `# ${name}
1104
+
1105
+ {{description}}
1106
+
1107
+ ## Context
1108
+
1109
+ Provide any relevant context here.
1110
+
1111
+ ## Requirements
1112
+
1113
+ - Requirement one
1114
+ - Requirement two
1115
+
1116
+ ## Output Format
1117
+
1118
+ Describe the expected output format.
1119
+ `;
1120
+ }
1121
+ var init_templates = __esm({
1122
+ "src/lib/templates.ts"() {
1123
+ "use strict";
889
1124
  }
890
1125
  });
891
1126
 
892
- // src/commands/search.ts
893
- import { Command as Command3 } from "commander";
894
- var searchCommand = new Command3("search").description("Search the registry for packages").argument("<query>", "Search query").option("--type <type>", "Filter by type (prompt, rule, plan)").option("--category <category>", "Filter by category").option("--json", "Output as JSON").action(async (query, options) => {
895
- try {
896
- const results = await searchPackages(query, {
897
- type: options.type,
898
- category: options.category
899
- });
900
- if (results.length === 0) {
901
- logger.info("No packages found matching your query.");
1127
+ // src/lib/init.ts
1128
+ import fs9 from "fs";
1129
+ import path9 from "path";
1130
+ import { stringify as stringify3 } from "yaml";
1131
+ function createPackage(options) {
1132
+ const {
1133
+ name,
1134
+ type,
1135
+ description,
1136
+ author,
1137
+ license = "MIT",
1138
+ tags = [],
1139
+ category = "other",
1140
+ projectDir = process.cwd()
1141
+ } = options;
1142
+ const manifest = {
1143
+ name,
1144
+ version: "1.0.0",
1145
+ type,
1146
+ description,
1147
+ author,
1148
+ license
1149
+ };
1150
+ if (tags.length > 0) manifest["tags"] = tags;
1151
+ manifest["category"] = category;
1152
+ const contentFile = `${type}.md`;
1153
+ manifest["content_file"] = contentFile;
1154
+ const yamlContent = stringify3(manifest);
1155
+ const manifestPath = path9.join(projectDir, "planmode.yaml");
1156
+ fs9.writeFileSync(manifestPath, yamlContent, "utf-8");
1157
+ const stubs = {
1158
+ plan: getPlanTemplate(name),
1159
+ rule: getRuleTemplate(name),
1160
+ prompt: getPromptTemplate(name)
1161
+ };
1162
+ const contentPath = path9.join(projectDir, contentFile);
1163
+ fs9.writeFileSync(contentPath, stubs[type] ?? stubs["plan"], "utf-8");
1164
+ return {
1165
+ files: ["planmode.yaml", contentFile],
1166
+ manifestPath,
1167
+ contentPath
1168
+ };
1169
+ }
1170
+ var init_init = __esm({
1171
+ "src/lib/init.ts"() {
1172
+ "use strict";
1173
+ init_templates();
1174
+ }
1175
+ });
1176
+
1177
+ // src/commands/init.ts
1178
+ var init_exports = {};
1179
+ __export(init_exports, {
1180
+ initCommand: () => initCommand,
1181
+ initInteractive: () => initInteractive
1182
+ });
1183
+ import { Command as Command9 } from "commander";
1184
+ import * as p6 from "@clack/prompts";
1185
+ async function initInteractive() {
1186
+ p6.intro("Create a new planmode package");
1187
+ const result = await p6.group(
1188
+ {
1189
+ name: () => p6.text({
1190
+ message: "Package name",
1191
+ placeholder: "my-awesome-plan",
1192
+ validate(input) {
1193
+ if (!input) return "Package name is required";
1194
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(input))
1195
+ return "Lowercase letters, numbers, and hyphens only";
1196
+ }
1197
+ }),
1198
+ type: () => p6.select({
1199
+ message: "Package type",
1200
+ options: [
1201
+ { value: "plan", label: "Plan", hint: "multi-step implementation guide" },
1202
+ { value: "rule", label: "Rule", hint: "always-on coding constraint" },
1203
+ { value: "prompt", label: "Prompt", hint: "single-use templated prompt" }
1204
+ ]
1205
+ }),
1206
+ description: () => p6.text({
1207
+ message: "Description",
1208
+ placeholder: "A short description of what this package does"
1209
+ }),
1210
+ author: () => p6.text({
1211
+ message: "Author (GitHub username)",
1212
+ placeholder: "username"
1213
+ }),
1214
+ license: () => p6.text({
1215
+ message: "License",
1216
+ defaultValue: "MIT",
1217
+ placeholder: "MIT"
1218
+ }),
1219
+ category: () => p6.select({
1220
+ message: "Category",
1221
+ options: CATEGORIES.map((cat) => ({ value: cat, label: cat })),
1222
+ initialValue: "other"
1223
+ }),
1224
+ tags: () => p6.text({
1225
+ message: "Tags (comma-separated)",
1226
+ placeholder: "nextjs, tailwind, starter"
1227
+ })
1228
+ },
1229
+ {
1230
+ onCancel() {
1231
+ p6.cancel("Cancelled.");
1232
+ process.exit(0);
1233
+ }
1234
+ }
1235
+ );
1236
+ const tags = result.tags ? result.tags.split(",").map((t) => t.trim().toLowerCase()).filter(Boolean) : [];
1237
+ const output = createPackage({
1238
+ name: result.name,
1239
+ type: result.type,
1240
+ description: result.description ?? "",
1241
+ author: result.author ?? "",
1242
+ license: result.license || "MIT",
1243
+ tags,
1244
+ category: result.category
1245
+ });
1246
+ p6.log.success(`Created ${output.files.join(", ")}`);
1247
+ p6.outro(`Edit ${output.files[1]}, then run \`planmode publish\` when ready.`);
1248
+ }
1249
+ var CATEGORIES, initCommand;
1250
+ var init_init2 = __esm({
1251
+ "src/commands/init.ts"() {
1252
+ "use strict";
1253
+ init_logger();
1254
+ init_init();
1255
+ init_prompts();
1256
+ CATEGORIES = [
1257
+ "frontend",
1258
+ "backend",
1259
+ "devops",
1260
+ "database",
1261
+ "testing",
1262
+ "mobile",
1263
+ "ai-ml",
1264
+ "design",
1265
+ "security",
1266
+ "other"
1267
+ ];
1268
+ initCommand = new Command9("init").description("Initialize a new package in the current directory").action(async () => {
1269
+ try {
1270
+ if (isInteractive()) {
1271
+ await initInteractive();
1272
+ } else {
1273
+ logger.error("Interactive terminal required for `planmode init`. Use a TTY.");
1274
+ process.exit(1);
1275
+ }
1276
+ } catch (err) {
1277
+ logger.error(err.message);
1278
+ process.exit(1);
1279
+ }
1280
+ });
1281
+ }
1282
+ });
1283
+
1284
+ // src/lib/doctor.ts
1285
+ import fs10 from "fs";
1286
+ import path10 from "path";
1287
+ import crypto2 from "crypto";
1288
+ function computeHash(content) {
1289
+ return `sha256:${crypto2.createHash("sha256").update(content).digest("hex")}`;
1290
+ }
1291
+ function runDoctor(projectDir = process.cwd()) {
1292
+ const issues = [];
1293
+ const lockfile = readLockfile(projectDir);
1294
+ const entries = Object.entries(lockfile.packages);
1295
+ for (const [name, entry] of entries) {
1296
+ const fullPath = path10.join(projectDir, entry.installed_to);
1297
+ if (!fs10.existsSync(fullPath)) {
1298
+ issues.push({
1299
+ severity: "error",
1300
+ message: `Missing file for "${name}": ${entry.installed_to}`,
1301
+ fix: `Run \`planmode install ${name}\` to reinstall`
1302
+ });
1303
+ continue;
1304
+ }
1305
+ const content = fs10.readFileSync(fullPath, "utf-8");
1306
+ const actualHash = computeHash(content);
1307
+ if (actualHash !== entry.content_hash) {
1308
+ issues.push({
1309
+ severity: "warning",
1310
+ message: `Content hash mismatch for "${name}" at ${entry.installed_to}`,
1311
+ fix: "File was modified locally. Run `planmode update " + name + "` to restore, or ignore if intentional"
1312
+ });
1313
+ }
1314
+ }
1315
+ const claudeMdPath = path10.join(projectDir, "CLAUDE.md");
1316
+ const imports = listImports(projectDir);
1317
+ const installedPlans = entries.filter(([, entry]) => entry.type === "plan").map(([name]) => name);
1318
+ for (const planName of installedPlans) {
1319
+ if (!imports.includes(planName)) {
1320
+ issues.push({
1321
+ severity: "error",
1322
+ message: `Plan "${planName}" is installed but missing from CLAUDE.md imports`,
1323
+ fix: `Add \`- @plans/${planName}.md\` to the # Planmode section of CLAUDE.md`
1324
+ });
1325
+ }
1326
+ }
1327
+ for (const importName of imports) {
1328
+ if (!installedPlans.includes(importName)) {
1329
+ const filePath = path10.join(projectDir, "plans", `${importName}.md`);
1330
+ if (!fs10.existsSync(filePath)) {
1331
+ issues.push({
1332
+ severity: "error",
1333
+ message: `CLAUDE.md imports "${importName}" but the file doesn't exist at plans/${importName}.md`,
1334
+ fix: `Run \`planmode install ${importName}\` or remove the import from CLAUDE.md`
1335
+ });
1336
+ } else {
1337
+ issues.push({
1338
+ severity: "warning",
1339
+ message: `CLAUDE.md imports "${importName}" but it's not tracked in planmode.lock`,
1340
+ fix: "This plan was added manually. No action needed unless you want lockfile tracking."
1341
+ });
1342
+ }
1343
+ }
1344
+ }
1345
+ if (installedPlans.length > 0 && !fs10.existsSync(claudeMdPath)) {
1346
+ issues.push({
1347
+ severity: "error",
1348
+ message: "CLAUDE.md is missing but plans are installed",
1349
+ fix: "Run `planmode install <any-plan>` to recreate it, or create it manually with a # Planmode section"
1350
+ });
1351
+ }
1352
+ const plansDir = path10.join(projectDir, "plans");
1353
+ if (fs10.existsSync(plansDir)) {
1354
+ const planFiles = fs10.readdirSync(plansDir).filter((f) => f.endsWith(".md"));
1355
+ for (const file of planFiles) {
1356
+ const name = file.replace(/\.md$/, "");
1357
+ if (!lockfile.packages[name]) {
1358
+ issues.push({
1359
+ severity: "warning",
1360
+ message: `Untracked plan file: plans/${file}`,
1361
+ fix: "This file isn't managed by planmode. Ignore if intentional."
1362
+ });
1363
+ }
1364
+ }
1365
+ }
1366
+ return {
1367
+ issues,
1368
+ packagesChecked: entries.length,
1369
+ healthy: issues.filter((i) => i.severity === "error").length === 0
1370
+ };
1371
+ }
1372
+ var init_doctor = __esm({
1373
+ "src/lib/doctor.ts"() {
1374
+ "use strict";
1375
+ init_lockfile();
1376
+ init_claude_md();
1377
+ }
1378
+ });
1379
+
1380
+ // src/lib/context.ts
1381
+ import fs15 from "fs";
1382
+ import path15 from "path";
1383
+ import { parse as parse4, stringify as stringify6 } from "yaml";
1384
+ function getContextPath(projectDir) {
1385
+ return path15.join(projectDir, CONTEXT_DIR, CONTEXT_FILE);
1386
+ }
1387
+ function emptyIndex() {
1388
+ return { version: 1, repos: [] };
1389
+ }
1390
+ function readContextIndex(projectDir = process.cwd()) {
1391
+ const filePath = getContextPath(projectDir);
1392
+ try {
1393
+ const raw = fs15.readFileSync(filePath, "utf-8");
1394
+ const data = parse4(raw);
1395
+ return data ?? emptyIndex();
1396
+ } catch {
1397
+ return emptyIndex();
1398
+ }
1399
+ }
1400
+ function writeContextIndex(index, projectDir = process.cwd()) {
1401
+ const dirPath = path15.join(projectDir, CONTEXT_DIR);
1402
+ fs15.mkdirSync(dirPath, { recursive: true });
1403
+ const filePath = getContextPath(projectDir);
1404
+ fs15.writeFileSync(filePath, stringify6(index), "utf-8");
1405
+ }
1406
+ function walkDirectory(dirPath) {
1407
+ const files = [];
1408
+ function walk(currentPath) {
1409
+ let entries;
1410
+ try {
1411
+ entries = fs15.readdirSync(currentPath, { withFileTypes: true });
1412
+ } catch {
1413
+ return;
1414
+ }
1415
+ for (const entry of entries) {
1416
+ if (entry.name.startsWith(".") && IGNORED_DIRS.has(entry.name)) continue;
1417
+ if (IGNORED_DIRS.has(entry.name)) continue;
1418
+ const fullPath = path15.join(currentPath, entry.name);
1419
+ if (entry.isDirectory()) {
1420
+ walk(fullPath);
1421
+ } else if (entry.isFile()) {
1422
+ const ext = path15.extname(entry.name).toLowerCase();
1423
+ if (!SUPPORTED_EXTENSIONS.has(ext)) continue;
1424
+ try {
1425
+ const stat = fs15.statSync(fullPath);
1426
+ const relativePath = path15.relative(dirPath, fullPath);
1427
+ files.push({
1428
+ path: relativePath,
1429
+ extension: ext,
1430
+ size: stat.size,
1431
+ modified_at: stat.mtime.toISOString()
1432
+ });
1433
+ } catch {
1434
+ }
1435
+ }
1436
+ }
1437
+ }
1438
+ walk(dirPath);
1439
+ return files;
1440
+ }
1441
+ function addContextRepo(repoPath, options = {}) {
1442
+ const projectDir = options.projectDir ?? process.cwd();
1443
+ const absolutePath = path15.resolve(projectDir, repoPath);
1444
+ if (!fs15.existsSync(absolutePath)) {
1445
+ throw new Error(`Directory not found: ${repoPath}`);
1446
+ }
1447
+ if (!fs15.statSync(absolutePath).isDirectory()) {
1448
+ throw new Error(`Not a directory: ${repoPath}`);
1449
+ }
1450
+ const index = readContextIndex(projectDir);
1451
+ const relative = path15.relative(projectDir, absolutePath);
1452
+ const isInsideProject = !relative.startsWith("..") && !path15.isAbsolute(relative);
1453
+ const storedPath = isInsideProject ? relative : absolutePath;
1454
+ const existing = index.repos.find(
1455
+ (r) => r.repo.path === storedPath || r.repo.name === options.name
1456
+ );
1457
+ if (existing) {
1458
+ throw new Error(
1459
+ `Context repo already exists: ${existing.repo.name ?? existing.repo.path}. Use \`planmode context reindex\` to refresh.`
1460
+ );
1461
+ }
1462
+ logger.info(`Scanning ${absolutePath}...`);
1463
+ const files = walkDirectory(absolutePath);
1464
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1465
+ const repoIndex = {
1466
+ repo: {
1467
+ path: storedPath,
1468
+ name: options.name,
1469
+ added_at: now
1470
+ },
1471
+ files,
1472
+ indexed_at: now,
1473
+ file_count: files.length,
1474
+ total_size: files.reduce((sum, f) => sum + f.size, 0)
1475
+ };
1476
+ index.repos.push(repoIndex);
1477
+ writeContextIndex(index, projectDir);
1478
+ logger.success(`Added "${options.name ?? storedPath}" \u2014 ${files.length} file(s), ${formatSize(repoIndex.total_size)}`);
1479
+ const breakdown = getTypeBreakdown(files);
1480
+ if (breakdown.length > 0) {
1481
+ logger.dim(` ${breakdown.join(", ")}`);
1482
+ }
1483
+ return repoIndex;
1484
+ }
1485
+ function removeContextRepo(pathOrName, projectDir = process.cwd()) {
1486
+ const index = readContextIndex(projectDir);
1487
+ const idx = index.repos.findIndex(
1488
+ (r) => r.repo.path === pathOrName || r.repo.name === pathOrName
1489
+ );
1490
+ if (idx === -1) {
1491
+ throw new Error(`Context repo not found: ${pathOrName}`);
1492
+ }
1493
+ const removed = index.repos[idx];
1494
+ index.repos.splice(idx, 1);
1495
+ writeContextIndex(index, projectDir);
1496
+ logger.success(`Removed "${removed.repo.name ?? removed.repo.path}"`);
1497
+ }
1498
+ function reindexContext(pathOrName, projectDir = process.cwd()) {
1499
+ const index = readContextIndex(projectDir);
1500
+ if (index.repos.length === 0) {
1501
+ throw new Error("No context repos configured. Use `planmode context add <path>` first.");
1502
+ }
1503
+ const targets = pathOrName ? index.repos.filter(
1504
+ (r) => r.repo.path === pathOrName || r.repo.name === pathOrName
1505
+ ) : index.repos;
1506
+ if (pathOrName && targets.length === 0) {
1507
+ throw new Error(`Context repo not found: ${pathOrName}`);
1508
+ }
1509
+ for (const repo of targets) {
1510
+ const absolutePath = path15.resolve(projectDir, repo.repo.path);
1511
+ if (!fs15.existsSync(absolutePath)) {
1512
+ logger.warn(`Directory not found, skipping: ${repo.repo.path}`);
1513
+ continue;
1514
+ }
1515
+ logger.info(`Re-scanning ${repo.repo.name ?? repo.repo.path}...`);
1516
+ const files = walkDirectory(absolutePath);
1517
+ repo.files = files;
1518
+ repo.indexed_at = (/* @__PURE__ */ new Date()).toISOString();
1519
+ repo.file_count = files.length;
1520
+ repo.total_size = files.reduce((sum, f) => sum + f.size, 0);
1521
+ logger.success(`Reindexed "${repo.repo.name ?? repo.repo.path}" \u2014 ${files.length} file(s), ${formatSize(repo.total_size)}`);
1522
+ }
1523
+ writeContextIndex(index, projectDir);
1524
+ }
1525
+ function getContextSummary(projectDir = process.cwd()) {
1526
+ const index = readContextIndex(projectDir);
1527
+ return {
1528
+ totalRepos: index.repos.length,
1529
+ totalFiles: index.repos.reduce((sum, r) => sum + r.file_count, 0),
1530
+ totalSize: index.repos.reduce((sum, r) => sum + r.total_size, 0),
1531
+ repos: index.repos.map((r) => ({
1532
+ name: r.repo.name ?? r.repo.path,
1533
+ path: r.repo.path,
1534
+ fileCount: r.file_count,
1535
+ totalSize: r.total_size,
1536
+ typeBreakdown: getTypeBreakdown(r.files),
1537
+ indexedAt: r.indexed_at
1538
+ }))
1539
+ };
1540
+ }
1541
+ function getTypeBreakdown(files) {
1542
+ const counts = /* @__PURE__ */ new Map();
1543
+ for (const file of files) {
1544
+ counts.set(file.extension, (counts.get(file.extension) ?? 0) + 1);
1545
+ }
1546
+ return Array.from(counts.entries()).sort((a, b) => b[1] - a[1]).map(([ext, count]) => `${ext}: ${count}`);
1547
+ }
1548
+ function formatSize(bytes) {
1549
+ if (bytes < 1024) return `${bytes} B`;
1550
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1551
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1552
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
1553
+ }
1554
+ var CONTEXT_DIR, CONTEXT_FILE, SUPPORTED_EXTENSIONS, IGNORED_DIRS;
1555
+ var init_context = __esm({
1556
+ "src/lib/context.ts"() {
1557
+ "use strict";
1558
+ init_logger();
1559
+ CONTEXT_DIR = ".planmode";
1560
+ CONTEXT_FILE = "context.yaml";
1561
+ SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
1562
+ ".txt",
1563
+ ".md",
1564
+ ".markdown",
1565
+ ".pdf",
1566
+ ".rtf",
1567
+ ".doc",
1568
+ ".docx",
1569
+ ".csv",
1570
+ ".tsv",
1571
+ ".json",
1572
+ ".yaml",
1573
+ ".yml",
1574
+ ".xml",
1575
+ ".html",
1576
+ ".htm",
1577
+ ".rst",
1578
+ ".org",
1579
+ ".tex",
1580
+ ".log"
1581
+ ]);
1582
+ IGNORED_DIRS = /* @__PURE__ */ new Set([
1583
+ "node_modules",
1584
+ ".git",
1585
+ "dist",
1586
+ "build",
1587
+ ".next",
1588
+ "__pycache__",
1589
+ ".venv",
1590
+ "venv",
1591
+ ".tox",
1592
+ "target",
1593
+ "out",
1594
+ ".cache",
1595
+ ".turbo",
1596
+ "coverage",
1597
+ ".nyc_output"
1598
+ ]);
1599
+ }
1600
+ });
1601
+
1602
+ // src/commands/interactive.ts
1603
+ var interactive_exports = {};
1604
+ __export(interactive_exports, {
1605
+ runInteractiveMenu: () => runInteractiveMenu
1606
+ });
1607
+ import fs16 from "fs";
1608
+ import path16 from "path";
1609
+ import os3 from "os";
1610
+ import * as p12 from "@clack/prompts";
1611
+ function isFirstRun() {
1612
+ const configPath = path16.join(os3.homedir(), ".planmode", "config");
1613
+ const hasConfig = fs16.existsSync(configPath);
1614
+ const lockfile = readLockfile();
1615
+ const hasPackages = Object.keys(lockfile.packages).length > 0;
1616
+ return !hasConfig && !hasPackages;
1617
+ }
1618
+ async function runInteractiveMenu() {
1619
+ if (isFirstRun()) {
1620
+ await firstRunFlow();
1621
+ } else {
1622
+ await mainMenu();
1623
+ }
1624
+ }
1625
+ async function firstRunFlow() {
1626
+ p12.intro("planmode");
1627
+ p12.note(
1628
+ [
1629
+ "planmode installs AI plans, rules, and prompts into your project.",
1630
+ "Plans work with Claude Code automatically via CLAUDE.md imports.",
1631
+ "",
1632
+ " Plans - step-by-step guides Claude follows to build things",
1633
+ " Rules - always-on constraints that shape every AI interaction",
1634
+ " Prompts - reusable templates you run once to get output"
1635
+ ].join("\n"),
1636
+ "Welcome"
1637
+ );
1638
+ const action = handleCancel(
1639
+ await p12.select({
1640
+ message: "Let's get you started. What would you like to do?",
1641
+ options: [
1642
+ { value: "browse", label: "Browse popular packages", hint: "see what's available" },
1643
+ { value: "search", label: "Search for something specific" },
1644
+ { value: "create", label: "Create your own package", hint: "start from scratch" }
1645
+ ]
1646
+ })
1647
+ );
1648
+ switch (action) {
1649
+ case "browse":
1650
+ await featuredFlow();
1651
+ break;
1652
+ case "search":
1653
+ await searchFlow();
1654
+ break;
1655
+ case "create": {
1656
+ const { initInteractive: initInteractive2 } = await Promise.resolve().then(() => (init_init2(), init_exports));
1657
+ await initInteractive2();
1658
+ break;
1659
+ }
1660
+ }
1661
+ const cont = handleCancel(
1662
+ await p12.confirm({
1663
+ message: "Continue exploring?",
1664
+ initialValue: true
1665
+ })
1666
+ );
1667
+ if (cont) {
1668
+ await mainMenu();
1669
+ } else {
1670
+ p12.outro("Run `planmode` anytime to come back.");
1671
+ }
1672
+ }
1673
+ async function featuredFlow() {
1674
+ const index = await withSpinner(
1675
+ "Loading packages...",
1676
+ () => fetchIndex()
1677
+ );
1678
+ const plans = index.packages.filter((pkg) => pkg.type === "plan");
1679
+ const rules = index.packages.filter((pkg) => pkg.type === "rule");
1680
+ const prompts = index.packages.filter((pkg) => pkg.type === "prompt");
1681
+ const featured = [
1682
+ ...plans.slice(0, 5),
1683
+ ...rules.slice(0, 3),
1684
+ ...prompts.slice(0, 3)
1685
+ ];
1686
+ if (featured.length === 0) {
1687
+ p12.log.warn("No packages in the registry yet.");
1688
+ return;
1689
+ }
1690
+ const planOptions = plans.slice(0, 5).map((pkg) => ({
1691
+ value: pkg.name,
1692
+ label: pkg.name,
1693
+ hint: pkg.description.length > 55 ? pkg.description.slice(0, 55) + "..." : pkg.description
1694
+ }));
1695
+ const ruleOptions = rules.slice(0, 3).map((pkg) => ({
1696
+ value: pkg.name,
1697
+ label: pkg.name,
1698
+ hint: pkg.description.length > 55 ? pkg.description.slice(0, 55) + "..." : pkg.description
1699
+ }));
1700
+ const promptOptions = prompts.slice(0, 3).map((pkg) => ({
1701
+ value: pkg.name,
1702
+ label: pkg.name,
1703
+ hint: pkg.description.length > 55 ? pkg.description.slice(0, 55) + "..." : pkg.description
1704
+ }));
1705
+ const allOptions = [];
1706
+ if (planOptions.length > 0) {
1707
+ allOptions.push(...planOptions);
1708
+ }
1709
+ if (ruleOptions.length > 0) {
1710
+ allOptions.push(...ruleOptions);
1711
+ }
1712
+ if (promptOptions.length > 0) {
1713
+ allOptions.push(...promptOptions);
1714
+ }
1715
+ const selected = handleCancel(
1716
+ await p12.select({
1717
+ message: `${index.packages.length} packages available. Pick one to install:`,
1718
+ options: [
1719
+ ...allOptions,
1720
+ { value: "__more__", label: "Browse by category..." }
1721
+ ]
1722
+ })
1723
+ );
1724
+ if (selected === "__more__") {
1725
+ await browseFlow();
1726
+ return;
1727
+ }
1728
+ await installOrDetailFlow(selected);
1729
+ }
1730
+ async function mainMenu() {
1731
+ p12.intro("planmode");
1732
+ while (true) {
1733
+ const action = handleCancel(
1734
+ await p12.select({
1735
+ message: "What would you like to do?",
1736
+ options: [
1737
+ { value: "search", label: "Search packages", hint: "find packages by keyword" },
1738
+ { value: "browse", label: "Browse by category" },
1739
+ { value: "install", label: "Install a package", hint: "install by name" },
1740
+ { value: "create", label: "Create a new package" },
1741
+ { value: "list", label: "My installed packages" },
1742
+ { value: "context", label: "Manage context", hint: "document directories for AI" },
1743
+ { value: "doctor", label: "Health check" },
1744
+ { value: "exit", label: "Exit" }
1745
+ ]
1746
+ })
1747
+ );
1748
+ switch (action) {
1749
+ case "search":
1750
+ await searchFlow();
1751
+ break;
1752
+ case "browse":
1753
+ await browseFlow();
1754
+ break;
1755
+ case "install":
1756
+ await installFlow();
1757
+ break;
1758
+ case "create": {
1759
+ const { initInteractive: initInteractive2 } = await Promise.resolve().then(() => (init_init2(), init_exports));
1760
+ await initInteractive2();
1761
+ break;
1762
+ }
1763
+ case "list":
1764
+ listFlow();
1765
+ break;
1766
+ case "context":
1767
+ await contextFlow();
1768
+ break;
1769
+ case "doctor":
1770
+ doctorFlow();
1771
+ break;
1772
+ case "exit":
1773
+ p12.outro("Goodbye!");
1774
+ return;
1775
+ }
1776
+ }
1777
+ }
1778
+ async function searchFlow() {
1779
+ const query = handleCancel(
1780
+ await p12.text({
1781
+ message: "Search for packages:",
1782
+ placeholder: "e.g. nextjs, tailwind, auth",
1783
+ validate(input) {
1784
+ if (!input) return "Please enter a search query";
1785
+ }
1786
+ })
1787
+ );
1788
+ const results = await withSpinner(
1789
+ "Searching registry...",
1790
+ () => searchPackages(query)
1791
+ );
1792
+ if (results.length === 0) {
1793
+ p12.log.warn("No packages found matching your query.");
1794
+ return;
1795
+ }
1796
+ p12.log.info(`Found ${results.length} package(s)`);
1797
+ await packageSelectionFlow(results.map((r) => ({
1798
+ name: r.name,
1799
+ type: r.type,
1800
+ version: r.version,
1801
+ description: r.description
1802
+ })));
1803
+ }
1804
+ async function browseFlow() {
1805
+ const category = handleCancel(
1806
+ await p12.select({
1807
+ message: "Select a category:",
1808
+ options: CATEGORIES2.map((cat) => ({
1809
+ value: cat,
1810
+ label: cat
1811
+ }))
1812
+ })
1813
+ );
1814
+ const results = await withSpinner(
1815
+ `Loading ${category} packages...`,
1816
+ () => searchPackages("", { category })
1817
+ );
1818
+ if (results.length === 0) {
1819
+ p12.log.warn(`No packages found in category "${category}".`);
1820
+ return;
1821
+ }
1822
+ p12.log.info(`Found ${results.length} package(s) in "${category}"`);
1823
+ await packageSelectionFlow(results.map((r) => ({
1824
+ name: r.name,
1825
+ type: r.type,
1826
+ version: r.version,
1827
+ description: r.description
1828
+ })));
1829
+ }
1830
+ async function packageSelectionFlow(packages) {
1831
+ const selected = handleCancel(
1832
+ await p12.select({
1833
+ message: "Select a package:",
1834
+ options: [
1835
+ ...packages.map((pkg) => ({
1836
+ value: pkg.name,
1837
+ label: `${pkg.name} (${pkg.type} v${pkg.version})`,
1838
+ hint: pkg.description.length > 60 ? pkg.description.slice(0, 60) + "..." : pkg.description
1839
+ })),
1840
+ { value: "__back__", label: "Back" }
1841
+ ]
1842
+ })
1843
+ );
1844
+ if (selected === "__back__") return;
1845
+ await installOrDetailFlow(selected);
1846
+ }
1847
+ async function installOrDetailFlow(packageName) {
1848
+ const action = handleCancel(
1849
+ await p12.select({
1850
+ message: `${packageName}:`,
1851
+ options: [
1852
+ { value: "install", label: "Install" },
1853
+ { value: "details", label: "View details" },
1854
+ { value: "back", label: "Back" }
1855
+ ]
1856
+ })
1857
+ );
1858
+ if (action === "install") {
1859
+ try {
1860
+ await installPackage(packageName, { interactive: true });
1861
+ p12.log.success(`Installed ${packageName}`);
1862
+ } catch (err) {
1863
+ p12.log.error(err.message);
1864
+ }
1865
+ } else if (action === "details") {
1866
+ try {
1867
+ const meta = await withSpinner(
1868
+ "Fetching package details...",
1869
+ () => fetchPackageMetadata(packageName)
1870
+ );
1871
+ const lines = [
1872
+ `Type: ${meta.type}`,
1873
+ `Author: ${meta.author}`,
1874
+ `License: ${meta.license}`,
1875
+ `Category: ${meta.category}`,
1876
+ `Downloads: ${meta.downloads.toLocaleString()}`,
1877
+ `Versions: ${meta.versions.join(", ")}`,
1878
+ `Repository: ${meta.repository}`
1879
+ ];
1880
+ if (meta.tags?.length) {
1881
+ lines.push(`Tags: ${meta.tags.join(", ")}`);
1882
+ }
1883
+ if (meta.dependencies?.rules?.length) {
1884
+ lines.push(`Dep (rules): ${meta.dependencies.rules.join(", ")}`);
1885
+ }
1886
+ if (meta.dependencies?.plans?.length) {
1887
+ lines.push(`Dep (plans): ${meta.dependencies.plans.join(", ")}`);
1888
+ }
1889
+ p12.note(lines.join("\n"), `${meta.name}@${meta.latest_version}`);
1890
+ const nextAction = handleCancel(
1891
+ await p12.confirm({
1892
+ message: "Install this package?",
1893
+ initialValue: true
1894
+ })
1895
+ );
1896
+ if (nextAction) {
1897
+ try {
1898
+ await installPackage(packageName, { interactive: true });
1899
+ p12.log.success(`Installed ${packageName}`);
1900
+ } catch (err) {
1901
+ p12.log.error(err.message);
1902
+ }
1903
+ }
1904
+ } catch (err) {
1905
+ p12.log.error(err.message);
1906
+ }
1907
+ }
1908
+ }
1909
+ async function installFlow() {
1910
+ const packageName = handleCancel(
1911
+ await p12.text({
1912
+ message: "Package name to install:",
1913
+ placeholder: "e.g. nextjs-tailwind-starter",
1914
+ validate(input) {
1915
+ if (!input) return "Please enter a package name";
1916
+ }
1917
+ })
1918
+ );
1919
+ try {
1920
+ await installPackage(packageName, { interactive: true });
1921
+ p12.log.success(`Installed ${packageName}`);
1922
+ } catch (err) {
1923
+ p12.log.error(err.message);
1924
+ }
1925
+ }
1926
+ async function contextFlow() {
1927
+ const summary = getContextSummary();
1928
+ if (summary.totalRepos === 0) {
1929
+ p12.log.info("No context repos yet.");
1930
+ } else {
1931
+ const lines = summary.repos.map(
1932
+ (r) => `${r.name} \u2014 ${r.fileCount} file(s), ${formatSize(r.totalSize)}`
1933
+ );
1934
+ p12.note(lines.join("\n"), `${summary.totalRepos} context repo(s)`);
1935
+ }
1936
+ const action = handleCancel(
1937
+ await p12.select({
1938
+ message: "What would you like to do?",
1939
+ options: [
1940
+ { value: "add", label: "Add a directory" },
1941
+ { value: "remove", label: "Remove a directory" },
1942
+ { value: "reindex", label: "Re-index all" },
1943
+ { value: "back", label: "Back" }
1944
+ ]
1945
+ })
1946
+ );
1947
+ if (action === "back") return;
1948
+ if (action === "add") {
1949
+ const dirPath = handleCancel(
1950
+ await p12.text({
1951
+ message: "Path to document directory:",
1952
+ placeholder: "e.g. docs, specs, ./reference",
1953
+ validate(input) {
1954
+ if (!input) return "Please enter a directory path";
1955
+ }
1956
+ })
1957
+ );
1958
+ const name = handleCancel(
1959
+ await p12.text({
1960
+ message: "Label (optional):",
1961
+ placeholder: "e.g. Project Documentation"
1962
+ })
1963
+ );
1964
+ try {
1965
+ await withSpinner(
1966
+ "Indexing documents...",
1967
+ async () => addContextRepo(dirPath, { name: name || void 0 }),
1968
+ "Indexing complete"
1969
+ );
1970
+ } catch (err) {
1971
+ p12.log.error(err.message);
1972
+ }
1973
+ } else if (action === "remove") {
1974
+ if (summary.totalRepos === 0) {
1975
+ p12.log.warn("No context repos to remove.");
1976
+ return;
1977
+ }
1978
+ const selected = handleCancel(
1979
+ await p12.select({
1980
+ message: "Select a repo to remove:",
1981
+ options: [
1982
+ ...summary.repos.map((r) => ({
1983
+ value: r.name,
1984
+ label: r.name,
1985
+ hint: `${r.fileCount} files, ${formatSize(r.totalSize)}`
1986
+ })),
1987
+ { value: "__back__", label: "Back" }
1988
+ ]
1989
+ })
1990
+ );
1991
+ if (selected === "__back__") return;
1992
+ try {
1993
+ removeContextRepo(selected);
1994
+ p12.log.success(`Removed "${selected}"`);
1995
+ } catch (err) {
1996
+ p12.log.error(err.message);
1997
+ }
1998
+ } else if (action === "reindex") {
1999
+ if (summary.totalRepos === 0) {
2000
+ p12.log.warn("No context repos to reindex.");
2001
+ return;
2002
+ }
2003
+ try {
2004
+ await withSpinner(
2005
+ "Re-scanning documents...",
2006
+ async () => reindexContext(),
2007
+ "Reindex complete"
2008
+ );
2009
+ } catch (err) {
2010
+ p12.log.error(err.message);
2011
+ }
2012
+ }
2013
+ }
2014
+ function listFlow() {
2015
+ const lockfile = readLockfile();
2016
+ const entries = Object.entries(lockfile.packages);
2017
+ if (entries.length === 0) {
2018
+ p12.log.info('No packages installed. Select "Install a package" to get started.');
2019
+ return;
2020
+ }
2021
+ const lines = entries.map(
2022
+ ([name, entry]) => `${name} (${entry.type} v${entry.version}) -> ${entry.installed_to}`
2023
+ );
2024
+ p12.note(lines.join("\n"), "Installed packages");
2025
+ }
2026
+ function doctorFlow() {
2027
+ const result = runDoctor();
2028
+ if (result.issues.length === 0) {
2029
+ p12.log.success(`Checked ${result.packagesChecked} package(s) \u2014 no issues found.`);
2030
+ return;
2031
+ }
2032
+ const errors = result.issues.filter((i) => i.severity === "error");
2033
+ const warnings = result.issues.filter((i) => i.severity === "warning");
2034
+ for (const issue of errors) {
2035
+ p12.log.error(issue.message);
2036
+ }
2037
+ for (const issue of warnings) {
2038
+ p12.log.warn(issue.message);
2039
+ }
2040
+ if (errors.length > 0) {
2041
+ p12.log.error(`${errors.length} error(s), ${warnings.length} warning(s)`);
2042
+ } else {
2043
+ p12.log.warn(`${warnings.length} warning(s)`);
2044
+ }
2045
+ }
2046
+ var CATEGORIES2;
2047
+ var init_interactive = __esm({
2048
+ "src/commands/interactive.ts"() {
2049
+ "use strict";
2050
+ init_prompts();
2051
+ init_registry();
2052
+ init_installer();
2053
+ init_lockfile();
2054
+ init_doctor();
2055
+ init_context();
2056
+ CATEGORIES2 = [
2057
+ "frontend",
2058
+ "backend",
2059
+ "devops",
2060
+ "database",
2061
+ "testing",
2062
+ "mobile",
2063
+ "ai-ml",
2064
+ "design",
2065
+ "security",
2066
+ "other"
2067
+ ];
2068
+ }
2069
+ });
2070
+
2071
+ // src/index.ts
2072
+ import { Command as Command17 } from "commander";
2073
+
2074
+ // src/commands/install.ts
2075
+ init_installer();
2076
+ init_logger();
2077
+ init_prompts();
2078
+ import { Command } from "commander";
2079
+ import * as p2 from "@clack/prompts";
2080
+ function parseVariables(pairs) {
2081
+ const vars = {};
2082
+ for (const pair of pairs) {
2083
+ const eq = pair.indexOf("=");
2084
+ if (eq === -1) {
2085
+ throw new Error(`Invalid variable format: "${pair}". Use --set key=value`);
2086
+ }
2087
+ vars[pair.slice(0, eq)] = pair.slice(eq + 1);
2088
+ }
2089
+ return vars;
2090
+ }
2091
+ var installCommand = new Command("install").description("Install a package into the current project").argument("<package>", "Package name (e.g., nextjs-tailwind-starter)").option("-v, --version <version>", "Install specific version").option("--rule", "Force install as a rule to .claude/rules/").option("--no-input", "Fail if any required variable is missing").option("--set <key=value...>", "Set template variables (e.g., --set project_name=myapp)").action(
2092
+ async (packageName, options) => {
2093
+ try {
2094
+ const interactive = isInteractive() && options.input !== false;
2095
+ const variables = options.set ? parseVariables(options.set) : void 0;
2096
+ if (interactive) {
2097
+ p2.intro(`Installing ${packageName}`);
2098
+ } else {
2099
+ logger.blank();
2100
+ }
2101
+ await installPackage(packageName, {
2102
+ version: options.version,
2103
+ forceRule: options.rule,
2104
+ noInput: options.input === false,
2105
+ variables,
2106
+ interactive
2107
+ });
2108
+ if (interactive) {
2109
+ p2.outro("Done!");
2110
+ } else {
2111
+ logger.blank();
2112
+ }
2113
+ } catch (err) {
2114
+ logger.error(err.message);
2115
+ process.exit(1);
2116
+ }
2117
+ }
2118
+ );
2119
+
2120
+ // src/commands/uninstall.ts
2121
+ init_installer();
2122
+ init_logger();
2123
+ import { Command as Command2 } from "commander";
2124
+ var uninstallCommand = new Command2("uninstall").description("Remove an installed package").argument("<package>", "Package name").action(async (packageName) => {
2125
+ try {
2126
+ logger.blank();
2127
+ await uninstallPackage(packageName);
2128
+ logger.blank();
2129
+ } catch (err) {
2130
+ logger.error(err.message);
2131
+ process.exit(1);
2132
+ }
2133
+ });
2134
+
2135
+ // src/commands/search.ts
2136
+ init_registry();
2137
+ init_installer();
2138
+ init_logger();
2139
+ init_prompts();
2140
+ import { Command as Command3 } from "commander";
2141
+ import * as p3 from "@clack/prompts";
2142
+ var searchCommand = new Command3("search").description("Search the registry for packages").argument("<query>", "Search query").option("--type <type>", "Filter by type (prompt, rule, plan)").option("--category <category>", "Filter by category").option("--json", "Output as JSON").action(async (query, options) => {
2143
+ try {
2144
+ const results = await withSpinner(
2145
+ "Searching registry...",
2146
+ () => searchPackages(query, {
2147
+ type: options.type,
2148
+ category: options.category
2149
+ })
2150
+ );
2151
+ if (results.length === 0) {
2152
+ if (isInteractive() && !options.json) {
2153
+ p3.log.warn("No packages found matching your query.");
2154
+ } else {
2155
+ logger.info("No packages found matching your query.");
2156
+ }
902
2157
  return;
903
2158
  }
904
2159
  if (options.json) {
905
2160
  console.log(JSON.stringify(results, null, 2));
906
2161
  return;
907
2162
  }
908
- logger.blank();
909
- logger.table(
910
- ["name", "type", "version", "description"],
911
- results.map((pkg) => [
912
- pkg.name,
913
- pkg.type,
914
- pkg.version,
915
- pkg.description.length > 50 ? pkg.description.slice(0, 50) + "..." : pkg.description
916
- ])
2163
+ if (!isInteractive()) {
2164
+ logger.blank();
2165
+ logger.table(
2166
+ ["name", "type", "version", "description"],
2167
+ results.map((pkg) => [
2168
+ pkg.name,
2169
+ pkg.type,
2170
+ pkg.version,
2171
+ pkg.description.length > 50 ? pkg.description.slice(0, 50) + "..." : pkg.description
2172
+ ])
2173
+ );
2174
+ logger.blank();
2175
+ return;
2176
+ }
2177
+ const selected = handleCancel(
2178
+ await p3.select({
2179
+ message: `Found ${results.length} package(s). Select one:`,
2180
+ options: [
2181
+ ...results.map((pkg) => ({
2182
+ value: pkg.name,
2183
+ label: `${pkg.name} (${pkg.type} v${pkg.version})`,
2184
+ hint: pkg.description.length > 60 ? pkg.description.slice(0, 60) + "..." : pkg.description
2185
+ })),
2186
+ { value: "__none__", label: "Cancel" }
2187
+ ]
2188
+ })
917
2189
  );
918
- logger.blank();
2190
+ if (selected === "__none__") return;
2191
+ const action = handleCancel(
2192
+ await p3.select({
2193
+ message: `${selected}:`,
2194
+ options: [
2195
+ { value: "install", label: "Install" },
2196
+ { value: "details", label: "View details" },
2197
+ { value: "back", label: "Cancel" }
2198
+ ]
2199
+ })
2200
+ );
2201
+ if (action === "install") {
2202
+ try {
2203
+ await installPackage(selected, { interactive: true });
2204
+ p3.log.success(`Installed ${selected}`);
2205
+ } catch (err) {
2206
+ p3.log.error(err.message);
2207
+ }
2208
+ } else if (action === "details") {
2209
+ const meta = await withSpinner(
2210
+ "Fetching package details...",
2211
+ () => fetchPackageMetadata(selected)
2212
+ );
2213
+ const lines = [
2214
+ `Type: ${meta.type}`,
2215
+ `Author: ${meta.author}`,
2216
+ `License: ${meta.license}`,
2217
+ `Category: ${meta.category}`,
2218
+ `Downloads: ${meta.downloads.toLocaleString()}`,
2219
+ `Versions: ${meta.versions.join(", ")}`,
2220
+ `Repository: ${meta.repository}`
2221
+ ];
2222
+ if (meta.tags?.length) {
2223
+ lines.push(`Tags: ${meta.tags.join(", ")}`);
2224
+ }
2225
+ p3.note(lines.join("\n"), `${meta.name}@${meta.latest_version}`);
2226
+ }
919
2227
  } catch (err) {
920
2228
  logger.error(err.message);
921
2229
  process.exit(1);
@@ -923,6 +2231,10 @@ var searchCommand = new Command3("search").description("Search the registry for
923
2231
  });
924
2232
 
925
2233
  // src/commands/run.ts
2234
+ init_manifest();
2235
+ init_template();
2236
+ init_logger();
2237
+ init_prompts();
926
2238
  import { Command as Command4 } from "commander";
927
2239
  import fs8 from "fs";
928
2240
  import path8 from "path";
@@ -957,18 +2269,8 @@ var runCommand = new Command4("run").description("Run a templated prompt and out
957
2269
  process.exit(1);
958
2270
  }
959
2271
  if (manifest?.variables && Object.keys(manifest.variables).length > 0) {
960
- const values = {};
961
- for (const [name, def] of Object.entries(manifest.variables)) {
962
- if (def.type === "resolved") continue;
963
- if (vars[name] !== void 0) {
964
- values[name] = vars[name];
965
- } else if (def.default !== void 0) {
966
- values[name] = def.default;
967
- } else if (def.required && options.input === false) {
968
- logger.error(`Missing required variable: --${name}`);
969
- process.exit(1);
970
- }
971
- }
2272
+ const noInput = options.input === false;
2273
+ const values = await promptForVariables(manifest.variables, vars, noInput);
972
2274
  for (const [name, def] of Object.entries(manifest.variables)) {
973
2275
  if (def.type !== "resolved") continue;
974
2276
  values[name] = await resolveVariable(def, values);
@@ -988,143 +2290,161 @@ var runCommand = new Command4("run").description("Run a templated prompt and out
988
2290
 
989
2291
  // src/commands/publish.ts
990
2292
  import { Command as Command5 } from "commander";
2293
+ import * as p4 from "@clack/prompts";
991
2294
 
992
2295
  // src/lib/publisher.ts
2296
+ init_manifest();
2297
+ init_config();
2298
+ init_git();
2299
+ init_logger();
2300
+ init_prompts();
993
2301
  async function publishPackage(options = {}) {
994
2302
  const cwd = options.projectDir ?? process.cwd();
2303
+ const interactive = options.interactive ?? false;
995
2304
  const token = options.token ?? getGitHubToken();
996
2305
  if (!token) {
997
2306
  throw new Error("Not authenticated. Run `planmode login` first.");
998
2307
  }
999
- logger.info("Reading planmode.yaml...");
1000
- const manifest = readManifest(cwd);
1001
- const errors = validateManifest(manifest, true);
1002
- if (errors.length > 0) {
1003
- throw new Error(`Invalid manifest:
2308
+ const doValidate = async () => {
2309
+ const manifest2 = readManifest(cwd);
2310
+ const errors = validateManifest(manifest2, true);
2311
+ if (errors.length > 0) {
2312
+ throw new Error(`Invalid manifest:
1004
2313
  ${errors.map((e) => ` - ${e}`).join("\n")}`);
1005
- }
2314
+ }
2315
+ return manifest2;
2316
+ };
2317
+ const manifest = interactive ? await withSpinner("Validating manifest...", doValidate, "Manifest valid") : await (async () => {
2318
+ logger.info("Reading planmode.yaml...");
2319
+ return doValidate();
2320
+ })();
1006
2321
  const remoteUrl = await getRemoteUrl(cwd);
1007
2322
  if (!remoteUrl) {
1008
2323
  throw new Error("No git remote found. Push your code to GitHub first.");
1009
2324
  }
1010
2325
  const sha = await getHeadSha(cwd);
1011
2326
  const tag = `v${manifest.version}`;
1012
- logger.info(`Creating tag ${tag}...`);
1013
- try {
1014
- await createTag(cwd, tag);
1015
- } catch {
1016
- logger.dim(`Tag ${tag} already exists, using existing`);
1017
- }
1018
- try {
1019
- await pushTag(cwd, tag);
1020
- logger.success(`Pushed tag ${tag}`);
1021
- } catch {
1022
- logger.dim(`Tag ${tag} already pushed`);
1023
- }
1024
- logger.info("Submitting to registry...");
1025
- const headers = {
1026
- Authorization: `Bearer ${token}`,
1027
- Accept: "application/vnd.github.v3+json",
1028
- "User-Agent": "planmode-cli",
1029
- "Content-Type": "application/json"
2327
+ const doTag = async () => {
2328
+ try {
2329
+ await createTag(cwd, tag);
2330
+ } catch {
2331
+ }
2332
+ try {
2333
+ await pushTag(cwd, tag);
2334
+ } catch {
2335
+ }
1030
2336
  };
1031
- await fetch("https://api.github.com/repos/kaihannonen/planmode.org/forks", {
1032
- method: "POST",
1033
- headers
1034
- });
1035
- const userRes = await fetch("https://api.github.com/user", { headers });
1036
- if (!userRes.ok) {
1037
- throw new Error("Failed to authenticate with GitHub. Check your token.");
2337
+ if (interactive) {
2338
+ await withSpinner(`Creating tag ${tag}...`, doTag, `Tag ${tag} ready`);
2339
+ } else {
2340
+ logger.info(`Creating tag ${tag}...`);
2341
+ await doTag();
2342
+ logger.success(`Pushed tag ${tag}`);
1038
2343
  }
1039
- const user = await userRes.json();
1040
- const repoPath = remoteUrl.replace(/^https?:\/\//, "").replace(/\.git$/, "");
1041
- const metadataContent = JSON.stringify(
1042
- {
1043
- name: manifest.name,
1044
- description: manifest.description,
1045
- author: manifest.author,
1046
- license: manifest.license,
1047
- repository: repoPath,
1048
- category: manifest.category ?? "other",
1049
- tags: manifest.tags ?? [],
1050
- type: manifest.type,
1051
- models: manifest.models ?? [],
1052
- latest_version: manifest.version,
1053
- versions: [manifest.version],
1054
- downloads: 0,
1055
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
1056
- updated_at: (/* @__PURE__ */ new Date()).toISOString(),
1057
- dependencies: manifest.dependencies,
1058
- variables: manifest.variables
1059
- },
1060
- null,
1061
- 2
1062
- );
1063
- const versionContent = JSON.stringify(
1064
- {
1065
- version: manifest.version,
1066
- published_at: (/* @__PURE__ */ new Date()).toISOString(),
1067
- source: {
2344
+ const doSubmit = async () => {
2345
+ const headers = {
2346
+ Authorization: `Bearer ${token}`,
2347
+ Accept: "application/vnd.github.v3+json",
2348
+ "User-Agent": "planmode-cli",
2349
+ "Content-Type": "application/json"
2350
+ };
2351
+ await fetch("https://api.github.com/repos/kaihannonen/planmode.org/forks", {
2352
+ method: "POST",
2353
+ headers
2354
+ });
2355
+ const userRes = await fetch("https://api.github.com/user", { headers });
2356
+ if (!userRes.ok) {
2357
+ throw new Error("Failed to authenticate with GitHub. Check your token.");
2358
+ }
2359
+ const user = await userRes.json();
2360
+ const repoPath = remoteUrl.replace(/^https?:\/\//, "").replace(/\.git$/, "");
2361
+ const metadataContent = JSON.stringify(
2362
+ {
2363
+ name: manifest.name,
2364
+ description: manifest.description,
2365
+ author: manifest.author,
2366
+ license: manifest.license,
1068
2367
  repository: repoPath,
1069
- tag,
1070
- sha
2368
+ category: manifest.category ?? "other",
2369
+ tags: manifest.tags ?? [],
2370
+ type: manifest.type,
2371
+ models: manifest.models ?? [],
2372
+ latest_version: manifest.version,
2373
+ versions: [manifest.version],
2374
+ downloads: 0,
2375
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
2376
+ updated_at: (/* @__PURE__ */ new Date()).toISOString(),
2377
+ dependencies: manifest.dependencies,
2378
+ variables: manifest.variables
1071
2379
  },
1072
- files: ["planmode.yaml", manifest.content_file ?? "inline"],
1073
- content_hash: `sha256:${sha.slice(0, 16)}`
1074
- },
1075
- null,
1076
- 2
1077
- );
1078
- const branchName = `add-${manifest.name}-${manifest.version}`;
1079
- const refRes = await fetch(
1080
- `https://api.github.com/repos/${user.login}/planmode.org/git/ref/heads/main`,
1081
- { headers }
1082
- );
1083
- if (!refRes.ok) {
1084
- throw new Error("Failed to access registry fork. Make sure the fork exists.");
1085
- }
1086
- const refData = await refRes.json();
1087
- const baseSha = refData.object.sha;
1088
- await fetch(`https://api.github.com/repos/${user.login}/planmode.org/git/refs`, {
1089
- method: "POST",
1090
- headers,
1091
- body: JSON.stringify({
1092
- ref: `refs/heads/${branchName}`,
1093
- sha: baseSha
1094
- })
1095
- });
1096
- await fetch(
1097
- `https://api.github.com/repos/${user.login}/planmode.org/contents/registry/packages/${manifest.name}/metadata.json`,
1098
- {
1099
- method: "PUT",
2380
+ null,
2381
+ 2
2382
+ );
2383
+ const versionContent = JSON.stringify(
2384
+ {
2385
+ version: manifest.version,
2386
+ published_at: (/* @__PURE__ */ new Date()).toISOString(),
2387
+ source: {
2388
+ repository: repoPath,
2389
+ tag,
2390
+ sha
2391
+ },
2392
+ files: ["planmode.yaml", manifest.content_file ?? "inline"],
2393
+ content_hash: `sha256:${sha.slice(0, 16)}`
2394
+ },
2395
+ null,
2396
+ 2
2397
+ );
2398
+ const branchName = `add-${manifest.name}-${manifest.version}`;
2399
+ const refRes = await fetch(
2400
+ `https://api.github.com/repos/${user.login}/planmode.org/git/ref/heads/main`,
2401
+ { headers }
2402
+ );
2403
+ if (!refRes.ok) {
2404
+ throw new Error("Failed to access registry fork. Make sure the fork exists.");
2405
+ }
2406
+ const refData = await refRes.json();
2407
+ const baseSha = refData.object.sha;
2408
+ await fetch(`https://api.github.com/repos/${user.login}/planmode.org/git/refs`, {
2409
+ method: "POST",
1100
2410
  headers,
1101
2411
  body: JSON.stringify({
1102
- message: `Add ${manifest.name}@${manifest.version}`,
1103
- content: Buffer.from(metadataContent).toString("base64"),
1104
- branch: branchName
2412
+ ref: `refs/heads/${branchName}`,
2413
+ sha: baseSha
1105
2414
  })
1106
- }
1107
- );
1108
- await fetch(
1109
- `https://api.github.com/repos/${user.login}/planmode.org/contents/registry/packages/${manifest.name}/versions/${manifest.version}.json`,
1110
- {
1111
- method: "PUT",
2415
+ });
2416
+ await fetch(
2417
+ `https://api.github.com/repos/${user.login}/planmode.org/contents/registry/packages/${manifest.name}/metadata.json`,
2418
+ {
2419
+ method: "PUT",
2420
+ headers,
2421
+ body: JSON.stringify({
2422
+ message: `Add ${manifest.name}@${manifest.version}`,
2423
+ content: Buffer.from(metadataContent).toString("base64"),
2424
+ branch: branchName
2425
+ })
2426
+ }
2427
+ );
2428
+ await fetch(
2429
+ `https://api.github.com/repos/${user.login}/planmode.org/contents/registry/packages/${manifest.name}/versions/${manifest.version}.json`,
2430
+ {
2431
+ method: "PUT",
2432
+ headers,
2433
+ body: JSON.stringify({
2434
+ message: `Add ${manifest.name}@${manifest.version} version metadata`,
2435
+ content: Buffer.from(versionContent).toString("base64"),
2436
+ branch: branchName
2437
+ })
2438
+ }
2439
+ );
2440
+ const prRes = await fetch("https://api.github.com/repos/kaihannonen/planmode.org/pulls", {
2441
+ method: "POST",
1112
2442
  headers,
1113
2443
  body: JSON.stringify({
1114
- message: `Add ${manifest.name}@${manifest.version} version metadata`,
1115
- content: Buffer.from(versionContent).toString("base64"),
1116
- branch: branchName
1117
- })
1118
- }
1119
- );
1120
- const prRes = await fetch("https://api.github.com/repos/kaihannonen/planmode.org/pulls", {
1121
- method: "POST",
1122
- headers,
1123
- body: JSON.stringify({
1124
- title: `Add ${manifest.name}@${manifest.version}`,
1125
- head: `${user.login}:${branchName}`,
1126
- base: "main",
1127
- body: `## New package: ${manifest.name}
2444
+ title: `Add ${manifest.name}@${manifest.version}`,
2445
+ head: `${user.login}:${branchName}`,
2446
+ base: "main",
2447
+ body: `## New package: ${manifest.name}
1128
2448
 
1129
2449
  - **Type:** ${manifest.type}
1130
2450
  - **Version:** ${manifest.version}
@@ -1132,28 +2452,51 @@ ${errors.map((e) => ` - ${e}`).join("\n")}`);
1132
2452
  - **Author:** ${manifest.author}
1133
2453
 
1134
2454
  Submitted via \`planmode publish\`.`
1135
- })
1136
- });
1137
- if (!prRes.ok) {
1138
- const err = await prRes.text();
1139
- throw new Error(`Failed to create PR: ${err}`);
2455
+ })
2456
+ });
2457
+ if (!prRes.ok) {
2458
+ const err = await prRes.text();
2459
+ throw new Error(`Failed to create PR: ${err}`);
2460
+ }
2461
+ const pr = await prRes.json();
2462
+ return pr.html_url;
2463
+ };
2464
+ let prUrl;
2465
+ if (interactive) {
2466
+ prUrl = await withSpinner(
2467
+ "Submitting to registry...",
2468
+ doSubmit,
2469
+ "Submitted to registry"
2470
+ );
2471
+ } else {
2472
+ logger.info("Submitting to registry...");
2473
+ prUrl = await doSubmit();
2474
+ logger.success(`Published ${manifest.name}@${manifest.version}`);
2475
+ logger.info(`PR: ${prUrl}`);
1140
2476
  }
1141
- const pr = await prRes.json();
1142
- logger.success(`Published ${manifest.name}@${manifest.version}`);
1143
- logger.info(`PR: ${pr.html_url}`);
1144
2477
  return {
1145
- prUrl: pr.html_url,
2478
+ prUrl,
1146
2479
  packageName: manifest.name,
1147
2480
  version: manifest.version
1148
2481
  };
1149
2482
  }
1150
2483
 
1151
2484
  // src/commands/publish.ts
2485
+ init_logger();
2486
+ init_prompts();
1152
2487
  var publishCommand = new Command5("publish").description("Publish the current directory as a package to the registry").action(async () => {
1153
2488
  try {
1154
- logger.blank();
1155
- const result = await publishPackage();
1156
- logger.blank();
2489
+ if (isInteractive()) {
2490
+ p4.intro("Publishing package");
2491
+ } else {
2492
+ logger.blank();
2493
+ }
2494
+ const result = await publishPackage({ interactive: isInteractive() });
2495
+ if (isInteractive()) {
2496
+ p4.outro(`Published ${result.packageName}@${result.version} \u2014 PR: ${result.prUrl}`);
2497
+ } else {
2498
+ logger.blank();
2499
+ }
1157
2500
  } catch (err) {
1158
2501
  logger.error(err.message);
1159
2502
  process.exit(1);
@@ -1161,38 +2504,76 @@ var publishCommand = new Command5("publish").description("Publish the current di
1161
2504
  });
1162
2505
 
1163
2506
  // src/commands/update.ts
2507
+ init_installer();
2508
+ init_lockfile();
2509
+ init_logger();
2510
+ init_prompts();
1164
2511
  import { Command as Command6 } from "commander";
2512
+ import * as p5 from "@clack/prompts";
1165
2513
  var updateCommand = new Command6("update").description("Update installed packages to latest compatible versions").argument("[package]", "Package name (omit to update all)").action(async (packageName) => {
1166
2514
  try {
1167
- logger.blank();
2515
+ const interactive = isInteractive();
2516
+ if (interactive) {
2517
+ p5.intro("Updating packages");
2518
+ } else {
2519
+ logger.blank();
2520
+ }
1168
2521
  if (packageName) {
1169
- const updated = await updatePackage(packageName);
2522
+ const updated = interactive ? await withSpinner(
2523
+ `Checking ${packageName} for updates...`,
2524
+ () => updatePackage(packageName)
2525
+ ) : await updatePackage(packageName);
1170
2526
  if (!updated) {
1171
- logger.info("Already up to date.");
2527
+ if (interactive) {
2528
+ p5.log.info("Already up to date.");
2529
+ } else {
2530
+ logger.info("Already up to date.");
2531
+ }
1172
2532
  }
1173
2533
  } else {
1174
2534
  const lockfile = readLockfile();
1175
2535
  const names = Object.keys(lockfile.packages);
1176
2536
  if (names.length === 0) {
1177
- logger.info("No packages installed.");
2537
+ if (interactive) {
2538
+ p5.log.info("No packages installed.");
2539
+ } else {
2540
+ logger.info("No packages installed.");
2541
+ }
2542
+ if (interactive) p5.outro("Nothing to update.");
1178
2543
  return;
1179
2544
  }
1180
- let updatedCount = 0;
1181
- for (const name of names) {
1182
- try {
1183
- const updated = await updatePackage(name);
1184
- if (updated) updatedCount++;
1185
- } catch (err) {
1186
- logger.warn(`Failed to update ${name}: ${err.message}`);
2545
+ const doUpdate = async () => {
2546
+ let updatedCount2 = 0;
2547
+ for (const name of names) {
2548
+ try {
2549
+ const updated = await updatePackage(name);
2550
+ if (updated) updatedCount2++;
2551
+ } catch (err) {
2552
+ logger.warn(`Failed to update ${name}: ${err.message}`);
2553
+ }
1187
2554
  }
1188
- }
2555
+ return updatedCount2;
2556
+ };
2557
+ const updatedCount = interactive ? await withSpinner("Checking for updates...", doUpdate) : await doUpdate();
1189
2558
  if (updatedCount === 0) {
1190
- logger.info("All packages are up to date.");
2559
+ if (interactive) {
2560
+ p5.log.info("All packages are up to date.");
2561
+ } else {
2562
+ logger.info("All packages are up to date.");
2563
+ }
1191
2564
  } else {
1192
- logger.success(`Updated ${updatedCount} package${updatedCount > 1 ? "s" : ""}.`);
2565
+ if (interactive) {
2566
+ p5.log.success(`Updated ${updatedCount} package${updatedCount > 1 ? "s" : ""}.`);
2567
+ } else {
2568
+ logger.success(`Updated ${updatedCount} package${updatedCount > 1 ? "s" : ""}.`);
2569
+ }
1193
2570
  }
1194
2571
  }
1195
- logger.blank();
2572
+ if (interactive) {
2573
+ p5.outro("Done!");
2574
+ } else {
2575
+ logger.blank();
2576
+ }
1196
2577
  } catch (err) {
1197
2578
  logger.error(err.message);
1198
2579
  process.exit(1);
@@ -1200,6 +2581,8 @@ var updateCommand = new Command6("update").description("Update installed package
1200
2581
  });
1201
2582
 
1202
2583
  // src/commands/list.ts
2584
+ init_lockfile();
2585
+ init_logger();
1203
2586
  import { Command as Command7 } from "commander";
1204
2587
  var listCommand = new Command7("list").description("List all installed packages").action(() => {
1205
2588
  const lockfile = readLockfile();
@@ -1222,6 +2605,8 @@ var listCommand = new Command7("list").description("List all installed packages"
1222
2605
  });
1223
2606
 
1224
2607
  // src/commands/info.ts
2608
+ init_registry();
2609
+ init_logger();
1225
2610
  import { Command as Command8 } from "commander";
1226
2611
  var infoCommand = new Command8("info").description("Show detailed info about a package").argument("<package>", "Package name").action(async (packageName) => {
1227
2612
  try {
@@ -1236,194 +2621,33 @@ var infoCommand = new Command8("info").description("Show detailed info about a p
1236
2621
  console.log(` Category: ${meta.category}`);
1237
2622
  console.log(` Downloads: ${meta.downloads.toLocaleString()}`);
1238
2623
  console.log(` Repository: ${meta.repository}`);
1239
- if (meta.models && meta.models.length > 0) {
1240
- console.log(` Models: ${meta.models.join(", ")}`);
1241
- }
1242
- if (meta.tags && meta.tags.length > 0) {
1243
- console.log(` Tags: ${meta.tags.join(", ")}`);
1244
- }
1245
- console.log(` Versions: ${meta.versions.join(", ")}`);
1246
- if (meta.dependencies) {
1247
- if (meta.dependencies.rules && meta.dependencies.rules.length > 0) {
1248
- console.log(` Dep (rules): ${meta.dependencies.rules.join(", ")}`);
1249
- }
1250
- if (meta.dependencies.plans && meta.dependencies.plans.length > 0) {
1251
- console.log(` Dep (plans): ${meta.dependencies.plans.join(", ")}`);
1252
- }
1253
- }
1254
- if (meta.variables) {
1255
- logger.blank();
1256
- logger.bold(" Variables:");
1257
- for (const [name, def] of Object.entries(meta.variables)) {
1258
- const required = def.required ? " (required)" : "";
1259
- const defaultVal = def.default !== void 0 ? ` [default: ${def.default}]` : "";
1260
- console.log(` ${name}: ${def.type}${required}${defaultVal} \u2014 ${def.description}`);
1261
- if (def.options) {
1262
- console.log(` options: ${def.options.join(", ")}`);
1263
- }
1264
- }
1265
- }
1266
- logger.blank();
1267
- } catch (err) {
1268
- logger.error(err.message);
1269
- process.exit(1);
1270
- }
1271
- });
1272
-
1273
- // src/commands/init.ts
1274
- import { Command as Command9 } from "commander";
1275
-
1276
- // src/lib/init.ts
1277
- import fs9 from "fs";
1278
- import path9 from "path";
1279
- import { stringify as stringify3 } from "yaml";
1280
-
1281
- // src/lib/templates.ts
1282
- function getPlanTemplate(name) {
1283
- return `# ${name}
1284
-
1285
- ## Prerequisites
1286
-
1287
- - List any tools, dependencies, or setup required before starting
1288
-
1289
- ## Steps
1290
-
1291
- 1. **Step one** \u2014 Description of what to do first
1292
- 2. **Step two** \u2014 Description of what to do next
1293
- 3. **Step three** \u2014 Description of the final step
1294
-
1295
- ## Verification
1296
-
1297
- - [ ] Verify step one completed successfully
1298
- - [ ] Verify step two completed successfully
1299
- - [ ] Verify the final result works as expected
1300
- `;
1301
- }
1302
- function getRuleTemplate(name) {
1303
- return `# ${name}
1304
-
1305
- ## Code Style
1306
-
1307
- - Follow consistent naming conventions
1308
- - Keep functions small and focused
1309
-
1310
- ## Best Practices
1311
-
1312
- - Prefer composition over inheritance
1313
- - Write self-documenting code
1314
-
1315
- ## Avoid
1316
-
1317
- - Do not use deprecated APIs
1318
- - Do not ignore error handling
1319
- `;
1320
- }
1321
- function getPromptTemplate(name) {
1322
- return `# ${name}
1323
-
1324
- {{description}}
1325
-
1326
- ## Context
1327
-
1328
- Provide any relevant context here.
1329
-
1330
- ## Requirements
1331
-
1332
- - Requirement one
1333
- - Requirement two
1334
-
1335
- ## Output Format
1336
-
1337
- Describe the expected output format.
1338
- `;
1339
- }
1340
-
1341
- // src/lib/init.ts
1342
- function createPackage(options) {
1343
- const {
1344
- name,
1345
- type,
1346
- description,
1347
- author,
1348
- license = "MIT",
1349
- tags = [],
1350
- category = "other",
1351
- projectDir = process.cwd()
1352
- } = options;
1353
- const manifest = {
1354
- name,
1355
- version: "1.0.0",
1356
- type,
1357
- description,
1358
- author,
1359
- license
1360
- };
1361
- if (tags.length > 0) manifest["tags"] = tags;
1362
- manifest["category"] = category;
1363
- const contentFile = `${type}.md`;
1364
- manifest["content_file"] = contentFile;
1365
- const yamlContent = stringify3(manifest);
1366
- const manifestPath = path9.join(projectDir, "planmode.yaml");
1367
- fs9.writeFileSync(manifestPath, yamlContent, "utf-8");
1368
- const stubs = {
1369
- plan: getPlanTemplate(name),
1370
- rule: getRuleTemplate(name),
1371
- prompt: getPromptTemplate(name)
1372
- };
1373
- const contentPath = path9.join(projectDir, contentFile);
1374
- fs9.writeFileSync(contentPath, stubs[type] ?? stubs["plan"], "utf-8");
1375
- return {
1376
- files: ["planmode.yaml", contentFile],
1377
- manifestPath,
1378
- contentPath
1379
- };
1380
- }
1381
-
1382
- // src/commands/init.ts
1383
- async function prompt(question) {
1384
- const { createInterface } = await import("readline");
1385
- const rl = createInterface({ input: process.stdin, output: process.stdout });
1386
- return new Promise((resolve) => {
1387
- rl.question(question, (answer) => {
1388
- rl.close();
1389
- resolve(answer.trim());
1390
- });
1391
- });
1392
- }
1393
- var initCommand = new Command9("init").description("Initialize a new package in the current directory").action(async () => {
1394
- try {
1395
- logger.blank();
1396
- logger.bold("Initialize a new Planmode package");
1397
- logger.blank();
1398
- const name = await prompt("Package name: ");
1399
- if (!name) {
1400
- logger.error("Package name is required.");
1401
- process.exit(1);
2624
+ if (meta.models && meta.models.length > 0) {
2625
+ console.log(` Models: ${meta.models.join(", ")}`);
2626
+ }
2627
+ if (meta.tags && meta.tags.length > 0) {
2628
+ console.log(` Tags: ${meta.tags.join(", ")}`);
2629
+ }
2630
+ console.log(` Versions: ${meta.versions.join(", ")}`);
2631
+ if (meta.dependencies) {
2632
+ if (meta.dependencies.rules && meta.dependencies.rules.length > 0) {
2633
+ console.log(` Dep (rules): ${meta.dependencies.rules.join(", ")}`);
2634
+ }
2635
+ if (meta.dependencies.plans && meta.dependencies.plans.length > 0) {
2636
+ console.log(` Dep (plans): ${meta.dependencies.plans.join(", ")}`);
2637
+ }
2638
+ }
2639
+ if (meta.variables) {
2640
+ logger.blank();
2641
+ logger.bold(" Variables:");
2642
+ for (const [name, def] of Object.entries(meta.variables)) {
2643
+ const required = def.required ? " (required)" : "";
2644
+ const defaultVal = def.default !== void 0 ? ` [default: ${def.default}]` : "";
2645
+ console.log(` ${name}: ${def.type}${required}${defaultVal} \u2014 ${def.description}`);
2646
+ if (def.options) {
2647
+ console.log(` options: ${def.options.join(", ")}`);
2648
+ }
2649
+ }
1402
2650
  }
1403
- const typeInput = await prompt("Type (plan/rule/prompt) [plan]: ");
1404
- const type = typeInput || "plan";
1405
- const description = await prompt("Description: ");
1406
- const author = await prompt("Author (GitHub username): ");
1407
- const license = await prompt("License [MIT]: ") || "MIT";
1408
- const tagsInput = await prompt("Tags (comma-separated): ");
1409
- const tags = tagsInput ? tagsInput.split(",").map((t) => t.trim().toLowerCase()) : [];
1410
- const category = await prompt(
1411
- "Category (frontend/backend/devops/database/testing/mobile/ai-ml/security/other) [other]: "
1412
- ) || "other";
1413
- const result = createPackage({
1414
- name,
1415
- type,
1416
- description,
1417
- author,
1418
- license,
1419
- tags,
1420
- category
1421
- });
1422
- logger.success(`Created ${result.files.join(", ")}`);
1423
- logger.blank();
1424
- logger.info(
1425
- `Edit ${result.files[1]}, then run \`planmode publish\` when ready.`
1426
- );
1427
2651
  logger.blank();
1428
2652
  } catch (err) {
1429
2653
  logger.error(err.message);
@@ -1431,8 +2655,15 @@ var initCommand = new Command9("init").description("Initialize a new package in
1431
2655
  }
1432
2656
  });
1433
2657
 
2658
+ // src/index.ts
2659
+ init_init2();
2660
+
1434
2661
  // src/commands/login.ts
2662
+ init_config();
2663
+ init_logger();
2664
+ init_prompts();
1435
2665
  import { Command as Command10 } from "commander";
2666
+ import * as p7 from "@clack/prompts";
1436
2667
  import { execSync } from "child_process";
1437
2668
  var loginCommand = new Command10("login").description("Configure GitHub authentication").option("--token <token>", "GitHub personal access token").option("--gh", "Read token from GitHub CLI (gh auth token)").action(async (options) => {
1438
2669
  let token;
@@ -1445,37 +2676,61 @@ var loginCommand = new Command10("login").description("Configure GitHub authenti
1445
2676
  logger.error("Failed to read token from GitHub CLI. Make sure `gh` is installed and authenticated.");
1446
2677
  process.exit(1);
1447
2678
  }
1448
- } else {
1449
- const { createInterface } = await import("readline");
1450
- const rl = createInterface({ input: process.stdin, output: process.stdout });
1451
- token = await new Promise((resolve) => {
1452
- rl.question("GitHub personal access token: ", (answer) => {
1453
- rl.close();
1454
- resolve(answer.trim());
1455
- });
2679
+ } else if (isInteractive()) {
2680
+ p7.intro("planmode login");
2681
+ const value = await p7.password({
2682
+ message: "GitHub personal access token:",
2683
+ validate(input) {
2684
+ if (!input) return "Token is required";
2685
+ }
1456
2686
  });
2687
+ token = handleCancel(value);
2688
+ } else {
2689
+ logger.error("No token provided. Use --token <token> or --gh.");
2690
+ process.exit(1);
1457
2691
  }
1458
2692
  if (!token) {
1459
2693
  logger.error("No token provided.");
1460
2694
  process.exit(1);
1461
2695
  }
1462
- logger.info("Validating token...");
1463
- const response = await fetch("https://api.github.com/user", {
1464
- headers: {
1465
- Authorization: `Bearer ${token}`,
1466
- "User-Agent": "planmode-cli"
2696
+ const validateToken = async () => {
2697
+ const response = await fetch("https://api.github.com/user", {
2698
+ headers: {
2699
+ Authorization: `Bearer ${token}`,
2700
+ "User-Agent": "planmode-cli"
2701
+ }
2702
+ });
2703
+ if (!response.ok) {
2704
+ throw new Error("Invalid token. GitHub API returned: " + response.status);
2705
+ }
2706
+ return await response.json();
2707
+ };
2708
+ try {
2709
+ const user = await withSpinner(
2710
+ "Validating token...",
2711
+ validateToken,
2712
+ "Token validated"
2713
+ );
2714
+ setGitHubToken(token);
2715
+ if (isInteractive()) {
2716
+ p7.log.success(`Authenticated as ${user.login}`);
2717
+ p7.outro("You're all set!");
2718
+ } else {
2719
+ logger.success(`Authenticated as ${user.login}`);
2720
+ }
2721
+ } catch (err) {
2722
+ if (isInteractive()) {
2723
+ p7.log.error(err.message);
2724
+ p7.outro("Authentication failed.");
2725
+ } else {
2726
+ logger.error(err.message);
1467
2727
  }
1468
- });
1469
- if (!response.ok) {
1470
- logger.error("Invalid token. GitHub API returned: " + response.status);
1471
2728
  process.exit(1);
1472
2729
  }
1473
- const user = await response.json();
1474
- setGitHubToken(token);
1475
- logger.success(`Authenticated as ${user.login}`);
1476
2730
  });
1477
2731
 
1478
2732
  // src/commands/mcp.ts
2733
+ init_logger();
1479
2734
  import { Command as Command11 } from "commander";
1480
2735
  import { execSync as execSync2 } from "child_process";
1481
2736
  var mcpCommand = new Command11("mcp").description("Manage MCP server registration with Claude Code");
@@ -1506,125 +2761,67 @@ mcpCommand.command("remove").description("Remove the planmode MCP server from Cl
1506
2761
  });
1507
2762
 
1508
2763
  // src/commands/doctor.ts
2764
+ init_doctor();
2765
+ init_logger();
2766
+ init_prompts();
1509
2767
  import { Command as Command12 } from "commander";
1510
-
1511
- // src/lib/doctor.ts
1512
- import fs10 from "fs";
1513
- import path10 from "path";
1514
- import crypto2 from "crypto";
1515
- function computeHash(content) {
1516
- return `sha256:${crypto2.createHash("sha256").update(content).digest("hex")}`;
1517
- }
1518
- function runDoctor(projectDir = process.cwd()) {
1519
- const issues = [];
1520
- const lockfile = readLockfile(projectDir);
1521
- const entries = Object.entries(lockfile.packages);
1522
- for (const [name, entry] of entries) {
1523
- const fullPath = path10.join(projectDir, entry.installed_to);
1524
- if (!fs10.existsSync(fullPath)) {
1525
- issues.push({
1526
- severity: "error",
1527
- message: `Missing file for "${name}": ${entry.installed_to}`,
1528
- fix: `Run \`planmode install ${name}\` to reinstall`
1529
- });
1530
- continue;
1531
- }
1532
- const content = fs10.readFileSync(fullPath, "utf-8");
1533
- const actualHash = computeHash(content);
1534
- if (actualHash !== entry.content_hash) {
1535
- issues.push({
1536
- severity: "warning",
1537
- message: `Content hash mismatch for "${name}" at ${entry.installed_to}`,
1538
- fix: "File was modified locally. Run `planmode update " + name + "` to restore, or ignore if intentional"
1539
- });
1540
- }
1541
- }
1542
- const claudeMdPath = path10.join(projectDir, "CLAUDE.md");
1543
- const imports = listImports(projectDir);
1544
- const installedPlans = entries.filter(([, entry]) => entry.type === "plan").map(([name]) => name);
1545
- for (const planName of installedPlans) {
1546
- if (!imports.includes(planName)) {
1547
- issues.push({
1548
- severity: "error",
1549
- message: `Plan "${planName}" is installed but missing from CLAUDE.md imports`,
1550
- fix: `Add \`- @plans/${planName}.md\` to the # Planmode section of CLAUDE.md`
1551
- });
1552
- }
1553
- }
1554
- for (const importName of imports) {
1555
- if (!installedPlans.includes(importName)) {
1556
- const filePath = path10.join(projectDir, "plans", `${importName}.md`);
1557
- if (!fs10.existsSync(filePath)) {
1558
- issues.push({
1559
- severity: "error",
1560
- message: `CLAUDE.md imports "${importName}" but the file doesn't exist at plans/${importName}.md`,
1561
- fix: `Run \`planmode install ${importName}\` or remove the import from CLAUDE.md`
1562
- });
1563
- } else {
1564
- issues.push({
1565
- severity: "warning",
1566
- message: `CLAUDE.md imports "${importName}" but it's not tracked in planmode.lock`,
1567
- fix: "This plan was added manually. No action needed unless you want lockfile tracking."
1568
- });
1569
- }
1570
- }
1571
- }
1572
- if (installedPlans.length > 0 && !fs10.existsSync(claudeMdPath)) {
1573
- issues.push({
1574
- severity: "error",
1575
- message: "CLAUDE.md is missing but plans are installed",
1576
- fix: "Run `planmode install <any-plan>` to recreate it, or create it manually with a # Planmode section"
1577
- });
1578
- }
1579
- const plansDir = path10.join(projectDir, "plans");
1580
- if (fs10.existsSync(plansDir)) {
1581
- const planFiles = fs10.readdirSync(plansDir).filter((f) => f.endsWith(".md"));
1582
- for (const file of planFiles) {
1583
- const name = file.replace(/\.md$/, "");
1584
- if (!lockfile.packages[name]) {
1585
- issues.push({
1586
- severity: "warning",
1587
- message: `Untracked plan file: plans/${file}`,
1588
- fix: "This file isn't managed by planmode. Ignore if intentional."
1589
- });
1590
- }
1591
- }
1592
- }
1593
- return {
1594
- issues,
1595
- packagesChecked: entries.length,
1596
- healthy: issues.filter((i) => i.severity === "error").length === 0
1597
- };
1598
- }
1599
-
1600
- // src/commands/doctor.ts
2768
+ import * as p8 from "@clack/prompts";
1601
2769
  var doctorCommand = new Command12("doctor").description("Check project health: verify installed packages, imports, and file integrity").action(() => {
2770
+ const interactive = isInteractive();
1602
2771
  const result = runDoctor();
1603
- logger.blank();
1604
- logger.bold(`Checked ${result.packagesChecked} package(s)`);
1605
- logger.blank();
1606
- if (result.issues.length === 0) {
1607
- logger.success("Everything looks good. No issues found.");
2772
+ if (interactive) {
2773
+ p8.intro("Health check");
2774
+ } else {
2775
+ logger.blank();
2776
+ }
2777
+ if (interactive) {
2778
+ p8.log.info(`Checked ${result.packagesChecked} package(s)`);
2779
+ } else {
2780
+ logger.bold(`Checked ${result.packagesChecked} package(s)`);
1608
2781
  logger.blank();
2782
+ }
2783
+ if (result.issues.length === 0) {
2784
+ if (interactive) {
2785
+ p8.outro("Everything looks good. No issues found.");
2786
+ } else {
2787
+ logger.success("Everything looks good. No issues found.");
2788
+ logger.blank();
2789
+ }
1609
2790
  return;
1610
2791
  }
1611
2792
  const errors = result.issues.filter((i) => i.severity === "error");
1612
2793
  const warnings = result.issues.filter((i) => i.severity === "warning");
1613
2794
  for (const issue of errors) {
1614
- logger.error(issue.message);
1615
- if (issue.fix) logger.dim(` Fix: ${issue.fix}`);
2795
+ if (interactive) {
2796
+ p8.log.error(issue.message);
2797
+ } else {
2798
+ logger.error(issue.message);
2799
+ if (issue.fix) logger.dim(` Fix: ${issue.fix}`);
2800
+ }
1616
2801
  }
1617
2802
  for (const issue of warnings) {
1618
- logger.warn(issue.message);
1619
- if (issue.fix) logger.dim(` Fix: ${issue.fix}`);
2803
+ if (interactive) {
2804
+ p8.log.warn(issue.message);
2805
+ } else {
2806
+ logger.warn(issue.message);
2807
+ if (issue.fix) logger.dim(` Fix: ${issue.fix}`);
2808
+ }
1620
2809
  }
1621
- logger.blank();
1622
- if (errors.length > 0) {
1623
- logger.error(`${errors.length} error(s), ${warnings.length} warning(s)`);
2810
+ if (interactive) {
2811
+ if (errors.length > 0) {
2812
+ p8.outro(`${errors.length} error(s), ${warnings.length} warning(s)`);
2813
+ } else {
2814
+ p8.outro(`${warnings.length} warning(s)`);
2815
+ }
1624
2816
  } else {
1625
- logger.warn(`${warnings.length} warning(s)`);
2817
+ logger.blank();
2818
+ if (errors.length > 0) {
2819
+ logger.error(`${errors.length} error(s), ${warnings.length} warning(s)`);
2820
+ } else {
2821
+ logger.warn(`${warnings.length} warning(s)`);
2822
+ }
2823
+ logger.blank();
1626
2824
  }
1627
- logger.blank();
1628
2825
  if (errors.length > 0) {
1629
2826
  process.exit(1);
1630
2827
  }
@@ -1632,8 +2829,12 @@ var doctorCommand = new Command12("doctor").description("Check project health: v
1632
2829
 
1633
2830
  // src/commands/test.ts
1634
2831
  import { Command as Command13 } from "commander";
2832
+ import * as p9 from "@clack/prompts";
1635
2833
 
1636
2834
  // src/lib/tester.ts
2835
+ init_manifest();
2836
+ init_template();
2837
+ init_registry();
1637
2838
  async function testPackage(projectDir = process.cwd()) {
1638
2839
  const issues = [];
1639
2840
  const checks = [];
@@ -1762,33 +2963,62 @@ async function testPackage(projectDir = process.cwd()) {
1762
2963
  }
1763
2964
 
1764
2965
  // src/commands/test.ts
2966
+ init_logger();
2967
+ init_prompts();
1765
2968
  var testCommand = new Command13("test").description("Test the current package before publishing: validate manifest, render templates, check dependencies").action(async () => {
1766
2969
  try {
1767
- logger.blank();
1768
- logger.bold("Testing package...");
1769
- logger.blank();
2970
+ const interactive = isInteractive();
2971
+ if (interactive) {
2972
+ p9.intro("Testing package");
2973
+ } else {
2974
+ logger.blank();
2975
+ logger.bold("Testing package...");
2976
+ logger.blank();
2977
+ }
1770
2978
  const result = await testPackage();
1771
2979
  for (const check of result.checks) {
1772
2980
  if (check.passed) {
1773
- logger.success(check.name);
2981
+ if (interactive) {
2982
+ p9.log.success(check.name);
2983
+ } else {
2984
+ logger.success(check.name);
2985
+ }
1774
2986
  } else {
1775
2987
  const issue = result.issues.find((i) => i.check === check.name);
1776
2988
  if (issue?.severity === "error") {
1777
- logger.error(`${check.name}: ${issue.message}`);
2989
+ if (interactive) {
2990
+ p9.log.error(`${check.name}: ${issue.message}`);
2991
+ } else {
2992
+ logger.error(`${check.name}: ${issue.message}`);
2993
+ }
1778
2994
  } else if (issue) {
1779
- logger.warn(`${check.name}: ${issue.message}`);
2995
+ if (interactive) {
2996
+ p9.log.warn(`${check.name}: ${issue.message}`);
2997
+ } else {
2998
+ logger.warn(`${check.name}: ${issue.message}`);
2999
+ }
1780
3000
  }
1781
3001
  }
1782
3002
  }
1783
- logger.blank();
1784
- if (result.passed) {
1785
- logger.success(`All checks passed. Ready to publish.`);
3003
+ if (interactive) {
3004
+ if (result.passed) {
3005
+ p9.outro("All checks passed. Ready to publish.");
3006
+ } else {
3007
+ const errors = result.issues.filter((i) => i.severity === "error");
3008
+ const warnings = result.issues.filter((i) => i.severity === "warning");
3009
+ p9.outro(`${errors.length} error(s), ${warnings.length} warning(s). Fix errors before publishing.`);
3010
+ }
1786
3011
  } else {
1787
- const errors = result.issues.filter((i) => i.severity === "error");
1788
- const warnings = result.issues.filter((i) => i.severity === "warning");
1789
- logger.error(`${errors.length} error(s), ${warnings.length} warning(s). Fix errors before publishing.`);
3012
+ logger.blank();
3013
+ if (result.passed) {
3014
+ logger.success(`All checks passed. Ready to publish.`);
3015
+ } else {
3016
+ const errors = result.issues.filter((i) => i.severity === "error");
3017
+ const warnings = result.issues.filter((i) => i.severity === "warning");
3018
+ logger.error(`${errors.length} error(s), ${warnings.length} warning(s). Fix errors before publishing.`);
3019
+ }
3020
+ logger.blank();
1790
3021
  }
1791
- logger.blank();
1792
3022
  if (!result.passed) {
1793
3023
  process.exit(1);
1794
3024
  }
@@ -1800,6 +3030,7 @@ var testCommand = new Command13("test").description("Test the current package be
1800
3030
 
1801
3031
  // src/commands/record.ts
1802
3032
  import { Command as Command14 } from "commander";
3033
+ import * as p10 from "@clack/prompts";
1803
3034
  import fs12 from "fs";
1804
3035
  import path12 from "path";
1805
3036
 
@@ -1823,8 +3054,8 @@ function startRecording(projectDir = process.cwd()) {
1823
3054
  async function startRecordingAsync(projectDir = process.cwd()) {
1824
3055
  const recordingPath = startRecording(projectDir);
1825
3056
  const git = simpleGit2(projectDir);
1826
- const log = await git.log({ n: 1 });
1827
- const sha = log.latest?.hash;
3057
+ const log8 = await git.log({ n: 1 });
3058
+ const sha = log8.latest?.hash;
1828
3059
  if (!sha) {
1829
3060
  throw new Error("No commits found in this repository.");
1830
3061
  }
@@ -1841,12 +3072,12 @@ async function stopRecording(projectDir = process.cwd(), options = {}) {
1841
3072
  }
1842
3073
  const startSha = fs11.readFileSync(recordingPath, "utf-8").trim();
1843
3074
  const git = simpleGit2(projectDir);
1844
- const log = await git.log({ from: startSha, to: "HEAD" });
1845
- if (log.total === 0) {
3075
+ const log8 = await git.log({ from: startSha, to: "HEAD" });
3076
+ if (log8.total === 0) {
1846
3077
  fs11.unlinkSync(recordingPath);
1847
3078
  throw new Error("No commits since recording started. Nothing to capture.");
1848
3079
  }
1849
- const commits = [...log.all].reverse();
3080
+ const commits = [...log8.all].reverse();
1850
3081
  const steps = [];
1851
3082
  const allFilesChanged = /* @__PURE__ */ new Set();
1852
3083
  for (const commit of commits) {
@@ -1933,6 +3164,8 @@ function generateManifest(name, author) {
1933
3164
  }
1934
3165
 
1935
3166
  // src/commands/record.ts
3167
+ init_logger();
3168
+ init_prompts();
1936
3169
  var recordCommand = new Command14("record").description("Record git activity and generate a plan from commits");
1937
3170
  recordCommand.command("start").description("Start recording \u2014 saves current HEAD as the starting point").action(async () => {
1938
3171
  try {
@@ -1948,12 +3181,26 @@ recordCommand.command("start").description("Start recording \u2014 saves current
1948
3181
  });
1949
3182
  recordCommand.command("stop").description("Stop recording and generate a plan from commits since start").option("--name <name>", "Package name (auto-inferred if not provided)").option("--author <author>", "Author GitHub username").option("--dir <dir>", "Output directory for the generated package (default: current directory)").action(async (options) => {
1950
3183
  try {
1951
- logger.blank();
1952
- logger.info("Analyzing commits...");
1953
- const result = await stopRecording(process.cwd(), {
1954
- name: options.name,
1955
- author: options.author
1956
- });
3184
+ const interactive = isInteractive();
3185
+ if (interactive) {
3186
+ p10.intro("Generating plan from recording");
3187
+ } else {
3188
+ logger.blank();
3189
+ }
3190
+ const result = interactive ? await withSpinner(
3191
+ "Analyzing commits...",
3192
+ () => stopRecording(process.cwd(), {
3193
+ name: options.name,
3194
+ author: options.author
3195
+ }),
3196
+ "Analysis complete"
3197
+ ) : await (async () => {
3198
+ logger.info("Analyzing commits...");
3199
+ return stopRecording(process.cwd(), {
3200
+ name: options.name,
3201
+ author: options.author
3202
+ });
3203
+ })();
1957
3204
  const outDir = options.dir ?? process.cwd();
1958
3205
  fs12.mkdirSync(outDir, { recursive: true });
1959
3206
  fs12.writeFileSync(path12.join(outDir, "planmode.yaml"), result.manifestContent, "utf-8");
@@ -1966,8 +3213,12 @@ recordCommand.command("stop").description("Stop recording and generate a plan fr
1966
3213
  }
1967
3214
  logger.blank();
1968
3215
  logger.success("Created planmode.yaml and plan.md");
1969
- logger.dim("Edit the generated plan, then run `planmode test` to validate and `planmode publish` when ready.");
1970
- logger.blank();
3216
+ if (interactive) {
3217
+ p10.outro("Edit the generated plan, then run `planmode test` to validate and `planmode publish` when ready.");
3218
+ } else {
3219
+ logger.dim("Edit the generated plan, then run `planmode test` to validate and `planmode publish` when ready.");
3220
+ logger.blank();
3221
+ }
1971
3222
  } catch (err) {
1972
3223
  logger.error(err.message);
1973
3224
  process.exit(1);
@@ -1983,6 +3234,7 @@ recordCommand.command("status").description("Check if a recording is in progress
1983
3234
 
1984
3235
  // src/commands/snapshot.ts
1985
3236
  import { Command as Command15 } from "commander";
3237
+ import * as p11 from "@clack/prompts";
1986
3238
  import fs14 from "fs";
1987
3239
  import path14 from "path";
1988
3240
 
@@ -2257,14 +3509,29 @@ function detectCategory(data) {
2257
3509
  }
2258
3510
 
2259
3511
  // src/commands/snapshot.ts
2260
- var snapshotCommand = new Command15("snapshot").description("Analyze the current project and generate a plan that recreates this setup").option("--name <name>", "Package name (auto-inferred from project)").option("--author <author>", "Author GitHub username").option("--dir <dir>", "Output directory for the generated package (default: current directory)").action((options) => {
3512
+ init_logger();
3513
+ init_prompts();
3514
+ var snapshotCommand = new Command15("snapshot").description("Analyze the current project and generate a plan that recreates this setup").option("--name <name>", "Package name (auto-inferred from project)").option("--author <author>", "Author GitHub username").option("--dir <dir>", "Output directory for the generated package (default: current directory)").action(async (options) => {
2261
3515
  try {
2262
- logger.blank();
2263
- logger.info("Analyzing project...");
2264
- const result = takeSnapshot(process.cwd(), {
2265
- name: options.name,
2266
- author: options.author
2267
- });
3516
+ const interactive = isInteractive();
3517
+ if (interactive) {
3518
+ p11.intro("Taking project snapshot");
3519
+ } else {
3520
+ logger.blank();
3521
+ }
3522
+ const doSnapshot = () => Promise.resolve(
3523
+ takeSnapshot(process.cwd(), {
3524
+ name: options.name,
3525
+ author: options.author
3526
+ })
3527
+ );
3528
+ const result = interactive ? await withSpinner("Analyzing project...", doSnapshot, "Analysis complete") : (() => {
3529
+ logger.info("Analyzing project...");
3530
+ return takeSnapshot(process.cwd(), {
3531
+ name: options.name,
3532
+ author: options.author
3533
+ });
3534
+ })();
2268
3535
  const outDir = options.dir ?? process.cwd();
2269
3536
  fs14.mkdirSync(outDir, { recursive: true });
2270
3537
  fs14.writeFileSync(path14.join(outDir, "planmode.yaml"), result.manifestContent, "utf-8");
@@ -2279,8 +3546,97 @@ var snapshotCommand = new Command15("snapshot").description("Analyze the current
2279
3546
  logger.dim(` Tools detected: ${result.data.detectedTools.map((t) => t.name).join(", ") || "none"}`);
2280
3547
  logger.blank();
2281
3548
  logger.success("Created planmode.yaml and plan.md");
2282
- logger.dim("Edit the generated plan, then run `planmode test` to validate and `planmode publish` when ready.");
3549
+ if (interactive) {
3550
+ p11.outro("Edit the generated plan, then run `planmode test` to validate and `planmode publish` when ready.");
3551
+ } else {
3552
+ logger.dim("Edit the generated plan, then run `planmode test` to validate and `planmode publish` when ready.");
3553
+ logger.blank();
3554
+ }
3555
+ } catch (err) {
3556
+ logger.error(err.message);
3557
+ process.exit(1);
3558
+ }
3559
+ });
3560
+
3561
+ // src/commands/context.ts
3562
+ init_context();
3563
+ init_logger();
3564
+ init_prompts();
3565
+ import { Command as Command16 } from "commander";
3566
+ var contextCommand = new Command16("context").description("Manage project document context for AI");
3567
+ contextCommand.command("add <path>").description("Add a document directory to the project context").option("--name <name>", "Human-readable label for this directory").action(async (dirPath, options) => {
3568
+ try {
3569
+ const interactive = isInteractive();
3570
+ if (interactive) {
3571
+ await withSpinner(
3572
+ "Indexing documents...",
3573
+ async () => addContextRepo(dirPath, { name: options.name }),
3574
+ "Indexing complete"
3575
+ );
3576
+ } else {
3577
+ logger.blank();
3578
+ addContextRepo(dirPath, { name: options.name });
3579
+ logger.blank();
3580
+ }
3581
+ } catch (err) {
3582
+ logger.error(err.message);
3583
+ process.exit(1);
3584
+ }
3585
+ });
3586
+ contextCommand.command("remove <path-or-name>").description("Remove a directory from the project context").action((pathOrName) => {
3587
+ try {
3588
+ logger.blank();
3589
+ removeContextRepo(pathOrName);
3590
+ logger.blank();
3591
+ } catch (err) {
3592
+ logger.error(err.message);
3593
+ process.exit(1);
3594
+ }
3595
+ });
3596
+ contextCommand.command("list").description("Show all directories in the project context").option("--json", "Output as JSON").action((options) => {
3597
+ try {
3598
+ const summary = getContextSummary();
3599
+ if (options.json) {
3600
+ console.log(JSON.stringify(summary, null, 2));
3601
+ return;
3602
+ }
3603
+ logger.blank();
3604
+ if (summary.totalRepos === 0) {
3605
+ logger.info("No context repos configured. Run `planmode context add <path>` to add one.");
3606
+ logger.blank();
3607
+ return;
3608
+ }
3609
+ logger.bold(`${summary.totalRepos} context repo(s) \u2014 ${summary.totalFiles} file(s), ${formatSize(summary.totalSize)}`);
2283
3610
  logger.blank();
3611
+ for (const repo of summary.repos) {
3612
+ logger.info(`${repo.name}`);
3613
+ logger.dim(` Path: ${repo.path}`);
3614
+ logger.dim(` Files: ${repo.fileCount} (${formatSize(repo.totalSize)})`);
3615
+ if (repo.typeBreakdown.length > 0) {
3616
+ logger.dim(` Types: ${repo.typeBreakdown.join(", ")}`);
3617
+ }
3618
+ logger.dim(` Indexed: ${repo.indexedAt}`);
3619
+ logger.blank();
3620
+ }
3621
+ } catch (err) {
3622
+ logger.error(err.message);
3623
+ process.exit(1);
3624
+ }
3625
+ });
3626
+ contextCommand.command("reindex [path-or-name]").description("Re-scan files in one or all context directories").action(async (pathOrName) => {
3627
+ try {
3628
+ const interactive = isInteractive();
3629
+ if (interactive) {
3630
+ await withSpinner(
3631
+ "Re-scanning documents...",
3632
+ async () => reindexContext(pathOrName),
3633
+ "Reindex complete"
3634
+ );
3635
+ } else {
3636
+ logger.blank();
3637
+ reindexContext(pathOrName);
3638
+ logger.blank();
3639
+ }
2284
3640
  } catch (err) {
2285
3641
  logger.error(err.message);
2286
3642
  process.exit(1);
@@ -2288,8 +3644,9 @@ var snapshotCommand = new Command15("snapshot").description("Analyze the current
2288
3644
  });
2289
3645
 
2290
3646
  // src/index.ts
2291
- var program = new Command16();
2292
- program.name("planmode").description("The open source package manager for AI plans, rules, and prompts.").version("0.2.2");
3647
+ init_prompts();
3648
+ var program = new Command17();
3649
+ program.name("planmode").description("The open source package manager for AI plans, rules, and prompts.").version("0.4.0");
2293
3650
  program.addCommand(installCommand);
2294
3651
  program.addCommand(uninstallCommand);
2295
3652
  program.addCommand(searchCommand);
@@ -2305,4 +3662,10 @@ program.addCommand(doctorCommand);
2305
3662
  program.addCommand(testCommand);
2306
3663
  program.addCommand(recordCommand);
2307
3664
  program.addCommand(snapshotCommand);
2308
- program.parse();
3665
+ program.addCommand(contextCommand);
3666
+ if (process.argv.length <= 2 && isInteractive()) {
3667
+ const { runInteractiveMenu: runInteractiveMenu2 } = await Promise.resolve().then(() => (init_interactive(), interactive_exports));
3668
+ runInteractiveMenu2();
3669
+ } else {
3670
+ program.parse();
3671
+ }