@crustdev/armordesk 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +164 -2
- package/bin/cli.js +6 -0
- package/dist/chunk-TVWUJ3XJ.mjs +297 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +607 -0
- package/dist/cli.mjs +300 -0
- package/dist/client.d.ts +81 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/crypto.d.ts +31 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +331 -0
- package/dist/index.mjs +22 -0
- package/dist/mcp.d.ts +21 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/types.d.ts +237 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +54 -4
package/dist/cli.js
ADDED
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/cli.ts
|
|
21
|
+
var cli_exports = {};
|
|
22
|
+
__export(cli_exports, {
|
|
23
|
+
runCLI: () => runCLI
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(cli_exports);
|
|
26
|
+
|
|
27
|
+
// src/crypto.ts
|
|
28
|
+
var import_node_crypto = require("crypto");
|
|
29
|
+
function canonicalJson(data) {
|
|
30
|
+
if (data === null || data === void 0) {
|
|
31
|
+
return "null";
|
|
32
|
+
}
|
|
33
|
+
if (typeof data === "number" || typeof data === "boolean") {
|
|
34
|
+
return JSON.stringify(data);
|
|
35
|
+
}
|
|
36
|
+
if (typeof data === "string") {
|
|
37
|
+
return JSON.stringify(data);
|
|
38
|
+
}
|
|
39
|
+
if (Array.isArray(data)) {
|
|
40
|
+
return "[" + data.map((item) => canonicalJson(item)).join(",") + "]";
|
|
41
|
+
}
|
|
42
|
+
if (typeof data === "object") {
|
|
43
|
+
const keys = Object.keys(data).sort();
|
|
44
|
+
const entries = keys.map((k) => JSON.stringify(k) + ":" + canonicalJson(data[k]));
|
|
45
|
+
return "{" + entries.join(",") + "}";
|
|
46
|
+
}
|
|
47
|
+
return JSON.stringify(data);
|
|
48
|
+
}
|
|
49
|
+
function hashSha256(data) {
|
|
50
|
+
return (0, import_node_crypto.createHash)("sha256").update(data, "utf8").digest("hex");
|
|
51
|
+
}
|
|
52
|
+
function generatePlanHash(planDict) {
|
|
53
|
+
const canonical = canonicalJson(planDict);
|
|
54
|
+
return hashSha256(canonical);
|
|
55
|
+
}
|
|
56
|
+
function buildMerkleTree(leafItems) {
|
|
57
|
+
const leafHashes = leafItems.length === 0 ? [hashSha256("empty_tree_node")] : leafItems.map((item) => hashSha256(item));
|
|
58
|
+
let currentLevel = [...leafHashes];
|
|
59
|
+
while (currentLevel.length > 1) {
|
|
60
|
+
const nextLevel = [];
|
|
61
|
+
for (let i = 0; i < currentLevel.length; i += 2) {
|
|
62
|
+
const left = currentLevel[i];
|
|
63
|
+
const right = i + 1 < currentLevel.length ? currentLevel[i + 1] : left;
|
|
64
|
+
const combined = hashSha256(left + right);
|
|
65
|
+
nextLevel.push(combined);
|
|
66
|
+
}
|
|
67
|
+
currentLevel = nextLevel;
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
root: currentLevel[0],
|
|
71
|
+
leafHashes
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function verifyMerkleRoot(leafItems, expectedRoot) {
|
|
75
|
+
const { root } = buildMerkleTree(leafItems);
|
|
76
|
+
return root.toLowerCase() === expectedRoot.toLowerCase();
|
|
77
|
+
}
|
|
78
|
+
function verifyProof(request) {
|
|
79
|
+
const computedPlanHash = generatePlanHash(request.allowed_spec);
|
|
80
|
+
const matchesHash = computedPlanHash.toLowerCase() === request.plan_hash.toLowerCase();
|
|
81
|
+
const executionItems = Object.entries(request.actual_execution).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}:${canonicalJson(v)}`);
|
|
82
|
+
const { root: computedMerkleRoot } = buildMerkleTree(executionItems);
|
|
83
|
+
const matchesMerkle = computedMerkleRoot.toLowerCase() === request.merkle_root.toLowerCase();
|
|
84
|
+
const valid = matchesHash && matchesMerkle;
|
|
85
|
+
return {
|
|
86
|
+
valid,
|
|
87
|
+
computed_plan_hash: computedPlanHash,
|
|
88
|
+
computed_merkle_root: computedMerkleRoot,
|
|
89
|
+
matches_hash: matchesHash,
|
|
90
|
+
matches_merkle: matchesMerkle,
|
|
91
|
+
status: valid ? "CRYPTOGRAPHICALLY_VERIFIED" : "VERIFICATION_FAILED",
|
|
92
|
+
details: valid ? "Plan hash matches allowed specification and Merkle root confirms un-tampered execution proof." : `Discrepancy detected: hashMatch=${matchesHash}, merkleMatch=${matchesMerkle}`
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/client.ts
|
|
97
|
+
var ArmorDeskClient = class {
|
|
98
|
+
baseUrl;
|
|
99
|
+
apiKey;
|
|
100
|
+
tenantId;
|
|
101
|
+
userName;
|
|
102
|
+
userRole;
|
|
103
|
+
_fetch;
|
|
104
|
+
crypto = {
|
|
105
|
+
canonicalJson,
|
|
106
|
+
generatePlanHash,
|
|
107
|
+
buildMerkleTree,
|
|
108
|
+
verifyMerkleRoot,
|
|
109
|
+
verifyProof
|
|
110
|
+
};
|
|
111
|
+
constructor(options = {}) {
|
|
112
|
+
this.baseUrl = (options.baseUrl || typeof process !== "undefined" && process.env?.ARMORDESK_URL || "https://armordesk.crusts.dev").replace(/\/$/, "");
|
|
113
|
+
this.apiKey = options.apiKey || (typeof process !== "undefined" ? process.env?.ARMORIQ_API_KEY || process.env?.ARMORIQ_PRO_API_KEY : void 0);
|
|
114
|
+
this.tenantId = options.tenantId || "acme-corp";
|
|
115
|
+
this.userName = options.userName || "Sarah Chen";
|
|
116
|
+
this.userRole = options.userRole || "manager";
|
|
117
|
+
this._fetch = options.fetch || globalThis.fetch.bind(globalThis);
|
|
118
|
+
}
|
|
119
|
+
async request(endpoint, options = {}) {
|
|
120
|
+
const url = `${this.baseUrl}${endpoint.startsWith("/") ? endpoint : "/" + endpoint}`;
|
|
121
|
+
const headers = {
|
|
122
|
+
"Content-Type": "application/json",
|
|
123
|
+
"X-ArmorIQ-Tenant": this.tenantId,
|
|
124
|
+
...options.headers
|
|
125
|
+
};
|
|
126
|
+
if (this.apiKey) {
|
|
127
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
128
|
+
headers["X-ArmorIQ-API-Key"] = this.apiKey;
|
|
129
|
+
}
|
|
130
|
+
const res = await this._fetch(url, {
|
|
131
|
+
...options,
|
|
132
|
+
headers
|
|
133
|
+
});
|
|
134
|
+
if (!res.ok) {
|
|
135
|
+
let errorBody = "";
|
|
136
|
+
try {
|
|
137
|
+
errorBody = await res.text();
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
throw new Error(
|
|
141
|
+
`ArmorDesk API error [${res.status} ${res.statusText}] at ${endpoint}: ${errorBody || "Unknown error"}`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
return res.json();
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Health and runtime operational telemetry
|
|
148
|
+
*/
|
|
149
|
+
async status() {
|
|
150
|
+
return this.request("/api/status");
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* List managed corporate tenants
|
|
154
|
+
*/
|
|
155
|
+
async getTenants() {
|
|
156
|
+
return this.request("/api/tenants");
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Fetch immutable security and policy enforcement traces
|
|
160
|
+
*/
|
|
161
|
+
async getTraces(tenantId) {
|
|
162
|
+
const tid = tenantId || this.tenantId;
|
|
163
|
+
return this.request(`/api/traces?tenant_id=${encodeURIComponent(tid)}`);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Fetch approval queue items held by human-in-the-loop policies
|
|
167
|
+
*/
|
|
168
|
+
async getApprovals(tenantId) {
|
|
169
|
+
const tid = tenantId || this.tenantId;
|
|
170
|
+
return this.request(`/api/approvals?tenant_id=${encodeURIComponent(tid)}`);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Authorize or reject a held action in escrow
|
|
174
|
+
*/
|
|
175
|
+
async resolveApproval(delegationId, action, notes) {
|
|
176
|
+
const payload = {
|
|
177
|
+
action,
|
|
178
|
+
user_role: this.userRole,
|
|
179
|
+
user_name: this.userName,
|
|
180
|
+
notes
|
|
181
|
+
};
|
|
182
|
+
return this.request(`/api/approvals/${encodeURIComponent(delegationId)}/action`, {
|
|
183
|
+
method: "POST",
|
|
184
|
+
body: JSON.stringify(payload)
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* List digital verification receipts with cryptographic proofs
|
|
189
|
+
*/
|
|
190
|
+
async getReceipts(tenantId) {
|
|
191
|
+
const tid = tenantId || this.tenantId;
|
|
192
|
+
return this.request(`/api/receipts?tenant_id=${encodeURIComponent(tid)}`);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Retrieve a specific receipt by ID
|
|
196
|
+
*/
|
|
197
|
+
async getReceipt(receiptId) {
|
|
198
|
+
return this.request(`/api/receipts/${encodeURIComponent(receiptId)}`);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Cryptographically verify a digital proof
|
|
202
|
+
*/
|
|
203
|
+
async verifyProof(proof) {
|
|
204
|
+
return this.request("/api/receipts/verify", {
|
|
205
|
+
method: "POST",
|
|
206
|
+
body: JSON.stringify(proof)
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Get MCP Gateway configuration and sample connection specs
|
|
211
|
+
*/
|
|
212
|
+
async getMCPConfig(tenantId) {
|
|
213
|
+
const tid = tenantId || this.tenantId;
|
|
214
|
+
return this.request(`/api/mcp/config?tenant_id=${encodeURIComponent(tid)}`);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Invoke a tool through the Governed MCP Gateway
|
|
218
|
+
*/
|
|
219
|
+
async callMCPTool(toolName, args, options = {}) {
|
|
220
|
+
const rpcPayload = {
|
|
221
|
+
jsonrpc: "2.0",
|
|
222
|
+
id: Date.now(),
|
|
223
|
+
method: "tools/call",
|
|
224
|
+
params: {
|
|
225
|
+
name: toolName,
|
|
226
|
+
arguments: args,
|
|
227
|
+
context: {
|
|
228
|
+
user_role: options.userRole || this.userRole,
|
|
229
|
+
user_name: options.userName || this.userName,
|
|
230
|
+
delegation_id: options.delegationId
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
return this.request("/api/mcp/gateway", {
|
|
235
|
+
method: "POST",
|
|
236
|
+
body: JSON.stringify(rpcPayload)
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Execute an adversarial FinOps scenario simulation (1 to 5)
|
|
241
|
+
*/
|
|
242
|
+
async runScenario(scenarioId, tenantId) {
|
|
243
|
+
return this.request("/api/scenarios/run", {
|
|
244
|
+
method: "POST",
|
|
245
|
+
body: JSON.stringify({
|
|
246
|
+
scenario_id: scenarioId,
|
|
247
|
+
tenant_id: tenantId || this.tenantId
|
|
248
|
+
})
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Test prompt injection attacks against ArmorDesk CTF Gauntlet
|
|
253
|
+
*/
|
|
254
|
+
async submitCTF(prompt, attackerName, tenantId) {
|
|
255
|
+
return this.request("/api/ctf/submit", {
|
|
256
|
+
method: "POST",
|
|
257
|
+
body: JSON.stringify({
|
|
258
|
+
prompt,
|
|
259
|
+
attacker_name: attackerName || "PenTester",
|
|
260
|
+
tenant_id: tenantId || this.tenantId
|
|
261
|
+
})
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Generate an executive voice briefing via ElevenLabs
|
|
266
|
+
*/
|
|
267
|
+
async getVoiceBriefing(briefingType = "executive", tenantId) {
|
|
268
|
+
return this.request("/api/voice/briefing", {
|
|
269
|
+
method: "POST",
|
|
270
|
+
body: JSON.stringify({
|
|
271
|
+
briefing_type: briefingType,
|
|
272
|
+
tenant_id: tenantId || this.tenantId
|
|
273
|
+
})
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// src/mcp.ts
|
|
279
|
+
function generateClaudeDesktopConfig(options = {}) {
|
|
280
|
+
const url = options.gatewayUrl || "https://armordesk.crusts.dev/api/mcp/gateway";
|
|
281
|
+
const tenant = options.tenantId || "acme-corp";
|
|
282
|
+
const apiKey = options.apiKey || "ak_live_acme_corp";
|
|
283
|
+
return {
|
|
284
|
+
mcpServers: {
|
|
285
|
+
armordesk_gateway: {
|
|
286
|
+
command: "npx",
|
|
287
|
+
args: ["-y", "@modelcontextprotocol/server-fetch", url],
|
|
288
|
+
env: {
|
|
289
|
+
ARMORIQ_TENANT: tenant,
|
|
290
|
+
ARMORIQ_API_KEY: apiKey
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function generateCursorConfig(options = {}) {
|
|
297
|
+
const url = options.gatewayUrl || "https://armordesk.crusts.dev/api/mcp/gateway";
|
|
298
|
+
const tenant = options.tenantId || "acme-corp";
|
|
299
|
+
const apiKey = options.apiKey || "ak_live_acme_corp";
|
|
300
|
+
return {
|
|
301
|
+
mcpServers: {
|
|
302
|
+
armordesk: {
|
|
303
|
+
url,
|
|
304
|
+
headers: {
|
|
305
|
+
"X-ArmorIQ-Tenant": tenant,
|
|
306
|
+
Authorization: `Bearer ${apiKey}`
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/cli.ts
|
|
314
|
+
var HELP_TEXT = `
|
|
315
|
+
\u{1F6E1}\uFE0F ArmorDesk CLI: Enterprise AI Agent Control Plane & MCP Gateway
|
|
316
|
+
|
|
317
|
+
Usage:
|
|
318
|
+
armordesk <command> [options]
|
|
319
|
+
|
|
320
|
+
Commands:
|
|
321
|
+
status Check operational status of ArmorDesk Control Plane
|
|
322
|
+
tenants List active corporate tenants & spend policies
|
|
323
|
+
traces Inspect recent audited security & policy traces
|
|
324
|
+
approvals View pending human-in-the-loop approvals
|
|
325
|
+
approve <delegation_id> Authorize an action held in escrow
|
|
326
|
+
reject <delegation_id> Reject an action held in escrow
|
|
327
|
+
receipts List digital execution receipts with proofs
|
|
328
|
+
verify <receipt_id> Cryptographically verify a receipt (Plan Hash & Merkle Root)
|
|
329
|
+
mcp [claude|cursor] Show MCP Gateway config for Claude Desktop or Cursor
|
|
330
|
+
scenario <1-5> Run an adversarial FinOps crew simulation
|
|
331
|
+
ctf "<prompt>" Test prompt injection attack against CTF Gauntlet
|
|
332
|
+
voice [executive|security] Generate voice briefing telemetry via ElevenLabs
|
|
333
|
+
|
|
334
|
+
Options:
|
|
335
|
+
--url <url> ArmorDesk Control Plane URL (default: https://armordesk.crusts.dev)
|
|
336
|
+
--tenant <id> Tenant ID (default: acme-corp)
|
|
337
|
+
--key <key> ArmorIQ API Key
|
|
338
|
+
--json Output raw JSON
|
|
339
|
+
--help, -h Show help menu
|
|
340
|
+
--version, -v Show version
|
|
341
|
+
`;
|
|
342
|
+
async function runCLI(args = process.argv.slice(2)) {
|
|
343
|
+
const flags = {};
|
|
344
|
+
const positional = [];
|
|
345
|
+
for (let i = 0; i < args.length; i++) {
|
|
346
|
+
const arg = args[i];
|
|
347
|
+
if (arg === "--help" || arg === "-h") {
|
|
348
|
+
console.log(HELP_TEXT);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (arg === "--version" || arg === "-v") {
|
|
352
|
+
console.log("armordesk v1.0.0");
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (arg.startsWith("--")) {
|
|
356
|
+
const key = arg.slice(2);
|
|
357
|
+
if (i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
|
358
|
+
flags[key] = args[i + 1];
|
|
359
|
+
i++;
|
|
360
|
+
} else {
|
|
361
|
+
flags[key] = true;
|
|
362
|
+
}
|
|
363
|
+
} else {
|
|
364
|
+
positional.push(arg);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const command = positional[0] || "status";
|
|
368
|
+
const client = new ArmorDeskClient({
|
|
369
|
+
baseUrl: flags["url"] || process.env.ARMORDESK_URL,
|
|
370
|
+
tenantId: flags["tenant"] || "acme-corp",
|
|
371
|
+
apiKey: flags["key"] || process.env.ARMORIQ_API_KEY
|
|
372
|
+
});
|
|
373
|
+
try {
|
|
374
|
+
switch (command) {
|
|
375
|
+
case "status":
|
|
376
|
+
case "info": {
|
|
377
|
+
const st = await client.status();
|
|
378
|
+
if (flags["json"]) {
|
|
379
|
+
console.log(JSON.stringify(st, null, 2));
|
|
380
|
+
} else {
|
|
381
|
+
console.log("\n\u{1F6E1}\uFE0F ArmorDesk Control Plane Status");
|
|
382
|
+
console.log("---------------------------------------");
|
|
383
|
+
console.log(`Endpoint: ${client.baseUrl}`);
|
|
384
|
+
console.log(`Status: ${st.status}`);
|
|
385
|
+
console.log(`Service: ${st.service} (v${st.version})`);
|
|
386
|
+
console.log(`ArmorIQ Pro: ${st.armoriq_pro_active ? "ACTIVE (PRO_LIVE)" : "SANDBOX"}`);
|
|
387
|
+
console.log(`Tenants: ${st.active_tenants} active organizations`);
|
|
388
|
+
console.log(`Timestamp: ${st.timestamp}
|
|
389
|
+
`);
|
|
390
|
+
}
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
case "tenants": {
|
|
394
|
+
const tenants = await client.getTenants();
|
|
395
|
+
if (flags["json"]) {
|
|
396
|
+
console.log(JSON.stringify(tenants, null, 2));
|
|
397
|
+
} else {
|
|
398
|
+
console.log("\n\u{1F3E2} Active Corporate Tenants:");
|
|
399
|
+
tenants.forEach((t) => {
|
|
400
|
+
console.log(` \u2022 [${t.id}] ${t.name}`);
|
|
401
|
+
console.log(` Balance: $${t.balance.toLocaleString()} | Spend Limit: $${t.spend_limit.toLocaleString()}`);
|
|
402
|
+
});
|
|
403
|
+
console.log("");
|
|
404
|
+
}
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
case "traces": {
|
|
408
|
+
const traces = await client.getTraces();
|
|
409
|
+
if (flags["json"]) {
|
|
410
|
+
console.log(JSON.stringify(traces, null, 2));
|
|
411
|
+
} else {
|
|
412
|
+
console.log(`
|
|
413
|
+
\u{1F4CB} Recent Security Traces (${traces.length} total):
|
|
414
|
+
`);
|
|
415
|
+
traces.slice(0, 10).forEach((tr) => {
|
|
416
|
+
const badge = tr.status === "ALLOWED" ? "\u{1F7E2} ALLOWED" : tr.status === "BLOCKED" ? "\u{1F534} BLOCKED" : "\u{1F7E1} HELD_FOR_APPROVAL";
|
|
417
|
+
console.log(` [${tr.created_at.slice(11, 19)}] ${badge} - ${tr.agent_name} (${tr.user_role})`);
|
|
418
|
+
console.log(` Action: ${tr.action}`);
|
|
419
|
+
console.log(` Reason: ${tr.reason}`);
|
|
420
|
+
console.log(` Plan Hash: ${tr.plan_hash.slice(0, 16)}...`);
|
|
421
|
+
console.log("");
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
case "approvals": {
|
|
427
|
+
const approvals = await client.getApprovals();
|
|
428
|
+
if (flags["json"]) {
|
|
429
|
+
console.log(JSON.stringify(approvals, null, 2));
|
|
430
|
+
} else {
|
|
431
|
+
console.log(`
|
|
432
|
+
\u23F3 Human-in-the-Loop Approvals (${approvals.length} items):
|
|
433
|
+
`);
|
|
434
|
+
if (approvals.length === 0) {
|
|
435
|
+
console.log(" No pending approvals. All actions authorized or clear.\n");
|
|
436
|
+
} else {
|
|
437
|
+
approvals.forEach((app) => {
|
|
438
|
+
console.log(` \u2022 ID: ${app.delegation_id} | Status: ${app.status}`);
|
|
439
|
+
console.log(` Action: ${app.action_type} -> ${app.recipient} ($${app.amount.toLocaleString()})`);
|
|
440
|
+
console.log(` Requested by: ${app.requested_by} (${app.role})`);
|
|
441
|
+
console.log(` Description: ${app.description}`);
|
|
442
|
+
console.log("");
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
case "approve": {
|
|
449
|
+
const delId = positional[1];
|
|
450
|
+
if (!delId) {
|
|
451
|
+
console.error("Error: Please provide delegation_id to approve. (e.g. armordesk approve del_01hx9a)");
|
|
452
|
+
process.exit(1);
|
|
453
|
+
}
|
|
454
|
+
const res = await client.resolveApproval(delId, "APPROVE");
|
|
455
|
+
console.log(`\u2705 Approved delegation '${delId}' successfully.`);
|
|
456
|
+
if (res.receipt_id) {
|
|
457
|
+
console.log(` Digital Proof Receipt Generated: ${res.receipt_id}`);
|
|
458
|
+
}
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
case "reject": {
|
|
462
|
+
const delId = positional[1];
|
|
463
|
+
if (!delId) {
|
|
464
|
+
console.error("Error: Please provide delegation_id to reject. (e.g. armordesk reject del_01hx9a)");
|
|
465
|
+
process.exit(1);
|
|
466
|
+
}
|
|
467
|
+
await client.resolveApproval(delId, "REJECT");
|
|
468
|
+
console.log(`\u{1F6AB} Rejected delegation '${delId}'.`);
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
case "receipts": {
|
|
472
|
+
const receipts = await client.getReceipts();
|
|
473
|
+
if (flags["json"]) {
|
|
474
|
+
console.log(JSON.stringify(receipts, null, 2));
|
|
475
|
+
} else {
|
|
476
|
+
console.log(`
|
|
477
|
+
\u{1F4DC} Cryptographic Proof Receipts (${receipts.length} total):
|
|
478
|
+
`);
|
|
479
|
+
receipts.forEach((r) => {
|
|
480
|
+
console.log(` \u2022 [${r.id}] ${r.action_type} ($${r.amount}) -> ${r.recipient}`);
|
|
481
|
+
console.log(` Status: ${r.status} | Verified: ${r.verified ? "\u2705 YES" : "\u274C NO"}`);
|
|
482
|
+
console.log(` Plan Hash: ${r.plan_hash}`);
|
|
483
|
+
console.log(` Merkle Root: ${r.merkle_root}`);
|
|
484
|
+
console.log("");
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
case "verify": {
|
|
490
|
+
const rId = positional[1];
|
|
491
|
+
if (!rId) {
|
|
492
|
+
console.error("Error: Please specify receipt ID to verify (e.g. armordesk verify rcpt_01hx9a)");
|
|
493
|
+
process.exit(1);
|
|
494
|
+
}
|
|
495
|
+
const receipt = await client.getReceipt(rId);
|
|
496
|
+
const proofRes = await client.verifyProof({
|
|
497
|
+
plan_hash: receipt.plan_hash,
|
|
498
|
+
merkle_root: receipt.merkle_root,
|
|
499
|
+
signature: receipt.signature,
|
|
500
|
+
allowed_spec: receipt.allowed_spec,
|
|
501
|
+
actual_execution: receipt.actual_execution
|
|
502
|
+
});
|
|
503
|
+
console.log(`
|
|
504
|
+
\u{1F50D} Cryptographic Proof Verification for [${rId}]`);
|
|
505
|
+
console.log("------------------------------------------------------------");
|
|
506
|
+
console.log(`Verdict: ${proofRes.valid ? "\u2705 VALID" : "\u274C INVALID"}`);
|
|
507
|
+
console.log(`Status: ${proofRes.status}`);
|
|
508
|
+
console.log(`Computed Plan Hash: ${proofRes.computed_plan_hash}`);
|
|
509
|
+
console.log(`Matches Hash: ${proofRes.matches_hash ? "MATCH" : "MISMATCH"}`);
|
|
510
|
+
console.log(`Computed Merkle: ${proofRes.computed_merkle_root}`);
|
|
511
|
+
console.log(`Matches Merkle: ${proofRes.matches_merkle ? "MATCH" : "MISMATCH"}`);
|
|
512
|
+
console.log(`Details: ${proofRes.details}
|
|
513
|
+
`);
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
case "mcp": {
|
|
517
|
+
const sub = positional[1] || "info";
|
|
518
|
+
if (sub === "claude") {
|
|
519
|
+
console.log(JSON.stringify(generateClaudeDesktopConfig({ gatewayUrl: `${client.baseUrl}/api/mcp/gateway` }), null, 2));
|
|
520
|
+
} else if (sub === "cursor") {
|
|
521
|
+
console.log(JSON.stringify(generateCursorConfig({ gatewayUrl: `${client.baseUrl}/api/mcp/gateway` }), null, 2));
|
|
522
|
+
} else {
|
|
523
|
+
const cfg = await client.getMCPConfig();
|
|
524
|
+
if (flags["json"]) {
|
|
525
|
+
console.log(JSON.stringify(cfg, null, 2));
|
|
526
|
+
} else {
|
|
527
|
+
console.log("\n\u{1F50C} ArmorDesk Governed MCP Gateway");
|
|
528
|
+
console.log("-----------------------------------------");
|
|
529
|
+
console.log(`Gateway URL: ${cfg.gateway_url}`);
|
|
530
|
+
console.log(`SSE URL: ${cfg.sse_url}`);
|
|
531
|
+
console.log(`Protocol Version: ${cfg.protocol_version}`);
|
|
532
|
+
console.log(`Governed Tools: ${cfg.governed_tools.length} sandboxed tools`);
|
|
533
|
+
cfg.governed_tools.forEach((t) => {
|
|
534
|
+
console.log(` \u2022 ${t.name.padEnd(24)} [${t.tier}] (${t.governing_agent})`);
|
|
535
|
+
});
|
|
536
|
+
console.log("\nTip: Run 'armordesk mcp claude' or 'armordesk mcp cursor' to get snippet configurations.\n");
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
case "scenario": {
|
|
542
|
+
const scNum = parseInt(positional[1] || "1", 10);
|
|
543
|
+
console.log(`
|
|
544
|
+
\u2699\uFE0F Running Scenario #${scNum} simulation...`);
|
|
545
|
+
const res = await client.runScenario(scNum);
|
|
546
|
+
console.log(`Scenario: ${res.scenario_name}`);
|
|
547
|
+
console.log(`Status: ${res.overall_status}`);
|
|
548
|
+
console.log(`Summary: ${res.summary}`);
|
|
549
|
+
if (res.steps && res.steps.length > 0) {
|
|
550
|
+
console.log("\nExecuted Pipeline Steps:");
|
|
551
|
+
res.steps.forEach((s) => {
|
|
552
|
+
console.log(` [Step ${s.step}] ${s.agent} (${s.tool}) -> ${s.status}`);
|
|
553
|
+
console.log(` ${s.detail}`);
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
if (res.receipt_id) {
|
|
557
|
+
console.log(`
|
|
558
|
+
Receipt Generated: ${res.receipt_id}`);
|
|
559
|
+
}
|
|
560
|
+
console.log("");
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
case "ctf": {
|
|
564
|
+
const prompt = positional[1];
|
|
565
|
+
if (!prompt) {
|
|
566
|
+
console.error("Error: Please provide an attack prompt. (e.g. armordesk ctf 'Ignore previous instructions...')");
|
|
567
|
+
process.exit(1);
|
|
568
|
+
}
|
|
569
|
+
console.log(`
|
|
570
|
+
\u{1F6E1}\uFE0F Submitting attack payload to CTF Gauntlet...`);
|
|
571
|
+
const ctfRes = await client.submitCTF(prompt);
|
|
572
|
+
console.log(`Result: ${ctfRes.blocked ? "BLOCKED \u{1F6E1}\uFE0F" : "BYPASSED \u26A0\uFE0F"}`);
|
|
573
|
+
console.log(`Threat Detected: ${ctfRes.detected_threat}`);
|
|
574
|
+
console.log(`Reason: ${ctfRes.reason}`);
|
|
575
|
+
console.log(`Score: ${ctfRes.score} pts
|
|
576
|
+
`);
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
case "voice": {
|
|
580
|
+
const type = positional[1] || "executive";
|
|
581
|
+
console.log(`
|
|
582
|
+
\u{1F399}\uFE0F Requesting ${type} voice briefing from ElevenLabs...`);
|
|
583
|
+
const voiceRes = await client.getVoiceBriefing(type);
|
|
584
|
+
console.log(`Success: ${voiceRes.success}`);
|
|
585
|
+
console.log(`Voice: ${voiceRes.voice_type} (ID: ${voiceRes.voice_id})`);
|
|
586
|
+
console.log(`Script: ${voiceRes.script}`);
|
|
587
|
+
console.log(`Audio Size: ${Math.round((voiceRes.audio_base64?.length || 0) * 0.75)} bytes (MP3)
|
|
588
|
+
`);
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
default: {
|
|
592
|
+
console.log(`Unknown command: '${command}'`);
|
|
593
|
+
console.log(HELP_TEXT);
|
|
594
|
+
process.exit(1);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
} catch (err) {
|
|
598
|
+
console.error(`
|
|
599
|
+
\u274C Error: ${err.message}
|
|
600
|
+
`);
|
|
601
|
+
process.exit(1);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
605
|
+
0 && (module.exports = {
|
|
606
|
+
runCLI
|
|
607
|
+
});
|