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.
- package/LICENSE +191 -0
- package/README.md +115 -0
- package/assets/devcontainer/README.md +177 -0
- package/assets/devcontainer/devcontainer.json +83 -0
- package/assets/devcontainer/hooks/initialize.sh +55 -0
- package/assets/devcontainer/hooks/post-create.sh +67 -0
- package/assets/devcontainer/hooks/post-start.sh +34 -0
- package/assets/devcontainer/ssh-config-install.sh +172 -0
- package/assets/devcontainer/start.sh +485 -0
- package/assets/devcontainer/utils/codex-cli-update.sh +9 -0
- package/assets/devcontainer/utils/coding-agent-cli-update.sh +360 -0
- package/assets/devcontainer/utils/deps-install.sh +87 -0
- package/assets/devcontainer/utils/ssh-bootstrap.sh +200 -0
- package/dist/bin/cli.cjs +12 -0
- package/dist/bin/cli.d.cts +1 -0
- package/dist/bin/cli.d.mts +1 -0
- package/dist/bin/cli.mjs +14 -0
- package/dist/bin/cli.mjs.map +1 -0
- package/dist/main-BuEptwlL.cjs +1707 -0
- package/dist/main-ZFTrSVgt.mjs +1685 -0
- package/dist/main-ZFTrSVgt.mjs.map +1 -0
- package/dist/main.cjs +7 -0
- package/dist/main.d.cts +24 -0
- package/dist/main.d.cts.map +1 -0
- package/dist/main.d.mts +24 -0
- package/dist/main.d.mts.map +1 -0
- package/dist/main.mjs +3 -0
- package/docs/README.md +34 -0
- package/docs/architecture.md +75 -0
- package/docs/conventions.md +29 -0
- package/docs/development.md +59 -0
- package/docs/features/README.md +14 -0
- package/docs/features/generated-config-and-state.md +64 -0
- package/docs/features/github-auth-refresh.md +64 -0
- package/docs/features/lifecycle.md +64 -0
- package/docs/features/ssh-config-and-proxy.md +60 -0
- package/docs/features/start-and-shell.md +83 -0
- package/docs/testing.md +63 -0
- package/docs/todo.md +170 -0
- package/package.json +128 -0
- package/src/bin/cli.ts +9 -0
- package/src/coding-agents.ts +22 -0
- package/src/config.ts +81 -0
- package/src/constants.ts +9 -0
- package/src/devcontainer-cli.ts +57 -0
- package/src/devcontainer.ts +394 -0
- package/src/doctor.ts +139 -0
- package/src/github-git-auth.ts +143 -0
- package/src/jsonc.ts +123 -0
- package/src/list.ts +102 -0
- package/src/main.ts +362 -0
- package/src/metadata.ts +84 -0
- package/src/paths.ts +117 -0
- package/src/process.ts +94 -0
- package/src/shell.ts +60 -0
- package/src/ssh-config.ts +113 -0
- package/src/ssh-key.ts +52 -0
- 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":"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"}
|
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;
|
package/dist/main.d.cts
ADDED
|
@@ -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"}
|
package/dist/main.d.mts
ADDED
|
@@ -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
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.
|