@aiaiai-pt/martha-cli 0.26.0 → 0.27.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.
Files changed (2) hide show
  1. package/dist/index.js +572 -75
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -11081,7 +11081,7 @@ init_config();
11081
11081
  init_errors();
11082
11082
 
11083
11083
  // src/version.ts
11084
- var CLI_VERSION = "0.26.0";
11084
+ var CLI_VERSION = "0.27.1";
11085
11085
 
11086
11086
  // src/commands/sessions.ts
11087
11087
  function relativeTime(iso) {
@@ -11358,6 +11358,9 @@ import { spawn } from "node:child_process";
11358
11358
  import { randomBytes as randomBytes2 } from "node:crypto";
11359
11359
  import { closeSync, openSync, promises as fs5 } from "node:fs";
11360
11360
  import * as path4 from "node:path";
11361
+ function sleep(ms) {
11362
+ return new Promise((r) => setTimeout(r, ms));
11363
+ }
11361
11364
  var DEFAULT_TIMEOUT_MS = 20000;
11362
11365
  var MAX_OUTPUT = 256 * 1024;
11363
11366
  function runnerDir(cwd) {
@@ -11452,16 +11455,87 @@ class Journal {
11452
11455
  try {
11453
11456
  const raw = JSON.parse(await fs5.readFile(this.file(), "utf8"));
11454
11457
  if (raw["entries"] !== undefined) {
11455
- return raw;
11458
+ const f = raw;
11459
+ if (!f.secrets)
11460
+ f.secrets = {};
11461
+ if (!f.jobs)
11462
+ f.jobs = {};
11463
+ return f;
11456
11464
  }
11457
- return { entries: raw, secrets: {} };
11465
+ return {
11466
+ entries: raw,
11467
+ secrets: {},
11468
+ jobs: {}
11469
+ };
11458
11470
  } catch {
11459
- return { entries: {}, secrets: {} };
11471
+ return { entries: {}, secrets: {}, jobs: {} };
11460
11472
  }
11461
11473
  }
11462
11474
  async save(data) {
11463
11475
  await fs5.mkdir(runnerDir(this.cwd), { recursive: true });
11464
- 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]);
11465
11539
  }
11466
11540
  static key(sessionId, toolCallId) {
11467
11541
  return `${sessionId}:${toolCallId}`;
@@ -11521,7 +11595,12 @@ function parseHostExecRequests(data) {
11521
11595
  continue;
11522
11596
  if (!t.tool_call_id)
11523
11597
  continue;
11524
- 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
+ });
11525
11604
  }
11526
11605
  return out;
11527
11606
  }
@@ -11917,12 +11996,247 @@ function parseLocalAgentRequests(data) {
11917
11996
  out.push({
11918
11997
  toolCallId: t.tool_call_id,
11919
11998
  task: t.args.task,
11920
- 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
11921
12001
  });
11922
12002
  }
11923
12003
  return out;
11924
12004
  }
11925
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
+
11926
12240
  // src/commands/chat.ts
