@aiaiai-pt/martha-cli 0.18.0 → 0.20.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.18.0";
11080
+ var CLI_VERSION = "0.20.0";
11063
11081
 
11064
11082
  // src/commands/sessions.ts
11065
11083
  function relativeTime(iso) {
@@ -13559,27 +13577,6 @@ var workflowsConfig = {
13559
13577
 
13560
13578
  // src/commands/agents.ts
13561
13579
  init_errors();
13562
-
13563
- // src/lib/collections.ts
13564
- init_errors();
13565
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
13566
- async function resolveCollectionIdForGrant(ctx, ref) {
13567
- if (UUID_RE.test(ref)) {
13568
- return ref;
13569
- }
13570
- const all = await ctx.api.get("/api/admin/collections", {
13571
- params: { active_only: "false" }
13572
- });
13573
- const bySlug = all.find((c) => c.slug === ref);
13574
- if (bySlug)
13575
- return bySlug.id;
13576
- const byName = all.find((c) => c.name === ref);
13577
- if (byName)
13578
- return byName.id;
13579
- throw new CLIError(`Collection '${ref}' not found (tried UUID, slug, and name).`, 3 /* NotFound */, "Run `martha documents collections --tree` to see available collections.");
13580
- }
13581
-
13582
- // src/commands/agents.ts
13583
13580
  function parseSetFlags(items) {
13584
13581
  const result = {};
13585
13582
  for (const item of items) {
@@ -13756,7 +13753,10 @@ var agentsConfig = {
13756
13753
  console.log(` Updated: ${agent.updated_at}`);
13757
13754
  },
13758
13755
  extraCommands: (parentCmd, getCtx, isJson) => {
13759
- parentCmd.command("add-function <agent> <functionName>").description("Add a function to an agent").option("--set <items...>", "Config overrides (key=value)").option("--collection <slug-or-id>", "Scope the grant to one collection (#372 PR2). " + "Server rejects with 400 if the function is collection-agnostic.").action(async (agent, functionName, opts) => {
13756
+ parentCmd.command("add-function <agent> <functionName>").description("Add a function to an agent").option("--set <items...>", "Config overrides (key=value)").option("--collection <slug-or-id>", "REMOVED (#609) collection access is managed separately; see error").action(async (agent, functionName, opts) => {
13757
+ if (opts.collection) {
13758
+ throw new CLIError("`--collection` was removed; collection access is managed separately: " + "`martha collections grant <collection> --to agent:<id>`.", 4 /* Validation */);
13759
+ }
13760
13760
  const ctx = getCtx();
13761
13761
  const body = {
13762
13762
  function_name: functionName
@@ -13764,36 +13764,29 @@ var agentsConfig = {
13764
13764
  if (opts.set) {
13765
13765
  body.config_overrides = parseSetFlags(opts.set);
13766
13766
  }
13767
- if (opts.collection) {
13768
- body.collection_id = await resolveCollectionIdForGrant(ctx, opts.collection);
13769
- }
13770
13767
  const result = await ctx.api.post(`${API_PATH}/${encodeURIComponent(agent)}/functions`, body);
13771
13768
  if (isJson()) {
13772
13769
  console.log(JSON.stringify(result, null, 2));
13773
13770
  return;
13774
13771
  }
13775
- const scopeLabel = opts.collection ? ` (scope: ${opts.collection})` : "";
13776
- console.log(`Added function '${functionName}' to agent '${agent}'${scopeLabel}`);
13772
+ console.log(`Added function '${functionName}' to agent '${agent}'`);
13777
13773
  });
13778
- parentCmd.command("remove-function <agent> <functionName>").description("Remove a function from an agent").option("--collection <slug-or-id>", "Revoke just the given collection scope (#372 PR2). " + "Omit to revoke the root grant (collection_id IS NULL).").action(async (agent, functionName, opts) => {
13779
- const ctx = getCtx();
13780
- let endpoint = `${API_PATH}/${encodeURIComponent(agent)}/functions/${encodeURIComponent(functionName)}`;
13774
+ parentCmd.command("remove-function <agent> <functionName>").description("Remove a function from an agent").option("--collection <slug-or-id>", "REMOVED (#609) collection access is managed separately; see error").action(async (agent, functionName, opts) => {
13781
13775
  if (opts.collection) {
13782
- const collId = await resolveCollectionIdForGrant(ctx, opts.collection);
13783
- endpoint += `?collection_id=${encodeURIComponent(collId)}`;
13776
+ throw new CLIError("`--collection` was removed; collection access is managed separately: " + "`martha collections revoke <collection> --to agent:<id>`.", 4 /* Validation */);
13784
13777
  }
13778
+ const ctx = getCtx();
13779
+ const endpoint = `${API_PATH}/${encodeURIComponent(agent)}/functions/${encodeURIComponent(functionName)}`;
13785
13780
  await ctx.api.del(endpoint);
13786
13781
  if (isJson()) {
13787
13782
  console.log(JSON.stringify({
13788
13783
  agent,
13789
13784
  function_name: functionName,
13790
- collection: opts.collection ?? null,
13791
13785
  removed: true
13792
13786
  }));
13793
13787
  return;
13794
13788
  }
13795
- const scopeLabel = opts.collection ? ` (scope: ${opts.collection})` : "";
13796
- console.log(`Removed function '${functionName}' from agent '${agent}'${scopeLabel}`);
13789
+ console.log(`Removed function '${functionName}' from agent '${agent}'`);
13797
13790
  });
13798
13791
  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) => {
13799
13792
  const ctx = getCtx();
@@ -13875,6 +13868,57 @@ Usage:
13875
13868
  ].join(" "));
13876
13869
  }
13877
13870
  });
13871
+ 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) => {
13872
+ const ctx = getCtx();
13873
+ const body = {
13874
+ sub_agent_name: subAgent
13875
+ };
13876
+ if (opts.set) {
13877
+ body.config_overrides = parseSetFlags(opts.set);
13878
+ }
13879
+ const result = await ctx.api.post(`${API_PATH}/${encodeURIComponent(coordinator)}/agents`, body);
13880
+ if (isJson()) {
13881
+ console.log(JSON.stringify(result, null, 2));
13882
+ return;
13883
+ }
13884
+ console.log(`Agent '${coordinator}' may now delegate to '${subAgent}'`);
13885
+ });
13886
+ parentCmd.command("remove-agent <coordinator> <subAgent>").description("Revoke a coordinator's delegation grant to a sub-agent").action(async (coordinator, subAgent) => {
13887
+ const ctx = getCtx();
13888
+ const endpoint = `${API_PATH}/${encodeURIComponent(coordinator)}/agents/${encodeURIComponent(subAgent)}`;
13889
+ await ctx.api.del(endpoint);
13890
+ if (isJson()) {
13891
+ console.log(JSON.stringify({
13892
+ coordinator,
13893
+ sub_agent_name: subAgent,
13894
+ removed: true
13895
+ }));
13896
+ return;
13897
+ }
13898
+ console.log(`Revoked sub-agent '${subAgent}' from coordinator '${coordinator}'`);
13899
+ });
13900
+ parentCmd.command("agents <coordinator>").description("List the sub-agents a coordinator may delegate to").action(async (coordinator) => {
13901
+ const ctx = getCtx();
13902
+ const items = await ctx.api.get(`${API_PATH}/${encodeURIComponent(coordinator)}/agents`);
13903
+ if (isJson()) {
13904
+ console.log(JSON.stringify(items, null, 2));
13905
+ return;
13906
+ }
13907
+ if (items.length === 0) {
13908
+ console.log(source_default.dim("No sub-agents granted."));
13909
+ return;
13910
+ }
13911
+ const nameW = Math.max(8, ...items.map((s) => String(s.name ?? "").length));
13912
+ const header = ["SUB-AGENT", "DESCRIPTION"].map((h, i) => h.padEnd([nameW, 40][i])).join(" ");
13913
+ console.log(source_default.bold(header));
13914
+ console.log(source_default.dim("-".repeat(header.length)));
13915
+ for (const s of items) {
13916
+ console.log([
13917
+ String(s.name ?? "").padEnd(nameW),
13918
+ String(s.description ?? "-")
13919
+ ].join(" "));
13920
+ }
13921
+ });
13878
13922
  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) => {
13879
13923
  const ctx = getCtx();
13880
13924
  const path5 = `${API_PATH}/${encodeURIComponent(agent)}`;
@@ -17051,7 +17095,10 @@ ${clients.length} client(s)`));
17051
17095
  }
17052
17096
  console.log(`Deleted client '${nameOrId}'`);
17053
17097
  });
17054
- cmd.command("grant <clientNameOrId> <type> <defName>").description("Grant a client access to a function, workflow, or agent").option("--config <items...>", "Config overrides (key=value, functions/agents only)").option("--collection <slug-or-id>", "Scope the grant to one collection (function grants only; #372 PR2)").action(async (clientNameOrId, type, defName, opts) => {
17098
+ cmd.command("grant <clientNameOrId> <type> <defName>").description("Grant a client access to a function, workflow, or agent").option("--config <items...>", "Config overrides (key=value, functions/agents only)").option("--collection <slug-or-id>", "REMOVED (#609) collection access is managed separately; see error").action(async (clientNameOrId, type, defName, opts) => {
17099
+ if (opts.collection) {
17100
+ throw new CLIError("`--collection` was removed; collection access is managed separately: " + "`martha collections grant <collection> --to client:<id>`.", 4 /* Validation */);
17101
+ }
17055
17102
  if (!VALID_TYPES.has(type)) {
17056
17103
  throw new CLIError(`Invalid type: '${type}'. Must be function, workflow, or agent.`, 4 /* Validation */);
17057
17104
  }
@@ -17079,30 +17126,23 @@ ${clients.length} client(s)`));
17079
17126
  }
17080
17127
  body.config_overrides = parseSetFlags(opts.config);
17081
17128
  }
