@aiaiai-pt/martha-cli 0.61.0 → 0.61.2
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 +36 -0
- package/dist/index.js +70 -41
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
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.2] — 2026-10-07
|
|
6
|
+
|
|
7
|
+
### Fixed — `martha chat` no longer prints `{"v": 1}` before every answer (#1601)
|
|
8
|
+
|
|
9
|
+
Since 2026-08-06 every chat stream opens with a named `stream_start` frame
|
|
10
|
+
(`event: stream_start` / `data: {"v": 1}`). The chat parser read only `data:`
|
|
11
|
+
lines, so the frame was printed as answer text. It now parses SSE events
|
|
12
|
+
properly and skips named events. The same fall-through printed structured
|
|
13
|
+
frames as raw JSON: `agent_progress` frames are now ignored, and a
|
|
14
|
+
`handover_placeholder` (a human took over) shows its message.
|
|
15
|
+
|
|
16
|
+
## [0.61.1] — 2026-09-25
|
|
17
|
+
|
|
18
|
+
### Fixed — carries 0.60.2's redirect fix onto the 0.61 line
|
|
19
|
+
|
|
20
|
+
`main` shipped the same-host redirect fix as 0.60.2 while `uio` released 0.61.0
|
|
21
|
+
the same day; the main → uio sync brings that fix into the 0.61 line. No other
|
|
22
|
+
change: `martha triggers list/create` and `definitions apply` of a `kind: Trigger`
|
|
23
|
+
no longer read as "Not authenticated" behind the TLS proxy (see 0.60.2 below).
|
|
24
|
+
|
|
5
25
|
## [0.61.0] — 2026-09-24
|
|
6
26
|
|
|
7
27
|
### Added — `martha spend` and `martha budget` (#1107)
|
|
@@ -20,6 +40,22 @@ an `organizations` claim and no `tenant_id`, so every command that defaults to t
|
|
|
20
40
|
tenant failed with `No tenant_id claim in access token`. The CLI now resolves the tenant exactly
|
|
21
41
|
as the API does: `tenant_id`, else the first organization.
|
|
22
42
|
|
|
43
|
+
## [0.60.2] — 2026-09-24
|
|
44
|
+
|
|
45
|
+
### Fixed — a redirect no longer turns an authenticated request into "Not authenticated"
|
|
46
|
+
|
|
47
|
+
`martha triggers list`, `triggers create` and `definitions apply` of a
|
|
48
|
+
`kind: Trigger` all failed with `Not authenticated` for a logged-in user. The
|
|
49
|
+
API answers `POST /api/admin/triggers` (no trailing slash) with
|
|
50
|
+
`307 → http://<host>/api/admin/triggers/` — a scheme downgrade behind the TLS
|
|
51
|
+
proxy. `fetch` followed it cross-origin and dropped `Authorization`, so the API
|
|
52
|
+
saw no token. Measured on martha.nomadriver.co, 2026-09-24.
|
|
53
|
+
|
|
54
|
+
Every request now handles redirects itself (`redirect: "manual"`): same host
|
|
55
|
+
only, always on the original scheme, at most 3 hops; 307/308 keep method and
|
|
56
|
+
body. A redirect to any other host is refused with a clear error and the
|
|
57
|
+
bearer token is never sent there.
|
|
58
|
+
|
|
23
59
|
## [0.60.0] — 2026-08-20
|
|
24
60
|
|
|
25
61
|
### Added — `kind: ClientAccess`, so a declared workflow can also be authorized
|
package/dist/index.js
CHANGED
|
@@ -9427,41 +9427,15 @@ async function* parseGenericSSE(body) {
|
|
|
9427
9427
|
}
|
|
9428
9428
|
}
|
|
9429
9429
|
async function* parseSSEStream(body) {
|
|
9430
|
-
const
|
|
9431
|
-
|
|
9432
|
-
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9436
|
-
if (done)
|
|
9437
|
-
|
|
9438
|
-
buffer += decoder.decode(value, { stream: true });
|
|
9439
|
-
const lines = buffer.split(`
|
|
9440
|
-
`);
|
|
9441
|
-
buffer = lines.pop();
|
|
9442
|
-
for (const line of lines) {
|
|
9443
|
-
const trimmed = line.trim();
|
|
9444
|
-
if (!trimmed || trimmed.startsWith(":"))
|
|
9445
|
-
continue;
|
|
9446
|
-
if (trimmed.startsWith("data: ")) {
|
|
9447
|
-
const data = trimmed.slice(6);
|
|
9448
|
-
const event = parseDataLine(data);
|
|
9449
|
-
if (event) {
|
|
9450
|
-
yield event;
|
|
9451
|
-
if (event.type === "done" || event.type === "error")
|
|
9452
|
-
return;
|
|
9453
|
-
}
|
|
9454
|
-
}
|
|
9455
|
-
}
|
|
9456
|
-
}
|
|
9457
|
-
if (buffer.trim().startsWith("data: ")) {
|
|
9458
|
-
const data = buffer.trim().slice(6);
|
|
9459
|
-
const event = parseDataLine(data);
|
|
9460
|
-
if (event)
|
|
9461
|
-
yield event;
|
|
9430
|
+
for await (const sse of parseGenericSSE(body)) {
|
|
9431
|
+
if (sse.event && sse.event !== "message")
|
|
9432
|
+
continue;
|
|
9433
|
+
const event = parseDataLine(sse.data);
|
|
9434
|
+
if (event) {
|
|
9435
|
+
yield event;
|
|
9436
|
+
if (event.type === "done" || event.type === "error")
|
|
9437
|
+
return;
|
|
9462
9438
|
}
|
|
9463
|
-
} finally {
|
|
9464
|
-
reader.releaseLock();
|
|
9465
9439
|
}
|
|
9466
9440
|
}
|
|
9467
9441
|
function parseDataLine(data) {
|
|
@@ -9488,6 +9462,12 @@ function parseDataLine(data) {
|
|
|
9488
9462
|
if (obj.type === "clear") {
|
|
9489
9463
|
return { type: "clear", data: "" };
|
|
9490
9464
|
}
|
|
9465
|
+
if (obj.type === "handover_placeholder") {
|
|
9466
|
+
return { type: "token", data: String(obj.message ?? "") };
|
|
9467
|
+
}
|
|
9468
|
+
if (obj.type === "agent_progress" || obj.type === "widget") {
|
|
9469
|
+
return null;
|
|
9470
|
+
}
|
|
9491
9471
|
if (obj.type === "done") {
|
|
9492
9472
|
return {
|
|
9493
9473
|
type: "done",
|
|
@@ -9508,6 +9488,7 @@ function parseDataLine(data) {
|
|
|
9508
9488
|
// src/lib/api-client.ts
|
|
9509
9489
|
var exports_api_client = {};
|
|
9510
9490
|
__export(exports_api_client, {
|
|
9491
|
+
sameHostRedirect: () => sameHostRedirect,
|
|
9511
9492
|
MarthaAPIClient: () => MarthaAPIClient
|
|
9512
9493
|
});
|
|
9513
9494
|
import fs3 from "node:fs";
|
|
@@ -9516,6 +9497,33 @@ function guessMimeType(filename) {
|
|
|
9516
9497
|
const ext = nodePath.extname(filename).toLowerCase();
|
|
9517
9498
|
return MIME_TYPES[ext] ?? "application/octet-stream";
|
|
9518
9499
|
}
|
|
9500
|
+
function isRedirect(status) {
|
|
9501
|
+
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
|
9502
|
+
}
|
|
9503
|
+
function sameHostRedirect(from, location) {
|
|
9504
|
+
if (!location)
|
|
9505
|
+
return null;
|
|
9506
|
+
const origin = new URL(from);
|
|
9507
|
+
let next;
|
|
9508
|
+
try {
|
|
9509
|
+
next = new URL(location, origin);
|
|
9510
|
+
} catch {
|
|
9511
|
+
return null;
|
|
9512
|
+
}
|
|
9513
|
+
if (next.hostname !== origin.hostname)
|
|
9514
|
+
return null;
|
|
9515
|
+
const port = (u) => u.port || (u.protocol === "https:" ? "443" : "80");
|
|
9516
|
+
if (next.protocol !== origin.protocol) {
|
|
9517
|
+
if (port(next) !== (next.protocol === "https:" ? "443" : "80") || port(origin) !== (origin.protocol === "https:" ? "443" : "80")) {
|
|
9518
|
+
return null;
|
|
9519
|
+
}
|
|
9520
|
+
next.protocol = origin.protocol;
|
|
9521
|
+
next.port = origin.port;
|
|
9522
|
+
} else if (port(next) !== port(origin)) {
|
|
9523
|
+
return null;
|
|
9524
|
+
}
|
|
9525
|
+
return next.toString();
|
|
9526
|
+
}
|
|
9519
9527
|
|
|
9520
9528
|
class MarthaAPIClient {
|
|
9521
9529
|
baseUrl;
|
|
@@ -9533,6 +9541,27 @@ class MarthaAPIClient {
|
|
|
9533
9541
|
process.stderr.write(`[DEBUG] ${msg}
|
|
9534
9542
|
`);
|
|
9535
9543
|
}
|
|
9544
|
+
async fetchSameHost(url, init) {
|
|
9545
|
+
let target = url;
|
|
9546
|
+
let current = { ...init, redirect: "manual" };
|
|
9547
|
+
let res = await fetch(target, current);
|
|
9548
|
+
for (let hop = 0;isRedirect(res.status); hop++) {
|
|
9549
|
+
const location = res.headers.get("location");
|
|
9550
|
+
const next = sameHostRedirect(target, location);
|
|
9551
|
+
if (!next || hop >= MAX_REDIRECTS) {
|
|
9552
|
+
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.");
|
|
9553
|
+
}
|
|
9554
|
+
if (res.status !== 307 && res.status !== 308 && current.method !== "GET" && current.method !== "HEAD") {
|
|
9555
|
+
const headers = { ...current.headers };
|
|
9556
|
+
delete headers["Content-Type"];
|
|
9557
|
+
current = { ...current, method: "GET", body: undefined, headers };
|
|
9558
|
+
}
|
|
9559
|
+
this.log(` → ${res.status} redirect to ${next}`);
|
|
9560
|
+
target = next;
|
|
9561
|
+
res = await fetch(target, current);
|
|
9562
|
+
}
|
|
9563
|
+
return res;
|
|
9564
|
+
}
|
|
9536
9565
|
async get(path3, opts) {
|
|
9537
9566
|
return this.request("GET", path3, opts);
|
|
9538
9567
|
}
|
|
@@ -9560,7 +9589,7 @@ class MarthaAPIClient {
|
|
|
9560
9589
|
formData.append("file", blob, fileName);
|
|
9561
9590
|
let res;
|
|
9562
9591
|
try {
|
|
9563
|
-
res = await
|
|
9592
|
+
res = await this.fetchSameHost(url, {
|
|
9564
9593
|
method: "POST",
|
|
9565
9594
|
headers: {
|
|
9566
9595
|
Authorization: `Bearer ${token}`,
|
|
@@ -9590,7 +9619,7 @@ class MarthaAPIClient {
|
|
|
9590
9619
|
const timeout = opts?.timeout ?? this.defaultTimeout;
|
|
9591
9620
|
let res;
|
|
9592
9621
|
try {
|
|
9593
|
-
res = await
|
|
9622
|
+
res = await this.fetchSameHost(url, {
|
|
9594
9623
|
method: "GET",
|
|
9595
9624
|
headers: {
|
|
9596
9625
|
Authorization: `Bearer ${token}`,
|
|
@@ -9619,7 +9648,7 @@ class MarthaAPIClient {
|
|
|
9619
9648
|
const timeout = opts?.timeout ?? 120000;
|
|
9620
9649
|
let res;
|
|
9621
9650
|
try {
|
|
9622
|
-
res = await
|
|
9651
|
+
res = await this.fetchSameHost(url, {
|
|
9623
9652
|
method: "POST",
|
|
9624
9653
|
headers: {
|
|
9625
9654
|
Authorization: `Bearer ${token}`,
|
|
@@ -9648,7 +9677,7 @@ class MarthaAPIClient {
|
|
|
9648
9677
|
const token = await this.getAccessToken();
|
|
9649
9678
|
const url = this.buildUrl(path3, opts?.params);
|
|
9650
9679
|
const timeout = opts?.timeout ?? 120000;
|
|
9651
|
-
const res = await
|
|
9680
|
+
const res = await this.fetchSameHost(url, {
|
|
9652
9681
|
method: "POST",
|
|
9653
9682
|
headers: {
|
|
9654
9683
|
Authorization: `Bearer ${token}`,
|
|
@@ -9682,7 +9711,7 @@ class MarthaAPIClient {
|
|
|
9682
9711
|
if (opts?.body !== undefined)
|
|
9683
9712
|
this.log(` body: ${JSON.stringify(opts.body).slice(0, 500)}`);
|
|
9684
9713
|
try {
|
|
9685
|
-
return await
|
|
9714
|
+
return await this.fetchSameHost(url, {
|
|
9686
9715
|
method,
|
|
9687
9716
|
headers,
|
|
9688
9717
|
body: opts?.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
@@ -9730,7 +9759,7 @@ class MarthaAPIClient {
|
|
|
9730
9759
|
return url.toString();
|
|
9731
9760
|
}
|
|
9732
9761
|
}
|
|
9733
|
-
var MIME_TYPES;
|
|
9762
|
+
var MIME_TYPES, MAX_REDIRECTS = 3;
|
|
9734
9763
|
var init_api_client = __esm(() => {
|
|
9735
9764
|
init_errors();
|
|
9736
9765
|
MIME_TYPES = {
|
|
@@ -11142,7 +11171,7 @@ init_config();
|
|
|
11142
11171
|
init_errors();
|
|
11143
11172
|
|
|
11144
11173
|
// src/version.ts
|
|
11145
|
-
var CLI_VERSION = "0.61.
|
|
11174
|
+
var CLI_VERSION = "0.61.2";
|
|
11146
11175
|
|
|
11147
11176
|
// src/commands/sessions.ts
|
|
11148
11177
|
function relativeTime(iso) {
|