planmode 0.2.2 → 0.3.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,872 @@ 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/commands/interactive.ts
1381
+ var interactive_exports = {};
1382
+ __export(interactive_exports, {
1383
+ runInteractiveMenu: () => runInteractiveMenu
1384
+ });
1385
+ import fs15 from "fs";
1386
+ import path15 from "path";
1387
+ import os3 from "os";
1388
+ import * as p12 from "@clack/prompts";
1389
+ function isFirstRun() {
1390
+ const configPath = path15.join(os3.homedir(), ".planmode", "config");
1391
+ const hasConfig = fs15.existsSync(configPath);
1392
+ const lockfile = readLockfile();
1393
+ const hasPackages = Object.keys(lockfile.packages).length > 0;
1394
+ return !hasConfig && !hasPackages;
1395
+ }
1396
+ async function runInteractiveMenu() {
1397
+ if (isFirstRun()) {
1398
+ await firstRunFlow();
1399
+ } else {
1400
+ await mainMenu();
1401
+ }
1402
+ }
1403
+ async function firstRunFlow() {
1404
+ p12.intro("planmode");
1405
+ p12.note(
1406
+ [
1407
+ "planmode installs AI plans, rules, and prompts into your project.",
1408
+ "Plans work with Claude Code automatically via CLAUDE.md imports.",
1409
+ "",
1410
+ " Plans - step-by-step guides Claude follows to build things",
1411
+ " Rules - always-on constraints that shape every AI interaction",
1412
+ " Prompts - reusable templates you run once to get output"
1413
+ ].join("\n"),
1414
+ "Welcome"
1415
+ );
1416
+ const action = handleCancel(
1417
+ await p12.select({
1418
+ message: "Let's get you started. What would you like to do?",
1419
+ options: [
1420
+ { value: "browse", label: "Browse popular packages", hint: "see what's available" },
1421
+ { value: "search", label: "Search for something specific" },
1422
+ { value: "create", label: "Create your own package", hint: "start from scratch" }
1423
+ ]
1424
+ })
1425
+ );
1426
+ switch (action) {
1427
+ case "browse":
1428
+ await featuredFlow();
1429
+ break;
1430
+ case "search":
1431
+ await searchFlow();
1432
+ break;
1433
+ case "create": {
1434
+ const { initInteractive: initInteractive2 } = await Promise.resolve().then(() => (init_init2(), init_exports));
1435
+ await initInteractive2();
1436
+ break;
1437
+ }
1438
+ }
1439
+ const cont = handleCancel(
1440
+ await p12.confirm({
1441
+ message: "Continue exploring?",
1442
+ initialValue: true
1443
+ })
1444
+ );
1445
+ if (cont) {
1446
+ await mainMenu();
1447
+ } else {
1448
+ p12.outro("Run `planmode` anytime to come back.");
1449
+ }
1450
+ }
1451
+ async function featuredFlow() {
1452
+ const index = await withSpinner(
1453
+ "Loading packages...",
1454
+ () => fetchIndex()
1455
+ );
1456
+ const plans = index.packages.filter((pkg) => pkg.type === "plan");
1457
+ const rules = index.packages.filter((pkg) => pkg.type === "rule");
1458
+ const prompts = index.packages.filter((pkg) => pkg.type === "prompt");
1459
+ const featured = [
1460
+ ...plans.slice(0, 5),
1461
+ ...rules.slice(0, 3),
1462
+ ...prompts.slice(0, 3)
1463
+ ];
1464
+ if (featured.length === 0) {
1465
+ p12.log.warn("No packages in the registry yet.");
1466
+ return;
1467
+ }
1468
+ const planOptions = plans.slice(0, 5).map((pkg) => ({
1469
+ value: pkg.name,
1470
+ label: pkg.name,
1471
+ hint: pkg.description.length > 55 ? pkg.description.slice(0, 55) + "..." : pkg.description
1472
+ }));
1473
+ const ruleOptions = rules.slice(0, 3).map((pkg) => ({
1474
+ value: pkg.name,
1475
+ label: pkg.name,
1476
+ hint: pkg.description.length > 55 ? pkg.description.slice(0, 55) + "..." : pkg.description
1477
+ }));
1478
+ const promptOptions = prompts.slice(0, 3).map((pkg) => ({
1479
+ value: pkg.name,
1480
+ label: pkg.name,
1481
+ hint: pkg.description.length > 55 ? pkg.description.slice(0, 55) + "..." : pkg.description
1482
+ }));
1483
+ const allOptions = [];
1484
+ if (planOptions.length > 0) {
1485
+ allOptions.push(...planOptions);
1486
+ }
1487
+ if (ruleOptions.length > 0) {
1488
+ allOptions.push(...ruleOptions);
1489
+ }
1490
+ if (promptOptions.length > 0) {
1491
+ allOptions.push(...promptOptions);
1492
+ }
1493
+ const selected = handleCancel(
1494
+ await p12.select({
1495
+ message: `${index.packages.length} packages available. Pick one to install:`,
1496
+ options: [
1497
+ ...allOptions,
1498
+ { value: "__more__", label: "Browse by category..." }
1499
+ ]
1500
+ })
1501
+ );
1502
+ if (selected === "__more__") {
1503
+ await browseFlow();
1504
+ return;
1505
+ }
1506
+ await installOrDetailFlow(selected);
1507
+ }
1508
+ async function mainMenu() {
1509
+ p12.intro("planmode");
1510
+ while (true) {
1511
+ const action = handleCancel(
1512
+ await p12.select({
1513
+ message: "What would you like to do?",
1514
+ options: [
1515
+ { value: "search", label: "Search packages", hint: "find packages by keyword" },
1516
+ { value: "browse", label: "Browse by category" },
1517
+ { value: "install", label: "Install a package", hint: "install by name" },
1518
+ { value: "create", label: "Create a new package" },
1519
+ { value: "list", label: "My installed packages" },
1520
+ { value: "doctor", label: "Health check" },
1521
+ { value: "exit", label: "Exit" }
1522
+ ]
1523
+ })
1524
+ );
1525
+ switch (action) {
1526
+ case "search":
1527
+ await searchFlow();
1528
+ break;
1529
+ case "browse":
1530
+ await browseFlow();
1531
+ break;
1532
+ case "install":
1533
+ await installFlow();
1534
+ break;
1535
+ case "create": {
1536
+ const { initInteractive: initInteractive2 } = await Promise.resolve().then(() => (init_init2(), init_exports));
1537
+ await initInteractive2();
1538
+ break;
1539
+ }
1540
+ case "list":
1541
+ listFlow();
1542
+ break;
1543
+ case "doctor":
1544
+ doctorFlow();
1545
+ break;
1546
+ case "exit":
1547
+ p12.outro("Goodbye!");
1548
+ return;
1549
+ }
1550
+ }
1551
+ }
1552
+ async function searchFlow() {
1553
+ const query = handleCancel(
1554
+ await p12.text({
1555
+ message: "Search for packages:",
1556
+ placeholder: "e.g. nextjs, tailwind, auth",
1557
+ validate(input) {
1558
+ if (!input) return "Please enter a search query";
1559
+ }
1560
+ })
1561
+ );
1562
+ const results = await withSpinner(
1563
+ "Searching registry...",
1564
+ () => searchPackages(query)
1565
+ );
1566
+ if (results.length === 0) {
1567
+ p12.log.warn("No packages found matching your query.");
1568
+ return;
1569
+ }
1570
+ p12.log.info(`Found ${results.length} package(s)`);
1571
+ await packageSelectionFlow(results.map((r) => ({
1572
+ name: r.name,
1573
+ type: r.type,
1574
+ version: r.version,
1575
+ description: r.description
1576
+ })));
1577
+ }
1578
+ async function browseFlow() {
1579
+ const category = handleCancel(
1580
+ await p12.select({
1581
+ message: "Select a category:",
1582
+ options: CATEGORIES2.map((cat) => ({
1583
+ value: cat,
1584
+ label: cat
1585
+ }))
1586
+ })
1587
+ );
1588
+ const results = await withSpinner(
1589
+ `Loading ${category} packages...`,
1590
+ () => searchPackages("", { category })
1591
+ );
1592
+ if (results.length === 0) {
1593
+ p12.log.warn(`No packages found in category "${category}".`);
1594
+ return;
1595
+ }
1596
+ p12.log.info(`Found ${results.length} package(s) in "${category}"`);
1597
+ await packageSelectionFlow(results.map((r) => ({
1598
+ name: r.name,
1599
+ type: r.type,
1600
+ version: r.version,
1601
+ description: r.description
1602
+ })));
1603
+ }
1604
+ async function packageSelectionFlow(packages) {
1605
+ const selected = handleCancel(
1606
+ await p12.select({
1607
+ message: "Select a package:",
1608
+ options: [
1609
+ ...packages.map((pkg) => ({
1610
+ value: pkg.name,
1611
+ label: `${pkg.name} (${pkg.type} v${pkg.version})`,
1612
+ hint: pkg.description.length > 60 ? pkg.description.slice(0, 60) + "..." : pkg.description
1613
+ })),
1614
+ { value: "__back__", label: "Back" }
1615
+ ]
1616
+ })
1617
+ );
1618
+ if (selected === "__back__") return;
1619
+ await installOrDetailFlow(selected);
1620
+ }
1621
+ async function installOrDetailFlow(packageName) {
1622
+ const action = handleCancel(
1623
+ await p12.select({
1624
+ message: `${packageName}:`,
1625
+ options: [
1626
+ { value: "install", label: "Install" },
1627
+ { value: "details", label: "View details" },
1628
+ { value: "back", label: "Back" }
1629
+ ]
1630
+ })
1631
+ );
1632
+ if (action === "install") {
1633
+ try {
1634
+ await installPackage(packageName, { interactive: true });
1635
+ p12.log.success(`Installed ${packageName}`);
1636
+ } catch (err) {
1637
+ p12.log.error(err.message);
1638
+ }
1639
+ } else if (action === "details") {
1640
+ try {
1641
+ const meta = await withSpinner(
1642
+ "Fetching package details...",
1643
+ () => fetchPackageMetadata(packageName)
1644
+ );
1645
+ const lines = [
1646
+ `Type: ${meta.type}`,
1647
+ `Author: ${meta.author}`,
1648
+ `License: ${meta.license}`,
1649
+ `Category: ${meta.category}`,
1650
+ `Downloads: ${meta.downloads.toLocaleString()}`,
1651
+ `Versions: ${meta.versions.join(", ")}`,
1652
+ `Repository: ${meta.repository}`
1653
+ ];
1654
+ if (meta.tags?.length) {
1655
+ lines.push(`Tags: ${meta.tags.join(", ")}`);
1656
+ }
1657
+ if (meta.dependencies?.rules?.length) {
1658
+ lines.push(`Dep (rules): ${meta.dependencies.rules.join(", ")}`);
1659
+ }
1660
+ if (meta.dependencies?.plans?.length) {
1661
+ lines.push(`Dep (plans): ${meta.dependencies.plans.join(", ")}`);
1662
+ }
1663
+ p12.note(lines.join("\n"), `${meta.name}@${meta.latest_version}`);
1664
+ const nextAction = handleCancel(
1665
+ await p12.confirm({
1666
+ message: "Install this package?",
1667
+ initialValue: true
1668
+ })
1669
+ );
1670
+ if (nextAction) {
1671
+ try {
1672
+ await installPackage(packageName, { interactive: true });
1673
+ p12.log.success(`Installed ${packageName}`);
1674
+ } catch (err) {
1675
+ p12.log.error(err.message);
1676
+ }
1677
+ }
1678
+ } catch (err) {
1679
+ p12.log.error(err.message);
1680
+ }
1681
+ }
1682
+ }
1683
+ async function installFlow() {
1684
+ const packageName = handleCancel(
1685
+ await p12.text({
1686
+ message: "Package name to install:",
1687
+ placeholder: "e.g. nextjs-tailwind-starter",
1688
+ validate(input) {
1689
+ if (!input) return "Please enter a package name";
1690
+ }
1691
+ })
1692
+ );
1693
+ try {
1694
+ await installPackage(packageName, { interactive: true });
1695
+ p12.log.success(`Installed ${packageName}`);
1696
+ } catch (err) {
1697
+ p12.log.error(err.message);
1698
+ }
1699
+ }
1700
+ function listFlow() {
1701
+ const lockfile = readLockfile();
1702
+ const entries = Object.entries(lockfile.packages);
1703
+ if (entries.length === 0) {
1704
+ p12.log.info('No packages installed. Select "Install a package" to get started.');
1705
+ return;
1706
+ }
1707
+ const lines = entries.map(
1708
+ ([name, entry]) => `${name} (${entry.type} v${entry.version}) -> ${entry.installed_to}`
1709
+ );
1710
+ p12.note(lines.join("\n"), "Installed packages");
1711
+ }
1712
+ function doctorFlow() {
1713
+ const result = runDoctor();
1714
+ if (result.issues.length === 0) {
1715
+ p12.log.success(`Checked ${result.packagesChecked} package(s) \u2014 no issues found.`);
1716
+ return;
1717
+ }
1718
+ const errors = result.issues.filter((i) => i.severity === "error");
1719
+ const warnings = result.issues.filter((i) => i.severity === "warning");
1720
+ for (const issue of errors) {
1721
+ p12.log.error(issue.message);
1722
+ }
1723
+ for (const issue of warnings) {
1724
+ p12.log.warn(issue.message);
1725
+ }
1726
+ if (errors.length > 0) {
1727
+ p12.log.error(`${errors.length} error(s), ${warnings.length} warning(s)`);
1728
+ } else {
1729
+ p12.log.warn(`${warnings.length} warning(s)`);
1730
+ }
1731
+ }
1732
+ var CATEGORIES2;
1733
+ var init_interactive = __esm({
1734
+ "src/commands/interactive.ts"() {
1735
+ "use strict";
1736
+ init_prompts();
1737
+ init_registry();
1738
+ init_installer();
1739
+ init_lockfile();
1740
+ init_doctor();
1741
+ CATEGORIES2 = [
1742
+ "frontend",
1743
+ "backend",
1744
+ "devops",
1745
+ "database",
1746
+ "testing",
1747
+ "mobile",
1748
+ "ai-ml",
1749
+ "design",
1750
+ "security",
1751
+ "other"
1752
+ ];
1753
+ }
1754
+ });
1755
+
1756
+ // src/index.ts
1757
+ import { Command as Command16 } from "commander";
1758
+
1759
+ // src/commands/install.ts
1760
+ init_installer();
1761
+ init_logger();
1762
+ init_prompts();
1763
+ import { Command } from "commander";
1764
+ import * as p2 from "@clack/prompts";
1765
+ function parseVariables(pairs) {
1766
+ const vars = {};
1767
+ for (const pair of pairs) {
1768
+ const eq = pair.indexOf("=");
1769
+ if (eq === -1) {
1770
+ throw new Error(`Invalid variable format: "${pair}". Use --set key=value`);
1771
+ }
1772
+ vars[pair.slice(0, eq)] = pair.slice(eq + 1);
1773
+ }
1774
+ return vars;
1775
+ }
1776
+ 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(
1777
+ async (packageName, options) => {
1778
+ try {
1779
+ const interactive = isInteractive() && options.input !== false;
1780
+ const variables = options.set ? parseVariables(options.set) : void 0;
1781
+ if (interactive) {
1782
+ p2.intro(`Installing ${packageName}`);
1783
+ } else {
1784
+ logger.blank();
1785
+ }
1786
+ await installPackage(packageName, {
1787
+ version: options.version,
1788
+ forceRule: options.rule,
1789
+ noInput: options.input === false,
1790
+ variables,
1791
+ interactive
1792
+ });
1793
+ if (interactive) {
1794
+ p2.outro("Done!");
1795
+ } else {
1796
+ logger.blank();
1797
+ }
1798
+ } catch (err) {
1799
+ logger.error(err.message);
1800
+ process.exit(1);
1801
+ }
1802
+ }
1803
+ );
1804
+
1805
+ // src/commands/uninstall.ts
1806
+ init_installer();
1807
+ init_logger();
1808
+ import { Command as Command2 } from "commander";
1809
+ var uninstallCommand = new Command2("uninstall").description("Remove an installed package").argument("<package>", "Package name").action(async (packageName) => {
1810
+ try {
1811
+ logger.blank();
1812
+ await uninstallPackage(packageName);
1813
+ logger.blank();
1814
+ } catch (err) {
1815
+ logger.error(err.message);
1816
+ process.exit(1);
1817
+ }
1818
+ });
1819
+
1820
+ // src/commands/search.ts
1821
+ init_registry();
1822
+ init_installer();
1823
+ init_logger();
1824
+ init_prompts();
1825
+ import { Command as Command3 } from "commander";
1826
+ import * as p3 from "@clack/prompts";
1827
+ 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) => {
1828
+ try {
1829
+ const results = await withSpinner(
1830
+ "Searching registry...",
1831
+ () => searchPackages(query, {
1832
+ type: options.type,
1833
+ category: options.category
1834
+ })
1835
+ );
1836
+ if (results.length === 0) {
1837
+ if (isInteractive() && !options.json) {
1838
+ p3.log.warn("No packages found matching your query.");
1839
+ } else {
1840
+ logger.info("No packages found matching your query.");
1841
+ }
902
1842
  return;
903
1843
  }
904
1844
  if (options.json) {
905
1845
  console.log(JSON.stringify(results, null, 2));
906
1846
  return;
907
1847
  }
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
- ])
1848
+ if (!isInteractive()) {
1849
+ logger.blank();
1850
+ logger.table(
1851
+ ["name", "type", "version", "description"],
1852
+ results.map((pkg) => [
1853
+ pkg.name,
1854
+ pkg.type,
1855
+ pkg.version,
1856
+ pkg.description.length > 50 ? pkg.description.slice(0, 50) + "..." : pkg.description
1857
+ ])
1858
+ );
1859
+ logger.blank();
1860
+ return;
1861
+ }
1862
+ const selected = handleCancel(
1863
+ await p3.select({
1864
+ message: `Found ${results.length} package(s). Select one:`,
1865
+ options: [
1866
+ ...results.map((pkg) => ({
1867
+ value: pkg.name,
1868
+ label: `${pkg.name} (${pkg.type} v${pkg.version})`,
1869
+ hint: pkg.description.length > 60 ? pkg.description.slice(0, 60) + "..." : pkg.description
1870
+ })),
1871
+ { value: "__none__", label: "Cancel" }
1872
+ ]
1873
+ })
917
1874
  );
