open source is social v-it.org
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'hopper-f5m3fl47-stash-plc-commands'

# Conflicts:
# CLAUDE.md
# README.md
# src/cli.js
# src/cmd/plc-verify.js

+176 -208
-2
CLAUDE.md
··· 6 6 7 7 vit is a Bun CLI with subcommands for DID:PLC operations and Bluesky OAuth: 8 8 - `vit login` 9 - - `vit plc-register` 10 - - `vit plc-verify` 11 9 - `vit firehose` 12 10 - `vit pds-record` 13 11
-39
README.md
··· 54 54 55 55 The access token is DPoP-bound, meaning it requires a DPoP proof JWT for each API request. The token cannot be used as a simple Bearer token. 56 56 57 - ## plc-register 58 - 59 - Generate and register a DID:PLC genesis operation. 60 - 61 - ### Usage 62 - 63 - ```bash 64 - vit plc-register --help 65 - ``` 66 - 67 - ### Options 68 - 69 - - `--out <dir>` - Output directory for keys (default: `plc_keys`) 70 - - `--curve <curve>` - Rotation key curve (`k256` or `p256`, default: `k256`) 71 - - `--aka <uri>` - `alsoKnownAs` entry (may be repeated) 72 - - `--pds <url>` - PDS endpoint URL 73 - - `--dry-run` - Build and print but do not POST to PLC 74 - - `-v, --verbose` - Show verbose output 75 - 76 - ## plc-verify 77 - 78 - Verify your saved DID against the PLC directory. 79 - 80 - ### Usage 81 - 82 - ```bash 83 - vit plc-verify 84 - ``` 85 - 86 - This reads `did` from `vit.json` and: 87 - - Resolves the DID document via `https://plc.directory/{did}` 88 - - Fetches the audit log 89 - - Prints a summary of handles, services, verification methods, and operation history 90 - 91 - ### Options 92 - 93 - - `--did <did>` - Check a specific DID (overrides config) 94 - - `-v, --verbose` - Show full API responses 95 - 96 57 ## firehose 97 58 98 59 Listen to Bluesky Jetstream for custom record events.
+15
lab/README.md
··· 1 + # lab/ 2 + 3 + Stashed experimental scripts. These are not part of the vit CLI. 4 + 5 + ## Scripts 6 + 7 + - `plc-register.js` — Generate and register a DID:PLC genesis operation 8 + - `plc-verify.js` — Verify a DID document and audit log from the PLC directory 9 + 10 + ## Usage 11 + 12 + ```bash 13 + bun lab/plc-register.js --help 14 + bun lab/plc-verify.js --help 15 + ```
+78
lab/plc-verify.js
··· 1 + #!/usr/bin/env bun 2 + // SPDX-License-Identifier: AGPL-3.0-only 3 + // Copyright (c) 2026 sol pbc 4 + 5 + import { Command } from 'commander'; 6 + 7 + const program = new Command(); 8 + program 9 + .name('plc-verify') 10 + .description('Verify PLC directory entry for a DID') 11 + .requiredOption('--did <did>', 'DID to check (required)') 12 + .option('-v, --verbose', 'Show full API responses') 13 + .action(async (opts) => { 14 + try { 15 + const did = opts.did; 16 + 17 + if (!did.startsWith('did:plc:')) { 18 + throw new Error(`Expected a did:plc: identifier, got: ${did}`); 19 + } 20 + 21 + console.log(`Checking DID: ${did}\n`); 22 + 23 + const docUrl = `https://plc.directory/${did}`; 24 + const docRes = await fetch(docUrl); 25 + if (!docRes.ok) { 26 + throw new Error(`PLC directory returned ${docRes.status} for ${docUrl}`); 27 + } 28 + const doc = await docRes.json(); 29 + 30 + if (opts.verbose) { 31 + console.log('[verbose] DID document:'); 32 + console.log(JSON.stringify(doc, null, 2)); 33 + console.log(); 34 + } 35 + 36 + const handles = doc.alsoKnownAs ?? []; 37 + const services = doc.service ?? []; 38 + const verificationMethods = doc.verificationMethod ?? []; 39 + 40 + console.log(`DID document:`); 41 + console.log(` id: ${doc.id}`); 42 + console.log(` handles: ${handles.join(', ') || '(none)'}`); 43 + console.log(` services: ${services.map(s => `${s.id} (${s.type})`).join(', ') || '(none)'}`); 44 + console.log(` verification methods: ${verificationMethods.length}`); 45 + console.log(); 46 + 47 + const auditUrl = `https://plc.directory/${did}/log/audit`; 48 + const auditRes = await fetch(auditUrl); 49 + if (!auditRes.ok) { 50 + throw new Error(`PLC directory returned ${auditRes.status} for ${auditUrl}`); 51 + } 52 + const auditLog = await auditRes.json(); 53 + 54 + if (opts.verbose) { 55 + console.log('[verbose] Audit log:'); 56 + console.log(JSON.stringify(auditLog, null, 2)); 57 + console.log(); 58 + } 59 + 60 + console.log(`Audit log: ${auditLog.length} operation(s)`); 61 + if (auditLog.length > 0) { 62 + const first = auditLog[0]; 63 + const last = auditLog[auditLog.length - 1]; 64 + console.log(` first: ${first.createdAt}`); 65 + if (auditLog.length > 1) { 66 + console.log(` latest: ${last.createdAt}`); 67 + } 68 + } 69 + console.log(); 70 + 71 + console.log('All checks passed.'); 72 + } catch (err) { 73 + console.error(err instanceof Error ? err.message : String(err)); 74 + process.exitCode = 1; 75 + } 76 + }); 77 + 78 + program.parse();
-2
skills/vit/SKILL.md
··· 24 24 | `vit doctor` | Check vit setup status (alias for init) | 25 25 | `vit login --handle <h>` | Browser-based ATProto OAuth, saves tokens to vit.json | 26 26 | `vit config [action]` | Read/write vit.json config (list, set, delete) | 27 - | `vit plc-register` | Generate and register a DID:PLC genesis operation | 28 - | `vit plc-verify` | Verify DID document and audit log from PLC directory | 29 27 | `vit firehose` | Listen to Bluesky Jetstream for custom record events | 30 28 | `vit pds-record` | Write/read org.v-it records on authenticated PDS | 31 29
-4
src/cli.js
··· 6 6 import registerDoctor from './cmd/doctor.js'; 7 7 import registerInit from './cmd/init.js'; 8 8 import registerLogin from './cmd/login.js'; 9 - import registerPlcRegister from './cmd/plc-register.js'; 10 - import registerPlcVerify from './cmd/plc-verify.js'; 11 9 import registerFirehose from './cmd/firehose.js'; 12 10 import registerPdsRecord from './cmd/pds-record.js'; 13 11 import registerSetup from './cmd/setup.js'; ··· 22 20 registerDoctor(program); 23 21 registerInit(program); 24 22 registerLogin(program); 25 - registerPlcRegister(program); 26 - registerPlcVerify(program); 27 23 registerFirehose(program); 28 24 registerPdsRecord(program); 29 25 registerSetup(program);
+83 -80
src/cmd/plc-register.js lab/plc-register.js
··· 1 + #!/usr/bin/env bun 1 2 // SPDX-License-Identifier: AGPL-3.0-only 2 3 // Copyright (c) 2026 sol pbc 3 4 ··· 8 9 import bs58 from 'bs58'; 9 10 import { mkdirSync, writeFileSync } from 'node:fs'; 10 11 import { resolve } from 'node:path'; 12 + import { Command } from 'commander'; 11 13 12 14 const MC_P256_PUB = new Uint8Array([0x80, 0x24]); 13 15 const MC_K256_PUB = new Uint8Array([0xe7, 0x01]); ··· 220 222 return previous; 221 223 } 222 224 223 - export default function register(program) { 224 - program 225 - .command('plc-register') 226 - .description('Generate & register a DID:PLC genesis operation.') 227 - .option('--out <dir>', 'Output directory for keys (default: plc_keys)', 'plc_keys') 228 - .option('--curve <curve>', 'Rotation key curve', 'k256') 229 - .option('--aka <uri>', 'alsoKnownAs entry (e.g., at://alice.example). May repeat.', collect, []) 230 - .option('--pds <url>', 'PDS endpoint URL (e.g., https://pds.example.com)') 231 - .option('--dry-run', 'Build & print but do not POST to PLC') 232 - .option('-v, --verbose', 'Show verbose output') 233 - .action(async (opts) => { 234 - if (!['k256', 'p256'].includes(opts.curve)) { 235 - console.error(`error: option '--curve' must be 'k256' or 'p256'`); 236 - process.exit(1); 237 - } 238 - const kb = generateRotationKey(opts.out, opts.curve, opts.verbose); 239 - const unsigned = buildUnsignedOp([kb.didKey], opts.aka, opts.pds ?? null); 225 + const program = new Command(); 226 + program 227 + .name('plc-register') 228 + .description('Generate & register a DID:PLC genesis operation.') 229 + .option('--out <dir>', 'Output directory for keys (default: plc_keys)', 'plc_keys') 230 + .option('--curve <curve>', 'Rotation key curve', 'k256') 231 + .option('--aka <uri>', 'alsoKnownAs entry (e.g., at://alice.example). May repeat.', collect, []) 232 + .option('--pds <url>', 'PDS endpoint URL (e.g., https://pds.example.com)') 233 + .option('--dry-run', 'Build & print but do not POST to PLC') 234 + .option('-v, --verbose', 'Show verbose output') 235 + .action(async (opts) => { 236 + if (!['k256', 'p256'].includes(opts.curve)) { 237 + console.error(`error: option '--curve' must be 'k256' or 'p256'`); 238 + process.exit(1); 239 + } 240 + const kb = generateRotationKey(opts.out, opts.curve, opts.verbose); 241 + const unsigned = buildUnsignedOp([kb.didKey], opts.aka, opts.pds ?? null); 242 + 243 + if (opts.verbose) { 244 + console.log('[verbose] Unsigned operation:'); 245 + console.log(JSON.stringify(unsigned, null, 2)); 246 + } 240 247 241 - if (opts.verbose) { 242 - console.log('[verbose] Unsigned operation:'); 243 - console.log(JSON.stringify(unsigned, null, 2)); 244 - } 248 + const unsignedCbor = dagCborEncode(unsigned); 249 + if (opts.verbose) { 250 + console.log(`[verbose] Encoded CBOR size: ${unsignedCbor.length} bytes`); 251 + } 245 252 246 - const unsignedCbor = dagCborEncode(unsigned); 247 - if (opts.verbose) { 248 - console.log(`[verbose] Encoded CBOR size: ${unsignedCbor.length} bytes`); 249 - } 253 + const rawSig = signLowSRaw(opts.curve, kb.privateKeyBytes, unsignedCbor); 254 + const sigB64u = b64urlNopad(rawSig); 250 255 251 - const rawSig = signLowSRaw(opts.curve, kb.privateKeyBytes, unsignedCbor); 252 - const sigB64u = b64urlNopad(rawSig); 256 + if (opts.verbose) { 257 + console.log(`[verbose] Signature (base64url): ${sigB64u}`); 258 + } 253 259 254 - if (opts.verbose) { 255 - console.log(`[verbose] Signature (base64url): ${sigB64u}`); 256 - } 260 + const signed = { ...unsigned, sig: sigB64u }; 261 + const did = derivePlcDid(signed); 257 262 258 - const signed = { ...unsigned, sig: sigB64u }; 259 - const did = derivePlcDid(signed); 263 + if (opts.verbose) { 264 + const signedCbor = dagCborEncode(signed); 265 + const digest = sha256(signedCbor); 266 + console.log(`[verbose] SHA256 of signed op: ${Buffer.from(digest).toString('hex')}`); 267 + } 260 268 261 - if (opts.verbose) { 262 - const signedCbor = dagCborEncode(signed); 263 - const digest = sha256(signedCbor); 264 - console.log(`[verbose] SHA256 of signed op: ${Buffer.from(digest).toString('hex')}`); 265 - } 269 + writeFileSync(`${opts.out}/genesis_unsigned.dag-cbor`, Buffer.from(unsignedCbor)); 270 + writeFileSync(`${opts.out}/genesis_signed.json`, `${JSON.stringify(signed)}\n`); 271 + writeFileSync(`${opts.out}/did.txt`, `${did}\n`); 272 + writeFileSync(`${opts.out}/rotation_did_key.txt`, `${kb.didKey}\n`); 266 273 267 - writeFileSync(`${opts.out}/genesis_unsigned.dag-cbor`, Buffer.from(unsignedCbor)); 268 - writeFileSync(`${opts.out}/genesis_signed.json`, `${JSON.stringify(signed)}\n`); 269 - writeFileSync(`${opts.out}/did.txt`, `${did}\n`); 270 - writeFileSync(`${opts.out}/rotation_did_key.txt`, `${kb.didKey}\n`); 274 + if (opts.verbose) { 275 + console.log(`[verbose] Wrote genesis_unsigned.dag-cbor (${unsignedCbor.length} bytes)`); 276 + console.log('[verbose] Wrote genesis_signed.json'); 277 + console.log('[verbose] Wrote did.txt'); 278 + console.log('[verbose] Wrote rotation_did_key.txt'); 279 + } 271 280 272 - if (opts.verbose) { 273 - console.log(`[verbose] Wrote genesis_unsigned.dag-cbor (${unsignedCbor.length} bytes)`); 274 - console.log('[verbose] Wrote genesis_signed.json'); 275 - console.log('[verbose] Wrote did.txt'); 276 - console.log('[verbose] Wrote rotation_did_key.txt'); 277 - } 281 + console.log(`Rotation key (did:key): ${kb.didKey}`); 282 + console.log(`DID (derived): ${did}`); 283 + console.log(`Wrote keys & artifacts to: ${resolve(opts.out)}`); 278 284 279 - console.log(`Rotation key (did:key): ${kb.didKey}`); 280 - console.log(`DID (derived): ${did}`); 281 - console.log(`Wrote keys & artifacts to: ${resolve(opts.out)}`); 285 + if (opts.dryRun) { 286 + console.log('Dry run selected; not POSTing to PLC.'); 287 + return; 288 + } 282 289 283 - if (opts.dryRun) { 284 - console.log('Dry run selected; not POSTing to PLC.'); 285 - return; 286 - } 290 + const url = `https://plc.directory/${did}`; 291 + if (opts.verbose) { 292 + console.log(`[verbose] POSTing to ${url}`); 293 + console.log('[verbose] Request body:'); 294 + console.log(JSON.stringify(signed, null, 2)); 295 + } 287 296 288 - const url = `https://plc.directory/${did}`; 289 - if (opts.verbose) { 290 - console.log(`[verbose] POSTing to ${url}`); 291 - console.log('[verbose] Request body:'); 292 - console.log(JSON.stringify(signed, null, 2)); 297 + try { 298 + const resp = await fetch(url, { 299 + method: 'POST', 300 + headers: { 'Content-Type': 'application/json' }, 301 + body: JSON.stringify(signed), 302 + signal: AbortSignal.timeout(10000), 303 + }); 304 + const text = await resp.text(); 305 + console.log(`POST ${url} -> ${resp.status}`); 306 + console.log(text.slice(0, 5000)); 307 + if (resp.ok) { 308 + console.log('Registration appears successful.'); 309 + } else { 310 + console.log('Registration failed; see response above.'); 293 311 } 312 + } catch (e) { 313 + process.stderr.write(`Error POSTing to PLC: ${e}\n`); 314 + process.exit(2); 315 + } 316 + }); 294 317 295 - try { 296 - const resp = await fetch(url, { 297 - method: 'POST', 298 - headers: { 'Content-Type': 'application/json' }, 299 - body: JSON.stringify(signed), 300 - signal: AbortSignal.timeout(10000), 301 - }); 302 - const text = await resp.text(); 303 - console.log(`POST ${url} -> ${resp.status}`); 304 - console.log(text.slice(0, 5000)); 305 - if (resp.ok) { 306 - console.log('Registration appears successful.'); 307 - } else { 308 - console.log('Registration failed; see response above.'); 309 - } 310 - } catch (e) { 311 - process.stderr.write(`Error POSTing to PLC: ${e}\n`); 312 - process.exit(2); 313 - } 314 - }); 315 - } 318 + program.parse();
-81
src/cmd/plc-verify.js
··· 1 - // SPDX-License-Identifier: AGPL-3.0-only 2 - // Copyright (c) 2026 sol pbc 3 - 4 - import { loadConfig } from '../lib/config.js'; 5 - 6 - export default function register(program) { 7 - program 8 - .command('plc-verify') 9 - .description('Verify PLC directory entry for saved Bluesky DID') 10 - .option('-v, --verbose', 'Show full API responses') 11 - .option('--did <did>', 'DID to check (overrides saved credentials)') 12 - .action(async (opts) => { 13 - try { 14 - const config = loadConfig(); 15 - const did = opts.did || config.did; 16 - 17 - if (!did) { 18 - throw new Error('No DID found. Run `vit login` first or pass --did <did>.'); 19 - } 20 - 21 - if (!did.startsWith('did:plc:')) { 22 - throw new Error(`Expected a did:plc: identifier, got: ${did}`); 23 - } 24 - 25 - console.log(`Checking DID: ${did}\n`); 26 - 27 - const docUrl = `https://plc.directory/${did}`; 28 - const docRes = await fetch(docUrl); 29 - if (!docRes.ok) { 30 - throw new Error(`PLC directory returned ${docRes.status} for ${docUrl}`); 31 - } 32 - const doc = await docRes.json(); 33 - 34 - if (opts.verbose) { 35 - console.log('[verbose] DID document:'); 36 - console.log(JSON.stringify(doc, null, 2)); 37 - console.log(); 38 - } 39 - 40 - const handles = doc.alsoKnownAs ?? []; 41 - const services = doc.service ?? []; 42 - const verificationMethods = doc.verificationMethod ?? []; 43 - 44 - console.log(`DID document:`); 45 - console.log(` id: ${doc.id}`); 46 - console.log(` handles: ${handles.join(', ') || '(none)'}`); 47 - console.log(` services: ${services.map(s => `${s.id} (${s.type})`).join(', ') || '(none)'}`); 48 - console.log(` verification methods: ${verificationMethods.length}`); 49 - console.log(); 50 - 51 - const auditUrl = `https://plc.directory/${did}/log/audit`; 52 - const auditRes = await fetch(auditUrl); 53 - if (!auditRes.ok) { 54 - throw new Error(`PLC directory returned ${auditRes.status} for ${auditUrl}`); 55 - } 56 - const auditLog = await auditRes.json(); 57 - 58 - if (opts.verbose) { 59 - console.log('[verbose] Audit log:'); 60 - console.log(JSON.stringify(auditLog, null, 2)); 61 - console.log(); 62 - } 63 - 64 - console.log(`Audit log: ${auditLog.length} operation(s)`); 65 - if (auditLog.length > 0) { 66 - const first = auditLog[0]; 67 - const last = auditLog[auditLog.length - 1]; 68 - console.log(` first: ${first.createdAt}`); 69 - if (auditLog.length > 1) { 70 - console.log(` latest: ${last.createdAt}`); 71 - } 72 - } 73 - console.log(); 74 - 75 - console.log('All checks passed.'); 76 - } catch (err) { 77 - console.error(err instanceof Error ? err.message : String(err)); 78 - process.exitCode = 1; 79 - } 80 - }); 81 - }