@aiaiai-pt/martha-cli 0.16.1 → 0.19.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.
Files changed (2) hide show
  1. package/dist/index.js +519 -80
  2. package/package.json +1 -1
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;
@@ -11077,7 +11059,7 @@ import { createInterface as createInterface2 } from "node:readline";
11077
11059
  init_errors();
11078
11060
 
11079
11061
  // src/version.ts
11080
- var CLI_VERSION = "0.16.1";
11062
+ var CLI_VERSION = "0.19.0";
11081
11063
 
11082
11064
  // src/commands/sessions.ts
11083
11065
  function relativeTime(iso) {
@@ -13577,27 +13559,6 @@ var workflowsConfig = {
13577
13559
 
13578
13560
  // src/commands/agents.ts
13579
13561
  init_errors();
13580
-
13581
- // src/lib/collections.ts
13582
- init_errors();
13583
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
13584
- async function resolveCollectionIdForGrant(ctx, ref) {
13585
- if (UUID_RE.test(ref)) {
13586
- return ref;
13587
- }
13588
- const all = await ctx.api.get("/api/admin/collections", {
13589
- params: { active_only: "false" }
13590
- });
13591
- const bySlug = all.find((c) => c.slug === ref);
13592
- if (bySlug)
13593
- return bySlug.id;
13594
- const byName = all.find((c) => c.name === ref);
13595
- if (byName)
13596
- return byName.id;
13597
- throw new CLIError(`Collection '${ref}' not found (tried UUID, slug, and name).`, 3 /* NotFound */, "Run `martha documents collections --tree` to see available collections.");
13598
- }
13599
-
13600
- // src/commands/agents.ts
13601
13562
  function parseSetFlags(items) {
13602
13563
  const result = {};
13603
13564
  for (const item of items) {
@@ -13774,7 +13735,10 @@ var agentsConfig = {
13774
13735
  console.log(` Updated: ${agent.updated_at}`);
13775
13736
  },
13776
13737
  extraCommands: (parentCmd, getCtx, isJson) => {
13777
- 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) => {
13738
+ 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) => {
13739
+ if (opts.collection) {
13740
+ throw new CLIError("`--collection` was removed; collection access is managed separately: " + "`martha collections grant <collection> --to agent:<id>`.", 4 /* Validation */);
13741
+ }
13778
13742
  const ctx = getCtx();
13779
13743
  const body = {
13780
13744
  function_name: functionName
@@ -13782,36 +13746,29 @@ var agentsConfig = {
13782
13746
  if (opts.set) {
13783
13747
  body.config_overrides = parseSetFlags(opts.set);
13784
13748
  }
13785
- if (opts.collection) {
13786
- body.collection_id = await resolveCollectionIdForGrant(ctx, opts.collection);
13787
- }
13788
13749
  const result = await ctx.api.post(`${API_PATH}/${encodeURIComponent(agent)}/functions`, body);
13789
13750
  if (isJson()) {
13790
13751
  console.log(JSON.stringify(result, null, 2));
13791
13752
  return;
13792
13753
  }
13793
- const scopeLabel = opts.collection ? ` (scope: ${opts.collection})` : "";
13794
- console.log(`Added function '${functionName}' to agent '${agent}'${scopeLabel}`);
13754
+ console.log(`Added function '${functionName}' to agent '${agent}'`);
13795
13755
  });
13796
- 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) => {
13797
- const ctx = getCtx();
13798
- let endpoint = `${API_PATH}/${encodeURIComponent(agent)}/functions/${encodeURIComponent(functionName)}`;
13756
+ 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) => {
13799
13757
  if (opts.collection) {
13800
- const collId = await resolveCollectionIdForGrant(ctx, opts.collection);
13801
- endpoint += `?collection_id=${encodeURIComponent(collId)}`;
13758
+ throw new CLIError("`--collection` was removed; collection access is managed separately: " + "`martha collections revoke <collection> --to agent:<id>`.", 4 /* Validation */);
13802
13759
  }
13760
+ const ctx = getCtx();
13761
+ const endpoint = `${API_PATH}/${encodeURIComponent(agent)}/functions/${encodeURIComponent(functionName)}`;
13803
13762
  await ctx.api.del(endpoint);
13804
13763
  if (isJson()) {
13805
13764
  console.log(JSON.stringify({
13806
13765
  agent,
13807
13766
  function_name: functionName,
13808
- collection: opts.collection ?? null,
13809
13767
  removed: true
13810
13768
  }));
13811
13769
  return;
13812
13770
  }
13813
- const scopeLabel = opts.collection ? ` (scope: ${opts.collection})` : "";
13814
- console.log(`Removed function '${functionName}' from agent '${agent}'${scopeLabel}`);
13771
+ console.log(`Removed function '${functionName}' from agent '${agent}'`);
13815
13772
  });
13816
13773
  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) => {
13817
13774
  const ctx = getCtx();
@@ -14892,6 +14849,7 @@ function registerApprovalCommands(program2) {
14892
14849
  }
14893
14850
 
14894
14851
  // src/commands/tasks.ts
14852
+ import { readFileSync } from "node:fs";
14895
14853
  init_errors();
14896
14854
  var truncate2 = (s, n) => s.length > n ? s.slice(0, n - 1) + "…" : s;
14897
14855
  var sleep3 = (ms, signal) => new Promise((resolve) => {
@@ -15332,13 +15290,25 @@ ${items.length} task(s) available`));
15332
15290
  process.off("SIGTERM", onSignal);
15333
15291
  }
15334
15292
  });
15335
- cmd.command("complete <id>").description("Complete a claimed task with outcome").requiredOption("--outcome <json>", "Outcome data as JSON string").action(async (id, opts) => {
15293
+ cmd.command("complete <id>").description("Complete a claimed task with outcome").option("--outcome <json>", "Outcome data as JSON string").option("--outcome-file <path>", "Read outcome JSON from a file instead of --outcome (for large outcomes)").action(async (id, opts) => {
15336
15294
  const ctx = getCtx();
15295
+ let rawOutcome;
15296
+ if (opts.outcomeFile) {
15297
+ try {
15298
+ rawOutcome = readFileSync(opts.outcomeFile, "utf-8");
15299
+ } catch {
15300
+ throw new CLIError(`--outcome-file could not be read: ${opts.outcomeFile}`, 4 /* Validation */);
15301
+ }
15302
+ } else if (opts.outcome) {
15303
+ rawOutcome = opts.outcome;
15304
+ } else {
15305
+ throw new CLIError("provide --outcome <json> or --outcome-file <path>", 4 /* Validation */);
15306
+ }
15337
15307
  let outcome;
15338
15308
  try {
15339
- outcome = JSON.parse(opts.outcome);
15309
+ outcome = JSON.parse(rawOutcome);
15340
15310
  } catch {
15341
- throw new CLIError("--outcome must be valid JSON", 4 /* Validation */);
15311
+ throw new CLIError("outcome must be valid JSON", 4 /* Validation */);
15342
15312
  }
15343
15313
  const result = await ctx.api.post(`/api/tasks/${encodeURIComponent(id)}/complete`, { outcome });
15344
15314
  if (isJson()) {
@@ -17056,7 +17026,10 @@ ${clients.length} client(s)`));
17056
17026
  }
17057
17027
  console.log(`Deleted client '${nameOrId}'`);
17058
17028
  });
