@aiaiai-pt/martha-cli 0.19.0 → 0.22.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.20.0] — 2026-06-21
8
+
9
+ ### Added — #649 agent→agent delegation surfaces
10
+ - `martha agents add-agent <coordinator> <sub-agent> [--set k=v]` grants a coordinator the ability to delegate to a sub-agent; `martha agents remove-agent <coordinator> <sub-agent>` revokes it; `martha agents agents <coordinator>` lists the granted sub-agents. All support `--json`. Backed by the existing `/api/admin/definitions/agents/{name}/agents` routes (#648). Self-delegation surfaces the API's 400 (`an agent cannot delegate to itself`); an unknown sub-agent returns 404.
11
+
12
+ <!-- NOTE: package.json was already at 0.19.0 on main while this CHANGELOG's latest entry was 0.16.1 — versions 0.17.0–0.19.0 shipped without CHANGELOG sections (pre-existing drift). This 0.20.0 bump is the MINOR bump for #649 off the real 0.19.0 base; the 0.17–0.19 gap is not reconstructed here. -->
13
+
7
14
  ## [0.16.1] — 2026-06-14
8
15
 
9
16
  ### Fixed
package/dist/index.js CHANGED
@@ -5,25 +5,43 @@ 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;
8
13
  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
+ }
9
21
  target = mod != null ? __create(__getProtoOf(mod)) : {};
10
22
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
23
  for (let key of __getOwnPropNames(mod))
12
24
  if (!__hasOwnProp.call(to, key))
13
25
  __defProp(to, key, {
14
- get: () => mod[key],
26
+ get: __accessProp.bind(mod, key),
15
27
  enumerable: true
16
28
  });
29
+ if (canCache)
30
+ cache.set(mod, to);
17
31
  return to;
18
32
  };
19
33
  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
+ }
20
38
  var __export = (target, all) => {
21
39
  for (var name in all)
22
40
  __defProp(target, name, {
23
41
  get: all[name],
24
42
  enumerable: true,
25
43
  configurable: true,
26
- set: (newValue) => all[name] = () => newValue
44
+ set: __exportSetter.bind(all, name)
27
45
  });
28
46
  };
29
47
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -1001,7 +1019,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1001
1019
  this._exitCallback = (err) => {
1002
1020
  if (err.code !== "commander.executeSubCommandAsync") {
1003
1021
  throw err;
1004
- } else {}
1022
+ }
1005
1023
  };
1006
1024
  }
1007
1025
  return this;
@@ -11059,7 +11077,7 @@ import { createInterface as createInterface2 } from "node:readline";
11059
11077
  init_errors();
11060
11078
 
11061
11079
  // src/version.ts
11062
- var CLI_VERSION = "0.19.0";
11080
+ var CLI_VERSION = "0.22.0";
11063
11081
 
11064
11082
  // src/commands/sessions.ts
11065
11083
  function relativeTime(iso) {
@@ -11331,7 +11349,302 @@ function createApprovalTagStripper() {
11331
11349
  };
11332
11350
  }
11333
11351
 
