@aiaiai-pt/martha-cli 0.25.0 → 0.26.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 +60 -15
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2158,6 +2158,8 @@ function extractDetail(body) {
2158
2158
  const nested = obj.detail;
2159
2159
  if (typeof nested.message === "string")
2160
2160
  return nested.message;
2161
+ if (typeof nested.code === "string")
2162
+ return nested.code;
2161
2163
  if (nested.code === "mcp_oauth_authorization_required" && typeof nested.authorization_url === "string") {
2162
2164
  return "MCP OAuth authorization required";
2163
2165
  }
@@ -11073,12 +11075,13 @@ ${items.length} ${config.typeNamePlural}`));
11073
11075
  }
11074
11076
 
11075
11077
  // src/commands/chat.ts
11078
+ import { createHash as createHash2 } from "node:crypto";
11076
11079
  import { createInterface as createInterface2 } from "node:readline";
11077
11080
  init_config();
11078
11081
  init_errors();
11079
11082
 
11080
11083
  // src/version.ts
11081
- var CLI_VERSION = "0.25.0";
11084
+ var CLI_VERSION = "0.26.0";
11082
11085
 
11083
11086
  // src/commands/sessions.ts
11084
11087
  function relativeTime(iso) {
@@ -11352,6 +11355,7 @@ function createApprovalTagStripper() {
11352
11355
 
11353
11356
  // src/lib/local-exec.ts
11354
11357
  import { spawn } from "node:child_process";
11358
+ import { randomBytes as randomBytes2 } from "node:crypto";
11355
11359
  import { closeSync, openSync, promises as fs5 } from "node:fs";
11356
11360
  import * as path4 from "node:path";
11357
11361
  var DEFAULT_TIMEOUT_MS = 20000;
@@ -11432,6 +11436,9 @@ async function runHostCommand(command, toolCallId, opts) {
11432
11436
  function safeName(s) {
11433
11437
  return s.replace(/[^a-zA-Z0-9_.-]/g, "_").slice(0, 120) || "task";
11434
11438
  }
11439
+ function mintExecSecret() {
11440
+ return randomBytes2(32).toString("hex");
11441
+ }
11435
11442
 
11436
11443
  class Journal {
11437
11444
  cwd;
@@ -11443,9 +11450,13 @@ class Journal {
11443
11450
  }
11444
11451
  async load() {
11445
11452
  try {
11446
- return JSON.parse(await fs5.readFile(this.file(), "utf8"));
11453
+ const raw = JSON.parse(await fs5.readFile(this.file(), "utf8"));
11454
+ if (raw["entries"] !== undefined) {
11455
+ return raw;
11456
+ }
11457
+ return { entries: raw, secrets: {} };
11447
11458
  } catch {
11448
- return {};
11459
+ return { entries: {}, secrets: {} };
11449
11460
  }
11450
11461
  }
11451
11462
  async save(data) {
@@ -11456,11 +11467,11 @@ class Journal {
11456
11467
  return `${sessionId}:${toolCallId}`;
11457
11468
  }
11458
11469
  async lookup(sessionId, toolCallId) {
11459
- return (await this.load())[Journal.key(sessionId, toolCallId)];
11470
+ return (await this.load()).entries[Journal.key(sessionId, toolCallId)];
11460
11471
  }
11461
11472
  async recordRunning(sessionId, toolCallId, command, now) {
11462
11473
  const data = await this.load();
11463
- data[Journal.key(sessionId, toolCallId)] = {
11474
+ data.entries[Journal.key(sessionId, toolCallId)] = {
11464
11475
  state: "running",
11465
11476
  command,
11466
11477
  started_at: now
@@ -11470,8 +11481,8 @@ class Journal {
11470
11481
  async recordDone(sessionId, toolCallId, result) {
11471
11482
  const data = await this.load();
11472
11483
  const k = Journal.key(sessionId, toolCallId);
11473
- const prev = data[k];
11474
- data[k] = {
11484
+ const prev = data.entries[k];
11485
+ data.entries[k] = {
11475
11486
  state: "done",
11476
11487
  command: prev?.command ?? "",
11477
11488
  started_at: prev?.started_at ?? "",
@@ -11479,6 +11490,14 @@ class Journal {
11479
11490
  };
11480
11491
  await this.save(data);
11481
11492
  }
11493
+ async recordSecret(sessionId, toolCallId, secret) {
11494
+ const data = await this.load();
11495
+ data.secrets[Journal.key(sessionId, toolCallId)] = secret;
11496
+ await this.save(data);
11497
+ }
11498
+ async lookupSecret(sessionId, toolCallId) {
11499
+ return (await this.load()).secrets[Journal.key(sessionId, toolCallId)];
11500
+ }
11482
11501
  }
11483
11502
  function decideFromJournal(entry) {
11484
11503
  if (!entry)
@@ -12104,9 +12123,28 @@ async function resolveLocalAgent(sessionId, toolCallId, req, state) {
12104
12123
  log_path: result.log_path
12105
12124
  };
12106
12125
  }
12107
- async function _postToolResult(ctx, sessionId, toolCallId, result, label) {
12126
+ async function claimGate(ctx, sessionId, toolCallId, state) {
12127
+ if (state.claimed.has(toolCallId))
12128
+ return;
12129
+ const secret = state.secrets.get(toolCallId) ?? mintExecSecret();
12130
+ const secretHash = createHash2("sha256").update(secret).digest("hex");
12131
+ state.secrets.set(toolCallId, secret);
12132
+ await state.journal.recordSecret(sessionId, toolCallId, secret);
12108
12133
  try {
12109
- await ctx.api.post(`/api/chat/${encodeURIComponent(sessionId)}/tool-result`, { tool_call_id: toolCallId, result });
12134
+ await ctx.api.post(`/api/chat/${encodeURIComponent(sessionId)}/tool-claim`, { tool_call_id: toolCallId, secret_hash: secretHash });
12135
+ state.claimed.add(toolCallId);
12136
+ } catch (e) {
12137
+ process.stderr.write(source_default.yellow(`[local-exec] failed to claim gate for ${toolCallId}: ${String(e)}
12138
+ `));
12139
+ }
12140
+ }
12141
+ async function _postToolResult(ctx, sessionId, toolCallId, result, label, execSecret) {
12142
+ try {
12143
+ await ctx.api.post(`/api/chat/${encodeURIComponent(sessionId)}/tool-result`, {
12144
+ tool_call_id: toolCallId,
12145
+ result,
12146
+ ...execSecret !== undefined ? { exec_secret: execSecret } : {}
12147
+ });
12110
12148
  } catch (e) {
12111
12149
  process.stderr.write(source_default.red(`[${label}] failed to POST result: ${String(e)}
12112
12150
  `));
@@ -12118,40 +12156,45 @@ async function handleHostExecFrame(ctx, sessionId, data, state) {
12118
12156
  if (state.handled.has(id))
12119
12157
  continue;
12120
12158
  state.handled.add(id);
12159
+ await claimGate(ctx, sessionId, id, state);
12121
12160
  const result = await resolveHostExec(sessionId, id, req.command, state);
12122
- await _postToolResult(ctx, sessionId, id, result, "host_exec");
12161
+ await _postToolResult(ctx, sessionId, id, result, "host_exec", state.secrets.get(id));
12123
12162
  }
12124
12163
  for (const req of parseHostScreenshotRequests(data)) {
12125
12164
  const id = req.toolCallId;
12126
12165
  if (state.handled.has(id))
12127
12166
  continue;
12128
12167
  state.handled.add(id);
12168
+ await claimGate(ctx, sessionId, id, state);
12129
12169
  const result = await resolveHostScreenshot(ctx, sessionId, req, state);
12130
- await _postToolResult(ctx, sessionId, id, result, "host_screenshot");
12170
+ await _postToolResult(ctx, sessionId, id, result, "host_screenshot", state.secrets.get(id));
12131
12171
  }
12132
12172
  for (const req of parseHostBrowserRequests(data)) {
12133
12173
  const id = req.toolCallId;
12134
12174
  if (state.handled.has(id))
12135
12175
  continue;
12136
12176
  state.handled.add(id);
12177
+ await claimGate(ctx, sessionId, id, state);
12137
12178
  const result = await resolveHostBrowser(ctx, sessionId, req, state);
12138
- await _postToolResult(ctx, sessionId, id, result, "host_browser");
12179
+ await _postToolResult(ctx, sessionId, id, result, "host_browser", state.secrets.get(id));
12139
12180
  }
12140
12181
  for (const req of parseHostFileRequests(data)) {
12141
12182
  const id = req.toolCallId;
12142
12183
  if (state.handled.has(id))
12143
12184
  continue;
12144
12185
  state.handled.add(id);
12186
+ await claimGate(ctx, sessionId, id, state);
12145
12187
  const result = await resolveHostFile(req, state);
12146
- await _postToolResult(ctx, sessionId, id, result, req.action);
12188
+ await _postToolResult(ctx, sessionId, id, result, req.action, state.secrets.get(id));
12147
12189
  }
12148
12190
  for (const req of parseLocalAgentRequests(data)) {
12149
12191
  const id = req.toolCallId;
12150
12192
  if (state.handled.has(id))
12151
12193
  continue;
12152
12194
  state.handled.add(id);
12195
+ await claimGate(ctx, sessionId, id, state);
12153
12196
  const result = await resolveLocalAgent(sessionId, id, req, state);
12154
- await _postToolResult(ctx, sessionId, id, result, "local_agent");
12197
+ await _postToolResult(ctx, sessionId, id, result, "local_agent", state.secrets.get(id));
12155
12198
  }
12156
12199
  }
12157
12200
  function parseSlashCommand(input) {
@@ -12209,7 +12252,9 @@ async function sendMessage(ctx, sessionId, message, opts = {}) {
12209
12252
  autoYes: opts.localExec.autoYes,
12210
12253
  localAgent: opts.localExec.agent,
12211
12254
  handled: new Set,
12212
- journal: new Journal(opts.localExec.cwd)
12255
+ journal: new Journal(opts.localExec.cwd),
12256
+ secrets: new Map,
12257
+ claimed: new Set
12213
12258
  } : null;
12214
12259
  const reqBody = { content: message };
12215
12260
  if (opts.localExec)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiai-pt/martha-cli",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "description": "Terminal-first client for the Martha AI platform",
5
5
  "homepage": "https://docs.martha.nomadriver.co",
6
6
  "repository": {