11927
12241
  async function uploadImageArtifact(ctx, sessionId, png) {
11928
12242
  try {
@@ -11941,7 +12255,8 @@ async function uploadImageArtifact(ctx, sessionId, png) {
11941
12255
  return { error: `image upload failed: ${String(e)}` };
11942
12256
  }
11943
12257
  }
11944
- async function resolveHostExec(sessionId, toolCallId, command, state) {
12258
+ async function resolveHostExec(sessionId, toolCallId, req, state) {
12259
+ const command = req.command;
11945
12260
  const decision = decideFromJournal(await state.journal.lookup(sessionId, toolCallId));
11946
12261
  if (decision.kind === "replay")
11947
12262
  return decision.result;
@@ -11962,20 +12277,65 @@ async function resolveHostExec(sessionId, toolCallId, command, state) {
11962
12277
  message: "Host execution declined; re-run with --yes to allow."
11963
12278
  };
11964
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
+ }
11965
12305
  process.stderr.write(source_default.dim(`
11966
12306
  [host_exec] $ ${command}
11967
- (cwd: ${state.cwd})
12307
+ (cwd: ${cwd})
11968
12308
  `));
11969
12309
  await state.journal.recordRunning(sessionId, toolCallId, command, new Date().toISOString());
11970
12310
  let result;
11971
12311
  try {
11972
- result = await runHostCommand(command, toolCallId, { cwd: state.cwd });
12312
+ result = await runHostCommand(command, toolCallId, { cwd });
11973
12313
  } catch (e) {
11974
12314
  result = { stdout: "", stderr: String(e), exit_code: -1 };
11975
12315
  }
11976
12316
  await state.journal.recordDone(sessionId, toolCallId, result);
11977
12317
  return result;
11978
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
+ }
11979
12339
  async function resolveHostScreenshot(ctx, sessionId, req, state) {
11980
12340
  if (!state.autoYes) {
11981
12341
  process.stderr.write(source_default.yellow(`
@@ -12082,6 +12442,27 @@ async function resolveLocalAgent(sessionId, toolCallId, req, state) {
12082
12442
  }
12083
12443
  const cwd = req.cwd ? `${state.cwd}/${req.cwd}` : state.cwd;
12084
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
+ }
12085
12466
  process.stderr.write(source_default.dim(`
12086
12467
  [local_agent] (${invocation.source}) ${req.task.slice(0, 100)}
12087
12468
  `));
@@ -12157,9 +12538,27 @@ async function handleHostExecFrame(ctx, sessionId, data, state) {
12157
12538
  continue;
12158
12539
  state.handled.add(id);
12159
12540
  await claimGate(ctx, sessionId, id, state);
12160
- const result = await resolveHostExec(sessionId, id, req.command, state);
12541
+ const result = await resolveHostExec(sessionId, id, req, state);
12161
12542
  await _postToolResult(ctx, sessionId, id, result, "host_exec", state.secrets.get(id));
12162
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));
12561
+ }
12163
12562
  for (const req of parseHostScreenshotRequests(data)) {
12164
12563
  const id = req.toolCallId;
12165
12564
  if (state.handled.has(id))
@@ -13166,8 +13565,8 @@ function registerConfigCommand(program2) {
13166
13565
  }
13167
13566
 
13168
13567
  // src/commands/definitions-apply.ts
13169
- import fs7 from "node:fs";
13170
- import path6 from "node:path";
13568
+ import fs8 from "node:fs";
13569
+ import path7 from "node:path";
13171
13570
  init_dist();
13172
13571
  init_errors();
13173
13572
  var VALID_KINDS = new Set([
@@ -13209,20 +13608,20 @@ var SERVER_GENERATED_FIELDS = new Set([
13209
13608
  ]);
13210
13609
  var AGENT_MANAGED_FIELDS = new Set(["functions", "workflows"]);
13211
13610
  function loadDefinitions(inputPath) {
13212
- const resolved = path6.resolve(inputPath);
13213
- if (!fs7.existsSync(resolved)) {
13611
+ const resolved = path7.resolve(inputPath);
13612
+ if (!fs8.existsSync(resolved)) {
13214
13613
  throw new CLIError(`Path not found: ${inputPath}`, 1 /* Error */);
13215
13614
  }
13216
- const stat = fs7.statSync(resolved);
13615
+ const stat = fs8.statSync(resolved);
13217
13616
  if (stat.isDirectory()) {
13218
13617
  return loadDirectory(resolved);
13219
13618
  }
13220
13619
  return loadFile(resolved);
13221
13620
  }
13222
13621
  function loadDirectory(dirPath) {
13223
- const entries = fs7.readdirSync(dirPath).sort();
13622
+ const entries = fs8.readdirSync(dirPath).sort();
13224
13623
  const validExts = new Set([".yaml", ".yml", ".json"]);
13225
- 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));
13226
13625
  if (files.length === 0) {
13227
13626
  throw new CLIError(`No YAML or JSON files found in ${dirPath}`, 4 /* Validation */);
13228
13627
  }
@@ -13233,8 +13632,8 @@ function loadDirectory(dirPath) {
13233
13632
  return results;
13234
13633
  }
13235
13634
  function loadFile(filePath) {
13236
- const content = fs7.readFileSync(filePath, "utf-8");
13237
- const ext = path6.extname(filePath).toLowerCase();
13635
+ const content = fs8.readFileSync(filePath, "utf-8");
13636
+ const ext = path7.extname(filePath).toLowerCase();
13238
13637
  if (ext === ".json") {
13239
13638
  const parsed = parseJsonFile(content, filePath);
13240
13639
  return [toLocalDefinition(parsed, filePath)];
@@ -13656,7 +14055,7 @@ Examples:
13656
14055
  }
13657
14056
 
13658
14057
  // src/commands/definitions-export.ts
13659
- import fs8 from "node:fs";
14058
+ import fs9 from "node:fs";
13660
14059
  init_errors();
13661
14060
  async function exportDefinitions(ctx, opts, isJsonMode) {
13662
14061
  const params = {};
@@ -13672,7 +14071,7 @@ async function exportDefinitions(ctx, opts, isJsonMode) {
13672
14071
  const text = await res.text();
13673
14072
  if (opts.output) {
13674
14073
  try {
13675
- fs8.writeFileSync(opts.output, text);
14074
+ fs9.writeFileSync(opts.output, text);
13676
14075
  } catch (err) {
13677
14076
  throw new CLIError(`Failed to write ${opts.output}: ${err instanceof Error ? err.message : String(err)}`, 1 /* Error */);
13678
14077
  }
@@ -13980,7 +14379,7 @@ function colorStatus(status) {
13980
14379
  const fn = STATUS_COLORS[status] ?? source_default.white;
13981
14380
  return fn(status);
13982
14381
  }
13983
- function sleep(ms) {
14382
+ function sleep2(ms) {
13984
14383
  return new Promise((resolve2) => setTimeout(resolve2, ms));
13985
14384
  }
13986
14385
  async function pollUntilDone(ctx, executionId, intervalMs = 2000, timeoutMs = 300000) {
@@ -13990,7 +14389,7 @@ async function pollUntilDone(ctx, executionId, intervalMs = 2000, timeoutMs = 30
13990
14389
  if (TERMINAL_STATUSES.has(status.status)) {
13991
14390
  return status;
13992
14391
  }
13993
- await sleep(intervalMs);
14392
+ await sleep2(intervalMs);
13994
14393
  }
13995
14394
  throw new CLIError(`Timed out waiting for execution ${executionId}`, 1 /* Error */);
13996
14395
  }
@@ -14061,7 +14460,7 @@ Detached. Reattach with: martha workflows execution ${executionId} --follow`));
14061
14460
  }
14062
14461
  return;
14063
14462
  }
14064
- await sleep(1000);
14463
+ await sleep2(1000);
14065
14464
  current = await ctx.api.get(`/api/admin/executions/${encodeURIComponent(executionId)}`);
14066
14465
  }
