@aiaiai-pt/martha-cli 0.61.0 → 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 +25 -0
- package/dist/index.js +77 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
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
|
+
|
|
5
14
|
## [0.61.0] — 2026-09-24
|
|
6
15
|
|
|
7
16
|
### Added — `martha spend` and `martha budget` (#1107)
|
|
@@ -20,6 +29,22 @@ an `organizations` claim and no `tenant_id`, so every command that defaults to t
|
|
|
20
29
|
tenant failed with `No tenant_id claim in access token`. The CLI now resolves the tenant exactly
|
|
21
30
|
as the API does: `tenant_id`, else the first organization.
|
|
22
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
|
+
|
|
23
48
|
## [0.60.0] — 2026-08-20
|
|
24
49
|
|
|
25
50
|
### Added — `kind: ClientAccess`, so a declared workflow can also be authorized
|
package/dist/index.js
CHANGED
|
@@ -3528,6 +3528,7 @@ var require_stringify = __commonJS((exports) => {
|
|
|
3528
3528
|
nullStr: "null",
|
|
3529
3529
|
simpleKeys: false,
|
|
3530
3530
|
singleQuote: null,
|
|
3531
|
+
trailingComma: false,
|
|
3531
3532
|
trueStr: "true",
|
|
3532
3533
|
verifyAliasOrder: true
|
|
3533
3534
|
}, doc.schema.toStringOptions, options);
|
|
@@ -4036,13 +4037,20 @@ ${indent}${line}` : `
|
|
|
4036
4037
|
if (comment)
|
|
4037
4038
|
reqNewline = true;
|
|
4038
4039
|
let str = stringify.stringify(item, itemCtx, () => comment = null);
|
|
4039
|
-
|
|
4040
|
+
reqNewline || (reqNewline = lines.length > linesAtValue || str.includes(`
|
|
4041
|
+
`));
|
|
4042
|
+
if (i < items.length - 1) {
|
|
4040
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
|
+
}
|
|
4041
4052
|
if (comment)
|
|
4042
4053
|
str += stringifyComment.lineComment(str, itemIndent, commentString(comment));
|
|
4043
|
-
if (!reqNewline && (lines.length > linesAtValue || str.includes(`
|
|
4044
|
-
`)))
|
|
4045
|
-
reqNewline = true;
|
|
4046
4054
|
lines.push(str);
|
|
4047
4055
|
linesAtValue = lines.length;
|
|
4048
4056
|
}
|
|
@@ -6837,17 +6845,22 @@ var require_compose_node = __commonJS((exports) => {
|
|
|
6837
6845
|
case "block-map":
|
|
6838
6846
|
case "block-seq":
|
|
6839
6847
|
case "flow-collection":
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
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
|
+
}
|
|
6843
6856
|
break;
|
|
6844
6857
|
default: {
|
|
6845
6858
|
const message = token.type === "error" ? token.message : `Unsupported token (type: ${token.type})`;
|
|
6846
6859
|
onError(token, "UNEXPECTED_TOKEN", message);
|
|
6847
|
-
node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError);
|
|
6848
6860
|
isSrcToken = false;
|
|
6849
6861
|
}
|
|
6850
6862
|
}
|
|
6863
|
+
node ?? (node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError));
|
|
6851
6864
|
if (anchor && node.anchor === "")
|
|
6852
6865
|
onError(anchor, "BAD_ALIAS", "Anchor cannot be an empty string");
|
|
6853
6866
|
if (atKey && ctx.options.stringKeys && (!identity.isScalar(node) || typeof node.value !== "string" || node.tag && node.tag !== "tag:yaml.org,2002:str")) {
|
|
@@ -9508,6 +9521,7 @@ function parseDataLine(data) {
|
|
|
9508
9521
|
// src/lib/api-client.ts
|
|
9509
9522
|
var exports_api_client = {};
|
|
9510
9523
|
__export(exports_api_client, {
|
|
9524
|
+
sameHostRedirect: () => sameHostRedirect,
|
|
9511
9525
|
MarthaAPIClient: () => MarthaAPIClient
|
|
9512
9526
|
});
|
|
9513
9527
|
import fs3 from "node:fs";
|
|
@@ -9516,6 +9530,33 @@ function guessMimeType(filename) {
|
|
|
9516
9530
|
const ext = nodePath.extname(filename).toLowerCase();
|
|
9517
9531
|
return MIME_TYPES[ext] ?? "application/octet-stream";
|
|
9518
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
|
+
}
|
|
9519
9560
|
|
|
9520
9561
|
class MarthaAPIClient {
|
|
9521
9562
|
baseUrl;
|
|
@@ -9533,6 +9574,27 @@ class MarthaAPIClient {
|
|
|
9533
9574
|
process.stderr.write(`[DEBUG] ${msg}
|
|
9534
9575
|
`);
|
|
9535
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
|
+
}
|
|
9536
9598
|
async get(path3, opts) {
|
|
9537
9599
|
return this.request("GET", path3, opts);
|
|
9538
9600
|
}
|
|
@@ -9560,7 +9622,7 @@ class MarthaAPIClient {
|
|
|
9560
9622
|
formData.append("file", blob, fileName);
|
|
9561
9623
|
let res;
|
|
9562
9624
|
try {
|
|
9563
|
-
res = await
|
|
9625
|
+
res = await this.fetchSameHost(url, {
|
|
9564
9626
|
method: "POST",
|
|
9565
9627
|
headers: {
|
|
9566
9628
|
Authorization: `Bearer ${token}`,
|
|
@@ -9590,7 +9652,7 @@ class MarthaAPIClient {
|
|
|
9590
9652
|
const timeout = opts?.timeout ?? this.defaultTimeout;
|
|
9591
9653
|
let res;
|
|
9592
9654
|
try {
|
|
9593
|
-
res = await
|
|
9655
|
+
res = await this.fetchSameHost(url, {
|
|
9594
9656
|
method: "GET",
|
|
9595
9657
|
headers: {
|
|
9596
9658
|
Authorization: `Bearer ${token}`,
|
|
@@ -9619,7 +9681,7 @@ class MarthaAPIClient {
|
|
|
9619
9681
|
const timeout = opts?.timeout ?? 120000;
|
|
9620
9682
|
let res;
|
|
9621
9683
|
try {
|
|
9622
|
-
res = await
|
|
9684
|
+
res = await this.fetchSameHost(url, {
|
|
9623
9685
|
method: "POST",
|
|
9624
9686
|
headers: {
|
|
9625
9687
|
Authorization: `Bearer ${token}`,
|
|
@@ -9648,7 +9710,7 @@ class MarthaAPIClient {
|
|
|
9648
9710
|
const token = await this.getAccessToken();
|
|
9649
9711
|
const url = this.buildUrl(path3, opts?.params);
|
|
9650
9712
|
const timeout = opts?.timeout ?? 120000;
|
|
9651
|
-
const res = await
|
|
9713
|
+
const res = await this.fetchSameHost(url, {
|
|
9652
9714
|
method: "POST",
|
|
9653
9715
|
headers: {
|
|
9654
9716
|
Authorization: `Bearer ${token}`,
|
|
@@ -9682,7 +9744,7 @@ class MarthaAPIClient {
|
|
|
9682
9744
|
if (opts?.body !== undefined)
|
|
9683
9745
|
this.log(` body: ${JSON.stringify(opts.body).slice(0, 500)}`);
|
|
9684
9746
|
try {
|
|
9685
|
-
return await
|
|
9747
|
+
return await this.fetchSameHost(url, {
|
|
9686
9748
|
method,
|
|
9687
9749
|
headers,
|
|
9688
9750
|
body: opts?.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
@@ -9730,7 +9792,7 @@ class MarthaAPIClient {
|
|
|
9730
9792
|
return url.toString();
|
|
9731
9793
|
}
|
|
9732
9794
|
}
|
|
9733
|
-
var MIME_TYPES;
|
|
9795
|
+
var MIME_TYPES, MAX_REDIRECTS = 3;
|
|
9734
9796
|
var init_api_client = __esm(() => {
|
|
9735
9797
|
init_errors();
|
|
9736
9798
|
MIME_TYPES = {
|
|
@@ -11142,7 +11204,7 @@ init_config();
|
|
|
11142
11204
|
init_errors();
|
|
11143
11205
|
|
|
11144
11206
|
// src/version.ts
|
|
11145
|
-
var CLI_VERSION = "0.61.
|
|
11207
|
+
var CLI_VERSION = "0.61.1";
|
|
11146
11208
|
|
|
11147
11209
|
// src/commands/sessions.ts
|
|
11148
11210
|
function relativeTime(iso) {
|