@join-pantheon/cli 0.26.0 → 0.26.1

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/bin/pantheon.js CHANGED
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { existsSync } from "node:fs";
11
11
  import { dirname, resolve } from "node:path";
12
- import { fileURLToPath } from "node:url";
12
+ import { fileURLToPath, pathToFileURL } from "node:url";
13
13
 
14
14
  const here = dirname(fileURLToPath(import.meta.url));
15
15
  const root = resolve(here, "..");
@@ -25,4 +25,9 @@ if (!existsSync(entry)) {
25
25
  process.exit(1);
26
26
  }
27
27
 
28
- await import(entry);
28
+ // A path is not a URL, and on Windows that is fatal rather than pedantic: an
29
+ // absolute POSIX path happens to work as an import specifier, while `D:\...`
30
+ // is read as the scheme "d:" and Node refuses it — so `pantheon --version`,
31
+ // the first command anyone runs, died with ERR_UNSUPPORTED_ESM_URL_SCHEME on
32
+ // every Windows machine. A real Windows runner is what said so.
33
+ await import(pathToFileURL(entry).href);
package/dist/agents.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
3
- import { homedir } from "node:os";
3
+ import { homedir, platform } from "node:os";
4
4
  import { join, dirname, isAbsolute } from "node:path";
5
5
  import { launcher } from "./install.js";
6
+ import { agentSpawn, which } from "./which.js";
6
7
  import { PANTHEON_TOOL_NAMES, PANTHEON_TOOLS } from "./tools.js";
