@aiaiai-pt/martha-cli 0.27.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,13 @@ All notable changes to `@aiaiai-pt/martha-cli`. Format: [Keep a Changelog](https
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.28.0] — 2026-06-30
8
+
9
+ ### Added — #714 Slice 1 external-agent `event.emit` binding surfaces
10
+ - `martha agents grant-trigger <agent> <trigger>` binds an agent's emitted events to a trigger (so an agent-emitted event may fire it); `martha agents revoke-trigger <agent> <trigger>` removes the binding; `martha agents triggers <agent>` lists the triggers an agent may fire.
11
+ - `martha triggers allow-agent <trigger> <agentId>` accepts an agent as a source for a trigger; `martha triggers disallow-agent <trigger> <agentId>` removes it; `martha triggers agents <trigger>` lists the accepted agent sources.
12
+ - Both directions mutate the same `agent_trigger_access` binding (#714) and reflect each other. All support `--json`. The trigger is resolved by name → id; the agent side keys on the agent's stored (server-normalized) name and the agent id, respectively. Cross-tenant agent/trigger → 404 (IDOR guard).
13
+
7
14
  ## [0.20.0] — 2026-06-21
8
15
 
9
16
  ### Added — #649 agent→agent delegation surfaces
package/dist/index.js CHANGED
@@ -5,43 +5,25 @@ var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- function __accessProp(key) {
9
- return this[key];
10
- }
11
- var __toESMCache_node;
12
- var __toESMCache_esm;
13
8
  var __toESM = (mod, isNodeMode, target) => {
14
- var canCache = mod != null && typeof mod === "object";
15
- if (canCache) {
16
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
- var cached = cache.get(mod);
18
- if (cached)
19
- return cached;
20
- }
21
9
  target = mod != null ? __create(__getProtoOf(mod)) : {};
22
10
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
23
11
  for (let key of __getOwnPropNames(mod))
24
12
  if (!__hasOwnProp.call(to, key))
25
13
  __defProp(to, key, {
26
- get: __accessProp.bind(mod, key),
14
+ get: () => mod[key],
27
15
  enumerable: true
28
16
  });
29
- if (canCache)
30
- cache.set(mod, to);
31
17
  return to;
32
18
  };
33
19
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
34
- var __returnValue = (v) => v;
35
- function __exportSetter(name, newValue) {
36
- this[name] = __returnValue.bind(null, newValue);
37
- }
38
20
  var __export = (target, all) => {
39
21
  for (var name in all)
40
22
  __defProp(target, name, {
41
23
  get: all[name],
42
24
  enumerable: true,
43
25
  configurable: true,
44
- set: __exportSetter.bind(all, name)
26
+ set: (newValue) => all[name] = () => newValue
45
27
  });
46
28
  };
47
29
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -1019,7 +1001,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1019
1001
  this._exitCallback = (err) => {
1020
1002
  if (err.code !== "commander.executeSubCommandAsync") {
1021
1003
  throw err;
1022
- }
1004
+ } else {}
1023
1005
  };
1024
1006
  }
1025
1007
  return this;
@@ -11081,7 +11063,7 @@ init_config();
11081
11063
  init_errors();
11082
11064
 
11083
11065
  // src/version.ts
11084
- var CLI_VERSION = "0.27.0";
11066
+ var CLI_VERSION = "0.29.0";
11085
11067
 
11086
11068
  // src/commands/sessions.ts
