@aiaiai-pt/martha-cli 0.51.1 → 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.
- package/CHANGELOG.md +46 -0
- package/dist/index.js +313 -55
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,52 @@ 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
|
+
|
|
44
|
+
## [0.51.2] — 2026-07-19
|
|
45
|
+
|
|
46
|
+
### Fixed — selected-client local execution (#968)
|
|
47
|
+
|
|
48
|
+
- `martha chat --client <non-default> --local-exec` now carries the selected
|
|
49
|
+
client through tool claims, tool results, and local screenshot/browser
|
|
50
|
+
artifact uploads, while chats without a selected client keep the existing
|
|
51
|
+
auth-default behavior.
|
|
52
|
+
|
|
7
53
|
## [0.51.1] — 2026-07-17
|
|
8
54
|
|
|
9
55
|
### Fixed — hosted embed cache migration (#955)
|
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.
|
|
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
|
-
|
|
12701
|
-
|
|
12702
|
-
|
|
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
|
-
|
|
12705
|
-
|
|
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,11 +13111,21 @@ function parseJobToolRequests(data, toolName) {
|
|
|
13078
13111
|
}
|
|
13079
13112
|
|
|
13080
13113
|
// src/lib/local-tool-runner.ts
|
|
13081
|
-
|
|
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
|
+
}
|
|
13123
|
+
function selectedClientOptions(clientId) {
|
|
13124
|
+
return clientId ? { params: { selected_client: clientId } } : undefined;
|
|
13125
|
+
}
|
|
13126
|
+
async function uploadImageArtifact(ctx, sessionId, png, clientId) {
|
|
13082
13127
|
try {
|
|
13083
|
-
const mint = await ctx.api.post(`/api/chat/${encodeURIComponent(sessionId)}/local-artifact-upload`, {
|
|
13084
|
-
content_type: "image/png"
|
|
13085
|
-
});
|
|
13128
|
+
const mint = await ctx.api.post(`/api/chat/${encodeURIComponent(sessionId)}/local-artifact-upload`, { content_type: "image/png" }, selectedClientOptions(clientId));
|
|
13086
13129
|
const put = await fetch(mint.upload_url, {
|
|
13087
13130
|
method: "PUT",
|
|
13088
13131
|
headers: { "Content-Type": "image/png" },
|
|
@@ -13117,7 +13160,7 @@ async function resolveHostExec(sessionId, toolCallId, req, state) {
|
|
|
13117
13160
|
message: "Host execution declined; re-run with --yes to allow."
|
|
13118
13161
|
};
|
|
13119
13162
|
}
|
|
13120
|
-
const cwd =
|
|
13163
|
+
const cwd = resolveRequestCwd(state.cwd, req.cwd);
|
|
13121
13164
|
if (req.background) {
|
|
13122
13165
|
const jobId = mintJobId();
|
|
13123
13166
|
process.stderr.write(source_default.dim(`
|
|
@@ -13198,7 +13241,7 @@ async function resolveHostScreenshot(ctx, sessionId, req, state) {
|
|
|
13198
13241
|
} catch (e) {
|
|
13199
13242
|
return { error: true, message: `screenshot failed: ${String(e)}` };
|
|
13200
13243
|
}
|
|
13201
|
-
const uploaded = await uploadImageArtifact(ctx, sessionId, png);
|
|
13244
|
+
const uploaded = await uploadImageArtifact(ctx, sessionId, png, state.clientId);
|
|
13202
13245
|
if ("error" in uploaded)
|
|
13203
13246
|
return { error: true, message: uploaded.error };
|
|
13204
13247
|
return {
|
|
@@ -13225,7 +13268,7 @@ async function resolveHostBrowser(ctx, sessionId, req, state) {
|
|
|
13225
13268
|
if ("error" in result)
|
|
13226
13269
|
return { error: true, message: result.message };
|
|
13227
13270
|
if ("png" in result) {
|
|
13228
|
-
const uploaded = await uploadImageArtifact(ctx, sessionId, result.png);
|
|
13271
|
+
const uploaded = await uploadImageArtifact(ctx, sessionId, result.png, state.clientId);
|
|
13229
13272
|
if ("error" in uploaded)
|
|
13230
13273
|
return { error: true, message: uploaded.error };
|
|
13231
13274
|
return {
|
|
@@ -13280,7 +13323,7 @@ async function resolveLocalAgent(sessionId, toolCallId, req, state) {
|
|
|
13280
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.`
|
|
13281
13324
|
};
|
|
13282
13325
|
}
|
|
13283
|
-
const cwd =
|
|
13326
|
+
const cwd = resolveRequestCwd(state.cwd, req.cwd);
|
|
13284
13327
|
const { bin, args, stdin } = buildSpawn(invocation, req.task);
|
|
13285
13328
|
if (req.background) {
|
|
13286
13329
|
const jobId = mintJobId();
|
|
@@ -13452,27 +13495,29 @@ function createLocalExecState(opts) {
|
|
|
13452
13495
|
cwd: opts.cwd,
|
|
13453
13496
|
autoYes: opts.autoYes,
|
|
13454
13497
|
localAgent: opts.localAgent,
|
|
13498
|
+
clientId: opts.clientId,
|
|
13455
13499
|
handled: new Set,
|
|
13456
13500
|
journal: new Journal(opts.cwd),
|
|
13457
13501
|
secrets: new Map,
|
|
13458
13502
|
claimed: new Set
|
|
13459
13503
|
};
|
|
13460
13504
|
}
|
|
13461
|
-
function makeChatTransport(ctx, sessionId) {
|
|
13505
|
+
function makeChatTransport(ctx, sessionId, clientId) {
|
|
13462
13506
|
const base = `/api/chat/${encodeURIComponent(sessionId)}`;
|
|
13507
|
+
const requestOptions = selectedClientOptions(clientId);
|
|
13463
13508
|
return {
|
|
13464
13509
|
async claim(toolCallId, secretHash) {
|
|
13465
13510
|
await ctx.api.post(`${base}/tool-claim`, {
|
|
13466
13511
|
tool_call_id: toolCallId,
|
|
13467
13512
|
secret_hash: secretHash
|
|
13468
|
-
});
|
|
13513
|
+
}, requestOptions);
|
|
13469
13514
|
},
|
|
13470
13515
|
async postResult(toolCallId, result, execSecret) {
|
|
13471
13516
|
await ctx.api.post(`${base}/tool-result`, {
|
|
13472
13517
|
tool_call_id: toolCallId,
|
|
13473
13518
|
result,
|
|
13474
13519
|
...execSecret !== undefined ? { exec_secret: execSecret } : {}
|
|
13475
|
-
});
|
|
13520
|
+
}, requestOptions);
|
|
13476
13521
|
}
|
|
13477
13522
|
};
|
|
13478
13523
|
}
|
|
@@ -13573,7 +13618,8 @@ async function sendMessage(ctx, sessionId, message, opts = {}) {
|
|
|
13573
13618
|
const localExecState = opts.localExec ? createLocalExecState({
|
|
13574
13619
|
cwd: opts.localExec.cwd,
|
|
13575
13620
|
autoYes: opts.localExec.autoYes,
|
|
13576
|
-
localAgent: opts.localExec.agent
|
|
13621
|
+
localAgent: opts.localExec.agent,
|
|
13622
|
+
clientId: opts.clientId
|
|
13577
13623
|
}) : null;
|
|
13578
13624
|
const reqBody = { content: message };
|
|
13579
13625
|
if (opts.localExec)
|
|
@@ -13627,7 +13673,7 @@ async function sendMessage(ctx, sessionId, message, opts = {}) {
|
|
|
13627
13673
|
if (opts.onToolStatus)
|
|
13628
13674
|
opts.onToolStatus(event.data);
|
|
13629
13675
|
if (localExecState) {
|
|
13630
|
-
await handleToolStatusData(ctx, sessionId, event.data, localExecState, makeChatTransport(ctx, sessionId));
|
|
13676
|
+
await handleToolStatusData(ctx, sessionId, event.data, localExecState, makeChatTransport(ctx, sessionId, opts.clientId));
|
|
13631
13677
|
}
|
|
13632
13678
|
break;
|
|
13633
13679
|
case "clear":
|
|
@@ -14508,9 +14554,195 @@ function registerConfigCommand(program2) {
|
|
|
14508
14554
|
|
|
14509
14555
|
// src/commands/definitions-apply.ts
|
|
14510
14556
|
import fs8 from "node:fs";
|
|
14511
|
-
import
|
|
14557
|
+
import path8 from "node:path";
|
|
14512
14558
|
init_dist();
|
|
14513
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
|
|
14514
14746
|
var VALID_KINDS = new Set([
|
|
14515
14747
|
"Function",
|
|
14516
14748
|
"Workflow",
|
|
@@ -14545,12 +14777,11 @@ var SERVER_GENERATED_FIELDS = new Set([
|
|
|
14545
14777
|
"has_parallel",
|
|
14546
14778
|
"has_conditions",
|
|
14547
14779
|
"function_refs",
|
|
14548
|
-
"format_version",
|
|
14549
14780
|
"tenant_id"
|
|
14550
14781
|
]);
|
|
14551
14782
|
var AGENT_MANAGED_FIELDS = new Set(["functions", "workflows"]);
|
|
14552
14783
|
function loadDefinitions(inputPath) {
|
|
14553
|
-
const resolved =
|
|
14784
|
+
const resolved = path8.resolve(inputPath);
|
|
14554
14785
|
if (!fs8.existsSync(resolved)) {
|
|
14555
14786
|
throw new CLIError(`Path not found: ${inputPath}`, 1 /* Error */);
|
|
14556
14787
|
}
|
|
@@ -14563,7 +14794,7 @@ function loadDefinitions(inputPath) {
|
|
|
14563
14794
|
function loadDirectory(dirPath) {
|
|
14564
14795
|
const entries = fs8.readdirSync(dirPath).sort();
|
|
14565
14796
|
const validExts = new Set([".yaml", ".yml", ".json"]);
|
|
14566
|
-
const files = entries.filter((e) => validExts.has(
|
|
14797
|
+
const files = entries.filter((e) => validExts.has(path8.extname(e).toLowerCase())).map((e) => path8.join(dirPath, e));
|
|
14567
14798
|
if (files.length === 0) {
|
|
14568
14799
|
throw new CLIError(`No YAML or JSON files found in ${dirPath}`, 4 /* Validation */);
|
|
14569
14800
|
}
|
|
@@ -14575,7 +14806,7 @@ function loadDirectory(dirPath) {
|
|
|
14575
14806
|
}
|
|
14576
14807
|
function loadFile(filePath) {
|
|
14577
14808
|
const content = fs8.readFileSync(filePath, "utf-8");
|
|
14578
|
-
const ext =
|
|
14809
|
+
const ext = path8.extname(filePath).toLowerCase();
|
|
14579
14810
|
if (ext === ".json") {
|
|
14580
14811
|
const parsed = parseJsonFile(content, filePath);
|
|
14581
14812
|
return [toLocalDefinition(parsed, filePath)];
|
|
@@ -14635,7 +14866,9 @@ function toLocalDefinition(raw, source) {
|
|
|
14635
14866
|
continue;
|
|
14636
14867
|
body[k] = v;
|
|
14637
14868
|
}
|
|
14638
|
-
|
|
14869
|
+
const typedKind = kind;
|
|
14870
|
+
const normalizedBody = typedKind === "Function" || typedKind === "Workflow" ? normalizeDefinitionWriteBody(typedKind, body) : body;
|
|
14871
|
+
return { kind: typedKind, name, body: normalizedBody, source };
|
|
14639
14872
|
}
|
|
14640
14873
|
async function computeOperations(defs, api) {
|
|
14641
14874
|
const ops = [];
|
|
@@ -14810,7 +15043,7 @@ async function executeApply(api, ops, defs, warnings) {
|
|
|
14810
15043
|
}
|
|
14811
15044
|
const def = defsByKey.get(`${op.kind}:${op.name}`);
|
|
14812
15045
|
const apiPath = KIND_TO_API_PATH[op.kind];
|
|
14813
|
-
const body = { ...def.body };
|
|
15046
|
+
const body = op.kind === "Function" || op.kind === "Workflow" ? normalizeDefinitionWriteBody(op.kind, def.body) : { ...def.body };
|
|
14814
15047
|
if (op.kind === "Agent") {
|
|
14815
15048
|
for (const field of AGENT_MANAGED_FIELDS) {
|
|
14816
15049
|
if (field in body) {
|
|
@@ -14837,6 +15070,9 @@ async function executeApply(api, ops, defs, warnings) {
|
|
|
14837
15070
|
});
|
|
14838
15071
|
} else {
|
|
14839
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
|
+
}
|
|
14840
15076
|
const portWarnings = extractPortWarnings(res);
|
|
14841
15077
|
results.push({
|
|
14842
15078
|
name: op.name,
|
|
@@ -15119,6 +15355,8 @@ var functionsConfig = {
|
|
|
15119
15355
|
typeName: "function",
|
|
15120
15356
|
typeNamePlural: "functions",
|
|
15121
15357
|
apiPath: "/api/admin/definitions/functions",
|
|
15358
|
+
normalizeBody: (body) => normalizeDefinitionWriteBody("Function", body),
|
|
15359
|
+
verifyUpdateResult: (body, result) => verifyDefinitionUpdateResponse("Function", body, result),
|
|
15122
15360
|
extractList: (data) => {
|
|
15123
15361
|
if (!Array.isArray(data)) {
|
|
15124
15362
|
throw new Error("Unexpected API response shape for functions list");
|
|
@@ -15750,6 +15988,8 @@ var workflowsConfig = {
|
|
|
15750
15988
|
typeName: "workflow",
|
|
15751
15989
|
typeNamePlural: "workflows",
|
|
15752
15990
|
apiPath: "/api/admin/definitions/workflows",
|
|
15991
|
+
normalizeBody: (body) => normalizeDefinitionWriteBody("Workflow", body),
|
|
15992
|
+
verifyUpdateResult: (body, result) => verifyDefinitionUpdateResponse("Workflow", body, result),
|
|
15753
15993
|
extractList: (data) => {
|
|
15754
15994
|
if (!Array.isArray(data)) {
|
|
15755
15995
|
throw new Error("Unexpected API response shape for workflows list");
|
|
@@ -16137,6 +16377,14 @@ function colorStatus2(status) {
|
|
|
16137
16377
|
const fn = STATUS_COLORS2[status] ?? source_default.white;
|
|
16138
16378
|
return fn(status);
|
|
16139
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
|
+
}
|
|
16140
16388
|
async function pollIngestionUntilDone(ctx, documentId, intervalMs = 2000, timeoutMs = 300000) {
|
|
16141
16389
|
const deadline = Date.now() + timeoutMs;
|
|
16142
16390
|
while (Date.now() < deadline) {
|
|
@@ -16376,7 +16624,9 @@ ${items.length} collections`));
|
|
|
16376
16624
|
const ctx = getCtx();
|
|
16377
16625
|
const col = await resolveCollection(ctx, id);
|
|
16378
16626
|
const extraPromptIds = opts.clear ? [] : promptIds;
|
|
16379
|
-
const result = await ctx.api.put(`/api/admin/collections/${encodeURIComponent(col.id)}`, {
|
|
16627
|
+
const result = await ctx.api.put(`/api/admin/collections/${encodeURIComponent(col.id)}`, {
|
|
16628
|
+
extra_prompt_ids: extraPromptIds
|
|
16629
|
+
});
|
|
16380
16630
|
if (isJson()) {
|
|
16381
16631
|
console.log(JSON.stringify({ extra_prompt_ids: result.extra_prompt_ids ?? [] }, null, 2));
|
|
16382
16632
|
return;
|
|
@@ -16482,9 +16732,10 @@ ${items.length} collections`));
|
|
|
16482
16732
|
const params = {};
|
|
16483
16733
|
if (!opts.inactive)
|
|
16484
16734
|
params.active_only = "true";
|
|
16485
|
-
const
|
|
16735
|
+
const res = await ctx.api.get(`/api/admin/collections/${encodeURIComponent(collectionId)}/documents`, { params });
|
|
16736
|
+
const items = unwrapList(res);
|
|
16486
16737
|
if (isJson()) {
|
|
16487
|
-
console.log(JSON.stringify(
|
|
16738
|
+
console.log(JSON.stringify(res, null, 2));
|
|
16488
16739
|
return;
|
|
16489
16740
|
}
|
|
16490
16741
|
const columns = [
|
|
@@ -16498,8 +16749,8 @@ ${items.length} collections`));
|
|
|
16498
16749
|
accessor: (i) => i.size_bytes != null ? formatBytes(i.size_bytes) : "-"
|
|
16499
16750
|
},
|
|
16500
16751
|
{
|
|
16501
|
-
header: "
|
|
16502
|
-
accessor: (i) => i
|
|
16752
|
+
header: "INGESTION",
|
|
16753
|
+
accessor: (i) => ingestionLabel(i)
|
|
16503
16754
|
}
|
|
16504
16755
|
];
|
|
16505
16756
|
const widths = columns.map((col) => items.length === 0 ? col.header.length : Math.max(col.header.length, ...items.map((item) => col.accessor(item).length)));
|
|
@@ -16528,9 +16779,11 @@ ${items.length} documents`));
|
|
|
16528
16779
|
if (doc.size_bytes != null) {
|
|
16529
16780
|
console.log(` Size: ${formatBytes(doc.size_bytes)}`);
|
|
16530
16781
|
}
|
|
16531
|
-
console.log(`
|
|
16782
|
+
console.log(` Upload: ${colorStatus2(doc.status ?? "-")}`);
|
|
16783
|
+
console.log(` Ingestion: ${colorStatus2(ingestionLabel(doc))}`);
|
|
16532
16784
|
if (doc.chunk_count != null) {
|
|
16533
|
-
|
|
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}`);
|
|
16534
16787
|
}
|
|
16535
16788
|
console.log(` Active: ${doc.is_active !== false ? source_default.green("yes") : source_default.dim("no")}`);
|
|
16536
16789
|
if (doc.created_at)
|
|
@@ -18200,8 +18453,11 @@ ${data.length} spec(s)`));
|
|
|
18200
18453
|
if (opts.force)
|
|
18201
18454
|
params.force = "true";
|
|
18202
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";
|
|
18203
18457
|
if (isJson()) {
|
|
18204
18458
|
console.log(JSON.stringify(result, null, 2));
|
|
18459
|
+
if (syncFailed)
|
|
18460
|
+
process.exitCode = 1 /* Error */;
|
|
18205
18461
|
return;
|
|
18206
18462
|
}
|
|
18207
18463
|
console.log(result.message ?? "Sync completed.");
|
|
@@ -18217,6 +18473,8 @@ ${data.length} spec(s)`));
|
|
|
18217
18473
|
console.log(source_default.red(` - ${err}`));
|
|
18218
18474
|
}
|
|
18219
18475
|
}
|
|
18476
|
+
if (syncFailed)
|
|
18477
|
+
process.exitCode = 1 /* Error */;
|
|
18220
18478
|
});
|
|
18221
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) => {
|
|
18222
18480
|
const ctx = getCtx();
|
|
@@ -18366,8 +18624,8 @@ ${data.length} spec(s)`));
|
|
|
18366
18624
|
Resources:
|
|
18367
18625
|
`));
|
|
18368
18626
|
for (const r of resources) {
|
|
18369
|
-
const
|
|
18370
|
-
console.log(` ${source_default.cyan(r.label || r.name)}` + source_default.dim(` → ${
|
|
18627
|
+
const path9 = r.path || `/${r.name}`;
|
|
18628
|
+
console.log(` ${source_default.cyan(r.label || r.name)}` + source_default.dim(` → ${path9}`));
|
|
18371
18629
|
}
|
|
18372
18630
|
}
|
|
18373
18631
|
try {
|
|
@@ -18387,14 +18645,14 @@ Functions:
|
|
|
18387
18645
|
console.log(source_default.dim(`
|
|
18388
18646
|
Proxy: martha integrations proxy ${name} GET /<path>`));
|
|
18389
18647
|
});
|
|
18390
|
-
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,
|
|
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) => {
|
|
18391
18649
|
const ctx = getCtx();
|
|
18392
18650
|
const upperMethod = method.toUpperCase();
|
|
18393
18651
|
const allowed = new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
|
|
18394
18652
|
if (!allowed.has(upperMethod)) {
|
|
18395
18653
|
throw new CLIError(`Invalid method: ${method}`, 4 /* Validation */, "Allowed: GET, POST, PUT, PATCH, DELETE");
|
|
18396
18654
|
}
|
|
18397
|
-
const cleanPath =
|
|
18655
|
+
const cleanPath = path9.startsWith("/") ? path9.slice(1) : path9;
|
|
18398
18656
|
const proxyUrl = `/api/admin/plugins/${encodeURIComponent(name)}/${cleanPath}`;
|
|
18399
18657
|
const params = {};
|
|
18400
18658
|
if (opts.query) {
|
|
@@ -18772,11 +19030,11 @@ async function readStdin2() {
|
|
|
18772
19030
|
|
|
18773
19031
|
// src/commands/messaging.ts
|
|
18774
19032
|
init_errors();
|
|
18775
|
-
async function messagingFetch(baseUrl,
|
|
18776
|
-
if (/^(https?:)?\/\//i.test(
|
|
19033
|
+
async function messagingFetch(baseUrl, path9, opts) {
|
|
19034
|
+
if (/^(https?:)?\/\//i.test(path9)) {
|
|
18777
19035
|
throw new CLIError("Absolute URL paths are not allowed", 1 /* Error */);
|
|
18778
19036
|
}
|
|
18779
|
-
const url = new URL(
|
|
19037
|
+
const url = new URL(path9, baseUrl);
|
|
18780
19038
|
const headers = {
|
|
18781
19039
|
"Content-Type": "application/json"
|
|
18782
19040
|
};
|
|
@@ -20214,16 +20472,16 @@ ${wikis.length} wiki${wikis.length === 1 ? "" : "s"}`));
|
|
|
20214
20472
|
console.log(source_default.dim(`
|
|
20215
20473
|
${pages.length} pages`));
|
|
20216
20474
|
});
|
|
20217
|
-
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 (
|
|
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) => {
|
|
20218
20476
|
const ctx = getCtx();
|
|
20219
|
-
const safe =
|
|
20477
|
+
const safe = path9.split("/").map(encodeURIComponent).join("/");
|
|
20220
20478
|
const slug = encodeURIComponent(opts.wiki ?? DEFAULT_WIKI_SLUG);
|
|
20221
20479
|
const endpoint = `/api/wiki/${slug}/pages/${safe}`;
|
|
20222
20480
|
const resp = await ctx.api.getRaw(endpoint, {
|
|
20223
20481
|
headers: { Accept: "text/markdown,*/*" }
|
|
20224
20482
|
});
|
|
20225
20483
|
if (resp.status === 404) {
|
|
20226
|
-
throw new CLIError(`page not found: ${
|
|
20484
|
+
throw new CLIError(`page not found: ${path9}`, 3 /* NotFound */);
|
|
20227
20485
|
}
|
|
20228
20486
|
if (!resp.ok) {
|
|
20229
20487
|
const detail = await resp.text();
|
|
@@ -20235,12 +20493,12 @@ ${pages.length} pages`));
|
|
|
20235
20493
|
if (!isJson()) {
|
|
20236
20494
|
console.log(source_default.dim(`wrote ${body.length} bytes to ${opts.out}`));
|
|
20237
20495
|
} else {
|
|
20238
|
-
console.log(JSON.stringify({ path:
|
|
20496
|
+
console.log(JSON.stringify({ path: path9, bytes: body.length, file: opts.out }));
|
|
20239
20497
|
}
|
|
20240
20498
|
return;
|
|
20241
20499
|
}
|
|
20242
20500
|
if (isJson()) {
|
|
20243
|
-
console.log(JSON.stringify({ path:
|
|
20501
|
+
console.log(JSON.stringify({ path: path9, body, etag: resp.headers.get("ETag") }));
|
|
20244
20502
|
} else {
|
|
20245
20503
|
process.stdout.write(body);
|
|
20246
20504
|
}
|
|
@@ -21932,11 +22190,11 @@ function registerInitCommand(program2) {
|
|
|
21932
22190
|
|
|
21933
22191
|
// src/commands/doctor.ts
|
|
21934
22192
|
var FETCH_TIMEOUT_MS = 5000;
|
|
21935
|
-
function resolveApiUrl(base,
|
|
21936
|
-
return new URL(
|
|
22193
|
+
function resolveApiUrl(base, path9) {
|
|
22194
|
+
return new URL(path9, base).toString();
|
|
21937
22195
|
}
|
|
21938
|
-
function apiUrl(ctx,
|
|
21939
|
-
return resolveApiUrl(ctx.profile.api_url,
|
|
22196
|
+
function apiUrl(ctx, path9) {
|
|
22197
|
+
return resolveApiUrl(ctx.profile.api_url, path9);
|
|
21940
22198
|
}
|
|
21941
22199
|
async function fetchJSON(url, init) {
|
|
21942
22200
|
const res = await fetch(url, {
|
|
@@ -22234,15 +22492,15 @@ function registerDoctorCommand(program2) {
|
|
|
22234
22492
|
// src/commands/skill.ts
|
|
22235
22493
|
init_errors();
|
|
22236
22494
|
import fs12 from "node:fs";
|
|
22237
|
-
import
|
|
22495
|
+
import path9 from "node:path";
|
|
22238
22496
|
import { fileURLToPath } from "node:url";
|
|
22239
22497
|
function locateSkill() {
|
|
22240
|
-
const here =
|
|
22498
|
+
const here = path9.dirname(fileURLToPath(import.meta.url));
|
|
22241
22499
|
const candidates = [
|
|
22242
|
-
|
|
22243
|
-
|
|
22244
|
-
|
|
22245
|
-
|
|
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")
|
|
22246
22504
|
];
|
|
22247
22505
|
for (const p of candidates) {
|
|
22248
22506
|
if (fs12.existsSync(p))
|