@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.
- package/dist/index.js +519 -80
- 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:
|
|
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.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>", "
|
|
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
|
-
|
|
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>", "
|
|
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
|
-
|
|
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
|
-
|
|
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").
|
|
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(
|
|
15309
|
+
outcome = JSON.parse(rawOutcome);
|
|
15340
15310
|
} catch {
|
|
15341
|
-
throw new CLIError("
|
|
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>", "
|
|
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
|
-
|
|
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>", "
|
|
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
|
-
|
|
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);
|