11087
11069
  function relativeTime(iso) {
@@ -13512,7 +13494,7 @@ function registerConfigCommand(program2) {
13512
13494
  config.current_profile = name;
13513
13495
  }
13514
13496
  saveConfig(config);
13515
- if (!!program2.opts().json) {
13497
+ if (program2.opts().json) {
13516
13498
  console.log(JSON.stringify({
13517
13499
  name,
13518
13500
  profile,
@@ -13556,7 +13538,7 @@ function registerConfigCommand(program2) {
13556
13538
  config.current_profile = remaining[0] ?? "default";
13557
13539
  }
13558
13540
  saveConfig(config);
13559
- if (!!program2.opts().json) {
13541
+ if (program2.opts().json) {
13560
13542
  console.log(JSON.stringify({ name, deleted: true }));
13561
13543
  return;
13562
13544
  }
@@ -15066,6 +15048,50 @@ var agentsConfig = {
15066
15048
  }
15067
15049
  console.log(`Removed function '${functionName}' from agent '${agent}'`);
15068
15050
  });
15051
+ parentCmd.command("grant-trigger <agent> <trigger>").description("Allow an agent's emitted events to fire a trigger").action(async (agent, trigger) => {
15052
+ const ctx = getCtx();
15053
+ const t = await ctx.api.get(`/api/admin/triggers/${encodeURIComponent(trigger)}`);
15054
+ const result = await ctx.api.post(`${API_PATH}/${encodeURIComponent(agent)}/triggers`, { trigger_id: t.id });
15055
+ if (isJson()) {
15056
+ console.log(JSON.stringify(result, null, 2));
15057
+ return;
15058
+ }
15059
+ console.log(`Granted trigger '${trigger}' to agent '${agent}'`);
15060
+ });
15061
+ parentCmd.command("revoke-trigger <agent> <trigger>").description("Revoke an agent's binding to a trigger").action(async (agent, trigger) => {
15062
+ const ctx = getCtx();
15063
+ const t = await ctx.api.get(`/api/admin/triggers/${encodeURIComponent(trigger)}`);
15064
+ await ctx.api.del(`${API_PATH}/${encodeURIComponent(agent)}/triggers/${encodeURIComponent(String(t.id))}`);
15065
+ if (isJson()) {
15066
+ console.log(JSON.stringify({ agent, trigger, removed: true }));
15067
+ return;
15068
+ }
15069
+ console.log(`Revoked trigger '${trigger}' from agent '${agent}'`);
15070
+ });
15071
+ parentCmd.command("triggers <agent>").description("List triggers an agent may fire").action(async (agent) => {
15072
+ const ctx = getCtx();
15073
+ const items = await ctx.api.get(`${API_PATH}/${encodeURIComponent(agent)}/triggers`);
15074
+ if (isJson()) {
15075
+ console.log(JSON.stringify(items, null, 2));
15076
+ return;
15077
+ }
15078
+ if (items.length === 0) {
15079
+ console.log(source_default.dim("No triggers bound to this agent."));
15080
+ return;
15081
+ }
15082
+ const nameW = Math.max(7, ...items.map((b) => String(b.trigger_name ?? "").length));
15083
+ const header = ["TRIGGER", "GRANTED BY", "GRANTED AT"].map((h, i) => h.padEnd([nameW, 16, 20][i])).join(" ");
15084
+ console.log(source_default.bold(header));
15085
+ console.log(source_default.dim("-".repeat(header.length)));
15086
+ for (const b of items) {
15087
+ const date = b.granted_at ? new Date(b.granted_at).toISOString().slice(0, 16).replace("T", " ") : "-";
15088
+ console.log([
15089
+ String(b.trigger_name ?? "").padEnd(nameW),
15090
+ String(b.granted_by ?? "-").padEnd(16),
15091
+ date.padEnd(20)
15092
+ ].join(" "));
15093
+ }
15094
+ });
15069
15095
  parentCmd.command("provision-auth <agent>").description("Provision, switch, or rotate agent auth credentials").requiredOption("--method <method>", "Auth method: service-account or api-key").action(async (agent, opts) => {
15070
15096
  const ctx = getCtx();
15071
15097
  const methodMap = {
@@ -15495,6 +15521,51 @@ var triggersConfig = {
15495
15521
  }
15496
15522
  }
15497
15523
  });
15524
+ parentCmd.command("allow-agent <trigger> <agentId>").description("Accept an agent's emitted events as a source for a trigger").action(async (trigger, agentId) => {
15525
+ const ctx = getCtx();
15526
+ const t = await ctx.api.get(`/api/admin/triggers/${encodeURIComponent(trigger)}`);
15527
+ const result = await ctx.api.post(`/api/admin/triggers/${encodeURIComponent(String(t.id))}/agents`, { agent_id: agentId });
15528
+ if (isJson()) {
15529
+ console.log(JSON.stringify(result, null, 2));
15530
+ return;
15531
+ }
15532
+ console.log(`Accepted agent '${agentId}' as a source for '${trigger}'`);
15533
+ });
15534
+ parentCmd.command("disallow-agent <trigger> <agentId>").description("Remove an agent as a source for a trigger").action(async (trigger, agentId) => {
15535
+ const ctx = getCtx();
15536
+ const t = await ctx.api.get(`/api/admin/triggers/${encodeURIComponent(trigger)}`);
15537
+ await ctx.api.del(`/api/admin/triggers/${encodeURIComponent(String(t.id))}/agents/${encodeURIComponent(agentId)}`);
15538
+ if (isJson()) {
15539
+ console.log(JSON.stringify({ trigger, agent_id: agentId, removed: true }));
15540
+ return;
15541
+ }
15542
+ console.log(`Removed agent '${agentId}' as a source for '${trigger}'`);
15543
+ });
15544
+ parentCmd.command("agents <trigger>").description("List the agents accepted as sources for a trigger").action(async (trigger) => {
15545
+ const ctx = getCtx();
15546
+ const t = await ctx.api.get(`/api/admin/triggers/${encodeURIComponent(trigger)}`);
15547
+ const items = await ctx.api.get(`/api/admin/triggers/${encodeURIComponent(String(t.id))}/agents`);
15548
+ if (isJson()) {
15549
+ console.log(JSON.stringify(items, null, 2));
15550
+ return;
15551
+ }
15552
+ if (items.length === 0) {
15553
+ console.log(source_default.dim("No agent sources accepted for this trigger."));
15554
+ return;
15555
+ }
15556
+ const nameW = Math.max(5, ...items.map((b) => String(b.agent_name ?? "").length));
15557
+ const header = ["AGENT", "AGENT ID", "GRANTED AT"].map((h, i) => h.padEnd([nameW, 36, 20][i])).join(" ");
15558
+ console.log(source_default.bold(header));
15559
+ console.log(source_default.dim("-".repeat(header.length)));
15560
+ for (const b of items) {
15561
+ const date = b.granted_at ? new Date(b.granted_at).toISOString().slice(0, 16).replace("T", " ") : "-";
15562
+ console.log([
15563
+ String(b.agent_name ?? "-").padEnd(nameW),
15564
+ String(b.agent_id ?? "-").padEnd(36),
15565
+ date.padEnd(20)
15566
+ ].join(" "));
15567
+ }
15568
+ });
15498
15569
  }
15499
15570
  };
15500
15571
 
@@ -16488,6 +16559,119 @@ function registerApprovalCommands(program2) {
16488
16559
  });
16489
16560
  }
16490
16561
 
16562
+ // src/commands/asks.ts
16563
+ init_errors();
16564
+ var KINDS = ["agent_question", "agent_approval_request"];
16565
+ function statusColor(status) {
16566
+ if (status === "approved")
16567
+ return source_default.green(status);
16568
+ if (status === "rejected")
16569
+ return source_default.red(status);
16570
+ if (status === "expired")
16571
+ return source_default.dim(status);
16572
+ return source_default.yellow(status);
16573
+ }
16574
+ function renderAsk(a) {
16575
+ const lines = [
16576
+ `${source_default.bold(a.case_id ?? "?")} ${statusColor(a.status ?? "?")} ${source_default.dim(a.kind ?? "")}`,
16577
+ ` ${a.reason ?? ""}`
16578
+ ];
16579
+ if (a.context_summary)
16580
+ lines.push(` ${source_default.dim(a.context_summary)}`);
16581
+ if (a.decision) {
16582
+ lines.push(` ${source_default.bold("decision:")} ${statusColor(a.decision)}`);
16583
+ if (a.comment)
16584
+ lines.push(` ${source_default.bold("answer:")} ${a.comment}`);
16585
+ }
16586
+ if (a.expires_at)
16587
+ lines.push(` ${source_default.dim(`expires ${a.expires_at}`)}`);
16588
+ return lines.join(`
16589
+ `);
16590
+ }
16591
+ function registerAskCommands(program2) {
16592
+ const cmd = program2.command("asks").description("Agent asks — questions/approval requests to the human queue (agent credentials)");
16593
+ function getCtx() {
16594
+ const ctx = createContext({
16595
+ profileOverride: program2.opts().profile,
16596
+ verbose: program2.opts().verbose
16597
+ });
16598
+ if (program2.opts().apiUrl)
16599
+ ctx.profile.api_url = program2.opts().apiUrl;
16600
+ return ctx;
16601
+ }
16602
+ function isJson() {
16603
+ return !!program2.opts().json;
16604
+ }
16605
+ cmd.command("create").description("File an ask (the server attributes it to the token's agent)").requiredOption("--reason <text>", "The question / what approval is requested").option("--kind <kind>", `${KINDS.join(" | ")} (default agent_question)`, "agent_question").option("--context <text>", "Context summary for the reviewer").option("--questions <json>", "Structured questions JSON array (ask_user shape: question/header/options/multiSelect)").option("--expires-in <seconds>", "Auto-expire the ask after N seconds").option("--idempotency-key <key>", "Dedup key — retries with the same key do not double-file").action(async (opts) => {
16606
+ if (!KINDS.includes(opts.kind)) {
16607
+ throw new CLIError(`--kind must be one of: ${KINDS.join(", ")}`, 4 /* Validation */);
16608
+ }
16609
+ let questions;
16610
+ if (opts.questions) {
16611
+ try {
16612
+ questions = JSON.parse(opts.questions);
16613
+ } catch {
16614
+ throw new CLIError("--questions must be valid JSON", 4 /* Validation */);
16615
+ }
16616
+ }
16617
+ let expiresIn;
16618
+ if (opts.expiresIn) {
16619
+ expiresIn = parseInt(opts.expiresIn, 10);
16620
+ if (isNaN(expiresIn) || expiresIn < 60) {
16621
+ throw new CLIError("--expires-in must be an integer >= 60 (seconds)", 4 /* Validation */);
16622
+ }
16623
+ }
16624
+ const ctx = getCtx();
16625
+ const ask = await ctx.api.post("/api/agent/asks", {
16626
+ kind: opts.kind,
16627
+ reason: opts.reason,
16628
+ context_summary: opts.context,
16629
+ questions,
16630
+ expires_in_s: expiresIn,
16631
+ idempotency_key: opts.idempotencyKey
16632
+ });
16633
+ if (isJson()) {
16634
+ console.log(JSON.stringify(ask, null, 2));
16635
+ return;
16636
+ }
16637
+ console.log(`Filed ask ${source_default.bold(ask.case_id)} (${ask.status})`);
16638
+ console.log(source_default.dim(`Poll the answer with: martha asks get ${ask.case_id}`));
16639
+ });
16640
+ cmd.command("list").description("List this agent's own asks (newest first)").option("--status <status>", "Filter by status (pending/approved/rejected/expired)").option("--limit <n>", "Max results", "50").action(async (opts) => {
16641
+ const limitN = parseInt(opts.limit, 10);
16642
+ if (isNaN(limitN) || limitN < 1) {
16643
+ throw new CLIError("--limit must be a positive integer", 4 /* Validation */);
16644
+ }
16645
+ const ctx = getCtx();
16646
+ const params = { limit: String(limitN) };
16647
+ if (opts.status)
16648
+ params.status = opts.status;
16649
+ const items = await ctx.api.get("/api/agent/asks", {
16650
+ params
16651
+ });
16652
+ if (isJson()) {
16653
+ console.log(JSON.stringify(items, null, 2));
16654
+ return;
16655
+ }
16656
+ if (items.length === 0) {
16657
+ console.log(source_default.dim("No asks."));
16658
+ return;
16659
+ }
16660
+ console.log(items.map(renderAsk).join(`
16661
+
16662
+ `));
16663
+ });
16664
+ cmd.command("get <caseId>").description("Show one own ask — status, and decision+answer once resolved").action(async (caseId) => {
16665
+ const ctx = getCtx();
16666
+ const ask = await ctx.api.get(`/api/agent/asks/${caseId}`);
16667
+ if (isJson()) {
16668
+ console.log(JSON.stringify(ask, null, 2));
16669
+ return;
16670
+ }
16671
+ console.log(renderAsk(ask));
16672
+ });
16673
+ }
16674
+
16491
16675
  // src/commands/tasks.ts
16492
16676
  import { readFileSync as readFileSync2 } from "node:fs";
16493
16677
  init_errors();
@@ -16508,7 +16692,7 @@ var sleep4 = (ms, signal) => new Promise((resolve2) => {
16508
16692
  signal?.addEventListener("abort", onAbort, { once: true });
16509
16693
  });
16510
16694
  var fmtDate2 = (v) => typeof v === "string" ? new Date(v).toISOString().slice(0, 16).replace("T", " ") : "-";
16511
- var statusColor = (status) => {
16695
+ var statusColor2 = (status) => {
16512
16696
  switch (status) {
16513
16697
  case "open":
16514
16698
  return source_default.yellow(status);
@@ -16611,7 +16795,7 @@ function registerTaskCommands(program2) {
16611
16795
  const row = columns.map((col, i) => {
16612
16796
  let val = col.accessor(item);
16613
16797
  if (col.header === "STATUS")
16614
- val = statusColor(val);
16798
+ val = statusColor2(val);
16615
16799
  if (col.header === "PRIORITY")
16616
16800
  val = priorityColor(val);
16617
16801
  return col.header === "STATUS" || col.header === "PRIORITY" ? val.padEnd(widths[i] + (val.length - col.accessor(item).length)) : val.padEnd(widths[i]);
@@ -16630,7 +16814,7 @@ ${items.length} task(s)`));
16630
16814
  }
16631
16815
  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)}`);
16632
16816
  });
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").action(async (opts) => {
16817
+ 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) => {
16634
16818
  const ctx = getCtx();
16635
16819
  const body = {
16636
16820
  agent_definition_id: opts.agent,
@@ -16652,13 +16836,15 @@ ${items.length} task(s)`));
16652
16836
  body.assigned_agent_id = opts.assignedAgent;
16653
16837
  if (opts.sticky)
16654
16838
  body.sticky_assignment = true;
16839
+ if (opts.autoExecute === false)
16840
+ body.auto_execute = false;
16655
16841
  const result = await ctx.api.post("/api/admin/tasks", body);
16656
16842
  if (isJson()) {
16657
16843
  console.log(JSON.stringify(result, null, 2));
16658
16844
  return;
16659
16845
  }
16660
16846
  console.log(`Created task ${source_default.cyan(String(result.id ?? ""))}`);
16661
- console.log(` Status: ${statusColor(String(result.status ?? "-"))}`);
16847
+ console.log(` Status: ${statusColor2(String(result.status ?? "-"))}`);
16662
16848
  console.log(` Priority: ${priorityColor(String(result.priority ?? "-"))}`);
16663
16849
  console.log(` Goal: ${truncate2(String(result.goal ?? "-"), 60)}`);
16664
16850
  });
@@ -16672,7 +16858,7 @@ ${items.length} task(s)`));
16672
16858
  console.log(source_default.bold("Task Details"));
16673
16859
  console.log(source_default.dim("-".repeat(50)));
16674
16860
  console.log(`${source_default.cyan("ID:")} ${item.id ?? "-"}`);
16675
- console.log(`${source_default.cyan("Status:")} ${statusColor(String(item.status ?? "-"))}`);
16861
+ console.log(`${source_default.cyan("Status:")} ${statusColor2(String(item.status ?? "-"))}`);
16676
16862
  console.log(`${source_default.cyan("Priority:")} ${priorityColor(String(item.priority ?? "-"))}`);
16677
16863
  console.log(`${source_default.cyan("Title:")} ${item.title ?? "-"}`);
16678
16864
  console.log(`${source_default.cyan("Agent:")} ${item.agent_definition_id ?? "-"}`);
@@ -16783,7 +16969,7 @@ ${items.length} task(s) available`));
16783
16969
  return;
16784
16970
  }
16785
16971
  console.log(`Claimed task ${source_default.cyan(String(result.id ?? "").slice(0, 8))}`);
16786
- console.log(` Status: ${statusColor(String(result.status ?? "-"))}`);
16972
+ console.log(` Status: ${statusColor2(String(result.status ?? "-"))}`);
16787
16973
  console.log(` Executor: ${result.executor_id ?? "-"}`);
16788
16974
  console.log(` Lease: expires ${fmtDate2(result.lease_expires_at)}`);
16789
16975
  });
@@ -16956,11 +17142,106 @@ ${items.length} task(s) available`));
16956
17142
  return;
16957
17143
  }
