toes/src/cli/setup.ts
Claude eb8ef0dd4d
Rename stats to metrics and add data size metric
Rename the "stats" CLI command, tool app, and all internal references
to "metrics". Add file size tracking from each app's DATA_DIR as a new
metric, shown in both the CLI table and web UI.

https://claude.ai/code/session_013agP8J1cCfrWZkueZ33jQB
2026-02-12 15:28:20 +00:00

310 lines
7.6 KiB
TypeScript

import { program } from 'commander'
import color from 'kleur'
import pkg from '../../package.json'
import { withPager } from './pager'
import {
cleanApp,
configShow,
cronList,
cronLog,
cronRun,
cronStatus,
diffApp,
envList,
envRm,
envSet,
getApp,
historyApp,
infoApp,
listApps,
logApp,
newApp,
openApp,
pullApp,
pushApp,
renameApp,
restartApp,
rmApp,
rollbackApp,
stashApp,
stashListApp,
stashPopApp,
startApp,
metricsApp,
statusApp,
stopApp,
syncApp,
versionsApp,
} from './commands'
program
.name('toes')
.version(`v${pkg.version}`, '-v, --version')
.addHelpText('beforeAll', (ctx) => {
if (ctx.command === program) {
return color.bold().cyan('🐾 Toes') + color.gray(' - personal web appliance\n')
}
return ''
})
.addHelpCommand(false)
.configureOutput({
writeOut: (str) => {
const colored = str
.replace(/^([A-Z][\w ]*:)/gm, color.yellow('$1'))
process.stdout.write(colored)
},
})
program
.command('version', { hidden: true })
.action(() => console.log(program.version()))
// Apps
program
.command('list')
.helpGroup('Apps:')
.description('List all apps')
.option('-t, --tools', 'show only tools')
.option('-a, --apps', 'show only apps (exclude tools)')
.action(listApps)
program
.command('info')
.helpGroup('Apps:')
.description('Show info for an app')
.argument('[name]', 'app name (uses current directory if omitted)')
.action(infoApp)
program
.command('new')
.helpGroup('Apps:')
.description('Create a new toes app')
.argument('[name]', 'app name (uses current directory if omitted)')
.option('--ssr', 'SSR template with pages directory (default)')
.option('--bare', 'minimal template with no pages')
.option('--spa', 'single-page app with client-side rendering')
.action(newApp)
program
.command('get')
.helpGroup('Apps:')
.description('Download an app from server')
.argument('<name>', 'app name')
.action(getApp)
program
.command('open')
.helpGroup('Apps:')
.description('Open an app in browser')
.argument('[name]', 'app name (uses current directory if omitted)')
.action(openApp)
program
.command('rename')
.helpGroup('Apps:')
.description('Rename an app')
.argument('[name]', 'app name (uses current directory if omitted)')
.argument('<new-name>', 'new app name')
.action(renameApp)
program
.command('rm')
.helpGroup('Apps:')
.description('Remove an app from the server')
.argument('[name]', 'app name (uses current directory if omitted)')
.action(rmApp)
// Lifecycle
program
.command('start')
.helpGroup('Lifecycle:')
.description('Start an app')
.argument('[name]', 'app name (uses current directory if omitted)')
.action(startApp)
program
.command('stop')
.helpGroup('Lifecycle:')
.description('Stop an app')
.argument('[name]', 'app name (uses current directory if omitted)')
.action(stopApp)
program
.command('restart')
.helpGroup('Lifecycle:')
.description('Restart an app')
.argument('[name]', 'app name (uses current directory if omitted)')
.action(restartApp)
program
.command('logs')
.helpGroup('Lifecycle:')
.description('Show logs for an app')
.argument('[name]', 'app name (uses current directory if omitted)')
.option('-f, --follow', 'follow log output')
.option('-d, --date <date>', 'show logs from a specific date (YYYY-MM-DD)')
.option('-s, --since <duration>', 'show logs since duration (e.g., 1h, 2d)')
.option('-g, --grep <pattern>', 'filter logs by pattern')
.action(logApp)
program
.command('log', { hidden: true })
.argument('[name]', 'app name (uses current directory if omitted)')
.option('-f, --follow', 'follow log output')
.option('-d, --date <date>', 'show logs from a specific date (YYYY-MM-DD)')
.option('-s, --since <duration>', 'show logs since duration (e.g., 1h, 2d)')
.option('-g, --grep <pattern>', 'filter logs by pattern')
.action(logApp)
program
.command('metrics')
.helpGroup('Lifecycle:')
.description('Show CPU, memory, and disk metrics for apps')
.argument('[name]', 'app name (uses current directory if omitted)')
.action(metricsApp)
const cron = program
.command('cron')
.helpGroup('Lifecycle:')
.description('Manage cron jobs')
.argument('[app]', 'app name (list jobs for specific app)')
.action(cronList)
cron
.command('log')
.description('Show cron job logs')
.argument('[target]', 'app name or job (app:name)')
.option('-f, --follow', 'follow log output')
.action(cronLog)
cron
.command('status')
.description('Show detailed status for a job')
.argument('<job>', 'job identifier (app:name)')
.action(cronStatus)
cron
.command('run')
.description('Run a job immediately')
.argument('<job>', 'job identifier (app:name)')
.action(cronRun)
// Sync
program
.command('push')
.helpGroup('Sync:')
.description('Push local changes to server')
.option('-f, --force', 'overwrite remote changes')
.action(pushApp)
program
.command('pull')
.helpGroup('Sync:')
.description('Pull changes from server')
.option('-f, --force', 'overwrite local changes')
.action(pullApp)
program
.command('status')
.helpGroup('Sync:')
.description('Show what would be pushed/pulled')
.action(statusApp)
program
.command('diff')
.helpGroup('Sync:')
.description('Show diff of changed files')
.action(() => withPager(diffApp))
program
.command('sync')
.helpGroup('Sync:')
.description('Watch and sync changes bidirectionally')
.action(syncApp)
program
.command('clean')
.helpGroup('Sync:')
.description('Remove local files not on server')
.option('-f, --force', 'skip confirmation')
.option('-n, --dry-run', 'show what would be removed')
.action(cleanApp)
const stash = program
.command('stash')
.helpGroup('Sync:')
.description('Stash local changes')
.action(stashApp)
stash
.command('pop')
.description('Restore stashed changes')
.action(stashPopApp)
stash
.command('list')
.description('List all stashes')
.action(stashListApp)
// Config
program
.command('config')
.helpGroup('Config:')
.description('Show current host configuration')
.action(configShow)
const env = program
.command('env')
.helpGroup('Config:')
.description('Manage environment variables')
.argument('[name]', 'app name (uses current directory if omitted)')
.option('-g, --global', 'manage global variables shared by all apps')
.action(envList)
env
.command('set')
.description('Set an environment variable')
.argument('[name]', 'app name (uses current directory if omitted)')
.argument('<key>', 'variable name')
.argument('[value]', 'variable value (or use KEY=value format)')
.option('-g, --global', 'set a global variable shared by all apps')
.action(envSet)
env
.command('rm')
.description('Remove an environment variable')
.argument('[name]', 'app name (uses current directory if omitted)')
.argument('<key>', 'variable name to remove')
.option('-g, --global', 'remove a global variable')
.action(envRm)
program
.command('versions')
.helpGroup('Config:')
.description('List deployed versions')
.argument('[name]', 'app name (uses current directory if omitted)')
.action(versionsApp)
program
.command('history')
.helpGroup('Config:')
.description('Show file changes between versions')
.argument('[name]', 'app name (uses current directory if omitted)')
.action(historyApp)
program
.command('rollback')
.helpGroup('Config:')
.description('Rollback to a previous version')
.argument('[name]', 'app name (uses current directory if omitted)')
.option('-v, --version <version>', 'version to rollback to (prompts if omitted)')
.action((name, options) => rollbackApp(name, options.version))
export { program }