@aiaiai-pt/martha-cli 0.51.2 → 0.51.7

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/index.js +298 -44
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,43 @@ All notable changes to `@aiaiai-pt/martha-cli`. Format: [Keep a Changelog](https
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.51.5] — 2026-07-20
8
+
9
+ ### Fixed — host tools ran in a doubled working directory
10
+
11
+ - The runner concatenated its scoped `--cwd` with a per-call `cwd`
12
+ (`` `${state.cwd}/${req.cwd}` ``), so an ABSOLUTE request path — what an agent
13
+ sends naturally when its prompt names the repo — became
14
+ `/Users/me/repo//Users/me/repo` and every `host_exec` failed with ENOENT /
15
+ MODULE_NOT_FOUND. Now resolved properly (absolute used as-is, relative joined)
16
+ and still confined inside the scoped workspace.
17
+
18
+ ## [0.51.4] — 2026-07-20
19
+
20
+ ### Added — `MARTHA_BROWSER_HEADFUL` for WAF-protected pages
21
+
22
+ - `host_browser` launched Chromium headless unconditionally, so any site that
23
+ fingerprints headless Chrome (Cloudflare & co.) served it 403 — exactly the
24
+ pages the tool exists to reach. Set `MARTHA_BROWSER_HEADFUL=1` on the runner
25
+ to launch headful with a real Chrome UA and a 1440x900 viewport. Default is
26
+ unchanged (headless), so CI and display-less servers are unaffected.
27
+ - Verified against `piscapisca.pt/carros`: 403 headless, 200 headful with live
28
+ listing data, same machine and same tool.
29
+
30
+ ## [0.51.3] — 2026-07-20
31
+
32
+ ### Fixed — `host_browser` could never find playwright
33
+
34
+ - `host_browser` resolved `playwright` with a bare specifier, i.e. relative to
35
+ `martha-cli/dist/` — never the runner's `--cwd`. Its own error message told
36
+ users to `npm i -D playwright` (naturally read as "in my project"), which was
37
+ exactly where the CLI would not look, so the tool reported "not installed"
38
+ forever on fully-provisioned machines. Now resolved from `process.cwd()`
39
+ first, falling back to the CLI-local install; the failure message names both
40
+ locations searched.
41
+ - Accept playwright's CJS export shape (`.default`) when loaded by file URL —
42
+ otherwise resolution succeeded but `chromium` was `undefined`.
43
+
7
44
  ## [0.51.2] — 2026-07-19
8
45
 
9
46
  ### Fixed — selected-client local execution (#968)
package/dist/index.js CHANGED
@@ -11042,6 +11042,7 @@ ${items.length} ${config.typeNamePlural}`));
11042
11042
  if (config.normalizeBody)
11043
11043
  body = config.normalizeBody(body);
11044
11044
  const result = await ctx.api.put(`${config.apiPath}/${encodeURIComponent(name)}`, body);
11045
+ config.verifyUpdateResult?.(body, result);
11045
11046
  if (isJson()) {
11046
11047
  console.log(JSON.stringify(result, null, 2));
11047
11048
  return;
@@ -11119,7 +11120,7 @@ init_config();
11119
11120
  init_errors();
11120
11121
 
11121
11122
  // src/version.ts
11122
- var CLI_VERSION = "0.51.2";
11123
+ var CLI_VERSION = "0.51.7";
11123
11124
 
11124
11125
  // src/commands/sessions.ts
11125
11126
  function relativeTime(iso) {
@@ -12262,6 +12263,7 @@ function parseLocalAgentRequests(data) {
12262
12263
  }
12263
12264
 
12264
12265
  // src/lib/local-tool-runner.ts
12266
+ import path7 from "node:path";
12265
12267
  import { createHash as createHash2 } from "node:crypto";
12266
12268
  init_errors();
12267
12269
 
@@ -12686,23 +12688,54 @@ ${lines.join(`
12686
12688
  return { error: true, message: `${req.action} failed: ${String(e)}` };
12687
12689
  }
12688
12690
  }
