diff --git a/package-lock.json b/package-lock.json index 5678fc8..b176fe6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,15 @@ { "name": "@quillai-network/wachai", - "version": "0.0.1", + "version": "0.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@quillai-network/wachai", - "version": "0.0.1", + "version": "0.0.3", "license": "MIT", "dependencies": { "@quillai-network/mandates-core": "^0.1.0", - "@quillai-network/wachai": "^0.0.1", "@xmtp/node-sdk": "^5.3.0", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", @@ -521,26 +520,6 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@quillai-network/wachai": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@quillai-network/wachai/-/wachai-0.0.1.tgz", - "integrity": "sha512-iMBecGrKWq4LqISlVfuZ7iZzqlY5f26C5xtew6g/x/Lcv+Rin5nOsKa0CfZveU/kM0u4xm+YuzKNaiB/0iOVHw==", - "dependencies": { - "@quillai-network/mandates-core": "^0.1.0", - "@xmtp/node-sdk": "^5.3.0", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "commander": "^12.1.0", - "ethers": "^6.13.5", - "ulid": "^2.3.0" - }, - "bin": { - "wachai": "bin/wachai.js" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.57.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", diff --git a/src/ascii.ts b/src/ascii.ts new file mode 100644 index 0000000..afca9ec --- /dev/null +++ b/src/ascii.ts @@ -0,0 +1,161 @@ +/** + * WachAI Terminal ASCII Art Module + * Provides colorful ASCII art banners and decorations for the CLI + */ + +// ANSI color codes for terminal output +const colors = { + reset: "\x1b[0m", + bold: "\x1b[1m", + dim: "\x1b[2m", + + // Foreground colors + black: "\x1b[30m", + red: "\x1b[31m", + green: "\x1b[32m", + yellow: "\x1b[33m", + blue: "\x1b[34m", + magenta: "\x1b[35m", + cyan: "\x1b[36m", + white: "\x1b[37m", + + // Bright foreground colors + brightRed: "\x1b[91m", + brightGreen: "\x1b[92m", + brightYellow: "\x1b[93m", + brightBlue: "\x1b[94m", + brightMagenta: "\x1b[95m", + brightCyan: "\x1b[96m", + brightWhite: "\x1b[97m", +}; + +/** + * Check if terminal supports colors + */ +function supportsColor(): boolean { + // Check for common environment indicators + if (process.env.NO_COLOR) return false; + if (process.env.FORCE_COLOR) return true; + if (!process.stdout.isTTY) return false; + return true; +} + +/** + * Apply color to text (only if terminal supports it) + */ +function colorize(text: string, color: string): string { + if (!supportsColor()) return text; + return `${color}${text}${colors.reset}`; +} + +/** + * Main WachAI ASCII art logo with gradient colors + */ +export function getWachAILogo(): string { + const c = supportsColor() ? colors : { + reset: "", bold: "", dim: "", + cyan: "", brightCyan: "", magenta: "", brightMagenta: "", + yellow: "", brightYellow: "", green: "", brightGreen: "", + blue: "", brightBlue: "", white: "", brightWhite: "", red: "", brightRed: "" + }; + + // Gradient effect using cyan -> magenta -> yellow + const logo = ` +${c.brightCyan}██╗ ██╗${c.cyan} █████╗ ${c.brightMagenta}██████╗${c.magenta}██╗ ██╗${c.brightYellow} █████╗ ${c.yellow}██╗${c.reset} +${c.brightCyan}██║ ██║${c.cyan}██╔══██╗ ${c.brightMagenta}██╔════╝${c.magenta}██║ ██║${c.brightYellow}██╔══██╗${c.yellow}██║${c.reset} +${c.brightCyan}██║ █╗ ██║${c.cyan}███████║ ${c.brightMagenta}██║ ${c.magenta}███████║${c.brightYellow}███████║${c.yellow}██║${c.reset} +${c.brightCyan}██║███╗██║${c.cyan}██╔══██║ ${c.brightMagenta}██║ ${c.magenta}██╔══██║${c.brightYellow}██╔══██║${c.yellow}██║${c.reset} +${c.brightCyan}╚███╔███╔╝${c.cyan}██║ ██║ ${c.brightMagenta}╚██████╗${c.magenta}██║ ██║${c.brightYellow}██║ ██║${c.yellow}██║${c.reset} +${c.brightCyan} ╚══╝╚══╝ ${c.cyan}╚═╝ ╚═╝ ${c.brightMagenta} ╚═════╝${c.magenta}╚═╝ ╚═╝${c.brightYellow}╚═╝ ╚═╝${c.yellow}╚═╝${c.reset} +`; + + return logo; +} + +/** + * Terminal subtitle with styling + */ +export function getTerminalSubtitle(): string { + const c = supportsColor() ? colors : { reset: "", dim: "", brightWhite: "", cyan: "" }; + + return `${c.dim}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset} +${c.brightWhite} ⚡ ${c.cyan}M A N D A T E S T E R M I N A L${c.brightWhite} ⚡${c.reset} +${c.dim}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${c.reset}`; +} + +/** + * Get the full banner (logo + subtitle) + */ +export function getFullBanner(): string { + return `${getWachAILogo()}${getTerminalSubtitle()}`; +} + +/** + * Decorative box around text + */ +export function boxText(text: string, title?: string): string { + const c = supportsColor() ? colors : { reset: "", cyan: "", dim: "", brightWhite: "" }; + const lines = text.split('\n'); + const maxLen = Math.max(...lines.map(l => l.replace(/\x1b\[[0-9;]*m/g, '').length)); + const width = Math.max(maxLen + 4, (title?.length ?? 0) + 6); + + const top = title + ? `${c.cyan}╭─${c.brightWhite} ${title} ${c.cyan}${'─'.repeat(width - title.length - 4)}╮${c.reset}` + : `${c.cyan}╭${'─'.repeat(width)}╮${c.reset}`; + const bottom = `${c.cyan}╰${'─'.repeat(width)}╯${c.reset}`; + + const paddedLines = lines.map(line => { + const visibleLen = line.replace(/\x1b\[[0-9;]*m/g, '').length; + const padding = ' '.repeat(width - visibleLen - 2); + return `${c.cyan}│${c.reset} ${line}${padding}${c.cyan}│${c.reset}`; + }); + + return [top, ...paddedLines, bottom].join('\n'); +} + +/** + * Status indicator with color + */ +export function statusIcon(status: 'success' | 'error' | 'warning' | 'info'): string { + const c = supportsColor() ? colors : { reset: "", green: "", red: "", yellow: "", blue: "" }; + const icons: Record = { + success: `${c.green}✓${c.reset}`, + error: `${c.red}✗${c.reset}`, + warning: `${c.yellow}⚠${c.reset}`, + info: `${c.blue}ℹ${c.reset}`, + }; + return icons[status] ?? icons.info; +} + +/** + * Progress spinner frames (for animated CLI) + */ +export const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; + +/** + * Section header with decorative styling + */ +export function sectionHeader(title: string): string { + const c = supportsColor() ? colors : { reset: "", brightCyan: "", dim: "" }; + return `\n${c.brightCyan}▸ ${title}${c.reset}\n${c.dim}${'─'.repeat(title.length + 2)}${c.reset}`; +} + +/** + * Compact one-line banner for minimal output + */ +export function getCompactBanner(): string { + const c = supportsColor() ? colors : { reset: "", bold: "", dim: "", cyan: "", magenta: "", yellow: "" }; + return `${c.bold}${c.cyan}Wach${c.magenta}AI${c.reset} ${c.yellow}Terminal${c.reset} ${c.dim}v0.0.3${c.reset}`; +} + +/** + * Credits/footer text + */ +export function getFooter(): string { + const c = supportsColor() ? colors : { reset: "", dim: "", cyan: "" }; + return `${c.dim}─────────────────────────────────────────────────────────${c.reset} +${c.dim}Powered by ${c.cyan}QuillAI Network${c.dim} │ github.com/quillai-network${c.reset}`; +} + +// Export colors for external use +export { colors, colorize, supportsColor }; diff --git a/src/cli.ts b/src/cli.ts index d536775..123ff95 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -21,6 +21,7 @@ import { importAndStorePrivateKey, loadWalletFile, } from "./walletStore"; +import { getFullBanner, getFooter, colors, supportsColor } from "./ascii"; function requireAddress(name: string, value: string) { if (!isAddress(value)) throw new Error(`Invalid ${name} address: ${value}`); @@ -106,32 +107,24 @@ async function createSwapMandateFromRegistry(opts: { async function main() { const program = new Command(); - const banner = String.raw` - /$$ /$$ /$$ /$$$$$$ /$$$$$$ /$$$$$$$$ /$$ /$$ -| $$ /$ | $$ | $$ /$$__ $$|_ $$_/ |__ $$__/ |__/ | $$ -| $$ /$$$| $$ /$$$$$$ /$$$$$$$| $$$$$$$ | $$ \ $$ | $$ | $$ /$$$$$$ /$$$$$$ /$$$$$$/$$$$ /$$ /$$$$$$$ /$$$$$$ | $$ -| $$/$$ $$ $$ |____ $$ /$$_____/| $$__ $$| $$$$$$$$ | $$ | $$ /$$__ $$ /$$__ $$| $$_ $$_ $$| $$| $$__ $$ |____ $$| $$ -| $$$$_ $$$$ /$$$$$$$| $$ | $$ \ $$| $$__ $$ | $$ | $$| $$$$$$$$| $$ \__/| $$ \ $$ \ $$| $$| $$ \ $$ /$$$$$$$| $$ -| $$$/ \ $$$ /$$__ $$| $$ | $$ | $$| $$ | $$ | $$ | $$| $$_____/| $$ | $$ | $$ | $$| $$| $$ | $$ /$$__ $$| $$ -| $$/ \ $$| $$$$$$$| $$$$$$$| $$ | $$| $$ | $$ /$$$$$$ | $$| $$$$$$$| $$ | $$ | $$ | $$| $$| $$ | $$| $$$$$$$| $$ -|__/ \__/ \_______/ \_______/|__/ |__/|__/ |__/|______/ |__/ \_______/|__/ |__/ |__/ |__/|__/|__/ |__/ \_______/|__/ - - -`; + // Get colorful ASCII art banner + const banner = getFullBanner(); + const c = supportsColor() ? colors : { reset: "", dim: "", cyan: "", yellow: "", green: "" }; const intro = - `WachAI Mandates are signed, verifiable agreements between a server and a client.\n` + - `- The server creates the mandate (offer) and signs first\n` + - `- The client signs second (accept)\n` + - `This CLI stores mandates locally so you can sign/verify by mandateId.\n` + - `Repo: https://github.com/quillai-network/WachAI-Terminal\n`; + `\n${c.cyan}WachAI Mandates${c.reset} are signed, verifiable agreements between a server and a client.\n\n` + + `${c.yellow}•${c.reset} The server creates the mandate (offer) and signs first\n` + + `${c.yellow}•${c.reset} The client signs second (accept)\n` + + `${c.yellow}•${c.reset} This CLI stores mandates locally so you can sign/verify by mandateId\n\n` + + `${c.dim}Repo: https://github.com/quillai-network/WachAI-Terminal${c.reset}\n`; program .name("wachai") .description("WachAI mandates CLI") - .version("0.0.1"); + .version("0.0.3"); program.addHelpText("beforeAll", `${banner}\n${intro}\n`); + program.addHelpText("after", `\n${getFooter()}`); program.addHelpText( "afterAll", `\nExamples:\n` +