14067
14466
  } finally {
@@ -14392,8 +14791,8 @@ function registerProjectionCommands(parentCmd, getCtx, isJson) {
14392
14791
  } catch {}
14393
14792
  }
14394
14793
  if (opts.output) {
14395
- const fs9 = await import("node:fs/promises");
14396
- 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");
14397
14796
  if (!isJson()) {
14398
14797
  console.error(source_default.dim(`Wrote ${format} projection of '${name}' to ${opts.output}`));
14399
14798
  }
@@ -14800,8 +15199,8 @@ Usage:
14800
15199
  });
14801
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) => {
14802
15201
  const ctx = getCtx();
14803
- const path7 = `${API_PATH}/${encodeURIComponent(agent)}`;
14804
- const current = await ctx.api.get(path7);
15202
+ const path8 = `${API_PATH}/${encodeURIComponent(agent)}`;
15203
+ const current = await ctx.api.get(path8);
14805
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;
14806
15205
  if (!mutating) {
14807
15206
  if (isJson()) {
@@ -14853,7 +15252,7 @@ Usage:
14853
15252
  roots.delete(c);
14854
15253
  body.self_grant_collection_roots = [...roots];
14855
15254
  }
14856
- const updated = await ctx.api.put(path7, body);
15255
+ const updated = await ctx.api.put(path8, body);
14857
15256
  if (isJson()) {
14858
15257
  console.log(JSON.stringify(policyView(updated), null, 2));
14859
15258
  return;
@@ -15100,7 +15499,7 @@ var triggersConfig = {
15100
15499
  };
15101
15500
 
15102
15501
  // src/commands/documents.ts
15103
- import fs9 from "node:fs";
15502
+ import fs10 from "node:fs";
15104
15503
  init_errors();
15105
15504
  var TERMINAL_STATUSES2 = new Set(["ready", "error"]);
15106
15505
  var SPINNER_FRAMES2 = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
@@ -15110,7 +15509,7 @@ function nextSpinner() {
15110
15509
  spinnerIdx++;
15111
15510
  return frame;
15112
15511
  }
15113
- function sleep2(ms) {
15512
+ function sleep3(ms) {
15114
15513
  return new Promise((resolve2) => setTimeout(resolve2, ms));
15115
15514
  }
15116
15515
  function formatBytes(bytes) {
@@ -15170,7 +15569,7 @@ async function pollIngestionUntilDone(ctx, documentId, intervalMs = 2000, timeou
15170
15569
  if (TERMINAL_STATUSES2.has(status.ingestion_status)) {
15171
15570
  return status;
15172
15571
  }
15173
- await sleep2(intervalMs);
15572
+ await sleep3(intervalMs);
15174
15573
  }
15175
15574
  throw new CLIError(`Timed out waiting for ingestion of document ${documentId}`, 1 /* Error */);
15176
15575
  }
@@ -15245,7 +15644,7 @@ Detached. Check status with: martha documents status ${documentId}`));
15245
15644
  }
15246
15645
  return;
15247
15646
  }
15248
- await sleep2(1000);
15647
+ await sleep3(1000);
15249
15648
  }
15250
15649
  } finally {
15251
15650
  process.removeListener("SIGINT", sigintHandler);
@@ -15461,7 +15860,7 @@ ${items.length} collections`));
15461
15860
  });
