open source is social v-it.org
0
fork

Configure Feed

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

at main 131 lines 5.0 kB view raw
1// SPDX-License-Identifier: MIT 2// Copyright (c) 2026 sol pbc 3 4import { DEFAULT_EXPLORE_URL } from '../lib/constants.js'; 5import { readBeaconSet } from '../lib/vit-dir.js'; 6import { brand } from '../lib/brand.js'; 7import { jsonOk, jsonError } from '../lib/json-output.js'; 8import { errorMessage, formatError } from '../lib/error-format.js'; 9 10function timeAgo(isoString) { 11 const seconds = Math.floor((Date.now() - new Date(isoString).getTime()) / 1000); 12 if (seconds < 60) return `${seconds}s ago`; 13 const minutes = Math.floor(seconds / 60); 14 if (minutes < 60) return `${minutes}m ago`; 15 const hours = Math.floor(minutes / 60); 16 if (hours < 24) return `${hours}h ago`; 17 const days = Math.floor(hours / 24); 18 return `${days}d ago`; 19} 20 21function resolveUrl(opts) { 22 return opts.exploreUrl || process.env.VIT_EXPLORE_URL || DEFAULT_EXPLORE_URL; 23} 24 25function unavailableMessage(baseUrl) { 26 try { 27 return `${new URL(baseUrl).host} is unavailable. try 'vit explore caps --beacon .' for network-wide discovery.`; 28 } catch { 29 return `${baseUrl} is unavailable. try 'vit explore caps --beacon .' for network-wide discovery.`; 30 } 31} 32 33export default function register(program) { 34 program 35 .command('inbox') 36 .description('Show caps addressed to your project beacon (project-centric view)') 37 .option('--kind <kind>', 'Filter by cap kind (e.g. request)') 38 .option('--sort <sort>', 'Sort order: recent (default) or want-vouches', 'recent') 39 .option('--limit <n>', 'Limit number of caps') 40 .option('--json', 'Output as JSON') 41 .option('--explore-url <url>', 'Explore API base URL') 42 .action(async (opts) => { 43 try { 44 const beaconSet = readBeaconSet(); 45 if (beaconSet.size === 0) { 46 const msg = "no beacon set — run 'vit init' first (inbox requires a project beacon)"; 47 if (opts.json) { 48 jsonError(msg); 49 return; 50 } 51 console.error(msg); 52 process.exitCode = 1; 53 return; 54 } 55 56 const beacon = [...beaconSet].join(','); 57 const baseUrl = resolveUrl(opts); 58 59 let data; 60 try { 61 const url = new URL('/api/caps', baseUrl); 62 url.searchParams.set('beacon', beacon); 63 if (opts.kind) url.searchParams.set('kind', opts.kind); 64 if (opts.sort === 'want-vouches') url.searchParams.set('sort', 'want-vouches'); 65 if (opts.limit) url.searchParams.set('limit', opts.limit); 66 67 const res = await fetch(url); 68 if (!res.ok) throw new Error(`explore API returned ${res.status}`); 69 data = await res.json(); 70 } catch (err) { 71 const msg = errorMessage(err); 72 const finalMsg = msg.startsWith('explore API returned ') 73 ? msg 74 : unavailableMessage(baseUrl); 75 if (opts.json) { 76 jsonError(finalMsg); 77 return; 78 } 79 console.error(finalMsg); 80 console.error("fallback: try 'vit explore caps --beacon .' to query the explore index directly."); 81 process.exitCode = 1; 82 return; 83 } 84 85 if (opts.json) { 86 jsonOk({ caps: data.caps || [], cursor: data.cursor || null }); 87 return; 88 } 89 90 const caps = data.caps || []; 91 const beaconDisplay = [...beaconSet][0]; 92 93 console.log(`${brand} inbox — ${beaconDisplay}`); 94 console.log(''); 95 96 if (caps.length === 0) { 97 const kindFilter = opts.kind ? ` (kind: ${opts.kind})` : ''; 98 console.log(`no caps found${kindFilter}.`); 99 if (opts.kind) { 100 console.log(`hint: to request a feature, run 'vit ship --kind request --beacon <url>'`); 101 } 102 return; 103 } 104 105 for (const cap of caps) { 106 const wantCount = cap.want_vouch_count ?? 0; 107 const wantStr = wantCount === 1 ? '1 want' : `${wantCount} wants`; 108 const age = cap.created_at ? timeAgo(cap.created_at) : ''; 109 const handle = cap.handle ? `@${cap.handle}` : cap.did || 'unknown'; 110 const kind = cap.kind || (cap.record_json ? (() => { try { return JSON.parse(cap.record_json).kind || ''; } catch { return ''; } })() : ''); 111 112 console.log(` ${cap.ref} ${handle} ${wantStr} ${age}`); 113 if (cap.title) console.log(` ${cap.title}${kind ? ` [${kind}]` : ''}`); 114 if (cap.description) console.log(` ${cap.description}`); 115 console.log(''); 116 } 117 118 const kindNote = opts.kind ? ` ${opts.kind}` : ''; 119 console.log(`${caps.length} open${kindNote} cap${caps.length === 1 ? '' : 's'}`); 120 console.log(`tip: 'vit vouch <ref> --kind want' to signal demand`); 121 console.log(` 'vit ship --recap <ref>' to ship an implementation`); 122 } catch (err) { 123 if (opts.json) { 124 jsonError(err); 125 return; 126 } 127 console.error(formatError(err, { verbose: false })); 128 process.exitCode = 1; 129 } 130 }); 131}