@aiaiai-pt/martha-cli 0.58.1 → 0.61.1
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 +66 -0
- package/dist/index.js +336 -71
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,72 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@aiaiai-pt/martha-cli`. Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This project adheres to semver — `0.x` releases may include breaking changes between minor versions.
|
|
4
4
|
|
|
5
|
+
## [0.61.1] — 2026-09-25
|
|
6
|
+
|
|
7
|
+
### Fixed — carries 0.60.2's redirect fix onto the 0.61 line
|
|
8
|
+
|
|
9
|
+
`main` shipped the same-host redirect fix as 0.60.2 while `uio` released 0.61.0
|
|
10
|
+
the same day; the main → uio sync brings that fix into the 0.61 line. No other
|
|
11
|
+
change: `martha triggers list/create` and `definitions apply` of a `kind: Trigger`
|
|
12
|
+
no longer read as "Not authenticated" behind the TLS proxy (see 0.60.2 below).
|
|
13
|
+
|
|
14
|
+
## [0.61.0] — 2026-09-24
|
|
15
|
+
|
|
16
|
+
### Added — `martha spend` and `martha budget` (#1107)
|
|
17
|
+
|
|
18
|
+
- `martha spend [--tenant <id>] [--group-by day|agent|model|surface|billing_owner] [--since] [--until]`
|
|
19
|
+
reads a tenant's LLM spend from Martha's own ledger (`GET /api/admin/tenants/{t}/spend`).
|
|
20
|
+
- `martha budget list|set|rm` manages per-tenant and per-AI-Agent LLM spend ceilings
|
|
21
|
+
(`warn`, `require_approval` — the run suspends until an ApprovalCase is approved — or `block`).
|
|
22
|
+
|
|
23
|
+
Both default to your own tenant; another tenant needs super-admin (the API answers 403).
|
|
24
|
+
|
|
25
|
+
### Fixed — commands that default to "your tenant" on realms that use Keycloak Organizations
|
|
26
|
+
|
|
27
|
+
On realms that model tenants as Keycloak Organizations (ubiwhere.io) the access token carries
|
|
28
|
+
an `organizations` claim and no `tenant_id`, so every command that defaults to the caller's
|
|
29
|
+
tenant failed with `No tenant_id claim in access token`. The CLI now resolves the tenant exactly
|
|
30
|
+
as the API does: `tenant_id`, else the first organization.
|
|
31
|
+
|
|
32
|
+
## [0.60.2] — 2026-09-24
|
|
33
|
+
|
|
34
|
+
### Fixed — a redirect no longer turns an authenticated request into "Not authenticated"
|
|
35
|
+
|
|
36
|
+
`martha triggers list`, `triggers create` and `definitions apply` of a
|
|
37
|
+
`kind: Trigger` all failed with `Not authenticated` for a logged-in user. The
|
|
38
|
+
API answers `POST /api/admin/triggers` (no trailing slash) with
|
|
39
|
+
`307 → http://<host>/api/admin/triggers/` — a scheme downgrade behind the TLS
|
|
40
|
+
proxy. `fetch` followed it cross-origin and dropped `Authorization`, so the API
|
|
41
|
+
saw no token. Measured on martha.nomadriver.co, 2026-09-24.
|
|
42
|
+
|
|
43
|
+
Every request now handles redirects itself (`redirect: "manual"`): same host
|
|
44
|
+
only, always on the original scheme, at most 3 hops; 307/308 keep method and
|
|
45
|
+
body. A redirect to any other host is refused with a clear error and the
|
|
46
|
+
bearer token is never sent there.
|
|
47
|
+
|
|
48
|
+
## [0.60.0] — 2026-08-20
|
|
49
|
+
|
|
50
|
+
### Added — `kind: ClientAccess`, so a declared workflow can also be authorized
|
|
51
|
+
|
|
52
|
+
`definitions apply` could declare a Function, a Workflow, an Agent and a
|
|
53
|
+
Trigger. It could not declare the GRANTS that let a principal call them —
|
|
54
|
+
those existed only as `martha clients grant`. So anything that COMPILES a
|
|
55
|
+
workflow installed the workflow without the authority to run it, and the gap
|
|
56
|
+
only showed at run time as `Unknown function: <name>` on a function that is
|
|
57
|
+
registered and active for the tenant.
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
kind: ClientAccess
|
|
61
|
+
name: martha-cli-zero # client_key, name or id — what `martha clients` takes
|
|
62
|
+
grants:
|
|
63
|
+
- { type: function, name: get_entity_entities_type_code_entity_id_get }
|
|
64
|
+
- { type: workflow, name: some_workflow }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Additive, like the rest of apply: a document that drops a grant does not revoke
|
|
68
|
+
it. `ClientAccess` documents are applied LAST within a run, so one file can
|
|
69
|
+
carry a workflow and the authority to run it in any order.
|
|
70
|
+
|
|
5
71
|
## [0.58.1] — 2026-08-04
|
|
6
72
|
|
|
7
73
|
### Fixed — provisioning called an existing agent blind when it wasn't
|
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;
|
|
@@ -3546,6 +3528,7 @@ var require_stringify = __commonJS((exports) => {
|
|
|
3546
3528
|
nullStr: "null",
|
|
3547
3529
|
simpleKeys: false,
|
|
3548
3530
|
singleQuote: null,
|
|
3531
|
+
trailingComma: false,
|
|
3549
3532
|
trueStr: "true",
|
|
3550
3533
|
verifyAliasOrder: true
|
|
3551
3534
|
}, doc.schema.toStringOptions, options);
|
|
@@ -4054,13 +4037,20 @@ ${indent}${line}` : `
|
|
|
4054
4037
|
if (comment)
|
|
4055
4038
|
reqNewline = true;
|
|
4056
4039
|
let str = stringify.stringify(item, itemCtx, () => comment = null);
|
|
4057
|
-
|
|
4040
|
+
reqNewline || (reqNewline = lines.length > linesAtValue || str.includes(`
|
|
4041
|
+
`));
|
|
4042
|
+
if (i < items.length - 1) {
|
|
4058
4043
|
str += ",";
|
|
4044
|
+
} else if (ctx.options.trailingComma) {
|
|
4045
|
+
if (ctx.options.lineWidth > 0) {
|
|
4046
|
+
reqNewline || (reqNewline = lines.reduce((sum, line) => sum + line.length + 2, 2) + (str.length + 2) > ctx.options.lineWidth);
|
|
4047
|
+
}
|
|
4048
|
+
if (reqNewline) {
|
|
4049
|
+
str += ",";
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4059
4052
|
if (comment)
|
|
4060
4053
|
str += stringifyComment.lineComment(str, itemIndent, commentString(comment));
|
|
4061
|
-
if (!reqNewline && (lines.length > linesAtValue || str.includes(`
|
|
4062
|
-
`)))
|
|
4063
|
-
reqNewline = true;
|
|
4064
4054
|
lines.push(str);
|
|
4065
4055
|
linesAtValue = lines.length;
|
|
4066
4056
|
}
|
|
@@ -6855,17 +6845,22 @@ var require_compose_node = __commonJS((exports) => {
|
|
|
6855
6845
|
case "block-map":
|
|
6856
6846
|
case "block-seq":
|
|
6857
6847
|
case "flow-collection":
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6848
|
+
try {
|
|
6849
|
+
node = composeCollection.composeCollection(CN, ctx, token, props, onError);
|
|
6850
|
+
if (anchor)
|
|
6851
|
+
node.anchor = anchor.source.substring(1);
|
|
6852
|
+
} catch (error) {
|
|
6853
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6854
|
+
onError(token, "RESOURCE_EXHAUSTION", message);
|
|
6855
|
+
}
|
|
6861
6856
|
break;
|
|
6862
6857
|
default: {
|
|
6863
6858
|
const message = token.type === "error" ? token.message : `Unsupported token (type: ${token.type})`;
|
|
6864
6859
|
onError(token, "UNEXPECTED_TOKEN", message);
|
|
6865
|
-
node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError);
|
|
6866
6860
|
isSrcToken = false;
|
|
6867
6861
|
}
|
|
6868
6862
|
}
|
|
6863
|
+
node ?? (node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError));
|
|
6869
6864
|
if (anchor && node.anchor === "")
|
|
6870
6865
|
onError(anchor, "BAD_ALIAS", "Anchor cannot be an empty string");
|
|
6871
6866
|
if (atKey && ctx.options.stringKeys && (!identity.isScalar(node) || typeof node.value !== "string" || node.tag && node.tag !== "tag:yaml.org,2002:str")) {
|
|
@@ -9526,6 +9521,7 @@ function parseDataLine(data) {
|
|
|
9526
9521
|
// src/lib/api-client.ts
|
|
9527
9522
|
var exports_api_client = {};
|
|
9528
9523
|
__export(exports_api_client, {
|
|
9524
|
+
sameHostRedirect: () => sameHostRedirect,
|
|
9529
9525
|
MarthaAPIClient: () => MarthaAPIClient
|
|
9530
9526
|
});
|
|
9531
9527
|
import fs3 from "node:fs";
|
|
@@ -9534,6 +9530,33 @@ function guessMimeType(filename) {
|
|
|
9534
9530
|
const ext = nodePath.extname(filename).toLowerCase();
|
|
9535
9531
|
return MIME_TYPES[ext] ?? "application/octet-stream";
|
|
9536
9532
|
}
|
|
9533
|
+
function isRedirect(status) {
|
|
9534
|
+
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
|
9535
|
+
}
|
|
9536
|
+
function sameHostRedirect(from, location) {
|
|
9537
|
+
if (!location)
|
|
9538
|
+
return null;
|
|
9539
|
+
const origin = new URL(from);
|
|
9540
|
+
let next;
|
|
9541
|
+
try {
|
|
9542
|
+
next = new URL(location, origin);
|
|
9543
|
+
} catch {
|
|
9544
|
+
return null;
|
|
9545
|
+
}
|
|
9546
|
+
if (next.hostname !== origin.hostname)
|
|
9547
|
+
return null;
|
|
9548
|
+
const port = (u) => u.port || (u.protocol === "https:" ? "443" : "80");
|
|
9549
|
+
if (next.protocol !== origin.protocol) {
|
|
9550
|
+
if (port(next) !== (next.protocol === "https:" ? "443" : "80") || port(origin) !== (origin.protocol === "https:" ? "443" : "80")) {
|
|
9551
|
+
return null;
|
|
9552
|
+
}
|
|
9553
|
+
next.protocol = origin.protocol;
|
|
9554
|
+
next.port = origin.port;
|
|
9555
|
+
} else if (port(next) !== port(origin)) {
|
|
9556
|
+
return null;
|
|
9557
|
+
}
|
|
9558
|
+
return next.toString();
|
|
9559
|
+
}
|
|
9537
9560
|
|
|
9538
9561
|
class MarthaAPIClient {
|
|
9539
9562
|
baseUrl;
|
|
@@ -9551,6 +9574,27 @@ class MarthaAPIClient {
|
|
|
9551
9574
|
process.stderr.write(`[DEBUG] ${msg}
|
|
9552
9575
|
`);
|
|
9553
9576
|
}
|
|
9577
|
+
async fetchSameHost(url, init) {
|
|
9578
|
+
let target = url;
|
|
9579
|
+
let current = { ...init, redirect: "manual" };
|
|
9580
|
+
let res = await fetch(target, current);
|
|
9581
|
+
for (let hop = 0;isRedirect(res.status); hop++) {
|
|
9582
|
+
const location = res.headers.get("location");
|
|
9583
|
+
const next = sameHostRedirect(target, location);
|
|
9584
|
+
if (!next || hop >= MAX_REDIRECTS) {
|
|
9585
|
+
throw new CLIError(next ? `Too many redirects (${MAX_REDIRECTS}) from ${url}` : `Refused redirect from ${target} to ${location ?? "(no Location)"} — ` + "the credential is only ever sent to the configured API host", 1 /* Error */, "Check the profile's api_url; the server may be missing a trailing-slash route or proxy headers.");
|
|
9586
|
+
}
|
|
9587
|
+
if (res.status !== 307 && res.status !== 308 && current.method !== "GET" && current.method !== "HEAD") {
|
|
9588
|
+
const headers = { ...current.headers };
|
|
9589
|
+
delete headers["Content-Type"];
|
|
9590
|
+
current = { ...current, method: "GET", body: undefined, headers };
|
|
9591
|
+
}
|
|
9592
|
+
this.log(` → ${res.status} redirect to ${next}`);
|
|
9593
|
+
target = next;
|
|
9594
|
+
res = await fetch(target, current);
|
|
9595
|
+
}
|
|
9596
|
+
return res;
|
|
9597
|
+
}
|
|
9554
9598
|
async get(path3, opts) {
|
|
9555
9599
|
return this.request("GET", path3, opts);
|
|
9556
9600
|
}
|
|
@@ -9578,7 +9622,7 @@ class MarthaAPIClient {
|
|
|
9578
9622
|
formData.append("file", blob, fileName);
|
|
9579
9623
|
let res;
|
|
9580
9624
|
try {
|
|
9581
|
-
res = await
|
|
9625
|
+
res = await this.fetchSameHost(url, {
|
|
9582
9626
|
method: "POST",
|
|
9583
9627
|
headers: {
|
|
9584
9628
|
Authorization: `Bearer ${token}`,
|
|
@@ -9608,7 +9652,7 @@ class MarthaAPIClient {
|
|
|
9608
9652
|
const timeout = opts?.timeout ?? this.defaultTimeout;
|
|
9609
9653
|
let res;
|
|
9610
9654
|
try {
|
|
9611
|
-
res = await
|
|
9655
|
+
res = await this.fetchSameHost(url, {
|
|
9612
9656
|
method: "GET",
|
|
9613
9657
|
headers: {
|
|
9614
9658
|
Authorization: `Bearer ${token}`,
|
|
@@ -9637,7 +9681,7 @@ class MarthaAPIClient {
|
|
|
9637
9681
|
const timeout = opts?.timeout ?? 120000;
|
|
9638
9682
|
let res;
|
|
9639
9683
|
try {
|
|
9640
|
-
res = await
|
|
9684
|
+
res = await this.fetchSameHost(url, {
|
|
9641
9685
|
method: "POST",
|
|
9642
9686
|
headers: {
|
|
9643
9687
|
Authorization: `Bearer ${token}`,
|
|
@@ -9666,7 +9710,7 @@ class MarthaAPIClient {
|
|
|
9666
9710
|
const token = await this.getAccessToken();
|
|
9667
9711
|
const url = this.buildUrl(path3, opts?.params);
|
|
9668
9712
|
const timeout = opts?.timeout ?? 120000;
|
|
9669
|
-
const res = await
|
|
9713
|
+
const res = await this.fetchSameHost(url, {
|
|
9670
9714
|
method: "POST",
|
|
9671
9715
|
headers: {
|
|
9672
9716
|
Authorization: `Bearer ${token}`,
|
|
@@ -9700,7 +9744,7 @@ class MarthaAPIClient {
|
|
|
9700
9744
|
if (opts?.body !== undefined)
|
|
9701
9745
|
this.log(` body: ${JSON.stringify(opts.body).slice(0, 500)}`);
|
|
9702
9746
|
try {
|
|
9703
|
-
return await
|
|
9747
|
+
return await this.fetchSameHost(url, {
|
|
9704
9748
|
method,
|
|
9705
9749
|
headers,
|
|
9706
9750
|
body: opts?.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
@@ -9748,7 +9792,7 @@ class MarthaAPIClient {
|
|
|
9748
9792
|
return url.toString();
|
|
9749
9793
|
}
|
|
9750
9794
|
}
|
|
9751
|
-
var MIME_TYPES;
|
|
9795
|
+
var MIME_TYPES, MAX_REDIRECTS = 3;
|
|
9752
9796
|
var init_api_client = __esm(() => {
|
|
9753
9797
|
init_errors();
|
|
9754
9798
|
MIME_TYPES = {
|
|
@@ -10753,6 +10797,18 @@ function belongsToClient(tokens, clientId) {
|
|
|
10753
10797
|
const azp = claims?.azp ?? claims?.client_id;
|
|
10754
10798
|
return azp ? azp === clientId : true;
|
|
10755
10799
|
}
|
|
10800
|
+
function tenantFromClaims(payload) {
|
|
10801
|
+
const tid = payload.tenant_id;
|
|
10802
|
+
if (typeof tid === "string" && tid)
|
|
10803
|
+
return tid;
|
|
10804
|
+
const orgs = payload.organizations;
|
|
10805
|
+
if (orgs && typeof orgs === "object" && !Array.isArray(orgs)) {
|
|
10806
|
+
const first = Object.keys(orgs)[0];
|
|
10807
|
+
if (first)
|
|
10808
|
+
return first;
|
|
10809
|
+
}
|
|
10810
|
+
return null;
|
|
10811
|
+
}
|
|
10756
10812
|
function createContext(opts) {
|
|
10757
10813
|
const config = loadConfig(opts.configDir);
|
|
10758
10814
|
const profileName = opts.profileOverride ?? process.env.MARTHA_PROFILE ?? config.current_profile;
|
|
@@ -10811,10 +10867,9 @@ function createContext(opts) {
|
|
|
10811
10867
|
});
|
|
10812
10868
|
const getTenantId = async () => {
|
|
10813
10869
|
const token = await getAccessToken();
|
|
10814
|
-
const
|
|
10815
|
-
|
|
10816
|
-
|
|
10817
|
-
throw new CLIError("No tenant_id claim in access token.", 2 /* Auth */);
|
|
10870
|
+
const tid = tenantFromClaims(decodeJwtPayload(token));
|
|
10871
|
+
if (!tid) {
|
|
10872
|
+
throw new CLIError("No tenant_id or organizations claim in access token.", 2 /* Auth */);
|
|
10818
10873
|
}
|
|
10819
10874
|
return tid;
|
|
10820
10875
|
};
|
|
@@ -11149,7 +11204,7 @@ init_config();
|
|
|
11149
11204
|
init_errors();
|
|
11150
11205
|
|
|
11151
11206
|
// src/version.ts
|
|
11152
|
-
var CLI_VERSION = "0.
|
|
11207
|
+
var CLI_VERSION = "0.61.1";
|
|
11153
11208
|
|
|
11154
11209
|
// src/commands/sessions.ts
|
|
11155
11210
|
function relativeTime(iso) {
|
|
@@ -12831,9 +12886,16 @@ class BrowserSession {
|
|
|
12831
12886
|
|
|
12832
12887
|
// src/lib/host-files.ts
|
|
12833
12888
|
import { promises as fs6 } from "node:fs";
|
|
12889
|
+
import * as os3 from "node:os";
|
|
12834
12890
|
import * as path5 from "node:path";
|
|
12835
12891
|
var READ_CAP = 256 * 1024;
|
|
12836
12892
|
function resolvePath(cwd, p) {
|
|
12893
|
+
if (p === "~") {
|
|
12894
|
+
return os3.homedir();
|
|
12895
|
+
}
|
|
12896
|
+
if (p.startsWith("~/")) {
|
|
12897
|
+
return path5.join(os3.homedir(), p.slice(2));
|
|
12898
|
+
}
|
|
12837
12899
|
return path5.isAbsolute(p) ? p : path5.resolve(cwd, p);
|
|
12838
12900
|
}
|
|
12839
12901
|
async function applyFileAction(cwd, req) {
|
|
@@ -14787,8 +14849,8 @@ function registerPrincipalCommands(program2) {
|
|
|
14787
14849
|
import { accessSync, constants, mkdirSync, writeFileSync } from "node:fs";
|
|
14788
14850
|
import { existsSync } from "node:fs";
|
|
14789
14851
|
import { execFile as execFile2, spawn as spawn3 } from "node:child_process";
|
|
14790
|
-
import { homedir } from "node:os";
|
|
14791
|
-
import { join as
|
|
14852
|
+
import { homedir as homedir2 } from "node:os";
|
|
14853
|
+
import { join as join4 } from "node:path";
|
|
14792
14854
|
init_errors();
|
|
14793
14855
|
var AGENTS_PATH2 = "/api/admin/definitions/agents";
|
|
14794
14856
|
var SUPPORTED_CHANNELS = ["buzz"];
|
|
@@ -14820,35 +14882,35 @@ function buzzAppDataCandidates(platform, env2, home) {
|
|
|
14820
14882
|
];
|
|
14821
14883
|
const base = () => {
|
|
14822
14884
|
if (platform === "darwin")
|
|
14823
|
-
return
|
|
14885
|
+
return join4(home, "Library", "Application Support");
|
|
14824
14886
|
if (platform === "win32")
|
|
14825
|
-
return env2.APPDATA ??
|
|
14826
|
-
return env2.XDG_DATA_HOME ??
|
|
14887
|
+
return env2.APPDATA ?? join4(home, "AppData", "Roaming");
|
|
14888
|
+
return env2.XDG_DATA_HOME ?? join4(home, ".local", "share");
|
|
14827
14889
|
};
|
|
14828
14890
|
const root = base();
|
|
14829
|
-
return ids.map(({ build, id }) => ({ build, dir:
|
|
14891
|
+
return ids.map(({ build, id }) => ({ build, dir: join4(root, id) }));
|
|
14830
14892
|
}
|
|
14831
14893
|
function chooseAppDataDir(candidates, exists) {
|
|
14832
14894
|
return candidates.find((c) => exists(c.dir)) ?? null;
|
|
14833
14895
|
}
|
|
14834
14896
|
function buzzAppCandidates(platform, env2, home) {
|
|
14835
14897
|
if (platform === "darwin") {
|
|
14836
|
-
return ["/Applications/Buzz.app",
|
|
14898
|
+
return ["/Applications/Buzz.app", join4(home, "Applications", "Buzz.app")];
|
|
14837
14899
|
}
|
|
14838
14900
|
if (platform === "win32") {
|
|
14839
|
-
const base = env2.LOCALAPPDATA ??
|
|
14840
|
-
return [
|
|
14901
|
+
const base = env2.LOCALAPPDATA ?? join4(home, "AppData", "Local");
|
|
14902
|
+
return [join4(base, "Programs", "Buzz")];
|
|
14841
14903
|
}
|
|
14842
14904
|
return [
|
|
14843
14905
|
"/opt/Buzz",
|
|
14844
14906
|
"/usr/bin/buzz-desktop",
|
|
14845
|
-
|
|
14907
|
+
join4(home, ".local", "bin", "buzz-desktop")
|
|
14846
14908
|
];
|
|
14847
14909
|
}
|
|
14848
14910
|
var BUZZ_RELEASES_URL = "https://github.com/block/buzz/releases/latest";
|
|
14849
14911
|
function buzzSidecarDirs(platform, env2, home) {
|
|
14850
14912
|
if (platform === "darwin") {
|
|
14851
|
-
return buzzAppCandidates(platform, env2, home).map((app) =>
|
|
14913
|
+
return buzzAppCandidates(platform, env2, home).map((app) => join4(app, "Contents", "MacOS"));
|
|
14852
14914
|
}
|
|
14853
14915
|
if (platform === "win32") {
|
|
14854
14916
|
return buzzAppCandidates(platform, env2, home);
|
|
@@ -14862,7 +14924,7 @@ function resolveBuzzBinary(bin, opts) {
|
|
|
14862
14924
|
if (bin.includes("/") || bin.includes("\\"))
|
|
14863
14925
|
return null;
|
|
14864
14926
|
for (const dir of buzzSidecarDirs(opts.platform, opts.env, opts.home)) {
|
|
14865
|
-
const candidate =
|
|
14927
|
+
const candidate = join4(dir, bin);
|
|
14866
14928
|
if (opts.isExecutable(candidate))
|
|
14867
14929
|
return { path: candidate, source: "desktop" };
|
|
14868
14930
|
}
|
|
@@ -14910,7 +14972,7 @@ function resolveOnPath(bin, env2, platform, isExecutable) {
|
|
|
14910
14972
|
const sep = platform === "win32" ? ";" : ":";
|
|
14911
14973
|
for (const dir of (env2.PATH ?? "").split(sep).filter(Boolean)) {
|
|
14912
14974
|
for (const ext of exts) {
|
|
14913
|
-
const candidate =
|
|
14975
|
+
const candidate = join4(dir, bin + ext);
|
|
14914
14976
|
if (isExecutable(candidate))
|
|
14915
14977
|
return candidate;
|
|
14916
14978
|
}
|
|
@@ -14932,7 +14994,7 @@ function whichBuzz(bin) {
|
|
|
14932
14994
|
return resolveBuzzBinary(bin, {
|
|
14933
14995
|
platform: process.platform,
|
|
14934
14996
|
env: process.env,
|
|
14935
|
-
home:
|
|
14997
|
+
home: homedir2(),
|
|
14936
14998
|
isExecutable: executableOnDisk
|
|
14937
14999
|
});
|
|
14938
15000
|
}
|
|
@@ -15248,14 +15310,14 @@ function registerChannelCommands(program2) {
|
|
|
15248
15310
|
console.log(json);
|
|
15249
15311
|
return;
|
|
15250
15312
|
}
|
|
15251
|
-
const chosen = opts.appData ? { build: "release", dir: String(opts.appData) } : chooseAppDataDir(buzzAppDataCandidates(process.platform, process.env,
|
|
15313
|
+
const chosen = opts.appData ? { build: "release", dir: String(opts.appData) } : chooseAppDataDir(buzzAppDataCandidates(process.platform, process.env, homedir2()), (d) => existsSync(d));
|
|
15252
15314
|
if (!chosen) {
|
|
15253
|
-
const candidates = buzzAppDataCandidates(process.platform, process.env,
|
|
15254
|
-
const appInstalled = buzzAppCandidates(process.platform, process.env,
|
|
15315
|
+
const candidates = buzzAppDataCandidates(process.platform, process.env, homedir2());
|
|
15316
|
+
const appInstalled = buzzAppCandidates(process.platform, process.env, homedir2()).some((p) => existsSync(p));
|
|
15255
15317
|
throw new CLIError(appInstalled ? "Buzz Desktop has no data directory yet." : "Buzz Desktop is not installed.", 3 /* NotFound */, missingAppDataMessage(candidates, appInstalled));
|
|
15256
15318
|
}
|
|
15257
|
-
const dir =
|
|
15258
|
-
const file =
|
|
15319
|
+
const dir = join4(chosen.dir, "custom_harnesses");
|
|
15320
|
+
const file = join4(dir, `${def.id}.json`);
|
|
15259
15321
|
if (existsSync(file) && !opts.force) {
|
|
15260
15322
|
throw new CLIError(`${file} already exists.`, 5 /* Conflict */, "Re-run with --force to overwrite it.");
|
|
15261
15323
|
}
|
|
@@ -15326,7 +15388,7 @@ function registerChannelCommands(program2) {
|
|
|
15326
15388
|
}
|
|
15327
15389
|
const acp = whichBuzz(opts.buzzAcpBin);
|
|
15328
15390
|
const childPath = [
|
|
15329
|
-
...buzzSidecarDirs(process.platform, process.env,
|
|
15391
|
+
...buzzSidecarDirs(process.platform, process.env, homedir2()).filter((d) => existsSync(d)),
|
|
15330
15392
|
process.env.PATH ?? ""
|
|
15331
15393
|
].filter(Boolean).join(process.platform === "win32" ? ";" : ":");
|
|
15332
15394
|
if (opts.dryRun) {
|
|
@@ -15767,6 +15829,7 @@ function registerConfigCommand(program2) {
|
|
|
15767
15829
|
}
|
|
15768
15830
|
|
|
15769
15831
|
// src/commands/definitions-apply.ts
|
|
15832
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
15770
15833
|
import fs8 from "node:fs";
|
|
15771
15834
|
import path8 from "node:path";
|
|
15772
15835
|
init_dist();
|
|
@@ -15988,16 +16051,19 @@ var VALID_KINDS = new Set([
|
|
|
15988
16051
|
"Function",
|
|
15989
16052
|
"Workflow",
|
|
15990
16053
|
"Agent",
|
|
15991
|
-
"Trigger"
|
|
16054
|
+
"Trigger",
|
|
16055
|
+
"ClientAccess"
|
|
15992
16056
|
]);
|
|
15993
16057
|
var KIND_TO_API_PATH = {
|
|
15994
16058
|
Function: "/api/admin/definitions/functions",
|
|
15995
16059
|
Workflow: "/api/admin/definitions/workflows",
|
|
15996
16060
|
Agent: "/api/admin/definitions/agents",
|
|
15997
|
-
Trigger: "/api/admin/triggers"
|
|
16061
|
+
Trigger: "/api/admin/triggers",
|
|
16062
|
+
ClientAccess: "/api/admin/definitions/client-access"
|
|
15998
16063
|
};
|
|
15999
16064
|
var KIND_TO_PLURAL = {
|
|
16000
16065
|
Function: "Functions",
|
|
16066
|
+
ClientAccess: "Client access",
|
|
16001
16067
|
Workflow: "Workflows",
|
|
16002
16068
|
Agent: "Agents",
|
|
16003
16069
|
Trigger: "Triggers"
|
|
@@ -16246,10 +16312,18 @@ function localSubsetOf(local, remote) {
|
|
|
16246
16312
|
if (typeof local === "object") {
|
|
16247
16313
|
const lObj = local;
|
|
16248
16314
|
const rObj = remote;
|
|
16249
|
-
return Object.keys(lObj).every((key) =>
|
|
16315
|
+
return Object.keys(lObj).every((key) => {
|
|
16316
|
+
if (key === "value" && typeof lObj.value === "string" && typeof rObj.value_sha256 === "string" && (rObj.value === null || rObj.value === undefined)) {
|
|
16317
|
+
return sha256Prefix(lObj.value) === rObj.value_sha256;
|
|
16318
|
+
}
|
|
16319
|
+
return localSubsetOf(lObj[key], rObj[key]);
|
|
16320
|
+
});
|
|
16250
16321
|
}
|
|
16251
16322
|
return false;
|
|
16252
16323
|
}
|
|
16324
|
+
function sha256Prefix(value) {
|
|
16325
|
+
return createHash3("sha256").update(value, "utf8").digest("hex").slice(0, 16);
|
|
16326
|
+
}
|
|
16253
16327
|
var SYMBOLS = {
|
|
16254
16328
|
create: "+",
|
|
16255
16329
|
update: "~",
|
|
@@ -16272,7 +16346,8 @@ function renderPlan(ops) {
|
|
|
16272
16346
|
"Function",
|
|
16273
16347
|
"Workflow",
|
|
16274
16348
|
"Agent",
|
|
16275
|
-
"Trigger"
|
|
16349
|
+
"Trigger",
|
|
16350
|
+
"ClientAccess"
|
|
16276
16351
|
]) {
|
|
16277
16352
|
const kindOps = grouped.get(kind);
|
|
16278
16353
|
if (!kindOps)
|
|
@@ -16321,6 +16396,7 @@ async function executeApply(api, ops, defs, warnings) {
|
|
|
16321
16396
|
defsByKey.set(`${d.kind}:${d.name}`, d);
|
|
16322
16397
|
}
|
|
16323
16398
|
const results = [];
|
|
16399
|
+
ops = [...ops].sort((a, b) => (a.kind === "ClientAccess" ? 1 : 0) - (b.kind === "ClientAccess" ? 1 : 0));
|
|
16324
16400
|
for (const op of ops) {
|
|
16325
16401
|
if (op.action === "skip") {
|
|
16326
16402
|
results.push({ name: op.name, kind: op.kind, action: "skipped" });
|
|
@@ -19238,11 +19314,11 @@ ${items.length} task(s) available`));
|
|
|
19238
19314
|
}
|
|
19239
19315
|
|
|
19240
19316
|
// src/commands/runner.ts
|
|
19241
|
-
import
|
|
19317
|
+
import os4 from "node:os";
|
|
19242
19318
|
init_errors();
|
|
19243
19319
|
function hostLabel() {
|
|
19244
19320
|
try {
|
|
19245
|
-
return
|
|
19321
|
+
return os4.hostname() || undefined;
|
|
19246
19322
|
} catch {
|
|
19247
19323
|
return;
|
|
19248
19324
|
}
|
|
@@ -21626,6 +21702,129 @@ ${models.length} models`));
|
|
|
21626
21702
|
});
|
|
21627
21703
|
}
|
|
21628
21704
|
|
|
21705
|
+
// src/commands/spend.ts
|
|
21706
|
+
init_errors();
|
|
21707
|
+
var GROUPINGS = ["day", "agent", "model", "surface", "billing_owner"];
|
|
21708
|
+
function usd(value) {
|
|
21709
|
+
return value == null ? "-" : `$${Number(value).toFixed(4)}`;
|
|
21710
|
+
}
|
|
21711
|
+
function registerSpendCommand(program2) {
|
|
21712
|
+
program2.command("spend").description("LLM spend for a tenant, from Martha's own ledger").option("--tenant <id>", "Tenant (defaults to your own; others need super-admin)").option("--group-by <dim>", `One of: ${GROUPINGS.join(", ")}`, "day").option("--since <YYYY-MM-DD>", "Inclusive UTC start day").option("--until <YYYY-MM-DD>", "Exclusive UTC end day").action(async (opts) => {
|
|
21713
|
+
const ctx = createContext({
|
|
21714
|
+
profileOverride: program2.opts().profile,
|
|
21715
|
+
verbose: program2.opts().verbose
|
|
21716
|
+
});
|
|
21717
|
+
if (program2.opts().apiUrl) {
|
|
21718
|
+
ctx.profile.api_url = program2.opts().apiUrl;
|
|
21719
|
+
}
|
|
21720
|
+
const isJson = !!program2.opts().json;
|
|
21721
|
+
if (!GROUPINGS.includes(opts.groupBy)) {
|
|
21722
|
+
throw new CLIError(`--group-by must be one of: ${GROUPINGS.join(", ")}`, 4 /* Validation */);
|
|
21723
|
+
}
|
|
21724
|
+
const tenant = opts.tenant ?? await ctx.getTenantId();
|
|
21725
|
+
const params = new URLSearchParams({ group_by: opts.groupBy });
|
|
21726
|
+
if (opts.since)
|
|
21727
|
+
params.set("since", opts.since);
|
|
21728
|
+
if (opts.until)
|
|
21729
|
+
params.set("until", opts.until);
|
|
21730
|
+
const data = await ctx.api.get(`/api/admin/tenants/${encodeURIComponent(tenant)}/spend?${params.toString()}`);
|
|
21731
|
+
if (isJson) {
|
|
21732
|
+
console.log(JSON.stringify(data, null, 2));
|
|
21733
|
+
return;
|
|
21734
|
+
}
|
|
21735
|
+
if (data.rows.length === 0) {
|
|
21736
|
+
console.log(source_default.dim(`No LLM spend recorded for ${data.tenant_id}.`));
|
|
21737
|
+
return;
|
|
21738
|
+
}
|
|
21739
|
+
const cols = [
|
|
21740
|
+
{ header: data.group_by.toUpperCase(), accessor: (r) => r.key ?? "-" },
|
|
21741
|
+
{ header: "CALLS", accessor: (r) => String(r.calls) },
|
|
21742
|
+
{ header: "PROMPT", accessor: (r) => String(r.prompt_tokens) },
|
|
21743
|
+
{ header: "COMPLETION", accessor: (r) => String(r.completion_tokens) },
|
|
21744
|
+
{ header: "CACHE READ", accessor: (r) => String(r.cache_read_tokens) },
|
|
21745
|
+
{ header: "CACHE WRITE", accessor: (r) => String(r.cache_write_tokens) },
|
|
21746
|
+
{ header: "COST", accessor: (r) => usd(r.cost_usd) },
|
|
21747
|
+
{ header: "UNPRICED", accessor: (r) => String(r.unpriced_calls) }
|
|
21748
|
+
];
|
|
21749
|
+
const widths = cols.map((col) => Math.max(col.header.length, ...data.rows.map((r) => col.accessor(r).length)));
|
|
21750
|
+
const header = cols.map((col, i) => col.header.padEnd(widths[i])).join(" ");
|
|
21751
|
+
console.log(source_default.bold(header));
|
|
21752
|
+
console.log(source_default.dim("-".repeat(header.length)));
|
|
21753
|
+
for (const row of data.rows) {
|
|
21754
|
+
console.log(cols.map((col, i) => col.accessor(row).padEnd(widths[i])).join(" "));
|
|
21755
|
+
}
|
|
21756
|
+
const t = data.totals;
|
|
21757
|
+
console.log(source_default.dim(`
|
|
21758
|
+
${data.tenant_id}: ${t.calls} calls, ${usd(t.cost_usd)}` + (t.unpriced_calls ? `, ${t.unpriced_calls} unpriced (not included in cost)` : "")));
|
|
21759
|
+
});
|
|
21760
|
+
}
|
|
21761
|
+
|
|
21762
|
+
// src/commands/budget.ts
|
|
21763
|
+
init_errors();
|
|
21764
|
+
var PERIODS = ["day", "month"];
|
|
21765
|
+
var ACTIONS = ["warn", "require_approval", "block"];
|
|
21766
|
+
function oneOf(value, allowed, flag) {
|
|
21767
|
+
if (!allowed.includes(value)) {
|
|
21768
|
+
throw new CLIError(`${flag} must be one of: ${allowed.join(", ")}`, 4 /* Validation */);
|
|
21769
|
+
}
|
|
21770
|
+
}
|
|
21771
|
+
function registerBudgetCommands(program2) {
|
|
21772
|
+
const budget = program2.command("budget").description("LLM spend ceilings for a tenant");
|
|
21773
|
+
const context = () => {
|
|
21774
|
+
const ctx = createContext({
|
|
21775
|
+
profileOverride: program2.opts().profile,
|
|
21776
|
+
verbose: program2.opts().verbose
|
|
21777
|
+
});
|
|
21778
|
+
if (program2.opts().apiUrl)
|
|
21779
|
+
ctx.profile.api_url = program2.opts().apiUrl;
|
|
21780
|
+
return ctx;
|
|
21781
|
+
};
|
|
21782
|
+
const path9 = async (ctx, tenant) => `/api/admin/tenants/${encodeURIComponent(tenant ?? await ctx.getTenantId())}/llm-budgets`;
|
|
21783
|
+
budget.command("list").description("List the tenant's ceilings").option("--tenant <id>", "Tenant (defaults to your own)").action(async (opts) => {
|
|
21784
|
+
const ctx = context();
|
|
21785
|
+
const data = await ctx.api.get(await path9(ctx, opts.tenant));
|
|
21786
|
+
if (program2.opts().json) {
|
|
21787
|
+
console.log(JSON.stringify(data, null, 2));
|
|
21788
|
+
return;
|
|
21789
|
+
}
|
|
21790
|
+
if (data.budgets.length === 0) {
|
|
21791
|
+
console.log(source_default.dim(`No LLM spend ceilings for ${data.tenant_id}.`));
|
|
21792
|
+
return;
|
|
21793
|
+
}
|
|
21794
|
+
for (const b of data.budgets) {
|
|
21795
|
+
const scope = b.agent_id ? `agent ${b.agent_id}` : "tenant";
|
|
21796
|
+
console.log(`${b.id} ${scope.padEnd(42)} ${b.period.padEnd(6)} $${b.limit_usd} ${b.action}`);
|
|
21797
|
+
}
|
|
21798
|
+
});
|
|
21799
|
+
budget.command("set").description("Create or replace a ceiling (one per tenant-or-agent + period)").requiredOption("--period <p>", `One of: ${PERIODS.join(", ")}`).requiredOption("--limit <usd>", "Ceiling in USD (cache reads included)").requiredOption("--action <a>", `One of: ${ACTIONS.join(", ")}`).option("--agent <uuid>", "Scope to one AI Agent (default: whole tenant)").option("--note <text>", "Why this ceiling exists").option("--tenant <id>", "Tenant (defaults to your own)").action(async (opts) => {
|
|
21800
|
+
oneOf(opts.period, PERIODS, "--period");
|
|
21801
|
+
oneOf(opts.action, ACTIONS, "--action");
|
|
21802
|
+
if (!/^\d+(\.\d{1,4})?$/.test(opts.limit)) {
|
|
21803
|
+
throw new CLIError("--limit must be a non-negative USD amount", 4 /* Validation */);
|
|
21804
|
+
}
|
|
21805
|
+
const ctx = context();
|
|
21806
|
+
const saved = await ctx.api.put(await path9(ctx, opts.tenant), {
|
|
21807
|
+
period: opts.period,
|
|
21808
|
+
limit_usd: opts.limit,
|
|
21809
|
+
action: opts.action,
|
|
21810
|
+
agent_id: opts.agent ?? null,
|
|
21811
|
+
note: opts.note ?? null
|
|
21812
|
+
});
|
|
21813
|
+
if (program2.opts().json)
|
|
21814
|
+
console.log(JSON.stringify(saved, null, 2));
|
|
21815
|
+
else
|
|
21816
|
+
console.log(`${saved.id} ${saved.period} $${saved.limit_usd} -> ${saved.action}`);
|
|
21817
|
+
});
|
|
21818
|
+
budget.command("rm <budgetId>").description("Delete a ceiling").option("--tenant <id>", "Tenant (defaults to your own)").action(async (budgetId, opts) => {
|
|
21819
|
+
const ctx = context();
|
|
21820
|
+
await ctx.api.del(`${await path9(ctx, opts.tenant)}/${encodeURIComponent(budgetId)}`);
|
|
21821
|
+
if (program2.opts().json)
|
|
21822
|
+
console.log(JSON.stringify({ deleted: budgetId }));
|
|
21823
|
+
else
|
|
21824
|
+
console.log(`deleted ${budgetId}`);
|
|
21825
|
+
});
|
|
21826
|
+
}
|
|
21827
|
+
|
|
21629
21828
|
// src/commands/wiki.ts
|
|
21630
21829
|
import fs11 from "node:fs";
|
|
21631
21830
|
init_errors();
|
|
@@ -22481,7 +22680,7 @@ async function resolveCredentialFlag(value) {
|
|
|
22481
22680
|
|
|
22482
22681
|
// src/commands/policy.ts
|
|
22483
22682
|
init_errors();
|
|
22484
|
-
var
|
|
22683
|
+
var ACTIONS2 = ["allow", "require_approval", "block"];
|
|
22485
22684
|
var KINDS2 = ["risk_tag", "risk_level", "capability"];
|
|
22486
22685
|
var SCOPES = ["any", "agent", "client"];
|
|
22487
22686
|
var truncate4 = (s, n) => s.length > n ? s.slice(0, n - 1) + "…" : s;
|
|
@@ -22551,8 +22750,8 @@ function registerPolicyCommands(program2) {
|
|
|
22551
22750
|
console.log(columns.map((c, i) => c.get(r).padEnd(widths[i])).join(" "));
|
|
22552
22751
|
}
|
|
22553
22752
|
});
|
|
22554
|
-
cmd.command("set-default <action>").description(`Set the opt-in default action (${
|
|
22555
|
-
assertOneOf(action,
|
|
22753
|
+
cmd.command("set-default <action>").description(`Set the opt-in default action (${ACTIONS2.join("|")})`).action(async (action) => {
|
|
22754
|
+
assertOneOf(action, ACTIONS2, "action");
|
|
22556
22755
|
const ctx = getCtx();
|
|
22557
22756
|
const res = await ctx.api.put("/api/admin/policy/settings", { default_action: action });
|
|
22558
22757
|
if (isJson()) {
|
|
@@ -22576,9 +22775,9 @@ function registerPolicyCommands(program2) {
|
|
|
22576
22775
|
console.log(`Safe-default bundle ${enabled ? source_default.yellow("enabled") : source_default.dim("disabled")}.`);
|
|
22577
22776
|
});
|
|
22578
22777
|
const rule = cmd.command("rule").description("Manage explicit policy rules");
|
|
22579
|
-
rule.command("add").description("Create or update a rule").requiredOption(`--kind <kind>`, `Match kind (${KINDS2.join("|")})`).requiredOption("--value <value>", "Match value (a risk tag, level, or capability ref)").requiredOption(`--action <action>`, `Action (${
|
|
22778
|
+
rule.command("add").description("Create or update a rule").requiredOption(`--kind <kind>`, `Match kind (${KINDS2.join("|")})`).requiredOption("--value <value>", "Match value (a risk tag, level, or capability ref)").requiredOption(`--action <action>`, `Action (${ACTIONS2.join("|")})`).option(`--scope <scope>`, `Principal scope (${SCOPES.join("|")})`, "any").option("--note <text>", "Optional human note").action(async (opts) => {
|
|
22580
22779
|
assertOneOf(opts.kind, KINDS2, "--kind");
|
|
22581
|
-
assertOneOf(opts.action,
|
|
22780
|
+
assertOneOf(opts.action, ACTIONS2, "--action");
|
|
22582
22781
|
assertOneOf(opts.scope, SCOPES, "--scope");
|
|
22583
22782
|
const ctx = getCtx();
|
|
22584
22783
|
const res = await ctx.api.post("/api/admin/policy/rules", {
|
|
@@ -22609,6 +22808,7 @@ function registerPolicyCommands(program2) {
|
|
|
22609
22808
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
22610
22809
|
init_errors();
|
|
22611
22810
|
var BASE = "/api/admin/agent-step/trust-roots";
|
|
22811
|
+
var FLEET_BASE = "/api/admin/agent-step/fleet";
|
|
22612
22812
|
function resolveKeyMaterial(value) {
|
|
22613
22813
|
if (value.startsWith("@")) {
|
|
22614
22814
|
const path9 = value.slice(1);
|
|
@@ -22722,6 +22922,69 @@ function registerAgentStepCommands(program2) {
|
|
|
22722
22922
|
console.log(source_default.green(`Rotated trust root '${tr.name}'`));
|
|
22723
22923
|
printOne(tr);
|
|
22724
22924
|
});
|
|
22925
|
+
const fleet = cmd.command("fleet").description("Pause / resume this tenant's agent_step fleet (#1041)");
|
|
22926
|
+
function printFleet(s) {
|
|
22927
|
+
const state = s.effective_paused ? source_default.red(`PAUSED (${s.effective_scope})`) : source_default.green("running");
|
|
22928
|
+
console.log(`agent_step fleet: ${source_default.bold(state)}`);
|
|
22929
|
+
if (s.effective_reason)
|
|
22930
|
+
console.log(` Reason: ${s.effective_reason}`);
|
|
22931
|
+
if (s.paused_by)
|
|
22932
|
+
console.log(` Paused by: ${s.paused_by}`);
|
|
22933
|
+
if (s.paused_at)
|
|
22934
|
+
console.log(` Paused at: ${s.paused_at}`);
|
|
22935
|
+
if (s.paused_workflows.length) {
|
|
22936
|
+
console.log(` Quarantined workflows: ${s.paused_workflows.join(", ")}`);
|
|
22937
|
+
}
|
|
22938
|
+
if (s.global_paused) {
|
|
22939
|
+
console.log(source_default.yellow(` NOTE: the PLATFORM-wide break-glass flag is set — every tenant is halted,
|
|
22940
|
+
` + " and resuming this tenant will not restart work until it is cleared."));
|
|
22941
|
+
}
|
|
22942
|
+
}
|
|
22943
|
+
fleet.command("status").description("Show whether agent_step work is currently halted").action(async () => {
|
|
22944
|
+
const ctx = getCtx();
|
|
22945
|
+
const s = await ctx.api.get(FLEET_BASE);
|
|
22946
|
+
if (isJson()) {
|
|
22947
|
+
console.log(JSON.stringify(s, null, 2));
|
|
22948
|
+
return;
|
|
22949
|
+
}
|
|
22950
|
+
printFleet(s);
|
|
22951
|
+
});
|
|
22952
|
+
fleet.command("pause").description("Halt all agent_step runs for this tenant (in-flight steps drain)").requiredOption("-r, --reason <text>", "Why you are stopping the fleet — recorded in the audit log and shown on stopped runs").action(async (opts) => {
|
|
22953
|
+
const ctx = getCtx();
|
|
22954
|
+
const s = await ctx.api.put(FLEET_BASE, {
|
|
22955
|
+
paused: true,
|
|
22956
|
+
reason: opts.reason
|
|
22957
|
+
});
|
|
22958
|
+
if (isJson()) {
|
|
22959
|
+
console.log(JSON.stringify(s, null, 2));
|
|
22960
|
+
return;
|
|
22961
|
+
}
|
|
22962
|
+
console.log(source_default.yellow(`Fleet paused. No new agent_step will start; in-flight steps finish their
|
|
22963
|
+
` + "current attempt and then stop instead of retrying."));
|
|
22964
|
+
printFleet(s);
|
|
22965
|
+
});
|
|
22966
|
+
fleet.command("resume").description("Allow agent_step runs for this tenant again").action(async () => {
|
|
22967
|
+
const ctx = getCtx();
|
|
22968
|
+
const s = await ctx.api.put(FLEET_BASE, { paused: false });
|
|
22969
|
+
if (isJson()) {
|
|
22970
|
+
console.log(JSON.stringify(s, null, 2));
|
|
22971
|
+
return;
|
|
22972
|
+
}
|
|
22973
|
+
console.log(source_default.green("Fleet resumed."));
|
|
22974
|
+
printFleet(s);
|
|
22975
|
+
});
|
|
22976
|
+
fleet.command("quarantine [workflows...]").description("Halt only the named workflows (pass none to clear the quarantine list)").action(async (workflows) => {
|
|
22977
|
+
const ctx = getCtx();
|
|
22978
|
+
const s = await ctx.api.put(FLEET_BASE, {
|
|
22979
|
+
paused_workflows: workflows ?? []
|
|
22980
|
+
});
|
|
22981
|
+
if (isJson()) {
|
|
22982
|
+
console.log(JSON.stringify(s, null, 2));
|
|
22983
|
+
return;
|
|
22984
|
+
}
|
|
22985
|
+
console.log(workflows?.length ? source_default.yellow(`Quarantined: ${workflows.join(", ")}`) : source_default.green("Quarantine cleared."));
|
|
22986
|
+
printFleet(s);
|
|
22987
|
+
});
|
|
22725
22988
|
}
|
|
22726
22989
|
|
|
22727
22990
|
// src/commands/authz.ts
|
|
@@ -24142,6 +24405,8 @@ registerNotificationCommands(program2);
|
|
|
24142
24405
|
registerMessagingCommands(program2);
|
|
24143
24406
|
registerClientCommands(program2);
|
|
24144
24407
|
registerModelsCommand(program2);
|
|
24408
|
+
registerSpendCommand(program2);
|
|
24409
|
+
registerBudgetCommands(program2);
|
|
24145
24410
|
registerSessionCommands(program2);
|
|
24146
24411
|
registerMCPCommands(program2);
|
|
24147
24412
|
registerPolicyCommands(program2);
|