@aiaiai-pt/martha-cli 0.16.1 → 0.18.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 +480 -25
  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.18.0";
11081
11063
 
11082
11064
  // src/commands/sessions.ts
11083
11065
  function relativeTime(iso) {
@@ -14892,6 +14874,7 @@ function registerApprovalCommands(program2) {
14892
14874
  }
14893
14875
 
14894
14876
  // src/commands/tasks.ts
14877
+ import { readFileSync } from "node:fs";
14895
14878
  init_errors();
14896
14879
  var truncate2 = (s, n) => s.length > n ? s.slice(0, n - 1) + "…" : s;
14897
14880
  var sleep3 = (ms, signal) => new Promise((resolve) => {
@@ -15332,13 +15315,25 @@ ${items.length} task(s) available`));
15332
15315
  process.off("SIGTERM", onSignal);
15333
15316
  }
15334
15317
  });
15335
- cmd.command("complete <id>").description("Complete a claimed task with outcome").requiredOption("--outcome <json>", "Outcome data as JSON string").action(async (id, opts) => {
15318
+ 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
15319
  const ctx = getCtx();
15320
+ let rawOutcome;
15321
+ if (opts.outcomeFile) {
15322
+ try {
15323
+ rawOutcome = readFileSync(opts.outcomeFile, "utf-8");
15324
+ } catch {
15325
+ throw new CLIError(`--outcome-file could not be read: ${opts.outcomeFile}`, 4 /* Validation */);
15326
+ }
15327
+ } else if (opts.outcome) {
15328
+ rawOutcome = opts.outcome;
15329
+ } else {
15330
+ throw new CLIError("provide --outcome <json> or --outcome-file <path>", 4 /* Validation */);
15331
+ }
15337
15332
  let outcome;
15338
15333
  try {
15339
- outcome = JSON.parse(opts.outcome);
15334
+ outcome = JSON.parse(rawOutcome);
15340
15335
  } catch {
15341
- throw new CLIError("--outcome must be valid JSON", 4 /* Validation */);
15336
+ throw new CLIError("outcome must be valid JSON", 4 /* Validation */);
15342
15337
  }
15343
15338
  const result = await ctx.api.post(`/api/tasks/${encodeURIComponent(id)}/complete`, { outcome });
15344
15339
  if (isJson()) {
@@ -18398,6 +18393,465 @@ function registerPolicyCommands(program2) {
18398
18393
  });
18399
18394
  }
18400
18395
 
18396
+ // src/commands/authz.ts
18397
+ init_errors();
18398
+
18399
+ // src/lib/authz.ts
18400
+ init_errors();
18401
+ var EX_UNAVAILABLE = 69;
18402
+ var PRINCIPAL_TYPES = ["user", "client", "agent", "group"];
18403
+ function parsePrincipal(to) {
18404
+ const idx = to.indexOf(":");
18405
+ if (idx <= 0 || idx === to.length - 1) {
18406
+ throw new CLIError(`Invalid --to '${to}'. Use <type>:<id>, e.g. user:<sub>, client:5, agent:<uuid>, group:<id>.`, 4 /* Validation */);
18407
+ }
18408
+ const type = to.slice(0, idx).trim().toLowerCase();
18409
+ const id = to.slice(idx + 1).trim();
18410
+ switch (type) {
18411
+ case "user":
18412
+ return { granteeType: "user", granteeId: id };
18413
+ case "group":
18414
+ return { granteeType: "group", granteeId: id };
18415
+ case "client":
18416
+ return { granteeType: "application", granteeId: `client_${id}` };
18417
+ case "agent":
18418
+ return { granteeType: "application", granteeId: `agent_${id}` };
18419
+ default:
18420
+ throw new CLIError(`Unknown principal type '${type}'. Use one of: ${PRINCIPAL_TYPES.join(", ")}.`, 4 /* Validation */);
18421
+ }
18422
+ }
18423
+ function formatPrincipal(granteeType, granteeId) {
18424
+ if (granteeType === "application") {
18425
+ const m = /^(client|agent)_(.+)$/.exec(granteeId);
18426
+ if (m)
18427
+ return `${m[1]}:${m[2]}`;
18428
+ return `application:${granteeId}`;
18429
+ }
18430
+ return `${granteeType}:${granteeId}`;
18431
+ }
18432
+ async function listGrants(ctx, p) {
18433
+ const params = {
18434
+ entity_type: p.entityType,
18435
+ action: p.action
18436
+ };
18437
+ if (p.granteeType)
18438
+ params.grantee_type = p.granteeType;
18439
+ if (p.granteeId)
18440
+ params.grantee_id = p.granteeId;
18441
+ if (p.objectId)
18442
+ params.object_id = p.objectId;
18443
+ if (p.tenant)
18444
+ params.tenant = p.tenant;
18445
+ return ctx.api.get("/api/admin/authz/grants", { params });
18446
+ }
18447
+ async function grant(ctx, p) {
18448
+ await ctx.api.post("/api/admin/authz/grants", {
18449
+ grantee_type: p.granteeType,
18450
+ grantee_id: p.granteeId,
18451
+ entity_type: p.entityType,
18452
+ action: p.action,
18453
+ object_id: p.objectId ?? null,
18454
+ tenant: p.tenant
18455
+ });
18456
+ }
18457
+ async function revoke(ctx, p) {
18458
+ await ctx.api.del("/api/admin/authz/grants", {
18459
+ body: {
18460
+ grantee_type: p.granteeType,
18461
+ grantee_id: p.granteeId,
18462
+ entity_type: p.entityType,
18463
+ action: p.action,
18464
+ object_id: p.objectId ?? null,
18465
+ tenant: p.tenant
18466
+ }
18467
+ });
18468
+ }
18469
+ async function listGroups(ctx, tenant) {
18470
+ const params = {};
18471
+ if (tenant)
18472
+ params.tenant = tenant;
18473
+ return ctx.api.get("/api/admin/authz/groups", { params });
18474
+ }
18475
+ async function createGroup(ctx, name, tenant) {
18476
+ return ctx.api.post("/api/admin/authz/groups", { name, tenant });
18477
+ }
18478
+ async function deleteGroup(ctx, groupId, tenant) {
18479
+ const params = {};
18480
+ if (tenant)
18481
+ params.tenant = tenant;
18482
+ await ctx.api.del(`/api/admin/authz/groups/${encodeURIComponent(groupId)}`, {
18483
+ params
18484
+ });
18485
+ }
18486
+ async function addMember(ctx, groupId, userSub, tenant) {
18487
+ await ctx.api.post(`/api/admin/authz/groups/${encodeURIComponent(groupId)}/members`, { user_sub: userSub, tenant });
18488
+ }
18489
+ async function removeMember(ctx, groupId, userSub, tenant) {
18490
+ const params = {};
18491
+ if (tenant)
18492
+ params.tenant = tenant;
18493
+ await ctx.api.del(`/api/admin/authz/groups/${encodeURIComponent(groupId)}/members/${encodeURIComponent(userSub)}`, { params });
18494
+ }
18495
+ async function listMembers(ctx, groupId, tenant) {
18496
+ const params = {};
18497
+ if (tenant)
18498
+ params.tenant = tenant;
18499
+ return ctx.api.get(`/api/admin/authz/groups/${encodeURIComponent(groupId)}/members`, { params });
18500
+ }
18501
+ function extractTyped(err) {
18502
+ if (!(err instanceof MarthaAPIError))
18503
+ return null;
18504
+ const body = err.body;
18505
+ const fromObj = (o) => {
18506
+ if (o && typeof o === "object") {
18507
+ const r = o;
18508
+ if (typeof r.code === "string") {
18509
+ return {
18510
+ code: r.code,
18511
+ message: typeof r.message === "string" ? r.message : err.message,
18512
+ hint: typeof r.hint === "string" ? r.hint : "",
18513
+ status: err.status
18514
+ };
18515
+ }
18516
+ }
18517
+ return null;
18518
+ };
18519
+ return fromObj(body?.detail) ?? fromObj(body);
18520
+ }
18521
+ function emitAuthzError(err, json) {
18522
+ const typed = extractTyped(err);
18523
+ if (typed) {
18524
+ const transient = typed.code === "ums_unavailable" || typed.status >= 500;
18525
+ const exit = transient ? EX_UNAVAILABLE : err.exitCode ?? 1 /* Error */;
18526
+ if (json) {
18527
+ process.stdout.write(JSON.stringify({
18528
+ code: typed.code,
18529
+ message: typed.message,
18530
+ hint: typed.hint
18531
+ }) + `
18532
+ `);
18533
+ } else {
18534
+ process.stderr.write(`Error: ${typed.message}
18535
+ `);
18536
+ if (typed.hint)
18537
+ process.stderr.write(`
18538
+ ${typed.hint}
18539
+ `);
18540
+ if (transient)
18541
+ process.stderr.write(`
18542
+ (transient — safe to retry)
18543
+ `);
18544
+ }
18545
+ process.exitCode = exit;
18546
+ return;
18547
+ }
18548
+ if (err instanceof CLIError) {
18549
+ if (json) {
18550
+ process.stdout.write(JSON.stringify({
18551
+ code: "cli_error",
18552
+ message: err.message,
18553
+ hint: err.suggestion ?? ""
18554
+ }) + `
18555
+ `);
18556
+ process.exitCode = err.exitCode;
18557
+ return;
18558
+ }
18559
+ throw err;
18560
+ }
18561
+ const message = err instanceof Error ? err.message : String(err);
18562
+ if (json) {
18563
+ process.stdout.write(JSON.stringify({ code: "error", message, hint: "" }) + `
18564
+ `);
18565
+ process.exitCode = 1 /* Error */;
18566
+ return;
18567
+ }
18568
+ throw err;
18569
+ }
18570
+
18571
+ // src/commands/authz.ts
18572
+ var DEFAULT_ENTITY = "document_collection";
18573
+ var DEFAULT_ACTION = "read";
18574
+ function registerAuthzCommands(program2) {
18575
+ function getCtx() {
18576
+ const ctx = createContext({
18577
+ profileOverride: program2.opts().profile,
18578
+ verbose: program2.opts().verbose
18579
+ });
18580
+ if (program2.opts().apiUrl)
18581
+ ctx.profile.api_url = program2.opts().apiUrl;
18582
+ return ctx;
18583
+ }
18584
+ const isJson = () => !!program2.opts().json;
18585
+ registerCollections(program2, getCtx, isJson);
18586
+ registerAccess(program2, getCtx, isJson);
18587
+ registerGroups(program2, getCtx, isJson);
18588
+ }
18589
+ function renderGrantsTable(resp) {
18590
+ if (resp.grants.length === 0) {
18591
+ return source_default.dim(`No ${resp.action} grants on ${resp.entity_type} in ${resp.tenant_id}.`);
18592
+ }
18593
+ const lines = resp.grants.map((g) => {
18594
+ const who = formatPrincipal(g.grantee_type, g.grantee_id);
18595
+ const what = g.scope === "root" ? source_default.yellow("(all — see-all root grant)") : g.object_id;
18596
+ return ` ${who.padEnd(28)} ${what}`;
18597
+ });
18598
+ return source_default.bold(`${resp.action} grants on ${resp.entity_type} (${resp.tenant_id}):`) + `
18599
+ ` + lines.join(`
18600
+ `);
18601
+ }
18602
+ function registerCollections(program2, getCtx, isJson) {
18603
+ const cmd = program2.command("collections").description("Manage who can read a document collection (UMS authz)");
18604
+ 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) => {
18605
+ const ctx = getCtx();
18606
+ const json = isJson();
18607
+ try {
18608
+ const principal = parsePrincipal(opts.to);
18609
+ const objectId = await resolveCollectionIdForGrant(ctx, collection);
18610
+ await grant(ctx, {
18611
+ ...principal,
18612
+ entityType: DEFAULT_ENTITY,
18613
+ action: opts.action,
18614
+ objectId,
18615
+ tenant: opts.tenant
18616
+ });
18617
+ emitOk(json, {
18618
+ status: "granted",
18619
+ entity_type: DEFAULT_ENTITY,
18620
+ action: opts.action,
18621
+ object_id: objectId,
18622
+ grantee: opts.to,
18623
+ text: `Granted ${opts.action} on collection ${collection} to ${opts.to}.`
18624
+ });
18625
+ } catch (err) {
18626
+ emitAuthzError(err, json);
18627
+ }
18628
+ });
18629
+ 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) => {
18630
+ const ctx = getCtx();
18631
+ const json = isJson();
18632
+ try {
18633
+ const principal = parsePrincipal(opts.to);
18634
+ const objectId = await resolveCollectionIdForGrant(ctx, collection);
18635
+ await revoke(ctx, {
18636
+ ...principal,
18637
+ entityType: DEFAULT_ENTITY,
18638
+ action: opts.action,
18639
+ objectId,
18640
+ tenant: opts.tenant
18641
+ });
18642
+ emitOk(json, {
18643
+ status: "revoked",
18644
+ object_id: objectId,
18645
+ grantee: opts.to,
18646
+ text: `Revoked ${opts.action} on collection ${collection} from ${opts.to}.`
18647
+ });
18648
+ } catch (err) {
18649
+ emitAuthzError(err, json);
18650
+ }
18651
+ });
18652
+ 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) => {
18653
+ const ctx = getCtx();
18654
+ const json = isJson();
18655
+ try {
18656
+ const objectId = await resolveCollectionIdForGrant(ctx, collection);
18657
+ const resp = await listGrants(ctx, {
18658
+ entityType: DEFAULT_ENTITY,
18659
+ action: opts.action,
18660
+ objectId,
18661
+ tenant: opts.tenant
18662
+ });
18663
+ if (json) {
18664
+ console.log(JSON.stringify(resp, null, 2));
18665
+ } else {
18666
+ console.log(renderGrantsTable(resp));
18667
+ }
18668
+ } catch (err) {
18669
+ emitAuthzError(err, json);
18670
+ }
18671
+ });
18672
+ }
18673
+ function registerAccess(program2, getCtx, isJson) {
18674
+ const cmd = program2.command("access").description("Generic two-axis grant management (any entity type)");
18675
+ 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) => {
18676
+ const ctx = getCtx();
18677
+ const json = isJson();
18678
+ try {
18679
+ const principal = parsePrincipal(opts.to);
18680
+ await grant(ctx, {
18681
+ ...principal,
18682
+ entityType: opts.entity,
18683
+ action: opts.action,
18684
+ objectId: opts.object ?? null,
18685
+ tenant: opts.tenant
18686
+ });
18687
+ emitOk(json, {
18688
+ status: "granted",
18689
+ entity_type: opts.entity,
18690
+ action: opts.action,
18691
+ object_id: opts.object ?? null,
18692
+ grantee: opts.to,
18693
+ text: `Granted ${opts.action} on ${opts.entity}${opts.object ? ` ${opts.object}` : " (all)"} to ${opts.to}.`
18694
+ });
18695
+ } catch (err) {
18696
+ emitAuthzError(err, json);
18697
+ }
18698
+ });
18699
+ 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) => {
18700
+ const ctx = getCtx();
18701
+ const json = isJson();
18702
+ try {
18703
+ const principal = parsePrincipal(opts.to);
18704
+ await revoke(ctx, {
18705
+ ...principal,
18706
+ entityType: opts.entity,
18707
+ action: opts.action,
18708
+ objectId: opts.object ?? null,
18709
+ tenant: opts.tenant
18710
+ });
18711
+ emitOk(json, {
18712
+ status: "revoked",
18713
+ entity_type: opts.entity,
18714
+ object_id: opts.object ?? null,
18715
+ grantee: opts.to,
18716
+ text: `Revoked ${opts.action} on ${opts.entity}${opts.object ? ` ${opts.object}` : " (all)"} from ${opts.to}.`
18717
+ });
18718
+ } catch (err) {
18719
+ emitAuthzError(err, json);
18720
+ }
18721
+ });
18722
+ 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) => {
18723
+ const ctx = getCtx();
18724
+ const json = isJson();
18725
+ try {
18726
+ if (!opts.to && !opts.object) {
18727
+ throw new CLIError("Provide either --to <type:id> (by-principal) or --object <id> (by-object).", 4 /* Validation */);
18728
+ }
18729
+ const principal = opts.to ? parsePrincipal(opts.to) : undefined;
18730
+ const resp = await listGrants(ctx, {
18731
+ entityType: opts.entity,
18732
+ action: opts.action,
18733
+ granteeType: principal?.granteeType,
18734
+ granteeId: principal?.granteeId,
18735
+ objectId: opts.object,
18736
+ tenant: opts.tenant
18737
+ });
18738
+ if (json) {
18739
+ console.log(JSON.stringify(resp, null, 2));
18740
+ } else {
18741
+ console.log(renderGrantsTable(resp));
18742
+ }
18743
+ } catch (err) {
18744
+ emitAuthzError(err, json);
18745
+ }
18746
+ });
18747
+ }
18748
+ function registerGroups(program2, getCtx, isJson) {
18749
+ const cmd = program2.command("groups").description("Manage UMS-native authz groups (#644)");
18750
+ cmd.command("create <name>").description("Create a group").option("--tenant <tenant>", "Target tenant (super-admin only)").action(async (name, opts) => {
18751
+ const ctx = getCtx();
18752
+ const json = isJson();
18753
+ try {
18754
+ const g = await createGroup(ctx, name, opts.tenant);
18755
+ if (json) {
18756
+ console.log(JSON.stringify(g, null, 2));
18757
+ } else {
18758
+ console.log(`Created group ${source_default.bold(g.id)} (${g.name ?? name}).`);
18759
+ console.log(source_default.dim(`Grant it a collection: martha collections grant <col> --to group:${g.id}`));
18760
+ }
18761
+ } catch (err) {
18762
+ emitAuthzError(err, json);
18763
+ }
18764
+ });
18765
+ cmd.command("list").description("List groups in the tenant").option("--tenant <tenant>", "Target tenant (super-admin only)").action(async (opts) => {
18766
+ const ctx = getCtx();
18767
+ const json = isJson();
18768
+ try {
18769
+ const groups = await listGroups(ctx, opts.tenant);
18770
+ if (json) {
18771
+ console.log(JSON.stringify(groups, null, 2));
18772
+ } else if (groups.length === 0) {
18773
+ console.log(source_default.dim("No groups."));
18774
+ } else {
18775
+ for (const g of groups) {
18776
+ console.log(`${source_default.bold(g.id)} ${g.name ?? ""}`);
18777
+ }
18778
+ }
18779
+ } catch (err) {
18780
+ emitAuthzError(err, json);
18781
+ }
18782
+ });
18783
+ cmd.command("delete <groupId>").description("Delete a group").option("--tenant <tenant>", "Target tenant (super-admin only)").action(async (groupId, opts) => {
18784
+ const ctx = getCtx();
18785
+ const json = isJson();
18786
+ try {
18787
+ await deleteGroup(ctx, groupId, opts.tenant);
18788
+ emitOk(json, {
18789
+ status: "deleted",
18790
+ group_id: groupId,
18791
+ text: `Deleted group ${groupId}.`
18792
+ });
18793
+ } catch (err) {
18794
+ emitAuthzError(err, json);
18795
+ }
18796
+ });
18797
+ 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) => {
18798
+ const ctx = getCtx();
18799
+ const json = isJson();
18800
+ try {
18801
+ await addMember(ctx, groupId, userSub, opts.tenant);
18802
+ emitOk(json, {
18803
+ status: "member_added",
18804
+ group_id: groupId,
18805
+ user_sub: userSub,
18806
+ text: `Added ${userSub} to group ${groupId}.`
18807
+ });
18808
+ } catch (err) {
18809
+ emitAuthzError(err, json);
18810
+ }
18811
+ });
18812
+ 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) => {
18813
+ const ctx = getCtx();
18814
+ const json = isJson();
18815
+ try {
18816
+ await removeMember(ctx, groupId, userSub, opts.tenant);
18817
+ emitOk(json, {
18818
+ status: "member_removed",
18819
+ group_id: groupId,
18820
+ user_sub: userSub,
18821
+ text: `Removed ${userSub} from group ${groupId}.`
18822
+ });
18823
+ } catch (err) {
18824
+ emitAuthzError(err, json);
18825
+ }
18826
+ });
18827
+ cmd.command("members <groupId>").description("List a group's members").option("--tenant <tenant>", "Target tenant (super-admin only)").action(async (groupId, opts) => {
18828
+ const ctx = getCtx();
18829
+ const json = isJson();
18830
+ try {
18831
+ const members = await listMembers(ctx, groupId, opts.tenant);
18832
+ if (json) {
18833
+ console.log(JSON.stringify(members, null, 2));
18834
+ } else if (members.length === 0) {
18835
+ console.log(source_default.dim("No members."));
18836
+ } else {
18837
+ for (const m of members)
18838
+ console.log(m);
18839
+ }
18840
+ } catch (err) {
18841
+ emitAuthzError(err, json);
18842
+ }
18843
+ });
18844
+ }
18845
+ function emitOk(json, payload) {
18846
+ if (json) {
18847
+ const rest = { ...payload };
18848
+ delete rest.text;
18849
+ console.log(JSON.stringify(rest, null, 2));
18850
+ } else {
18851
+ console.log(payload.text);
18852
+ }
18853
+ }
18854
+
18401
18855
  // src/commands/init.ts
18402
18856
  import readline from "node:readline/promises";
18403
18857
  init_config();
@@ -19197,6 +19651,7 @@ registerModelsCommand(program2);
19197
19651
  registerSessionCommands(program2);
19198
19652
  registerMCPCommands(program2);
19199
19653
  registerPolicyCommands(program2);
19654
+ registerAuthzCommands(program2);
19200
19655
  registerInitCommand(program2);
19201
19656
  registerDoctorCommand(program2);
19202
19657
  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.18.0",
4
4
  "description": "Terminal-first client for the Martha AI platform",
5
5
  "homepage": "https://docs.martha.nomadriver.co",
6
6
  "repository": {