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/mcp.js CHANGED
@@ -1,112 +1,361 @@
1
1
  #!/usr/bin/env node
2
-
3
- // src/mcp.ts
4
- import fs12 from "fs";
5
- import path12 from "path";
6
- import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
7
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
- import { z } from "zod/v4";
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
+ };
9
11
 
10
12
  // src/lib/logger.ts
11
- var RESET = "\x1B[0m";
12
- var RED = "\x1B[31m";
13
- var GREEN = "\x1B[32m";
14
- var YELLOW = "\x1B[33m";
15
- var CYAN = "\x1B[36m";
16
- var DIM = "\x1B[2m";
17
- var BOLD = "\x1B[1m";
18
13
  function stripAnsi(str) {
19
14
  return str.replace(/\x1b\[[0-9;]*m/g, "");
20
15
  }
21
- var capturing = false;
22
- var captured = [];
23
- var logger = {
24
- capture() {
25
- capturing = true;
26
- captured = [];
27
- },
28
- flush() {
29
- const messages = captured;
30
- captured = [];
16
+ var RESET, RED, GREEN, YELLOW, CYAN, DIM, BOLD, capturing, captured, logger;
17
+ var init_logger = __esm({
18
+ "src/lib/logger.ts"() {
19
+ "use strict";
20
+ RESET = "\x1B[0m";
21
+ RED = "\x1B[31m";
22
+ GREEN = "\x1B[32m";
23
+ YELLOW = "\x1B[33m";
24
+ CYAN = "\x1B[36m";
25
+ DIM = "\x1B[2m";
26
+ BOLD = "\x1B[1m";
31
27
  capturing = false;
32
- return messages;
33
- },
34
- isCapturing() {
35
- return capturing;
36
- },
37
- info(msg) {
38
- const text = `info ${msg}`;
39
- if (capturing) {
40
- captured.push(stripAnsi(text));
41
- } else {
42
- console.log(`${CYAN}info${RESET} ${msg}`);
43
- }
44
- },
45
- success(msg) {
46
- const text = `\u2713 ${msg}`;
47
- if (capturing) {
48
- captured.push(stripAnsi(text));
49
- } else {
50
- console.log(`${GREEN}\u2713${RESET} ${msg}`);
51
- }
52
- },
53
- warn(msg) {
54
- const text = `warn ${msg}`;
55
- if (capturing) {
56
- captured.push(stripAnsi(text));
57
- } else {
58
- console.log(`${YELLOW}warn${RESET} ${msg}`);
59
- }
60
- },
61
- error(msg) {
62
- const text = `error ${msg}`;
63
- if (capturing) {
64
- captured.push(stripAnsi(text));
65
- } else {
66
- console.error(`${RED}error${RESET} ${msg}`);
67
- }
68
- },
69
- dim(msg) {
70
- if (capturing) {
71
- captured.push(msg);
72
- } else {
73
- console.log(`${DIM}${msg}${RESET}`);
74
- }
75
- },
76
- bold(msg) {
77
- if (capturing) {
78
- captured.push(msg);
79
- } else {
80
- console.log(`${BOLD}${msg}${RESET}`);
81
- }
82
- },
83
- table(headers, rows) {
84
- const colWidths = headers.map(
85
- (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
86
- );
87
- const header = headers.map((h, i) => h.toUpperCase().padEnd(colWidths[i])).join(" ");
88
- if (capturing) {
89
- captured.push(` ${header}`);
90
- for (const row of rows) {
91
- const line = row.map((cell, i) => cell.padEnd(colWidths[i])).join(" ");
92
- captured.push(` ${line}`);
28
+ captured = [];
29
+ logger = {
30
+ capture() {
31
+ capturing = true;
32
+ captured = [];
33
+ },
34
+ flush() {
35
+ const messages = captured;
36
+ captured = [];
37
+ capturing = false;
38
+ return messages;
39
+ },
40
+ isCapturing() {
41
+ return capturing;
42
+ },
43
+ info(msg) {
44
+ const text2 = `info ${msg}`;
45
+ if (capturing) {
46
+ captured.push(stripAnsi(text2));
47
+ } else {
48
+ console.log(`${CYAN}info${RESET} ${msg}`);
49
+ }
50
+ },
51
+ success(msg) {
52
+ const text2 = `\u2713 ${msg}`;
53
+ if (capturing) {
54
+ captured.push(stripAnsi(text2));
55
+ } else {
56
+ console.log(`${GREEN}\u2713${RESET} ${msg}`);
57
+ }
58
+ },
59
+ warn(msg) {
60
+ const text2 = `warn ${msg}`;
61
+ if (capturing) {
62
+ captured.push(stripAnsi(text2));
63
+ } else {
64
+ console.log(`${YELLOW}warn${RESET} ${msg}`);
65
+ }
66
+ },
67
+ error(msg) {
68
+ const text2 = `error ${msg}`;
69
+ if (capturing) {
70
+ captured.push(stripAnsi(text2));
71
+ } else {
72
+ console.error(`${RED}error${RESET} ${msg}`);
73
+ }
74
+ },
75
+ dim(msg) {
76
+ if (capturing) {
77
+ captured.push(msg);
78
+ } else {
79
+ console.log(`${DIM}${msg}${RESET}`);
80
+ }
81
+ },
82
+ bold(msg) {
83
+ if (capturing) {
84
+ captured.push(msg);
85
+ } else {
86
+ console.log(`${BOLD}${msg}${RESET}`);
87
+ }
88
+ },
89
+ table(headers, rows) {
90
+ const colWidths = headers.map(
91
+ (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
92
+ );
93
+ const header = headers.map((h, i) => h.toUpperCase().padEnd(colWidths[i])).join(" ");
94
+ if (capturing) {
95
+ captured.push(` ${header}`);
96
+ for (const row of rows) {
97
+ const line = row.map((cell, i) => cell.padEnd(colWidths[i])).join(" ");
98
+ captured.push(` ${line}`);
99
+ }
100
+ } else {
101
+ console.log(` ${DIM}${header}${RESET}`);
102
+ for (const row of rows) {
103
+ const line = row.map((cell, i) => cell.padEnd(colWidths[i])).join(" ");
104
+ console.log(` ${line}`);
105
+ }
106
+ }
107
+ },
108
+ blank() {
109
+ if (capturing) {
110
+ captured.push("");
111
+ } else {
112
+ console.log();
113
+ }
93
114
  }
94
- } else {
95
- console.log(` ${DIM}${header}${RESET}`);
96
- for (const row of rows) {
97
- const line = row.map((cell, i) => cell.padEnd(colWidths[i])).join(" ");
98
- console.log(` ${line}`);
115
+ };
116
+ }
117
+ });
118
+
119
+ // src/lib/context.ts
120
+ var context_exports = {};
121
+ __export(context_exports, {
122
+ addContextRepo: () => addContextRepo,
123
+ formatSize: () => formatSize,
124
+ getContextSummary: () => getContextSummary,
125
+ readContextIndex: () => readContextIndex,
126
+ reindexContext: () => reindexContext,
127
+ removeContextRepo: () => removeContextRepo,
128
+ walkDirectory: () => walkDirectory,
129
+ writeContextIndex: () => writeContextIndex
130
+ });
131
+ import fs12 from "fs";
132
+ import path12 from "path";
133
+ import { parse as parse4, stringify as stringify6 } from "yaml";
134
+ function getContextPath(projectDir) {
135
+ return path12.join(projectDir, CONTEXT_DIR, CONTEXT_FILE);
136
+ }
137
+ function emptyIndex() {
138
+ return { version: 1, repos: [] };
139
+ }
140
+ function readContextIndex(projectDir = process.cwd()) {
141
+ const filePath = getContextPath(projectDir);
142
+ try {
143
+ const raw = fs12.readFileSync(filePath, "utf-8");
144
+ const data = parse4(raw);
145
+ return data ?? emptyIndex();
146
+ } catch {
147
+ return emptyIndex();
148
+ }
149
+ }
150
+ function writeContextIndex(index, projectDir = process.cwd()) {
151
+ const dirPath = path12.join(projectDir, CONTEXT_DIR);
152
+ fs12.mkdirSync(dirPath, { recursive: true });
153
+ const filePath = getContextPath(projectDir);
154
+ fs12.writeFileSync(filePath, stringify6(index), "utf-8");
155
+ }
156
+ function walkDirectory(dirPath) {
157
+ const files = [];
158
+ function walk(currentPath) {
159
+ let entries;
160
+ try {
161
+ entries = fs12.readdirSync(currentPath, { withFileTypes: true });
162
+ } catch {
163
+ return;
164
+ }
165
+ for (const entry of entries) {
166
+ if (entry.name.startsWith(".") && IGNORED_DIRS.has(entry.name)) continue;
167
+ if (IGNORED_DIRS.has(entry.name)) continue;
168
+ const fullPath = path12.join(currentPath, entry.name);
169
+ if (entry.isDirectory()) {
170
+ walk(fullPath);
171
+ } else if (entry.isFile()) {
172
+ const ext = path12.extname(entry.name).toLowerCase();
173
+ if (!SUPPORTED_EXTENSIONS.has(ext)) continue;
174
+ try {
175
+ const stat = fs12.statSync(fullPath);
176
+ const relativePath = path12.relative(dirPath, fullPath);
177
+ files.push({
178
+ path: relativePath,
179
+ extension: ext,
180
+ size: stat.size,
181
+ modified_at: stat.mtime.toISOString()
182
+ });
183
+ } catch {
184
+ }
99
185
  }
100
186
  }
101
- },
102
- blank() {
103
- if (capturing) {
104
- captured.push("");
105
- } else {
106
- console.log();
187
+ }
188
+ walk(dirPath);
189
+ return files;
190
+ }
191
+ function addContextRepo(repoPath, options = {}) {
192
+ const projectDir = options.projectDir ?? process.cwd();
193
+ const absolutePath = path12.resolve(projectDir, repoPath);
194
+ if (!fs12.existsSync(absolutePath)) {
195
+ throw new Error(`Directory not found: ${repoPath}`);
196
+ }
197
+ if (!fs12.statSync(absolutePath).isDirectory()) {
198
+ throw new Error(`Not a directory: ${repoPath}`);
199
+ }
200
+ const index = readContextIndex(projectDir);
201
+ const relative = path12.relative(projectDir, absolutePath);
202
+ const isInsideProject = !relative.startsWith("..") && !path12.isAbsolute(relative);
203
+ const storedPath = isInsideProject ? relative : absolutePath;
204
+ const existing = index.repos.find(
205
+ (r) => r.repo.path === storedPath || r.repo.name === options.name
206
+ );
207
+ if (existing) {
208
+ throw new Error(
209
+ `Context repo already exists: ${existing.repo.name ?? existing.repo.path}. Use \`planmode context reindex\` to refresh.`
210
+ );
211
+ }
212
+ logger.info(`Scanning ${absolutePath}...`);
213
+ const files = walkDirectory(absolutePath);
214
+ const now = (/* @__PURE__ */ new Date()).toISOString();
215
+ const repoIndex = {
216
+ repo: {
217
+ path: storedPath,
218
+ name: options.name,
219
+ added_at: now
220
+ },
221
+ files,
222
+ indexed_at: now,
223
+ file_count: files.length,
224
+ total_size: files.reduce((sum, f) => sum + f.size, 0)
225
+ };
226
+ index.repos.push(repoIndex);
227
+ writeContextIndex(index, projectDir);
228
+ logger.success(`Added "${options.name ?? storedPath}" \u2014 ${files.length} file(s), ${formatSize(repoIndex.total_size)}`);
229
+ const breakdown = getTypeBreakdown(files);
230
+ if (breakdown.length > 0) {
231
+ logger.dim(` ${breakdown.join(", ")}`);
232
+ }
233
+ return repoIndex;
234
+ }
235
+ function removeContextRepo(pathOrName, projectDir = process.cwd()) {
236
+ const index = readContextIndex(projectDir);
237
+ const idx = index.repos.findIndex(
238
+ (r) => r.repo.path === pathOrName || r.repo.name === pathOrName
239
+ );
240
+ if (idx === -1) {
241
+ throw new Error(`Context repo not found: ${pathOrName}`);
242
+ }
243
+ const removed = index.repos[idx];
244
+ index.repos.splice(idx, 1);
245
+ writeContextIndex(index, projectDir);
246
+ logger.success(`Removed "${removed.repo.name ?? removed.repo.path}"`);
247
+ }
248
+ function reindexContext(pathOrName, projectDir = process.cwd()) {
249
+ const index = readContextIndex(projectDir);
250
+ if (index.repos.length === 0) {
251
+ throw new Error("No context repos configured. Use `planmode context add <path>` first.");
252
+ }
253
+ const targets = pathOrName ? index.repos.filter(
254
+ (r) => r.repo.path === pathOrName || r.repo.name === pathOrName
255
+ ) : index.repos;
256
+ if (pathOrName && targets.length === 0) {
257
+ throw new Error(`Context repo not found: ${pathOrName}`);
258
+ }
259
+ for (const repo of targets) {
260
+ const absolutePath = path12.resolve(projectDir, repo.repo.path);
261
+ if (!fs12.existsSync(absolutePath)) {
262
+ logger.warn(`Directory not found, skipping: ${repo.repo.path}`);
263
+ continue;
107
264
  }
265
+ logger.info(`Re-scanning ${repo.repo.name ?? repo.repo.path}...`);
266
+ const files = walkDirectory(absolutePath);
267
+ repo.files = files;
268
+ repo.indexed_at = (/* @__PURE__ */ new Date()).toISOString();
269
+ repo.file_count = files.length;
270
+ repo.total_size = files.reduce((sum, f) => sum + f.size, 0);
271
+ logger.success(`Reindexed "${repo.repo.name ?? repo.repo.path}" \u2014 ${files.length} file(s), ${formatSize(repo.total_size)}`);
108
272
  }
109
- };
273
+ writeContextIndex(index, projectDir);
274
+ }
275
+ function getContextSummary(projectDir = process.cwd()) {
276
+ const index = readContextIndex(projectDir);
277
+ return {
278
+ totalRepos: index.repos.length,
279
+ totalFiles: index.repos.reduce((sum, r) => sum + r.file_count, 0),
280
+ totalSize: index.repos.reduce((sum, r) => sum + r.total_size, 0),
281
+ repos: index.repos.map((r) => ({
282
+ name: r.repo.name ?? r.repo.path,
283
+ path: r.repo.path,
284
+ fileCount: r.file_count,
285
+ totalSize: r.total_size,
286
+ typeBreakdown: getTypeBreakdown(r.files),
287
+ indexedAt: r.indexed_at
288
+ }))
289
+ };
290
+ }
291
+ function getTypeBreakdown(files) {
292
+ const counts = /* @__PURE__ */ new Map();
293
+ for (const file of files) {
294
+ counts.set(file.extension, (counts.get(file.extension) ?? 0) + 1);
295
+ }
296
+ return Array.from(counts.entries()).sort((a, b) => b[1] - a[1]).map(([ext, count]) => `${ext}: ${count}`);
297
+ }
298
+ function formatSize(bytes) {
299
+ if (bytes < 1024) return `${bytes} B`;
300
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
301
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
302
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
303
+ }
304
+ var CONTEXT_DIR, CONTEXT_FILE, SUPPORTED_EXTENSIONS, IGNORED_DIRS;
305
+ var init_context = __esm({
306
+ "src/lib/context.ts"() {
307
+ "use strict";
308
+ init_logger();
309
+ CONTEXT_DIR = ".planmode";
310
+ CONTEXT_FILE = "context.yaml";
311
+ SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
312
+ ".txt",
313
+ ".md",
314
+ ".markdown",
315
+ ".pdf",
316
+ ".rtf",
317
+ ".doc",
318
+ ".docx",
319
+ ".csv",
320
+ ".tsv",
321
+ ".json",
322
+ ".yaml",
323
+ ".yml",
324
+ ".xml",
325
+ ".html",
326
+ ".htm",
327
+ ".rst",
328
+ ".org",
329
+ ".tex",
330
+ ".log"
331
+ ]);
332
+ IGNORED_DIRS = /* @__PURE__ */ new Set([
333
+ "node_modules",
334
+ ".git",
335
+ "dist",
336
+ "build",
337
+ ".next",
338
+ "__pycache__",
339
+ ".venv",
340
+ "venv",
341
+ ".tox",
342
+ "target",
343
+ "out",
344
+ ".cache",
345
+ ".turbo",
346
+ "coverage",
347
+ ".nyc_output"
348
+ ]);
349
+ }
350
+ });
351
+
352
+ // src/mcp.ts
353
+ init_logger();
354
+ import fs13 from "fs";
355
+ import path13 from "path";
356
+ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
357
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
358
+ import { z } from "zod/v4";
110
359
 
111
360
  // src/lib/registry.ts
112
361
  import fs2 from "fs";
@@ -651,6 +900,9 @@ function coerceValue(raw, def) {
651
900
  }
652
901
  }
653
902
 
903
+ // src/lib/installer.ts
904
+ init_logger();
905
+
654
906
  // src/lib/analytics.ts
655
907
  var API_BASE = "https://api.planmode.org";
656
908
  function trackDownload(packageName) {
@@ -660,6 +912,118 @@ function trackDownload(packageName) {
660
912
  });
661
913
  }
662
914
 
915
+ // src/lib/prompts.ts
916
+ import * as p from "@clack/prompts";
917
+ function isInteractive() {
918
+ return Boolean(process.stdin.isTTY) && !process.env.CI;
919
+ }
920
+ function handleCancel(value) {
921
+ if (p.isCancel(value)) {
922
+ p.cancel("Cancelled.");
923
+ process.exit(0);
924
+ }
925
+ return value;
926
+ }
927
+ async function promptForVariable(name, def) {
928
+ switch (def.type) {
929
+ case "enum": {
930
+ const value = await p.select({
931
+ message: def.description || name,
932
+ options: (def.options ?? []).map((opt) => ({
933
+ value: opt,
934
+ label: opt
935
+ })),
936
+ initialValue: def.default !== void 0 ? String(def.default) : void 0
937
+ });
938
+ return handleCancel(value);
939
+ }
940
+ case "boolean": {
941
+ const value = await p.confirm({
942
+ message: def.description || name,
943
+ initialValue: def.default !== void 0 ? Boolean(def.default) : false
944
+ });
945
+ return handleCancel(value);
946
+ }
947
+ case "number": {
948
+ const value = await p.text({
949
+ message: def.description || name,
950
+ placeholder: def.default !== void 0 ? String(def.default) : void 0,
951
+ defaultValue: def.default !== void 0 ? String(def.default) : void 0,
952
+ validate(input) {
953
+ if (isNaN(Number(input))) return "Must be a number";
954
+ }
955
+ });
956
+ return Number(handleCancel(value));
957
+ }
958
+ case "string":
959
+ default: {
960
+ const value = await p.text({
961
+ message: def.description || name,
962
+ placeholder: def.default !== void 0 ? String(def.default) : void 0,
963
+ defaultValue: def.default !== void 0 ? String(def.default) : void 0,
964
+ validate(input) {
965
+ if (def.required && !input) return `${name} is required`;
966
+ }
967
+ });
968
+ return handleCancel(value);
969
+ }
970
+ }
971
+ }
972
+ async function promptForVariables(variableDefs, provided, noInput = false) {
973
+ const values = {};
974
+ for (const [name, def] of Object.entries(variableDefs)) {
975
+ if (def.type === "resolved") continue;
976
+ if (provided[name] !== void 0) {
977
+ values[name] = coerceValue2(provided[name], def);
978
+ } else if (def.default !== void 0) {
979
+ if (isInteractive() && !noInput) {
980
+ values[name] = await promptForVariable(name, def);
981
+ } else {
982
+ values[name] = def.default;
983
+ }
984
+ } else if (def.required) {
985
+ if (isInteractive() && !noInput) {
986
+ values[name] = await promptForVariable(name, def);
987
+ } else {
988
+ throw new Error(`Missing required variable: ${name} -- ${def.description}`);
989
+ }
990
+ }
991
+ }
992
+ return values;
993
+ }
994
+ function coerceValue2(raw, def) {
995
+ switch (def.type) {
996
+ case "number":
997
+ return Number(raw);
998
+ case "boolean":
999
+ return raw === "true" || raw === "1" || raw === "yes";
1000
+ case "enum":
1001
+ if (def.options && !def.options.includes(raw)) {
1002
+ throw new Error(
1003
+ `Invalid value "${raw}" for enum variable. Options: ${def.options.join(", ")}`
1004
+ );
1005
+ }
1006
+ return raw;
1007
+ default:
1008
+ return raw;
1009
+ }
1010
+ }
1011
+ async function withSpinner(message, fn, successMessage) {
1012
+ if (!isInteractive()) {
1013
+ return fn();
1014
+ }
1015
+ const s = p.spinner();
1016
+ s.start(message);
1017
+ try {
1018
+ const result = await fn();
1019
+ s.stop(successMessage ?? message);
1020
+ return result;
1021
+ } catch (err) {
1022
+ s.stop(`Failed: ${message}`);
1023
+ throw err;
1024
+ }
1025
+ }
1026
+
663
1027
  // src/lib/installer.ts
664
1028
  function getInstallDir(type) {
665
1029
  switch (type) {
@@ -679,38 +1043,60 @@ function contentHash(content) {
679
1043
  }
680
1044
  async function installPackage(packageName, options = {}) {
681
1045
  const projectDir = options.projectDir ?? process.cwd();
1046
+ const interactive = options.interactive ?? (isInteractive() && !options.noInput);
682
1047
  const locked = getLockedVersion(packageName, projectDir);
683
1048
  if (locked && !options.version) {
684
1049
  logger.dim(`${packageName}@${locked.version} already installed`);
685
1050
  return;
686
1051
  }
687
- logger.info(`Resolving ${packageName}...`);
688
- const { version, metadata } = await resolveVersion(packageName, options.version);
689
- const versionMeta = await fetchVersionMetadata(packageName, version);
690
- logger.info(`Fetching ${packageName}@${version}...`);
691
- const basePath = versionMeta.source.path ? `${versionMeta.source.path}/` : "";
692
- const manifestRaw = await fetchFileAtTag(
693
- versionMeta.source.repository,
694
- versionMeta.source.tag,
695
- `${basePath}planmode.yaml`
696
- );
697
- const manifest = parseManifest(manifestRaw);
698
- let content;
699
- if (manifest.content) {
700
- content = manifest.content;
701
- } else if (manifest.content_file) {
702
- content = await fetchFileAtTag(
1052
+ const resolveAndFetch = async () => {
1053
+ const { version: version2, metadata: metadata2 } = await resolveVersion(packageName, options.version);
1054
+ const versionMeta2 = await fetchVersionMetadata(packageName, version2);
1055
+ return { version: version2, metadata: metadata2, versionMeta: versionMeta2 };
1056
+ };
1057
+ const { version, metadata, versionMeta } = interactive ? await withSpinner(
1058
+ `Resolving ${packageName}...`,
1059
+ resolveAndFetch,
1060
+ `Resolved ${packageName}`
1061
+ ) : await (async () => {
1062
+ logger.info(`Resolving ${packageName}...`);
1063
+ return resolveAndFetch();
1064
+ })();
1065
+ const fetchContent = async () => {
1066
+ const basePath = versionMeta.source.path ? `${versionMeta.source.path}/` : "";
1067
+ const manifestRaw = await fetchFileAtTag(
703
1068
  versionMeta.source.repository,
704
1069
  versionMeta.source.tag,
705
- `${basePath}${manifest.content_file}`
1070
+ `${basePath}planmode.yaml`
706
1071
  );
707
- } else {
708
- throw new Error("Package has no content or content_file");
709
- }
1072
+ const manifest2 = parseManifest(manifestRaw);
1073
+ let content2;
1074
+ if (manifest2.content) {
1075
+ content2 = manifest2.content;
1076
+ } else if (manifest2.content_file) {
1077
+ content2 = await fetchFileAtTag(
1078
+ versionMeta.source.repository,
1079
+ versionMeta.source.tag,
1080
+ `${basePath}${manifest2.content_file}`
1081
+ );
1082
+ } else {
1083
+ throw new Error("Package has no content or content_file");
1084
+ }
1085
+ return { manifest: manifest2, content: content2 };
1086
+ };
1087
+ const { manifest, content: rawContent } = interactive ? await withSpinner(
1088
+ `Fetching ${packageName}@${version}...`,
1089
+ fetchContent,
1090
+ `Fetched ${packageName}@${version}`
1091
+ ) : await (async () => {
1092
+ logger.info(`Fetching ${packageName}@${version}...`);
1093
+ return fetchContent();
1094
+ })();
1095
+ let content = rawContent;
710
1096
  if (manifest.variables && Object.keys(manifest.variables).length > 0) {
711
1097
  const provided = options.variables ?? {};
712
- if (options.noInput) {
713
- const values = collectVariableValues(manifest.variables, provided);
1098
+ if (interactive) {
1099
+ const values = await promptForVariables(manifest.variables, provided, false);
714
1100
  content = renderTemplate(content, values);
715
1101
  } else {
716
1102
  const values = collectVariableValues(manifest.variables, provided);
@@ -772,7 +1158,8 @@ async function installPackage(packageName, options = {}) {
772
1158
  await installPackage(name, {
773
1159
  version: range === "*" ? void 0 : range,
774
1160
  projectDir,
775
- noInput: options.noInput
1161
+ noInput: options.noInput,
1162
+ interactive: options.interactive
776
1163
  });
777
1164
  }
778
1165
  }
@@ -917,141 +1304,154 @@ function createPackage(options) {
917
1304
  }
918
1305
 
919
1306
  // src/lib/publisher.ts
1307
+ init_logger();
920
1308
  async function publishPackage(options = {}) {
921
1309
  const cwd = options.projectDir ?? process.cwd();
1310
+ const interactive = options.interactive ?? false;
922
1311
  const token = options.token ?? getGitHubToken();
923
1312
  if (!token) {
924
1313
  throw new Error("Not authenticated. Run `planmode login` first.");
925
1314
  }
926
- logger.info("Reading planmode.yaml...");
927
- const manifest = readManifest(cwd);
928
- const errors = validateManifest(manifest, true);
929
- if (errors.length > 0) {
930
- throw new Error(`Invalid manifest:
1315
+ const doValidate = async () => {
1316
+ const manifest2 = readManifest(cwd);
1317
+ const errors = validateManifest(manifest2, true);
1318
+ if (errors.length > 0) {
1319
+ throw new Error(`Invalid manifest:
931
1320
  ${errors.map((e) => ` - ${e}`).join("\n")}`);
932
- }
1321
+ }
1322
+ return manifest2;
1323
+ };
1324
+ const manifest = interactive ? await withSpinner("Validating manifest...", doValidate, "Manifest valid") : await (async () => {
1325
+ logger.info("Reading planmode.yaml...");
1326
+ return doValidate();
1327
+ })();
933
1328
  const remoteUrl = await getRemoteUrl(cwd);
934
1329
  if (!remoteUrl) {
935
1330
  throw new Error("No git remote found. Push your code to GitHub first.");
936
1331
  }
937
1332
  const sha = await getHeadSha(cwd);
938
1333
  const tag = `v${manifest.version}`;
939
- logger.info(`Creating tag ${tag}...`);
940
- try {
941
- await createTag(cwd, tag);
942
- } catch {
943
- logger.dim(`Tag ${tag} already exists, using existing`);
944
- }
945
- try {
946
- await pushTag(cwd, tag);
1334
+ const doTag = async () => {
1335
+ try {
1336
+ await createTag(cwd, tag);
1337
+ } catch {
1338
+ }
1339
+ try {
1340
+ await pushTag(cwd, tag);
1341
+ } catch {
1342
+ }
1343
+ };
1344
+ if (interactive) {
1345
+ await withSpinner(`Creating tag ${tag}...`, doTag, `Tag ${tag} ready`);
1346
+ } else {
1347
+ logger.info(`Creating tag ${tag}...`);
1348
+ await doTag();
947
1349
  logger.success(`Pushed tag ${tag}`);
948
- } catch {
949
- logger.dim(`Tag ${tag} already pushed`);
950
1350
  }
951
- logger.info("Submitting to registry...");
952
- const headers = {
953
- Authorization: `Bearer ${token}`,
954
- Accept: "application/vnd.github.v3+json",
955
- "User-Agent": "planmode-cli",
956
- "Content-Type": "application/json"
957
- };
958
- await fetch("https://api.github.com/repos/kaihannonen/planmode.org/forks", {
959
- method: "POST",
960
- headers
961
- });
962
- const userRes = await fetch("https://api.github.com/user", { headers });
963
- if (!userRes.ok) {
964
- throw new Error("Failed to authenticate with GitHub. Check your token.");
965
- }
966
- const user = await userRes.json();
967
- const repoPath = remoteUrl.replace(/^https?:\/\//, "").replace(/\.git$/, "");
968
- const metadataContent = JSON.stringify(
969
- {
970
- name: manifest.name,
971
- description: manifest.description,
972
- author: manifest.author,
973
- license: manifest.license,
974
- repository: repoPath,
975
- category: manifest.category ?? "other",
976
- tags: manifest.tags ?? [],
977
- type: manifest.type,
978
- models: manifest.models ?? [],
979
- latest_version: manifest.version,
980
- versions: [manifest.version],
981
- downloads: 0,
982
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
983
- updated_at: (/* @__PURE__ */ new Date()).toISOString(),
984
- dependencies: manifest.dependencies,
985
- variables: manifest.variables
986
- },
987
- null,
988
- 2
989
- );
990
- const versionContent = JSON.stringify(
991
- {
992
- version: manifest.version,
993
- published_at: (/* @__PURE__ */ new Date()).toISOString(),
994
- source: {
1351
+ const doSubmit = async () => {
1352
+ const headers = {
1353
+ Authorization: `Bearer ${token}`,
1354
+ Accept: "application/vnd.github.v3+json",
1355
+ "User-Agent": "planmode-cli",
1356
+ "Content-Type": "application/json"
1357
+ };
1358
+ await fetch("https://api.github.com/repos/kaihannonen/planmode.org/forks", {
1359
+ method: "POST",
1360
+ headers
1361
+ });
1362
+ const userRes = await fetch("https://api.github.com/user", { headers });
1363
+ if (!userRes.ok) {
1364
+ throw new Error("Failed to authenticate with GitHub. Check your token.");
1365
+ }
1366
+ const user = await userRes.json();
1367
+ const repoPath = remoteUrl.replace(/^https?:\/\//, "").replace(/\.git$/, "");
1368
+ const metadataContent = JSON.stringify(
1369
+ {
1370
+ name: manifest.name,
1371
+ description: manifest.description,
1372
+ author: manifest.author,
1373
+ license: manifest.license,
995
1374
  repository: repoPath,
996
- tag,
997
- sha
1375
+ category: manifest.category ?? "other",
1376
+ tags: manifest.tags ?? [],
1377
+ type: manifest.type,
1378
+ models: manifest.models ?? [],
1379
+ latest_version: manifest.version,
1380
+ versions: [manifest.version],
1381
+ downloads: 0,
1382
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
1383
+ updated_at: (/* @__PURE__ */ new Date()).toISOString(),
1384
+ dependencies: manifest.dependencies,
1385
+ variables: manifest.variables
998
1386
  },
999
- files: ["planmode.yaml", manifest.content_file ?? "inline"],
1000
- content_hash: `sha256:${sha.slice(0, 16)}`
1001
- },
1002
- null,
1003
- 2
1004
- );
1005
- const branchName = `add-${manifest.name}-${manifest.version}`;
1006
- const refRes = await fetch(
1007
- `https://api.github.com/repos/${user.login}/planmode.org/git/ref/heads/main`,
1008
- { headers }
1009
- );
1010
- if (!refRes.ok) {
1011
- throw new Error("Failed to access registry fork. Make sure the fork exists.");
1012
- }
1013
- const refData = await refRes.json();
1014
- const baseSha = refData.object.sha;
1015
- await fetch(`https://api.github.com/repos/${user.login}/planmode.org/git/refs`, {
1016
- method: "POST",
1017
- headers,
1018
- body: JSON.stringify({
1019
- ref: `refs/heads/${branchName}`,
1020
- sha: baseSha
1021
- })
1022
- });
1023
- await fetch(
1024
- `https://api.github.com/repos/${user.login}/planmode.org/contents/registry/packages/${manifest.name}/metadata.json`,
1025
- {
1026
- method: "PUT",
1387
+ null,
1388
+ 2
1389
+ );
1390
+ const versionContent = JSON.stringify(
1391
+ {
1392
+ version: manifest.version,
1393
+ published_at: (/* @__PURE__ */ new Date()).toISOString(),
1394
+ source: {
1395
+ repository: repoPath,
1396
+ tag,
1397
+ sha
1398
+ },
1399
+ files: ["planmode.yaml", manifest.content_file ?? "inline"],
1400
+ content_hash: `sha256:${sha.slice(0, 16)}`
1401
+ },
1402
+ null,
1403
+ 2
1404
+ );
1405
+ const branchName = `add-${manifest.name}-${manifest.version}`;
1406
+ const refRes = await fetch(
1407
+ `https://api.github.com/repos/${user.login}/planmode.org/git/ref/heads/main`,
1408
+ { headers }
1409
+ );
1410
+ if (!refRes.ok) {
1411
+ throw new Error("Failed to access registry fork. Make sure the fork exists.");
1412
+ }
1413
+ const refData = await refRes.json();
1414
+ const baseSha = refData.object.sha;
1415
+ await fetch(`https://api.github.com/repos/${user.login}/planmode.org/git/refs`, {
1416
+ method: "POST",
1027
1417
  headers,
1028
1418
  body: JSON.stringify({
1029
- message: `Add ${manifest.name}@${manifest.version}`,
1030
- content: Buffer.from(metadataContent).toString("base64"),
1031
- branch: branchName
1419
+ ref: `refs/heads/${branchName}`,
1420
+ sha: baseSha
1032
1421
  })
1033
- }
1034
- );
1035
- await fetch(
1036
- `https://api.github.com/repos/${user.login}/planmode.org/contents/registry/packages/${manifest.name}/versions/${manifest.version}.json`,
1037
- {
1038
- method: "PUT",
1422
+ });
1423
+ await fetch(
1424
+ `https://api.github.com/repos/${user.login}/planmode.org/contents/registry/packages/${manifest.name}/metadata.json`,
1425
+ {
1426
+ method: "PUT",
1427
+ headers,
1428
+ body: JSON.stringify({
1429
+ message: `Add ${manifest.name}@${manifest.version}`,
1430
+ content: Buffer.from(metadataContent).toString("base64"),
1431
+ branch: branchName
1432
+ })
1433
+ }
1434
+ );
1435
+ await fetch(
1436
+ `https://api.github.com/repos/${user.login}/planmode.org/contents/registry/packages/${manifest.name}/versions/${manifest.version}.json`,
1437
+ {
1438
+ method: "PUT",
1439
+ headers,
1440
+ body: JSON.stringify({
1441
+ message: `Add ${manifest.name}@${manifest.version} version metadata`,
1442
+ content: Buffer.from(versionContent).toString("base64"),
1443
+ branch: branchName
1444
+ })
1445
+ }
1446
+ );
1447
+ const prRes = await fetch("https://api.github.com/repos/kaihannonen/planmode.org/pulls", {
1448
+ method: "POST",
1039
1449
  headers,
1040
1450
  body: JSON.stringify({
1041
- message: `Add ${manifest.name}@${manifest.version} version metadata`,
1042
- content: Buffer.from(versionContent).toString("base64"),
1043
- branch: branchName
1044
- })
1045
- }
1046
- );
1047
- const prRes = await fetch("https://api.github.com/repos/kaihannonen/planmode.org/pulls", {
1048
- method: "POST",
1049
- headers,
1050
- body: JSON.stringify({
1051
- title: `Add ${manifest.name}@${manifest.version}`,
1052
- head: `${user.login}:${branchName}`,
1053
- base: "main",
1054
- body: `## New package: ${manifest.name}
1451
+ title: `Add ${manifest.name}@${manifest.version}`,
1452
+ head: `${user.login}:${branchName}`,
1453
+ base: "main",
1454
+ body: `## New package: ${manifest.name}
1055
1455
 
1056
1456
  - **Type:** ${manifest.type}
1057
1457
  - **Version:** ${manifest.version}
@@ -1059,17 +1459,30 @@ ${errors.map((e) => ` - ${e}`).join("\n")}`);
1059
1459
  - **Author:** ${manifest.author}
1060
1460
 
1061
1461
  Submitted via \`planmode publish\`.`
1062
- })
1063
- });
1064
- if (!prRes.ok) {
1065
- const err = await prRes.text();
1066
- throw new Error(`Failed to create PR: ${err}`);
1462
+ })
1463
+ });
1464
+ if (!prRes.ok) {
1465
+ const err = await prRes.text();
1466
+ throw new Error(`Failed to create PR: ${err}`);
1467
+ }
1468
+ const pr = await prRes.json();
1469
+ return pr.html_url;
1470
+ };
1471
+ let prUrl;
1472
+ if (interactive) {
1473
+ prUrl = await withSpinner(
1474
+ "Submitting to registry...",
1475
+ doSubmit,
1476
+ "Submitted to registry"
1477
+ );
1478
+ } else {
1479
+ logger.info("Submitting to registry...");
1480
+ prUrl = await doSubmit();
1481
+ logger.success(`Published ${manifest.name}@${manifest.version}`);
1482
+ logger.info(`PR: ${prUrl}`);
1067
1483
  }
1068
- const pr = await prRes.json();
1069
- logger.success(`Published ${manifest.name}@${manifest.version}`);
1070
- logger.info(`PR: ${pr.html_url}`);
1071
1484
  return {
1072
- prUrl: pr.html_url,
1485
+ prUrl,
1073
1486
  packageName: manifest.name,
1074
1487
  version: manifest.version
1075
1488
  };
@@ -1692,6 +2105,7 @@ function detectCategory(data) {
1692
2105
  }
1693
2106
 
1694
2107
  // src/mcp.ts
2108
+ init_context();
1695
2109
  function withCapture(fn) {
1696
2110
  logger.capture();
1697
2111
  try {
@@ -1714,9 +2128,9 @@ async function withCaptureAsync(fn) {
1714
2128
  throw Object.assign(err, { capturedMessages: messages });
1715
2129
  }
1716
2130
  }
1717
- function textResult(text, isError = false) {
2131
+ function textResult(text2, isError = false) {
1718
2132
  return {
1719
- content: [{ type: "text", text }],
2133
+ content: [{ type: "text", text: text2 }],
1720
2134
  isError
1721
2135
  };
1722
2136
  }
@@ -1969,14 +2383,14 @@ server.registerTool(
1969
2383
  const entry = lockfile.packages[packageName];
1970
2384
  if (!entry) {
1971
2385
  const candidates = [
1972
- path12.join(dir, "plans", `${packageName}.md`),
1973
- path12.join(dir, ".claude", "rules", `${packageName}.md`),
1974
- path12.join(dir, "prompts", `${packageName}.md`)
2386
+ path13.join(dir, "plans", `${packageName}.md`),
2387
+ path13.join(dir, ".claude", "rules", `${packageName}.md`),
2388
+ path13.join(dir, "prompts", `${packageName}.md`)
1975
2389
  ];
1976
2390
  for (const candidate of candidates) {
1977
- if (fs12.existsSync(candidate)) {
1978
- const content2 = fs12.readFileSync(candidate, "utf-8");
1979
- const relativePath = path12.relative(dir, candidate);
2391
+ if (fs13.existsSync(candidate)) {
2392
+ const content2 = fs13.readFileSync(candidate, "utf-8");
2393
+ const relativePath = path13.relative(dir, candidate);
1980
2394
  return textResult(`# ${packageName}
1981
2395
  **Location:** ${relativePath}
1982
2396
 
@@ -1990,14 +2404,14 @@ ${content2}`);
1990
2404
  true
1991
2405
  );
1992
2406
  }
1993
- const fullPath = path12.join(dir, entry.installed_to);
1994
- if (!fs12.existsSync(fullPath)) {
2407
+ const fullPath = path13.join(dir, entry.installed_to);
2408
+ if (!fs13.existsSync(fullPath)) {
1995
2409
  return textResult(
1996
2410
  `Package '${packageName}' is in the lockfile but the file is missing at ${entry.installed_to}. Try reinstalling with planmode_install.`,
1997
2411
  true
1998
2412
  );
1999
2413
  }
2000
- const content = fs12.readFileSync(fullPath, "utf-8");
2414
+ const content = fs13.readFileSync(fullPath, "utf-8");
2001
2415
  return textResult(
2002
2416
  `# ${packageName} (${entry.type} v${entry.version})
2003
2417
  **Location:** ${entry.installed_to}
@@ -2184,17 +2598,17 @@ server.registerTool(
2184
2598
  async ({ prompt: promptName, variables, projectDir }) => {
2185
2599
  try {
2186
2600
  const dir = projectDir ?? process.cwd();
2187
- const localPath = path12.join(dir, "prompts", `${promptName}.md`);
2188
- const localManifestPath = path12.join(dir, "prompts", promptName, "planmode.yaml");
2601
+ const localPath = path13.join(dir, "prompts", `${promptName}.md`);
2602
+ const localManifestPath = path13.join(dir, "prompts", promptName, "planmode.yaml");
2189
2603
  let content;
2190
2604
  let manifest;
2191
- if (fs12.existsSync(localManifestPath)) {
2192
- const raw = fs12.readFileSync(localManifestPath, "utf-8");
2605
+ if (fs13.existsSync(localManifestPath)) {
2606
+ const raw = fs13.readFileSync(localManifestPath, "utf-8");
2193
2607
  manifest = parseManifest(raw);
2194
- const promptDir = path12.join(dir, "prompts", promptName);
2608
+ const promptDir = path13.join(dir, "prompts", promptName);
2195
2609
  content = readPackageContent(promptDir, manifest);
2196
- } else if (fs12.existsSync(localPath)) {
2197
- content = fs12.readFileSync(localPath, "utf-8");
2610
+ } else if (fs13.existsSync(localPath)) {
2611
+ content = fs13.readFileSync(localPath, "utf-8");
2198
2612
  } else {
2199
2613
  return textResult(
2200
2614
  `Prompt '${promptName}' not found locally. Install it first using the planmode_install tool.`,
@@ -2376,6 +2790,128 @@ server.registerTool(
2376
2790
  }
2377
2791
  }
2378
2792
  );
2793
+ server.registerTool(
2794
+ "planmode_context_add",
2795
+ {
2796
+ description: "Add a document directory to the project context. Indexes all text-based files (md, pdf, txt, json, yaml, csv, html, etc.) and stores their metadata in .planmode/context.yaml. The AI can then see what documents are available and read them on demand.",
2797
+ inputSchema: {
2798
+ path: z.string().describe("Path to the document directory (relative to project or absolute)"),
2799
+ name: z.string().optional().describe("Human-readable label for this directory"),
2800
+ projectDir: z.string().optional().describe("Project directory (default: current working directory)")
2801
+ }
2802
+ },
2803
+ async ({ path: dirPath, name, projectDir }) => {
2804
+ try {
2805
+ const { result, messages } = withCapture(
2806
+ () => addContextRepo(dirPath, { name, projectDir })
2807
+ );
2808
+ const breakdown = result.files.length > 0 ? `
2809
+ Types: ${getTypeBreakdownText(result)}` : "";
2810
+ return textResult(
2811
+ formatMessages(
2812
+ messages,
2813
+ `Added "${name ?? result.repo.path}" \u2014 ${result.file_count} file(s), ${formatSize(result.total_size)}${breakdown}`
2814
+ )
2815
+ );
2816
+ } catch (err) {
2817
+ return errorResult("Error adding context repo", err);
2818
+ }
2819
+ }
2820
+ );
2821
+ server.registerTool(
2822
+ "planmode_context_remove",
2823
+ {
2824
+ description: "Remove a document directory from the project context",
2825
+ inputSchema: {
2826
+ pathOrName: z.string().describe("Path or name of the context repo to remove"),
2827
+ projectDir: z.string().optional().describe("Project directory (default: current working directory)")
2828
+ }
2829
+ },
2830
+ async ({ pathOrName, projectDir }) => {
2831
+ try {
2832
+ const { messages } = withCapture(
2833
+ () => removeContextRepo(pathOrName, projectDir)
2834
+ );
2835
+ return textResult(formatMessages(messages) || `Removed "${pathOrName}" from context.`);
2836
+ } catch (err) {
2837
+ return errorResult("Error removing context repo", err);
2838
+ }
2839
+ }
2840
+ );
2841
+ server.registerTool(
2842
+ "planmode_context_list",
2843
+ {
2844
+ description: "List all document directories in the project context with file counts, sizes, and type breakdowns. Use this to see what reference documents are available for the project.",
2845
+ inputSchema: {
2846
+ detailed: z.boolean().optional().describe("Include full file listings for each repo (default: false, shows only summaries)"),
2847
+ projectDir: z.string().optional().describe("Project directory (default: current working directory)")
2848
+ }
2849
+ },
2850
+ async ({ detailed, projectDir }) => {
2851
+ try {
2852
+ const summary = getContextSummary(projectDir);
2853
+ if (summary.totalRepos === 0) {
2854
+ return textResult("No context repos configured. Use planmode_context_add to add a document directory.");
2855
+ }
2856
+ const lines = [
2857
+ `**${summary.totalRepos} context repo(s)** \u2014 ${summary.totalFiles} file(s), ${formatSize(summary.totalSize)}`,
2858
+ ""
2859
+ ];
2860
+ for (const repo of summary.repos) {
2861
+ lines.push(`### ${repo.name}`);
2862
+ lines.push(`- **Path:** ${repo.path}`);
2863
+ lines.push(`- **Files:** ${repo.fileCount} (${formatSize(repo.totalSize)})`);
2864
+ if (repo.typeBreakdown.length > 0) {
2865
+ lines.push(`- **Types:** ${repo.typeBreakdown.join(", ")}`);
2866
+ }
2867
+ lines.push(`- **Indexed:** ${repo.indexedAt}`);
2868
+ if (detailed) {
2869
+ const index = (await Promise.resolve().then(() => (init_context(), context_exports))).readContextIndex(projectDir);
2870
+ const repoIndex = index.repos.find(
2871
+ (r) => r.repo.path === repo.path || r.repo.name === repo.name
2872
+ );
2873
+ if (repoIndex && repoIndex.files.length > 0) {
2874
+ lines.push("", "**Files:**");
2875
+ for (const file of repoIndex.files) {
2876
+ lines.push(`- \`${file.path}\` (${file.extension}, ${formatSize(file.size)})`);
2877
+ }
2878
+ }
2879
+ }
2880
+ lines.push("");
2881
+ }
2882
+ return textResult(lines.join("\n"));
2883
+ } catch (err) {
2884
+ return errorResult("Error listing context", err);
2885
+ }
2886
+ }
2887
+ );
2888
+ server.registerTool(
2889
+ "planmode_context_reindex",
2890
+ {
2891
+ description: "Re-scan files in one or all context directories to update the file index",
2892
+ inputSchema: {
2893
+ pathOrName: z.string().optional().describe("Path or name of a specific repo to reindex (omit to reindex all)"),
2894
+ projectDir: z.string().optional().describe("Project directory (default: current working directory)")
2895
+ }
2896
+ },
2897
+ async ({ pathOrName, projectDir }) => {
2898
+ try {
2899
+ const { messages } = withCapture(
2900
+ () => reindexContext(pathOrName, projectDir)
2901
+ );
2902
+ return textResult(formatMessages(messages) || "Reindex complete.");
2903
+ } catch (err) {
2904
+ return errorResult("Error reindexing context", err);
2905
+ }
2906
+ }
2907
+ );
2908
+ function getTypeBreakdownText(repoIndex) {
2909
+ const counts = /* @__PURE__ */ new Map();
2910
+ for (const file of repoIndex.files) {
2911
+ counts.set(file.extension, (counts.get(file.extension) ?? 0) + 1);
2912
+ }
2913
+ return Array.from(counts.entries()).sort((a, b) => b[1] - a[1]).map(([ext, count]) => `${ext}: ${count}`).join(", ");
2914
+ }
2379
2915
  server.registerResource(
2380
2916
  "installed-packages",
2381
2917
  new ResourceTemplate("planmode://packages/{name}", {
@@ -2408,10 +2944,10 @@ server.registerResource(
2408
2944
  }]
2409
2945
  };
2410
2946
  }
2411
- const fullPath = path12.join(process.cwd(), entry.installed_to);
2947
+ const fullPath = path13.join(process.cwd(), entry.installed_to);
2412
2948
  let content;
2413
2949
  try {
2414
- content = fs12.readFileSync(fullPath, "utf-8");
2950
+ content = fs13.readFileSync(fullPath, "utf-8");
2415
2951
  } catch {
2416
2952
  content = `File not found at ${entry.installed_to}. The package may need to be reinstalled.`;
2417
2953
  }