17082
- if (opts.collection) {
17083
- if (accessType !== "function") {
17084
- throw new CLIError("--collection only applies to function grants (#372 PR2).", 4 /* Validation */);
17085
- }
17086
- body.collection_id = await resolveCollectionIdForGrant(ctx, opts.collection);
17087
- }
17088
17129
  const result = await ctx.api.post(endpoint, body);
17089
17130
  if (isJson()) {
17090
17131
  console.log(JSON.stringify(result, null, 2));
17091
17132
  return;
17092
17133
  }
17093
- const scopeLabel = opts.collection ? ` (scope: ${opts.collection})` : "";
17094
- console.log(`Granted ${accessType} '${defName}' to client '${clientNameOrId}'${scopeLabel}`);
17134
+ console.log(`Granted ${accessType} '${defName}' to client '${clientNameOrId}'`);
17095
17135
  });
17096
- cmd.command("revoke <clientNameOrId> <type> <defName>").description("Revoke a client's access to a function, workflow, or agent").option("--collection <slug-or-id>", "Revoke just the given collection scope (function grants only; #372 PR2). " + "Omit to revoke the root grant (collection_id IS NULL).").action(async (clientNameOrId, type, defName, opts) => {
17136
+ cmd.command("revoke <clientNameOrId> <type> <defName>").description("Revoke a client's access to a function, workflow, or agent").option("--collection <slug-or-id>", "REMOVED (#609) collection access is managed separately; see error").action(async (clientNameOrId, type, defName, opts) => {
17137
+ if (opts.collection) {
17138
+ throw new CLIError("`--collection` was removed; collection access is managed separately: " + "`martha collections revoke <collection> --to client:<id>`.", 4 /* Validation */);
17139
+ }
17097
17140
  if (!VALID_TYPES.has(type)) {
17098
17141
  throw new CLIError(`Invalid type: '${type}'. Must be function, workflow, or agent.`, 4 /* Validation */);
17099
17142
  }
17100
17143
  const accessType = type;
17101
17144
  const ctx = getCtx();
17102
17145
  const clientId = await resolveClientId(ctx, clientNameOrId);
17103
- if (opts.collection && accessType !== "function") {
17104
- throw new CLIError("--collection only applies to function grants (#372 PR2).", 4 /* Validation */);
17105
- }
17106
17146
  let endpoint;
17107
17147
  if (accessType === "agent") {
17108
17148
  endpoint = `${DEFS_API}/clients/${clientId}/agents/${encodeURIComponent(defName)}`;
@@ -17110,10 +17150,6 @@ ${clients.length} client(s)`));
17110
17150
  const defId = await resolveDefinitionId(ctx, accessType, defName);
17111
17151
  const typePlural = `${accessType}s`;
17112
17152
  endpoint = `${DEFS_API}/clients/${clientId}/${typePlural}/${encodeURIComponent(defId)}`;
17113
- if (opts.collection) {
17114
- const collId = await resolveCollectionIdForGrant(ctx, opts.collection);
17115
- endpoint += `?collection_id=${encodeURIComponent(collId)}`;
17116
- }
17117
17153
  }
17118
17154
  await ctx.api.del(endpoint);
17119
17155
  if (isJson()) {
@@ -17121,13 +17157,11 @@ ${clients.length} client(s)`));
17121
17157
  client: clientNameOrId,
17122
17158
  type: accessType,
17123
17159
  name: defName,
17124
- collection: opts.collection ?? null,
17125
17160
  revoked: true
17126
17161
  }));