7
8
  export function launchArgs(agent, prompt, name) {
8
9
  if (agent === "claude-code")
@@ -38,7 +39,7 @@ const sh = (cmd) => {
38
39
  };
39
40
  export function cliOnPath(name) {
40
41
  if (!binaryCache.has(`path:${name}`))
41
- binaryCache.set(`path:${name}`, sh(`command -v ${name}`));
42
+ binaryCache.set(`path:${name}`, which(name));
42
43
  return binaryCache.get(`path:${name}`) ?? null;
43
44
  }
44
45
  export function agentById(id) {
@@ -87,7 +88,8 @@ const binaryCache = new Map();
87
88
  export function resolveBinary(backend) {
88
89
  for (const b of backend.binaries) {
89
90
  if (!binaryCache.has(b)) {
90
- let found = sh(`command -v ${b}`) ? b : null;
91
+ const hit = which(b);
92
+ let found = hit ? (platform() === "win32" ? hit : b) : null;
91
93
  if (found && b === "agent") {
92
94
  const v = `${sh("agent --version 2>/dev/null | head -1") ?? ""} ${sh("agent --help 2>/dev/null | head -3") ?? ""}`;
93
95
  if (!/cursor/i.test(v))
@@ -119,7 +121,8 @@ export function copilotSignedIn(run = runStatus, env = process.env, gh = () => !
119
121
  }
120
122
  const runStatus = (binary, args) => {
121
123
  try {
122
- const out = execFileSync(binary, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 8_000, env: { ...process.env, NO_COLOR: "1" } });
124
+ const sp = agentSpawn(binary, args);
125
+ const out = execFileSync(sp.command, sp.args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 8_000, env: { ...process.env, NO_COLOR: "1" }, ...sp.options });
123
126
  return { status: 0, out };
124
127
  }
125
128
  catch (e) {
package/dist/cli.js CHANGED
@@ -10,6 +10,7 @@ import { randomBytes } from "node:crypto";
10
10
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
11
11
  import { basename, dirname, join, resolve } from "node:path";
12
12
  import { PantheonClient } from "./client.js";
13
+ import { agentSpawn } from "./which.js";
13
14
  import { fromInvite, looksLikeInviteCode, resolveInviteCode, load, save, toInvite, toJoinLink, resolveFor, loadAll, repoInfo, bindRepo, findMembership, setDefaultSpace, removeMembership, configFor, CONFIG_DIR, CONFIG_PATH, DEFAULT_RELAY, resolveForHook } from "./config.js";
14
15
  import { fmtLimit } from "./plans.js";
15
16
  import { validEmail, validUsername } from "./accounts.js";
@@ -2318,7 +2319,10 @@ async function main() {
2318
2319
  const prompt = o ? dp(o.id, dt(o.body)) : `Read Pantheon act ${a.id}${goalId ? ` and the thread of goal ${goalId}` : ""}. Explain in plain words what happened and whether anything is owed by me, then give me numbered next steps. Do not act until I choose.`;
2319
2320
  console.error(D(`opening ${agent.id}…`));
2320
2321
  const { spawnSync } = await import("node:child_process");
2321
- spawnSync(agent.binary, launchArgs(agent.id, prompt, sessionName(prompt)), { stdio: "inherit" });
2322
+ {
2323
+ const sp = agentSpawn(agent.binary, launchArgs(agent.id, prompt, sessionName(prompt)));
2324
+ spawnSync(sp.command, sp.args, { stdio: "inherit", ...sp.options });
2325
+ }
2322
2326
  return;
2323
2327
  }
2324
2328
  if (choice === "2") {
@@ -2382,7 +2386,10 @@ async function main() {
2382
2386
  const bin = cliOnPath("grok") ?? die("grok is not on PATH here");
2383
2387
  console.error(D("opening grok…"));
2384
2388
  const { spawnSync } = await import("node:child_process");
2385
- spawnSync(bin, [hud.prompt], { stdio: "inherit" });
2389
+ {
2390
+ const sp = agentSpawn(bin, [hud.prompt]);
2391
+ spawnSync(sp.command, sp.args, { stdio: "inherit", ...sp.options });
2392
+ }
2386
2393
  return;
2387
2394
  }
2388
2395
  if (hud.prompt) {
@@ -2391,7 +2398,10 @@ async function main() {
2391
2398
  die("no agent CLI found on this machine (claude, cursor-agent, codex, or copilot)");
2392
2399
  console.error(D(`opening ${agent.id}…`));
2393
2400
  const { spawnSync } = await import("node:child_process");
2394
- spawnSync(agent.binary, launchArgs(agent.id, hud.prompt, sessionName(hud.prompt)), { stdio: "inherit" });
2401
+ {
2402
+ const sp = agentSpawn(agent.binary, launchArgs(agent.id, hud.prompt, sessionName(hud.prompt)));
2403
+ spawnSync(sp.command, sp.args, { stdio: "inherit", ...sp.options });
2404
+ }
2395
2405
  return;
2396
2406
  }
2397
2407
  if (hud.decision)
@@ -2414,7 +2424,10 @@ async function main() {
2414
2424
  die("no agent CLI found on this machine (claude, cursor-agent, codex, or copilot)");
2415
2425
  console.error(D(`opening ${agent.id} with the question…`));
2416
2426
  const { spawnSync } = await import("node:child_process");
2417
- spawnSync(agent.binary, launchArgs(agent.id, prompt, sessionName(prompt)), { stdio: "inherit" });
2427
+ {
2428
+ const sp = agentSpawn(agent.binary, launchArgs(agent.id, prompt, sessionName(prompt)));
2429
+ spawnSync(sp.command, sp.args, { stdio: "inherit", ...sp.options });
2430
+ }
2418
2431
  return;
2419
2432
  }
2420
2433
  case "approvals": {
package/dist/doctor.js CHANGED
@@ -14,6 +14,7 @@ import { hookState, readIntent, agentHookStates } from "./notify.js";
14
14
  import { loadPersonas } from "./persona.js";
15
15
  import { workerProc, menubarProc, notifierProc } from "./machine.js";
16
16
  import { chooseAgent, describeChoice } from "./agents.js";
17
+ import { onPathNamed } from "./which.js";
17
18
  import { VERSION } from "./version.js";
18
19
  import { serviceInstalled } from "./service.js";
19
20
  import { hostEnv } from "./host-env.js";
@@ -135,7 +136,7 @@ export async function doctor(projectDir) {
135
136
  if (branch)
136
137
  add("pass", "git branch", branch);
137
138
  }
138
- const agents = ["claude", "codex", "cursor-agent", "copilot"].filter((a) => sh(`command -v ${a}`));
139
+ const agents = ["claude", "codex", "cursor-agent", "copilot"].filter((a) => onPathNamed(a));
139
140
  agents.length
140
141
  ? add("pass", "agent cli", agents.join(", "), undefined, agents.join(", "))
141
142
  : add("warn", "agent cli", "found none of claude / codex / cursor-agent / copilot on PATH", "fine if you only drive agents through a GUI (Conductor, Cursor, VS Code), but `pantheon worker` needs a CLI to spawn", "none of claude / codex / cursor-agent / copilot on PATH");
@@ -1,10 +1,10 @@
1
1
  import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { dirname, join } from "node:path";
4
- import { HOOK_MARKER, MAX_CONSECUTIVE_BLOCKS } from "./notify.js";
4
+ import { MAX_CONSECUTIVE_BLOCKS, isOurHookCommand } from "./notify.js";
5
5
  import { copilotHome } from "./agents.js";
6
6
  function ours(cmd) {
7
- return !!cmd?.includes(HOOK_MARKER);
7
+ return isOurHookCommand(cmd);
8
8
  }
9
9
  function readJson(file) {
10
10
  if (!existsSync(file))
package/dist/install.js CHANGED
@@ -7,18 +7,15 @@ import { grantToolPermissions, claudeSettingsFile, cursorCliConfigFile } from ".
7
7
  import { PANTHEON_TOOL_NAMES, WRITE_TOOL_NAMES } from "./tools.js";
8
8
  import { load } from "./config.js";
9
9
  import { loadProfile } from "./profile.js";
10
+ import { agentSpawn, onPathNamed, which } from "./which.js";
10
11
  const HERE = dirname(fileURLToPath(import.meta.url));
11
12
  export function onPath() {
12
- try {
13
- execFileSync("sh", ["-lc", "command -v pantheon"], { stdio: ["ignore", "ignore", "ignore"] });
14
- return true;
15
- }
16
- catch {
17
- return false;
18
- }
13
+ return onPathNamed("pantheon");
19
14
  }
20
- export function launcher(force) {
15
+ export function launcher(force, os = platform()) {
21
16
  const portable = force === "portable" || (force !== "local" && onPath());
17
+ if (os === "win32")
18
+ return { command: process.execPath, args: [resolve(HERE, "cli.js"), "mcp"], portable: false };
22
19
  return portable
23
20
  ? { command: "pantheon", args: ["mcp"], portable: true }
24
21
  : { command: process.execPath, args: [resolve(HERE, "cli.js"), "mcp"], portable: false };
@@ -159,9 +156,17 @@ export function install(projectDir, cfg, force) {
159
156
  function localEntry() {
160
157
  return { command: process.execPath, args: [resolve(HERE, "cli.js"), "mcp"] };
161
158
  }
162
- function userEntry() {
159
+ function userEntry(os = platform()) {
160
+ if (os === "win32") {
161
+ const node = process.execPath;
162
+ const entry = resolve(HERE, "cli.js");
163
+ if (existsSync(entry))
164
+ return { command: node, args: [entry, "mcp"] };
165
+ const abs = which("pantheon");
166
+ return abs ? { command: abs, args: ["mcp"] } : { command: "pantheon", args: ["mcp"] };
167
+ }
163
168
  try {
164
- const abs = execFileSync("sh", ["-lc", "command -v pantheon"], { encoding: "utf8" }).trim();
169
+ const abs = which("pantheon") ?? "";
165
170
  if (abs && !isVersionManagedPath(abs))
166
171
  return { command: abs, args: ["mcp"] };
167
172
  if (abs)
@@ -176,18 +181,17 @@ function isVersionManagedPath(p) {
176
181
  }
177
182
  function claudeUserScope(entry) {
178
183
  const tool = "claude code";
179
- try {
180
- execFileSync("sh", ["-lc", "command -v claude"], { stdio: "ignore" });
181
- }
182
- catch {
184
+ const claudeBin = which("claude");
185
+ if (!claudeBin)
183
186
  return { tool, where: "-", status: "skipped (claude not on PATH)" };
184
- }
185
187
  try {
188
+ const remove = agentSpawn(claudeBin, ["mcp", "remove", "pantheon", "-s", "user"]);
189
+ const add = agentSpawn(claudeBin, ["mcp", "add", "pantheon", "-s", "user", "--", entry.command, ...entry.args]);
186
190
  try {
187
- execFileSync("claude", ["mcp", "remove", "pantheon", "-s", "user"], { stdio: "ignore" });
191
+ execFileSync(remove.command, remove.args, { stdio: "ignore", ...remove.options });
188
192
  }
189
193
  catch { }
190
- execFileSync("claude", ["mcp", "add", "pantheon", "-s", "user", "--", entry.command, ...entry.args], { stdio: "ignore" });
194
+ execFileSync(add.command, add.args, { stdio: "ignore", ...add.options });
191
195
  return { tool, where: "user scope", status: "ready in every project and every Conductor window" };
192
196
  }
193
197
  catch (e) {
@@ -207,13 +211,8 @@ function copilotUserScope(entry) {
207
211
  const tool = "copilot";
208
212
  const file = copilotMcpConfigFile();
209
213
  let present = existsSync(dirname(file));
210
- if (!present) {
211
- try {
212
- execFileSync("sh", ["-lc", "command -v copilot"], { stdio: "ignore" });
213
- present = true;
214
- }
215
- catch { }
216
- }
214
+ if (!present && onPathNamed("copilot"))
215
+ present = true;
217
216
  if (!present)
218
217
  return { tool, where: "-", status: "skipped (copilot not on PATH)" };
219
218
  try {
package/dist/notify.js CHANGED
@@ -5,6 +5,9 @@ import { whichPantheon } from "./service.js";
5
5
  import { detectForHooks, agentById } from "./agents.js";
6
6
  import { codexHookState, cursorHookState, copilotHookState, installCodexHooks, installCursorHooks, installCopilotHooks, removeCodexHooks, removeCursorHooks, removeCopilotHooks, } from "./hooks-install.js";
7
7
  export const HOOK_MARKER = "pantheon hook";
8
+ export const HOOK_RE = /pantheon(?:\.cmd|\.exe|\.bat)?"?\s+hook\s/i;
9
+ export const isOurHookCommand = (cmd) => !!cmd && (cmd.includes(HOOK_MARKER) || HOOK_RE.test(cmd));
10
+ export const hookBin = (bin) => (/\s/.test(bin) ? `"${bin}"` : bin);
8
11
  export function settingsPath(scope, projectDir = process.cwd()) {
9
12
  return scope === "user"
10
13
  ? join(homedir(), ".claude", "settings.json")
@@ -25,7 +28,7 @@ function write(file, doc) {
25
28
  writeFileSync(file, JSON.stringify(doc, null, 2) + "\n");
26
29
  }
27
30
  function ours(g) {
28
- return g.hooks?.some((h) => h.command?.includes(HOOK_MARKER)) ?? false;
31
+ return g.hooks?.some((h) => isOurHookCommand(h.command)) ?? false;
29
32
  }
30
33
  export function hookState(projectDir = process.cwd(), only) {
31
34
  for (const scope of (only ? [only] : ["project", "user"])) {
@@ -46,7 +49,7 @@ export function hookState(projectDir = process.cwd(), only) {
46
49
  if (!hit)
47
50
  continue;
48
51
  events.push(event);
49
- command ??= hit.hooks.find((h) => h.command.includes(HOOK_MARKER)).command;
52
+ command ??= hit.hooks.find((h) => isOurHookCommand(h.command)).command;
50
53
  }
51
54
  if (events.length)
52
55
  return { installed: true, scope, file, command, events };
@@ -137,7 +140,10 @@ export function repairHook(home) {
137
140
  function installClaudeHook(scope, projectDir = process.cwd()) {
138
141
  const file = settingsPath(scope, projectDir);
139
142
  const doc = read(file);
140
- const bin = whichPantheon();
143
+ const found = whichPantheon();
144
+ if (!found)
145
+ throw new Error("pantheon binary not found on PATH");
146
+ const bin = hookBin(found);
141
147
  const events = {
142
148
  Stop: `${bin} hook stop`,
143
149
  UserPromptSubmit: `${bin} hook prompt`,
@@ -16,6 +16,7 @@ import { desktopUnavailable, ephemeralWarning, hostEnv } from "./host-env.js";
16
16
  import { existsSync } from "node:fs";
17
17
  import { grant as grantCap, loadPolicy, policyPath, savePolicy } from "./worker-policy.js";
18
18
  import { Relay } from "./relay.js";
19
+ import { which } from "./which.js";
19
20
  const sh = (cmd) => {
20
21
  try {
21
22
  return execFileSync("sh", ["-lc", cmd], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || null;
@@ -126,7 +127,7 @@ export function findReach(port) {
126
127
  if (ts && /^\d+\.\d+\.\d+\.\d+$/.test(ts)) {
127
128
  return { url: `ws://${ts}:${port}`, how: `tailscale (${ts})`, shareable: true };
128
129
  }
129
- if (sh("command -v cloudflared")) {
130
+ if (which("cloudflared")) {
130
131
  return {
131
132
  url: `ws://127.0.0.1:${port}`, how: "localhost", shareable: false,
132
133
  hint: `cloudflared is installed. To let a teammate reach you:\n` +
package/dist/service.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { writeFileSync, mkdirSync, existsSync, unlinkSync } from "node:fs";
2
2
  import { execFileSync } from "node:child_process";
3
3
  import { homedir, platform } from "node:os";
4
+ import { which } from "./which.js";
4
5
  import { dirname, join, resolve } from "node:path";
5
6
  import { fileURLToPath } from "node:url";
6
7
  import { launcher } from "./install.js";
@@ -29,6 +30,8 @@ function argvFor(kind, args) {
29
30
  return abs ? [abs, kind, ...args] : [process.execPath, resolve(HERE, "cli.js"), kind, ...args];
30
31
  }
31
32
  export function whichPantheon() {
33
+ if (platform() === "win32")
34
+ return which("pantheon");
32
35
  try {
33
36
  const p = execFileSync("/usr/bin/which", ["pantheon"], { encoding: "utf8" }).trim();
34
37
  return p.startsWith("/") && existsSync(p) ? p : null;
@@ -43,6 +43,8 @@ export function updateStatus(deps) {
43
43
  export function versionLine(version, behind, latest) {
44
44
  if (behind <= 0)
45
45
  return `pantheon ${version}`;
46
+ if (latest && !semverGt(latest, version))
47
+ return `pantheon ${version}`;
46
48
  return latest
47
49
  ? `pantheon ${version} · ${latest} available — run \`pantheon upgrade\``
48
50
  : `pantheon ${version} · ${behind} update${behind === 1 ? "" : "s"} behind — run \`pantheon upgrade\``;
package/dist/which.js ADDED
@@ -0,0 +1,73 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { accessSync, constants, existsSync, statSync } from "node:fs";
3
+ import { win32 } from "node:path";
4
+ import { platform } from "node:os";
5
+ export const DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.PS1";
6
+ const isWindows = (os = platform()) => os === "win32";
7
+ function runnable(file) {
8
+ try {
9
+ if (!existsSync(file) || !statSync(file).isFile())
10
+ return false;
11
+ if (isWindows())
12
+ return true;
13
+ accessSync(file, constants.X_OK);
14
+ return true;
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ }
20
+ export function whichWindows(name, env = process.env, exists = runnable) {
21
+ if (!name)
22
+ return null;
23
+ const exts = (env.PATHEXT || DEFAULT_PATHEXT).split(";").map((e) => e.trim()).filter(Boolean);
24
+ const ext = win32.extname(name);
25
+ const hasExt = !!ext && exts.some((e) => e.toLowerCase() === ext.toLowerCase());
26
+ const candidates = (base) => hasExt ? [base] : exts.flatMap((e) => (e === e.toLowerCase() ? [base + e] : [base + e.toLowerCase(), base + e]));
27
+ if (win32.isAbsolute(name)) {
28
+ for (const c of candidates(name))
29
+ if (exists(c))
30
+ return c;
31
+ return null;
32
+ }
33
+ for (const dir of (env.PATH || env.Path || "").split(";")) {
34
+ const d = dir.replace(/^"|"$/g, "").trim();
35
+ if (!d)
36
+ continue;
37
+ for (const c of candidates(win32.join(d, name)))
38
+ if (exists(c))
39
+ return c;
40
+ }
41
+ return null;
42
+ }
43
+ export function which(name, os = platform(), env = process.env) {
44
+ if (isWindows(os))
45
+ return whichWindows(name, env);
46
+ try {
47
+ const out = execFileSync("sh", ["-lc", `command -v ${name}`], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
48
+ if (!out)
49
+ return null;
50
+ return out.startsWith("/") && !runnable(out) ? null : out;
51
+ }
52
+ catch {
53
+ return null;
54
+ }
55
+ }
56
+ export const onPathNamed = (name, os = platform(), env = process.env) => which(name, os, env) !== null;
57
+ export function spawnShape(binary, os = platform()) {
58
+ if (!isWindows(os))
59
+ return { command: binary, shell: false };
60
+ return { command: binary, shell: /\.(cmd|bat)$/i.test(binary) };
61
+ }
62
+ const cmdQuote = (a) => (/[\s"&|<>^()]/.test(a) || a === "" ? `"${a.replace(/"/g, '\\"')}"` : a);
63
+ export function agentSpawn(binary, args, os = platform()) {
64
+ if (!isWindows(os) || !/\.(cmd|bat)$/i.test(binary))
65
+ return { command: binary, args: [...args], options: {} };
66
+ const line = [binary, ...args].map(cmdQuote).join(" ");
67
+ return {
68
+ command: process.env.ComSpec || "cmd.exe",
69
+ args: ["/d", "/s", "/c", `"${line}"`],
70
+ options: { windowsVerbatimArguments: true },
71
+ };
72
+ }
73
+ //# sourceMappingURL=which.js.map
package/dist/worker.js CHANGED
@@ -5,6 +5,7 @@ import { homedir } from "node:os";
5
5
  import { join, resolve } from "node:path";
6
6
  import { PantheonClient } from "./client.js";
7
7
  import { headlessSpawn, materializeSpawnFiles, chooseAgent, describeChoice } from "./agents.js";
8
+ import { agentSpawn } from "./which.js";
8
9
  import { VERSION } from "./version.js";
9
10
  import { applyAnswer, loadPolicy, mayAsk, needsSelfUpgrade, recordAsk, savePolicy, useGrant, } from "./worker-policy.js";
10
11
  import { agentHookStates, repairHook } from "./notify.js";
@@ -616,7 +617,8 @@ export class Worker {
616
617
  return { obligation: o, branch, worktree, ok: false, detail };
617
618
  }
618
619
  const detail = await new Promise((res) => {
619
- const child = procSpawn(command, args, { cwd: worktree, stdio: ["ignore", "pipe", "pipe"], env });
620
+ const sp = agentSpawn(command, args);
621
+ const child = procSpawn(sp.command, sp.args, { cwd: worktree, stdio: ["ignore", "pipe", "pipe"], env, ...sp.options });
620
622
  let out = "";
621
623
  child.stdout.on("data", (b) => { out += b.toString(); });
622
624
  child.stderr.on("data", (b) => { out += b.toString(); });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@join-pantheon/cli",
3
- "version": "0.26.0",
4
- "description": "Let your coding agent collaborate directly with your teammates' agents — ask, agree an interface, and deliver, without a human relaying messages.",
3
+ "version": "0.26.1",
4
+ "description": "Let your coding agent collaborate directly with your teammates' agents \u2014 ask, agree an interface, and deliver, without a human relaying messages.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "pantheon": "bin/pantheon.js"
@@ -10,13 +10,15 @@
10
10
  "node": ">=22"
11
11
  },
12
12
  "scripts": {
13
- "build": "rm -rf dist && tsc -p .",
13
+ "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p .",
14
14
  "dev": "tsc -p . --watch",
15
15
  "relay": "node dist/cli.js relay",
16
16
  "test": "PANTHEON_HOME=\"$(mktemp -d)\" HOME=\"$(mktemp -d)\" node --test --test-force-exit dist/test/*.test.js",
17
17
  "prepare": "npm run build",
18
18
  "prepublishOnly": "npm run build && npm test",
19
- "start": "node dist/cli.js relay"
19
+ "start": "node dist/cli.js relay",
20
+ "test:ci": "node scripts/test.mjs",
21
+ "smoke": "node scripts/smoke.mjs"
20
22
  },
21
23
  "dependencies": {
22
24
  "@modelcontextprotocol/sdk": "^1.0.0",