opena2a-cli 0.7.1 → 0.8.0

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.
Files changed (65) hide show
  1. package/README.md +51 -603
  2. package/dist/adapters/docker.d.ts.map +1 -1
  3. package/dist/adapters/docker.js +5 -2
  4. package/dist/adapters/docker.js.map +1 -1
  5. package/dist/adapters/registry.d.ts.map +1 -1
  6. package/dist/adapters/registry.js +1 -0
  7. package/dist/adapters/registry.js.map +1 -1
  8. package/dist/adapters/types.d.ts +2 -0
  9. package/dist/adapters/types.d.ts.map +1 -1
  10. package/dist/commands/create/index.d.ts +21 -0
  11. package/dist/commands/create/index.d.ts.map +1 -0
  12. package/dist/commands/create/index.js +44 -0
  13. package/dist/commands/create/index.js.map +1 -0
  14. package/dist/commands/create/skill.d.ts +35 -0
  15. package/dist/commands/create/skill.d.ts.map +1 -0
  16. package/dist/commands/create/skill.js +250 -0
  17. package/dist/commands/create/skill.js.map +1 -0
  18. package/dist/commands/create/templates.d.ts +47 -0
  19. package/dist/commands/create/templates.d.ts.map +1 -0
  20. package/dist/commands/create/templates.js +392 -0
  21. package/dist/commands/create/templates.js.map +1 -0
  22. package/dist/commands/detect.js +2 -2
  23. package/dist/commands/detect.js.map +1 -1
  24. package/dist/commands/guard-harden.d.ts +15 -0
  25. package/dist/commands/guard-harden.d.ts.map +1 -0
  26. package/dist/commands/guard-harden.js +242 -0
  27. package/dist/commands/guard-harden.js.map +1 -0
  28. package/dist/commands/guard.d.ts +3 -1
  29. package/dist/commands/guard.d.ts.map +1 -1
  30. package/dist/commands/guard.js +25 -1
  31. package/dist/commands/guard.js.map +1 -1
  32. package/dist/commands/identity.d.ts +5 -0
  33. package/dist/commands/identity.d.ts.map +1 -1
  34. package/dist/commands/identity.js +515 -11
  35. package/dist/commands/identity.js.map +1 -1
  36. package/dist/commands/login.d.ts +24 -0
  37. package/dist/commands/login.d.ts.map +1 -0
  38. package/dist/commands/login.js +238 -0
  39. package/dist/commands/login.js.map +1 -0
  40. package/dist/commands/mcp-audit.d.ts.map +1 -1
  41. package/dist/commands/mcp-audit.js +4 -0
  42. package/dist/commands/mcp-audit.js.map +1 -1
  43. package/dist/index.js +124 -5
  44. package/dist/index.js.map +1 -1
  45. package/dist/lib/registry-client.d.ts +59 -0
  46. package/dist/lib/registry-client.d.ts.map +1 -0
  47. package/dist/lib/registry-client.js +169 -0
  48. package/dist/lib/registry-client.js.map +1 -0
  49. package/dist/semantic/command-index.json +27 -0
  50. package/dist/shield/status.d.ts.map +1 -1
  51. package/dist/shield/status.js +22 -1
  52. package/dist/shield/status.js.map +1 -1
  53. package/dist/util/aim-client.d.ts +141 -0
  54. package/dist/util/aim-client.d.ts.map +1 -0
  55. package/dist/util/aim-client.js +225 -0
  56. package/dist/util/aim-client.js.map +1 -0
  57. package/dist/util/auth.d.ts +23 -0
  58. package/dist/util/auth.d.ts.map +1 -0
  59. package/dist/util/auth.js +54 -0
  60. package/dist/util/auth.js.map +1 -0
  61. package/dist/util/server-url.d.ts +15 -0
  62. package/dist/util/server-url.d.ts.map +1 -0
  63. package/dist/util/server-url.js +42 -0
  64. package/dist/util/server-url.js.map +1 -0
  65. package/package.json +1 -1
@@ -37,6 +37,9 @@ exports.identity = identity;
37
37
  const path = __importStar(require("path"));
38
38
  const fs = __importStar(require("fs"));
39
39
  const colors_js_1 = require("../util/colors.js");