15462
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) => {
15463
15862
  const ctx = getCtx();
15464
- if (!fs9.existsSync(filePath)) {
15863
+ if (!fs10.existsSync(filePath)) {
15465
15864
  throw new CLIError(`File not found: ${filePath}`, 4 /* Validation */);
15466
15865
  }
15467
15866
  const result = await ctx.api.upload(`/api/admin/collections/${encodeURIComponent(collectionId)}/documents`, filePath);
@@ -16090,10 +16489,10 @@ function registerApprovalCommands(program2) {
16090
16489
  }
16091
16490
 
16092
16491
  // src/commands/tasks.ts
16093
- import { readFileSync } from "node:fs";
16492
+ import { readFileSync as readFileSync2 } from "node:fs";
16094
16493
  init_errors();
16095
16494
  var truncate2 = (s, n) => s.length > n ? s.slice(0, n - 1) + "…" : s;
16096
- var sleep3 = (ms, signal) => new Promise((resolve2) => {
16495
+ var sleep4 = (ms, signal) => new Promise((resolve2) => {
16097
16496
  if (signal?.aborted) {
16098
16497
  resolve2();
16099
16498
  return;
@@ -16231,7 +16630,7 @@ ${items.length} task(s)`));
16231
16630
  }
16232
16631
  console.log(`Open: ${source_default.yellow(data.open ?? 0)} ` + `Running: ${source_default.blue(data.running ?? 0)} ` + `Claimed: ${source_default.magenta(data.claimed ?? 0)} ` + `Completed: ${source_default.green(data.completed ?? 0)} ` + `Failed: ${source_default.red(data.failed ?? 0)} ` + `Cancelled: ${source_default.dim(data.cancelled ?? 0)}`);
16233
16632
  });
16234
- cmd.command("create").description("Create a new task").requiredOption("--agent <name-or-id>", "Agent definition ID").requiredOption("--goal <text>", "Goal / instruction for the agent").option("--title <text>", "Short title for the task").option("--priority <priority>", "Priority (low, medium, high, urgent)", "medium").option("--context <json>", "Structured context as JSON string").option("--team <team-id>", "Assign task to a team").option("--assigned-agent <agent-id>", "Assign task to a specific agent").option("--sticky", "Keep assignment even if agent goes offline").action(async (opts) => {
16633
+ cmd.command("create").description("Create a new task").requiredOption("--agent <name-or-id>", "Agent definition ID").requiredOption("--goal <text>", "Goal / instruction for the agent").option("--title <text>", "Short title for the task").option("--priority <priority>", "Priority (low, medium, high, urgent)", "medium").option("--context <json>", "Structured context as JSON string").option("--team <team-id>", "Assign task to a team").option("--assigned-agent <agent-id>", "Assign task to a specific agent").option("--sticky", "Keep assignment even if agent goes offline").option("--no-auto-execute", "Create the task as claimable (open) for an external runner to pull, instead of auto-running it via the cloud workflow").action(async (opts) => {
16235
16634
  const ctx = getCtx();
16236
16635
  const body = {
16237
16636
  agent_definition_id: opts.agent,
@@ -16253,6 +16652,8 @@ ${items.length} task(s)`));
16253
16652
  body.assigned_agent_id = opts.assignedAgent;
16254
16653
  if (opts.sticky)
16255
16654
  body.sticky_assignment = true;
16655
+ if (opts.autoExecute === false)
16656
+ body.auto_execute = false;
16256
16657
  const result = await ctx.api.post("/api/admin/tasks", body);
16257
16658
  if (isJson()) {
16258
16659
  console.log(JSON.stringify(result, null, 2));
@@ -16459,7 +16860,7 @@ ${items.length} task(s) available`));
16459
16860
  if (reconnects > maxReconnects) {
16460
16861
  throw new CLIError(`Exceeded --max-reconnects (${maxReconnects})`, 1 /* Error */);
16461
16862
  }
16462
- await sleep3(backoffMs, abort.signal);
16863
+ await sleep4(backoffMs, abort.signal);
16463
16864
  backoffMs = Math.min(backoffMs * 2, maxBackoff);
16464
16865
  continue;
16465
16866
  }
@@ -16523,7 +16924,7 @@ ${items.length} task(s) available`));
16523
16924
  if (reconnects > maxReconnects) {
16524
16925
  throw new CLIError(`Exceeded --max-reconnects (${maxReconnects})`, 1 /* Error */);
16525
16926
  }
16526
- await sleep3(backoffMs, abort.signal);
16927
+ await sleep4(backoffMs, abort.signal);
16527
16928
  backoffMs = Math.min(backoffMs * 2, maxBackoff);
16528
16929
  }
16529
16930
  } finally {
@@ -16536,7 +16937,7 @@ ${items.length} task(s) available`));
16536
16937
  let rawOutcome;
16537
16938
  if (opts.outcomeFile) {
16538
16939
  try {
16539
- rawOutcome = readFileSync(opts.outcomeFile, "utf-8");
16940
+ rawOutcome = readFileSync2(opts.outcomeFile, "utf-8");
16540
16941
  } catch {
16541
16942
  throw new CLIError(`--outcome-file could not be read: ${opts.outcomeFile}`, 4 /* Validation */);
16542
16943
  }
@@ -16562,6 +16963,101 @@ ${items.length} task(s) available`));
16562
16963
  });
