๐Ÿ“„ cli.ts  โ€ข  27836 bytes
#!/usr/bin/env bun
/**
 * CmdCode V0.5 - CLI ๅ…ฅๅฃ
 * 
 * ็”จๆณ•๏ผš
 *   cmdcode                        # ไบคไบ’REPLๆจกๅผ
 *   cmdcode -p "ๆ็คบ่ฏ"            # ๅ•ๆฌกๆ‰ง่กŒๆจกๅผ
 *   cmdcode -p "ๆ็คบ" --continue   # ็ปง็ปญไธŠๆฌกไผš่ฏ
 *   cmdcode --sessions             # ๅˆ—ๅ‡บๆ‰€ๆœ‰ไผš่ฏ
 *   cmdcode -h                     # ๅธฎๅŠฉ
 */
import { parseArgs } from 'node:util'
import * as readline from 'node:readline'
import { t, initI18n } from './i18n.js'
import { createChatEngine, createChatEngineFromHistory, setGlobalPAVREnabled, isGlobalPAVREnabled } from './chat-factory.js'
import { loadConfig, updateAppConfig } from './config.js'
import { saveSession, loadSession, getLatestSessionId, listSessions } from './session.js'
import { BUILTIN_PROVIDERS, testConnection, type ModelProvider, type CustomModelConfig } from './models.js'
import { 
  // ๅฏ†้’ฅๆฑ ็ฎก็†
  loadChatKeyPool, addChatKey, removeChatKey,
  loadEmbeddingKeyPool, addEmbeddingKey, removeEmbeddingKey,
  // ็ฎ€ๅŒ–ๆŽฅๅฃ
  saveKeys
} from './apikeys.js'
import {
  register, login, validateToken, loadUserCache, clearUserCache,
  restoreWorkspaceSnapshot,
} from './user.js'
import {
  createUserModel, loadUserModel, updateUserModel, deleteUserModel,
  listUserModels, getUserDefaultModel, setUserDefaultModel,
  testUserModelConnection, countUserModels, type UserModelConfig
} from './user-models.js'
import { setUserWorkspace, setUsername } from './tools.js'
import { printHelp } from './commands/help.js'
import { interactiveModelSetup } from './commands/model.js'
import { countHistoryMessages, getDirSize, printBanner, restoreWorkspaceFromSnapshot } from './commands/workspace.js'
import { userAuthFlow } from './commands/auth.js'
import { replLoop } from './commands/repl.js'
import { existsSync, mkdirSync, writeFileSync, chmodSync } from 'node:fs'
import { isUsingFallbackKey } from './crypto-util.js'
// ๅ‘้‡่ฎฐๅฟ†็ณป็ปŸ
import {
  initMemorySystem
} from './memory/memoryManager.js'
import { join } from 'node:path'
import { homedir } from 'node:os'
import { getSystemPrompt } from './chat.js'
import { SYSTEM_PROMPT_TEMPLATE } from './system-prompt.js'

const VERSION = '0.5.0'
const API_BASE = 'https://cmdcode.cn/xiaoc/cmdcode_api.php'

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// ANSI ๆ ทๅผๅทฅๅ…ท
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
const C = {
  reset: '\x1b[0m',
  bold: '\x1b[1m',
  dim: '\x1b[2m',
  italic: '\x1b[3m',
  // ๅ‰ๆ™ฏ่‰ฒ
  black: '\x1b[30m',
  red: '\x1b[31m',
  green: '\x1b[32m',
  yellow: '\x1b[33m',
  blue: '\x1b[34m',
  magenta: '\x1b[35m',
  cyan: '\x1b[36m',
  white: '\x1b[37m',
  // ไบฎ่‰ฒ
  brightBlack: '\x1b[90m',
  brightRed: '\x1b[91m',
  brightGreen: '\x1b[92m',
  brightYellow: '\x1b[93m',
  brightBlue: '\x1b[94m',
  brightMagenta: '\x1b[95m',
  brightCyan: '\x1b[96m',
  brightWhite: '\x1b[97m',
  // ่ƒŒๆ™ฏ่‰ฒ
  bgBlack: '\x1b[40m',
  bgRed: '\x1b[41m',
  bgGreen: '\x1b[42m',
  bgYellow: '\x1b[43m',
  bgBlue: '\x1b[44m',
  bgMagenta: '\x1b[45m',
  bgCyan: '\x1b[46m',
  bgWhite: '\x1b[47m',
}

