opena2a-cli 0.5.9 → 0.5.11

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.
@@ -1,7 +1,70 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.identity = identity;
37
+ const path = __importStar(require("path"));
38
+ const fs = __importStar(require("fs"));
4
39
  const colors_js_1 = require("../util/colors.js");
40
+ const USAGE = [
41
+ '',
42
+ 'Usage: opena2a identity <subcommand>',
43
+ '',
44
+ 'Identity & Keys',
45
+ ' list Show local agent identity',
46
+ ' create --name <n> Create a new agent identity',
47
+ ' sign --data <d> Sign a string with agent private key',
48
+ ' sign --file <f> Sign a file with agent private key',
49
+ ' verify Verify a signature against a public key',
50
+ '',
51
+ 'Trust & Audit',
52
+ ' trust Show trust score with factor breakdown',
53
+ ' audit [--limit N] Show recent audit events',
54
+ ' log Log an audit event manually',
55
+ '',
56
+ 'Policy',
57
+ ' policy Show current capability policy',
58
+ ' policy load <file> Load a YAML capability policy',
59
+ ' check <capability> Check if a capability is allowed',
60
+ '',
61
+ 'Cross-Tool Integration',
62
+ ' attach [--tools <list>] Wire tools to identity (audit + trust)',
63
+ ' attach --all Enable all detected tools',
64
+ ' detach Remove cross-tool wiring',
65
+ ' sync Sync events from enabled tools',
66
+ '',
67
+ ].join('\n');
5
68
  async function identity(options) {
6
69
  const sub = options.subcommand;
7
70
  switch (sub) {
@@ -14,13 +77,25 @@ async function identity(options) {
14
77
  return handleTrust(options);
15
78
  case 'audit':
16
79
  return handleAudit(options);
80
+ case 'log':
81
+ return handleLog(options);
82
+ case 'policy':
83
+ return handlePolicy(options);
84
+ case 'check':
85
+ return handleCheck(options);
86
+ case 'sign':
87
+ return handleSign(options);
88
+ case 'verify':
89
+ return handleVerify(options);
90
+ case 'attach':
91
+ return handleAttach(options);
92
+ case 'detach':
93
+ return handleDetach(options);
94
+ case 'sync':
95
+ return handleSync(options);
17
96
  default:
18
97
  process.stderr.write(`Unknown identity subcommand: ${sub}\n`);
19
- process.stderr.write('\nUsage: opena2a identity <list|create|trust|audit>\n\n');
20
- process.stderr.write(' list Show local agent identity\n');
21
- process.stderr.write(' create --name <n> Create a new agent identity\n');
22
- process.stderr.write(' trust Show trust score\n');
23
- process.stderr.write(' audit [--limit N] Show recent audit events\n');
98
+ process.stderr.write(USAGE + '\n');
24
99
  return 1;
25
100
  }
26
101
  }
@@ -34,6 +109,9 @@ async function loadAimCore() {
34
109
  return null;
35
110
  }
36
111
  }
112
+ // ---------------------------------------------------------------------------
113
+ // list / show
114
+ // ---------------------------------------------------------------------------
37
115
  async function handleList(options) {
38
116
  const mod = await loadAimCore();
39
117
  if (!mod)
@@ -52,6 +130,7 @@ async function handleList(options) {
52
130
  process.stdout.write(` Name: ${id.agentName}\n`);
53
131
  process.stdout.write(` Public Key: ${(0, colors_js_1.dim)(id.publicKey.slice(0, 32) + '...')}\n`);
54
132
  process.stdout.write(` Created: ${id.createdAt}\n`);
133
+ process.stdout.write(` Data Dir: ${(0, colors_js_1.dim)(aim.getDataDir())}\n`);
55
134
  process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
56
135
  return 0;
57
136
  }
@@ -60,6 +139,9 @@ async function handleList(options) {
60
139
  return 1;
61
140
  }
62
141
  }
