pantrybase 0.0.0-stage → 0.2.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/cli.js ADDED
@@ -0,0 +1,441 @@
1
+ import {
2
+ Embedder,
3
+ MODEL_ID,
4
+ dataPaths,
5
+ homeDir,
6
+ log,
7
+ modelsDir,
8
+ openContext,
9
+ packageRoot,
10
+ packageVersion,
11
+ reconcile,
12
+ resolveProjectRoot,
13
+ searchRecipes,
14
+ startUiServer
15
+ } from "./chunk-QUWSBZ2V.js";
16
+
17
+ // src/cli.ts
18
+ import { spawn } from "child_process";
19
+
20
+ // src/install/install.ts
21
+ import fs from "fs";
22
+ import path from "path";
23
+
24
+ // src/install/run.ts
25
+ import { spawnSync } from "child_process";
26
+ function run(cmd, args, opts = {}) {
27
+ const r = spawnSync(cmd, args, {
28
+ cwd: opts.cwd,
29
+ env: { ...process.env, ...opts.env },
30
+ encoding: "utf8",
31
+ timeout: opts.timeoutMs ?? 10 * 6e4,
32
+ maxBuffer: 64 * 1024 * 1024
33
+ });
34
+ return {
35
+ ok: r.status === 0 && !r.error,
36
+ code: r.status,
37
+ stdout: r.stdout ?? "",
38
+ stderr: r.error ? String(r.error) : r.stderr ?? ""
39
+ };
40
+ }
41
+ function tail(s, n = 400) {
42
+ const t = s.trim();
43
+ return t.length > n ? `\u2026${t.slice(-n)}` : t;
44
+ }
45
+
46
+ // src/install/claude.ts
47
+ var PLUGIN_ID = "pantrybase@pantrybase";
48
+ var MARKETPLACE = "pantrybase";
49
+ function json(args) {
50
+ const r = run("claude", args, { timeoutMs: 6e4 });
51
+ if (!r.ok) return void 0;
52
+ try {
53
+ return JSON.parse(r.stdout);
54
+ } catch {
55
+ return void 0;
56
+ }
57
+ }
58
+ function claudeVersion() {
59
+ const r = run("claude", ["--version"], { timeoutMs: 2e4 });
60
+ return r.ok ? r.stdout.trim() : null;
61
+ }
62
+ function registeredPlugin() {
63
+ return json(["plugin", "list", "--json"])?.find((p) => p.id === PLUGIN_ID);
64
+ }
65
+ function registeredMarketplace() {
66
+ return json(["plugin", "marketplace", "list", "--json"])?.find(
67
+ (m) => m.name === MARKETPLACE
68
+ );
69
+ }
70
+ function registerPlugin(stableRoot) {
71
+ const steps = [];
72
+ if (!claudeVersion()) {
73
+ return [
74
+ {
75
+ step: "claude CLI",
76
+ ok: false,
77
+ detail: "`claude` not found on PATH; install Claude Code, then re-run `pantrybase install`"
78
+ }
79
+ ];
80
+ }
81
+ const market = registeredMarketplace();
82
+ if (market && (market.path ?? market.installLocation) !== stableRoot) {
83
+ run("claude", ["plugin", "marketplace", "remove", MARKETPLACE], { timeoutMs: 6e4 });
84
+ }
85
+ const again = market && (market.path ?? market.installLocation) === stableRoot;
86
+ const m = again ? run("claude", ["plugin", "marketplace", "update", MARKETPLACE], { timeoutMs: 12e4 }) : run("claude", ["plugin", "marketplace", "add", stableRoot], { timeoutMs: 12e4 });
87
+ steps.push({
88
+ step: again ? "marketplace update" : "marketplace add",
89
+ ok: m.ok,
90
+ detail: m.ok ? stableRoot : tail(m.stderr || m.stdout)
91
+ });
92
+ if (!m.ok) return steps;
93
+ const installed = registeredPlugin();
94
+ const p = installed ? run("claude", ["plugin", "update", PLUGIN_ID, "--scope", "user"], { timeoutMs: 12e4 }) : run("claude", ["plugin", "install", PLUGIN_ID, "--scope", "user"], { timeoutMs: 12e4 });
95
+ steps.push({
96
+ step: installed ? "plugin update" : "plugin install",
97
+ ok: p.ok,
98
+ detail: p.ok ? `${PLUGIN_ID} (user scope)` : tail(p.stderr || p.stdout)
99
+ });
100
+ return steps;
101
+ }
102
+ function unregisterPlugin() {
103
+ const steps = [];
104
+ if (registeredPlugin()) {
105
+ const u = run("claude", ["plugin", "uninstall", PLUGIN_ID, "--scope", "user"], {
106
+ timeoutMs: 12e4
107
+ });
108
+ steps.push({
109
+ step: "plugin uninstall",
110
+ ok: u.ok,
111
+ detail: u.ok ? PLUGIN_ID : tail(u.stderr || u.stdout)
112
+ });
113
+ }
114
+ if (registeredMarketplace()) {
115
+ const r = run("claude", ["plugin", "marketplace", "remove", MARKETPLACE], {
116
+ timeoutMs: 6e4
117
+ });
118
+ steps.push({
119
+ step: "marketplace remove",
120
+ ok: r.ok,
121
+ detail: r.ok ? MARKETPLACE : tail(r.stderr || r.stdout)
122
+ });
123
+ }
124
+ return steps;
125
+ }
126
+
127
+ // src/install/install.ts
128
+ function readJson(file) {
129
+ try {
130
+ return JSON.parse(fs.readFileSync(file, "utf8"));
131
+ } catch {
132
+ return void 0;
133
+ }
134
+ }
135
+ function installRecord() {
136
+ return readJson(path.join(homeDir(), "install.json"));
137
+ }
138
+ function materialize(from) {
139
+ const home = homeDir();
140
+ const tmp = path.join(home, "tmp");
141
+ const pluginDir = path.join(home, "plugin");
142
+ fs.mkdirSync(tmp, { recursive: true });
143
+ fs.mkdirSync(pluginDir, { recursive: true });
144
+ const steps = [];
145
+ const isDev = fs.existsSync(path.join(from, "src")) && fs.existsSync(path.join(from, "node_modules", ".bin", "tsup"));
146
+ for (const old of fs.readdirSync(tmp)) if (old.endsWith(".tgz")) fs.rmSync(path.join(tmp, old));
147
+ const pack = run(
148
+ "npm",
149
+ ["pack", from, "--pack-destination", tmp, "--json", ...isDev ? [] : ["--ignore-scripts"]],
150
+ {
151
+ cwd: from
152
+ }
153
+ );
154
+ if (!pack.ok) throw new Error(`npm pack failed: ${tail(pack.stderr || pack.stdout)}`);
155
+ const packed = JSON.parse(pack.stdout.slice(pack.stdout.indexOf("[")))[0];
156
+ if (!packed) throw new Error("npm pack produced no tarball");
157
+ steps.push({
158
+ step: "pack",
159
+ ok: true,
160
+ detail: `${packed.filename}${isDev ? " (built from source)" : ""}`
161
+ });
162
+ fs.writeFileSync(
163
+ path.join(pluginDir, "package.json"),
164
+ `${JSON.stringify({ private: true, dependencies: { pantrybase: `file:../tmp/${packed.filename}` } }, null, 2)}
165
+ `
166
+ );
167
+ fs.rmSync(path.join(pluginDir, "node_modules", "pantrybase"), { recursive: true, force: true });
168
+ fs.rmSync(path.join(pluginDir, "package-lock.json"), { force: true });
169
+ const inst = run("npm", ["install", "--omit=dev", "--no-audit", "--no-fund"], { cwd: pluginDir });
170
+ if (!inst.ok) throw new Error(`npm install failed: ${tail(inst.stderr || inst.stdout)}`);
171
+ const stableRoot = path.join(pluginDir, "node_modules", "pantrybase");
172
+ const fresh = path.join(from, "dist", "server.js");
173
+ const installed = path.join(stableRoot, "dist", "server.js");
174
+ if (isDev && fs.existsSync(fresh) && fs.readFileSync(fresh, "utf8") !== fs.readFileSync(installed, "utf8")) {
175
+ throw new Error(`installed copy at ${stableRoot} does not match the freshly built package`);
176
+ }
177
+ steps.push({ step: "install dependencies", ok: true, detail: stableRoot });
178
+ return { stableRoot, version: packed.version, steps };
179
+ }
180
+ function smokeTest(stableRoot, node = process.execPath) {
181
+ const native = run(
182
+ node,
183
+ [
184
+ "-e",
185
+ `const r=require('node:module').createRequire(${JSON.stringify(path.join(stableRoot, "package.json"))});const D=r('better-sqlite3');const v=r('sqlite-vec');const db=new D(':memory:');v.load(db);db.exec("create virtual table t using fts5(x, tokenize='trigram')");const s=r('sharp');console.log(JSON.stringify({sqlite:db.prepare('select sqlite_version() v').get().v,vec:db.prepare('select vec_version() v').get().v,vips:s.versions.vips}))`
186
+ ],
187
+ { timeoutMs: 6e4 }
188
+ );
189
+ const probe = run(node, [path.join(stableRoot, "dist", "server.js"), "--probe"], {
190
+ timeoutMs: 6e4
191
+ });
192
+ let tools = 0;
193
+ try {
194
+ tools = JSON.parse(probe.stdout).tools.length;
195
+ } catch {
196
+ }
197
+ return [
198
+ {
199
+ step: "native modules",
200
+ ok: native.ok,
201
+ detail: native.ok ? native.stdout.trim() : tail(native.stderr)
202
+ },
203
+ {
204
+ step: "MCP server probe",
205
+ ok: probe.ok && tools > 0,
206
+ detail: probe.ok ? `${tools} tools` : tail(probe.stderr)
207
+ }
208
+ ];
209
+ }
210
+ function modelCached() {
211
+ return fs.existsSync(path.join(modelsDir(), ...MODEL_ID.split("/")));
212
+ }
213
+ function warmModel(stableRoot, node = process.execPath) {
214
+ if (modelCached()) return { step: "embedding model", ok: true, detail: `${MODEL_ID} (cached)` };
215
+ const r = run(node, [path.join(stableRoot, "dist", "cli.js"), "warm-model"], {
216
+ timeoutMs: 20 * 6e4
217
+ });
218
+ return {
219
+ step: "embedding model",
220
+ ok: r.ok,
221
+ detail: r.ok ? `${MODEL_ID} downloaded` : tail(r.stderr || r.stdout)
222
+ };
223
+ }
224
+ function install(opts = {}) {
225
+ const from = path.resolve(opts.from ?? packageRoot);
226
+ const home = homeDir();
227
+ const node = process.execPath;
228
+ const { stableRoot, version, steps } = materialize(from);
229
+ fs.writeFileSync(path.join(home, "node-path"), node);
230
+ fs.writeFileSync(path.join(home, "root-path"), stableRoot);
231
+ steps.push({ step: "pin node", ok: true, detail: `${node} (${process.version})` });
232
+ steps.push(...smokeTest(stableRoot, node));
233
+ if (opts.model !== false) steps.push(warmModel(stableRoot, node));
234
+ let registered = false;
235
+ if (opts.register !== false) {
236
+ const reg = registerPlugin(stableRoot);
237
+ steps.push(...reg);
238
+ registered = reg.length > 0 && reg.every((s) => s.ok);
239
+ }
240
+ const record = {
241
+ version,
242
+ node,
243
+ stableRoot,
244
+ from,
245
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
246
+ registered
247
+ };
248
+ fs.writeFileSync(path.join(home, "install.json"), `${JSON.stringify(record, null, 2)}
249
+ `);
250
+ return { ok: steps.every((s) => s.ok), steps };
251
+ }
252
+ function uninstall(opts = {}) {
253
+ const home = homeDir();
254
+ const steps = unregisterPlugin();
255
+ for (const f of ["plugin", "tmp", "node-path", "root-path", "install.json", "compile-cache"]) {
256
+ fs.rmSync(path.join(home, f), { recursive: true, force: true });
257
+ }
258
+ steps.push({ step: "remove stable copy", ok: true, detail: path.join(home, "plugin") });
259
+ if (opts.purge) {
260
+ fs.rmSync(modelsDir(), { recursive: true, force: true });
261
+ steps.push({ step: "remove model cache", ok: true, detail: modelsDir() });
262
+ }
263
+ return steps;
264
+ }
265
+ function doctor(projectRoot, dataDir) {
266
+ const rec = installRecord();
267
+ const out = [];
268
+ const [major = 0, minor = 0] = process.versions.node.split(".").map(Number);
269
+ out.push({
270
+ step: "node",
271
+ ok: major > 22 || major === 22 && minor >= 12,
272
+ detail: `${process.version} at ${process.execPath}${rec && rec.node !== process.execPath ? ` (pinned: ${rec.node})` : ""}`
273
+ });
274
+ out.push({
275
+ step: "installed copy",
276
+ ok: !!rec && fs.existsSync(path.join(rec.stableRoot, "dist", "server.js")),
277
+ detail: rec ? `${rec.version} at ${rec.stableRoot}` : "not installed \u2014 run `npx pantrybase install`"
278
+ });
279
+ if (rec)
280
+ out.push(...smokeTest(rec.stableRoot, fs.existsSync(rec.node) ? rec.node : process.execPath));
281
+ out.push({
282
+ step: "embedding model",
283
+ ok: modelCached(),
284
+ detail: modelCached() ? `${MODEL_ID} in ${modelsDir()}` : "not downloaded yet (first search will fetch ~34 MB)"
285
+ });
286
+ const cv = claudeVersion();
287
+ out.push({ step: "claude CLI", ok: !!cv, detail: cv ?? "not found on PATH" });
288
+ if (cv) {
289
+ const p = registeredPlugin();
290
+ const m = registeredMarketplace();
291
+ out.push({
292
+ step: "plugin registered",
293
+ ok: !!p?.enabled,
294
+ detail: p ? `${PLUGIN_ID} ${p.version ?? ""} ${p.enabled ? "enabled" : "DISABLED"}, read from ${p.readFromFolder ?? m?.path ?? "?"}` : "not installed \u2014 run `npx pantrybase install`"
295
+ });
296
+ }
297
+ const recipes = fs.existsSync(path.join(dataDir, "recipes")) ? fs.readdirSync(path.join(dataDir, "recipes")).filter((f) => f.endsWith(".md")).length : 0;
298
+ out.push({
299
+ step: "project data",
300
+ ok: true,
301
+ detail: fs.existsSync(dataDir) ? `${dataDir} (${recipes} recipes)` : `none yet in ${projectRoot}; created on first use`
302
+ });
303
+ return out;
304
+ }
305
+
306
+ // src/cli.ts
307
+ var pkg = { version: packageVersion() };
308
+ var USAGE = `pantrybase ${pkg.version}
309
+
310
+ Setup
311
+ pantrybase install [--no-model] [--no-register]
312
+ install the Claude Code plugin (stable copy in ~/.pantrybase,
313
+ native modules checked, embedding model downloaded, plugin
314
+ registered at user scope)
315
+ pantrybase uninstall [--purge] remove the plugin (--purge also deletes the model cache;
316
+ recipes in ./pantrybase are never touched)
317
+ pantrybase doctor check node, native modules, model, plugin registration
318
+
319
+ Recipes (in ./pantrybase of the current directory)
320
+ pantrybase rebuild [--full] [--reembed]
321
+ re-index recipes/*.md (--full re-parses every file,
322
+ --reembed recomputes embeddings)
323
+ pantrybase embed embed pending chunks now
324
+ pantrybase search <query\u2026> quick search from the terminal
325
+ pantrybase ui [--port N] [--open]
326
+ serve the web UI on 127.0.0.1 (default port 7433)
327
+ pantrybase stats counts and paths
328
+ pantrybase version
329
+ `;
330
+ function printSteps(steps) {
331
+ for (const s of steps) console.log(`${s.ok ? "\u2714" : "\u2718"} ${s.step.padEnd(22)} ${s.detail}`);
332
+ return steps.every((s) => s.ok);
333
+ }
334
+ async function main(argv) {
335
+ const [cmd, ...rest] = argv;
336
+ switch (cmd) {
337
+ case "install": {
338
+ console.log(`Installing pantrybase ${pkg.version}\u2026`);
339
+ const r = install({
340
+ model: !rest.includes("--no-model"),
341
+ register: !rest.includes("--no-register")
342
+ });
343
+ printSteps(r.steps);
344
+ console.log(
345
+ r.ok ? "\nDone. Start a new Claude Code session (or run /reload-plugins) and try /pantrybase:ingest or /pantrybase:cook." : "\nSome steps failed \u2014 see above, then run `pantrybase doctor`."
346
+ );
347
+ return r.ok ? 0 : 1;
348
+ }
349
+ case "uninstall": {
350
+ const ok = printSteps(uninstall({ purge: rest.includes("--purge") }));
351
+ return ok ? 0 : 1;
352
+ }
353
+ case "doctor": {
354
+ const root = resolveProjectRoot();
355
+ return printSteps(doctor(root, dataPaths(root).data)) ? 0 : 1;
356
+ }
357
+ case "warm-model": {
358
+ await new Embedder().load();
359
+ return 0;
360
+ }
361
+ case "rebuild": {
362
+ const ctx = openContext(void 0, { reconcile: false });
363
+ const r = reconcile(ctx, {
364
+ full: rest.includes("--full"),
365
+ reembed: rest.includes("--reembed")
366
+ });
367
+ console.log(JSON.stringify(r, null, 2));
368
+ await ctx.embedQueue.run();
369
+ ctx.close();
370
+ return r.errors.length ? 1 : 0;
371
+ }
372
+ case "embed": {
373
+ const ctx = openContext();
374
+ await ctx.embedQueue.run();
375
+ console.log(JSON.stringify(ctx.store.stats(), null, 2));
376
+ ctx.close();
377
+ return 0;
378
+ }
379
+ case "search": {
380
+ const ctx = openContext();
381
+ if (ctx.embedder) await ctx.embedder.load();
382
+ await ctx.embedQueue.run();
383
+ const res = await searchRecipes(ctx, { query: rest.join(" "), limit: 10 });
384
+ for (const r of res.results) {
385
+ console.log(
386
+ `${(r.score ?? 0).toFixed(4)} ${r.slug} \u2014 ${r.title} [${r.matched.join(",")}]`
387
+ );
388
+ }
389
+ console.log(
390
+ `(${res.results.length} results, vector ${res.vector_used ? "used" : "not used"})`
391
+ );
392
+ ctx.close();
393
+ return 0;
394
+ }
395
+ case "ui": {
396
+ const portArg = rest.indexOf("--port");
397
+ const ctx = openContext();
398
+ const ui = await startUiServer(ctx, {
399
+ port: portArg >= 0 ? Number(rest[portArg + 1]) : void 0,
400
+ version: pkg.version
401
+ });
402
+ console.log(`pantrybase UI: ${ui.url} (data: ${ctx.paths.data}) \u2014 Ctrl-C to stop`);
403
+ if (rest.includes("--open")) {
404
+ const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
405
+ spawn(opener, [ui.url], { stdio: "ignore", detached: true }).unref();
406
+ }
407
+ await new Promise(() => {
408
+ });
409
+ return 0;
410
+ }
411
+ case "stats": {
412
+ const ctx = openContext();
413
+ console.log(JSON.stringify({ ...ctx.store.stats(), data_dir: ctx.paths.data }, null, 2));
414
+ ctx.close();
415
+ return 0;
416
+ }
417
+ case "version":
418
+ case "--version":
419
+ case "-v":
420
+ console.log(pkg.version);
421
+ return 0;
422
+ case void 0:
423
+ case "-h":
424
+ case "--help":
425
+ case "help":
426
+ console.log(USAGE);
427
+ return 0;
428
+ default:
429
+ log(`unknown command: ${cmd}`);
430
+ console.log(USAGE);
431
+ return 1;
432
+ }
433
+ }
434
+ main(process.argv.slice(2)).then(
435
+ (code) => process.exit(code),
436
+ (e) => {
437
+ log("fatal:", e instanceof Error ? e.message : e);
438
+ process.exit(1);
439
+ }
440
+ );
441
+ //# sourceMappingURL=cli.js.map