@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.
Files changed (2) hide show
  1. package/dist/index.js +555 -86
  2. 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.22.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 mint = await ctx.api.post(`/api/chat/${encodeURIComponent(sessionId)}/local-artifact-upload`, {
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: `Screenshot of ${req.url} captured (${png.length} bytes).`,
11616
- _images: [{ storage_key: mint.storage_key }]
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 { error: true, message: `image upload failed: ${String(e)}` };
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((resolve) => {
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
- resolve(-1);
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
- resolve(-1);
12302
+ resolve2(-1);
11834
12303
  return;
11835
12304
  }
11836
- resolve(n - 1);
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((resolve) => {
12748
+ return new Promise((resolve2) => {
12280
12749
  rl.question(`${question}${suffix}: `, (answer) => {
12281
- resolve(answer.trim() || defaultValue || "");
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((resolve) => {
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
- resolve(answer.trim().toLowerCase() === "y");
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 fs6 from "node:fs";
12608
- import path5 from "node:path";
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 = path5.resolve(inputPath);
12651
- if (!fs6.existsSync(resolved)) {
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 = fs6.statSync(resolved);
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 = fs6.readdirSync(dirPath).sort();
13130
+ const entries = fs7.readdirSync(dirPath).sort();
12662
13131
  const validExts = new Set([".yaml", ".yml", ".json"]);
12663
- const files = entries.filter((e) => validExts.has(path5.extname(e).toLowerCase())).map((e) => path5.join(dirPath, e));
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 = fs6.readFileSync(filePath, "utf-8");
12675
- const ext = path5.extname(filePath).toLowerCase();
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 fs7 from "node:fs";
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
- fs7.writeFileSync(opts.output, text);
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((resolve) => setTimeout(resolve, ms));
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 fs8 = await import("node:fs/promises");
13834
- await fs8.writeFile(opts.output, toWrite, "utf-8");
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 path6 = `${API_PATH}/${encodeURIComponent(agent)}`;
14242
- const current = await ctx.api.get(path6);
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(path6, body);
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 fs8 from "node:fs";
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((resolve) => setTimeout(resolve, ms));
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 (!fs8.existsSync(filePath)) {
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((resolve) => {
15822
+ var sleep3 = (ms, signal) => new Promise((resolve2) => {
15354
15823
  if (signal?.aborted) {
15355
- resolve();
15824
+ resolve2();
15356
15825
  return;
15357
15826
  }
15358
15827
  const t = setTimeout(() => {
15359
15828
  signal?.removeEventListener("abort", onAbort);
15360
- resolve();
15829
+ resolve2();
15361
15830
  }, ms);
15362
15831
  const onAbort = () => {
15363
15832
  clearTimeout(t);
15364
- resolve();
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 path6 = r.path || `/${r.name}`;
16299
- console.log(` ${source_default.cyan(r.label || r.name)}` + source_default.dim(` → ${path6}`));
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, path6, opts) => {
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 = path6.startsWith("/") ? path6.slice(1) : path6;
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 fs9 = await import("node:fs/promises");
16537
- return (await fs9.readFile(value.slice(1), "utf-8")).trim();
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 fs9 = await import("node:fs/promises");
16653
- credentialValue = await fs9.readFile(credentialValue.slice(1), "utf-8");
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, path6, opts) {
16704
- if (/^(https?:)?\/\//i.test(path6)) {
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(path6, baseUrl);
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 fs9 from "node:fs";
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 (path6, opts) => {
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 = path6.split("/").map(encodeURIComponent).join("/");
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: ${path6}`, 3 /* NotFound */);
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
- fs9.writeFileSync(opts.out, body);
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: path6, bytes: body.length, file: opts.out }));
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: path6, body, etag: resp.headers.get("ETag") }));
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 (!fs9.existsSync(file)) {
18622
+ if (!fs10.existsSync(file)) {
18154
18623
  throw new CLIError(`override file not found: ${file}`, 3 /* NotFound */);
18155
18624
  }
18156
- const content = fs9.readFileSync(file, "utf8");
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
- fs9.writeFileSync(opts.out, resp.body);
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 (!fs9.existsSync(opts.file)) {
18665
+ if (!fs10.existsSync(opts.file)) {
18197
18666
  throw new CLIError(`schema file not found: ${opts.file}`, 3 /* NotFound */);
18198
18667
  }
18199
- const body = fs9.readFileSync(opts.file, "utf8");
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((resolve) => setTimeout(resolve, intervalMs));
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((resolve) => setTimeout(resolve, 1000));
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 fs10 = await import("node:fs/promises");
18726
- return (await fs10.readFile(value.slice(1), "utf-8")).trim();
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 fs10 from "node:fs";
19913
- import path6 from "node:path";
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 = path6.dirname(fileURLToPath(import.meta.url));
20385
+ const here = path7.dirname(fileURLToPath(import.meta.url));
19917
20386
  const candidates = [
19918
- path6.join(here, "skills", "martha-cli", "SKILL.md"),
19919
- path6.join(here, "..", "skills", "martha-cli", "SKILL.md"),
19920
- path6.join(here, "..", "..", "..", "skills", "martha-cli", "SKILL.md"),
19921
- path6.join(here, "..", "..", "skills", "martha-cli", "SKILL.md")
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 (fs10.existsSync(p))
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 = fs10.readFileSync(skillPath, "utf-8");
20403
+ const body = fs11.readFileSync(skillPath, "utf-8");
19935
20404
  process.stdout.write(body);
19936
20405
  }
19937
20406
  function registerSkillCommand(program2) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiai-pt/martha-cli",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Terminal-first client for the Martha AI platform",
5
5
  "homepage": "https://docs.martha.nomadriver.co",
6
6
  "repository": {