@aiaiai-pt/martha-cli 0.25.0 → 0.27.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.
Files changed (2) hide show
  1. package/dist/index.js +529 -85
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2158,6 +2158,8 @@ function extractDetail(body) {
2158
2158
  const nested = obj.detail;
2159
2159
  if (typeof nested.message === "string")
2160
2160
  return nested.message;
2161
+ if (typeof nested.code === "string")
2162
+ return nested.code;
2161
2163
  if (nested.code === "mcp_oauth_authorization_required" && typeof nested.authorization_url === "string") {
2162
2164
  return "MCP OAuth authorization required";
2163
2165
  }
@@ -11073,12 +11075,13 @@ ${items.length} ${config.typeNamePlural}`));
11073
11075
  }
11074
11076
 
11075
11077
  // src/commands/chat.ts
11078
+ import { createHash as createHash2 } from "node:crypto";
11076
11079
  import { createInterface as createInterface2 } from "node:readline";
11077
11080
  init_config();
11078
11081
  init_errors();
11079
11082
 
11080
11083
  // src/version.ts
11081
- var CLI_VERSION = "0.25.0";
11084
+ var CLI_VERSION = "0.27.0";
11082
11085
 
11083
11086
  // src/commands/sessions.ts
11084
11087
  function relativeTime(iso) {
@@ -11352,8 +11355,12 @@ function createApprovalTagStripper() {
11352
11355
 
11353
11356
  // src/lib/local-exec.ts
11354
11357
  import { spawn } from "node:child_process";
11358
+ import { randomBytes as randomBytes2 } from "node:crypto";
11355
11359
  import { closeSync, openSync, promises as fs5 } from "node:fs";
11356
11360
  import * as path4 from "node:path";
11361
+ function sleep(ms) {
11362
+ return new Promise((r) => setTimeout(r, ms));
11363
+ }
11357
11364
  var DEFAULT_TIMEOUT_MS = 20000;
11358
11365
  var MAX_OUTPUT = 256 * 1024;
11359
11366
  function runnerDir(cwd) {
@@ -11432,6 +11439,9 @@ async function runHostCommand(command, toolCallId, opts) {
11432
11439
  function safeName(s) {
11433
11440
  return s.replace(/[^a-zA-Z0-9_.-]/g, "_").slice(0, 120) || "task";
11434
11441
  }
11442
+ function mintExecSecret() {
11443
+ return randomBytes2(32).toString("hex");
11444
+ }
11435
11445
 
11436
11446
  class Journal {
11437
11447
  cwd;
@@ -11443,24 +11453,99 @@ class Journal {
11443
11453
  }
11444
11454
  async load() {
11445
11455
  try {
11446
- return JSON.parse(await fs5.readFile(this.file(), "utf8"));
11456
+ const raw = JSON.parse(await fs5.readFile(this.file(), "utf8"));
11457
+ if (raw["entries"] !== undefined) {
11458
+ const f = raw;
11459
+ if (!f.secrets)
11460
+ f.secrets = {};
11461
+ if (!f.jobs)
11462
+ f.jobs = {};
11463
+ return f;
11464
+ }
11465
+ return {
11466
+ entries: raw,
11467
+ secrets: {},
11468
+ jobs: {}
11469
+ };
11447
11470
  } catch {
11448
- return {};
11471
+ return { entries: {}, secrets: {}, jobs: {} };
11449
11472
  }
11450
11473
  }
11451
11474
  async save(data) {
11452
11475
  await fs5.mkdir(runnerDir(this.cwd), { recursive: true });
11453
- await fs5.writeFile(this.file(), JSON.stringify(data, null, 2));
11476
+ const tmp = `${this.file()}.${randomBytes2(6).toString("hex")}.tmp`;
11477
+ await fs5.writeFile(tmp, JSON.stringify(data, null, 2));
11478
+ await fs5.rename(tmp, this.file());
11479
+ }
11480
+ chain = Promise.resolve();
11481
+ async acquireFileLock() {
11482
+ const lockPath = path4.join(runnerDir(this.cwd), "journal.lock");
11483
+ await fs5.mkdir(runnerDir(this.cwd), { recursive: true });
11484
+ const deadline = Date.now() + 5000;
11485
+ for (;; ) {
11486
+ try {
11487
+ const fh = await fs5.open(lockPath, "wx");
11488
+ await fh.write(String(process.pid));
11489
+ await fh.close();
11490
+ return async () => {
11491
+ await fs5.rm(lockPath, { force: true });
11492
+ };
11493
+ } catch (e) {
11494
+ if (e.code !== "EEXIST")
11495
+ throw e;
11496
+ try {
11497
+ const st = await fs5.stat(lockPath);
11498
+ if (Date.now() - st.mtimeMs > 1e4) {
11499
+ await fs5.rm(lockPath, { force: true });
11500
+ continue;
11501
+ }
11502
+ } catch {
11503
+ continue;
11504
+ }
11505
+ if (Date.now() > deadline) {
11506
+ return async () => {};
11507
+ }
11508
+ await sleep(15 + Math.floor(Math.random() * 25));
11509
+ }
11510
+ }
11511
+ }
11512
+ async transact(fn) {
11513
+ const run = async () => {
11514
+ const release = await this.acquireFileLock();
11515
+ try {
11516
+ const data = await this.load();
11517
+ const out = await fn(data);
11518
+ await this.save(data);
11519
+ return out;
11520
+ } finally {
11521
+ await release();
11522
+ }
11523
+ };
11524
+ const result = this.chain.then(run, run);
11525
+ this.chain = result.then(() => {
11526
+ return;
11527
+ }, () => {
11528
+ return;
11529
+ });
11530
+ return result;
11531
+ }
11532
+ async recordJob(job) {
11533
+ await this.transact((data) => {
11534
+ data.jobs[job.job_id] = job;
11535
+ });
11536
+ }
11537
+ async lookupJob(jobId) {
11538
+ return this.transact((data) => data.jobs[jobId]);
11454
11539
  }
11455
11540
  static key(sessionId, toolCallId) {
11456
11541
  return `${sessionId}:${toolCallId}`;
11457
11542
  }
11458
11543
  async lookup(sessionId, toolCallId) {
11459
- return (await this.load())[Journal.key(sessionId, toolCallId)];
11544
+ return (await this.load()).entries[Journal.key(sessionId, toolCallId)];
11460
11545
  }
11461
11546
  async recordRunning(sessionId, toolCallId, command, now) {
11462
11547
  const data = await this.load();
11463
- data[Journal.key(sessionId, toolCallId)] = {
11548
+ data.entries[Journal.key(sessionId, toolCallId)] = {
11464
11549
  state: "running",
11465
11550
  command,
11466
11551
  started_at: now
@@ -11470,8 +11555,8 @@ class Journal {
11470
11555
  async recordDone(sessionId, toolCallId, result) {
11471
11556
  const data = await this.load();
11472
11557
  const k = Journal.key(sessionId, toolCallId);
11473
- const prev = data[k];
11474
- data[k] = {
11558
+ const prev = data.entries[k];
11559
+ data.entries[k] = {
11475
11560
  state: "done",
11476
11561
  command: prev?.command ?? "",
11477
11562
  started_at: prev?.started_at ?? "",
@@ -11479,6 +11564,14 @@ class Journal {
11479
11564
  };
11480
11565
  await this.save(data);
11481
11566
  }
11567
+ async recordSecret(sessionId, toolCallId, secret) {
11568
+ const data = await this.load();
11569
+ data.secrets[Journal.key(sessionId, toolCallId)] = secret;
11570
+ await this.save(data);
11571
+ }
11572
+ async lookupSecret(sessionId, toolCallId) {
11573
+ return (await this.load()).secrets[Journal.key(sessionId, toolCallId)];
11574
+ }
11482
11575
  }
11483
11576
  function decideFromJournal(entry) {
11484
11577
  if (!entry)
@@ -11502,7 +11595,12 @@ function parseHostExecRequests(data) {
11502
11595
  continue;
11503
11596
  if (!t.tool_call_id)
11504
11597
  continue;
11505
- out.push({ toolCallId: t.tool_call_id, command: t.args?.command ?? "" });
11598
+ out.push({
11599
+ toolCallId: t.tool_call_id,
11600
+ command: t.args?.command ?? "",
11601
+ background: t.args?.background === true,
11602
+ cwd: typeof t.args?.cwd === "string" ? t.args.cwd : undefined
11603
+ });
11506
11604
  }
11507
11605
  return out;
11508
11606
  }
@@ -11898,12 +11996,247 @@ function parseLocalAgentRequests(data) {
11898
11996
  out.push({
11899
11997
  toolCallId: t.tool_call_id,
11900
11998
  task: t.args.task,
11901
- cwd: typeof t.args.cwd === "string" ? t.args.cwd : undefined
11999
+ cwd: typeof t.args.cwd === "string" ? t.args.cwd : undefined,
12000
+ background: t.args.background === true
11902
12001
  });
11903
12002
  }
11904
12003
  return out;
11905
12004
  }
11906
12005
 
12006
+ // src/lib/local-jobs.ts
12007
+ import { execFileSync, spawn as spawn2 } from "node:child_process";
12008
+ import { randomBytes as randomBytes3 } from "node:crypto";
12009
+ import { closeSync as closeSync2, openSync as openSync2, readFileSync, promises as fs7 } from "node:fs";
12010
+ import * as path6 from "node:path";
12011
+ var POLL_CAP = 64 * 1024;
12012
+ function mintJobId() {
12013
+ return `job_${randomBytes3(6).toString("hex")}`;
12014
+ }
12015
+ var SENTINEL_TAIL = `
12016
+ __martha_ec=$?
12017
+ printf '%s' "$__martha_ec" > "$MARTHA_JOB_SENTINEL.tmp" 2>/dev/null
12018
+ mv -f "$MARTHA_JOB_SENTINEL.tmp" "$MARTHA_JOB_SENTINEL" 2>/dev/null
12019
+ exit $__martha_ec`;
12020
+ function runnerDir2(cwd) {
12021
+ return path6.join(cwd, ".martha-runner");
12022
+ }
12023
+ function getProcessStartTime(pid) {
12024
+ try {
12025
+ if (process.platform === "linux") {
12026
+ const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
12027
+ const tail = stat.slice(stat.lastIndexOf(")") + 1).trim().split(/\s+/);
12028
+ return tail[19] ?? null;
12029
+ }
12030
+ const out = execFileSync("ps", ["-o", "lstart=", "-p", String(pid)], {
12031
+ encoding: "utf8"
12032
+ }).trim();
12033
+ return out || null;
12034
+ } catch {
12035
+ return null;
12036
+ }
12037
+ }
12038
+ function isProcessAlive(pid) {
12039
+ if (pid <= 0)
12040
+ return false;
12041
+ try {
12042
+ process.kill(pid, 0);
12043
+ return true;
12044
+ } catch (e) {
12045
+ return e.code === "EPERM";
12046
+ }
12047
+ }
12048
+ async function readSentinel(p) {
12049
+ try {
12050
+ const raw = (await fs7.readFile(p, "utf8")).trim();
12051
+ if (raw === "")
12052
+ return { present: true };
12053
+ const n = parseInt(raw, 10);
12054
+ return { present: true, code: Number.isNaN(n) ? undefined : n };
12055
+ } catch {
12056
+ return { present: false };
12057
+ }
12058
+ }
12059
+ async function readLogOrNull(p) {
12060
+ try {
12061
+ return await fs7.readFile(p);
12062
+ } catch {
12063
+ return null;
12064
+ }
12065
+ }
12066
+ async function writeSentinelAtomic(p, content) {
12067
+ const tmp = `${p}.${randomBytes3(4).toString("hex")}.tmp`;
12068
+ await fs7.writeFile(tmp, content);
12069
+ await fs7.rename(tmp, p);
12070
+ }
12071
+ async function dispatchBackgroundJob(command, jobId, journal, opts) {
12072
+ if (process.platform === "win32") {
12073
+ throw new Error("background jobs are not supported on Windows (POSIX-only: /bin/sh + " + "process groups). Run this command in the foreground instead.");
12074
+ }
12075
+ const dir = runnerDir2(opts.cwd);
12076
+ await fs7.mkdir(dir, { recursive: true });
12077
+ const logPath = path6.join(dir, `${jobId}.log`);
12078
+ const sentinelPath = path6.join(dir, `${jobId}.exit`);
12079
+ await fs7.writeFile(logPath, "");
12080
+ const fd = openSync2(logPath, "a");
12081
+ const env2 = { ...process.env, MARTHA_JOB_SENTINEL: sentinelPath };
12082
+ const stdin = opts.stdinData != null ? "pipe" : "ignore";
12083
+ const child = opts.argv ? spawn2("/bin/sh", ["-c", `"$@"${SENTINEL_TAIL}`, "martha-job", ...opts.argv], {
12084
+ shell: false,
12085
+ cwd: opts.cwd,
12086
+ detached: true,
12087
+ env: env2,
12088
+ stdio: [stdin, fd, fd]
12089
+ }) : spawn2("/bin/sh", ["-c", `( ${command} )${SENTINEL_TAIL}`], {
12090
+ shell: false,
12091
+ cwd: opts.cwd,
12092
+ detached: true,
12093
+ env: env2,
12094
+ stdio: [stdin, fd, fd]
12095
+ });
12096
+ closeSync2(fd);
12097
+ if (opts.stdinData != null && child.stdin) {
12098
+ child.stdin.on("error", () => {});
12099
+ child.stdin.write(opts.stdinData);
12100
+ child.stdin.end();
12101
+ }
12102
+ const pid = child.pid ?? -1;
12103
+ const start_time = pid > 0 ? getProcessStartTime(pid) : null;
12104
+ child.unref();
12105
+ const record = {
12106
+ job_id: jobId,
12107
+ command,
12108
+ pid,
12109
+ start_time,
12110
+ started_at: new Date().toISOString(),
12111
+ log_path: logPath,
12112
+ sentinel_path: sentinelPath,
12113
+ offset: 0,
12114
+ status: "running"
12115
+ };
12116
+ await journal.recordJob(record);
12117
+ return { job_id: jobId, status: "running", log_path: logPath, pid };
12118
+ }
12119
+ function startTimeMatches(job) {
12120
+ if (!job.start_time)
12121
+ return true;
12122
+ const live = getProcessStartTime(job.pid);
12123
+ if (live == null)
12124
+ return true;
12125
+ return live === job.start_time;
12126
+ }
12127
+ async function classifyAndRead(job) {
12128
+ const sentinel = await readSentinel(job.sentinel_path);
12129
+ const logBuf = await readLogOrNull(job.log_path);
12130
+ let status;
12131
+ let done;
12132
+ let exit_code;
12133
+ if (sentinel.present) {
12134
+ done = true;
12135
+ exit_code = sentinel.code;
12136
+ status = job.status === "killed" ? "killed" : "done";
12137
+ job.status = status;
12138
+ } else if (isProcessAlive(job.pid) && startTimeMatches(job)) {
12139
+ status = "running";
12140
+ done = false;
12141
+ } else if (logBuf == null) {
12142
+ status = "expired";
12143
+ done = true;
12144
+ } else {
12145
+ status = "crashed";
12146
+ done = true;
12147
+ job.status = "crashed";
12148
+ }
12149
+ let new_output = "";
12150
+ let bytes_remaining = 0;
12151
+ if (logBuf != null) {
12152
+ const slice = logBuf.subarray(job.offset, job.offset + POLL_CAP);
12153
+ new_output = slice.toString("utf8");
12154
+ job.offset += slice.length;
12155
+ bytes_remaining = Math.max(0, logBuf.length - job.offset);
12156
+ }
12157
+ return {
12158
+ job_id: job.job_id,
12159
+ status,
12160
+ exit_code,
12161
+ new_output,
12162
+ done,
12163
+ bytes_remaining
12164
+ };
12165
+ }
12166
+ async function pollJob(journal, jobId) {
12167
+ return journal.transact(async (data) => {
12168
+ const job = data.jobs[jobId];
12169
+ if (!job) {
12170
+ return {
12171
+ job_id: jobId,
12172
+ status: "unknown",
12173
+ new_output: "",
12174
+ done: true,
12175
+ bytes_remaining: 0,
12176
+ message: `No background job with id ${jobId} is known on this machine.`
12177
+ };
12178
+ }
12179
+ return classifyAndRead(job);
12180
+ });
12181
+ }
12182
+ async function killJob(journal, jobId) {
12183
+ return journal.transact(async (data) => {
12184
+ const job = data.jobs[jobId];
12185
+ if (!job) {
12186
+ return {
12187
+ job_id: jobId,
12188
+ status: "unknown",
12189
+ new_output: "",
12190
+ done: true,
12191
+ bytes_remaining: 0,
12192
+ message: `No background job with id ${jobId} is known on this machine.`
12193
+ };
12194
+ }
12195
+ const sentinel = await readSentinel(job.sentinel_path);
12196
+ if (!sentinel.present) {
12197
+ for (const sig of ["SIGTERM", "SIGKILL"]) {
12198
+ try {
12199
+ if (job.pid > 0)
12200
+ process.kill(-job.pid, sig);
12201
+ } catch {}
12202
+ }
12203
+ job.status = "killed";
12204
+ await writeSentinelAtomic(job.sentinel_path, "137");
12205
+ }
12206
+ const result = await classifyAndRead(job);
12207
+ if (result.status === "done" && job.status === "killed") {
12208
+ result.status = "killed";
12209
+ }
12210
+ result.message = "Background job stopped.";
12211
+ return result;
12212
+ });
12213
+ }
12214
+ function parseHostOutputRequests(data) {
12215
+ return parseJobToolRequests(data, "host_output");
12216
+ }
12217
+ function parseHostKillRequests(data) {
12218
+ return parseJobToolRequests(data, "host_kill");
12219
+ }
12220
+ function parseJobToolRequests(data, toolName) {
12221
+ let tools;
12222
+ try {
12223
+ tools = JSON.parse(data);
12224
+ } catch {
12225
+ return [];
12226
+ }
12227
+ if (!Array.isArray(tools))
12228
+ return [];
12229
+ const out = [];
12230
+ for (const t of tools) {
12231
+ if (t.tool_name !== toolName || t.status !== "awaiting_input")
12232
+ continue;
12233
+ if (!t.tool_call_id || !t.args?.job_id)
12234
+ continue;
12235
+ out.push({ toolCallId: t.tool_call_id, jobId: t.args.job_id });
12236
+ }
12237
+ return out;
12238
+ }
12239
+
11907
12240
  // src/commands/chat.ts
11908
12241
  async function uploadImageArtifact(ctx, sessionId, png) {
11909
12242
  try {
@@ -11922,7 +12255,8 @@ async function uploadImageArtifact(ctx, sessionId, png) {
11922
12255
  return { error: `image upload failed: ${String(e)}` };
11923
12256
  }
11924
12257
  }
11925
- async function resolveHostExec(sessionId, toolCallId, command, state) {
12258
+ async function resolveHostExec(sessionId, toolCallId, req, state) {
12259
+ const command = req.command;
11926
12260
  const decision = decideFromJournal(await state.journal.lookup(sessionId, toolCallId));
11927
12261
  if (decision.kind === "replay")
11928
12262
  return decision.result;
@@ -11943,20 +12277,65 @@ async function resolveHostExec(sessionId, toolCallId, command, state) {
11943
12277
  message: "Host execution declined; re-run with --yes to allow."
11944
12278
  };
11945
12279
  }
12280
+ const cwd = req.cwd ? `${state.cwd}/${req.cwd}` : state.cwd;
12281
+ if (req.background) {
12282
+ const jobId = mintJobId();
12283
+ process.stderr.write(source_default.dim(`
12284
+ [host_exec:bg] $ ${command}
12285
+ (job ${jobId}, cwd: ${cwd})
12286
+ `));
12287
+ try {
12288
+ const handle = await dispatchBackgroundJob(command, jobId, state.journal, {
12289
+ cwd
12290
+ });
12291
+ return {
12292
+ job_id: handle.job_id,
12293
+ status: "running",
12294
+ pid: handle.pid,
12295
+ log_path: handle.log_path,
12296
+ message: `Dispatched in the background as ${handle.job_id}. Poll host_output(` + `{job_id:"${handle.job_id}"}) for new output + status; host_kill to stop it.`
12297
+ };
12298
+ } catch (e) {
12299
+ return {
12300
+ error: true,
12301
+ message: `failed to dispatch background job: ${String(e)}`
12302
+ };
12303
+ }
12304
+ }
11946
12305
  process.stderr.write(source_default.dim(`
11947
12306
  [host_exec] $ ${command}
11948
- (cwd: ${state.cwd})
12307
+ (cwd: ${cwd})
11949
12308
  `));
11950
12309
  await state.journal.recordRunning(sessionId, toolCallId, command, new Date().toISOString());
11951
12310
  let result;
11952
12311
  try {
11953
- result = await runHostCommand(command, toolCallId, { cwd: state.cwd });
12312
+ result = await runHostCommand(command, toolCallId, { cwd });
11954
12313
  } catch (e) {
11955
12314
  result = { stdout: "", stderr: String(e), exit_code: -1 };
11956
12315
  }
11957
12316
  await state.journal.recordDone(sessionId, toolCallId, result);
11958
12317
  return result;
11959
12318
  }
12319
+ async function resolveHostOutput(req, state) {
12320
+ process.stderr.write(source_default.dim(`
12321
+ [host_output] ${req.jobId}
12322
+ `));
12323
+ try {
12324
+ return await pollJob(state.journal, req.jobId);
12325
+ } catch (e) {
12326
+ return { error: true, message: `host_output failed: ${String(e)}` };
12327
+ }
12328
+ }
12329
+ async function resolveHostKill(req, state) {
12330
+ process.stderr.write(source_default.dim(`
12331
+ [host_kill] ${req.jobId}
12332
+ `));
12333
+ try {
12334
+ return await killJob(state.journal, req.jobId);
12335
+ } catch (e) {
12336
+ return { error: true, message: `host_kill failed: ${String(e)}` };
12337
+ }
12338
+ }
11960
12339
  async function resolveHostScreenshot(ctx, sessionId, req, state) {
11961
12340
  if (!state.autoYes) {
11962
12341
  process.stderr.write(source_default.yellow(`
@@ -12063,6 +12442,27 @@ async function resolveLocalAgent(sessionId, toolCallId, req, state) {
12063
12442
  }
12064
12443
  const cwd = req.cwd ? `${state.cwd}/${req.cwd}` : state.cwd;
12065
12444
  const { bin, args, stdin } = buildSpawn(invocation, req.task);
12445
+ if (req.background) {
12446
+ const jobId = mintJobId();
12447
+ process.stderr.write(source_default.dim(`
12448
+ [local_agent:bg] (${invocation.source}) ${req.task.slice(0, 80)} (job ${jobId})
12449
+ `));
12450
+ try {
12451
+ const handle = await dispatchBackgroundJob(`local_agent:${invocation.source}`, jobId, state.journal, { cwd, argv: [bin, ...args], stdinData: stdin ?? undefined });
12452
+ return {
12453
+ job_id: handle.job_id,
12454
+ status: "running",
12455
+ pid: handle.pid,
12456
+ log_path: handle.log_path,
12457
+ message: `Delegated to ${invocation.source} in the background as ${handle.job_id}. ` + `Poll host_output({job_id:"${handle.job_id}"}) for its progress + final ` + `result; host_kill to stop it.`
12458
+ };
12459
+ } catch (e) {
12460
+ return {
12461
+ error: true,
12462
+ message: `local agent failed to start (${invocation.source}): ${String(e)}. ` + `Pick an installed agent with --local-agent <claude|codex|cursor-agent>.`
12463
+ };
12464
+ }
12465
+ }
12066
12466
  process.stderr.write(source_default.dim(`
12067
12467
  [local_agent] (${invocation.source}) ${req.task.slice(0, 100)}
12068
12468
  `));
@@ -12104,9 +12504,28 @@ async function resolveLocalAgent(sessionId, toolCallId, req, state) {
12104
12504
  log_path: result.log_path
12105
12505
  };
12106
12506
  }
