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-nhmtk74r-ship-skim-split'

# Conflicts:
# README.md
# src/cmd/pds-record.js

+149 -110
+1 -2
ARCHITECTURE.md
··· 72 72 73 73 - Custom lexicons for vouch (`org.v-it.vouch`), evidence, and other vit record types. 74 74 - Cap-specific fields (intent, scope, risk, kind, beacon, provenance) as lexicon properties. 75 - - Runtime lexicon validation (currently `validate: false` in pds-record.js). 76 - - Migration of `org.v-it.hello` references to `org.v-it.cap` in CLI commands. 75 + - Runtime lexicon validation (currently `validate: false` in CLI write commands).
+2 -1
CLAUDE.md
··· 7 7 vit is a Bun CLI with subcommands for DID:PLC operations and Bluesky OAuth: 8 8 - `vit login` 9 9 - `vit firehose` 10 - - `vit pds-record` 10 + - `vit ship` 11 + - `vit skim` 11 12 12 13 Source layout: 13 14 - `bin/vit.js` - executable entrypoint
+19 -10
README.md
··· 67 67 ### Options 68 68 69 69 - `--did <did>` - Filter by DID (reads saved DID from config if not provided) 70 - - `--collection <nsid>` - Collection NSID to filter (default: `org.v-it.hello`) 70 + - `--collection <nsid>` - Collection NSID to filter (default: `org.v-it.cap`) 71 71 - `-v, --verbose` - Show full JSON for each event 72 72 73 - ## pds-record 73 + ## ship 74 + 75 + Write a cap (org.v-it.cap record) to the authenticated PDS. 76 + 77 + ```bash 78 + vit ship "hello from caps" 79 + ``` 80 + 81 + | Option | Description | 82 + |---|---| 83 + | `--did <did>` | DID to use (default: from config) | 74 84 75 - Write and read a custom `org.v-it.hello` record on the authenticated PDS. 85 + ## skim 76 86 77 - ### Usage 87 + List caps from the authenticated PDS. 78 88 79 89 ```bash 80 - vit pds-record --message "hello world" 90 + vit skim 81 91 ``` 82 92 83 - ### Options 84 - 85 - - `--did <did>` - DID to use (overrides config) 86 - - `--message <msg>` - Message to write (default: `hello world`) 87 - - `-v, --verbose` - Show full API responses 93 + | Option | Description | 94 + |---|---| 95 + | `--did <did>` | DID to use (default: from config) | 96 + | `--limit <n>` | Max records to return (default: 25) |
+2 -1
skills/vit/SKILL.md
··· 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 27 | `vit firehose` | Listen to Bluesky Jetstream for custom record events | 28 - | `vit pds-record` | Write/read org.v-it records on authenticated PDS | 28 + | `vit ship <text>` | Write a cap to the authenticated PDS | 29 + | `vit skim` | List caps from the authenticated PDS | 29 30 30 31 For full option details, see [README.md](../../README.md). 31 32
+4 -2
src/cli.js
··· 7 7 import registerInit from './cmd/init.js'; 8 8 import registerLogin from './cmd/login.js'; 9 9 import registerFirehose from './cmd/firehose.js'; 10 - import registerPdsRecord from './cmd/pds-record.js'; 10 + import registerShip from './cmd/ship.js'; 11 + import registerSkim from './cmd/skim.js'; 11 12 import registerSetup from './cmd/setup.js'; 12 13 13 14 const program = new Command(); ··· 21 22 registerInit(program); 22 23 registerLogin(program); 23 24 registerFirehose(program); 24 - registerPdsRecord(program); 25 + registerShip(program); 26 + registerSkim(program); 25 27 registerSetup(program); 26 28 27 29 export { program };
+2 -2
src/cmd/firehose.js
··· 4 4 import { loadConfig } from '../lib/config.js'; 5 5 6 6 const JETSTREAM_URL = 'wss://jetstream2.us-east.bsky.network/subscribe'; 7 - const DEFAULT_COLLECTION = 'org.v-it.hello'; 7 + const DEFAULT_COLLECTION = 'org.v-it.cap'; 8 8 9 9 let ws = null; 10 10 let shuttingDown = false; ··· 35 35 return `[${time}] ${operation} ${collection} from ${didShort} rkey=${rkey}`; 36 36 } 37 37 38 - const message = event.commit?.record?.message; 38 + const message = event.commit?.record?.text; 39 39 if (typeof message === 'string') { 40 40 return `[${time}] ${operation} ${collection} from ${didShort} rkey=${rkey} — "${message}"`; 41 41 }
-90
src/cmd/pds-record.js
··· 1 - // SPDX-License-Identifier: AGPL-3.0-only 2 - // Copyright (c) 2026 sol pbc 3 - 4 - import { Agent } from '@atproto/api'; 5 - import { loadConfig } from '../lib/config.js'; 6 - import { createOAuthClient, createSessionStore } from '../lib/oauth.js'; 7 - import { configPath } from '../lib/paths.js'; 8 - 9 - export default function register(program) { 10 - program 11 - .command('pds-record') 12 - .description('Write and read a custom org.v-it.hello record on the authenticated PDS') 13 - .option('--did <did>', 'DID to use (overrides saved credentials)') 14 - .option('--message <msg>', 'Message to write', 'hello world') 15 - .action(async (opts) => { 16 - try { 17 - const config = loadConfig(); 18 - const did = opts.did || config.did; 19 - 20 - if (!did) { 21 - throw new Error('No DID found. Run `vit login` first or pass --did <did>.'); 22 - } 23 - 24 - const sessionStore = createSessionStore(); 25 - const sessionData = await sessionStore.get(did); 26 - if (!sessionData) { 27 - throw new Error( 28 - `No session found for ${did} in ${configPath('bsky_session.json')}. Run \`vit login\` first to authenticate.`, 29 - ); 30 - } 31 - 32 - const client = createOAuthClient({ 33 - stateStore: { 34 - set: async () => {}, 35 - get: async () => undefined, 36 - del: async () => {}, 37 - }, 38 - sessionStore, 39 - redirectUri: 'http://127.0.0.1', 40 - }); 41 - 42 - const session = await client.restore(did); 43 - const agent = new Agent(session); 44 - const pds = (await session.getTokenInfo(false)).aud; 45 - 46 - const record = { 47 - $type: 'org.v-it.hello', 48 - message: opts.message, 49 - createdAt: new Date().toISOString(), 50 - }; 51 - 52 - const putArgs = { 53 - repo: did, 54 - collection: 'org.v-it.hello', 55 - rkey: 'self', 56 - record, 57 - validate: false, 58 - }; 59 - const putResult = await agent.com.atproto.repo.putRecord(putArgs); 60 - console.log( 61 - JSON.stringify({ 62 - ts: new Date().toISOString(), 63 - pds, 64 - xrpc: 'com.atproto.repo.putRecord', 65 - request: putArgs, 66 - response: putResult.data, 67 - }), 68 - ); 69 - 70 - const getArgs = { 71 - repo: did, 72 - collection: 'org.v-it.hello', 73 - rkey: 'self', 74 - }; 75 - const getResult = await agent.com.atproto.repo.getRecord(getArgs); 76 - console.log( 77 - JSON.stringify({ 78 - ts: new Date().toISOString(), 79 - pds, 80 - xrpc: 'com.atproto.repo.getRecord', 81 - request: getArgs, 82 - response: getResult.data, 83 - }), 84 - ); 85 - } catch (err) { 86 - console.error(err instanceof Error ? err.message : String(err)); 87 - process.exitCode = 1; 88 - } 89 - }); 90 - }
+62
src/cmd/ship.js
··· 1 + // SPDX-License-Identifier: AGPL-3.0-only 2 + // Copyright (c) 2026 sol pbc 3 + 4 + import { Agent } from '@atproto/api'; 5 + import { TID } from '@atproto/common-web'; 6 + import { loadEnv } from '../lib/env.js'; 7 + import { createOAuthClient, createSessionStore } from '../lib/oauth.js'; 8 + 9 + export default function register(program) { 10 + program 11 + .command('ship <text>') 12 + .description('Write a cap to the authenticated PDS') 13 + .option('--did <did>', 'DID to use (default: from .env)') 14 + .action(async (text, opts) => { 15 + try { 16 + const { BSKY_DID: envDid } = loadEnv(); 17 + const did = opts.did || envDid; 18 + 19 + const clientId = `http://localhost?redirect_uri=${encodeURIComponent('http://127.0.0.1')}&scope=${encodeURIComponent('atproto transition:generic')}`; 20 + const sessionStore = createSessionStore(); 21 + const client = createOAuthClient({ 22 + clientId, 23 + sessionStore, 24 + stateStore: { 25 + set: async () => {}, 26 + get: async () => undefined, 27 + del: async () => {}, 28 + }, 29 + redirectUri: 'http://127.0.0.1', 30 + }); 31 + const session = await client.restore(did); 32 + const agent = new Agent(session); 33 + 34 + const record = { 35 + $type: 'org.v-it.cap', 36 + text, 37 + createdAt: new Date().toISOString(), 38 + }; 39 + const rkey = TID.nextStr(); 40 + const putArgs = { 41 + repo: did, 42 + collection: 'org.v-it.cap', 43 + rkey, 44 + record, 45 + validate: false, 46 + }; 47 + const putRes = await agent.com.atproto.repo.putRecord(putArgs); 48 + console.log( 49 + JSON.stringify({ 50 + ts: new Date().toISOString(), 51 + pds: session.serverMetadata?.issuer, 52 + xrpc: 'com.atproto.repo.putRecord', 53 + request: putArgs, 54 + response: putRes.data, 55 + }), 56 + ); 57 + } catch (e) { 58 + console.error(e.message); 59 + process.exitCode = 1; 60 + } 61 + }); 62 + }
+55
src/cmd/skim.js
··· 1 + // SPDX-License-Identifier: AGPL-3.0-only 2 + // Copyright (c) 2026 sol pbc 3 + 4 + import { Agent } from '@atproto/api'; 5 + import { loadEnv } from '../lib/env.js'; 6 + import { createOAuthClient, createSessionStore } from '../lib/oauth.js'; 7 + 8 + export default function register(program) { 9 + program 10 + .command('skim') 11 + .description('List caps from the authenticated PDS') 12 + .option('--did <did>', 'DID to use (default: from .env)') 13 + .option('--limit <n>', 'Max records to return', '25') 14 + .action(async (opts) => { 15 + try { 16 + const { BSKY_DID: envDid } = loadEnv(); 17 + const did = opts.did || envDid; 18 + 19 + const clientId = `http://localhost?redirect_uri=${encodeURIComponent('http://127.0.0.1')}&scope=${encodeURIComponent('atproto transition:generic')}`; 20 + const sessionStore = createSessionStore(); 21 + const client = createOAuthClient({ 22 + clientId, 23 + sessionStore, 24 + stateStore: { 25 + set: async () => {}, 26 + get: async () => undefined, 27 + del: async () => {}, 28 + }, 29 + redirectUri: 'http://127.0.0.1', 30 + }); 31 + const session = await client.restore(did); 32 + const agent = new Agent(session); 33 + 34 + const listArgs = { 35 + repo: did, 36 + collection: 'org.v-it.cap', 37 + limit: parseInt(opts.limit, 10), 38 + }; 39 + const listRes = await agent.com.atproto.repo.listRecords(listArgs); 40 + for (const rec of listRes.data.records) { 41 + console.log( 42 + JSON.stringify({ 43 + ts: new Date().toISOString(), 44 + pds: session.serverMetadata?.issuer, 45 + xrpc: 'com.atproto.repo.listRecords', 46 + record: rec, 47 + }), 48 + ); 49 + } 50 + } catch (e) { 51 + console.error(e.message); 52 + process.exitCode = 1; 53 + } 54 + }); 55 + }
+2 -2
src/lib/oauth.js
··· 42 42 }; 43 43 } 44 44 45 - export function createOAuthClient({ stateStore, sessionStore, redirectUri }) { 45 + export function createOAuthClient({ stateStore, sessionStore, redirectUri, clientId }) { 46 46 return new NodeOAuthClient({ 47 47 requestLock, 48 48 clientMetadata: { 49 - client_id: 'https://v-it.org/client-metadata.json', 49 + client_id: clientId || 'https://v-it.org/client-metadata.json', 50 50 client_name: 'vit CLI', 51 51 application_type: 'native', 52 52 grant_types: ['authorization_code', 'refresh_token'],