Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699#!/usr/bin/env node/** * ac-keeps - Interactive CLI for keeping KidLisp pieces on Tezos * * Usage: * ac-keeps list - List your KidLisp pieces * ac-keeps list --top - List by most popular (hits) * ac-keeps list --recent - List most recent (default) * ac-keeps keep <code> - Keep a piece on Ghostnet * ac-keeps status <code> - Check if already kept * ac-keeps wallet - Connect/view Tezos wallet * ac-keeps wallet disconnect - Disconnect wallet */
import { promises as fs } from 'fs';import { fileURLToPath } from 'url';import { dirname, join } from 'path';import { connect } from '../system/backend/database.mjs';import readline from 'readline';import * as cliWallet from './cli-wallet.mjs';
const __filename = fileURLToPath(import.meta.url);const __dirname = dirname(__filename);const TOKEN_FILE = join(process.env.HOME, '.ac-token');
// Contract address - Ghostnet v3const CONTRACT_ADDRESS = "KT1StXrQNvRd9dNPpHdCGEstcGiBV6neq79K";const NETWORK = "ghostnet";const KEEP_FEE = 5; // XTZ
// Colorsconst RESET = '\x1b[0m';const BOLD = '\x1b[1m';const DIM = '\x1b[2m';const CYAN = '\x1b[36m';const GREEN = '\x1b[32m';const YELLOW = '\x1b[33m';const RED = '\x1b[31m';const BLUE = '\x1b[34m';const MAGENTA = '\x1b[35m';
// Check authenticationasync function checkAuth() { try { const tokenData = await fs.readFile(TOKEN_FILE, 'utf8'); const tokens = JSON.parse(tokenData); if (tokens.expires_at && Date.now() > tokens.expires_at) { console.log(`${RED}❌ Token expired${RESET}`); console.log(`${DIM}Run: ac-login${RESET}\n`); process.exit(1); } return tokens; } catch (err) { console.log(`${RED}❌ Not logged in${RESET}`); console.log(`${DIM}Run: ac-login${RESET}\n`); process.exit(1); }}
// Get user ID and info from tokenasync function getUserId(tokens) { const { db } = await connect(); // Try to find user by email const email = tokens.user?.email; if (!email) { console.log(`${RED}❌ No email in token${RESET}`); process.exit(1); } // Find user by Auth0 sub const user = await db.collection('users').findOne({ _id: tokens.user.sub }); if (!user) { console.log(`${RED}❌ User not found in database${RESET}`); process.exit(1); } // Extract handle from atproto.handle (e.g. "jeffrey.at.aesthetic.computer" -> "jeffrey") const fullHandle = user.atproto?.handle || ''; const handle = fullHandle.replace('.at.aesthetic.computer', '') || email.split('@')[0]; return { id: user._id, handle, email, fullHandle };}
// List KidLisp pieces (default sort by top hits)async function listPieces(userId, userInfo, sortBy = 'top', limit = 50) { const { db } = await connect(); const sort = sortBy === 'recent' ? { when: -1 } : { hits: -1 }; const pieces = await db.collection('kidlisp') .find({ user: userId }) .sort(sort) .limit(limit) .toArray(); const total = await db.collection('kidlisp').countDocuments({ user: userId }); const keptCount = await db.collection('kidlisp').countDocuments({ user: userId, 'tezos.minted': true }); console.log(`\n${BOLD}${CYAN}╔════════════════════════════════════════════════════════════════╗${RESET}`); console.log(`${BOLD}${CYAN}║ 🎨 Your KidLisp Pieces${RESET} ${BOLD}${CYAN}║${RESET}`); console.log(`${BOLD}${CYAN}╚════════════════════════════════════════════════════════════════╝${RESET}\n`); // Show user info if (userInfo) { console.log(`${DIM}👤 ${RESET}${BOLD}@${userInfo.handle}${RESET} ${DIM}(${userInfo.email})${RESET}`); } console.log(`${DIM}📊 ${total} pieces | ${GREEN}${keptCount} kept${RESET}${DIM} | Showing ${pieces.length} (sorted by ${sortBy})${RESET}\n`); pieces.forEach((p, i) => { const hits = p.hits || 0; const code = p.code; const preview = colorizeKidlisp(p.source.substring(0, 65).replace(/\n/g, ' ')); const date = new Date(p.when).toLocaleDateString(); const minted = p.tezos?.minted ? `${GREEN}✓ kept${RESET}` : `${RED}unkept${RESET}`; const url = `https://prompt.ac/$${code}`; console.log(`${BOLD}${(i+1).toString().padStart(2)}.${RESET} ${YELLOW}$${code}${RESET} ${minted} ${DIM}· 💫 ${hits} hits · ${date}${RESET}`); console.log(` ${preview}${RESET}`); console.log(` ${DIM}${BLUE}${url}${RESET}\n`); }); console.log(`${DIM}To keep a piece (5 ꜩ): ${BOLD}ac-keeps keep <code>${RESET}\n`);}
// CSS color map (subset of common ones)const CSS_COLORS = { red: [255, 0, 0], blue: [0, 0, 255], green: [0, 128, 0], yellow: [255, 255, 0], orange: [255, 165, 0], purple: [128, 0, 128], pink: [255, 192, 203], black: [0, 0, 0], white: [255, 255, 255], gray: [128, 128, 128], brown: [165, 42, 42], salmon: [250, 128, 114], beige: [245, 245, 220], coral: [255, 127, 80], crimson: [220, 20, 60], cyan: [0, 255, 255], gold: [255, 215, 0], indigo: [75, 0, 130], lime: [0, 255, 0], magenta: [255, 0, 255], maroon: [128, 0, 0], navy: [0, 0, 128], olive: [128, 128, 0], teal: [0, 128, 128], violet: [238, 130, 238], aqua: [0, 255, 255], azure: [240, 255, 255], chocolate: [210, 105, 30], darkred: [139, 0, 0], darkblue: [0, 0, 139], darkgreen: [0, 100, 0], deeppink: [255, 20, 147], hotpink: [255, 105, 180], lavender: [230, 230, 250], lightblue: [173, 216, 230], lightgreen: [144, 238, 144], lightsteelblue: [176, 196, 222], limegreen: [50, 205, 50], mediumseagreen: [60, 179, 113], orangered: [255, 69, 0], palegreen: [152, 251, 152], plum: [221, 160, 221], royalblue: [65, 105, 225], skyblue: [135, 206, 235], steelblue: [70, 130, 180], tomato: [255, 99, 71], turquoise: [64, 224, 208], yellowgreen: [154, 205, 50], orchid: [218, 112, 214], fuchsia: [255, 0, 255], tan: [210, 180, 140], sienna: [160, 82, 45],};const CSS_COLOR_NAMES = Object.keys(CSS_COLORS);
// Rainbow colors for animated effectconst RAINBOW_COLORS = [[255,0,0], [255,165,0], [255,255,0], [0,128,0], [0,0,255], [75,0,130], [238,130,238]];
// Helper to make RGB ANSI codefunction rgb(r, g, b) { return `\x1b[38;2;${r};${g};${b}m`;}
// KidLisp syntax colorizer for terminal (matches kidlisp.mjs style)// Uses token-based approach to avoid double-processingfunction colorizeKidlisp(source) { // Tokenize: split into colorizable tokens and preserve spacing/operators const tokens = []; let remaining = source; let rainbowIdx = 0; // Pattern to match: fade:xxx, rainbow, zebra, color names, $refs, numbers, timing, words, parens, or single chars const tokenPattern = /fade:[a-zA-Z0-9:-]+|\brainbow\b|\bzebra\b|\$[a-zA-Z0-9_-]+|\b\d*\.?\d+s!?\b|\b\d+(\.\d+)?\b|"[^"]*"|;[^\n]*|\([a-zA-Z][a-zA-Z0-9-]*|\)|\b[a-zA-Z][a-zA-Z0-9]*\b|./g; let match; while ((match = tokenPattern.exec(source)) !== null) { const tok = match[0]; const lower = tok.toLowerCase(); // fade: expressions - emerald green if (tok.startsWith('fade:')) { tokens.push(rgb(60, 179, 113) + tok + RESET); } // rainbow - cycling rainbow colors else if (lower === 'rainbow') { const c = RAINBOW_COLORS[rainbowIdx % RAINBOW_COLORS.length]; rainbowIdx++; tokens.push(rgb(c[0], c[1], c[2]) + tok + RESET); } // zebra - inverse video else if (lower === 'zebra') { tokens.push('\x1b[7m' + tok + RESET); } // CSS color names else if (CSS_COLOR_NAMES.includes(lower)) { const c = CSS_COLORS[lower]; tokens.push(rgb(c[0], c[1], c[2]) + tok + RESET); } // Piece references ($xxx) - lime green else if (tok.startsWith('$')) { tokens.push(rgb(50, 205, 50) + tok + RESET); } // Comments - dim gray else if (tok.startsWith(';')) { tokens.push(DIM + tok + RESET); } // String literals - yellow else if (tok.startsWith('"')) { tokens.push(YELLOW + tok + RESET); } // Timing patterns (1s, 0.5s) - yellow else if (/^\d*\.?\d+s!?$/.test(tok)) { tokens.push(YELLOW + tok + RESET); } // Function calls (open paren + name) - dim paren, yellow name else if (tok.startsWith('(') && tok.length > 1) { tokens.push(DIM + '(' + RESET + YELLOW + tok.slice(1) + RESET); } // Numbers - magenta/pink else if (/^\d+(\.\d+)?$/.test(tok)) { tokens.push(MAGENTA + tok + RESET); } // Close parens - dim else if (tok === ')') { tokens.push(DIM + ')' + RESET); } // Everything else unchanged else { tokens.push(tok); } } return tokens.join('');}
// Check Keep statusasync function checkStatus(userId, code) { const { db } = await connect(); const cleanCode = code.replace(/^\$/, ''); const piece = await db.collection('kidlisp').findOne({ user: userId, code: cleanCode }); if (!piece) { console.log(`${RED}❌ Piece $${cleanCode} not found${RESET}\n`); return; } console.log(`\n${BOLD}${CYAN}Keep Status: ${YELLOW}$${cleanCode}${RESET}\n`); console.log(`${DIM}${piece.source.substring(0, 80)}...${RESET}\n`); if (piece.tezos?.minted) { console.log(`${GREEN}✅ Already minted as Keep!${RESET}`); console.log(`${DIM}Token ID: ${piece.tezos.tokenId}${RESET}`); console.log(`${DIM}Transaction: ${piece.tezos.opHash}${RESET}`); if (piece.tezos.ipfs) { console.log(`${DIM}IPFS: ${piece.tezos.ipfs.artifact}${RESET}`); } } else { console.log(`${YELLOW}⚪ Not yet kept${RESET}`); console.log(`${DIM}Run: ${BOLD}ac-keeps keep $${cleanCode}${RESET}\n`); } console.log('');}
// Connect wallet command - now with QR optionasync function connectWallet(tokens, userId = null, useQR = false) { console.log(`\n${BOLD}${CYAN}╔════════════════════════════════════════════════════════════════╗${RESET}`); console.log(`${BOLD}${CYAN}║ 🔷 Connect Tezos Wallet ║${RESET}`); console.log(`${BOLD}${CYAN}╚════════════════════════════════════════════════════════════════╝${RESET}\n`); try { // Initialize and connect await cliWallet.init(NETWORK); // Check if already connected let address = cliWallet.getAddress(); if (address) { const balance = await cliWallet.fetchBalance(address, NETWORK); const domain = await cliWallet.fetchDomain(address, NETWORK); const displayName = domain || `${address.slice(0, 8)}...${address.slice(-6)}`; console.log(`${GREEN}✅ Already connected${RESET}`); console.log(`${CYAN}Address:${RESET} ${displayName}`); console.log(`${CYAN}Full:${RESET} ${address}`); if (balance !== null) { console.log(`${CYAN}Balance:${RESET} ${balance.toFixed(2)} ꜩ`); } console.log(`${CYAN}Network:${RESET} ${NETWORK}\n`); return address; } // Ask user how they want to connect if not specified if (!useQR) { console.log(`${CYAN}How do you want to connect?${RESET}\n`); console.log(` ${BOLD}1${RESET} - 📱 Scan QR code with mobile wallet (Temple/Kukai)`); console.log(` ${BOLD}2${RESET} - ⌨️ Enter address manually`); console.log(); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const choice = await new Promise(resolve => { rl.question(`${GREEN}Enter choice (1/2): ${RESET}`, answer => { rl.close(); resolve(answer.trim()); }); }); if (choice === '1') { useQR = true; } } if (useQR) { // Show wallet selection for QR console.log(`\n${CYAN}Select wallet:${RESET}\n`); console.log(` ${BOLD}1${RESET} - Temple Wallet (Beacon P2P)`); console.log(` ${BOLD}2${RESET} - Kukai Wallet (WalletConnect)`); console.log(); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const walletChoice = await new Promise(resolve => { rl.question(`${GREEN}Enter choice (1/2): ${RESET}`, answer => { rl.close(); resolve(answer.trim()); }); }); const walletType = walletChoice === '2' ? 'kukai' : 'temple'; address = await cliWallet.connectViaQR(walletType, NETWORK); } else { // Manual address entry address = await cliWallet.connect(NETWORK); } // Update AC database with new address (pass userId for direct DB fallback) if (address) { await updateMongoDBWallet(userId, address, NETWORK); if (tokens?.access_token) { await cliWallet.updateDatabaseAddress(address, NETWORK, tokens.access_token, userId); } } return address; } catch (err) { console.log(`${RED}❌ Wallet connection failed${RESET}`); console.log(`${DIM}${err.message}${RESET}\n`); return null; }}
// Update MongoDB with wallet address directlyasync function updateMongoDBWallet(userId, address, network) { if (!userId) return false; try { const { db } = await connect(); // Fetch domain for this address const domain = await cliWallet.fetchDomain(address, network); await db.collection("users").updateOne( { _id: userId }, { $set: { "tezos.address": address, "tezos.network": network, "tezos.domain": domain, "tezos.connectedAt": new Date(), }, } ); console.log(`${GREEN}✅ Wallet saved to AC profile${RESET}`); if (domain) { console.log(`${DIM} Domain: ${domain}${RESET}`); } console.log(`${DIM} Address: ${address.slice(0, 12)}...${RESET}\n`); return true; } catch (err) { console.log(`${DIM}⚠️ Could not save to MongoDB: ${err.message}${RESET}`); return false; }}
// Show wallet statusasync function walletStatus() { await cliWallet.init(NETWORK); const address = cliWallet.getAddress(); if (!address) { console.log(`${YELLOW}⚪ No wallet connected${RESET}`); console.log(`${DIM}Run: ${BOLD}ac-keeps wallet${RESET} ${DIM}to connect${RESET}\n`); return null; } const balance = await cliWallet.fetchBalance(address, NETWORK); const domain = await cliWallet.fetchDomain(address, NETWORK); console.log(`${GREEN}✅ Wallet connected${RESET}`); if (domain) { console.log(`${CYAN}Domain:${RESET} ${domain}`); } console.log(`${CYAN}Address:${RESET} ${address}`); if (balance !== null) { console.log(`${CYAN}Balance:${RESET} ${balance.toFixed(2)} ꜩ`); } console.log(`${CYAN}Network:${RESET} ${NETWORK}\n`); return address;}
// Disconnect walletasync function disconnectWallet() { await cliWallet.init(NETWORK); await cliWallet.disconnect(); console.log(`${GREEN}✅ Wallet disconnected${RESET}\n`);}
// Keep a piece (USER PAYS via Beacon wallet)async function keepPiece(userId, code, tokens) { const cleanCode = code.replace(/^\$/, ''); console.log(`\n${BOLD}${CYAN}╔════════════════════════════════════════════════════════════════╗${RESET}`); console.log(`${BOLD}${CYAN}║ 🏺 Keeping: ${YELLOW}$${cleanCode}${RESET} ${BOLD}${CYAN}║${RESET}`); console.log(`${BOLD}${CYAN}╚════════════════════════════════════════════════════════════════╝${RESET}\n`); // Check piece exists and not already kept const { db } = await connect(); const piece = await db.collection('kidlisp').findOne({ user: userId, code: cleanCode }); if (!piece) { console.log(`${RED}❌ Piece $${cleanCode} not found${RESET}\n`); return; } if (piece.tezos?.minted) { console.log(`${YELLOW}⚠️ Already kept!${RESET}`); console.log(`${DIM}Token ID: ${piece.tezos.tokenId}${RESET}\n`); return; } // Initialize wallet and check connection await cliWallet.init(NETWORK); let walletAddress = cliWallet.getAddress(); if (!walletAddress) { console.log(`${YELLOW}⚠️ No wallet connected. Connecting now...${RESET}\n`); walletAddress = await connectWallet(tokens, userId); if (!walletAddress) { return; } } // Lookup .tez domain for better UX const domain = await cliWallet.fetchDomain(walletAddress, NETWORK); const displayAddress = domain ? `${domain} (${walletAddress.slice(0, 8)}...${walletAddress.slice(-6)})` : `${walletAddress.slice(0, 8)}...${walletAddress.slice(-6)}`; console.log(`${DIM}${piece.source.substring(0, 80)}...${RESET}\n`); console.log(`${CYAN}📍 Destination:${RESET} ${displayAddress}`); console.log(`${CYAN}🌐 Network:${RESET} ${NETWORK}`); console.log(`${CYAN}💰 Fee:${RESET} Sponsored by AC (testnet)${RESET}`); console.log(''); // Confirmation const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const confirmed = await new Promise((resolve) => { rl.question(`${YELLOW}Keep this piece? (y/n): ${RESET}`, (answer) => { rl.close(); resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes'); }); }); if (!confirmed) { console.log(`${DIM}Cancelled${RESET}\n`); return; } // Call mint endpoint (server-side minting, token goes to user's address) console.log(`\n${CYAN}📡 Minting to ${displayAddress}...${RESET}\n`); const endpoint = process.env.AC_ENDPOINT || 'https://localhost:8888/api/keep-mint'; const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${tokens.access_token}`, }, body: JSON.stringify({ piece: cleanCode, mode: 'mint' // Server-side mint to user's stored address }), }); if (!response.ok) { console.log(`${RED}❌ Request failed: ${response.status}${RESET}\n`); return; } // Stream SSE events const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { try { const data = JSON.parse(line.slice(6)); if (data.type === 'progress') { console.log(`${CYAN}▸${RESET} ${data.data?.stage || ''}: ${data.data?.message || ''}`); } else if (data.type === 'complete') { console.log(`\n${GREEN}🏺 KEPT!${RESET}\n`); console.log(`${CYAN}Token ID:${RESET} ${data.data.tokenId}`); console.log(`${CYAN}Owner:${RESET} ${displayAddress}`); console.log(`${CYAN}Contract:${RESET} ${data.data.contract}`); console.log(`${CYAN}Transaction:${RESET} ${data.data.opHash}`); console.log(`${CYAN}View:${RESET} ${data.data.objktUrl}\n`); } else if (data.type === 'error') { console.log(`${RED}❌ Error: ${data.data?.error || 'Unknown error'}${RESET}\n`); return; } } catch (e) { // Ignore parse errors } } } }}
// Interactive modeasync function interactive(userId, tokens, userInfo) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); console.log(`\n${BOLD}${MAGENTA}╔════════════════════════════════════════════════════════════════╗${RESET}`); console.log(`${BOLD}${MAGENTA}║ 🏳️ AC Keeps - Interactive Mode ║${RESET}`); console.log(`${BOLD}${MAGENTA}╚════════════════════════════════════════════════════════════════╝${RESET}\n`); // Show user info console.log(`${DIM}👤 ${RESET}${BOLD}@${userInfo.handle}${RESET} ${DIM}(${userInfo.email})${RESET}\n`); // Show wallet status on start await cliWallet.init(NETWORK); const walletAddr = cliWallet.getAddress(); if (walletAddr) { const domain = await cliWallet.fetchDomain(walletAddr, NETWORK); const display = domain || `${walletAddr.slice(0, 8)}...${walletAddr.slice(-6)}`; console.log(`${GREEN}🔷 Wallet:${RESET} ${display}\n`); } else { console.log(`${YELLOW}⚪ No wallet connected${RESET} ${DIM}(run: wallet)${RESET}\n`); } console.log(`${DIM}Commands: list, top, keep <code>, status <code>, wallet, exit${RESET}\n`); const prompt = () => { rl.question(`${CYAN}keeps>${RESET} `, async (input) => { const [cmd, ...args] = input.trim().split(/\s+/); switch (cmd) { case 'list': await listPieces(userId, userInfo, 'recent', 20); break; case 'top': await listPieces(userId, userInfo, 'top', 20); break; case 'keep': if (args.length === 0) { console.log(`${RED}Usage: keep <code>${RESET}\n`); } else { await keepPiece(userId, args[0], tokens); } break; case 'wallet': if (args[0] === 'disconnect') { await disconnectWallet(); } else if (args[0] === 'qr') { await connectWallet(tokens, userId, true); } else { await connectWallet(tokens, userId); } break; case 'status': if (args.length === 0) { console.log(`${RED}Usage: status <code>${RESET}\n`); } else { await checkStatus(userId, args[0]); } break; case 'exit': case 'quit': console.log(`${DIM}Goodbye!${RESET}\n`); rl.close(); process.exit(0); return; case '': break; default: console.log(`${RED}Unknown command: ${cmd}${RESET}\n`); console.log(`${DIM}Commands: list, top, keep <code>, status <code>, wallet [qr], exit${RESET}\n`); } prompt(); }); }; prompt();}
// Main(async () => { const tokens = await checkAuth(); const userInfo = await getUserId(tokens); const userId = userInfo.id; const args = process.argv.slice(2); const command = args[0]; if (!command) { // No command - enter interactive mode await interactive(userId, tokens, userInfo); return; } switch (command) { case 'list': const sortBy = args.includes('--recent') ? 'recent' : 'top'; const limit = parseInt(args.find(a => a.match(/^\d+$/))) || 50; await listPieces(userId, userInfo, sortBy, limit); break; case 'keep': if (args.length < 2) { console.log(`${RED}Usage: ac-keeps keep <code>${RESET}\n`); process.exit(1); } await keepPiece(userId, args[1], tokens); break; case 'wallet': if (args[1] === 'disconnect') { await disconnectWallet(); } else if (args[1] === 'status') { await walletStatus(); } else if (args[1] === 'qr' || args.includes('--qr')) { // QR code scanning await connectWallet(tokens, userId, true); } else { await connectWallet(tokens, userId); } break; case 'status': if (args.length < 2) { console.log(`${RED}Usage: ac-keeps status <code>${RESET}\n`); process.exit(1); } await checkStatus(userId, args[1]); break; default: console.log(`${RED}Unknown command: ${command}${RESET}\n`); console.log(`${DIM}Usage:${RESET}`); console.log(` ac-keeps - Interactive mode`); console.log(` ac-keeps list - List recent pieces`); console.log(` ac-keeps list --top - List by popularity`); console.log(` ac-keeps keep <code> - Keep a piece on Tezos`); console.log(` ac-keeps wallet - Connect Tezos wallet`); console.log(` ac-keeps wallet disconnect - Disconnect wallet`); console.log(` ac-keeps status <code> - Check status`); console.log(''); process.exit(1); } process.exit(0);})();