17059
- 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) => {
17029
+ 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) => {
17030
+ if (opts.collection) {
17031
+ throw new CLIError("`--collection` was removed; collection access is managed separately: " + "`martha collections grant <collection> --to client:<id>`.", 4 /* Validation */);
17032
+ }
17060
17033
  if (!VALID_TYPES.has(type)) {
17061
17034
  throw new CLIError(`Invalid type: '${type}'. Must be function, workflow, or agent.`, 4 /* Validation */);
17062
17035
  }
@@ -17084,30 +17057,23 @@ ${clients.length} client(s)`));
17084
17057
  }
17085
17058
  body.config_overrides = parseSetFlags(opts.config);
17086
17059
  }
17087
- if (opts.collection) {
17088
- if (accessType !== "function") {
17089
- throw new CLIError("--collection only applies to function grants (#372 PR2).", 4 /* Validation */);
17090
- }
17091
- body.collection_id = await resolveCollectionIdForGrant(ctx, opts.collection);
17092
- }
17093
17060
  const result = await ctx.api.post(endpoint, body);
17094
17061
  if (isJson()) {
17095
17062
  console.log(JSON.stringify(result, null, 2));
17096
17063
  return;
17097
17064
  }
17098
- const scopeLabel = opts.collection ? ` (scope: ${opts.collection})` : "";
17099
- console.log(`Granted ${accessType} '${defName}' to client '${clientNameOrId}'${scopeLabel}`);
17065
+ console.log(`Granted ${accessType} '${defName}' to client '${clientNameOrId}'`);
17100
17066
  });
17101
- 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) => {
17067
+ 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) => {
17068
+ if (opts.collection) {
17069
+ throw new CLIError("`--collection` was removed; collection access is managed separately: " + "`martha collections revoke <collection> --to client:<id>`.", 4 /* Validation */);
17070
+ }
17102
17071
  if (!VALID_TYPES.has(type)) {
17103
17072
  throw new CLIError(`Invalid type: '${type}'. Must be function, workflow, or agent.`, 4 /* Validation */);
17104
17073
  }
17105
17074
  const accessType = type;
17106
17075
  const ctx = getCtx();
17107
17076
  const clientId = await resolveClientId(ctx, clientNameOrId);
17108
- if (opts.collection && accessType !== "function") {
17109
- throw new CLIError("--collection only applies to function grants (#372 PR2).", 4 /* Validation */);
17110
- }
17111
17077
  let endpoint;
17112
17078
  if (accessType === "agent") {
17113
17079
  endpoint = `${DEFS_API}/clients/${clientId}/agents/${encodeURIComponent(defName)}`;
@@ -17115,10 +17081,6 @@ ${clients.length} client(s)`));
17115
17081
  const defId = await resolveDefinitionId(ctx, accessType, defName);
