@aiaiai-pt/martha-cli 0.22.0 → 0.23.0
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/dist/index.js +555 -86
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11077,7 +11077,7 @@ import { createInterface as createInterface2 } from "node:readline";
|
|
|
11077
11077
|
init_errors();
|
|
11078
11078
|
|
|
11079
11079
|
// src/version.ts
|
|
11080
|
-
var CLI_VERSION = "0.
|
|
11080
|
+
var CLI_VERSION = "0.23.0";
|
|
11081
11081
|
|
|
11082
11082
|
// src/commands/sessions.ts
|
|
11083
11083
|
function relativeTime(iso) {
|
|
@@ -11538,7 +11538,387 @@ function parseHostScreenshotRequests(data) {
|
|
|
11538
11538
|
return out;
|
|
11539
11539
|
}
|
|
11540
11540
|
|
|
11541
|
+
// src/lib/host-browser.ts
|
|
11542
|
+
var DOM_CAP = 16 * 1024;
|
|
11543
|
+
var CONSOLE_CAP = 200;
|
|
11544
|
+
function parseHostBrowserRequests(data) {
|
|
11545
|
+
let tools;
|
|
11546
|
+
try {
|
|
11547
|
+
tools = JSON.parse(data);
|
|
11548
|
+
} catch {
|
|
11549
|
+
return [];
|
|
11550
|
+
}
|
|
11551
|
+
if (!Array.isArray(tools))
|
|
11552
|
+
return [];
|
|
11553
|
+
const valid = [
|
|
11554
|
+
"navigate",
|
|
11555
|
+
"click",
|
|
11556
|
+
"read_console",
|
|
11557
|
+
"read_dom",
|
|
11558
|
+
"screenshot"
|
|
11559
|
+
];
|
|
11560
|
+
const out = [];
|
|
11561
|
+
for (const t of tools) {
|
|
11562
|
+
if (t.tool_name !== "host_browser" || t.status !== "awaiting_input")
|
|
11563
|
+
continue;
|
|
11564
|
+
if (!t.tool_call_id)
|
|
11565
|
+
continue;
|
|
11566
|
+
const action = t.args?.action;
|
|
11567
|
+
if (!action || !valid.includes(action))
|
|
11568
|
+
continue;
|
|
11569
|
+
out.push({
|
|
11570
|
+
toolCallId: t.tool_call_id,
|
|
11571
|
+
action,
|
|
11572
|
+
url: t.args?.url,
|
|
11573
|
+
selector: t.args?.selector,
|
|
11574
|
+
waitMs: typeof t.args?.wait_ms === "number" ? t.args.wait_ms : 500,
|
|
11575
|
+
fullPage: t.args?.full_page !== false
|
|
11576
|
+
});
|
|
11577
|
+
}
|
|
11578
|
+
return out;
|
|
11579
|
+
}
|
|
11580
|
+
async function applyBrowserAction(page, consoleBuffer, req) {
|
|
11581
|
+
try {
|
|
11582
|
+
switch (req.action) {
|
|
11583
|
+
case "navigate": {
|
|
11584
|
+
if (!req.url)
|
|
11585
|
+
return { error: true, message: "navigate requires `url`" };
|
|
11586
|
+
await page.goto(req.url, { waitUntil: "load", timeout: 30000 });
|
|
11587
|
+
if (req.waitMs > 0)
|
|
11588
|
+
await page.waitForTimeout(req.waitMs);
|
|
11589
|
+
const title = await page.title();
|
|
11590
|
+
const url = page.url();
|
|
11591
|
+
return { text: `Navigated to ${url} (title: ${title || "<none>"}).` };
|
|
11592
|
+
}
|
|
11593
|
+
case "click": {
|
|
11594
|
+
if (!req.selector)
|
|
11595
|
+
return { error: true, message: "click requires `selector`" };
|
|
11596
|
+
await page.click(req.selector, { timeout: 1e4 });
|
|
11597
|
+
if (req.waitMs > 0)
|
|
11598
|
+
await page.waitForTimeout(req.waitMs);
|
|
11599
|
+
return { text: `Clicked ${req.selector}.` };
|
|
11600
|
+
}
|
|
11601
|
+
case "read_console": {
|
|
11602
|
+
if (req.waitMs > 0)
|
|
11603
|
+
await page.waitForTimeout(req.waitMs);
|
|
11604
|
+
const entries = consoleBuffer.slice(-CONSOLE_CAP);
|
|
11605
|
+
if (entries.length === 0)
|
|
11606
|
+
return {
|
|
11607
|
+
text: "No console messages, page errors, or failed requests captured yet."
|
|
11608
|
+
};
|
|
11609
|
+
const lines = entries.map((e) => {
|
|
11610
|
+
if (e.kind === "console")
|
|
11611
|
+
return `[console.${e.level}] ${e.text}`;
|
|
11612
|
+
if (e.kind === "pageerror")
|
|
11613
|
+
return `[pageerror] ${e.text}`;
|
|
11614
|
+
return `[requestfailed] ${e.url} — ${e.text}`;
|
|
11615
|
+
});
|
|
11616
|
+
return {
|
|
11617
|
+
text: `Captured ${entries.length} console/error/network entries:
|
|
11618
|
+
${lines.join(`
|
|
11619
|
+
`)}`
|
|
11620
|
+
};
|
|
11621
|
+
}
|
|
11622
|
+
case "read_dom": {
|
|
11623
|
+
if (req.waitMs > 0)
|
|
11624
|
+
await page.waitForTimeout(req.waitMs);
|
|
11625
|
+
let html;
|
|
11626
|
+
if (req.selector) {
|
|
11627
|
+
html = await page.evaluate((sel) => {
|
|
11628
|
+
const el = document.querySelector(sel);
|
|
11629
|
+
return el ? el.outerHTML : `__NOT_FOUND__:${sel}`;
|
|
11630
|
+
}, req.selector);
|
|
11631
|
+
if (typeof html === "string" && html.startsWith("__NOT_FOUND__:"))
|
|
11632
|
+
return {
|
|
11633
|
+
error: true,
|
|
11634
|
+
message: `No element matched selector: ${req.selector}`
|
|
11635
|
+
};
|
|
11636
|
+
} else {
|
|
11637
|
+
html = await page.content();
|
|
11638
|
+
}
|
|
11639
|
+
const truncated = html.length > DOM_CAP;
|
|
11640
|
+
return {
|
|
11641
|
+
text: (truncated ? html.slice(0, DOM_CAP) + `
|
|
11642
|
+
…[truncated]` : html) || ""
|
|
11643
|
+
};
|
|
11644
|
+
}
|
|
11645
|
+
case "screenshot": {
|
|
11646
|
+
if (req.waitMs > 0)
|
|
11647
|
+
await page.waitForTimeout(req.waitMs);
|
|
11648
|
+
const buf = await page.screenshot({
|
|
11649
|
+
fullPage: req.fullPage,
|
|
11650
|
+
type: "png"
|
|
11651
|
+
});
|
|
11652
|
+
return { png: buf };
|
|
11653
|
+
}
|
|
11654
|
+
default:
|
|
11655
|
+
return { error: true, message: `unknown action: ${req.action}` };
|
|
11656
|
+
}
|
|
11657
|
+
} catch (e) {
|
|
11658
|
+
return { error: true, message: `${req.action} failed: ${String(e)}` };
|
|
11659
|
+
}
|
|
11660
|
+
}
|
|
11661
|
+
|
|
11662
|
+
class BrowserSession {
|
|
11663
|
+
browser = null;
|
|
11664
|
+
page = null;
|
|
11665
|
+
consoleBuffer = [];
|
|
11666
|
+
async ensurePage() {
|
|
11667
|
+
if (this.page)
|
|
11668
|
+
return this.page;
|
|
11669
|
+
const moduleName = "playwright";
|
|
11670
|
+
let chromium;
|
|
11671
|
+
try {
|
|
11672
|
+
({ chromium } = await import(moduleName));
|
|
11673
|
+
} catch {
|
|
11674
|
+
throw new Error("playwright is not installed on the runner. Install it to use host_browser: `npm i -D playwright && npx playwright install chromium`");
|
|
11675
|
+
}
|
|
11676
|
+
this.browser = await chromium.launch({ headless: true });
|
|
11677
|
+
this.page = await this.browser.newPage();
|
|
11678
|
+
this.page.on("console", (msg) => this.consoleBuffer.push({
|
|
11679
|
+
kind: "console",
|
|
11680
|
+
level: msg.type(),
|
|
11681
|
+
text: msg.text()
|
|
11682
|
+
}));
|
|
11683
|
+
this.page.on("pageerror", (err) => this.consoleBuffer.push({
|
|
11684
|
+
kind: "pageerror",
|
|
11685
|
+
text: String(err?.message ?? err)
|
|
11686
|
+
}));
|
|
11687
|
+
this.page.on("requestfailed", (request) => this.consoleBuffer.push({
|
|
11688
|
+
kind: "requestfailed",
|
|
11689
|
+
url: request.url(),
|
|
11690
|
+
text: request.failure()?.errorText ?? "failed"
|
|
11691
|
+
}));
|
|
11692
|
+
return this.page;
|
|
11693
|
+
}
|
|
11694
|
+
async run(req) {
|
|
11695
|
+
let page;
|
|
11696
|
+
try {
|
|
11697
|
+
page = await this.ensurePage();
|
|
11698
|
+
} catch (e) {
|
|
11699
|
+
return { error: true, message: String(e) };
|
|
11700
|
+
}
|
|
11701
|
+
return applyBrowserAction(page, this.consoleBuffer, req);
|
|
11702
|
+
}
|
|
11703
|
+
async close() {
|
|
11704
|
+
if (this.browser) {
|
|
11705
|
+
try {
|
|
11706
|
+
await this.browser.close();
|
|
11707
|
+
} catch {}
|
|
11708
|
+
this.browser = null;
|
|
11709
|
+
this.page = null;
|
|
11710
|
+
}
|
|
11711
|
+
}
|
|
11712
|
+
}
|
|
11713
|
+
|
|
11714
|
+
// src/lib/host-files.ts
|
|
11715
|
+
import { promises as fs6 } from "node:fs";
|
|
11716
|
+
import * as path5 from "node:path";
|
|
11717
|
+
var READ_CAP = 256 * 1024;
|
|
11718
|
+
function resolvePath(cwd, p) {
|
|
11719
|
+
return path5.isAbsolute(p) ? p : path5.resolve(cwd, p);
|
|
11720
|
+
}
|
|
11721
|
+
async function applyFileAction(cwd, req) {
|
|
11722
|
+
if (!req.path)
|
|
11723
|
+
return { error: true, message: `${req.action} requires \`path\`` };
|
|
11724
|
+
const abs = resolvePath(cwd, req.path);
|
|
11725
|
+
try {
|
|
11726
|
+
switch (req.action) {
|
|
11727
|
+
case "host_read": {
|
|
11728
|
+
const raw = await fs6.readFile(abs, "utf8");
|
|
11729
|
+
let body = raw;
|
|
11730
|
+
if (typeof req.offset === "number" || typeof req.limit === "number") {
|
|
11731
|
+
const lines = raw.split(`
|
|
11732
|
+
`);
|
|
11733
|
+
const start = Math.max(0, (req.offset ?? 1) - 1);
|
|
11734
|
+
const end = typeof req.limit === "number" ? start + req.limit : lines.length;
|
|
11735
|
+
body = lines.slice(start, end).join(`
|
|
11736
|
+
`);
|
|
11737
|
+
}
|
|
11738
|
+
const truncated = body.length > READ_CAP;
|
|
11739
|
+
if (truncated)
|
|
11740
|
+
body = body.slice(0, READ_CAP) + `
|
|
11741
|
+
…[truncated]`;
|
|
11742
|
+
return { text: body === "" ? "(empty file)" : body };
|
|
11743
|
+
}
|
|
11744
|
+
case "host_write": {
|
|
11745
|
+
if (typeof req.content !== "string")
|
|
11746
|
+
return { error: true, message: "host_write requires `content`" };
|
|
11747
|
+
await fs6.mkdir(path5.dirname(abs), { recursive: true });
|
|
11748
|
+
await fs6.writeFile(abs, req.content, "utf8");
|
|
11749
|
+
return {
|
|
11750
|
+
text: `Wrote ${Buffer.byteLength(req.content, "utf8")} bytes to ${req.path}.`
|
|
11751
|
+
};
|
|
11752
|
+
}
|
|
11753
|
+
case "host_edit": {
|
|
11754
|
+
if (typeof req.oldString !== "string" || typeof req.newString !== "string")
|
|
11755
|
+
return {
|
|
11756
|
+
error: true,
|
|
11757
|
+
message: "host_edit requires `old_string` and `new_string`"
|
|
11758
|
+
};
|
|
11759
|
+
if (req.oldString === req.newString)
|
|
11760
|
+
return {
|
|
11761
|
+
error: true,
|
|
11762
|
+
message: "old_string and new_string are identical"
|
|
11763
|
+
};
|
|
11764
|
+
const raw = await fs6.readFile(abs, "utf8");
|
|
11765
|
+
const count = req.oldString ? raw.split(req.oldString).length - 1 : 0;
|
|
11766
|
+
if (count === 0)
|
|
11767
|
+
return { error: true, message: "old_string not found in file" };
|
|
11768
|
+
if (count > 1 && !req.replaceAll)
|
|
11769
|
+
return {
|
|
11770
|
+
error: true,
|
|
11771
|
+
message: `old_string is not unique (${count} matches); pass replace_all=true to replace all`
|
|
11772
|
+
};
|
|
11773
|
+
const next = req.replaceAll ? raw.split(req.oldString).join(req.newString) : raw.replace(req.oldString, req.newString);
|
|
11774
|
+
await fs6.writeFile(abs, next, "utf8");
|
|
11775
|
+
return {
|
|
11776
|
+
text: `Edited ${req.path} (${req.replaceAll ? count : 1} replacement${(req.replaceAll ? count : 1) === 1 ? "" : "s"}).`
|
|
11777
|
+
};
|
|
11778
|
+
}
|
|
11779
|
+
default:
|
|
11780
|
+
return { error: true, message: `unknown file action: ${req.action}` };
|
|
11781
|
+
}
|
|
11782
|
+
} catch (e) {
|
|
11783
|
+
return { error: true, message: `${req.action} failed: ${String(e)}` };
|
|
11784
|
+
}
|
|
11785
|
+
}
|
|
11786
|
+
var VALID = ["host_read", "host_write", "host_edit"];
|
|
11787
|
+
function parseHostFileRequests(data) {
|
|
11788
|
+
let tools;
|
|
11789
|
+
try {
|
|
11790
|
+
tools = JSON.parse(data);
|
|
11791
|
+
} catch {
|
|
11792
|
+
return [];
|
|
11793
|
+
}
|
|
11794
|
+
if (!Array.isArray(tools))
|
|
11795
|
+
return [];
|
|
11796
|
+
const out = [];
|
|
11797
|
+
for (const t of tools) {
|
|
11798
|
+
if (t.status !== "awaiting_input" || !t.tool_call_id)
|
|
11799
|
+
continue;
|
|
11800
|
+
if (!VALID.includes(t.tool_name))
|
|
11801
|
+
continue;
|
|
11802
|
+
const a = t.args ?? {};
|
|
11803
|
+
out.push({
|
|
11804
|
+
toolCallId: t.tool_call_id,
|
|
11805
|
+
action: t.tool_name,
|
|
11806
|
+
path: a.path,
|
|
11807
|
+
offset: typeof a.offset === "number" ? a.offset : undefined,
|
|
11808
|
+
limit: typeof a.limit === "number" ? a.limit : undefined,
|
|
11809
|
+
content: typeof a.content === "string" ? a.content : undefined,
|
|
11810
|
+
oldString: typeof a.old_string === "string" ? a.old_string : undefined,
|
|
11811
|
+
newString: typeof a.new_string === "string" ? a.new_string : undefined,
|
|
11812
|
+
replaceAll: a.replace_all === true
|
|
11813
|
+
});
|
|
11814
|
+
}
|
|
11815
|
+
return out;
|
|
11816
|
+
}
|
|
11817
|
+
|
|
11818
|
+
// src/lib/local-agent.ts
|
|
11819
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
11820
|
+
var DEFAULT_LOCAL_AGENT_CMD = "codex exec";
|
|
11821
|
+
var DEFAULT_LOCAL_AGENT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11822
|
+
var MAX_OUTPUT2 = 256 * 1024;
|
|
11823
|
+
function buildAgentArgv(agentCmd, task) {
|
|
11824
|
+
const parts = agentCmd.trim().split(/\s+/).filter(Boolean);
|
|
11825
|
+
if (parts.length === 0)
|
|
11826
|
+
throw new Error("MARTHA_LOCAL_AGENT_CMD is empty");
|
|
11827
|
+
return [...parts, task];
|
|
11828
|
+
}
|
|
11829
|
+
async function runLocalAgent(task, opts) {
|
|
11830
|
+
const argv = buildAgentArgv(opts.agentCmd, task);
|
|
11831
|
+
const cap = opts.maxOutputBytes ?? MAX_OUTPUT2;
|
|
11832
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_LOCAL_AGENT_TIMEOUT_MS;
|
|
11833
|
+
return await new Promise((resolve2, reject) => {
|
|
11834
|
+
let out = "";
|
|
11835
|
+
let settled = false;
|
|
11836
|
+
let child;
|
|
11837
|
+
try {
|
|
11838
|
+
child = spawn2(argv[0], argv.slice(1), {
|
|
11839
|
+
cwd: opts.cwd,
|
|
11840
|
+
shell: false,
|
|
11841
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
11842
|
+
});
|
|
11843
|
+
} catch (e) {
|
|
11844
|
+
reject(e);
|
|
11845
|
+
return;
|
|
11846
|
+
}
|
|
11847
|
+
const append = (chunk) => {
|
|
11848
|
+
if (out.length < cap)
|
|
11849
|
+
out += chunk.toString("utf8");
|
|
11850
|
+
};
|
|
11851
|
+
child.stdout?.on("data", append);
|
|
11852
|
+
child.stderr?.on("data", append);
|
|
11853
|
+
const timer = setTimeout(() => {
|
|
11854
|
+
if (settled)
|
|
11855
|
+
return;
|
|
11856
|
+
settled = true;
|
|
11857
|
+
child.kill("SIGKILL");
|
|
11858
|
+
resolve2({
|
|
11859
|
+
text: out.slice(0, cap),
|
|
11860
|
+
exit_code: -1,
|
|
11861
|
+
timed_out: true
|
|
11862
|
+
});
|
|
11863
|
+
}, timeoutMs);
|
|
11864
|
+
child.on("error", (e) => {
|
|
11865
|
+
if (settled)
|
|
11866
|
+
return;
|
|
11867
|
+
settled = true;
|
|
11868
|
+
clearTimeout(timer);
|
|
11869
|
+
reject(e);
|
|
11870
|
+
});
|
|
11871
|
+
child.on("close", (code) => {
|
|
11872
|
+
if (settled)
|
|
11873
|
+
return;
|
|
11874
|
+
settled = true;
|
|
11875
|
+
clearTimeout(timer);
|
|
11876
|
+
resolve2({ text: out.slice(0, cap), exit_code: code ?? -1 });
|
|
11877
|
+
});
|
|
11878
|
+
});
|
|
11879
|
+
}
|
|
11880
|
+
function parseLocalAgentRequests(data) {
|
|
11881
|
+
let tools;
|
|
11882
|
+
try {
|
|
11883
|
+
tools = JSON.parse(data);
|
|
11884
|
+
} catch {
|
|
11885
|
+
return [];
|
|
11886
|
+
}
|
|
11887
|
+
if (!Array.isArray(tools))
|
|
11888
|
+
return [];
|
|
11889
|
+
const out = [];
|
|
11890
|
+
for (const t of tools) {
|
|
11891
|
+
if (t.tool_name !== "local_agent" || t.status !== "awaiting_input")
|
|
11892
|
+
continue;
|
|
11893
|
+
if (!t.tool_call_id || !t.args?.task)
|
|
11894
|
+
continue;
|
|
11895
|
+
out.push({
|
|
11896
|
+
toolCallId: t.tool_call_id,
|
|
11897
|
+
task: t.args.task,
|
|
11898
|
+
cwd: typeof t.args.cwd === "string" ? t.args.cwd : undefined
|
|
11899
|
+
});
|
|
11900
|
+
}
|
|
11901
|
+
return out;
|
|
11902
|
+
}
|
|
11903
|
+
|
|
11541
11904
|
// src/commands/chat.ts
|
|
11905
|
+
async function uploadImageArtifact(ctx, sessionId, png) {
|
|
11906
|
+
try {
|
|
11907
|
+
const mint = await ctx.api.post(`/api/chat/${encodeURIComponent(sessionId)}/local-artifact-upload`, {
|
|
11908
|
+
content_type: "image/png"
|
|
11909
|
+
});
|
|
11910
|
+
const put = await fetch(mint.upload_url, {
|
|
11911
|
+
method: "PUT",
|
|
11912
|
+
headers: { "Content-Type": "image/png" },
|
|
11913
|
+
body: new Uint8Array(png)
|
|
11914
|
+
});
|
|
11915
|
+
if (!put.ok)
|
|
11916
|
+
return { error: `image upload failed: HTTP ${put.status}` };
|
|
11917
|
+
return { storage_key: mint.storage_key };
|
|
11918
|
+
} catch (e) {
|
|
11919
|
+
return { error: `image upload failed: ${String(e)}` };
|
|
11920
|
+
}
|
|
11921
|
+
}
|
|
11542
11922
|
async function resolveHostExec(sessionId, toolCallId, command, state) {
|
|
11543
11923
|
const decision = decideFromJournal(await state.journal.lookup(sessionId, toolCallId));
|
|
11544
11924
|
if (decision.kind === "replay")
|
|
@@ -11596,27 +11976,89 @@ async function resolveHostScreenshot(ctx, sessionId, req, state) {
|
|
|
11596
11976
|
} catch (e) {
|
|
11597
11977
|
return { error: true, message: `screenshot failed: ${String(e)}` };
|
|
11598
11978
|
}
|
|
11979
|
+
const uploaded = await uploadImageArtifact(ctx, sessionId, png);
|
|
11980
|
+
if ("error" in uploaded)
|
|
11981
|
+
return { error: true, message: uploaded.error };
|
|
11982
|
+
return {
|
|
11983
|
+
text: `Screenshot of ${req.url} captured (${png.length} bytes).`,
|
|
11984
|
+
_images: [{ storage_key: uploaded.storage_key }]
|
|
11985
|
+
};
|
|
11986
|
+
}
|
|
11987
|
+
async function resolveHostBrowser(ctx, sessionId, req, state) {
|
|
11988
|
+
if (!state.autoYes) {
|
|
11989
|
+
process.stderr.write(source_default.yellow(`
|
|
11990
|
+
[host_browser] refused (re-run with --yes to allow): ${req.action}${req.url ? ` ${req.url}` : ""}
|
|
11991
|
+
`));
|
|
11992
|
+
return {
|
|
11993
|
+
declined: true,
|
|
11994
|
+
message: "Host browser declined; re-run with --yes to allow."
|
|
11995
|
+
};
|
|
11996
|
+
}
|
|
11997
|
+
process.stderr.write(source_default.dim(`
|
|
11998
|
+
[host_browser] ${req.action}${req.url ? ` ${req.url}` : ""}${req.selector ? ` (${req.selector})` : ""}
|
|
11999
|
+
`));
|
|
12000
|
+
if (!state.browser)
|
|
12001
|
+
state.browser = new BrowserSession;
|
|
12002
|
+
const result = await state.browser.run(req);
|
|
12003
|
+
if ("error" in result)
|
|
12004
|
+
return { error: true, message: result.message };
|
|
12005
|
+
if ("png" in result) {
|
|
12006
|
+
const uploaded = await uploadImageArtifact(ctx, sessionId, result.png);
|
|
12007
|
+
if ("error" in uploaded)
|
|
12008
|
+
return { error: true, message: uploaded.error };
|
|
12009
|
+
return {
|
|
12010
|
+
text: `Screenshot of the current page captured (${result.png.length} bytes).`,
|
|
12011
|
+
_images: [{ storage_key: uploaded.storage_key }]
|
|
12012
|
+
};
|
|
12013
|
+
}
|
|
12014
|
+
return result;
|
|
12015
|
+
}
|
|
12016
|
+
async function resolveHostFile(req, state) {
|
|
12017
|
+
const mutating = req.action === "host_write" || req.action === "host_edit";
|
|
12018
|
+
if (mutating && !state.autoYes) {
|
|
12019
|
+
process.stderr.write(source_default.yellow(`
|
|
12020
|
+
[${req.action}] refused (re-run with --yes to allow): ${req.path}
|
|
12021
|
+
`));
|
|
12022
|
+
return {
|
|
12023
|
+
declined: true,
|
|
12024
|
+
message: `${req.action} declined; re-run with --yes to allow.`
|
|
12025
|
+
};
|
|
12026
|
+
}
|
|
12027
|
+
process.stderr.write(source_default.dim(`
|
|
12028
|
+
[${req.action}] ${req.path ?? "<no path>"}
|
|
12029
|
+
`));
|
|
12030
|
+
const result = await applyFileAction(state.cwd, req);
|
|
12031
|
+
if ("error" in result)
|
|
12032
|
+
return { error: true, message: result.message };
|
|
12033
|
+
return result;
|
|
12034
|
+
}
|
|
12035
|
+
async function resolveLocalAgent(req, state) {
|
|
12036
|
+
if (!state.autoYes) {
|
|
12037
|
+
process.stderr.write(source_default.yellow(`
|
|
12038
|
+
[local_agent] refused (re-run with --yes to allow): ${req.task.slice(0, 80)}
|
|
12039
|
+
`));
|
|
12040
|
+
return {
|
|
12041
|
+
declined: true,
|
|
12042
|
+
message: "Local agent delegation declined; re-run with --yes to allow."
|
|
12043
|
+
};
|
|
12044
|
+
}
|
|
12045
|
+
const agentCmd = process.env.MARTHA_LOCAL_AGENT_CMD || DEFAULT_LOCAL_AGENT_CMD;
|
|
12046
|
+
const cwd = req.cwd ? `${state.cwd}/${req.cwd}` : state.cwd;
|
|
12047
|
+
process.stderr.write(source_default.dim(`
|
|
12048
|
+
[local_agent] (${agentCmd}) ${req.task.slice(0, 100)}
|
|
12049
|
+
`));
|
|
11599
12050
|
try {
|
|
11600
|
-
const
|
|
11601
|
-
content_type: "image/png"
|
|
11602
|
-
});
|
|
11603
|
-
const put = await fetch(mint.upload_url, {
|
|
11604
|
-
method: "PUT",
|
|
11605
|
-
headers: { "Content-Type": "image/png" },
|
|
11606
|
-
body: new Uint8Array(png)
|
|
11607
|
-
});
|
|
11608
|
-
if (!put.ok) {
|
|
11609
|
-
return {
|
|
11610
|
-
error: true,
|
|
11611
|
-
message: `image upload failed: HTTP ${put.status}`
|
|
11612
|
-
};
|
|
11613
|
-
}
|
|
12051
|
+
const result = await runLocalAgent(req.task, { cwd, agentCmd });
|
|
11614
12052
|
return {
|
|
11615
|
-
text:
|
|
11616
|
-
|
|
12053
|
+
text: result.text || "(local agent produced no output)",
|
|
12054
|
+
exit_code: result.exit_code,
|
|
12055
|
+
...result.timed_out ? { timed_out: true } : {}
|
|
11617
12056
|
};
|
|
11618
12057
|
} catch (e) {
|
|
11619
|
-
return {
|
|
12058
|
+
return {
|
|
12059
|
+
error: true,
|
|
12060
|
+
message: `local agent failed to start (${agentCmd}): ${String(e)}. ` + `Set MARTHA_LOCAL_AGENT_CMD to an installed agent command.`
|
|
12061
|
+
};
|
|
11620
12062
|
}
|
|
11621
12063
|
}
|
|
11622
12064
|
async function _postToolResult(ctx, sessionId, toolCallId, result, label) {
|
|
@@ -11644,6 +12086,30 @@ async function handleHostExecFrame(ctx, sessionId, data, state) {
|
|
|
11644
12086
|
const result = await resolveHostScreenshot(ctx, sessionId, req, state);
|
|
11645
12087
|
await _postToolResult(ctx, sessionId, id, result, "host_screenshot");
|
|
11646
12088
|
}
|
|
12089
|
+
for (const req of parseHostBrowserRequests(data)) {
|
|
12090
|
+
const id = req.toolCallId;
|
|
12091
|
+
if (state.handled.has(id))
|
|
12092
|
+
continue;
|
|
12093
|
+
state.handled.add(id);
|
|
12094
|
+
const result = await resolveHostBrowser(ctx, sessionId, req, state);
|
|
12095
|
+
await _postToolResult(ctx, sessionId, id, result, "host_browser");
|
|
12096
|
+
}
|
|
12097
|
+
for (const req of parseHostFileRequests(data)) {
|
|
12098
|
+
const id = req.toolCallId;
|
|
12099
|
+
if (state.handled.has(id))
|
|
12100
|
+
continue;
|
|
12101
|
+
state.handled.add(id);
|
|
12102
|
+
const result = await resolveHostFile(req, state);
|
|
12103
|
+
await _postToolResult(ctx, sessionId, id, result, req.action);
|
|
12104
|
+
}
|
|
12105
|
+
for (const req of parseLocalAgentRequests(data)) {
|
|
12106
|
+
const id = req.toolCallId;
|
|
12107
|
+
if (state.handled.has(id))
|
|
12108
|
+
continue;
|
|
12109
|
+
state.handled.add(id);
|
|
12110
|
+
const result = await resolveLocalAgent(req, state);
|
|
12111
|
+
await _postToolResult(ctx, sessionId, id, result, "local_agent");
|
|
12112
|
+
}
|
|
11647
12113
|
}
|
|
11648
12114
|
function parseSlashCommand(input) {
|
|
11649
12115
|
const trimmed = input.trim();
|
|
@@ -11768,6 +12234,9 @@ async function sendMessage(ctx, sessionId, message, opts = {}) {
|
|
|
11768
12234
|
return { response: fullResponse, aborted: true };
|
|
11769
12235
|
}
|
|
11770
12236
|
throw err;
|
|
12237
|
+
} finally {
|
|
12238
|
+
if (localExecState?.browser)
|
|
12239
|
+
await localExecState.browser.close();
|
|
11771
12240
|
}
|
|
11772
12241
|
} else {
|
|
11773
12242
|
const text = await res.text();
|
|
@@ -11818,11 +12287,11 @@ function formatToolStatus(data) {
|
|
|
11818
12287
|
}
|
|
11819
12288
|
}
|
|
11820
12289
|
function promptPick(rl, count) {
|
|
11821
|
-
return new Promise((
|
|
12290
|
+
return new Promise((resolve2) => {
|
|
11822
12291
|
rl.question(source_default.dim(`Pick 1-${count} (or Enter to cancel): `), (ans) => {
|
|
11823
12292
|
const trimmed = ans.trim();
|
|
11824
12293
|
if (!trimmed) {
|
|
11825
|
-
|
|
12294
|
+
resolve2(-1);
|
|
11826
12295
|
return;
|
|
11827
12296
|
}
|
|
11828
12297
|
const n = parseInt(trimmed, 10);
|
|
@@ -11830,10 +12299,10 @@ function promptPick(rl, count) {
|
|
|
11830
12299
|
process.stderr.write(source_default.yellow(`Invalid choice.
|
|
11831
12300
|
|
|
11832
12301
|
`));
|
|
11833
|
-
|
|
12302
|
+
resolve2(-1);
|
|
11834
12303
|
return;
|
|
11835
12304
|
}
|
|
11836
|
-
|
|
12305
|
+
resolve2(n - 1);
|
|
11837
12306
|
});
|
|
11838
12307
|
});
|
|
11839
12308
|
}
|
|
@@ -12276,9 +12745,9 @@ init_errors();
|
|
|
12276
12745
|
import { createInterface as createInterface3 } from "node:readline";
|
|
12277
12746
|
function prompt(rl, question, defaultValue) {
|
|
12278
12747
|
const suffix = defaultValue ? ` [${defaultValue}]` : "";
|
|
12279
|
-
return new Promise((
|
|
12748
|
+
return new Promise((resolve2) => {
|
|
12280
12749
|
rl.question(`${question}${suffix}: `, (answer) => {
|
|
12281
|
-
|
|
12750
|
+
resolve2(answer.trim() || defaultValue || "");
|
|
12282
12751
|
});
|
|
12283
12752
|
});
|
|
12284
12753
|
}
|
|
@@ -12574,14 +13043,14 @@ function registerConfigCommand(program2) {
|
|
|
12574
13043
|
if (!process.stdin.isTTY) {
|
|
12575
13044
|
throw new CLIError("Cannot confirm in non-interactive mode.", 4 /* Validation */, "Use --yes to skip confirmation.");
|
|
12576
13045
|
}
|
|
12577
|
-
const ok = await new Promise((
|
|
13046
|
+
const ok = await new Promise((resolve2) => {
|
|
12578
13047
|
const rl = createInterface3({
|
|
12579
13048
|
input: process.stdin,
|
|
12580
13049
|
output: process.stderr
|
|
12581
13050
|
});
|
|
12582
13051
|
rl.question(`Delete profile '${name}'? (y/N) `, (answer) => {
|
|
12583
13052
|
rl.close();
|
|
12584
|
-
|
|
13053
|
+
resolve2(answer.trim().toLowerCase() === "y");
|
|
12585
13054
|
});
|
|
12586
13055
|
});
|
|
12587
13056
|
if (!ok) {
|
|
@@ -12604,8 +13073,8 @@ function registerConfigCommand(program2) {
|
|
|
12604
13073
|
}
|
|
12605
13074
|
|
|
12606
13075
|
// src/commands/definitions-apply.ts
|
|
12607
|
-
import
|
|
12608
|
-
import
|
|
13076
|
+
import fs7 from "node:fs";
|
|
13077
|
+
import path6 from "node:path";
|
|
12609
13078
|
init_dist();
|
|
12610
13079
|
init_errors();
|
|
12611
13080
|
var VALID_KINDS = new Set([
|
|
@@ -12647,20 +13116,20 @@ var SERVER_GENERATED_FIELDS = new Set([
|
|
|
12647
13116
|
]);
|
|
12648
13117
|
var AGENT_MANAGED_FIELDS = new Set(["functions", "workflows"]);
|
|
12649
13118
|
function loadDefinitions(inputPath) {
|
|
12650
|
-
const resolved =
|
|
12651
|
-
if (!
|
|
13119
|
+
const resolved = path6.resolve(inputPath);
|
|
13120
|
+
if (!fs7.existsSync(resolved)) {
|
|
12652
13121
|
throw new CLIError(`Path not found: ${inputPath}`, 1 /* Error */);
|
|
12653
13122
|
}
|
|
12654
|
-
const stat =
|
|
13123
|
+
const stat = fs7.statSync(resolved);
|
|
12655
13124
|
if (stat.isDirectory()) {
|
|
12656
13125
|
return loadDirectory(resolved);
|
|
12657
13126
|
}
|
|
12658
13127
|
return loadFile(resolved);
|
|
12659
13128
|
}
|
|
12660
13129
|
function loadDirectory(dirPath) {
|
|
12661
|
-
const entries =
|
|
13130
|
+
const entries = fs7.readdirSync(dirPath).sort();
|
|
12662
13131
|
const validExts = new Set([".yaml", ".yml", ".json"]);
|
|
12663
|
-
const files = entries.filter((e) => validExts.has(
|
|
13132
|
+
const files = entries.filter((e) => validExts.has(path6.extname(e).toLowerCase())).map((e) => path6.join(dirPath, e));
|
|
12664
13133
|
if (files.length === 0) {
|
|
12665
13134
|
throw new CLIError(`No YAML or JSON files found in ${dirPath}`, 4 /* Validation */);
|
|
12666
13135
|
}
|
|
@@ -12671,8 +13140,8 @@ function loadDirectory(dirPath) {
|
|
|
12671
13140
|
return results;
|
|
12672
13141
|
}
|
|
12673
13142
|
function loadFile(filePath) {
|
|
12674
|
-
const content =
|
|
12675
|
-
const ext =
|
|
13143
|
+
const content = fs7.readFileSync(filePath, "utf-8");
|
|
13144
|
+
const ext = path6.extname(filePath).toLowerCase();
|
|
12676
13145
|
if (ext === ".json") {
|
|
12677
13146
|
const parsed = parseJsonFile(content, filePath);
|
|
12678
13147
|
return [toLocalDefinition(parsed, filePath)];
|
|
@@ -13094,7 +13563,7 @@ Examples:
|
|
|
13094
13563
|
}
|
|
13095
13564
|
|
|
13096
13565
|
// src/commands/definitions-export.ts
|
|
13097
|
-
import
|
|
13566
|
+
import fs8 from "node:fs";
|
|
13098
13567
|
init_errors();
|
|
13099
13568
|
async function exportDefinitions(ctx, opts, isJsonMode) {
|
|
13100
13569
|
const params = {};
|
|
@@ -13110,7 +13579,7 @@ async function exportDefinitions(ctx, opts, isJsonMode) {
|
|
|
13110
13579
|
const text = await res.text();
|
|
13111
13580
|
if (opts.output) {
|
|
13112
13581
|
try {
|
|
13113
|
-
|
|
13582
|
+
fs8.writeFileSync(opts.output, text);
|
|
13114
13583
|
} catch (err) {
|
|
13115
13584
|
throw new CLIError(`Failed to write ${opts.output}: ${err instanceof Error ? err.message : String(err)}`, 1 /* Error */);
|
|
13116
13585
|
}
|
|
@@ -13419,7 +13888,7 @@ function colorStatus(status) {
|
|
|
13419
13888
|
return fn(status);
|
|
13420
13889
|
}
|
|
13421
13890
|
function sleep(ms) {
|
|
13422
|
-
return new Promise((
|
|
13891
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
13423
13892
|
}
|
|
13424
13893
|
async function pollUntilDone(ctx, executionId, intervalMs = 2000, timeoutMs = 300000) {
|
|
13425
13894
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -13830,8 +14299,8 @@ function registerProjectionCommands(parentCmd, getCtx, isJson) {
|
|
|
13830
14299
|
} catch {}
|
|
13831
14300
|
}
|
|
13832
14301
|
if (opts.output) {
|
|
13833
|
-
const
|
|
13834
|
-
await
|
|
14302
|
+
const fs9 = await import("node:fs/promises");
|
|
14303
|
+
await fs9.writeFile(opts.output, toWrite, "utf-8");
|
|
13835
14304
|
if (!isJson()) {
|
|
13836
14305
|
console.error(source_default.dim(`Wrote ${format} projection of '${name}' to ${opts.output}`));
|
|
13837
14306
|
}
|
|
@@ -14238,8 +14707,8 @@ Usage:
|
|
|
14238
14707
|
});
|
|
14239
14708
|
parentCmd.command("self-grant <agent>").description("View or configure an agent's self-provisioning policy (request-then-approve). " + "With no flags, prints the current policy.").option("--enable", "Allow the agent to REQUEST capability grants").option("--disable", "Disallow self-provisioning").option("--scope <scope>", "Scope ceiling: none | read_only | read_write").option("--allow <ref...>", "Add allow-list refs (function:NAME or mcp:integration/name)").option("--remove <ref...>", "Remove allow-list refs").option("--collection-root <id...>", "Add collection-subtree roots the agent may self-grant collection-scoped functions into").option("--remove-collection-root <id...>", "Remove collection roots").option("--max-pending <n>", "Max concurrently-pending requests").action(async (agent, opts) => {
|
|
14240
14709
|
const ctx = getCtx();
|
|
14241
|
-
const
|
|
14242
|
-
const current = await ctx.api.get(
|
|
14710
|
+
const path7 = `${API_PATH}/${encodeURIComponent(agent)}`;
|
|
14711
|
+
const current = await ctx.api.get(path7);
|
|
14243
14712
|
const mutating = opts.enable || opts.disable || opts.scope !== undefined || (opts.allow?.length ?? 0) > 0 || (opts.remove?.length ?? 0) > 0 || (opts.collectionRoot?.length ?? 0) > 0 || (opts.removeCollectionRoot?.length ?? 0) > 0 || opts.maxPending !== undefined;
|
|
14244
14713
|
if (!mutating) {
|
|
14245
14714
|
if (isJson()) {
|
|
@@ -14291,7 +14760,7 @@ Usage:
|
|
|
14291
14760
|
roots.delete(c);
|
|
14292
14761
|
body.self_grant_collection_roots = [...roots];
|
|
14293
14762
|
}
|
|
14294
|
-
const updated = await ctx.api.put(
|
|
14763
|
+
const updated = await ctx.api.put(path7, body);
|
|
14295
14764
|
if (isJson()) {
|
|
14296
14765
|
console.log(JSON.stringify(policyView(updated), null, 2));
|
|
14297
14766
|
return;
|
|
@@ -14416,7 +14885,7 @@ var triggersConfig = {
|
|
|
14416
14885
|
};
|
|
14417
14886
|
|
|
14418
14887
|
// src/commands/documents.ts
|
|
14419
|
-
import
|
|
14888
|
+
import fs9 from "node:fs";
|
|
14420
14889
|
init_errors();
|
|
14421
14890
|
var TERMINAL_STATUSES2 = new Set(["ready", "error"]);
|
|
14422
14891
|
var SPINNER_FRAMES2 = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
@@ -14427,7 +14896,7 @@ function nextSpinner() {
|
|
|
14427
14896
|
return frame;
|
|
14428
14897
|
}
|
|
14429
14898
|
function sleep2(ms) {
|
|
14430
|
-
return new Promise((
|
|
14899
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
14431
14900
|
}
|
|
14432
14901
|
function formatBytes(bytes) {
|
|
14433
14902
|
if (bytes < 1024)
|
|
@@ -14718,7 +15187,7 @@ ${items.length} collections`));
|
|
|
14718
15187
|
});
|
|
14719
15188
|
cmd.command("upload <collection-id> <file>").description("Upload a document to a collection").option("--wait", "Wait for ingestion to complete").option("--follow", "Follow ingestion progress in real time").action(async (collectionId, filePath, opts) => {
|
|
14720
15189
|
const ctx = getCtx();
|
|
14721
|
-
if (!
|
|
15190
|
+
if (!fs9.existsSync(filePath)) {
|
|
14722
15191
|
throw new CLIError(`File not found: ${filePath}`, 4 /* Validation */);
|
|
14723
15192
|
}
|
|
14724
15193
|
const result = await ctx.api.upload(`/api/admin/collections/${encodeURIComponent(collectionId)}/documents`, filePath);
|
|
@@ -15350,18 +15819,18 @@ function registerApprovalCommands(program2) {
|
|
|
15350
15819
|
import { readFileSync } from "node:fs";
|
|
15351
15820
|
init_errors();
|
|
15352
15821
|
var truncate2 = (s, n) => s.length > n ? s.slice(0, n - 1) + "…" : s;
|
|
15353
|
-
var sleep3 = (ms, signal) => new Promise((
|
|
15822
|
+
var sleep3 = (ms, signal) => new Promise((resolve2) => {
|
|
15354
15823
|
if (signal?.aborted) {
|
|
15355
|
-
|
|
15824
|
+
resolve2();
|
|
15356
15825
|
return;
|
|
15357
15826
|
}
|
|
15358
15827
|
const t = setTimeout(() => {
|
|
15359
15828
|
signal?.removeEventListener("abort", onAbort);
|
|
15360
|
-
|
|
15829
|
+
resolve2();
|
|
15361
15830
|
}, ms);
|
|
15362
15831
|
const onAbort = () => {
|
|
15363
15832
|
clearTimeout(t);
|
|
15364
|
-
|
|
15833
|
+
resolve2();
|
|
15365
15834
|
};
|
|
15366
15835
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
15367
15836
|
});
|
|
@@ -16295,8 +16764,8 @@ ${data.length} spec(s)`));
|
|
|
16295
16764
|
Resources:
|
|
16296
16765
|
`));
|
|
16297
16766
|
for (const r of resources) {
|
|
16298
|
-
const
|
|
16299
|
-
console.log(` ${source_default.cyan(r.label || r.name)}` + source_default.dim(` → ${
|
|
16767
|
+
const path7 = r.path || `/${r.name}`;
|
|
16768
|
+
console.log(` ${source_default.cyan(r.label || r.name)}` + source_default.dim(` → ${path7}`));
|
|
16300
16769
|
}
|
|
16301
16770
|
}
|
|
16302
16771
|
try {
|
|
@@ -16316,14 +16785,14 @@ Functions:
|
|
|
16316
16785
|
console.log(source_default.dim(`
|
|
16317
16786
|
Proxy: martha integrations proxy ${name} GET /<path>`));
|
|
16318
16787
|
});
|
|
16319
|
-
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,
|
|
16788
|
+
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, path7, opts) => {
|
|
16320
16789
|
const ctx = getCtx();
|
|
16321
16790
|
const upperMethod = method.toUpperCase();
|
|
16322
16791
|
const allowed = new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
|
|
16323
16792
|
if (!allowed.has(upperMethod)) {
|
|
16324
16793
|
throw new CLIError(`Invalid method: ${method}`, 4 /* Validation */, "Allowed: GET, POST, PUT, PATCH, DELETE");
|
|
16325
16794
|
}
|
|
16326
|
-
const cleanPath =
|
|
16795
|
+
const cleanPath = path7.startsWith("/") ? path7.slice(1) : path7;
|
|
16327
16796
|
const proxyUrl = `/api/admin/plugins/${encodeURIComponent(name)}/${cleanPath}`;
|
|
16328
16797
|
const params = {};
|
|
16329
16798
|
if (opts.query) {
|
|
@@ -16533,8 +17002,8 @@ async function resolveCredentialValue(value) {
|
|
|
16533
17002
|
if (value === "-")
|
|
16534
17003
|
return readStdin();
|
|
16535
17004
|
if (value.startsWith("@")) {
|
|
16536
|
-
const
|
|
16537
|
-
return (await
|
|
17005
|
+
const fs10 = await import("node:fs/promises");
|
|
17006
|
+
return (await fs10.readFile(value.slice(1), "utf-8")).trim();
|
|
16538
17007
|
}
|
|
16539
17008
|
return value;
|
|
16540
17009
|
}
|
|
@@ -16649,8 +17118,8 @@ Notification connections`));
|
|
|
16649
17118
|
if (credentialValue === "-") {
|
|
16650
17119
|
credentialValue = await readStdin2();
|
|
16651
17120
|
} else if (credentialValue?.startsWith("@")) {
|
|
16652
|
-
const
|
|
16653
|
-
credentialValue = await
|
|
17121
|
+
const fs10 = await import("node:fs/promises");
|
|
17122
|
+
credentialValue = await fs10.readFile(credentialValue.slice(1), "utf-8");
|
|
16654
17123
|
}
|
|
16655
17124
|
if (!credentialValue) {
|
|
16656
17125
|
throw new Error("--credential-value is required (use '-' for stdin or '@path' for file).");
|
|
@@ -16700,11 +17169,11 @@ async function readStdin2() {
|
|
|
16700
17169
|
|
|
16701
17170
|
// src/commands/messaging.ts
|
|
16702
17171
|
init_errors();
|
|
16703
|
-
async function messagingFetch(baseUrl,
|
|
16704
|
-
if (/^(https?:)?\/\//i.test(
|
|
17172
|
+
async function messagingFetch(baseUrl, path7, opts) {
|
|
17173
|
+
if (/^(https?:)?\/\//i.test(path7)) {
|
|
16705
17174
|
throw new CLIError("Absolute URL paths are not allowed", 1 /* Error */);
|
|
16706
17175
|
}
|
|
16707
|
-
const url = new URL(
|
|
17176
|
+
const url = new URL(path7, baseUrl);
|
|
16708
17177
|
const headers = {
|
|
16709
17178
|
"Content-Type": "application/json"
|
|
16710
17179
|
};
|
|
@@ -17984,7 +18453,7 @@ ${models.length} models`));
|
|
|
17984
18453
|
}
|
|
17985
18454
|
|
|
17986
18455
|
// src/commands/wiki.ts
|
|
17987
|
-
import
|
|
18456
|
+
import fs10 from "node:fs";
|
|
17988
18457
|
init_errors();
|
|
17989
18458
|
function registerWikiCommands(program2) {
|
|
17990
18459
|
const cmd = program2.command("wiki").description("Manage tenant wiki pages, settings, schema, recompile (#245 D5.4)");
|
|
@@ -18031,14 +18500,14 @@ function registerWikiCommands(program2) {
|
|
|
18031
18500
|
console.log(source_default.dim(`
|
|
18032
18501
|
${pages.length} pages`));
|
|
18033
18502
|
});
|
|
18034
|
-
cmd.command("get <path>").description("Fetch the raw markdown body of a wiki page").option("--out <file>", "Write body to file instead of stdout").action(async (
|
|
18503
|
+
cmd.command("get <path>").description("Fetch the raw markdown body of a wiki page").option("--out <file>", "Write body to file instead of stdout").action(async (path7, opts) => {
|
|
18035
18504
|
const ctx = getCtx();
|
|
18036
|
-
const safe =
|
|
18505
|
+
const safe = path7.split("/").map(encodeURIComponent).join("/");
|
|
18037
18506
|
const resp = await ctx.api.getRaw(`/api/wiki/pages/${safe}`, {
|
|
18038
18507
|
headers: { Accept: "text/markdown,*/*" }
|
|
18039
18508
|
});
|
|
18040
18509
|
if (resp.status === 404) {
|
|
18041
|
-
throw new CLIError(`page not found: ${
|
|
18510
|
+
throw new CLIError(`page not found: ${path7}`, 3 /* NotFound */);
|
|
18042
18511
|
}
|
|
18043
18512
|
if (!resp.ok) {
|
|
18044
18513
|
const detail = await resp.text();
|
|
@@ -18046,16 +18515,16 @@ ${pages.length} pages`));
|
|
|
18046
18515
|
}
|
|
18047
18516
|
const body = await resp.text();
|
|
18048
18517
|
if (opts.out) {
|
|
18049
|
-
|
|
18518
|
+
fs10.writeFileSync(opts.out, body);
|
|
18050
18519
|
if (!isJson()) {
|
|
18051
18520
|
console.log(source_default.dim(`wrote ${body.length} bytes to ${opts.out}`));
|
|
18052
18521
|
} else {
|
|
18053
|
-
console.log(JSON.stringify({ path:
|
|
18522
|
+
console.log(JSON.stringify({ path: path7, bytes: body.length, file: opts.out }));
|
|
18054
18523
|
}
|
|
18055
18524
|
return;
|
|
18056
18525
|
}
|
|
18057
18526
|
if (isJson()) {
|
|
18058
|
-
console.log(JSON.stringify({ path:
|
|
18527
|
+
console.log(JSON.stringify({ path: path7, body, etag: resp.headers.get("ETag") }));
|
|
18059
18528
|
} else {
|
|
18060
18529
|
process.stdout.write(body);
|
|
18061
18530
|
}
|
|
@@ -18150,10 +18619,10 @@ ${pages.length} pages`));
|
|
|
18150
18619
|
}
|
|
18151
18620
|
if (opts.compilePromptOverrideFile) {
|
|
18152
18621
|
const file = String(opts.compilePromptOverrideFile);
|
|
18153
|
-
if (!
|
|
18622
|
+
if (!fs10.existsSync(file)) {
|
|
18154
18623
|
throw new CLIError(`override file not found: ${file}`, 3 /* NotFound */);
|
|
18155
18624
|
}
|
|
18156
|
-
const content =
|
|
18625
|
+
const content = fs10.readFileSync(file, "utf8");
|
|
18157
18626
|
const bytes = Buffer.byteLength(content, "utf8");
|
|
18158
18627
|
if (bytes > 16 * 1024) {
|
|
18159
18628
|
throw new CLIError(`compile prompt override exceeds 16 KB (${bytes} bytes)`, 4 /* Validation */);
|
|
@@ -18177,7 +18646,7 @@ ${pages.length} pages`));
|
|
|
18177
18646
|
const ctx = getCtx();
|
|
18178
18647
|
const resp = await ctx.api.get("/api/wiki/schema");
|
|
18179
18648
|
if (opts.out) {
|
|
18180
|
-
|
|
18649
|
+
fs10.writeFileSync(opts.out, resp.body);
|
|
18181
18650
|
if (!isJson()) {
|
|
18182
18651
|
console.log(source_default.dim(`wrote ${resp.body.length} bytes to ${opts.out}`));
|
|
18183
18652
|
} else {
|
|
@@ -18193,10 +18662,10 @@ ${pages.length} pages`));
|
|
|
18193
18662
|
});
|
|
18194
18663
|
schemaCmd.command("set").description("Replace the tenant wiki schema with the contents of <file>").requiredOption("--file <file>", "Path to schema markdown file").action(async (opts) => {
|
|
18195
18664
|
const ctx = getCtx();
|
|
18196
|
-
if (!
|
|
18665
|
+
if (!fs10.existsSync(opts.file)) {
|
|
18197
18666
|
throw new CLIError(`schema file not found: ${opts.file}`, 3 /* NotFound */);
|
|
18198
18667
|
}
|
|
18199
|
-
const body =
|
|
18668
|
+
const body = fs10.readFileSync(opts.file, "utf8");
|
|
18200
18669
|
const bytes = Buffer.byteLength(body, "utf8");
|
|
18201
18670
|
if (bytes > 64 * 1024) {
|
|
18202
18671
|
throw new CLIError(`schema body exceeds 64 KB (${bytes} bytes)`, 4 /* Validation */);
|
|
@@ -18532,7 +19001,7 @@ async function pollConnectionActive(ctx, connectionId) {
|
|
|
18532
19001
|
});
|
|
18533
19002
|
if (conn?.status === "active")
|
|
18534
19003
|
return;
|
|
18535
|
-
await new Promise((
|
|
19004
|
+
await new Promise((resolve2) => setTimeout(resolve2, intervalMs));
|
|
18536
19005
|
}
|
|
18537
19006
|
throw new CLIError(`Timed out waiting for OAuth authorization (${Math.round(timeoutMs / 1000)}s).`, 1 /* Error */, "The browser consent may not have completed. Re-run to try again, or check " + "the connection status in the admin UI.");
|
|
18538
19007
|
}
|
|
@@ -18594,7 +19063,7 @@ async function pollExecution(ctx, executionId, timeoutMs) {
|
|
|
18594
19063
|
if (["completed", "failed", "cancelled", "timeout"].includes(last.status)) {
|
|
18595
19064
|
return last;
|
|
18596
19065
|
}
|
|
18597
|
-
await new Promise((
|
|
19066
|
+
await new Promise((resolve2) => setTimeout(resolve2, 1000));
|
|
18598
19067
|
}
|
|
18599
19068
|
throw new CLIError(`Timed out waiting for MCP workflow execution ${executionId}`, 1 /* Error */, last ? `Last status: ${last.status}` : undefined);
|
|
18600
19069
|
}
|
|
@@ -18722,8 +19191,8 @@ async function resolveCredentialFlag(value) {
|
|
|
18722
19191
|
return Buffer.concat(chunks).toString("utf-8").trim();
|
|
18723
19192
|
}
|
|
18724
19193
|
if (value.startsWith("@")) {
|
|
18725
|
-
const
|
|
18726
|
-
return (await
|
|
19194
|
+
const fs11 = await import("node:fs/promises");
|
|
19195
|
+
return (await fs11.readFile(value.slice(1), "utf-8")).trim();
|
|
18727
19196
|
}
|
|
18728
19197
|
return value;
|
|
18729
19198
|
}
|
|
@@ -19909,19 +20378,19 @@ function registerDoctorCommand(program2) {
|
|
|
19909
20378
|
|
|
19910
20379
|
// src/commands/skill.ts
|
|
19911
20380
|
init_errors();
|
|
19912
|
-
import
|
|
19913
|
-
import
|
|
20381
|
+
import fs11 from "node:fs";
|
|
20382
|
+
import path7 from "node:path";
|
|
19914
20383
|
import { fileURLToPath } from "node:url";
|
|
19915
20384
|
function locateSkill() {
|
|
19916
|
-
const here =
|
|
20385
|
+
const here = path7.dirname(fileURLToPath(import.meta.url));
|
|
19917
20386
|
const candidates = [
|
|
19918
|
-
|
|
19919
|
-
|
|
19920
|
-
|
|
19921
|
-
|
|
20387
|
+
path7.join(here, "skills", "martha-cli", "SKILL.md"),
|
|
20388
|
+
path7.join(here, "..", "skills", "martha-cli", "SKILL.md"),
|
|
20389
|
+
path7.join(here, "..", "..", "..", "skills", "martha-cli", "SKILL.md"),
|
|
20390
|
+
path7.join(here, "..", "..", "skills", "martha-cli", "SKILL.md")
|
|
19922
20391
|
];
|
|
19923
20392
|
for (const p of candidates) {
|
|
19924
|
-
if (
|
|
20393
|
+
if (fs11.existsSync(p))
|
|
19925
20394
|
return p;
|
|
19926
20395
|
}
|
|
19927
20396
|
return null;
|
|
@@ -19931,7 +20400,7 @@ async function skillCommand() {
|
|
|
19931
20400
|
if (!skillPath) {
|
|
19932
20401
|
throw new CLIError("SKILL.md not found in the installed package. Reinstall via `npm i -g @aiaiai-pt/martha-cli` or `npx -y @aiaiai-pt/martha-cli@latest skill`.", 1 /* Error */);
|
|
19933
20402
|
}
|
|
19934
|
-
const body =
|
|
20403
|
+
const body = fs11.readFileSync(skillPath, "utf-8");
|
|
19935
20404
|
process.stdout.write(body);
|
|
19936
20405
|
}
|
|
19937
20406
|
function registerSkillCommand(program2) {
|