16958
17144
  console.log(`Completed task ${source_default.cyan(String(result.id ?? "").slice(0, 8))}`);
16959
- console.log(` Status: ${statusColor(String(result.status ?? "-"))}`);
17145
+ console.log(` Status: ${statusColor2(String(result.status ?? "-"))}`);
16960
17146
  console.log(` Completed at: ${fmtDate2(result.completed_at)}`);
16961
17147
  });
16962
17148
  }
16963
17149
 
17150
+ // src/commands/runner.ts
17151
+ init_errors();
17152
+ function sleep5(ms) {
17153
+ return new Promise((r) => setTimeout(r, ms));
17154
+ }
17155
+ async function runOnce(ctx, opts, handled) {
17156
+ const tasks = await ctx.api.get("/api/tasks/poll", {
17157
+ params: { agent_id: opts.agent, limit: String(opts.pollLimit ?? 5) }
17158
+ });
17159
+ const candidates = Array.isArray(tasks) ? tasks : [];
17160
+ const task = candidates.find((t) => (t.status ?? "open") === "open" && !handled?.has(t.id));
17161
+ if (!task)
17162
+ return { ran: false };
17163
+ if (!opts.autoYes) {
17164
+ process.stderr.write(source_default.yellow(`
17165
+ [runner] refused task ${task.id} — re-run with --yes to allow host execution
17166
+ `));
17167
+ return { ran: false, taskId: task.id, status: "refused" };
17168
+ }
17169
+ handled?.add(task.id);
17170
+ const claimed = await ctx.api.post(`/api/tasks/${encodeURIComponent(task.id)}/claim`, {});
17171
+ const goal = claimed.goal ?? task.goal ?? "";
17172
+ process.stderr.write(source_default.dim(`
17173
+ [runner] claimed ${task.id} — running: ${goal}
17174
+ `));
17175
+ let result;
17176
+ try {
17177
+ result = await runHostCommand(goal, `task-${task.id}`, { cwd: opts.cwd });
17178
+ } catch (e) {
17179
+ result = { stdout: "", stderr: String(e), exit_code: -1 };
17180
+ }
17181
+ const exit_code = result.exit_code;
17182
+ const stdout = result.stdout || result.stderr || "";
17183
+ const status = exit_code === 0 ? "completed" : "failed";
17184
+ await ctx.api.post(`/api/tasks/${encodeURIComponent(task.id)}/complete`, {
17185
+ outcome: { stdout, exit_code },
17186
+ status
17187
+ });
17188
+ process.stderr.write(source_default.dim(`[runner] completed ${task.id} — ${status} (exit ${exit_code})
17189
+ `));
17190
+ return { ran: true, taskId: task.id, status, exitCode: exit_code };
17191
+ }
17192
+ function registerRunnerCommand(program2) {
17193
+ 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) => {
17194
+ const ctx = createContext({
17195
+ profileOverride: program2.opts().profile,
17196
+ verbose: program2.opts().verbose
17197
+ });
17198
+ if (program2.opts().apiUrl)
17199
+ ctx.profile.api_url = program2.opts().apiUrl;
17200
+ const pollInterval = parseInt(opts.pollInterval, 10);
17201
+ if (isNaN(pollInterval) || pollInterval < 250) {
17202
+ throw new CLIError("--poll-interval must be >= 250 (ms)", 4 /* Validation */);
17203
+ }
17204
+ const runOpts = {
17205
+ agent: opts.agent,
17206
+ cwd: opts.cwd,
17207
+ autoYes: !!opts.yes
17208
+ };
17209
+ process.stderr.write(source_default.dim(`[runner] serving agent ${opts.agent} (cwd ${opts.cwd})${opts.once ? " — once" : ""}
17210
+ `));
17211
+ if (opts.once) {
17212
+ await runOnce(ctx, runOpts);
17213
+ return;
17214
+ }
17215
+ let stop = false;
17216
+ const onSignal = () => {
17217
+ stop = true;
17218
+ process.stderr.write(`
17219
+ [runner] stopping…
17220
+ `);
17221
+ };
17222
+ process.on("SIGINT", onSignal);
17223
+ process.on("SIGTERM", onSignal);
17224
+ const handled = new Set;
17225
+ try {
17226
+ while (!stop) {
17227
+ let r;
17228
+ try {
17229
+ r = await runOnce(ctx, runOpts, handled);
17230
+ } catch (e) {
17231
+ process.stderr.write(source_default.red(`[runner] error: ${String(e)}
17232
+ `));
17233
+ r = { ran: false };
17234
+ }
17235
+ if (!r.ran)
17236
+ await sleep5(pollInterval);
17237
+ }
17238
+ } finally {
17239
+ process.off("SIGINT", onSignal);
17240
+ process.off("SIGTERM", onSignal);
17241
+ }
17242
+ });
17243
+ }
17244
+
16964
17245
  // src/commands/teams.ts