11352
+ // src/lib/local-exec.ts
11353
+ import { spawn } from "node:child_process";
11354
+ import { closeSync, openSync, promises as fs5 } from "node:fs";
11355
+ import * as path4 from "node:path";
11356
+ var DEFAULT_TIMEOUT_MS = 20000;
11357
+ var MAX_OUTPUT = 256 * 1024;
11358
+ function runnerDir(cwd) {
11359
+ return path4.join(cwd, ".martha-runner");
11360
+ }
11361
+ async function runHostCommand(command, toolCallId, opts) {
11362
+ const dir = runnerDir(opts.cwd);
11363
+ await fs5.mkdir(dir, { recursive: true });
11364
+ const logPath = path4.join(dir, `${safeName(toolCallId)}.log`);
11365
+ await fs5.writeFile(logPath, "");
11366
+ const fd = openSync(logPath, "a");
11367
+ const cap = opts.maxOutputBytes ?? MAX_OUTPUT;
11368
+ const readLog = async () => {
11369
+ try {
11370
+ const buf = await fs5.readFile(logPath);
11371
+ return buf.subarray(-cap).toString("utf8");
11372
+ } catch {
11373
+ return "";
11374
+ }
11375
+ };
11376
+ return await new Promise((resolve, reject) => {
11377
+ let settled = false;
11378
+ const child = spawn(command, {
11379
+ shell: true,
11380
+ cwd: opts.cwd,
11381
+ detached: true,
11382
+ stdio: ["ignore", fd, fd]
11383
+ });
11384
+ closeSync(fd);
11385
+ const timer = setTimeout(async () => {
11386
+ if (settled)
11387
+ return;
11388
+ settled = true;
11389
+ child.unref();
11390
+ resolve({
11391
+ stdout: await readLog(),
11392
+ stderr: "",
11393
+ exit_code: 0,
11394
+ backgrounded: true,
11395
+ pid: child.pid
11396
+ });
11397
+ }, opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
11398
+ child.on("error", (e) => {
11399
+ if (settled)
11400
+ return;
11401
+ settled = true;
11402
+ clearTimeout(timer);
11403
+ reject(e);
11404
+ });
11405
+ child.on("close", async (code) => {
11406
+ if (settled)
11407
+ return;
11408
+ settled = true;
11409
+ clearTimeout(timer);
11410
+ resolve({ stdout: await readLog(), stderr: "", exit_code: code ?? -1 });
11411
+ });
11412
+ });
11413
+ }
11414
+ function safeName(s) {
11415
+ return s.replace(/[^a-zA-Z0-9_.-]/g, "_").slice(0, 120) || "task";
11416
+ }
11417
+
11418
+ class Journal {
11419
+ cwd;
11420
+ constructor(cwd) {
11421
+ this.cwd = cwd;
11422
+ }
11423
+ file() {
11424
+ return path4.join(runnerDir(this.cwd), "journal.json");
11425
+ }
11426
+ async load() {
11427
+ try {
11428
+ return JSON.parse(await fs5.readFile(this.file(), "utf8"));
11429
+ } catch {
11430
+ return {};
11431
+ }
11432
+ }
11433
+ async save(data) {
11434
+ await fs5.mkdir(runnerDir(this.cwd), { recursive: true });
11435
+ await fs5.writeFile(this.file(), JSON.stringify(data, null, 2));
11436
+ }
11437
+ static key(sessionId, toolCallId) {
11438
+ return `${sessionId}:${toolCallId}`;
11439
+ }
11440
+ async lookup(sessionId, toolCallId) {
11441
+ return (await this.load())[Journal.key(sessionId, toolCallId)];
11442
+ }
11443
+ async recordRunning(sessionId, toolCallId, command, now) {
11444
+ const data = await this.load();
11445
+ data[Journal.key(sessionId, toolCallId)] = {
11446
+ state: "running",
11447
+ command,
11448
+ started_at: now
11449
+ };
11450
+ await this.save(data);
11451
+ }
11452
+ async recordDone(sessionId, toolCallId, result) {
11453
+ const data = await this.load();
11454
+ const k = Journal.key(sessionId, toolCallId);
11455
+ const prev = data[k];
11456
+ data[k] = {
11457
+ state: "done",
11458
+ command: prev?.command ?? "",
11459
+ started_at: prev?.started_at ?? "",
11460
+ result
11461
+ };
11462
+ await this.save(data);
11463
+ }
11464
+ }
11465
+ function decideFromJournal(entry) {
11466
+ if (!entry)
11467
+ return { kind: "fresh" };
11468
+ if (entry.state === "done" && entry.result)
11469
+ return { kind: "replay", result: entry.result };
11470
+ return { kind: "crashed", command: entry.command };
11471
+ }
11472
+ function parseHostExecRequests(data) {
11473
+ let tools;
11474
+ try {
11475
+ tools = JSON.parse(data);
11476
+ } catch {
11477
+ return [];
11478
+ }
11479
+ if (!Array.isArray(tools))
11480
+ return [];
11481
+ const out = [];
11482
+ for (const t of tools) {
11483
+ if (t.tool_name !== "host_exec" || t.status !== "awaiting_input")
11484
+ continue;
11485
+ if (!t.tool_call_id)
11486
+ continue;
11487
+ out.push({ toolCallId: t.tool_call_id, command: t.args?.command ?? "" });
11488
+ }
11489
+ return out;
11490
+ }
11491
+
11492
+ // src/lib/host-screenshot.ts
11493
+ async function captureScreenshot(url, opts) {
11494
+ const moduleName = "playwright";
11495
+ let chromium;
11496
+ try {
11497
+ ({ chromium } = await import(moduleName));
11498
+ } catch {
11499
+ throw new Error("playwright is not installed on the runner. Install it to use host_screenshot: `npm i -D playwright && npx playwright install chromium`");
11500
+ }
11501
+ const browser = await chromium.launch({ headless: true });
11502
+ try {
11503
+ const page = await browser.newPage();
11504
+ await page.goto(url, { waitUntil: "load", timeout: 30000 });
11505
+ if (opts.waitMs > 0)
11506
+ await page.waitForTimeout(opts.waitMs);
11507
+ const buf = await page.screenshot({
11508
+ fullPage: opts.fullPage,
11509
+ type: "png"
11510
+ });
11511
+ return buf;
11512
+ } finally {
11513
+ await browser.close();
11514
+ }
11515
+ }
11516
+ function parseHostScreenshotRequests(data) {
11517
+ let tools;
11518
+ try {
11519
+ tools = JSON.parse(data);
11520
+ } catch {
11521
+ return [];
11522
+ }
11523
+ if (!Array.isArray(tools))
11524
+ return [];
11525
+ const out = [];
11526
+ for (const t of tools) {
11527
+ if (t.tool_name !== "host_screenshot" || t.status !== "awaiting_input")
11528
+ continue;
11529
+ if (!t.tool_call_id || !t.args?.url)
11530
+ continue;
11531
+ out.push({
11532
+ toolCallId: t.tool_call_id,
11533
+ url: t.args.url,
11534
+ fullPage: t.args.full_page !== false,
11535
+ waitMs: typeof t.args.wait_ms === "number" ? t.args.wait_ms : 500
11536
+ });
11537
+ }
11538
+ return out;
11539
+ }
11540
+
11334
11541
  // src/commands/chat.ts
11542
+ async function resolveHostExec(sessionId, toolCallId, command, state) {
11543
+ const decision = decideFromJournal(await state.journal.lookup(sessionId, toolCallId));
11544
+ if (decision.kind === "replay")
11545
+ return decision.result;
11546
+ if (decision.kind === "crashed") {
11547
+ return {
11548
+ error: true,
11549
+ exit_code: -1,
11550
+ message: `A previous run did not complete; not re-running: ${decision.command}`
11551
+ };
11552
+ }
11553
+ if (!state.autoYes) {
11554
+ process.stderr.write(source_default.yellow(`
11555
+ [host_exec] refused (re-run with --yes to allow): ${command}
11556
+ `));
11557
+ return {
11558
+ declined: true,
11559
+ exit_code: -1,
11560
+ message: "Host execution declined; re-run with --yes to allow."
11561
+ };
11562
+ }
11563
+ process.stderr.write(source_default.dim(`
11564
+ [host_exec] $ ${command}
11565
+ (cwd: ${state.cwd})
11566
+ `));
11567
+ await state.journal.recordRunning(sessionId, toolCallId, command, new Date().toISOString());
11568
+ let result;
11569
+ try {
11570
+ result = await runHostCommand(command, toolCallId, { cwd: state.cwd });
11571
+ } catch (e) {
11572
+ result = { stdout: "", stderr: String(e), exit_code: -1 };
11573
+ }
11574
+ await state.journal.recordDone(sessionId, toolCallId, result);
11575
+ return result;
11576
+ }
11577
+ async function resolveHostScreenshot(ctx, sessionId, req, state) {
11578
+ if (!state.autoYes) {
11579
+ process.stderr.write(source_default.yellow(`
11580
+ [host_screenshot] refused (re-run with --yes to allow): ${req.url}
11581
+ `));
11582
+ return {
11583
+ declined: true,
11584
+ message: "Host screenshot declined; re-run with --yes to allow."
11585
+ };
11586
+ }
11587
+ process.stderr.write(source_default.dim(`
11588
+ [host_screenshot] ${req.url}
11589
+ `));
11590
+ let png;
11591
+ try {
11592
+ png = await captureScreenshot(req.url, {
11593
+ fullPage: req.fullPage,
11594
+ waitMs: req.waitMs
11595
+ });
11596
+ } catch (e) {
11597
+ return { error: true, message: `screenshot failed: ${String(e)}` };
11598
+ }
11599
+ try {
11600
+ const mint = await ctx.api.post(`/api/chat/${encodeURIComponent(sessionId)}/local-artifact-upload`, {
11601
+ content_type: "image/png"
11602
+ });
11603
+ const put = await fetch(mint.upload_url, {
11604
+ method: "PUT",
11605
+ headers: { "Content-Type": "image/png" },
11606
+ body: new Uint8Array(png)
11607
+ });
11608
+ if (!put.ok) {
11609
+ return {
11610
+ error: true,
11611
+ message: `image upload failed: HTTP ${put.status}`
11612
+ };
11613
+ }
11614
+ return {
11615
+ text: `Screenshot of ${req.url} captured (${png.length} bytes).`,
11616
+ _images: [{ storage_key: mint.storage_key }]
11617
+ };
11618
+ } catch (e) {
11619
+ return { error: true, message: `image upload failed: ${String(e)}` };
11620
+ }
11621
+ }
11622
+ async function _postToolResult(ctx, sessionId, toolCallId, result, label) {
11623
+ try {
11624
+ await ctx.api.post(`/api/chat/${encodeURIComponent(sessionId)}/tool-result`, { tool_call_id: toolCallId, result });
11625
+ } catch (e) {
11626
+ process.stderr.write(source_default.red(`[${label}] failed to POST result: ${String(e)}
11627
+ `));
11628
+ }
11629
+ }
11630
+ async function handleHostExecFrame(ctx, sessionId, data, state) {
11631
+ for (const req of parseHostExecRequests(data)) {
11632
+ const id = req.toolCallId;
11633
+ if (state.handled.has(id))
11634
+ continue;
11635
+ state.handled.add(id);
11636
+ const result = await resolveHostExec(sessionId, id, req.command, state);
11637
+ await _postToolResult(ctx, sessionId, id, result, "host_exec");
11638
+ }
11639
+ for (const req of parseHostScreenshotRequests(data)) {
11640
+ const id = req.toolCallId;
11641
+ if (state.handled.has(id))
11642
+ continue;
11643
+ state.handled.add(id);
11644
+ const result = await resolveHostScreenshot(ctx, sessionId, req, state);
11645
+ await _postToolResult(ctx, sessionId, id, result, "host_screenshot");
11646
+ }
11647
+ }
11335
11648
  function parseSlashCommand(input) {
11336
11649
  const trimmed = input.trim();
11337
11650
  if (!trimmed.startsWith("/"))
@@ -11382,7 +11695,16 @@ async function sendMessage(ctx, sessionId, message, opts = {}) {
11382
11695
  params.selected_agent = opts.agentName;
11383
11696
  reqOpts.params = params;
11384
11697
  }
11385
- const res = await ctx.api.postRaw(`/api/chat/${encodeURIComponent(sessionId)}`, { content: message }, reqOpts);
11698
+ const localExecState = opts.localExec ? {
11699
+ cwd: opts.localExec.cwd,
11700
+ autoYes: opts.localExec.autoYes,
11701
+ handled: new Set,
11702
+ journal: new Journal(opts.localExec.cwd)
11703
+ } : null;
11704
+ const reqBody = { content: message };
11705
+ if (opts.localExec)
11706
+ reqBody.capability_keys = ["local_exec"];
11707
+ const res = await ctx.api.postRaw(`/api/chat/${encodeURIComponent(sessionId)}`, reqBody, reqOpts);
11386
11708
  const contentType = res.headers.get("content-type") ?? "";
11387
11709
  let fullResponse = "";
11388
11710
  if (contentType.includes("application/json")) {
@@ -11430,6 +11752,9 @@ async function sendMessage(ctx, sessionId, message, opts = {}) {
11430
11752
  case "tool_status":
11431
11753
  if (opts.onToolStatus)
11432
11754
  opts.onToolStatus(event.data);
11755
+ if (localExecState) {
11756
+ await handleHostExecFrame(ctx, sessionId, event.data, localExecState);
11757
+ }
11433
11758
  break;
11434
11759
  case "clear":
11435
11760
  fullResponse = "";
@@ -11650,7 +11975,7 @@ async function showHistoryPicker(ctx, rl) {
11650
11975
  }
11651
11976
  return data.items[idx].session_id;
11652
11977
  }
11653
- async function runRepl(ctx, initialSessionId, clientId, agentName, showTools, timeoutMs) {
11978
+ async function runRepl(ctx, initialSessionId, clientId, agentName, showTools, timeoutMs, localExec) {
11654
11979
  let sessionId = initialSessionId;
11655
11980
  process.stderr.write(source_default.dim(`martha v${CLI_VERSION} | session: ${sessionId.slice(0, 8)}...
11656
11981
  `));
@@ -11787,6 +12112,7 @@ Cancelled.
11787
12112
  try {
11788
12113
  const result = await sendMessage(ctx, sessionId, input, {
11789
12114
  timeoutMs,
12115
+ localExec,
11790
12116
  onToken: (token) => process.stdout.write(stripper.push(token)),
11791
12117
  onToolCall: showTools ? (data) => process.stderr.write(formatToolCall(data)) : undefined,
11792
12118
  onToolResult: (data) => {
@@ -11842,7 +12168,7 @@ Error: ${err instanceof Error ? err.message : String(err)}
11842
12168
  process.stderr.write(`
11843
12169
  `);
11844
12170
  }
11845
- async function runOneShot(ctx, sessionId, message, isJson, clientId, agentName, showTools, timeoutMs) {
12171
+ async function runOneShot(ctx, sessionId, message, isJson, clientId, agentName, showTools, timeoutMs, localExec) {
11846
12172
  const toolCalls = [];
11847
12173
  function recordToolStatus(data) {
11848
12174
  try {
@@ -11866,6 +12192,7 @@ async function runOneShot(ctx, sessionId, message, isJson, clientId, agentName,
11866
12192
  clientId,
11867
12193
  agentName,
11868
12194
  timeoutMs,
12195
+ localExec,
11869
12196
  onToolStatus: isJson ? recordToolStatus : showTools ? (data) => {
11870
12197
  recordToolStatus(data);
11871
12198
  process.stderr.write(formatToolStatus(data));
@@ -11889,7 +12216,7 @@ async function runOneShot(ctx, sessionId, message, isJson, clientId, agentName,
11889
12216
  }
11890
12217
  }
11891
12218
  function registerChatCommand(program2) {
11892
- program2.command("chat").description("Chat with a Martha agent").option("--session <id>", "Resume an existing session").option("--client <nameOrId>", "Use a specific client (name or ID)").option("--agent <name>", "Drive the chat with a specific copilot agent (defaults to profile.default_agent)").option("--message <text>", "Send a single message (non-interactive)").option("--show-tools", "Show tool calls and results during chat").option("--timeout <seconds>", "Per-message HTTP timeout in seconds (default: 600)").action(async (opts) => {
12219
+ program2.command("chat").description("Chat with a Martha agent").option("--session <id>", "Resume an existing session").option("--client <nameOrId>", "Use a specific client (name or ID)").option("--agent <name>", "Drive the chat with a specific copilot agent (defaults to profile.default_agent)").option("--message <text>", "Send a single message (non-interactive)").option("--show-tools", "Show tool calls and results during chat").option("--local-exec", "Allow the agent to run host tools on THIS machine (#568). Requires the server to have LOCAL_EXEC_ENABLED=1.").option("--yes", "Auto-approve host tool execution (required with --local-exec for non-interactive runs).").option("--timeout <seconds>", "Per-message HTTP timeout in seconds (default: 600)").action(async (opts) => {
11893
12220
  const ctx = createContext({
11894
12221
  profileOverride: program2.opts().profile,
11895
12222
  verbose: program2.opts().verbose
@@ -11924,13 +12251,14 @@ function registerChatCommand(program2) {
11924
12251
  }
11925
12252
  const showTools = !!opts.showTools;
11926
12253
  const timeoutMs = opts.timeout ? parseTimeoutSeconds(opts.timeout) * 1000 : undefined;
12254
+ const localExec = opts.localExec ? { cwd: process.cwd(), autoYes: !!opts.yes } : undefined;
11927
12255
  if (opts.message) {
11928
- await runOneShot(ctx, sessionId, opts.message, isJson, clientId, agentName, showTools, timeoutMs);
12256
+ await runOneShot(ctx, sessionId, opts.message, isJson, clientId, agentName, showTools, timeoutMs, localExec);
11929
12257
  } else {
11930
12258
  if (isJson) {
11931
12259
  throw new CLIError("The --json flag requires --message for non-interactive mode.", 4 /* Validation */, 'Example: martha --json chat --message "hello"');
11932
12260
  }
11933
- await runRepl(ctx, sessionId, clientId, agentName, showTools, timeoutMs);
12261
+ await runRepl(ctx, sessionId, clientId, agentName, showTools, timeoutMs, localExec);
11934
12262
  }
11935
12263
  });
11936
12264
  }
@@ -12276,20 +12604,27 @@ function registerConfigCommand(program2) {
12276
12604
  }
12277
12605
 
12278
12606
  // src/commands/definitions-apply.ts
12279
- import fs5 from "node:fs";
12280
- import path4 from "node:path";
12607
+ import fs6 from "node:fs";
12608
+ import path5 from "node:path";
12281
12609
  init_dist();
12282
12610
  init_errors();
12283
- var VALID_KINDS = new Set(["Function", "Workflow", "Agent"]);
12611
+ var VALID_KINDS = new Set([
12612
+ "Function",
12613
+ "Workflow",
12614
+ "Agent",
12615
+ "Trigger"
12616
+ ]);
12284
12617
  var KIND_TO_API_PATH = {
12285
12618
  Function: "/api/admin/definitions/functions",
12286
12619
  Workflow: "/api/admin/definitions/workflows",
12287
- Agent: "/api/admin/definitions/agents"
12620
+ Agent: "/api/admin/definitions/agents",
12621
+ Trigger: "/api/admin/triggers"
12288
12622
  };
12289
12623
  var KIND_TO_PLURAL = {
12290
12624
  Function: "Functions",
12291
12625
  Workflow: "Workflows",
12292
- Agent: "Agents"
12626
+ Agent: "Agents",
12627
+ Trigger: "Triggers"
12293
12628
  };
12294
12629
  var SERVER_GENERATED_FIELDS = new Set([
12295
12630
  "id",
@@ -12312,20 +12647,20 @@ var SERVER_GENERATED_FIELDS = new Set([
12312
12647
  ]);
12313
12648
  var AGENT_MANAGED_FIELDS = new Set(["functions", "workflows"]);
12314
12649
  function loadDefinitions(inputPath) {
12315
- const resolved = path4.resolve(inputPath);
12316
- if (!fs5.existsSync(resolved)) {
12650
+ const resolved = path5.resolve(inputPath);
12651
+ if (!fs6.existsSync(resolved)) {
12317
12652
  throw new CLIError(`Path not found: ${inputPath}`, 1 /* Error */);
12318
12653
  }
12319
- const stat = fs5.statSync(resolved);
12654
+ const stat = fs6.statSync(resolved);
12320
12655
  if (stat.isDirectory()) {
12321
12656
  return loadDirectory(resolved);
12322
12657
  }
12323
12658
  return loadFile(resolved);
12324
12659
  }
12325
12660
  function loadDirectory(dirPath) {
12326
- const entries = fs5.readdirSync(dirPath).sort();
12661
+ const entries = fs6.readdirSync(dirPath).sort();
12327
12662
  const validExts = new Set([".yaml", ".yml", ".json"]);
12328
- const files = entries.filter((e) => validExts.has(path4.extname(e).toLowerCase())).map((e) => path4.join(dirPath, e));
12663
+ const files = entries.filter((e) => validExts.has(path5.extname(e).toLowerCase())).map((e) => path5.join(dirPath, e));
12329
12664
  if (files.length === 0) {
12330
12665
  throw new CLIError(`No YAML or JSON files found in ${dirPath}`, 4 /* Validation */);
12331
12666
  }
@@ -12336,8 +12671,8 @@ function loadDirectory(dirPath) {
12336
12671
  return results;
12337
12672
  }
12338
12673
  function loadFile(filePath) {
12339
- const content = fs5.readFileSync(filePath, "utf-8");
12340
- const ext = path4.extname(filePath).toLowerCase();
12674
+ const content = fs6.readFileSync(filePath, "utf-8");
12675
+ const ext = path5.extname(filePath).toLowerCase();
12341
12676
  if (ext === ".json") {
12342
12677
  const parsed = parseJsonFile(content, filePath);
12343
12678
  return [toLocalDefinition(parsed, filePath)];
@@ -12759,7 +13094,7 @@ Examples:
12759
13094
  }
12760
13095
 
12761
13096
  // src/commands/definitions-export.ts
12762
- import fs6 from "node:fs";
13097
+ import fs7 from "node:fs";
12763
13098
  init_errors();
12764
13099
  async function exportDefinitions(ctx, opts, isJsonMode) {
12765
13100
  const params = {};
@@ -12775,7 +13110,7 @@ async function exportDefinitions(ctx, opts, isJsonMode) {
12775
13110
  const text = await res.text();
12776
13111
  if (opts.output) {
12777
13112
  try {
12778
- fs6.writeFileSync(opts.output, text);
13113
+ fs7.writeFileSync(opts.output, text);
12779
13114
  } catch (err) {
12780
13115
  throw new CLIError(`Failed to write ${opts.output}: ${err instanceof Error ? err.message : String(err)}`, 1 /* Error */);
12781
13116
  }
@@ -13495,8 +13830,8 @@ function registerProjectionCommands(parentCmd, getCtx, isJson) {
13495
13830
  } catch {}
13496
13831
  }
13497
13832
  if (opts.output) {
13498
- const fs7 = await import("node:fs/promises");
13499
- await fs7.writeFile(opts.output, toWrite, "utf-8");
13833
+ const fs8 = await import("node:fs/promises");
13834
+ await fs8.writeFile(opts.output, toWrite, "utf-8");
13500
13835
  if (!isJson()) {
13501
13836
  console.error(source_default.dim(`Wrote ${format} projection of '${name}' to ${opts.output}`));
13502
13837
  }
@@ -13850,10 +14185,61 @@ Usage:
13850
14185
  ].join(" "));
13851
14186
  }
13852
14187
  });
14188
+ parentCmd.command("add-agent <coordinator> <subAgent>").description("Grant a coordinator agent the ability to delegate to a sub-agent").option("--set <items...>", "Config overrides (key=value)").action(async (coordinator, subAgent, opts) => {
14189
+ const ctx = getCtx();
14190
+ const body = {
14191
+ sub_agent_name: subAgent
14192
+ };
14193
+ if (opts.set) {
14194
+ body.config_overrides = parseSetFlags(opts.set);
14195
+ }
14196
+ const result = await ctx.api.post(`${API_PATH}/${encodeURIComponent(coordinator)}/agents`, body);
14197
+ if (isJson()) {
14198
+ console.log(JSON.stringify(result, null, 2));
14199
+ return;
14200
+ }
14201
+ console.log(`Agent '${coordinator}' may now delegate to '${subAgent}'`);
14202
+ });
14203
+ parentCmd.command("remove-agent <coordinator> <subAgent>").description("Revoke a coordinator's delegation grant to a sub-agent").action(async (coordinator, subAgent) => {
14204
+ const ctx = getCtx();
14205
+ const endpoint = `${API_PATH}/${encodeURIComponent(coordinator)}/agents/${encodeURIComponent(subAgent)}`;
14206
+ await ctx.api.del(endpoint);
14207
+ if (isJson()) {
14208
+ console.log(JSON.stringify({
14209
+ coordinator,
14210
+ sub_agent_name: subAgent,
14211
+ removed: true
14212
+ }));
14213
+ return;
14214
+ }
14215
+ console.log(`Revoked sub-agent '${subAgent}' from coordinator '${coordinator}'`);
14216
+ });
14217
+ parentCmd.command("agents <coordinator>").description("List the sub-agents a coordinator may delegate to").action(async (coordinator) => {
14218
+ const ctx = getCtx();
14219
+ const items = await ctx.api.get(`${API_PATH}/${encodeURIComponent(coordinator)}/agents`);
14220
+ if (isJson()) {
14221
+ console.log(JSON.stringify(items, null, 2));
14222
+ return;
14223
+ }
14224
+ if (items.length === 0) {
14225
+ console.log(source_default.dim("No sub-agents granted."));
14226
+ return;
14227
+ }
14228
+ const nameW = Math.max(8, ...items.map((s) => String(s.name ?? "").length));
14229
+ const header = ["SUB-AGENT", "DESCRIPTION"].map((h, i) => h.padEnd([nameW, 40][i])).join(" ");
14230
+ console.log(source_default.bold(header));
14231
+ console.log(source_default.dim("-".repeat(header.length)));
14232
+ for (const s of items) {
14233
+ console.log([
14234
+ String(s.name ?? "").padEnd(nameW),
14235
+ String(s.description ?? "-")
14236
+ ].join(" "));
14237
+ }
14238
+ });
13853
14239
  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) => {
13854
14240
  const ctx = getCtx();
13855
- const path5 = `${API_PATH}/${encodeURIComponent(agent)}`;
13856
- const current = await ctx.api.get(path5);
14241
+ const path6 = `${API_PATH}/${encodeURIComponent(agent)}`;
14242
+ const current = await ctx.api.get(path6);
13857
14243
  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;
13858
14244
  if (!mutating) {
13859
14245
  if (isJson()) {
@@ -13905,7 +14291,7 @@ Usage:
13905
14291
  roots.delete(c);
13906
14292
  body.self_grant_collection_roots = [...roots];
13907
14293
  }
13908
- const updated = await ctx.api.put(path5, body);
14294
+ const updated = await ctx.api.put(path6, body);
13909
14295
  if (isJson()) {
13910
14296
  console.log(JSON.stringify(policyView(updated), null, 2));
13911
14297
  return;
@@ -13917,8 +14303,120 @@ Usage:
13917
14303
  }
13918
14304
  };
13919
14305
 
14306
+ // src/commands/triggers.ts
14307
+ init_errors();
14308
+ function triggerKind(item) {
14309
+ if (item.schedule_config)
14310
+ return "schedule";
14311
+ if (item.deadman_config)
14312
+ return "deadman";
14313
+ return "event";
14314
+ }
14315
+ function scheduleSummary(sched) {
14316
+ return String(sched.cron ?? sched.interval ?? sched.spec ?? "schedule");
14317
+ }
14318
+ var triggersConfig = {
14319
+ typeName: "trigger",
14320
+ typeNamePlural: "triggers",
14321
+ apiPath: "/api/admin/triggers",
14322
+ extractList: (data) => {
14323
+ if (!Array.isArray(data)) {
14324
+ throw new Error("Unexpected API response shape for triggers list");
14325
+ }
14326
+ return data;
14327
+ },
14328
+ listColumns: [
14329
+ { header: "NAME", accessor: (item) => String(item.name ?? "") },
14330
+ { header: "KIND", accessor: (item) => triggerKind(item) },
14331
+ {
14332
+ header: "EVENT / SCHEDULE",
14333
+ accessor: (item) => {
14334
+ const sched = item.schedule_config;
14335
+ if (sched)
14336
+ return scheduleSummary(sched);
14337
+ return String(item.event_type ?? "-");
14338
+ }
14339
+ },
14340
+ { header: "TARGET", accessor: (item) => String(item.target_name ?? "-") },
14341
+ {
14342
+ header: "STATUS",
14343
+ accessor: (item) => item.is_active !== false ? "active" : "inactive"
14344
+ }
14345
+ ],
14346
+ renderDetail: (t) => {
14347
+ console.log(source_default.bold(`Trigger: ${t.name}
14348
+ `));
14349
+ if (t.description)
14350
+ console.log(` ${t.description}
14351
+ `);
14352
+ console.log(` Kind: ${triggerKind(t)}`);
14353
+ console.log(` Status: ${t.is_active !== false ? source_default.green("active") : source_default.dim("inactive")}`);
14354
+ if (t.event_type)
14355
+ console.log(` Event type: ${t.event_type}`);
14356
+ const filter = t.event_filter;
14357
+ if (filter && Object.keys(filter).length)
14358
+ console.log(` Event filter: ${JSON.stringify(filter)}`);
14359
+ const sched = t.schedule_config;
14360
+ if (sched)
14361
+ console.log(` Schedule: ${scheduleSummary(sched)} ${source_default.dim(JSON.stringify(sched))}`);
14362
+ const deadman = t.deadman_config;
14363
+ if (deadman)
14364
+ console.log(` Deadman: ${JSON.stringify(deadman)}`);
14365
+ console.log(` Target: ${t.target_type ?? "workflow"} → ${t.target_name ?? "-"}`);
14366
+ const mapping = t.input_mapping;
14367
+ if (mapping && Object.keys(mapping).length) {
14368
+ console.log(`
14369
+ ${source_default.bold("Input mapping:")}`);
14370
+ for (const [k, v] of Object.entries(mapping)) {
14371
+ console.log(` ${k.padEnd(20)} ${source_default.dim(String(v))}`);
14372
+ }
14373
+ }
14374
+ if (t.dedup_key)
14375
+ console.log(`
14376
+ Dedup key: ${t.dedup_key}`);
14377
+ if (t.version)
14378
+ console.log(` Version: ${t.version}`);
14379
+ if (t.updated_at)
14380
+ console.log(` Updated: ${t.updated_at}`);
14381
+ },
14382
+ extraListOptions: (cmd) => {
14383
+ cmd.option("--active", "Only active triggers");
14384
+ },
14385
+ buildListParams: (opts) => {
14386
+ const params = {};
14387
+ if (opts.active)
14388
+ params.active = "true";
14389
+ return params;
14390
+ },
14391
+ extraCommands: (parentCmd, getCtx, isJson) => {
14392
+ parentCmd.command("test <name>").description("Dry-run a trigger against a sample event (no workflow started)").requiredOption("--event <json>", "Sample CloudEvent JSON to match against").action(async (name, opts) => {
14393
+ let event;
14394
+ try {
14395
+ event = JSON.parse(opts.event);
14396
+ } catch {
14397
+ throw new CLIError("--event must be valid JSON", 4 /* Validation */, `Example: martha triggers test ${name} --event '{"type":"facility_booking.booking.created","data":{}}'`);
14398
+ }
14399
+ const ctx = getCtx();
14400
+ const res = await ctx.api.post(`/api/admin/triggers/${encodeURIComponent(name)}/test`, { event });
14401
+ if (isJson()) {
14402
+ console.log(JSON.stringify(res, null, 2));
14403
+ return;
14404
+ }
14405
+ console.log(res.matched ? source_default.green(`✓ trigger '${name}' WOULD fire for this event`) : source_default.yellow(`✗ trigger '${name}' would NOT fire for this event`));
14406
+ console.log(` Event-type / filter match: ${res.filter_result ? source_default.green("yes") : source_default.red("no")}`);
14407
+ if (res.input_mapping_result) {
14408
+ console.log(`
14409
+ ${source_default.bold("Resolved input mapping:")}`);
14410
+ for (const [k, v] of Object.entries(res.input_mapping_result)) {
14411
+ console.log(` ${k.padEnd(20)} ${source_default.dim(JSON.stringify(v))}`);
14412
+ }
14413
+ }
14414
+ });
14415
+ }
14416
+ };
14417
+
13920
14418
  // src/commands/documents.ts
13921
- import fs7 from "node:fs";
14419
+ import fs8 from "node:fs";
13922
14420
  init_errors();
13923
14421
  var TERMINAL_STATUSES2 = new Set(["ready", "error"]);
13924
14422
  var SPINNER_FRAMES2 = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
@@ -14220,7 +14718,7 @@ ${items.length} collections`));
14220
14718
  });
14221
14719
  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) => {
14222
14720
  const ctx = getCtx();
14223
- if (!fs7.existsSync(filePath)) {
14721
+ if (!fs8.existsSync(filePath)) {
14224
14722
  throw new CLIError(`File not found: ${filePath}`, 4 /* Validation */);
14225
14723
  }
14226
14724
  const result = await ctx.api.upload(`/api/admin/collections/${encodeURIComponent(collectionId)}/documents`, filePath);
@@ -15797,8 +16295,8 @@ ${data.length} spec(s)`));
15797
16295
  Resources:
15798
16296
  `));
15799
16297
  for (const r of resources) {
15800
- const path5 = r.path || `/${r.name}`;
15801
- console.log(` ${source_default.cyan(r.label || r.name)}` + source_default.dim(` → ${path5}`));
16298
+ const path6 = r.path || `/${r.name}`;
16299
+ console.log(` ${source_default.cyan(r.label || r.name)}` + source_default.dim(` → ${path6}`));
15802
16300
  }
15803
16301
  }
15804
16302
  try {
@@ -15818,14 +16316,14 @@ Functions:
15818
16316
  console.log(source_default.dim(`
15819
16317
  Proxy: martha integrations proxy ${name} GET /<path>`));
15820
16318
  });
15821
- 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, path5, opts) => {
16319
+ 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, path6, opts) => {
15822
16320
  const ctx = getCtx();
15823
16321
  const upperMethod = method.toUpperCase();
15824
16322
  const allowed = new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
15825
16323
  if (!allowed.has(upperMethod)) {
15826
16324
  throw new CLIError(`Invalid method: ${method}`, 4 /* Validation */, "Allowed: GET, POST, PUT, PATCH, DELETE");
15827
16325
  }
15828
- const cleanPath = path5.startsWith("/") ? path5.slice(1) : path5;
16326
+ const cleanPath = path6.startsWith("/") ? path6.slice(1) : path6;
15829
16327
  const proxyUrl = `/api/admin/plugins/${encodeURIComponent(name)}/${cleanPath}`;
15830
16328
  const params = {};
15831
16329
  if (opts.query) {
@@ -16035,8 +16533,8 @@ async function resolveCredentialValue(value) {
16035
16533
  if (value === "-")
16036
16534
  return readStdin();
16037
16535
  if (value.startsWith("@")) {
16038
- const fs8 = await import("node:fs/promises");
16039
- return (await fs8.readFile(value.slice(1), "utf-8")).trim();
16536
+ const fs9 = await import("node:fs/promises");
16537
+ return (await fs9.readFile(value.slice(1), "utf-8")).trim();
16040
16538
  }
16041
16539
  return value;
16042
16540
  }
@@ -16151,8 +16649,8 @@ Notification connections`));
16151
16649
  if (credentialValue === "-") {
16152
16650
  credentialValue = await readStdin2();
16153
16651
  } else if (credentialValue?.startsWith("@")) {
16154
- const fs8 = await import("node:fs/promises");
16155
- credentialValue = await fs8.readFile(credentialValue.slice(1), "utf-8");
16652
+ const fs9 = await import("node:fs/promises");
16653
+ credentialValue = await fs9.readFile(credentialValue.slice(1), "utf-8");
16156
16654
  }
16157
16655
  if (!credentialValue) {
16158
16656
  throw new Error("--credential-value is required (use '-' for stdin or '@path' for file).");
@@ -16202,11 +16700,11 @@ async function readStdin2() {
16202
16700
 
16203
16701
  // src/commands/messaging.ts
16204
16702
  init_errors();
16205
- async function messagingFetch(baseUrl, path5, opts) {
16206
- if (/^(https?:)?\/\//i.test(path5)) {
16703
+ async function messagingFetch(baseUrl, path6, opts) {
16704
+ if (/^(https?:)?\/\//i.test(path6)) {
16207
16705
  throw new CLIError("Absolute URL paths are not allowed", 1 /* Error */);
16208
16706
  }
16209
- const url = new URL(path5, baseUrl);
16707
+ const url = new URL(path6, baseUrl);
16210
16708
  const headers = {
16211
16709
  "Content-Type": "application/json"
16212
16710
  };
@@ -17486,7 +17984,7 @@ ${models.length} models`));
17486
17984
  }
17487
17985
 
17488
17986
  // src/commands/wiki.ts
17489
- import fs8 from "node:fs";
17987
+ import fs9 from "node:fs";
17490
17988
  init_errors();
17491
17989
  function registerWikiCommands(program2) {
17492
17990
  const cmd = program2.command("wiki").description("Manage tenant wiki pages, settings, schema, recompile (#245 D5.4)");
@@ -17533,14 +18031,14 @@ function registerWikiCommands(program2) {
17533
18031
  console.log(source_default.dim(`
17534
18032
  ${pages.length} pages`));
17535
18033
  });
17536
- 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 (path5, opts) => {
18034
+ 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 (path6, opts) => {
17537
18035
  const ctx = getCtx();
17538
- const safe = path5.split("/").map(encodeURIComponent).join("/");
18036
+ const safe = path6.split("/").map(encodeURIComponent).join("/");
17539
18037
  const resp = await ctx.api.getRaw(`/api/wiki/pages/${safe}`, {
17540
18038
  headers: { Accept: "text/markdown,*/*" }
17541
18039
  });
17542
18040
  if (resp.status === 404) {
17543
- throw new CLIError(`page not found: ${path5}`, 3 /* NotFound */);
18041
+ throw new CLIError(`page not found: ${path6}`, 3 /* NotFound */);
17544
18042
  }
17545
18043
  if (!resp.ok) {
17546
18044
  const detail = await resp.text();
@@ -17548,16 +18046,16 @@ ${pages.length} pages`));
17548
18046
  }
17549
18047
  const body = await resp.text();
17550
18048
  if (opts.out) {
17551
- fs8.writeFileSync(opts.out, body);
18049
+ fs9.writeFileSync(opts.out, body);
17552
18050
  if (!isJson()) {
17553
18051
  console.log(source_default.dim(`wrote ${body.length} bytes to ${opts.out}`));
17554
18052
  } else {
17555
- console.log(JSON.stringify({ path: path5, bytes: body.length, file: opts.out }));
18053
+ console.log(JSON.stringify({ path: path6, bytes: body.length, file: opts.out }));
17556
18054
  }
17557
18055
  return;
17558
18056
  }
17559
18057
  if (isJson()) {
17560
- console.log(JSON.stringify({ path: path5, body, etag: resp.headers.get("ETag") }));
18058
+ console.log(JSON.stringify({ path: path6, body, etag: resp.headers.get("ETag") }));
17561
18059
  } else {
17562
18060
  process.stdout.write(body);
17563
18061
  }
@@ -17652,10 +18150,10 @@ ${pages.length} pages`));
17652
18150
  }
17653
18151
  if (opts.compilePromptOverrideFile) {
17654
18152
  const file = String(opts.compilePromptOverrideFile);
17655
- if (!fs8.existsSync(file)) {
18153
+ if (!fs9.existsSync(file)) {
17656
18154
  throw new CLIError(`override file not found: ${file}`, 3 /* NotFound */);
17657
18155
  }
17658
- const content = fs8.readFileSync(file, "utf8");
18156
+ const content = fs9.readFileSync(file, "utf8");
17659
18157
  const bytes = Buffer.byteLength(content, "utf8");
17660
18158
  if (bytes > 16 * 1024) {
17661
18159
  throw new CLIError(`compile prompt override exceeds 16 KB (${bytes} bytes)`, 4 /* Validation */);
@@ -17679,7 +18177,7 @@ ${pages.length} pages`));
17679
18177
  const ctx = getCtx();
17680
18178
  const resp = await ctx.api.get("/api/wiki/schema");
17681
18179
  if (opts.out) {
17682
- fs8.writeFileSync(opts.out, resp.body);
18180
+ fs9.writeFileSync(opts.out, resp.body);
17683
18181
  if (!isJson()) {
17684
18182
  console.log(source_default.dim(`wrote ${resp.body.length} bytes to ${opts.out}`));
17685
18183
  } else {
@@ -17695,10 +18193,10 @@ ${pages.length} pages`));
17695
18193
  });
17696
18194
  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) => {
17697
18195
  const ctx = getCtx();
17698
- if (!fs8.existsSync(opts.file)) {
18196
+ if (!fs9.existsSync(opts.file)) {
17699
18197
  throw new CLIError(`schema file not found: ${opts.file}`, 3 /* NotFound */);
17700
18198
  }
17701
- const body = fs8.readFileSync(opts.file, "utf8");
18199
+ const body = fs9.readFileSync(opts.file, "utf8");
17702
18200
  const bytes = Buffer.byteLength(body, "utf8");
17703
18201
  if (bytes > 64 * 1024) {
17704
18202
  throw new CLIError(`schema body exceeds 64 KB (${bytes} bytes)`, 4 /* Validation */);
@@ -18224,8 +18722,8 @@ async function resolveCredentialFlag(value) {
18224
18722
  return Buffer.concat(chunks).toString("utf-8").trim();
18225
18723
  }
18226
18724
  if (value.startsWith("@")) {
18227
- const fs9 = await import("node:fs/promises");
18228
- return (await fs9.readFile(value.slice(1), "utf-8")).trim();
18725
+ const fs10 = await import("node:fs/promises");
18726
+ return (await fs10.readFile(value.slice(1), "utf-8")).trim();
18229
18727
  }
18230
18728
  return value;
18231
18729
  }
@@ -19411,19 +19909,19 @@ function registerDoctorCommand(program2) {
19411
19909
 
19412
19910
  // src/commands/skill.ts
19413
19911
  init_errors();
19414
- import fs9 from "node:fs";
19415
- import path5 from "node:path";
19912
+ import fs10 from "node:fs";
19913
+ import path6 from "node:path";
19416
19914
  import { fileURLToPath } from "node:url";
19417
19915
  function locateSkill() {
19418
- const here = path5.dirname(fileURLToPath(import.meta.url));
19916
+ const here = path6.dirname(fileURLToPath(import.meta.url));
19419
19917
  const candidates = [
19420
- path5.join(here, "skills", "martha-cli", "SKILL.md"),
19421
- path5.join(here, "..", "skills", "martha-cli", "SKILL.md"),
19422
- path5.join(here, "..", "..", "..", "skills", "martha-cli", "SKILL.md"),
19423
- path5.join(here, "..", "..", "skills", "martha-cli", "SKILL.md")
19918
+ path6.join(here, "skills", "martha-cli", "SKILL.md"),
19919
+ path6.join(here, "..", "skills", "martha-cli", "SKILL.md"),
19920
+ path6.join(here, "..", "..", "..", "skills", "martha-cli", "SKILL.md"),
19921
+ path6.join(here, "..", "..", "skills", "martha-cli", "SKILL.md")
19424
19922
  ];
19425
19923
  for (const p of candidates) {
19426
- if (fs9.existsSync(p))
19924
+ if (fs10.existsSync(p))
19427
19925
  return p;
19428
19926
  }
19429
19927
  return null;
@@ -19433,7 +19931,7 @@ async function skillCommand() {
19433
19931
  if (!skillPath) {
19434
19932
  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 */);
19435
19933
  }
19436
- const body = fs9.readFileSync(skillPath, "utf-8");
19934
+ const body = fs10.readFileSync(skillPath, "utf-8");
19437
19935
  process.stdout.write(body);
19438
19936
  }
19439
19937
  function registerSkillCommand(program2) {
@@ -19618,6 +20116,7 @@ registerConfigCommand(program2);
19618
20116
  registerDefinitionCommands(program2, functionsConfig);
19619
20117
  registerDefinitionCommands(program2, workflowsConfig);
19620
20118
  registerDefinitionCommands(program2, agentsConfig);
20119
+ registerDefinitionCommands(program2, triggersConfig);
19621
20120
  registerDefinitionsApply(program2);
19622
20121
  registerDefinitionsExport(program2);
19623
20122
  registerDocumentCommands(program2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiai-pt/martha-cli",
3
- "version": "0.19.0",
3
+ "version": "0.22.0",
4
4
  "description": "Terminal-first client for the Martha AI platform",
5
5
  "homepage": "https://docs.martha.nomadriver.co",
6
6
  "repository": {
@@ -279,6 +279,16 @@ martha agents functions <agent> # List grant
279
279
 
280
280
  `--collection` scopes the grant to one collection per the #372 PR2 cascade. Omit it to grant at the root (tenant-wide); pass it on `remove-function` to revoke just that scope. Server returns 400 if the function is collection-agnostic (e.g. `recall`).
281
281
 
282
+ **Sub-agent grants (agent→agent delegation):**
283
+
284
+ ```bash
285
+ martha agents add-agent <coordinator> <sub-agent> [--set key=value] # Let <coordinator> delegate to <sub-agent>
286
+ martha agents remove-agent <coordinator> <sub-agent> # Revoke the delegation grant
287
+ martha agents agents <coordinator> # List the sub-agents <coordinator> may delegate to
288
+ ```
289
+
290
+ A grant means the coordinator agent *may* delegate to the sub-agent at invoke time (the sub-agent loads as an `_is_agent` delegate tool); the sub-agent's own tool calls still pass the policy gate under its own grants. `--set key=value` records `config_overrides` for the delegation. Self-delegation is rejected with a clear 400 (`an agent cannot delegate to itself`), and granting an unknown sub-agent returns 404. All three commands support `--json`.
291
+
282
292
  ---
283
293
 
284
294
  ## Tasks: queue + executor lifecycle