12107
- async function _postToolResult(ctx, sessionId, toolCallId, result, label) {
12507
+ async function claimGate(ctx, sessionId, toolCallId, state) {
12508
+ if (state.claimed.has(toolCallId))
12509
+ return;
12510
+ const secret = state.secrets.get(toolCallId) ?? mintExecSecret();
12511
+ const secretHash = createHash2("sha256").update(secret).digest("hex");
12512
+ state.secrets.set(toolCallId, secret);
12513
+ await state.journal.recordSecret(sessionId, toolCallId, secret);
12108
12514
  try {
12109
- await ctx.api.post(`/api/chat/${encodeURIComponent(sessionId)}/tool-result`, { tool_call_id: toolCallId, result });
12515
+ await ctx.api.post(`/api/chat/${encodeURIComponent(sessionId)}/tool-claim`, { tool_call_id: toolCallId, secret_hash: secretHash });
12516
+ state.claimed.add(toolCallId);
12517
+ } catch (e) {
12518
+ process.stderr.write(source_default.yellow(`[local-exec] failed to claim gate for ${toolCallId}: ${String(e)}
12519
+ `));
12520
+ }
12521
+ }
12522
+ async function _postToolResult(ctx, sessionId, toolCallId, result, label, execSecret) {
12523
+ try {
12524
+ await ctx.api.post(`/api/chat/${encodeURIComponent(sessionId)}/tool-result`, {
12525
+ tool_call_id: toolCallId,
12526
+ result,
12527
+ ...execSecret !== undefined ? { exec_secret: execSecret } : {}
12528
+ });
12110
12529
  } catch (e) {
12111
12530
  process.stderr.write(source_default.red(`[${label}] failed to POST result: ${String(e)}
12112
12531
  `));
@@ -12118,40 +12537,63 @@ async function handleHostExecFrame(ctx, sessionId, data, state) {
12118
12537
  if (state.handled.has(id))
12119
12538
  continue;
12120
12539
  state.handled.add(id);
12121
- const result = await resolveHostExec(sessionId, id, req.command, state);
12122
- await _postToolResult(ctx, sessionId, id, result, "host_exec");
12540
+ await claimGate(ctx, sessionId, id, state);
12541
+ const result = await resolveHostExec(sessionId, id, req, state);
12542
+ await _postToolResult(ctx, sessionId, id, result, "host_exec", state.secrets.get(id));
12543
+ }
12544
+ for (const req of parseHostOutputRequests(data)) {
12545
+ const id = req.toolCallId;
12546
+ if (state.handled.has(id))
12547
+ continue;
12548
+ state.handled.add(id);
12549
+ await claimGate(ctx, sessionId, id, state);
12550
+ const result = await resolveHostOutput(req, state);
12551
+ await _postToolResult(ctx, sessionId, id, result, "host_output", state.secrets.get(id));
12552
+ }
12553
+ for (const req of parseHostKillRequests(data)) {
12554
+ const id = req.toolCallId;
12555
+ if (state.handled.has(id))
12556
+ continue;
12557
+ state.handled.add(id);
12558
+ await claimGate(ctx, sessionId, id, state);
12559
+ const result = await resolveHostKill(req, state);
12560
+ await _postToolResult(ctx, sessionId, id, result, "host_kill", state.secrets.get(id));
12123
12561
  }
12124
12562
  for (const req of parseHostScreenshotRequests(data)) {
12125
12563
  const id = req.toolCallId;
12126
12564
  if (state.handled.has(id))
12127
12565
  continue;
12128
12566
  state.handled.add(id);
12567
+ await claimGate(ctx, sessionId, id, state);
12129
12568
  const result = await resolveHostScreenshot(ctx, sessionId, req, state);
12130
- await _postToolResult(ctx, sessionId, id, result, "host_screenshot");
12569
+ await _postToolResult(ctx, sessionId, id, result, "host_screenshot", state.secrets.get(id));
12131
12570
  }
12132
12571
  for (const req of parseHostBrowserRequests(data)) {
12133
12572
  const id = req.toolCallId;
12134
12573
  if (state.handled.has(id))
12135
12574
  continue;
12136
12575
  state.handled.add(id);
12576
+ await claimGate(ctx, sessionId, id, state);
12137
12577
  const result = await resolveHostBrowser(ctx, sessionId, req, state);
12138
- await _postToolResult(ctx, sessionId, id, result, "host_browser");
12578
+ await _postToolResult(ctx, sessionId, id, result, "host_browser", state.secrets.get(id));
12139
12579
  }
12140
12580
  for (const req of parseHostFileRequests(data)) {
12141
12581
  const id = req.toolCallId;
12142
12582
  if (state.handled.has(id))
12143
12583
  continue;
12144
12584
  state.handled.add(id);
12585
+ await claimGate(ctx, sessionId, id, state);
12145
12586
  const result = await resolveHostFile(req, state);
12146
- await _postToolResult(ctx, sessionId, id, result, req.action);
12587
+ await _postToolResult(ctx, sessionId, id, result, req.action, state.secrets.get(id));
12147
12588
  }
12148
12589
  for (const req of parseLocalAgentRequests(data)) {
12149
12590
  const id = req.toolCallId;
12150
12591
  if (state.handled.has(id))
12151
12592
  continue;
12152
12593
  state.handled.add(id);
12594
+ await claimGate(ctx, sessionId, id, state);
12153
12595
  const result = await resolveLocalAgent(sessionId, id, req, state);
12154
- await _postToolResult(ctx, sessionId, id, result, "local_agent");
12596
+ await _postToolResult(ctx, sessionId, id, result, "local_agent", state.secrets.get(id));
12155
12597
  }
12156
12598
  }
12157
12599
  function parseSlashCommand(input) {
@@ -12209,7 +12651,9 @@ async function sendMessage(ctx, sessionId, message, opts = {}) {
12209
12651
  autoYes: opts.localExec.autoYes,
12210
12652
  localAgent: opts.localExec.agent,
12211
12653
  handled: new Set,
12212
- journal: new Journal(opts.localExec.cwd)
12654
+ journal: new Journal(opts.localExec.cwd),
12655
+ secrets: new Map,
12656
+ claimed: new Set
12213
12657
  } : null;
12214
12658
  const reqBody = { content: message };
12215
12659
  if (opts.localExec)
@@ -13121,8 +13565,8 @@ function registerConfigCommand(program2) {
13121
13565
  }
13122
13566
 
13123
13567
  // src/commands/definitions-apply.ts
13124
- import fs7 from "node:fs";
13125
- import path6 from "node:path";
13568
+ import fs8 from "node:fs";
13569
+ import path7 from "node:path";
13126
13570
  init_dist();
13127
13571
  init_errors();
13128
13572
  var VALID_KINDS = new Set([
@@ -13164,20 +13608,20 @@ var SERVER_GENERATED_FIELDS = new Set([
13164
13608
  ]);
13165
13609
  var AGENT_MANAGED_FIELDS = new Set(["functions", "workflows"]);
13166
13610
  function loadDefinitions(inputPath) {
13167
- const resolved = path6.resolve(inputPath);
13168
- if (!fs7.existsSync(resolved)) {
13611
+ const resolved = path7.resolve(inputPath);
13612
+ if (!fs8.existsSync(resolved)) {
13169
13613
  throw new CLIError(`Path not found: ${inputPath}`, 1 /* Error */);
13170
13614
  }
13171
- const stat = fs7.statSync(resolved);
13615
+ const stat = fs8.statSync(resolved);
13172
13616
  if (stat.isDirectory()) {
13173
13617
  return loadDirectory(resolved);
13174
13618
  }
13175
13619
  return loadFile(resolved);
13176
13620
  }
13177
13621
  function loadDirectory(dirPath) {
13178
- const entries = fs7.readdirSync(dirPath).sort();
13622
+ const entries = fs8.readdirSync(dirPath).sort();
13179
13623
  const validExts = new Set([".yaml", ".yml", ".json"]);
13180
- const files = entries.filter((e) => validExts.has(path6.extname(e).toLowerCase())).map((e) => path6.join(dirPath, e));
13624
+ const files = entries.filter((e) => validExts.has(path7.extname(e).toLowerCase())).map((e) => path7.join(dirPath, e));
13181
13625
  if (files.length === 0) {
13182
13626
  throw new CLIError(`No YAML or JSON files found in ${dirPath}`, 4 /* Validation */);
13183
13627
  }
@@ -13188,8 +13632,8 @@ function loadDirectory(dirPath) {
13188
13632
  return results;
13189
13633
  }
13190
13634
  function loadFile(filePath) {
13191
- const content = fs7.readFileSync(filePath, "utf-8");
13192
- const ext = path6.extname(filePath).toLowerCase();
13635
+ const content = fs8.readFileSync(filePath, "utf-8");
13636
+ const ext = path7.extname(filePath).toLowerCase();
13193
13637
  if (ext === ".json") {
13194
13638
  const parsed = parseJsonFile(content, filePath);
13195
13639
  return [toLocalDefinition(parsed, filePath)];
@@ -13611,7 +14055,7 @@ Examples:
13611
14055
  }
13612
14056
 
13613
14057
  // src/commands/definitions-export.ts
13614
- import fs8 from "node:fs";
14058
+ import fs9 from "node:fs";
13615
14059
  init_errors();
13616
14060
  async function exportDefinitions(ctx, opts, isJsonMode) {
13617
14061
  const params = {};
@@ -13627,7 +14071,7 @@ async function exportDefinitions(ctx, opts, isJsonMode) {
13627
14071
  const text = await res.text();
13628
14072
  if (opts.output) {
13629
14073
  try {
13630
- fs8.writeFileSync(opts.output, text);
14074
+ fs9.writeFileSync(opts.output, text);
13631
14075
  } catch (err) {
13632
14076
  throw new CLIError(`Failed to write ${opts.output}: ${err instanceof Error ? err.message : String(err)}`, 1 /* Error */);
13633
14077
  }
@@ -13935,7 +14379,7 @@ function colorStatus(status) {
13935
14379
  const fn = STATUS_COLORS[status] ?? source_default.white;
13936
14380
  return fn(status);
13937
14381
  }
13938
- function sleep(ms) {
14382
+ function sleep2(ms) {
13939
14383
  return new Promise((resolve2) => setTimeout(resolve2, ms));
13940
14384
  }
13941
14385
  async function pollUntilDone(ctx, executionId, intervalMs = 2000, timeoutMs = 300000) {
@@ -13945,7 +14389,7 @@ async function pollUntilDone(ctx, executionId, intervalMs = 2000, timeoutMs = 30
13945
14389
  if (TERMINAL_STATUSES.has(status.status)) {
13946
14390
  return status;
13947
14391
  }
13948
- await sleep(intervalMs);
14392
+ await sleep2(intervalMs);
13949
14393
  }
13950
14394
  throw new CLIError(`Timed out waiting for execution ${executionId}`, 1 /* Error */);
13951
14395
  }
@@ -14016,7 +14460,7 @@ Detached. Reattach with: martha workflows execution ${executionId} --follow`));
14016
14460
  }
14017
14461
  return;
14018
14462
  }
14019
- await sleep(1000);
14463
+ await sleep2(1000);
14020
14464
  current = await ctx.api.get(`/api/admin/executions/${encodeURIComponent(executionId)}`);
14021
14465
  }
14022
14466
  } finally {
@@ -14347,8 +14791,8 @@ function registerProjectionCommands(parentCmd, getCtx, isJson) {
14347
14791
  } catch {}
14348
14792
  }
14349
14793
  if (opts.output) {
14350
- const fs9 = await import("node:fs/promises");
14351
- await fs9.writeFile(opts.output, toWrite, "utf-8");
14794
+ const fs10 = await import("node:fs/promises");
14795
+ await fs10.writeFile(opts.output, toWrite, "utf-8");
14352
14796
  if (!isJson()) {
14353
14797
  console.error(source_default.dim(`Wrote ${format} projection of '${name}' to ${opts.output}`));
14354
14798
  }
@@ -14755,8 +15199,8 @@ Usage:
14755
15199
  });
14756
15200
  parentCmd.command("self-grant <agent>").description("View or configure an agent's self-provisioning policy (request-then-approve). " + "With no flags, prints the current policy.").option("--enable", "Allow the agent to REQUEST capability grants").option("--disable", "Disallow self-provisioning").option("--scope <scope>", "Scope ceiling: none | read_only | read_write").option("--allow <ref...>", "Add allow-list refs (function:NAME or mcp:integration/name)").option("--remove <ref...>", "Remove allow-list refs").option("--collection-root <id...>", "Add collection-subtree roots the agent may self-grant collection-scoped functions into").option("--remove-collection-root <id...>", "Remove collection roots").option("--max-pending <n>", "Max concurrently-pending requests").action(async (agent, opts) => {
14757
15201
  const ctx = getCtx();
14758
- const path7 = `${API_PATH}/${encodeURIComponent(agent)}`;
14759
- const current = await ctx.api.get(path7);
15202
+ const path8 = `${API_PATH}/${encodeURIComponent(agent)}`;
15203
+ const current = await ctx.api.get(path8);
14760
15204
  const mutating = opts.enable || opts.disable || opts.scope !== undefined || (opts.allow?.length ?? 0) > 0 || (opts.remove?.length ?? 0) > 0 || (opts.collectionRoot?.length ?? 0) > 0 || (opts.removeCollectionRoot?.length ?? 0) > 0 || opts.maxPending !== undefined;
14761
15205
  if (!mutating) {
14762
15206
  if (isJson()) {
@@ -14808,7 +15252,7 @@ Usage:
14808
15252
  roots.delete(c);
14809
15253
  body.self_grant_collection_roots = [...roots];
14810
15254
  }
14811
- const updated = await ctx.api.put(path7, body);
15255
+ const updated = await ctx.api.put(path8, body);
14812
15256
  if (isJson()) {
14813
15257
  console.log(JSON.stringify(policyView(updated), null, 2));
14814
15258
  return;
@@ -15055,7 +15499,7 @@ var triggersConfig = {
15055
15499
  };
15056
15500
 
15057
15501
  // src/commands/documents.ts
15058
- import fs9 from "node:fs";
15502
+ import fs10 from "node:fs";
15059
15503
  init_errors();
15060
15504
  var TERMINAL_STATUSES2 = new Set(["ready", "error"]);
15061
15505
  var SPINNER_FRAMES2 = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
@@ -15065,7 +15509,7 @@ function nextSpinner() {
15065
15509
  spinnerIdx++;
15066
15510
  return frame;
15067
15511
  }
15068
- function sleep2(ms) {
15512
+ function sleep3(ms) {
15069
15513
  return new Promise((resolve2) => setTimeout(resolve2, ms));
15070
15514
  }
15071
15515
  function formatBytes(bytes) {
@@ -15125,7 +15569,7 @@ async function pollIngestionUntilDone(ctx, documentId, intervalMs = 2000, timeou
15125
15569
  if (TERMINAL_STATUSES2.has(status.ingestion_status)) {
15126
15570
  return status;
15127
15571
  }
15128
- await sleep2(intervalMs);
15572
+ await sleep3(intervalMs);
15129
15573
  }
15130
15574
  throw new CLIError(`Timed out waiting for ingestion of document ${documentId}`, 1 /* Error */);
15131
15575
  }
@@ -15200,7 +15644,7 @@ Detached. Check status with: martha documents status ${documentId}`));
15200
15644
  }
15201
15645
  return;
15202
15646
  }
15203
- await sleep2(1000);
15647
+ await sleep3(1000);
15204
15648
  }
15205
15649
  } finally {
15206
15650
  process.removeListener("SIGINT", sigintHandler);
@@ -15416,7 +15860,7 @@ ${items.length} collections`));
15416
15860
  });
15417
15861
  cmd.command("upload <collection-id> <file>").description("Upload a document to a collection").option("--wait", "Wait for ingestion to complete").option("--follow", "Follow ingestion progress in real time").action(async (collectionId, filePath, opts) => {
15418
15862
  const ctx = getCtx();
15419
- if (!fs9.existsSync(filePath)) {
15863
+ if (!fs10.existsSync(filePath)) {
15420
15864
  throw new CLIError(`File not found: ${filePath}`, 4 /* Validation */);
15421
15865
  }
15422
15866
  const result = await ctx.api.upload(`/api/admin/collections/${encodeURIComponent(collectionId)}/documents`, filePath);
@@ -16045,10 +16489,10 @@ function registerApprovalCommands(program2) {
16045
16489
  }
16046
16490
 
16047
16491
  // src/commands/tasks.ts
16048
- import { readFileSync } from "node:fs";
16492
+ import { readFileSync as readFileSync2 } from "node:fs";
16049
16493
  init_errors();
16050
16494
  var truncate2 = (s, n) => s.length > n ? s.slice(0, n - 1) + "…" : s;
16051
- var sleep3 = (ms, signal) => new Promise((resolve2) => {
16495
+ var sleep4 = (ms, signal) => new Promise((resolve2) => {
16052
16496
  if (signal?.aborted) {
16053
16497
  resolve2();
16054
16498
  return;
@@ -16414,7 +16858,7 @@ ${items.length} task(s) available`));
16414
16858
  if (reconnects > maxReconnects) {
16415
16859
  throw new CLIError(`Exceeded --max-reconnects (${maxReconnects})`, 1 /* Error */);
16416
16860
  }
16417
- await sleep3(backoffMs, abort.signal);
16861
+ await sleep4(backoffMs, abort.signal);
16418
16862
  backoffMs = Math.min(backoffMs * 2, maxBackoff);
16419
16863
  continue;
16420
16864
  }
@@ -16478,7 +16922,7 @@ ${items.length} task(s) available`));
16478
16922
  if (reconnects > maxReconnects) {
16479
16923
  throw new CLIError(`Exceeded --max-reconnects (${maxReconnects})`, 1 /* Error */);
16480
16924
  }
16481
- await sleep3(backoffMs, abort.signal);
16925
+ await sleep4(backoffMs, abort.signal);
16482
16926
  backoffMs = Math.min(backoffMs * 2, maxBackoff);
16483
16927
  }
16484
16928
  } finally {
@@ -16491,7 +16935,7 @@ ${items.length} task(s) available`));
16491
16935
  let rawOutcome;
16492
16936
  if (opts.outcomeFile) {
16493
16937
  try {
16494
- rawOutcome = readFileSync(opts.outcomeFile, "utf-8");
16938
+ rawOutcome = readFileSync2(opts.outcomeFile, "utf-8");
16495
16939
  } catch {
16496
16940
  throw new CLIError(`--outcome-file could not be read: ${opts.outcomeFile}`, 4 /* Validation */);
16497
16941
  }
@@ -16993,8 +17437,8 @@ ${data.length} spec(s)`));
16993
17437
  Resources:
16994
17438
  `));
16995
17439
  for (const r of resources) {
16996
- const path7 = r.path || `/${r.name}`;
16997
- console.log(` ${source_default.cyan(r.label || r.name)}` + source_default.dim(` → ${path7}`));
17440
+ const path8 = r.path || `/${r.name}`;
17441
+ console.log(` ${source_default.cyan(r.label || r.name)}` + source_default.dim(` → ${path8}`));
16998
17442
  }
16999
17443
  }