918
- logger.blank();
1875
+ if (selected === "__none__") return;
1876
+ const action = handleCancel(
1877
+ await p3.select({
1878
+ message: `${selected}:`,
1879
+ options: [
1880
+ { value: "install", label: "Install" },
1881
+ { value: "details", label: "View details" },
1882
+ { value: "back", label: "Cancel" }
1883
+ ]
1884
+ })
1885
+ );
1886
+ if (action === "install") {
1887
+ try {
1888
+ await installPackage(selected, { interactive: true });
1889
+ p3.log.success(`Installed ${selected}`);
1890
+ } catch (err) {
1891
+ p3.log.error(err.message);
1892
+ }
1893
+ } else if (action === "details") {
1894
+ const meta = await withSpinner(
1895
+ "Fetching package details...",
1896
+ () => fetchPackageMetadata(selected)
1897
+ );
1898
+ const lines = [
1899
+ `Type: ${meta.type}`,
1900
+ `Author: ${meta.author}`,
1901
+ `License: ${meta.license}`,
1902
+ `Category: ${meta.category}`,
1903
+ `Downloads: ${meta.downloads.toLocaleString()}`,
1904
+ `Versions: ${meta.versions.join(", ")}`,
1905
+ `Repository: ${meta.repository}`
1906
+ ];
1907
+ if (meta.tags?.length) {
1908
+ lines.push(`Tags: ${meta.tags.join(", ")}`);
1909
+ }
1910
+ p3.note(lines.join("\n"), `${meta.name}@${meta.latest_version}`);
1911
+ }
919
1912
  } catch (err) {
920
1913
  logger.error(err.message);
921
1914
  process.exit(1);
@@ -923,6 +1916,10 @@ var searchCommand = new Command3("search").description("Search the registry for
923
1916
  });