16563
16964
  }
16564
16965
 
16966
+ // src/commands/runner.ts
16967
+ init_errors();
16968
+ function sleep5(ms) {
16969
+ return new Promise((r) => setTimeout(r, ms));
16970
+ }
16971
+ async function runOnce(ctx, opts, handled) {
16972
+ const tasks = await ctx.api.get("/api/tasks/poll", {
16973
+ params: { agent_id: opts.agent, limit: String(opts.pollLimit ?? 5) }
16974
+ });
16975
+ const candidates = Array.isArray(tasks) ? tasks : [];
16976
+ const task = candidates.find((t) => (t.status ?? "open") === "open" && !handled?.has(t.id));
16977
+ if (!task)
16978
+ return { ran: false };
16979
+ if (!opts.autoYes) {
16980
+ process.stderr.write(source_default.yellow(`
16981
+ [runner] refused task ${task.id} — re-run with --yes to allow host execution
16982
+ `));
16983
+ return { ran: false, taskId: task.id, status: "refused" };
16984
+ }
16985
+ handled?.add(task.id);
16986
+ const claimed = await ctx.api.post(`/api/tasks/${encodeURIComponent(task.id)}/claim`, {});
16987
+ const goal = claimed.goal ?? task.goal ?? "";
16988
+ process.stderr.write(source_default.dim(`
16989
+ [runner] claimed ${task.id} — running: ${goal}
16990
+ `));
16991
+ let result;
16992
+ try {
16993
+ result = await runHostCommand(goal, `task-${task.id}`, { cwd: opts.cwd });
16994
+ } catch (e) {
16995
+ result = { stdout: "", stderr: String(e), exit_code: -1 };
16996
+ }
16997
+ const exit_code = result.exit_code;
16998
+ const stdout = result.stdout || result.stderr || "";
16999
+ const status = exit_code === 0 ? "completed" : "failed";
17000
+ await ctx.api.post(`/api/tasks/${encodeURIComponent(task.id)}/complete`, {
17001
+ outcome: { stdout, exit_code },
17002
+ status
17003
+ });
17004
+ process.stderr.write(source_default.dim(`[runner] completed ${task.id} — ${status} (exit ${exit_code})
17005
+ `));
17006
+ return { ran: true, taskId: task.id, status, exitCode: exit_code };
17007
+ }
17008
+ function registerRunnerCommand(program2) {
17009
+ program2.command("runner").description("Run as an external agent's executor: claim tasks and run them on this host.").requiredOption("--agent <id>", "Agent definition id this runner serves").option("--once", "Claim + run a single task, then exit").option("--poll-interval <ms>", "Poll interval when idle (ms)", "3000").option("--cwd <dir>", "Scoped workspace for task execution", process.cwd()).option("--yes", "Consent to run host commands (required to execute)").action(async (opts) => {
17010
+ const ctx = createContext({
17011
+ profileOverride: program2.opts().profile,
17012
+ verbose: program2.opts().verbose
17013
+ });
17014
+ if (program2.opts().apiUrl)
17015
+ ctx.profile.api_url = program2.opts().apiUrl;
17016
+ const pollInterval = parseInt(opts.pollInterval, 10);
17017
+ if (isNaN(pollInterval) || pollInterval < 250) {
17018
+ throw new CLIError("--poll-interval must be >= 250 (ms)", 4 /* Validation */);
17019
+ }
17020
+ const runOpts = {
17021
+ agent: opts.agent,
17022
+ cwd: opts.cwd,
17023
+ autoYes: !!opts.yes
17024
+ };
17025
+ process.stderr.write(source_default.dim(`[runner] serving agent ${opts.agent} (cwd ${opts.cwd})${opts.once ? " — once" : ""}
17026
+ `));
17027
+ if (opts.once) {
17028
+ await runOnce(ctx, runOpts);
17029
+ return;
17030
+ }
17031
+ let stop = false;
17032
+ const onSignal = () => {
17033
+ stop = true;
17034
+ process.stderr.write(`
17035
+ [runner] stopping…
17036
+ `);
17037
+ };
17038
+ process.on("SIGINT", onSignal);
17039
+ process.on("SIGTERM", onSignal);
17040
+ const handled = new Set;
17041
+ try {
17042
+ while (!stop) {
17043
+ let r;
17044
+ try {
17045
+ r = await runOnce(ctx, runOpts, handled);
17046
+ } catch (e) {
17047
+ process.stderr.write(source_default.red(`[runner] error: ${String(e)}
17048
+ `));
17049
+ r = { ran: false };
17050
+ }
17051
+ if (!r.ran)
17052
+ await sleep5(pollInterval);
17053
+ }
17054
+ } finally {
17055
+ process.off("SIGINT", onSignal);
17056
+ process.off("SIGTERM", onSignal);
17057
+ }
17058
+ });
17059
+ }
17060
+
16565
17061
  // src/commands/teams.ts
