open source is social v-it.org
at main 146 lines 4.9 kB view raw
1// SPDX-License-Identifier: MIT 2// Copyright (c) 2026 sol pbc 3 4import { requireDid, loadConfig } from '../lib/config.js'; 5import { restoreAgent } from '../lib/oauth.js'; 6import { readFollowing, writeFollowing } from '../lib/vit-dir.js'; 7import { mark } from '../lib/brand.js'; 8import { jsonOk, jsonError } from '../lib/json-output.js'; 9 10export default function register(program) { 11 program 12 .command('follow') 13 .argument('<handle>', 'Handle to follow (e.g. alice.bsky.social)') 14 .description('Add an account to this project\'s following list') 15 .option('-v, --verbose', 'Show step-by-step details') 16 .option('--json', 'Output as JSON') 17 .option('--did <did>', 'DID to use for handle resolution') 18 .action(async (handle, opts) => { 19 try { 20 const { verbose } = opts; 21 const vlog = opts.json ? (...a) => console.error(...a) : console.log; 22 handle = handle.replace(/^@/, ''); 23 24 if (opts.json && !(opts.did || loadConfig().did)) { 25 jsonError('no DID configured', "run 'vit login <handle>' first"); 26 return; 27 } 28 const did = requireDid(opts); 29 if (!did) return; 30 if (verbose) vlog(`[verbose] DID: ${did}`); 31 32 const list = readFollowing(); 33 if (list.some(e => e.handle === handle)) { 34 if (opts.json) { 35 jsonError(`already following ${handle}`); 36 return; 37 } 38 console.error(`already following ${handle}`); 39 process.exitCode = 1; 40 return; 41 } 42 43 const { agent } = await restoreAgent(did); 44 if (verbose) vlog('[verbose] session restored'); 45 46 const resolved = await agent.resolveHandle({ handle }); 47 const targetDid = resolved.data.did; 48 if (verbose) vlog(`[verbose] resolved ${handle} to ${targetDid}`); 49 50 list.push({ handle, did: targetDid, followedAt: new Date().toISOString() }); 51 writeFollowing(list); 52 if (opts.json) { 53 jsonOk({ handle, did: targetDid, followedAt: list[list.length - 1].followedAt }); 54 return; 55 } 56 console.log(`${mark} following ${handle} (${targetDid})`); 57 } catch (err) { 58 const msg = err instanceof Error ? err.message : String(err); 59 if (opts.json) { 60 jsonError(msg); 61 return; 62 } 63 console.error(msg); 64 process.exitCode = 1; 65 } 66 }); 67 68 program 69 .command('unfollow') 70 .argument('<handle>', 'Handle to unfollow (e.g. alice.bsky.social)') 71 .description('Remove an account from this project\'s following list') 72 .option('-v, --verbose', 'Show step-by-step details') 73 .option('--json', 'Output as JSON') 74 .action(async (handle, opts) => { 75 try { 76 const { verbose } = opts; 77 const vlog = opts.json ? (...a) => console.error(...a) : console.log; 78 handle = handle.replace(/^@/, ''); 79 80 const list = readFollowing(); 81 const filtered = list.filter(e => e.handle !== handle); 82 if (filtered.length === list.length) { 83 if (opts.json) { 84 jsonError(`not following ${handle}`); 85 return; 86 } 87 console.error(`not following ${handle}`); 88 process.exitCode = 1; 89 return; 90 } 91 92 writeFollowing(filtered); 93 if (verbose) vlog(`[verbose] removed ${handle} from following list`); 94 if (opts.json) { 95 jsonOk({ handle }); 96 return; 97 } 98 console.log(`${mark} unfollowed ${handle}`); 99 } catch (err) { 100 const msg = err instanceof Error ? err.message : String(err); 101 if (opts.json) { 102 jsonError(msg); 103 return; 104 } 105 console.error(msg); 106 process.exitCode = 1; 107 } 108 }); 109 110 program 111 .command('following') 112 .description('List accounts in this project\'s following list') 113 .option('-v, --verbose', 'Show step-by-step details') 114 .option('--json', 'Output as JSON') 115 .action(async (opts) => { 116 try { 117 const list = readFollowing(); 118 if (list.length === 0) { 119 if (opts.json) { 120 jsonOk({ following: [], hint: "run 'vit follow <handle>' to add accounts" }); 121 return; 122 } 123 console.log('not following anyone yet.'); 124 console.log(''); 125 console.log("run 'vit follow <handle>' to start seeing what people are shipping."); 126 console.log("try 'vit scan' to discover active publishers on the network."); 127 return; 128 } 129 if (opts.json) { 130 jsonOk({ following: list }); 131 return; 132 } 133 for (const e of list) { 134 console.log(`${e.handle} (${e.did})`); 135 } 136 } catch (err) { 137 const msg = err instanceof Error ? err.message : String(err); 138 if (opts.json) { 139 jsonError(msg); 140 return; 141 } 142 console.error(msg); 143 process.exitCode = 1; 144 } 145 }); 146}