@aiaiai-pt/martha-cli 0.14.0 → 0.16.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 +438 -77
- package/package.json +1 -1
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.16.0] — 2026-06-13
|
|
8
|
+
|
|
9
|
+
### Added — #567 fluid in-conversation human approval
|
|
10
|
+
- `martha chat` gains in-conversation approval slash commands, so you can resolve a pending approval without leaving the copilot: `/approvals` (list pending), `/approval <id>` (detail), `/approve <id> [note]`, `/reject <id> [note]`. Every action runs under **your** profile token via the admin approvals API — never a copilot tool-call (agents request, humans grant).
|
|
11
|
+
- `/approve` and `/reject` re-fetch the case from the server and show its real capability / risk / assignee before asking you to confirm, so the decision is made on server truth rather than agent-authored chat text.
|
|
12
|
+
- When the agent requests a capability, the chat surfaces a one-line hint with the `/approve <id>` command for the new pending case.
|
|
13
|
+
|
|
7
14
|
## [0.10.0] — 2026-06-10
|
|
8
15
|
|
|
9
16
|
### Added — #540 Phase 2.0/2.1 chat copilot + task operator
|
package/dist/index.js
CHANGED
|
@@ -11077,7 +11077,7 @@ import { createInterface as createInterface2 } from "node:readline";
|
|
|
11077
11077
|
init_errors();
|
|
11078
11078
|
|
|
11079
11079
|
// src/version.ts
|
|
11080
|
-
var CLI_VERSION = "0.
|
|
11080
|
+
var CLI_VERSION = "0.16.0";
|
|
11081
11081
|
|
|
11082
11082
|
// src/commands/sessions.ts
|
|
11083
11083
|
function relativeTime(iso) {
|
|
@@ -11191,6 +11191,164 @@ ${data.total} session(s)`));
|
|
|
11191
11191
|
});
|
|
11192
11192
|
}
|
|
11193
11193
|
|
|
11194
|
+
// src/lib/approvals-client.ts
|
|
11195
|
+
function parsePendingApprovalHint(data) {
|
|
11196
|
+
let obj;
|
|
11197
|
+
try {
|
|
11198
|
+
obj = JSON.parse(data);
|
|
11199
|
+
} catch {
|
|
11200
|
+
return null;
|
|
11201
|
+
}
|
|
11202
|
+
if (!obj || typeof obj !== "object")
|
|
11203
|
+
return null;
|
|
11204
|
+
const rec = obj;
|
|
11205
|
+
const caseId = rec.case_id;
|
|
11206
|
+
if (typeof caseId !== "string" || !caseId)
|
|
11207
|
+
return null;
|
|
11208
|
+
if (rec.status !== "pending")
|
|
11209
|
+
return null;
|
|
11210
|
+
const capabilityRef = typeof rec.capability_ref === "string" ? rec.capability_ref : undefined;
|
|
11211
|
+
return { caseId, capabilityRef };
|
|
11212
|
+
}
|
|
11213
|
+
var truncate = (s, n) => s.length > n ? s.slice(0, n - 1) + "…" : s;
|
|
11214
|
+
var fmtDate = (v) => typeof v === "string" ? new Date(v).toISOString().slice(0, 16).replace("T", " ") : "-";
|
|
11215
|
+
async function listApprovals(ctx, opts = {}) {
|
|
11216
|
+
const params = {
|
|
11217
|
+
limit: String(opts.limit ?? 50),
|
|
11218
|
+
skip: "0"
|
|
11219
|
+
};
|
|
11220
|
+
if (opts.status)
|
|
11221
|
+
params.status = opts.status;
|
|
11222
|
+
if (opts.assignedTo)
|
|
11223
|
+
params.assigned_to = opts.assignedTo;
|
|
11224
|
+
return ctx.api.get("/api/admin/approvals", { params });
|
|
11225
|
+
}
|
|
11226
|
+
async function getApproval(ctx, id) {
|
|
11227
|
+
return ctx.api.get(`/api/admin/approvals/${encodeURIComponent(id)}`);
|
|
11228
|
+
}
|
|
11229
|
+
async function resolveApproval(ctx, id, decision, comment) {
|
|
11230
|
+
const body = { decision };
|
|
11231
|
+
if (comment)
|
|
11232
|
+
body.comment = comment;
|
|
11233
|
+
return ctx.api.put(`/api/admin/approvals/${encodeURIComponent(id)}/resolve`, body);
|
|
11234
|
+
}
|
|
11235
|
+
function renderApprovalTable(items) {
|
|
11236
|
+
if (items.length === 0)
|
|
11237
|
+
return source_default.dim("No approval cases found.");
|
|
11238
|
+
const columns = [
|
|
11239
|
+
{ header: "ID", accessor: (r) => String(r.id ?? "").slice(0, 8) },
|
|
11240
|
+
{ header: "STATUS", accessor: (r) => String(r.status ?? "-") },
|
|
11241
|
+
{
|
|
11242
|
+
header: "REASON",
|
|
11243
|
+
accessor: (r) => truncate(String(r.reason ?? "-"), 40)
|
|
11244
|
+
},
|
|
11245
|
+
{ header: "ASSIGNED TO", accessor: (r) => String(r.assigned_to ?? "-") },
|
|
11246
|
+
{ header: "CREATED", accessor: (r) => fmtDate(r.created_at) }
|
|
11247
|
+
];
|
|
11248
|
+
const widths = columns.map((col) => Math.max(col.header.length, ...items.map((i) => col.accessor(i).length)));
|
|
11249
|
+
const header = columns.map((c, i) => c.header.padEnd(widths[i])).join(" ");
|
|
11250
|
+
const lines = [
|
|
11251
|
+
source_default.bold(header),
|
|
11252
|
+
source_default.dim("-".repeat(header.length)),
|
|
11253
|
+
...items.map((item) => columns.map((c, i) => c.accessor(item).padEnd(widths[i])).join(" ")),
|
|
11254
|
+
"",
|
|
11255
|
+
source_default.dim(`${items.length} approval(s)`)
|
|
11256
|
+
];
|
|
11257
|
+
return lines.join(`
|
|
11258
|
+
`);
|
|
11259
|
+
}
|
|
11260
|
+
function renderApprovalDetail(item) {
|
|
11261
|
+
const ctx = item.context_data ?? {};
|
|
11262
|
+
const cap = ctx.capability_ref ?? [ctx.capability_type, ctx.capability_name].filter(Boolean).join(":");
|
|
11263
|
+
const risk = ctx.risk_tags?.join(", ") ?? ctx.risk_level;
|
|
11264
|
+
const rows = [
|
|
11265
|
+
["ID", String(item.id ?? "-")],
|
|
11266
|
+
["Status", String(item.status ?? "-")],
|
|
11267
|
+
["Reason", String(item.reason ?? "-")]
|
|
11268
|
+
];
|
|
11269
|
+
if (cap)
|
|
11270
|
+
rows.push(["Capability", String(cap)]);
|
|
11271
|
+
if (risk)
|
|
11272
|
+
rows.push(["Risk", String(risk)]);
|
|
11273
|
+
if (item.context_summary)
|
|
11274
|
+
rows.push(["Context", String(item.context_summary)]);
|
|
11275
|
+
rows.push(["Assigned To", String(item.assigned_to ?? "-")]);
|
|
11276
|
+
rows.push(["Resolved By", String(item.resolved_by ?? "-")]);
|
|
11277
|
+
if (item.resolution)
|
|
11278
|
+
rows.push(["Resolution", String(item.resolution)]);
|
|
11279
|
+
if (item.workflow_execution_id)
|
|
11280
|
+
rows.push(["Workflow Exec", String(item.workflow_execution_id)]);
|
|
11281
|
+
rows.push(["Created", fmtDate(item.created_at)]);
|
|
11282
|
+
if (item.resolved_at)
|
|
11283
|
+
rows.push(["Resolved", fmtDate(item.resolved_at)]);
|
|
11284
|
+
rows.push(["Expires", fmtDate(item.expires_at)]);
|
|
11285
|
+
const labelWidth = Math.max(...rows.map(([k]) => k.length)) + 1;
|
|
11286
|
+
const body = rows.map(([k, v]) => `${source_default.cyan((k + ":").padEnd(labelWidth + 1))} ${v}`).join(`
|
|
11287
|
+
`);
|
|
11288
|
+
return `${source_default.bold("Approval Case")}
|
|
11289
|
+
${source_default.dim("-".repeat(40))}
|
|
11290
|
+
${body}`;
|
|
11291
|
+
}
|
|
11292
|
+
|
|
11293
|
+
// src/lib/widget-tags.ts
|
|
11294
|
+
var OPEN = "<approval_widget>";
|
|
11295
|
+
var CLOSE = "</approval_widget>";
|
|
11296
|
+
function stripApprovalWidgetTags(text) {
|
|
11297
|
+
return text.replace(/<approval_widget>[\s\S]*?<\/approval_widget>/g, "");
|
|
11298
|
+
}
|
|
11299
|
+
function partialTailLength(buf, token) {
|
|
11300
|
+
const max = Math.min(buf.length, token.length - 1);
|
|
11301
|
+
for (let n = max;n > 0; n--) {
|
|
11302
|
+
if (buf.slice(buf.length - n) === token.slice(0, n))
|
|
11303
|
+
return n;
|
|
11304
|
+
}
|
|
11305
|
+
return 0;
|
|
11306
|
+
}
|
|
11307
|
+
function createApprovalTagStripper() {
|
|
11308
|
+
let buf = "";
|
|
11309
|
+
let inside = false;
|
|
11310
|
+
function drain() {
|
|
11311
|
+
let out = "";
|
|
11312
|
+
for (;; ) {
|
|
11313
|
+
if (!inside) {
|
|
11314
|
+
const open = buf.indexOf(OPEN);
|
|
11315
|
+
if (open !== -1) {
|
|
11316
|
+
out += buf.slice(0, open);
|
|
11317
|
+
buf = buf.slice(open + OPEN.length);
|
|
11318
|
+
inside = true;
|
|
11319
|
+
continue;
|
|
11320
|
+
}
|
|
11321
|
+
const hold = partialTailLength(buf, OPEN);
|
|
11322
|
+
out += buf.slice(0, buf.length - hold);
|
|
11323
|
+
buf = buf.slice(buf.length - hold);
|
|
11324
|
+
return out;
|
|
11325
|
+
} else {
|
|
11326
|
+
const close = buf.indexOf(CLOSE);
|
|
11327
|
+
if (close !== -1) {
|
|
11328
|
+
buf = buf.slice(close + CLOSE.length);
|
|
11329
|
+
inside = false;
|
|
11330
|
+
continue;
|
|
11331
|
+
}
|
|
11332
|
+
const hold = partialTailLength(buf, CLOSE);
|
|
11333
|
+
buf = buf.slice(buf.length - hold);
|
|
11334
|
+
return out;
|
|
11335
|
+
}
|
|
11336
|
+
}
|
|
11337
|
+
}
|
|
11338
|
+
return {
|
|
11339
|
+
push(token) {
|
|
11340
|
+
buf += token;
|
|
11341
|
+
return drain();
|
|
11342
|
+
},
|
|
11343
|
+
flush() {
|
|
11344
|
+
const tail = inside ? "" : buf;
|
|
11345
|
+
buf = "";
|
|
11346
|
+
inside = false;
|
|
11347
|
+
return tail;
|
|
11348
|
+
}
|
|
11349
|
+
};
|
|
11350
|
+
}
|
|
11351
|
+
|
|
11194
11352
|
// src/commands/chat.ts
|
|
11195
11353
|
function parseSlashCommand(input) {
|
|
11196
11354
|
const trimmed = input.trim();
|
|
@@ -11210,7 +11368,11 @@ var KNOWN_SLASH_COMMANDS = new Set([
|
|
|
11210
11368
|
"session",
|
|
11211
11369
|
"quit",
|
|
11212
11370
|
"clear",
|
|
11213
|
-
"history"
|
|
11371
|
+
"history",
|
|
11372
|
+
"approvals",
|
|
11373
|
+
"approval",
|
|
11374
|
+
"approve",
|
|
11375
|
+
"reject"
|
|
11214
11376
|
]);
|
|
11215
11377
|
async function createSession(ctx, clientId) {
|
|
11216
11378
|
try {
|
|
@@ -11368,6 +11530,106 @@ function promptPick(rl, count) {
|
|
|
11368
11530
|
});
|
|
11369
11531
|
});
|
|
11370
11532
|
}
|
|
11533
|
+
function shortId(id) {
|
|
11534
|
+
return id.slice(0, 8);
|
|
11535
|
+
}
|
|
11536
|
+
function printApprovalError(err) {
|
|
11537
|
+
if (err instanceof CLIError) {
|
|
11538
|
+
const suffix = err.suggestion ? source_default.dim(` (${err.suggestion})`) : "";
|
|
11539
|
+
process.stderr.write(source_default.red(`Error: ${err.message}`) + suffix + `
|
|
11540
|
+
|
|
11541
|
+
`);
|
|
11542
|
+
} else {
|
|
11543
|
+
process.stderr.write(source_default.red(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
11544
|
+
|
|
11545
|
+
`));
|
|
11546
|
+
}
|
|
11547
|
+
}
|
|
11548
|
+
function approvalHintFromToolResult(data) {
|
|
11549
|
+
const hint = parsePendingApprovalHint(data);
|
|
11550
|
+
if (!hint)
|
|
11551
|
+
return null;
|
|
11552
|
+
const cap = hint.capabilityRef ? ` ${source_default.bold(hint.capabilityRef)}` : "";
|
|
11553
|
+
return source_default.yellow(`
|
|
11554
|
+
[!] The agent requested a capability${cap}.`) + source_default.dim(`
|
|
11555
|
+
Review: /approval ${hint.caseId}` + `
|
|
11556
|
+
Approve (as you, not the agent): /approve ${hint.caseId}` + `
|
|
11557
|
+
Deny: /reject ${hint.caseId}
|
|
11558
|
+
`);
|
|
11559
|
+
}
|
|
11560
|
+
async function runApprovalReadSlash(ctx, command, args) {
|
|
11561
|
+
try {
|
|
11562
|
+
if (command === "approvals") {
|
|
11563
|
+
const items = await listApprovals(ctx, { status: "pending" });
|
|
11564
|
+
process.stderr.write(`
|
|
11565
|
+
` + renderApprovalTable(items) + `
|
|
11566
|
+
|
|
11567
|
+
`);
|
|
11568
|
+
return;
|
|
11569
|
+
}
|
|
11570
|
+
const id = args.split(/\s+/)[0] ?? "";
|
|
11571
|
+
if (!id) {
|
|
11572
|
+
process.stderr.write(source_default.yellow(`Usage: /approval <case_id>
|
|
11573
|
+
|
|
11574
|
+
`));
|
|
11575
|
+
return;
|
|
11576
|
+
}
|
|
11577
|
+
const item = await getApproval(ctx, id);
|
|
11578
|
+
process.stderr.write(`
|
|
11579
|
+
` + renderApprovalDetail(item) + `
|
|
11580
|
+
|
|
11581
|
+
`);
|
|
11582
|
+
} catch (err) {
|
|
11583
|
+
printApprovalError(err);
|
|
11584
|
+
}
|
|
11585
|
+
}
|
|
11586
|
+
async function stageApprovalConfirmation(ctx, command, args) {
|
|
11587
|
+
try {
|
|
11588
|
+
const id = args.split(/\s+/)[0] ?? "";
|
|
11589
|
+
if (!id) {
|
|
11590
|
+
process.stderr.write(source_default.yellow(`Usage: /${command} <case_id> [comment]
|
|
11591
|
+
|
|
11592
|
+
`));
|
|
11593
|
+
return null;
|
|
11594
|
+
}
|
|
11595
|
+
const decision = command === "approve" ? "approved" : "rejected";
|
|
11596
|
+
const verb = decision === "approved" ? "Approve" : "Reject";
|
|
11597
|
+
const comment = args.slice(id.length).trim() || undefined;
|
|
11598
|
+
const item = await getApproval(ctx, id);
|
|
11599
|
+
if (item.status && item.status !== "pending") {
|
|
11600
|
+
process.stderr.write(source_default.yellow(`Case ${shortId(id)} is already ${item.status} — nothing to ${verb.toLowerCase()}.
|
|
11601
|
+
|
|
11602
|
+
`));
|
|
11603
|
+
return null;
|
|
11604
|
+
}
|
|
11605
|
+
process.stderr.write(`
|
|
11606
|
+
` + renderApprovalDetail(item) + `
|
|
11607
|
+
`);
|
|
11608
|
+
process.stderr.write(source_default.dim(`${verb} this case under your own user (not the agent)? [y/N] `));
|
|
11609
|
+
return { id, decision, comment };
|
|
11610
|
+
} catch (err) {
|
|
11611
|
+
printApprovalError(err);
|
|
11612
|
+
return null;
|
|
11613
|
+
}
|
|
11614
|
+
}
|
|
11615
|
+
async function applyPendingApproval(ctx, pending, answer) {
|
|
11616
|
+
const yes = ["y", "yes"].includes(answer.trim().toLowerCase());
|
|
11617
|
+
if (!yes) {
|
|
11618
|
+
process.stderr.write(source_default.dim(`Cancelled.
|
|
11619
|
+
|
|
11620
|
+
`));
|
|
11621
|
+
return;
|
|
11622
|
+
}
|
|
11623
|
+
const verb = pending.decision === "approved" ? "Approved" : "Rejected";
|
|
11624
|
+
try {
|
|
11625
|
+
await resolveApproval(ctx, pending.id, pending.decision, pending.comment);
|
|
11626
|
+
process.stderr.write(source_default.green(`${verb} case ${shortId(pending.id)}.
|
|
11627
|
+
|
|
11628
|
+
`));
|
|
11629
|
+
} catch (err) {
|
|
11630
|
+
printApprovalError(err);
|
|
11631
|
+
}
|
|
11632
|
+
}
|
|
11371
11633
|
async function showHistoryPicker(ctx, rl) {
|
|
11372
11634
|
let data;
|
|
11373
11635
|
try {
|
|
@@ -11421,8 +11683,16 @@ async function runRepl(ctx, initialSessionId, clientId, agentName, showTools, ti
|
|
|
11421
11683
|
});
|
|
11422
11684
|
let activeAbort = null;
|
|
11423
11685
|
let streaming = false;
|
|
11686
|
+
let pendingApproval = null;
|
|
11424
11687
|
rl.on("SIGINT", () => {
|
|
11425
|
-
if (
|
|
11688
|
+
if (pendingApproval) {
|
|
11689
|
+
pendingApproval = null;
|
|
11690
|
+
process.stderr.write(source_default.dim(`
|
|
11691
|
+
Cancelled.
|
|
11692
|
+
|
|
11693
|
+
`));
|
|
11694
|
+
rl.prompt();
|
|
11695
|
+
} else if (streaming && activeAbort) {
|
|
11426
11696
|
activeAbort.abort();
|
|
11427
11697
|
process.stdout.write(`
|
|
11428
11698
|
`);
|
|
@@ -11441,6 +11711,13 @@ async function runRepl(ctx, initialSessionId, clientId, agentName, showTools, ti
|
|
|
11441
11711
|
rl.prompt();
|
|
11442
11712
|
for await (const line of rl) {
|
|
11443
11713
|
const input = line.trim();
|
|
11714
|
+
if (pendingApproval) {
|
|
11715
|
+
const p = pendingApproval;
|
|
11716
|
+
pendingApproval = null;
|
|
11717
|
+
await applyPendingApproval(ctx, p, input);
|
|
11718
|
+
rl.prompt();
|
|
11719
|
+
continue;
|
|
11720
|
+
}
|
|
11444
11721
|
if (!input) {
|
|
11445
11722
|
rl.prompt();
|
|
11446
11723
|
continue;
|
|
@@ -11469,6 +11746,16 @@ async function runRepl(ctx, initialSessionId, clientId, agentName, showTools, ti
|
|
|
11469
11746
|
process.stderr.write(` /clear Clear screen
|
|
11470
11747
|
`);
|
|
11471
11748
|
process.stderr.write(` /quit Exit chat
|
|
11749
|
+
`);
|
|
11750
|
+
process.stderr.write(source_default.dim(` Approvals (run as you, not the agent):
|
|
11751
|
+
`));
|
|
11752
|
+
process.stderr.write(` /approvals List pending approval cases
|
|
11753
|
+
`);
|
|
11754
|
+
process.stderr.write(` /approval <id> Show an approval case
|
|
11755
|
+
`);
|
|
11756
|
+
process.stderr.write(` /approve <id> [note] Approve a case
|
|
11757
|
+
`);
|
|
11758
|
+
process.stderr.write(` /reject <id> [note] Reject a case
|
|
11472
11759
|
`);
|
|
11473
11760
|
process.stderr.write(`
|
|
11474
11761
|
`);
|
|
@@ -11491,6 +11778,19 @@ async function runRepl(ctx, initialSessionId, clientId, agentName, showTools, ti
|
|
|
11491
11778
|
case "clear":
|
|
11492
11779
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
11493
11780
|
break;
|
|
11781
|
+
case "approvals":
|
|
11782
|
+
case "approval":
|
|
11783
|
+
await runApprovalReadSlash(ctx, slash.command, slash.args);
|
|
11784
|
+
break;
|
|
11785
|
+
case "approve":
|
|
11786
|
+
case "reject": {
|
|
11787
|
+
const staged = await stageApprovalConfirmation(ctx, slash.command, slash.args);
|
|
11788
|
+
if (staged) {
|
|
11789
|
+
pendingApproval = staged;
|
|
11790
|
+
continue;
|
|
11791
|
+
}
|
|
11792
|
+
break;
|
|
11793
|
+
}
|
|
11494
11794
|
case "quit":
|
|
11495
11795
|
rl.close();
|
|
11496
11796
|
return;
|
|
@@ -11500,26 +11800,43 @@ async function runRepl(ctx, initialSessionId, clientId, agentName, showTools, ti
|
|
|
11500
11800
|
}
|
|
11501
11801
|
streaming = true;
|
|
11502
11802
|
activeAbort = new AbortController;
|
|
11803
|
+
let stripper = createApprovalTagStripper();
|
|
11804
|
+
const approvalHints = [];
|
|
11503
11805
|
try {
|
|
11504
11806
|
const result = await sendMessage(ctx, sessionId, input, {
|
|
11505
11807
|
timeoutMs,
|
|
11506
|
-
onToken: (token) => process.stdout.write(token),
|
|
11808
|
+
onToken: (token) => process.stdout.write(stripper.push(token)),
|
|
11507
11809
|
onToolCall: showTools ? (data) => process.stderr.write(formatToolCall(data)) : undefined,
|
|
11508
|
-
onToolResult:
|
|
11810
|
+
onToolResult: (data) => {
|
|
11811
|
+
if (showTools)
|
|
11812
|
+
process.stderr.write(formatToolResult(data));
|
|
11813
|
+
const hint = approvalHintFromToolResult(data);
|
|
11814
|
+
if (hint)
|
|
11815
|
+
approvalHints.push(hint);
|
|
11816
|
+
},
|
|
11509
11817
|
onToolStatus: (data) => {
|
|
11510
11818
|
const out = formatToolStatus(data);
|
|
11511
11819
|
if (out)
|
|
11512
11820
|
process.stderr.write(out);
|
|
11513
11821
|
},
|
|
11514
|
-
onClear:
|
|
11515
|
-
|
|
11822
|
+
onClear: () => {
|
|
11823
|
+
stripper = createApprovalTagStripper();
|
|
11824
|
+
if (showTools) {
|
|
11825
|
+
process.stderr.write(source_default.dim(` --- tool iteration ---
|
|
11826
|
+
`));
|
|
11827
|
+
}
|
|
11828
|
+
},
|
|
11516
11829
|
signal: activeAbort.signal,
|
|
11517
11830
|
clientId,
|
|
11518
11831
|
agentName
|
|
11519
11832
|
});
|
|
11833
|
+
process.stdout.write(stripper.flush());
|
|
11520
11834
|
if (!result.aborted) {
|
|
11521
11835
|
process.stdout.write(`
|
|
11522
11836
|
|
|
11837
|
+
`);
|
|
11838
|
+
for (const hint of approvalHints)
|
|
11839
|
+
process.stderr.write(hint + `
|
|
11523
11840
|
`);
|
|
11524
11841
|
}
|
|
11525
11842
|
} catch (err) {
|
|
@@ -11586,7 +11903,7 @@ async function runOneShot(ctx, sessionId, message, isJson, clientId, agentName,
|
|
|
11586
11903
|
tool_calls: toolCalls
|
|
11587
11904
|
}));
|
|
11588
11905
|
} else {
|
|
11589
|
-
console.log(response);
|
|
11906
|
+
console.log(stripApprovalWidgetTags(response));
|
|
11590
11907
|
}
|
|
11591
11908
|
}
|
|
11592
11909
|
function registerChatCommand(program2) {
|
|
@@ -14505,8 +14822,6 @@ No google_drive sources found.`));
|
|
|
14505
14822
|
|
|
14506
14823
|
// src/commands/approvals.ts
|
|
14507
14824
|
init_errors();
|
|
14508
|
-
var truncate = (s, n) => s.length > n ? s.slice(0, n - 1) + "…" : s;
|
|
14509
|
-
var fmtDate = (v) => typeof v === "string" ? new Date(v).toISOString().slice(0, 16).replace("T", " ") : "-";
|
|
14510
14825
|
function registerApprovalCommands(program2) {
|
|
14511
14826
|
const cmd = program2.command("approvals").description("Manage approval cases");
|
|
14512
14827
|
function getCtx() {
|
|
@@ -14527,55 +14842,16 @@ function registerApprovalCommands(program2) {
|
|
|
14527
14842
|
if (isNaN(limitN) || limitN < 1) {
|
|
14528
14843
|
throw new CLIError("--limit must be a positive integer", 4 /* Validation */);
|
|
14529
14844
|
}
|
|
14530
|
-
const
|
|
14531
|
-
|
|
14532
|
-
|
|
14533
|
-
|
|
14534
|
-
|
|
14535
|
-
params.status = opts.status;
|
|
14536
|
-
if (opts.assignedTo)
|
|
14537
|
-
params.assigned_to = opts.assignedTo;
|
|
14538
|
-
const data = await ctx.api.get("/api/admin/approvals", { params });
|
|
14539
|
-
const items = data;
|
|
14845
|
+
const items = await listApprovals(ctx, {
|
|
14846
|
+
status: opts.status,
|
|
14847
|
+
assignedTo: opts.assignedTo,
|
|
14848
|
+
limit: limitN
|
|
14849
|
+
});
|
|
14540
14850
|
if (isJson()) {
|
|
14541
14851
|
console.log(JSON.stringify(items, null, 2));
|
|
14542
14852
|
return;
|
|
14543
14853
|
}
|
|
14544
|
-
|
|
14545
|
-
console.log(source_default.dim("No approval cases found."));
|
|
14546
|
-
return;
|
|
14547
|
-
}
|
|
14548
|
-
const columns = [
|
|
14549
|
-
{
|
|
14550
|
-
header: "ID",
|
|
14551
|
-
accessor: (r) => String(r.id ?? "").slice(0, 8)
|
|
14552
|
-
},
|
|
14553
|
-
{
|
|
14554
|
-
header: "STATUS",
|
|
14555
|
-
accessor: (r) => String(r.status ?? "-")
|
|
14556
|
-
},
|
|
14557
|
-
{
|
|
14558
|
-
header: "REASON",
|
|
14559
|
-
accessor: (r) => truncate(String(r.reason ?? "-"), 40)
|
|
14560
|
-
},
|
|
14561
|
-
{
|
|
14562
|
-
header: "ASSIGNED TO",
|
|
14563
|
-
accessor: (r) => String(r.assigned_to ?? "-")
|
|
14564
|
-
},
|
|
14565
|
-
{
|
|
14566
|
-
header: "CREATED",
|
|
14567
|
-
accessor: (r) => fmtDate(r.created_at)
|
|
14568
|
-
}
|
|
14569
|
-
];
|
|
14570
|
-
const widths = columns.map((col) => Math.max(col.header.length, ...items.map((item) => col.accessor(item).length)));
|
|
14571
|
-
const header = columns.map((col, i) => col.header.padEnd(widths[i])).join(" ");
|
|
14572
|
-
console.log(source_default.bold(header));
|
|
14573
|
-
console.log(source_default.dim("-".repeat(header.length)));
|
|
14574
|
-
for (const item of items) {
|
|
14575
|
-
console.log(columns.map((col, i) => col.accessor(item).padEnd(widths[i])).join(" "));
|
|
14576
|
-
}
|
|
14577
|
-
console.log(source_default.dim(`
|
|
14578
|
-
${items.length} approval(s)`));
|
|
14854
|
+
console.log(renderApprovalTable(items));
|
|
14579
14855
|
});
|
|
14580
14856
|
cmd.command("stats").description("Show approval statistics").action(async () => {
|
|
14581
14857
|
const ctx = getCtx();
|
|
@@ -14588,31 +14864,16 @@ ${items.length} approval(s)`));
|
|
|
14588
14864
|
});
|
|
14589
14865
|
cmd.command("get <id>").description("Show approval case details").action(async (id) => {
|
|
14590
14866
|
const ctx = getCtx();
|
|
14591
|
-
const item = await ctx
|
|
14867
|
+
const item = await getApproval(ctx, id);
|
|
14592
14868
|
if (isJson()) {
|
|
14593
14869
|
console.log(JSON.stringify(item, null, 2));
|
|
14594
14870
|
return;
|
|
14595
14871
|
}
|
|
14596
|
-
console.log(
|
|
14597
|
-
console.log(source_default.dim("-".repeat(40)));
|
|
14598
|
-
console.log(`${source_default.cyan("ID:")} ${item.id ?? "-"}`);
|
|
14599
|
-
console.log(`${source_default.cyan("Status:")} ${item.status ?? "-"}`);
|
|
14600
|
-
console.log(`${source_default.cyan("Reason:")} ${item.reason ?? "-"}`);
|
|
14601
|
-
console.log(`${source_default.cyan("Context Summary:")} ${item.context_summary ?? "-"}`);
|
|
14602
|
-
console.log(`${source_default.cyan("Resolution:")} ${item.resolution ?? "-"}`);
|
|
14603
|
-
console.log(`${source_default.cyan("Assigned To:")} ${item.assigned_to ?? "-"}`);
|
|
14604
|
-
console.log(`${source_default.cyan("Resolved By:")} ${item.resolved_by ?? "-"}`);
|
|
14605
|
-
console.log(`${source_default.cyan("Workflow Execution ID:")} ${item.workflow_execution_id ?? "-"}`);
|
|
14606
|
-
console.log(`${source_default.cyan("Created:")} ${fmtDate(item.created_at)}`);
|
|
14607
|
-
console.log(`${source_default.cyan("Resolved:")} ${fmtDate(item.resolved_at)}`);
|
|
14608
|
-
console.log(`${source_default.cyan("Expires:")} ${fmtDate(item.expires_at)}`);
|
|
14872
|
+
console.log(renderApprovalDetail(item));
|
|
14609
14873
|
});
|
|
14610
14874
|
cmd.command("approve <id>").description("Approve a case").option("--comment <text>", "Resolution comment").action(async (id, opts) => {
|
|
14611
14875
|
const ctx = getCtx();
|
|
14612
|
-
const
|
|
14613
|
-
if (opts.comment)
|
|
14614
|
-
body.comment = opts.comment;
|
|
14615
|
-
const result = await ctx.api.put(`/api/admin/approvals/${encodeURIComponent(id)}/resolve`, body);
|
|
14876
|
+
const result = await resolveApproval(ctx, id, "approved", opts.comment);
|
|
14616
14877
|
if (isJson()) {
|
|
14617
14878
|
console.log(JSON.stringify(result, null, 2));
|
|
14618
14879
|
return;
|
|
@@ -14621,10 +14882,7 @@ ${items.length} approval(s)`));
|
|
|
14621
14882
|
});
|
|
14622
14883
|
cmd.command("reject <id>").description("Reject a case").option("--comment <text>", "Resolution comment").action(async (id, opts) => {
|
|
14623
14884
|
const ctx = getCtx();
|
|
14624
|
-
const
|
|
14625
|
-
if (opts.comment)
|
|
14626
|
-
body.comment = opts.comment;
|
|
14627
|
-
const result = await ctx.api.put(`/api/admin/approvals/${encodeURIComponent(id)}/resolve`, body);
|
|
14885
|
+
const result = await resolveApproval(ctx, id, "rejected", opts.comment);
|
|
14628
14886
|
if (isJson()) {
|
|
14629
14887
|
console.log(JSON.stringify(result, null, 2));
|
|
14630
14888
|
return;
|
|
@@ -18746,6 +19004,108 @@ function registerSkillCommand(program2) {
|
|
|
18746
19004
|
});
|
|
18747
19005
|
}
|
|
18748
19006
|
|
|
19007
|
+
// src/commands/update.ts
|
|
19008
|
+
import { spawnSync } from "node:child_process";
|
|
19009
|
+
init_errors();
|
|
19010
|
+
var PKG = "@aiaiai-pt/martha-cli";
|
|
19011
|
+
var LATEST_URL = "https://registry.npmjs.org/@aiaiai-pt%2Fmartha-cli/latest";
|
|
19012
|
+
function compareSemver2(a, b) {
|
|
19013
|
+
const parse = (v) => v.replace(/^v/, "").split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
|
|
19014
|
+
const pa = parse(a);
|
|
19015
|
+
const pb = parse(b);
|
|
19016
|
+
for (let i = 0;i < Math.max(pa.length, pb.length); i++) {
|
|
19017
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
19018
|
+
if (d !== 0)
|
|
19019
|
+
return d > 0 ? 1 : -1;
|
|
19020
|
+
}
|
|
19021
|
+
return 0;
|
|
19022
|
+
}
|
|
19023
|
+
async function fetchLatest() {
|
|
19024
|
+
try {
|
|
19025
|
+
const res = await fetch(LATEST_URL, {
|
|
19026
|
+
headers: { accept: "application/json" }
|
|
19027
|
+
});
|
|
19028
|
+
if (!res.ok)
|
|
19029
|
+
return null;
|
|
19030
|
+
const j = await res.json();
|
|
19031
|
+
return j.version ?? null;
|
|
19032
|
+
} catch {
|
|
19033
|
+
return null;
|
|
19034
|
+
}
|
|
19035
|
+
}
|
|
19036
|
+
function resolveManager(explicit) {
|
|
19037
|
+
if (explicit) {
|
|
19038
|
+
if (explicit !== "npm" && explicit !== "bun") {
|
|
19039
|
+
throw new CLIError(`--manager must be 'npm' or 'bun' (got '${explicit}').`, 4 /* Validation */);
|
|
19040
|
+
}
|
|
19041
|
+
return explicit;
|
|
19042
|
+
}
|
|
19043
|
+
return process.versions?.bun ? "bun" : "npm";
|
|
19044
|
+
}
|
|
19045
|
+
function registerUpdateCommand(program2) {
|
|
19046
|
+
program2.command("update").description("Update the Martha CLI to the latest published version").option("--check", "Report whether a newer version is available; don't install").option("--yes", "Skip the confirmation prompt").option("--manager <pm>", "Package manager to use: npm | bun (default: auto)").action(async (opts) => {
|
|
19047
|
+
const isJson = !!program2.opts().json;
|
|
19048
|
+
const latest = await fetchLatest();
|
|
19049
|
+
if (!latest) {
|
|
19050
|
+
throw new CLIError(`Could not reach the npm registry to determine the latest version (${LATEST_URL}). Check your network.`, 1 /* Error */);
|
|
19051
|
+
}
|
|
19052
|
+
const updateAvailable = compareSemver2(CLI_VERSION, latest) < 0;
|
|
19053
|
+
if (opts.check) {
|
|
19054
|
+
if (isJson) {
|
|
19055
|
+
console.log(JSON.stringify({
|
|
19056
|
+
current: CLI_VERSION,
|
|
19057
|
+
latest,
|
|
19058
|
+
update_available: updateAvailable
|
|
19059
|
+
}, null, 2));
|
|
19060
|
+
} else if (updateAvailable) {
|
|
19061
|
+
console.log(`Update available: ${CLI_VERSION} ${source_default.dim("→")} ${source_default.green(latest)}. Run ${source_default.cyan("martha update")}.`);
|
|
19062
|
+
} else {
|
|
19063
|
+
console.log(`Up to date (${CLI_VERSION}).`);
|
|
19064
|
+
}
|
|
19065
|
+
if (updateAvailable)
|
|
19066
|
+
process.exitCode = 1;
|
|
19067
|
+
return;
|
|
19068
|
+
}
|
|
19069
|
+
if (!updateAvailable) {
|
|
19070
|
+
if (isJson) {
|
|
19071
|
+
console.log(JSON.stringify({
|
|
19072
|
+
current: CLI_VERSION,
|
|
19073
|
+
latest,
|
|
19074
|
+
updated: false,
|
|
19075
|
+
reason: "already_latest"
|
|
19076
|
+
}, null, 2));
|
|
19077
|
+
} else {
|
|
19078
|
+
console.log(`Already on the latest version (${CLI_VERSION}).`);
|
|
19079
|
+
}
|
|
19080
|
+
return;
|
|
19081
|
+
}
|
|
19082
|
+
if (!opts.yes) {
|
|
19083
|
+
if (!process.stdin.isTTY) {
|
|
19084
|
+
throw new CLIError(`Update available (${CLI_VERSION} → ${latest}). Re-run with --yes to install.`, 4 /* Validation */);
|
|
19085
|
+
}
|
|
19086
|
+
const ok = await confirm(`Update martha-cli ${CLI_VERSION} → ${latest}?`);
|
|
19087
|
+
if (!ok) {
|
|
19088
|
+
console.error("Cancelled.");
|
|
19089
|
+
return;
|
|
19090
|
+
}
|
|
19091
|
+
}
|
|
19092
|
+
const pm = resolveManager(opts.manager);
|
|
19093
|
+
const args = pm === "bun" ? ["install", "-g", `${PKG}@latest`] : ["i", "-g", `${PKG}@latest`];
|
|
19094
|
+
if (!isJson) {
|
|
19095
|
+
console.error(source_default.dim(`$ ${pm} ${args.join(" ")}`));
|
|
19096
|
+
}
|
|
19097
|
+
const r = spawnSync(pm, args, { stdio: isJson ? "ignore" : "inherit" });
|
|
19098
|
+
if (r.error || r.status !== 0) {
|
|
19099
|
+
throw new CLIError(`${pm} install failed${r.error ? `: ${r.error.message}` : ` (exit ${r.status})`}. ` + `Try manually: ${pm} ${args.join(" ")}`, 1 /* Error */);
|
|
19100
|
+
}
|
|
19101
|
+
if (isJson) {
|
|
19102
|
+
console.log(JSON.stringify({ current: CLI_VERSION, latest, updated: true, manager: pm }, null, 2));
|
|
19103
|
+
} else {
|
|
19104
|
+
console.log(source_default.green(`Updated to ${latest}.`));
|
|
19105
|
+
}
|
|
19106
|
+
});
|
|
19107
|
+
}
|
|
19108
|
+
|
|
18749
19109
|
// src/index.ts
|
|
18750
19110
|
var program2 = new Command;
|
|
18751
19111
|
program2.name("martha").description("Terminal-first client for the Martha AI platform").version(CLI_VERSION).option("--profile <name>", "Use a named profile").option("--json", "Machine-readable JSON output").option("--quiet", "Suppress non-essential output").option("--verbose", "Verbose logging").option("--no-color", "Disable color output").option("--api-url <url>", "Override API base URL");
|
|
@@ -18813,6 +19173,7 @@ registerPolicyCommands(program2);
|
|
|
18813
19173
|
registerInitCommand(program2);
|
|
18814
19174
|
registerDoctorCommand(program2);
|
|
18815
19175
|
registerSkillCommand(program2);
|
|
19176
|
+
registerUpdateCommand(program2);
|
|
18816
19177
|
async function main() {
|
|
18817
19178
|
try {
|
|
18818
19179
|
await program2.parseAsync(process.argv);
|