16566
17062
  init_errors();
16567
17063
  var API_PATH3 = "/api/admin/teams";
@@ -17038,8 +17534,8 @@ ${data.length} spec(s)`));
17038
17534
  Resources:
17039
17535
  `));
17040
17536
  for (const r of resources) {
17041
- const path7 = r.path || `/${r.name}`;
17042
- console.log(` ${source_default.cyan(r.label || r.name)}` + source_default.dim(` → ${path7}`));
17537
+ const path8 = r.path || `/${r.name}`;
17538
+ console.log(` ${source_default.cyan(r.label || r.name)}` + source_default.dim(` → ${path8}`));
17043
17539
  }
17044
17540
  }
17045
17541
  try {
@@ -17059,14 +17555,14 @@ Functions:
17059
17555
  console.log(source_default.dim(`
17060
17556
  Proxy: martha integrations proxy ${name} GET /<path>`));
17061
17557
  });
17062
- 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) => {
17558
+ 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) => {
17063
17559
  const ctx = getCtx();
17064
17560
  const upperMethod = method.toUpperCase();
17065
17561
  const allowed = new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
17066
17562
  if (!allowed.has(upperMethod)) {
17067
17563
  throw new CLIError(`Invalid method: ${method}`, 4 /* Validation */, "Allowed: GET, POST, PUT, PATCH, DELETE");
17068
17564
  }
17069
- const cleanPath = path7.startsWith("/") ? path7.slice(1) : path7;
17565
+ const cleanPath = path8.startsWith("/") ? path8.slice(1) : path8;
17070
17566
  const proxyUrl = `/api/admin/plugins/${encodeURIComponent(name)}/${cleanPath}`;
17071
17567
  const params = {};
17072
17568
  if (opts.query) {
@@ -17276,8 +17772,8 @@ async function resolveCredentialValue(value) {
17276
17772
  if (value === "-")
17277
17773
  return readStdin();
17278
17774
  if (value.startsWith("@")) {
17279
- const fs10 = await import("node:fs/promises");
17280
- return (await fs10.readFile(value.slice(1), "utf-8")).trim();
17775
+ const fs11 = await import("node:fs/promises");
17776
+ return (await fs11.readFile(value.slice(1), "utf-8")).trim();
17281
17777
  }
17282
17778
  return value;
17283
17779
  }
@@ -17392,8 +17888,8 @@ Notification connections`));
17392
17888
  if (credentialValue === "-") {
17393
17889
  credentialValue = await readStdin2();
17394
17890
  } else if (credentialValue?.startsWith("@")) {
17395
- const fs10 = await import("node:fs/promises");
17396
- credentialValue = await fs10.readFile(credentialValue.slice(1), "utf-8");
17891
+ const fs11 = await import("node:fs/promises");
17892
+ credentialValue = await fs11.readFile(credentialValue.slice(1), "utf-8");
17397
17893
  }
17398
17894
  if (!credentialValue) {
17399
17895
  throw new Error("--credential-value is required (use '-' for stdin or '@path' for file).");
@@ -17443,11 +17939,11 @@ async function readStdin2() {
17443
17939
 
17444
17940
  // src/commands/messaging.ts
17445
17941
  init_errors();
17446
- async function messagingFetch(baseUrl, path7, opts) {
17447
- if (/^(https?:)?\/\//i.test(path7)) {
17942
+ async function messagingFetch(baseUrl, path8, opts) {
17943
+ if (/^(https?:)?\/\//i.test(path8)) {
17448
17944
  throw new CLIError("Absolute URL paths are not allowed", 1 /* Error */);
17449
17945
  }
17450
- const url = new URL(path7, baseUrl);
17946
+ const url = new URL(path8, baseUrl);
17451
17947
  const headers = {
17452
17948
  "Content-Type": "application/json"
17453
17949
  };
@@ -18727,7 +19223,7 @@ ${models.length} models`));
18727
19223
  }
