···6677vit is a Bun CLI with subcommands for DID:PLC operations and Bluesky OAuth:
88- `vit login`
99-- `vit plc-register`
1010-- `vit plc-verify`
119- `vit firehose`
1210- `vit pds-record`
1311
-39
README.md
···54545555The 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.
56565757-## plc-register
5858-5959-Generate and register a DID:PLC genesis operation.
6060-6161-### Usage
6262-6363-```bash
6464-vit plc-register --help
6565-```
6666-6767-### Options
6868-6969-- `--out <dir>` - Output directory for keys (default: `plc_keys`)
7070-- `--curve <curve>` - Rotation key curve (`k256` or `p256`, default: `k256`)
7171-- `--aka <uri>` - `alsoKnownAs` entry (may be repeated)
7272-- `--pds <url>` - PDS endpoint URL
7373-- `--dry-run` - Build and print but do not POST to PLC
7474-- `-v, --verbose` - Show verbose output
7575-7676-## plc-verify
7777-7878-Verify your saved DID against the PLC directory.
7979-8080-### Usage
8181-8282-```bash
8383-vit plc-verify
8484-```
8585-8686-This reads `did` from `vit.json` and:
8787-- Resolves the DID document via `https://plc.directory/{did}`
8888-- Fetches the audit log
8989-- Prints a summary of handles, services, verification methods, and operation history
9090-9191-### Options
9292-9393-- `--did <did>` - Check a specific DID (overrides config)
9494-- `-v, --verbose` - Show full API responses
9595-9657## firehose
97589859Listen to Bluesky Jetstream for custom record events.
+15
lab/README.md
···11+# lab/
22+33+Stashed experimental scripts. These are not part of the vit CLI.
44+55+## Scripts
66+77+- `plc-register.js` — Generate and register a DID:PLC genesis operation
88+- `plc-verify.js` — Verify a DID document and audit log from the PLC directory
99+1010+## Usage
1111+1212+```bash
1313+bun lab/plc-register.js --help
1414+bun lab/plc-verify.js --help
1515+```
···2424| `vit doctor` | Check vit setup status (alias for init) |
2525| `vit login --handle <h>` | Browser-based ATProto OAuth, saves tokens to vit.json |
2626| `vit config [action]` | Read/write vit.json config (list, set, delete) |
2727-| `vit plc-register` | Generate and register a DID:PLC genesis operation |
2828-| `vit plc-verify` | Verify DID document and audit log from PLC directory |
2927| `vit firehose` | Listen to Bluesky Jetstream for custom record events |
3028| `vit pds-record` | Write/read org.v-it records on authenticated PDS |
3129
-4
src/cli.js
···66import registerDoctor from './cmd/doctor.js';
77import registerInit from './cmd/init.js';
88import registerLogin from './cmd/login.js';
99-import registerPlcRegister from './cmd/plc-register.js';
1010-import registerPlcVerify from './cmd/plc-verify.js';
119import registerFirehose from './cmd/firehose.js';
1210import registerPdsRecord from './cmd/pds-record.js';
1311import registerSetup from './cmd/setup.js';
···2220registerDoctor(program);
2321registerInit(program);
2422registerLogin(program);
2525-registerPlcRegister(program);
2626-registerPlcVerify(program);
2723registerFirehose(program);
2824registerPdsRecord(program);
2925registerSetup(program);
+83-80
src/cmd/plc-register.js
lab/plc-register.js
···11+#!/usr/bin/env bun
12// SPDX-License-Identifier: AGPL-3.0-only
23// Copyright (c) 2026 sol pbc
34···89import bs58 from 'bs58';
910import { mkdirSync, writeFileSync } from 'node:fs';
1011import { resolve } from 'node:path';
1212+import { Command } from 'commander';
11131214const MC_P256_PUB = new Uint8Array([0x80, 0x24]);
1315const MC_K256_PUB = new Uint8Array([0xe7, 0x01]);
···220222 return previous;
221223}
222224223223-export default function register(program) {
224224- program
225225- .command('plc-register')
226226- .description('Generate & register a DID:PLC genesis operation.')
227227- .option('--out <dir>', 'Output directory for keys (default: plc_keys)', 'plc_keys')
228228- .option('--curve <curve>', 'Rotation key curve', 'k256')
229229- .option('--aka <uri>', 'alsoKnownAs entry (e.g., at://alice.example). May repeat.', collect, [])
230230- .option('--pds <url>', 'PDS endpoint URL (e.g., https://pds.example.com)')
231231- .option('--dry-run', 'Build & print but do not POST to PLC')
232232- .option('-v, --verbose', 'Show verbose output')
233233- .action(async (opts) => {
234234- if (!['k256', 'p256'].includes(opts.curve)) {
235235- console.error(`error: option '--curve' must be 'k256' or 'p256'`);
236236- process.exit(1);
237237- }
238238- const kb = generateRotationKey(opts.out, opts.curve, opts.verbose);
239239- const unsigned = buildUnsignedOp([kb.didKey], opts.aka, opts.pds ?? null);
225225+const program = new Command();
226226+program
227227+ .name('plc-register')
228228+ .description('Generate & register a DID:PLC genesis operation.')
229229+ .option('--out <dir>', 'Output directory for keys (default: plc_keys)', 'plc_keys')
230230+ .option('--curve <curve>', 'Rotation key curve', 'k256')
231231+ .option('--aka <uri>', 'alsoKnownAs entry (e.g., at://alice.example). May repeat.', collect, [])
232232+ .option('--pds <url>', 'PDS endpoint URL (e.g., https://pds.example.com)')
233233+ .option('--dry-run', 'Build & print but do not POST to PLC')
234234+ .option('-v, --verbose', 'Show verbose output')
235235+ .action(async (opts) => {
236236+ if (!['k256', 'p256'].includes(opts.curve)) {
237237+ console.error(`error: option '--curve' must be 'k256' or 'p256'`);
238238+ process.exit(1);
239239+ }
240240+ const kb = generateRotationKey(opts.out, opts.curve, opts.verbose);
241241+ const unsigned = buildUnsignedOp([kb.didKey], opts.aka, opts.pds ?? null);
242242+243243+ if (opts.verbose) {
244244+ console.log('[verbose] Unsigned operation:');
245245+ console.log(JSON.stringify(unsigned, null, 2));
246246+ }
240247241241- if (opts.verbose) {
242242- console.log('[verbose] Unsigned operation:');
243243- console.log(JSON.stringify(unsigned, null, 2));
244244- }
248248+ const unsignedCbor = dagCborEncode(unsigned);
249249+ if (opts.verbose) {
250250+ console.log(`[verbose] Encoded CBOR size: ${unsignedCbor.length} bytes`);
251251+ }
245252246246- const unsignedCbor = dagCborEncode(unsigned);
247247- if (opts.verbose) {
248248- console.log(`[verbose] Encoded CBOR size: ${unsignedCbor.length} bytes`);
249249- }
253253+ const rawSig = signLowSRaw(opts.curve, kb.privateKeyBytes, unsignedCbor);
254254+ const sigB64u = b64urlNopad(rawSig);
250255251251- const rawSig = signLowSRaw(opts.curve, kb.privateKeyBytes, unsignedCbor);
252252- const sigB64u = b64urlNopad(rawSig);
256256+ if (opts.verbose) {
257257+ console.log(`[verbose] Signature (base64url): ${sigB64u}`);
258258+ }
253259254254- if (opts.verbose) {
255255- console.log(`[verbose] Signature (base64url): ${sigB64u}`);
256256- }
260260+ const signed = { ...unsigned, sig: sigB64u };
261261+ const did = derivePlcDid(signed);
257262258258- const signed = { ...unsigned, sig: sigB64u };
259259- const did = derivePlcDid(signed);
263263+ if (opts.verbose) {
264264+ const signedCbor = dagCborEncode(signed);
265265+ const digest = sha256(signedCbor);
266266+ console.log(`[verbose] SHA256 of signed op: ${Buffer.from(digest).toString('hex')}`);
267267+ }
260268261261- if (opts.verbose) {
262262- const signedCbor = dagCborEncode(signed);
263263- const digest = sha256(signedCbor);
264264- console.log(`[verbose] SHA256 of signed op: ${Buffer.from(digest).toString('hex')}`);
265265- }
269269+ writeFileSync(`${opts.out}/genesis_unsigned.dag-cbor`, Buffer.from(unsignedCbor));
270270+ writeFileSync(`${opts.out}/genesis_signed.json`, `${JSON.stringify(signed)}\n`);
271271+ writeFileSync(`${opts.out}/did.txt`, `${did}\n`);
272272+ writeFileSync(`${opts.out}/rotation_did_key.txt`, `${kb.didKey}\n`);
266273267267- writeFileSync(`${opts.out}/genesis_unsigned.dag-cbor`, Buffer.from(unsignedCbor));
268268- writeFileSync(`${opts.out}/genesis_signed.json`, `${JSON.stringify(signed)}\n`);
269269- writeFileSync(`${opts.out}/did.txt`, `${did}\n`);
270270- writeFileSync(`${opts.out}/rotation_did_key.txt`, `${kb.didKey}\n`);
274274+ if (opts.verbose) {
275275+ console.log(`[verbose] Wrote genesis_unsigned.dag-cbor (${unsignedCbor.length} bytes)`);
276276+ console.log('[verbose] Wrote genesis_signed.json');
277277+ console.log('[verbose] Wrote did.txt');
278278+ console.log('[verbose] Wrote rotation_did_key.txt');
279279+ }
271280272272- if (opts.verbose) {
273273- console.log(`[verbose] Wrote genesis_unsigned.dag-cbor (${unsignedCbor.length} bytes)`);
274274- console.log('[verbose] Wrote genesis_signed.json');
275275- console.log('[verbose] Wrote did.txt');
276276- console.log('[verbose] Wrote rotation_did_key.txt');
277277- }
281281+ console.log(`Rotation key (did:key): ${kb.didKey}`);
282282+ console.log(`DID (derived): ${did}`);
283283+ console.log(`Wrote keys & artifacts to: ${resolve(opts.out)}`);
278284279279- console.log(`Rotation key (did:key): ${kb.didKey}`);
280280- console.log(`DID (derived): ${did}`);
281281- console.log(`Wrote keys & artifacts to: ${resolve(opts.out)}`);
285285+ if (opts.dryRun) {
286286+ console.log('Dry run selected; not POSTing to PLC.');
287287+ return;
288288+ }
282289283283- if (opts.dryRun) {
284284- console.log('Dry run selected; not POSTing to PLC.');
285285- return;
286286- }
290290+ const url = `https://plc.directory/${did}`;
291291+ if (opts.verbose) {
292292+ console.log(`[verbose] POSTing to ${url}`);
293293+ console.log('[verbose] Request body:');
294294+ console.log(JSON.stringify(signed, null, 2));
295295+ }
287296288288- const url = `https://plc.directory/${did}`;
289289- if (opts.verbose) {
290290- console.log(`[verbose] POSTing to ${url}`);
291291- console.log('[verbose] Request body:');
292292- console.log(JSON.stringify(signed, null, 2));
297297+ try {
298298+ const resp = await fetch(url, {
299299+ method: 'POST',
300300+ headers: { 'Content-Type': 'application/json' },
301301+ body: JSON.stringify(signed),
302302+ signal: AbortSignal.timeout(10000),
303303+ });
304304+ const text = await resp.text();
305305+ console.log(`POST ${url} -> ${resp.status}`);
306306+ console.log(text.slice(0, 5000));
307307+ if (resp.ok) {
308308+ console.log('Registration appears successful.');
309309+ } else {
310310+ console.log('Registration failed; see response above.');
293311 }
312312+ } catch (e) {
313313+ process.stderr.write(`Error POSTing to PLC: ${e}\n`);
314314+ process.exit(2);
315315+ }
316316+ });
294317295295- try {
296296- const resp = await fetch(url, {
297297- method: 'POST',
298298- headers: { 'Content-Type': 'application/json' },
299299- body: JSON.stringify(signed),
300300- signal: AbortSignal.timeout(10000),
301301- });
302302- const text = await resp.text();
303303- console.log(`POST ${url} -> ${resp.status}`);
304304- console.log(text.slice(0, 5000));
305305- if (resp.ok) {
306306- console.log('Registration appears successful.');
307307- } else {
308308- console.log('Registration failed; see response above.');
309309- }
310310- } catch (e) {
311311- process.stderr.write(`Error POSTing to PLC: ${e}\n`);
312312- process.exit(2);
313313- }
314314- });
315315-}
318318+program.parse();
-81
src/cmd/plc-verify.js
···11-// SPDX-License-Identifier: AGPL-3.0-only
22-// Copyright (c) 2026 sol pbc
33-44-import { loadConfig } from '../lib/config.js';
55-66-export default function register(program) {
77- program
88- .command('plc-verify')
99- .description('Verify PLC directory entry for saved Bluesky DID')
1010- .option('-v, --verbose', 'Show full API responses')
1111- .option('--did <did>', 'DID to check (overrides saved credentials)')
1212- .action(async (opts) => {
1313- try {
1414- const config = loadConfig();
1515- const did = opts.did || config.did;
1616-1717- if (!did) {
1818- throw new Error('No DID found. Run `vit login` first or pass --did <did>.');
1919- }
2020-2121- if (!did.startsWith('did:plc:')) {
2222- throw new Error(`Expected a did:plc: identifier, got: ${did}`);
2323- }
2424-2525- console.log(`Checking DID: ${did}\n`);
2626-2727- const docUrl = `https://plc.directory/${did}`;
2828- const docRes = await fetch(docUrl);
2929- if (!docRes.ok) {
3030- throw new Error(`PLC directory returned ${docRes.status} for ${docUrl}`);
3131- }
3232- const doc = await docRes.json();
3333-3434- if (opts.verbose) {
3535- console.log('[verbose] DID document:');
3636- console.log(JSON.stringify(doc, null, 2));
3737- console.log();
3838- }
3939-4040- const handles = doc.alsoKnownAs ?? [];
4141- const services = doc.service ?? [];
4242- const verificationMethods = doc.verificationMethod ?? [];
4343-4444- console.log(`DID document:`);
4545- console.log(` id: ${doc.id}`);
4646- console.log(` handles: ${handles.join(', ') || '(none)'}`);
4747- console.log(` services: ${services.map(s => `${s.id} (${s.type})`).join(', ') || '(none)'}`);
4848- console.log(` verification methods: ${verificationMethods.length}`);
4949- console.log();
5050-5151- const auditUrl = `https://plc.directory/${did}/log/audit`;
5252- const auditRes = await fetch(auditUrl);
5353- if (!auditRes.ok) {
5454- throw new Error(`PLC directory returned ${auditRes.status} for ${auditUrl}`);
5555- }
5656- const auditLog = await auditRes.json();
5757-5858- if (opts.verbose) {
5959- console.log('[verbose] Audit log:');
6060- console.log(JSON.stringify(auditLog, null, 2));
6161- console.log();
6262- }
6363-6464- console.log(`Audit log: ${auditLog.length} operation(s)`);
6565- if (auditLog.length > 0) {
6666- const first = auditLog[0];
6767- const last = auditLog[auditLog.length - 1];
6868- console.log(` first: ${first.createdAt}`);
6969- if (auditLog.length > 1) {
7070- console.log(` latest: ${last.createdAt}`);
7171- }
7272- }
7373- console.log();
7474-7575- console.log('All checks passed.');
7676- } catch (err) {
7777- console.error(err instanceof Error ? err.message : String(err));
7878- process.exitCode = 1;
7979- }
8080- });
8181-}