planmode 0.2.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2080 -717
- package/dist/mcp.js +798 -262
- package/package.json +2 -1
- package/src/commands/context.ts +111 -0
- 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 +556 -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 +11 -2
- package/src/lib/context.ts +265 -0
- package/src/lib/installer.ts +57 -29
- package/src/lib/prompts.ts +159 -0
- package/src/lib/publisher.ts +176 -144
- package/src/mcp.ts +146 -0
- package/src/types/index.ts +28 -0
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,1187 @@ 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/lib/context.ts
|
|
1381
|
+
import fs15 from "fs";
|
|
1382
|
+
import path15 from "path";
|
|
1383
|
+
import { parse as parse4, stringify as stringify6 } from "yaml";
|
|
1384
|
+
function getContextPath(projectDir) {
|
|
1385
|
+
return path15.join(projectDir, CONTEXT_DIR, CONTEXT_FILE);
|
|
1386
|
+
}
|
|
1387
|
+
function emptyIndex() {
|
|
1388
|
+
return { version: 1, repos: [] };
|
|
1389
|
+
}
|
|
1390
|
+
function readContextIndex(projectDir = process.cwd()) {
|
|
1391
|
+
const filePath = getContextPath(projectDir);
|
|
1392
|
+
try {
|
|
1393
|
+
const raw = fs15.readFileSync(filePath, "utf-8");
|
|
1394
|
+
const data = parse4(raw);
|
|
1395
|
+
return data ?? emptyIndex();
|
|
1396
|
+
} catch {
|
|
1397
|
+
return emptyIndex();
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
function writeContextIndex(index, projectDir = process.cwd()) {
|
|
1401
|
+
const dirPath = path15.join(projectDir, CONTEXT_DIR);
|
|
1402
|
+
fs15.mkdirSync(dirPath, { recursive: true });
|
|
1403
|
+
const filePath = getContextPath(projectDir);
|
|
1404
|
+
fs15.writeFileSync(filePath, stringify6(index), "utf-8");
|
|
1405
|
+
}
|
|
1406
|
+
function walkDirectory(dirPath) {
|
|
1407
|
+
const files = [];
|
|
1408
|
+
function walk(currentPath) {
|
|
1409
|
+
let entries;
|
|
1410
|
+
try {
|
|
1411
|
+
entries = fs15.readdirSync(currentPath, { withFileTypes: true });
|
|
1412
|
+
} catch {
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
for (const entry of entries) {
|
|
1416
|
+
if (entry.name.startsWith(".") && IGNORED_DIRS.has(entry.name)) continue;
|
|
1417
|
+
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
1418
|
+
const fullPath = path15.join(currentPath, entry.name);
|
|
1419
|
+
if (entry.isDirectory()) {
|
|
1420
|
+
walk(fullPath);
|
|
1421
|
+
} else if (entry.isFile()) {
|
|
1422
|
+
const ext = path15.extname(entry.name).toLowerCase();
|
|
1423
|
+
if (!SUPPORTED_EXTENSIONS.has(ext)) continue;
|
|
1424
|
+
try {
|
|
1425
|
+
const stat = fs15.statSync(fullPath);
|
|
1426
|
+
const relativePath = path15.relative(dirPath, fullPath);
|
|
1427
|
+
files.push({
|
|
1428
|
+
path: relativePath,
|
|
1429
|
+
extension: ext,
|
|
1430
|
+
size: stat.size,
|
|
1431
|
+
modified_at: stat.mtime.toISOString()
|
|
1432
|
+
});
|
|
1433
|
+
} catch {
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
walk(dirPath);
|
|
1439
|
+
return files;
|
|
1440
|
+
}
|
|
1441
|
+
function addContextRepo(repoPath, options = {}) {
|
|
1442
|
+
const projectDir = options.projectDir ?? process.cwd();
|
|
1443
|
+
const absolutePath = path15.resolve(projectDir, repoPath);
|
|
1444
|
+
if (!fs15.existsSync(absolutePath)) {
|
|
1445
|
+
throw new Error(`Directory not found: ${repoPath}`);
|
|
1446
|
+
}
|
|
1447
|
+
if (!fs15.statSync(absolutePath).isDirectory()) {
|
|
1448
|
+
throw new Error(`Not a directory: ${repoPath}`);
|
|
1449
|
+
}
|
|
1450
|
+
const index = readContextIndex(projectDir);
|
|
1451
|
+
const relative = path15.relative(projectDir, absolutePath);
|
|
1452
|
+
const isInsideProject = !relative.startsWith("..") && !path15.isAbsolute(relative);
|
|
1453
|
+
const storedPath = isInsideProject ? relative : absolutePath;
|
|
1454
|
+
const existing = index.repos.find(
|
|
1455
|
+
(r) => r.repo.path === storedPath || r.repo.name === options.name
|
|
1456
|
+
);
|
|
1457
|
+
if (existing) {
|
|
1458
|
+
throw new Error(
|
|
1459
|
+
`Context repo already exists: ${existing.repo.name ?? existing.repo.path}. Use \`planmode context reindex\` to refresh.`
|
|
1460
|
+
);
|
|
1461
|
+
}
|
|
1462
|
+
logger.info(`Scanning ${absolutePath}...`);
|
|
1463
|
+
const files = walkDirectory(absolutePath);
|
|
1464
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1465
|
+
const repoIndex = {
|
|
1466
|
+
repo: {
|
|
1467
|
+
path: storedPath,
|
|
1468
|
+
name: options.name,
|
|
1469
|
+
added_at: now
|
|
1470
|
+
},
|
|
1471
|
+
files,
|
|
1472
|
+
indexed_at: now,
|
|
1473
|
+
file_count: files.length,
|
|
1474
|
+
total_size: files.reduce((sum, f) => sum + f.size, 0)
|
|
1475
|
+
};
|
|
1476
|
+
index.repos.push(repoIndex);
|
|
1477
|
+
writeContextIndex(index, projectDir);
|
|
1478
|
+
logger.success(`Added "${options.name ?? storedPath}" \u2014 ${files.length} file(s), ${formatSize(repoIndex.total_size)}`);
|
|
1479
|
+
const breakdown = getTypeBreakdown(files);
|
|
1480
|
+
if (breakdown.length > 0) {
|
|
1481
|
+
logger.dim(` ${breakdown.join(", ")}`);
|
|
1482
|
+
}
|
|
1483
|
+
return repoIndex;
|
|
1484
|
+
}
|
|
1485
|
+
function removeContextRepo(pathOrName, projectDir = process.cwd()) {
|
|
1486
|
+
const index = readContextIndex(projectDir);
|
|
1487
|
+
const idx = index.repos.findIndex(
|
|
1488
|
+
(r) => r.repo.path === pathOrName || r.repo.name === pathOrName
|
|
1489
|
+
);
|
|
1490
|
+
if (idx === -1) {
|
|
1491
|
+
throw new Error(`Context repo not found: ${pathOrName}`);
|
|
1492
|
+
}
|
|
1493
|
+
const removed = index.repos[idx];
|
|
1494
|
+
index.repos.splice(idx, 1);
|
|
1495
|
+
writeContextIndex(index, projectDir);
|
|
1496
|
+
logger.success(`Removed "${removed.repo.name ?? removed.repo.path}"`);
|
|
1497
|
+
}
|
|
1498
|
+
function reindexContext(pathOrName, projectDir = process.cwd()) {
|
|
1499
|
+
const index = readContextIndex(projectDir);
|
|
1500
|
+
if (index.repos.length === 0) {
|
|
1501
|
+
throw new Error("No context repos configured. Use `planmode context add <path>` first.");
|
|
1502
|
+
}
|
|
1503
|
+
const targets = pathOrName ? index.repos.filter(
|
|
1504
|
+
(r) => r.repo.path === pathOrName || r.repo.name === pathOrName
|
|
1505
|
+
) : index.repos;
|
|
1506
|
+
if (pathOrName && targets.length === 0) {
|
|
1507
|
+
throw new Error(`Context repo not found: ${pathOrName}`);
|
|
1508
|
+
}
|
|
1509
|
+
for (const repo of targets) {
|
|
1510
|
+
const absolutePath = path15.resolve(projectDir, repo.repo.path);
|
|
1511
|
+
if (!fs15.existsSync(absolutePath)) {
|
|
1512
|
+
logger.warn(`Directory not found, skipping: ${repo.repo.path}`);
|
|
1513
|
+
continue;
|
|
1514
|
+
}
|
|
1515
|
+
logger.info(`Re-scanning ${repo.repo.name ?? repo.repo.path}...`);
|
|
1516
|
+
const files = walkDirectory(absolutePath);
|
|
1517
|
+
repo.files = files;
|
|
1518
|
+
repo.indexed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1519
|
+
repo.file_count = files.length;
|
|
1520
|
+
repo.total_size = files.reduce((sum, f) => sum + f.size, 0);
|
|
1521
|
+
logger.success(`Reindexed "${repo.repo.name ?? repo.repo.path}" \u2014 ${files.length} file(s), ${formatSize(repo.total_size)}`);
|
|
1522
|
+
}
|
|
1523
|
+
writeContextIndex(index, projectDir);
|
|
1524
|
+
}
|
|
1525
|
+
function getContextSummary(projectDir = process.cwd()) {
|
|
1526
|
+
const index = readContextIndex(projectDir);
|
|
1527
|
+
return {
|
|
1528
|
+
totalRepos: index.repos.length,
|
|
1529
|
+
totalFiles: index.repos.reduce((sum, r) => sum + r.file_count, 0),
|
|
1530
|
+
totalSize: index.repos.reduce((sum, r) => sum + r.total_size, 0),
|
|
1531
|
+
repos: index.repos.map((r) => ({
|
|
1532
|
+
name: r.repo.name ?? r.repo.path,
|
|
1533
|
+
path: r.repo.path,
|
|
1534
|
+
fileCount: r.file_count,
|
|
1535
|
+
totalSize: r.total_size,
|
|
1536
|
+
typeBreakdown: getTypeBreakdown(r.files),
|
|
1537
|
+
indexedAt: r.indexed_at
|
|
1538
|
+
}))
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
function getTypeBreakdown(files) {
|
|
1542
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1543
|
+
for (const file of files) {
|
|
1544
|
+
counts.set(file.extension, (counts.get(file.extension) ?? 0) + 1);
|
|
1545
|
+
}
|
|
1546
|
+
return Array.from(counts.entries()).sort((a, b) => b[1] - a[1]).map(([ext, count]) => `${ext}: ${count}`);
|
|
1547
|
+
}
|
|
1548
|
+
function formatSize(bytes) {
|
|
1549
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
1550
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
1551
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
1552
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
1553
|
+
}
|
|
1554
|
+
var CONTEXT_DIR, CONTEXT_FILE, SUPPORTED_EXTENSIONS, IGNORED_DIRS;
|
|
1555
|
+
var init_context = __esm({
|
|
1556
|
+
"src/lib/context.ts"() {
|
|
1557
|
+
"use strict";
|
|
1558
|
+
init_logger();
|
|
1559
|
+
CONTEXT_DIR = ".planmode";
|
|
1560
|
+
CONTEXT_FILE = "context.yaml";
|
|
1561
|
+
SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1562
|
+
".txt",
|
|
1563
|
+
".md",
|
|
1564
|
+
".markdown",
|
|
1565
|
+
".pdf",
|
|
1566
|
+
".rtf",
|
|
1567
|
+
".doc",
|
|
1568
|
+
".docx",
|
|
1569
|
+
".csv",
|
|
1570
|
+
".tsv",
|
|
1571
|
+
".json",
|
|
1572
|
+
".yaml",
|
|
1573
|
+
".yml",
|
|
1574
|
+
".xml",
|
|
1575
|
+
".html",
|
|
1576
|
+
".htm",
|
|
1577
|
+
".rst",
|
|
1578
|
+
".org",
|
|
1579
|
+
".tex",
|
|
1580
|
+
".log"
|
|
1581
|
+
]);
|
|
1582
|
+
IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
1583
|
+
"node_modules",
|
|
1584
|
+
".git",
|
|
1585
|
+
"dist",
|
|
1586
|
+
"build",
|
|
1587
|
+
".next",
|
|
1588
|
+
"__pycache__",
|
|
1589
|
+
".venv",
|
|
1590
|
+
"venv",
|
|
1591
|
+
".tox",
|
|
1592
|
+
"target",
|
|
1593
|
+
"out",
|
|
1594
|
+
".cache",
|
|
1595
|
+
".turbo",
|
|
1596
|
+
"coverage",
|
|
1597
|
+
".nyc_output"
|
|
1598
|
+
]);
|
|
1599
|
+
}
|
|
1600
|
+
});
|
|
1601
|
+
|
|
1602
|
+
// src/commands/interactive.ts
|
|
1603
|
+
var interactive_exports = {};
|
|
1604
|
+
__export(interactive_exports, {
|
|
1605
|
+
runInteractiveMenu: () => runInteractiveMenu
|
|
1606
|
+
});
|
|
1607
|
+
import fs16 from "fs";
|
|
1608
|
+
import path16 from "path";
|
|
1609
|
+
import os3 from "os";
|
|
1610
|
+
import * as p12 from "@clack/prompts";
|
|
1611
|
+
function isFirstRun() {
|
|
1612
|
+
const configPath = path16.join(os3.homedir(), ".planmode", "config");
|
|
1613
|
+
const hasConfig = fs16.existsSync(configPath);
|
|
1614
|
+
const lockfile = readLockfile();
|
|
1615
|
+
const hasPackages = Object.keys(lockfile.packages).length > 0;
|
|
1616
|
+
return !hasConfig && !hasPackages;
|
|
1617
|
+
}
|
|
1618
|
+
async function runInteractiveMenu() {
|
|
1619
|
+
if (isFirstRun()) {
|
|
1620
|
+
await firstRunFlow();
|
|
1621
|
+
} else {
|
|
1622
|
+
await mainMenu();
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
async function firstRunFlow() {
|
|
1626
|
+
p12.intro("planmode");
|
|
1627
|
+
p12.note(
|
|
1628
|
+
[
|
|
1629
|
+
"planmode installs AI plans, rules, and prompts into your project.",
|
|
1630
|
+
"Plans work with Claude Code automatically via CLAUDE.md imports.",
|
|
1631
|
+
"",
|
|
1632
|
+
" Plans - step-by-step guides Claude follows to build things",
|
|
1633
|
+
" Rules - always-on constraints that shape every AI interaction",
|
|
1634
|
+
" Prompts - reusable templates you run once to get output"
|
|
1635
|
+
].join("\n"),
|
|
1636
|
+
"Welcome"
|
|
1637
|
+
);
|
|
1638
|
+
const action = handleCancel(
|
|
1639
|
+
await p12.select({
|
|
1640
|
+
message: "Let's get you started. What would you like to do?",
|
|
1641
|
+
options: [
|
|
1642
|
+
{ value: "browse", label: "Browse popular packages", hint: "see what's available" },
|
|
1643
|
+
{ value: "search", label: "Search for something specific" },
|
|
1644
|
+
{ value: "create", label: "Create your own package", hint: "start from scratch" }
|
|
1645
|
+
]
|
|
1646
|
+
})
|
|
1647
|
+
);
|
|
1648
|
+
switch (action) {
|
|
1649
|
+
case "browse":
|
|
1650
|
+
await featuredFlow();
|
|
1651
|
+
break;
|
|
1652
|
+
case "search":
|
|
1653
|
+
await searchFlow();
|
|
1654
|
+
break;
|
|
1655
|
+
case "create": {
|
|
1656
|
+
const { initInteractive: initInteractive2 } = await Promise.resolve().then(() => (init_init2(), init_exports));
|
|
1657
|
+
await initInteractive2();
|
|
1658
|
+
break;
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
const cont = handleCancel(
|
|
1662
|
+
await p12.confirm({
|
|
1663
|
+
message: "Continue exploring?",
|
|
1664
|
+
initialValue: true
|
|
1665
|
+
})
|
|
1666
|
+
);
|
|
1667
|
+
if (cont) {
|
|
1668
|
+
await mainMenu();
|
|
1669
|
+
} else {
|
|
1670
|
+
p12.outro("Run `planmode` anytime to come back.");
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
async function featuredFlow() {
|
|
1674
|
+
const index = await withSpinner(
|
|
1675
|
+
"Loading packages...",
|
|
1676
|
+
() => fetchIndex()
|
|
1677
|
+
);
|
|
1678
|
+
const plans = index.packages.filter((pkg) => pkg.type === "plan");
|
|
1679
|
+
const rules = index.packages.filter((pkg) => pkg.type === "rule");
|
|
1680
|
+
const prompts = index.packages.filter((pkg) => pkg.type === "prompt");
|
|
1681
|
+
const featured = [
|
|
1682
|
+
...plans.slice(0, 5),
|
|
1683
|
+
...rules.slice(0, 3),
|
|
1684
|
+
...prompts.slice(0, 3)
|
|
1685
|
+
];
|
|
1686
|
+
if (featured.length === 0) {
|
|
1687
|
+
p12.log.warn("No packages in the registry yet.");
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
const planOptions = plans.slice(0, 5).map((pkg) => ({
|
|
1691
|
+
value: pkg.name,
|
|
1692
|
+
label: pkg.name,
|
|
1693
|
+
hint: pkg.description.length > 55 ? pkg.description.slice(0, 55) + "..." : pkg.description
|
|
1694
|
+
}));
|
|
1695
|
+
const ruleOptions = rules.slice(0, 3).map((pkg) => ({
|
|
1696
|
+
value: pkg.name,
|
|
1697
|
+
label: pkg.name,
|
|
1698
|
+
hint: pkg.description.length > 55 ? pkg.description.slice(0, 55) + "..." : pkg.description
|
|
1699
|
+
}));
|
|
1700
|
+
const promptOptions = prompts.slice(0, 3).map((pkg) => ({
|
|
1701
|
+
value: pkg.name,
|
|
1702
|
+
label: pkg.name,
|
|
1703
|
+
hint: pkg.description.length > 55 ? pkg.description.slice(0, 55) + "..." : pkg.description
|
|
1704
|
+
}));
|
|
1705
|
+
const allOptions = [];
|
|
1706
|
+
if (planOptions.length > 0) {
|
|
1707
|
+
allOptions.push(...planOptions);
|
|
1708
|
+
}
|
|
1709
|
+
if (ruleOptions.length > 0) {
|
|
1710
|
+
allOptions.push(...ruleOptions);
|
|
1711
|
+
}
|
|
1712
|
+
if (promptOptions.length > 0) {
|
|
1713
|
+
allOptions.push(...promptOptions);
|
|
1714
|
+
}
|
|
1715
|
+
const selected = handleCancel(
|
|
1716
|
+
await p12.select({
|
|
1717
|
+
message: `${index.packages.length} packages available. Pick one to install:`,
|
|
1718
|
+
options: [
|
|
1719
|
+
...allOptions,
|
|
1720
|
+
{ value: "__more__", label: "Browse by category..." }
|
|
1721
|
+
]
|
|
1722
|
+
})
|
|
1723
|
+
);
|
|
1724
|
+
if (selected === "__more__") {
|
|
1725
|
+
await browseFlow();
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1728
|
+
await installOrDetailFlow(selected);
|
|
1729
|
+
}
|
|
1730
|
+
async function mainMenu() {
|
|
1731
|
+
p12.intro("planmode");
|
|
1732
|
+
while (true) {
|
|
1733
|
+
const action = handleCancel(
|
|
1734
|
+
await p12.select({
|
|
1735
|
+
message: "What would you like to do?",
|
|
1736
|
+
options: [
|
|
1737
|
+
{ value: "search", label: "Search packages", hint: "find packages by keyword" },
|
|
1738
|
+
{ value: "browse", label: "Browse by category" },
|
|
1739
|
+
{ value: "install", label: "Install a package", hint: "install by name" },
|
|
1740
|
+
{ value: "create", label: "Create a new package" },
|
|
1741
|
+
{ value: "list", label: "My installed packages" },
|
|
1742
|
+
{ value: "context", label: "Manage context", hint: "document directories for AI" },
|
|
1743
|
+
{ value: "doctor", label: "Health check" },
|
|
1744
|
+
{ value: "exit", label: "Exit" }
|
|
1745
|
+
]
|
|
1746
|
+
})
|
|
1747
|
+
);
|
|
1748
|
+
switch (action) {
|
|
1749
|
+
case "search":
|
|
1750
|
+
await searchFlow();
|
|
1751
|
+
break;
|
|
1752
|
+
case "browse":
|
|
1753
|
+
await browseFlow();
|
|
1754
|
+
break;
|
|
1755
|
+
case "install":
|
|
1756
|
+
await installFlow();
|
|
1757
|
+
break;
|
|
1758
|
+
case "create": {
|
|
1759
|
+
const { initInteractive: initInteractive2 } = await Promise.resolve().then(() => (init_init2(), init_exports));
|
|
1760
|
+
await initInteractive2();
|
|
1761
|
+
break;
|
|
1762
|
+
}
|
|
1763
|
+
case "list":
|
|
1764
|
+
listFlow();
|
|
1765
|
+
break;
|
|
1766
|
+
case "context":
|
|
1767
|
+
await contextFlow();
|
|
1768
|
+
break;
|
|
1769
|
+
case "doctor":
|
|
1770
|
+
doctorFlow();
|
|
1771
|
+
break;
|
|
1772
|
+
case "exit":
|
|
1773
|
+
p12.outro("Goodbye!");
|
|
1774
|
+
return;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
async function searchFlow() {
|
|
1779
|
+
const query = handleCancel(
|
|
1780
|
+
await p12.text({
|
|
1781
|
+
message: "Search for packages:",
|
|
1782
|
+
placeholder: "e.g. nextjs, tailwind, auth",
|
|
1783
|
+
validate(input) {
|
|
1784
|
+
if (!input) return "Please enter a search query";
|
|
1785
|
+
}
|
|
1786
|
+
})
|
|
1787
|
+
);
|
|
1788
|
+
const results = await withSpinner(
|
|
1789
|
+
"Searching registry...",
|
|
1790
|
+
() => searchPackages(query)
|
|
1791
|
+
);
|
|
1792
|
+
if (results.length === 0) {
|
|
1793
|
+
p12.log.warn("No packages found matching your query.");
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
p12.log.info(`Found ${results.length} package(s)`);
|
|
1797
|
+
await packageSelectionFlow(results.map((r) => ({
|
|
1798
|
+
name: r.name,
|
|
1799
|
+
type: r.type,
|
|
1800
|
+
version: r.version,
|
|
1801
|
+
description: r.description
|
|
1802
|
+
})));
|
|
1803
|
+
}
|
|
1804
|
+
async function browseFlow() {
|
|
1805
|
+
const category = handleCancel(
|
|
1806
|
+
await p12.select({
|
|
1807
|
+
message: "Select a category:",
|
|
1808
|
+
options: CATEGORIES2.map((cat) => ({
|
|
1809
|
+
value: cat,
|
|
1810
|
+
label: cat
|
|
1811
|
+
}))
|
|
1812
|
+
})
|
|
1813
|
+
);
|
|
1814
|
+
const results = await withSpinner(
|
|
1815
|
+
`Loading ${category} packages...`,
|
|
1816
|
+
() => searchPackages("", { category })
|
|
1817
|
+
);
|
|
1818
|
+
if (results.length === 0) {
|
|
1819
|
+
p12.log.warn(`No packages found in category "${category}".`);
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
p12.log.info(`Found ${results.length} package(s) in "${category}"`);
|
|
1823
|
+
await packageSelectionFlow(results.map((r) => ({
|
|
1824
|
+
name: r.name,
|
|
1825
|
+
type: r.type,
|
|
1826
|
+
version: r.version,
|
|
1827
|
+
description: r.description
|
|
1828
|
+
})));
|
|
1829
|
+
}
|
|
1830
|
+
async function packageSelectionFlow(packages) {
|
|
1831
|
+
const selected = handleCancel(
|
|
1832
|
+
await p12.select({
|
|
1833
|
+
message: "Select a package:",
|
|
1834
|
+
options: [
|
|
1835
|
+
...packages.map((pkg) => ({
|
|
1836
|
+
value: pkg.name,
|
|
1837
|
+
label: `${pkg.name} (${pkg.type} v${pkg.version})`,
|
|
1838
|
+
hint: pkg.description.length > 60 ? pkg.description.slice(0, 60) + "..." : pkg.description
|
|
1839
|
+
})),
|
|
1840
|
+
{ value: "__back__", label: "Back" }
|
|
1841
|
+
]
|
|
1842
|
+
})
|
|
1843
|
+
);
|
|
1844
|
+
if (selected === "__back__") return;
|
|
1845
|
+
await installOrDetailFlow(selected);
|
|
1846
|
+
}
|
|
1847
|
+
async function installOrDetailFlow(packageName) {
|
|
1848
|
+
const action = handleCancel(
|
|
1849
|
+
await p12.select({
|
|
1850
|
+
message: `${packageName}:`,
|
|
1851
|
+
options: [
|
|
1852
|
+
{ value: "install", label: "Install" },
|
|
1853
|
+
{ value: "details", label: "View details" },
|
|
1854
|
+
{ value: "back", label: "Back" }
|
|
1855
|
+
]
|
|
1856
|
+
})
|
|
1857
|
+
);
|
|
1858
|
+
if (action === "install") {
|
|
1859
|
+
try {
|
|
1860
|
+
await installPackage(packageName, { interactive: true });
|
|
1861
|
+
p12.log.success(`Installed ${packageName}`);
|
|
1862
|
+
} catch (err) {
|
|
1863
|
+
p12.log.error(err.message);
|
|
1864
|
+
}
|
|
1865
|
+
} else if (action === "details") {
|
|
1866
|
+
try {
|
|
1867
|
+
const meta = await withSpinner(
|
|
1868
|
+
"Fetching package details...",
|
|
1869
|
+
() => fetchPackageMetadata(packageName)
|
|
1870
|
+
);
|
|
1871
|
+
const lines = [
|
|
1872
|
+
`Type: ${meta.type}`,
|
|
1873
|
+
`Author: ${meta.author}`,
|
|
1874
|
+
`License: ${meta.license}`,
|
|
1875
|
+
`Category: ${meta.category}`,
|
|
1876
|
+
`Downloads: ${meta.downloads.toLocaleString()}`,
|
|
1877
|
+
`Versions: ${meta.versions.join(", ")}`,
|
|
1878
|
+
`Repository: ${meta.repository}`
|
|
1879
|
+
];
|
|
1880
|
+
if (meta.tags?.length) {
|
|
1881
|
+
lines.push(`Tags: ${meta.tags.join(", ")}`);
|
|
1882
|
+
}
|
|
1883
|
+
if (meta.dependencies?.rules?.length) {
|
|
1884
|
+
lines.push(`Dep (rules): ${meta.dependencies.rules.join(", ")}`);
|
|
1885
|
+
}
|
|
1886
|
+
if (meta.dependencies?.plans?.length) {
|
|
1887
|
+
lines.push(`Dep (plans): ${meta.dependencies.plans.join(", ")}`);
|
|
1888
|
+
}
|
|
1889
|
+
p12.note(lines.join("\n"), `${meta.name}@${meta.latest_version}`);
|
|
1890
|
+
const nextAction = handleCancel(
|
|
1891
|
+
await p12.confirm({
|
|
1892
|
+
message: "Install this package?",
|
|
1893
|
+
initialValue: true
|
|
1894
|
+
})
|
|
1895
|
+
);
|
|
1896
|
+
if (nextAction) {
|
|
1897
|
+
try {
|
|
1898
|
+
await installPackage(packageName, { interactive: true });
|
|
1899
|
+
p12.log.success(`Installed ${packageName}`);
|
|
1900
|
+
} catch (err) {
|
|
1901
|
+
p12.log.error(err.message);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
} catch (err) {
|
|
1905
|
+
p12.log.error(err.message);
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
async function installFlow() {
|
|
1910
|
+
const packageName = handleCancel(
|
|
1911
|
+
await p12.text({
|
|
1912
|
+
message: "Package name to install:",
|
|
1913
|
+
placeholder: "e.g. nextjs-tailwind-starter",
|
|
1914
|
+
validate(input) {
|
|
1915
|
+
if (!input) return "Please enter a package name";
|
|
1916
|
+
}
|
|
1917
|
+
})
|
|
1918
|
+
);
|
|
1919
|
+
try {
|
|
1920
|
+
await installPackage(packageName, { interactive: true });
|
|
1921
|
+
p12.log.success(`Installed ${packageName}`);
|
|
1922
|
+
} catch (err) {
|
|
1923
|
+
p12.log.error(err.message);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
async function contextFlow() {
|
|
1927
|
+
const summary = getContextSummary();
|
|
1928
|
+
if (summary.totalRepos === 0) {
|
|
1929
|
+
p12.log.info("No context repos yet.");
|
|
1930
|
+
} else {
|
|
1931
|
+
const lines = summary.repos.map(
|
|
1932
|
+
(r) => `${r.name} \u2014 ${r.fileCount} file(s), ${formatSize(r.totalSize)}`
|
|
1933
|
+
);
|
|
1934
|
+
p12.note(lines.join("\n"), `${summary.totalRepos} context repo(s)`);
|
|
1935
|
+
}
|
|
1936
|
+
const action = handleCancel(
|
|
1937
|
+
await p12.select({
|
|
1938
|
+
message: "What would you like to do?",
|
|
1939
|
+
options: [
|
|
1940
|
+
{ value: "add", label: "Add a directory" },
|
|
1941
|
+
{ value: "remove", label: "Remove a directory" },
|
|
1942
|
+
{ value: "reindex", label: "Re-index all" },
|
|
1943
|
+
{ value: "back", label: "Back" }
|
|
1944
|
+
]
|
|
1945
|
+
})
|
|
1946
|
+
);
|
|
1947
|
+
if (action === "back") return;
|
|
1948
|
+
if (action === "add") {
|
|
1949
|
+
const dirPath = handleCancel(
|
|
1950
|
+
await p12.text({
|
|
1951
|
+
message: "Path to document directory:",
|
|
1952
|
+
placeholder: "e.g. docs, specs, ./reference",
|
|
1953
|
+
validate(input) {
|
|
1954
|
+
if (!input) return "Please enter a directory path";
|
|
1955
|
+
}
|
|
1956
|
+
})
|
|
1957
|
+
);
|
|
1958
|
+
const name = handleCancel(
|
|
1959
|
+
await p12.text({
|
|
1960
|
+
message: "Label (optional):",
|
|
1961
|
+
placeholder: "e.g. Project Documentation"
|
|
1962
|
+
})
|
|
1963
|
+
);
|
|
1964
|
+
try {
|
|
1965
|
+
await withSpinner(
|
|
1966
|
+
"Indexing documents...",
|
|
1967
|
+
async () => addContextRepo(dirPath, { name: name || void 0 }),
|
|
1968
|
+
"Indexing complete"
|
|
1969
|
+
);
|
|
1970
|
+
} catch (err) {
|
|
1971
|
+
p12.log.error(err.message);
|
|
1972
|
+
}
|
|
1973
|
+
} else if (action === "remove") {
|
|
1974
|
+
if (summary.totalRepos === 0) {
|
|
1975
|
+
p12.log.warn("No context repos to remove.");
|
|
1976
|
+
return;
|
|
1977
|
+
}
|
|
1978
|
+
const selected = handleCancel(
|
|
1979
|
+
await p12.select({
|
|
1980
|
+
message: "Select a repo to remove:",
|
|
1981
|
+
options: [
|
|
1982
|
+
...summary.repos.map((r) => ({
|
|
1983
|
+
value: r.name,
|
|
1984
|
+
label: r.name,
|
|
1985
|
+
hint: `${r.fileCount} files, ${formatSize(r.totalSize)}`
|
|
1986
|
+
})),
|
|
1987
|
+
{ value: "__back__", label: "Back" }
|
|
1988
|
+
]
|
|
1989
|
+
})
|
|
1990
|
+
);
|
|
1991
|
+
if (selected === "__back__") return;
|
|
1992
|
+
try {
|
|
1993
|
+
removeContextRepo(selected);
|
|
1994
|
+
p12.log.success(`Removed "${selected}"`);
|
|
1995
|
+
} catch (err) {
|
|
1996
|
+
p12.log.error(err.message);
|
|
1997
|
+
}
|
|
1998
|
+
} else if (action === "reindex") {
|
|
1999
|
+
if (summary.totalRepos === 0) {
|
|
2000
|
+
p12.log.warn("No context repos to reindex.");
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
try {
|
|
2004
|
+
await withSpinner(
|
|
2005
|
+
"Re-scanning documents...",
|
|
2006
|
+
async () => reindexContext(),
|
|
2007
|
+
"Reindex complete"
|
|
2008
|
+
);
|
|
2009
|
+
} catch (err) {
|
|
2010
|
+
p12.log.error(err.message);
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
function listFlow() {
|
|
2015
|
+
const lockfile = readLockfile();
|
|
2016
|
+
const entries = Object.entries(lockfile.packages);
|
|
2017
|
+
if (entries.length === 0) {
|
|
2018
|
+
p12.log.info('No packages installed. Select "Install a package" to get started.');
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
2021
|
+
const lines = entries.map(
|
|
2022
|
+
([name, entry]) => `${name} (${entry.type} v${entry.version}) -> ${entry.installed_to}`
|
|
2023
|
+
);
|
|
2024
|
+
p12.note(lines.join("\n"), "Installed packages");
|
|
2025
|
+
}
|
|
2026
|
+
function doctorFlow() {
|
|
2027
|
+
const result = runDoctor();
|
|
2028
|
+
if (result.issues.length === 0) {
|
|
2029
|
+
p12.log.success(`Checked ${result.packagesChecked} package(s) \u2014 no issues found.`);
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
const errors = result.issues.filter((i) => i.severity === "error");
|
|
2033
|
+
const warnings = result.issues.filter((i) => i.severity === "warning");
|
|
2034
|
+
for (const issue of errors) {
|
|
2035
|
+
p12.log.error(issue.message);
|
|
2036
|
+
}
|
|
2037
|
+
for (const issue of warnings) {
|
|
2038
|
+
p12.log.warn(issue.message);
|
|
2039
|
+
}
|
|
2040
|
+
if (errors.length > 0) {
|
|
2041
|
+
p12.log.error(`${errors.length} error(s), ${warnings.length} warning(s)`);
|
|
2042
|
+
} else {
|
|
2043
|
+
p12.log.warn(`${warnings.length} warning(s)`);
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
var CATEGORIES2;
|
|
2047
|
+
var init_interactive = __esm({
|
|
2048
|
+
"src/commands/interactive.ts"() {
|
|
2049
|
+
"use strict";
|
|
2050
|
+
init_prompts();
|
|
2051
|
+
init_registry();
|
|
2052
|
+
init_installer();
|
|
2053
|
+
init_lockfile();
|
|
2054
|
+
init_doctor();
|
|
2055
|
+
init_context();
|
|
2056
|
+
CATEGORIES2 = [
|
|
2057
|
+
"frontend",
|
|
2058
|
+
"backend",
|
|
2059
|
+
"devops",
|
|
2060
|
+
"database",
|
|
2061
|
+
"testing",
|
|
2062
|
+
"mobile",
|
|
2063
|
+
"ai-ml",
|
|
2064
|
+
"design",
|
|
2065
|
+
"security",
|
|
2066
|
+
"other"
|
|
2067
|
+
];
|
|
2068
|
+
}
|
|
2069
|
+
});
|
|
2070
|
+
|
|
2071
|
+
// src/index.ts
|
|
2072
|
+
import { Command as Command17 } from "commander";
|
|
2073
|
+
|
|
2074
|
+
// src/commands/install.ts
|
|
2075
|
+
init_installer();
|
|
2076
|
+
init_logger();
|
|
2077
|
+
init_prompts();
|
|
2078
|
+
import { Command } from "commander";
|
|
2079
|
+
import * as p2 from "@clack/prompts";
|
|
2080
|
+
function parseVariables(pairs) {
|
|
2081
|
+
const vars = {};
|
|
2082
|
+
for (const pair of pairs) {
|
|
2083
|
+
const eq = pair.indexOf("=");
|
|
2084
|
+
if (eq === -1) {
|
|
2085
|
+
throw new Error(`Invalid variable format: "${pair}". Use --set key=value`);
|
|
2086
|
+
}
|
|
2087
|
+
vars[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
2088
|
+
}
|
|
2089
|
+
return vars;
|
|
2090
|
+
}
|
|
2091
|
+
var installCommand = new Command("install").description("Install a package into the current project").argument("<package>", "Package name (e.g., nextjs-tailwind-starter)").option("-v, --version <version>", "Install specific version").option("--rule", "Force install as a rule to .claude/rules/").option("--no-input", "Fail if any required variable is missing").option("--set <key=value...>", "Set template variables (e.g., --set project_name=myapp)").action(
|
|
2092
|
+
async (packageName, options) => {
|
|
2093
|
+
try {
|
|
2094
|
+
const interactive = isInteractive() && options.input !== false;
|
|
2095
|
+
const variables = options.set ? parseVariables(options.set) : void 0;
|
|
2096
|
+
if (interactive) {
|
|
2097
|
+
p2.intro(`Installing ${packageName}`);
|
|
2098
|
+
} else {
|
|
2099
|
+
logger.blank();
|
|
2100
|
+
}
|
|
2101
|
+
await installPackage(packageName, {
|
|
2102
|
+
version: options.version,
|
|
2103
|
+
forceRule: options.rule,
|
|
2104
|
+
noInput: options.input === false,
|
|
2105
|
+
variables,
|
|
2106
|
+
interactive
|
|
2107
|
+
});
|
|
2108
|
+
if (interactive) {
|
|
2109
|
+
p2.outro("Done!");
|
|
2110
|
+
} else {
|
|
2111
|
+
logger.blank();
|
|
2112
|
+
}
|
|
2113
|
+
} catch (err) {
|
|
2114
|
+
logger.error(err.message);
|
|
2115
|
+
process.exit(1);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
);
|
|
2119
|
+
|
|
2120
|
+
// src/commands/uninstall.ts
|
|
2121
|
+
init_installer();
|
|
2122
|
+
init_logger();
|
|
2123
|
+
import { Command as Command2 } from "commander";
|
|
2124
|
+
var uninstallCommand = new Command2("uninstall").description("Remove an installed package").argument("<package>", "Package name").action(async (packageName) => {
|
|
2125
|
+
try {
|
|
2126
|
+
logger.blank();
|
|
2127
|
+
await uninstallPackage(packageName);
|
|
2128
|
+
logger.blank();
|
|
2129
|
+
} catch (err) {
|
|
2130
|
+
logger.error(err.message);
|
|
2131
|
+
process.exit(1);
|
|
2132
|
+
}
|
|
2133
|
+
});
|
|
2134
|
+
|
|
2135
|
+
// src/commands/search.ts
|
|
2136
|
+
init_registry();
|
|
2137
|
+
init_installer();
|
|
2138
|
+
init_logger();
|
|
2139
|
+
init_prompts();
|
|
2140
|
+
import { Command as Command3 } from "commander";
|
|
2141
|
+
import * as p3 from "@clack/prompts";
|
|
2142
|
+
var searchCommand = new Command3("search").description("Search the registry for packages").argument("<query>", "Search query").option("--type <type>", "Filter by type (prompt, rule, plan)").option("--category <category>", "Filter by category").option("--json", "Output as JSON").action(async (query, options) => {
|
|
2143
|
+
try {
|
|
2144
|
+
const results = await withSpinner(
|
|
2145
|
+
"Searching registry...",
|
|
2146
|
+
() => searchPackages(query, {
|
|
2147
|
+
type: options.type,
|
|
2148
|
+
category: options.category
|
|
2149
|
+
})
|
|
2150
|
+
);
|
|
2151
|
+
if (results.length === 0) {
|
|
2152
|
+
if (isInteractive() && !options.json) {
|
|
2153
|
+
p3.log.warn("No packages found matching your query.");
|
|
2154
|
+
} else {
|
|
2155
|
+
logger.info("No packages found matching your query.");
|
|
2156
|
+
}
|
|
902
2157
|
return;
|
|
903
2158
|
}
|
|
904
2159
|
if (options.json) {
|
|
905
2160
|
console.log(JSON.stringify(results, null, 2));
|
|
906
2161
|
return;
|
|
907
2162
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
pkg
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
2163
|
+
if (!isInteractive()) {
|
|
2164
|
+
logger.blank();
|
|
2165
|
+
logger.table(
|
|
2166
|
+
["name", "type", "version", "description"],
|
|
2167
|
+
results.map((pkg) => [
|
|
2168
|
+
pkg.name,
|
|
2169
|
+
pkg.type,
|
|
2170
|
+
pkg.version,
|
|
2171
|
+
pkg.description.length > 50 ? pkg.description.slice(0, 50) + "..." : pkg.description
|
|
2172
|
+
])
|
|
2173
|
+
);
|
|
2174
|
+
logger.blank();
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
const selected = handleCancel(
|
|
2178
|
+
await p3.select({
|
|
2179
|
+
message: `Found ${results.length} package(s). Select one:`,
|
|
2180
|
+
options: [
|
|
2181
|
+
...results.map((pkg) => ({
|
|
2182
|
+
value: pkg.name,
|
|
2183
|
+
label: `${pkg.name} (${pkg.type} v${pkg.version})`,
|
|
2184
|
+
hint: pkg.description.length > 60 ? pkg.description.slice(0, 60) + "..." : pkg.description
|
|
2185
|
+
})),
|
|
2186
|
+
{ value: "__none__", label: "Cancel" }
|
|
2187
|
+
]
|
|
2188
|
+
})
|
|
917
2189
|
);
|
|
918
|
-
|
|
2190
|
+
if (selected === "__none__") return;
|
|
2191
|
+
const action = handleCancel(
|
|
2192
|
+
await p3.select({
|
|
2193
|
+
message: `${selected}:`,
|
|
2194
|
+
options: [
|
|
2195
|
+
{ value: "install", label: "Install" },
|
|
2196
|
+
{ value: "details", label: "View details" },
|
|
2197
|
+
{ value: "back", label: "Cancel" }
|
|
2198
|
+
]
|
|
2199
|
+
})
|
|
2200
|
+
);
|
|
2201
|
+
if (action === "install") {
|
|
2202
|
+
try {
|
|
2203
|
+
await installPackage(selected, { interactive: true });
|
|
2204
|
+
p3.log.success(`Installed ${selected}`);
|
|
2205
|
+
} catch (err) {
|
|
2206
|
+
p3.log.error(err.message);
|
|
2207
|
+
}
|
|
2208
|
+
} else if (action === "details") {
|
|
2209
|
+
const meta = await withSpinner(
|
|
2210
|
+
"Fetching package details...",
|
|
2211
|
+
() => fetchPackageMetadata(selected)
|
|
2212
|
+
);
|
|
2213
|
+
const lines = [
|
|
2214
|
+
`Type: ${meta.type}`,
|
|
2215
|
+
`Author: ${meta.author}`,
|
|
2216
|
+
`License: ${meta.license}`,
|
|
2217
|
+
`Category: ${meta.category}`,
|
|
2218
|
+
`Downloads: ${meta.downloads.toLocaleString()}`,
|
|
2219
|
+
`Versions: ${meta.versions.join(", ")}`,
|
|
2220
|
+
`Repository: ${meta.repository}`
|
|
2221
|
+
];
|
|
2222
|
+
if (meta.tags?.length) {
|
|
2223
|
+
lines.push(`Tags: ${meta.tags.join(", ")}`);
|
|
2224
|
+
}
|
|
2225
|
+
p3.note(lines.join("\n"), `${meta.name}@${meta.latest_version}`);
|
|
2226
|
+
}
|
|
919
2227
|
} catch (err) {
|
|
920
2228
|
logger.error(err.message);
|
|
921
2229
|
process.exit(1);
|
|
@@ -923,6 +2231,10 @@ var searchCommand = new Command3("search").description("Search the registry for
|
|
|
923
2231
|
});
|
|
924
2232
|
|
|
925
2233
|
// src/commands/run.ts
|
|
2234
|
+
init_manifest();
|
|
2235
|
+
init_template();
|
|
2236
|
+
init_logger();
|
|
2237
|
+
init_prompts();
|
|
926
2238
|
import { Command as Command4 } from "commander";
|
|
927
2239
|
import fs8 from "fs";
|
|
928
2240
|
import path8 from "path";
|
|
@@ -957,18 +2269,8 @@ var runCommand = new Command4("run").description("Run a templated prompt and out
|
|
|
957
2269
|
process.exit(1);
|
|
958
2270
|
}
|
|
959
2271
|
if (manifest?.variables && Object.keys(manifest.variables).length > 0) {
|
|
960
|
-
const
|
|
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
|
-
}
|
|
2272
|
+
const noInput = options.input === false;
|
|
2273
|
+
const values = await promptForVariables(manifest.variables, vars, noInput);
|
|
972
2274
|
for (const [name, def] of Object.entries(manifest.variables)) {
|
|
973
2275
|
if (def.type !== "resolved") continue;
|
|
974
2276
|
values[name] = await resolveVariable(def, values);
|
|
@@ -988,143 +2290,161 @@ var runCommand = new Command4("run").description("Run a templated prompt and out
|
|
|
988
2290
|
|
|
989
2291
|
// src/commands/publish.ts
|
|
990
2292
|
import { Command as Command5 } from "commander";
|
|
2293
|
+
import * as p4 from "@clack/prompts";
|
|
991
2294
|
|
|
992
2295
|
// src/lib/publisher.ts
|
|
2296
|
+
init_manifest();
|
|
2297
|
+
init_config();
|
|
2298
|
+
init_git();
|
|
2299
|
+
init_logger();
|
|
2300
|
+
init_prompts();
|
|
993
2301
|
async function publishPackage(options = {}) {
|
|
994
2302
|
const cwd = options.projectDir ?? process.cwd();
|
|
2303
|
+
const interactive = options.interactive ?? false;
|
|
995
2304
|
const token = options.token ?? getGitHubToken();
|
|
996
2305
|
if (!token) {
|
|
997
2306
|
throw new Error("Not authenticated. Run `planmode login` first.");
|
|
998
2307
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
2308
|
+
const doValidate = async () => {
|
|
2309
|
+
const manifest2 = readManifest(cwd);
|
|
2310
|
+
const errors = validateManifest(manifest2, true);
|
|
2311
|
+
if (errors.length > 0) {
|
|
2312
|
+
throw new Error(`Invalid manifest:
|
|
1004
2313
|
${errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
1005
|
-
|
|
2314
|
+
}
|
|
2315
|
+
return manifest2;
|
|
2316
|
+
};
|
|
2317
|
+
const manifest = interactive ? await withSpinner("Validating manifest...", doValidate, "Manifest valid") : await (async () => {
|
|
2318
|
+
logger.info("Reading planmode.yaml...");
|
|
2319
|
+
return doValidate();
|
|
2320
|
+
})();
|
|
1006
2321
|
const remoteUrl = await getRemoteUrl(cwd);
|
|
1007
2322
|
if (!remoteUrl) {
|
|
1008
2323
|
throw new Error("No git remote found. Push your code to GitHub first.");
|
|
1009
2324
|
}
|
|
1010
2325
|
const sha = await getHeadSha(cwd);
|
|
1011
2326
|
const tag = `v${manifest.version}`;
|
|
1012
|
-
|
|
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"
|
|
2327
|
+
const doTag = async () => {
|
|
2328
|
+
try {
|
|
2329
|
+
await createTag(cwd, tag);
|
|
2330
|
+
} catch {
|
|
2331
|
+
}
|
|
2332
|
+
try {
|
|
2333
|
+
await pushTag(cwd, tag);
|
|
2334
|
+
} catch {
|
|
2335
|
+
}
|
|
1030
2336
|
};
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
throw new Error("Failed to authenticate with GitHub. Check your token.");
|
|
2337
|
+
if (interactive) {
|
|
2338
|
+
await withSpinner(`Creating tag ${tag}...`, doTag, `Tag ${tag} ready`);
|
|
2339
|
+
} else {
|
|
2340
|
+
logger.info(`Creating tag ${tag}...`);
|
|
2341
|
+
await doTag();
|
|
2342
|
+
logger.success(`Pushed tag ${tag}`);
|
|
1038
2343
|
}
|
|
1039
|
-
const
|
|
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: {
|
|
2344
|
+
const doSubmit = async () => {
|
|
2345
|
+
const headers = {
|
|
2346
|
+
Authorization: `Bearer ${token}`,
|
|
2347
|
+
Accept: "application/vnd.github.v3+json",
|
|
2348
|
+
"User-Agent": "planmode-cli",
|
|
2349
|
+
"Content-Type": "application/json"
|
|
2350
|
+
};
|
|
2351
|
+
await fetch("https://api.github.com/repos/kaihannonen/planmode.org/forks", {
|
|
2352
|
+
method: "POST",
|
|
2353
|
+
headers
|
|
2354
|
+
});
|
|
2355
|
+
const userRes = await fetch("https://api.github.com/user", { headers });
|
|
2356
|
+
if (!userRes.ok) {
|
|
2357
|
+
throw new Error("Failed to authenticate with GitHub. Check your token.");
|
|
2358
|
+
}
|
|
2359
|
+
const user = await userRes.json();
|
|
2360
|
+
const repoPath = remoteUrl.replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
2361
|
+
const metadataContent = JSON.stringify(
|
|
2362
|
+
{
|
|
2363
|
+
name: manifest.name,
|
|
2364
|
+
description: manifest.description,
|
|
2365
|
+
author: manifest.author,
|
|
2366
|
+
license: manifest.license,
|
|
1068
2367
|
repository: repoPath,
|
|
1069
|
-
|
|
1070
|
-
|
|
2368
|
+
category: manifest.category ?? "other",
|
|
2369
|
+
tags: manifest.tags ?? [],
|
|
2370
|
+
type: manifest.type,
|
|
2371
|
+
models: manifest.models ?? [],
|
|
2372
|
+
latest_version: manifest.version,
|
|
2373
|
+
versions: [manifest.version],
|
|
2374
|
+
downloads: 0,
|
|
2375
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2376
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2377
|
+
dependencies: manifest.dependencies,
|
|
2378
|
+
variables: manifest.variables
|
|
1071
2379
|
},
|
|
1072
|
-
|
|
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
|
-
|
|
2380
|
+
null,
|
|
2381
|
+
2
|
|
2382
|
+
);
|
|
2383
|
+
const versionContent = JSON.stringify(
|
|
2384
|
+
{
|
|
2385
|
+
version: manifest.version,
|
|
2386
|
+
published_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2387
|
+
source: {
|
|
2388
|
+
repository: repoPath,
|
|
2389
|
+
tag,
|
|
2390
|
+
sha
|
|
2391
|
+
},
|
|
2392
|
+
files: ["planmode.yaml", manifest.content_file ?? "inline"],
|
|
2393
|
+
content_hash: `sha256:${sha.slice(0, 16)}`
|
|
2394
|
+
},
|
|
2395
|
+
null,
|
|
2396
|
+
2
|
|
2397
|
+
);
|
|
2398
|
+
const branchName = `add-${manifest.name}-${manifest.version}`;
|
|
2399
|
+
const refRes = await fetch(
|
|
2400
|
+
`https://api.github.com/repos/${user.login}/planmode.org/git/ref/heads/main`,
|
|
2401
|
+
{ headers }
|
|
2402
|
+
);
|
|
2403
|
+
if (!refRes.ok) {
|
|
2404
|
+
throw new Error("Failed to access registry fork. Make sure the fork exists.");
|
|
2405
|
+
}
|
|
2406
|
+
const refData = await refRes.json();
|
|
2407
|
+
const baseSha = refData.object.sha;
|
|
2408
|
+
await fetch(`https://api.github.com/repos/${user.login}/planmode.org/git/refs`, {
|
|
2409
|
+
method: "POST",
|
|
1100
2410
|
headers,
|
|
1101
2411
|
body: JSON.stringify({
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
branch: branchName
|
|
2412
|
+
ref: `refs/heads/${branchName}`,
|
|
2413
|
+
sha: baseSha
|
|
1105
2414
|
})
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
2415
|
+
});
|
|
2416
|
+
await fetch(
|
|
2417
|
+
`https://api.github.com/repos/${user.login}/planmode.org/contents/registry/packages/${manifest.name}/metadata.json`,
|
|
2418
|
+
{
|
|
2419
|
+
method: "PUT",
|
|
2420
|
+
headers,
|
|
2421
|
+
body: JSON.stringify({
|
|
2422
|
+
message: `Add ${manifest.name}@${manifest.version}`,
|
|
2423
|
+
content: Buffer.from(metadataContent).toString("base64"),
|
|
2424
|
+
branch: branchName
|
|
2425
|
+
})
|
|
2426
|
+
}
|
|
2427
|
+
);
|
|
2428
|
+
await fetch(
|
|
2429
|
+
`https://api.github.com/repos/${user.login}/planmode.org/contents/registry/packages/${manifest.name}/versions/${manifest.version}.json`,
|
|
2430
|
+
{
|
|
2431
|
+
method: "PUT",
|
|
2432
|
+
headers,
|
|
2433
|
+
body: JSON.stringify({
|
|
2434
|
+
message: `Add ${manifest.name}@${manifest.version} version metadata`,
|
|
2435
|
+
content: Buffer.from(versionContent).toString("base64"),
|
|
2436
|
+
branch: branchName
|
|
2437
|
+
})
|
|
2438
|
+
}
|
|
2439
|
+
);
|
|
2440
|
+
const prRes = await fetch("https://api.github.com/repos/kaihannonen/planmode.org/pulls", {
|
|
2441
|
+
method: "POST",
|
|
1112
2442
|
headers,
|
|
1113
2443
|
body: JSON.stringify({
|
|
1114
|
-
|
|
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}
|
|
2444
|
+
title: `Add ${manifest.name}@${manifest.version}`,
|
|
2445
|
+
head: `${user.login}:${branchName}`,
|
|
2446
|
+
base: "main",
|
|
2447
|
+
body: `## New package: ${manifest.name}
|
|
1128
2448
|
|
|
1129
2449
|
- **Type:** ${manifest.type}
|
|
1130
2450
|
- **Version:** ${manifest.version}
|
|
@@ -1132,28 +2452,51 @@ ${errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
|
1132
2452
|
- **Author:** ${manifest.author}
|
|
1133
2453
|
|
|
1134
2454
|
Submitted via \`planmode publish\`.`
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
2455
|
+
})
|
|
2456
|
+
});
|
|
2457
|
+
if (!prRes.ok) {
|
|
2458
|
+
const err = await prRes.text();
|
|
2459
|
+
throw new Error(`Failed to create PR: ${err}`);
|
|
2460
|
+
}
|
|
2461
|
+
const pr = await prRes.json();
|
|
2462
|
+
return pr.html_url;
|
|
2463
|
+
};
|
|
2464
|
+
let prUrl;
|
|
2465
|
+
if (interactive) {
|
|
2466
|
+
prUrl = await withSpinner(
|
|
2467
|
+
"Submitting to registry...",
|
|
2468
|
+
doSubmit,
|
|
2469
|
+
"Submitted to registry"
|
|
2470
|
+
);
|
|
2471
|
+
} else {
|
|
2472
|
+
logger.info("Submitting to registry...");
|
|
2473
|
+
prUrl = await doSubmit();
|
|
2474
|
+
logger.success(`Published ${manifest.name}@${manifest.version}`);
|
|
2475
|
+
logger.info(`PR: ${prUrl}`);
|
|
1140
2476
|
}
|
|
1141
|
-
const pr = await prRes.json();
|
|
1142
|
-
logger.success(`Published ${manifest.name}@${manifest.version}`);
|
|
1143
|
-
logger.info(`PR: ${pr.html_url}`);
|
|
1144
2477
|
return {
|
|
1145
|
-
prUrl
|
|
2478
|
+
prUrl,
|
|
1146
2479
|
packageName: manifest.name,
|
|
1147
2480
|
version: manifest.version
|
|
1148
2481
|
};
|
|
1149
2482
|
}
|
|
1150
2483
|
|
|
1151
2484
|
// src/commands/publish.ts
|
|
2485
|
+
init_logger();
|
|
2486
|
+
init_prompts();
|
|
1152
2487
|
var publishCommand = new Command5("publish").description("Publish the current directory as a package to the registry").action(async () => {
|
|
1153
2488
|
try {
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
2489
|
+
if (isInteractive()) {
|
|
2490
|
+
p4.intro("Publishing package");
|
|
2491
|
+
} else {
|
|
2492
|
+
logger.blank();
|
|
2493
|
+
}
|
|
2494
|
+
const result = await publishPackage({ interactive: isInteractive() });
|
|
2495
|
+
if (isInteractive()) {
|
|
2496
|
+
p4.outro(`Published ${result.packageName}@${result.version} \u2014 PR: ${result.prUrl}`);
|
|
2497
|
+
} else {
|
|
2498
|
+
logger.blank();
|
|
2499
|
+
}
|
|
1157
2500
|
} catch (err) {
|
|
1158
2501
|
logger.error(err.message);
|
|
1159
2502
|
process.exit(1);
|
|
@@ -1161,38 +2504,76 @@ var publishCommand = new Command5("publish").description("Publish the current di
|
|
|
1161
2504
|
});
|
|
1162
2505
|
|
|
1163
2506
|
// src/commands/update.ts
|
|
2507
|
+
init_installer();
|
|
2508
|
+
init_lockfile();
|
|
2509
|
+
init_logger();
|
|
2510
|
+
init_prompts();
|
|
1164
2511
|
import { Command as Command6 } from "commander";
|
|
2512
|
+
import * as p5 from "@clack/prompts";
|
|
1165
2513
|
var updateCommand = new Command6("update").description("Update installed packages to latest compatible versions").argument("[package]", "Package name (omit to update all)").action(async (packageName) => {
|
|
1166
2514
|
try {
|
|
1167
|
-
|
|
2515
|
+
const interactive = isInteractive();
|
|
2516
|
+
if (interactive) {
|
|
2517
|
+
p5.intro("Updating packages");
|
|
2518
|
+
} else {
|
|
2519
|
+
logger.blank();
|
|
2520
|
+
}
|
|
1168
2521
|
if (packageName) {
|
|
1169
|
-
const updated = await
|
|
2522
|
+
const updated = interactive ? await withSpinner(
|
|
2523
|
+
`Checking ${packageName} for updates...`,
|
|
2524
|
+
() => updatePackage(packageName)
|
|
2525
|
+
) : await updatePackage(packageName);
|
|
1170
2526
|
if (!updated) {
|
|
1171
|
-
|
|
2527
|
+
if (interactive) {
|
|
2528
|
+
p5.log.info("Already up to date.");
|
|
2529
|
+
} else {
|
|
2530
|
+
logger.info("Already up to date.");
|
|
2531
|
+
}
|
|
1172
2532
|
}
|
|
1173
2533
|
} else {
|
|
1174
2534
|
const lockfile = readLockfile();
|
|
1175
2535
|
const names = Object.keys(lockfile.packages);
|
|
1176
2536
|
if (names.length === 0) {
|
|
1177
|
-
|
|
2537
|
+
if (interactive) {
|
|
2538
|
+
p5.log.info("No packages installed.");
|
|
2539
|
+
} else {
|
|
2540
|
+
logger.info("No packages installed.");
|
|
2541
|
+
}
|
|
2542
|
+
if (interactive) p5.outro("Nothing to update.");
|
|
1178
2543
|
return;
|
|
1179
2544
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
2545
|
+
const doUpdate = async () => {
|
|
2546
|
+
let updatedCount2 = 0;
|
|
2547
|
+
for (const name of names) {
|
|
2548
|
+
try {
|
|
2549
|
+
const updated = await updatePackage(name);
|
|
2550
|
+
if (updated) updatedCount2++;
|
|
2551
|
+
} catch (err) {
|
|
2552
|
+
logger.warn(`Failed to update ${name}: ${err.message}`);
|
|
2553
|
+
}
|
|
1187
2554
|
}
|
|
1188
|
-
|
|
2555
|
+
return updatedCount2;
|
|
2556
|
+
};
|
|
2557
|
+
const updatedCount = interactive ? await withSpinner("Checking for updates...", doUpdate) : await doUpdate();
|
|
1189
2558
|
if (updatedCount === 0) {
|
|
1190
|
-
|
|
2559
|
+
if (interactive) {
|
|
2560
|
+
p5.log.info("All packages are up to date.");
|
|
2561
|
+
} else {
|
|
2562
|
+
logger.info("All packages are up to date.");
|
|
2563
|
+
}
|
|
1191
2564
|
} else {
|
|
1192
|
-
|
|
2565
|
+
if (interactive) {
|
|
2566
|
+
p5.log.success(`Updated ${updatedCount} package${updatedCount > 1 ? "s" : ""}.`);
|
|
2567
|
+
} else {
|
|
2568
|
+
logger.success(`Updated ${updatedCount} package${updatedCount > 1 ? "s" : ""}.`);
|
|
2569
|
+
}
|
|
1193
2570
|
}
|
|
1194
2571
|
}
|
|
1195
|
-
|
|
2572
|
+
if (interactive) {
|
|
2573
|
+
p5.outro("Done!");
|
|
2574
|
+
} else {
|
|
2575
|
+
logger.blank();
|
|
2576
|
+
}
|
|
1196
2577
|
} catch (err) {
|
|
1197
2578
|
logger.error(err.message);
|
|
1198
2579
|
process.exit(1);
|
|
@@ -1200,6 +2581,8 @@ var updateCommand = new Command6("update").description("Update installed package
|
|
|
1200
2581
|
});
|
|
1201
2582
|
|
|
1202
2583
|
// src/commands/list.ts
|
|
2584
|
+
init_lockfile();
|
|
2585
|
+
init_logger();
|
|
1203
2586
|
import { Command as Command7 } from "commander";
|
|
1204
2587
|
var listCommand = new Command7("list").description("List all installed packages").action(() => {
|
|
1205
2588
|
const lockfile = readLockfile();
|
|
@@ -1222,6 +2605,8 @@ var listCommand = new Command7("list").description("List all installed packages"
|
|
|
1222
2605
|
});
|
|
1223
2606
|
|
|
1224
2607
|
// src/commands/info.ts
|
|
2608
|
+
init_registry();
|
|
2609
|
+
init_logger();
|
|
1225
2610
|
import { Command as Command8 } from "commander";
|
|
1226
2611
|
var infoCommand = new Command8("info").description("Show detailed info about a package").argument("<package>", "Package name").action(async (packageName) => {
|
|
1227
2612
|
try {
|
|
@@ -1236,194 +2621,33 @@ var infoCommand = new Command8("info").description("Show detailed info about a p
|
|
|
1236
2621
|
console.log(` Category: ${meta.category}`);
|
|
1237
2622
|
console.log(` Downloads: ${meta.downloads.toLocaleString()}`);
|
|
1238
2623
|
console.log(` Repository: ${meta.repository}`);
|
|
1239
|
-
if (meta.models && meta.models.length > 0) {
|
|
1240
|
-
console.log(` Models: ${meta.models.join(", ")}`);
|
|
1241
|
-
}
|
|
1242
|
-
if (meta.tags && meta.tags.length > 0) {
|
|
1243
|
-
console.log(` Tags: ${meta.tags.join(", ")}`);
|
|
1244
|
-
}
|
|
1245
|
-
console.log(` Versions: ${meta.versions.join(", ")}`);
|
|
1246
|
-
if (meta.dependencies) {
|
|
1247
|
-
if (meta.dependencies.rules && meta.dependencies.rules.length > 0) {
|
|
1248
|
-
console.log(` Dep (rules): ${meta.dependencies.rules.join(", ")}`);
|
|
1249
|
-
}
|
|
1250
|
-
if (meta.dependencies.plans && meta.dependencies.plans.length > 0) {
|
|
1251
|
-
console.log(` Dep (plans): ${meta.dependencies.plans.join(", ")}`);
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
if (meta.variables) {
|
|
1255
|
-
logger.blank();
|
|
1256
|
-
logger.bold(" Variables:");
|
|
1257
|
-
for (const [name, def] of Object.entries(meta.variables)) {
|
|
1258
|
-
const required = def.required ? " (required)" : "";
|
|
1259
|
-
const defaultVal = def.default !== void 0 ? ` [default: ${def.default}]` : "";
|
|
1260
|
-
console.log(` ${name}: ${def.type}${required}${defaultVal} \u2014 ${def.description}`);
|
|
1261
|
-
if (def.options) {
|
|
1262
|
-
console.log(` options: ${def.options.join(", ")}`);
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
logger.blank();
|
|
1267
|
-
} catch (err) {
|
|
1268
|
-
logger.error(err.message);
|
|
1269
|
-
process.exit(1);
|
|
1270
|
-
}
|
|
1271
|
-
});
|
|
1272
|
-
|
|
1273
|
-
// src/commands/init.ts
|
|
1274
|
-
import { Command as Command9 } from "commander";
|
|
1275
|
-
|
|
1276
|
-
// src/lib/init.ts
|
|
1277
|
-
import fs9 from "fs";
|
|
1278
|
-
import path9 from "path";
|
|
1279
|
-
import { stringify as stringify3 } from "yaml";
|
|
1280
|
-
|
|
1281
|
-
// src/lib/templates.ts
|
|
1282
|
-
function getPlanTemplate(name) {
|
|
1283
|
-
return `# ${name}
|
|
1284
|
-
|
|
1285
|
-
## Prerequisites
|
|
1286
|
-
|
|
1287
|
-
- List any tools, dependencies, or setup required before starting
|
|
1288
|
-
|
|
1289
|
-
## Steps
|
|
1290
|
-
|
|
1291
|
-
1. **Step one** \u2014 Description of what to do first
|
|
1292
|
-
2. **Step two** \u2014 Description of what to do next
|
|
1293
|
-
3. **Step three** \u2014 Description of the final step
|
|
1294
|
-
|
|
1295
|
-
## Verification
|
|
1296
|
-
|
|
1297
|
-
- [ ] Verify step one completed successfully
|
|
1298
|
-
- [ ] Verify step two completed successfully
|
|
1299
|
-
- [ ] Verify the final result works as expected
|
|
1300
|
-
`;
|
|
1301
|
-
}
|
|
1302
|
-
function getRuleTemplate(name) {
|
|
1303
|
-
return `# ${name}
|
|
1304
|
-
|
|
1305
|
-
## Code Style
|
|
1306
|
-
|
|
1307
|
-
- Follow consistent naming conventions
|
|
1308
|
-
- Keep functions small and focused
|
|
1309
|
-
|
|
1310
|
-
## Best Practices
|
|
1311
|
-
|
|
1312
|
-
- Prefer composition over inheritance
|
|
1313
|
-
- Write self-documenting code
|
|
1314
|
-
|
|
1315
|
-
## Avoid
|
|
1316
|
-
|
|
1317
|
-
- Do not use deprecated APIs
|
|
1318
|
-
- Do not ignore error handling
|
|
1319
|
-
`;
|
|
1320
|
-
}
|
|
1321
|
-
function getPromptTemplate(name) {
|
|
1322
|
-
return `# ${name}
|
|
1323
|
-
|
|
1324
|
-
{{description}}
|
|
1325
|
-
|
|
1326
|
-
## Context
|
|
1327
|
-
|
|
1328
|
-
Provide any relevant context here.
|
|
1329
|
-
|
|
1330
|
-
## Requirements
|
|
1331
|
-
|
|
1332
|
-
- Requirement one
|
|
1333
|
-
- Requirement two
|
|
1334
|
-
|
|
1335
|
-
## Output Format
|
|
1336
|
-
|
|
1337
|
-
Describe the expected output format.
|
|
1338
|
-
`;
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
// src/lib/init.ts
|
|
1342
|
-
function createPackage(options) {
|
|
1343
|
-
const {
|
|
1344
|
-
name,
|
|
1345
|
-
type,
|
|
1346
|
-
description,
|
|
1347
|
-
author,
|
|
1348
|
-
license = "MIT",
|
|
1349
|
-
tags = [],
|
|
1350
|
-
category = "other",
|
|
1351
|
-
projectDir = process.cwd()
|
|
1352
|
-
} = options;
|
|
1353
|
-
const manifest = {
|
|
1354
|
-
name,
|
|
1355
|
-
version: "1.0.0",
|
|
1356
|
-
type,
|
|
1357
|
-
description,
|
|
1358
|
-
author,
|
|
1359
|
-
license
|
|
1360
|
-
};
|
|
1361
|
-
if (tags.length > 0) manifest["tags"] = tags;
|
|
1362
|
-
manifest["category"] = category;
|
|
1363
|
-
const contentFile = `${type}.md`;
|
|
1364
|
-
manifest["content_file"] = contentFile;
|
|
1365
|
-
const yamlContent = stringify3(manifest);
|
|
1366
|
-
const manifestPath = path9.join(projectDir, "planmode.yaml");
|
|
1367
|
-
fs9.writeFileSync(manifestPath, yamlContent, "utf-8");
|
|
1368
|
-
const stubs = {
|
|
1369
|
-
plan: getPlanTemplate(name),
|
|
1370
|
-
rule: getRuleTemplate(name),
|
|
1371
|
-
prompt: getPromptTemplate(name)
|
|
1372
|
-
};
|
|
1373
|
-
const contentPath = path9.join(projectDir, contentFile);
|
|
1374
|
-
fs9.writeFileSync(contentPath, stubs[type] ?? stubs["plan"], "utf-8");
|
|
1375
|
-
return {
|
|
1376
|
-
files: ["planmode.yaml", contentFile],
|
|
1377
|
-
manifestPath,
|
|
1378
|
-
contentPath
|
|
1379
|
-
};
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
// src/commands/init.ts
|
|
1383
|
-
async function prompt(question) {
|
|
1384
|
-
const { createInterface } = await import("readline");
|
|
1385
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1386
|
-
return new Promise((resolve) => {
|
|
1387
|
-
rl.question(question, (answer) => {
|
|
1388
|
-
rl.close();
|
|
1389
|
-
resolve(answer.trim());
|
|
1390
|
-
});
|
|
1391
|
-
});
|
|
1392
|
-
}
|
|
1393
|
-
var initCommand = new Command9("init").description("Initialize a new package in the current directory").action(async () => {
|
|
1394
|
-
try {
|
|
1395
|
-
logger.blank();
|
|
1396
|
-
logger.bold("Initialize a new Planmode package");
|
|
1397
|
-
logger.blank();
|
|
1398
|
-
const name = await prompt("Package name: ");
|
|
1399
|
-
if (!name) {
|
|
1400
|
-
logger.error("Package name is required.");
|
|
1401
|
-
process.exit(1);
|
|
2624
|
+
if (meta.models && meta.models.length > 0) {
|
|
2625
|
+
console.log(` Models: ${meta.models.join(", ")}`);
|
|
2626
|
+
}
|
|
2627
|
+
if (meta.tags && meta.tags.length > 0) {
|
|
2628
|
+
console.log(` Tags: ${meta.tags.join(", ")}`);
|
|
2629
|
+
}
|
|
2630
|
+
console.log(` Versions: ${meta.versions.join(", ")}`);
|
|
2631
|
+
if (meta.dependencies) {
|
|
2632
|
+
if (meta.dependencies.rules && meta.dependencies.rules.length > 0) {
|
|
2633
|
+
console.log(` Dep (rules): ${meta.dependencies.rules.join(", ")}`);
|
|
2634
|
+
}
|
|
2635
|
+
if (meta.dependencies.plans && meta.dependencies.plans.length > 0) {
|
|
2636
|
+
console.log(` Dep (plans): ${meta.dependencies.plans.join(", ")}`);
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
if (meta.variables) {
|
|
2640
|
+
logger.blank();
|
|
2641
|
+
logger.bold(" Variables:");
|
|
2642
|
+
for (const [name, def] of Object.entries(meta.variables)) {
|
|
2643
|
+
const required = def.required ? " (required)" : "";
|
|
2644
|
+
const defaultVal = def.default !== void 0 ? ` [default: ${def.default}]` : "";
|
|
2645
|
+
console.log(` ${name}: ${def.type}${required}${defaultVal} \u2014 ${def.description}`);
|
|
2646
|
+
if (def.options) {
|
|
2647
|
+
console.log(` options: ${def.options.join(", ")}`);
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
1402
2650
|
}
|
|
1403
|
-
const typeInput = await prompt("Type (plan/rule/prompt) [plan]: ");
|
|
1404
|
-
const type = typeInput || "plan";
|
|
1405
|
-
const description = await prompt("Description: ");
|
|
1406
|
-
const author = await prompt("Author (GitHub username): ");
|
|
1407
|
-
const license = await prompt("License [MIT]: ") || "MIT";
|
|
1408
|
-
const tagsInput = await prompt("Tags (comma-separated): ");
|
|
1409
|
-
const tags = tagsInput ? tagsInput.split(",").map((t) => t.trim().toLowerCase()) : [];
|
|
1410
|
-
const category = await prompt(
|
|
1411
|
-
"Category (frontend/backend/devops/database/testing/mobile/ai-ml/security/other) [other]: "
|
|
1412
|
-
) || "other";
|
|
1413
|
-
const result = createPackage({
|
|
1414
|
-
name,
|
|
1415
|
-
type,
|
|
1416
|
-
description,
|
|
1417
|
-
author,
|
|
1418
|
-
license,
|
|
1419
|
-
tags,
|
|
1420
|
-
category
|
|
1421
|
-
});
|
|
1422
|
-
logger.success(`Created ${result.files.join(", ")}`);
|
|
1423
|
-
logger.blank();
|
|
1424
|
-
logger.info(
|
|
1425
|
-
`Edit ${result.files[1]}, then run \`planmode publish\` when ready.`
|
|
1426
|
-
);
|
|
1427
2651
|
logger.blank();
|
|
1428
2652
|
} catch (err) {
|
|
1429
2653
|
logger.error(err.message);
|
|
@@ -1431,8 +2655,15 @@ var initCommand = new Command9("init").description("Initialize a new package in
|
|
|
1431
2655
|
}
|
|
1432
2656
|
});
|
|
1433
2657
|
|
|
2658
|
+
// src/index.ts
|
|
2659
|
+
init_init2();
|
|
2660
|
+
|
|
1434
2661
|
// src/commands/login.ts
|
|
2662
|
+
init_config();
|
|
2663
|
+
init_logger();
|
|
2664
|
+
init_prompts();
|
|
1435
2665
|
import { Command as Command10 } from "commander";
|
|
2666
|
+
import * as p7 from "@clack/prompts";
|
|
1436
2667
|
import { execSync } from "child_process";
|
|
1437
2668
|
var loginCommand = new Command10("login").description("Configure GitHub authentication").option("--token <token>", "GitHub personal access token").option("--gh", "Read token from GitHub CLI (gh auth token)").action(async (options) => {
|
|
1438
2669
|
let token;
|
|
@@ -1445,37 +2676,61 @@ var loginCommand = new Command10("login").description("Configure GitHub authenti
|
|
|
1445
2676
|
logger.error("Failed to read token from GitHub CLI. Make sure `gh` is installed and authenticated.");
|
|
1446
2677
|
process.exit(1);
|
|
1447
2678
|
}
|
|
1448
|
-
} else {
|
|
1449
|
-
|
|
1450
|
-
const
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
});
|
|
2679
|
+
} else if (isInteractive()) {
|
|
2680
|
+
p7.intro("planmode login");
|
|
2681
|
+
const value = await p7.password({
|
|
2682
|
+
message: "GitHub personal access token:",
|
|
2683
|
+
validate(input) {
|
|
2684
|
+
if (!input) return "Token is required";
|
|
2685
|
+
}
|
|
1456
2686
|
});
|
|
2687
|
+
token = handleCancel(value);
|
|
2688
|
+
} else {
|
|
2689
|
+
logger.error("No token provided. Use --token <token> or --gh.");
|
|
2690
|
+
process.exit(1);
|
|
1457
2691
|
}
|
|
1458
2692
|
if (!token) {
|
|
1459
2693
|
logger.error("No token provided.");
|
|
1460
2694
|
process.exit(1);
|
|
1461
2695
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
2696
|
+
const validateToken = async () => {
|
|
2697
|
+
const response = await fetch("https://api.github.com/user", {
|
|
2698
|
+
headers: {
|
|
2699
|
+
Authorization: `Bearer ${token}`,
|
|
2700
|
+
"User-Agent": "planmode-cli"
|
|
2701
|
+
}
|
|
2702
|
+
});
|
|
2703
|
+
if (!response.ok) {
|
|
2704
|
+
throw new Error("Invalid token. GitHub API returned: " + response.status);
|
|
2705
|
+
}
|
|
2706
|
+
return await response.json();
|
|
2707
|
+
};
|
|
2708
|
+
try {
|
|
2709
|
+
const user = await withSpinner(
|
|
2710
|
+
"Validating token...",
|
|
2711
|
+
validateToken,
|
|
2712
|
+
"Token validated"
|
|
2713
|
+
);
|
|
2714
|
+
setGitHubToken(token);
|
|
2715
|
+
if (isInteractive()) {
|
|
2716
|
+
p7.log.success(`Authenticated as ${user.login}`);
|
|
2717
|
+
p7.outro("You're all set!");
|
|
2718
|
+
} else {
|
|
2719
|
+
logger.success(`Authenticated as ${user.login}`);
|
|
2720
|
+
}
|
|
2721
|
+
} catch (err) {
|
|
2722
|
+
if (isInteractive()) {
|
|
2723
|
+
p7.log.error(err.message);
|
|
2724
|
+
p7.outro("Authentication failed.");
|
|
2725
|
+
} else {
|
|
2726
|
+
logger.error(err.message);
|
|
1467
2727
|
}
|
|
1468
|
-
});
|
|
1469
|
-
if (!response.ok) {
|
|
1470
|
-
logger.error("Invalid token. GitHub API returned: " + response.status);
|
|
1471
2728
|
process.exit(1);
|
|
1472
2729
|
}
|
|
1473
|
-
const user = await response.json();
|
|
1474
|
-
setGitHubToken(token);
|
|
1475
|
-
logger.success(`Authenticated as ${user.login}`);
|
|
1476
2730
|
});
|
|
1477
2731
|
|
|
1478
2732
|
// src/commands/mcp.ts
|
|
2733
|
+
init_logger();
|
|
1479
2734
|
import { Command as Command11 } from "commander";
|
|
1480
2735
|
import { execSync as execSync2 } from "child_process";
|
|
1481
2736
|
var mcpCommand = new Command11("mcp").description("Manage MCP server registration with Claude Code");
|
|
@@ -1506,125 +2761,67 @@ mcpCommand.command("remove").description("Remove the planmode MCP server from Cl
|
|
|
1506
2761
|
});
|
|
1507
2762
|
|
|
1508
2763
|
// src/commands/doctor.ts
|
|
2764
|
+
init_doctor();
|
|
2765
|
+
init_logger();
|
|
2766
|
+
init_prompts();
|
|
1509
2767
|
import { Command as Command12 } from "commander";
|
|
1510
|
-
|
|
1511
|
-
// src/lib/doctor.ts
|
|
1512
|
-
import fs10 from "fs";
|
|
1513
|
-
import path10 from "path";
|
|
1514
|
-
import crypto2 from "crypto";
|
|
1515
|
-
function computeHash(content) {
|
|
1516
|
-
return `sha256:${crypto2.createHash("sha256").update(content).digest("hex")}`;
|
|
1517
|
-
}
|
|
1518
|
-
function runDoctor(projectDir = process.cwd()) {
|
|
1519
|
-
const issues = [];
|
|
1520
|
-
const lockfile = readLockfile(projectDir);
|
|
1521
|
-
const entries = Object.entries(lockfile.packages);
|
|
1522
|
-
for (const [name, entry] of entries) {
|
|
1523
|
-
const fullPath = path10.join(projectDir, entry.installed_to);
|
|
1524
|
-
if (!fs10.existsSync(fullPath)) {
|
|
1525
|
-
issues.push({
|
|
1526
|
-
severity: "error",
|
|
1527
|
-
message: `Missing file for "${name}": ${entry.installed_to}`,
|
|
1528
|
-
fix: `Run \`planmode install ${name}\` to reinstall`
|
|
1529
|
-
});
|
|
1530
|
-
continue;
|
|
1531
|
-
}
|
|
1532
|
-
const content = fs10.readFileSync(fullPath, "utf-8");
|
|
1533
|
-
const actualHash = computeHash(content);
|
|
1534
|
-
if (actualHash !== entry.content_hash) {
|
|
1535
|
-
issues.push({
|
|
1536
|
-
severity: "warning",
|
|
1537
|
-
message: `Content hash mismatch for "${name}" at ${entry.installed_to}`,
|
|
1538
|
-
fix: "File was modified locally. Run `planmode update " + name + "` to restore, or ignore if intentional"
|
|
1539
|
-
});
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
const claudeMdPath = path10.join(projectDir, "CLAUDE.md");
|
|
1543
|
-
const imports = listImports(projectDir);
|
|
1544
|
-
const installedPlans = entries.filter(([, entry]) => entry.type === "plan").map(([name]) => name);
|
|
1545
|
-
for (const planName of installedPlans) {
|
|
1546
|
-
if (!imports.includes(planName)) {
|
|
1547
|
-
issues.push({
|
|
1548
|
-
severity: "error",
|
|
1549
|
-
message: `Plan "${planName}" is installed but missing from CLAUDE.md imports`,
|
|
1550
|
-
fix: `Add \`- @plans/${planName}.md\` to the # Planmode section of CLAUDE.md`
|
|
1551
|
-
});
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
for (const importName of imports) {
|
|
1555
|
-
if (!installedPlans.includes(importName)) {
|
|
1556
|
-
const filePath = path10.join(projectDir, "plans", `${importName}.md`);
|
|
1557
|
-
if (!fs10.existsSync(filePath)) {
|
|
1558
|
-
issues.push({
|
|
1559
|
-
severity: "error",
|
|
1560
|
-
message: `CLAUDE.md imports "${importName}" but the file doesn't exist at plans/${importName}.md`,
|
|
1561
|
-
fix: `Run \`planmode install ${importName}\` or remove the import from CLAUDE.md`
|
|
1562
|
-
});
|
|
1563
|
-
} else {
|
|
1564
|
-
issues.push({
|
|
1565
|
-
severity: "warning",
|
|
1566
|
-
message: `CLAUDE.md imports "${importName}" but it's not tracked in planmode.lock`,
|
|
1567
|
-
fix: "This plan was added manually. No action needed unless you want lockfile tracking."
|
|
1568
|
-
});
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
}
|
|
1572
|
-
if (installedPlans.length > 0 && !fs10.existsSync(claudeMdPath)) {
|
|
1573
|
-
issues.push({
|
|
1574
|
-
severity: "error",
|
|
1575
|
-
message: "CLAUDE.md is missing but plans are installed",
|
|
1576
|
-
fix: "Run `planmode install <any-plan>` to recreate it, or create it manually with a # Planmode section"
|
|
1577
|
-
});
|
|
1578
|
-
}
|
|
1579
|
-
const plansDir = path10.join(projectDir, "plans");
|
|
1580
|
-
if (fs10.existsSync(plansDir)) {
|
|
1581
|
-
const planFiles = fs10.readdirSync(plansDir).filter((f) => f.endsWith(".md"));
|
|
1582
|
-
for (const file of planFiles) {
|
|
1583
|
-
const name = file.replace(/\.md$/, "");
|
|
1584
|
-
if (!lockfile.packages[name]) {
|
|
1585
|
-
issues.push({
|
|
1586
|
-
severity: "warning",
|
|
1587
|
-
message: `Untracked plan file: plans/${file}`,
|
|
1588
|
-
fix: "This file isn't managed by planmode. Ignore if intentional."
|
|
1589
|
-
});
|
|
1590
|
-
}
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
return {
|
|
1594
|
-
issues,
|
|
1595
|
-
packagesChecked: entries.length,
|
|
1596
|
-
healthy: issues.filter((i) => i.severity === "error").length === 0
|
|
1597
|
-
};
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
|
-
// src/commands/doctor.ts
|
|
2768
|
+
import * as p8 from "@clack/prompts";
|
|
1601
2769
|
var doctorCommand = new Command12("doctor").description("Check project health: verify installed packages, imports, and file integrity").action(() => {
|
|
2770
|
+
const interactive = isInteractive();
|
|
1602
2771
|
const result = runDoctor();
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
2772
|
+
if (interactive) {
|
|
2773
|
+
p8.intro("Health check");
|
|
2774
|
+
} else {
|
|
2775
|
+
logger.blank();
|
|
2776
|
+
}
|
|
2777
|
+
if (interactive) {
|
|
2778
|
+
p8.log.info(`Checked ${result.packagesChecked} package(s)`);
|
|
2779
|
+
} else {
|
|
2780
|
+
logger.bold(`Checked ${result.packagesChecked} package(s)`);
|
|
1608
2781
|
logger.blank();
|
|
2782
|
+
}
|
|
2783
|
+
if (result.issues.length === 0) {
|
|
2784
|
+
if (interactive) {
|
|
2785
|
+
p8.outro("Everything looks good. No issues found.");
|
|
2786
|
+
} else {
|
|
2787
|
+
logger.success("Everything looks good. No issues found.");
|
|
2788
|
+
logger.blank();
|
|
2789
|
+
}
|
|
1609
2790
|
return;
|
|
1610
2791
|
}
|
|
1611
2792
|
const errors = result.issues.filter((i) => i.severity === "error");
|
|
1612
2793
|
const warnings = result.issues.filter((i) => i.severity === "warning");
|
|
1613
2794
|
for (const issue of errors) {
|
|
1614
|
-
|
|
1615
|
-
|
|
2795
|
+
if (interactive) {
|
|
2796
|
+
p8.log.error(issue.message);
|
|
2797
|
+
} else {
|
|
2798
|
+
logger.error(issue.message);
|
|
2799
|
+
if (issue.fix) logger.dim(` Fix: ${issue.fix}`);
|
|
2800
|
+
}
|
|
1616
2801
|
}
|
|
1617
2802
|
for (const issue of warnings) {
|
|
1618
|
-
|
|
1619
|
-
|
|
2803
|
+
if (interactive) {
|
|
2804
|
+
p8.log.warn(issue.message);
|
|
2805
|
+
} else {
|
|
2806
|
+
logger.warn(issue.message);
|
|
2807
|
+
if (issue.fix) logger.dim(` Fix: ${issue.fix}`);
|
|
2808
|
+
}
|
|
1620
2809
|
}
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
2810
|
+
if (interactive) {
|
|
2811
|
+
if (errors.length > 0) {
|
|
2812
|
+
p8.outro(`${errors.length} error(s), ${warnings.length} warning(s)`);
|
|
2813
|
+
} else {
|
|
2814
|
+
p8.outro(`${warnings.length} warning(s)`);
|
|
2815
|
+
}
|
|
1624
2816
|
} else {
|
|
1625
|
-
logger.
|
|
2817
|
+
logger.blank();
|
|
2818
|
+
if (errors.length > 0) {
|
|
2819
|
+
logger.error(`${errors.length} error(s), ${warnings.length} warning(s)`);
|
|
2820
|
+
} else {
|
|
2821
|
+
logger.warn(`${warnings.length} warning(s)`);
|
|
2822
|
+
}
|
|
2823
|
+
logger.blank();
|
|
1626
2824
|
}
|
|
1627
|
-
logger.blank();
|
|
1628
2825
|
if (errors.length > 0) {
|
|
1629
2826
|
process.exit(1);
|
|
1630
2827
|
}
|
|
@@ -1632,8 +2829,12 @@ var doctorCommand = new Command12("doctor").description("Check project health: v
|
|
|
1632
2829
|
|
|
1633
2830
|
// src/commands/test.ts
|
|
1634
2831
|
import { Command as Command13 } from "commander";
|
|
2832
|
+
import * as p9 from "@clack/prompts";
|
|
1635
2833
|
|
|
1636
2834
|
// src/lib/tester.ts
|
|
2835
|
+
init_manifest();
|
|
2836
|
+
init_template();
|
|
2837
|
+
init_registry();
|
|
1637
2838
|
async function testPackage(projectDir = process.cwd()) {
|
|
1638
2839
|
const issues = [];
|
|
1639
2840
|
const checks = [];
|
|
@@ -1762,33 +2963,62 @@ async function testPackage(projectDir = process.cwd()) {
|
|
|
1762
2963
|
}
|
|
1763
2964
|
|
|
1764
2965
|
// src/commands/test.ts
|
|
2966
|
+
init_logger();
|
|
2967
|
+
init_prompts();
|
|
1765
2968
|
var testCommand = new Command13("test").description("Test the current package before publishing: validate manifest, render templates, check dependencies").action(async () => {
|
|
1766
2969
|
try {
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
2970
|
+
const interactive = isInteractive();
|
|
2971
|
+
if (interactive) {
|
|
2972
|
+
p9.intro("Testing package");
|
|
2973
|
+
} else {
|
|
2974
|
+
logger.blank();
|
|
2975
|
+
logger.bold("Testing package...");
|
|
2976
|
+
logger.blank();
|
|
2977
|
+
}
|
|
1770
2978
|
const result = await testPackage();
|
|
1771
2979
|
for (const check of result.checks) {
|
|
1772
2980
|
if (check.passed) {
|
|
1773
|
-
|
|
2981
|
+
if (interactive) {
|
|
2982
|
+
p9.log.success(check.name);
|
|
2983
|
+
} else {
|
|
2984
|
+
logger.success(check.name);
|
|
2985
|
+
}
|
|
1774
2986
|
} else {
|
|
1775
2987
|
const issue = result.issues.find((i) => i.check === check.name);
|
|
1776
2988
|
if (issue?.severity === "error") {
|
|
1777
|
-
|
|
2989
|
+
if (interactive) {
|
|
2990
|
+
p9.log.error(`${check.name}: ${issue.message}`);
|
|
2991
|
+
} else {
|
|
2992
|
+
logger.error(`${check.name}: ${issue.message}`);
|
|
2993
|
+
}
|
|
1778
2994
|
} else if (issue) {
|
|
1779
|
-
|
|
2995
|
+
if (interactive) {
|
|
2996
|
+
p9.log.warn(`${check.name}: ${issue.message}`);
|
|
2997
|
+
} else {
|
|
2998
|
+
logger.warn(`${check.name}: ${issue.message}`);
|
|
2999
|
+
}
|
|
1780
3000
|
}
|
|
1781
3001
|
}
|
|
1782
3002
|
}
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
3003
|
+
if (interactive) {
|
|
3004
|
+
if (result.passed) {
|
|
3005
|
+
p9.outro("All checks passed. Ready to publish.");
|
|
3006
|
+
} else {
|
|
3007
|
+
const errors = result.issues.filter((i) => i.severity === "error");
|
|
3008
|
+
const warnings = result.issues.filter((i) => i.severity === "warning");
|
|
3009
|
+
p9.outro(`${errors.length} error(s), ${warnings.length} warning(s). Fix errors before publishing.`);
|
|
3010
|
+
}
|
|
1786
3011
|
} else {
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
3012
|
+
logger.blank();
|
|
3013
|
+
if (result.passed) {
|
|
3014
|
+
logger.success(`All checks passed. Ready to publish.`);
|
|
3015
|
+
} else {
|
|
3016
|
+
const errors = result.issues.filter((i) => i.severity === "error");
|
|
3017
|
+
const warnings = result.issues.filter((i) => i.severity === "warning");
|
|
3018
|
+
logger.error(`${errors.length} error(s), ${warnings.length} warning(s). Fix errors before publishing.`);
|
|
3019
|
+
}
|
|
3020
|
+
logger.blank();
|
|
1790
3021
|
}
|
|
1791
|
-
logger.blank();
|
|
1792
3022
|
if (!result.passed) {
|
|
1793
3023
|
process.exit(1);
|
|
1794
3024
|
}
|
|
@@ -1800,6 +3030,7 @@ var testCommand = new Command13("test").description("Test the current package be
|
|
|
1800
3030
|
|
|
1801
3031
|
// src/commands/record.ts
|
|
1802
3032
|
import { Command as Command14 } from "commander";
|
|
3033
|
+
import * as p10 from "@clack/prompts";
|
|
1803
3034
|
import fs12 from "fs";
|
|
1804
3035
|
import path12 from "path";
|
|
1805
3036
|
|
|
@@ -1823,8 +3054,8 @@ function startRecording(projectDir = process.cwd()) {
|
|
|
1823
3054
|
async function startRecordingAsync(projectDir = process.cwd()) {
|
|
1824
3055
|
const recordingPath = startRecording(projectDir);
|
|
1825
3056
|
const git = simpleGit2(projectDir);
|
|
1826
|
-
const
|
|
1827
|
-
const sha =
|
|
3057
|
+
const log8 = await git.log({ n: 1 });
|
|
3058
|
+
const sha = log8.latest?.hash;
|
|
1828
3059
|
if (!sha) {
|
|
1829
3060
|
throw new Error("No commits found in this repository.");
|
|
1830
3061
|
}
|
|
@@ -1841,12 +3072,12 @@ async function stopRecording(projectDir = process.cwd(), options = {}) {
|
|
|
1841
3072
|
}
|
|
1842
3073
|
const startSha = fs11.readFileSync(recordingPath, "utf-8").trim();
|
|
1843
3074
|
const git = simpleGit2(projectDir);
|
|
1844
|
-
const
|
|
1845
|
-
if (
|
|
3075
|
+
const log8 = await git.log({ from: startSha, to: "HEAD" });
|
|
3076
|
+
if (log8.total === 0) {
|
|
1846
3077
|
fs11.unlinkSync(recordingPath);
|
|
1847
3078
|
throw new Error("No commits since recording started. Nothing to capture.");
|
|
1848
3079
|
}
|
|
1849
|
-
const commits = [...
|
|
3080
|
+
const commits = [...log8.all].reverse();
|
|
1850
3081
|
const steps = [];
|
|
1851
3082
|
const allFilesChanged = /* @__PURE__ */ new Set();
|
|
1852
3083
|
for (const commit of commits) {
|
|
@@ -1933,6 +3164,8 @@ function generateManifest(name, author) {
|
|
|
1933
3164
|
}
|
|
1934
3165
|
|
|
1935
3166
|
// src/commands/record.ts
|
|
3167
|
+
init_logger();
|
|
3168
|
+
init_prompts();
|
|
1936
3169
|
var recordCommand = new Command14("record").description("Record git activity and generate a plan from commits");
|
|
1937
3170
|
recordCommand.command("start").description("Start recording \u2014 saves current HEAD as the starting point").action(async () => {
|
|
1938
3171
|
try {
|
|
@@ -1948,12 +3181,26 @@ recordCommand.command("start").description("Start recording \u2014 saves current
|
|
|
1948
3181
|
});
|
|
1949
3182
|
recordCommand.command("stop").description("Stop recording and generate a plan from commits since start").option("--name <name>", "Package name (auto-inferred if not provided)").option("--author <author>", "Author GitHub username").option("--dir <dir>", "Output directory for the generated package (default: current directory)").action(async (options) => {
|
|
1950
3183
|
try {
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
}
|
|
3184
|
+
const interactive = isInteractive();
|
|
3185
|
+
if (interactive) {
|
|
3186
|
+
p10.intro("Generating plan from recording");
|
|
3187
|
+
} else {
|
|
3188
|
+
logger.blank();
|
|
3189
|
+
}
|
|
3190
|
+
const result = interactive ? await withSpinner(
|
|
3191
|
+
"Analyzing commits...",
|
|
3192
|
+
() => stopRecording(process.cwd(), {
|
|
3193
|
+
name: options.name,
|
|
3194
|
+
author: options.author
|
|
3195
|
+
}),
|
|
3196
|
+
"Analysis complete"
|
|
3197
|
+
) : await (async () => {
|
|
3198
|
+
logger.info("Analyzing commits...");
|
|
3199
|
+
return stopRecording(process.cwd(), {
|
|
3200
|
+
name: options.name,
|
|
3201
|
+
author: options.author
|
|
3202
|
+
});
|
|
3203
|
+
})();
|
|
1957
3204
|
const outDir = options.dir ?? process.cwd();
|
|
1958
3205
|
fs12.mkdirSync(outDir, { recursive: true });
|
|
1959
3206
|
fs12.writeFileSync(path12.join(outDir, "planmode.yaml"), result.manifestContent, "utf-8");
|
|
@@ -1966,8 +3213,12 @@ recordCommand.command("stop").description("Stop recording and generate a plan fr
|
|
|
1966
3213
|
}
|
|
1967
3214
|
logger.blank();
|
|
1968
3215
|
logger.success("Created planmode.yaml and plan.md");
|
|
1969
|
-
|
|
1970
|
-
|
|
3216
|
+
if (interactive) {
|
|
3217
|
+
p10.outro("Edit the generated plan, then run `planmode test` to validate and `planmode publish` when ready.");
|
|
3218
|
+
} else {
|
|
3219
|
+
logger.dim("Edit the generated plan, then run `planmode test` to validate and `planmode publish` when ready.");
|
|
3220
|
+
logger.blank();
|
|
3221
|
+
}
|
|
1971
3222
|
} catch (err) {
|
|
1972
3223
|
logger.error(err.message);
|
|
1973
3224
|
process.exit(1);
|
|
@@ -1983,6 +3234,7 @@ recordCommand.command("status").description("Check if a recording is in progress
|
|
|
1983
3234
|
|
|
1984
3235
|
// src/commands/snapshot.ts
|
|
1985
3236
|
import { Command as Command15 } from "commander";
|
|
3237
|
+
import * as p11 from "@clack/prompts";
|
|
1986
3238
|
import fs14 from "fs";
|
|
1987
3239
|
import path14 from "path";
|
|
1988
3240
|
|
|
@@ -2257,14 +3509,29 @@ function detectCategory(data) {
|
|
|
2257
3509
|
}
|
|
2258
3510
|
|
|
2259
3511
|
// src/commands/snapshot.ts
|
|
2260
|
-
|
|
3512
|
+
init_logger();
|
|
3513
|
+
init_prompts();
|
|
3514
|
+
var snapshotCommand = new Command15("snapshot").description("Analyze the current project and generate a plan that recreates this setup").option("--name <name>", "Package name (auto-inferred from project)").option("--author <author>", "Author GitHub username").option("--dir <dir>", "Output directory for the generated package (default: current directory)").action(async (options) => {
|
|
2261
3515
|
try {
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
}
|
|
3516
|
+
const interactive = isInteractive();
|
|
3517
|
+
if (interactive) {
|
|
3518
|
+
p11.intro("Taking project snapshot");
|
|
3519
|
+
} else {
|
|
3520
|
+
logger.blank();
|
|
3521
|
+
}
|
|
3522
|
+
const doSnapshot = () => Promise.resolve(
|
|
3523
|
+
takeSnapshot(process.cwd(), {
|
|
3524
|
+
name: options.name,
|
|
3525
|
+
author: options.author
|
|
3526
|
+
})
|
|
3527
|
+
);
|
|
3528
|
+
const result = interactive ? await withSpinner("Analyzing project...", doSnapshot, "Analysis complete") : (() => {
|
|
3529
|
+
logger.info("Analyzing project...");
|
|
3530
|
+
return takeSnapshot(process.cwd(), {
|
|
3531
|
+
name: options.name,
|
|
3532
|
+
author: options.author
|
|
3533
|
+
});
|
|
3534
|
+
})();
|
|
2268
3535
|
const outDir = options.dir ?? process.cwd();
|
|
2269
3536
|
fs14.mkdirSync(outDir, { recursive: true });
|
|
2270
3537
|
fs14.writeFileSync(path14.join(outDir, "planmode.yaml"), result.manifestContent, "utf-8");
|
|
@@ -2279,8 +3546,97 @@ var snapshotCommand = new Command15("snapshot").description("Analyze the current
|
|
|
2279
3546
|
logger.dim(` Tools detected: ${result.data.detectedTools.map((t) => t.name).join(", ") || "none"}`);
|
|
2280
3547
|
logger.blank();
|
|
2281
3548
|
logger.success("Created planmode.yaml and plan.md");
|
|
2282
|
-
|
|
3549
|
+
if (interactive) {
|
|
3550
|
+
p11.outro("Edit the generated plan, then run `planmode test` to validate and `planmode publish` when ready.");
|
|
3551
|
+
} else {
|
|
3552
|
+
logger.dim("Edit the generated plan, then run `planmode test` to validate and `planmode publish` when ready.");
|
|
3553
|
+
logger.blank();
|
|
3554
|
+
}
|
|
3555
|
+
} catch (err) {
|
|
3556
|
+
logger.error(err.message);
|
|
3557
|
+
process.exit(1);
|
|
3558
|
+
}
|
|
3559
|
+
});
|
|
3560
|
+
|
|
3561
|
+
// src/commands/context.ts
|
|
3562
|
+
init_context();
|
|
3563
|
+
init_logger();
|
|
3564
|
+
init_prompts();
|
|
3565
|
+
import { Command as Command16 } from "commander";
|
|
3566
|
+
var contextCommand = new Command16("context").description("Manage project document context for AI");
|
|
3567
|
+
contextCommand.command("add <path>").description("Add a document directory to the project context").option("--name <name>", "Human-readable label for this directory").action(async (dirPath, options) => {
|
|
3568
|
+
try {
|
|
3569
|
+
const interactive = isInteractive();
|
|
3570
|
+
if (interactive) {
|
|
3571
|
+
await withSpinner(
|
|
3572
|
+
"Indexing documents...",
|
|
3573
|
+
async () => addContextRepo(dirPath, { name: options.name }),
|
|
3574
|
+
"Indexing complete"
|
|
3575
|
+
);
|
|
3576
|
+
} else {
|
|
3577
|
+
logger.blank();
|
|
3578
|
+
addContextRepo(dirPath, { name: options.name });
|
|
3579
|
+
logger.blank();
|
|
3580
|
+
}
|
|
3581
|
+
} catch (err) {
|
|
3582
|
+
logger.error(err.message);
|
|
3583
|
+
process.exit(1);
|
|
3584
|
+
}
|
|
3585
|
+
});
|
|
3586
|
+
contextCommand.command("remove <path-or-name>").description("Remove a directory from the project context").action((pathOrName) => {
|
|
3587
|
+
try {
|
|
3588
|
+
logger.blank();
|
|
3589
|
+
removeContextRepo(pathOrName);
|
|
3590
|
+
logger.blank();
|
|
3591
|
+
} catch (err) {
|
|
3592
|
+
logger.error(err.message);
|
|
3593
|
+
process.exit(1);
|
|
3594
|
+
}
|
|
3595
|
+
});
|
|
3596
|
+
contextCommand.command("list").description("Show all directories in the project context").option("--json", "Output as JSON").action((options) => {
|
|
3597
|
+
try {
|
|
3598
|
+
const summary = getContextSummary();
|
|
3599
|
+
if (options.json) {
|
|
3600
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
3601
|
+
return;
|
|
3602
|
+
}
|
|
3603
|
+
logger.blank();
|
|
3604
|
+
if (summary.totalRepos === 0) {
|
|
3605
|
+
logger.info("No context repos configured. Run `planmode context add <path>` to add one.");
|
|
3606
|
+
logger.blank();
|
|
3607
|
+
return;
|
|
3608
|
+
}
|
|
3609
|
+
logger.bold(`${summary.totalRepos} context repo(s) \u2014 ${summary.totalFiles} file(s), ${formatSize(summary.totalSize)}`);
|
|
2283
3610
|
logger.blank();
|
|
3611
|
+
for (const repo of summary.repos) {
|
|
3612
|
+
logger.info(`${repo.name}`);
|
|
3613
|
+
logger.dim(` Path: ${repo.path}`);
|
|
3614
|
+
logger.dim(` Files: ${repo.fileCount} (${formatSize(repo.totalSize)})`);
|
|
3615
|
+
if (repo.typeBreakdown.length > 0) {
|
|
3616
|
+
logger.dim(` Types: ${repo.typeBreakdown.join(", ")}`);
|
|
3617
|
+
}
|
|
3618
|
+
logger.dim(` Indexed: ${repo.indexedAt}`);
|
|
3619
|
+
logger.blank();
|
|
3620
|
+
}
|
|
3621
|
+
} catch (err) {
|
|
3622
|
+
logger.error(err.message);
|
|
3623
|
+
process.exit(1);
|
|
3624
|
+
}
|
|
3625
|
+
});
|
|
3626
|
+
contextCommand.command("reindex [path-or-name]").description("Re-scan files in one or all context directories").action(async (pathOrName) => {
|
|
3627
|
+
try {
|
|
3628
|
+
const interactive = isInteractive();
|
|
3629
|
+
if (interactive) {
|
|
3630
|
+
await withSpinner(
|
|
3631
|
+
"Re-scanning documents...",
|
|
3632
|
+
async () => reindexContext(pathOrName),
|
|
3633
|
+
"Reindex complete"
|
|
3634
|
+
);
|
|
3635
|
+
} else {
|
|
3636
|
+
logger.blank();
|
|
3637
|
+
reindexContext(pathOrName);
|
|
3638
|
+
logger.blank();
|
|
3639
|
+
}
|
|
2284
3640
|
} catch (err) {
|
|
2285
3641
|
logger.error(err.message);
|
|
2286
3642
|
process.exit(1);
|
|
@@ -2288,8 +3644,9 @@ var snapshotCommand = new Command15("snapshot").description("Analyze the current
|
|
|
2288
3644
|
});
|
|
2289
3645
|
|
|
2290
3646
|
// src/index.ts
|
|
2291
|
-
|
|
2292
|
-
program
|
|
3647
|
+
init_prompts();
|
|
3648
|
+
var program = new Command17();
|
|
3649
|
+
program.name("planmode").description("The open source package manager for AI plans, rules, and prompts.").version("0.4.0");
|
|
2293
3650
|
program.addCommand(installCommand);
|
|
2294
3651
|
program.addCommand(uninstallCommand);
|
|
2295
3652
|
program.addCommand(searchCommand);
|
|
@@ -2305,4 +3662,10 @@ program.addCommand(doctorCommand);
|
|
|
2305
3662
|
program.addCommand(testCommand);
|
|
2306
3663
|
program.addCommand(recordCommand);
|
|
2307
3664
|
program.addCommand(snapshotCommand);
|
|
2308
|
-
program.
|
|
3665
|
+
program.addCommand(contextCommand);
|
|
3666
|
+
if (process.argv.length <= 2 && isInteractive()) {
|
|
3667
|
+
const { runInteractiveMenu: runInteractiveMenu2 } = await Promise.resolve().then(() => (init_interactive(), interactive_exports));
|
|
3668
|
+
runInteractiveMenu2();
|
|
3669
|
+
} else {
|
|
3670
|
+
program.parse();
|
|
3671
|
+
}
|