142
+ // ---------------------------------------------------------------------------
143
+ // create
144
+ // ---------------------------------------------------------------------------
63
145
  async function handleCreate(options) {
64
146
  const mod = await loadAimCore();
65
147
  if (!mod)
@@ -73,12 +155,30 @@ async function handleCreate(options) {
73
155
  const isJson = options.format === 'json';
74
156
  try {
75
157
  const aim = new mod.AIMCore({ agentName: name });
76
- const id = aim.getIdentity();
158
+ // Check if identity already exists
159
+ let existing = false;
160
+ try {
161
+ const prev = aim.getIdentity();
162
+ if (prev && prev.agentId) {
163
+ existing = true;
164
+ }
165
+ }
166
+ catch {
167
+ // No existing identity -- good, we'll create one
168
+ }
169
+ const id = aim.getOrCreateIdentity();
77
170
  if (isJson) {
78
- process.stdout.write(JSON.stringify(id, null, 2) + '\n');
171
+ process.stdout.write(JSON.stringify({ ...id, created: !existing }, null, 2) + '\n');
79
172
  return 0;
80
173
  }
81
- process.stdout.write((0, colors_js_1.green)('Identity created') + '\n');
174
+ if (existing) {
175
+ process.stdout.write((0, colors_js_1.yellow)('Identity already exists') + '\n');
176
+ process.stdout.write((0, colors_js_1.dim)(' aim-core uses a single identity per data directory.') + '\n');
177
+ process.stdout.write((0, colors_js_1.dim)(' To start fresh, remove ~/.opena2a/aim-core/ and re-run.') + '\n\n');
178
+ }
179
+ else {
180
+ process.stdout.write((0, colors_js_1.green)('Identity created') + '\n');
181
+ }
82
182
  process.stdout.write(` Agent ID: ${(0, colors_js_1.cyan)(id.agentId)}\n`);
83
183
  process.stdout.write(` Name: ${id.agentName}\n`);
84
184
  process.stdout.write(` Public Key: ${(0, colors_js_1.dim)(id.publicKey.slice(0, 32) + '...')}\n`);
@@ -90,6 +190,9 @@ async function handleCreate(options) {
90
190
  return 1;
91
191
  }
92
192
  }
193
+ // ---------------------------------------------------------------------------
194
+ // trust
195
+ // ---------------------------------------------------------------------------
93
196
  async function handleTrust(options) {
94
197
  const mod = await loadAimCore();
95
198
  if (!mod)
@@ -98,24 +201,70 @@ async function handleTrust(options) {
98
201
  try {
99
202
  const aim = new mod.AIMCore({ agentName: 'default' });
100
203
  aim.getIdentity(); // ensure identity exists
204
+ // Auto-sync trust hints if a manifest exists (tools are attached)
205
+ const targetDir = path.resolve(options.dir ?? process.cwd());
206
+ let hasManifest = false;
207
+ try {
208
+ const { readManifest } = await import('../identity/manifest.js');
209
+ const { collectTrustHints } = await import('../identity/trust-collector.js');
210
+ const manifest = readManifest(targetDir);
211
+ if (manifest) {
212
+ hasManifest = true;
213
+ const { hints } = collectTrustHints(targetDir, manifest);
214
+ aim.setTrustHints(hints);
215
+ }
216
+ }
217
+ catch {
218
+ // Identity module not available or manifest missing — that's fine
219
+ }
101
220
  const trust = aim.calculateTrust();
102
221
  if (isJson) {
103
- process.stdout.write(JSON.stringify(trust, null, 2) + '\n');
222
+ process.stdout.write(JSON.stringify({ ...trust, attached: hasManifest }, null, 2) + '\n');
104
223
  return 0;
105
224
  }
225
+ const displayScore = trust.score ?? Math.round((trust.overall ?? 0) * 100);
226
+ const displayGrade = trust.grade ?? scoreToGrade(displayScore);
227
+ const gradeColor = displayScore >= 80 ? colors_js_1.green : displayScore >= 60 ? colors_js_1.yellow : colors_js_1.red;
106
228
  process.stdout.write((0, colors_js_1.bold)('Trust Score') + '\n');
107
229
  process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
108
- const displayScore = trust.score ?? Math.round((trust.overall ?? 0) * 100);
109
- const displayGrade = trust.grade ?? (displayScore >= 80 ? 'strong' : displayScore >= 60 ? 'good' : displayScore >= 40 ? 'moderate' : displayScore >= 20 ? 'improving' : 'needs-attention');
110
- process.stdout.write(` Score: ${(0, colors_js_1.bold)(String(displayScore))} Grade: ${(0, colors_js_1.bold)(displayGrade)}\n`);
230
+ process.stdout.write(` Score: ${gradeColor((0, colors_js_1.bold)(String(displayScore) + '/100'))} (${gradeColor(displayGrade)})\n`);
111
231
  process.stdout.write('\n');
112
- process.stdout.write(' Factors:\n');
232
+ process.stdout.write((0, colors_js_1.bold)(' Factors:') + '\n');
113
233
  for (const [factor, value] of Object.entries(trust.factors)) {
114
234
  const label = factor.replace(/([A-Z])/g, ' $1').toLowerCase().trim();
115
- const status = value > 0 ? (0, colors_js_1.green)('active') : (0, colors_js_1.dim)('inactive');
116
- process.stdout.write(` ${label.padEnd(22)} ${status}\n`);
235
+ const pct = Math.round(value * 100);
236
+ const bar = progressBar(pct, 20);
237
+ process.stdout.write(` ${label.padEnd(18)} ${bar} ${pct}%\n`);
117
238
  }
118
239
  process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
240
+ if (options.verbose) {
241
+ if (trust.calculatedAt) {
242
+ process.stdout.write((0, colors_js_1.dim)(` Calculated: ${trust.calculatedAt}`) + '\n');
243
+ }
244
+ // Show improvement suggestions for factors at 0%
245
+ const zeroFactors = Object.entries(trust.factors).filter(([, v]) => v === 0);
246
+ if (zeroFactors.length > 0) {
247
+ process.stdout.write('\n' + (0, colors_js_1.bold)(' How to improve:') + '\n');
248
+ const factorSuggestions = {
249
+ secretsManaged: 'npx secretless-ai init',
250
+ configSigned: 'opena2a guard sign',
251
+ skillsVerified: 'npx hackmyagent secure',
252
+ networkControlled: 'opena2a runtime --init',
253
+ heartbeatMonitored: 'opena2a shield init',
254
+ };
255
+ for (const [factor] of zeroFactors) {
256
+ const suggestion = factorSuggestions[factor];
257
+ if (suggestion) {
258
+ const label = factor.replace(/([A-Z])/g, ' $1').toLowerCase().trim();
259
+ process.stdout.write(` ${label.padEnd(18)} ${(0, colors_js_1.dim)(suggestion)}\n`);
260
+ }
261
+ }
262
+ }
263
+ }
264
+ if (!hasManifest) {
265
+ process.stdout.write('\n' + (0, colors_js_1.dim)(' No tools attached. Run: opena2a identity attach --all') + '\n');
266
+ process.stdout.write((0, colors_js_1.dim)(' Attaching tools improves trust by syncing real security state.') + '\n');
267
+ }
119
268
  return 0;
120
269
  }
121
270
  catch (err) {
@@ -123,6 +272,9 @@ async function handleTrust(options) {
123
272
  return 1;
124
273
  }
125
274
  }
275
+ // ---------------------------------------------------------------------------
276
+ // audit
277
+ // ---------------------------------------------------------------------------
126
278
  async function handleAudit(options) {
127
279
  const mod = await loadAimCore();
128
280
  if (!mod)
@@ -138,15 +290,16 @@ async function handleAudit(options) {
138
290
  }
139
291
  if (events.length === 0) {
140
292
  process.stdout.write((0, colors_js_1.dim)('No audit events recorded yet.') + '\n');
141
- process.stdout.write((0, colors_js_1.dim)('Events are logged when using aim-core in your agent.') + '\n');
293
+ process.stdout.write((0, colors_js_1.dim)('Log events with: opena2a identity log --action <action> --target <target>') + '\n');
142
294
  return 0;
143
295
  }
144
296
  process.stdout.write((0, colors_js_1.bold)(`Audit Log (last ${events.length})`) + '\n');
145
297
  process.stdout.write((0, colors_js_1.gray)('-'.repeat(70)) + '\n');
146
298
  for (const e of events) {
147
299
  const ts = e.timestamp.slice(0, 19).replace('T', ' ');
148
- const resultColor = e.result === 'allowed' ? colors_js_1.green : e.result === 'denied' ? colors_js_1.yellow : colors_js_1.dim;
149
- process.stdout.write(` ${(0, colors_js_1.dim)(ts)} ${e.action.padEnd(16)} ${e.target.padEnd(16)} ${resultColor(e.result)}\n`);
300
+ const resultColor = e.result === 'allowed' ? colors_js_1.green : e.result === 'denied' ? colors_js_1.red : colors_js_1.yellow;
301
+ const pluginLabel = e.plugin && e.plugin !== 'unknown' ? (0, colors_js_1.dim)(` [${e.plugin}]`) : '';
302
+ process.stdout.write(` ${(0, colors_js_1.dim)(ts)} ${(e.action ?? '').padEnd(16)} ${(e.target ?? '').padEnd(16)} ${resultColor(e.result ?? '')}${pluginLabel}\n`);
150
303
  }
151
304
  process.stdout.write((0, colors_js_1.gray)('-'.repeat(70)) + '\n');
152
305
  return 0;
@@ -156,4 +309,695 @@ async function handleAudit(options) {
156
309
  return 1;
157
310
  }
158
311
  }
312
+ // ---------------------------------------------------------------------------
313
+ // log -- write an audit event
314
+ // ---------------------------------------------------------------------------
315
+ async function handleLog(options) {
316
+ const mod = await loadAimCore();
317
+ if (!mod)
318
+ return 1;
319
+ const action = options.action;
320
+ if (!action) {
321
+ process.stderr.write('Missing required option: --action <action>\n');
322
+ process.stderr.write('Usage: opena2a identity log --action db:read --target customers [--result allowed]\n');
323
+ return 1;
324
+ }
325
+ const isJson = options.format === 'json';
326
+ try {
327
+ const aim = new mod.AIMCore({ agentName: 'default' });
328
+ aim.getIdentity(); // ensure identity exists
329
+ const validResults = ['allowed', 'denied', 'error'];
330
+ const resultInput = options.result ?? 'allowed';
331
+ if (!validResults.includes(resultInput)) {
332
+ process.stderr.write(`Invalid --result value: ${resultInput}\n`);
333
+ process.stderr.write('Valid values: allowed, denied, error\n');
334
+ return 1;
335
+ }
336
+ const event = aim.logEvent({
337
+ action,
338
+ target: options.target ?? '',
339
+ result: resultInput,
340
+ plugin: options.plugin ?? 'cli',
341
+ });
342
+ if (isJson) {
343
+ process.stdout.write(JSON.stringify(event, null, 2) + '\n');
344
+ return 0;
345
+ }
346
+ process.stdout.write((0, colors_js_1.green)('Event logged') + '\n');
347
+ process.stdout.write(` Action: ${event.action}\n`);
348
+ process.stdout.write(` Target: ${event.target}\n`);
349
+ process.stdout.write(` Result: ${event.result}\n`);
350
+ process.stdout.write(` Time: ${(0, colors_js_1.dim)(event.timestamp)}\n`);
351
+ return 0;
352
+ }
353
+ catch (err) {
354
+ process.stderr.write(`Failed to log event: ${err instanceof Error ? err.message : String(err)}\n`);
355
+ return 1;
356
+ }
357
+ }
358
+ // ---------------------------------------------------------------------------
359
+ // policy -- show or load capability policy
360
+ // ---------------------------------------------------------------------------
361
+ async function handlePolicy(options) {
362
+ const mod = await loadAimCore();
363
+ if (!mod)
364
+ return 1;
365
+ const isJson = options.format === 'json';
366
+ const args = options.file ? ['load', options.file] : [];
367
+ // If first positional arg is "load", load a YAML policy
368
+ if (args[0] === 'load' || options.file) {
369
+ return handlePolicyLoad(mod, options);
370
+ }
371
+ // Otherwise show the current policy
372
+ try {
373
+ const aim = new mod.AIMCore({ agentName: 'default' });
374
+ const p = aim.loadPolicy();
375
+ if (isJson) {
376
+ process.stdout.write(JSON.stringify(p, null, 2) + '\n');
377
+ return 0;
378
+ }
379
+ process.stdout.write((0, colors_js_1.bold)('Capability Policy') + '\n');
380
+ process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
381
+ process.stdout.write(` Default: ${p.defaultAction === 'deny' ? (0, colors_js_1.red)('deny') : (0, colors_js_1.green)('allow')}\n`);
382
+ process.stdout.write(` Rules: ${p.rules.length}\n`);
383
+ process.stdout.write('\n');
384
+ if (p.rules.length === 0) {
385
+ process.stdout.write((0, colors_js_1.dim)(' No rules defined.') + '\n');
386
+ process.stdout.write((0, colors_js_1.dim)(' Load a policy: opena2a identity policy --file policy.yaml') + '\n');
387
+ }
388
+ else {
389
+ for (const rule of p.rules) {
390
+ const actionColor = rule.action === 'allow' ? colors_js_1.green : colors_js_1.red;
391
+ const pluginNote = rule.plugins?.length ? (0, colors_js_1.dim)(` (plugins: ${rule.plugins.join(', ')})`) : '';
392
+ process.stdout.write(` ${actionColor(rule.action.padEnd(5))} ${rule.capability}${pluginNote}\n`);
393
+ }
394
+ }
395
+ process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
396
+ return 0;
397
+ }
398
+ catch (err) {
399
+ process.stderr.write(`Failed to read policy: ${err instanceof Error ? err.message : String(err)}\n`);
400
+ return 1;
401
+ }
402
+ }
403
+ async function handlePolicyLoad(mod, options) {
404
+ const filePath = options.file;
405
+ if (!filePath) {
406
+ process.stderr.write('Missing file path.\n');
407
+ process.stderr.write('Usage: opena2a identity policy --file policy.yaml\n');
408
+ return 1;
409
+ }
410
+ const isJson = options.format === 'json';
411
+ const resolved = path.resolve(filePath);
412
+ if (!fs.existsSync(resolved)) {
413
+ process.stderr.write(`File not found: ${resolved}\n`);
414
+ return 1;
415
+ }
416
+ try {
417
+ const content = fs.readFileSync(resolved, 'utf-8');
418
+ let parsed;
419
+ if (resolved.endsWith('.json')) {
420
+ parsed = JSON.parse(content);
421
+ }
422
+ else if (resolved.endsWith('.yaml') || resolved.endsWith('.yml')) {
423
+ parsed = parseSimpleYamlPolicy(content);
424
+ }
425
+ else {
426
+ process.stderr.write('Unsupported file format. Use .json or .yaml/.yml\n');
427
+ return 1;
428
+ }
429
+ const aim = new mod.AIMCore({ agentName: 'default' });
430
+ aim.savePolicy(parsed);
431
+ if (isJson) {
432
+ process.stdout.write(JSON.stringify({ loaded: true, rules: parsed.rules?.length ?? 0, path: resolved }, null, 2) + '\n');
433
+ return 0;
434
+ }
435
+ process.stdout.write((0, colors_js_1.green)('Policy loaded') + '\n');
436
+ process.stdout.write(` File: ${(0, colors_js_1.dim)(resolved)}\n`);
437
+ process.stdout.write(` Default: ${parsed.defaultAction}\n`);
438
+ process.stdout.write(` Rules: ${parsed.rules?.length ?? 0}\n`);
439
+ return 0;
440
+ }
441
+ catch (err) {
442
+ process.stderr.write(`Failed to load policy: ${err instanceof Error ? err.message : String(err)}\n`);
443
+ return 1;
444
+ }
445
+ }
446
+ // ---------------------------------------------------------------------------
447
+ // check -- check if a capability is allowed
448
+ // ---------------------------------------------------------------------------
449
+ async function handleCheck(options) {
450
+ const mod = await loadAimCore();
451
+ if (!mod)
452
+ return 1;
453
+ const capability = options.capability;
454
+ if (!capability) {
455
+ process.stderr.write('Missing capability to check.\n');
456
+ process.stderr.write('Usage: opena2a identity check <capability> [--plugin <name>]\n');
457
+ return 1;
458
+ }
459
+ const isJson = options.format === 'json';
460
+ try {
461
+ const aim = new mod.AIMCore({ agentName: 'default' });
462
+ aim.loadPolicy(); // load from file
463
+ const allowed = aim.checkCapability(capability, options.plugin);
464
+ if (isJson) {
465
+ process.stdout.write(JSON.stringify({ capability, allowed, plugin: options.plugin ?? null }, null, 2) + '\n');
466
+ return 0;
467
+ }
468
+ const label = allowed ? (0, colors_js_1.green)('ALLOWED') : (0, colors_js_1.red)('DENIED');
469
+ process.stdout.write(`${label} ${capability}`);
470
+ if (options.plugin)
471
+ process.stdout.write((0, colors_js_1.dim)(` (plugin: ${options.plugin})`));
472
+ process.stdout.write('\n');
473
+ return allowed ? 0 : 1;
474
+ }
475
+ catch (err) {
476
+ process.stderr.write(`Failed to check capability: ${err instanceof Error ? err.message : String(err)}\n`);
477
+ return 1;
478
+ }
479
+ }
480
+ // ---------------------------------------------------------------------------
481
+ // sign -- sign data with agent private key
482
+ // ---------------------------------------------------------------------------
483
+ async function handleSign(options) {
484
+ const mod = await loadAimCore();
485
+ if (!mod)
486
+ return 1;
487
+ let data = options.data;
488
+ let label = data;
489
+ let dataBytes;
490
+ if (options.file && options.subcommand === 'sign') {
491
+ // Sign file contents
492
+ const resolved = path.resolve(options.file);
493
+ if (!fs.existsSync(resolved)) {
494
+ process.stderr.write(`File not found: ${resolved}\n`);
495
+ return 1;
496
+ }
497
+ const fileContents = fs.readFileSync(resolved);
498
+ dataBytes = new Uint8Array(fileContents);
499
+ label = path.basename(resolved);
500
+ }
501
+ else if (data) {
502
+ dataBytes = new TextEncoder().encode(data);
503
+ }
504
+ else {
505
+ process.stderr.write('Missing required option: --data <string> or --file <path>\n');
506
+ process.stderr.write('Usage:\n');
507
+ process.stderr.write(' opena2a identity sign --data "message to sign"\n');
508
+ process.stderr.write(' opena2a identity sign --file ./config.json\n');
509
+ return 1;
510
+ }
511
+ const isJson = options.format === 'json';
512
+ try {
513
+ const aim = new mod.AIMCore({ agentName: 'default' });
514
+ const id = aim.getIdentity();
515
+ const signature = aim.sign(dataBytes);
516
+ const sigBase64 = Buffer.from(signature).toString('base64');
517
+ if (isJson) {
518
+ process.stdout.write(JSON.stringify({
519
+ ...(options.file ? { file: path.resolve(options.file) } : { data }),
520
+ signature: sigBase64,
521
+ publicKey: id.publicKey,
522
+ agentId: id.agentId,
523
+ }, null, 2) + '\n');
524
+ return 0;
525
+ }
526
+ process.stdout.write((0, colors_js_1.bold)('Signature') + '\n');
527
+ const displayLabel = label && label.length > 60 ? label.slice(0, 60) + '...' : (label ?? '');
528
+ if (options.file) {
529
+ process.stdout.write(` File: ${(0, colors_js_1.dim)(path.resolve(options.file))}\n`);
530
+ }
531
+ else {
532
+ process.stdout.write(` Data: ${(0, colors_js_1.dim)(displayLabel)}\n`);
533
+ }
534
+ process.stdout.write(` Signature: ${sigBase64}\n`);
535
+ process.stdout.write(` Public Key: ${(0, colors_js_1.dim)(id.publicKey)}\n`);
536
+ return 0;
537
+ }
538
+ catch (err) {
539
+ process.stderr.write(`Failed to sign: ${err instanceof Error ? err.message : String(err)}\n`);
540
+ return 1;
541
+ }
542
+ }
543
+ // ---------------------------------------------------------------------------
544
+ // verify -- verify a signature
545
+ // ---------------------------------------------------------------------------
546
+ async function handleVerify(options) {
547
+ const mod = await loadAimCore();
548
+ if (!mod)
549
+ return 1;
550
+ const data = options.data;
551
+ const signature = options.signature;
552
+ const publicKey = options.publicKey;
553
+ if (!data || !signature || !publicKey) {
554
+ process.stderr.write('Missing required options.\n');
555
+ process.stderr.write('Usage: opena2a identity verify --data "message" --signature <base64> --public-key <base64>\n');
556
+ return 1;
557
+ }
558
+ const isJson = options.format === 'json';
559
+ try {
560
+ const aim = new mod.AIMCore({ agentName: 'default' });
561
+ const dataBytes = new TextEncoder().encode(data);
562
+ const sigBytes = new Uint8Array(Buffer.from(signature, 'base64'));
563
+ const valid = aim.verify(dataBytes, sigBytes, publicKey);
564
+ if (isJson) {
565
+ process.stdout.write(JSON.stringify({ valid, data, publicKey }, null, 2) + '\n');
566
+ return 0;
567
+ }
568
+ if (valid) {
569
+ process.stdout.write((0, colors_js_1.green)('VALID') + ' Signature verified\n');
570
+ }
571
+ else {
572
+ process.stdout.write((0, colors_js_1.red)('INVALID') + ' Signature verification failed\n');
573
+ }
574
+ return valid ? 0 : 1;
575
+ }
576
+ catch (err) {
577
+ process.stderr.write(`Failed to verify: ${err instanceof Error ? err.message : String(err)}\n`);
578
+ return 1;
579
+ }
580
+ }
581
+ // ---------------------------------------------------------------------------
582
+ // attach -- wire tools to identity
583
+ // ---------------------------------------------------------------------------
584
+ async function handleAttach(options) {
585
+ const mod = await loadAimCore();
586
+ if (!mod)
587
+ return 1;
588
+ const isJson = options.format === 'json';
589
+ const targetDir = path.resolve(options.dir ?? process.cwd());
590
+ try {
591
+ // 1. Get or create identity
592
+ const agentName = options.name ?? 'default';
593
+ const aim = new mod.AIMCore({ agentName });
594
+ const id = aim.getOrCreateIdentity();
595
+ if (!isJson) {
596
+ process.stdout.write((0, colors_js_1.bold)('Attaching identity to tools') + '\n');
597
+ process.stdout.write((0, colors_js_1.gray)('-'.repeat(60)) + '\n');
598
+ process.stdout.write(` Agent: ${(0, colors_js_1.cyan)(id.agentId)}\n`);
599
+ process.stdout.write(` Name: ${id.agentName}\n`);
600
+ process.stdout.write(` Directory: ${(0, colors_js_1.dim)(targetDir)}\n\n`);
601
+ }
602
+ // 2. Determine which tools to enable
603
+ const { readManifest, writeManifest } = await import('../identity/manifest.js');
604
+ const { collectTrustHints } = await import('../identity/trust-collector.js');
605
+ const { importAllToolEvents } = await import('../identity/bridges.js');
606
+ const existing = readManifest(targetDir);
607
+ let enabledTools = {
608
+ secretless: false,
609
+ configguard: false,
610
+ arp: false,
611
+ hma: false,
612
+ shield: false,
613
+ };
614
+ if (options.all) {
615
+ // Enable all
616
+ enabledTools = { secretless: true, configguard: true, arp: true, hma: true, shield: true };
617
+ }
618
+ else if (options.tools) {
619
+ // Enable specific tools, merge with existing
620
+ const requested = options.tools.split(',').map(t => t.trim().toLowerCase());
621
+ const knownTools = ['secretless', 'configguard', 'guard', 'arp', 'hma', 'hackmyagent', 'shield'];
622
+ const unknown = requested.filter(t => !knownTools.includes(t));
623
+ if (unknown.length > 0) {
624
+ process.stderr.write(`Unknown tool(s): ${unknown.join(', ')}\n`);
625
+ process.stderr.write(`Valid tools: secretless, configguard, arp, hma, shield\n`);
626
+ return 1;
627
+ }
628
+ if (existing) {
629
+ enabledTools = { ...existing.tools };
630
+ }
631
+ for (const tool of requested) {
632
+ if (tool === 'secretless')
633
+ enabledTools.secretless = true;
634
+ if (tool === 'configguard' || tool === 'guard')
635
+ enabledTools.configguard = true;
636
+ if (tool === 'arp')
637
+ enabledTools.arp = true;
638
+ if (tool === 'hma' || tool === 'hackmyagent')
639
+ enabledTools.hma = true;
640
+ if (tool === 'shield')
641
+ enabledTools.shield = true;
642
+ }
643
+ }
644
+ else if (existing) {
645
+ // Re-attach with existing config
646
+ enabledTools = existing.tools;
647
+ }
648
+ else {
649
+ // First attach with no flags — enable all by default
650
+ enabledTools = { secretless: true, configguard: true, arp: true, hma: true, shield: true };
651
+ }
652
+ // 3. Collect trust hints from enabled tools
653
+ const manifest = {
654
+ version: '1',
655
+ agent: { name: id.agentName, agentId: id.agentId, publicKey: id.publicKey, created: id.createdAt },
656
+ tools: enabledTools,
657
+ bridging: { autoSync: options.autoSync ?? true, lastSyncAt: null },
658
+ registry: { contribute: false, gtin: false, sensorToken: null },
659
+ };
660
+ const { hints, details } = collectTrustHints(targetDir, manifest);
661
+ if (!isJson) {
662
+ if (options.tools) {
663
+ process.stdout.write((0, colors_js_1.bold)(' Requested tools: ') + options.tools + '\n\n');
664
+ }
665
+ process.stdout.write((0, colors_js_1.bold)(' Tool Detection:') + '\n');
666
+ // Show all tools (not just enabled ones) so user sees the full picture
667
+ const allToolNames = [
668
+ { key: 'secretless', label: 'Secretless' },
669
+ { key: 'configguard', label: 'ConfigGuard' },
670
+ { key: 'arp', label: 'ARP' },
671
+ { key: 'hma', label: 'HMA' },
672
+ { key: 'shield', label: 'Shield' },
673
+ ];
674
+ for (const t of allToolNames) {
675
+ const isEnabled = enabledTools[t.key];
676
+ const detail = details.find(d => d.tool === t.label);
677
+ let icon;
678
+ let reason;
679
+ if (!isEnabled) {
680
+ icon = (0, colors_js_1.dim)(' SKIP ');
681
+ reason = 'not requested';
682
+ }
683
+ else if (detail?.active) {
684
+ icon = (0, colors_js_1.green)('ACTIVE');
685
+ reason = detail.reason;
686
+ }
687
+ else {
688
+ icon = (0, colors_js_1.yellow)(' OFF ');
689
+ reason = detail?.reason ?? 'not detected';
690
+ }
691
+ const suffix = '';
692
+ process.stdout.write(` ${icon} ${t.label.padEnd(14)} ${(0, colors_js_1.dim)(reason)}${suffix}\n`);
693
+ }
694
+ process.stdout.write('\n');
695
+ }
696
+ // 4. Apply trust hints
697
+ aim.setTrustHints(hints);
698
+ // 5. Calculate trust score BEFORE sync
699
+ const trustBefore = aim.calculateTrust();
700
+ // 6. Import events from enabled tools
701
+ const bridgeResults = importAllToolEvents(aim, targetDir, enabledTools);
702
+ // 7. Calculate trust score AFTER sync
703
+ const trustAfter = aim.calculateTrust();
704
+ // 8. Write manifest
705
+ manifest.bridging.lastSyncAt = new Date().toISOString();
706
+ writeManifest(targetDir, manifest);
707
+ // 9. Log the attach event
708
+ aim.logEvent({
709
+ action: 'identity.attach',
710
+ target: targetDir,
711
+ result: 'allowed',
712
+ plugin: 'opena2a-cli',
713
+ });
714
+ if (isJson) {
715
+ process.stdout.write(JSON.stringify({
716
+ agentId: id.agentId,
717
+ name: id.agentName,
718
+ tools: enabledTools,
719
+ hints,
720
+ bridgeResults: bridgeResults.total,
721
+ trustBefore: { score: trustBefore.score, grade: trustBefore.grade },
722
+ trustAfter: { score: trustAfter.score, grade: trustAfter.grade },
723
+ manifestPath: path.join(targetDir, '.opena2a', 'agent.yaml'),
724
+ }, null, 2) + '\n');
725
+ return 0;
726
+ }
727
+ // 10. Display results
728
+ if (bridgeResults.total.imported > 0) {
729
+ process.stdout.write((0, colors_js_1.bold)(' Event Sync:') + '\n');
730
+ const tools = ['shield', 'arp', 'hma', 'configguard', 'secretless'];
731
+ for (const t of tools) {
732
+ const r = bridgeResults[t];
733
+ if (r.imported > 0 || r.skipped > 0) {
734
+ process.stdout.write(` ${t.padEnd(14)} ${(0, colors_js_1.green)(`+${r.imported}`)} imported${r.skipped > 0 ? (0, colors_js_1.dim)(`, ${r.skipped} skipped`) : ''}\n`);
735
+ }
736
+ }
737
+ process.stdout.write('\n');
738
+ }
739
+ process.stdout.write((0, colors_js_1.bold)(' Trust Score:') + '\n');
740
+ const beforeColor = trustBefore.score >= 60 ? colors_js_1.yellow : colors_js_1.red;
741
+ const afterColor = trustAfter.score >= 80 ? colors_js_1.green : trustAfter.score >= 60 ? colors_js_1.yellow : colors_js_1.red;
742
+ const delta = trustAfter.score - trustBefore.score;
743
+ const deltaLabel = delta > 0 ? (0, colors_js_1.green)(`+${delta}`) : delta < 0 ? (0, colors_js_1.red)(`${delta}`) : (0, colors_js_1.dim)('+0');
744
+ process.stdout.write(` ${beforeColor(String(trustBefore.score))} -> ${afterColor((0, colors_js_1.bold)(String(trustAfter.score)))} (${deltaLabel})\n`);
745
+ process.stdout.write(` Grade: ${afterColor(trustAfter.grade)}\n\n`);
746
+ // Active hints
747
+ const activeHintCount = Object.values(hints).filter(Boolean).length;
748
+ const totalHintCount = Object.keys(hints).length;
749
+ process.stdout.write(` Trust factors active: ${(0, colors_js_1.green)(String(activeHintCount))}/${totalHintCount}\n`);
750
+ process.stdout.write((0, colors_js_1.gray)('-'.repeat(60)) + '\n');
751
+ process.stdout.write((0, colors_js_1.dim)(` Manifest: ${path.join(targetDir, '.opena2a', 'agent.yaml')}`) + '\n');
752
+ // Suggestions for inactive tools
753
+ const inactiveTools = details.filter(d => !d.active);
754
+ if (inactiveTools.length > 0) {
755
+ process.stdout.write('\n' + (0, colors_js_1.dim)(' To improve your trust score:') + '\n');
756
+ for (const t of inactiveTools) {
757
+ const suggestion = getToolSuggestion(t.tool);
758
+ if (suggestion) {
759
+ process.stdout.write((0, colors_js_1.dim)(` ${t.tool}: ${suggestion}`) + '\n');
760
+ }
761
+ }
762
+ }
763
+ return 0;
764
+ }
765
+ catch (err) {
766
+ process.stderr.write(`Failed to attach: ${err instanceof Error ? err.message : String(err)}\n`);
767
+ return 1;
768
+ }
769
+ }
770
+ function getToolSuggestion(tool) {
771
+ switch (tool) {
772
+ case 'Secretless': return 'npx secretless-ai init';
773
+ case 'ConfigGuard': return 'opena2a guard sign';
774
+ case 'ARP': return 'opena2a runtime --init';
775
+ case 'HMA': return 'npx hackmyagent secure';
776
+ case 'Shield': return 'opena2a shield init';
777
+ default: return null;
778
+ }
779
+ }
780
+ // ---------------------------------------------------------------------------
781
+ // detach -- remove cross-tool wiring
782
+ // ---------------------------------------------------------------------------
783
+ async function handleDetach(options) {
784
+ const mod = await loadAimCore();
785
+ if (!mod)
786
+ return 1;
787
+ const isJson = options.format === 'json';
788
+ const targetDir = path.resolve(options.dir ?? process.cwd());
789
+ try {
790
+ const { readManifest, removeManifest } = await import('../identity/manifest.js');
791
+ const manifest = readManifest(targetDir);
792
+ if (!manifest) {
793
+ if (isJson) {
794
+ process.stdout.write(JSON.stringify({ detached: false, reason: 'no manifest found' }, null, 2) + '\n');
795
+ }
796
+ else {
797
+ process.stderr.write('No identity attachment found in this directory.\n');
798
+ process.stderr.write((0, colors_js_1.dim)('Run: opena2a identity attach') + '\n');
799
+ }
800
+ return 1;
801
+ }
802
+ // Log detach event before removing
803
+ const aim = new mod.AIMCore({ agentName: manifest.agent.name });
804
+ aim.logEvent({
805
+ action: 'identity.detach',
806
+ target: targetDir,
807
+ result: 'allowed',
808
+ plugin: 'opena2a-cli',
809
+ });
810
+ // Clear trust hints
811
+ aim.setTrustHints({});
812
+ // Remove manifest
813
+ removeManifest(targetDir);
814
+ if (isJson) {
815
+ process.stdout.write(JSON.stringify({ detached: true, agentId: manifest.agent.agentId }, null, 2) + '\n');
816
+ }
817
+ else {
818
+ process.stdout.write((0, colors_js_1.green)('Identity detached') + '\n');
819
+ process.stdout.write(` Agent: ${manifest.agent.agentId}\n`);
820
+ process.stdout.write(` Directory: ${(0, colors_js_1.dim)(targetDir)}\n`);
821
+ process.stdout.write((0, colors_js_1.dim)('\n Identity, audit log, and tool configs are preserved.') + '\n');
822
+ process.stdout.write((0, colors_js_1.dim)(' Only the cross-tool wiring was removed.') + '\n');
823
+ }
824
+ return 0;
825
+ }
826
+ catch (err) {
827
+ process.stderr.write(`Failed to detach: ${err instanceof Error ? err.message : String(err)}\n`);
828
+ return 1;
829
+ }
830
+ }
831
+ // ---------------------------------------------------------------------------
832
+ // sync -- re-sync events from enabled tools
833
+ // ---------------------------------------------------------------------------
834
+ async function handleSync(options) {
835
+ const mod = await loadAimCore();
836
+ if (!mod)
837
+ return 1;
838
+ const isJson = options.format === 'json';
839
+ const targetDir = path.resolve(options.dir ?? process.cwd());
840
+ try {
841
+ const { readManifest, writeManifest } = await import('../identity/manifest.js');
842
+ const { applyTrustHints } = await import('../identity/trust-collector.js');
843
+ const { importAllToolEvents } = await import('../identity/bridges.js');
844
+ const manifest = readManifest(targetDir);
845
+ if (!manifest) {
846
+ if (isJson) {
847
+ process.stdout.write(JSON.stringify({ synced: false, reason: 'no manifest found' }, null, 2) + '\n');
848
+ }
849
+ else {
850
+ process.stderr.write('No identity attachment found. Run: opena2a identity attach\n');
851
+ }
852
+ return 1;
853
+ }
854
+ const aim = new mod.AIMCore({ agentName: manifest.agent.name });
855
+ // Refresh trust hints
856
+ const { hints, score } = applyTrustHints(aim, targetDir, manifest);
857
+ // Import new events
858
+ const bridgeResults = importAllToolEvents(aim, targetDir, manifest.tools);
859
+ // Update manifest sync timestamp
860
+ manifest.bridging.lastSyncAt = new Date().toISOString();
861
+ writeManifest(targetDir, manifest);
862
+ if (isJson) {
863
+ process.stdout.write(JSON.stringify({
864
+ synced: true,
865
+ imported: bridgeResults.total.imported,
866
+ skipped: bridgeResults.total.skipped,
867
+ trustScore: score.score,
868
+ trustGrade: score.grade,
869
+ }, null, 2) + '\n');
870
+ return 0;
871
+ }
872
+ process.stdout.write((0, colors_js_1.green)('Sync complete') + '\n');
873
+ process.stdout.write(` Events imported: ${bridgeResults.total.imported}\n`);
874
+ if (bridgeResults.total.skipped > 0) {
875
+ process.stdout.write(` Skipped (dedup): ${bridgeResults.total.skipped}\n`);
876
+ }
877
+ const scoreColor = score.score >= 80 ? colors_js_1.green : score.score >= 60 ? colors_js_1.yellow : colors_js_1.red;
878
+ process.stdout.write(` Trust score: ${scoreColor((0, colors_js_1.bold)(`${score.score}/100`))} (${scoreColor(score.grade)})\n`);
879
+ const activeHints = Object.entries(hints).filter(([, v]) => v).map(([k]) => k);
880
+ if (activeHints.length > 0) {
881
+ process.stdout.write(` Active factors: ${activeHints.join(', ')}\n`);
882
+ }
883
+ return 0;
884
+ }
885
+ catch (err) {
886
+ process.stderr.write(`Failed to sync: ${err instanceof Error ? err.message : String(err)}\n`);
887
+ return 1;
888
+ }
889
+ }
890
+ // ---------------------------------------------------------------------------
891
+ // Helpers
892
+ // ---------------------------------------------------------------------------
893
+ function scoreToGrade(score) {
894
+ if (score >= 80)
895
+ return 'strong';
896
+ if (score >= 60)
897
+ return 'good';
898
+ if (score >= 40)
899
+ return 'moderate';
900
+ if (score >= 20)
901
+ return 'improving';
902
+ return 'needs-attention';
903
+ }
904
+ function progressBar(pct, width) {
905
+ const filled = Math.round((pct / 100) * width);
906
+ const empty = width - filled;
907
+ return (0, colors_js_1.green)('#'.repeat(filled)) + (0, colors_js_1.dim)('.'.repeat(empty));
908
+ }
909
+ /**
910
+ * Parse a simple YAML capability policy file.
911
+ *
912
+ * Supports the format:
913
+ * version: "1"
914
+ * defaultAction: deny
915
+ * rules:
916
+ * - capability: "db:read"
917
+ * action: allow
918
+ * - capability: "net:*"
919
+ * action: deny
920
+ * plugins:
921
+ * - untrusted-plugin
922
+ */
923
+ function parseSimpleYamlPolicy(content) {
924
+ const lines = content.split('\n');
925
+ let version = '1';
926
+ let defaultAction = 'deny';
927
+ const rules = [];
928
+ let inRules = false;
929
+ let currentRule = null;
930
+ let inPlugins = false;
931
+ for (const line of lines) {
932
+ const trimmed = line.trim();
933
+ if (trimmed === '' || trimmed.startsWith('#'))
934
+ continue;
935
+ // Top-level keys
936
+ if (!line.startsWith(' ') && !line.startsWith('\t')) {
937
+ inRules = false;
938
+ inPlugins = false;
939
+ if (currentRule?.capability && currentRule?.action) {
940
+ rules.push(currentRule);
941
+ currentRule = null;
942
+ }
943
+ }
944
+ const kvMatch = trimmed.match(/^(\w+):\s*(.*)$/);
945
+ if (kvMatch && !inRules) {
946
+ const [, key, val] = kvMatch;
947
+ const cleanVal = val.replace(/^["']|["']$/g, '');
948
+ if (key === 'version')
949
+ version = cleanVal;
950
+ if (key === 'defaultAction')
951
+ defaultAction = cleanVal;
952
+ if (key === 'rules')
953
+ inRules = true;
954
+ continue;
955
+ }
956
+ if (inRules) {
957
+ // New rule entry (starts with "- ")
958
+ if (trimmed.startsWith('- ')) {
959
+ if (currentRule?.capability && currentRule?.action) {
960
+ rules.push(currentRule);
961
+ }
962
+ currentRule = {};
963
+ inPlugins = false;
964
+ const inlineKv = trimmed.slice(2).match(/^(\w+):\s*(.*)$/);
965
+ if (inlineKv) {
966
+ const cleanVal = inlineKv[2].replace(/^["']|["']$/g, '');
967
+ if (inlineKv[1] === 'capability')
968
+ currentRule.capability = cleanVal;
969
+ if (inlineKv[1] === 'action')
970
+ currentRule.action = cleanVal;
971
+ }
972
+ continue;
973
+ }
974
+ // Rule properties
975
+ if (currentRule && kvMatch) {
976
+ const [, key, val] = kvMatch;
977
+ const cleanVal = val.replace(/^["']|["']$/g, '');
978
+ if (key === 'capability')
979
+ currentRule.capability = cleanVal;
980
+ if (key === 'action')
981
+ currentRule.action = cleanVal;
982
+ if (key === 'plugins') {
983
+ inPlugins = true;
984
+ currentRule.plugins = [];
985
+ }
986
+ continue;
987
+ }
988
+ // Plugin list items
989
+ if (inPlugins && currentRule && trimmed.startsWith('- ')) {
990
+ const pluginName = trimmed.slice(2).replace(/^["']|["']$/g, '');
991
+ if (!currentRule.plugins)
992
+ currentRule.plugins = [];
993
+ currentRule.plugins.push(pluginName);
994
+ }
995
+ }
996
+ }
997
+ // Flush last rule
998
+ if (currentRule?.capability && currentRule?.action) {
999
+ rules.push(currentRule);
1000
+ }
1001
+ return { version, defaultAction, rules };
1002
+ }
159
1003
  //# sourceMappingURL=identity.js.map