open source is social v-it.org
0
fork

Configure Feed

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

add vit vouch command

Creates app.bsky.feed.like record to publicly endorse a vetted cap.

Enforces vet-before-vouch gate.

Works for both humans and coding agents.

+183
+2
src/cli.js
··· 13 13 import registerShip from './cmd/ship.js'; 14 14 import registerSkim from './cmd/skim.js'; 15 15 import registerVet from './cmd/vet.js'; 16 + import registerVouch from './cmd/vouch.js'; 16 17 import registerFollow from './cmd/follow.js'; 17 18 import registerSetup from './cmd/setup.js'; 18 19 ··· 33 34 registerSkim(program); 34 35 registerRemix(program); 35 36 registerVet(program); 37 + registerVouch(program); 36 38 registerFollow(program); 37 39 registerSetup(program); 38 40
+126
src/cmd/vouch.js
··· 1 + // SPDX-License-Identifier: AGPL-3.0-only 2 + // Copyright (c) 2026 sol pbc 3 + 4 + import { requireDid } from '../lib/config.js'; 5 + import { CAP_COLLECTION } from '../lib/constants.js'; 6 + import { restoreAgent } from '../lib/oauth.js'; 7 + import { appendLog, readProjectConfig, readFollowing, readLog } from '../lib/vit-dir.js'; 8 + import { resolveRef, REF_PATTERN } from '../lib/cap-ref.js'; 9 + 10 + export default function register(program) { 11 + program 12 + .command('vouch') 13 + .argument('<ref>', 'Three-word cap reference (e.g. fast-cache-invalidation)') 14 + .description('Publicly endorse a vetted cap') 15 + .option('--did <did>', 'DID to use') 16 + .option('-v, --verbose', 'Show step-by-step details') 17 + .action(async (ref, opts) => { 18 + try { 19 + const { verbose } = opts; 20 + 21 + if (!REF_PATTERN.test(ref)) { 22 + console.error('invalid ref. expected three lowercase words with dashes (e.g. fast-cache-invalidation)'); 23 + process.exitCode = 1; 24 + return; 25 + } 26 + 27 + const did = requireDid(opts); 28 + if (!did) return; 29 + if (verbose) console.log(`[verbose] DID: ${did}`); 30 + 31 + const projectConfig = readProjectConfig(); 32 + const beacon = projectConfig.beacon; 33 + if (!beacon) { 34 + console.error("no beacon set. run 'vit init' in a project directory first."); 35 + process.exitCode = 1; 36 + return; 37 + } 38 + if (verbose) console.log(`[verbose] beacon: ${beacon}`); 39 + 40 + const trusted = readLog('trusted.jsonl'); 41 + const trustedEntry = trusted.find(e => e.ref === ref); 42 + if (!trustedEntry) { 43 + console.error(`cap '${ref}' is not yet vetted. vet it first:`); 44 + console.error(''); 45 + console.error(` vit vet ${ref}`); 46 + console.error(''); 47 + console.error('after reviewing, trust it with:'); 48 + console.error(''); 49 + console.error(` vit vet ${ref} --trust`); 50 + process.exitCode = 1; 51 + return; 52 + } 53 + if (verbose) console.log(`[verbose] trusted entry found, uri: ${trustedEntry.uri}`); 54 + 55 + const { agent } = await restoreAgent(did); 56 + if (verbose) console.log('[verbose] session restored'); 57 + 58 + const following = readFollowing(); 59 + const dids = following.map(e => e.did); 60 + dids.push(did); 61 + if (verbose) console.log(`[verbose] querying ${dids.length} accounts`); 62 + 63 + let match = null; 64 + for (const repoDid of dids) { 65 + try { 66 + const res = await agent.com.atproto.repo.listRecords({ 67 + repo: repoDid, 68 + collection: CAP_COLLECTION, 69 + limit: 50, 70 + }); 71 + for (const rec of res.data.records) { 72 + if (rec.value.beacon !== beacon) continue; 73 + const recRef = resolveRef(rec.value, rec.cid); 74 + if (recRef === ref) { 75 + if (!match || (rec.value.createdAt || '') > (match.value.createdAt || '')) { 76 + match = rec; 77 + } 78 + } 79 + } 80 + } catch (err) { 81 + if (verbose) console.log(`[verbose] ${repoDid}: error fetching caps: ${err.message}`); 82 + } 83 + } 84 + 85 + if (!match) { 86 + console.error(`no cap found with ref '${ref}' for this beacon.`); 87 + process.exitCode = 1; 88 + return; 89 + } 90 + 91 + const now = new Date().toISOString(); 92 + const likeRecord = { 93 + $type: 'app.bsky.feed.like', 94 + subject: { 95 + uri: match.uri, 96 + cid: match.cid, 97 + }, 98 + createdAt: now, 99 + }; 100 + if (verbose) console.log(`[verbose] creating like for ${match.uri}`); 101 + const res = await agent.com.atproto.repo.createRecord({ 102 + repo: did, 103 + collection: 'app.bsky.feed.like', 104 + record: likeRecord, 105 + }); 106 + 107 + try { 108 + appendLog('vouched.jsonl', { 109 + ref, 110 + uri: match.uri, 111 + cid: match.cid, 112 + likeUri: res.data.uri, 113 + ts: now, 114 + }); 115 + } catch (logErr) { 116 + console.error('warning: failed to write vouched.jsonl:', logErr.message); 117 + } 118 + if (verbose) console.log('[verbose] logged to vouched.jsonl'); 119 + 120 + console.log(`vouched: ${ref} (${match.uri})`); 121 + } catch (err) { 122 + console.error(err instanceof Error ? err.message : String(err)); 123 + process.exitCode = 1; 124 + } 125 + }); 126 + }
+55
test/vouch.test.js
··· 1 + // SPDX-License-Identifier: AGPL-3.0-only 2 + // Copyright (c) 2026 sol pbc 3 + 4 + import { describe, test, expect } from 'bun:test'; 5 + import { run } from './helpers.js'; 6 + import { mkdirSync, rmSync } from 'node:fs'; 7 + import { tmpdir } from 'node:os'; 8 + import { join } from 'node:path'; 9 + 10 + describe('vit vouch', () => { 11 + test('shows help with <ref> argument', () => { 12 + const result = run('vouch --help'); 13 + expect(result.stdout).toContain('<ref>'); 14 + }); 15 + 16 + test('fails with no arguments', () => { 17 + const result = run('vouch', '/tmp'); 18 + expect(result.exitCode).not.toBe(0); 19 + }); 20 + 21 + test('rejects invalid ref format', () => { 22 + const result = run('vouch not-valid', '/tmp'); 23 + expect(result.exitCode).not.toBe(0); 24 + expect(result.stderr).toContain('invalid ref'); 25 + }); 26 + 27 + test('errors when no DID configured', () => { 28 + const configHome = join(tmpdir(), '.test-vouch-config-' + Math.random().toString(36).slice(2)); 29 + mkdirSync(configHome, { recursive: true }); 30 + const result = run('vouch fast-cache-invalidation', '/tmp', { XDG_CONFIG_HOME: configHome }); 31 + expect(result.exitCode).not.toBe(0); 32 + expect(result.stderr).toContain('no DID configured'); 33 + rmSync(configHome, { recursive: true, force: true }); 34 + }); 35 + 36 + test('errors when no beacon is set', () => { 37 + const result = run('vouch fast-cache-invalidation --did did:plc:test123', '/tmp'); 38 + expect(result.exitCode).not.toBe(0); 39 + expect(result.stderr).toContain('no beacon set'); 40 + }); 41 + 42 + test('works from both agent and non-agent contexts', () => { 43 + const inAgent = run('vouch fast-cache-invalidation --did did:plc:test123', '/tmp', { CLAUDECODE: '1' }); 44 + expect(inAgent.stderr).not.toContain('must be run by a human'); 45 + expect(inAgent.stderr).not.toContain('should be run by a coding agent'); 46 + 47 + const notAgent = run('vouch fast-cache-invalidation --did did:plc:test123', '/tmp', { 48 + CLAUDECODE: '', 49 + GEMINI_CLI: '', 50 + CODEX_CI: '', 51 + }); 52 + expect(notAgent.stderr).not.toContain('must be run by a human'); 53 + expect(notAgent.stderr).not.toContain('should be run by a coding agent'); 54 + }); 55 + });