@tjamescouch/agentchat 0.13.0 → 0.14.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.
- package/bin/agentchat.js +43 -0
- package/lib/identity.js +78 -0
- package/package.json +1 -1
package/bin/agentchat.js
CHANGED
|
@@ -550,6 +550,8 @@ program
|
|
|
550
550
|
.option('-e, --export', 'Export public key for sharing (JSON to stdout)')
|
|
551
551
|
.option('-r, --rotate', 'Rotate to new keypair (signs new key with old key)')
|
|
552
552
|
.option('--verify-chain', 'Verify the rotation chain')
|
|
553
|
+
.option('--revoke [reason]', 'Generate signed revocation notice (outputs JSON)')
|
|
554
|
+
.option('--verify-revocation <file>', 'Verify a revocation notice file')
|
|
553
555
|
.option('-f, --file <path>', 'Identity file path', DEFAULT_IDENTITY_PATH)
|
|
554
556
|
.option('-n, --name <name>', 'Agent name (for --generate)', `agent-${process.pid}`)
|
|
555
557
|
.option('--force', 'Overwrite existing identity')
|
|
@@ -638,6 +640,43 @@ program
|
|
|
638
640
|
process.exit(1);
|
|
639
641
|
}
|
|
640
642
|
|
|
643
|
+
} else if (options.revoke) {
|
|
644
|
+
// Generate revocation notice
|
|
645
|
+
const identity = await Identity.load(options.file);
|
|
646
|
+
const reason = typeof options.revoke === 'string' ? options.revoke : 'revoked';
|
|
647
|
+
|
|
648
|
+
console.error(`Generating revocation notice for identity...`);
|
|
649
|
+
console.error(` Agent ID: ${identity.getAgentId()}`);
|
|
650
|
+
console.error(` Reason: ${reason}`);
|
|
651
|
+
console.error('');
|
|
652
|
+
console.error('WARNING: Publishing this notice declares your key as untrusted.');
|
|
653
|
+
console.error('');
|
|
654
|
+
|
|
655
|
+
const notice = identity.revoke(reason);
|
|
656
|
+
console.log(JSON.stringify(notice, null, 2));
|
|
657
|
+
|
|
658
|
+
} else if (options.verifyRevocation) {
|
|
659
|
+
// Verify a revocation notice file
|
|
660
|
+
const noticeData = await fs.readFile(options.verifyRevocation, 'utf-8');
|
|
661
|
+
const notice = JSON.parse(noticeData);
|
|
662
|
+
|
|
663
|
+
console.log('Verifying revocation notice...');
|
|
664
|
+
const isValid = Identity.verifyRevocation(notice);
|
|
665
|
+
|
|
666
|
+
if (isValid) {
|
|
667
|
+
console.log('Revocation notice is VALID');
|
|
668
|
+
console.log(` Agent ID: ${notice.agent_id}`);
|
|
669
|
+
console.log(` Fingerprint: ${notice.fingerprint}`);
|
|
670
|
+
console.log(` Reason: ${notice.reason}`);
|
|
671
|
+
console.log(` Timestamp: ${notice.timestamp}`);
|
|
672
|
+
if (notice.original_agent_id) {
|
|
673
|
+
console.log(` Original Agent ID: ${notice.original_agent_id}`);
|
|
674
|
+
}
|
|
675
|
+
} else {
|
|
676
|
+
console.error('Revocation notice is INVALID');
|
|
677
|
+
process.exit(1);
|
|
678
|
+
}
|
|
679
|
+
|
|
641
680
|
} else {
|
|
642
681
|
// Default: show if exists, otherwise show help
|
|
643
682
|
const exists = await Identity.exists(options.file);
|
|
@@ -648,6 +687,10 @@ program
|
|
|
648
687
|
console.log(` Fingerprint: ${identity.getFingerprint()}`);
|
|
649
688
|
console.log(` Agent ID: ${identity.getAgentId()}`);
|
|
650
689
|
console.log(` Created: ${identity.created}`);
|
|
690
|
+
if (identity.rotations.length > 0) {
|
|
691
|
+
console.log(` Rotations: ${identity.rotations.length}`);
|
|
692
|
+
console.log(` Original Agent ID: ${identity.getOriginalAgentId()}`);
|
|
693
|
+
}
|
|
651
694
|
} else {
|
|
652
695
|
console.log('No identity found.');
|
|
653
696
|
console.log(`Use --generate to create one at ${options.file}`);
|
package/lib/identity.js
CHANGED
|
@@ -295,4 +295,82 @@ export class Identity {
|
|
|
295
295
|
getOriginalAgentId() {
|
|
296
296
|
return pubkeyToAgentId(this.getOriginalPubkey());
|
|
297
297
|
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Generate a signed revocation notice for this identity
|
|
301
|
+
* A revocation notice declares that the key should no longer be trusted
|
|
302
|
+
* @param {string} reason - Reason for revocation (e.g., "compromised", "retired", "lost")
|
|
303
|
+
* @returns {object} Revocation notice with pubkey, reason, signature, timestamp
|
|
304
|
+
*/
|
|
305
|
+
revoke(reason = 'revoked') {
|
|
306
|
+
if (!this.privkey) {
|
|
307
|
+
throw new Error('Private key not available - cannot create revocation notice');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const timestamp = new Date().toISOString();
|
|
311
|
+
|
|
312
|
+
// Create revocation content to sign
|
|
313
|
+
const revocationContent = JSON.stringify({
|
|
314
|
+
type: 'REVOCATION',
|
|
315
|
+
pubkey: this.pubkey,
|
|
316
|
+
agent_id: this.getAgentId(),
|
|
317
|
+
reason,
|
|
318
|
+
timestamp
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Sign with the key being revoked (proves ownership)
|
|
322
|
+
const signature = this.sign(revocationContent);
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
type: 'REVOCATION',
|
|
326
|
+
pubkey: this.pubkey,
|
|
327
|
+
agent_id: this.getAgentId(),
|
|
328
|
+
fingerprint: this.getFingerprint(),
|
|
329
|
+
reason,
|
|
330
|
+
timestamp,
|
|
331
|
+
signature,
|
|
332
|
+
// Include rotation history for full chain verification
|
|
333
|
+
rotations: this.rotations.length > 0 ? this.rotations : undefined,
|
|
334
|
+
original_agent_id: this.rotations.length > 0 ? this.getOriginalAgentId() : undefined
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Verify a revocation notice
|
|
340
|
+
* Checks that the signature is valid using the pubkey in the notice
|
|
341
|
+
* @param {object} notice - Revocation notice to verify
|
|
342
|
+
* @returns {boolean} True if signature is valid
|
|
343
|
+
*/
|
|
344
|
+
static verifyRevocation(notice) {
|
|
345
|
+
if (!notice || notice.type !== 'REVOCATION') {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
const revocationContent = JSON.stringify({
|
|
351
|
+
type: 'REVOCATION',
|
|
352
|
+
pubkey: notice.pubkey,
|
|
353
|
+
agent_id: notice.agent_id,
|
|
354
|
+
reason: notice.reason,
|
|
355
|
+
timestamp: notice.timestamp
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
return Identity.verify(revocationContent, notice.signature, notice.pubkey);
|
|
359
|
+
} catch {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Check if a pubkey has been revoked by checking against a revocation notice
|
|
366
|
+
* @param {string} pubkey - Public key to check
|
|
367
|
+
* @param {object} notice - Revocation notice
|
|
368
|
+
* @returns {boolean} True if the pubkey matches the revoked key
|
|
369
|
+
*/
|
|
370
|
+
static isRevoked(pubkey, notice) {
|
|
371
|
+
if (!Identity.verifyRevocation(notice)) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
return notice.pubkey === pubkey;
|
|
375
|
+
}
|
|
298
376
|
}
|