// ๆฃ€ๆต‹็ปˆ็ซฏๆ˜ฏๅฆๆ”ฏๆŒ้ขœ่‰ฒ
const supportsColor = process.stdout.isTTY && (process.env.TERM !== 'dumb')
const color = supportsColor ? C : Object.fromEntries(Object.keys(C).map(k => [k, ''])) as typeof C

/** ๅ“็‰Œไธป้ข˜่‰ฒ - Claude Code ็ปๅ…ธๆฉ˜้ป„้ฃŽๆ ผ๏ผˆๅ— supportsColor ๆŽงๅˆถ๏ผ‰ */
const BRAND = supportsColor ? '\x1b[38;5;208m' : ''    // CmdCodeๅ“็‰Œ่‰ฒ - ๆ ‡ๅ‡†ๆฉ˜้ป„
const BRAND_DIM = supportsColor ? '\x1b[38;5;214m' : '' // ไบฎๆฉ˜่‰ฒ
const ACCENT = supportsColor ? '\x1b[38;5;220m' : ''    // ๅผบ่ฐƒ่‰ฒ - ้‡‘้ป„
const MUTED = supportsColor ? '\x1b[38;5;240m' : ''     // ๆฌก่ฆไฟกๆฏ - ๆทฑ็ฐ
const SUCCESS = color.brightGreen // ๆˆๅŠŸ
const ERROR = color.brightRed     // ้”™่ฏฏ
const WARN = color.brightYellow   // ่ญฆๅ‘Š

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// ๆ˜พ็คบๅ‡ฝๆ•ฐ
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•


/** ่ฎก็ฎ—็›ฎๅฝ•ๅคงๅฐ๏ผˆๅญ—่Š‚๏ผ‰ */
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - ไผš่ฏ็บงๅญ˜ๅ‚จ๏ผˆttyd ๆ—  X ๆ˜พ็คบ็Žฏๅขƒ๏ผ‰ */
let sessionClipboard = ''
// ๅฎ‰ๅ…จ๏ผš็งป่‡ณ็”จๆˆทไธ“ๅฑž็›ฎๅฝ•๏ผŒ้ฟๅ…ๅคš็”จๆˆทๅ…ฑไบซ/tmpๅฏผ่‡ดๆณ„้œฒ
const CLIPBOARD_FILE = join(homedir(), '.cmdcode', 'clipboard')
// ็กฎไฟๅ‰ช่ดดๆฟๆ–‡ไปถๆƒ้™ไธบ600๏ผˆไป…็”จๆˆทๅฏ่ฏปๅ†™๏ผ‰
function ensureClipboardSecure(): void {
  try { chmodSync(CLIPBOARD_FILE, 0o600) } catch { /* ignore */ }
}
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// ๅฎ‰ๅ…จๅฏ†้’ฅๅบ“็ฎก็†
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

/** ๅฎ‰ๅ…จๅฏ†็ ่พ“ๅ…ฅ๏ผˆ้š่—่พ“ๅ…ฅ๏ผ‰ */
async function askPassword(prompt: string): Promise<string> {
  return new Promise((resolve) => {
    process.stdout.write(prompt)
    process.stdin.setRawMode(true)
    let password = ''
    process.stdin.on('data', (data: Buffer) => {
      const char = data.toString('utf8')
      if (char === '\n' || char === '\r' || char === '\u0004') {
        process.stdin.setRawMode(false)
        console.log('') // ๆข่กŒ
        resolve(password)
      } else if (char === '\u0003') {
        process.stdin.setRawMode(false)
        resolve('')
      } else {
        password += char
        process.stdout.write('*')
      }
    })
  })
}

async function copyToClipboard(text: string): Promise<boolean> {
  try {
    sessionClipboard = text
    const { writeFileSync } = require('node:fs')
    writeFileSync(CLIPBOARD_FILE, text, 'utf-8')
    return true
  } catch {
    return false
  }
}

async function pasteFromClipboard(): Promise<string> {
  try {
    // ไผ˜ๅ…ˆไฝฟ็”จไผš่ฏๅ‰ช่ดดๆฟ
    if (sessionClipboard) {
      return sessionClipboard
    }
    // ๅฐ่ฏ•่ฏปๅ–ๆ–‡ไปถ
    const { existsSync, readFileSync } = require('node:fs')
    if (existsSync(CLIPBOARD_FILE)) {
      return readFileSync(CLIPBOARD_FILE, 'utf-8')
    }
    return ''
  } catch {
    return ''
  }
}