924
1917
 
925
1918
  // src/commands/run.ts
1919
+ init_manifest();
1920
+ init_template();
1921
+ init_logger();
1922
+ init_prompts();
926
1923
  import { Command as Command4 } from "commander";
927
1924
  import fs8 from "fs";
928
1925
  import path8 from "path";
@@ -957,18 +1954,8 @@ var runCommand = new Command4("run").description("Run a templated prompt and out
957
1954
  process.exit(1);
958
1955
  }
959
1956
  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
- }
1957
+ const noInput = options.input === false;
1958
+ const values = await promptForVariables(manifest.variables, vars, noInput);
972
1959
  for (const [name, def] of Object.entries(manifest.variables)) {
973
1960
  if (def.type !== "resolved") continue;
974
1961
  values[name] = await resolveVariable(def, values);
@@ -988,143 +1975,161 @@ var runCommand = new Command4("run").description("Run a templated prompt and out
988
1975
 
989
1976
  // src/commands/publish.ts
990
1977
  import { Command as Command5 } from "commander";
1978
+ import * as p4 from "@clack/prompts";
991
1979
 
992
1980
  // src/lib/publisher.ts
1981
+ init_manifest();
1982
+ init_config();
1983
+ init_git();
1984
+ init_logger();
1985
+ init_prompts();
993
1986
  async function publishPackage(options = {}) {
994
1987
  const cwd = options.projectDir ?? process.cwd();
1988
+ const interactive = options.interactive ?? false;
995
1989
  const token = options.token ?? getGitHubToken();
996
1990
  if (!token) {
997
1991
  throw new Error("Not authenticated. Run `planmode login` first.");
998
1992
  }
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:
1993
+ const doValidate = async () => {
1994
+ const manifest2 = readManifest(cwd);
1995
+ const errors = validateManifest(manifest2, true);
1996
+ if (errors.length > 0) {
1997
+ throw new Error(`Invalid manifest:
1004
1998
  ${errors.map((e) => ` - ${e}`).join("\n")}`);
1005
- }
1999
+ }
2000
+ return manifest2;
2001
+ };
2002
+ const manifest = interactive ? await withSpinner("Validating manifest...", doValidate, "Manifest valid") : await (async () => {
2003
+ logger.info("Reading planmode.yaml...");
2004
+ return doValidate();
2005
+ })();
1006
2006
  const remoteUrl = await getRemoteUrl(cwd);
1007
2007
  if (!remoteUrl) {
1008
2008
  throw new Error("No git remote found. Push your code to GitHub first.");
1009
2009
  }
1010
2010
  const sha = await getHeadSha(cwd);
1011
- 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"
2011
+ const tag = `v${manifest.version}`;
2012
+ const doTag = async () => {
2013
+ try {
2014
+ await createTag(cwd, tag);
2015
+ } catch {
2016
+ }
2017
+ try {
2018
+ await pushTag(cwd, tag);
2019
+ } catch {
2020
+ }
1030
2021
  };
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.");
2022
+ if (interactive) {
2023
+ await withSpinner(`Creating tag ${tag}...`, doTag, `Tag ${tag} ready`);
2024
+ } else {
2025
+ logger.info(`Creating tag ${tag}...`);
2026
+ await doTag();
2027
+ logger.success(`Pushed tag ${tag}`);
1038
2028
  }
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: {
2029
+ const doSubmit = async () => {
2030
+ const headers = {
2031
+ Authorization: `Bearer ${token}`,
2032
+ Accept: "application/vnd.github.v3+json",
2033
+ "User-Agent": "planmode-cli",
2034
+ "Content-Type": "application/json"
2035
+ };
2036
+ await fetch("https://api.github.com/repos/kaihannonen/planmode.org/forks", {
2037
+ method: "POST",
2038
+ headers
2039
+ });
2040
+ const userRes = await fetch("https://api.github.com/user", { headers });
2041
+ if (!userRes.ok) {
2042
+ throw new Error("Failed to authenticate with GitHub. Check your token.");
2043
+ }
2044
+ const user = await userRes.json();
2045
+ const repoPath = remoteUrl.replace(/^https?:\/\//, "").replace(/\.git$/, "");
2046
+ const metadataContent = JSON.stringify(
2047
+ {
2048
+ name: manifest.name,
2049
+ description: manifest.description,
2050
+ author: manifest.author,
2051
+ license: manifest.license,
1068
2052
  repository: repoPath,
1069
- tag,
1070
- sha
2053
+ category: manifest.category ?? "other",
2054
+ tags: manifest.tags ?? [],
2055
+ type: manifest.type,
2056
+ models: manifest.models ?? [],
2057
+ latest_version: manifest.version,
2058
+ versions: [manifest.version],
2059
+ downloads: 0,
2060
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
2061
+ updated_at: (/* @__PURE__ */ new Date()).toISOString(),
2062
+ dependencies: manifest.dependencies,
2063
+ variables: manifest.variables
1071
2064
  },
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",
2065
+ null,
2066
+ 2
2067
+ );
2068
+ const versionContent = JSON.stringify(
2069
+ {
2070
+ version: manifest.version,
2071
+ published_at: (/* @__PURE__ */ new Date()).toISOString(),
2072
+ source: {
2073
+ repository: repoPath,
2074
+ tag,
2075
+ sha
2076
+ },
2077
+ files: ["planmode.yaml", manifest.content_file ?? "inline"],
2078
+ content_hash: `sha256:${sha.slice(0, 16)}`
2079
+ },
2080
+ null,
2081
+ 2
2082
+ );
2083
+ const branchName = `add-${manifest.name}-${manifest.version}`;
2084
+ const refRes = await fetch(
2085
+ `https://api.github.com/repos/${user.login}/planmode.org/git/ref/heads/main`,
2086
+ { headers }
2087
+ );
2088
+ if (!refRes.ok) {
2089
+ throw new Error("Failed to access registry fork. Make sure the fork exists.");
2090
+ }
2091
+ const refData = await refRes.json();
2092
+ const baseSha = refData.object.sha;
2093
+ await fetch(`https://api.github.com/repos/${user.login}/planmode.org/git/refs`, {
2094
+ method: "POST",
1100
2095
  headers,
1101
2096
  body: JSON.stringify({
1102
- message: `Add ${manifest.name}@${manifest.version}`,
1103
- content: Buffer.from(metadataContent).toString("base64"),
1104
- branch: branchName
2097
+ ref: `refs/heads/${branchName}`,
2098
+ sha: baseSha
1105
2099
  })
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",
2100
+ });
2101
+ await fetch(
2102
+ `https://api.github.com/repos/${user.login}/planmode.org/contents/registry/packages/${manifest.name}/metadata.json`,
2103
+ {
2104
+ method: "PUT",
2105
+ headers,
2106
+ body: JSON.stringify({
2107
+ message: `Add ${manifest.name}@${manifest.version}`,
2108
+ content: Buffer.from(metadataContent).toString("base64"),
2109
+ branch: branchName
2110
+ })
2111
+ }
2112
+ );
2113
+ await fetch(
2114
+ `https://api.github.com/repos/${user.login}/planmode.org/contents/registry/packages/${manifest.name}/versions/${manifest.version}.json`,
2115
+ {
2116
+ method: "PUT",
2117
+ headers,
2118
+ body: JSON.stringify({
2119
+ message: `Add ${manifest.name}@${manifest.version} version metadata`,
2120
+ content: Buffer.from(versionContent).toString("base64"),
2121
+ branch: branchName
2122
+ })
2123
+ }
2124
+ );
2125
+ const prRes = await fetch("https://api.github.com/repos/kaihannonen/planmode.org/pulls", {
2126
+ method: "POST",
1112
2127
  headers,
1113
2128
  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}
2129
+ title: `Add ${manifest.name}@${manifest.version}`,
2130
+ head: `${user.login}:${branchName}`,
2131
+ base: "main",
2132
+ body: `## New package: ${manifest.name}
1128
2133
 
1129
2134
  - **Type:** ${manifest.type}
1130
2135
  - **Version:** ${manifest.version}
@@ -1132,28 +2137,51 @@ ${errors.map((e) => ` - ${e}`).join("\n")}`);
1132
2137
  - **Author:** ${manifest.author}
1133
2138
 
1134
2139
  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}`);
2140
+ })
2141
+ });
2142
+ if (!prRes.ok) {
2143
+ const err = await prRes.text();
2144
+ throw new Error(`Failed to create PR: ${err}`);
2145
+ }
2146
+ const pr = await prRes.json();
2147
+ return pr.html_url;
2148
+ };
2149
+ let prUrl;
2150
+ if (interactive) {
2151
+ prUrl = await withSpinner(
2152
+ "Submitting to registry...",
2153
+ doSubmit,
2154
+ "Submitted to registry"
2155
+ );
2156
+ } else {
2157
+ logger.info("Submitting to registry...");
2158
+ prUrl = await doSubmit();
2159
+ logger.success(`Published ${manifest.name}@${manifest.version}`);
2160
+ logger.info(`PR: ${prUrl}`);
1140
2161
  }
1141
- const pr = await prRes.json();
1142
- logger.success(`Published ${manifest.name}@${manifest.version}`);
1143
- logger.info(`PR: ${pr.html_url}`);
1144
2162
  return {
1145
- prUrl: pr.html_url,
2163
+ prUrl,
1146
2164
  packageName: manifest.name,
1147
2165
  version: manifest.version
1148
2166
  };
1149
2167
  }
1150
2168
 
1151
2169
  // src/commands/publish.ts
2170
+ init_logger();
2171
+ init_prompts();
1152
2172
  var publishCommand = new Command5("publish").description("Publish the current directory as a package to the registry").action(async () => {
1153
2173
  try {
1154
- logger.blank();
1155
- const result = await publishPackage();
1156
- logger.blank();
2174
+ if (isInteractive()) {
2175
+ p4.intro("Publishing package");
2176
+ } else {
2177
+ logger.blank();
2178
+ }
2179
+ const result = await publishPackage({ interactive: isInteractive() });
2180
+ if (isInteractive()) {
2181
+ p4.outro(`Published ${result.packageName}@${result.version} \u2014 PR: ${result.prUrl}`);
2182
+ } else {
2183
+ logger.blank();
2184
+ }
1157
2185
  } catch (err) {
1158
2186
  logger.error(err.message);
1159
2187
  process.exit(1);
@@ -1161,269 +2189,150 @@ var publishCommand = new Command5("publish").description("Publish the current di
1161
2189
  });
1162
2190
 
1163
2191
  // src/commands/update.ts
2192
+ init_installer();
2193
+ init_lockfile();
2194
+ init_logger();
2195
+ init_prompts();
1164
2196
  import { Command as Command6 } from "commander";
2197
+ import * as p5 from "@clack/prompts";
1165
2198
  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
2199
  try {
1167
- logger.blank();
2200
+ const interactive = isInteractive();
2201
+ if (interactive) {
2202
+ p5.intro("Updating packages");
2203
+ } else {
2204
+ logger.blank();
2205
+ }
1168
2206
  if (packageName) {
1169
- const updated = await updatePackage(packageName);
2207
+ const updated = interactive ? await withSpinner(
2208
+ `Checking ${packageName} for updates...`,
2209
+ () => updatePackage(packageName)
2210
+ ) : await updatePackage(packageName);
1170
2211
  if (!updated) {
1171
- logger.info("Already up to date.");
2212
+ if (interactive) {
2213
+ p5.log.info("Already up to date.");
2214
+ } else {
2215
+ logger.info("Already up to date.");
2216
+ }
1172
2217
  }
1173
2218
  } else {
1174
2219
  const lockfile = readLockfile();
1175
2220
  const names = Object.keys(lockfile.packages);
1176
2221
  if (names.length === 0) {
1177
- logger.info("No packages installed.");
2222
+ if (interactive) {
2223
+ p5.log.info("No packages installed.");
2224
+ } else {
2225
+ logger.info("No packages installed.");
2226
+ }
2227
+ if (interactive) p5.outro("Nothing to update.");
1178
2228
  return;
1179
2229
  }
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}`);
2230
+ const doUpdate = async () => {
2231
+ let updatedCount2 = 0;
2232
+ for (const name of names) {
2233
+ try {
2234
+ const updated = await updatePackage(name);
2235
+ if (updated) updatedCount2++;
2236
+ } catch (err) {
2237
+ logger.warn(`Failed to update ${name}: ${err.message}`);
2238
+ }
1187
2239
  }
1188
- }
2240
+ return updatedCount2;
2241
+ };
2242
+ const updatedCount = interactive ? await withSpinner("Checking for updates...", doUpdate) : await doUpdate();
1189
2243
  if (updatedCount === 0) {
1190
- logger.info("All packages are up to date.");
2244
+ if (interactive) {
2245
+ p5.log.info("All packages are up to date.");
2246
+ } else {
2247
+ logger.info("All packages are up to date.");
2248
+ }
1191
2249
  } else {
1192
- logger.success(`Updated ${updatedCount} package${updatedCount > 1 ? "s" : ""}.`);
1193
- }
1194
- }
1195
- logger.blank();
1196
- } catch (err) {
1197
- logger.error(err.message);
1198
- process.exit(1);
1199
- }
1200
- });
1201
-
1202
- // src/commands/list.ts
1203
- import { Command as Command7 } from "commander";
1204
- var listCommand = new Command7("list").description("List all installed packages").action(() => {
1205
- const lockfile = readLockfile();
1206
- const entries = Object.entries(lockfile.packages);
1207
- if (entries.length === 0) {
1208
- logger.info("No packages installed. Run `planmode install <package>` to get started.");
1209
- return;
1210
- }
1211
- logger.blank();
1212
- logger.table(
1213
- ["name", "type", "version", "location"],
1214
- entries.map(([name, entry]) => [
1215
- name,
1216
- entry.type,
1217
- entry.version,
1218
- entry.installed_to
1219
- ])
1220
- );
1221
- logger.blank();
1222
- });
1223
-
1224
- // src/commands/info.ts
1225
- import { Command as Command8 } from "commander";
1226
- var infoCommand = new Command8("info").description("Show detailed info about a package").argument("<package>", "Package name").action(async (packageName) => {
1227
- try {
1228
- const meta = await fetchPackageMetadata(packageName);
1229
- logger.blank();
1230
- logger.bold(`${meta.name}@${meta.latest_version}`);
1231
- logger.blank();
1232
- console.log(` Description: ${meta.description}`);
1233
- console.log(` Type: ${meta.type}`);
1234
- console.log(` Author: ${meta.author}`);
1235
- console.log(` License: ${meta.license}`);
1236
- console.log(` Category: ${meta.category}`);
1237
- console.log(` Downloads: ${meta.downloads.toLocaleString()}`);
1238
- 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(", ")}`);
2250
+ if (interactive) {
2251
+ p5.log.success(`Updated ${updatedCount} package${updatedCount > 1 ? "s" : ""}.`);
2252
+ } else {
2253
+ logger.success(`Updated ${updatedCount} package${updatedCount > 1 ? "s" : ""}.`);
1263
2254
  }
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
- }
2255
+ }
2256
+ }
2257
+ if (interactive) {
2258
+ p5.outro("Done!");
2259
+ } else {
2260
+ logger.blank();
2261
+ }
2262
+ } catch (err) {
2263
+ logger.error(err.message);
2264
+ process.exit(1);
2265
+ }
2266
+ });
1381
2267
 
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 () => {
2268
+ // src/commands/list.ts
2269
+ init_lockfile();
2270
+ init_logger();
2271
+ import { Command as Command7 } from "commander";
2272
+ var listCommand = new Command7("list").description("List all installed packages").action(() => {
2273
+ const lockfile = readLockfile();
2274
+ const entries = Object.entries(lockfile.packages);
2275
+ if (entries.length === 0) {
2276
+ logger.info("No packages installed. Run `planmode install <package>` to get started.");
2277
+ return;
2278
+ }
2279
+ logger.blank();
2280
+ logger.table(
2281
+ ["name", "type", "version", "location"],
2282
+ entries.map(([name, entry]) => [
2283
+ name,
2284
+ entry.type,
2285
+ entry.version,
2286
+ entry.installed_to
2287
+ ])
2288
+ );
2289
+ logger.blank();
2290
+ });
2291
+
2292
+ // src/commands/info.ts
2293
+ init_registry();
2294
+ init_logger();
2295
+ import { Command as Command8 } from "commander";
2296
+ var infoCommand = new Command8("info").description("Show detailed info about a package").argument("<package>", "Package name").action(async (packageName) => {
1394
2297
  try {
2298
+ const meta = await fetchPackageMetadata(packageName);
1395
2299
  logger.blank();
1396
- logger.bold("Initialize a new Planmode package");
2300
+ logger.bold(`${meta.name}@${meta.latest_version}`);
1397
2301
  logger.blank();
1398
- const name = await prompt("Package name: ");
1399
- if (!name) {
1400
- logger.error("Package name is required.");
1401
- process.exit(1);
2302
+ console.log(` Description: ${meta.description}`);
2303
+ console.log(` Type: ${meta.type}`);
2304
+ console.log(` Author: ${meta.author}`);
2305
+ console.log(` License: ${meta.license}`);
2306
+ console.log(` Category: ${meta.category}`);
2307
+ console.log(` Downloads: ${meta.downloads.toLocaleString()}`);
2308
+ console.log(` Repository: ${meta.repository}`);
2309
+ if (meta.models && meta.models.length > 0) {
2310
+ console.log(` Models: ${meta.models.join(", ")}`);
2311
+ }
2312
+ if (meta.tags && meta.tags.length > 0) {
2313
+ console.log(` Tags: ${meta.tags.join(", ")}`);
2314
+ }
2315
+ console.log(` Versions: ${meta.versions.join(", ")}`);
2316
+ if (meta.dependencies) {
2317
+ if (meta.dependencies.rules && meta.dependencies.rules.length > 0) {
2318
+ console.log(` Dep (rules): ${meta.dependencies.rules.join(", ")}`);
2319
+ }
2320
+ if (meta.dependencies.plans && meta.dependencies.plans.length > 0) {
2321
+ console.log(` Dep (plans): ${meta.dependencies.plans.join(", ")}`);
2322
+ }
2323
+ }
2324
+ if (meta.variables) {
2325
+ logger.blank();
2326
+ logger.bold(" Variables:");
2327
+ for (const [name, def] of Object.entries(meta.variables)) {
2328
+ const required = def.required ? " (required)" : "";
2329
+ const defaultVal = def.default !== void 0 ? ` [default: ${def.default}]` : "";
2330
+ console.log(` ${name}: ${def.type}${required}${defaultVal} \u2014 ${def.description}`);
2331
+ if (def.options) {
2332
+ console.log(` options: ${def.options.join(", ")}`);
2333
+ }
2334
+ }
1402
2335
  }
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
2336
  logger.blank();
1428
2337
  } catch (err) {
1429
2338
  logger.error(err.message);
@@ -1431,8 +2340,15 @@ var initCommand = new Command9("init").description("Initialize a new package in
1431
2340
  }
1432
2341
  });
1433
2342
 
2343
+ // src/index.ts
2344
+ init_init2();
2345
+
1434
2346
  // src/commands/login.ts
2347
+ init_config();
2348
+ init_logger();
2349
+ init_prompts();
1435
2350
  import { Command as Command10 } from "commander";
2351
+ import * as p7 from "@clack/prompts";
1436
2352
  import { execSync } from "child_process";
1437
2353
  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
2354
  let token;
@@ -1445,37 +2361,61 @@ var loginCommand = new Command10("login").description("Configure GitHub authenti
1445
2361
  logger.error("Failed to read token from GitHub CLI. Make sure `gh` is installed and authenticated.");
1446
2362
  process.exit(1);
1447
2363
  }
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
- });
2364
+ } else if (isInteractive()) {
2365
+ p7.intro("planmode login");
2366
+ const value = await p7.password({
2367
+ message: "GitHub personal access token:",
2368
+ validate(input) {
2369
+ if (!input) return "Token is required";
2370
+ }
1456
2371
  });
2372
+ token = handleCancel(value);
2373
+ } else {
2374
+ logger.error("No token provided. Use --token <token> or --gh.");
2375
+ process.exit(1);
1457
2376
  }
1458
2377
  if (!token) {
1459
2378
  logger.error("No token provided.");
1460
2379
  process.exit(1);
1461
2380
  }
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"
2381
+ const validateToken = async () => {
2382
+ const response = await fetch("https://api.github.com/user", {
2383
+ headers: {
2384
+ Authorization: `Bearer ${token}`,
2385
+ "User-Agent": "planmode-cli"
2386
+ }
2387
+ });
2388
+ if (!response.ok) {
2389
+ throw new Error("Invalid token. GitHub API returned: " + response.status);
2390
+ }
2391
+ return await response.json();
2392
+ };
2393
+ try {
2394
+ const user = await withSpinner(
2395
+ "Validating token...",
2396
+ validateToken,
2397
+ "Token validated"
2398
+ );
2399
+ setGitHubToken(token);
2400
+ if (isInteractive()) {
2401
+ p7.log.success(`Authenticated as ${user.login}`);
2402
+ p7.outro("You're all set!");
2403
+ } else {
2404
+ logger.success(`Authenticated as ${user.login}`);
2405
+ }
2406
+ } catch (err) {
2407
+ if (isInteractive()) {
2408
+ p7.log.error(err.message);
2409
+ p7.outro("Authentication failed.");
2410
+ } else {
2411
+ logger.error(err.message);
1467
2412
  }
1468
- });
1469
- if (!response.ok) {
1470
- logger.error("Invalid token. GitHub API returned: " + response.status);
1471
2413
  process.exit(1);
1472
2414
  }
1473
- const user = await response.json();
1474
- setGitHubToken(token);
1475
- logger.success(`Authenticated as ${user.login}`);
1476
2415
  });
1477
2416
 
1478
2417
  // src/commands/mcp.ts
2418
+ init_logger();
1479
2419
  import { Command as Command11 } from "commander";
1480
2420
  import { execSync as execSync2 } from "child_process";
1481
2421
  var mcpCommand = new Command11("mcp").description("Manage MCP server registration with Claude Code");
@@ -1506,125 +2446,67 @@ mcpCommand.command("remove").description("Remove the planmode MCP server from Cl
1506
2446
  });
1507
2447
 
1508
2448
  // src/commands/doctor.ts
2449
+ init_doctor();
2450
+ init_logger();
2451
+ init_prompts();
1509
2452
  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
2453
+ import * as p8 from "@clack/prompts";
1601
2454
  var doctorCommand = new Command12("doctor").description("Check project health: verify installed packages, imports, and file integrity").action(() => {
2455
+ const interactive = isInteractive();
1602
2456
  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.");
2457
+ if (interactive) {
2458
+ p8.intro("Health check");
2459
+ } else {
1608
2460
  logger.blank();
2461
+ }
2462
+ if (interactive) {
2463
+ p8.log.info(`Checked ${result.packagesChecked} package(s)`);
2464
+ } else {
2465
+ logger.bold(`Checked ${result.packagesChecked} package(s)`);
2466
+ logger.blank();
2467
+ }
2468
+ if (result.issues.length === 0) {
2469
+ if (interactive) {
2470
+ p8.outro("Everything looks good. No issues found.");
2471
+ } else {
2472
+ logger.success("Everything looks good. No issues found.");
2473
+ logger.blank();
2474
+ }
1609
2475
  return;
1610
2476
  }
1611
2477
  const errors = result.issues.filter((i) => i.severity === "error");
1612
2478
  const warnings = result.issues.filter((i) => i.severity === "warning");
1613
2479
  for (const issue of errors) {
1614
- logger.error(issue.message);
1615
- if (issue.fix) logger.dim(` Fix: ${issue.fix}`);
2480
+ if (interactive) {
2481
+ p8.log.error(issue.message);
2482
+ } else {
2483
+ logger.error(issue.message);
2484
+ if (issue.fix) logger.dim(` Fix: ${issue.fix}`);
2485
+ }
1616
2486
  }
1617
2487
  for (const issue of warnings) {
1618
- logger.warn(issue.message);
1619
- if (issue.fix) logger.dim(` Fix: ${issue.fix}`);
2488
+ if (interactive) {
2489
+ p8.log.warn(issue.message);
2490
+ } else {
2491
+ logger.warn(issue.message);
2492
+ if (issue.fix) logger.dim(` Fix: ${issue.fix}`);
2493
+ }
1620
2494
  }
1621
- logger.blank();
1622
- if (errors.length > 0) {
1623
- logger.error(`${errors.length} error(s), ${warnings.length} warning(s)`);
2495
+ if (interactive) {
2496
+ if (errors.length > 0) {
2497
+ p8.outro(`${errors.length} error(s), ${warnings.length} warning(s)`);
2498
+ } else {
2499
+ p8.outro(`${warnings.length} warning(s)`);
2500
+ }
1624
2501
  } else {
1625
- logger.warn(`${warnings.length} warning(s)`);
2502
+ logger.blank();
2503
+ if (errors.length > 0) {
2504
+ logger.error(`${errors.length} error(s), ${warnings.length} warning(s)`);
2505
+ } else {
2506
+ logger.warn(`${warnings.length} warning(s)`);
2507
+ }
2508
+ logger.blank();
1626
2509
  }
1627
- logger.blank();
1628
2510
  if (errors.length > 0) {
1629
2511
  process.exit(1);
1630
2512
  }
@@ -1632,8 +2514,12 @@ var doctorCommand = new Command12("doctor").description("Check project health: v
1632
2514
 
1633
2515
  // src/commands/test.ts
1634
2516
  import { Command as Command13 } from "commander";
2517
+ import * as p9 from "@clack/prompts";
1635
2518
 
1636
2519
  // src/lib/tester.ts
2520
+ init_manifest();
2521
+ init_template();
2522
+ init_registry();
1637
2523
  async function testPackage(projectDir = process.cwd()) {
1638
2524
  const issues = [];
1639
2525
  const checks = [];
@@ -1762,33 +2648,62 @@ async function testPackage(projectDir = process.cwd()) {
1762
2648
  }
1763
2649
 
1764
2650
  // src/commands/test.ts
2651
+ init_logger();
2652
+ init_prompts();
1765
2653
  var testCommand = new Command13("test").description("Test the current package before publishing: validate manifest, render templates, check dependencies").action(async () => {
1766
2654
  try {
1767
- logger.blank();
1768
- logger.bold("Testing package...");
1769
- logger.blank();
2655
+ const interactive = isInteractive();
2656
+ if (interactive) {
2657
+ p9.intro("Testing package");
2658
+ } else {
2659
+ logger.blank();
2660
+ logger.bold("Testing package...");
2661
+ logger.blank();
2662
+ }
1770
2663
  const result = await testPackage();
1771
2664
  for (const check of result.checks) {
1772
2665
  if (check.passed) {
1773
- logger.success(check.name);
2666
+ if (interactive) {
2667
+ p9.log.success(check.name);
2668
+ } else {
2669
+ logger.success(check.name);
2670
+ }
1774
2671
  } else {
1775
2672
  const issue = result.issues.find((i) => i.check === check.name);
1776
2673
  if (issue?.severity === "error") {
1777
- logger.error(`${check.name}: ${issue.message}`);
2674
+ if (interactive) {
2675
+ p9.log.error(`${check.name}: ${issue.message}`);
2676
+ } else {
2677
+ logger.error(`${check.name}: ${issue.message}`);
2678
+ }
1778
2679
  } else if (issue) {
1779
- logger.warn(`${check.name}: ${issue.message}`);
2680
+ if (interactive) {
2681
+ p9.log.warn(`${check.name}: ${issue.message}`);
2682
+ } else {
2683
+ logger.warn(`${check.name}: ${issue.message}`);
2684
+ }
1780
2685
  }
1781
2686
  }
1782
2687
  }
1783
- logger.blank();
1784
- if (result.passed) {
1785
- logger.success(`All checks passed. Ready to publish.`);
2688
+ if (interactive) {
2689
+ if (result.passed) {
2690
+ p9.outro("All checks passed. Ready to publish.");
2691
+ } else {
2692
+ const errors = result.issues.filter((i) => i.severity === "error");
2693
+ const warnings = result.issues.filter((i) => i.severity === "warning");
2694
+ p9.outro(`${errors.length} error(s), ${warnings.length} warning(s). Fix errors before publishing.`);
2695
+ }
1786
2696
  } 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.`);
2697
+ logger.blank();
2698
+ if (result.passed) {
2699
+ logger.success(`All checks passed. Ready to publish.`);
2700
+ } else {
2701
+ const errors = result.issues.filter((i) => i.severity === "error");
2702
+ const warnings = result.issues.filter((i) => i.severity === "warning");
2703
+ logger.error(`${errors.length} error(s), ${warnings.length} warning(s). Fix errors before publishing.`);
2704
+ }
2705
+ logger.blank();
1790
2706
  }