16965
17246
  init_errors();
16966
17247
  var API_PATH3 = "/api/admin/teams";
@@ -19873,7 +20154,7 @@ async function resolveCredentialFlag(value) {
19873
20154
  // src/commands/policy.ts
19874
20155
  init_errors();
19875
20156
  var ACTIONS = ["allow", "require_approval", "block"];
19876
- var KINDS = ["risk_tag", "risk_level", "capability"];
20157
+ var KINDS2 = ["risk_tag", "risk_level", "capability"];
19877
20158
  var SCOPES = ["any", "agent", "client"];
19878
20159
  var truncate4 = (s, n) => s.length > n ? s.slice(0, n - 1) + "…" : s;
19879
20160
  function assertOneOf(value, allowed, flag) {
@@ -19969,8 +20250,8 @@ function registerPolicyCommands(program2) {
19969
20250
  console.log(`Safe-default bundle ${enabled ? source_default.yellow("enabled") : source_default.dim("disabled")}.`);
19970
20251
  });
19971
20252
  const rule = cmd.command("rule").description("Manage explicit policy rules");
19972
- rule.command("add").description("Create or update a rule").requiredOption(`--kind <kind>`, `Match kind (${KINDS.join("|")})`).requiredOption("--value <value>", "Match value (a risk tag, level, or capability ref)").requiredOption(`--action <action>`, `Action (${ACTIONS.join("|")})`).option(`--scope <scope>`, `Principal scope (${SCOPES.join("|")})`, "any").option("--note <text>", "Optional human note").action(async (opts) => {
19973
- assertOneOf(opts.kind, KINDS, "--kind");
20253
+ rule.command("add").description("Create or update a rule").requiredOption(`--kind <kind>`, `Match kind (${KINDS2.join("|")})`).requiredOption("--value <value>", "Match value (a risk tag, level, or capability ref)").requiredOption(`--action <action>`, `Action (${ACTIONS.join("|")})`).option(`--scope <scope>`, `Principal scope (${SCOPES.join("|")})`, "any").option("--note <text>", "Optional human note").action(async (opts) => {
20254
+ assertOneOf(opts.kind, KINDS2, "--kind");
19974
20255
  assertOneOf(opts.action, ACTIONS, "--action");
19975
20256
  assertOneOf(opts.scope, SCOPES, "--scope");
19976
20257
  const ctx = getCtx();
@@ -21266,7 +21547,9 @@ registerDocumentCommands(program2);
21266
21547
  registerDocumentSyncCommands(program2);
21267
21548
  registerWikiCommands(program2);
21268
21549
  registerApprovalCommands(program2);
21550
+ registerAskCommands(program2);
21269
21551
  registerTaskCommands(program2);
21552
+ registerRunnerCommand(program2);
21270
21553
  registerTeamCommands(program2);
21271
21554
  registerIntegrationCommands(program2);
21272
21555
  registerConnectionCommands(program2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiai-pt/martha-cli",
3
- "version": "0.27.0",
3
+ "version": "0.29.0",
4
4
  "description": "Terminal-first client for the Martha AI platform",
5
5
  "homepage": "https://docs.martha.nomadriver.co",
6
6
  "repository": {