17116
17082
  const typePlural = `${accessType}s`;
17117
17083
  endpoint = `${DEFS_API}/clients/${clientId}/${typePlural}/${encodeURIComponent(defId)}`;
17118
- if (opts.collection) {
17119
- const collId = await resolveCollectionIdForGrant(ctx, opts.collection);
17120
- endpoint += `?collection_id=${encodeURIComponent(collId)}`;
17121
- }
17122
17084
  }
17123
17085
  await ctx.api.del(endpoint);
17124
17086
  if (isJson()) {
@@ -17126,13 +17088,11 @@ ${clients.length} client(s)`));
17126
17088
  client: clientNameOrId,
17127
17089
  type: accessType,
17128
17090
  name: defName,
17129
- collection: opts.collection ?? null,
17130
17091
  revoked: true
17131
17092
  }));
17132
17093
  return;
17133
17094
  }
17134
- const scopeLabel = opts.collection ? ` (scope: ${opts.collection})` : "";
17135
- console.log(`Revoked ${accessType} '${defName}' from client '${clientNameOrId}'${scopeLabel}`);
17095
+ console.log(`Revoked ${accessType} '${defName}' from client '${clientNameOrId}'`);
17136
17096
  });
17137
17097
  const embed = cmd.command("embed").description("Create, configure, and test embeddable chat clients");
17138
17098
  function addEmbedConfigOptions(command) {
@@ -18398,6 +18358,484 @@ function registerPolicyCommands(program2) {
18398
18358
  });
18399
18359
  }
18400
18360
 
18361
+ // src/commands/authz.ts
18362
+ init_errors();
18363
+
18364
+ // src/lib/collections.ts
18365
+ init_errors();
18366
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
18367
+ async function resolveCollectionIdForGrant(ctx, ref) {
18368
+ if (UUID_RE.test(ref)) {
18369
+ return ref;
18370
+ }
18371
+ const all = await ctx.api.get("/api/admin/collections", {
18372
+ params: { active_only: "false" }
18373
+ });
18374
+ const bySlug = all.find((c) => c.slug === ref);
18375
+ if (bySlug)
18376
+ return bySlug.id;
18377
+ const byName = all.find((c) => c.name === ref);
18378
+ if (byName)
18379
+ return byName.id;
18380
+ throw new CLIError(`Collection '${ref}' not found (tried UUID, slug, and name).`, 3 /* NotFound */, "Run `martha documents collections --tree` to see available collections.");
18381
+ }
18382
+
18383
+ // src/lib/authz.ts
18384
+ init_errors();
18385
+ var EX_UNAVAILABLE = 69;
18386
+ var PRINCIPAL_TYPES = ["user", "client", "agent", "group"];
18387
+ function parsePrincipal(to) {
18388
+ const idx = to.indexOf(":");
18389
+ if (idx <= 0 || idx === to.length - 1) {
18390
+ throw new CLIError(`Invalid --to '${to}'. Use <type>:<id>, e.g. user:<sub>, client:5, agent:<uuid>, group:<id>.`, 4 /* Validation */);
18391
+ }
18392
+ const type = to.slice(0, idx).trim().toLowerCase();
18393
+ const id = to.slice(idx + 1).trim();
18394
+ switch (type) {
18395
+ case "user":
18396
+ return { granteeType: "user", granteeId: id };
18397
+ case "group":
18398
+ return { granteeType: "group", granteeId: id };
18399
+ case "client":
18400
+ return { granteeType: "application", granteeId: `client_${id}` };
18401
+ case "agent":
18402
+ return { granteeType: "application", granteeId: `agent_${id}` };
18403
+ default:
18404
+ throw new CLIError(`Unknown principal type '${type}'. Use one of: ${PRINCIPAL_TYPES.join(", ")}.`, 4 /* Validation */);
18405
+ }
18406
+ }
18407
+ function formatPrincipal(granteeType, granteeId) {
18408
+ if (granteeType === "application") {
18409
+ const m = /^(client|agent)_(.+)$/.exec(granteeId);
18410
+ if (m)
18411
+ return `${m[1]}:${m[2]}`;
18412
+ return `application:${granteeId}`;
18413
+ }
18414
+ return `${granteeType}:${granteeId}`;
18415
+ }
18416
+ async function listGrants(ctx, p) {
18417
+ const params = {
18418
+ entity_type: p.entityType,
18419
+ action: p.action
18420
+ };
18421
+ if (p.granteeType)
18422
+ params.grantee_type = p.granteeType;
18423
+ if (p.granteeId)
18424
+ params.grantee_id = p.granteeId;
18425
+ if (p.objectId)
18426
+ params.object_id = p.objectId;
18427
+ if (p.tenant)
18428
+ params.tenant = p.tenant;
18429
+ return ctx.api.get("/api/admin/authz/grants", { params });
18430
+ }
18431
+ async function grant(ctx, p) {
18432
+ await ctx.api.post("/api/admin/authz/grants", {
18433
+ grantee_type: p.granteeType,
18434
+ grantee_id: p.granteeId,
18435
+ entity_type: p.entityType,
18436
+ action: p.action,
18437
+ object_id: p.objectId ?? null,
18438
+ tenant: p.tenant
18439
+ });
18440
+ }
18441
+ async function revoke(ctx, p) {
18442
+ await ctx.api.del("/api/admin/authz/grants", {
18443
+ body: {
18444
+ grantee_type: p.granteeType,
18445
+ grantee_id: p.granteeId,
18446
+ entity_type: p.entityType,
18447
+ action: p.action,
18448
+ object_id: p.objectId ?? null,
18449
+ tenant: p.tenant
18450
+ }
18451
+ });
18452
+ }
18453
+ async function listGroups(ctx, tenant) {
18454
+ const params = {};
18455
+ if (tenant)
18456
+ params.tenant = tenant;
18457
+ return ctx.api.get("/api/admin/authz/groups", { params });
18458
+ }
18459
+ async function createGroup(ctx, name, tenant) {
18460
+ return ctx.api.post("/api/admin/authz/groups", { name, tenant });
18461
+ }
18462
+ async function deleteGroup(ctx, groupId, tenant) {
18463
+ const params = {};
18464
+ if (tenant)
18465
+ params.tenant = tenant;
18466
+ await ctx.api.del(`/api/admin/authz/groups/${encodeURIComponent(groupId)}`, {
18467
+ params
18468
+ });
18469
+ }
18470
+ async function addMember(ctx, groupId, userSub, tenant) {
18471
+ await ctx.api.post(`/api/admin/authz/groups/${encodeURIComponent(groupId)}/members`, { user_sub: userSub, tenant });
18472
+ }
18473
+ async function removeMember(ctx, groupId, userSub, tenant) {
18474
+ const params = {};
18475
+ if (tenant)
18476
+ params.tenant = tenant;
18477
+ await ctx.api.del(`/api/admin/authz/groups/${encodeURIComponent(groupId)}/members/${encodeURIComponent(userSub)}`, { params });
18478
+ }
18479
+ async function listMembers(ctx, groupId, tenant) {
18480
+ const params = {};
18481
+ if (tenant)
18482
+ params.tenant = tenant;
18483
+ return ctx.api.get(`/api/admin/authz/groups/${encodeURIComponent(groupId)}/members`, { params });
18484
+ }
18485
+ function extractTyped(err) {
18486
+ if (!(err instanceof MarthaAPIError))
18487
+ return null;
18488
+ const body = err.body;
18489
+ const fromObj = (o) => {
18490
+ if (o && typeof o === "object") {
18491
+ const r = o;
18492
+ if (typeof r.code === "string") {
18493
+ return {
18494
+ code: r.code,
18495
+ message: typeof r.message === "string" ? r.message : err.message,
18496
+ hint: typeof r.hint === "string" ? r.hint : "",
18497
+ status: err.status
18498
+ };
18499
+ }
18500
+ }
18501
+ return null;
18502
+ };
18503
+ return fromObj(body?.detail) ?? fromObj(body);
18504
+ }
18505
+ function emitAuthzError(err, json) {
18506
+ const typed = extractTyped(err);
18507
+ if (typed) {
18508
+ const transient = typed.code === "ums_unavailable" || typed.status >= 500;
18509
+ const exit = transient ? EX_UNAVAILABLE : err.exitCode ?? 1 /* Error */;
18510
+ if (json) {
18511
+ process.stdout.write(JSON.stringify({
18512
+ code: typed.code,
18513
+ message: typed.message,
18514
+ hint: typed.hint
18515
+ }) + `
18516
+ `);
18517
+ } else {
18518
+ process.stderr.write(`Error: ${typed.message}
18519
+ `);
18520
+ if (typed.hint)
18521
+ process.stderr.write(`
18522
+ ${typed.hint}
18523
+ `);
18524
+ if (transient)
18525
+ process.stderr.write(`
18526
+ (transient — safe to retry)
18527
+ `);
18528
+ }
18529
+ process.exitCode = exit;
18530
+ return;
18531
+ }
18532
+ if (err instanceof CLIError) {
18533
+ if (json) {
18534
+ process.stdout.write(JSON.stringify({
18535
+ code: "cli_error",
18536
+ message: err.message,
18537
+ hint: err.suggestion ?? ""
18538
+ }) + `
18539
+ `);
18540
+ process.exitCode = err.exitCode;
18541
+ return;
18542
+ }
18543
+ throw err;
18544
+ }
18545
+ const message = err instanceof Error ? err.message : String(err);
18546
+ if (json) {
18547
+ process.stdout.write(JSON.stringify({ code: "error", message, hint: "" }) + `
18548
+ `);
18549
+ process.exitCode = 1 /* Error */;
18550
+ return;
18551
+ }
18552
+ throw err;
18553
+ }
18554
+
18555
+ // src/commands/authz.ts
18556
+ var DEFAULT_ENTITY = "document_collection";
18557
+ var DEFAULT_ACTION = "read";
18558
+ function registerAuthzCommands(program2) {
18559
+ function getCtx() {
18560
+ const ctx = createContext({
18561
+ profileOverride: program2.opts().profile,
18562
+ verbose: program2.opts().verbose
18563
+ });
18564
+ if (program2.opts().apiUrl)
18565
+ ctx.profile.api_url = program2.opts().apiUrl;
18566
+ return ctx;
18567
+ }
18568
+ const isJson = () => !!program2.opts().json;
18569
+ registerCollections(program2, getCtx, isJson);
18570
+ registerAccess(program2, getCtx, isJson);
18571
+ registerGroups(program2, getCtx, isJson);
18572
+ }
18573
+ function renderGrantsTable(resp) {
18574
+ if (resp.grants.length === 0) {
18575
+ return source_default.dim(`No ${resp.action} grants on ${resp.entity_type} in ${resp.tenant_id}.`);
18576
+ }
18577
+ const lines = resp.grants.map((g) => {
18578
+ const who = formatPrincipal(g.grantee_type, g.grantee_id);
18579
+ const what = g.scope === "root" ? source_default.yellow("(all — see-all root grant)") : g.object_id;
18580
+ return ` ${who.padEnd(28)} ${what}`;
18581
+ });
18582
+ return source_default.bold(`${resp.action} grants on ${resp.entity_type} (${resp.tenant_id}):`) + `
18583
+ ` + lines.join(`
18584
+ `);
18585
+ }
18586
+ function registerCollections(program2, getCtx, isJson) {
18587
+ const cmd = program2.command("collections").description("Manage who can read a document collection (UMS authz)");
18588
+ cmd.command("grant <collection>").description("Grant read access on a collection to a principal").requiredOption("--to <type:id>", "user|client|agent|group:<id>").option("--action <action>", "Action to grant", DEFAULT_ACTION).option("--tenant <tenant>", "Target tenant (super-admin only)").action(async (collection, opts) => {
18589
+ const ctx = getCtx();
18590
+ const json = isJson();
18591
+ try {
18592
+ const principal = parsePrincipal(opts.to);
18593
+ const objectId = await resolveCollectionIdForGrant(ctx, collection);
18594
+ await grant(ctx, {
18595
+ ...principal,
18596
+ entityType: DEFAULT_ENTITY,
18597
+ action: opts.action,
18598
+ objectId,
18599
+ tenant: opts.tenant
18600
+ });
18601
+ emitOk(json, {
18602
+ status: "granted",
18603
+ entity_type: DEFAULT_ENTITY,
18604
+ action: opts.action,
18605
+ object_id: objectId,
18606
+ grantee: opts.to,
18607
+ text: `Granted ${opts.action} on collection ${collection} to ${opts.to}.`
18608
+ });
18609
+ } catch (err) {
18610
+ emitAuthzError(err, json);
18611
+ }
18612
+ });
18613
+ cmd.command("revoke <collection>").description("Revoke a principal's read access on a collection").requiredOption("--to <type:id>", "user|client|agent|group:<id>").option("--action <action>", "Action to revoke", DEFAULT_ACTION).option("--tenant <tenant>", "Target tenant (super-admin only)").action(async (collection, opts) => {
18614
+ const ctx = getCtx();
18615
+ const json = isJson();
18616
+ try {
18617
+ const principal = parsePrincipal(opts.to);
18618
+ const objectId = await resolveCollectionIdForGrant(ctx, collection);
18619
+ await revoke(ctx, {
18620
+ ...principal,
18621
+ entityType: DEFAULT_ENTITY,
18622
+ action: opts.action,
18623
+ objectId,
18624
+ tenant: opts.tenant
18625
+ });
18626
+ emitOk(json, {
18627
+ status: "revoked",
18628
+ object_id: objectId,
18629
+ grantee: opts.to,
18630
+ text: `Revoked ${opts.action} on collection ${collection} from ${opts.to}.`
18631
+ });
18632
+ } catch (err) {
18633
+ emitAuthzError(err, json);
18634
+ }
18635
+ });
18636
+ cmd.command("access <collection>").description("Show who can read a collection (by-object)").option("--action <action>", "Action", DEFAULT_ACTION).option("--tenant <tenant>", "Target tenant (super-admin only)").action(async (collection, opts) => {
18637
+ const ctx = getCtx();
18638
+ const json = isJson();
18639
+ try {
18640
+ const objectId = await resolveCollectionIdForGrant(ctx, collection);
18641
+ const resp = await listGrants(ctx, {
18642
+ entityType: DEFAULT_ENTITY,
18643
+ action: opts.action,
18644
+ objectId,
18645
+ tenant: opts.tenant
18646
+ });
18647
+ if (json) {
18648
+ console.log(JSON.stringify(resp, null, 2));
18649
+ } else {
18650
+ console.log(renderGrantsTable(resp));
18651
+ }
18652
+ } catch (err) {
18653
+ emitAuthzError(err, json);
18654
+ }
18655
+ });
18656
+ }
18657
+ function registerAccess(program2, getCtx, isJson) {
18658
+ const cmd = program2.command("access").description("Generic two-axis grant management (any entity type)");
18659
+ cmd.command("grant").description("Grant access to a principal on an entity/object").requiredOption("--to <type:id>", "user|client|agent|group:<id>").option("--entity <entity_type>", "Entity type", DEFAULT_ENTITY).option("--object <id>", "Object id (omit for a tenant-root see-all grant)").option("--action <action>", "Action", DEFAULT_ACTION).option("--tenant <tenant>", "Target tenant (super-admin only)").action(async (opts) => {
18660
+ const ctx = getCtx();
18661
+ const json = isJson();
18662
+ try {
18663
+ const principal = parsePrincipal(opts.to);
18664
+ await grant(ctx, {
18665
+ ...principal,
18666
+ entityType: opts.entity,
18667
+ action: opts.action,
18668
+ objectId: opts.object ?? null,
18669
+ tenant: opts.tenant
18670
+ });
18671
+ emitOk(json, {
18672
+ status: "granted",
18673
+ entity_type: opts.entity,
18674
+ action: opts.action,
18675
+ object_id: opts.object ?? null,
18676
+ grantee: opts.to,
18677
+ text: `Granted ${opts.action} on ${opts.entity}${opts.object ? ` ${opts.object}` : " (all)"} to ${opts.to}.`
18678
+ });
18679
+ } catch (err) {
18680
+ emitAuthzError(err, json);
18681
+ }
18682
+ });
18683
+ cmd.command("revoke").description("Revoke a principal's access on an entity/object").requiredOption("--to <type:id>", "user|client|agent|group:<id>").option("--entity <entity_type>", "Entity type", DEFAULT_ENTITY).option("--object <id>", "Object id (omit for the tenant-root grant)").option("--action <action>", "Action", DEFAULT_ACTION).option("--tenant <tenant>", "Target tenant (super-admin only)").action(async (opts) => {
18684
+ const ctx = getCtx();
18685
+ const json = isJson();
18686
+ try {
18687
+ const principal = parsePrincipal(opts.to);
18688
+ await revoke(ctx, {
18689
+ ...principal,
18690
+ entityType: opts.entity,
18691
+ action: opts.action,
18692
+ objectId: opts.object ?? null,
18693
+ tenant: opts.tenant
18694
+ });
18695
+ emitOk(json, {
18696
+ status: "revoked",
18697
+ entity_type: opts.entity,
18698
+ object_id: opts.object ?? null,
18699
+ grantee: opts.to,
18700
+ text: `Revoked ${opts.action} on ${opts.entity}${opts.object ? ` ${opts.object}` : " (all)"} from ${opts.to}.`
18701
+ });
18702
+ } catch (err) {
18703
+ emitAuthzError(err, json);
18704
+ }
18705
+ });
18706
+ cmd.command("list").description("List grants by-principal (--to) or by-object (--object)").option("--to <type:id>", "by-principal: this grantee's grants").option("--object <id>", "by-object: grantees of this object").option("--entity <entity_type>", "Entity type", DEFAULT_ENTITY).option("--action <action>", "Action", DEFAULT_ACTION).option("--tenant <tenant>", "Target tenant (super-admin only)").action(async (opts) => {
18707
+ const ctx = getCtx();
18708
+ const json = isJson();
18709
+ try {
18710
+ if (!opts.to && !opts.object) {
18711
+ throw new CLIError("Provide either --to <type:id> (by-principal) or --object <id> (by-object).", 4 /* Validation */);
18712
+ }
18713
+ const principal = opts.to ? parsePrincipal(opts.to) : undefined;
18714
+ const resp = await listGrants(ctx, {
18715
+ entityType: opts.entity,
18716
+ action: opts.action,
18717
+ granteeType: principal?.granteeType,
18718
+ granteeId: principal?.granteeId,
18719
+ objectId: opts.object,
18720
+ tenant: opts.tenant
18721
+ });
18722
+ if (json) {
18723
+ console.log(JSON.stringify(resp, null, 2));
18724
+ } else {
18725
+ console.log(renderGrantsTable(resp));
18726
+ }
18727
+ } catch (err) {
18728
+ emitAuthzError(err, json);
18729
+ }
18730
+ });
18731
+ }
18732
+ function registerGroups(program2, getCtx, isJson) {
18733
+ const cmd = program2.command("groups").description("Manage UMS-native authz groups (#644)");
18734
+ cmd.command("create <name>").description("Create a group").option("--tenant <tenant>", "Target tenant (super-admin only)").action(async (name, opts) => {
18735
+ const ctx = getCtx();
18736
+ const json = isJson();
18737
+ try {
18738
+ const g = await createGroup(ctx, name, opts.tenant);
18739
+ if (json) {
18740
+ console.log(JSON.stringify(g, null, 2));
18741
+ } else {
18742
+ console.log(`Created group ${source_default.bold(g.id)} (${g.name ?? name}).`);
18743
+ console.log(source_default.dim(`Grant it a collection: martha collections grant <col> --to group:${g.id}`));
18744
+ }
18745
+ } catch (err) {
18746
+ emitAuthzError(err, json);
18747
+ }
18748
+ });
18749
+ cmd.command("list").description("List groups in the tenant").option("--tenant <tenant>", "Target tenant (super-admin only)").action(async (opts) => {
18750
+ const ctx = getCtx();
18751
+ const json = isJson();
18752
+ try {
18753
+ const groups = await listGroups(ctx, opts.tenant);
18754
+ if (json) {
18755
+ console.log(JSON.stringify(groups, null, 2));
18756
+ } else if (groups.length === 0) {
18757
+ console.log(source_default.dim("No groups."));
18758
+ } else {
18759
+ for (const g of groups) {
18760
+ console.log(`${source_default.bold(g.id)} ${g.name ?? ""}`);
18761
+ }
18762
+ }
18763
+ } catch (err) {
18764
+ emitAuthzError(err, json);
18765
+ }
18766
+ });
18767
+ cmd.command("delete <groupId>").description("Delete a group").option("--tenant <tenant>", "Target tenant (super-admin only)").action(async (groupId, opts) => {
18768
+ const ctx = getCtx();
18769
+ const json = isJson();
18770
+ try {
18771
+ await deleteGroup(ctx, groupId, opts.tenant);
18772
+ emitOk(json, {
18773
+ status: "deleted",
18774
+ group_id: groupId,
18775
+ text: `Deleted group ${groupId}.`
18776
+ });
18777
+ } catch (err) {
18778
+ emitAuthzError(err, json);
18779
+ }
18780
+ });
18781
+ cmd.command("add-member <groupId> <userSub>").description("Add a user to a group").option("--tenant <tenant>", "Target tenant (super-admin only)").action(async (groupId, userSub, opts) => {
18782
+ const ctx = getCtx();
18783
+ const json = isJson();
18784
+ try {
18785
+ await addMember(ctx, groupId, userSub, opts.tenant);
18786
+ emitOk(json, {
18787
+ status: "member_added",
18788
+ group_id: groupId,
18789
+ user_sub: userSub,
18790
+ text: `Added ${userSub} to group ${groupId}.`
18791
+ });
18792
+ } catch (err) {
18793
+ emitAuthzError(err, json);
18794
+ }
18795
+ });
18796
+ cmd.command("remove-member <groupId> <userSub>").description("Remove a user from a group").option("--tenant <tenant>", "Target tenant (super-admin only)").action(async (groupId, userSub, opts) => {
18797
+ const ctx = getCtx();
18798
+ const json = isJson();
18799
+ try {
18800
+ await removeMember(ctx, groupId, userSub, opts.tenant);
18801
+ emitOk(json, {
18802
+ status: "member_removed",
18803
+ group_id: groupId,
18804
+ user_sub: userSub,
18805
+ text: `Removed ${userSub} from group ${groupId}.`
18806
+ });
18807
+ } catch (err) {
18808
+ emitAuthzError(err, json);
18809
+ }
18810
+ });
18811
+ cmd.command("members <groupId>").description("List a group's members").option("--tenant <tenant>", "Target tenant (super-admin only)").action(async (groupId, opts) => {
18812
+ const ctx = getCtx();
18813
+ const json = isJson();
18814
+ try {
18815
+ const members = await listMembers(ctx, groupId, opts.tenant);
18816
+ if (json) {
18817
+ console.log(JSON.stringify(members, null, 2));
18818
+ } else if (members.length === 0) {
18819
+ console.log(source_default.dim("No members."));
18820
+ } else {
18821
+ for (const m of members)
18822
+ console.log(m);
18823
+ }
18824
+ } catch (err) {
18825
+ emitAuthzError(err, json);
18826
+ }
18827
+ });
18828
+ }
18829
+ function emitOk(json, payload) {
18830
+ if (json) {
18831
+ const rest = { ...payload };
18832
+ delete rest.text;
18833
+ console.log(JSON.stringify(rest, null, 2));
18834
+ } else {
18835
+ console.log(payload.text);
18836
+ }
18837
+ }
18838
+
18401
18839
  // src/commands/init.ts
18402
18840
  import readline from "node:readline/promises";
18403
18841
  init_config();
@@ -19197,6 +19635,7 @@ registerModelsCommand(program2);
19197
19635
  registerSessionCommands(program2);
19198
19636
  registerMCPCommands(program2);
19199
19637
  registerPolicyCommands(program2);
19638
+ registerAuthzCommands(program2);
19200
19639
  registerInitCommand(program2);
19201
19640
  registerDoctorCommand(program2);
19202
19641
  registerSkillCommand(program2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiai-pt/martha-cli",
3
- "version": "0.16.1",
3
+ "version": "0.19.0",
4
4
  "description": "Terminal-first client for the Martha AI platform",
5
5
  "homepage": "https://docs.martha.nomadriver.co",
6
6
  "repository": {