@aiaiai-pt/martha-cli 0.19.0 → 0.22.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/CHANGELOG.md +7 -0
- package/dist/index.js +564 -65
- package/package.json +1 -1
- package/skills/martha-cli/SKILL.md +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,13 @@ All notable changes to `@aiaiai-pt/martha-cli`. Format: [Keep a Changelog](https
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.20.0] — 2026-06-21
|
|
8
|
+
|
|
9
|
+
### Added — #649 agent→agent delegation surfaces
|
|
10
|
+
- `martha agents add-agent <coordinator> <sub-agent> [--set k=v]` grants a coordinator the ability to delegate to a sub-agent; `martha agents remove-agent <coordinator> <sub-agent>` revokes it; `martha agents agents <coordinator>` lists the granted sub-agents. All support `--json`. Backed by the existing `/api/admin/definitions/agents/{name}/agents` routes (#648). Self-delegation surfaces the API's 400 (`an agent cannot delegate to itself`); an unknown sub-agent returns 404.
|
|
11
|
+
|
|
12
|
+
<!-- NOTE: package.json was already at 0.19.0 on main while this CHANGELOG's latest entry was 0.16.1 — versions 0.17.0–0.19.0 shipped without CHANGELOG sections (pre-existing drift). This 0.20.0 bump is the MINOR bump for #649 off the real 0.19.0 base; the 0.17–0.19 gap is not reconstructed here. -->
|
|
13
|
+
|
|
7
14
|
## [0.16.1] — 2026-06-14
|
|
8
15
|
|
|
9
16
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -5,25 +5,43 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
function __accessProp(key) {
|
|
9
|
+
return this[key];
|
|
10
|
+
}
|
|
11
|
+
var __toESMCache_node;
|
|
12
|
+
var __toESMCache_esm;
|
|
8
13
|
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
+
var canCache = mod != null && typeof mod === "object";
|
|
15
|
+
if (canCache) {
|
|
16
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
+
var cached = cache.get(mod);
|
|
18
|
+
if (cached)
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
9
21
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
22
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
23
|
for (let key of __getOwnPropNames(mod))
|
|
12
24
|
if (!__hasOwnProp.call(to, key))
|
|
13
25
|
__defProp(to, key, {
|
|
14
|
-
get: (
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
15
27
|
enumerable: true
|
|
16
28
|
});
|
|
29
|
+
if (canCache)
|
|
30
|
+
cache.set(mod, to);
|
|
17
31
|
return to;
|
|
18
32
|
};
|
|
19
33
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
34
|
+
var __returnValue = (v) => v;
|
|
35
|
+
function __exportSetter(name, newValue) {
|
|
36
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
37
|
+
}
|
|
20
38
|
var __export = (target, all) => {
|
|
21
39
|
for (var name in all)
|
|
22
40
|
__defProp(target, name, {
|
|
23
41
|
get: all[name],
|
|
24
42
|
enumerable: true,
|
|
25
43
|
configurable: true,
|
|
26
|
-
set: (
|
|
44
|
+
set: __exportSetter.bind(all, name)
|
|
27
45
|
});
|
|
28
46
|
};
|
|
29
47
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -1001,7 +1019,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1001
1019
|
this._exitCallback = (err) => {
|
|
1002
1020
|
if (err.code !== "commander.executeSubCommandAsync") {
|
|
1003
1021
|
throw err;
|
|
1004
|
-
}
|
|
1022
|
+
}
|
|
1005
1023
|
};
|
|
1006
1024
|
}
|
|
1007
1025
|
return this;
|
|
@@ -11059,7 +11077,7 @@ import { createInterface as createInterface2 } from "node:readline";
|
|
|
11059
11077
|
init_errors();
|
|
11060
11078
|
|
|
11061
11079
|
// src/version.ts
|
|
11062
|
-
var CLI_VERSION = "0.
|
|
11080
|
+
var CLI_VERSION = "0.22.0";
|
|
11063
11081
|
|
|
11064
11082
|
// src/commands/sessions.ts
|
|
11065
11083
|
function relativeTime(iso) {
|
|
@@ -11331,7 +11349,302 @@ function createApprovalTagStripper() {
|
|
|
11331
11349
|
};
|
|
11332
11350
|
}
|
|
11333
11351
|
|
|
11352
|
+
// src/lib/local-exec.ts
|
|
11353
|
+
import { spawn } from "node:child_process";
|
|
11354
|
+
import { closeSync, openSync, promises as fs5 } from "node:fs";
|
|
11355
|
+
import * as path4 from "node:path";
|
|
11356
|
+
var DEFAULT_TIMEOUT_MS = 20000;
|
|
11357
|
+
var MAX_OUTPUT = 256 * 1024;
|
|
11358
|
+
function runnerDir(cwd) {
|
|
11359
|
+
return path4.join(cwd, ".martha-runner");
|
|
11360
|
+
}
|
|
11361
|
+
async function runHostCommand(command, toolCallId, opts) {
|
|
11362
|
+
const dir = runnerDir(opts.cwd);
|
|
11363
|
+
await fs5.mkdir(dir, { recursive: true });
|
|
11364
|
+
const logPath = path4.join(dir, `${safeName(toolCallId)}.log`);
|
|
11365
|
+
await fs5.writeFile(logPath, "");
|
|
11366
|
+
const fd = openSync(logPath, "a");
|
|
11367
|
+
const cap = opts.maxOutputBytes ?? MAX_OUTPUT;
|
|
11368
|
+
const readLog = async () => {
|
|
11369
|
+
try {
|
|
11370
|
+
const buf = await fs5.readFile(logPath);
|
|
11371
|
+
return buf.subarray(-cap).toString("utf8");
|
|
11372
|
+
} catch {
|
|
11373
|
+
return "";
|
|
11374
|
+
}
|
|
11375
|
+
};
|
|
11376
|
+
return await new Promise((resolve, reject) => {
|
|
11377
|
+
let settled = false;
|
|
11378
|
+
const child = spawn(command, {
|
|
11379
|
+
shell: true,
|
|
11380
|
+
cwd: opts.cwd,
|
|
11381
|
+
detached: true,
|
|
11382
|
+
stdio: ["ignore", fd, fd]
|
|
11383
|
+
});
|
|
11384
|
+
closeSync(fd);
|
|
11385
|
+
const timer = setTimeout(async () => {
|
|
11386
|
+
if (settled)
|
|
11387
|
+
return;
|
|
11388
|
+
settled = true;
|
|
11389
|
+
child.unref();
|
|
11390
|
+
resolve({
|
|
11391
|
+
stdout: await readLog(),
|
|
11392
|
+
stderr: "",
|
|
11393
|
+
exit_code: 0,
|
|
11394
|
+
backgrounded: true,
|
|
11395
|
+
pid: child.pid
|
|
11396
|
+
});
|
|
11397
|
+
}, opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
11398
|
+
child.on("error", (e) => {
|
|
11399
|
+
if (settled)
|
|
11400
|
+
return;
|
|
11401
|
+
settled = true;
|
|
11402
|
+
clearTimeout(timer);
|
|
11403
|
+
reject(e);
|
|
11404
|
+
});
|
|
11405
|
+
child.on("close", async (code) => {
|
|
11406
|
+
if (settled)
|
|
11407
|
+
return;
|
|
11408
|
+
settled = true;
|
|
11409
|
+
clearTimeout(timer);
|
|
11410
|
+
resolve({ stdout: await readLog(), stderr: "", exit_code: code ?? -1 });
|
|
11411
|
+
});
|
|
11412
|
+
});
|
|
11413
|
+
}
|
|
11414
|
+
function safeName(s) {
|
|
11415
|
+
return s.replace(/[^a-zA-Z0-9_.-]/g, "_").slice(0, 120) || "task";
|
|
11416
|
+
}
|
|
11417
|
+
|
|
11418
|
+
class Journal {
|
|
11419
|
+
cwd;
|
|
11420
|
+
constructor(cwd) {
|
|
11421
|
+
this.cwd = cwd;
|
|
11422
|
+
}
|
|
11423
|
+
file() {
|
|
11424
|
+
return path4.join(runnerDir(this.cwd), "journal.json");
|
|
11425
|
+
}
|
|
11426
|
+
async load() {
|
|
11427
|
+
try {
|
|
11428
|
+
return JSON.parse(await fs5.readFile(this.file(), "utf8"));
|
|
11429
|
+
} catch {
|
|
11430
|
+
return {};
|
|
11431
|
+
}
|
|
11432
|
+
}
|
|
11433
|
+
async save(data) {
|
|
11434
|
+
await fs5.mkdir(runnerDir(this.cwd), { recursive: true });
|
|
11435
|
+
await fs5.writeFile(this.file(), JSON.stringify(data, null, 2));
|
|
11436
|
+
}
|
|
11437
|
+
static key(sessionId, toolCallId) {
|
|
11438
|
+
return `${sessionId}:${toolCallId}`;
|
|
11439
|
+
}
|
|
11440
|
+
async lookup(sessionId, toolCallId) {
|
|
11441
|
+
return (await this.load())[Journal.key(sessionId, toolCallId)];
|
|
11442
|
+
}
|
|
11443
|
+
async recordRunning(sessionId, toolCallId, command, now) {
|
|
11444
|
+
const data = await this.load();
|
|
11445
|
+
data[Journal.key(sessionId, toolCallId)] = {
|
|
11446
|
+
state: "running",
|
|
11447
|
+
command,
|
|
11448
|
+
started_at: now
|
|
11449
|
+
};
|
|
11450
|
+
await this.save(data);
|
|
11451
|
+
}
|
|
11452
|
+
async recordDone(sessionId, toolCallId, result) {
|
|
11453
|
+
const data = await this.load();
|
|
11454
|
+
const k = Journal.key(sessionId, toolCallId);
|
|
11455
|
+
const prev = data[k];
|
|
11456
|
+
data[k] = {
|
|
11457
|
+
state: "done",
|
|
11458
|
+
command: prev?.command ?? "",
|
|
11459
|
+
started_at: prev?.started_at ?? "",
|
|
11460
|
+
result
|
|
11461
|
+
};
|
|
11462
|
+
await this.save(data);
|
|
11463
|
+
}
|
|
11464
|
+
}
|
|
11465
|
+
function decideFromJournal(entry) {
|
|
11466
|
+
if (!entry)
|
|
11467
|
+
return { kind: "fresh" };
|
|
11468
|
+
if (entry.state === "done" && entry.result)
|
|
11469
|
+
return { kind: "replay", result: entry.result };
|
|
11470
|
+
return { kind: "crashed", command: entry.command };
|
|
11471
|
+
}
|
|
11472
|
+
function parseHostExecRequests(data) {
|
|
11473
|
+
let tools;
|
|
11474
|
+
try {
|
|
11475
|
+
tools = JSON.parse(data);
|
|
11476
|
+
} catch {
|
|
11477
|
+
return [];
|
|
11478
|
+
}
|
|
11479
|
+
if (!Array.isArray(tools))
|
|
11480
|
+
return [];
|
|
11481
|
+
const out = [];
|
|
11482
|
+
for (const t of tools) {
|
|
11483
|
+
if (t.tool_name !== "host_exec" || t.status !== "awaiting_input")
|
|
11484
|
+
continue;
|
|
11485
|
+
if (!t.tool_call_id)
|
|
11486
|
+
continue;
|
|
11487
|
+
out.push({ toolCallId: t.tool_call_id, command: t.args?.command ?? "" });
|
|
11488
|
+
}
|
|
11489
|
+
return out;
|
|
11490
|
+
}
|
|
11491
|
+
|
|
11492
|
+
// src/lib/host-screenshot.ts
|
|
11493
|
+
async function captureScreenshot(url, opts) {
|
|
11494
|
+
const moduleName = "playwright";
|
|
11495
|
+
let chromium;
|
|
11496
|
+
try {
|
|
11497
|
+
({ chromium } = await import(moduleName));
|
|
11498
|
+
} catch {
|
|
11499
|
+
throw new Error("playwright is not installed on the runner. Install it to use host_screenshot: `npm i -D playwright && npx playwright install chromium`");
|
|
11500
|
+
}
|
|
11501
|
+
const browser = await chromium.launch({ headless: true });
|
|
11502
|
+
try {
|
|
11503
|
+
const page = await browser.newPage();
|
|
11504
|
+
await page.goto(url, { waitUntil: "load", timeout: 30000 });
|
|
11505
|
+
if (opts.waitMs > 0)
|
|
11506
|
+
await page.waitForTimeout(opts.waitMs);
|
|
11507
|
+
const buf = await page.screenshot({
|
|
11508
|
+
fullPage: opts.fullPage,
|
|
11509
|
+
type: "png"
|
|
11510
|
+
});
|
|
11511
|
+
return buf;
|
|
11512
|
+
} finally {
|
|
11513
|
+
await browser.close();
|
|
11514
|
+
}
|
|
11515
|
+
}
|
|
11516
|
+
function parseHostScreenshotRequests(data) {
|
|
11517
|
+
let tools;
|
|
11518
|
+
try {
|
|
11519
|
+
tools = JSON.parse(data);
|
|
11520
|
+
} catch {
|
|
11521
|
+
return [];
|
|
11522
|
+
}
|
|
11523
|
+
if (!Array.isArray(tools))
|
|
11524
|
+
return [];
|
|
11525
|
+
const out = [];
|
|
11526
|
+
for (const t of tools) {
|
|
11527
|
+
if (t.tool_name !== "host_screenshot" || t.status !== "awaiting_input")
|
|
11528
|
+
continue;
|
|
11529
|
+
if (!t.tool_call_id || !t.args?.url)
|
|
11530
|
+
continue;
|
|
11531
|
+
out.push({
|
|
11532
|
+
toolCallId: t.tool_call_id,
|
|
11533
|
+
url: t.args.url,
|
|
11534
|
+
fullPage: t.args.full_page !== false,
|
|
11535
|
+
waitMs: typeof t.args.wait_ms === "number" ? t.args.wait_ms : 500
|
|
11536
|
+
});
|
|
11537
|
+
}
|
|
11538
|
+
return out;
|
|
11539
|
+
}
|
|
11540
|
+
|
|
11334
11541
|
// src/commands/chat.ts
|
|
11542
|
+
async function resolveHostExec(sessionId, toolCallId, command, state) {
|
|
11543
|
+
const decision = decideFromJournal(await state.journal.lookup(sessionId, toolCallId));
|
|
11544
|
+
if (decision.kind === "replay")
|
|
11545
|
+
return decision.result;
|
|
11546
|
+
if (decision.kind === "crashed") {
|
|
11547
|
+
return {
|
|
11548
|
+
error: true,
|
|
11549
|
+
exit_code: -1,
|
|
11550
|
+
message: `A previous run did not complete; not re-running: ${decision.command}`
|
|
11551
|
+
};
|
|
11552
|
+
}
|
|
11553
|
+
if (!state.autoYes) {
|
|
11554
|
+
process.stderr.write(source_default.yellow(`
|
|
11555
|
+
[host_exec] refused (re-run with --yes to allow): ${command}
|
|
11556
|
+
`));
|
|
11557
|
+
return {
|
|
11558
|
+
declined: true,
|
|
11559
|
+
exit_code: -1,
|
|
11560
|
+
message: "Host execution declined; re-run with --yes to allow."
|
|
11561
|
+
};
|
|
11562
|
+
}
|
|
11563
|
+
process.stderr.write(source_default.dim(`
|
|
11564
|
+
[host_exec] $ ${command}
|
|
11565
|
+
(cwd: ${state.cwd})
|
|
11566
|
+
`));
|
|
11567
|
+
await state.journal.recordRunning(sessionId, toolCallId, command, new Date().toISOString());
|
|
11568
|
+
let result;
|
|
11569
|
+
try {
|
|
11570
|
+
result = await runHostCommand(command, toolCallId, { cwd: state.cwd });
|
|
11571
|
+
} catch (e) {
|
|
11572
|
+
result = { stdout: "", stderr: String(e), exit_code: -1 };
|
|
11573
|
+
}
|
|
11574
|
+
await state.journal.recordDone(sessionId, toolCallId, result);
|
|
11575
|
+
return result;
|
|
11576
|
+
}
|
|
11577
|
+
async function resolveHostScreenshot(ctx, sessionId, req, state) {
|
|
11578
|
+
if (!state.autoYes) {
|
|
11579
|
+
process.stderr.write(source_default.yellow(`
|
|
11580
|
+
[host_screenshot] refused (re-run with --yes to allow): ${req.url}
|
|
11581
|
+
`));
|
|
11582
|
+
return {
|
|
11583
|
+
declined: true,
|
|
11584
|
+
message: "Host screenshot declined; re-run with --yes to allow."
|
|
11585
|
+
};
|
|
11586
|
+
}
|
|
11587
|
+
process.stderr.write(source_default.dim(`
|
|
11588
|
+
[host_screenshot] ${req.url}
|
|
11589
|
+
`));
|
|
11590
|
+
let png;
|
|
11591
|
+
try {
|
|
11592
|
+
png = await captureScreenshot(req.url, {
|
|
11593
|
+
fullPage: req.fullPage,
|
|
11594
|
+
waitMs: req.waitMs
|
|
11595
|
+
});
|
|
11596
|
+
} catch (e) {
|
|
11597
|
+
return { error: true, message: `screenshot failed: ${String(e)}` };
|
|
11598
|
+
}
|
|
11599
|
+
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
|
+
}
|
|
11614
|
+
return {
|
|
11615
|
+
text: `Screenshot of ${req.url} captured (${png.length} bytes).`,
|
|
11616
|
+
_images: [{ storage_key: mint.storage_key }]
|
|
11617
|
+
};
|
|
11618
|
+
} catch (e) {
|
|
11619
|
+
return { error: true, message: `image upload failed: ${String(e)}` };
|
|
11620
|
+
}
|
|
11621
|
+
}
|
|
11622
|
+
async function _postToolResult(ctx, sessionId, toolCallId, result, label) {
|
|
11623
|
+
try {
|
|
11624
|
+
await ctx.api.post(`/api/chat/${encodeURIComponent(sessionId)}/tool-result`, { tool_call_id: toolCallId, result });
|
|
11625
|
+
} catch (e) {
|
|
11626
|
+
process.stderr.write(source_default.red(`[${label}] failed to POST result: ${String(e)}
|
|
11627
|
+
`));
|
|
11628
|
+
}
|
|
11629
|
+
}
|
|
11630
|
+
async function handleHostExecFrame(ctx, sessionId, data, state) {
|
|
11631
|
+
for (const req of parseHostExecRequests(data)) {
|
|
11632
|
+
const id = req.toolCallId;
|
|
11633
|
+
if (state.handled.has(id))
|
|
11634
|
+
continue;
|
|
11635
|
+
state.handled.add(id);
|
|
11636
|
+
const result = await resolveHostExec(sessionId, id, req.command, state);
|
|
11637
|
+
await _postToolResult(ctx, sessionId, id, result, "host_exec");
|
|
11638
|
+
}
|
|
11639
|
+
for (const req of parseHostScreenshotRequests(data)) {
|
|
11640
|
+
const id = req.toolCallId;
|
|
11641
|
+
if (state.handled.has(id))
|
|
11642
|
+
continue;
|
|
11643
|
+
state.handled.add(id);
|
|
11644
|
+
const result = await resolveHostScreenshot(ctx, sessionId, req, state);
|
|
11645
|
+
await _postToolResult(ctx, sessionId, id, result, "host_screenshot");
|
|
11646
|
+
}
|
|
11647
|
+
}
|
|
11335
11648
|
function parseSlashCommand(input) {
|
|
11336
11649
|
const trimmed = input.trim();
|
|
11337
11650
|
if (!trimmed.startsWith("/"))
|
|
@@ -11382,7 +11695,16 @@ async function sendMessage(ctx, sessionId, message, opts = {}) {
|
|
|
11382
11695
|
params.selected_agent = opts.agentName;
|
|
11383
11696
|
reqOpts.params = params;
|
|
11384
11697
|
}
|
|
11385
|
-
const
|
|
11698
|
+
const localExecState = opts.localExec ? {
|
|
11699
|
+
cwd: opts.localExec.cwd,
|
|
11700
|
+
autoYes: opts.localExec.autoYes,
|
|
11701
|
+
handled: new Set,
|
|
11702
|
+
journal: new Journal(opts.localExec.cwd)
|
|
11703
|
+
} : null;
|
|
11704
|
+
const reqBody = { content: message };
|
|
11705
|
+
if (opts.localExec)
|
|
11706
|
+
reqBody.capability_keys = ["local_exec"];
|
|
11707
|
+
const res = await ctx.api.postRaw(`/api/chat/${encodeURIComponent(sessionId)}`, reqBody, reqOpts);
|
|
11386
11708
|
const contentType = res.headers.get("content-type") ?? "";
|
|
11387
11709
|
let fullResponse = "";
|
|
11388
11710
|
if (contentType.includes("application/json")) {
|
|
@@ -11430,6 +11752,9 @@ async function sendMessage(ctx, sessionId, message, opts = {}) {
|
|
|
11430
11752
|
case "tool_status":
|
|
11431
11753
|
if (opts.onToolStatus)
|
|
11432
11754
|
opts.onToolStatus(event.data);
|
|
11755
|
+
if (localExecState) {
|
|
11756
|
+
await handleHostExecFrame(ctx, sessionId, event.data, localExecState);
|
|
11757
|
+
}
|
|
11433
11758
|
break;
|
|
11434
11759
|
case "clear":
|
|
11435
11760
|
fullResponse = "";
|
|
@@ -11650,7 +11975,7 @@ async function showHistoryPicker(ctx, rl) {
|
|
|
11650
11975
|
}
|
|
11651
11976
|
return data.items[idx].session_id;
|
|
11652
11977
|
}
|
|
11653
|
-
async function runRepl(ctx, initialSessionId, clientId, agentName, showTools, timeoutMs) {
|
|
11978
|
+
async function runRepl(ctx, initialSessionId, clientId, agentName, showTools, timeoutMs, localExec) {
|
|
11654
11979
|
let sessionId = initialSessionId;
|
|
11655
11980
|
process.stderr.write(source_default.dim(`martha v${CLI_VERSION} | session: ${sessionId.slice(0, 8)}...
|
|
11656
11981
|
`));
|
|
@@ -11787,6 +12112,7 @@ Cancelled.
|
|
|
11787
12112
|
try {
|
|
11788
12113
|
const result = await sendMessage(ctx, sessionId, input, {
|
|
11789
12114
|
timeoutMs,
|
|
12115
|
+
localExec,
|
|
11790
12116
|
onToken: (token) => process.stdout.write(stripper.push(token)),
|
|
11791
12117
|
onToolCall: showTools ? (data) => process.stderr.write(formatToolCall(data)) : undefined,
|
|
11792
12118
|
onToolResult: (data) => {
|
|
@@ -11842,7 +12168,7 @@ Error: ${err instanceof Error ? err.message : String(err)}
|
|
|
11842
12168
|
process.stderr.write(`
|
|
11843
12169
|
`);
|
|
11844
12170
|
}
|
|
11845
|
-
async function runOneShot(ctx, sessionId, message, isJson, clientId, agentName, showTools, timeoutMs) {
|
|
12171
|
+
async function runOneShot(ctx, sessionId, message, isJson, clientId, agentName, showTools, timeoutMs, localExec) {
|
|
11846
12172
|
const toolCalls = [];
|
|
11847
12173
|
function recordToolStatus(data) {
|
|
11848
12174
|
try {
|
|
@@ -11866,6 +12192,7 @@ async function runOneShot(ctx, sessionId, message, isJson, clientId, agentName,
|
|
|
11866
12192
|
clientId,
|
|
11867
12193
|
agentName,
|
|
11868
12194
|
timeoutMs,
|
|
12195
|
+
localExec,
|
|
11869
12196
|
onToolStatus: isJson ? recordToolStatus : showTools ? (data) => {
|
|
11870
12197
|
recordToolStatus(data);
|
|
11871
12198
|
process.stderr.write(formatToolStatus(data));
|
|
@@ -11889,7 +12216,7 @@ async function runOneShot(ctx, sessionId, message, isJson, clientId, agentName,
|
|
|
11889
12216
|
}
|
|
11890
12217
|
}
|
|
11891
12218
|
function registerChatCommand(program2) {
|
|
11892
|
-
program2.command("chat").description("Chat with a Martha agent").option("--session <id>", "Resume an existing session").option("--client <nameOrId>", "Use a specific client (name or ID)").option("--agent <name>", "Drive the chat with a specific copilot agent (defaults to profile.default_agent)").option("--message <text>", "Send a single message (non-interactive)").option("--show-tools", "Show tool calls and results during chat").option("--timeout <seconds>", "Per-message HTTP timeout in seconds (default: 600)").action(async (opts) => {
|
|
12219
|
+
program2.command("chat").description("Chat with a Martha agent").option("--session <id>", "Resume an existing session").option("--client <nameOrId>", "Use a specific client (name or ID)").option("--agent <name>", "Drive the chat with a specific copilot agent (defaults to profile.default_agent)").option("--message <text>", "Send a single message (non-interactive)").option("--show-tools", "Show tool calls and results during chat").option("--local-exec", "Allow the agent to run host tools on THIS machine (#568). Requires the server to have LOCAL_EXEC_ENABLED=1.").option("--yes", "Auto-approve host tool execution (required with --local-exec for non-interactive runs).").option("--timeout <seconds>", "Per-message HTTP timeout in seconds (default: 600)").action(async (opts) => {
|
|
11893
12220
|
const ctx = createContext({
|
|
11894
12221
|
profileOverride: program2.opts().profile,
|
|
11895
12222
|
verbose: program2.opts().verbose
|
|
@@ -11924,13 +12251,14 @@ function registerChatCommand(program2) {
|
|
|
11924
12251
|
}
|
|
11925
12252
|
const showTools = !!opts.showTools;
|
|
11926
12253
|
const timeoutMs = opts.timeout ? parseTimeoutSeconds(opts.timeout) * 1000 : undefined;
|
|
12254
|
+
const localExec = opts.localExec ? { cwd: process.cwd(), autoYes: !!opts.yes } : undefined;
|
|
11927
12255
|
if (opts.message) {
|
|
11928
|
-
await runOneShot(ctx, sessionId, opts.message, isJson, clientId, agentName, showTools, timeoutMs);
|
|
12256
|
+
await runOneShot(ctx, sessionId, opts.message, isJson, clientId, agentName, showTools, timeoutMs, localExec);
|
|
11929
12257
|
} else {
|
|
11930
12258
|
if (isJson) {
|
|
11931
12259
|
throw new CLIError("The --json flag requires --message for non-interactive mode.", 4 /* Validation */, 'Example: martha --json chat --message "hello"');
|
|
11932
12260
|
}
|
|
11933
|
-
await runRepl(ctx, sessionId, clientId, agentName, showTools, timeoutMs);
|
|
12261
|
+
await runRepl(ctx, sessionId, clientId, agentName, showTools, timeoutMs, localExec);
|
|
11934
12262
|
}
|
|
11935
12263
|
});
|
|
11936
12264
|
}
|
|
@@ -12276,20 +12604,27 @@ function registerConfigCommand(program2) {
|
|
|
12276
12604
|
}
|
|
12277
12605
|
|
|
12278
12606
|
// src/commands/definitions-apply.ts
|
|
12279
|
-
import
|
|
12280
|
-
import
|
|
12607
|
+
import fs6 from "node:fs";
|
|
12608
|
+
import path5 from "node:path";
|
|
12281
12609
|
init_dist();
|
|
12282
12610
|
init_errors();
|
|
12283
|
-
var VALID_KINDS = new Set([
|
|
12611
|
+
var VALID_KINDS = new Set([
|
|
12612
|
+
"Function",
|
|
12613
|
+
"Workflow",
|
|
12614
|
+
"Agent",
|
|
12615
|
+
"Trigger"
|
|
12616
|
+
]);
|
|
12284
12617
|
var KIND_TO_API_PATH = {
|
|
12285
12618
|
Function: "/api/admin/definitions/functions",
|
|
12286
12619
|
Workflow: "/api/admin/definitions/workflows",
|
|
12287
|
-
Agent: "/api/admin/definitions/agents"
|
|
12620
|
+
Agent: "/api/admin/definitions/agents",
|
|
12621
|
+
Trigger: "/api/admin/triggers"
|
|
12288
12622
|
};
|
|
12289
12623
|
var KIND_TO_PLURAL = {
|
|
12290
12624
|
Function: "Functions",
|
|
12291
12625
|
Workflow: "Workflows",
|
|
12292
|
-
Agent: "Agents"
|
|
12626
|
+
Agent: "Agents",
|
|
12627
|
+
Trigger: "Triggers"
|
|
12293
12628
|
};
|
|
12294
12629
|
var SERVER_GENERATED_FIELDS = new Set([
|
|
12295
12630
|
"id",
|
|
@@ -12312,20 +12647,20 @@ var SERVER_GENERATED_FIELDS = new Set([
|
|
|
12312
12647
|
]);
|
|
12313
12648
|
var AGENT_MANAGED_FIELDS = new Set(["functions", "workflows"]);
|
|
12314
12649
|
function loadDefinitions(inputPath) {
|
|
12315
|
-
const resolved =
|
|
12316
|
-
if (!
|
|
12650
|
+
const resolved = path5.resolve(inputPath);
|
|
12651
|
+
if (!fs6.existsSync(resolved)) {
|
|
12317
12652
|
throw new CLIError(`Path not found: ${inputPath}`, 1 /* Error */);
|
|
12318
12653
|
}
|
|
12319
|
-
const stat =
|
|
12654
|
+
const stat = fs6.statSync(resolved);
|
|
12320
12655
|
if (stat.isDirectory()) {
|
|
12321
12656
|
return loadDirectory(resolved);
|
|
12322
12657
|
}
|
|
12323
12658
|
return loadFile(resolved);
|
|
12324
12659
|
}
|
|
12325
12660
|
function loadDirectory(dirPath) {
|
|
12326
|
-
const entries =
|
|
12661
|
+
const entries = fs6.readdirSync(dirPath).sort();
|
|
12327
12662
|
const validExts = new Set([".yaml", ".yml", ".json"]);
|
|
12328
|
-
const files = entries.filter((e) => validExts.has(
|
|
12663
|
+
const files = entries.filter((e) => validExts.has(path5.extname(e).toLowerCase())).map((e) => path5.join(dirPath, e));
|
|
12329
12664
|
if (files.length === 0) {
|
|
12330
12665
|
throw new CLIError(`No YAML or JSON files found in ${dirPath}`, 4 /* Validation */);
|
|
12331
12666
|
}
|
|
@@ -12336,8 +12671,8 @@ function loadDirectory(dirPath) {
|
|
|
12336
12671
|
return results;
|
|
12337
12672
|
}
|
|
12338
12673
|
function loadFile(filePath) {
|
|
12339
|
-
const content =
|
|
12340
|
-
const ext =
|
|
12674
|
+
const content = fs6.readFileSync(filePath, "utf-8");
|
|
12675
|
+
const ext = path5.extname(filePath).toLowerCase();
|
|
12341
12676
|
if (ext === ".json") {
|
|
12342
12677
|
const parsed = parseJsonFile(content, filePath);
|
|
12343
12678
|
return [toLocalDefinition(parsed, filePath)];
|
|
@@ -12759,7 +13094,7 @@ Examples:
|
|
|
12759
13094
|
}
|
|
12760
13095
|
|
|
12761
13096
|
// src/commands/definitions-export.ts
|
|
12762
|
-
import
|
|
13097
|
+
import fs7 from "node:fs";
|
|
12763
13098
|
init_errors();
|
|
12764
13099
|
async function exportDefinitions(ctx, opts, isJsonMode) {
|
|
12765
13100
|
const params = {};
|
|
@@ -12775,7 +13110,7 @@ async function exportDefinitions(ctx, opts, isJsonMode) {
|
|
|
12775
13110
|
const text = await res.text();
|
|
12776
13111
|
if (opts.output) {
|
|
12777
13112
|
try {
|
|
12778
|
-
|
|
13113
|
+
fs7.writeFileSync(opts.output, text);
|
|
12779
13114
|
} catch (err) {
|
|
12780
13115
|
throw new CLIError(`Failed to write ${opts.output}: ${err instanceof Error ? err.message : String(err)}`, 1 /* Error */);
|
|
12781
13116
|
}
|
|
@@ -13495,8 +13830,8 @@ function registerProjectionCommands(parentCmd, getCtx, isJson) {
|
|
|
13495
13830
|
} catch {}
|
|
13496
13831
|
}
|
|
13497
13832
|
if (opts.output) {
|
|
13498
|
-
const
|
|
13499
|
-
await
|
|
13833
|
+
const fs8 = await import("node:fs/promises");
|
|
13834
|
+
await fs8.writeFile(opts.output, toWrite, "utf-8");
|
|
13500
13835
|
if (!isJson()) {
|
|
13501
13836
|
console.error(source_default.dim(`Wrote ${format} projection of '${name}' to ${opts.output}`));
|
|
13502
13837
|
}
|
|
@@ -13850,10 +14185,61 @@ Usage:
|
|
|
13850
14185
|
].join(" "));
|
|
13851
14186
|
}
|
|
13852
14187
|
});
|
|
14188
|
+
parentCmd.command("add-agent <coordinator> <subAgent>").description("Grant a coordinator agent the ability to delegate to a sub-agent").option("--set <items...>", "Config overrides (key=value)").action(async (coordinator, subAgent, opts) => {
|
|
14189
|
+
const ctx = getCtx();
|
|
14190
|
+
const body = {
|
|
14191
|
+
sub_agent_name: subAgent
|
|
14192
|
+
};
|
|
14193
|
+
if (opts.set) {
|
|
14194
|
+
body.config_overrides = parseSetFlags(opts.set);
|
|
14195
|
+
}
|
|
14196
|
+
const result = await ctx.api.post(`${API_PATH}/${encodeURIComponent(coordinator)}/agents`, body);
|
|
14197
|
+
if (isJson()) {
|
|
14198
|
+
console.log(JSON.stringify(result, null, 2));
|
|
14199
|
+
return;
|
|
14200
|
+
}
|
|
14201
|
+
console.log(`Agent '${coordinator}' may now delegate to '${subAgent}'`);
|
|
14202
|
+
});
|
|
14203
|
+
parentCmd.command("remove-agent <coordinator> <subAgent>").description("Revoke a coordinator's delegation grant to a sub-agent").action(async (coordinator, subAgent) => {
|
|
14204
|
+
const ctx = getCtx();
|
|
14205
|
+
const endpoint = `${API_PATH}/${encodeURIComponent(coordinator)}/agents/${encodeURIComponent(subAgent)}`;
|
|
14206
|
+
await ctx.api.del(endpoint);
|
|
14207
|
+
if (isJson()) {
|
|
14208
|
+
console.log(JSON.stringify({
|
|
14209
|
+
coordinator,
|
|
14210
|
+
sub_agent_name: subAgent,
|
|
14211
|
+
removed: true
|
|
14212
|
+
}));
|
|
14213
|
+
return;
|
|
14214
|
+
}
|
|
14215
|
+
console.log(`Revoked sub-agent '${subAgent}' from coordinator '${coordinator}'`);
|
|
14216
|
+
});
|
|
14217
|
+
parentCmd.command("agents <coordinator>").description("List the sub-agents a coordinator may delegate to").action(async (coordinator) => {
|
|
14218
|
+
const ctx = getCtx();
|
|
14219
|
+
const items = await ctx.api.get(`${API_PATH}/${encodeURIComponent(coordinator)}/agents`);
|
|
14220
|
+
if (isJson()) {
|
|
14221
|
+
console.log(JSON.stringify(items, null, 2));
|
|
14222
|
+
return;
|
|
14223
|
+
}
|
|
14224
|
+
if (items.length === 0) {
|
|
14225
|
+
console.log(source_default.dim("No sub-agents granted."));
|
|
14226
|
+
return;
|
|
14227
|
+
}
|
|
14228
|
+
const nameW = Math.max(8, ...items.map((s) => String(s.name ?? "").length));
|
|
14229
|
+
const header = ["SUB-AGENT", "DESCRIPTION"].map((h, i) => h.padEnd([nameW, 40][i])).join(" ");
|
|
14230
|
+
console.log(source_default.bold(header));
|
|
14231
|
+
console.log(source_default.dim("-".repeat(header.length)));
|
|
14232
|
+
for (const s of items) {
|
|
14233
|
+
console.log([
|
|
14234
|
+
String(s.name ?? "").padEnd(nameW),
|
|
14235
|
+
String(s.description ?? "-")
|
|
14236
|
+
].join(" "));
|
|
14237
|
+
}
|
|
14238
|
+
});
|
|
13853
14239
|
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) => {
|
|
13854
14240
|
const ctx = getCtx();
|
|
13855
|
-
const
|
|
13856
|
-
const current = await ctx.api.get(
|
|
14241
|
+
const path6 = `${API_PATH}/${encodeURIComponent(agent)}`;
|
|
14242
|
+
const current = await ctx.api.get(path6);
|
|
13857
14243
|
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;
|
|
13858
14244
|
if (!mutating) {
|
|
13859
14245
|
if (isJson()) {
|
|
@@ -13905,7 +14291,7 @@ Usage:
|
|
|
13905
14291
|
roots.delete(c);
|
|
13906
14292
|
body.self_grant_collection_roots = [...roots];
|
|
13907
14293
|
}
|
|
13908
|
-
const updated = await ctx.api.put(
|
|
14294
|
+
const updated = await ctx.api.put(path6, body);
|
|
13909
14295
|
if (isJson()) {
|
|
13910
14296
|
console.log(JSON.stringify(policyView(updated), null, 2));
|
|
13911
14297
|
return;
|
|
@@ -13917,8 +14303,120 @@ Usage:
|
|
|
13917
14303
|
}
|
|
13918
14304
|
};
|
|
13919
14305
|
|
|
14306
|
+
// src/commands/triggers.ts
|
|
14307
|
+
init_errors();
|
|
14308
|
+
function triggerKind(item) {
|
|
14309
|
+
if (item.schedule_config)
|
|
14310
|
+
return "schedule";
|
|
14311
|
+
if (item.deadman_config)
|
|
14312
|
+
return "deadman";
|
|
14313
|
+
return "event";
|
|
14314
|
+
}
|
|
14315
|
+
function scheduleSummary(sched) {
|
|
14316
|
+
return String(sched.cron ?? sched.interval ?? sched.spec ?? "schedule");
|
|
14317
|
+
}
|
|
14318
|
+
var triggersConfig = {
|
|
14319
|
+
typeName: "trigger",
|
|
14320
|
+
typeNamePlural: "triggers",
|
|
14321
|
+
apiPath: "/api/admin/triggers",
|
|
14322
|
+
extractList: (data) => {
|
|
14323
|
+
if (!Array.isArray(data)) {
|
|
14324
|
+
throw new Error("Unexpected API response shape for triggers list");
|
|
14325
|
+
}
|
|
14326
|
+
return data;
|
|
14327
|
+
},
|
|
14328
|
+
listColumns: [
|
|
14329
|
+
{ header: "NAME", accessor: (item) => String(item.name ?? "") },
|
|
14330
|
+
{ header: "KIND", accessor: (item) => triggerKind(item) },
|
|
14331
|
+
{
|
|
14332
|
+
header: "EVENT / SCHEDULE",
|
|
14333
|
+
accessor: (item) => {
|
|
14334
|
+
const sched = item.schedule_config;
|
|
14335
|
+
if (sched)
|
|
14336
|
+
return scheduleSummary(sched);
|
|
14337
|
+
return String(item.event_type ?? "-");
|
|
14338
|
+
}
|
|
14339
|
+
},
|
|
14340
|
+
{ header: "TARGET", accessor: (item) => String(item.target_name ?? "-") },
|
|
14341
|
+
{
|
|
14342
|
+
header: "STATUS",
|
|
14343
|
+
accessor: (item) => item.is_active !== false ? "active" : "inactive"
|
|
14344
|
+
}
|
|
14345
|
+
],
|
|
14346
|
+
renderDetail: (t) => {
|
|
14347
|
+
console.log(source_default.bold(`Trigger: ${t.name}
|
|
14348
|
+
`));
|
|
14349
|
+
if (t.description)
|
|
14350
|
+
console.log(` ${t.description}
|
|
14351
|
+
`);
|
|
14352
|
+
console.log(` Kind: ${triggerKind(t)}`);
|
|
14353
|
+
console.log(` Status: ${t.is_active !== false ? source_default.green("active") : source_default.dim("inactive")}`);
|
|
14354
|
+
if (t.event_type)
|
|
14355
|
+
console.log(` Event type: ${t.event_type}`);
|
|
14356
|
+
const filter = t.event_filter;
|
|
14357
|
+
if (filter && Object.keys(filter).length)
|
|
14358
|
+
console.log(` Event filter: ${JSON.stringify(filter)}`);
|
|
14359
|
+
const sched = t.schedule_config;
|
|
14360
|
+
if (sched)
|
|
14361
|
+
console.log(` Schedule: ${scheduleSummary(sched)} ${source_default.dim(JSON.stringify(sched))}`);
|
|
14362
|
+
const deadman = t.deadman_config;
|
|
14363
|
+
if (deadman)
|
|
14364
|
+
console.log(` Deadman: ${JSON.stringify(deadman)}`);
|
|
14365
|
+
console.log(` Target: ${t.target_type ?? "workflow"} → ${t.target_name ?? "-"}`);
|
|
14366
|
+
const mapping = t.input_mapping;
|
|
14367
|
+
if (mapping && Object.keys(mapping).length) {
|
|
14368
|
+
console.log(`
|
|
14369
|
+
${source_default.bold("Input mapping:")}`);
|
|
14370
|
+
for (const [k, v] of Object.entries(mapping)) {
|
|
14371
|
+
console.log(` ${k.padEnd(20)} ${source_default.dim(String(v))}`);
|
|
14372
|
+
}
|
|
14373
|
+
}
|
|
14374
|
+
if (t.dedup_key)
|
|
14375
|
+
console.log(`
|
|
14376
|
+
Dedup key: ${t.dedup_key}`);
|
|
14377
|
+
if (t.version)
|
|
14378
|
+
console.log(` Version: ${t.version}`);
|
|
14379
|
+
if (t.updated_at)
|
|
14380
|
+
console.log(` Updated: ${t.updated_at}`);
|
|
14381
|
+
},
|
|
14382
|
+
extraListOptions: (cmd) => {
|
|
14383
|
+
cmd.option("--active", "Only active triggers");
|
|
14384
|
+
},
|
|
14385
|
+
buildListParams: (opts) => {
|
|
14386
|
+
const params = {};
|
|
14387
|
+
if (opts.active)
|
|
14388
|
+
params.active = "true";
|
|
14389
|
+
return params;
|
|
14390
|
+
},
|
|
14391
|
+
extraCommands: (parentCmd, getCtx, isJson) => {
|
|
14392
|
+
parentCmd.command("test <name>").description("Dry-run a trigger against a sample event (no workflow started)").requiredOption("--event <json>", "Sample CloudEvent JSON to match against").action(async (name, opts) => {
|
|
14393
|
+
let event;
|
|
14394
|
+
try {
|
|
14395
|
+
event = JSON.parse(opts.event);
|
|
14396
|
+
} catch {
|
|
14397
|
+
throw new CLIError("--event must be valid JSON", 4 /* Validation */, `Example: martha triggers test ${name} --event '{"type":"facility_booking.booking.created","data":{}}'`);
|
|
14398
|
+
}
|
|
14399
|
+
const ctx = getCtx();
|
|
14400
|
+
const res = await ctx.api.post(`/api/admin/triggers/${encodeURIComponent(name)}/test`, { event });
|
|
14401
|
+
if (isJson()) {
|
|
14402
|
+
console.log(JSON.stringify(res, null, 2));
|
|
14403
|
+
return;
|
|
14404
|
+
}
|
|
14405
|
+
console.log(res.matched ? source_default.green(`✓ trigger '${name}' WOULD fire for this event`) : source_default.yellow(`✗ trigger '${name}' would NOT fire for this event`));
|
|
14406
|
+
console.log(` Event-type / filter match: ${res.filter_result ? source_default.green("yes") : source_default.red("no")}`);
|
|
14407
|
+
if (res.input_mapping_result) {
|
|
14408
|
+
console.log(`
|
|
14409
|
+
${source_default.bold("Resolved input mapping:")}`);
|
|
14410
|
+
for (const [k, v] of Object.entries(res.input_mapping_result)) {
|
|
14411
|
+
console.log(` ${k.padEnd(20)} ${source_default.dim(JSON.stringify(v))}`);
|
|
14412
|
+
}
|
|
14413
|
+
}
|
|
14414
|
+
});
|
|
14415
|
+
}
|
|
14416
|
+
};
|
|
14417
|
+
|
|
13920
14418
|
// src/commands/documents.ts
|
|
13921
|
-
import
|
|
14419
|
+
import fs8 from "node:fs";
|
|
13922
14420
|
init_errors();
|
|
13923
14421
|
var TERMINAL_STATUSES2 = new Set(["ready", "error"]);
|
|
13924
14422
|
var SPINNER_FRAMES2 = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
@@ -14220,7 +14718,7 @@ ${items.length} collections`));
|
|
|
14220
14718
|
});
|
|
14221
14719
|
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) => {
|
|
14222
14720
|
const ctx = getCtx();
|
|
14223
|
-
if (!
|
|
14721
|
+
if (!fs8.existsSync(filePath)) {
|
|
14224
14722
|
throw new CLIError(`File not found: ${filePath}`, 4 /* Validation */);
|
|
14225
14723
|
}
|
|
14226
14724
|
const result = await ctx.api.upload(`/api/admin/collections/${encodeURIComponent(collectionId)}/documents`, filePath);
|
|
@@ -15797,8 +16295,8 @@ ${data.length} spec(s)`));
|
|
|
15797
16295
|
Resources:
|
|
15798
16296
|
`));
|
|
15799
16297
|
for (const r of resources) {
|
|
15800
|
-
const
|
|
15801
|
-
console.log(` ${source_default.cyan(r.label || r.name)}` + source_default.dim(` → ${
|
|
16298
|
+
const path6 = r.path || `/${r.name}`;
|
|
16299
|
+
console.log(` ${source_default.cyan(r.label || r.name)}` + source_default.dim(` → ${path6}`));
|
|
15802
16300
|
}
|
|
15803
16301
|
}
|
|
15804
16302
|
try {
|
|
@@ -15818,14 +16316,14 @@ Functions:
|
|
|
15818
16316
|
console.log(source_default.dim(`
|
|
15819
16317
|
Proxy: martha integrations proxy ${name} GET /<path>`));
|
|
15820
16318
|
});
|
|
15821
|
-
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,
|
|
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) => {
|
|
15822
16320
|
const ctx = getCtx();
|
|
15823
16321
|
const upperMethod = method.toUpperCase();
|
|
15824
16322
|
const allowed = new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
|
|
15825
16323
|
if (!allowed.has(upperMethod)) {
|
|
15826
16324
|
throw new CLIError(`Invalid method: ${method}`, 4 /* Validation */, "Allowed: GET, POST, PUT, PATCH, DELETE");
|
|
15827
16325
|
}
|
|
15828
|
-
const cleanPath =
|
|
16326
|
+
const cleanPath = path6.startsWith("/") ? path6.slice(1) : path6;
|
|
15829
16327
|
const proxyUrl = `/api/admin/plugins/${encodeURIComponent(name)}/${cleanPath}`;
|
|
15830
16328
|
const params = {};
|
|
15831
16329
|
if (opts.query) {
|
|
@@ -16035,8 +16533,8 @@ async function resolveCredentialValue(value) {
|
|
|
16035
16533
|
if (value === "-")
|
|
16036
16534
|
return readStdin();
|
|
16037
16535
|
if (value.startsWith("@")) {
|
|
16038
|
-
const
|
|
16039
|
-
return (await
|
|
16536
|
+
const fs9 = await import("node:fs/promises");
|
|
16537
|
+
return (await fs9.readFile(value.slice(1), "utf-8")).trim();
|
|
16040
16538
|
}
|
|
16041
16539
|
return value;
|
|
16042
16540
|
}
|
|
@@ -16151,8 +16649,8 @@ Notification connections`));
|
|
|
16151
16649
|
if (credentialValue === "-") {
|
|
16152
16650
|
credentialValue = await readStdin2();
|
|
16153
16651
|
} else if (credentialValue?.startsWith("@")) {
|
|
16154
|
-
const
|
|
16155
|
-
credentialValue = await
|
|
16652
|
+
const fs9 = await import("node:fs/promises");
|
|
16653
|
+
credentialValue = await fs9.readFile(credentialValue.slice(1), "utf-8");
|
|
16156
16654
|
}
|
|
16157
16655
|
if (!credentialValue) {
|
|
16158
16656
|
throw new Error("--credential-value is required (use '-' for stdin or '@path' for file).");
|
|
@@ -16202,11 +16700,11 @@ async function readStdin2() {
|
|
|
16202
16700
|
|
|
16203
16701
|
// src/commands/messaging.ts
|
|
16204
16702
|
init_errors();
|
|
16205
|
-
async function messagingFetch(baseUrl,
|
|
16206
|
-
if (/^(https?:)?\/\//i.test(
|
|
16703
|
+
async function messagingFetch(baseUrl, path6, opts) {
|
|
16704
|
+
if (/^(https?:)?\/\//i.test(path6)) {
|
|
16207
16705
|
throw new CLIError("Absolute URL paths are not allowed", 1 /* Error */);
|
|
16208
16706
|
}
|
|
16209
|
-
const url = new URL(
|
|
16707
|
+
const url = new URL(path6, baseUrl);
|
|
16210
16708
|
const headers = {
|
|
16211
16709
|
"Content-Type": "application/json"
|
|
16212
16710
|
};
|
|
@@ -17486,7 +17984,7 @@ ${models.length} models`));
|
|
|
17486
17984
|
}
|
|
17487
17985
|
|
|
17488
17986
|
// src/commands/wiki.ts
|
|
17489
|
-
import
|
|
17987
|
+
import fs9 from "node:fs";
|
|
17490
17988
|
init_errors();
|
|
17491
17989
|
function registerWikiCommands(program2) {
|
|
17492
17990
|
const cmd = program2.command("wiki").description("Manage tenant wiki pages, settings, schema, recompile (#245 D5.4)");
|
|
@@ -17533,14 +18031,14 @@ function registerWikiCommands(program2) {
|
|
|
17533
18031
|
console.log(source_default.dim(`
|
|
17534
18032
|
${pages.length} pages`));
|
|
17535
18033
|
});
|
|
17536
|
-
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 (
|
|
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) => {
|
|
17537
18035
|
const ctx = getCtx();
|
|
17538
|
-
const safe =
|
|
18036
|
+
const safe = path6.split("/").map(encodeURIComponent).join("/");
|
|
17539
18037
|
const resp = await ctx.api.getRaw(`/api/wiki/pages/${safe}`, {
|
|
17540
18038
|
headers: { Accept: "text/markdown,*/*" }
|
|
17541
18039
|
});
|
|
17542
18040
|
if (resp.status === 404) {
|
|
17543
|
-
throw new CLIError(`page not found: ${
|
|
18041
|
+
throw new CLIError(`page not found: ${path6}`, 3 /* NotFound */);
|
|
17544
18042
|
}
|
|
17545
18043
|
if (!resp.ok) {
|
|
17546
18044
|
const detail = await resp.text();
|
|
@@ -17548,16 +18046,16 @@ ${pages.length} pages`));
|
|
|
17548
18046
|
}
|
|
17549
18047
|
const body = await resp.text();
|
|
17550
18048
|
if (opts.out) {
|
|
17551
|
-
|
|
18049
|
+
fs9.writeFileSync(opts.out, body);
|
|
17552
18050
|
if (!isJson()) {
|
|
17553
18051
|
console.log(source_default.dim(`wrote ${body.length} bytes to ${opts.out}`));
|
|
17554
18052
|
} else {
|
|
17555
|
-
console.log(JSON.stringify({ path:
|
|
18053
|
+
console.log(JSON.stringify({ path: path6, bytes: body.length, file: opts.out }));
|
|
17556
18054
|
}
|
|
17557
18055
|
return;
|
|
17558
18056
|
}
|
|
17559
18057
|
if (isJson()) {
|
|
17560
|
-
console.log(JSON.stringify({ path:
|
|
18058
|
+
console.log(JSON.stringify({ path: path6, body, etag: resp.headers.get("ETag") }));
|
|
17561
18059
|
} else {
|
|
17562
18060
|
process.stdout.write(body);
|
|
17563
18061
|
}
|
|
@@ -17652,10 +18150,10 @@ ${pages.length} pages`));
|
|
|
17652
18150
|
}
|
|
17653
18151
|
if (opts.compilePromptOverrideFile) {
|
|
17654
18152
|
const file = String(opts.compilePromptOverrideFile);
|
|
17655
|
-
if (!
|
|
18153
|
+
if (!fs9.existsSync(file)) {
|
|
17656
18154
|
throw new CLIError(`override file not found: ${file}`, 3 /* NotFound */);
|
|
17657
18155
|
}
|
|
17658
|
-
const content =
|
|
18156
|
+
const content = fs9.readFileSync(file, "utf8");
|
|
17659
18157
|
const bytes = Buffer.byteLength(content, "utf8");
|
|
17660
18158
|
if (bytes > 16 * 1024) {
|
|
17661
18159
|
throw new CLIError(`compile prompt override exceeds 16 KB (${bytes} bytes)`, 4 /* Validation */);
|
|
@@ -17679,7 +18177,7 @@ ${pages.length} pages`));
|
|
|
17679
18177
|
const ctx = getCtx();
|
|
17680
18178
|
const resp = await ctx.api.get("/api/wiki/schema");
|
|
17681
18179
|
if (opts.out) {
|
|
17682
|
-
|
|
18180
|
+
fs9.writeFileSync(opts.out, resp.body);
|
|
17683
18181
|
if (!isJson()) {
|
|
17684
18182
|
console.log(source_default.dim(`wrote ${resp.body.length} bytes to ${opts.out}`));
|
|
17685
18183
|
} else {
|
|
@@ -17695,10 +18193,10 @@ ${pages.length} pages`));
|
|
|
17695
18193
|
});
|
|
17696
18194
|
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) => {
|
|
17697
18195
|
const ctx = getCtx();
|
|
17698
|
-
if (!
|
|
18196
|
+
if (!fs9.existsSync(opts.file)) {
|
|
17699
18197
|
throw new CLIError(`schema file not found: ${opts.file}`, 3 /* NotFound */);
|
|
17700
18198
|
}
|
|
17701
|
-
const body =
|
|
18199
|
+
const body = fs9.readFileSync(opts.file, "utf8");
|
|
17702
18200
|
const bytes = Buffer.byteLength(body, "utf8");
|
|
17703
18201
|
if (bytes > 64 * 1024) {
|
|
17704
18202
|
throw new CLIError(`schema body exceeds 64 KB (${bytes} bytes)`, 4 /* Validation */);
|
|
@@ -18224,8 +18722,8 @@ async function resolveCredentialFlag(value) {
|
|
|
18224
18722
|
return Buffer.concat(chunks).toString("utf-8").trim();
|
|
18225
18723
|
}
|
|
18226
18724
|
if (value.startsWith("@")) {
|
|
18227
|
-
const
|
|
18228
|
-
return (await
|
|
18725
|
+
const fs10 = await import("node:fs/promises");
|
|
18726
|
+
return (await fs10.readFile(value.slice(1), "utf-8")).trim();
|
|
18229
18727
|
}
|
|
18230
18728
|
return value;
|
|
18231
18729
|
}
|
|
@@ -19411,19 +19909,19 @@ function registerDoctorCommand(program2) {
|
|
|
19411
19909
|
|
|
19412
19910
|
// src/commands/skill.ts
|
|
19413
19911
|
init_errors();
|
|
19414
|
-
import
|
|
19415
|
-
import
|
|
19912
|
+
import fs10 from "node:fs";
|
|
19913
|
+
import path6 from "node:path";
|
|
19416
19914
|
import { fileURLToPath } from "node:url";
|
|
19417
19915
|
function locateSkill() {
|
|
19418
|
-
const here =
|
|
19916
|
+
const here = path6.dirname(fileURLToPath(import.meta.url));
|
|
19419
19917
|
const candidates = [
|
|
19420
|
-
|
|
19421
|
-
|
|
19422
|
-
|
|
19423
|
-
|
|
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")
|
|
19424
19922
|
];
|
|
19425
19923
|
for (const p of candidates) {
|
|
19426
|
-
if (
|
|
19924
|
+
if (fs10.existsSync(p))
|
|
19427
19925
|
return p;
|
|
19428
19926
|
}
|
|
19429
19927
|
return null;
|
|
@@ -19433,7 +19931,7 @@ async function skillCommand() {
|
|
|
19433
19931
|
if (!skillPath) {
|
|
19434
19932
|
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 */);
|
|
19435
19933
|
}
|
|
19436
|
-
const body =
|
|
19934
|
+
const body = fs10.readFileSync(skillPath, "utf-8");
|
|
19437
19935
|
process.stdout.write(body);
|
|
19438
19936
|
}
|
|
19439
19937
|
function registerSkillCommand(program2) {
|
|
@@ -19618,6 +20116,7 @@ registerConfigCommand(program2);
|
|
|
19618
20116
|
registerDefinitionCommands(program2, functionsConfig);
|
|
19619
20117
|
registerDefinitionCommands(program2, workflowsConfig);
|
|
19620
20118
|
registerDefinitionCommands(program2, agentsConfig);
|
|
20119
|
+
registerDefinitionCommands(program2, triggersConfig);
|
|
19621
20120
|
registerDefinitionsApply(program2);
|
|
19622
20121
|
registerDefinitionsExport(program2);
|
|
19623
20122
|
registerDocumentCommands(program2);
|
package/package.json
CHANGED
|
@@ -279,6 +279,16 @@ martha agents functions <agent> # List grant
|
|
|
279
279
|
|
|
280
280
|
`--collection` scopes the grant to one collection per the #372 PR2 cascade. Omit it to grant at the root (tenant-wide); pass it on `remove-function` to revoke just that scope. Server returns 400 if the function is collection-agnostic (e.g. `recall`).
|
|
281
281
|
|
|
282
|
+
**Sub-agent grants (agent→agent delegation):**
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
martha agents add-agent <coordinator> <sub-agent> [--set key=value] # Let <coordinator> delegate to <sub-agent>
|
|
286
|
+
martha agents remove-agent <coordinator> <sub-agent> # Revoke the delegation grant
|
|
287
|
+
martha agents agents <coordinator> # List the sub-agents <coordinator> may delegate to
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
A grant means the coordinator agent *may* delegate to the sub-agent at invoke time (the sub-agent loads as an `_is_agent` delegate tool); the sub-agent's own tool calls still pass the policy gate under its own grants. `--set key=value` records `config_overrides` for the delegation. Self-delegation is rejected with a clear 400 (`an agent cannot delegate to itself`), and granting an unknown sub-agent returns 404. All three commands support `--json`.
|
|
291
|
+
|
|
282
292
|
---
|
|
283
293
|
|
|
284
294
|
## Tasks: queue + executor lifecycle
|