@aiaiai-pt/martha-cli 0.16.0 → 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.
- package/CHANGELOG.md +5 -0
- package/dist/index.js +507 -25
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,11 @@ All notable changes to `@aiaiai-pt/martha-cli`. Format: [Keep a Changelog](https
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.16.1] — 2026-06-14
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- `martha update` now updates the **same global prefix the on-PATH `martha` came from**. It picked the package manager from the *runtime* (`process.versions.bun`), but a bun-installed CLI runs under node, so it chose `npm i -g` and updated a different prefix than the one on PATH — leaving `martha --version` on the old version (the 0.15→0.16 split-install report). Detection is now based on the install path of the running binary (bun's global lives under `~/.bun/…`), falling back to the runtime hint then npm. `--manager npm|bun` still overrides.
|
|
11
|
+
|
|
7
12
|
## [0.16.0] — 2026-06-13
|
|
8
13
|
|
|
9
14
|
### Added — #567 fluid in-conversation human approval
|
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:
|
|
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:
|
|
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.
|
|
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").
|
|
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(
|
|
15334
|
+
outcome = JSON.parse(rawOutcome);
|
|
15340
15335
|
} catch {
|
|
15341
|
-
throw new CLIError("
|
|
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();
|
|
@@ -19006,6 +19460,8 @@ function registerSkillCommand(program2) {
|
|
|
19006
19460
|
|
|
19007
19461
|
// src/commands/update.ts
|
|
19008
19462
|
import { spawnSync } from "node:child_process";
|
|
19463
|
+
import { realpathSync } from "node:fs";
|
|
19464
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
19009
19465
|
init_errors();
|
|
19010
19466
|
var PKG = "@aiaiai-pt/martha-cli";
|
|
19011
19467
|
var LATEST_URL = "https://registry.npmjs.org/@aiaiai-pt%2Fmartha-cli/latest";
|
|
@@ -19033,6 +19489,28 @@ async function fetchLatest() {
|
|
|
19033
19489
|
return null;
|
|
19034
19490
|
}
|
|
19035
19491
|
}
|
|
19492
|
+
function managerFromInstallPath(paths) {
|
|
19493
|
+
const joined = paths.filter(Boolean).join(`
|
|
19494
|
+
`);
|
|
19495
|
+
if (!joined)
|
|
19496
|
+
return null;
|
|
19497
|
+
if (/[\\/]\.bun[\\/]/.test(joined))
|
|
19498
|
+
return "bun";
|
|
19499
|
+
if (/[\\/]node_modules[\\/]/.test(joined))
|
|
19500
|
+
return "npm";
|
|
19501
|
+
return null;
|
|
19502
|
+
}
|
|
19503
|
+
function detectManagerFromInstallPath() {
|
|
19504
|
+
const candidates = [];
|
|
19505
|
+
try {
|
|
19506
|
+
if (process.argv[1])
|
|
19507
|
+
candidates.push(realpathSync(process.argv[1]));
|
|
19508
|
+
} catch {}
|
|
19509
|
+
try {
|
|
19510
|
+
candidates.push(realpathSync(fileURLToPath2(import.meta.url)));
|
|
19511
|
+
} catch {}
|
|
19512
|
+
return managerFromInstallPath(candidates);
|
|
19513
|
+
}
|
|
19036
19514
|
function resolveManager(explicit) {
|
|
19037
19515
|
if (explicit) {
|
|
19038
19516
|
if (explicit !== "npm" && explicit !== "bun") {
|
|
@@ -19040,6 +19518,9 @@ function resolveManager(explicit) {
|
|
|
19040
19518
|
}
|
|
19041
19519
|
return explicit;
|
|
19042
19520
|
}
|
|
19521
|
+
const fromInstall = detectManagerFromInstallPath();
|
|
19522
|
+
if (fromInstall)
|
|
19523
|
+
return fromInstall;
|
|
19043
19524
|
return process.versions?.bun ? "bun" : "npm";
|
|
19044
19525
|
}
|
|
19045
19526
|
function registerUpdateCommand(program2) {
|
|
@@ -19170,6 +19651,7 @@ registerModelsCommand(program2);
|
|
|
19170
19651
|
registerSessionCommands(program2);
|
|
19171
19652
|
registerMCPCommands(program2);
|
|
19172
19653
|
registerPolicyCommands(program2);
|
|
19654
|
+
registerAuthzCommands(program2);
|
|
19173
19655
|
registerInitCommand(program2);
|
|
19174
19656
|
registerDoctorCommand(program2);
|
|
19175
19657
|
registerSkillCommand(program2);
|