12691
+ var REAL_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 " + "(KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36";
12689
12692
 
12690
12693
  class BrowserSession {
12691
12694
  browser = null;
12692
12695
  page = null;
12693
12696
  consoleBuffer = [];
12697
+ headful() {
12698
+ const v = (process.env.MARTHA_BROWSER_HEADFUL || "").toLowerCase();
12699
+ return v === "1" || v === "true" || v === "yes";
12700
+ }
12694
12701
  async ensurePage() {
12695
12702
  if (this.page)
12696
12703
  return this.page;
12697
12704
  const moduleName = "playwright";
12698
12705
  let chromium;
12706
+ const candidates = [];
12699
12707
  try {
12700
- ({ chromium } = await import(moduleName));
12701
- } catch {
12702
- throw new Error("playwright is not installed on the runner. Install it to use host_browser: `npm i -D playwright && npx playwright install chromium`");
12708
+ const { createRequire: createRequire2 } = await import("node:module");
12709
+ const { pathToFileURL } = await import("node:url");
12710
+ const path5 = await import("node:path");
12711
+ const req = createRequire2(pathToFileURL(path5.join(process.cwd(), "noop.js")));
12712
+ candidates.push(pathToFileURL(req.resolve(moduleName)).href);
12713
+ } catch {}
12714
+ candidates.push(moduleName);
12715
+ let lastErr;
12716
+ let loaded = null;
12717
+ for (const spec of candidates) {
12718
+ try {
12719
+ loaded = await import(spec);
12720
+ break;
12721
+ } catch (e) {
12722
+ lastErr = e;
12723
+ }
12724
+ }
12725
+ if (!loaded) {
12726
+ throw new Error(`playwright is not installed where the runner can load it (looked in ${process.cwd()} and alongside the martha CLI). Install it in the runner's --cwd: \`npm i -D playwright && npx playwright install chromium\`` + (lastErr instanceof Error ? ` — ${lastErr.message}` : ""));
12727
+ }
12728
+ const pw = loaded.chromium ? loaded : loaded.default;
12729
+ if (!pw?.chromium) {
12730
+ throw new Error(`playwright loaded but exposes no \`chromium\` export (from ${process.cwd()}); check the installed playwright version.`);
12703
12731
  }
12704
- this.browser = await chromium.launch({ headless: true });
12705
- this.page = await this.browser.newPage();
12732
+ chromium = pw.chromium;
12733
+ const headful = this.headful();
12734
+ this.browser = await chromium.launch({ headless: !headful });
12735
+ this.page = headful ? await (await this.browser.newContext({
12736
+ userAgent: REAL_UA,
12737
+ viewport: { width: 1440, height: 900 }
12738
+ })).newPage() : await this.browser.newPage();
12706
12739
  this.page.on("console", (msg) => this.consoleBuffer.push({
12707
12740
  kind: "console",
12708
12741
  level: msg.type(),
@@ -13078,6 +13111,15 @@ function parseJobToolRequests(data, toolName) {
13078
13111
  }
13079
13112
 
13080
13113
  // src/lib/local-tool-runner.ts
13114
+ function resolveRequestCwd(stateCwd, reqCwd) {
13115
+ if (!reqCwd)
13116
+ return stateCwd;
13117
+ const base = path7.resolve(stateCwd);
13118
+ const target = path7.isAbsolute(reqCwd) ? path7.resolve(reqCwd) : path7.resolve(base, reqCwd);
13119
+ const rel = path7.relative(base, target);
13120
+ const escapes = rel.startsWith("..") || path7.isAbsolute(rel);
13121
+ return escapes ? base : target;
13122
+ }
13081
13123
  function selectedClientOptions(clientId) {
13082
13124
  return clientId ? { params: { selected_client: clientId } } : undefined;
13083
13125
  }
@@ -13118,7 +13160,7 @@ async function resolveHostExec(sessionId, toolCallId, req, state) {
13118
13160
  message: "Host execution declined; re-run with --yes to allow."
13119
13161
  };
13120
13162
  }
13121
- const cwd = req.cwd ? `${state.cwd}/${req.cwd}` : state.cwd;
13163
+ const cwd = resolveRequestCwd(state.cwd, req.cwd);
13122
13164
  if (req.background) {
13123
13165
  const jobId = mintJobId();
13124
13166
  process.stderr.write(source_default.dim(`
@@ -13281,7 +13323,7 @@ async function resolveLocalAgent(sessionId, toolCallId, req, state) {
13281
13323
  message: `A previous local_agent run (${decision.command}) did not complete; ` + `not re-running. Its log survives under .martha-runner/ — read it with host_read.`
13282
13324
  };
13283
13325
  }
13284
- const cwd = req.cwd ? `${state.cwd}/${req.cwd}` : state.cwd;
13326
+ const cwd = resolveRequestCwd(state.cwd, req.cwd);
13285
13327
  const { bin, args, stdin } = buildSpawn(invocation, req.task);
13286
13328
  if (req.background) {
13287
13329
  const jobId = mintJobId();
@@ -14512,9 +14554,195 @@ function registerConfigCommand(program2) {
14512
14554
 
14513
14555
  // src/commands/definitions-apply.ts
14514
14556
  import fs8 from "node:fs";
14515
- import path7 from "node:path";
14557
+ import path8 from "node:path";
14516
14558
  init_dist();
14517
14559
  init_errors();
14560
+
14561
+ // src/commands/definition-payload.ts
14562
+ init_errors();
14563
+ var MUTABLE_FIELDS = {
14564
+ Function: new Set([
14565
+ "tool_name",
14566
+ "description",
14567
+ "endpoint",
14568
+ "http_method",
14569
+ "handler",
14570
+ "category",
14571
+ "parameters",
14572
+ "request_body",
14573
+ "auth",
14574
+ "extra_headers",
14575
+ "tags",
14576
+ "is_active",
14577
+ "collection_id_param",
14578
+ "risk_tags",
14579
+ "risk_level"
14580
+ ]),
14581
+ Workflow: new Set([
14582
+ "description",
14583
+ "format_version",
14584
+ "nodes",
14585
+ "edges",
14586
+ "variables",
14587
+ "input_schema",
14588
+ "on_error",
14589
+ "timeout_seconds",
14590
+ "ui_metadata",
14591
+ "is_active"
14592
+ ])
14593
+ };
14594
+ function isPlainObject(value) {
14595
+ return value !== null && typeof value === "object" && !Array.isArray(value);
14596
+ }
14597
+ function valuesEqual(left, right) {
14598
+ if (left === right)
14599
+ return true;
14600
+ if (left === null || right === null)
14601
+ return false;
14602
+ if (left === undefined || right === undefined)
14603
+ return false;
14604
+ if (typeof left !== typeof right)
14605
+ return false;
14606
+ if (Array.isArray(left) || Array.isArray(right)) {
14607
+ if (!Array.isArray(left) || !Array.isArray(right))
14608
+ return false;
14609
+ return left.length === right.length && left.every((value, index) => valuesEqual(value, right[index]));
14610
+ }
14611
+ if (isPlainObject(left) && isPlainObject(right)) {
14612
+ const leftKeys = Object.keys(left);
14613
+ const rightKeys = Object.keys(right);
14614
+ return leftKeys.length === rightKeys.length && leftKeys.every((key) => (key in right) && valuesEqual(left[key], right[key]));
14615
+ }
14616
+ return false;
14617
+ }
14618
+ function requestedValueMatches(requested, observed) {
14619
+ if (requested === observed)
14620
+ return true;
14621
+ if (requested === null || requested === undefined) {
14622
+ return observed === null || observed === undefined;
14623
+ }
14624
+ if (observed === null || observed === undefined)
14625
+ return false;
14626
+ if (typeof requested !== typeof observed)
14627
+ return false;
14628
+ if (Array.isArray(requested)) {
14629
+ if (!Array.isArray(observed) || requested.length !== observed.length) {
14630
+ return false;
14631
+ }
14632
+ return requested.every((value, index) => requestedValueMatches(value, observed[index]));
14633
+ }
14634
+ if (isPlainObject(requested)) {
14635
+ if (!isPlainObject(observed))
14636
+ return false;
14637
+ const requestedKeys = Object.keys(requested);
14638
+ if (requestedKeys.length === 0) {
14639
+ return Object.keys(observed).length === 0;
14640
+ }
14641
+ return requestedKeys.every((key) => requestedValueMatches(requested[key], observed[key]));
14642
+ }
14643
+ return false;
14644
+ }
14645
+ function normalizeDefinitionWriteBody(kind, body) {
14646
+ const normalized = { ...body };
14647
+ if (!("definition" in normalized))
14648
+ return normalized;
14649
+ const nested = normalized.definition;
14650
+ if (!isPlainObject(nested)) {
14651
+ throw new CLIError("'definition' must be an object", 4 /* Validation */);
14652
+ }
14653
+ if ("definition" in nested) {
14654
+ throw new CLIError("Only one 'definition' wrapper is supported", 4 /* Validation */);
14655
+ }
14656
+ delete normalized.definition;
14657
+ for (const [key, nestedValue] of Object.entries(nested)) {
14658
+ if (key in normalized && !valuesEqual(normalized[key], nestedValue)) {
14659
+ throw new CLIError(`Conflicting values for '${key}' in flat and nested ${kind} fields`, 4 /* Validation */);
14660
+ }
14661
+ if (!(key in normalized))
14662
+ normalized[key] = nestedValue;
14663
+ }
14664
+ return normalized;
14665
+ }
14666
+ function canonicalizeWireValue(kind, field, value) {
14667
+ if (kind === "Function" && field === "http_method") {
14668
+ return typeof value === "string" ? value.toUpperCase() : value;
14669
+ }
14670
+ if (kind === "Function" && field === "request_body" && isPlainObject(value)) {
14671
+ const canonical = {
14672
+ content_type: "application/json",
14673
+ required: false,
14674
+ schema_def: null,
14675
+ ...value
14676
+ };
14677
+ if ("schema" in canonical) {
14678
+ canonical.schema_def = canonical.schema;
14679
+ delete canonical.schema;
14680
+ }
14681
+ return canonical;
14682
+ }
14683
+ if (kind === "Function" && field === "auth" && isPlainObject(value)) {
14684
+ return {
14685
+ scheme: null,
14686
+ header: null,
14687
+ query_param: null,
14688
+ value: null,
14689
+ credential_source: null,
14690
+ ...value
14691
+ };
14692
+ }
14693
+ if (kind === "Workflow" && field === "nodes" && Array.isArray(value)) {
14694
+ return value.map((node) => isPlainObject(node) ? { label: null, config: {}, position: null, ...node } : node);
14695
+ }
14696
+ if (kind === "Workflow" && field === "edges" && Array.isArray(value)) {
14697
+ return value.map((edge) => isPlainObject(edge) ? { source_handle: null, condition: null, label: null, ...edge } : edge);
14698
+ }
14699
+ return value;
14700
+ }
14701
+ function canonicalizeParameter(value) {
14702
+ if (!isPlainObject(value))
14703
+ return value;
14704
+ return {
14705
+ description: null,
14706
+ required: false,
14707
+ location: "query",
14708
+ enum: null,
14709
+ default: null,
14710
+ ...value
14711
+ };
14712
+ }
14713
+ function parameterMapMatches(requested, observed) {
14714
+ if (!isPlainObject(requested) || !isPlainObject(observed)) {
14715
+ return requestedValueMatches(requested, observed);
14716
+ }
14717
+ const requestedNames = Object.keys(requested);
14718
+ const observedNames = Object.keys(observed);
14719
+ return requestedNames.length === observedNames.length && requestedNames.every((name) => (name in observed) && valuesEqual(canonicalizeParameter(requested[name]), canonicalizeParameter(observed[name])));
14720
+ }
14721
+ function fieldValueMatches(kind, field, requested, observed) {
14722
+ if (kind === "Function" && field === "parameters") {
14723
+ return parameterMapMatches(requested, observed);
14724
+ }
14725
+ const isExactTypedValue = kind === "Function" && (field === "request_body" || field === "auth") || kind === "Workflow" && (field === "nodes" || field === "edges");
14726
+ if (isExactTypedValue) {
14727
+ return valuesEqual(requested, observed);
14728
+ }
14729
+ const isExactReplacementMap = kind === "Function" && field === "extra_headers" || kind === "Workflow" && (field === "variables" || field === "input_schema" || field === "ui_metadata");
14730
+ if (isExactReplacementMap) {
14731
+ return valuesEqual(requested, observed);
14732
+ }
14733
+ return requestedValueMatches(requested, observed);
14734
+ }
14735
+ function verifyDefinitionUpdateResponse(kind, requestedBody, response) {
14736
+ const requested = normalizeDefinitionWriteBody(kind, requestedBody);
14737
+ const observed = normalizeDefinitionWriteBody(kind, response);
14738
+ const mutableFields = MUTABLE_FIELDS[kind];
14739
+ const mismatches = Object.keys(requested).filter((field) => mutableFields.has(field) && !fieldValueMatches(kind, field, canonicalizeWireValue(kind, field, requested[field]), canonicalizeWireValue(kind, field, observed[field])));
14740
+ if (mismatches.length > 0) {
14741
+ throw new CLIError(`Server response did not persist requested field(s): ${mismatches.join(", ")}`, 1 /* Error */);
14742
+ }
14743
+ }
14744
+
14745
+ // src/commands/definitions-apply.ts
14518
14746
  var VALID_KINDS = new Set([
14519
14747
  "Function",
14520
14748
  "Workflow",
@@ -14549,12 +14777,11 @@ var SERVER_GENERATED_FIELDS = new Set([
14549
14777
  "has_parallel",
14550
14778
  "has_conditions",
14551
14779
  "function_refs",
14552
- "format_version",
14553
14780
  "tenant_id"
14554
14781
  ]);
14555
14782
  var AGENT_MANAGED_FIELDS = new Set(["functions", "workflows"]);
14556
14783
  function loadDefinitions(inputPath) {
14557
- const resolved = path7.resolve(inputPath);
14784
+ const resolved = path8.resolve(inputPath);
14558
14785
  if (!fs8.existsSync(resolved)) {
14559
14786
  throw new CLIError(`Path not found: ${inputPath}`, 1 /* Error */);
14560
14787
  }
@@ -14567,7 +14794,7 @@ function loadDefinitions(inputPath) {
14567
14794
  function loadDirectory(dirPath) {
14568
14795
  const entries = fs8.readdirSync(dirPath).sort();
14569
14796
  const validExts = new Set([".yaml", ".yml", ".json"]);
14570
- const files = entries.filter((e) => validExts.has(path7.extname(e).toLowerCase())).map((e) => path7.join(dirPath, e));
14797
+ const files = entries.filter((e) => validExts.has(path8.extname(e).toLowerCase())).map((e) => path8.join(dirPath, e));
14571
14798
  if (files.length === 0) {
14572
14799
  throw new CLIError(`No YAML or JSON files found in ${dirPath}`, 4 /* Validation */);
14573
14800
  }
@@ -14579,7 +14806,7 @@ function loadDirectory(dirPath) {
14579
14806
  }
14580
14807
  function loadFile(filePath) {
14581
14808
  const content = fs8.readFileSync(filePath, "utf-8");
14582
- const ext = path7.extname(filePath).toLowerCase();
14809
+ const ext = path8.extname(filePath).toLowerCase();
14583
14810
  if (ext === ".json") {
14584
14811
  const parsed = parseJsonFile(content, filePath);
14585
14812
  return [toLocalDefinition(parsed, filePath)];
@@ -14639,7 +14866,9 @@ function toLocalDefinition(raw, source) {
14639
14866
  continue;
14640
14867
  body[k] = v;
14641
14868
  }
14642
- return { kind, name, body, source };
14869
+ const typedKind = kind;
14870
+ const normalizedBody = typedKind === "Function" || typedKind === "Workflow" ? normalizeDefinitionWriteBody(typedKind, body) : body;
14871
+ return { kind: typedKind, name, body: normalizedBody, source };
14643
14872
  }
14644
14873
  async function computeOperations(defs, api) {
14645
14874
  const ops = [];
@@ -14814,7 +15043,7 @@ async function executeApply(api, ops, defs, warnings) {
14814
15043
  }
14815
15044
  const def = defsByKey.get(`${op.kind}:${op.name}`);
14816
15045
  const apiPath = KIND_TO_API_PATH[op.kind];
14817
- const body = { ...def.body };
15046
+ const body = op.kind === "Function" || op.kind === "Workflow" ? normalizeDefinitionWriteBody(op.kind, def.body) : { ...def.body };
14818
15047
  if (op.kind === "Agent") {
14819
15048
  for (const field of AGENT_MANAGED_FIELDS) {
14820
15049
  if (field in body) {
@@ -14841,6 +15070,9 @@ async function executeApply(api, ops, defs, warnings) {
14841
15070
  });
14842
15071
  } else {
14843
15072
  const res = await api.put(`${apiPath}/${encodeURIComponent(op.name)}`, body);
15073
+ if (op.kind === "Function" || op.kind === "Workflow") {
15074
+ verifyDefinitionUpdateResponse(op.kind, body, res);
15075
+ }
14844
15076
  const portWarnings = extractPortWarnings(res);
14845
15077
  results.push({
14846
15078
  name: op.name,
@@ -15123,6 +15355,8 @@ var functionsConfig = {
15123
15355
  typeName: "function",
15124
15356
  typeNamePlural: "functions",
15125
15357
  apiPath: "/api/admin/definitions/functions",
15358
+ normalizeBody: (body) => normalizeDefinitionWriteBody("Function", body),
15359
+ verifyUpdateResult: (body, result) => verifyDefinitionUpdateResponse("Function", body, result),
15126
15360
  extractList: (data) => {
15127
15361
  if (!Array.isArray(data)) {
15128
15362
  throw new Error("Unexpected API response shape for functions list");
@@ -15754,6 +15988,8 @@ var workflowsConfig = {
15754
15988
  typeName: "workflow",
15755
15989
  typeNamePlural: "workflows",
15756
15990
  apiPath: "/api/admin/definitions/workflows",
15991
+ normalizeBody: (body) => normalizeDefinitionWriteBody("Workflow", body),
15992
+ verifyUpdateResult: (body, result) => verifyDefinitionUpdateResponse("Workflow", body, result),
15757
15993
  extractList: (data) => {
15758
15994
  if (!Array.isArray(data)) {
15759
15995
  throw new Error("Unexpected API response shape for workflows list");
@@ -16141,6 +16377,14 @@ function colorStatus2(status) {
16141
16377
  const fn = STATUS_COLORS2[status] ?? source_default.white;
16142
16378
  return fn(status);
16143
16379
  }
16380
+ function unwrapList(res) {
16381
+ if (Array.isArray(res))
16382
+ return res;
16383
+ return res?.items ?? [];
16384
+ }
16385
+ function ingestionLabel(doc) {
16386
+ return doc.ingestion_status ?? "not ingested";
16387
+ }
16144
16388
  async function pollIngestionUntilDone(ctx, documentId, intervalMs = 2000, timeoutMs = 300000) {
16145
16389
  const deadline = Date.now() + timeoutMs;
16146
16390
  while (Date.now() < deadline) {
@@ -16380,7 +16624,9 @@ ${items.length} collections`));
16380
16624
  const ctx = getCtx();
16381
16625
  const col = await resolveCollection(ctx, id);
16382
16626
  const extraPromptIds = opts.clear ? [] : promptIds;
16383
- const result = await ctx.api.put(`/api/admin/collections/${encodeURIComponent(col.id)}`, { extra_prompt_ids: extraPromptIds });
16627
+ const result = await ctx.api.put(`/api/admin/collections/${encodeURIComponent(col.id)}`, {
16628
+ extra_prompt_ids: extraPromptIds
16629
+ });
16384
16630
  if (isJson()) {
16385
16631
  console.log(JSON.stringify({ extra_prompt_ids: result.extra_prompt_ids ?? [] }, null, 2));
16386
16632
  return;
@@ -16486,9 +16732,10 @@ ${items.length} collections`));
16486
16732
  const params = {};
16487
16733
  if (!opts.inactive)
16488
16734
  params.active_only = "true";
16489
- const items = await ctx.api.get(`/api/admin/collections/${encodeURIComponent(collectionId)}/documents`, { params });
16735
+ const res = await ctx.api.get(`/api/admin/collections/${encodeURIComponent(collectionId)}/documents`, { params });
16736
+ const items = unwrapList(res);
16490
16737
  if (isJson()) {
16491
- console.log(JSON.stringify(items, null, 2));
16738
+ console.log(JSON.stringify(res, null, 2));
16492
16739
  return;
16493
16740
  }
16494
16741
  const columns = [
@@ -16502,8 +16749,8 @@ ${items.length} collections`));
16502
16749
  accessor: (i) => i.size_bytes != null ? formatBytes(i.size_bytes) : "-"
16503
16750
  },
16504
16751
  {
16505
- header: "STATUS",
16506
- accessor: (i) => i.ingestion_status ?? i.status ?? "-"
16752
+ header: "INGESTION",
16753
+ accessor: (i) => ingestionLabel(i)
16507
16754
  }
16508
16755
  ];
16509
16756
  const widths = columns.map((col) => items.length === 0 ? col.header.length : Math.max(col.header.length, ...items.map((item) => col.accessor(item).length)));
@@ -16532,9 +16779,11 @@ ${items.length} documents`));
16532
16779
  if (doc.size_bytes != null) {
16533
16780
  console.log(` Size: ${formatBytes(doc.size_bytes)}`);
16534
16781
  }
16535
- console.log(` Status: ${colorStatus2(doc.ingestion_status ?? doc.status ?? "-")}`);
16782
+ console.log(` Upload: ${colorStatus2(doc.status ?? "-")}`);
16783
+ console.log(` Ingestion: ${colorStatus2(ingestionLabel(doc))}`);
16536
16784
  if (doc.chunk_count != null) {
16537
- console.log(` Chunks: ${doc.chunk_count}`);
16785
+ const warn = doc.ingestion_status === "ready" && doc.chunk_count === 0 ? source_default.yellow(" ← ready but nothing retrievable") : "";
16786
+ console.log(` Chunks: ${doc.chunk_count}${warn}`);
16538
16787
  }
16539
16788
  console.log(` Active: ${doc.is_active !== false ? source_default.green("yes") : source_default.dim("no")}`);
16540
16789
  if (doc.created_at)
@@ -18204,8 +18453,11 @@ ${data.length} spec(s)`));
18204
18453
  if (opts.force)
18205
18454
  params.force = "true";
18206
18455
  const result = await ctx.api.post(`/api/admin/definitions/openapi-specs/${encodeURIComponent(id)}/sync`, undefined, { params });
18456
+ const syncFailed = result.status === "error" || result.status === "partial";
18207
18457
  if (isJson()) {
18208
18458
  console.log(JSON.stringify(result, null, 2));
18459
+ if (syncFailed)
18460
+ process.exitCode = 1 /* Error */;
18209
18461
  return;
18210
18462
  }
18211
18463
  console.log(result.message ?? "Sync completed.");
@@ -18221,6 +18473,8 @@ ${data.length} spec(s)`));
18221
18473
  console.log(source_default.red(` - ${err}`));
18222
18474
  }
18223
18475
  }
18476
+ if (syncFailed)
18477
+ process.exitCode = 1 /* Error */;
18224
18478
  });
18225
18479
  cmd.command("import-openapi").description("Import an OpenAPI spec").requiredOption("--source <url>", "OpenAPI spec URL").requiredOption("--name <name>", "Integration name").option("--prefix <prefix>", "Function name prefix").option("--include-tags <tags>", "Comma-separated tags to include").option("--exclude-tags <tags>", "Comma-separated tags to exclude").option("--dry-run", "Preview without creating (default)", true).option("--no-dry-run", "Actually create the functions").option("--force", "Force import even with conflicts").option("--auth-scheme <scheme>", "Auth scheme: apiKey, bearer, basic, oauth2, none").option("--auth-header <header>", "Header name for auth (default: Authorization)").option("--auth-value <value>", "Auth value or env var reference (${VAR})").option("--auth-query-param <param>", "Query parameter name for apiKey auth").option("--auth-credential-source <key>", "Vault credential lookup key").action(async (opts) => {
18226
18480
  const ctx = getCtx();
@@ -18370,8 +18624,8 @@ ${data.length} spec(s)`));
18370
18624
  Resources:
