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.
- package/dist/commands/identity.d.ts +12 -0
- package/dist/commands/identity.d.ts.map +1 -1
- package/dist/commands/identity.js +862 -18
- package/dist/commands/identity.js.map +1 -1
- package/dist/commands/shield.d.ts +2 -0
- package/dist/commands/shield.d.ts.map +1 -1
- package/dist/commands/shield.js +6 -5
- package/dist/commands/shield.js.map +1 -1
- package/dist/identity/bridges.d.ts +66 -0
- package/dist/identity/bridges.d.ts.map +1 -0
- package/dist/identity/bridges.js +314 -0
- package/dist/identity/bridges.js.map +1 -0
- package/dist/identity/index.d.ts +7 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/index.js +18 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/identity/manifest.d.ts +38 -0
- package/dist/identity/manifest.d.ts.map +1 -0
- package/dist/identity/manifest.js +128 -0
- package/dist/identity/manifest.js.map +1 -0
- package/dist/identity/trust-collector.d.ts +43 -0
- package/dist/identity/trust-collector.d.ts.map +1 -0
- package/dist/identity/trust-collector.js +159 -0
- package/dist/identity/trust-collector.js.map +1 -0
- package/dist/index.js +31 -3
- package/dist/index.js.map +1 -1
- package/dist/shield/init.d.ts +2 -0
- package/dist/shield/init.d.ts.map +1 -1
- package/dist/shield/init.js +12 -5
- package/dist/shield/init.js.map +1 -1
- package/dist/shield/integrity.js +2 -2
- package/dist/shield/integrity.js.map +1 -1
- package/dist/shield/status.js +1 -2
- package/dist/shield/status.js.map +1 -1
- package/package.json +1 -1
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
116
|
-
|
|
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)('
|
|
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.
|
|
149
|
-
|
|
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
|