40
+ const server_url_js_1 = require("../util/server-url.js");
41
+ const aim_client_js_1 = require("../util/aim-client.js");
42
+ const auth_js_1 = require("../util/auth.js");
40
43
  const USAGE = [
41
44
  '',
42
45
  'Usage: opena2a identity <subcommand>',
@@ -64,8 +67,29 @@ const USAGE = [
64
67
  ' detach Remove cross-tool wiring',
65
68
  ' sync Sync events from enabled tools',
66
69
  '',
70
+ 'Server Integration',
71
+ ' connect <url> Connect local identity to an AIM server',
72
+ ' disconnect Remove server association (keep local identity)',
73
+ '',
74
+ 'Server Flags (for create, list, trust, audit, log):',
75
+ ' --server <url> AIM server URL (e.g. localhost:8080, cloud)',
76
+ ' --api-key <key> AIM API key for authentication',
77
+ '',
67
78
  ].join('\n');
68
79
  async function identity(options) {
80
+ // Normalize --json flag to format
81
+ if (options.json) {
82
+ options.format = 'json';
83
+ }
84
+ // Resolve --server URL when provided
85
+ if (options.server) {
86
+ options.server = (0, server_url_js_1.resolveServerUrl)(options.server);
87
+ const serverCommands = ['create', 'list', 'show', 'trust'];
88
+ if (!serverCommands.includes(options.subcommand)) {
89
+ process.stderr.write((0, colors_js_1.yellow)(`Warning: --server is not supported for "identity ${options.subcommand}". Operating in local mode.`) + '\n\n');
90
+ options.server = undefined;
91
+ }
92
+ }
69
93
  const sub = options.subcommand;
70
94
  switch (sub) {
71
95
  case 'list':
@@ -93,6 +117,10 @@ async function identity(options) {
93
117
  return handleDetach(options);
94
118
  case 'sync':
95
119
  return handleSync(options);
120
+ case 'connect':
121
+ return handleConnect(options);
122
+ case 'disconnect':
123
+ return handleDisconnect(options);
96
124
  default:
97
125
  process.stderr.write(`Unknown identity subcommand: ${sub}\n`);
98
126
  process.stderr.write(USAGE + '\n');
@@ -110,6 +138,96 @@ async function loadAimCore() {
110
138
  }
111
139
  }
112
140
  // ---------------------------------------------------------------------------
141
+ // Server helpers
142
+ // ---------------------------------------------------------------------------
143
+ /**
144
+ * Build an AimClient from options. Returns null if no server is specified
145
+ * and no stored config exists.
146
+ */
147
+ function getServerClient(options) {
148
+ const serverFlag = options.server;
149
+ const config = (0, aim_client_js_1.loadServerConfig)();
150
+ const apiKey = options.apiKey ?? config?.apiKey;
151
+ if (serverFlag) {
152
+ const url = (0, server_url_js_1.resolveServerUrl)(serverFlag);
153
+ // If no explicit API key, check global auth for matching server
154
+ if (!apiKey) {
155
+ const globalAuth = (0, auth_js_1.loadAuth)();
156
+ if (globalAuth && (0, auth_js_1.isAuthValid)(globalAuth) && globalAuth.serverUrl === url) {
157
+ return new aim_client_js_1.AimClient(url, { accessToken: globalAuth.accessToken });
158
+ }
159
+ }
160
+ return new aim_client_js_1.AimClient(url, { apiKey });
161
+ }
162
+ if (config?.serverUrl) {
163
+ return new aim_client_js_1.AimClient(config.serverUrl, { apiKey });
164
+ }
165
+ // No server flag and no stored config -- try global auth
166
+ const globalAuth = (0, auth_js_1.loadAuth)();
167
+ if (globalAuth && (0, auth_js_1.isAuthValid)(globalAuth)) {
168
+ return new aim_client_js_1.AimClient(globalAuth.serverUrl, { accessToken: globalAuth.accessToken });
169
+ }
170
+ return null;
171
+ }
172
+ /**
173
+ * Check server health. Returns true if reachable, false otherwise.
174
+ * On failure, writes a clear error message.
175
+ */
176
+ async function checkServerHealth(client, serverUrl) {
177
+ try {
178
+ await client.health();
179
+ return true;
180
+ }
181
+ catch (err) {
182
+ if (err instanceof aim_client_js_1.AimServerError && err.serverMessage) {
183
+ process.stderr.write(`Cannot connect to AIM server at ${serverUrl}. Verify the server is running.\n`);
184
+ process.stderr.write(` Detail: ${err.serverMessage}\n`);
185
+ }
186
+ else {
187
+ process.stderr.write(`Cannot connect to AIM server at ${serverUrl}. Verify the server is running.\n`);
188
+ }
189
+ return false;
190
+ }
191
+ }
192
+ /**
193
+ * Resolve the auth token from stored config.
194
+ */
195
+ function getStoredAuth() {
196
+ const config = (0, aim_client_js_1.loadServerConfig)();
197
+ if (config) {
198
+ return {
199
+ token: config.accessToken ?? undefined,
200
+ apiKey: config.apiKey ?? undefined,
201
+ agentId: config.agentId,
202
+ serverUrl: config.serverUrl,
203
+ };
204
+ }
205
+ // Fallback: check global auth credentials from "opena2a login"
206
+ const globalAuth = (0, auth_js_1.loadAuth)();
207
+ if (globalAuth && (0, auth_js_1.isAuthValid)(globalAuth)) {
208
+ return { token: globalAuth.accessToken, serverUrl: globalAuth.serverUrl };
209
+ }
210
+ return {};
211
+ }
212
+ /**
213
+ * Format server error for display.
214
+ */
215
+ function formatServerError(err) {
216
+ if (err instanceof aim_client_js_1.AimServerError) {
217
+ if (err.statusCode === 401) {
218
+ return 'Authentication failed. Check your --api-key or run: opena2a identity connect <url>';
219
+ }
220
+ if (err.statusCode === 403) {
221
+ return 'Access denied. Your API key may lack required permissions.';
222
+ }
223
+ if (err.statusCode === 404) {
224
+ return 'Agent not found on server. It may have been deleted or never registered.';
225
+ }
226
+ return err.message;
227
+ }
228
+ return err instanceof Error ? err.message : String(err);
229
+ }
230
+ // ---------------------------------------------------------------------------
113
231
  // list / show
114
232
  // ---------------------------------------------------------------------------
115
233
  async function handleList(options) {
@@ -132,8 +250,84 @@ async function handleList(options) {
132
250
  return 0;
133
251
  }
134
252
  const id = aim.getIdentity();
253
+ // If server is configured, fetch enriched data
254
+ const client = getServerClient(options);
255
+ const serverConfig = (0, aim_client_js_1.loadServerConfig)();
256
+ const auth = getStoredAuth();
257
+ let serverAgent = null;
258
+ let serverAgentList = null;
259
+ const effectiveServerUrl = serverConfig?.serverUrl ?? auth.serverUrl;
260
+ // If we have a server client with auth, try to fetch server data
261
+ if (client && auth.token) {
262
+ try {
263
+ if (serverConfig?.agentId) {
264
+ // Fetch specific agent if we have a registered agent ID
265
+ serverAgent = await client.getAgent(auth.token, serverConfig.agentId);
266
+ }
267
+ else {
268
+ // No specific agent ID — list all agents from server
269
+ serverAgentList = await client.listAgents(auth.token);
270
+ }
271
+ }
272
+ catch (err) {
273
+ if (!isJson) {
274
+ process.stderr.write((0, colors_js_1.yellow)(`Warning: Could not fetch server data: ${formatServerError(err)}`) + '\n');
275
+ process.stderr.write((0, colors_js_1.yellow)('Showing local identity only.') + '\n\n');
276
+ }
277
+ }
278
+ }
279
+ else if (client && serverConfig?.agentId && auth.apiKey) {
280
+ try {
281
+ const loginResp = await client.login({ name: id.agentName, apiKey: auth.apiKey });
282
+ serverAgent = await client.getAgent(loginResp.accessToken, serverConfig.agentId);
283
+ (0, aim_client_js_1.saveServerConfig)({ ...serverConfig, accessToken: loginResp.accessToken, refreshToken: loginResp.refreshToken });
284
+ }
285
+ catch (err) {
286
+ if (!isJson) {
287
+ process.stderr.write((0, colors_js_1.yellow)(`Warning: Could not fetch server data: ${formatServerError(err)}`) + '\n');
288
+ process.stderr.write((0, colors_js_1.yellow)('Showing local identity only.') + '\n\n');
289
+ }
290
+ }
291
+ }
292
+ // If server returned a list of agents (from global auth), show them
293
+ if (serverAgentList && serverAgentList.agents?.length > 0) {
294
+ if (isJson) {
295
+ process.stdout.write(JSON.stringify({
296
+ local: { ...id },
297
+ server: { url: effectiveServerUrl, agents: serverAgentList.agents, total: serverAgentList.total },
298
+ }, null, 2) + '\n');
299
+ return 0;
300
+ }
301
+ process.stdout.write((0, colors_js_1.bold)('Local Identity') + '\n');
302
+ process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
303
+ process.stdout.write(` Agent ID: ${(0, colors_js_1.cyan)(id.agentId)}\n`);
304
+ process.stdout.write(` Name: ${id.agentName}\n`);
305
+ process.stdout.write(` Public Key: ${(0, colors_js_1.dim)(id.publicKey.slice(0, 32) + '...')}\n`);
306
+ process.stdout.write(` Created: ${id.createdAt}\n`);
307
+ process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n\n');
308
+ process.stdout.write((0, colors_js_1.bold)(`Server Agents (${effectiveServerUrl})`) + '\n');
309
+ process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
310
+ for (const agent of serverAgentList.agents) {
311
+ const statusColor = agent.status === 'verified' ? colors_js_1.green : agent.status === 'active' ? colors_js_1.cyan : colors_js_1.yellow;
312
+ process.stdout.write(` ${(0, colors_js_1.cyan)(agent.name)} (${(0, colors_js_1.dim)(agent.id)})\n`);
313
+ process.stdout.write(` Status: ${statusColor(agent.status)} Trust: ${agent.trustScore}\n`);
314
+ }
315
+ process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
316
+ process.stdout.write((0, colors_js_1.dim)(` ${serverAgentList.total} agent(s) total`) + '\n');
317
+ return 0;
318
+ }
135
319
  if (isJson) {
136
- process.stdout.write(JSON.stringify(id, null, 2) + '\n');
320
+ const result = { ...id };
321
+ if (serverAgent) {
322
+ result.server = {
323
+ id: serverAgent.id,
324
+ name: serverAgent.name,
325
+ trustScore: serverAgent.trustScore,
326
+ status: serverAgent.status,
327
+ serverUrl: effectiveServerUrl,
328
+ };
329
+ }
330
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
137
331
  return 0;
138
332
  }
139
333
  process.stdout.write((0, colors_js_1.bold)('Agent Identity') + '\n');
@@ -143,6 +337,19 @@ async function handleList(options) {
143
337
  process.stdout.write(` Public Key: ${(0, colors_js_1.dim)(id.publicKey.slice(0, 32) + '...')}\n`);
144
338
  process.stdout.write(` Created: ${id.createdAt}\n`);
145
339
  process.stdout.write(` Data Dir: ${(0, colors_js_1.dim)(aim.getDataDir())}\n`);
340
+ if (serverAgent && serverConfig) {
341
+ process.stdout.write('\n' + (0, colors_js_1.bold)(' Server') + '\n');
342
+ process.stdout.write(` Server URL: ${(0, colors_js_1.cyan)(serverConfig.serverUrl)}\n`);
343
+ process.stdout.write(` Server ID: ${serverAgent.id}\n`);
344
+ process.stdout.write(` Status: ${serverAgent.status === 'verified' ? (0, colors_js_1.green)(serverAgent.status) : (0, colors_js_1.yellow)(serverAgent.status)}\n`);
345
+ process.stdout.write(` Trust Score: ${serverAgent.trustScore}\n`);
346
+ }
347
+ else if (serverConfig) {
348
+ process.stdout.write('\n' + (0, colors_js_1.bold)(' Server') + '\n');
349
+ process.stdout.write(` Server URL: ${(0, colors_js_1.cyan)(serverConfig.serverUrl)}\n`);
350
+ process.stdout.write(` Server ID: ${serverConfig.agentId}\n`);
351
+ process.stdout.write(` Status: ${(0, colors_js_1.dim)('offline or unreachable')}\n`);
352
+ }
146
353
  process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
147
354
  return 0;
148
355
  }
@@ -162,13 +369,17 @@ async function handleCreate(options) {
162
369
  if (!name) {
163
370
  process.stderr.write('Missing required option: --name <name>\n');
164
371
  process.stderr.write('Usage: opena2a identity create --name my-agent\n');
372
+ process.stderr.write(' opena2a identity create --name my-agent --server localhost:8080 --api-key <key>\n');
165
373
  return 1;
166
374
  }
167
375
  const isJson = options.format === 'json';
168
376
  try {
377
+ // If --server is provided, register on the server
378
+ if (options.server) {
379
+ return handleServerCreate(options, name, isJson);
380
+ }
381
+ // Local-only creation
169
382
  const aim = new mod.AIMCore({ agentName: name });
170
- // Check if identity file already exists BEFORE calling getIdentity
171
- // (getIdentity creates one if missing, so we check the file directly)
172
383
  const dataDir = aim.getDataDir();
173
384
  const identityPath = await import('node:path').then(p => p.join(dataDir, 'identity.json'));
174
385
  const { existsSync } = await import('node:fs');
@@ -197,6 +408,82 @@ async function handleCreate(options) {
197
408
  return 1;
198
409
  }
199
410
  }
411
+ /**
412
+ * Register agent on the AIM server and store the result locally.
413
+ */
414
+ async function handleServerCreate(options, name, isJson) {
415
+ const serverUrl = (0, server_url_js_1.resolveServerUrl)(options.server);
416
+ const apiKey = options.apiKey;
417
+ if (!apiKey) {
418
+ process.stderr.write('Missing required option: --api-key <key>\n');
419
+ process.stderr.write('The AIM server requires an API key for agent registration.\n');
420
+ process.stderr.write(`Usage: opena2a identity create --name ${name} --server ${options.server} --api-key <key>\n`);
421
+ return 1;
422
+ }
423
+ const client = new aim_client_js_1.AimClient(serverUrl);
424
+ // 1. Health check
425
+ if (!(await checkServerHealth(client, serverUrl))) {
426
+ return 1;
427
+ }
428
+ try {
429
+ // 2. Register on server
430
+ const resp = await client.register({ name, displayName: name, description: `Agent ${name} registered via OpenA2A CLI` }, apiKey);
431
+ // 3. Also create local identity via aim-core
432
+ const mod = await loadAimCore();
433
+ let localId = null;
434
+ if (mod) {
435
+ const aim = new mod.AIMCore({ agentName: name });
436
+ localId = aim.getIdentity();
437
+ }
438
+ // 4. Store server config locally
439
+ const config = {
440
+ serverUrl,
441
+ agentId: resp.agentId,
442
+ apiKey,
443
+ registeredAt: new Date().toISOString(),
444
+ };
445
+ (0, aim_client_js_1.saveServerConfig)(config);
446
+ if (isJson) {
447
+ process.stdout.write(JSON.stringify({
448
+ created: true,
449
+ server: {
450
+ id: resp.agentId,
451
+ name: resp.name,
452
+ displayName: resp.displayName,
453
+ publicKey: resp.publicKey,
454
+ trustScore: resp.trustScore,
455
+ status: resp.status,
456
+ serverUrl,
457
+ },
458
+ local: localId ? {
459
+ agentId: localId.agentId,
460
+ agentName: localId.agentName,
461
+ publicKey: localId.publicKey,
462
+ } : null,
463
+ }, null, 2) + '\n');
464
+ return 0;
465
+ }
466
+ process.stdout.write((0, colors_js_1.green)('Agent registered on AIM server') + '\n');
467
+ process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
468
+ process.stdout.write(` Server ID: ${(0, colors_js_1.cyan)(resp.agentId)}\n`);
469
+ process.stdout.write(` Name: ${resp.name}\n`);
470
+ process.stdout.write(` Display Name: ${resp.displayName}\n`);
471
+ process.stdout.write(` Public Key: ${(0, colors_js_1.dim)((resp.publicKey ?? '').slice(0, 32) + '...')}\n`);
472
+ process.stdout.write(` Trust Score: ${resp.trustScore}\n`);
473
+ process.stdout.write(` Status: ${resp.status === 'verified' ? (0, colors_js_1.green)(resp.status) : (0, colors_js_1.yellow)(resp.status)}\n`);
474
+ process.stdout.write(` Server URL: ${(0, colors_js_1.dim)(serverUrl)}\n`);
475
+ if (localId) {
476
+ process.stdout.write(`\n Local ID: ${(0, colors_js_1.dim)(localId.agentId)}\n`);
477
+ }
478
+ process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
479
+ process.stdout.write((0, colors_js_1.dim)('Server config stored in ~/.opena2a/aim-core/identities/server.json') + '\n');
480
+ return 0;
481
+ }
482
+ catch (err) {
483
+ process.stderr.write(`Failed to register on server: ${formatServerError(err)}\n`);
484
+ return 1;
485
+ }
486
+ }
200
487
  // ---------------------------------------------------------------------------
201
488
  // trust
202
489
  // ---------------------------------------------------------------------------
@@ -208,6 +495,29 @@ async function handleTrust(options) {
208
495
  try {
209
496
  const aim = new mod.AIMCore({ agentName: 'default' });
210
497
  aim.getIdentity(); // ensure identity exists
498
+ // If server is configured, fetch server-side trust data
499
+ const client = getServerClient(options);
500
+ const serverConfig = (0, aim_client_js_1.loadServerConfig)();
501
+ let serverAgent = null;
502
+ if (client && serverConfig?.agentId) {
503
+ try {
504
+ const auth = getStoredAuth();
505
+ if (auth.token) {
506
+ serverAgent = await client.getAgent(auth.token, serverConfig.agentId);
507
+ }
508
+ else if (auth.apiKey) {
509
+ const id = aim.getIdentity();
510
+ const loginResp = await client.login({ name: id.agentName, apiKey: auth.apiKey });
511
+ serverAgent = await client.getAgent(loginResp.accessToken, serverConfig.agentId);
512
+ (0, aim_client_js_1.saveServerConfig)({ ...serverConfig, accessToken: loginResp.accessToken, refreshToken: loginResp.refreshToken });
513
+ }
514
+ }
515
+ catch (err) {
516
+ if (!isJson) {
517
+ process.stderr.write((0, colors_js_1.yellow)(`Warning: Could not fetch server trust data: ${formatServerError(err)}`) + '\n\n');
518
+ }
519
+ }
520
+ }
211
521
  // Auto-sync trust hints if a manifest exists (tools are attached)
212
522
  const targetDir = path.resolve(options.dir ?? process.cwd());
213
523
  let hasManifest = false;
@@ -222,11 +532,19 @@ async function handleTrust(options) {
222
532
  }
223
533
  }
224
534
  catch {
225
- // Identity module not available or manifest missing — that's fine
535
+ // Identity module not available or manifest missing
226
536
  }
227
537
  const trust = aim.calculateTrust();
228
538
  if (isJson) {
229
- process.stdout.write(JSON.stringify({ ...trust, attached: hasManifest }, null, 2) + '\n');
539
+ const result = { ...trust, attached: hasManifest };
540
+ if (serverAgent) {
541
+ result.server = {
542
+ trustScore: serverAgent.trustScore,
543
+ status: serverAgent.status,
544
+ serverUrl: serverConfig?.serverUrl,
545
+ };
546
+ }
547
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
230
548
  return 0;
231
549
  }
232
550
  const displayScore = trust.score ?? Math.round((trust.overall ?? 0) * 100);
@@ -235,6 +553,12 @@ async function handleTrust(options) {
235
553
  process.stdout.write((0, colors_js_1.bold)('Trust Score') + '\n');
236
554
  process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
237
555
  process.stdout.write(` Score: ${gradeColor((0, colors_js_1.bold)(String(displayScore) + '/100'))} (${gradeColor(displayGrade)})\n`);
556
+ // Show server trust score if available
557
+ if (serverAgent) {
558
+ const serverScore = serverAgent.trustScore;
559
+ const serverColor = serverScore >= 80 ? colors_js_1.green : serverScore >= 60 ? colors_js_1.yellow : colors_js_1.red;
560
+ process.stdout.write(` Server: ${serverColor((0, colors_js_1.bold)(String(serverScore) + '/100'))} ${(0, colors_js_1.dim)('(from AIM server)')}\n`);
561
+ }
238
562
  process.stdout.write('\n');
239
563
  process.stdout.write((0, colors_js_1.bold)(' Factors:') + '\n');
240
564
  for (const [factor, value] of Object.entries(trust.factors)) {
@@ -248,7 +572,6 @@ async function handleTrust(options) {
248
572
  if (trust.calculatedAt) {
249
573
  process.stdout.write((0, colors_js_1.dim)(` Calculated: ${trust.calculatedAt}`) + '\n');
250
574
  }
251
- // Show improvement suggestions for factors at 0%
252
575
  const zeroFactors = Object.entries(trust.factors).filter(([, v]) => v === 0);
253
576
  if (zeroFactors.length > 0) {
254
577
  process.stdout.write('\n' + (0, colors_js_1.bold)(' How to improve:') + '\n');
@@ -323,6 +646,62 @@ async function handleAudit(options) {
323
646
  return 1;
324
647
  const isJson = options.format === 'json';
325
648
  const limit = options.limit ?? 10;
649
+ // If server is configured, fetch server-side audit logs
650
+ const client = getServerClient(options);
651
+ const serverConfig = (0, aim_client_js_1.loadServerConfig)();
652
+ if (client && serverConfig?.agentId) {
653
+ try {
654
+ const auth = getStoredAuth();
655
+ let token = auth.token;
656
+ if (!token && auth.apiKey) {
657
+ const aim = new mod.AIMCore({ agentName: 'default' });
658
+ const id = aim.getIdentity();
659
+ const loginResp = await client.login({ name: id.agentName, apiKey: auth.apiKey });
660
+ token = loginResp.accessToken;
661
+ (0, aim_client_js_1.saveServerConfig)({ ...serverConfig, accessToken: loginResp.accessToken, refreshToken: loginResp.refreshToken });
662
+ }
663
+ if (token) {
664
+ const resp = await client.getAuditLogs(token, serverConfig.agentId, { pageSize: limit });
665
+ if (isJson) {
666
+ process.stdout.write(JSON.stringify({
667
+ source: 'server',
668
+ serverUrl: serverConfig.serverUrl,
669
+ total: resp.total,
670
+ auditLogs: resp.auditLogs,
671
+ }, null, 2) + '\n');
672
+ return 0;
673
+ }
674
+ process.stdout.write((0, colors_js_1.bold)(`Server Audit Log (${resp.auditLogs.length} of ${resp.total})`) + '\n');
675
+ process.stdout.write((0, colors_js_1.dim)(` Server: ${serverConfig.serverUrl}`) + '\n');
676
+ process.stdout.write((0, colors_js_1.gray)('-'.repeat(70)) + '\n');
677
+ if (resp.auditLogs.length === 0) {
678
+ process.stdout.write((0, colors_js_1.dim)(' No server audit events recorded yet.') + '\n');
679
+ }
680
+ else {
681
+ for (const e of resp.auditLogs) {
682
+ const ts = (e.createdAt ?? '').slice(0, 19).replace('T', ' ');
683
+ process.stdout.write(` ${(0, colors_js_1.dim)(ts)} ${(e.action ?? '').padEnd(16)} ${(e.resource ?? '').padEnd(16)} ${(0, colors_js_1.dim)(e.details ?? '')}\n`);
684
+ }
685
+ }
686
+ process.stdout.write((0, colors_js_1.gray)('-'.repeat(70)) + '\n');
687
+ // Also show local audit events if --verbose
688
+ if (options.verbose) {
689
+ process.stdout.write('\n');
690
+ return showLocalAudit(mod, limit, isJson);
691
+ }
692
+ return 0;
693
+ }
694
+ }
695
+ catch (err) {
696
+ if (!isJson) {
697
+ process.stderr.write((0, colors_js_1.yellow)(`Warning: Could not fetch server audit logs: ${formatServerError(err)}`) + '\n');
698
+ process.stderr.write((0, colors_js_1.yellow)('Showing local audit log.') + '\n\n');
699
+ }
700
+ }
701
+ }
702
+ return showLocalAudit(mod, limit, isJson);
703
+ }
704
+ async function showLocalAudit(mod, limit, isJson) {
326
705
  try {
327
706
  const aim = new mod.AIMCore({ agentName: 'default' });
328
707
  const events = aim.readAuditLog({ limit });
@@ -398,6 +777,136 @@ async function handleLog(options) {
398
777
  }
399
778
  }
400
779
  // ---------------------------------------------------------------------------
780
+ // connect -- connect existing local identity to an AIM server
781
+ // ---------------------------------------------------------------------------
782
+ async function handleConnect(options) {
783
+ const mod = await loadAimCore();
784
+ if (!mod)
785
+ return 1;
786
+ // The URL comes from the positional arg or --server flag
787
+ const rawUrl = options.args?.[0] ?? options.server;
788
+ if (!rawUrl) {
789
+ process.stderr.write('Missing server URL.\n');
790
+ process.stderr.write('Usage: opena2a identity connect <url> --api-key <key>\n');
791
+ process.stderr.write(' opena2a identity connect localhost:8080 --api-key <key>\n');
792
+ return 1;
793
+ }
794
+ const apiKey = options.apiKey;
795
+ if (!apiKey) {
796
+ process.stderr.write('Missing required option: --api-key <key>\n');
797
+ process.stderr.write('The AIM server requires an API key for agent registration.\n');
798
+ return 1;
799
+ }
800
+ const serverUrl = (0, server_url_js_1.resolveServerUrl)(rawUrl);
801
+ const client = new aim_client_js_1.AimClient(serverUrl);
802
+ const isJson = options.format === 'json';
803
+ // 1. Health check
804
+ if (!(await checkServerHealth(client, serverUrl))) {
805
+ return 1;
806
+ }
807
+ try {
808
+ // 2. Load local identity
809
+ const aim = new mod.AIMCore({ agentName: 'default' });
810
+ const identityFile = path.join(aim.getDataDir(), 'identity.json');
811
+ if (!fs.existsSync(identityFile)) {
812
+ process.stderr.write('No local identity found. Create one first: opena2a identity create --name my-agent\n');
813
+ return 1;
814
+ }
815
+ const id = aim.getIdentity();
816
+ // 3. Register on server
817
+ const resp = await client.register({ name: id.agentName, displayName: id.agentName, description: `Agent ${id.agentName} registered via OpenA2A CLI` }, apiKey);
818
+ // 4. Store server config
819
+ const config = {
820
+ serverUrl,
821
+ agentId: resp.agentId,
822
+ apiKey,
823
+ registeredAt: new Date().toISOString(),
824
+ };
825
+ (0, aim_client_js_1.saveServerConfig)(config);
826
+ // 5. Log the connect event
827
+ aim.logEvent({
828
+ action: 'identity.connect',
829
+ target: serverUrl,
830
+ result: 'allowed',
831
+ plugin: 'opena2a-cli',
832
+ });
833
+ if (isJson) {
834
+ process.stdout.write(JSON.stringify({
835
+ connected: true,
836
+ localAgentId: id.agentId,
837
+ serverAgentId: resp.agentId,
838
+ serverUrl,
839
+ trustScore: resp.trustScore,
840
+ status: resp.status,
841
+ }, null, 2) + '\n');
842
+ return 0;
843
+ }
844
+ process.stdout.write((0, colors_js_1.green)('Connected to AIM server') + '\n');
845
+ process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
846
+ process.stdout.write(` Local ID: ${(0, colors_js_1.cyan)(id.agentId)}\n`);
847
+ process.stdout.write(` Server ID: ${(0, colors_js_1.cyan)(resp.agentId)}\n`);
848
+ process.stdout.write(` Server URL: ${(0, colors_js_1.dim)(serverUrl)}\n`);
849
+ process.stdout.write(` Trust Score: ${resp.trustScore}\n`);
850
+ process.stdout.write(` Status: ${resp.status === 'verified' ? (0, colors_js_1.green)(resp.status) : (0, colors_js_1.yellow)(resp.status)}\n`);
851
+ process.stdout.write((0, colors_js_1.gray)('-'.repeat(50)) + '\n');
852
+ process.stdout.write((0, colors_js_1.dim)('Future commands will automatically use this server.') + '\n');
853
+ process.stdout.write((0, colors_js_1.dim)('Disconnect with: opena2a identity disconnect') + '\n');
854
+ return 0;
855
+ }
856
+ catch (err) {
857
+ process.stderr.write(`Failed to connect to server: ${formatServerError(err)}\n`);
858
+ return 1;
859
+ }
860
+ }
861
+ // ---------------------------------------------------------------------------
862
+ // disconnect -- remove server association
863
+ // ---------------------------------------------------------------------------
864
+ async function handleDisconnect(options) {
865
+ const isJson = options.format === 'json';
866
+ const config = (0, aim_client_js_1.loadServerConfig)();
867
+ if (!config) {
868
+ if (isJson) {
869
+ process.stdout.write(JSON.stringify({ disconnected: false, reason: 'no server configured' }, null, 2) + '\n');
870
+ }
871
+ else {
872
+ process.stdout.write('No server connection configured.\n');
873
+ }
874
+ return 0;
875
+ }
876
+ const removed = (0, aim_client_js_1.removeServerConfig)();
877
+ // Log the disconnect event
878
+ try {
879
+ const mod = await loadAimCore();
880
+ if (mod) {
881
+ const aim = new mod.AIMCore({ agentName: 'default' });
882
+ aim.logEvent({
883
+ action: 'identity.disconnect',
884
+ target: config.serverUrl,
885
+ result: 'allowed',
886
+ plugin: 'opena2a-cli',
887
+ });
888
+ }
889
+ }
890
+ catch {
891
+ // Non-critical
892
+ }
893
+ if (isJson) {
894
+ process.stdout.write(JSON.stringify({
895
+ disconnected: removed,
896
+ serverUrl: config.serverUrl,
897
+ agentId: config.agentId,
898
+ }, null, 2) + '\n');
899
+ }
900
+ else {
901
+ process.stdout.write((0, colors_js_1.green)('Disconnected from AIM server') + '\n');
902
+ process.stdout.write(` Server URL: ${(0, colors_js_1.dim)(config.serverUrl)}\n`);
903
+ process.stdout.write(` Server ID: ${(0, colors_js_1.dim)(config.agentId)}\n`);
904
+ process.stdout.write((0, colors_js_1.dim)('\n Local identity and audit log are preserved.') + '\n');
905
+ process.stdout.write((0, colors_js_1.dim)(' Only the server association was removed.') + '\n');
906
+ }
907
+ return 0;
908
+ }
909
+ // ---------------------------------------------------------------------------
401
910
  // policy -- show or load capability policy
402
911
  // ---------------------------------------------------------------------------
403
912
  async function handlePolicy(options) {
@@ -654,11 +1163,9 @@ async function handleAttach(options) {
654
1163
  shield: false,
655
1164
  };
656
1165
  if (options.all) {
657
- // Enable all
658
1166
  enabledTools = { secretless: true, configguard: true, arp: true, hma: true, shield: true };
659
1167
  }
660
1168
  else if (options.tools) {
661
- // Enable specific tools, merge with existing
662
1169
  const requested = options.tools.split(',').map(t => t.trim().toLowerCase());
663
1170
  const knownTools = ['secretless', 'configguard', 'guard', 'arp', 'hma', 'hackmyagent', 'shield'];
664
1171
  const unknown = requested.filter(t => !knownTools.includes(t));
@@ -684,11 +1191,9 @@ async function handleAttach(options) {
684
1191
  }
685
1192
  }
686
1193
  else if (existing) {
687
- // Re-attach with existing config
688
1194
  enabledTools = existing.tools;
689
1195
  }
690
1196
  else {
691
- // First attach with no flags — enable all by default
692
1197
  enabledTools = { secretless: true, configguard: true, arp: true, hma: true, shield: true };
693
1198
  }
694
1199
  // 3. Collect trust hints from enabled tools
@@ -705,7 +1210,6 @@ async function handleAttach(options) {
705
1210
  process.stdout.write((0, colors_js_1.bold)(' Requested tools: ') + options.tools + '\n\n');
706
1211
  }
707
1212
  process.stdout.write((0, colors_js_1.bold)(' Tool Detection:') + '\n');
708
- // Show all tools (not just enabled ones) so user sees the full picture
709
1213
  const allToolNames = [
710
1214
  { key: 'secretless', label: 'Secretless' },
711
1215
  { key: 'configguard', label: 'ConfigGuard' },