#!/usr/bin/env node /** * Hunt Town CLI — AI-friendly tools for the first onchain cooperative */ import { Command } from 'commander'; import { config } from 'dotenv'; import { resolve } from 'path'; import { homedir } from 'os'; import { projectsCommand } from './commands/projects.js'; import { projectCommand } from './commands/project.js'; import { updatesCommand } from './commands/updates.js'; import { statsCommand } from './commands/stats.js'; import { leaderboardCommand } from './commands/leaderboard.js'; import { walletCommand } from './commands/wallet.js'; import { postUpdateCommand } from './commands/post-update.js'; import { voteCommand } from './commands/vote.js'; import { claimableCommand } from './commands/claimable.js'; import { claimCommand } from './commands/claim.js'; import { royaltyCommand } from './commands/royalty.js'; import { claimRoyaltyCommand } from './commands/claim-royalty.js'; import { createProjectCommand } from './commands/create-project.js'; import { topVotedCommand } from './commands/top-voted.js'; import { zapMintCommand } from './commands/zap-mint.js'; // Load env from ~/.hunttown/.env and local .env config({ path: resolve(homedir(), '.hunttown', '.env'), quiet: true }); config({ quiet: true }); /** Clean error messages for user-facing output */ function cleanError(e: unknown): string { if (!(e instanceof Error)) return String(e); const msg = e.message; const details = msg.match(/Details:\s*(.+?)(?:\n|$)/); if (details) return details[1].trim(); const revert = msg.match(/execution reverted[:\s]*(.+?)(?:\n|$)/); if (revert) return `Transaction reverted: ${revert[1].trim()}`; return msg.split('\n').find(l => l.trim().length > 0)?.trim() ?? msg; } /** Wrap async command handlers with error handling */ function run(fn: () => Promise) { return async () => { try { await fn(); process.exit(0); } catch (e) { console.error('❌', cleanError(e)); process.exit(1); } }; } const cli = new Command() .name('ht') .description('Hunt Town CLI — tools for the first onchain cooperative on Base') .version('0.1.0'); cli .command('projects') .description('List all Co-op projects') .action(run(projectsCommand)); cli .command('project') .description('Show detailed project information') .argument('', 'Project token symbol (e.g. ONCHAT, H1)') .option('--votes', 'Include voting stats (today/week/month)') .action((symbol, opts) => run(() => projectCommand(symbol, opts))()); cli .command('updates') .description('Show recent project updates') .option('-p, --project ', 'Filter by project symbol') .action((opts) => run(() => updatesCommand(opts))()); cli .command('stats') .description('Show Co-op overview statistics') .action(run(statsCommand)); cli .command('leaderboard') .description('Top projects by HUNT reserve (TVL)') .option('-n, --limit ', 'Number of projects to show', '20') .action((opts) => run(() => leaderboardCommand(opts))()); cli .command('top-voted') .description('Top voted projects by on-chain voting activity') .option('-p, --period ', 'Time period: today, week, or month', 'today') .option('-n, --limit ', 'Number of projects to show', '20') .option('-v, --verbose', 'Stream raw voting logs in real-time') .action((opts) => run(() => topVotedCommand(opts))()); cli .command('wallet') .description('Show wallet address and balances') .action(run(walletCommand)); cli .command('post-update') .description('Post a project update (burns HUNT)') .argument('', 'Project token symbol') .argument('', 'URL link for the update') .action((symbol, link) => run(() => postUpdateCommand(symbol, link))()); cli .command('vote') .description('Vote on a Co-op project') .argument('', 'Project token symbol (e.g. ONCHAT, H1)') .argument('', 'Number of voting points to cast') .action((symbol, amount) => run(() => voteCommand(symbol, amount))()); cli .command('claimable') .description('Check claimable HUNT from voting rewards') .option('-p, --project ', 'Check specific project only') .action((opts) => run(() => claimableCommand(opts))()); cli .command('claim') .description('Claim HUNT tokens from voting rewards') .argument('', 'Project token symbol') .option('--tokens ', 'Amount of project tokens to mint (optional)') .option('--donation ', 'Donation percentage in basis points (optional, 0-10000)') .action((symbol, opts) => run(() => claimCommand(symbol, opts))()); cli .command('royalty') .description('Check accumulated HUNT royalties') .action(run(royaltyCommand)); cli .command('claim-royalty') .description('Claim accumulated HUNT royalties') .action(run(claimRoyaltyCommand)); cli .command('create-project') .description('Create a new Co-op project with auto-generated bonding curve') .requiredOption('--name ', 'Project name') .requiredOption('--symbol ', 'Token symbol (max 11 chars)') .option('--max-supply ', 'Maximum token supply (default: 100000000)') .option('--mint-royalty ', 'Mint royalty in basis points (default: 100 = 1%)') .option('--burn-royalty ', 'Burn royalty in basis points (default: 100 = 1%)') .option('--preset ', 'Curve preset: small ($1K FDV), medium ($5K), large ($30K)') .option('--fdv ', 'Custom initial FDV target in USD') .option('--steps ', 'Manual price steps JSON (overrides preset/fdv)') .action((opts) => run(() => createProjectCommand(opts))()); cli .command('zap-mint') .description('Buy project tokens with ETH or USDC') .argument('', 'Project token symbol') .argument('', 'Amount of project tokens to mint') .option('--from ', 'Source token: eth or usdc (default: eth)') .option('--slippage ', 'Slippage tolerance percentage (default: 1.0)') .action((symbol, amount, opts) => run(() => zapMintCommand(symbol, amount, opts))()); cli.parse();