@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 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
- if (i < items.length - 1)
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
- node = composeCollection.composeCollection(CN, ctx, token, props, onError);
6841
- if (anchor)
6842
- node.anchor = anchor.source.substring(1);
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 fetch(url, {
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 fetch(url, {
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 fetch(url, {
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 fetch(url, {
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 fetch(url, {
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.0";
11207
+ var CLI_VERSION = "0.61.1";
11146
11208
 
11147
11209
  // src/commands/sessions.ts
11148
11210
  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.1",
4
4
  "description": "Terminal-first client for the Martha AI platform",
5
5
  "homepage": "https://docs.martha.nomadriver.co",
6
6
  "repository": {