18371
18625
  `));
18372
18626
  for (const r of resources) {
18373
- const path8 = r.path || `/${r.name}`;
18374
- console.log(` ${source_default.cyan(r.label || r.name)}` + source_default.dim(` → ${path8}`));
18627
+ const path9 = r.path || `/${r.name}`;
18628
+ console.log(` ${source_default.cyan(r.label || r.name)}` + source_default.dim(` → ${path9}`));
18375
18629
  }
18376
18630
  }
18377
18631
  try {
@@ -18391,14 +18645,14 @@ Functions:
18391
18645
  console.log(source_default.dim(`
18392
18646
  Proxy: martha integrations proxy ${name} GET /<path>`));
18393
18647
  });
18394
- cmd.command("proxy <name> <method> <path>").description("Send a request through the plugin proxy").option("--data <json>", "JSON request body").option("--query <params>", "Query params as key=val&key=val").action(async (name, method, path8, opts) => {
18648
+ cmd.command("proxy <name> <method> <path>").description("Send a request through the plugin proxy").option("--data <json>", "JSON request body").option("--query <params>", "Query params as key=val&key=val").action(async (name, method, path9, opts) => {
18395
18649
  const ctx = getCtx();
18396
18650
  const upperMethod = method.toUpperCase();
18397
18651
  const allowed = new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
18398
18652
  if (!allowed.has(upperMethod)) {
18399
18653
  throw new CLIError(`Invalid method: ${method}`, 4 /* Validation */, "Allowed: GET, POST, PUT, PATCH, DELETE");
18400
18654
  }
18401
- const cleanPath = path8.startsWith("/") ? path8.slice(1) : path8;
18655
+ const cleanPath = path9.startsWith("/") ? path9.slice(1) : path9;
18402
18656
  const proxyUrl = `/api/admin/plugins/${encodeURIComponent(name)}/${cleanPath}`;
18403
18657
  const params = {};
18404
18658
  if (opts.query) {
@@ -18776,11 +19030,11 @@ async function readStdin2() {
18776
19030
 
18777
19031
  // src/commands/messaging.ts
18778
19032
  init_errors();
18779
- async function messagingFetch(baseUrl, path8, opts) {
18780
- if (/^(https?:)?\/\//i.test(path8)) {
19033
+ async function messagingFetch(baseUrl, path9, opts) {
19034
+ if (/^(https?:)?\/\//i.test(path9)) {
18781
19035
  throw new CLIError("Absolute URL paths are not allowed", 1 /* Error */);
18782
19036
  }
18783
- const url = new URL(path8, baseUrl);
19037
+ const url = new URL(path9, baseUrl);
18784
19038
  const headers = {
18785
19039
  "Content-Type": "application/json"
18786
19040
  };
@@ -20218,16 +20472,16 @@ ${wikis.length} wiki${wikis.length === 1 ? "" : "s"}`));
20218
20472
  console.log(source_default.dim(`
20219
20473
  ${pages.length} pages`));
