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 +1720 -758
- package/dist/mcp.js +315 -155
- package/package.json +2 -1
- package/src/commands/doctor.ts +46 -14
- package/src/commands/init.ts +95 -47
- package/src/commands/install.ts +17 -2
- package/src/commands/interactive.ts +449 -0
- package/src/commands/login.ts +50 -23
- package/src/commands/publish.ts +15 -3
- package/src/commands/record.ts +32 -8
- package/src/commands/run.ts +6 -15
- package/src/commands/search.ts +89 -18
- package/src/commands/snapshot.ts +33 -9
- package/src/commands/test.ts +43 -13
- package/src/commands/update.ts +57 -15
- package/src/index.ts +9 -2
- package/src/lib/installer.ts +57 -29
- package/src/lib/prompts.ts +159 -0
- package/src/lib/publisher.ts +176 -144
package/dist/index.js
CHANGED
|
@@ -1,28 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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
|
|
320
|
-
return
|
|
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
|
|
600
|
-
var
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
const
|
|
676
|
-
|
|
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
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
)
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
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}
|
|
917
|
+
`${basePath}planmode.yaml`
|
|
741
918
|
);
|
|
742
|
-
|
|
743
|
-
|
|
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 (
|
|
748
|
-
const values =
|
|
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
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
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
|
-
|
|
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
|
-
|
|
861
|
-
|
|
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
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
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/
|
|
893
|
-
import
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
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
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
pkg
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
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
|
-
|
|
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
|
|
961
|
-
|
|
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
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
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
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
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
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
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
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
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
|
-
|
|
1070
|
-
|
|
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
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
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
|
-
|
|
1103
|
-
|
|
1104
|
-
branch: branchName
|
|
2097
|
+
ref: `refs/heads/${branchName}`,
|
|
2098
|
+
sha: baseSha
|
|
1105
2099
|
})
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
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
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
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
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
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
|
|
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
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
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
|
-
|
|
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
|
|
2207
|
+
const updated = interactive ? await withSpinner(
|
|
2208
|
+
`Checking ${packageName} for updates...`,
|
|
2209
|
+
() => updatePackage(packageName)
|
|
2210
|
+
) : await updatePackage(packageName);
|
|
1170
2211
|
if (!updated) {
|
|
1171
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
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
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
})
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
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/
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
}
|
|
1393
|
-
|
|
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(
|
|
2300
|
+
logger.bold(`${meta.name}@${meta.latest_version}`);
|
|
1397
2301
|
logger.blank();
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
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
|
-
|
|
1450
|
-
const
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
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
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
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
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
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
|
-
|
|
1615
|
-
|
|
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
|
-
|
|
1619
|
-
|
|
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
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
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.
|
|
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
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
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
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
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
|
|
1827
|
-
const sha =
|
|
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
|
|
1845
|
-
if (
|
|
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 = [...
|
|
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
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
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
|
-
|
|
1970
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
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
|
-
|
|
2283
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
+
}
|