opena2a-cli 0.5.4 → 0.5.6
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/README.md +35 -16
- package/dist/adapters/registry.d.ts.map +1 -1
- package/dist/adapters/registry.js +1 -0
- package/dist/adapters/registry.js.map +1 -1
- package/dist/commands/benchmark.js +1 -1
- package/dist/commands/demo.d.ts +21 -0
- package/dist/commands/demo.d.ts.map +1 -0
- package/dist/commands/demo.js +683 -0
- package/dist/commands/demo.js.map +1 -0
- package/dist/commands/detect.d.ts +58 -0
- package/dist/commands/detect.d.ts.map +1 -0
- package/dist/commands/detect.js +335 -0
- package/dist/commands/detect.js.map +1 -0
- package/dist/commands/gcp-sm-migration.d.ts +17 -0
- package/dist/commands/gcp-sm-migration.d.ts.map +1 -0
- package/dist/commands/gcp-sm-migration.js +295 -0
- package/dist/commands/gcp-sm-migration.js.map +1 -0
- package/dist/commands/identity.js +3 -1
- package/dist/commands/identity.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +21 -10
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/mcp-audit.d.ts +50 -0
- package/dist/commands/mcp-audit.d.ts.map +1 -0
- package/dist/commands/mcp-audit.js +501 -0
- package/dist/commands/mcp-audit.js.map +1 -0
- package/dist/commands/protect.d.ts.map +1 -1
- package/dist/commands/protect.js +10 -1
- package/dist/commands/protect.js.map +1 -1
- package/dist/commands/runtime.d.ts.map +1 -1
- package/dist/commands/runtime.js +106 -23
- package/dist/commands/runtime.js.map +1 -1
- package/dist/commands/self-register.js +1 -1
- package/dist/commands/self-register.js.map +1 -1
- package/dist/commands/soul.js +3 -3
- package/dist/commands/soul.js.map +1 -1
- package/dist/guided/wizard.js +2 -2
- package/dist/guided/wizard.js.map +1 -1
- package/dist/index.js +74 -6
- package/dist/index.js.map +1 -1
- package/dist/natural/llm-fallback.js +1 -1
- package/dist/report/interactive-html.js +1 -1
- package/dist/report/review-html.js +2 -2
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +43 -4
- package/dist/router.js.map +1 -1
- package/dist/semantic/command-index.json +3 -3
- package/dist/shield/init.d.ts.map +1 -1
- package/dist/shield/init.js +16 -1
- package/dist/shield/init.js.map +1 -1
- package/dist/shield/status.d.ts.map +1 -1
- package/dist/shield/status.js +22 -1
- package/dist/shield/status.js.map +1 -1
- package/dist/util/footer.d.ts +17 -0
- package/dist/util/footer.d.ts.map +1 -0
- package/dist/util/footer.js +25 -0
- package/dist/util/footer.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* opena2a demo -- Interactive demonstration of AIM capabilities.
|
|
4
|
+
*
|
|
5
|
+
* Runs a self-contained, narrated walkthrough showing the full AIM lifecycle
|
|
6
|
+
* in a temporary sandbox. No Docker or external services required.
|
|
7
|
+
*
|
|
8
|
+
* Scenarios:
|
|
9
|
+
* aim (default) -- Identity, policy, signing, credential migration
|
|
10
|
+
* dvaa -- Attack/defend loop against a vulnerable agent config
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.demo = demo;
|
|
47
|
+
const fs = __importStar(require("node:fs"));
|
|
48
|
+
const path = __importStar(require("node:path"));
|
|
49
|
+
const os = __importStar(require("node:os"));
|
|
50
|
+
const crypto = __importStar(require("node:crypto"));
|
|
51
|
+
const readline = __importStar(require("node:readline"));
|
|
52
|
+
const colors_js_1 = require("../util/colors.js");
|
|
53
|
+
const footer_js_1 = require("../util/footer.js");
|
|
54
|
+
// --- Helpers ---
|
|
55
|
+
const STEP_DELAY_MS = 300;
|
|
56
|
+
function sleep(ms) {
|
|
57
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
58
|
+
}
|
|
59
|
+
async function waitForEnter(interactive) {
|
|
60
|
+
if (!interactive)
|
|
61
|
+
return;
|
|
62
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
process.stderr.write((0, colors_js_1.dim)(' [Press Enter to continue]') + '\n');
|
|
65
|
+
rl.once('line', () => {
|
|
66
|
+
rl.close();
|
|
67
|
+
resolve();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function printStepHeader(step) {
|
|
72
|
+
process.stdout.write('\n');
|
|
73
|
+
process.stdout.write((0, colors_js_1.bold)((0, colors_js_1.cyan)(`Step ${step.step}/${step.total}: ${step.title}`)) + '\n');
|
|
74
|
+
process.stdout.write((0, colors_js_1.dim)(` ${step.description}`) + '\n');
|
|
75
|
+
}
|
|
76
|
+
function generateDemoId() {
|
|
77
|
+
return 'aim_demo_' + crypto.randomBytes(6).toString('hex');
|
|
78
|
+
}
|
|
79
|
+
function generateKeyPair() {
|
|
80
|
+
const bytes = crypto.randomBytes(32);
|
|
81
|
+
const hash = crypto.createHash('sha256').update(bytes).digest('hex');
|
|
82
|
+
return {
|
|
83
|
+
publicKey: 'ed25519:' + hash.slice(0, 16) + '...',
|
|
84
|
+
privateKeyHex: hash,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function nowISO() {
|
|
88
|
+
return new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
89
|
+
}
|
|
90
|
+
// --- Sandbox setup ---
|
|
91
|
+
function createSandbox(dir) {
|
|
92
|
+
if (dir) {
|
|
93
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
94
|
+
return dir;
|
|
95
|
+
}
|
|
96
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), 'opena2a-demo-'));
|
|
97
|
+
}
|
|
98
|
+
function cleanupSandbox(sandboxDir, keep) {
|
|
99
|
+
if (keep)
|
|
100
|
+
return;
|
|
101
|
+
try {
|
|
102
|
+
fs.rmSync(sandboxDir, { recursive: true, force: true });
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// Best-effort cleanup
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function writeSandboxFiles(sandboxDir) {
|
|
109
|
+
// package.json
|
|
110
|
+
fs.writeFileSync(path.join(sandboxDir, 'package.json'), JSON.stringify({
|
|
111
|
+
name: 'demo-ai-agent',
|
|
112
|
+
version: '1.0.0',
|
|
113
|
+
description: 'Sample AI agent for AIM demo',
|
|
114
|
+
main: 'index.js',
|
|
115
|
+
dependencies: { langchain: '^0.1.0', openai: '^4.0.0' },
|
|
116
|
+
}, null, 2));
|
|
117
|
+
// Fake .env with intentionally exposed credentials
|
|
118
|
+
fs.writeFileSync(path.join(sandboxDir, '.env'), [
|
|
119
|
+
'# Demo credentials (FAKE - for demonstration only)',
|
|
120
|
+
'OPENAI_API_KEY=sk-FAKE-demo-key-1234567890abcdef',
|
|
121
|
+
'DATABASE_URL=postgresql://admin:password123@localhost:5432/mydb',
|
|
122
|
+
'',
|
|
123
|
+
].join('\n'));
|
|
124
|
+
// MCP config
|
|
125
|
+
const mcpDir = path.join(sandboxDir, '.cursor');
|
|
126
|
+
fs.mkdirSync(mcpDir, { recursive: true });
|
|
127
|
+
fs.writeFileSync(path.join(mcpDir, 'mcp.json'), JSON.stringify({
|
|
128
|
+
mcpServers: {
|
|
129
|
+
filesystem: {
|
|
130
|
+
command: 'npx',
|
|
131
|
+
args: ['-y', '@modelcontextprotocol/server-filesystem', '/'],
|
|
132
|
+
env: {},
|
|
133
|
+
},
|
|
134
|
+
database: {
|
|
135
|
+
command: 'npx',
|
|
136
|
+
args: ['-y', 'mcp-server-postgres'],
|
|
137
|
+
env: { DB_URL: 'postgresql://admin:password123@localhost:5432/mydb' },
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
}, null, 2));
|
|
141
|
+
// A simple config.js with embedded key
|
|
142
|
+
fs.writeFileSync(path.join(sandboxDir, 'config.js'), [
|
|
143
|
+
'// Agent configuration',
|
|
144
|
+
'module.exports = {',
|
|
145
|
+
' apiKey: "sk-FAKE-hardcoded-key-abcdef1234",',
|
|
146
|
+
' model: "gpt-4",',
|
|
147
|
+
' maxTokens: 4096,',
|
|
148
|
+
'};',
|
|
149
|
+
'',
|
|
150
|
+
].join('\n'));
|
|
151
|
+
}
|
|
152
|
+
function writeDvaaFiles(sandboxDir) {
|
|
153
|
+
// package.json for vulnerable agent
|
|
154
|
+
fs.writeFileSync(path.join(sandboxDir, 'package.json'), JSON.stringify({
|
|
155
|
+
name: 'vulnerable-ai-agent',
|
|
156
|
+
version: '0.1.0',
|
|
157
|
+
description: 'Intentionally vulnerable AI agent for demo',
|
|
158
|
+
main: 'agent.js',
|
|
159
|
+
dependencies: { openai: '^4.0.0', express: '^4.18.0' },
|
|
160
|
+
}, null, 2));
|
|
161
|
+
// Hardcoded API key in config
|
|
162
|
+
fs.writeFileSync(path.join(sandboxDir, 'config.js'), [
|
|
163
|
+
'// DVAA agent config',
|
|
164
|
+
'const config = {',
|
|
165
|
+
' openaiKey: "sk-FAKE-vuln-key-deadbeef1234567890",',
|
|
166
|
+
' adminToken: "ghp_FAKE_admin_token_1234567890abcdef",',
|
|
167
|
+
' allowedTools: ["*"], // overprivileged',
|
|
168
|
+
' trustAllInputs: true,',
|
|
169
|
+
'};',
|
|
170
|
+
'module.exports = config;',
|
|
171
|
+
'',
|
|
172
|
+
].join('\n'));
|
|
173
|
+
// Permissive MCP config
|
|
174
|
+
const mcpDir = path.join(sandboxDir, '.cursor');
|
|
175
|
+
fs.mkdirSync(mcpDir, { recursive: true });
|
|
176
|
+
fs.writeFileSync(path.join(mcpDir, 'mcp.json'), JSON.stringify({
|
|
177
|
+
mcpServers: {
|
|
178
|
+
filesystem: {
|
|
179
|
+
command: 'npx',
|
|
180
|
+
args: ['-y', '@modelcontextprotocol/server-filesystem', '/'],
|
|
181
|
+
env: {},
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
}, null, 2));
|
|
185
|
+
// .env with credentials
|
|
186
|
+
fs.writeFileSync(path.join(sandboxDir, '.env'), [
|
|
187
|
+
'OPENAI_API_KEY=sk-FAKE-vuln-env-key-1234567890',
|
|
188
|
+
'ADMIN_SECRET=FAKE-super-secret-admin-password',
|
|
189
|
+
'',
|
|
190
|
+
].join('\n'));
|
|
191
|
+
// Overprivileged skill
|
|
192
|
+
fs.writeFileSync(path.join(sandboxDir, 'SKILL.md'), [
|
|
193
|
+
'---',
|
|
194
|
+
'name: data-exporter',
|
|
195
|
+
'tools: ["read-file", "write-file", "execute-shell", "network-external"]',
|
|
196
|
+
'---',
|
|
197
|
+
'',
|
|
198
|
+
'# Data Exporter Skill',
|
|
199
|
+
'This skill can read, write, execute, and send data externally.',
|
|
200
|
+
'',
|
|
201
|
+
].join('\n'));
|
|
202
|
+
}
|
|
203
|
+
// --- AIM Demo ---
|
|
204
|
+
async function runAimDemo(opts) {
|
|
205
|
+
const isCI = opts.ci ?? false;
|
|
206
|
+
const isInteractive = !isCI && (opts.interactive ?? false);
|
|
207
|
+
const keep = opts.keep ?? false;
|
|
208
|
+
const total = 8;
|
|
209
|
+
const auditLog = [];
|
|
210
|
+
const steps = [];
|
|
211
|
+
const sandboxDir = createSandbox(opts.dir);
|
|
212
|
+
const delayMs = isCI ? 0 : STEP_DELAY_MS;
|
|
213
|
+
try {
|
|
214
|
+
// Header
|
|
215
|
+
process.stdout.write('\n');
|
|
216
|
+
process.stdout.write((0, colors_js_1.bold)('AIM Agent Identity Management Demo') + '\n');
|
|
217
|
+
process.stdout.write((0, colors_js_1.dim)('='.repeat(38)) + '\n');
|
|
218
|
+
process.stdout.write((0, colors_js_1.dim)(' Self-contained walkthrough of the AIM lifecycle.') + '\n');
|
|
219
|
+
process.stdout.write((0, colors_js_1.dim)(' All operations run in a temporary sandbox.') + '\n');
|
|
220
|
+
// Step 1: Setup sandbox
|
|
221
|
+
printStepHeader({
|
|
222
|
+
step: 1, total,
|
|
223
|
+
title: 'Setting up sandbox',
|
|
224
|
+
description: 'Creating temporary project with sample agent configuration...',
|
|
225
|
+
});
|
|
226
|
+
await sleep(delayMs);
|
|
227
|
+
writeSandboxFiles(sandboxDir);
|
|
228
|
+
process.stdout.write((0, colors_js_1.green)(' Created sandbox at: ') + (0, colors_js_1.dim)(sandboxDir) + '\n');
|
|
229
|
+
process.stdout.write((0, colors_js_1.dim)(' Files: package.json, .env, .cursor/mcp.json, config.js') + '\n');
|
|
230
|
+
steps.push({ step: 1, title: 'Setting up sandbox', status: 'complete' });
|
|
231
|
+
await waitForEnter(isInteractive);
|
|
232
|
+
// Step 2: Security scan (before AIM)
|
|
233
|
+
printStepHeader({
|
|
234
|
+
step: 2, total,
|
|
235
|
+
title: 'Security scan (before AIM)',
|
|
236
|
+
description: 'Running security assessment of unprotected project...',
|
|
237
|
+
});
|
|
238
|
+
await sleep(delayMs);
|
|
239
|
+
const findingsBefore = { critical: 1, high: 2, medium: 1, low: 0 };
|
|
240
|
+
const scoreBefore = 22;
|
|
241
|
+
process.stdout.write('\n');
|
|
242
|
+
process.stdout.write(' Findings:\n');
|
|
243
|
+
process.stdout.write(' ' + (0, colors_js_1.red)('CRITICAL') + ' CRED-001 Hardcoded API key in .env\n');
|
|
244
|
+
process.stdout.write(' ' + (0, colors_js_1.yellow)('HIGH ') + ' CRED-002 Database password in connection string\n');
|
|
245
|
+
process.stdout.write(' ' + (0, colors_js_1.yellow)('HIGH ') + ' CRED-003 Hardcoded API key in config.js\n');
|
|
246
|
+
process.stdout.write(' ' + (0, colors_js_1.cyan)('MEDIUM ') + ' MCP-001 MCP server with root filesystem access\n');
|
|
247
|
+
process.stdout.write('\n');
|
|
248
|
+
process.stdout.write(' Result: ' + (0, colors_js_1.bold)((0, colors_js_1.red)('3 findings')) + ' (1 critical, 2 high)\n');
|
|
249
|
+
process.stdout.write(' Score: ' + (0, colors_js_1.bold)((0, colors_js_1.red)(String(scoreBefore) + '/100')) + '\n');
|
|
250
|
+
auditLog.push({ timestamp: nowISO(), action: 'scan.initial', target: 'project', outcome: 'complete' });
|
|
251
|
+
steps.push({ step: 2, title: 'Security scan (before AIM)', status: 'complete' });
|
|
252
|
+
await waitForEnter(isInteractive);
|
|
253
|
+
// Step 3: Create agent identity
|
|
254
|
+
printStepHeader({
|
|
255
|
+
step: 3, total,
|
|
256
|
+
title: 'Creating agent identity',
|
|
257
|
+
description: 'Generating Ed25519 keypair for demo-agent...',
|
|
258
|
+
});
|
|
259
|
+
await sleep(delayMs);
|
|
260
|
+
const agentId = generateDemoId();
|
|
261
|
+
const keyPair = generateKeyPair();
|
|
262
|
+
process.stdout.write('\n');
|
|
263
|
+
process.stdout.write(' Agent ID: ' + (0, colors_js_1.bold)(agentId) + '\n');
|
|
264
|
+
process.stdout.write(' Public Key: ' + (0, colors_js_1.dim)(keyPair.publicKey) + '\n');
|
|
265
|
+
process.stdout.write(' Algorithm: Ed25519\n');
|
|
266
|
+
// Write identity file to sandbox
|
|
267
|
+
const opena2aDir = path.join(sandboxDir, '.opena2a');
|
|
268
|
+
fs.mkdirSync(opena2aDir, { recursive: true });
|
|
269
|
+
fs.writeFileSync(path.join(opena2aDir, 'identity.json'), JSON.stringify({ agentId, publicKey: keyPair.publicKey, algorithm: 'Ed25519', createdAt: new Date().toISOString() }, null, 2));
|
|
270
|
+
auditLog.push({ timestamp: nowISO(), action: 'identity.create', target: 'demo-agent', outcome: 'allowed' });
|
|
271
|
+
steps.push({ step: 3, title: 'Creating agent identity', status: 'complete' });
|
|
272
|
+
await waitForEnter(isInteractive);
|
|
273
|
+
// Step 4: Apply capability policy
|
|
274
|
+
printStepHeader({
|
|
275
|
+
step: 4, total,
|
|
276
|
+
title: 'Applying capability policy',
|
|
277
|
+
description: 'Writing capability policy that restricts tool access...',
|
|
278
|
+
});
|
|
279
|
+
await sleep(delayMs);
|
|
280
|
+
const policy = {
|
|
281
|
+
version: 1,
|
|
282
|
+
agentId,
|
|
283
|
+
mode: 'enforce',
|
|
284
|
+
capabilities: {
|
|
285
|
+
allow: ['read-file', 'list-directory', 'search'],
|
|
286
|
+
deny: ['write-file', 'execute-shell', 'network-external'],
|
|
287
|
+
},
|
|
288
|
+
createdAt: new Date().toISOString(),
|
|
289
|
+
};
|
|
290
|
+
fs.writeFileSync(path.join(opena2aDir, 'policy.json'), JSON.stringify(policy, null, 2));
|
|
291
|
+
process.stdout.write('\n');
|
|
292
|
+
process.stdout.write(' Policy applied:\n');
|
|
293
|
+
process.stdout.write(' ' + (0, colors_js_1.green)('ALLOW') + ' read-file, list-directory, search\n');
|
|
294
|
+
process.stdout.write(' ' + (0, colors_js_1.red)('DENY ') + ' write-file, execute-shell, network-external\n');
|
|
295
|
+
process.stdout.write(' Mode: enforce\n');
|
|
296
|
+
auditLog.push({ timestamp: nowISO(), action: 'policy.apply', target: 'capability', outcome: 'allowed' });
|
|
297
|
+
steps.push({ step: 4, title: 'Applying capability policy', status: 'complete' });
|
|
298
|
+
await waitForEnter(isInteractive);
|
|
299
|
+
// Step 5: Sign configuration files
|
|
300
|
+
printStepHeader({
|
|
301
|
+
step: 5, total,
|
|
302
|
+
title: 'Signing configuration files',
|
|
303
|
+
description: 'Signing package.json and mcp config for tamper detection...',
|
|
304
|
+
});
|
|
305
|
+
await sleep(delayMs);
|
|
306
|
+
const filesToSign = ['package.json', '.cursor/mcp.json'];
|
|
307
|
+
const signatures = {};
|
|
308
|
+
for (const f of filesToSign) {
|
|
309
|
+
const content = fs.readFileSync(path.join(sandboxDir, f), 'utf-8');
|
|
310
|
+
const hash = crypto.createHash('sha256').update(content).digest('hex');
|
|
311
|
+
signatures[f] = hash;
|
|
312
|
+
}
|
|
313
|
+
const guardDir = path.join(opena2aDir, 'guard');
|
|
314
|
+
fs.mkdirSync(guardDir, { recursive: true });
|
|
315
|
+
fs.writeFileSync(path.join(guardDir, 'signatures.json'), JSON.stringify({ version: 1, signatures: Object.entries(signatures).map(([file, hash]) => ({ file, hash, signedAt: new Date().toISOString() })) }, null, 2));
|
|
316
|
+
process.stdout.write('\n');
|
|
317
|
+
for (const f of filesToSign) {
|
|
318
|
+
process.stdout.write(' Signed: ' + (0, colors_js_1.bold)(f) + ' ' + (0, colors_js_1.dim)('sha256:' + signatures[f].slice(0, 12) + '...') + '\n');
|
|
319
|
+
}
|
|
320
|
+
process.stdout.write(' Total: ' + (0, colors_js_1.bold)('2 files') + ' signed for tamper detection\n');
|
|
321
|
+
auditLog.push({ timestamp: nowISO(), action: 'config.sign', target: 'package.json', outcome: 'allowed' });
|
|
322
|
+
auditLog.push({ timestamp: nowISO(), action: 'config.sign', target: '.cursor/mcp.json', outcome: 'allowed' });
|
|
323
|
+
steps.push({ step: 5, title: 'Signing configuration files', status: 'complete' });
|
|
324
|
+
await waitForEnter(isInteractive);
|
|
325
|
+
// Step 6: Migrate credentials
|
|
326
|
+
printStepHeader({
|
|
327
|
+
step: 6, total,
|
|
328
|
+
title: 'Migrating credentials',
|
|
329
|
+
description: 'Moving hardcoded credentials to encrypted vault...',
|
|
330
|
+
});
|
|
331
|
+
await sleep(delayMs);
|
|
332
|
+
const vaultDir = path.join(opena2aDir, 'vault');
|
|
333
|
+
fs.mkdirSync(vaultDir, { recursive: true });
|
|
334
|
+
const migratedCreds = [
|
|
335
|
+
{ name: 'OPENAI_API_KEY', source: '.env', status: 'migrated' },
|
|
336
|
+
{ name: 'DATABASE_URL', source: '.env', status: 'migrated' },
|
|
337
|
+
{ name: 'apiKey', source: 'config.js', status: 'migrated' },
|
|
338
|
+
];
|
|
339
|
+
// Write a vault manifest
|
|
340
|
+
fs.writeFileSync(path.join(vaultDir, 'manifest.json'), JSON.stringify({ version: 1, entries: migratedCreds.map((c) => ({ name: c.name, source: c.source, migratedAt: new Date().toISOString() })) }, null, 2));
|
|
341
|
+
// Rewrite .env to use vault references
|
|
342
|
+
fs.writeFileSync(path.join(sandboxDir, '.env'), [
|
|
343
|
+
'# Credentials migrated to vault',
|
|
344
|
+
'OPENAI_API_KEY=vault://opena2a/OPENAI_API_KEY',
|
|
345
|
+
'DATABASE_URL=vault://opena2a/DATABASE_URL',
|
|
346
|
+
'',
|
|
347
|
+
].join('\n'));
|
|
348
|
+
// Rewrite config.js to use env var
|
|
349
|
+
fs.writeFileSync(path.join(sandboxDir, 'config.js'), [
|
|
350
|
+
'// Agent configuration (credentials migrated to vault)',
|
|
351
|
+
'module.exports = {',
|
|
352
|
+
' apiKey: process.env.OPENAI_API_KEY,',
|
|
353
|
+
' model: "gpt-4",',
|
|
354
|
+
' maxTokens: 4096,',
|
|
355
|
+
'};',
|
|
356
|
+
'',
|
|
357
|
+
].join('\n'));
|
|
358
|
+
process.stdout.write('\n');
|
|
359
|
+
for (const c of migratedCreds) {
|
|
360
|
+
process.stdout.write(' Migrated: ' + (0, colors_js_1.bold)(c.name) + ' from ' + (0, colors_js_1.dim)(c.source) + ' to vault\n');
|
|
361
|
+
}
|
|
362
|
+
process.stdout.write(' Total: ' + (0, colors_js_1.bold)('3 credentials') + ' moved to encrypted vault\n');
|
|
363
|
+
auditLog.push({ timestamp: nowISO(), action: 'credential.migrate', target: '.env', outcome: 'allowed' });
|
|
364
|
+
auditLog.push({ timestamp: nowISO(), action: 'credential.migrate', target: 'config.js', outcome: 'allowed' });
|
|
365
|
+
steps.push({ step: 6, title: 'Migrating credentials', status: 'complete' });
|
|
366
|
+
await waitForEnter(isInteractive);
|
|
367
|
+
// Step 7: Security scan (after AIM)
|
|
368
|
+
printStepHeader({
|
|
369
|
+
step: 7, total,
|
|
370
|
+
title: 'Security scan (after AIM)',
|
|
371
|
+
description: 'Running security assessment of protected project...',
|
|
372
|
+
});
|
|
373
|
+
await sleep(delayMs);
|
|
374
|
+
const findingsAfter = { critical: 0, high: 0, medium: 0, low: 1 };
|
|
375
|
+
const scoreAfter = 87;
|
|
376
|
+
process.stdout.write('\n');
|
|
377
|
+
process.stdout.write(' Findings:\n');
|
|
378
|
+
process.stdout.write(' ' + (0, colors_js_1.dim)('LOW ') + ' CONFIG-005 Consider enabling strict mode\n');
|
|
379
|
+
process.stdout.write('\n');
|
|
380
|
+
process.stdout.write(' Result: ' + (0, colors_js_1.green)('0 critical, 0 high') + '\n');
|
|
381
|
+
process.stdout.write(' Score: ' + (0, colors_js_1.bold)((0, colors_js_1.green)(String(scoreAfter) + '/100')) + ' ' + (0, colors_js_1.green)('(+' + String(scoreAfter - scoreBefore) + ' improvement)') + '\n');
|
|
382
|
+
auditLog.push({ timestamp: nowISO(), action: 'scan.final', target: 'project', outcome: 'complete' });
|
|
383
|
+
steps.push({ step: 7, title: 'Security scan (after AIM)', status: 'complete' });
|
|
384
|
+
await waitForEnter(isInteractive);
|
|
385
|
+
// Step 8: Audit log
|
|
386
|
+
printStepHeader({
|
|
387
|
+
step: 8, total,
|
|
388
|
+
title: 'Audit log',
|
|
389
|
+
description: 'Reviewing what happened...',
|
|
390
|
+
});
|
|
391
|
+
await sleep(delayMs);
|
|
392
|
+
process.stdout.write('\n');
|
|
393
|
+
for (const entry of auditLog) {
|
|
394
|
+
const action = entry.action.padEnd(20);
|
|
395
|
+
const target = entry.target.padEnd(20);
|
|
396
|
+
process.stdout.write(' ' + (0, colors_js_1.dim)(entry.timestamp) + ' ' + action + target + (0, colors_js_1.green)(entry.outcome) + '\n');
|
|
397
|
+
}
|
|
398
|
+
steps.push({ step: 8, title: 'Audit log', status: 'complete' });
|
|
399
|
+
// Summary
|
|
400
|
+
process.stdout.write('\n');
|
|
401
|
+
process.stdout.write((0, colors_js_1.bold)('Demo Complete') + '\n');
|
|
402
|
+
process.stdout.write((0, colors_js_1.dim)('='.repeat(16)) + '\n');
|
|
403
|
+
process.stdout.write('\n');
|
|
404
|
+
process.stdout.write(' Before AIM: ' + (0, colors_js_1.bold)((0, colors_js_1.red)(String(scoreBefore) + '/100')) + ' (3 findings, no identity, no governance)\n');
|
|
405
|
+
process.stdout.write(' After AIM: ' + (0, colors_js_1.bold)((0, colors_js_1.green)(String(scoreAfter) + '/100')) + ' (0 critical findings, identity active, policy enforced)\n');
|
|
406
|
+
process.stdout.write('\n');
|
|
407
|
+
process.stdout.write(' What happened:\n');
|
|
408
|
+
process.stdout.write(' 1. Created cryptographic agent identity (Ed25519)\n');
|
|
409
|
+
process.stdout.write(' 2. Applied least-privilege capability policy\n');
|
|
410
|
+
process.stdout.write(' 3. Signed config files for tamper detection\n');
|
|
411
|
+
process.stdout.write(' 4. Migrated hardcoded credentials to encrypted vault\n');
|
|
412
|
+
process.stdout.write('\n');
|
|
413
|
+
process.stdout.write(' Try it on your project:\n');
|
|
414
|
+
process.stdout.write((0, colors_js_1.cyan)(' opena2a init') + ' Start security assessment\n');
|
|
415
|
+
process.stdout.write((0, colors_js_1.cyan)(' opena2a protect') + ' Detect and migrate credentials\n');
|
|
416
|
+
process.stdout.write((0, colors_js_1.cyan)(' opena2a identity create') + ' Create agent identity\n');
|
|
417
|
+
process.stdout.write('\n');
|
|
418
|
+
if (keep) {
|
|
419
|
+
process.stdout.write((0, colors_js_1.dim)(' Sandbox preserved at: ' + sandboxDir) + '\n');
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
process.stdout.write((0, colors_js_1.dim)(' Sandbox cleaned up. No files were modified outside the demo.') + '\n');
|
|
423
|
+
}
|
|
424
|
+
return {
|
|
425
|
+
scenario: 'aim',
|
|
426
|
+
sandboxDir,
|
|
427
|
+
kept: keep,
|
|
428
|
+
steps,
|
|
429
|
+
scoreBefore,
|
|
430
|
+
scoreAfter,
|
|
431
|
+
findingsBefore,
|
|
432
|
+
findingsAfter,
|
|
433
|
+
auditLog,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
finally {
|
|
437
|
+
cleanupSandbox(sandboxDir, keep);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
// --- DVAA Demo ---
|
|
441
|
+
async function runDvaaDemo(opts) {
|
|
442
|
+
const isCI = opts.ci ?? false;
|
|
443
|
+
const isInteractive = !isCI && (opts.interactive ?? false);
|
|
444
|
+
const keep = opts.keep ?? false;
|
|
445
|
+
const total = 5;
|
|
446
|
+
const auditLog = [];
|
|
447
|
+
const steps = [];
|
|
448
|
+
const sandboxDir = createSandbox(opts.dir);
|
|
449
|
+
const delayMs = isCI ? 0 : STEP_DELAY_MS;
|
|
450
|
+
try {
|
|
451
|
+
// Header
|
|
452
|
+
process.stdout.write('\n');
|
|
453
|
+
process.stdout.write((0, colors_js_1.bold)('DVAA Attack/Defend Demo') + '\n');
|
|
454
|
+
process.stdout.write((0, colors_js_1.dim)('='.repeat(26)) + '\n');
|
|
455
|
+
process.stdout.write((0, colors_js_1.dim)(' Shows how AIM protects an agent from common attacks.') + '\n');
|
|
456
|
+
// Step 1: Set up vulnerable agent
|
|
457
|
+
printStepHeader({
|
|
458
|
+
step: 1, total,
|
|
459
|
+
title: 'Setting up vulnerable agent',
|
|
460
|
+
description: 'Creating a simulated vulnerable AI agent configuration...',
|
|
461
|
+
});
|
|
462
|
+
await sleep(delayMs);
|
|
463
|
+
writeDvaaFiles(sandboxDir);
|
|
464
|
+
process.stdout.write((0, colors_js_1.green)(' Created vulnerable agent sandbox at: ') + (0, colors_js_1.dim)(sandboxDir) + '\n');
|
|
465
|
+
process.stdout.write((0, colors_js_1.dim)(' Files: package.json, config.js, .env, .cursor/mcp.json, SKILL.md') + '\n');
|
|
466
|
+
steps.push({ step: 1, title: 'Setting up vulnerable agent', status: 'complete' });
|
|
467
|
+
await waitForEnter(isInteractive);
|
|
468
|
+
// Step 2: Security scan
|
|
469
|
+
printStepHeader({
|
|
470
|
+
step: 2, total,
|
|
471
|
+
title: 'Running security scan',
|
|
472
|
+
description: 'Scanning for vulnerabilities...',
|
|
473
|
+
});
|
|
474
|
+
await sleep(delayMs);
|
|
475
|
+
const scoreBefore = 18;
|
|
476
|
+
const findingsBefore = { critical: 1, high: 2, medium: 1, low: 0 };
|
|
477
|
+
process.stdout.write('\n');
|
|
478
|
+
process.stdout.write(' Findings:\n');
|
|
479
|
+
process.stdout.write(' ' + (0, colors_js_1.red)('CRITICAL') + ' CRED-001 Hardcoded API key in config.js\n');
|
|
480
|
+
process.stdout.write(' ' + (0, colors_js_1.yellow)('HIGH ') + ' MCP-003 MCP server with root filesystem access\n');
|
|
481
|
+
process.stdout.write(' ' + (0, colors_js_1.yellow)('HIGH ') + ' GOVERNANCE-001 No SOUL.md governance file\n');
|
|
482
|
+
process.stdout.write(' ' + (0, colors_js_1.cyan)('MEDIUM ') + ' SKILL-002 Overprivileged skill definitions\n');
|
|
483
|
+
process.stdout.write('\n');
|
|
484
|
+
process.stdout.write(' Score: ' + (0, colors_js_1.bold)((0, colors_js_1.red)(String(scoreBefore) + '/100')) + '\n');
|
|
485
|
+
auditLog.push({ timestamp: nowISO(), action: 'scan.initial', target: 'project', outcome: 'complete' });
|
|
486
|
+
steps.push({ step: 2, title: 'Running security scan', status: 'complete' });
|
|
487
|
+
await waitForEnter(isInteractive);
|
|
488
|
+
// Step 3: Apply AIM hardening
|
|
489
|
+
printStepHeader({
|
|
490
|
+
step: 3, total,
|
|
491
|
+
title: 'Applying AIM hardening',
|
|
492
|
+
description: 'Creating agent identity, governance, migrating credentials, signing config...',
|
|
493
|
+
});
|
|
494
|
+
await sleep(delayMs);
|
|
495
|
+
const agentId = generateDemoId();
|
|
496
|
+
const opena2aDir = path.join(sandboxDir, '.opena2a');
|
|
497
|
+
fs.mkdirSync(path.join(opena2aDir, 'guard'), { recursive: true });
|
|
498
|
+
fs.mkdirSync(path.join(opena2aDir, 'vault'), { recursive: true });
|
|
499
|
+
// Identity
|
|
500
|
+
process.stdout.write('\n');
|
|
501
|
+
process.stdout.write(' Creating agent identity... ' + (0, colors_js_1.green)('done') + '\n');
|
|
502
|
+
fs.writeFileSync(path.join(opena2aDir, 'identity.json'), JSON.stringify({ agentId, algorithm: 'Ed25519', createdAt: new Date().toISOString() }, null, 2));
|
|
503
|
+
auditLog.push({ timestamp: nowISO(), action: 'identity.create', target: agentId, outcome: 'allowed' });
|
|
504
|
+
// Governance
|
|
505
|
+
process.stdout.write(' Generating governance file... ' + (0, colors_js_1.green)('done') + '\n');
|
|
506
|
+
fs.writeFileSync(path.join(sandboxDir, 'SOUL.md'), [
|
|
507
|
+
'# Agent Governance',
|
|
508
|
+
'',
|
|
509
|
+
'## Identity',
|
|
510
|
+
'This agent operates under AIM identity management.',
|
|
511
|
+
'',
|
|
512
|
+
'## Boundaries',
|
|
513
|
+
'- No external network access without explicit approval',
|
|
514
|
+
'- No shell command execution',
|
|
515
|
+
'- Read-only filesystem access by default',
|
|
516
|
+
'',
|
|
517
|
+
'## Data Handling',
|
|
518
|
+
'- No credential storage in plaintext',
|
|
519
|
+
'- All secrets accessed via vault references',
|
|
520
|
+
'',
|
|
521
|
+
].join('\n'));
|
|
522
|
+
auditLog.push({ timestamp: nowISO(), action: 'governance.create', target: 'SOUL.md', outcome: 'allowed' });
|
|
523
|
+
// Credential migration
|
|
524
|
+
process.stdout.write(' Migrating credentials... ' + (0, colors_js_1.green)('done') + '\n');
|
|
525
|
+
fs.writeFileSync(path.join(sandboxDir, 'config.js'), [
|
|
526
|
+
'// DVAA agent config (hardened)',
|
|
527
|
+
'const config = {',
|
|
528
|
+
' openaiKey: process.env.OPENAI_API_KEY,',
|
|
529
|
+
' adminToken: process.env.ADMIN_TOKEN,',
|
|
530
|
+
' allowedTools: ["read-file", "search"],',
|
|
531
|
+
' trustAllInputs: false,',
|
|
532
|
+
'};',
|
|
533
|
+
'module.exports = config;',
|
|
534
|
+
'',
|
|
535
|
+
].join('\n'));
|
|
536
|
+
fs.writeFileSync(path.join(sandboxDir, '.env'), [
|
|
537
|
+
'# Credentials migrated to vault',
|
|
538
|
+
'OPENAI_API_KEY=vault://opena2a/OPENAI_API_KEY',
|
|
539
|
+
'ADMIN_SECRET=vault://opena2a/ADMIN_SECRET',
|
|
540
|
+
'',
|
|
541
|
+
].join('\n'));
|
|
542
|
+
auditLog.push({ timestamp: nowISO(), action: 'credential.migrate', target: 'config.js', outcome: 'allowed' });
|
|
543
|
+
// Signing
|
|
544
|
+
process.stdout.write(' Signing configuration... ' + (0, colors_js_1.green)('done') + '\n');
|
|
545
|
+
const configContent = fs.readFileSync(path.join(sandboxDir, 'config.js'), 'utf-8');
|
|
546
|
+
const configHash = crypto.createHash('sha256').update(configContent).digest('hex');
|
|
547
|
+
fs.writeFileSync(path.join(opena2aDir, 'guard', 'signatures.json'), JSON.stringify({ version: 1, signatures: [{ file: 'config.js', hash: configHash, signedAt: new Date().toISOString() }] }, null, 2));
|
|
548
|
+
auditLog.push({ timestamp: nowISO(), action: 'config.sign', target: 'config.js', outcome: 'allowed' });
|
|
549
|
+
// Policy
|
|
550
|
+
fs.writeFileSync(path.join(opena2aDir, 'policy.json'), JSON.stringify({
|
|
551
|
+
version: 1,
|
|
552
|
+
agentId,
|
|
553
|
+
mode: 'enforce',
|
|
554
|
+
capabilities: {
|
|
555
|
+
allow: ['read-file', 'search'],
|
|
556
|
+
deny: ['write-file', 'execute-shell', 'network-external', 'prompt-override'],
|
|
557
|
+
},
|
|
558
|
+
}, null, 2));
|
|
559
|
+
auditLog.push({ timestamp: nowISO(), action: 'policy.apply', target: 'capability', outcome: 'allowed' });
|
|
560
|
+
steps.push({ step: 3, title: 'Applying AIM hardening', status: 'complete' });
|
|
561
|
+
await waitForEnter(isInteractive);
|
|
562
|
+
// Step 4: Re-scan
|
|
563
|
+
printStepHeader({
|
|
564
|
+
step: 4, total,
|
|
565
|
+
title: 'Re-scanning after hardening',
|
|
566
|
+
description: 'Running security assessment of hardened project...',
|
|
567
|
+
});
|
|
568
|
+
await sleep(delayMs);
|
|
569
|
+
const scoreAfter = 91;
|
|
570
|
+
const findingsAfter = { critical: 0, high: 0, medium: 0, low: 1 };
|
|
571
|
+
process.stdout.write('\n');
|
|
572
|
+
process.stdout.write(' Findings:\n');
|
|
573
|
+
process.stdout.write(' ' + (0, colors_js_1.dim)('LOW ') + ' CONFIG-005 Consider enabling strict mode\n');
|
|
574
|
+
process.stdout.write('\n');
|
|
575
|
+
process.stdout.write(' Score: ' + (0, colors_js_1.bold)((0, colors_js_1.green)(String(scoreAfter) + '/100')) + ' ' + (0, colors_js_1.green)('(+' + String(scoreAfter - scoreBefore) + ' improvement)') + '\n');
|
|
576
|
+
auditLog.push({ timestamp: nowISO(), action: 'scan.final', target: 'project', outcome: 'complete' });
|
|
577
|
+
steps.push({ step: 4, title: 'Re-scanning after hardening', status: 'complete' });
|
|
578
|
+
await waitForEnter(isInteractive);
|
|
579
|
+
// Step 5: Attack simulation
|
|
580
|
+
printStepHeader({
|
|
581
|
+
step: 5, total,
|
|
582
|
+
title: 'Attack simulation',
|
|
583
|
+
description: 'Running adversarial probes against hardened configuration...',
|
|
584
|
+
});
|
|
585
|
+
await sleep(delayMs);
|
|
586
|
+
const attacks = [
|
|
587
|
+
{ name: 'prompt-injection', result: 'BLOCKED', reason: 'capability policy denies prompt override' },
|
|
588
|
+
{ name: 'credential-theft', result: 'BLOCKED', reason: 'credentials in vault, not in files' },
|
|
589
|
+
{ name: 'config-tampering', result: 'BLOCKED', reason: 'signature verification detects change' },
|
|
590
|
+
{ name: 'privilege-escalation', result: 'BLOCKED', reason: 'least-privilege policy enforced' },
|
|
591
|
+
];
|
|
592
|
+
process.stdout.write('\n');
|
|
593
|
+
for (const attack of attacks) {
|
|
594
|
+
const nameCol = attack.name.padEnd(24);
|
|
595
|
+
process.stdout.write(' ' + nameCol + (0, colors_js_1.green)('BLOCKED') + ' (' + (0, colors_js_1.dim)(attack.reason) + ')\n');
|
|
596
|
+
auditLog.push({ timestamp: nowISO(), action: 'attack.' + attack.name, target: 'agent', outcome: 'blocked' });
|
|
597
|
+
}
|
|
598
|
+
process.stdout.write('\n');
|
|
599
|
+
process.stdout.write(' Result: ' + (0, colors_js_1.bold)((0, colors_js_1.green)('4/4 attacks blocked')) + '\n');
|
|
600
|
+
steps.push({ step: 5, title: 'Attack simulation', status: 'complete' });
|
|
601
|
+
// Summary
|
|
602
|
+
process.stdout.write('\n');
|
|
603
|
+
process.stdout.write((0, colors_js_1.bold)('Demo Complete') + '\n');
|
|
604
|
+
process.stdout.write((0, colors_js_1.dim)('='.repeat(16)) + '\n');
|
|
605
|
+
process.stdout.write('\n');
|
|
606
|
+
process.stdout.write(' Before hardening: ' + (0, colors_js_1.bold)((0, colors_js_1.red)(String(scoreBefore) + '/100')) + ' (4 findings, no identity, no governance)\n');
|
|
607
|
+
process.stdout.write(' After hardening: ' + (0, colors_js_1.bold)((0, colors_js_1.green)(String(scoreAfter) + '/100')) + ' (0 critical, identity active, policy enforced)\n');
|
|
608
|
+
process.stdout.write(' Attacks blocked: ' + (0, colors_js_1.bold)((0, colors_js_1.green)('4/4')) + '\n');
|
|
609
|
+
process.stdout.write('\n');
|
|
610
|
+
process.stdout.write(' What was applied:\n');
|
|
611
|
+
process.stdout.write(' 1. Created cryptographic agent identity\n');
|
|
612
|
+
process.stdout.write(' 2. Generated SOUL.md governance file\n');
|
|
613
|
+
process.stdout.write(' 3. Migrated credentials to encrypted vault\n');
|
|
614
|
+
process.stdout.write(' 4. Signed configuration for tamper detection\n');
|
|
615
|
+
process.stdout.write(' 5. Applied least-privilege capability policy\n');
|
|
616
|
+
process.stdout.write('\n');
|
|
617
|
+
process.stdout.write(' Try it on your project:\n');
|
|
618
|
+
process.stdout.write((0, colors_js_1.cyan)(' opena2a init') + ' Start security assessment\n');
|
|
619
|
+
process.stdout.write((0, colors_js_1.cyan)(' opena2a protect') + ' Detect and migrate credentials\n');
|
|
620
|
+
process.stdout.write((0, colors_js_1.cyan)(' opena2a harden-soul') + ' Generate governance file\n');
|
|
621
|
+
process.stdout.write('\n');
|
|
622
|
+
if (keep) {
|
|
623
|
+
process.stdout.write((0, colors_js_1.dim)(' Sandbox preserved at: ' + sandboxDir) + '\n');
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
process.stdout.write((0, colors_js_1.dim)(' Sandbox cleaned up. No files were modified outside the demo.') + '\n');
|
|
627
|
+
}
|
|
628
|
+
return {
|
|
629
|
+
scenario: 'dvaa',
|
|
630
|
+
sandboxDir,
|
|
631
|
+
kept: keep,
|
|
632
|
+
steps,
|
|
633
|
+
scoreBefore,
|
|
634
|
+
scoreAfter,
|
|
635
|
+
findingsBefore,
|
|
636
|
+
findingsAfter,
|
|
637
|
+
auditLog,
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
finally {
|
|
641
|
+
cleanupSandbox(sandboxDir, keep);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
// --- Entry point ---
|
|
645
|
+
async function demo(opts) {
|
|
646
|
+
const scenario = opts.scenario ?? 'aim';
|
|
647
|
+
const format = opts.format ?? 'text';
|
|
648
|
+
let result;
|
|
649
|
+
// In JSON mode, capture the demo silently and only output the JSON result
|
|
650
|
+
const originalWrite = process.stdout.write.bind(process.stdout);
|
|
651
|
+
if (format === 'json') {
|
|
652
|
+
process.stdout.write = (() => true);
|
|
653
|
+
}
|
|
654
|
+
try {
|
|
655
|
+
if (scenario === 'dvaa') {
|
|
656
|
+
result = await runDvaaDemo(opts);
|
|
657
|
+
}
|
|
658
|
+
else if (scenario === 'aim' || !scenario) {
|
|
659
|
+
result = await runAimDemo(opts);
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
if (format === 'json')
|
|
663
|
+
process.stdout.write = originalWrite;
|
|
664
|
+
process.stderr.write(`Unknown demo scenario: ${scenario}\n`);
|
|
665
|
+
process.stderr.write('Available scenarios: aim (default), dvaa\n');
|
|
666
|
+
return 1;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
catch (err) {
|
|
670
|
+
if (format === 'json')
|
|
671
|
+
process.stdout.write = originalWrite;
|
|
672
|
+
process.stderr.write(`Demo error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
673
|
+
return 1;
|
|
674
|
+
}
|
|
675
|
+
// Restore stdout and output JSON if needed
|
|
676
|
+
if (format === 'json') {
|
|
677
|
+
process.stdout.write = originalWrite;
|
|
678
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
679
|
+
}
|
|
680
|
+
(0, footer_js_1.printFooter)({ ci: opts.ci, json: format === 'json' });
|
|
681
|
+
return 0;
|
|
682
|
+
}
|
|
683
|
+
//# sourceMappingURL=demo.js.map
|