@aiaiai-pt/martha-cli 0.65.0 → 0.66.1
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 +13 -0
- package/dist/index.js +301 -30
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@aiaiai-pt/martha-cli`. Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This project adheres to semver — `0.x` releases may include breaking changes between minor versions.
|
|
4
4
|
|
|
5
|
+
## [0.66.1] — 2026-11-01
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- `martha runner` and `martha chat --local-exec` no longer run a host command when its gate cannot be claimed. A claim that fails with a network error or 5xx is retried twice, then the gate is skipped. Previously any non-409 failure fell through to execution, and the server, which enforces the claim, rejected the result, so the command ran with nowhere to report. This is what happened for runs Temporal no longer had (#1859).
|
|
10
|
+
|
|
11
|
+
## [0.66.0] — 2026-11-01
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `martha definitions apply` provisions skills and agent grants (#2339). A `kind: Prompt` file creates the prompt, or a new version only when its content differs from production, promotes it and checks the production content's hash; `description`/`scope` metadata is updated when it differs. An Agent's `functions:` and `workflows:` are granted and read back; grants the file does not declare are reported as drift and never revoked. The dry-run plan lists pending grants and drift.
|
|
16
|
+
- `-f` can be given more than once (`-f agent/ -f agent/skills/`). Directories are still not recursed.
|
|
17
|
+
|
|
5
18
|
## [0.65.0] — 2026-10-24
|
|
6
19
|
|
|
7
20
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -11244,7 +11244,7 @@ init_config();
|
|
|
11244
11244
|
init_errors();
|
|
11245
11245
|
|
|
11246
11246
|
// src/version.ts
|
|
11247
|
-
var CLI_VERSION = "0.
|
|
11247
|
+
var CLI_VERSION = "0.66.1";
|
|
11248
11248
|
|
|
11249
11249
|
// src/commands/sessions.ts
|
|
11250
11250
|
function relativeTime(iso) {
|
|
@@ -13546,6 +13546,7 @@ async function resolveLocalAgent(sessionId, toolCallId, req, state) {
|
|
|
13546
13546
|
log_path: result.log_path
|
|
13547
13547
|
};
|
|
13548
13548
|
}
|
|
13549
|
+
var CLAIM_RETRY = { delaysMs: [1000, 3000] };
|
|
13549
13550
|
async function claimGate(sessionId, toolCallId, state, transport) {
|
|
13550
13551
|
if (state.claimed.has(toolCallId))
|
|
13551
13552
|
return true;
|
|
@@ -13553,19 +13554,25 @@ async function claimGate(sessionId, toolCallId, state, transport) {
|
|
|
13553
13554
|
const secretHash = createHash2("sha256").update(secret).digest("hex");
|
|
13554
13555
|
state.secrets.set(toolCallId, secret);
|
|
13555
13556
|
await state.journal.recordSecret(sessionId, toolCallId, secret);
|
|
13556
|
-
|
|
13557
|
-
|
|
13558
|
-
|
|
13559
|
-
|
|
13560
|
-
|
|
13561
|
-
|
|
13562
|
-
|
|
13557
|
+
for (let attempt = 0;; attempt++) {
|
|
13558
|
+
try {
|
|
13559
|
+
await transport.claim(toolCallId, secretHash);
|
|
13560
|
+
state.claimed.add(toolCallId);
|
|
13561
|
+
return true;
|
|
13562
|
+
} catch (e) {
|
|
13563
|
+
if (e instanceof MarthaAPIError && e.status === 409) {
|
|
13564
|
+
process.stderr.write(source_default.yellow(`[local-exec] gate ${toolCallId} is not pending (already resolved or timed out) — skipping execution
|
|
13563
13565
|
`));
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
13566
|
+
return false;
|
|
13567
|
+
}
|
|
13568
|
+
const final = e instanceof MarthaAPIError && e.status >= 400 && e.status < 500;
|
|
13569
|
+
if (final || attempt >= CLAIM_RETRY.delaysMs.length) {
|
|
13570
|
+
process.stderr.write(source_default.yellow(`[local-exec] failed to claim gate for ${toolCallId}: ${String(e)} — skipping execution
|
|
13567
13571
|
`));
|
|
13568
|
-
|
|
13572
|
+
return false;
|
|
13573
|
+
}
|
|
13574
|
+
await new Promise((r) => setTimeout(r, CLAIM_RETRY.delaysMs[attempt]));
|
|
13575
|
+
}
|
|
13569
13576
|
}
|
|
13570
13577
|
}
|
|
13571
13578
|
async function postToolResult(toolCallId, result, label, transport, execSecret) {
|
|
@@ -16092,21 +16099,24 @@ var VALID_KINDS = new Set([
|
|
|
16092
16099
|
"Workflow",
|
|
16093
16100
|
"Agent",
|
|
16094
16101
|
"Trigger",
|
|
16095
|
-
"ClientAccess"
|
|
16102
|
+
"ClientAccess",
|
|
16103
|
+
"Prompt"
|
|
16096
16104
|
]);
|
|
16097
16105
|
var KIND_TO_API_PATH = {
|
|
16098
16106
|
Function: "/api/admin/definitions/functions",
|
|
16099
16107
|
Workflow: "/api/admin/definitions/workflows",
|
|
16100
16108
|
Agent: "/api/admin/definitions/agents",
|
|
16101
16109
|
Trigger: "/api/admin/triggers",
|
|
16102
|
-
ClientAccess: "/api/admin/definitions/client-access"
|
|
16110
|
+
ClientAccess: "/api/admin/definitions/client-access",
|
|
16111
|
+
Prompt: "/api/admin/definitions/prompts"
|
|
16103
16112
|
};
|
|
16104
16113
|
var KIND_TO_PLURAL = {
|
|
16105
16114
|
Function: "Functions",
|
|
16106
16115
|
ClientAccess: "Client access",
|
|
16107
16116
|
Workflow: "Workflows",
|
|
16108
16117
|
Agent: "Agents",
|
|
16109
|
-
Trigger: "Triggers"
|
|
16118
|
+
Trigger: "Triggers",
|
|
16119
|
+
Prompt: "Prompts"
|
|
16110
16120
|
};
|
|
16111
16121
|
var SERVER_GENERATED_FIELDS = new Set([
|
|
16112
16122
|
"id",
|
|
@@ -16274,17 +16284,36 @@ async function computeOperations(defs, api) {
|
|
|
16274
16284
|
});
|
|
16275
16285
|
continue;
|
|
16276
16286
|
}
|
|
16277
|
-
const changedFields = diffFields(def.body, remote);
|
|
16287
|
+
const changedFields = def.kind === "Prompt" ? diffPromptFields(def.body, remote) : diffFields(def.body, remote);
|
|
16278
16288
|
const needsReactivation = remote.is_active === false && def.body.is_active !== false;
|
|
16279
16289
|
if (needsReactivation && !changedFields.includes("is_active")) {
|
|
16280
16290
|
changedFields.push("is_active");
|
|
16281
16291
|
}
|
|
16292
|
+
let pendingGrants;
|
|
16293
|
+
let driftGrants;
|
|
16294
|
+
if (def.kind === "Agent" && remote) {
|
|
16295
|
+
const grants = await computeAgentGrantDiff(api, def);
|
|
16296
|
+
if (grants.pending.functions.length > 0 || grants.pending.workflows.length > 0) {
|
|
16297
|
+
pendingGrants = grants.pending;
|
|
16298
|
+
}
|
|
16299
|
+
if (grants.drift.functions.length > 0 || grants.drift.workflows.length > 0) {
|
|
16300
|
+
driftGrants = grants.drift;
|
|
16301
|
+
}
|
|
16302
|
+
} else if (def.kind === "Agent" && !remote) {
|
|
16303
|
+
const fns = def.body.functions ?? [];
|
|
16304
|
+
const wfs = def.body.workflows ?? [];
|
|
16305
|
+
if (fns.length > 0 || wfs.length > 0) {
|
|
16306
|
+
pendingGrants = { functions: fns, workflows: wfs };
|
|
16307
|
+
}
|
|
16308
|
+
}
|
|
16282
16309
|
if (changedFields.length === 0) {
|
|
16283
16310
|
ops.push({
|
|
16284
16311
|
kind: def.kind,
|
|
16285
16312
|
name: def.name,
|
|
16286
16313
|
action: "skip",
|
|
16287
|
-
source: def.source
|
|
16314
|
+
source: def.source,
|
|
16315
|
+
pendingGrants,
|
|
16316
|
+
driftGrants
|
|
16288
16317
|
});
|
|
16289
16318
|
} else {
|
|
16290
16319
|
ops.push({
|
|
@@ -16293,7 +16322,9 @@ async function computeOperations(defs, api) {
|
|
|
16293
16322
|
action: "update",
|
|
16294
16323
|
changedFields,
|
|
16295
16324
|
source: def.source,
|
|
16296
|
-
reactivate: needsReactivation || undefined
|
|
16325
|
+
reactivate: needsReactivation || undefined,
|
|
16326
|
+
pendingGrants,
|
|
16327
|
+
driftGrants
|
|
16297
16328
|
});
|
|
16298
16329
|
}
|
|
16299
16330
|
}
|
|
@@ -16307,6 +16338,8 @@ function diffFields(local, remote) {
|
|
|
16307
16338
|
continue;
|
|
16308
16339
|
if (SERVER_GENERATED_FIELDS.has(key))
|
|
16309
16340
|
continue;
|
|
16341
|
+
if (AGENT_MANAGED_FIELDS.has(key))
|
|
16342
|
+
continue;
|
|
16310
16343
|
const localVal = local[key];
|
|
16311
16344
|
const remoteVal = flatRemote[key];
|
|
16312
16345
|
if (!localSubsetOf(localVal, remoteVal)) {
|
|
@@ -16315,6 +16348,32 @@ function diffFields(local, remote) {
|
|
|
16315
16348
|
}
|
|
16316
16349
|
return changed;
|
|
16317
16350
|
}
|
|
16351
|
+
function diffPromptFields(local, remote) {
|
|
16352
|
+
const changed = [];
|
|
16353
|
+
const prod = remote.production;
|
|
16354
|
+
for (const key of Object.keys(local)) {
|
|
16355
|
+
if (key === "name")
|
|
16356
|
+
continue;
|
|
16357
|
+
if (key === "change_summary")
|
|
16358
|
+
continue;
|
|
16359
|
+
if (SERVER_GENERATED_FIELDS.has(key))
|
|
16360
|
+
continue;
|
|
16361
|
+
if (key === "content") {
|
|
16362
|
+
const localContent = local.content;
|
|
16363
|
+
const remoteContent = prod?.content;
|
|
16364
|
+
if (localContent !== remoteContent) {
|
|
16365
|
+
changed.push("content");
|
|
16366
|
+
}
|
|
16367
|
+
} else {
|
|
16368
|
+
const localVal = local[key];
|
|
16369
|
+
const remoteVal = remote[key];
|
|
16370
|
+
if (!localSubsetOf(localVal, remoteVal)) {
|
|
16371
|
+
changed.push(key);
|
|
16372
|
+
}
|
|
16373
|
+
}
|
|
16374
|
+
}
|
|
16375
|
+
return changed;
|
|
16376
|
+
}
|
|
16318
16377
|
function flattenDefinitionWrapper(remote) {
|
|
16319
16378
|
const result = {};
|
|
16320
16379
|
for (const [key, val] of Object.entries(remote)) {
|
|
@@ -16387,7 +16446,8 @@ function renderPlan(ops) {
|
|
|
16387
16446
|
"Workflow",
|
|
16388
16447
|
"Agent",
|
|
16389
16448
|
"Trigger",
|
|
16390
|
-
"ClientAccess"
|
|
16449
|
+
"ClientAccess",
|
|
16450
|
+
"Prompt"
|
|
16391
16451
|
]) {
|
|
16392
16452
|
const kindOps = grouped.get(kind);
|
|
16393
16453
|
if (!kindOps)
|
|
@@ -16406,6 +16466,16 @@ function renderPlan(ops) {
|
|
|
16406
16466
|
line += source_default.dim("(no changes)");
|
|
16407
16467
|
}
|
|
16408
16468
|
console.log(line);
|
|
16469
|
+
const pend = op.pendingGrants;
|
|
16470
|
+
if (pend && pend.functions.length + pend.workflows.length > 0) {
|
|
16471
|
+
const names = [...pend.functions, ...pend.workflows.map((w) => `workflow:${w}`)];
|
|
16472
|
+
console.log(source_default.green(` + grant ${names.join(", ")}`));
|
|
16473
|
+
}
|
|
16474
|
+
const drift = op.driftGrants;
|
|
16475
|
+
if (drift && drift.functions.length + drift.workflows.length > 0) {
|
|
16476
|
+
const names = [...drift.functions, ...drift.workflows.map((w) => `workflow:${w}`)];
|
|
16477
|
+
console.log(source_default.yellow(` ! drift (granted, not declared; not revoked): ${names.join(", ")}`));
|
|
16478
|
+
}
|
|
16409
16479
|
}
|
|
16410
16480
|
console.log();
|
|
16411
16481
|
}
|
|
@@ -16438,22 +16508,51 @@ async function executeApply(api, ops, defs, warnings) {
|
|
|
16438
16508
|
const results = [];
|
|
16439
16509
|
ops = [...ops].sort((a, b) => (a.kind === "ClientAccess" ? 1 : 0) - (b.kind === "ClientAccess" ? 1 : 0));
|
|
16440
16510
|
for (const op of ops) {
|
|
16511
|
+
const def = defsByKey.get(`${op.kind}:${op.name}`);
|
|
16441
16512
|
if (op.action === "skip") {
|
|
16442
|
-
|
|
16513
|
+
if (op.kind !== "Agent") {
|
|
16514
|
+
results.push({ name: op.name, kind: op.kind, action: "skipped" });
|
|
16515
|
+
continue;
|
|
16516
|
+
}
|
|
16517
|
+
try {
|
|
16518
|
+
const added = await applyAgentGrants(api, def, warnings);
|
|
16519
|
+
results.push({
|
|
16520
|
+
name: op.name,
|
|
16521
|
+
kind: op.kind,
|
|
16522
|
+
action: added > 0 ? "updated" : "skipped"
|
|
16523
|
+
});
|
|
16524
|
+
} catch (err) {
|
|
16525
|
+
results.push({
|
|
16526
|
+
name: op.name,
|
|
16527
|
+
kind: op.kind,
|
|
16528
|
+
action: "failed",
|
|
16529
|
+
error: err instanceof Error ? err.message : String(err),
|
|
16530
|
+
source: def.source
|
|
16531
|
+
});
|
|
16532
|
+
}
|
|
16443
16533
|
continue;
|
|
16444
16534
|
}
|
|
16445
|
-
const def = defsByKey.get(`${op.kind}:${op.name}`);
|
|
16446
16535
|
const apiPath = KIND_TO_API_PATH[op.kind];
|
|
16536
|
+
if (op.kind === "Prompt") {
|
|
16537
|
+
try {
|
|
16538
|
+
const result = await applyPrompt(api, op, def);
|
|
16539
|
+
results.push(result);
|
|
16540
|
+
} catch (err) {
|
|
16541
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
16542
|
+
results.push({
|
|
16543
|
+
name: op.name,
|
|
16544
|
+
kind: op.kind,
|
|
16545
|
+
action: "failed",
|
|
16546
|
+
error: msg,
|
|
16547
|
+
source: def.source
|
|
16548
|
+
});
|
|
16549
|
+
}
|
|
16550
|
+
continue;
|
|
16551
|
+
}
|
|
16447
16552
|
const body = op.kind === "Function" || op.kind === "Workflow" ? normalizeDefinitionWriteBody(op.kind, def.body) : { ...def.body };
|
|
16448
16553
|
if (op.kind === "Agent") {
|
|
16449
16554
|
for (const field of AGENT_MANAGED_FIELDS) {
|
|
16450
16555
|
if (field in body) {
|
|
16451
|
-
const msg = `'${field}' in ${def.name} is managed separately and will be ignored`;
|
|
16452
|
-
if (warnings) {
|
|
16453
|
-
warnings.push(msg);
|
|
16454
|
-
} else {
|
|
16455
|
-
console.error(source_default.yellow(` Warning: ${msg}.`));
|
|
16456
|
-
}
|
|
16457
16556
|
delete body[field];
|
|
16458
16557
|
}
|
|
16459
16558
|
}
|
|
@@ -16486,6 +16585,9 @@ async function executeApply(api, ops, defs, warnings) {
|
|
|
16486
16585
|
port_warnings: portWarnings.length > 0 ? portWarnings : undefined
|
|
16487
16586
|
});
|
|
16488
16587
|
}
|
|
16588
|
+
if (op.kind === "Agent") {
|
|
16589
|
+
await applyAgentGrants(api, def, warnings);
|
|
16590
|
+
}
|
|
16489
16591
|
} catch (err) {
|
|
16490
16592
|
const msg = err instanceof Error ? err.message : String(err);
|
|
16491
16593
|
results.push({
|
|
@@ -16499,6 +16601,175 @@ async function executeApply(api, ops, defs, warnings) {
|
|
|
16499
16601
|
}
|
|
16500
16602
|
return results;
|
|
16501
16603
|
}
|
|
16604
|
+
async function applyPrompt(api, op, def) {
|
|
16605
|
+
const promptPath = KIND_TO_API_PATH.Prompt;
|
|
16606
|
+
const name = encodeURIComponent(def.name);
|
|
16607
|
+
const localContent = def.body.content;
|
|
16608
|
+
if (op.action === "create") {
|
|
16609
|
+
const createBody = {
|
|
16610
|
+
name: def.body.name,
|
|
16611
|
+
content: localContent
|
|
16612
|
+
};
|
|
16613
|
+
if (def.body.description)
|
|
16614
|
+
createBody.description = def.body.description;
|
|
16615
|
+
if (def.body.scope)
|
|
16616
|
+
createBody.scope = def.body.scope;
|
|
16617
|
+
const res = await api.post(promptPath, createBody);
|
|
16618
|
+
const readBack = await api.get(`${promptPath}/${name}`);
|
|
16619
|
+
const prodContent = readBack.production?.content;
|
|
16620
|
+
const localHash = createHash3("sha256").update(localContent).digest("hex");
|
|
16621
|
+
const remoteHash = prodContent ? createHash3("sha256").update(prodContent).digest("hex") : "";
|
|
16622
|
+
if (localHash !== remoteHash) {
|
|
16623
|
+
return {
|
|
16624
|
+
name: def.name,
|
|
16625
|
+
kind: "Prompt",
|
|
16626
|
+
action: "failed",
|
|
16627
|
+
error: `Content hash mismatch after create: local=${localHash.slice(0, 12)} remote=${remoteHash.slice(0, 12)}`,
|
|
16628
|
+
source: def.source
|
|
16629
|
+
};
|
|
16630
|
+
}
|
|
16631
|
+
const version = res.version_count ?? res.production?.version ?? 1;
|
|
16632
|
+
return {
|
|
16633
|
+
name: def.name,
|
|
16634
|
+
kind: "Prompt",
|
|
16635
|
+
action: "created",
|
|
16636
|
+
version
|
|
16637
|
+
};
|
|
16638
|
+
}
|
|
16639
|
+
const contentChanged = op.changedFields?.includes("content");
|
|
16640
|
+
const metadataFields = (op.changedFields ?? []).filter((f) => f !== "content");
|
|
16641
|
+
let version;
|
|
16642
|
+
if (contentChanged) {
|
|
16643
|
+
const versionRes = await api.post(`${promptPath}/${name}/versions`, {
|
|
16644
|
+
content: localContent,
|
|
16645
|
+
change_summary: def.body.change_summary ?? "Applied via definitions apply"
|
|
16646
|
+
});
|
|
16647
|
+
const newVersion = versionRes.version;
|
|
16648
|
+
version = newVersion;
|
|
16649
|
+
await api.post(`${promptPath}/${name}/promote`, {
|
|
16650
|
+
version: newVersion,
|
|
16651
|
+
environment: "production"
|
|
16652
|
+
});
|
|
16653
|
+
}
|
|
16654
|
+
if (metadataFields.length > 0) {
|
|
16655
|
+
const metaBody = {};
|
|
16656
|
+
if (metadataFields.includes("description")) {
|
|
16657
|
+
metaBody.description = def.body.description;
|
|
16658
|
+
}
|
|
16659
|
+
if (metadataFields.includes("scope")) {
|
|
16660
|
+
await api.put(`${promptPath}/${name}/scope`, {
|
|
16661
|
+
scope: def.body.scope
|
|
16662
|
+
});
|
|
16663
|
+
}
|
|
16664
|
+
if (Object.keys(metaBody).length > 0) {
|
|
16665
|
+
await api.put(`${promptPath}/${name}`, metaBody);
|
|
16666
|
+
}
|
|
16667
|
+
}
|
|
16668
|
+
if (contentChanged) {
|
|
16669
|
+
const readBack = await api.get(`${promptPath}/${name}`);
|
|
16670
|
+
const prodContent = readBack.production?.content;
|
|
16671
|
+
const localHash = createHash3("sha256").update(localContent).digest("hex");
|
|
16672
|
+
const remoteHash = prodContent ? createHash3("sha256").update(prodContent).digest("hex") : "";
|
|
16673
|
+
if (localHash !== remoteHash) {
|
|
16674
|
+
return {
|
|
16675
|
+
name: def.name,
|
|
16676
|
+
kind: "Prompt",
|
|
16677
|
+
action: "failed",
|
|
16678
|
+
error: `Content hash mismatch after update: local=${localHash.slice(0, 12)} remote=${remoteHash.slice(0, 12)}`,
|
|
16679
|
+
source: def.source
|
|
16680
|
+
};
|
|
16681
|
+
}
|
|
16682
|
+
}
|
|
16683
|
+
return {
|
|
16684
|
+
name: def.name,
|
|
16685
|
+
kind: "Prompt",
|
|
16686
|
+
action: "updated",
|
|
16687
|
+
version
|
|
16688
|
+
};
|
|
16689
|
+
}
|
|
16690
|
+
async function computeAgentGrantDiff(api, def) {
|
|
16691
|
+
const agentPath = KIND_TO_API_PATH.Agent;
|
|
16692
|
+
const name = encodeURIComponent(def.name);
|
|
16693
|
+
const declaredFunctions = def.body.functions ?? [];
|
|
16694
|
+
const declaredWorkflows = def.body.workflows ?? [];
|
|
16695
|
+
const pendingFns = [];
|
|
16696
|
+
const driftFns = [];
|
|
16697
|
+
const pendingWfs = [];
|
|
16698
|
+
const driftWfs = [];
|
|
16699
|
+
if (declaredFunctions.length > 0) {
|
|
16700
|
+
const remoteFns = await api.get(`${agentPath}/${name}/functions`);
|
|
16701
|
+
const remoteFnNames = new Set((Array.isArray(remoteFns) ? remoteFns : []).map((f) => f.function_name ?? f.name));
|
|
16702
|
+
for (const fn of declaredFunctions) {
|
|
16703
|
+
if (!remoteFnNames.has(fn))
|
|
16704
|
+
pendingFns.push(fn);
|
|
16705
|
+
}
|
|
16706
|
+
const declaredSet = new Set(declaredFunctions);
|
|
16707
|
+
for (const rn of remoteFnNames) {
|
|
16708
|
+
if (!declaredSet.has(rn))
|
|
16709
|
+
driftFns.push(rn);
|
|
16710
|
+
}
|
|
16711
|
+
}
|
|
16712
|
+
if (declaredWorkflows.length > 0) {
|
|
16713
|
+
const remoteWfs = await api.get(`${agentPath}/${name}/workflows`);
|
|
16714
|
+
const remoteWfNames = new Set((Array.isArray(remoteWfs) ? remoteWfs : []).map((w) => w.workflow_name ?? w.name));
|
|
16715
|
+
for (const wf of declaredWorkflows) {
|
|
16716
|
+
if (!remoteWfNames.has(wf))
|
|
16717
|
+
pendingWfs.push(wf);
|
|
16718
|
+
}
|
|
16719
|
+
const declaredSet = new Set(declaredWorkflows);
|
|
16720
|
+
for (const rn of remoteWfNames) {
|
|
16721
|
+
if (!declaredSet.has(rn))
|
|
16722
|
+
driftWfs.push(rn);
|
|
16723
|
+
}
|
|
16724
|
+
}
|
|
16725
|
+
return {
|
|
16726
|
+
pending: { functions: pendingFns, workflows: pendingWfs },
|
|
16727
|
+
drift: { functions: driftFns, workflows: driftWfs }
|
|
16728
|
+
};
|
|
16729
|
+
}
|
|
16730
|
+
async function applyAgentGrants(api, def, warnings) {
|
|
16731
|
+
const agentPath = KIND_TO_API_PATH.Agent;
|
|
16732
|
+
const name = encodeURIComponent(def.name);
|
|
16733
|
+
const diff = await computeAgentGrantDiff(api, def);
|
|
16734
|
+
for (const fn of diff.pending.functions) {
|
|
16735
|
+
await api.post(`${agentPath}/${name}/functions`, {
|
|
16736
|
+
function_name: fn
|
|
16737
|
+
});
|
|
16738
|
+
}
|
|
16739
|
+
for (const wf of diff.pending.workflows) {
|
|
16740
|
+
await api.post(`${agentPath}/${name}/workflows`, {
|
|
16741
|
+
workflow_name: wf
|
|
16742
|
+
});
|
|
16743
|
+
}
|
|
16744
|
+
const posted = diff.pending.functions.length + diff.pending.workflows.length;
|
|
16745
|
+
if (posted > 0) {
|
|
16746
|
+
const after = await computeAgentGrantDiff(api, def);
|
|
16747
|
+
const missing = [
|
|
16748
|
+
...after.pending.functions.map((f) => `function '${f}'`),
|
|
16749
|
+
...after.pending.workflows.map((w) => `workflow '${w}'`)
|
|
16750
|
+
];
|
|
16751
|
+
if (missing.length > 0) {
|
|
16752
|
+
throw new Error(`grants not on the server after apply for ${def.name}: ${missing.join(", ")}`);
|
|
16753
|
+
}
|
|
16754
|
+
}
|
|
16755
|
+
for (const fn of diff.drift.functions) {
|
|
16756
|
+
const msg = `drift: function '${fn}' granted to ${def.name} remotely but not declared in file`;
|
|
16757
|
+
if (warnings) {
|
|
16758
|
+
warnings.push(msg);
|
|
16759
|
+
} else {
|
|
16760
|
+
console.error(source_default.yellow(` Warning: ${msg}`));
|
|
16761
|
+
}
|
|
16762
|
+
}
|
|
16763
|
+
for (const wf of diff.drift.workflows) {
|
|
16764
|
+
const msg = `drift: workflow '${wf}' granted to ${def.name} remotely but not declared in file`;
|
|
16765
|
+
if (warnings) {
|
|
16766
|
+
warnings.push(msg);
|
|
16767
|
+
} else {
|
|
16768
|
+
console.error(source_default.yellow(` Warning: ${msg}`));
|
|
16769
|
+
}
|
|
16770
|
+
}
|
|
16771
|
+
return posted;
|
|
16772
|
+
}
|
|
16502
16773
|
function extractPortWarnings(response) {
|
|
16503
16774
|
const raw = response.port_warnings;
|
|
16504
16775
|
if (!Array.isArray(raw) || raw.length === 0)
|
|
@@ -16544,7 +16815,7 @@ function registerDefinitionsApply(program) {
|
|
|
16544
16815
|
if (!defCmd) {
|
|
16545
16816
|
defCmd = program.command("definitions").description("Cross-type definition operations");
|
|
16546
16817
|
}
|
|
16547
|
-
defCmd.command("apply").description("Apply definitions from YAML/JSON files (create or update, never deletes)").requiredOption("-f, --file <path>", "Path to a YAML/JSON file or directory of definitions").option("--dry-run", "Preview changes without applying").option("--yes", "Skip confirmation prompt").addHelpText("after", `
|
|
16818
|
+
defCmd.command("apply").description("Apply definitions from YAML/JSON files (create or update, never deletes)").requiredOption("-f, --file <path>", "Path to a YAML/JSON file or directory of definitions (repeatable; directories are not recursed)", (value, previous = []) => [...previous, value]).option("--dry-run", "Preview changes without applying").option("--yes", "Skip confirmation prompt").addHelpText("after", `
|
|
16548
16819
|
Examples:
|
|
16549
16820
|
$ martha definitions apply -f search.yaml Single file
|
|
16550
16821
|
$ martha definitions apply -f definitions/ Directory (flat)
|
|
@@ -16559,12 +16830,12 @@ Examples:
|
|
|
16559
16830
|
ctx.profile.api_url = program.opts().apiUrl;
|
|
16560
16831
|
}
|
|
16561
16832
|
const isJson = !!program.opts().json;
|
|
16562
|
-
const defs =
|
|
16833
|
+
const defs = opts.file.flatMap((f) => loadDefinitions(f));
|
|
16563
16834
|
if (defs.length === 0) {
|
|
16564
16835
|
if (isJson) {
|
|
16565
16836
|
console.log(JSON.stringify({ results: [], message: "No definitions found" }));
|
|
16566
16837
|
} else {
|
|
16567
|
-
console.log(`No definitions found in ${opts.file}`);
|
|
16838
|
+
console.log(`No definitions found in ${opts.file.join(", ")}`);
|
|
16568
16839
|
}
|
|
16569
16840
|
return;
|
|
16570
16841
|
}
|