/** REPL ่พ“ๅ…ฅ - ไฝฟ็”จๆ ‡ๅ‡† readline๏ผˆBun ๅ…ผๅฎน๏ผ‰ */
function askREPL(prompt: string): Promise<{ input: string; action: 'submit' | 'exit' }> {
  return new Promise((resolve) => {
    const stdin = process.stdin
    const stdout = process.stdout
    const wasRaw = stdin.isRaw

    // ่ฟ›ๅ…ฅ raw ๆจกๅผ๏ผŒๅ…ณ้—ญ่กŒ็ผ“ๅ†ฒ
    if (typeof stdin.setRawMode === 'function') {
      stdin.setRawMode(true)
    }
    readline.emitKeypressEvents(stdin)

    let buffer = ''
    let cursorPos = 0

    const render = () => {
      // ๆธ…้™ค่กŒๅนถ้‡็ป˜
      stdout.write('\r\x1b[K')
      stdout.write(prompt + ' ' + buffer)
      // ็งปๅŠจๅ…‰ๆ ‡ๅˆฐๆญฃ็กฎไฝ็ฝฎ
      if (cursorPos < buffer.length) {
        stdout.write(`\x1b[${buffer.length - cursorPos}D`)
      }
    }

    render()

    // Issue 7: SIGWINCH - terminal resize auto-redraw
    const onResize = () => render()
    process.stdout.on('resize', onResize)

    const cleanup = () => {
      if (typeof stdin.setRawMode === 'function') {
        stdin.setRawMode(wasRaw ?? false)
      }
      stdin.removeListener('keypress', onKeypress)
      process.stdout.removeListener('resize', onResize)
      stdout.write('\n')
    }

    const onKeypress = async (str: string, key: any) => {
      // Ctrl+E (0x05) ้€€ๅ‡บ
      if (key.ctrl && key.name === 'e') {
        cleanup()
        resolve({ input: '', action: 'exit' })
        return
      }

      // Ctrl+L ๆธ…ๅฑ
      if (key.ctrl && key.name === 'l') {
        stdout.write('\x1b[2J\x1b[H')
        render()
        return
      }

      // Ctrl+C (0x03) ๅคๅˆถ
      if (key.ctrl && key.name === 'c') {
        if (buffer.length > 0) {
          await copyToClipboard(buffer)
          // ๆ˜พ็คบๆ็คบๅŽ่‡ชๅŠจๆธ…้™ค๏ผˆไธ้ฎๆŒก่พ“ๅ…ฅ่กŒ๏ผ‰
          stdout.write(`\r\x1b[K${SUCCESS}โœ“ ${t("clip.copied")}${color.reset}`)
          setTimeout(() => {
            stdout.write('\r\x1b[K')
            render()
          }, 1000)
        }
        return
      }

      // Ctrl+X (0x18) ๅ‰ชๅˆ‡
      if (key.ctrl && key.name === 'x') {
        if (buffer.length > 0) {
          await copyToClipboard(buffer)
          buffer = ''
          cursorPos = 0
          stdout.write(`\r\x1b[K${SUCCESS}โœ“ ${t("clip.cut")}${color.reset}`)
          setTimeout(() => {
            stdout.write('\r\x1b[K')
            render()
          }, 1000)
        }
        return
      }

      // Ctrl+V (0x16) ็ฒ˜่ดด
      if (key.ctrl && key.name === 'v') {
        const pasted = await pasteFromClipboard()
        if (pasted) {
          const cleanPasted = pasted.replace(/[\r\n]+/g, ' ')
          const before = buffer.slice(0, cursorPos)
          const after = buffer.slice(cursorPos)
          buffer = before + cleanPasted + after
          cursorPos += cleanPasted.length
          render()
        }
        return
      }

      // Enter ๆไบค
      if (key.name === 'return' || key.name === 'enter') {
        cleanup()
        // ๆˆชๆ–ญ่ถ…้•ฟ่พ“ๅ…ฅ๏ผŒ้˜ฒๆญข่ฏฏ็ฒ˜่ดด้•ฟๆ–‡่ถ…ๅ‡บไธŠไธ‹ๆ–‡
        const limited = buffer.length > 8000 ? buffer.slice(0, 8000) : buffer
        resolve({ input: limited.trim(), action: 'submit' })
        return
      }

      // Backspace / Delete
      if (key.name === 'backspace') {
        if (cursorPos > 0) {
          const before = buffer.slice(0, cursorPos - 1)
          const after = buffer.slice(cursorPos)
          buffer = before + after
          cursorPos--
          render()
        }
        return
      }
      if (key.name === 'delete') {
        if (cursorPos < buffer.length) {
          const before = buffer.slice(0, cursorPos)
          const after = buffer.slice(cursorPos + 1)
          buffer = before + after
          render()
        }
        return
      }

      // ๅทฆๅณ็ฎญๅคด
      if (key.name === 'left') {
        if (cursorPos > 0) {
          cursorPos--
          render()
        }
        return
      }
      if (key.name === 'right') {
        if (cursorPos < buffer.length) {
          cursorPos++
          render()
        }
        return
      }

      // Home / End
      if (key.name === 'home') {
        cursorPos = 0
        render()
        return
      }
      if (key.name === 'end') {
        cursorPos = buffer.length
        render()
        return
      }

      // ๆ™ฎ้€šๅญ—็ฌฆ
      if (str && str.length === 1 && !key.ctrl && !key.meta) {
        const before = buffer.slice(0, cursorPos)
        const after = buffer.slice(cursorPos)
        buffer = before + str + after
        cursorPos++
        render()
      }
    }

    stdin.on('keypress', onKeypress)
  })
}

