@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 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 reader = body.getReader();
9431
- const decoder = new TextDecoder;
9432
- let buffer = "";
9433
- try {
9434
- while (true) {
9435
- const { done, value } = await reader.read();
9436
- if (done)
9437
- break;
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 fetch(url, {
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 fetch(url, {
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 fetch(url, {
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 fetch(url, {
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 fetch(url, {
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.0";
11174
+ var CLI_VERSION = "0.61.2";
11146
11175
 
11147
11176
  // src/commands/sessions.ts
11148
11177
  function relativeTime(iso) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiai-pt/martha-cli",
3
- "version": "0.61.0",
3
+ "version": "0.61.2",
4
4
  "description": "Terminal-first client for the Martha AI platform",
5
5
  "homepage": "https://docs.martha.nomadriver.co",
6
6
  "repository": {