20220
20474
  });
20221
- cmd.command("get <path>").description("Fetch the raw markdown body of a wiki page").option("--wiki <slug>", "Wiki slug (default: the tenant default wiki)").option("--out <file>", "Write body to file instead of stdout").action(async (path8, opts) => {
20475
+ cmd.command("get <path>").description("Fetch the raw markdown body of a wiki page").option("--wiki <slug>", "Wiki slug (default: the tenant default wiki)").option("--out <file>", "Write body to file instead of stdout").action(async (path9, opts) => {
20222
20476
  const ctx = getCtx();
20223
- const safe = path8.split("/").map(encodeURIComponent).join("/");
20477
+ const safe = path9.split("/").map(encodeURIComponent).join("/");
20224
20478
  const slug = encodeURIComponent(opts.wiki ?? DEFAULT_WIKI_SLUG);
20225
20479
  const endpoint = `/api/wiki/${slug}/pages/${safe}`;
20226
20480
  const resp = await ctx.api.getRaw(endpoint, {
20227
20481
  headers: { Accept: "text/markdown,*/*" }
20228
20482
  });
20229
20483
  if (resp.status === 404) {
20230
- throw new CLIError(`page not found: ${path8}`, 3 /* NotFound */);
20484
+ throw new CLIError(`page not found: ${path9}`, 3 /* NotFound */);
20231
20485
  }
20232
20486
  if (!resp.ok) {
20233
20487
  const detail = await resp.text();
@@ -20239,12 +20493,12 @@ ${pages.length} pages`));
20239
20493
  if (!isJson()) {
20240
20494
  console.log(source_default.dim(`wrote ${body.length} bytes to ${opts.out}`));
20241
20495
  } else {
20242
- console.log(JSON.stringify({ path: path8, bytes: body.length, file: opts.out }));
20496
+ console.log(JSON.stringify({ path: path9, bytes: body.length, file: opts.out }));
20243
20497
  }
20244
20498
  return;
20245
20499
  }
20246
20500
  if (isJson()) {
20247
- console.log(JSON.stringify({ path: path8, body, etag: resp.headers.get("ETag") }));
20501
+ console.log(JSON.stringify({ path: path9, body, etag: resp.headers.get("ETag") }));
20248
20502
  } else {
20249
20503
  process.stdout.write(body);
20250
20504
  }
@@ -21936,11 +22190,11 @@ function registerInitCommand(program2) {
21936
22190
 
21937
22191
  // src/commands/doctor.ts
21938
22192
  var FETCH_TIMEOUT_MS = 5000;
21939
- function resolveApiUrl(base, path8) {
21940
- return new URL(path8, base).toString();
22193
+ function resolveApiUrl(base, path9) {
22194
+ return new URL(path9, base).toString();
21941
22195
  }
21942
- function apiUrl(ctx, path8) {
21943
- return resolveApiUrl(ctx.profile.api_url, path8);
22196
+ function apiUrl(ctx, path9) {
22197
+ return resolveApiUrl(ctx.profile.api_url, path9);
21944
22198
  }
21945
22199
  async function fetchJSON(url, init) {
21946
22200
  const res = await fetch(url, {
@@ -22238,15 +22492,15 @@ function registerDoctorCommand(program2) {
22238
22492
  // src/commands/skill.ts
22239
22493
  init_errors();
22240
22494
  import fs12 from "node:fs";
22241
- import path8 from "node:path";
22495
+ import path9 from "node:path";
22242
22496
  import { fileURLToPath } from "node:url";
22243
22497
  function locateSkill() {
22244
- const here = path8.dirname(fileURLToPath(import.meta.url));
22498
+ const here = path9.dirname(fileURLToPath(import.meta.url));
22245
22499
  const candidates = [
22246
- path8.join(here, "skills", "martha-cli", "SKILL.md"),
22247
- path8.join(here, "..", "skills", "martha-cli", "SKILL.md"),
22248
- path8.join(here, "..", "..", "..", "skills", "martha-cli", "SKILL.md"),
22249
- path8.join(here, "..", "..", "skills", "martha-cli", "SKILL.md")
22500
+ path9.join(here, "skills", "martha-cli", "SKILL.md"),
22501
+ path9.join(here, "..", "skills", "martha-cli", "SKILL.md"),
22502
+ path9.join(here, "..", "..", "..", "skills", "martha-cli", "SKILL.md"),
22503
+ path9.join(here, "..", "..", "skills", "martha-cli", "SKILL.md")
22250
22504
  ];
22251
22505
  for (const p of candidates) {
22252
22506
  if (fs12.existsSync(p))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiai-pt/martha-cli",
3
- "version": "0.51.2",
3
+ "version": "0.51.7",
4
4
  "description": "Terminal-first client for the Martha AI platform",
5
5
  "homepage": "https://docs.martha.nomadriver.co",
6
6
  "repository": {