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