/** readline ๅ•่กŒ่พ“ๅ…ฅๅทฅๅ…ท */
function askQuestion(query: string): Promise<string> {
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  })
  return new Promise((resolve) => {
    rl.question(query, (answer) => {
      rl.close()
      resolve(answer.trim())
    })
  })
}

/** ๅฏ†็ ่พ“ๅ…ฅ๏ผˆ้š่—๏ผ‰ */
function askPassword(query: string): Promise<string> {
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  })
  return new Promise((resolve) => {
    if (process.stdout.isTTY) {
      const stdin = process.stdin
      const wasRaw = stdin.isRaw
      if (wasRaw !== undefined) stdin.setRawMode?.(true)
      
      process.stdout.write(query)
      let password = ''
      
      const onData = (char: Buffer) => {
        const c = char.toString()
        if (c === '\n' || c === '\r') {
          if (wasRaw !== undefined) stdin.setRawMode?.(wasRaw)
          stdin.removeListener('data', onData)
          rl.close()
          process.stdout.write('\n')
          resolve(password)
        } else if (c === '\u007F' || c === '\b') {
          if (password.length > 0) {
            password = password.slice(0, -1)
            process.stdout.write('\b \b')
          }
        } else if (c === '\u0003') {
          // Ctrl+C - ๅ–ๆถˆๅฏ†็ ่พ“ๅ…ฅ๏ผŒ่ฟ”ๅ›ž็ฉบๅญ—็ฌฆไธฒ
          if (wasRaw !== undefined) stdin.setRawMode?.(wasRaw)
          stdin.removeListener('data', onData)
          rl.close()
          process.stdout.write('\n')
          resolve('')
        } else {
          password += c
          process.stdout.write('*')
        }
      }
      stdin.on('data', onData)
    } else {
      rl.question(query, (answer) => {
        rl.close()
        resolve(answer.trim())
      })
    }
  })
}

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// ็”จๆˆท็™ปๅฝ•/ๆณจๅ†Œไบคไบ’ๆต็จ‹ โ†’ ๅทฒๆๅ–่‡ณ commands/auth.ts
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•



// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// ๅทฅไฝœๅŒบๅฟซ็…ง๏ผˆๆ–ญ็‚น็ปญไผ ๏ผ‰
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// ไธปๆต็จ‹
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

