@aiaiai-pt/martha-cli 0.27.1 → 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.1";
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]);
@@ -16660,7 +16844,7 @@ ${items.length} task(s)`));
16660
16844
  return;
16661
16845
  }
16662
16846
  console.log(`Created task ${source_default.cyan(String(result.id ?? ""))}`);
16663
- console.log(` Status: ${statusColor(String(result.status ?? "-"))}`);
16847
+ console.log(` Status: ${statusColor2(String(result.status ?? "-"))}`);
16664
16848
  console.log(` Priority: ${priorityColor(String(result.priority ?? "-"))}`);
16665
16849
  console.log(` Goal: ${truncate2(String(result.goal ?? "-"), 60)}`);
16666
16850
  });
@@ -16674,7 +16858,7 @@ ${items.length} task(s)`));
16674
16858
  console.log(source_default.bold("Task Details"));
16675
16859
  console.log(source_default.dim("-".repeat(50)));
16676
16860
  console.log(`${source_default.cyan("ID:")} ${item.id ?? "-"}`);
16677
- console.log(`${source_default.cyan("Status:")} ${statusColor(String(item.status ?? "-"))}`);
16861
+ console.log(`${source_default.cyan("Status:")} ${statusColor2(String(item.status ?? "-"))}`);
16678
16862
  console.log(`${source_default.cyan("Priority:")} ${priorityColor(String(item.priority ?? "-"))}`);
16679
16863
  console.log(`${source_default.cyan("Title:")} ${item.title ?? "-"}`);
16680
16864
  console.log(`${source_default.cyan("Agent:")} ${item.agent_definition_id ?? "-"}`);
@@ -16785,7 +16969,7 @@ ${items.length} task(s) available`));
16785
16969
  return;
16786
16970
  }
16787
16971
  console.log(`Claimed task ${source_default.cyan(String(result.id ?? "").slice(0, 8))}`);
16788
- console.log(` Status: ${statusColor(String(result.status ?? "-"))}`);
16972
+ console.log(` Status: ${statusColor2(String(result.status ?? "-"))}`);
16789
16973
  console.log(` Executor: ${result.executor_id ?? "-"}`);
16790
16974
  console.log(` Lease: expires ${fmtDate2(result.lease_expires_at)}`);
16791
16975
  });
@@ -16958,7 +17142,7 @@ ${items.length} task(s) available`));
16958
17142
  return;
16959
17143
  }
16960
17144
  console.log(`Completed task ${source_default.cyan(String(result.id ?? "").slice(0, 8))}`);
16961
- console.log(` Status: ${statusColor(String(result.status ?? "-"))}`);
17145
+ console.log(` Status: ${statusColor2(String(result.status ?? "-"))}`);
16962
17146
  console.log(` Completed at: ${fmtDate2(result.completed_at)}`);
16963
17147
  });
16964
17148
  }
@@ -19970,7 +20154,7 @@ async function resolveCredentialFlag(value) {
19970
20154
  // src/commands/policy.ts
19971
20155
  init_errors();
19972
20156
  var ACTIONS = ["allow", "require_approval", "block"];
19973
- var KINDS = ["risk_tag", "risk_level", "capability"];
20157
+ var KINDS2 = ["risk_tag", "risk_level", "capability"];
19974
20158
  var SCOPES = ["any", "agent", "client"];
19975
20159
  var truncate4 = (s, n) => s.length > n ? s.slice(0, n - 1) + "…" : s;
19976
20160
  function assertOneOf(value, allowed, flag) {
@@ -20066,8 +20250,8 @@ function registerPolicyCommands(program2) {
20066
20250
  console.log(`Safe-default bundle ${enabled ? source_default.yellow("enabled") : source_default.dim("disabled")}.`);
20067
20251
  });
20068
20252
  const rule = cmd.command("rule").description("Manage explicit policy rules");
20069
- 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) => {
20070
- 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");
20071
20255
  assertOneOf(opts.action, ACTIONS, "--action");
20072
20256
  assertOneOf(opts.scope, SCOPES, "--scope");
20073
20257
  const ctx = getCtx();
@@ -21363,6 +21547,7 @@ registerDocumentCommands(program2);
21363
21547
  registerDocumentSyncCommands(program2);
21364
21548
  registerWikiCommands(program2);
21365
21549
  registerApprovalCommands(program2);
21550
+ registerAskCommands(program2);
21366
21551
  registerTaskCommands(program2);
21367
21552
  registerRunnerCommand(program2);
21368
21553
  registerTeamCommands(program2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiai-pt/martha-cli",
3
- "version": "0.27.1",
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": {