Something went wrong. Try again.
A basic CLI to set up Tangled repos
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149import { Command } from 'commander';import { createRequire } from 'node:module';import { loginCommand } from './commands/login.js';import { logoutCommand } from './commands/logout.js';import { refreshCommand } from './commands/refresh.js';import { keepaliveCommand } from './commands/keepalive.js';import { clientSetupCommand, clientShowCommand, clientClearCommand,} from './commands/client.js';import { configSetCommand, configGetCommand } from './commands/config.js';import { userListCommand, userDefaultCommand } from './commands/user.js';import { repoCreateCommand } from './commands/repo.js';import { moveCommand } from './commands/move.js';import { copyCommand } from './commands/copy.js';
const { version } = createRequire(import.meta.url)('../package.json');
// Every command that acts as an account takes the same `-u` flag.const USER_FLAG = [ '-u, --user <user>', 'user to act as (handle or DID); defaults to the default user',];
export function buildProgram() { const program = new Command(); program .name('tangy') .description('A small CLI to set up and manage Tangled repos over ATProto.') .version(version);
program .command('login') .description('Log in an ATProto account via the browser (other logins are kept)') .argument('[handle]', 'your handle, e.g. alice.tngl.sh') .option('-u, --user <handle>', 'same as the handle argument') .action(loginCommand);
program .command('logout') .description('Log out one user and forget their stored credentials') .option(...USER_FLAG) .action(logoutCommand);
program .command('refresh') .description('Refresh stored sessions so they stay logged in (see `tangy keepalive`)') .option('-u, --user <user>', 'only refresh this user') .action(refreshCommand);
program .command('keepalive') .description('Refresh sessions twice daily in the background (launchd)') .argument('[action]', 'on | off | status', 'status') .action(keepaliveCommand);
const client = program .command('client') .description("Manage tangy's OAuth client identity (loopback or confidential)"); client .command('setup') .description('Become a confidential client, for sessions that can be kept alive for years') .argument('<base-url>', 'https directory URL the client files will be hosted under') .option('-o, --out <dir>', 'where to write the two files to host', 'tangy-client') .action(clientSetupCommand); client .command('show') .description('Show which client identity tangy runs as') .action(clientShowCommand); client .command('clear') .description('Forget the confidential client and go back to the loopback default') .action(clientClearCommand);
const user = program.command('user').description('Manage logged-in users'); user .command('list') .description('List logged-in users (* marks the default)') .action(userListCommand); user .command('default') .description('Show or set the user commands act as when -u is not given') .argument('[user]', 'handle or DID') .option('-u, --user <user>', 'same as the user argument') .action(userDefaultCommand);
// `configuration` is the canonical name; `config` is a friendly alias. const configuration = program .command('configuration') .alias('config') .description("Read and write a user's tangy settings"); configuration .command('set') .description('Set a setting (e.g. root, knot) for a user') .argument('<key>', 'setting name: root | knot | appview | api') .argument('<value>', 'setting value') .option(...USER_FLAG) .action(configSetCommand); configuration .command('get') .description("Show one or all of a user's settings") .argument('[key]', 'setting name') .option(...USER_FLAG) .action(configGetCommand);
const repo = program.command('repo').description('Work with repositories'); repo .command('create') .description('Create a repo locally and on Tangled') .argument('<name>', 'repository name') .option('-d, --description <text>', 'short repo description (max 140 chars)') .option('--no-push', 'do not push the initial commit after creating') .option(...USER_FLAG) .action(repoCreateCommand);
program .command('copy') .description('Copy open GitHub issues and PRs (comments, labels, reactions) into a Tangled repo') .argument('<github>', 'GitHub repo: owner/name or a github.com URL') .argument('[repo]', "target Tangled repo name (defaults to the GitHub repo's name)") .option('--no-issues', 'do not copy issues') .option('--no-pulls', 'do not copy pull requests') .option('--dry-run', 'show what would be copied without writing anything') .option(...USER_FLAG) .action(copyCommand);
program .command('move') .description('Move an existing git repo into the tangy root and onto Tangled') .argument('<dir>', 'path to an existing local git repository') .option('-d, --description <text>', 'short repo description (max 140 chars)') .option('--no-push', 'do not push after moving') .option(...USER_FLAG) .action(moveCommand);
return program;}
export async function main(argv) { const program = buildProgram(); try { await program.parseAsync(argv); } catch (err) { console.error(`\nError: ${err.message}`); process.exitCode = 1; }}