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