17127
17162
  return;
17128
17163
  }
17129
- const scopeLabel = opts.collection ? ` (scope: ${opts.collection})` : "";
17130
- console.log(`Revoked ${accessType} '${defName}' from client '${clientNameOrId}'${scopeLabel}`);
17164
+ console.log(`Revoked ${accessType} '${defName}' from client '${clientNameOrId}'`);
17131
17165
  });
17132
17166
  const embed = cmd.command("embed").description("Create, configure, and test embeddable chat clients");
17133
17167
  function addEmbedConfigOptions(command) {
@@ -18396,6 +18430,25 @@ function registerPolicyCommands(program2) {
18396
18430
  // src/commands/authz.ts
18397
18431
  init_errors();
18398
18432
 
18433
+ // src/lib/collections.ts
18434
+ init_errors();
18435
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
18436
+ async function resolveCollectionIdForGrant(ctx, ref) {
18437
+ if (UUID_RE.test(ref)) {
18438
+ return ref;
18439
+ }
18440
+ const all = await ctx.api.get("/api/admin/collections", {
18441
+ params: { active_only: "false" }
18442
+ });
18443
+ const bySlug = all.find((c) => c.slug === ref);
18444
+ if (bySlug)
18445
+ return bySlug.id;
18446
+ const byName = all.find((c) => c.name === ref);
18447
+ if (byName)
18448
+ return byName.id;
18449
+ throw new CLIError(`Collection '${ref}' not found (tried UUID, slug, and name).`, 3 /* NotFound */, "Run `martha documents collections --tree` to see available collections.");
18450
+ }
18451
+
18399
18452
  // src/lib/authz.ts
18400
18453
  init_errors();
18401
18454
  var EX_UNAVAILABLE = 69;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiai-pt/martha-cli",
3
- "version": "0.18.0",
3
+ "version": "0.20.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