open source is social v-it.org
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 sol pbc
3
4import { existsSync } from 'node:fs';
5import { resolve } from 'node:path';
6import { execFileSync } from 'node:child_process';
7import { parseGitUrl, toBeacon, beaconToHttps } from '../lib/beacon.js';
8import { requireNotAgent } from '../lib/agent.js';
9import { which } from '../lib/compat.js';
10import { mark, name } from '../lib/brand.js';
11import { formatError } from '../lib/error-format.js';
12
13export default function register(program) {
14 program
15 .command('adopt')
16 .argument('<beacon>', 'Beacon URI, git URL, or slug to adopt (e.g. vit:github.com/org/repo)')
17 .argument('[name]', 'Local directory name (defaults to repo name)')
18 .description('Fork or clone a project')
19 .option('-v, --verbose', 'Show step-by-step details')
20 .action(async (beacon, targetName, opts) => {
21 try {
22 const gate = requireNotAgent();
23 if (!gate.ok) {
24 console.error(`${name} adopt must be run by a human. run it in your own terminal.`);
25 process.exitCode = 1;
26 return;
27 }
28
29 const { verbose } = opts;
30
31 // resolve beacon
32 if (verbose) console.log(`[verbose] resolving beacon: ${beacon}`);
33 const httpsUrl = beaconToHttps(beacon);
34 const parsed = parseGitUrl(httpsUrl);
35 const beaconUri = 'vit:' + toBeacon(httpsUrl);
36 if (verbose) console.log(`[verbose] beacon: ${beaconUri}`);
37 if (verbose) console.log(`[verbose] https: ${httpsUrl}`);
38
39 // determine directory name
40 const dirName = targetName || parsed.repo;
41 const dirPath = resolve(dirName);
42 if (verbose) console.log(`[verbose] target directory: ${dirPath}`);
43
44 // fail fast if directory exists
45 if (existsSync(dirPath)) {
46 console.error(`Directory already exists: ${dirName}`);
47 process.exitCode = 1;
48 return;
49 }
50
51 // detect gh + github host
52 const ghPath = which('gh');
53 const isGitHub = parsed.host === 'github.com';
54 if (verbose) console.log(`[verbose] gh available: ${ghPath ? 'yes' : 'no'}, github host: ${isGitHub ? 'yes' : 'no'}`);
55
56 if (ghPath && isGitHub) {
57 if (verbose) console.log(`[verbose] gh found at ${ghPath}, forking via gh`);
58 try {
59 execFileSync('gh', ['repo', 'fork', httpsUrl, '--clone', '--', dirName], {
60 encoding: 'utf-8',
61 stdio: ['pipe', 'pipe', 'pipe'],
62 });
63 } catch (err) {
64 console.error(`Fork failed: ${(err.stderr || err.message || '').trim()}`);
65 process.exitCode = 1;
66 return;
67 }
68 } else {
69 if (verbose) console.log(`[verbose] cloning via git`);
70 try {
71 execFileSync('git', ['clone', httpsUrl, dirName], {
72 encoding: 'utf-8',
73 stdio: ['pipe', 'pipe', 'pipe'],
74 });
75 } catch (err) {
76 console.error(`Clone failed: ${(err.stderr || err.message || '').trim()}`);
77 process.exitCode = 1;
78 return;
79 }
80 }
81
82 // success output
83 console.log(`${mark} beacon: ${beaconUri}`);
84 console.log(`${mark} directory: ${dirName}`);
85 console.log(`run: cd ${dirName}`);
86 console.log('');
87 console.log(`next: start your agent and ask it to run '${name} init'`);
88 } catch (err) {
89 console.error(formatError(err, { verbose: opts.verbose }));
90 process.exitCode = 1;
91 }
92 });
93}