1791
- logger.blank();
1792
2707
  if (!result.passed) {
1793
2708
  process.exit(1);
1794
2709
  }
@@ -1800,6 +2715,7 @@ var testCommand = new Command13("test").description("Test the current package be
1800
2715
 
1801
2716
  // src/commands/record.ts
1802
2717
  import { Command as Command14 } from "commander";
2718
+ import * as p10 from "@clack/prompts";
1803
2719
  import fs12 from "fs";
1804
2720
  import path12 from "path";
1805
2721
 
@@ -1823,8 +2739,8 @@ function startRecording(projectDir = process.cwd()) {
1823
2739
  async function startRecordingAsync(projectDir = process.cwd()) {
1824
2740
  const recordingPath = startRecording(projectDir);
1825
2741
  const git = simpleGit2(projectDir);
1826
- const log = await git.log({ n: 1 });
1827
- const sha = log.latest?.hash;
2742
+ const log8 = await git.log({ n: 1 });
2743
+ const sha = log8.latest?.hash;
1828
2744
  if (!sha) {
1829
2745
  throw new Error("No commits found in this repository.");
1830
2746
  }
@@ -1841,12 +2757,12 @@ async function stopRecording(projectDir = process.cwd(), options = {}) {
1841
2757
  }
1842
2758
  const startSha = fs11.readFileSync(recordingPath, "utf-8").trim();
1843
2759
  const git = simpleGit2(projectDir);
1844
- const log = await git.log({ from: startSha, to: "HEAD" });
1845
- if (log.total === 0) {
2760
+ const log8 = await git.log({ from: startSha, to: "HEAD" });
2761
+ if (log8.total === 0) {
1846
2762
  fs11.unlinkSync(recordingPath);
1847
2763
  throw new Error("No commits since recording started. Nothing to capture.");
1848
2764
  }
1849
- const commits = [...log.all].reverse();
2765
+ const commits = [...log8.all].reverse();
1850
2766
  const steps = [];
1851
2767
  const allFilesChanged = /* @__PURE__ */ new Set();
1852
2768
  for (const commit of commits) {
@@ -1933,6 +2849,8 @@ function generateManifest(name, author) {
1933
2849
  }
1934
2850
 
1935
2851
  // src/commands/record.ts
2852
+ init_logger();
2853
+ init_prompts();
1936
2854
  var recordCommand = new Command14("record").description("Record git activity and generate a plan from commits");
1937
2855
  recordCommand.command("start").description("Start recording \u2014 saves current HEAD as the starting point").action(async () => {
1938
2856
  try {
@@ -1948,12 +2866,26 @@ recordCommand.command("start").description("Start recording \u2014 saves current
1948
2866
  });
1949
2867
  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
2868
  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
- });
2869
+ const interactive = isInteractive();
2870
+ if (interactive) {
2871
+ p10.intro("Generating plan from recording");
2872
+ } else {
2873
+ logger.blank();
2874
+ }
2875
+ const result = interactive ? await withSpinner(
2876
+ "Analyzing commits...",
2877
+ () => stopRecording(process.cwd(), {
2878
+ name: options.name,
2879
+ author: options.author
2880
+ }),
2881
+ "Analysis complete"
2882
+ ) : await (async () => {
2883
+ logger.info("Analyzing commits...");
2884
+ return stopRecording(process.cwd(), {
2885
+ name: options.name,
2886
+ author: options.author
2887
+ });
2888
+ })();
1957
2889
  const outDir = options.dir ?? process.cwd();
1958
2890
  fs12.mkdirSync(outDir, { recursive: true });
1959
2891
  fs12.writeFileSync(path12.join(outDir, "planmode.yaml"), result.manifestContent, "utf-8");
@@ -1966,8 +2898,12 @@ recordCommand.command("stop").description("Stop recording and generate a plan fr
1966
2898
  }
1967
2899
  logger.blank();
1968
2900
  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();
2901
+ if (interactive) {
2902
+ p10.outro("Edit the generated plan, then run `planmode test` to validate and `planmode publish` when ready.");
2903
+ } else {
2904
+ logger.dim("Edit the generated plan, then run `planmode test` to validate and `planmode publish` when ready.");
2905
+ logger.blank();
2906
+ }
1971
2907
  } catch (err) {
1972
2908
  logger.error(err.message);
1973
2909
  process.exit(1);
@@ -1983,6 +2919,7 @@ recordCommand.command("status").description("Check if a recording is in progress
1983
2919
 
1984
2920
  // src/commands/snapshot.ts
1985
2921
  import { Command as Command15 } from "commander";
2922
+ import * as p11 from "@clack/prompts";
1986
2923
  import fs14 from "fs";
1987
2924
  import path14 from "path";
1988
2925
 
@@ -2257,14 +3194,29 @@ function detectCategory(data) {
2257
3194
  }
2258
3195
 
2259
3196
  // 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) => {
3197
+ init_logger();
3198
+ init_prompts();
3199
+ 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
3200
  try {
2262
- logger.blank();
2263
- logger.info("Analyzing project...");
2264
- const result = takeSnapshot(process.cwd(), {
2265
- name: options.name,
2266
- author: options.author
2267
- });
3201
+ const interactive = isInteractive();
3202
+ if (interactive) {
3203
+ p11.intro("Taking project snapshot");
3204
+ } else {
3205
+ logger.blank();
3206
+ }
3207
+ const doSnapshot = () => Promise.resolve(
3208
+ takeSnapshot(process.cwd(), {
3209
+ name: options.name,
3210
+ author: options.author
3211
+ })
3212
+ );
3213
+ const result = interactive ? await withSpinner("Analyzing project...", doSnapshot, "Analysis complete") : (() => {
3214
+ logger.info("Analyzing project...");
3215
+ return takeSnapshot(process.cwd(), {
3216
+ name: options.name,
3217
+ author: options.author
3218
+ });
3219
+ })();
2268
3220
  const outDir = options.dir ?? process.cwd();
2269
3221
  fs14.mkdirSync(outDir, { recursive: true });
2270
3222
  fs14.writeFileSync(path14.join(outDir, "planmode.yaml"), result.manifestContent, "utf-8");
@@ -2279,8 +3231,12 @@ var snapshotCommand = new Command15("snapshot").description("Analyze the current
2279
3231
  logger.dim(` Tools detected: ${result.data.detectedTools.map((t) => t.name).join(", ") || "none"}`);
2280
3232
  logger.blank();
2281
3233
  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.");
2283
- logger.blank();
3234
+ if (interactive) {
3235
+ p11.outro("Edit the generated plan, then run `planmode test` to validate and `planmode publish` when ready.");
3236
+ } else {
3237
+ logger.dim("Edit the generated plan, then run `planmode test` to validate and `planmode publish` when ready.");
3238
+ logger.blank();
3239
+ }
2284
3240
  } catch (err) {
2285
3241
  logger.error(err.message);
2286
3242
  process.exit(1);
@@ -2288,8 +3244,9 @@ var snapshotCommand = new Command15("snapshot").description("Analyze the current
2288
3244
  });
2289
3245
 
2290
3246
  // src/index.ts
3247
+ init_prompts();
2291
3248
  var program = new Command16();
2292
- program.name("planmode").description("The open source package manager for AI plans, rules, and prompts.").version("0.2.2");
3249
+ program.name("planmode").description("The open source package manager for AI plans, rules, and prompts.").version("0.3.0");
2293
3250
  program.addCommand(installCommand);
2294
3251
  program.addCommand(uninstallCommand);
2295
3252
  program.addCommand(searchCommand);
@@ -2305,4 +3262,9 @@ program.addCommand(doctorCommand);
2305
3262
  program.addCommand(testCommand);
2306
3263
  program.addCommand(recordCommand);
2307
3264
  program.addCommand(snapshotCommand);
2308
- program.parse();
3265
+ if (process.argv.length <= 2 && isInteractive()) {
3266
+ const { runInteractiveMenu: runInteractiveMenu2 } = await Promise.resolve().then(() => (init_interactive(), interactive_exports));
3267
+ runInteractiveMenu2();
3268
+ } else {
3269
+ program.parse();
3270
+ }