async function main() {
  // ๅˆๅง‹ๅŒ–ๅ›ฝ้™…ๅŒ–็ณป็ปŸ๏ผˆๅฟ…้กปๅœจไปปไฝ•่พ“ๅ‡บไน‹ๅ‰๏ผ‰
  initI18n()
  
  // P4 #2.10: ็กฎไฟๅ‰ช่ดดๆฟๆ–‡ไปถๆƒ้™ๅฎ‰ๅ…จ
  ensureClipboardSecure()
  
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
  // ไฟกๅทๅค„็†ๅ™จ - ็กฎไฟ้€€ๅ‡บๆ—ถไฟๅญ˜ๅทฅไฝœๅŒบ
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
  let onExit: (() => Promise<void>) | null = null
  let isExiting = false
  
  const handleSignal = async (signal: string) => {
    if (isExiting) return
    isExiting = true
    console.log(`\n  ${MUTED}${t('signal.received', { signal })}${color.reset}`)
    if (onExit) {
      await onExit()
    }
    process.exit(0)
  }
  
  process.on('SIGINT', () => handleSignal('SIGINT'))
  process.on('SIGTERM', () => handleSignal('SIGTERM'))
  process.on('SIGHUP', () => handleSignal('SIGHUP'))
  
  const { values } = parseArgs({
    options: {
      prompt: { type: 'string', short: 'p' },
      continue: { type: 'boolean', short: 'c' },
      sessions: { type: 'boolean', short: 's' },
      model: { type: 'string', short: 'm' },
      version: { type: 'boolean', short: 'v' },
      help: { type: 'boolean', short: 'h' },
      pavr: { type: 'boolean', short: 'P', default: true },      // ๅฏ็”จ PAVR ๅพช็Žฏ
      'no-pavr': { type: 'boolean', short: 'N', default: false }, // ็ฆ็”จ PAVR ๅพช็Žฏ
    },
    strict: false,
  })

  // ่ฎพ็ฝฎ PAVR ๆจกๅผ
  if (values['no-pavr']) {
    setGlobalPAVREnabled(false)
  } else if (values.pavr) {
    setGlobalPAVREnabled(true)
  }

  if (values.help) { printHelp(VERSION, color, BRAND, MUTED, ACCENT, SUCCESS, ERROR, WARN); return }
  if (values.version) { console.log(`CmdCode V${VERSION}`); return }

  // ๐Ÿ”ง ็ฎก้“่พ“ๅ…ฅๆฃ€ๆต‹๏ผšstdin ไธๆ˜ฏ TTY ๆ—ถ๏ผŒ่ฏปๅ–ๅ…จ้ƒจๅ†…ๅฎนไฝœไธบ -p ๆจกๅผ่พ“ๅ…ฅ
  if (!process.stdin.isTTY && !values.prompt) {
    const pipedInput = await new Promise<string>((resolve) => {
      let data = ''
      process.stdin.setEncoding('utf-8')
      process.stdin.on('data', (chunk: string) => { data += chunk })
      process.stdin.on('end', () => resolve(data.trim()))
      // ่ถ…ๆ—ถไฟๆŠค๏ผš5็ง’ๅ†…ๆฒก่ฏปๅฎŒๅฐฑๅ…ˆๆไบค
      setTimeout(() => resolve(data.trim()), 5000)
    })
    if (pipedInput.length > 0) {
      values.prompt = pipedInput
    }
  }

  let config = loadConfig()
  if (values.model) config.model = values.model as string

  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
  // ๆ ธๅฟƒๆ”น่ฟ›1๏ผš็”จๆˆท็™ปๅฝ•/ๆณจๅ†Œ
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
  const userInfo = await userAuthFlow(VERSION, color, BRAND, ACCENT, MUTED, SUCCESS, ERROR, WARN, askQuestion, askPassword)

  // ่ฎพ็ฝฎ็”จๆˆทไธ“ๅฑžๅทฅไฝœๅŒบๅˆฐๆฒ™็ฎฑ็ณป็ปŸ
  setUserWorkspace(userInfo.workspaceDir)
  setUsername(userInfo.username)

  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
  // ๅˆๅง‹ๅŒ–ๅ‘้‡่ฎฐๅฟ†็ณป็ปŸ
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
  try {
    initMemorySystem()
    // ๆณจ๏ผšๅฏ†้’ฅๅบ“็Šถๆ€ๆฃ€ๆŸฅๅทฒ็งป่‡ณๅ„ๅŠŸ่ƒฝๆจกๅ—ๅ†…้ƒจๆŒ‰้œ€ๆ‰ง่กŒ
  } catch (e) {
    console.log(`  ${MUTED}${t('model.memory_init_failed')}${color.reset}`)
  }

  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
  // ๆ ธๅฟƒๆ”น่ฟ›2๏ผšๆ–ญ็‚น็ปญไผ  - ๆขๅคไธŠๆฌกๅทฅไฝœๅŒบ
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
  let restoredMessages: Message[] | undefined
  
  if (!values.prompt) {
    process.stdout.write(`  ${MUTED}${t("session.checking_snapshot")}${color.reset}`)
    try {
      const snapshot = await restoreWorkspaceSnapshot(userInfo)
      process.stdout.write('\r' + ' '.repeat(30) + '\r')
      if (snapshot) {
        const msgs = restoreWorkspaceFromSnapshot(snapshot, userInfo, { WARN, color })
        if (msgs && msgs.length > 0) {
          restoredMessages = msgs
          // ไธๅ†ๆ˜พ็คบๆขๅคไฟกๆฏ๏ผŒๅœจ printBanner ็ปŸไธ€ๅฑ•็คบ
        }
      } else {
        // ๆฒกๆœ‰ๅฟซ็…ง๏ผŒ้™้ป˜่ทณ่ฟ‡
      }
    } catch {
      process.stdout.write('\r' + ' '.repeat(30) + '\r')
      console.log(`  ${MUTED}${t('model.snapshot_check_failed')}${color.reset}`)
    }
  }

  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
  // ๆ ธๅฟƒๆ”น่ฟ›3๏ผšๆจกๅž‹้…็ฝฎ๏ผˆ็”จๆˆท่‡ชๅฎšไน‰ > ็ณป็ปŸ้ป˜่ฎค๏ผ‰
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
  
  // ๅ…ˆๆฃ€ๆŸฅ็”จๆˆทๆ˜ฏๅฆๆœ‰่‡ชๅฎšไน‰ๆจกๅž‹้…็ฝฎ
  const userDefaultModel = getUserDefaultModel(userInfo.username)
  let modelOnline = false
  let modelLatencyMs: number | undefined
  
  if (userDefaultModel) {
    // ไฝฟ็”จ็”จๆˆท่‡ชๅฎšไน‰ๆจกๅž‹
    config = {
      apiKey: userDefaultModel.apiKey,
      baseUrl: userDefaultModel.baseUrl,
      model: userDefaultModel.model,
      timeoutMs: config.timeoutMs,
    }
    
    process.stdout.write(`  ${ACCENT}${t("model.connecting")} ${userDefaultModel.name} ...${color.reset}`)
    const testResult = await testConnection(config.baseUrl, config.apiKey, config.model)
    process.stdout.write('\r' + ' '.repeat(40) + '\r')
    
    if (testResult.success) {
      modelOnline = true
      modelLatencyMs = testResult.latencyMs
    } else {
      console.log(`  ${WARN}${t("model.test_failed")} ${testResult.error}${color.reset}`)
      console.log(`  ${MUTED}${t('model.manage_hint')}${color.reset}`)
    }
  } else if (!config.apiKey) {
    // ็ณป็ปŸ้ป˜่ฎค้…็ฝฎไนŸๆฒกๆœ‰๏ผŒไบคไบ’ๅผ้…็ฝฎ
    const setup = await interactiveModelSetup(userInfo.username, color, BRAND, MUTED, SUCCESS, ERROR, WARN, ACCENT, askQuestion)
    if (!setup) {
      console.log('')
      console.log(`  ${ACCENT}${t('model.no_config_exit')}${color.reset}`)
      process.exit(0)
    }
    
    // ๅฆ‚ๆžœๆ˜ฏ็”จๆˆท่‡ชๅฎšไน‰้…็ฝฎ๏ผŒๅญ˜ๅ‚จๅˆฐ็”จๆˆท็›ฎๅฝ•
    if (setup.saveToUser) {
      createUserModel(userInfo.username, {
        name: setup.name || setup.model,
        model: setup.model,
        baseUrl: setup.baseUrl,
        apiKey: setup.apiKey,
        note1: setup.note1,
        note2: setup.note2,
      })
      console.log(`  ${SUCCESS}${t('model.saved')}${color.reset}`)
    } else {
      // ไฟๅญ˜ไธบ็ณป็ปŸ้ป˜่ฎค
      saveKeys(setup.apiKey, setup.baseUrl)
      updateAppConfig({ model: setup.model })
    }
    
    config = {
      apiKey: setup.apiKey,
      baseUrl: setup.baseUrl,
      model: setup.model,
      timeoutMs: config.timeoutMs,
    }

    console.log('')
    console.log(`  ${ACCENT}${t('model.encrypted_saved')}${color.reset}`)
    console.log('')
  } else {
    // ไฝฟ็”จ็ณป็ปŸ้ป˜่ฎค้…็ฝฎ
    process.stdout.write(`  ${ACCENT}${t("model.connecting")} ${config.model} ...${color.reset}`)
    const testResult = await testConnection(config.baseUrl, config.apiKey, config.model)
    process.stdout.write('\r' + ' '.repeat(40) + '\r')
    if (testResult.success) {
      modelOnline = true
      modelLatencyMs = testResult.latencyMs
    } else {
      console.log(`  ${WARN}${t("model.test_failed")} ${testResult.error}${color.reset}`)
      console.log(`  ${MUTED}${t('model.possible_reasons')}${color.reset}`)
      console.log(`  ${MUTED}${t('model.manage_hint2')}${color.reset}`)
      console.log('')
    }
  }

  // ๅˆ—ๅ‡บไผš่ฏ
  if (values.sessions) {
    const sessions = listSessions()
    if (sessions.length === 0) {
      console.log(`  ${MUTED}ๆš‚ๆ— ไผš่ฏ่ฎฐๅฝ•${color.reset}`)
    } else {
      console.log('')
      console.log(`  ${color.bold}${t('session.list_title')}${color.reset}`)
      console.log(`  ${MUTED}โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€${color.reset}`)
      for (const s of sessions) {
        console.log(`    ${BRAND}${s.id}${color.reset}`)
        console.log(`    ${MUTED}${t('session.messages')}: ${s.messageCount} ยท ${t('session.updated')}: ${s.updatedAt.slice(0, 19)}${color.reset}`)
        if (s.preview) console.log(`    ${MUTED}${t('session.preview')}: ${s.preview}${color.reset}`)
        console.log('')
      }
    }
    return
  }

  // ๅ•ๆฌกๆ‰ง่กŒๆจกๅผ
  if (values.prompt) {
    const prompt = values.prompt as string
    let messages: Message[] | undefined

    if (values.continue) {
      const sessionId = getLatestSessionId()
      if (sessionId) {
        messages = loadSession(sessionId) || undefined
        if (messages) {
          console.log(`  ${MUTED}${t('session.restored')} ${sessionId} (${messages.length} ${t('session.messages_count')})${color.reset}`)
        }
      } else {
        console.log(`  ${MUTED}${t('session.no_restore')}${color.reset}`)
      }
    }

    // ๆ”ฏๆŒ CMD_WORKSPACE ็Žฏๅขƒๅ˜้‡่ฆ†็›–ๅทฅไฝœ็›ฎๅฝ•
    const cmdWorkspace = process.env.CMD_WORKSPACE
    // Issue 10: Warn about potential path conflict
    if (values.continue && cmdWorkspace) {
      console.log(`  ${WARN}โš ๏ธ --continue + CMD_WORKSPACE: workspace changed, session files may be at original path${color.reset}`)
    }
    // ๐Ÿš€ ไฝฟ็”จๅทฅๅŽ‚ๅ‡ฝๆ•ฐๅˆ›ๅปบๅผ•ๆ“Ž๏ผˆๆ”ฏๆŒ PAVR ๅพช็Žฏ๏ผ‰
    const engine = messages
      ? createChatEngineFromHistory(messages, undefined, config, cmdWorkspace)
      : createChatEngine(undefined, config, undefined, cmdWorkspace)

    // ๅฆ‚ๆžœ่ฎพ็ฝฎไบ† CMD_WORKSPACE๏ผŒๅˆ‡ๆขๅˆฐ่ฏฅ็›ฎๅฝ•
    if (cmdWorkspace) {
      process.chdir(cmdWorkspace)
      setUserWorkspace(cmdWorkspace)
    }

    try {
      const finalText = await engine.chat(prompt)
      const sessionId = saveSession(engine.getHistory())
      console.log(`\n  ${MUTED}${t('session.saved')} ${sessionId}${color.reset}`)
      
      // ใ€่‡ชๅŠจไฟฎๅคใ€‘่งฃๆžๅคงๆจกๅž‹่พ“ๅ‡บ๏ผŒๆๅ–ไปฃ็ ๅ—ๅนถไฟๅญ˜ไธบๆ–‡ไปถ๏ผˆ่‹ฅๅคงๆจกๅž‹ๆœช่ฐƒ็”จ file_write๏ผ‰
      const codeBlockRegex = /```(\w+)?\s*\n([\s\S]*?)```/g
      let match
      let fileSaved = false
      while ((match = codeBlockRegex.exec(finalText)) !== null) {
        const lang = match[1] || 'txt'
        const code = match[2].trim()
        if (code.length > 0) {
          const ext = lang === 'python' ? 'py' : lang === 'javascript' ? 'js' : lang === 'typescript' ? 'ts' : lang
          const fileName = `solution.${ext}`
          try {
            writeFileSync(fileName, code, 'utf-8')
            console.log(`  โœ… Auto-saved code to ${fileName}`)
            fileSaved = true
          } catch (e: any) {
            console.error(`  โŒ Failed to save ${fileName}: ${e.message}`)
          }
        }
      }
      if (!fileSaved) {
        console.warn(`  โš ๏ธ No code block found in output.`)
      }
      
      process.exit(0)
    } catch (e: any) {
      if (e.status === 429) {
        console.error(`\n  ${WARN}${t("error.429_single")}${color.reset}`)
        console.error(`  ${MUTED}${t("error.switch_model_hint")}${color.reset}`)
      } else if (e.code === 'ECONNREFUSED' || e.code === 'ENOTFOUND' || e.code === 'ETIMEDOUT') {
        console.error(`\n  ${ERROR}${t("error.network_single")} ${e.message}${color.reset}`)
        console.error(`  ${MUTED}${t("error.check_network_hint")}${color.reset}`)
      } else {
        console.error(`\n  ${ERROR}${t("error.general_single")} ${e.message}${color.reset}`)
        console.error(`  ${MUTED}${t("error.model_hint_single")}${color.reset}`)
      }
      process.exit(1)
    }
    return
  }

  // ไบคไบ’REPLๆจกๅผ
  // ่Žทๅ–ๆจกๅž‹ๆ˜พ็คบๅ็งฐ
  const modelName = userDefaultModel?.name || config.model
  
  // ่ฎก็ฎ—ๅญ˜ๅ‚จ็ฉบ้—ดๅ’Œๅฏน่ฏๅކๅฒ
  const usedBytes = getDirSize(userInfo.workspaceDir)
  const historyCount = restoredMessages?.length || countHistoryMessages(userInfo.workspaceDir)
  
  printBanner(userInfo, { name: modelName, model: config.model, online: modelOnline, latencyMs: modelLatencyMs }, historyCount, usedBytes, { BRAND, ACCENT, MUTED, SUCCESS, WARN, color })

  // ๆ˜พ็คบ PAVR ็Šถๆ€
  console.log(`  ${isGlobalPAVREnabled() ? ACCENT : MUTED}๐Ÿš€ PAVR: ${isGlobalPAVREnabled() ? 'ๅฏ็”จ' : '็ฆ็”จ'}${color.reset}`)

  // ่ฎพ็ฝฎๅทฅไฝœๅŒบไธบ็”จๆˆทไธ“ๅฑž็›ฎๅฝ•
  process.chdir(userInfo.workspaceDir)

  // ๐Ÿš€ ไฝฟ็”จๅทฅๅŽ‚ๅ‡ฝๆ•ฐๅˆ›ๅปบๅผ•ๆ“Ž๏ผˆๆ”ฏๆŒ PAVR ๅพช็Žฏ๏ผ‰
  const engine = restoredMessages
    ? createChatEngineFromHistory(restoredMessages, undefined, config, userInfo.workspaceDir)
    : createChatEngine(undefined, config, undefined, userInfo.workspaceDir)

  // ้ป˜่ฎค็ณป็ปŸๆ็คบ่ฏ๏ผˆ็”จๆˆท่‡ชๅฎšไน‰ > ้ป˜่ฎคๆจกๆฟ๏ผŒ็ปŸไธ€ไปŽ secrets.enc ่ฏปๅ–๏ผ‰
  const customPrompt = getSystemPrompt()
  const DEFAULT_SYSTEM_PROMPT = customPrompt || SYSTEM_PROMPT_TEMPLATE + `

Current user workspace: ${process.cwd()}`

  // โ†’ REPL ไธปๅพช็Žฏๅทฒๆๅ–่‡ณ commands/repl.ts
  await replLoop({
    userInfo,
    engine,
    config,
    modelOnline,
    modelLatencyMs,
    userDefaultModel,
    defaultSystemPrompt: DEFAULT_SYSTEM_PROMPT,
    color,
    BRAND,
    ACCENT,
    MUTED,
    SUCCESS,
    ERROR,
    WARN,
    askREPL,
    askQuestion,
    askPassword,
    registerExitHandler: (handler) => { onExit = handler },
  })
}

main().catch(e => {
  console.error('Fatal:', e.message)
  process.exit(1)
})