boxdown 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +115 -0
  3. package/assets/devcontainer/README.md +177 -0
  4. package/assets/devcontainer/devcontainer.json +83 -0
  5. package/assets/devcontainer/hooks/initialize.sh +55 -0
  6. package/assets/devcontainer/hooks/post-create.sh +67 -0
  7. package/assets/devcontainer/hooks/post-start.sh +34 -0
  8. package/assets/devcontainer/ssh-config-install.sh +172 -0
  9. package/assets/devcontainer/start.sh +485 -0
  10. package/assets/devcontainer/utils/codex-cli-update.sh +9 -0
  11. package/assets/devcontainer/utils/coding-agent-cli-update.sh +360 -0
  12. package/assets/devcontainer/utils/deps-install.sh +87 -0
  13. package/assets/devcontainer/utils/ssh-bootstrap.sh +200 -0
  14. package/dist/bin/cli.cjs +12 -0
  15. package/dist/bin/cli.d.cts +1 -0
  16. package/dist/bin/cli.d.mts +1 -0
  17. package/dist/bin/cli.mjs +14 -0
  18. package/dist/bin/cli.mjs.map +1 -0
  19. package/dist/main-BuEptwlL.cjs +1707 -0
  20. package/dist/main-ZFTrSVgt.mjs +1685 -0
  21. package/dist/main-ZFTrSVgt.mjs.map +1 -0
  22. package/dist/main.cjs +7 -0
  23. package/dist/main.d.cts +24 -0
  24. package/dist/main.d.cts.map +1 -0
  25. package/dist/main.d.mts +24 -0
  26. package/dist/main.d.mts.map +1 -0
  27. package/dist/main.mjs +3 -0
  28. package/docs/README.md +34 -0
  29. package/docs/architecture.md +75 -0
  30. package/docs/conventions.md +29 -0
  31. package/docs/development.md +59 -0
  32. package/docs/features/README.md +14 -0
  33. package/docs/features/generated-config-and-state.md +64 -0
  34. package/docs/features/github-auth-refresh.md +64 -0
  35. package/docs/features/lifecycle.md +64 -0
  36. package/docs/features/ssh-config-and-proxy.md +60 -0
  37. package/docs/features/start-and-shell.md +83 -0
  38. package/docs/testing.md +63 -0
  39. package/docs/todo.md +170 -0
  40. package/package.json +128 -0
  41. package/src/bin/cli.ts +9 -0
  42. package/src/coding-agents.ts +22 -0
  43. package/src/config.ts +81 -0
  44. package/src/constants.ts +9 -0
  45. package/src/devcontainer-cli.ts +57 -0
  46. package/src/devcontainer.ts +394 -0
  47. package/src/doctor.ts +139 -0
  48. package/src/github-git-auth.ts +143 -0
  49. package/src/jsonc.ts +123 -0
  50. package/src/list.ts +102 -0
  51. package/src/main.ts +362 -0
  52. package/src/metadata.ts +84 -0
  53. package/src/paths.ts +117 -0
  54. package/src/process.ts +94 -0
  55. package/src/shell.ts +60 -0
  56. package/src/ssh-config.ts +113 -0
  57. package/src/ssh-key.ts +52 -0
  58. package/src/status.ts +327 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main-ZFTrSVgt.mjs","names":[],"sources":["../src/coding-agents.ts","../src/constants.ts","../src/devcontainer-cli.ts","../src/process.ts","../src/doctor.ts","../src/jsonc.ts","../src/shell.ts","../src/config.ts","../src/github-git-auth.ts","../src/ssh-key.ts","../src/ssh-config.ts","../src/status.ts","../src/devcontainer.ts","../src/list.ts","../src/metadata.ts","../src/paths.ts","../src/main.ts"],"sourcesContent":["export type CodingAgentCli = 'codex' | 'opencode' | 'claude' | 'antigravity'\nexport type CodingAgentCommandAlias = CodingAgentCli | 'cc'\n\nconst CODING_AGENT_ALIASES: Record<CodingAgentCommandAlias, CodingAgentCli> = {\n codex: 'codex',\n opencode: 'opencode',\n claude: 'claude',\n cc: 'claude',\n antigravity: 'antigravity'\n}\n\nexport function codingAgentFromCommand (command: string): CodingAgentCli | undefined {\n return CODING_AGENT_ALIASES[command as CodingAgentCommandAlias]\n}\n\nexport function codingAgentBinary (agent: CodingAgentCli): string {\n if (agent === 'antigravity') {\n return 'agy'\n }\n\n return agent\n}\n","export const DEVCONTAINER_CLI_VERSION = '0.84.1'\n\nexport const BOXDOWN_CONTAINER_DEVCONTAINER_DIR = '/opt/boxdown/devcontainer'\nexport const BOXDOWN_CONTAINER_STATE_DIR = '/opt/boxdown/state'\nexport const BOXDOWN_CONTAINER_SSH_DIR = `${BOXDOWN_CONTAINER_STATE_DIR}/ssh`\nexport const BOXDOWN_CONTAINER_SSH_PUBLIC_KEY_PATH = `${BOXDOWN_CONTAINER_SSH_DIR}/id_ed25519.pub`\n\nexport const PACKAGE_NAME = 'boxdown'\n","import { existsSync, readFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { dirname, join, resolve } from 'node:path'\n\nimport { DEVCONTAINER_CLI_VERSION } from './constants.ts'\nimport type { WorkspaceContext } from './paths.ts'\n\nexport interface DevcontainerCliCommand {\n command: string\n argsPrefix: string[]\n path: string\n version: string\n}\n\ninterface PackageJson {\n bin?: string | Record<string, string>\n version?: string\n}\n\nfunction devcontainerBinFromPackageJson (packageJson: PackageJson): string | undefined {\n return typeof packageJson.bin === 'string' ? packageJson.bin : packageJson.bin?.devcontainer\n}\n\nexport function resolveDevcontainerCli (context: WorkspaceContext): DevcontainerCliCommand {\n const requireFromBoxdown = createRequire(join(context.packageRoot, 'package.json'))\n let packageJsonPath: string\n\n try {\n packageJsonPath = requireFromBoxdown.resolve('@devcontainers/cli/package.json')\n } catch (error) {\n throw new Error(`Boxdown's packaged @devcontainers/cli dependency is missing. Reinstall boxdown so @devcontainers/cli@${DEVCONTAINER_CLI_VERSION} is available.`, { cause: error })\n }\n\n const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as PackageJson\n const bin = devcontainerBinFromPackageJson(packageJson)\n\n if (packageJson.version !== DEVCONTAINER_CLI_VERSION) {\n throw new Error(`Boxdown expected @devcontainers/cli@${DEVCONTAINER_CLI_VERSION} but resolved ${packageJson.version ?? 'an unknown version'}.`)\n }\n\n if (bin === undefined) {\n throw new Error('Boxdown resolved @devcontainers/cli, but the package does not expose a devcontainer binary.')\n }\n\n const cliPath = resolve(dirname(packageJsonPath), bin)\n\n if (!existsSync(cliPath)) {\n throw new Error(`Boxdown resolved @devcontainers/cli, but its devcontainer binary is missing: ${cliPath}`)\n }\n\n return {\n command: process.execPath,\n argsPrefix: [cliPath],\n path: cliPath,\n version: packageJson.version\n }\n}\n","import { spawn } from 'node:child_process'\n\nexport interface BufferedCommandOptions {\n cwd?: string\n env?: NodeJS.ProcessEnv\n input?: string\n mirrorStdout?: 'stdout' | 'stderr' | false\n mirrorStderr?: 'stdout' | 'stderr' | false\n}\n\nexport interface CommandResult {\n code: number\n stdout: string\n stderr: string\n}\n\nfunction mergedEnv (env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n return {\n ...process.env,\n ...(env ?? {})\n }\n}\n\nfunction writeChunk (target: 'stdout' | 'stderr' | false, chunk: Buffer): void {\n if (target === 'stdout') {\n process.stdout.write(chunk)\n } else if (target === 'stderr') {\n process.stderr.write(chunk)\n }\n}\n\nexport function runBuffered (command: string, args: string[], options: BufferedCommandOptions = {}): Promise<CommandResult> {\n return new Promise((resolve) => {\n const child = spawn(command, args, {\n cwd: options.cwd,\n env: mergedEnv(options.env),\n stdio: ['pipe', 'pipe', 'pipe']\n })\n\n const stdoutChunks: Buffer[] = []\n const stderrChunks: Buffer[] = []\n\n child.stdout.on('data', (chunk: Buffer) => {\n stdoutChunks.push(chunk)\n writeChunk(options.mirrorStdout ?? 'stdout', chunk)\n })\n\n child.stderr.on('data', (chunk: Buffer) => {\n stderrChunks.push(chunk)\n writeChunk(options.mirrorStderr ?? 'stderr', chunk)\n })\n\n child.on('error', (error) => {\n resolve({\n code: 127,\n stdout: Buffer.concat(stdoutChunks).toString('utf8'),\n stderr: `${Buffer.concat(stderrChunks).toString('utf8')}${error.message}\\n`\n })\n })\n\n child.on('close', (code) => {\n resolve({\n code: code ?? 1,\n stdout: Buffer.concat(stdoutChunks).toString('utf8'),\n stderr: Buffer.concat(stderrChunks).toString('utf8')\n })\n })\n\n if (options.input !== undefined) {\n child.stdin.end(options.input)\n } else {\n child.stdin.end()\n }\n })\n}\n\nexport function runInteractive (command: string, args: string[], options: Pick<BufferedCommandOptions, 'cwd' | 'env'> = {}): Promise<number> {\n return new Promise((resolve) => {\n const child = spawn(command, args, {\n cwd: options.cwd,\n env: mergedEnv(options.env),\n stdio: 'inherit'\n })\n\n child.on('error', (error) => {\n process.stderr.write(`${error.message}\\n`)\n resolve(127)\n })\n\n child.on('close', (code) => {\n resolve(code ?? 1)\n })\n })\n}\n","import { existsSync } from 'node:fs'\n\nimport { resolveDevcontainerCli } from './devcontainer-cli.ts'\nimport type { WorkspaceContext } from './paths.ts'\nimport { runBuffered } from './process.ts'\n\nexport type DoctorLevel = 'ok' | 'fail' | 'warn'\n\nexport interface DoctorCheck {\n name: string\n level: DoctorLevel\n message: string\n}\n\nfunction nodeVersionPasses (version: string): boolean {\n const major = Number.parseInt(version.split('.')[0] ?? '', 10)\n return Number.isInteger(major) && major >= 24\n}\n\nfunction check (name: string, pass: boolean, okMessage: string, failMessage: string): DoctorCheck {\n return {\n name,\n level: pass ? 'ok' : 'fail',\n message: pass ? okMessage : failMessage\n }\n}\n\nasync function commandWorks (command: string, args: string[]): Promise<boolean> {\n const result = await runBuffered(command, args, {\n mirrorStdout: false,\n mirrorStderr: false\n })\n\n return result.code === 0\n}\n\nasync function commandExists (command: string, args: string[]): Promise<boolean> {\n const result = await runBuffered(command, args, {\n mirrorStdout: false,\n mirrorStderr: false\n })\n\n return result.code !== 127\n}\n\nexport async function runDoctorChecks (context: WorkspaceContext): Promise<DoctorCheck[]> {\n const checks: DoctorCheck[] = []\n const nodeVersion = process.versions.node\n\n checks.push(check(\n 'node',\n nodeVersionPasses(nodeVersion),\n `Node ${nodeVersion}`,\n `Node ${nodeVersion}; expected >=24.0.0`\n ))\n\n checks.push(check(\n 'devcontainers-cli',\n await packagedDevcontainerCliWorks(context),\n 'Packaged @devcontainers/cli is available',\n 'Packaged @devcontainers/cli is required but was not available'\n ))\n\n checks.push(check(\n 'docker-cli',\n await commandWorks('docker', ['--version']),\n 'Docker CLI is available',\n 'Docker CLI is required but was not available'\n ))\n\n checks.push(check(\n 'docker-daemon',\n await commandWorks('docker', ['info']),\n 'Docker daemon is reachable',\n 'Docker daemon is required but was not reachable'\n ))\n\n checks.push(check(\n 'ssh',\n await commandExists('ssh', ['-V']),\n 'ssh is available',\n 'ssh is required but was not available'\n ))\n\n checks.push(check(\n 'ssh-keygen',\n await commandExists('ssh-keygen', ['-?']),\n 'ssh-keygen is available',\n 'ssh-keygen is required but was not available'\n ))\n\n checks.push(check(\n 'assets',\n existsSync(context.assetsDevcontainerDir),\n `Devcontainer assets found at ${context.assetsDevcontainerDir}`,\n `Missing Boxdown devcontainer assets: ${context.assetsDevcontainerDir}`\n ))\n\n if (await commandWorks('gh', ['--version'])) {\n const ghAuth = await commandWorks('gh', ['auth', 'status', '--hostname', 'github.com'])\n checks.push({\n name: 'gh-auth',\n level: ghAuth ? 'ok' : 'warn',\n message: ghAuth ? 'GitHub CLI auth is available' : 'GitHub CLI is available but not authenticated'\n })\n } else {\n checks.push({\n name: 'gh',\n level: 'warn',\n message: 'GitHub CLI is optional and was not available'\n })\n }\n\n return checks\n}\n\nasync function packagedDevcontainerCliWorks (context: WorkspaceContext): Promise<boolean> {\n try {\n const cli = resolveDevcontainerCli(context)\n return await commandWorks(cli.command, [...cli.argsPrefix, '--version'])\n } catch {\n return false\n }\n}\n\nexport function doctorHasFailures (checks: DoctorCheck[]): boolean {\n return checks.some((item) => item.level === 'fail')\n}\n\nexport function formatDoctorText (checks: DoctorCheck[]): string {\n const lines = ['Boxdown doctor', '']\n\n for (const item of checks) {\n lines.push(`[${item.level}] ${item.name}: ${item.message}`)\n }\n\n lines.push('', doctorHasFailures(checks) ? 'Result: failed' : 'Result: ok')\n return `${lines.join('\\n')}\\n`\n}\n","export function stripJsonComments (input: string): string {\n let output = ''\n let inString = false\n let escaped = false\n\n for (let index = 0; index < input.length; index += 1) {\n const char = input[index]\n const next = input[index + 1]\n\n if (char === undefined) {\n break\n }\n\n if (inString) {\n output += char\n\n if (escaped) {\n escaped = false\n } else if (char === '\\\\') {\n escaped = true\n } else if (char === '\"') {\n inString = false\n }\n\n continue\n }\n\n if (char === '\"') {\n inString = true\n output += char\n continue\n }\n\n if (char === '/' && next === '/') {\n while (index < input.length && input[index] !== '\\n') {\n index += 1\n }\n\n if (input[index] === '\\n') {\n output += '\\n'\n }\n\n continue\n }\n\n if (char === '/' && next === '*') {\n index += 2\n\n while (index < input.length) {\n if (input[index] === '\\n') {\n output += '\\n'\n }\n\n if (input[index] === '*' && input[index + 1] === '/') {\n index += 1\n break\n }\n\n index += 1\n }\n\n continue\n }\n\n output += char\n }\n\n return output\n}\n\nexport function stripTrailingCommas (input: string): string {\n let output = ''\n let inString = false\n let escaped = false\n\n for (let index = 0; index < input.length; index += 1) {\n const char = input[index]\n\n if (char === undefined) {\n break\n }\n\n if (inString) {\n output += char\n\n if (escaped) {\n escaped = false\n } else if (char === '\\\\') {\n escaped = true\n } else if (char === '\"') {\n inString = false\n }\n\n continue\n }\n\n if (char === '\"') {\n inString = true\n output += char\n continue\n }\n\n if (char === ',') {\n let nextIndex = index + 1\n\n while (/\\s/.test(input[nextIndex] ?? '')) {\n nextIndex += 1\n }\n\n if (input[nextIndex] === '}' || input[nextIndex] === ']') {\n continue\n }\n }\n\n output += char\n }\n\n return output\n}\n\nexport function parseJsonc <T> (input: string): T {\n return JSON.parse(stripTrailingCommas(stripJsonComments(input))) as T\n}\n","export function shellQuote (value: string): string {\n if (value.length === 0) {\n return \"''\"\n }\n\n return `'${value.replaceAll(\"'\", \"'\\\\''\")}'`\n}\n\nexport function sshConfigQuote (value: string): string {\n return `\"${value.replaceAll('\\\\', '\\\\\\\\').replaceAll('\"', '\\\\\"')}\"`\n}\n\nexport const DEFAULT_TTY_MAX_COLUMNS = 120\n\nexport function interactiveShellEnvArgs (env: NodeJS.ProcessEnv = process.env): string[] {\n return [\n `TERM=${env.TERM ?? 'xterm-256color'}`,\n 'COLORTERM=truecolor',\n `BOXDOWN_TTY_NORMALIZE=${env.BOXDOWN_TTY_NORMALIZE ?? '1'}`,\n `BOXDOWN_TTY_MAX_COLUMNS=${env.BOXDOWN_TTY_MAX_COLUMNS ?? String(DEFAULT_TTY_MAX_COLUMNS)}`\n ]\n}\n\nfunction interactiveTtySetupScript (): string {\n return [\n 'if [ -t 0 ]; then',\n ' case \"${BOXDOWN_TTY_NORMALIZE:-1}\" in',\n ' 0|false|FALSE|no|NO|off|OFF) ;;',\n ' *)',\n ' max_columns=\"${BOXDOWN_TTY_MAX_COLUMNS:-120}\"',\n ' if [ \"$max_columns\" -gt 0 ] 2>/dev/null; then',\n ' set -- $(stty size 2>/dev/null || true)',\n ' rows=\"${1:-}\"',\n ' columns=\"${2:-}\"',\n ' if [ -n \"$columns\" ] && [ \"$columns\" -gt \"$max_columns\" ] 2>/dev/null; then',\n ' stty cols \"$max_columns\" 2>/dev/null || true',\n ' export COLUMNS=\"$max_columns\"',\n ' if [ -n \"$rows\" ]; then export LINES=\"$rows\"; fi',\n ' printf \"Boxdown: terminal width clamped to %s columns (was %s). Set BOXDOWN_TTY_NORMALIZE=0 to disable.\\\\n\" \"$max_columns\" \"$columns\" >&2',\n ' fi',\n ' fi',\n ' ;;',\n ' esac',\n 'fi',\n ].join('\\n')\n}\n\nexport function interactiveShellScript (): string {\n return [\n interactiveTtySetupScript(),\n 'exec bash -i'\n ].join('\\n')\n}\n\nexport function interactiveCommandScript (): string {\n return [\n interactiveTtySetupScript(),\n 'exec \"$@\"'\n ].join('\\n')\n}\n","import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport {\n BOXDOWN_CONTAINER_DEVCONTAINER_DIR,\n BOXDOWN_CONTAINER_SSH_DIR,\n BOXDOWN_CONTAINER_SSH_PUBLIC_KEY_PATH\n} from './constants.ts'\nimport { parseJsonc } from './jsonc.ts'\nimport type { WorkspaceContext } from './paths.ts'\nimport { shellQuote } from './shell.ts'\n\nexport interface DevcontainerConfig {\n name?: string\n mounts?: string[]\n containerEnv?: Record<string, string>\n runArgs?: string[]\n initializeCommand?: string\n postCreateCommand?: string\n postStartCommand?: string\n [key: string]: unknown\n}\n\nexport function readBaseDevcontainerConfig (assetsDevcontainerDir: string): DevcontainerConfig {\n const configPath = join(assetsDevcontainerDir, 'devcontainer.json')\n return parseJsonc<DevcontainerConfig>(readFileSync(configPath, 'utf8'))\n}\n\nexport function buildGeneratedDevcontainerConfig (context: WorkspaceContext): DevcontainerConfig {\n const baseConfig = readBaseDevcontainerConfig(context.assetsDevcontainerDir)\n const mounts = Array.isArray(baseConfig.mounts)\n ? baseConfig.mounts.filter((mount): mount is string => typeof mount === 'string')\n : []\n\n const boxdownMounts = [\n `type=bind,source=${context.assetsDevcontainerDir},target=${BOXDOWN_CONTAINER_DEVCONTAINER_DIR},readonly`,\n `type=bind,source=${context.sshPublicKeyRuntimeDir},target=${BOXDOWN_CONTAINER_SSH_DIR},readonly`\n ]\n\n return {\n ...baseConfig,\n name: `Boxdown: ${context.workspaceBasename}`,\n mounts: [...mounts, ...boxdownMounts],\n initializeCommand: `BOXDOWN_WORKSPACE_FOLDER=${shellQuote(context.workspaceFolder)} bash ${shellQuote(join(context.assetsDevcontainerDir, 'hooks', 'initialize.sh'))}`,\n postCreateCommand: `bash ${shellQuote(`${BOXDOWN_CONTAINER_DEVCONTAINER_DIR}/hooks/post-create.sh`)}`,\n postStartCommand: `bash ${shellQuote(`${BOXDOWN_CONTAINER_DEVCONTAINER_DIR}/hooks/post-start.sh`)}`,\n containerEnv: {\n ...(baseConfig.containerEnv ?? {}),\n BOXDOWN_CONTAINER_WORKSPACE_FOLDER: '/workspaces/${localWorkspaceFolderBasename}',\n BOXDOWN_WORKSPACE_BASENAME: '${localWorkspaceFolderBasename}',\n DEVCONTAINER_SSH_PUBLIC_KEY_FILE: BOXDOWN_CONTAINER_SSH_PUBLIC_KEY_PATH\n }\n }\n}\n\nexport function writeGeneratedDevcontainerConfig (context: WorkspaceContext): DevcontainerConfig {\n const config = buildGeneratedDevcontainerConfig(context)\n mkdirSync(context.workspaceCacheDir, { recursive: true })\n writeFileSync(context.generatedConfigPath, `${JSON.stringify(config, null, 2)}\\n`)\n return config\n}\n\nexport function publishContainerPortFromConfig (config: DevcontainerConfig): string | undefined {\n return config.runArgs?.find((arg) => /^[0-9.]+::[0-9]+$/.test(arg))?.split('::')[1]\n}\n","import { runBuffered } from './process.ts'\n\nexport function canonicalGithubRemoteUrl (remoteUrl: string): string | undefined {\n const trimmed = remoteUrl.trim()\n const scpLike = /^git@github\\.com:([^/]+)\\/(.+)$/i.exec(trimmed)\n\n if (scpLike !== null) {\n return canonicalGithubUrlFromParts(scpLike[1], scpLike[2])\n }\n\n try {\n const parsed = new URL(trimmed)\n const protocol = parsed.protocol.toLowerCase()\n const host = parsed.hostname.toLowerCase()\n\n if (host !== 'github.com') {\n return undefined\n }\n\n if (protocol === 'ssh:' && parsed.username !== 'git') {\n return undefined\n }\n\n if (protocol !== 'https:' && protocol !== 'ssh:') {\n return undefined\n }\n\n const parts = parsed.pathname.split('/').filter((part) => part.length > 0)\n if (parts.length !== 2) {\n return undefined\n }\n\n const [owner, repo] = parts\n return canonicalGithubUrlFromParts(owner, repo)\n } catch {\n return undefined\n }\n}\n\nfunction canonicalGithubUrlFromParts (owner: string | undefined, repo: string | undefined): string | undefined {\n if (owner === undefined || repo === undefined) {\n return undefined\n }\n\n const normalizedOwner = owner.trim()\n const normalizedRepo = repo.trim().replace(/\\/+$/, '').replace(/\\.git$/i, '')\n\n if (\n normalizedOwner.length === 0 ||\n normalizedRepo.length === 0 ||\n normalizedOwner.includes('/') ||\n normalizedRepo.includes('/')\n ) {\n return undefined\n }\n\n return `https://github.com/${normalizedOwner}/${normalizedRepo}.git`\n}\n\nexport async function configureWorkspaceGithubGitAuth (workspaceFolder: string): Promise<boolean> {\n const insideWorkTree = await runWorkspaceGit(workspaceFolder, ['rev-parse', '--is-inside-work-tree'])\n\n if (insideWorkTree.code !== 0 || insideWorkTree.stdout.trim() !== 'true') {\n return false\n }\n\n const remoteConfig = await runWorkspaceGit(workspaceFolder, ['config', '--local', '--get-regexp', '^remote\\\\..*\\\\.url$'])\n\n if (remoteConfig.code !== 0) {\n return false\n }\n\n const githubRemotes = parseGithubRemoteConfig(remoteConfig.stdout)\n\n if (githubRemotes.length === 0) {\n return false\n }\n\n for (const remote of githubRemotes) {\n const fetchUrl = await runWorkspaceGit(workspaceFolder, ['remote', 'set-url', remote.name, remote.url])\n if (fetchUrl.code !== 0) {\n return false\n }\n\n const pushUrl = await runWorkspaceGit(workspaceFolder, ['remote', 'set-url', '--push', remote.name, remote.url])\n if (pushUrl.code !== 0) {\n return false\n }\n }\n\n await runWorkspaceGit(workspaceFolder, ['config', '--local', '--unset-all', 'credential.https://github.com.helper'])\n\n const resetHelper = await runWorkspaceGit(workspaceFolder, ['config', '--local', '--add', 'credential.https://github.com.helper', ''])\n if (resetHelper.code !== 0) {\n return false\n }\n\n const ghHelper = await runWorkspaceGit(workspaceFolder, ['config', '--local', '--add', 'credential.https://github.com.helper', '!gh auth git-credential'])\n if (ghHelper.code !== 0) {\n return false\n }\n\n for (const remoteUrl of new Set(githubRemotes.map((remote) => remote.url))) {\n const rewrite = await runWorkspaceGit(workspaceFolder, ['config', '--local', '--replace-all', `url.${remoteUrl}.insteadOf`, remoteUrl])\n if (rewrite.code !== 0) {\n return false\n }\n }\n\n return true\n}\n\nfunction parseGithubRemoteConfig (configOutput: string): Array<{ name: string, url: string }> {\n const remotes: Array<{ name: string, url: string }> = []\n\n for (const line of configOutput.split(/\\r?\\n/)) {\n const match = /^remote\\.(.+)\\.url\\s+(.+)$/.exec(line)\n if (match === null) {\n continue\n }\n\n const name = match[1]\n const rawUrl = match[2]\n if (name === undefined || rawUrl === undefined) {\n continue\n }\n\n const url = canonicalGithubRemoteUrl(rawUrl)\n if (url !== undefined) {\n remotes.push({ name, url })\n }\n }\n\n return remotes\n}\n\nasync function runWorkspaceGit (workspaceFolder: string, args: string[]) {\n return runBuffered('git', args, {\n cwd: workspaceFolder,\n mirrorStdout: false,\n mirrorStderr: false\n })\n}\n","import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\n\nimport type { WorkspaceContext } from './paths.ts'\nimport { runBuffered } from './process.ts'\n\nexport async function ensureHostSshKey (context: WorkspaceContext, quiet = false): Promise<void> {\n mkdirSync(context.sshKeyDir, { recursive: true, mode: 0o700 })\n\n if (!existsSync(context.sshKeyPath)) {\n if (!quiet) {\n process.stderr.write(`Generating Boxdown SSH identity: ${context.sshKeyPath}\\n`)\n }\n\n const result = await runBuffered('ssh-keygen', [\n '-t',\n 'ed25519',\n '-f',\n context.sshKeyPath,\n '-N',\n '',\n '-C',\n `${context.workspaceBasename}-devcontainer`\n ], {\n mirrorStdout: quiet ? false : 'stderr',\n mirrorStderr: 'stderr'\n })\n\n if (result.code !== 0) {\n throw new Error(`ssh-keygen failed while creating ${context.sshKeyPath}`)\n }\n }\n\n if (!existsSync(context.sshPublicKeyPath)) {\n const result = await runBuffered('ssh-keygen', ['-y', '-f', context.sshKeyPath], {\n mirrorStdout: false,\n mirrorStderr: 'stderr'\n })\n\n if (result.code !== 0) {\n throw new Error(`ssh-keygen failed while deriving ${context.sshPublicKeyPath}`)\n }\n\n writeFileSync(context.sshPublicKeyPath, result.stdout)\n }\n\n mkdirSync(context.sshPublicKeyRuntimeDir, { recursive: true, mode: 0o755 })\n writeFileSync(context.sshPublicKeyRuntimePath, readFileSync(context.sshPublicKeyPath, 'utf8'))\n\n chmodSync(context.sshKeyPath, 0o600)\n chmodSync(context.sshPublicKeyPath, 0o644)\n chmodSync(context.sshPublicKeyRuntimePath, 0o644)\n}\n","import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\n\nimport type { WorkspaceContext } from './paths.ts'\nimport { shellQuote, sshConfigQuote } from './shell.ts'\nimport { ensureHostSshKey } from './ssh-key.ts'\n\nexport function defaultSshAlias (workspaceBasename: string): string {\n return `${workspaceBasename}-devcontainer`\n}\n\nexport function validateSshAlias (alias: string): void {\n if (!/^[A-Za-z0-9_.-]+$/.test(alias)) {\n throw new Error(`SSH alias contains unsupported characters: ${alias}`)\n }\n}\n\nexport function defaultSshConfigPath (env: NodeJS.ProcessEnv = process.env): string {\n return env.BOXDOWN_SSH_CONFIG ?? env.DEVCONTAINER_SSH_CONFIG ?? join(env.HOME ?? '', '.ssh', 'config')\n}\n\nexport function buildProxyCommand (context: WorkspaceContext, alias: string): string {\n const cliPath = join(context.packageRoot, 'dist', 'bin', 'cli.cjs')\n\n return `${shellQuote(process.execPath)} ${shellQuote(cliPath)} ssh-proxy --workspace ${shellQuote(context.workspaceFolder)} --alias ${shellQuote(alias)}`\n}\n\nexport function buildSshConfigBlock (context: WorkspaceContext, alias: string): string {\n validateSshAlias(alias)\n\n return [\n `# BEGIN ${alias} boxdown devcontainer ssh`,\n `Host ${alias}`,\n ` HostName ${alias}`,\n ' User node',\n ' IdentityFile none',\n ` IdentityFile ${sshConfigQuote(context.sshKeyPath)}`,\n ' IdentitiesOnly yes',\n ` ProxyCommand ${buildProxyCommand(context, alias)}`,\n ' StrictHostKeyChecking no',\n ' UserKnownHostsFile /dev/null',\n ' LogLevel ERROR',\n `# END ${alias} boxdown devcontainer ssh`,\n ''\n ].join('\\n')\n}\n\nexport function replaceSshConfigBlock (existingConfig: string, alias: string, block: string): string {\n const begin = `# BEGIN ${alias} boxdown devcontainer ssh`\n const end = `# END ${alias} boxdown devcontainer ssh`\n const lines = existingConfig.split(/\\r?\\n/)\n const nextLines: string[] = []\n let skipping = false\n\n for (const line of lines) {\n if (line === begin) {\n skipping = true\n continue\n }\n\n if (line === end) {\n skipping = false\n continue\n }\n\n if (!skipping) {\n nextLines.push(line)\n }\n }\n\n while (nextLines.length > 0 && nextLines[nextLines.length - 1] === '') {\n nextLines.pop()\n }\n\n return `${nextLines.join('\\n')}${nextLines.length > 0 ? '\\n\\n' : ''}${block}`\n}\n\nexport async function installSshConfig (context: WorkspaceContext, alias: string, options: { quiet?: boolean, configPath?: string } = {}): Promise<void> {\n validateSshAlias(alias)\n await ensureHostSshKey(context, options.quiet ?? false)\n\n const sshConfigPath = options.configPath ?? defaultSshConfigPath()\n const sshConfigDir = dirname(sshConfigPath)\n mkdirSync(sshConfigDir, { recursive: true, mode: 0o700 })\n\n if (!existsSync(sshConfigPath)) {\n writeFileSync(sshConfigPath, '')\n }\n\n chmodSync(sshConfigDir, 0o700)\n chmodSync(sshConfigPath, 0o600)\n\n const existingConfig = readFileSync(sshConfigPath, 'utf8')\n const block = buildSshConfigBlock(context, alias)\n const nextConfig = replaceSshConfigBlock(existingConfig, alias, block)\n\n if (nextConfig !== existingConfig) {\n writeFileSync(sshConfigPath, nextConfig)\n if (!options.quiet) {\n process.stdout.write(`Installed SSH alias: ${alias}\\n`)\n }\n } else if (!options.quiet) {\n process.stdout.write(`SSH alias already up to date: ${alias}\\n`)\n }\n\n chmodSync(sshConfigPath, 0o600)\n\n if (!options.quiet) {\n process.stdout.write(`SSH config: ${sshConfigPath}\\n`)\n process.stdout.write(`Identity file: ${context.sshKeyPath}\\n\\n`)\n process.stdout.write(`Validate with:\\n ssh ${alias} 'whoami && pwd'\\n`)\n }\n}\n","import { readFileSync } from 'node:fs'\n\nimport type { WorkspaceContext } from './paths.ts'\nimport { buildSshConfigBlock, defaultSshConfigPath } from './ssh-config.ts'\n\nexport type SshAliasSource = 'default' | 'provided'\nexport type SshManagedBlockState = 'missing' | 'installed' | 'outdated'\n\nexport interface ContainerSummary {\n id: string\n name?: string\n state?: string\n status?: string\n localFolder?: string\n}\n\nexport interface StatusInfo {\n workspace: {\n folder: string\n basename: string\n id: string\n }\n ssh: {\n alias: string\n aliasSource: SshAliasSource\n configPath: string\n configExists: boolean\n managedBlockState: SshManagedBlockState\n keyPath: string\n keyExists: boolean\n publicKeyPath: string\n publicKeyExists: boolean\n publicKeyRuntimePath: string\n publicKeyRuntimeExists: boolean\n }\n paths: {\n cacheRoot: string\n dataRoot: string\n workspaceCacheDir: string\n workspaceDataDir: string\n generatedConfigPath: string\n generatedConfigExists: boolean\n assetsDevcontainerDir: string\n assetsDevcontainerExists: boolean\n }\n container: {\n found: boolean\n running: boolean\n id?: string\n name?: string\n state?: string\n status?: string\n }\n}\n\nexport interface SshConfigStatus {\n configPath: string\n configExists: boolean\n managedBlockState: SshManagedBlockState\n}\n\ninterface DockerPsJson {\n ID?: unknown\n Names?: unknown\n State?: unknown\n Status?: unknown\n Labels?: unknown\n}\n\nfunction dockerLabelsFromString (labels: string): Record<string, string> {\n return Object.fromEntries(labels\n .split(',')\n .map((label) => label.trim())\n .filter((label) => label.length > 0)\n .map((label) => {\n const separator = label.indexOf('=')\n return separator === -1 ? [label, ''] : [label.slice(0, separator), label.slice(separator + 1)]\n }))\n}\n\nexport function parseDockerPsJsonLines (output: string): ContainerSummary[] {\n const lines = output.split(/\\r?\\n/).filter((line) => line.trim().length > 0)\n\n return lines.map((line) => {\n let parsed: DockerPsJson\n\n try {\n parsed = JSON.parse(line) as DockerPsJson\n } catch {\n throw new Error(`Could not parse docker ps output: ${line}`)\n }\n\n if (typeof parsed.ID !== 'string' || parsed.ID.length === 0) {\n throw new Error(`Docker ps output is missing container ID: ${line}`)\n }\n\n return {\n id: parsed.ID,\n name: typeof parsed.Names === 'string' && parsed.Names.length > 0 ? parsed.Names : undefined,\n state: typeof parsed.State === 'string' && parsed.State.length > 0 ? parsed.State : undefined,\n status: typeof parsed.Status === 'string' && parsed.Status.length > 0 ? parsed.Status : undefined,\n localFolder: typeof parsed.Labels === 'string' ? dockerLabelsFromString(parsed.Labels)['devcontainer.local_folder'] : undefined\n }\n })\n}\n\nfunction readFileUtf8 (path: string): string {\n return readFileSync(path, 'utf8')\n}\n\nfunction managedSshBlockMarkers (alias: string): { begin: string, end: string } {\n return {\n begin: `# BEGIN ${alias} boxdown devcontainer ssh`,\n end: `# END ${alias} boxdown devcontainer ssh`\n }\n}\n\nfunction findManagedSshConfigBlock (config: string, alias: string): string | undefined {\n const { begin, end } = managedSshBlockMarkers(alias)\n const beginIndex = config.indexOf(begin)\n\n if (beginIndex === -1) {\n return undefined\n }\n\n const endIndex = config.indexOf(end, beginIndex)\n\n if (endIndex === -1) {\n return ''\n }\n\n const afterEndMarkerIndex = endIndex + end.length\n const afterEndLineIndex = config[afterEndMarkerIndex] === '\\n' ? afterEndMarkerIndex + 1 : afterEndMarkerIndex\n\n return config.slice(beginIndex, afterEndLineIndex)\n}\n\nexport function inspectSshConfigStatus (\n context: WorkspaceContext,\n alias: string,\n configPath: string,\n exists: (path: string) => boolean,\n readFile: (path: string) => string = readFileUtf8\n): SshConfigStatus {\n const configExists = exists(configPath)\n\n if (!configExists) {\n return {\n configPath,\n configExists,\n managedBlockState: 'missing'\n }\n }\n\n const config = readFile(configPath)\n const managedBlock = findManagedSshConfigBlock(config, alias)\n\n if (managedBlock === undefined) {\n return {\n configPath,\n configExists,\n managedBlockState: 'missing'\n }\n }\n\n return {\n configPath,\n configExists,\n managedBlockState: managedBlock === buildSshConfigBlock(context, alias) ? 'installed' : 'outdated'\n }\n}\n\nexport function createStatusInfo (\n context: WorkspaceContext,\n alias: string,\n container: ContainerSummary | undefined,\n exists: (path: string) => boolean,\n options: {\n aliasSource?: SshAliasSource\n sshConfigPath?: string\n readFile?: (path: string) => string\n } = {}\n): StatusInfo {\n const state = container?.state?.toLowerCase()\n const sshConfig = inspectSshConfigStatus(\n context,\n alias,\n options.sshConfigPath ?? defaultSshConfigPath(),\n exists,\n options.readFile\n )\n\n return {\n workspace: {\n folder: context.workspaceFolder,\n basename: context.workspaceBasename,\n id: context.workspaceId\n },\n ssh: {\n alias,\n aliasSource: options.aliasSource ?? 'provided',\n configPath: sshConfig.configPath,\n configExists: sshConfig.configExists,\n managedBlockState: sshConfig.managedBlockState,\n keyPath: context.sshKeyPath,\n keyExists: exists(context.sshKeyPath),\n publicKeyPath: context.sshPublicKeyPath,\n publicKeyExists: exists(context.sshPublicKeyPath),\n publicKeyRuntimePath: context.sshPublicKeyRuntimePath,\n publicKeyRuntimeExists: exists(context.sshPublicKeyRuntimePath)\n },\n paths: {\n cacheRoot: context.cacheRoot,\n dataRoot: context.dataRoot,\n workspaceCacheDir: context.workspaceCacheDir,\n workspaceDataDir: context.workspaceDataDir,\n generatedConfigPath: context.generatedConfigPath,\n generatedConfigExists: exists(context.generatedConfigPath),\n assetsDevcontainerDir: context.assetsDevcontainerDir,\n assetsDevcontainerExists: exists(context.assetsDevcontainerDir)\n },\n container: {\n found: container !== undefined,\n running: state === 'running',\n id: container?.id,\n name: container?.name,\n state: container?.state,\n status: container?.status\n }\n }\n}\n\nexport function statusIsHealthy (status: StatusInfo): boolean {\n return status.paths.generatedConfigExists &&\n status.paths.assetsDevcontainerExists &&\n status.ssh.keyExists &&\n status.ssh.publicKeyExists &&\n status.ssh.publicKeyRuntimeExists &&\n status.container.found &&\n status.container.running\n}\n\nconst color = {\n green: '\\u001B[32m',\n red: '\\u001B[31m',\n reset: '\\u001B[0m'\n}\n\nfunction colorize (value: string, colorName: 'green' | 'red', enabled: boolean): string {\n if (!enabled) {\n return value\n }\n\n return `${color[colorName]}${value}${color.reset}`\n}\n\nfunction existenceText (value: boolean, colorEnabled: boolean): string {\n return colorize(value ? 'exists' : 'missing', value ? 'green' : 'red', colorEnabled)\n}\n\nfunction runningText (value: boolean, colorEnabled: boolean): string {\n return colorize(value ? 'yes' : 'no', value ? 'green' : 'red', colorEnabled)\n}\n\nfunction managedBlockText (state: SshManagedBlockState, colorEnabled: boolean): string {\n return colorize(state, state === 'installed' ? 'green' : 'red', colorEnabled)\n}\n\nfunction aliasSourceText (source: SshAliasSource): string {\n return source === 'default' ? 'computed default' : 'provided'\n}\n\nfunction installedText (state: SshManagedBlockState): string {\n return state === 'installed' ? 'installed' : 'not installed'\n}\n\nfunction stateText (state: string, healthy: boolean, colorEnabled: boolean): string {\n return colorize(state, healthy ? 'green' : 'red', colorEnabled)\n}\n\nexport function formatStatusText (status: StatusInfo, options: { color?: boolean } = {}): string {\n const colorEnabled = options.color ?? false\n const containerState = status.container.found ? status.container.state ?? 'unknown' : 'absent'\n const healthy = statusIsHealthy(status)\n const lines = [\n 'Boxdown status',\n '',\n 'Workspace:',\n ` Path: ${status.workspace.folder}`,\n ` Name: ${status.workspace.basename}`,\n ` ID: ${status.workspace.id}`,\n ` SSH alias: ${status.ssh.alias} (${aliasSourceText(status.ssh.aliasSource)}; ${installedText(status.ssh.managedBlockState)})`,\n '',\n 'Paths:',\n ` Cache root: ${status.paths.cacheRoot}`,\n ` Data root: ${status.paths.dataRoot}`,\n ` Workspace cache: ${status.paths.workspaceCacheDir}`,\n ` Workspace data: ${status.paths.workspaceDataDir}`,\n ` Generated config: ${status.paths.generatedConfigPath} (${existenceText(status.paths.generatedConfigExists, colorEnabled)})`,\n ` Devcontainer assets: ${status.paths.assetsDevcontainerDir} (${existenceText(status.paths.assetsDevcontainerExists, colorEnabled)})`,\n '',\n 'SSH:',\n ` SSH config: ${status.ssh.configPath} (${existenceText(status.ssh.configExists, colorEnabled)})`,\n ` Boxdown SSH block: ${managedBlockText(status.ssh.managedBlockState, colorEnabled)}`,\n ` Private key: ${status.ssh.keyPath} (${existenceText(status.ssh.keyExists, colorEnabled)})`,\n ` Public key: ${status.ssh.publicKeyPath} (${existenceText(status.ssh.publicKeyExists, colorEnabled)})`,\n ` Runtime public key: ${status.ssh.publicKeyRuntimePath} (${existenceText(status.ssh.publicKeyRuntimeExists, colorEnabled)})`,\n '',\n 'Container:',\n ` State: ${stateText(containerState, healthy, colorEnabled)}`,\n ` Running: ${runningText(status.container.running, colorEnabled)}`\n ]\n\n if (status.container.id !== undefined) {\n lines.push(` ID: ${status.container.id}`)\n }\n\n if (status.container.name !== undefined) {\n lines.push(` Name: ${status.container.name}`)\n }\n\n if (status.container.status !== undefined) {\n lines.push(` Docker status: ${status.container.status}`)\n }\n\n return `${lines.join('\\n')}\\n`\n}\n","import {\n BOXDOWN_CONTAINER_DEVCONTAINER_DIR\n} from './constants.ts'\nimport { buildGeneratedDevcontainerConfig, publishContainerPortFromConfig, writeGeneratedDevcontainerConfig } from './config.ts'\nimport { codingAgentBinary, type CodingAgentCli } from './coding-agents.ts'\nimport { resolveDevcontainerCli } from './devcontainer-cli.ts'\nimport { configureWorkspaceGithubGitAuth } from './github-git-auth.ts'\nimport type { WorkspaceContext } from './paths.ts'\nimport { runBuffered, runInteractive } from './process.ts'\nimport { interactiveCommandScript, interactiveShellEnvArgs, interactiveShellScript } from './shell.ts'\nimport { ensureHostSshKey } from './ssh-key.ts'\nimport { type ContainerSummary, parseDockerPsJsonLines } from './status.ts'\n\nexport interface StartOptions {\n recreate?: boolean\n proxyMode?: boolean\n reuseRunning?: boolean\n}\n\nfunction devcontainerWorkspaceArgs (context: WorkspaceContext): string[] {\n return [\n '--workspace-folder',\n context.workspaceFolder,\n '--override-config',\n context.generatedConfigPath\n ]\n}\n\nfunction log (message: string, proxyMode = false): void {\n if (proxyMode) {\n process.stderr.write(`${message}\\n`)\n } else {\n process.stdout.write(`${message}\\n`)\n }\n}\n\nexport function parseContainerIdFromUpOutput (output: string): string | undefined {\n return /\"containerId\"\\s*:\\s*\"([^\"]+)\"/.exec(output)?.[1]\n}\n\nexport async function findWorkspaceContainer (context: WorkspaceContext): Promise<ContainerSummary | undefined> {\n const result = await runBuffered('docker', [\n 'ps',\n '-a',\n '--filter',\n `label=devcontainer.local_folder=${context.workspaceFolder}`,\n '--format',\n '{{json .}}'\n ], {\n mirrorStdout: false,\n mirrorStderr: false\n })\n\n if (result.code !== 0) {\n throw new Error(`Could not inspect devcontainer for ${context.workspaceFolder}`)\n }\n\n return parseDockerPsJsonLines(result.stdout)[0]\n}\n\nexport async function listWorkspaceContainers (): Promise<ContainerSummary[] | undefined> {\n const result = await runBuffered('docker', [\n 'ps',\n '-a',\n '--filter',\n 'label=devcontainer.local_folder',\n '--format',\n '{{json .}}'\n ], {\n mirrorStdout: false,\n mirrorStderr: false\n })\n\n if (result.code !== 0) {\n return undefined\n }\n\n return parseDockerPsJsonLines(result.stdout)\n}\n\nexport async function findRunningContainerId (context: WorkspaceContext): Promise<string | undefined> {\n const result = await runBuffered('docker', [\n 'ps',\n '--filter',\n `label=devcontainer.local_folder=${context.workspaceFolder}`,\n '--format',\n '{{.ID}}'\n ], {\n mirrorStdout: false,\n mirrorStderr: false\n })\n\n if (result.code !== 0) {\n return undefined\n }\n\n return result.stdout.split(/\\r?\\n/).find((line) => line.length > 0)\n}\n\nexport async function stopWorkspaceContainer (context: WorkspaceContext): Promise<void> {\n const container = await findWorkspaceContainer(context)\n\n if (container === undefined) {\n process.stdout.write(`No devcontainer found for: ${context.workspaceFolder}\\n`)\n return\n }\n\n if (container.state?.toLowerCase() !== 'running') {\n process.stdout.write(`Devcontainer is not running for: ${context.workspaceFolder}\\n`)\n return\n }\n\n const result = await runBuffered('docker', ['stop', container.id], {\n mirrorStdout: false,\n mirrorStderr: false\n })\n\n if (result.code !== 0) {\n throw new Error(`Could not stop devcontainer ${container.id}`)\n }\n\n process.stdout.write(`Stopped devcontainer: ${container.id}\\n`)\n}\n\nexport async function removeWorkspaceContainer (context: WorkspaceContext): Promise<void> {\n const container = await findWorkspaceContainer(context)\n\n if (container === undefined) {\n process.stdout.write(`No devcontainer found for: ${context.workspaceFolder}\\n`)\n return\n }\n\n const result = await runBuffered('docker', ['rm', '-f', container.id], {\n mirrorStdout: false,\n mirrorStderr: false\n })\n\n if (result.code !== 0) {\n throw new Error(`Could not remove devcontainer ${container.id}`)\n }\n\n process.stdout.write(`Removed devcontainer: ${container.id}\\n`)\n}\n\nexport async function startDevcontainer (context: WorkspaceContext, options: StartOptions = {}): Promise<string> {\n await ensureHostSshKey(context, options.proxyMode ?? false)\n writeGeneratedDevcontainerConfig(context)\n\n if (options.reuseRunning === true && options.recreate !== true) {\n const runningContainerId = await findRunningContainerId(context)\n\n if (runningContainerId !== undefined) {\n log(`Using running devcontainer for: ${context.workspaceFolder}`, options.proxyMode)\n return runningContainerId\n }\n }\n\n const cli = resolveDevcontainerCli(context)\n log(`Starting devcontainer for: ${context.workspaceFolder}`, options.proxyMode)\n\n const args = [\n 'up',\n ...devcontainerWorkspaceArgs(context)\n ]\n\n if (options.recreate === true) {\n args.push('--remove-existing-container')\n log('Removing existing dev container so create-time settings apply.', options.proxyMode)\n }\n\n const result = await runBuffered(cli.command, [...cli.argsPrefix, ...args], {\n mirrorStdout: options.proxyMode === true ? 'stderr' : 'stdout',\n mirrorStderr: 'stderr'\n })\n\n if (result.code !== 0) {\n throw new Error(`devcontainer up failed for ${context.workspaceFolder}`)\n }\n\n const containerId = parseContainerIdFromUpOutput(`${result.stdout}\\n${result.stderr}`) ?? await findRunningContainerId(context)\n\n if (containerId === undefined) {\n throw new Error(`Could not resolve devcontainer ID for ${context.workspaceFolder}`)\n }\n\n return containerId\n}\n\nexport async function printPortHint (context: WorkspaceContext, containerId: string): Promise<void> {\n const config = buildGeneratedDevcontainerConfig(context)\n const containerPort = publishContainerPortFromConfig(config)\n\n if (containerPort === undefined) {\n process.stderr.write('Warning: could not find a runArgs publish port.\\n')\n return\n }\n\n const result = await runBuffered('docker', ['port', containerId, `${containerPort}/tcp`], {\n mirrorStdout: false,\n mirrorStderr: false\n })\n\n const hostBinding = result.stdout.split(/\\r?\\n/).find((line) => line.length > 0)\n\n if (result.code === 0 && hostBinding !== undefined) {\n process.stdout.write(`\\nDev server available at: http://${hostBinding}\\n\\n`)\n } else {\n process.stderr.write(`Warning: container is running but port ${containerPort}/tcp is not mapped.\\n`)\n }\n}\n\nexport async function openShell (context: WorkspaceContext): Promise<number> {\n const cli = resolveDevcontainerCli(context)\n process.stdout.write('Dropping into container shell...\\n')\n\n return runInteractive(cli.command, [\n ...cli.argsPrefix,\n 'exec',\n ...devcontainerWorkspaceArgs(context),\n '--',\n 'env',\n ...interactiveShellEnvArgs(),\n 'bash',\n '-c',\n interactiveShellScript()\n ])\n}\n\nexport function codingAgentDevcontainerExecArgs (context: WorkspaceContext, agent: CodingAgentCli, agentArgs: string[] = []): string[] {\n return [\n 'exec',\n ...devcontainerWorkspaceArgs(context),\n '--',\n 'env',\n ...interactiveShellEnvArgs(),\n 'bash',\n '-c',\n interactiveCommandScript(),\n 'boxdown-agent',\n codingAgentBinary(agent),\n ...agentArgs\n ]\n}\n\nexport async function openCodingAgentCli (context: WorkspaceContext, agent: CodingAgentCli, agentArgs: string[] = []): Promise<number> {\n const cli = resolveDevcontainerCli(context)\n process.stdout.write(`Dropping into ${codingAgentBinary(agent)} inside the devcontainer...\\n`)\n\n return runInteractive(cli.command, [\n ...cli.argsPrefix,\n ...codingAgentDevcontainerExecArgs(context, agent, agentArgs)\n ])\n}\n\nexport async function ensureContainerSshRuntime (context: WorkspaceContext): Promise<void> {\n const cli = resolveDevcontainerCli(context)\n const result = await runBuffered(cli.command, [\n ...cli.argsPrefix,\n 'exec',\n ...devcontainerWorkspaceArgs(context),\n '--',\n 'bash',\n `${BOXDOWN_CONTAINER_DEVCONTAINER_DIR}/utils/ssh-bootstrap.sh`,\n 'runtime'\n ], {\n mirrorStdout: 'stderr',\n mirrorStderr: 'stderr'\n })\n\n if (result.code !== 0) {\n throw new Error('Failed to prepare devcontainer SSH runtime')\n }\n}\n\nexport async function refreshContainerCodingAgentClis (context: WorkspaceContext, proxyMode = false, agents: CodingAgentCli[] = []): Promise<void> {\n const cli = resolveDevcontainerCli(context)\n const result = await runBuffered(cli.command, [\n ...cli.argsPrefix,\n 'exec',\n ...devcontainerWorkspaceArgs(context),\n '--',\n 'bash',\n `${BOXDOWN_CONTAINER_DEVCONTAINER_DIR}/utils/coding-agent-cli-update.sh`,\n 'maybe-update',\n ...agents\n ], {\n mirrorStdout: proxyMode ? 'stderr' : 'stdout',\n mirrorStderr: 'stderr'\n })\n\n if (result.code !== 0) {\n process.stderr.write('Warning: could not refresh one or more coding-agent CLIs inside the devcontainer.\\n')\n }\n}\n\nexport async function runSshdProxy (containerId: string): Promise<number> {\n return runInteractive('docker', [\n 'exec',\n '-i',\n containerId,\n '/usr/sbin/sshd',\n '-i',\n '-o',\n 'LogLevel=QUIET',\n '-o',\n 'PubkeyAuthentication=yes',\n '-o',\n 'PasswordAuthentication=no',\n '-o',\n 'KbdInteractiveAuthentication=no',\n '-o',\n 'AllowTcpForwarding=yes',\n '-o',\n 'AllowStreamLocalForwarding=yes',\n '-o',\n 'PermitTTY=yes'\n ])\n}\n\nasync function hostGhTokenOrEmpty (): Promise<string> {\n const result = await runBuffered('gh', ['auth', 'token'], {\n mirrorStdout: false,\n mirrorStderr: false\n })\n\n if (result.code !== 0) {\n return ''\n }\n\n return result.stdout.trim()\n}\n\nexport async function refreshContainerGhAuth (context: WorkspaceContext): Promise<void> {\n await ensureHostSshKey(context, true)\n writeGeneratedDevcontainerConfig(context)\n\n const token = await hostGhTokenOrEmpty()\n\n if (token.length === 0) {\n return\n }\n\n const cli = resolveDevcontainerCli(context)\n const login = await runBuffered(cli.command, [\n ...cli.argsPrefix,\n 'exec',\n ...devcontainerWorkspaceArgs(context),\n '--',\n 'gh',\n 'auth',\n 'login',\n '--hostname',\n 'github.com',\n '--git-protocol',\n 'https',\n '--with-token',\n '--insecure-storage'\n ], {\n input: `${token}\\n`,\n mirrorStdout: false,\n mirrorStderr: false\n })\n\n if (login.code !== 0) {\n process.stderr.write('Warning: could not refresh GitHub CLI auth inside the devcontainer.\\n')\n return\n }\n\n const gitAuthConfigured = await configureWorkspaceGithubGitAuth(context.workspaceFolder)\n if (!gitAuthConfigured) {\n process.stderr.write('Warning: GitHub CLI auth refreshed, but GitHub Git auth was not configured for this workspace.\\n')\n }\n\n const verify = await runBuffered(cli.command, [\n ...cli.argsPrefix,\n 'exec',\n ...devcontainerWorkspaceArgs(context),\n '--',\n 'gh',\n 'auth',\n 'status',\n '--hostname',\n 'github.com'\n ], {\n mirrorStdout: false,\n mirrorStderr: false\n })\n\n if (verify.code === 0) {\n process.stdout.write('GitHub CLI auth refreshed inside the devcontainer.\\n')\n } else {\n process.stderr.write('Warning: GitHub CLI auth refresh completed, but verification failed.\\n')\n }\n}\n","import type { WorkspaceMetadata } from './metadata.ts'\nimport type { ContainerSummary } from './status.ts'\n\nexport interface WorkspaceListEntry extends WorkspaceMetadata {\n repoExists: boolean\n state: string\n container: {\n found: boolean\n running: boolean\n id?: string\n name?: string\n state?: string\n status?: string\n }\n}\n\nfunction containerState (container: ContainerSummary | undefined, dockerAvailable: boolean): string {\n if (!dockerAvailable) {\n return 'unknown'\n }\n\n return container?.state?.toLowerCase() ?? 'absent'\n}\n\nfunction displayState (repoExists: boolean, container: ContainerSummary | undefined, dockerAvailable: boolean): string {\n if (!repoExists) {\n return 'missing'\n }\n\n return containerState(container, dockerAvailable)\n}\n\nexport function createWorkspaceListEntries (\n metadata: WorkspaceMetadata[],\n containers: ContainerSummary[] | undefined,\n exists: (path: string) => boolean\n): WorkspaceListEntry[] {\n const dockerAvailable = containers !== undefined\n const containersByWorkspace = new Map(\n (containers ?? [])\n .filter((container) => container.localFolder !== undefined)\n .map((container) => [container.localFolder as string, container])\n )\n\n return metadata\n .map((item) => {\n const container = containersByWorkspace.get(item.workspaceFolder)\n const repoExists = exists(item.workspaceFolder)\n const state = displayState(repoExists, container, dockerAvailable)\n\n return {\n ...item,\n repoExists,\n state,\n container: {\n found: container !== undefined,\n running: container?.state?.toLowerCase() === 'running',\n id: container?.id,\n name: container?.name,\n state: container?.state ?? (dockerAvailable ? undefined : 'unknown'),\n status: container?.status\n }\n }\n })\n .sort((a, b) => a.workspaceBasename.localeCompare(b.workspaceBasename) || a.workspaceFolder.localeCompare(b.workspaceFolder))\n}\n\nfunction pad (value: string, width: number): string {\n return value.padEnd(width, ' ')\n}\n\nfunction containerLabel (entry: WorkspaceListEntry): string {\n if (!entry.container.found) {\n return '-'\n }\n\n return entry.container.name ?? entry.container.id ?? '-'\n}\n\nexport function formatWorkspaceListText (entries: WorkspaceListEntry[]): string {\n if (entries.length === 0) {\n return 'Boxdown list\\n\\nNo Boxdown workspaces found.\\n'\n }\n\n const rows = entries.map((entry) => [\n entry.state,\n entry.workspaceBasename,\n entry.workspaceFolder,\n entry.sshAlias,\n containerLabel(entry)\n ])\n const headers = ['STATE', 'REPO', 'PATH', 'SSH ALIAS', 'CONTAINER']\n const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => row[index]?.length ?? 0)))\n const lines = [\n 'Boxdown list',\n '',\n headers.map((header, index) => pad(header, widths[index] ?? header.length)).join(' '),\n rows.map((row) => row.map((value, index) => pad(value, widths[index] ?? value.length)).join(' ')).join('\\n')\n ]\n\n return `${lines.join('\\n')}\\n`\n}\n","import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport type { WorkspaceContext } from './paths.ts'\n\nexport const WORKSPACE_METADATA_VERSION = 1\n\nexport interface WorkspaceMetadata {\n version: 1\n workspaceId: string\n workspaceFolder: string\n workspaceBasename: string\n sshAlias: string\n firstSeenAt: string\n lastSeenAt: string\n}\n\nfunction isWorkspaceMetadata (value: unknown): value is WorkspaceMetadata {\n if (value === null || typeof value !== 'object') {\n return false\n }\n\n const candidate = value as Record<string, unknown>\n\n return candidate.version === WORKSPACE_METADATA_VERSION &&\n typeof candidate.workspaceId === 'string' &&\n typeof candidate.workspaceFolder === 'string' &&\n typeof candidate.workspaceBasename === 'string' &&\n typeof candidate.sshAlias === 'string' &&\n typeof candidate.firstSeenAt === 'string' &&\n typeof candidate.lastSeenAt === 'string'\n}\n\nexport function workspaceMetadataPath (context: WorkspaceContext): string {\n return join(context.workspaceDataDir, 'metadata.json')\n}\n\nexport function readWorkspaceMetadataFile (path: string): WorkspaceMetadata {\n const parsed = JSON.parse(readFileSync(path, 'utf8')) as unknown\n\n if (!isWorkspaceMetadata(parsed)) {\n throw new Error(`Invalid Boxdown workspace metadata: ${path}`)\n }\n\n return parsed\n}\n\nexport function writeWorkspaceMetadata (context: WorkspaceContext, sshAlias: string, now = new Date()): WorkspaceMetadata {\n const metadataPath = workspaceMetadataPath(context)\n const timestamp = now.toISOString()\n let firstSeenAt = timestamp\n\n if (existsSync(metadataPath)) {\n firstSeenAt = readWorkspaceMetadataFile(metadataPath).firstSeenAt\n }\n\n const metadata: WorkspaceMetadata = {\n version: WORKSPACE_METADATA_VERSION,\n workspaceId: context.workspaceId,\n workspaceFolder: context.workspaceFolder,\n workspaceBasename: context.workspaceBasename,\n sshAlias,\n firstSeenAt,\n lastSeenAt: timestamp\n }\n\n mkdirSync(context.workspaceDataDir, { recursive: true })\n writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}\\n`)\n return metadata\n}\n\nexport function listWorkspaceMetadata (dataRoot: string): WorkspaceMetadata[] {\n const workspacesDir = join(dataRoot, 'workspaces')\n\n if (!existsSync(workspacesDir)) {\n return []\n }\n\n return readdirSync(workspacesDir, { withFileTypes: true })\n .filter((entry) => entry.isDirectory())\n .map((entry) => join(workspacesDir, entry.name, 'metadata.json'))\n .filter((metadataPath) => existsSync(metadataPath))\n .map((metadataPath) => readWorkspaceMetadataFile(metadataPath))\n}\n","import { createHash } from 'node:crypto'\nimport { existsSync, realpathSync, statSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { basename, dirname, join, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nimport { PACKAGE_NAME } from './constants.ts'\n\nexport interface WorkspaceContextOptions {\n workspace?: string\n cwd?: string\n env?: NodeJS.ProcessEnv\n packageRoot?: string\n assetsDevcontainerDir?: string\n}\n\nexport interface WorkspaceContext {\n workspaceFolder: string\n workspaceBasename: string\n workspaceId: string\n packageRoot: string\n assetsDevcontainerDir: string\n cacheRoot: string\n dataRoot: string\n workspaceCacheDir: string\n workspaceDataDir: string\n generatedConfigPath: string\n sshKeyDir: string\n sshKeyPath: string\n sshPublicKeyPath: string\n sshPublicKeyRuntimeDir: string\n sshPublicKeyRuntimePath: string\n}\n\nexport function packageRootFromImportMeta (importMetaUrl = import.meta.url): string {\n return dirname(dirname(fileURLToPath(importMetaUrl)))\n}\n\nexport function workspaceIdFor (workspaceFolder: string): string {\n return createHash('sha256').update(workspaceFolder).digest('hex').slice(0, 16)\n}\n\nexport function resolveWorkspaceFolder (workspace: string | undefined, cwd = process.cwd()): string {\n const candidate = resolve(cwd, workspace ?? '.')\n\n if (!existsSync(candidate)) {\n throw new Error(`Workspace does not exist: ${candidate}`)\n }\n\n if (!statSync(candidate).isDirectory()) {\n throw new Error(`Workspace is not a directory: ${candidate}`)\n }\n\n return realpathSync(candidate)\n}\n\nexport function defaultCacheRoot (env: NodeJS.ProcessEnv = process.env): string {\n if (env.BOXDOWN_CACHE_HOME) {\n return env.BOXDOWN_CACHE_HOME\n }\n\n if (env.XDG_CACHE_HOME) {\n return join(env.XDG_CACHE_HOME, PACKAGE_NAME)\n }\n\n return join(homedir(), '.cache', PACKAGE_NAME)\n}\n\nexport function defaultDataRoot (env: NodeJS.ProcessEnv = process.env): string {\n if (env.BOXDOWN_DATA_HOME) {\n return env.BOXDOWN_DATA_HOME\n }\n\n if (env.XDG_DATA_HOME) {\n return join(env.XDG_DATA_HOME, PACKAGE_NAME)\n }\n\n return join(homedir(), '.local', 'share', PACKAGE_NAME)\n}\n\nexport function createWorkspaceContext (options: WorkspaceContextOptions = {}): WorkspaceContext {\n const env = options.env ?? process.env\n const workspaceFolder = resolveWorkspaceFolder(options.workspace, options.cwd)\n const workspaceBasename = basename(workspaceFolder)\n const workspaceId = workspaceIdFor(workspaceFolder)\n const packageRoot = options.packageRoot ?? packageRootFromImportMeta()\n const assetsDevcontainerDir = options.assetsDevcontainerDir ?? env.BOXDOWN_DEVCONTAINER_ASSETS_DIR ?? join(packageRoot, 'assets', 'devcontainer')\n const cacheRoot = defaultCacheRoot(env)\n const dataRoot = defaultDataRoot(env)\n const workspaceCacheDir = join(cacheRoot, 'workspaces', workspaceId)\n const workspaceDataDir = join(dataRoot, 'workspaces', workspaceId)\n\n return {\n workspaceFolder,\n workspaceBasename,\n workspaceId,\n packageRoot,\n assetsDevcontainerDir,\n cacheRoot,\n dataRoot,\n workspaceCacheDir,\n workspaceDataDir,\n generatedConfigPath: join(workspaceCacheDir, 'devcontainer.json'),\n sshKeyDir: join(workspaceDataDir, 'ssh'),\n sshKeyPath: join(workspaceDataDir, 'ssh', 'id_ed25519'),\n sshPublicKeyPath: join(workspaceDataDir, 'ssh', 'id_ed25519.pub'),\n sshPublicKeyRuntimeDir: join(workspaceDataDir, 'ssh-public'),\n sshPublicKeyRuntimePath: join(workspaceDataDir, 'ssh-public', 'id_ed25519.pub')\n }\n}\n","import { existsSync } from 'node:fs'\n\nimport { codingAgentFromCommand, type CodingAgentCli } from './coding-agents.ts'\nimport { doctorHasFailures, formatDoctorText, runDoctorChecks } from './doctor.ts'\nimport { startDevcontainer, printPortHint, openShell, openCodingAgentCli, ensureContainerSshRuntime, runSshdProxy, refreshContainerGhAuth, refreshContainerCodingAgentClis, findRunningContainerId, findWorkspaceContainer, stopWorkspaceContainer, removeWorkspaceContainer, listWorkspaceContainers } from './devcontainer.ts'\nimport { createWorkspaceListEntries, formatWorkspaceListText } from './list.ts'\nimport { listWorkspaceMetadata, writeWorkspaceMetadata } from './metadata.ts'\nimport { createWorkspaceContext, defaultDataRoot } from './paths.ts'\nimport { defaultSshAlias, installSshConfig } from './ssh-config.ts'\nimport { createStatusInfo, formatStatusText, statusIsHealthy } from './status.ts'\n\nexport type BoxdownCommand =\n | 'help'\n | 'start'\n | 'list'\n | 'status'\n | 'stop'\n | 'down'\n | 'doctor'\n | 'ssh-config-install'\n | 'ssh-proxy'\n | 'refresh-gh-token'\n | 'refresh-gh-token-running'\n | 'coding-agent'\n\nexport interface ParsedCli {\n command: BoxdownCommand\n agent?: CodingAgentCli\n agentArgs?: string[]\n workspace?: string\n alias?: string\n recreate: boolean\n json: boolean\n}\n\nexport const USAGE = `Usage:\n boxdown start [--workspace <path>] [--recreate]\n boxdown codex [--workspace <path>] [--recreate] [-- <codex args...>]\n boxdown claude [--workspace <path>] [--recreate] [-- <claude args...>]\n boxdown cc [--workspace <path>] [--recreate] [-- <claude args...>]\n boxdown opencode [--workspace <path>] [--recreate] [-- <opencode args...>]\n boxdown antigravity [--workspace <path>] [--recreate] [-- <agy args...>]\n boxdown list [--json]\n boxdown status [--workspace <path>] [--alias <name>] [--json]\n boxdown stop [--workspace <path>]\n boxdown down [--workspace <path>]\n boxdown doctor [--workspace <path>]\n boxdown ssh-config install [--workspace <path>] [--alias <name>]\n boxdown ssh-proxy [--workspace <path>] [--alias <name>]\n boxdown refresh-gh-token [--workspace <path>]\n boxdown refresh-gh-token-running [--workspace <path>]\n\nCommands:\n start Start or reuse the workspace devcontainer, then open\n an interactive shell inside it. Alias: shell.\n codex Start or reuse the devcontainer, then launch Codex.\n claude Start or reuse the devcontainer, then launch Claude\n Code. Alias: cc.\n opencode Start or reuse the devcontainer, then launch\n OpenCode.\n antigravity Start or reuse the devcontainer, then launch\n Antigravity CLI (agy).\n list List Boxdown-known devcontainer workspaces from any\n directory.\n status Show workspace state, generated paths, SSH key paths,\n and the matching devcontainer state.\n stop Stop the workspace devcontainer if it is running.\n down Remove the workspace devcontainer. Keeps Boxdown\n cache, generated config, data, and SSH keys.\n doctor Check required host tools and Boxdown assets.\n ssh-config install Install or update an SSH host alias for the workspace\n devcontainer.\n ssh-proxy Internal command used by the generated SSH\n ProxyCommand. Starts or reuses the devcontainer and\n bridges SSH over docker exec.\n refresh-gh-token Start or reuse the devcontainer, then copy host\n GitHub CLI auth into the container when available.\n refresh-gh-token-running Refresh GitHub CLI auth only if the workspace\n devcontainer is already running.\n\nOptions:\n --workspace <path> Target project directory. Defaults to the current directory.\n --alias <name> SSH host alias. Defaults to <repo-name>-devcontainer.\n --recreate Remove the existing devcontainer before starting.\n --json Print JSON output. Supported by status and list.\n --help, -h Show help.\n`\n\nexport function commandWritesWorkspaceMetadata (command: BoxdownCommand): boolean {\n return [\n 'start',\n 'ssh-config-install',\n 'ssh-proxy',\n 'refresh-gh-token',\n 'refresh-gh-token-running',\n 'coding-agent'\n ].includes(command)\n}\n\nexport function parseCliArgs (argv: string[]): ParsedCli {\n const args = [...argv]\n let workspace: string | undefined\n let alias: string | undefined\n let recreate = false\n let json = false\n let passthroughArgs: string[] | undefined\n const positional: string[] = []\n\n function parsed (command: BoxdownCommand): ParsedCli {\n if (json && command !== 'status' && command !== 'list') {\n throw new Error('--json is only supported with status and list')\n }\n\n if (passthroughArgs !== undefined) {\n throw new Error('-- passthrough is only supported with coding-agent commands')\n }\n\n return { command, workspace, alias, recreate, json }\n }\n\n function parsedCodingAgent (agent: CodingAgentCli): ParsedCli {\n if (json) {\n throw new Error('--json is only supported with status and list')\n }\n\n return {\n command: 'coding-agent',\n agent,\n agentArgs: passthroughArgs ?? [],\n workspace,\n alias,\n recreate,\n json\n }\n }\n\n while (args.length > 0) {\n const arg = args.shift()\n\n if (arg === undefined) {\n break\n }\n\n if (arg === '--') {\n passthroughArgs = args.splice(0)\n break\n }\n\n if (arg === '--help' || arg === '-h') {\n return parsed('help')\n }\n\n if (arg === '--workspace') {\n const value = args.shift()\n if (value === undefined) {\n throw new Error('--workspace requires a value')\n }\n workspace = value\n continue\n }\n\n if (arg === '--alias') {\n const value = args.shift()\n if (value === undefined) {\n throw new Error('--alias requires a value')\n }\n alias = value\n continue\n }\n\n if (arg === '--recreate') {\n recreate = true\n continue\n }\n\n if (arg === '--json') {\n json = true\n continue\n }\n\n if (arg.startsWith('-')) {\n throw new Error(`Unknown option: ${arg}`)\n }\n\n positional.push(arg)\n }\n\n if (positional.length === 0) {\n return parsed('help')\n }\n\n if (positional[0] === 'start' || positional[0] === 'shell') {\n return parsed('start')\n }\n\n const codingAgent = codingAgentFromCommand(positional[0] ?? '')\n if (codingAgent !== undefined) {\n if (positional.length > 1) {\n throw new Error('Coding-agent CLI arguments must come after --')\n }\n\n return parsedCodingAgent(codingAgent)\n }\n\n if (positional[0] === 'list' && positional.length === 1) {\n return parsed('list')\n }\n\n if (positional[0] === 'status' && positional.length === 1) {\n return parsed('status')\n }\n\n if (positional[0] === 'stop' && positional.length === 1) {\n return parsed('stop')\n }\n\n if (positional[0] === 'down' && positional.length === 1) {\n return parsed('down')\n }\n\n if (positional[0] === 'doctor' && positional.length === 1) {\n return parsed('doctor')\n }\n\n if (positional[0] === 'ssh-config') {\n if (positional.length === 1 || (positional[1] === 'install' && positional.length === 2)) {\n return parsed('ssh-config-install')\n }\n\n throw new Error(`Unknown ssh-config command: ${positional.slice(1).join(' ')}. Usage: boxdown ssh-config [install] [--workspace <path>] [--alias <name>]`)\n }\n\n if (positional[0] === 'ssh-proxy' && positional.length === 1) {\n return parsed('ssh-proxy')\n }\n\n if (positional[0] === 'refresh-gh-token' && positional.length === 1) {\n return parsed('refresh-gh-token')\n }\n\n if (positional[0] === 'refresh-gh-token-running' && positional.length === 1) {\n return parsed('refresh-gh-token-running')\n }\n\n throw new Error(`Unknown command: ${positional.join(' ')}`)\n}\n\nexport async function runCli (argv: string[] = process.argv.slice(2)): Promise<number> {\n try {\n const parsed = parseCliArgs(argv)\n\n if (parsed.command === 'help') {\n process.stdout.write(USAGE)\n return 0\n }\n\n if (parsed.command === 'list') {\n const metadata = listWorkspaceMetadata(defaultDataRoot())\n const containers = await listWorkspaceContainers()\n const entries = createWorkspaceListEntries(metadata, containers, existsSync)\n\n if (parsed.json) {\n process.stdout.write(`${JSON.stringify(entries, null, 2)}\\n`)\n } else {\n process.stdout.write(formatWorkspaceListText(entries))\n }\n\n return 0\n }\n\n const context = createWorkspaceContext({ workspace: parsed.workspace })\n const alias = parsed.alias ?? defaultSshAlias(context.workspaceBasename)\n const aliasSource = parsed.alias === undefined ? 'default' : 'provided'\n\n if (commandWritesWorkspaceMetadata(parsed.command)) {\n writeWorkspaceMetadata(context, alias)\n }\n\n if (parsed.command === 'ssh-config-install') {\n await installSshConfig(context, alias)\n return 0\n }\n\n if (parsed.command === 'status') {\n const container = await findWorkspaceContainer(context)\n const status = createStatusInfo(context, alias, container, existsSync, { aliasSource })\n\n if (parsed.json) {\n process.stdout.write(`${JSON.stringify(status, null, 2)}\\n`)\n } else {\n process.stdout.write(formatStatusText(status, { color: true }))\n }\n\n return statusIsHealthy(status) ? 0 : 1\n }\n\n if (parsed.command === 'stop') {\n await stopWorkspaceContainer(context)\n return 0\n }\n\n if (parsed.command === 'down') {\n await removeWorkspaceContainer(context)\n return 0\n }\n\n if (parsed.command === 'doctor') {\n const checks = await runDoctorChecks(context)\n process.stdout.write(formatDoctorText(checks))\n return doctorHasFailures(checks) ? 1 : 0\n }\n\n if (!existsSync(context.assetsDevcontainerDir)) {\n throw new Error(`Missing Boxdown devcontainer assets: ${context.assetsDevcontainerDir}`)\n }\n\n if (parsed.command === 'ssh-proxy') {\n await installSshConfig(context, alias, { quiet: true })\n const containerId = await startDevcontainer(context, {\n recreate: parsed.recreate,\n proxyMode: true,\n reuseRunning: true\n })\n await refreshContainerCodingAgentClis(context, true)\n await ensureContainerSshRuntime(context)\n return runSshdProxy(containerId)\n }\n\n if (parsed.command === 'refresh-gh-token-running') {\n const containerId = await findRunningContainerId(context)\n if (containerId === undefined) {\n throw new Error(`No running devcontainer found for: ${context.workspaceFolder}`)\n }\n await refreshContainerGhAuth(context)\n return 0\n }\n\n if (parsed.command === 'refresh-gh-token') {\n await startDevcontainer(context)\n await refreshContainerGhAuth(context)\n return 0\n }\n\n if (parsed.command === 'coding-agent') {\n const agent = parsed.agent\n if (agent === undefined) {\n throw new Error('Missing coding-agent command')\n }\n\n await startDevcontainer(context, { recreate: parsed.recreate })\n await refreshContainerCodingAgentClis(context, false, [agent])\n return openCodingAgentCli(context, agent, parsed.agentArgs ?? [])\n }\n\n const containerId = await startDevcontainer(context, { recreate: parsed.recreate })\n await printPortHint(context, containerId)\n return openShell(context)\n } catch (error) {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`)\n return 1\n }\n}\n"],"mappings":";;;;;;;;;AAGA,MAAM,uBAAwE;CAC5E,OAAO;CACP,UAAU;CACV,QAAQ;CACR,IAAI;CACJ,aAAa;CACd;AAED,SAAgB,uBAAwB,SAA6C;AACnF,QAAO,qBAAqB;;AAG9B,SAAgB,kBAAmB,OAA+B;AAChE,KAAI,UAAU,eAAe;AAC3B,SAAO;;AAGT,QAAO;;;;;ACpBT,MAAa,2BAA2B;AAExC,MAAa,qCAAqC;AAClD,MAAa,8BAA8B;AAC3C,MAAa,4BAA4B,GAAG,4BAA4B;AACxE,MAAa,wCAAwC,GAAG,0BAA0B;AAElF,MAAa,eAAe;;;;ACY5B,SAAS,+BAAgC,aAA8C;AACrF,QAAO,OAAO,YAAY,QAAQ,WAAW,YAAY,MAAM,YAAY,KAAK;;AAGlF,SAAgB,uBAAwB,SAAmD;CACzF,MAAM,qBAAqB,cAAc,KAAK,QAAQ,aAAa,eAAe,CAAC;CACnF,IAAI;AAEJ,KAAI;AACF,oBAAkB,mBAAmB,QAAQ,kCAAkC;UACxE,OAAO;AACd,QAAM,IAAI,MAAM,wGAAwG,yBAAyB,iBAAiB,EAAE,OAAO,OAAO,CAAC;;CAGrL,MAAM,cAAc,KAAK,MAAM,aAAa,iBAAiB,OAAO,CAAC;CACrE,MAAM,MAAM,+BAA+B,YAAY;AAEvD,KAAI,YAAY,sBAAsC;AACpD,QAAM,IAAI,MAAM,uCAAuC,yBAAyB,gBAAgB,YAAY,WAAW,qBAAqB,GAAG;;AAGjJ,KAAI,QAAQ,WAAW;AACrB,QAAM,IAAI,MAAM,8FAA8F;;CAGhH,MAAM,UAAU,QAAQ,QAAQ,gBAAgB,EAAE,IAAI;AAEtD,KAAI,CAAC,WAAW,QAAQ,EAAE;AACxB,QAAM,IAAI,MAAM,gFAAgF,UAAU;;AAG5G,QAAO;EACL,SAAS,QAAQ;EACjB,YAAY,CAAC,QAAQ;EACrB,MAAM;EACN,SAAS,YAAY;EACtB;;;;;ACvCH,SAAS,UAAW,KAA4C;AAC9D,QAAO;EACL,GAAG,QAAQ;EACX,GAAI,OAAO,EAAE;EACd;;AAGH,SAAS,WAAY,QAAqC,OAAqB;AAC7E,KAAI,WAAW,UAAU;AACvB,UAAQ,OAAO,MAAM,MAAM;YAClB,WAAW,UAAU;AAC9B,UAAQ,OAAO,MAAM,MAAM;;;AAI/B,SAAgB,YAAa,SAAiB,MAAgB,UAAkC,EAAE,EAA0B;AAC1H,QAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,QAAQ,MAAM,SAAS,MAAM;GACjC,KAAK,QAAQ;GACb,KAAK,UAAU,QAAQ,IAAI;GAC3B,OAAO;IAAC;IAAQ;IAAQ;IAAO;GAChC,CAAC;EAEF,MAAM,eAAyB,EAAE;EACjC,MAAM,eAAyB,EAAE;AAEjC,QAAM,OAAO,GAAG,SAAS,UAAkB;AACzC,gBAAa,KAAK,MAAM;AACxB,cAAW,QAAQ,gBAAgB,UAAU,MAAM;IACnD;AAEF,QAAM,OAAO,GAAG,SAAS,UAAkB;AACzC,gBAAa,KAAK,MAAM;AACxB,cAAW,QAAQ,gBAAgB,UAAU,MAAM;IACnD;AAEF,QAAM,GAAG,UAAU,UAAU;AAC3B,WAAQ;IACN,MAAM;IACN,QAAQ,OAAO,OAAO,aAAa,CAAC,SAAS,OAAO;IACpD,QAAQ,GAAG,OAAO,OAAO,aAAa,CAAC,SAAS,OAAO,GAAG,MAAM,QAAQ;IACzE,CAAC;IACF;AAEF,QAAM,GAAG,UAAU,SAAS;AAC1B,WAAQ;IACN,MAAM,QAAQ;IACd,QAAQ,OAAO,OAAO,aAAa,CAAC,SAAS,OAAO;IACpD,QAAQ,OAAO,OAAO,aAAa,CAAC,SAAS,OAAO;IACrD,CAAC;IACF;AAEF,MAAI,QAAQ,UAAU,WAAW;AAC/B,SAAM,MAAM,IAAI,QAAQ,MAAM;SACzB;AACL,SAAM,MAAM,KAAK;;GAEnB;;AAGJ,SAAgB,eAAgB,SAAiB,MAAgB,UAAuD,EAAE,EAAmB;AAC3I,QAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,QAAQ,MAAM,SAAS,MAAM;GACjC,KAAK,QAAQ;GACb,KAAK,UAAU,QAAQ,IAAI;GAC3B,OAAO;GACR,CAAC;AAEF,QAAM,GAAG,UAAU,UAAU;AAC3B,WAAQ,OAAO,MAAM,GAAG,MAAM,QAAQ,IAAI;AAC1C,WAAQ,IAAI;IACZ;AAEF,QAAM,GAAG,UAAU,SAAS;AAC1B,WAAQ,QAAQ,EAAE;IAClB;GACF;;;;;AC9EJ,SAAS,kBAAmB,SAA0B;CACpD,MAAM,QAAQ,OAAO,SAAS,QAAQ,MAAM,IAAI,CAAC,MAAM,IAAI,GAAG;AAC9D,QAAO,OAAO,UAAU,MAAM,IAAI,SAAS;;AAG7C,SAAS,MAAO,MAAc,MAAe,WAAmB,aAAkC;AAChG,QAAO;EACL;EACA,OAAO,OAAO,OAAO;EACrB,SAAS,OAAO,YAAY;EAC7B;;AAGH,eAAe,aAAc,SAAiB,MAAkC;CAC9E,MAAM,SAAS,MAAM,YAAY,SAAS,MAAM;EAC9C,cAAc;EACd,cAAc;EACf,CAAC;AAEF,QAAO,OAAO,SAAS;;AAGzB,eAAe,cAAe,SAAiB,MAAkC;CAC/E,MAAM,SAAS,MAAM,YAAY,SAAS,MAAM;EAC9C,cAAc;EACd,cAAc;EACf,CAAC;AAEF,QAAO,OAAO,SAAS;;AAGzB,eAAsB,gBAAiB,SAAmD;CACxF,MAAM,SAAwB,EAAE;CAChC,MAAM,cAAc,QAAQ,SAAS;AAErC,QAAO,KAAK,MACV,QACA,kBAAkB,YAAY,EAC9B,QAAQ,eACR,QAAQ,YAAY,qBACrB,CAAC;AAEF,QAAO,KAAK,MACV,qBACA,MAAM,6BAA6B,QAAQ,EAC3C,4CACA,gEACD,CAAC;AAEF,QAAO,KAAK,MACV,cACA,MAAM,aAAa,UAAU,CAAC,YAAY,CAAC,EAC3C,2BACA,+CACD,CAAC;AAEF,QAAO,KAAK,MACV,iBACA,MAAM,aAAa,UAAU,CAAC,OAAO,CAAC,EACtC,8BACA,kDACD,CAAC;AAEF,QAAO,KAAK,MACV,OACA,MAAM,cAAc,OAAO,CAAC,KAAK,CAAC,EAClC,oBACA,wCACD,CAAC;AAEF,QAAO,KAAK,MACV,cACA,MAAM,cAAc,cAAc,CAAC,KAAK,CAAC,EACzC,2BACA,+CACD,CAAC;AAEF,QAAO,KAAK,MACV,UACA,WAAW,QAAQ,sBAAsB,EACzC,gCAAgC,QAAQ,yBACxC,wCAAwC,QAAQ,wBACjD,CAAC;AAEF,KAAI,MAAM,aAAa,MAAM,CAAC,YAAY,CAAC,EAAE;EAC3C,MAAM,SAAS,MAAM,aAAa,MAAM;GAAC;GAAQ;GAAU;GAAc;GAAa,CAAC;AACvF,SAAO,KAAK;GACV,MAAM;GACN,OAAO,SAAS,OAAO;GACvB,SAAS,SAAS,iCAAiC;GACpD,CAAC;QACG;AACL,SAAO,KAAK;GACV,MAAM;GACN,OAAO;GACP,SAAS;GACV,CAAC;;AAGJ,QAAO;;AAGT,eAAe,6BAA8B,SAA6C;AACxF,KAAI;EACF,MAAM,MAAM,uBAAuB,QAAQ;AAC3C,SAAO,MAAM,aAAa,IAAI,SAAS,CAAC,GAAG,IAAI,YAAY,YAAY,CAAC;SAClE;AACN,SAAO;;;AAIX,SAAgB,kBAAmB,QAAgC;AACjE,QAAO,OAAO,MAAM,SAAS,KAAK,UAAU,OAAO;;AAGrD,SAAgB,iBAAkB,QAA+B;CAC/D,MAAM,QAAQ,CAAC,kBAAkB,GAAG;AAEpC,MAAK,MAAM,QAAQ,QAAQ;AACzB,QAAM,KAAK,IAAI,KAAK,MAAM,IAAI,KAAK,KAAK,IAAI,KAAK,UAAU;;AAG7D,OAAM,KAAK,IAAI,kBAAkB,OAAO,GAAG,mBAAmB,aAAa;AAC3E,QAAO,GAAG,MAAM,KAAK,KAAK,CAAC;;;;;ACzI7B,SAAgB,kBAAmB,OAAuB;CACxD,IAAI,SAAS;CACb,IAAI,WAAW;CACf,IAAI,UAAU;AAEd,MAAK,IAAI,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS,GAAG;EACpD,MAAM,OAAO,MAAM;EACnB,MAAM,OAAO,MAAM,QAAQ;AAE3B,MAAI,SAAS,WAAW;AACtB;;AAGF,MAAI,UAAU;AACZ,aAAU;AAEV,OAAI,SAAS;AACX,cAAU;cACD,SAAS,MAAM;AACxB,cAAU;cACD,SAAS,MAAK;AACvB,eAAW;;AAGb;;AAGF,MAAI,SAAS,MAAK;AAChB,cAAW;AACX,aAAU;AACV;;AAGF,MAAI,SAAS,OAAO,SAAS,KAAK;AAChC,UAAO,QAAQ,MAAM,UAAU,MAAM,WAAW,MAAM;AACpD,aAAS;;AAGX,OAAI,MAAM,WAAW,MAAM;AACzB,cAAU;;AAGZ;;AAGF,MAAI,SAAS,OAAO,SAAS,KAAK;AAChC,YAAS;AAET,UAAO,QAAQ,MAAM,QAAQ;AAC3B,QAAI,MAAM,WAAW,MAAM;AACzB,eAAU;;AAGZ,QAAI,MAAM,WAAW,OAAO,MAAM,QAAQ,OAAO,KAAK;AACpD,cAAS;AACT;;AAGF,aAAS;;AAGX;;AAGF,YAAU;;AAGZ,QAAO;;AAGT,SAAgB,oBAAqB,OAAuB;CAC1D,IAAI,SAAS;CACb,IAAI,WAAW;CACf,IAAI,UAAU;AAEd,MAAK,IAAI,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS,GAAG;EACpD,MAAM,OAAO,MAAM;AAEnB,MAAI,SAAS,WAAW;AACtB;;AAGF,MAAI,UAAU;AACZ,aAAU;AAEV,OAAI,SAAS;AACX,cAAU;cACD,SAAS,MAAM;AACxB,cAAU;cACD,SAAS,MAAK;AACvB,eAAW;;AAGb;;AAGF,MAAI,SAAS,MAAK;AAChB,cAAW;AACX,aAAU;AACV;;AAGF,MAAI,SAAS,KAAK;GAChB,IAAI,YAAY,QAAQ;AAExB,UAAO,KAAK,KAAK,MAAM,cAAc,GAAG,EAAE;AACxC,iBAAa;;AAGf,OAAI,MAAM,eAAe,OAAO,MAAM,eAAe,KAAK;AACxD;;;AAIJ,YAAU;;AAGZ,QAAO;;AAGT,SAAgB,WAAgB,OAAkB;AAChD,QAAO,KAAK,MAAM,oBAAoB,kBAAkB,MAAM,CAAC,CAAC;;;;;ACzHlE,SAAgB,WAAY,OAAuB;AACjD,KAAI,MAAM,WAAW,GAAG;AACtB,SAAO;;AAGT,QAAO,IAAI,MAAM,WAAW,KAAK,QAAQ,CAAC;;AAG5C,SAAgB,eAAgB,OAAuB;AACrD,QAAO,IAAI,MAAM,WAAW,MAAM,OAAO,CAAC,WAAW,MAAK,OAAM,CAAC;;AAGnE,MAAa,0BAA0B;AAEvC,SAAgB,wBAAyB,MAAyB,QAAQ,KAAe;AACvF,QAAO;EACL,QAAQ,IAAI,QAAQ;EACpB;EACA,yBAAyB,IAAI,yBAAyB;EACtD,2BAA2B,IAAI,2BAA2B,WAA+B;EAC1F;;AAGH,SAAS,4BAAqC;AAC5C,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,KAAK,KAAK;;AAGd,SAAgB,yBAAkC;AAChD,QAAO,CACL,2BAA2B,EAC3B,eACD,CAAC,KAAK,KAAK;;AAGd,SAAgB,2BAAoC;AAClD,QAAO,CACL,2BAA2B,EAC3B,cACD,CAAC,KAAK,KAAK;;;;;ACnCd,SAAgB,2BAA4B,uBAAmD;CAC7F,MAAM,aAAa,KAAK,uBAAuB,oBAAoB;AACnE,QAAO,WAA+B,aAAa,YAAY,OAAO,CAAC;;AAGzE,SAAgB,iCAAkC,SAA+C;CAC/F,MAAM,aAAa,2BAA2B,QAAQ,sBAAsB;CAC5E,MAAM,SAAS,MAAM,QAAQ,WAAW,OAAO,GAC3C,WAAW,OAAO,QAAQ,UAA2B,OAAO,UAAU,SAAS,GAC/E,EAAE;CAEN,MAAM,gBAAgB,CACpB,oBAAoB,QAAQ,sBAAsB,UAAU,mCAAmC,YAC/F,oBAAoB,QAAQ,uBAAuB,UAAU,0BAA0B,WACxF;AAED,QAAO;EACL,GAAG;EACH,MAAM,YAAY,QAAQ;EAC1B,QAAQ,CAAC,GAAG,QAAQ,GAAG,cAAc;EACrC,mBAAmB,4BAA4B,WAAW,QAAQ,gBAAgB,CAAC,QAAQ,WAAW,KAAK,QAAQ,uBAAuB,SAAS,gBAAgB,CAAC;EACpK,mBAAmB,QAAQ,WAAW,GAAG,mCAAmC,uBAAuB;EACnG,kBAAkB,QAAQ,WAAW,GAAG,mCAAmC,sBAAsB;EACjG,cAAc;GACZ,GAAI,WAAW,gBAAgB,EAAE;GACjC,oCAAoC;GACpC,4BAA4B;GAC5B,kCAAkC;GACnC;EACF;;AAGH,SAAgB,iCAAkC,SAA+C;CAC/F,MAAM,SAAS,iCAAiC,QAAQ;AACxD,WAAU,QAAQ,mBAAmB,EAAE,WAAW,MAAM,CAAC;AACzD,eAAc,QAAQ,qBAAqB,GAAG,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC,IAAI;AAClF,QAAO;;AAGT,SAAgB,+BAAgC,QAAgD;AAC9F,QAAO,OAAO,SAAS,MAAM,QAAQ,oBAAoB,KAAK,IAAI,CAAC,EAAE,MAAM,KAAK,CAAC;;;;;AC7DnF,SAAgB,yBAA0B,WAAuC;CAC/E,MAAM,UAAU,UAAU,MAAM;CAChC,MAAM,UAAU,mCAAmC,KAAK,QAAQ;AAEhE,KAAI,YAAY,MAAM;AACpB,SAAO,4BAA4B,QAAQ,IAAI,QAAQ,GAAG;;AAG5D,KAAI;EACF,MAAM,SAAS,IAAI,IAAI,QAAQ;EAC/B,MAAM,WAAW,OAAO,SAAS,aAAa;EAC9C,MAAM,OAAO,OAAO,SAAS,aAAa;AAE1C,MAAI,SAAS,cAAc;AACzB,UAAO;;AAGT,MAAI,aAAa,UAAU,OAAO,aAAa,OAAO;AACpD,UAAO;;AAGT,MAAI,aAAa,YAAY,aAAa,QAAQ;AAChD,UAAO;;EAGT,MAAM,QAAQ,OAAO,SAAS,MAAM,IAAI,CAAC,QAAQ,SAAS,KAAK,SAAS,EAAE;AAC1E,MAAI,MAAM,WAAW,GAAG;AACtB,UAAO;;EAGT,MAAM,CAAC,OAAO,QAAQ;AACtB,SAAO,4BAA4B,OAAO,KAAK;SACzC;AACN,SAAO;;;AAIX,SAAS,4BAA6B,OAA2B,MAA8C;AAC7G,KAAI,UAAU,aAAa,SAAS,WAAW;AAC7C,SAAO;;CAGT,MAAM,kBAAkB,MAAM,MAAM;CACpC,MAAM,iBAAiB,KAAK,MAAM,CAAC,QAAQ,QAAQ,GAAG,CAAC,QAAQ,WAAW,GAAG;AAE7E,KACE,gBAAgB,WAAW,KAC3B,eAAe,WAAW,KAC1B,gBAAgB,SAAS,IAAI,IAC7B,eAAe,SAAS,IAAI,EAC5B;AACA,SAAO;;AAGT,QAAO,sBAAsB,gBAAgB,GAAG,eAAe;;AAGjE,eAAsB,gCAAiC,iBAA2C;CAChG,MAAM,iBAAiB,MAAM,gBAAgB,iBAAiB,CAAC,aAAa,wBAAwB,CAAC;AAErG,KAAI,eAAe,SAAS,KAAK,eAAe,OAAO,MAAM,KAAK,QAAQ;AACxE,SAAO;;CAGT,MAAM,eAAe,MAAM,gBAAgB,iBAAiB;EAAC;EAAU;EAAW;EAAgB;EAAsB,CAAC;AAEzH,KAAI,aAAa,SAAS,GAAG;AAC3B,SAAO;;CAGT,MAAM,gBAAgB,wBAAwB,aAAa,OAAO;AAElE,KAAI,cAAc,WAAW,GAAG;AAC9B,SAAO;;AAGT,MAAK,MAAM,UAAU,eAAe;EAClC,MAAM,WAAW,MAAM,gBAAgB,iBAAiB;GAAC;GAAU;GAAW,OAAO;GAAM,OAAO;GAAI,CAAC;AACvG,MAAI,SAAS,SAAS,GAAG;AACvB,UAAO;;EAGT,MAAM,UAAU,MAAM,gBAAgB,iBAAiB;GAAC;GAAU;GAAW;GAAU,OAAO;GAAM,OAAO;GAAI,CAAC;AAChH,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAO;;;AAIX,OAAM,gBAAgB,iBAAiB;EAAC;EAAU;EAAW;EAAe;EAAuC,CAAC;CAEpH,MAAM,cAAc,MAAM,gBAAgB,iBAAiB;EAAC;EAAU;EAAW;EAAS;EAAwC;EAAG,CAAC;AACtI,KAAI,YAAY,SAAS,GAAG;AAC1B,SAAO;;CAGT,MAAM,WAAW,MAAM,gBAAgB,iBAAiB;EAAC;EAAU;EAAW;EAAS;EAAwC;EAA0B,CAAC;AAC1J,KAAI,SAAS,SAAS,GAAG;AACvB,SAAO;;AAGT,MAAK,MAAM,aAAa,IAAI,IAAI,cAAc,KAAK,WAAW,OAAO,IAAI,CAAC,EAAE;EAC1E,MAAM,UAAU,MAAM,gBAAgB,iBAAiB;GAAC;GAAU;GAAW;GAAiB,OAAO,UAAU;GAAa;GAAU,CAAC;AACvI,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAO;;;AAIX,QAAO;;AAGT,SAAS,wBAAyB,cAA4D;CAC5F,MAAM,UAAgD,EAAE;AAExD,MAAK,MAAM,QAAQ,aAAa,MAAM,QAAQ,EAAE;EAC9C,MAAM,QAAQ,6BAA6B,KAAK,KAAK;AACrD,MAAI,UAAU,MAAM;AAClB;;EAGF,MAAM,OAAO,MAAM;EACnB,MAAM,SAAS,MAAM;AACrB,MAAI,SAAS,aAAa,WAAW,WAAW;AAC9C;;EAGF,MAAM,MAAM,yBAAyB,OAAO;AAC5C,MAAI,QAAQ,WAAW;AACrB,WAAQ,KAAK;IAAE;IAAM;IAAK,CAAC;;;AAI/B,QAAO;;AAGT,eAAe,gBAAiB,iBAAyB,MAAgB;AACvE,QAAO,YAAY,OAAO,MAAM;EAC9B,KAAK;EACL,cAAc;EACd,cAAc;EACf,CAAC;;;;;ACxIJ,eAAsB,iBAAkB,SAA2B,QAAQ,OAAsB;AAC/F,WAAU,QAAQ,WAAW;EAAE,WAAW;EAAM,MAAM;EAAO,CAAC;AAE9D,KAAI,CAAC,WAAW,QAAQ,WAAW,EAAE;AACnC,MAAI,CAAC,OAAO;AACV,WAAQ,OAAO,MAAM,oCAAoC,QAAQ,WAAW,IAAI;;EAGlF,MAAM,SAAS,MAAM,YAAY,cAAc;GAC7C;GACA;GACA;GACA,QAAQ;GACR;GACA;GACA;GACA,GAAG,QAAQ,kBAAkB;GAC9B,EAAE;GACD,cAAc,QAAQ,QAAQ;GAC9B,cAAc;GACf,CAAC;AAEF,MAAI,OAAO,SAAS,GAAG;AACrB,SAAM,IAAI,MAAM,oCAAoC,QAAQ,aAAa;;;AAI7E,KAAI,CAAC,WAAW,QAAQ,iBAAiB,EAAE;EACzC,MAAM,SAAS,MAAM,YAAY,cAAc;GAAC;GAAM;GAAM,QAAQ;GAAW,EAAE;GAC/E,cAAc;GACd,cAAc;GACf,CAAC;AAEF,MAAI,OAAO,SAAS,GAAG;AACrB,SAAM,IAAI,MAAM,oCAAoC,QAAQ,mBAAmB;;AAGjF,gBAAc,QAAQ,kBAAkB,OAAO,OAAO;;AAGxD,WAAU,QAAQ,wBAAwB;EAAE,WAAW;EAAM,MAAM;EAAO,CAAC;AAC3E,eAAc,QAAQ,yBAAyB,aAAa,QAAQ,kBAAkB,OAAO,CAAC;AAE9F,WAAU,QAAQ,YAAY,IAAM;AACpC,WAAU,QAAQ,kBAAkB,IAAM;AAC1C,WAAU,QAAQ,yBAAyB,IAAM;;;;;AC3CnD,SAAgB,gBAAiB,mBAAmC;AAClE,QAAO,GAAG,kBAAkB;;AAG9B,SAAgB,iBAAkB,OAAqB;AACrD,KAAI,CAAC,oBAAoB,KAAK,MAAM,EAAE;AACpC,QAAM,IAAI,MAAM,8CAA8C,QAAQ;;;AAI1E,SAAgB,qBAAsB,MAAyB,QAAQ,KAAa;AAClF,QAAO,IAAI,sBAAsB,IAAI,2BAA2B,KAAK,IAAI,QAAQ,IAAI,QAAQ,SAAS;;AAGxG,SAAgB,kBAAmB,SAA2B,OAAuB;CACnF,MAAM,UAAU,KAAK,QAAQ,aAAa,QAAQ,OAAO,UAAU;AAEnE,QAAO,GAAG,WAAW,QAAQ,SAAS,CAAC,GAAG,WAAW,QAAQ,CAAC,yBAAyB,WAAW,QAAQ,gBAAgB,CAAC,WAAW,WAAW,MAAM;;AAGzJ,SAAgB,oBAAqB,SAA2B,OAAuB;AACrF,kBAAiB,MAAM;AAEvB,QAAO;EACL,WAAW,MAAM;EACjB,QAAQ;EACR,cAAc;EACd;EACA;EACA,kBAAkB,eAAe,QAAQ,WAAW;EACpD;EACA,kBAAkB,kBAAkB,SAAS,MAAM;EACnD;EACA;EACA;EACA,SAAS,MAAM;EACf;EACD,CAAC,KAAK,KAAK;;AAGd,SAAgB,sBAAuB,gBAAwB,OAAe,OAAuB;CACnG,MAAM,QAAQ,WAAW,MAAM;CAC/B,MAAM,MAAM,SAAS,MAAM;CAC3B,MAAM,QAAQ,eAAe,MAAM,QAAQ;CAC3C,MAAM,YAAsB,EAAE;CAC9B,IAAI,WAAW;AAEf,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,SAAS,OAAO;AAClB,cAAW;AACX;;AAGF,MAAI,SAAS,KAAK;AAChB,cAAW;AACX;;AAGF,MAAI,CAAC,UAAU;AACb,aAAU,KAAK,KAAK;;;AAIxB,QAAO,UAAU,SAAS,KAAK,UAAU,UAAU,SAAS,OAAO,IAAI;AACrE,YAAU,KAAK;;AAGjB,QAAO,GAAG,UAAU,KAAK,KAAK,GAAG,UAAU,SAAS,IAAI,SAAS,KAAK;;AAGxE,eAAsB,iBAAkB,SAA2B,OAAe,UAAoD,EAAE,EAAiB;AACvJ,kBAAiB,MAAM;AACvB,OAAM,iBAAiB,SAAS,QAAQ,SAAS,MAAM;CAEvD,MAAM,gBAAgB,QAAQ,cAAc,sBAAsB;CAClE,MAAM,eAAe,QAAQ,cAAc;AAC3C,WAAU,cAAc;EAAE,WAAW;EAAM,MAAM;EAAO,CAAC;AAEzD,KAAI,CAAC,WAAW,cAAc,EAAE;AAC9B,gBAAc,eAAe,GAAG;;AAGlC,WAAU,cAAc,IAAM;AAC9B,WAAU,eAAe,IAAM;CAE/B,MAAM,iBAAiB,aAAa,eAAe,OAAO;CAC1D,MAAM,QAAQ,oBAAoB,SAAS,MAAM;CACjD,MAAM,aAAa,sBAAsB,gBAAgB,OAAO,MAAM;AAEtE,KAAI,eAAe,gBAAgB;AACjC,gBAAc,eAAe,WAAW;AACxC,MAAI,CAAC,QAAQ,OAAO;AAClB,WAAQ,OAAO,MAAM,wBAAwB,MAAM,IAAI;;YAEhD,CAAC,QAAQ,OAAO;AACzB,UAAQ,OAAO,MAAM,iCAAiC,MAAM,IAAI;;AAGlE,WAAU,eAAe,IAAM;AAE/B,KAAI,CAAC,QAAQ,OAAO;AAClB,UAAQ,OAAO,MAAM,eAAe,cAAc,IAAI;AACtD,UAAQ,OAAO,MAAM,kBAAkB,QAAQ,WAAW,MAAM;AAChE,UAAQ,OAAO,MAAM,yBAAyB,MAAM,oBAAoB;;;;;;ACzC5E,SAAS,uBAAwB,QAAwC;AACvE,QAAO,OAAO,YAAY,OACvB,MAAM,IAAI,CACV,KAAK,UAAU,MAAM,MAAM,CAAC,CAC5B,QAAQ,UAAU,MAAM,SAAS,EAAE,CACnC,KAAK,UAAU;EACd,MAAM,YAAY,MAAM,QAAQ,IAAI;AACpC,SAAO,cAAc,CAAC,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,MAAM,MAAM,GAAG,UAAU,EAAE,MAAM,MAAM,YAAY,EAAE,CAAC;GAC/F,CAAC;;AAGP,SAAgB,uBAAwB,QAAoC;CAC1E,MAAM,QAAQ,OAAO,MAAM,QAAQ,CAAC,QAAQ,SAAS,KAAK,MAAM,CAAC,SAAS,EAAE;AAE5E,QAAO,MAAM,KAAK,SAAS;EACzB,IAAI;AAEJ,MAAI;AACF,YAAS,KAAK,MAAM,KAAK;UACnB;AACN,SAAM,IAAI,MAAM,qCAAqC,OAAO;;AAG9D,MAAI,OAAO,OAAO,OAAO,YAAY,OAAO,GAAG,WAAW,GAAG;AAC3D,SAAM,IAAI,MAAM,6CAA6C,OAAO;;AAGtE,SAAO;GACL,IAAI,OAAO;GACX,MAAM,OAAO,OAAO,UAAU,YAAY,OAAO,MAAM,SAAS,IAAI,OAAO,QAAQ;GACnF,OAAO,OAAO,OAAO,UAAU,YAAY,OAAO,MAAM,SAAS,IAAI,OAAO,QAAQ;GACpF,QAAQ,OAAO,OAAO,WAAW,YAAY,OAAO,OAAO,SAAS,IAAI,OAAO,SAAS;GACxF,aAAa,OAAO,OAAO,WAAW,WAAW,uBAAuB,OAAO,OAAO,CAAC,+BAA+B;GACvH;GACD;;AAGJ,SAAS,aAAc,MAAsB;AAC3C,QAAO,aAAa,MAAM,OAAO;;AAGnC,SAAS,uBAAwB,OAA+C;AAC9E,QAAO;EACL,OAAO,WAAW,MAAM;EACxB,KAAK,SAAS,MAAM;EACrB;;AAGH,SAAS,0BAA2B,QAAgB,OAAmC;CACrF,MAAM,EAAE,OAAO,QAAQ,uBAAuB,MAAM;CACpD,MAAM,aAAa,OAAO,QAAQ,MAAM;AAExC,KAAI,eAAe,CAAC,GAAG;AACrB,SAAO;;CAGT,MAAM,WAAW,OAAO,QAAQ,KAAK,WAAW;AAEhD,KAAI,aAAa,CAAC,GAAG;AACnB,SAAO;;CAGT,MAAM,sBAAsB,WAAW,IAAI;CAC3C,MAAM,oBAAoB,OAAO,yBAAyB,OAAO,sBAAsB,IAAI;AAE3F,QAAO,OAAO,MAAM,YAAY,kBAAkB;;AAGpD,SAAgB,uBACd,SACA,OACA,YACA,QACA,WAAqC,cACpB;CACjB,MAAM,eAAe,OAAO,WAAW;AAEvC,KAAI,CAAC,cAAc;AACjB,SAAO;GACL;GACA;GACA,mBAAmB;GACpB;;CAGH,MAAM,SAAS,SAAS,WAAW;CACnC,MAAM,eAAe,0BAA0B,QAAQ,MAAM;AAE7D,KAAI,iBAAiB,WAAW;AAC9B,SAAO;GACL;GACA;GACA,mBAAmB;GACpB;;AAGH,QAAO;EACL;EACA;EACA,mBAAmB,iBAAiB,oBAAoB,SAAS,MAAM,GAAG,cAAc;EACzF;;AAGH,SAAgB,iBACd,SACA,OACA,WACA,QACA,UAII,EAAE,EACM;CACZ,MAAM,QAAQ,WAAW,OAAO,aAAa;CAC7C,MAAM,YAAY,uBAChB,SACA,OACA,QAAQ,iBAAiB,sBAAsB,EAC/C,QACA,QAAQ,SACT;AAED,QAAO;EACL,WAAW;GACT,QAAQ,QAAQ;GAChB,UAAU,QAAQ;GAClB,IAAI,QAAQ;GACb;EACD,KAAK;GACH;GACA,aAAa,QAAQ,eAAe;GACpC,YAAY,UAAU;GACtB,cAAc,UAAU;GACxB,mBAAmB,UAAU;GAC7B,SAAS,QAAQ;GACjB,WAAW,OAAO,QAAQ,WAAW;GACrC,eAAe,QAAQ;GACvB,iBAAiB,OAAO,QAAQ,iBAAiB;GACjD,sBAAsB,QAAQ;GAC9B,wBAAwB,OAAO,QAAQ,wBAAwB;GAChE;EACD,OAAO;GACL,WAAW,QAAQ;GACnB,UAAU,QAAQ;GAClB,mBAAmB,QAAQ;GAC3B,kBAAkB,QAAQ;GAC1B,qBAAqB,QAAQ;GAC7B,uBAAuB,OAAO,QAAQ,oBAAoB;GAC1D,uBAAuB,QAAQ;GAC/B,0BAA0B,OAAO,QAAQ,sBAAsB;GAChE;EACD,WAAW;GACT,OAAO,cAAc;GACrB,SAAS,UAAU;GACnB,IAAI,WAAW;GACf,MAAM,WAAW;GACjB,OAAO,WAAW;GAClB,QAAQ,WAAW;GACpB;EACF;;AAGH,SAAgB,gBAAiB,QAA6B;AAC5D,QAAO,OAAO,MAAM,yBAClB,OAAO,MAAM,4BACb,OAAO,IAAI,aACX,OAAO,IAAI,mBACX,OAAO,IAAI,0BACX,OAAO,UAAU,SACjB,OAAO,UAAU;;AAGrB,MAAM,QAAQ;CACZ,OAAO;CACP,KAAK;CACL,OAAO;CACR;AAED,SAAS,SAAU,OAAe,WAA4B,SAA0B;AACtF,KAAI,CAAC,SAAS;AACZ,SAAO;;AAGT,QAAO,GAAG,MAAM,aAAa,QAAQ,MAAM;;AAG7C,SAAS,cAAe,OAAgB,cAA+B;AACrE,QAAO,SAAS,QAAQ,WAAW,WAAW,QAAQ,UAAU,OAAO,aAAa;;AAGtF,SAAS,YAAa,OAAgB,cAA+B;AACnE,QAAO,SAAS,QAAQ,QAAQ,MAAM,QAAQ,UAAU,OAAO,aAAa;;AAG9E,SAAS,iBAAkB,OAA6B,cAA+B;AACrF,QAAO,SAAS,OAAO,UAAU,cAAc,UAAU,OAAO,aAAa;;AAG/E,SAAS,gBAAiB,QAAgC;AACxD,QAAO,WAAW,YAAY,qBAAqB;;AAGrD,SAAS,cAAe,OAAqC;AAC3D,QAAO,UAAU,cAAc,cAAc;;AAG/C,SAAS,UAAW,OAAe,SAAkB,cAA+B;AAClF,QAAO,SAAS,OAAO,UAAU,UAAU,OAAO,aAAa;;AAGjE,SAAgB,iBAAkB,QAAoB,UAA+B,EAAE,EAAU;CAC/F,MAAM,eAAe,QAAQ,SAAS;CACtC,MAAM,iBAAiB,OAAO,UAAU,QAAQ,OAAO,UAAU,SAAS,YAAY;CACtF,MAAM,UAAU,gBAAgB,OAAO;CACvC,MAAM,QAAQ;EACZ;EACA;EACA;EACA,WAAW,OAAO,UAAU;EAC5B,WAAW,OAAO,UAAU;EAC5B,SAAS,OAAO,UAAU;EAC1B,gBAAgB,OAAO,IAAI,MAAM,IAAI,gBAAgB,OAAO,IAAI,YAAY,CAAC,IAAI,cAAc,OAAO,IAAI,kBAAkB,CAAC;EAC7H;EACA;EACA,iBAAiB,OAAO,MAAM;EAC9B,gBAAgB,OAAO,MAAM;EAC7B,sBAAsB,OAAO,MAAM;EACnC,qBAAqB,OAAO,MAAM;EAClC,uBAAuB,OAAO,MAAM,oBAAoB,IAAI,cAAc,OAAO,MAAM,uBAAuB,aAAa,CAAC;EAC5H,0BAA0B,OAAO,MAAM,sBAAsB,IAAI,cAAc,OAAO,MAAM,0BAA0B,aAAa,CAAC;EACpI;EACA;EACA,iBAAiB,OAAO,IAAI,WAAW,IAAI,cAAc,OAAO,IAAI,cAAc,aAAa,CAAC;EAChG,wBAAwB,iBAAiB,OAAO,IAAI,mBAAmB,aAAa;EACpF,kBAAkB,OAAO,IAAI,QAAQ,IAAI,cAAc,OAAO,IAAI,WAAW,aAAa,CAAC;EAC3F,iBAAiB,OAAO,IAAI,cAAc,IAAI,cAAc,OAAO,IAAI,iBAAiB,aAAa,CAAC;EACtG,yBAAyB,OAAO,IAAI,qBAAqB,IAAI,cAAc,OAAO,IAAI,wBAAwB,aAAa,CAAC;EAC5H;EACA;EACA,YAAY,UAAU,gBAAgB,SAAS,aAAa;EAC5D,cAAc,YAAY,OAAO,UAAU,SAAS,aAAa;EAClE;AAED,KAAI,OAAO,UAAU,OAAO,WAAW;AACrC,QAAM,KAAK,SAAS,OAAO,UAAU,KAAK;;AAG5C,KAAI,OAAO,UAAU,SAAS,WAAW;AACvC,QAAM,KAAK,WAAW,OAAO,UAAU,OAAO;;AAGhD,KAAI,OAAO,UAAU,WAAW,WAAW;AACzC,QAAM,KAAK,oBAAoB,OAAO,UAAU,SAAS;;AAG3D,QAAO,GAAG,MAAM,KAAK,KAAK,CAAC;;;;;AClT7B,SAAS,0BAA2B,SAAqC;AACvE,QAAO;EACL;EACA,QAAQ;EACR;EACA,QAAQ;EACT;;AAGH,SAAS,IAAK,SAAiB,YAAY,OAAa;AACtD,KAAI,WAAW;AACb,UAAQ,OAAO,MAAM,GAAG,QAAQ,IAAI;QAC/B;AACL,UAAQ,OAAO,MAAM,GAAG,QAAQ,IAAI;;;AAIxC,SAAgB,6BAA8B,QAAoC;AAChF,QAAO,gCAAgC,KAAK,OAAO,GAAG;;AAGxD,eAAsB,uBAAwB,SAAkE;CAC9G,MAAM,SAAS,MAAM,YAAY,UAAU;EACzC;EACA;EACA;EACA,mCAAmC,QAAQ;EAC3C;EACA;EACD,EAAE;EACD,cAAc;EACd,cAAc;EACf,CAAC;AAEF,KAAI,OAAO,SAAS,GAAG;AACrB,QAAM,IAAI,MAAM,sCAAsC,QAAQ,kBAAkB;;AAGlF,QAAO,uBAAuB,OAAO,OAAO,CAAC;;AAG/C,eAAsB,0BAAoE;CACxF,MAAM,SAAS,MAAM,YAAY,UAAU;EACzC;EACA;EACA;EACA;EACA;EACA;EACD,EAAE;EACD,cAAc;EACd,cAAc;EACf,CAAC;AAEF,KAAI,OAAO,SAAS,GAAG;AACrB,SAAO;;AAGT,QAAO,uBAAuB,OAAO,OAAO;;AAG9C,eAAsB,uBAAwB,SAAwD;CACpG,MAAM,SAAS,MAAM,YAAY,UAAU;EACzC;EACA;EACA,mCAAmC,QAAQ;EAC3C;EACA;EACD,EAAE;EACD,cAAc;EACd,cAAc;EACf,CAAC;AAEF,KAAI,OAAO,SAAS,GAAG;AACrB,SAAO;;AAGT,QAAO,OAAO,OAAO,MAAM,QAAQ,CAAC,MAAM,SAAS,KAAK,SAAS,EAAE;;AAGrE,eAAsB,uBAAwB,SAA0C;CACtF,MAAM,YAAY,MAAM,uBAAuB,QAAQ;AAEvD,KAAI,cAAc,WAAW;AAC3B,UAAQ,OAAO,MAAM,8BAA8B,QAAQ,gBAAgB,IAAI;AAC/E;;AAGF,KAAI,UAAU,OAAO,aAAa,KAAK,WAAW;AAChD,UAAQ,OAAO,MAAM,oCAAoC,QAAQ,gBAAgB,IAAI;AACrF;;CAGF,MAAM,SAAS,MAAM,YAAY,UAAU,CAAC,QAAQ,UAAU,GAAG,EAAE;EACjE,cAAc;EACd,cAAc;EACf,CAAC;AAEF,KAAI,OAAO,SAAS,GAAG;AACrB,QAAM,IAAI,MAAM,+BAA+B,UAAU,KAAK;;AAGhE,SAAQ,OAAO,MAAM,yBAAyB,UAAU,GAAG,IAAI;;AAGjE,eAAsB,yBAA0B,SAA0C;CACxF,MAAM,YAAY,MAAM,uBAAuB,QAAQ;AAEvD,KAAI,cAAc,WAAW;AAC3B,UAAQ,OAAO,MAAM,8BAA8B,QAAQ,gBAAgB,IAAI;AAC/E;;CAGF,MAAM,SAAS,MAAM,YAAY,UAAU;EAAC;EAAM;EAAM,UAAU;EAAG,EAAE;EACrE,cAAc;EACd,cAAc;EACf,CAAC;AAEF,KAAI,OAAO,SAAS,GAAG;AACrB,QAAM,IAAI,MAAM,iCAAiC,UAAU,KAAK;;AAGlE,SAAQ,OAAO,MAAM,yBAAyB,UAAU,GAAG,IAAI;;AAGjE,eAAsB,kBAAmB,SAA2B,UAAwB,EAAE,EAAmB;AAC/G,OAAM,iBAAiB,SAAS,QAAQ,aAAa,MAAM;AAC3D,kCAAiC,QAAQ;AAEzC,KAAI,QAAQ,iBAAiB,QAAQ,QAAQ,aAAa,MAAM;EAC9D,MAAM,qBAAqB,MAAM,uBAAuB,QAAQ;AAEhE,MAAI,uBAAuB,WAAW;AACpC,OAAI,mCAAmC,QAAQ,mBAAmB,QAAQ,UAAU;AACpF,UAAO;;;CAIX,MAAM,MAAM,uBAAuB,QAAQ;AAC3C,KAAI,8BAA8B,QAAQ,mBAAmB,QAAQ,UAAU;CAE/E,MAAM,OAAO,CACX,MACA,GAAG,0BAA0B,QAAQ,CACtC;AAED,KAAI,QAAQ,aAAa,MAAM;AAC7B,OAAK,KAAK,8BAA8B;AACxC,MAAI,kEAAkE,QAAQ,UAAU;;CAG1F,MAAM,SAAS,MAAM,YAAY,IAAI,SAAS,CAAC,GAAG,IAAI,YAAY,GAAG,KAAK,EAAE;EAC1E,cAAc,QAAQ,cAAc,OAAO,WAAW;EACtD,cAAc;EACf,CAAC;AAEF,KAAI,OAAO,SAAS,GAAG;AACrB,QAAM,IAAI,MAAM,8BAA8B,QAAQ,kBAAkB;;CAG1E,MAAM,cAAc,6BAA6B,GAAG,OAAO,OAAO,IAAI,OAAO,SAAS,IAAI,MAAM,uBAAuB,QAAQ;AAE/H,KAAI,gBAAgB,WAAW;AAC7B,QAAM,IAAI,MAAM,yCAAyC,QAAQ,kBAAkB;;AAGrF,QAAO;;AAGT,eAAsB,cAAe,SAA2B,aAAoC;CAClG,MAAM,SAAS,iCAAiC,QAAQ;CACxD,MAAM,gBAAgB,+BAA+B,OAAO;AAE5D,KAAI,kBAAkB,WAAW;AAC/B,UAAQ,OAAO,MAAM,oDAAoD;AACzE;;CAGF,MAAM,SAAS,MAAM,YAAY,UAAU;EAAC;EAAQ;EAAa,GAAG,cAAc;EAAM,EAAE;EACxF,cAAc;EACd,cAAc;EACf,CAAC;CAEF,MAAM,cAAc,OAAO,OAAO,MAAM,QAAQ,CAAC,MAAM,SAAS,KAAK,SAAS,EAAE;AAEhF,KAAI,OAAO,SAAS,KAAK,gBAAgB,WAAW;AAClD,UAAQ,OAAO,MAAM,qCAAqC,YAAY,MAAM;QACvE;AACL,UAAQ,OAAO,MAAM,0CAA0C,cAAc,uBAAuB;;;AAIxG,eAAsB,UAAW,SAA4C;CAC3E,MAAM,MAAM,uBAAuB,QAAQ;AAC3C,SAAQ,OAAO,MAAM,qCAAqC;AAE1D,QAAO,eAAe,IAAI,SAAS;EACjC,GAAG,IAAI;EACP;EACA,GAAG,0BAA0B,QAAQ;EACrC;EACA;EACA,GAAG,yBAAyB;EAC5B;EACA;EACA,wBAAwB;EACzB,CAAC;;AAGJ,SAAgB,gCAAiC,SAA2B,OAAuB,YAAsB,EAAE,EAAY;AACrI,QAAO;EACL;EACA,GAAG,0BAA0B,QAAQ;EACrC;EACA;EACA,GAAG,yBAAyB;EAC5B;EACA;EACA,0BAA0B;EAC1B;EACA,kBAAkB,MAAM;EACxB,GAAG;EACJ;;AAGH,eAAsB,mBAAoB,SAA2B,OAAuB,YAAsB,EAAE,EAAmB;CACrI,MAAM,MAAM,uBAAuB,QAAQ;AAC3C,SAAQ,OAAO,MAAM,iBAAiB,kBAAkB,MAAM,CAAC,+BAA+B;AAE9F,QAAO,eAAe,IAAI,SAAS,CACjC,GAAG,IAAI,YACP,GAAG,gCAAgC,SAAS,OAAO,UAAU,CAC9D,CAAC;;AAGJ,eAAsB,0BAA2B,SAA0C;CACzF,MAAM,MAAM,uBAAuB,QAAQ;CAC3C,MAAM,SAAS,MAAM,YAAY,IAAI,SAAS;EAC5C,GAAG,IAAI;EACP;EACA,GAAG,0BAA0B,QAAQ;EACrC;EACA;EACA,GAAG,mCAAmC;EACtC;EACD,EAAE;EACD,cAAc;EACd,cAAc;EACf,CAAC;AAEF,KAAI,OAAO,SAAS,GAAG;AACrB,QAAM,IAAI,MAAM,6CAA6C;;;AAIjE,eAAsB,gCAAiC,SAA2B,YAAY,OAAO,SAA2B,EAAE,EAAiB;CACjJ,MAAM,MAAM,uBAAuB,QAAQ;CAC3C,MAAM,SAAS,MAAM,YAAY,IAAI,SAAS;EAC5C,GAAG,IAAI;EACP;EACA,GAAG,0BAA0B,QAAQ;EACrC;EACA;EACA,GAAG,mCAAmC;EACtC;EACA,GAAG;EACJ,EAAE;EACD,cAAc,YAAY,WAAW;EACrC,cAAc;EACf,CAAC;AAEF,KAAI,OAAO,SAAS,GAAG;AACrB,UAAQ,OAAO,MAAM,sFAAsF;;;AAI/G,eAAsB,aAAc,aAAsC;AACxE,QAAO,eAAe,UAAU;EAC9B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;;AAGJ,eAAe,qBAAuC;CACpD,MAAM,SAAS,MAAM,YAAY,MAAM,CAAC,QAAQ,QAAQ,EAAE;EACxD,cAAc;EACd,cAAc;EACf,CAAC;AAEF,KAAI,OAAO,SAAS,GAAG;AACrB,SAAO;;AAGT,QAAO,OAAO,OAAO,MAAM;;AAG7B,eAAsB,uBAAwB,SAA0C;AACtF,OAAM,iBAAiB,SAAS,KAAK;AACrC,kCAAiC,QAAQ;CAEzC,MAAM,QAAQ,MAAM,oBAAoB;AAExC,KAAI,MAAM,WAAW,GAAG;AACtB;;CAGF,MAAM,MAAM,uBAAuB,QAAQ;CAC3C,MAAM,QAAQ,MAAM,YAAY,IAAI,SAAS;EAC3C,GAAG,IAAI;EACP;EACA,GAAG,0BAA0B,QAAQ;EACrC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,EAAE;EACD,OAAO,GAAG,MAAM;EAChB,cAAc;EACd,cAAc;EACf,CAAC;AAEF,KAAI,MAAM,SAAS,GAAG;AACpB,UAAQ,OAAO,MAAM,wEAAwE;AAC7F;;CAGF,MAAM,oBAAoB,MAAM,gCAAgC,QAAQ,gBAAgB;AACxF,KAAI,CAAC,mBAAmB;AACtB,UAAQ,OAAO,MAAM,mGAAmG;;CAG1H,MAAM,SAAS,MAAM,YAAY,IAAI,SAAS;EAC5C,GAAG,IAAI;EACP;EACA,GAAG,0BAA0B,QAAQ;EACrC;EACA;EACA;EACA;EACA;EACA;EACD,EAAE;EACD,cAAc;EACd,cAAc;EACf,CAAC;AAEF,KAAI,OAAO,SAAS,GAAG;AACrB,UAAQ,OAAO,MAAM,uDAAuD;QACvE;AACL,UAAQ,OAAO,MAAM,yEAAyE;;;;;;ACvXlG,SAAS,eAAgB,WAAyC,iBAAkC;AAClG,KAAI,CAAC,iBAAiB;AACpB,SAAO;;AAGT,QAAO,WAAW,OAAO,aAAa,IAAI;;AAG5C,SAAS,aAAc,YAAqB,WAAyC,iBAAkC;AACrH,KAAI,CAAC,YAAY;AACf,SAAO;;AAGT,QAAO,eAAe,WAAW,gBAAgB;;AAGnD,SAAgB,2BACd,UACA,YACA,QACsB;CACtB,MAAM,kBAAkB,eAAe;CACvC,MAAM,wBAAwB,IAAI,KAC/B,cAAc,EAAE,EACd,QAAQ,cAAc,UAAU,gBAAgB,UAAU,CAC1D,KAAK,cAAc,CAAC,UAAU,aAAuB,UAAU,CAAC,CACpE;AAED,QAAO,SACJ,KAAK,SAAS;EACb,MAAM,YAAY,sBAAsB,IAAI,KAAK,gBAAgB;EACjE,MAAM,aAAa,OAAO,KAAK,gBAAgB;EAC/C,MAAM,QAAQ,aAAa,YAAY,WAAW,gBAAgB;AAElE,SAAO;GACL,GAAG;GACH;GACA;GACA,WAAW;IACT,OAAO,cAAc;IACrB,SAAS,WAAW,OAAO,aAAa,KAAK;IAC7C,IAAI,WAAW;IACf,MAAM,WAAW;IACjB,OAAO,WAAW,UAAU,kBAAkB,YAAY;IAC1D,QAAQ,WAAW;IACpB;GACF;GACD,CACD,MAAM,GAAG,MAAM,EAAE,kBAAkB,cAAc,EAAE,kBAAkB,IAAI,EAAE,gBAAgB,cAAc,EAAE,gBAAgB,CAAC;;AAGjI,SAAS,IAAK,OAAe,OAAuB;AAClD,QAAO,MAAM,OAAO,OAAO,IAAI;;AAGjC,SAAS,eAAgB,OAAmC;AAC1D,KAAI,CAAC,MAAM,UAAU,OAAO;AAC1B,SAAO;;AAGT,QAAO,MAAM,UAAU,QAAQ,MAAM,UAAU,MAAM;;AAGvD,SAAgB,wBAAyB,SAAuC;AAC9E,KAAI,QAAQ,WAAW,GAAG;AACxB,SAAO;;CAGT,MAAM,OAAO,QAAQ,KAAK,UAAU;EAClC,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,eAAe,MAAM;EACtB,CAAC;CACF,MAAM,UAAU;EAAC;EAAS;EAAQ;EAAQ;EAAa;EAAY;CACnE,MAAM,SAAS,QAAQ,KAAK,QAAQ,UAAU,KAAK,IAAI,OAAO,QAAQ,GAAG,KAAK,KAAK,QAAQ,IAAI,QAAQ,UAAU,EAAE,CAAC,CAAC;CACrH,MAAM,QAAQ;EACZ;EACA;EACA,QAAQ,KAAK,QAAQ,UAAU,IAAI,QAAQ,OAAO,UAAU,OAAO,OAAO,CAAC,CAAC,KAAK,KAAK;EACtF,KAAK,KAAK,QAAQ,IAAI,KAAK,OAAO,UAAU,IAAI,OAAO,OAAO,UAAU,MAAM,OAAO,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,KAAK;EAC9G;AAED,QAAO,GAAG,MAAM,KAAK,KAAK,CAAC;;;;;AC/F7B,MAAa,6BAA6B;AAY1C,SAAS,oBAAqB,OAA4C;AACxE,KAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C,SAAO;;CAGT,MAAM,YAAY;AAElB,QAAO,UAAU,iBACf,OAAO,UAAU,gBAAgB,YACjC,OAAO,UAAU,oBAAoB,YACrC,OAAO,UAAU,sBAAsB,YACvC,OAAO,UAAU,aAAa,YAC9B,OAAO,UAAU,gBAAgB,YACjC,OAAO,UAAU,eAAe;;AAGpC,SAAgB,sBAAuB,SAAmC;AACxE,QAAO,KAAK,QAAQ,kBAAkB,gBAAgB;;AAGxD,SAAgB,0BAA2B,MAAiC;CAC1E,MAAM,SAAS,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC;AAErD,KAAI,CAAC,oBAAoB,OAAO,EAAE;AAChC,QAAM,IAAI,MAAM,uCAAuC,OAAO;;AAGhE,QAAO;;AAGT,SAAgB,uBAAwB,SAA2B,UAAkB,MAAM,IAAI,MAAM,EAAqB;CACxH,MAAM,eAAe,sBAAsB,QAAQ;CACnD,MAAM,YAAY,IAAI,aAAa;CACnC,IAAI,cAAc;AAElB,KAAI,WAAW,aAAa,EAAE;AAC5B,gBAAc,0BAA0B,aAAa,CAAC;;CAGxD,MAAM,WAA8B;EAClC;EACA,aAAa,QAAQ;EACrB,iBAAiB,QAAQ;EACzB,mBAAmB,QAAQ;EAC3B;EACA;EACA,YAAY;EACb;AAED,WAAU,QAAQ,kBAAkB,EAAE,WAAW,MAAM,CAAC;AACxD,eAAc,cAAc,GAAG,KAAK,UAAU,UAAU,MAAM,EAAE,CAAC,IAAI;AACrE,QAAO;;AAGT,SAAgB,sBAAuB,UAAuC;CAC5E,MAAM,gBAAgB,KAAK,UAAU,aAAa;AAElD,KAAI,CAAC,WAAW,cAAc,EAAE;AAC9B,SAAO,EAAE;;AAGX,QAAO,YAAY,eAAe,EAAE,eAAe,MAAM,CAAC,CACvD,QAAQ,UAAU,MAAM,aAAa,CAAC,CACtC,KAAK,UAAU,KAAK,eAAe,MAAM,MAAM,gBAAgB,CAAC,CAChE,QAAQ,iBAAiB,WAAW,aAAa,CAAC,CAClD,KAAK,iBAAiB,0BAA0B,aAAa,CAAC;;;;;AChDnE,SAAgB,0BAA2B,gBAAgB,OAAO,KAAK,KAAa;AAClF,QAAO,QAAQ,QAAQ,cAAc,cAAc,CAAC,CAAC;;AAGvD,SAAgB,eAAgB,iBAAiC;AAC/D,QAAO,WAAW,SAAS,CAAC,OAAO,gBAAgB,CAAC,OAAO,MAAM,CAAC,MAAM,GAAG,GAAG;;AAGhF,SAAgB,uBAAwB,WAA+B,MAAM,QAAQ,KAAK,EAAU;CAClG,MAAM,YAAY,QAAQ,KAAK,aAAa,IAAI;AAEhD,KAAI,CAAC,WAAW,UAAU,EAAE;AAC1B,QAAM,IAAI,MAAM,6BAA6B,YAAY;;AAG3D,KAAI,CAAC,SAAS,UAAU,CAAC,aAAa,EAAE;AACtC,QAAM,IAAI,MAAM,iCAAiC,YAAY;;AAG/D,QAAO,aAAa,UAAU;;AAGhC,SAAgB,iBAAkB,MAAyB,QAAQ,KAAa;AAC9E,KAAI,IAAI,oBAAoB;AAC1B,SAAO,IAAI;;AAGb,KAAI,IAAI,gBAAgB;AACtB,SAAO,KAAK,IAAI,gBAAgB,aAAa;;AAG/C,QAAO,KAAK,SAAS,EAAE,UAAU,aAAa;;AAGhD,SAAgB,gBAAiB,MAAyB,QAAQ,KAAa;AAC7E,KAAI,IAAI,mBAAmB;AACzB,SAAO,IAAI;;AAGb,KAAI,IAAI,eAAe;AACrB,SAAO,KAAK,IAAI,eAAe,aAAa;;AAG9C,QAAO,KAAK,SAAS,EAAE,UAAU,SAAS,aAAa;;AAGzD,SAAgB,uBAAwB,UAAmC,EAAE,EAAoB;CAC/F,MAAM,MAAM,QAAQ,OAAO,QAAQ;CACnC,MAAM,kBAAkB,uBAAuB,QAAQ,WAAW,QAAQ,IAAI;CAC9E,MAAM,oBAAoB,SAAS,gBAAgB;CACnD,MAAM,cAAc,eAAe,gBAAgB;CACnD,MAAM,cAAc,QAAQ,eAAe,2BAA2B;CACtE,MAAM,wBAAwB,QAAQ,yBAAyB,IAAI,mCAAmC,KAAK,aAAa,UAAU,eAAe;CACjJ,MAAM,YAAY,iBAAiB,IAAI;CACvC,MAAM,WAAW,gBAAgB,IAAI;CACrC,MAAM,oBAAoB,KAAK,WAAW,cAAc,YAAY;CACpE,MAAM,mBAAmB,KAAK,UAAU,cAAc,YAAY;AAElE,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,qBAAqB,KAAK,mBAAmB,oBAAoB;EACjE,WAAW,KAAK,kBAAkB,MAAM;EACxC,YAAY,KAAK,kBAAkB,OAAO,aAAa;EACvD,kBAAkB,KAAK,kBAAkB,OAAO,iBAAiB;EACjE,wBAAwB,KAAK,kBAAkB,aAAa;EAC5D,yBAAyB,KAAK,kBAAkB,cAAc,iBAAiB;EAChF;;;;;ACzEH,MAAa,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqDrB,SAAgB,+BAAgC,SAAkC;AAChF,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,SAAS,QAAQ;;AAGrB,SAAgB,aAAc,MAA2B;CACvD,MAAM,OAAO,CAAC,GAAG,KAAK;CACtB,IAAI;CACJ,IAAI;CACJ,IAAI,WAAW;CACf,IAAI,OAAO;CACX,IAAI;CACJ,MAAM,aAAuB,EAAE;CAE/B,SAAS,OAAQ,SAAoC;AACnD,MAAI,QAAQ,YAAY,YAAY,YAAY,QAAQ;AACtD,SAAM,IAAI,MAAM,gDAAgD;;AAGlE,MAAI,oBAAoB,WAAW;AACjC,SAAM,IAAI,MAAM,8DAA8D;;AAGhF,SAAO;GAAE;GAAS;GAAW;GAAO;GAAU;GAAM;;CAGtD,SAAS,kBAAmB,OAAkC;AAC5D,MAAI,MAAM;AACR,SAAM,IAAI,MAAM,gDAAgD;;AAGlE,SAAO;GACL,SAAS;GACT;GACA,WAAW,mBAAmB,EAAE;GAChC;GACA;GACA;GACA;GACD;;AAGH,QAAO,KAAK,SAAS,GAAG;EACtB,MAAM,MAAM,KAAK,OAAO;AAExB,MAAI,QAAQ,WAAW;AACrB;;AAGF,MAAI,QAAQ,MAAM;AAChB,qBAAkB,KAAK,OAAO,EAAE;AAChC;;AAGF,MAAI,QAAQ,YAAY,QAAQ,MAAM;AACpC,UAAO,OAAO,OAAO;;AAGvB,MAAI,QAAQ,eAAe;GACzB,MAAM,QAAQ,KAAK,OAAO;AAC1B,OAAI,UAAU,WAAW;AACvB,UAAM,IAAI,MAAM,+BAA+B;;AAEjD,eAAY;AACZ;;AAGF,MAAI,QAAQ,WAAW;GACrB,MAAM,QAAQ,KAAK,OAAO;AAC1B,OAAI,UAAU,WAAW;AACvB,UAAM,IAAI,MAAM,2BAA2B;;AAE7C,WAAQ;AACR;;AAGF,MAAI,QAAQ,cAAc;AACxB,cAAW;AACX;;AAGF,MAAI,QAAQ,UAAU;AACpB,UAAO;AACP;;AAGF,MAAI,IAAI,WAAW,IAAI,EAAE;AACvB,SAAM,IAAI,MAAM,mBAAmB,MAAM;;AAG3C,aAAW,KAAK,IAAI;;AAGtB,KAAI,WAAW,WAAW,GAAG;AAC3B,SAAO,OAAO,OAAO;;AAGvB,KAAI,WAAW,OAAO,WAAW,WAAW,OAAO,SAAS;AAC1D,SAAO,OAAO,QAAQ;;CAGxB,MAAM,cAAc,uBAAuB,WAAW,MAAM,GAAG;AAC/D,KAAI,gBAAgB,WAAW;AAC7B,MAAI,WAAW,SAAS,GAAG;AACzB,SAAM,IAAI,MAAM,gDAAgD;;AAGlE,SAAO,kBAAkB,YAAY;;AAGvC,KAAI,WAAW,OAAO,UAAU,WAAW,WAAW,GAAG;AACvD,SAAO,OAAO,OAAO;;AAGvB,KAAI,WAAW,OAAO,YAAY,WAAW,WAAW,GAAG;AACzD,SAAO,OAAO,SAAS;;AAGzB,KAAI,WAAW,OAAO,UAAU,WAAW,WAAW,GAAG;AACvD,SAAO,OAAO,OAAO;;AAGvB,KAAI,WAAW,OAAO,UAAU,WAAW,WAAW,GAAG;AACvD,SAAO,OAAO,OAAO;;AAGvB,KAAI,WAAW,OAAO,YAAY,WAAW,WAAW,GAAG;AACzD,SAAO,OAAO,SAAS;;AAGzB,KAAI,WAAW,OAAO,cAAc;AAClC,MAAI,WAAW,WAAW,KAAM,WAAW,OAAO,aAAa,WAAW,WAAW,GAAI;AACvF,UAAO,OAAO,qBAAqB;;AAGrC,QAAM,IAAI,MAAM,+BAA+B,WAAW,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC,6EAA6E;;AAG5J,KAAI,WAAW,OAAO,eAAe,WAAW,WAAW,GAAG;AAC5D,SAAO,OAAO,YAAY;;AAG5B,KAAI,WAAW,OAAO,sBAAsB,WAAW,WAAW,GAAG;AACnE,SAAO,OAAO,mBAAmB;;AAGnC,KAAI,WAAW,OAAO,8BAA8B,WAAW,WAAW,GAAG;AAC3E,SAAO,OAAO,2BAA2B;;AAG3C,OAAM,IAAI,MAAM,oBAAoB,WAAW,KAAK,IAAI,GAAG;;AAG7D,eAAsB,OAAQ,OAAiB,QAAQ,KAAK,MAAM,EAAE,EAAmB;AACrF,KAAI;EACF,MAAM,SAAS,aAAa,KAAK;AAEjC,MAAI,OAAO,YAAY,QAAQ;AAC7B,WAAQ,OAAO,MAAM,MAAM;AAC3B,UAAO;;AAGT,MAAI,OAAO,YAAY,QAAQ;GAC7B,MAAM,WAAW,sBAAsB,iBAAiB,CAAC;GACzD,MAAM,aAAa,MAAM,yBAAyB;GAClD,MAAM,UAAU,2BAA2B,UAAU,YAAY,WAAW;AAE5E,OAAI,OAAO,MAAM;AACf,YAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,SAAS,MAAM,EAAE,CAAC,IAAI;UACxD;AACL,YAAQ,OAAO,MAAM,wBAAwB,QAAQ,CAAC;;AAGxD,UAAO;;EAGT,MAAM,UAAU,uBAAuB,EAAE,WAAW,OAAO,WAAW,CAAC;EACvE,MAAM,QAAQ,OAAO,SAAS,gBAAgB,QAAQ,kBAAkB;EACxE,MAAM,cAAc,OAAO,UAAU,YAAY,YAAY;AAE7D,MAAI,+BAA+B,OAAO,QAAQ,EAAE;AAClD,0BAAuB,SAAS,MAAM;;AAGxC,MAAI,OAAO,YAAY,sBAAsB;AAC3C,SAAM,iBAAiB,SAAS,MAAM;AACtC,UAAO;;AAGT,MAAI,OAAO,YAAY,UAAU;GAC/B,MAAM,YAAY,MAAM,uBAAuB,QAAQ;GACvD,MAAM,SAAS,iBAAiB,SAAS,OAAO,WAAW,YAAY,EAAE,aAAa,CAAC;AAEvF,OAAI,OAAO,MAAM;AACf,YAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC,IAAI;UACvD;AACL,YAAQ,OAAO,MAAM,iBAAiB,QAAQ,EAAE,OAAO,MAAM,CAAC,CAAC;;AAGjE,UAAO,gBAAgB,OAAO,GAAG,IAAI;;AAGvC,MAAI,OAAO,YAAY,QAAQ;AAC7B,SAAM,uBAAuB,QAAQ;AACrC,UAAO;;AAGT,MAAI,OAAO,YAAY,QAAQ;AAC7B,SAAM,yBAAyB,QAAQ;AACvC,UAAO;;AAGT,MAAI,OAAO,YAAY,UAAU;GAC/B,MAAM,SAAS,MAAM,gBAAgB,QAAQ;AAC7C,WAAQ,OAAO,MAAM,iBAAiB,OAAO,CAAC;AAC9C,UAAO,kBAAkB,OAAO,GAAG,IAAI;;AAGzC,MAAI,CAAC,WAAW,QAAQ,sBAAsB,EAAE;AAC9C,SAAM,IAAI,MAAM,wCAAwC,QAAQ,wBAAwB;;AAG1F,MAAI,OAAO,YAAY,aAAa;AAClC,SAAM,iBAAiB,SAAS,OAAO,EAAE,OAAO,MAAM,CAAC;GACvD,MAAM,cAAc,MAAM,kBAAkB,SAAS;IACnD,UAAU,OAAO;IACjB,WAAW;IACX,cAAc;IACf,CAAC;AACF,SAAM,gCAAgC,SAAS,KAAK;AACpD,SAAM,0BAA0B,QAAQ;AACxC,UAAO,aAAa,YAAY;;AAGlC,MAAI,OAAO,YAAY,4BAA4B;GACjD,MAAM,cAAc,MAAM,uBAAuB,QAAQ;AACzD,OAAI,gBAAgB,WAAW;AAC7B,UAAM,IAAI,MAAM,sCAAsC,QAAQ,kBAAkB;;AAElF,SAAM,uBAAuB,QAAQ;AACrC,UAAO;;AAGT,MAAI,OAAO,YAAY,oBAAoB;AACzC,SAAM,kBAAkB,QAAQ;AAChC,SAAM,uBAAuB,QAAQ;AACrC,UAAO;;AAGT,MAAI,OAAO,YAAY,gBAAgB;GACrC,MAAM,QAAQ,OAAO;AACrB,OAAI,UAAU,WAAW;AACvB,UAAM,IAAI,MAAM,+BAA+B;;AAGjD,SAAM,kBAAkB,SAAS,EAAE,UAAU,OAAO,UAAU,CAAC;AAC/D,SAAM,gCAAgC,SAAS,OAAO,CAAC,MAAM,CAAC;AAC9D,UAAO,mBAAmB,SAAS,OAAO,OAAO,aAAa,EAAE,CAAC;;EAGnE,MAAM,cAAc,MAAM,kBAAkB,SAAS,EAAE,UAAU,OAAO,UAAU,CAAC;AACnF,QAAM,cAAc,SAAS,YAAY;AACzC,SAAO,UAAU,QAAQ;UAClB,OAAO;AACd,UAAQ,OAAO,MAAM,GAAG,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CAAC,IAAI;AACnF,SAAO"}
package/dist/main.cjs ADDED
@@ -0,0 +1,7 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_main = require('./main-BuEptwlL.cjs');
3
+
4
+ exports.USAGE = require_main.USAGE;
5
+ exports.commandWritesWorkspaceMetadata = require_main.commandWritesWorkspaceMetadata;
6
+ exports.parseCliArgs = require_main.parseCliArgs;
7
+ exports.runCli = require_main.runCli;
@@ -0,0 +1,24 @@
1
+ //#region src/coding-agents.d.ts
2
+ type CodingAgentCli = 'codex' | 'opencode' | 'claude' | 'antigravity';
3
+ type CodingAgentCommandAlias = CodingAgentCli | 'cc';
4
+ declare function codingAgentFromCommand(command: string): CodingAgentCli | undefined;
5
+ declare function codingAgentBinary(agent: CodingAgentCli): string;
6
+ //#endregion
7
+ //#region src/main.d.ts
8
+ type BoxdownCommand = 'help' | 'start' | 'list' | 'status' | 'stop' | 'down' | 'doctor' | 'ssh-config-install' | 'ssh-proxy' | 'refresh-gh-token' | 'refresh-gh-token-running' | 'coding-agent';
9
+ interface ParsedCli {
10
+ command: BoxdownCommand;
11
+ agent?: CodingAgentCli;
12
+ agentArgs?: string[];
13
+ workspace?: string;
14
+ alias?: string;
15
+ recreate: boolean;
16
+ json: boolean;
17
+ }
18
+ declare const USAGE = "Usage:\n boxdown start [--workspace <path>] [--recreate]\n boxdown codex [--workspace <path>] [--recreate] [-- <codex args...>]\n boxdown claude [--workspace <path>] [--recreate] [-- <claude args...>]\n boxdown cc [--workspace <path>] [--recreate] [-- <claude args...>]\n boxdown opencode [--workspace <path>] [--recreate] [-- <opencode args...>]\n boxdown antigravity [--workspace <path>] [--recreate] [-- <agy args...>]\n boxdown list [--json]\n boxdown status [--workspace <path>] [--alias <name>] [--json]\n boxdown stop [--workspace <path>]\n boxdown down [--workspace <path>]\n boxdown doctor [--workspace <path>]\n boxdown ssh-config install [--workspace <path>] [--alias <name>]\n boxdown ssh-proxy [--workspace <path>] [--alias <name>]\n boxdown refresh-gh-token [--workspace <path>]\n boxdown refresh-gh-token-running [--workspace <path>]\n\nCommands:\n start Start or reuse the workspace devcontainer, then open\n an interactive shell inside it. Alias: shell.\n codex Start or reuse the devcontainer, then launch Codex.\n claude Start or reuse the devcontainer, then launch Claude\n Code. Alias: cc.\n opencode Start or reuse the devcontainer, then launch\n OpenCode.\n antigravity Start or reuse the devcontainer, then launch\n Antigravity CLI (agy).\n list List Boxdown-known devcontainer workspaces from any\n directory.\n status Show workspace state, generated paths, SSH key paths,\n and the matching devcontainer state.\n stop Stop the workspace devcontainer if it is running.\n down Remove the workspace devcontainer. Keeps Boxdown\n cache, generated config, data, and SSH keys.\n doctor Check required host tools and Boxdown assets.\n ssh-config install Install or update an SSH host alias for the workspace\n devcontainer.\n ssh-proxy Internal command used by the generated SSH\n ProxyCommand. Starts or reuses the devcontainer and\n bridges SSH over docker exec.\n refresh-gh-token Start or reuse the devcontainer, then copy host\n GitHub CLI auth into the container when available.\n refresh-gh-token-running Refresh GitHub CLI auth only if the workspace\n devcontainer is already running.\n\nOptions:\n --workspace <path> Target project directory. Defaults to the current directory.\n --alias <name> SSH host alias. Defaults to <repo-name>-devcontainer.\n --recreate Remove the existing devcontainer before starting.\n --json Print JSON output. Supported by status and list.\n --help, -h Show help.\n";
19
+ declare function commandWritesWorkspaceMetadata(command: BoxdownCommand): boolean;
20
+ declare function parseCliArgs(argv: string[]): ParsedCli;
21
+ declare function runCli(argv?: string[]): Promise<number>;
22
+ //#endregion
23
+ export { BoxdownCommand, ParsedCli, USAGE, commandWritesWorkspaceMetadata, parseCliArgs, runCli };
24
+ //# sourceMappingURL=main.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.cts","names":[],"sources":["../src/coding-agents.ts","../src/main.ts"],"mappings":";KAAY,cAAA;AAAA,KACA,uBAAA,GAA0B,cAAA;AAAA,iBAUtB,sBAAA,CAAwB,OAAA,WAAkB,cAAA;AAAA,iBAI1C,iBAAA,CAAmB,KAAA,EAAO,cAAA;;;KCJ9B,cAAA;AAAA,UAcK,SAAA;EACf,OAAA,EAAS,cAAA;EACT,KAAA,GAAQ,cAAA;EACR,SAAA;EACA,SAAA;EACA,KAAA;EACA,QAAA;EACA,IAAA;AAAA;AAAA,cAGW,KAAA;AAAA,iBAqDG,8BAAA,CAAgC,OAAA,EAAS,cAAA;AAAA,iBAWzC,YAAA,CAAc,IAAA,aAAiB,SAAA;AAAA,iBAoJzB,MAAA,CAAQ,IAAA,cAAyC,OAAA"}
@@ -0,0 +1,24 @@
1
+ //#region src/coding-agents.d.ts
2
+ type CodingAgentCli = 'codex' | 'opencode' | 'claude' | 'antigravity';
3
+ type CodingAgentCommandAlias = CodingAgentCli | 'cc';
4
+ declare function codingAgentFromCommand(command: string): CodingAgentCli | undefined;
5
+ declare function codingAgentBinary(agent: CodingAgentCli): string;
6
+ //#endregion
7
+ //#region src/main.d.ts
8
+ type BoxdownCommand = 'help' | 'start' | 'list' | 'status' | 'stop' | 'down' | 'doctor' | 'ssh-config-install' | 'ssh-proxy' | 'refresh-gh-token' | 'refresh-gh-token-running' | 'coding-agent';
9
+ interface ParsedCli {
10
+ command: BoxdownCommand;
11
+ agent?: CodingAgentCli;
12
+ agentArgs?: string[];
13
+ workspace?: string;
14
+ alias?: string;
15
+ recreate: boolean;
16
+ json: boolean;
17
+ }
18
+ declare const USAGE = "Usage:\n boxdown start [--workspace <path>] [--recreate]\n boxdown codex [--workspace <path>] [--recreate] [-- <codex args...>]\n boxdown claude [--workspace <path>] [--recreate] [-- <claude args...>]\n boxdown cc [--workspace <path>] [--recreate] [-- <claude args...>]\n boxdown opencode [--workspace <path>] [--recreate] [-- <opencode args...>]\n boxdown antigravity [--workspace <path>] [--recreate] [-- <agy args...>]\n boxdown list [--json]\n boxdown status [--workspace <path>] [--alias <name>] [--json]\n boxdown stop [--workspace <path>]\n boxdown down [--workspace <path>]\n boxdown doctor [--workspace <path>]\n boxdown ssh-config install [--workspace <path>] [--alias <name>]\n boxdown ssh-proxy [--workspace <path>] [--alias <name>]\n boxdown refresh-gh-token [--workspace <path>]\n boxdown refresh-gh-token-running [--workspace <path>]\n\nCommands:\n start Start or reuse the workspace devcontainer, then open\n an interactive shell inside it. Alias: shell.\n codex Start or reuse the devcontainer, then launch Codex.\n claude Start or reuse the devcontainer, then launch Claude\n Code. Alias: cc.\n opencode Start or reuse the devcontainer, then launch\n OpenCode.\n antigravity Start or reuse the devcontainer, then launch\n Antigravity CLI (agy).\n list List Boxdown-known devcontainer workspaces from any\n directory.\n status Show workspace state, generated paths, SSH key paths,\n and the matching devcontainer state.\n stop Stop the workspace devcontainer if it is running.\n down Remove the workspace devcontainer. Keeps Boxdown\n cache, generated config, data, and SSH keys.\n doctor Check required host tools and Boxdown assets.\n ssh-config install Install or update an SSH host alias for the workspace\n devcontainer.\n ssh-proxy Internal command used by the generated SSH\n ProxyCommand. Starts or reuses the devcontainer and\n bridges SSH over docker exec.\n refresh-gh-token Start or reuse the devcontainer, then copy host\n GitHub CLI auth into the container when available.\n refresh-gh-token-running Refresh GitHub CLI auth only if the workspace\n devcontainer is already running.\n\nOptions:\n --workspace <path> Target project directory. Defaults to the current directory.\n --alias <name> SSH host alias. Defaults to <repo-name>-devcontainer.\n --recreate Remove the existing devcontainer before starting.\n --json Print JSON output. Supported by status and list.\n --help, -h Show help.\n";
19
+ declare function commandWritesWorkspaceMetadata(command: BoxdownCommand): boolean;
20
+ declare function parseCliArgs(argv: string[]): ParsedCli;
21
+ declare function runCli(argv?: string[]): Promise<number>;
22
+ //#endregion
23
+ export { BoxdownCommand, ParsedCli, USAGE, commandWritesWorkspaceMetadata, parseCliArgs, runCli };
24
+ //# sourceMappingURL=main.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.mts","names":[],"sources":["../src/coding-agents.ts","../src/main.ts"],"mappings":";KAAY,cAAA;AAAA,KACA,uBAAA,GAA0B,cAAA;AAAA,iBAUtB,sBAAA,CAAwB,OAAA,WAAkB,cAAA;AAAA,iBAI1C,iBAAA,CAAmB,KAAA,EAAO,cAAA;;;KCJ9B,cAAA;AAAA,UAcK,SAAA;EACf,OAAA,EAAS,cAAA;EACT,KAAA,GAAQ,cAAA;EACR,SAAA;EACA,SAAA;EACA,KAAA;EACA,QAAA;EACA,IAAA;AAAA;AAAA,cAGW,KAAA;AAAA,iBAqDG,8BAAA,CAAgC,OAAA,EAAS,cAAA;AAAA,iBAWzC,YAAA,CAAc,IAAA,aAAiB,SAAA;AAAA,iBAoJzB,MAAA,CAAQ,IAAA,cAAyC,OAAA"}
package/dist/main.mjs ADDED
@@ -0,0 +1,3 @@
1
+ import { i as runCli, n as commandWritesWorkspaceMetadata, r as parseCliArgs, t as USAGE } from "./main-ZFTrSVgt.mjs";
2
+
3
+ export { USAGE, commandWritesWorkspaceMetadata, parseCliArgs, runCli };
package/docs/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # Boxdown Documentation
2
+
3
+ Boxdown is a CLI-first Dev Container runner. It lets a project use a shared
4
+ devcontainer setup without copying a `.devcontainer/` directory into every
5
+ repository.
6
+
7
+ ## Guides
8
+
9
+ - [Development](./development.md) covers local setup, common commands, and how to
10
+ run Boxdown from source.
11
+ - [Testing](./testing.md) describes the test strategy and verification commands.
12
+ - [Architecture](./architecture.md) explains the project structure, generated
13
+ configuration flow, and important boundaries.
14
+ - [Conventions](./conventions.md) captures project-specific coding and
15
+ maintenance expectations.
16
+ - [Todo and next plans](./todo.md) captures the post-v1 roadmap and fresh-session
17
+ handoff context.
18
+
19
+ ## Feature Docs
20
+
21
+ - [Feature index](./features/README.md)
22
+ - [Start command](./features/start-and-shell.md)
23
+ - [Lifecycle commands](./features/lifecycle.md)
24
+ - [SSH config and proxy workflow](./features/ssh-config-and-proxy.md)
25
+ - [GitHub auth refresh](./features/github-auth-refresh.md)
26
+ - [Generated config and state](./features/generated-config-and-state.md)
27
+
28
+ ## Design Snapshot
29
+
30
+ Boxdown packages reusable devcontainer source under `assets/devcontainer/`.
31
+ At runtime it generates a per-workspace `devcontainer.json` outside the target
32
+ repository and invokes the Dev Containers CLI with `--override-config`. This
33
+ keeps consuming repositories clean while preserving normal Dev Container
34
+ lifecycle behavior.
@@ -0,0 +1,75 @@
1
+ # Architecture
2
+
3
+ ## Project Shape
4
+
5
+ Boxdown is a Node.js CLI package. The public contract is the `boxdown` binary,
6
+ not a JavaScript API.
7
+
8
+ Important directories:
9
+
10
+ - `src/` contains CLI orchestration, config generation, state resolution, and
11
+ SSH helpers.
12
+ - `assets/devcontainer/` contains reusable devcontainer source copied from
13
+ `gh-cp/.devcontainer`.
14
+ - `docs/` contains project and feature documentation.
15
+ - `__tests__/` contains unit tests for pure behavior.
16
+
17
+ ## Runtime Model
18
+
19
+ Boxdown does not copy `.devcontainer/` into consumer repositories. Instead it:
20
+
21
+ 1. Resolves the target workspace from `process.cwd()` or `--workspace`.
22
+ 2. Creates per-workspace cache and data paths.
23
+ 3. Ensures a per-workspace SSH key pair exists.
24
+ 4. Generates a `devcontainer.json` outside the target repository.
25
+ 5. Runs the Dev Containers CLI with `--workspace-folder` and
26
+ `--override-config`.
27
+
28
+ This makes the target repository the workspace while Boxdown remains the owner
29
+ of reusable configuration and runtime assets.
30
+
31
+ ## State Boundaries
32
+
33
+ Cache-like state belongs under `~/.cache/boxdown` or `BOXDOWN_CACHE_HOME`.
34
+ Persistent user state belongs under `~/.local/share/boxdown` or
35
+ `BOXDOWN_DATA_HOME`.
36
+
37
+ Per-workspace state is keyed by a hash of the resolved workspace path. This
38
+ prevents collisions between repositories with the same basename in different
39
+ folders.
40
+
41
+ ## Container Asset Mounts
42
+
43
+ The generated config mounts `assets/devcontainer/` read-only into the container
44
+ at `/opt/boxdown/devcontainer`.
45
+
46
+ When the host has `~/.agents`, the generated config also mounts it read-only at
47
+ `/home/node/.agents` so host-global agent configuration is available inside the
48
+ container without being copied into target repositories.
49
+
50
+ The container receives only a public SSH key mount. The private host key stays
51
+ on the host and is referenced from the user's SSH config.
52
+
53
+ ## Container Lifecycle Tooling
54
+
55
+ Boxdown-owned container assets install and refresh development tooling from
56
+ inside the devcontainer lifecycle. Coding-agent CLIs are refreshed through a
57
+ shared mounted utility: post-create runs an immediate install/update, while
58
+ post-start and SSH proxy setup run throttled best-effort refreshes for
59
+ already-running containers.
60
+
61
+ Tool refreshes are container-side behavior, not generated config schema.
62
+ Failures should warn without making the devcontainer unusable. SSH proxy
63
+ refresh output must stay off stdout because stdout carries SSH traffic. Locking
64
+ and throttling belong in the shared helper so individual hooks do not duplicate
65
+ update logic.
66
+
67
+ ## Important Invariants
68
+
69
+ - Boxdown must not create `.devcontainer/` in target repositories.
70
+ - Boxdown must not package or mount generated `.ssh/` private key material.
71
+ - Lifecycle scripts must work when run from mounted assets, not only from a
72
+ repo-local `.devcontainer/` directory.
73
+ - SSH aliases must be workspace-specific and idempotently replaceable.
74
+ - `gh-cp` is a source for current devcontainer assets, but Boxdown must not
75
+ modify the `gh-cp` repository.
@@ -0,0 +1,29 @@
1
+ # Conventions
2
+
3
+ ## Coding
4
+
5
+ - Keep the CLI dependency-light and prefer Node.js built-ins.
6
+ - Keep process execution isolated in the process/devcontainer helpers so pure
7
+ behavior remains easy to test.
8
+ - Prefer typed helper functions over shell string manipulation in TypeScript.
9
+ - Keep shell scripts POSIX-ish Bash and compatible with the original
10
+ devcontainer workflow.
11
+ - Preserve backwards-compatible fallbacks in asset scripts when possible, so the
12
+ same scripts can still run from a copied `.devcontainer/` layout.
13
+
14
+ ## Documentation
15
+
16
+ - Update feature docs when changing command behavior.
17
+ - Update architecture docs when changing state locations, generated config
18
+ shape, asset mounting, or SSH boundaries.
19
+ - Keep examples copy-pasteable and explicit about `--workspace` when a command
20
+ may affect another repository.
21
+
22
+ ## Maintenance
23
+
24
+ - Copy only tracked devcontainer source files from `gh-cp/.devcontainer`.
25
+ - Never copy generated `.devcontainer/.ssh/` material.
26
+ - Run `npm pack --dry-run --json` after changing `package.json.files` or
27
+ package assets.
28
+ - Add a changeset for published CLI behavior changes.
29
+ - Treat Docker-starting workflows as manual acceptance tests, not unit tests.
@@ -0,0 +1,59 @@
1
+ # Development
2
+
3
+ ## Requirements
4
+
5
+ - Node.js 24 or newer.
6
+ - pnpm 10.26 or newer.
7
+ - Docker for manual container workflow testing.
8
+ - OpenSSH tools for SSH config and proxy testing.
9
+ - GitHub CLI for `refresh-gh-token` workflow testing.
10
+
11
+ ## Setup
12
+
13
+ Install dependencies from the Boxdown repository root:
14
+
15
+ ```sh
16
+ pnpm install
17
+ ```
18
+
19
+ Run the CLI from source:
20
+
21
+ ```sh
22
+ pnpm run start -- --help
23
+ pnpm run start -- start --workspace /path/to/project
24
+ pnpm run start -- ssh-config install --workspace /path/to/project
25
+ ```
26
+
27
+ The published binary is `boxdown`, but local development goes through
28
+ `tsx src/bin/cli.ts` via `pnpm run start`.
29
+
30
+ ## Build
31
+
32
+ ```sh
33
+ pnpm run build
34
+ ```
35
+
36
+ The build emits both CommonJS and ESM files into `dist/`. The npm `bin` entry
37
+ points at `dist/bin/cli.cjs`, so run a build before testing the packed CLI.
38
+
39
+ ## Local Runtime Assets
40
+
41
+ Reusable devcontainer source lives in `assets/devcontainer/`. This is copied
42
+ from the latest `gh-cp/.devcontainer` source, with generated `.ssh/` material
43
+ excluded.
44
+
45
+ Boxdown does not keep a root `.devcontainer/` directory. That is deliberate:
46
+ the package should not look like it has project-local Dev Container config.
47
+
48
+ ## Running Against Another Project
49
+
50
+ Use `--workspace` while developing so you do not accidentally target the
51
+ Boxdown repo itself:
52
+
53
+ ```sh
54
+ pnpm run start -- start --workspace ~/projects/repos/example
55
+ pnpm run start -- ssh-config install --workspace ~/projects/repos/example
56
+ ```
57
+
58
+ Starting a real container writes Boxdown state under user cache/data directories
59
+ and may pull images or install the Dev Containers CLI.
@@ -0,0 +1,14 @@
1
+ # Feature Index
2
+
3
+ This directory explains Boxdown's user-facing capabilities and the main internal
4
+ flows behind them.
5
+
6
+ - [Start command](./start-and-shell.md)
7
+ - [Lifecycle commands](./lifecycle.md)
8
+ - [SSH config and proxy workflow](./ssh-config-and-proxy.md)
9
+ - [GitHub auth refresh](./github-auth-refresh.md)
10
+ - [Generated config and state](./generated-config-and-state.md)
11
+
12
+ Most features share the same first step: resolve a target workspace, derive
13
+ workspace-specific state paths, generate a devcontainer override config, and
14
+ delegate container lifecycle work to the Dev Containers CLI.
@@ -0,0 +1,64 @@
1
+ # Generated Config and State
2
+
3
+ ## Why Generate Config
4
+
5
+ The reusable devcontainer source is packaged with Boxdown, not copied into
6
+ target repositories. A literal packaged `devcontainer.json` still contains
7
+ repo-local lifecycle paths, so Boxdown generates a workspace-specific override
8
+ config before starting a container.
9
+
10
+ The generated config lets Boxdown keep target repositories clean while still
11
+ using the Dev Containers CLI as the lifecycle owner.
12
+
13
+ ## Config Location
14
+
15
+ Generated config is written under:
16
+
17
+ ```text
18
+ ~/.cache/boxdown/workspaces/<workspace-hash>/devcontainer.json
19
+ ```
20
+
21
+ `BOXDOWN_CACHE_HOME` overrides the cache root. `XDG_CACHE_HOME` is honored when
22
+ the Boxdown-specific override is not set.
23
+
24
+ ## Persistent State
25
+
26
+ Workspace data is written under:
27
+
28
+ ```text
29
+ ~/.local/share/boxdown/workspaces/<workspace-hash>/
30
+ ```
31
+
32
+ `BOXDOWN_DATA_HOME` overrides the data root. `XDG_DATA_HOME` is honored when the
33
+ Boxdown-specific override is not set.
34
+
35
+ SSH private keys live in persistent data. A public-key-only runtime directory is
36
+ used for the container mount.
37
+
38
+ Each touched workspace also records inventory metadata at:
39
+
40
+ ```text
41
+ ~/.local/share/boxdown/workspaces/<workspace-hash>/metadata.json
42
+ ```
43
+
44
+ `boxdown list` reads these metadata files as its source of truth, then enriches
45
+ entries with best-effort Docker state.
46
+
47
+ ## Generated Changes
48
+
49
+ Boxdown starts from `assets/devcontainer/devcontainer.json` and rewrites:
50
+
51
+ - `name`, to include the workspace basename.
52
+ - `initializeCommand`, to call the host asset script with the target workspace.
53
+ - `postCreateCommand`, to call mounted container assets.
54
+ - `postStartCommand`, to call mounted container assets.
55
+ - `mounts`, to add the read-only asset mount, public-key mount, and host
56
+ `~/.agents` mount when that directory exists.
57
+ - `containerEnv`, to point SSH bootstrap at the mounted public key and actual
58
+ container workspace.
59
+
60
+ The target repository is still the Dev Container workspace via
61
+ `--workspace-folder`.
62
+
63
+ Mounts are create-time container settings. Use `boxdown start --recreate` after
64
+ creating or removing host `~/.agents` so Docker receives the updated mount set.