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 246 lines 9.5 kB view raw
1// SPDX-License-Identifier: MIT 2// Copyright (c) 2026 sol pbc 3 4import { existsSync, writeFileSync } from 'node:fs'; 5import { execSync } from 'node:child_process'; 6import { join } from 'node:path'; 7import { toBeacon } from '../lib/beacon.js'; 8import { vitDir, readProjectConfig, writeProjectConfig } from '../lib/vit-dir.js'; 9import { requireAgent } from '../lib/agent.js'; 10import { mark, name, DOT_VIT_README } from '../lib/brand.js'; 11import { jsonOk, jsonError } from '../lib/json-output.js'; 12import { errorMessage, formatError } from '../lib/error-format.js'; 13 14export default function register(program) { 15 program 16 .command('init') 17 .description('Initialize .vit directory and set project beacon. Use the most official upstream or well-known git URL so all contributors converge on the same beacon.') 18 .option('--beacon <url>', 'Git URL (or "." to read from git remote upstream/origin) to derive the beacon URI') 19 .option('--secondary <url>', 'Secondary beacon URL for upstream cap discovery') 20 .option('--json', 'Output as JSON') 21 .option('-v, --verbose', 'Show step-by-step details') 22 .action(async (opts) => { 23 try { 24 const gate = requireAgent(); 25 if (!gate.ok) { 26 if (opts.json) { 27 jsonError('agent required', 'run vit init from a coding agent'); 28 return; 29 } 30 console.error(`${name} init should be run by a coding agent (e.g. claude code, gemini cli).`); 31 console.error(`open your agent and ask it to run '${name} init' for you.`); 32 process.exitCode = 1; 33 return; 34 } 35 36 const { verbose } = opts; 37 const vlog = opts.json ? (...a) => console.error(...a) : console.log; 38 const dir = vitDir(); 39 if (verbose) vlog(`[verbose] .vit dir: ${dir}`); 40 41 if (!opts.beacon && !opts.secondary) { 42 const config = readProjectConfig(); 43 if (config.beacon) { 44 if (opts.json) { 45 const out = { beacon: config.beacon }; 46 if (config.secondaryBeacon) out.secondaryBeacon = config.secondaryBeacon; 47 jsonOk(out); 48 return; 49 } 50 console.log(`${mark} beacon: ${config.beacon}`); 51 if (config.secondaryBeacon) { 52 console.log(`${mark} secondary beacon: ${config.secondaryBeacon}`); 53 } 54 console.log(`hint: to change the beacon, run: ${name} init --beacon <git-url>`); 55 return; 56 } 57 58 let isGitRepo = false; 59 try { 60 execSync('git rev-parse --is-inside-work-tree', { 61 encoding: 'utf-8', 62 stdio: ['pipe', 'pipe', 'pipe'], 63 }); 64 isGitRepo = true; 65 } catch {} 66 if (verbose) vlog(`[verbose] in git repo: ${isGitRepo ? 'yes' : 'no'}`); 67 68 const hasVitDir = existsSync(dir); 69 if (!isGitRepo) { 70 let remotes = []; 71 if (opts.json) { 72 jsonOk({ 73 status: hasVitDir ? 'no beacon' : 'not initialized', 74 git: false, 75 remotes, 76 }); 77 return; 78 } 79 console.log(hasVitDir ? 'status: no beacon' : 'status: not initialized'); 80 console.log('git: false'); 81 if (hasVitDir) { 82 console.log(`hint: run: ${name} init --beacon <canonical-git-url>`); 83 } else { 84 console.log(`hint: navigate to a git repository and run '${name} init' there.`); 85 console.log(` to start fresh: git init && git remote add origin <your-repo-url>`); 86 } 87 return; 88 } 89 90 console.log(hasVitDir ? 'status: no beacon' : 'status: not initialized'); 91 console.log('git: true'); 92 93 let remoteNames = []; 94 try { 95 remoteNames = execSync('git remote', { 96 encoding: 'utf-8', 97 stdio: ['pipe', 'pipe', 'pipe'], 98 }) 99 .trim() 100 .split('\n') 101 .filter(Boolean); 102 } catch (err) { 103 console.warn(`warning: failed to list git remotes: ${errorMessage(err)}`); 104 remoteNames = []; 105 } 106 if (verbose) vlog(`[verbose] remotes detected: ${remoteNames.length > 0 ? remoteNames.join(', ') : 'none'}`); 107 108 const remotes = []; 109 for (const name of remoteNames) { 110 try { 111 const url = execSync(`git config --get remote.${name}.url`, { 112 encoding: 'utf-8', 113 stdio: ['pipe', 'pipe', 'pipe'], 114 }).trim(); 115 if (url) remotes.push({ name, url }); 116 } catch (err) { 117 console.warn(`warning: failed to read git remote ${name} url: ${errorMessage(err)}`); 118 } 119 } 120 if (verbose && remotes.length > 0) { 121 vlog(`[verbose] remote urls: ${remotes.map(r => `${r.name}=${r.url}`).join(' ')}`); 122 } 123 124 if (opts.json) { 125 jsonOk({ 126 status: hasVitDir ? 'no beacon' : 'not initialized', 127 git: true, 128 remotes: remotes.map(r => ({ name: r.name, url: r.url })), 129 }); 130 return; 131 } 132 133 const remotesDisplay = remotes.length > 0 134 ? remotes.map(remote => `${remote.name}=${remote.url}`).join(' ') 135 : 'none'; 136 console.log(`remotes: ${remotesDisplay}`); 137 138 const upstream = remotes.find(remote => remote.name === 'upstream'); 139 const origin = remotes.find(remote => remote.name === 'origin'); 140 if (upstream) { 141 console.log('hint: detected upstream remote. upstream points to the canonical repo.'); 142 console.log(`hint: run: ${name} init --beacon ${upstream.url}`); 143 } else if (origin) { 144 console.log(`hint: run: ${name} init --beacon ${origin.url}`); 145 } else { 146 console.log(`hint: no git remotes found. run: ${name} init --beacon <canonical-git-url>`); 147 } 148 return; 149 } 150 151 if (opts.secondary && !opts.beacon) { 152 const config = readProjectConfig(); 153 if (!config.beacon) { 154 if (opts.json) { 155 jsonError("no primary beacon set — run 'vit init --beacon <url>' first"); 156 return; 157 } 158 console.error("no primary beacon set — run 'vit init --beacon <url>' first"); 159 process.exitCode = 1; 160 return; 161 } 162 163 const secondary = 'vit:' + toBeacon(opts.secondary); 164 const merged = { ...config, secondaryBeacon: secondary }; 165 writeProjectConfig(merged); 166 if (opts.json) { 167 jsonOk({ beacon: merged.beacon, secondaryBeacon: merged.secondaryBeacon }); 168 return; 169 } 170 console.log(`${mark} beacon: ${merged.beacon}`); 171 console.log(`${mark} secondary beacon: ${merged.secondaryBeacon}`); 172 return; 173 } 174 175 let gitUrl = opts.beacon; 176 if (gitUrl === '.') { 177 if (verbose) vlog('[verbose] resolving --beacon . via remote.upstream.url then remote.origin.url'); 178 let usedRemote = ''; 179 try { 180 gitUrl = execSync('git config --get remote.upstream.url', { 181 encoding: 'utf-8', 182 stdio: ['pipe', 'pipe', 'pipe'], 183 }).trim(); 184 if (gitUrl) usedRemote = 'upstream'; 185 } catch { 186 gitUrl = ''; 187 } 188 189 if (!gitUrl) { 190 try { 191 gitUrl = execSync('git config --get remote.origin.url', { 192 encoding: 'utf-8', 193 stdio: ['pipe', 'pipe', 'pipe'], 194 }).trim(); 195 if (gitUrl) usedRemote = 'origin'; 196 } catch { 197 gitUrl = ''; 198 } 199 } 200 201 if (!gitUrl) { 202 if (opts.json) { 203 jsonError('no git remote found', 'set a remote or provide a git URL directly'); 204 return; 205 } 206 console.error('No git remote found. Set a remote or provide a git URL directly.'); 207 process.exitCode = 1; 208 return; 209 } 210 if (verbose) vlog(`[verbose] Read git remote ${usedRemote}: ${gitUrl}`); 211 } 212 213 const beacon = 'vit:' + toBeacon(gitUrl); 214 if (verbose) vlog(`[verbose] Computed beacon: ${beacon}`); 215 const existing = readProjectConfig(); 216 const merged = { ...existing, beacon }; 217 if (opts.secondary) { 218 merged.secondaryBeacon = 'vit:' + toBeacon(opts.secondary); 219 } 220 writeProjectConfig(merged); 221 if (verbose) vlog(`[verbose] Wrote config.json`); 222 const readmePath = join(vitDir(), 'README.md'); 223 if (!existsSync(readmePath)) { 224 writeFileSync(readmePath, DOT_VIT_README); 225 if (verbose) vlog(`[verbose] Wrote .vit/README.md`); 226 } 227 if (opts.json) { 228 const out = { beacon: merged.beacon }; 229 if (merged.secondaryBeacon) out.secondaryBeacon = merged.secondaryBeacon; 230 jsonOk(out); 231 return; 232 } 233 console.log(`${mark} beacon: ${beacon}`); 234 if (merged.secondaryBeacon) { 235 console.log(`${mark} secondary beacon: ${merged.secondaryBeacon}`); 236 } 237 } catch (err) { 238 if (opts.json) { 239 jsonError(err); 240 return; 241 } 242 console.error(formatError(err, { verbose: opts.verbose })); 243 process.exitCode = 1; 244 } 245 }); 246}