import { 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 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 ', '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 ', '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('', 'https directory URL the client files will be hosted under') .option('-o, --out ', '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 ', '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('', 'setting name: root | knot | appview | api') .argument('', '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('', 'repository name') .option('-d, --description ', '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 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('', 'path to an existing local git repository') .option('-d, --description ', '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; } }