18728
19224
 
18729
19225
  // src/commands/wiki.ts
18730
- import fs10 from "node:fs";
19226
+ import fs11 from "node:fs";
18731
19227
  init_errors();
18732
19228
  function registerWikiCommands(program2) {
18733
19229
  const cmd = program2.command("wiki").description("Manage tenant wiki pages, settings, schema, recompile (#245 D5.4)");
@@ -18774,14 +19270,14 @@ function registerWikiCommands(program2) {
18774
19270
  console.log(source_default.dim(`
18775
19271
  ${pages.length} pages`));
18776
19272
  });
18777
- 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) => {
19273
+ 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) => {
18778
19274
  const ctx = getCtx();
18779
- const safe = path7.split("/").map(encodeURIComponent).join("/");
19275
+ const safe = path8.split("/").map(encodeURIComponent).join("/");
18780
19276
  const resp = await ctx.api.getRaw(`/api/wiki/pages/${safe}`, {
18781
19277
  headers: { Accept: "text/markdown,*/*" }
18782
19278
  });
18783
19279
  if (resp.status === 404) {
18784
- throw new CLIError(`page not found: ${path7}`, 3 /* NotFound */);
19280
+ throw new CLIError(`page not found: ${path8}`, 3 /* NotFound */);
18785
19281
  }
18786
19282
  if (!resp.ok) {
18787
19283
  const detail = await resp.text();
@@ -18789,16 +19285,16 @@ ${pages.length} pages`));
18789
19285
  }
18790
19286
  const body = await resp.text();
18791
19287
  if (opts.out) {
18792
- fs10.writeFileSync(opts.out, body);
19288
+ fs11.writeFileSync(opts.out, body);
18793
19289
  if (!isJson()) {
18794
19290
  console.log(source_default.dim(`wrote ${body.length} bytes to ${opts.out}`));
18795
19291
  } else {
18796
- console.log(JSON.stringify({ path: path7, bytes: body.length, file: opts.out }));
19292
+ console.log(JSON.stringify({ path: path8, bytes: body.length, file: opts.out }));
18797
19293
  }
18798
19294
  return;
18799
19295
  }
18800
19296
  if (isJson()) {
18801
- console.log(JSON.stringify({ path: path7, body, etag: resp.headers.get("ETag") }));
19297
+ console.log(JSON.stringify({ path: path8, body, etag: resp.headers.get("ETag") }));
18802
19298
  } else {
18803
19299
  process.stdout.write(body);
18804
19300
  }
@@ -18893,10 +19389,10 @@ ${pages.length} pages`));
18893
19389
  }
18894
19390
  if (opts.compilePromptOverrideFile) {
18895
19391
  const file = String(opts.compilePromptOverrideFile);
18896
- if (!fs10.existsSync(file)) {
19392
+ if (!fs11.existsSync(file)) {
18897
19393
  throw new CLIError(`override file not found: ${file}`, 3 /* NotFound */);
18898
19394
  }
18899
- const content = fs10.readFileSync(file, "utf8");
19395
+ const content = fs11.readFileSync(file, "utf8");
18900
19396
  const bytes = Buffer.byteLength(content, "utf8");
18901
19397
  if (bytes > 16 * 1024) {
18902
19398
  throw new CLIError(`compile prompt override exceeds 16 KB (${bytes} bytes)`, 4 /* Validation */);
@@ -18920,7 +19416,7 @@ ${pages.length} pages`));
18920
19416
  const ctx = getCtx();
18921
19417
  const resp = await ctx.api.get("/api/wiki/schema");
18922
19418
  if (opts.out) {
18923
- fs10.writeFileSync(opts.out, resp.body);
19419
+ fs11.writeFileSync(opts.out, resp.body);
18924
19420
  if (!isJson()) {
18925
19421
  console.log(source_default.dim(`wrote ${resp.body.length} bytes to ${opts.out}`));
18926
19422
  } else {
@@ -18936,10 +19432,10 @@ ${pages.length} pages`));
18936
19432
  });