17000
17444
  try {
@@ -17014,14 +17458,14 @@ Functions:
17014
17458
  console.log(source_default.dim(`
17015
17459
  Proxy: martha integrations proxy ${name} GET /<path>`));
17016
17460
  });
17017
- cmd.command("proxy <name> <method> <path>").description("Send a request through the plugin proxy").option("--data <json>", "JSON request body").option("--query <params>", "Query params as key=val&key=val").action(async (name, method, path7, opts) => {
17461
+ cmd.command("proxy <name> <method> <path>").description("Send a request through the plugin proxy").option("--data <json>", "JSON request body").option("--query <params>", "Query params as key=val&key=val").action(async (name, method, path8, opts) => {
17018
17462
  const ctx = getCtx();
17019
17463
  const upperMethod = method.toUpperCase();
17020
17464
  const allowed = new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
17021
17465
  if (!allowed.has(upperMethod)) {
17022
17466
  throw new CLIError(`Invalid method: ${method}`, 4 /* Validation */, "Allowed: GET, POST, PUT, PATCH, DELETE");
17023
17467
  }
17024
- const cleanPath = path7.startsWith("/") ? path7.slice(1) : path7;
17468
+ const cleanPath = path8.startsWith("/") ? path8.slice(1) : path8;
17025
17469
  const proxyUrl = `/api/admin/plugins/${encodeURIComponent(name)}/${cleanPath}`;
17026
17470
  const params = {};
17027
17471
  if (opts.query) {
@@ -17231,8 +17675,8 @@ async function resolveCredentialValue(value) {
17231
17675
  if (value === "-")
17232
17676
  return readStdin();
17233
17677
  if (value.startsWith("@")) {
17234
- const fs10 = await import("node:fs/promises");
17235
- return (await fs10.readFile(value.slice(1), "utf-8")).trim();
17678
+ const fs11 = await import("node:fs/promises");
17679
+ return (await fs11.readFile(value.slice(1), "utf-8")).trim();
17236
17680
  }
17237
17681
  return value;
17238
17682
  }
@@ -17347,8 +17791,8 @@ Notification connections`));
17347
17791
  if (credentialValue === "-") {
17348
17792
  credentialValue = await readStdin2();
17349
17793
  } else if (credentialValue?.startsWith("@")) {
17350
- const fs10 = await import("node:fs/promises");
17351
- credentialValue = await fs10.readFile(credentialValue.slice(1), "utf-8");
17794
+ const fs11 = await import("node:fs/promises");
17795
+ credentialValue = await fs11.readFile(credentialValue.slice(1), "utf-8");
17352
17796
  }
17353
17797
  if (!credentialValue) {
17354
17798
  throw new Error("--credential-value is required (use '-' for stdin or '@path' for file).");
@@ -17398,11 +17842,11 @@ async function readStdin2() {
17398
17842
 
17399
17843
  // src/commands/messaging.ts
17400
17844
  init_errors();
17401
- async function messagingFetch(baseUrl, path7, opts) {
17402
- if (/^(https?:)?\/\//i.test(path7)) {
17845
+ async function messagingFetch(baseUrl, path8, opts) {
17846
+ if (/^(https?:)?\/\//i.test(path8)) {
17403
17847
  throw new CLIError("Absolute URL paths are not allowed", 1 /* Error */);
17404
17848
  }
17405
- const url = new URL(path7, baseUrl);
17849
+ const url = new URL(path8, baseUrl);
17406
17850
  const headers = {
17407
17851
  "Content-Type": "application/json"
17408
17852
  };
@@ -18682,7 +19126,7 @@ ${models.length} models`));
18682
19126
  }
18683
19127
 
18684
19128
  // src/commands/wiki.ts
18685
- import fs10 from "node:fs";
19129
+ import fs11 from "node:fs";
18686
19130
  init_errors();
18687
19131
  function registerWikiCommands(program2) {
18688
19132
  const cmd = program2.command("wiki").description("Manage tenant wiki pages, settings, schema, recompile (#245 D5.4)");
@@ -18729,14 +19173,14 @@ function registerWikiCommands(program2) {
18729
19173
  console.log(source_default.dim(`
18730
19174
  ${pages.length} pages`));
18731
19175
  });
18732
- cmd.command("get <path>").description("Fetch the raw markdown body of a wiki page").option("--out <file>", "Write body to file instead of stdout").action(async (path7, opts) => {
19176
+ cmd.command("get <path>").description("Fetch the raw markdown body of a wiki page").option("--out <file>", "Write body to file instead of stdout").action(async (path8, opts) => {
18733
19177
  const ctx = getCtx();
18734
- const safe = path7.split("/").map(encodeURIComponent).join("/");
19178
+ const safe = path8.split("/").map(encodeURIComponent).join("/");
18735
19179
  const resp = await ctx.api.getRaw(`/api/wiki/pages/${safe}`, {
18736
19180
  headers: { Accept: "text/markdown,*/*" }
18737
19181
  });
18738
19182
  if (resp.status === 404) {
18739
- throw new CLIError(`page not found: ${path7}`, 3 /* NotFound */);
19183
+ throw new CLIError(`page not found: ${path8}`, 3 /* NotFound */);
18740
19184
  }
18741
19185
  if (!resp.ok) {
18742
19186
  const detail = await resp.text();
@@ -18744,16 +19188,16 @@ ${pages.length} pages`));
18744
19188
  }
18745
19189
  const body = await resp.text();
18746
19190
  if (opts.out) {
18747
- fs10.writeFileSync(opts.out, body);
19191
+ fs11.writeFileSync(opts.out, body);
18748
19192
  if (!isJson()) {
18749
19193
  console.log(source_default.dim(`wrote ${body.length} bytes to ${opts.out}`));
18750
19194
  } else {
18751
- console.log(JSON.stringify({ path: path7, bytes: body.length, file: opts.out }));
19195
+ console.log(JSON.stringify({ path: path8, bytes: body.length, file: opts.out }));
18752
19196
  }
18753
19197
  return;
18754
19198
  }
18755
19199
  if (isJson()) {
18756
- console.log(JSON.stringify({ path: path7, body, etag: resp.headers.get("ETag") }));
19200
+ console.log(JSON.stringify({ path: path8, body, etag: resp.headers.get("ETag") }));
18757
19201
  } else {
18758
19202
  process.stdout.write(body);
18759
19203
  }
@@ -18848,10 +19292,10 @@ ${pages.length} pages`));
18848
19292
  }
18849
19293
  if (opts.compilePromptOverrideFile) {
18850
19294
  const file = String(opts.compilePromptOverrideFile);
18851
- if (!fs10.existsSync(file)) {
19295
+ if (!fs11.existsSync(file)) {
18852
19296
  throw new CLIError(`override file not found: ${file}`, 3 /* NotFound */);
18853
19297
  }
18854
- const content = fs10.readFileSync(file, "utf8");
19298
+ const content = fs11.readFileSync(file, "utf8");
18855
19299
  const bytes = Buffer.byteLength(content, "utf8");
18856
19300
  if (bytes > 16 * 1024) {
18857
19301
  throw new CLIError(`compile prompt override exceeds 16 KB (${bytes} bytes)`, 4 /* Validation */);
@@ -18875,7 +19319,7 @@ ${pages.length} pages`));
18875
19319
  const ctx = getCtx();
18876
19320
  const resp = await ctx.api.get("/api/wiki/schema");
18877
19321
  if (opts.out) {
18878
- fs10.writeFileSync(opts.out, resp.body);
19322
+ fs11.writeFileSync(opts.out, resp.body);
18879
19323
  if (!isJson()) {
18880
19324
  console.log(source_default.dim(`wrote ${resp.body.length} bytes to ${opts.out}`));
18881
19325
  } else {
@@ -18891,10 +19335,10 @@ ${pages.length} pages`));
18891
19335
  });
18892
19336
  schemaCmd.command("set").description("Replace the tenant wiki schema with the contents of <file>").requiredOption("--file <file>", "Path to schema markdown file").action(async (opts) => {
18893
19337
  const ctx = getCtx();
18894
- if (!fs10.existsSync(opts.file)) {
19338
+ if (!fs11.existsSync(opts.file)) {
18895
19339
  throw new CLIError(`schema file not found: ${opts.file}`, 3 /* NotFound */);
18896
19340
  }
18897
- const body = fs10.readFileSync(opts.file, "utf8");
19341
+ const body = fs11.readFileSync(opts.file, "utf8");
18898
19342
  const bytes = Buffer.byteLength(body, "utf8");
18899
19343
  if (bytes > 64 * 1024) {
18900
19344
  throw new CLIError(`schema body exceeds 64 KB (${bytes} bytes)`, 4 /* Validation */);
@@ -19420,8 +19864,8 @@ async function resolveCredentialFlag(value) {
19420
19864
  return Buffer.concat(chunks).toString("utf-8").trim();
19421
19865
  }
19422
19866
  if (value.startsWith("@")) {
19423
- const fs11 = await import("node:fs/promises");
19424
- return (await fs11.readFile(value.slice(1), "utf-8")).trim();
19867
+ const fs12 = await import("node:fs/promises");
19868
+ return (await fs12.readFile(value.slice(1), "utf-8")).trim();
19425
19869
  }
19426
19870
  return value;
19427
19871
  }
@@ -20607,19 +21051,19 @@ function registerDoctorCommand(program2) {
20607
21051
 
20608
21052
  // src/commands/skill.ts
20609
21053
  init_errors();
20610
- import fs11 from "node:fs";
20611
- import path7 from "node:path";
21054
+ import fs12 from "node:fs";
21055
+ import path8 from "node:path";
20612
21056
  import { fileURLToPath } from "node:url";
20613
21057
  function locateSkill() {
20614
- const here = path7.dirname(fileURLToPath(import.meta.url));
21058
+ const here = path8.dirname(fileURLToPath(import.meta.url));
20615
21059
  const candidates = [
20616
- path7.join(here, "skills", "martha-cli", "SKILL.md"),
20617
- path7.join(here, "..", "skills", "martha-cli", "SKILL.md"),
20618
- path7.join(here, "..", "..", "..", "skills", "martha-cli", "SKILL.md"),
20619
- path7.join(here, "..", "..", "skills", "martha-cli", "SKILL.md")
21060
+ path8.join(here, "skills", "martha-cli", "SKILL.md"),
21061
+ path8.join(here, "..", "skills", "martha-cli", "SKILL.md"),
21062
+ path8.join(here, "..", "..", "..", "skills", "martha-cli", "SKILL.md"),
21063
+ path8.join(here, "..", "..", "skills", "martha-cli", "SKILL.md")
20620
21064
  ];
20621
21065
  for (const p of candidates) {
20622
- if (fs11.existsSync(p))
21066
+ if (fs12.existsSync(p))
20623
21067
  return p;
20624
21068
  }
20625
21069
  return null;
@@ -20629,7 +21073,7 @@ async function skillCommand() {
20629
21073
  if (!skillPath) {
20630
21074
  throw new CLIError("SKILL.md not found in the installed package. Reinstall via `npm i -g @aiaiai-pt/martha-cli` or `npx -y @aiaiai-pt/martha-cli@latest skill`.", 1 /* Error */);
20631
21075
  }
20632
- const body = fs11.readFileSync(skillPath, "utf-8");
21076
+ const body = fs12.readFileSync(skillPath, "utf-8");
20633
21077
  process.stdout.write(body);
20634
21078
  }
20635
21079
  function registerSkillCommand(program2) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiai-pt/martha-cli",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
4
4
  "description": "Terminal-first client for the Martha AI platform",
5
5
  "homepage": "https://docs.martha.nomadriver.co",
6
6
  "repository": {