18937
19433
  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) => {
18938
19434
  const ctx = getCtx();
18939
- if (!fs10.existsSync(opts.file)) {
19435
+ if (!fs11.existsSync(opts.file)) {
18940
19436
  throw new CLIError(`schema file not found: ${opts.file}`, 3 /* NotFound */);
18941
19437
  }
18942
- const body = fs10.readFileSync(opts.file, "utf8");
19438
+ const body = fs11.readFileSync(opts.file, "utf8");
18943
19439
  const bytes = Buffer.byteLength(body, "utf8");
18944
19440
  if (bytes > 64 * 1024) {
18945
19441
  throw new CLIError(`schema body exceeds 64 KB (${bytes} bytes)`, 4 /* Validation */);
@@ -19465,8 +19961,8 @@ async function resolveCredentialFlag(value) {
19465
19961
  return Buffer.concat(chunks).toString("utf-8").trim();
19466
19962
  }
19467
19963
  if (value.startsWith("@")) {
19468
- const fs11 = await import("node:fs/promises");
19469
- return (await fs11.readFile(value.slice(1), "utf-8")).trim();
19964
+ const fs12 = await import("node:fs/promises");
19965
+ return (await fs12.readFile(value.slice(1), "utf-8")).trim();
19470
19966
  }
19471
19967
  return value;
19472
19968
  }
@@ -20652,19 +21148,19 @@ function registerDoctorCommand(program2) {
20652
21148
 
20653
21149
  // src/commands/skill.ts
20654
21150
  init_errors();
20655
- import fs11 from "node:fs";
20656
- import path7 from "node:path";
21151
+ import fs12 from "node:fs";
21152
+ import path8 from "node:path";
20657
21153
  import { fileURLToPath } from "node:url";
20658
21154
  function locateSkill() {
20659
- const here = path7.dirname(fileURLToPath(import.meta.url));
21155
+ const here = path8.dirname(fileURLToPath(import.meta.url));
20660
21156
  const candidates = [
20661
- path7.join(here, "skills", "martha-cli", "SKILL.md"),
20662
- path7.join(here, "..", "skills", "martha-cli", "SKILL.md"),
20663
- path7.join(here, "..", "..", "..", "skills", "martha-cli", "SKILL.md"),
20664
- path7.join(here, "..", "..", "skills", "martha-cli", "SKILL.md")
21157
+ path8.join(here, "skills", "martha-cli", "SKILL.md"),
21158
+ path8.join(here, "..", "skills", "martha-cli", "SKILL.md"),
21159
+ path8.join(here, "..", "..", "..", "skills", "martha-cli", "SKILL.md"),
21160
+ path8.join(here, "..", "..", "skills", "martha-cli", "SKILL.md")
20665
21161
  ];
20666
21162
  for (const p of candidates) {
20667
- if (fs11.existsSync(p))
21163
+ if (fs12.existsSync(p))
20668
21164
  return p;
20669
21165
  }
20670
21166
  return null;
@@ -20674,7 +21170,7 @@ async function skillCommand() {
20674
21170
  if (!skillPath) {
20675
21171
  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 */);
20676
21172
  }
20677
- const body = fs11.readFileSync(skillPath, "utf-8");
21173
+ const body = fs12.readFileSync(skillPath, "utf-8");
20678
21174
  process.stdout.write(body);
20679
21175
  }
20680
21176
  function registerSkillCommand(program2) {
@@ -20868,6 +21364,7 @@ registerDocumentSyncCommands(program2);
20868
21364
  registerWikiCommands(program2);
20869
21365
  registerApprovalCommands(program2);
20870
21366
  registerTaskCommands(program2);
21367
+ registerRunnerCommand(program2);
20871
21368
  registerTeamCommands(program2);
20872
21369
  registerIntegrationCommands(program2);
20873
21370
  registerConnectionCommands(program2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiai-pt/martha-cli",
3
- "version": "0.26.0",
3
+ "version": "0.27.1",
4
4
  "description": "Terminal-first client for the Martha AI platform",
5
5
  "homepage": "https://docs.martha.nomadriver.co",
6
6
  "repository": {