@getmonoceros/workbench 1.13.3 → 1.14.1

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/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bin.ts","../src/devcontainer/docker-group-bootstrap.ts","../src/help.ts","../src/inner-args.ts","../src/main.ts","../src/commands/add-apt-packages.ts","../src/modify/index.ts","../src/config/io.ts","../src/config/schema.ts","../src/config/paths.ts","../src/config/env-file.ts","../src/init/feature-doc.ts","../src/init/manifest.ts","../src/util/ref.ts","../src/devcontainer/credentials.ts","../src/util/format.ts","../src/devcontainer/locate-running.ts","../src/config/global.ts","../src/init/components.ts","../src/proxy/index.ts","../src/proxy/dynamic.ts","../src/proxy/port-check.ts","../src/create/catalog.ts","../src/init/service-doc.ts","../src/create/scaffold.ts","../src/modify/yml.ts","../src/commands/add-feature.ts","../src/commands/add-from-url.ts","../src/commands/add-repo.ts","../src/commands/add-language.ts","../src/commands/add-port.ts","../src/commands/add-service.ts","../src/commands/apply.ts","../src/apply/index.ts","../src/config/state.ts","../src/config/transform.ts","../src/devcontainer/compose.ts","../src/util/mask-secrets.ts","../src/devcontainer/cli.ts","../src/devcontainer/runtime-pull-hint.ts","../src/devcontainer/docker-mode.ts","../src/devcontainer/identity.ts","../src/version.ts","../src/commands/_dispatch.ts","../src/commands/completion.ts","../src/commands/__complete.ts","../src/completion/resolve.ts","../src/commands/init.ts","../src/init/index.ts","../src/init/generator.ts","../src/commands/list-components.ts","../src/commands/logs.ts","../src/commands/port.ts","../src/commands/remove-apt-packages.ts","../src/commands/remove-feature.ts","../src/commands/remove.ts","../src/remove/index.ts","../src/commands/restore.ts","../src/restore/index.ts","../src/commands/remove-from-url.ts","../src/commands/remove-language.ts","../src/commands/remove-port.ts","../src/commands/remove-repo.ts","../src/commands/remove-service.ts","../src/commands/run.ts","../src/devcontainer/shell.ts","../src/devcontainer/run.ts","../src/commands/shell.ts","../src/commands/start.ts","../src/commands/status.ts","../src/commands/stop.ts","../src/commands/tunnel.ts","../src/tunnel/run.ts","../src/tunnel/resolve.ts","../src/tunnel/port-check.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { runMain } from 'citty';\nimport { bootstrapDockerGroup } from './devcontainer/docker-group-bootstrap.js';\nimport { maybeRenderHelp } from './help.js';\nimport { consumeInnerArgsFromProcessArgv } from './inner-args.js';\nimport { main } from './main.js';\n\n// On Linux: transparently re-exec under the docker group if the\n// current shell hasn't loaded it yet (typical after a fresh\n// `usermod -aG docker $USER` until the GNOME/KDE session is logged\n// out + back in). See docker-group-bootstrap.ts for the rationale.\n// No-op on macOS/Windows and when docker access already works.\n//\n// Runs BEFORE argv munging / help / runMain because if a re-exec\n// fires, we want the child process to receive the original argv\n// verbatim — let consumeInnerArgsFromProcessArgv / citty / help do\n// their work in the re-exec'd process, not in the about-to-die\n// parent.\nbootstrapDockerGroup();\n\n// Pull everything after `--` out of argv before citty starts parsing.\n// Otherwise citty's eager --help/--version handling shadows the inner\n// command (e.g. `monoceros run -- foo --help` would show monoceros run's\n// own help, not foo's).\nconsumeInnerArgsFromProcessArgv();\n\nasync function entry(): Promise<void> {\n // We render `--help` ourselves so the USAGE line shows positional\n // arguments *before* `[OPTIONS]`, matching the\n // `monoceros <command> <containername> [<args> …]` convention. Citty's\n // built-in renderer puts `[OPTIONS]` first which is the opposite. When\n // help was rendered, exit before handing off to citty so its own help\n // doesn't double up.\n if (await maybeRenderHelp(process.argv.slice(2), main)) {\n return;\n }\n await runMain(main);\n}\n\nentry().catch((err: unknown) => {\n // runMain handles its own errors; this catch is a safety net for the\n // help path.\n console.error(\n err instanceof Error ? (err.stack ?? err.message) : String(err),\n );\n process.exit(1);\n});\n","import { spawnSync } from 'node:child_process';\nimport { userInfo } from 'node:os';\n\n/**\n * Transparent recovery for the \"Linux + fresh usermod + same desktop\n * session\" trap.\n *\n * After `sudo usermod -aG docker $USER`, the user IS in /etc/group's\n * docker line — but the running desktop session loaded its group list\n * at GNOME/KDE login time and has no way to refresh. Every shell\n * spawned from that session inherits the stale list, so `docker info`\n * fails with \"permission denied while connecting to docker.sock\"\n * until the user either runs `newgrp docker` (per-shell), opens a\n * fresh login session (`su -l`, `ssh localhost`), or logs out of the\n * desktop entirely.\n *\n * That ceremony is real, well-known, and intentional (Linux process\n * credentials can't be live-updated for security reasons). But making\n * the *user* deal with it for every `monoceros …` invocation in a\n * fresh terminal tab is bad UX.\n *\n * This helper sidesteps it for monoceros's own process tree:\n *\n * 1. Probe `docker info`. If it works, no-op — we're good.\n * 2. If it fails AND the user is in /etc/group's docker line\n * (= `usermod` already ran successfully), re-exec ourselves\n * via `sg docker -c \"node …\"`. `sg` is the shadow-utils helper\n * that runs a single command under a different primary group;\n * it reads /etc/group fresh so the docker membership applies.\n * 3. If the user is NOT in /etc/group's docker line, return — the\n * caller will hit the docker failure on its own and surface a\n * \"run usermod\" error that's actually actionable.\n *\n * The re-exec is invisible to the user: bash sees a single\n * `monoceros …` command, history captures one line, ↑ arrow returns\n * the exact command. The sg sub-process lives only as long as\n * monoceros's own run, then exits.\n *\n * Linux-only. macOS / Windows Docker Desktop use different access\n * mechanisms (CLI-helper, named pipes) where group membership\n * doesn't apply — we early-return.\n */\n\nconst REEXEC_MARKER = 'MONOCEROS_DOCKER_GROUP_REEXEC';\n\n/**\n * Probes docker access and, if needed, transparently re-execs the\n * current node process under the docker group via `sg docker`. Never\n * returns if a re-exec fired (calls `process.exit` with the sg\n * sub-process's exit code).\n *\n * Returns when no recovery was needed or possible. The caller should\n * proceed with normal execution; downstream docker calls will either\n * work (recovery succeeded) or fail with the original permission\n * error (recovery wasn't applicable — typically usermod wasn't run).\n */\nexport function bootstrapDockerGroup(\n opts: {\n /** Override spawn behavior — tests inject deterministic responses. */\n runProbe?: (cmd: string, args: readonly string[]) => number;\n /** Tests inject the re-exec without actually fork+exec'ing. */\n reexec?: (argv: readonly string[]) => number;\n /** Override $USER detection. Tests inject a stable value. */\n username?: string;\n /** Override process.platform. Tests inject 'linux'. */\n platform?: NodeJS.Platform;\n /** Override env-marker check. Tests inject either undefined or '1'. */\n marker?: string;\n } = {},\n): void {\n const platform = opts.platform ?? process.platform;\n if (platform !== 'linux') return;\n\n const marker = opts.marker ?? process.env[REEXEC_MARKER];\n if (marker === '1') return; // already re-exec'd, don't loop\n\n const probe = opts.runProbe ?? defaultProbe;\n\n // `docker --version` doesn't touch the daemon — it's a client-only\n // call. If it fails, docker isn't installed at all. The downstream\n // error path will tell the user to install docker; we have nothing\n // to recover from.\n if (probe('docker', ['--version']) !== 0) return;\n\n // `docker info` does talk to the daemon. exit 0 = access works,\n // non-zero = either daemon down or permission denied. Either way\n // we have nothing to gain by trying sg — re-exec'ing under docker\n // group won't fix a stopped daemon.\n if (probe('docker', ['info']) === 0) return;\n\n // Daemon unreachable. Is this the \"usermod already ran\" case?\n const username = opts.username ?? userInfo().username;\n if (!isInDockerGroupViaEtcGroup(username, probe)) return;\n\n // Re-exec via sg. Use the exact argv that started us so the new\n // process is indistinguishable from the original invocation.\n const reexec = opts.reexec ?? defaultReexec;\n const exitCode = reexec(process.argv);\n process.exit(exitCode);\n}\n\n/**\n * Read /etc/group via getent and check whether `username` appears in\n * the docker line's member list. We use `getent` rather than parsing\n * /etc/group directly so NSS-backed group sources (LDAP, sssd, …)\n * are honored too. Returns false on any failure — the caller treats\n * \"we couldn't confirm membership\" the same as \"not a member\", which\n * is the safe default (no re-exec attempt → original error surfaces).\n */\nfunction isInDockerGroupViaEtcGroup(\n username: string,\n probe: (cmd: string, args: readonly string[]) => number,\n): boolean {\n // We need stdout, not just exit code, so the probe interface\n // doesn't suffice. Run getent directly.\n const result = spawnSync('getent', ['group', 'docker'], {\n encoding: 'utf8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n void probe; // signature parity with the rest of the helper\n if (result.status !== 0) return false;\n // getent output: `docker:x:984:user1,user2,parallels`\n // We want the comma-separated member list after the third colon.\n const fields = result.stdout.split(':');\n if (fields.length < 4) return false;\n const members = (fields[3] ?? '')\n .trim()\n .split(',')\n .map((m) => m.trim())\n .filter(Boolean);\n return members.includes(username);\n}\n\nfunction defaultProbe(cmd: string, args: readonly string[]): number {\n const result = spawnSync(cmd, [...args], { stdio: 'ignore' });\n return result.status ?? 1;\n}\n\nfunction defaultReexec(argv: readonly string[]): number {\n // argv[0] is the node binary, argv[1] is our bin.ts entry, argv[2+]\n // are the user-supplied flags/positionals. `sg docker -c \"…\"` needs\n // a single shell-quoted command string.\n const quoted = argv.map(shellQuote).join(' ');\n const env = { ...process.env, [REEXEC_MARKER]: '1' };\n const result = spawnSync('sg', ['docker', '-c', quoted], {\n stdio: 'inherit',\n env,\n });\n return result.status ?? 1;\n}\n\n/**\n * Single-quote-wrap an argv element so it survives `sg docker -c \"…\"`.\n * The wrapping pattern `'a'\\''b'` is the standard \"literal single\n * quotes inside a single-quoted string\" trick — close the quote,\n * escape a literal quote, reopen.\n */\nfunction shellQuote(arg: string): string {\n // Safe characters that don't need quoting at all.\n if (/^[\\w./@:=,+-]+$/.test(arg)) return arg;\n return `'${arg.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n// Exported for tests.\nexport const _internals = { isInDockerGroupViaEtcGroup, shellQuote };\n","import type { CommandDef } from 'citty';\n\n/**\n * Custom help renderer. Citty's built-in `renderUsage` has two\n * issues for Monoceros:\n *\n * 1. It puts `[OPTIONS]` *before* the positional arguments in the\n * USAGE line. We want positionals first, matching the\n * `monoceros <command> <containername> [<args> …]` shape\n * documented in konzept.md and docs/commands/README.md.\n * 2. It lists all subcommands in a flat pipe-separated USAGE line\n * (`monoceros init|shell|run|…`) and a flat COMMANDS block.\n * With 20+ commands that becomes unreadable.\n *\n * This module checks for `--help` / `-h` in argv before citty gets a\n * chance to print its own help. When triggered, it resolves the\n * matching subcommand and prints our own block: positional-first\n * USAGE, COMMANDS grouped by `meta.group`, and descriptions wrapped\n * to terminal width.\n */\n\ninterface ResolvedArg {\n name: string;\n type: 'positional' | 'string' | 'boolean' | 'number' | 'enum';\n required?: boolean;\n description?: string;\n default?: unknown;\n alias?: string | string[];\n valueHint?: string;\n}\n\nconst ANSI_BOLD = '\\x1b[1m';\nconst ANSI_UNDERLINE = '\\x1b[4m';\nconst ANSI_CYAN = '\\x1b[36m';\nconst ANSI_GREY = '\\x1b[90m';\nconst ANSI_RESET = '\\x1b[0m';\n\nfunction isTty(): boolean {\n return process.stdout.isTTY ?? false;\n}\n\nfunction color(text: string, ...codes: string[]): string {\n if (!isTty()) return text;\n return codes.join('') + text + ANSI_RESET;\n}\n\nconst bold = (s: string) => color(s, ANSI_BOLD);\nconst underline = (s: string) => color(s, ANSI_UNDERLINE);\nconst cyan = (s: string) => color(s, ANSI_CYAN);\nconst grey = (s: string) => color(s, ANSI_GREY);\n\n/**\n * Ordered list of command-group keys with a human-readable label.\n * Anything a command file tags via `meta.group` lands in the\n * matching bucket; anything ungrouped falls through to \"Other\".\n * The render order follows this array.\n */\nconst GROUPS: ReadonlyArray<{ key: string; label: string }> = [\n { key: 'lifecycle', label: 'Container lifecycle' },\n { key: 'run', label: 'Run + inspect' },\n { key: 'edit', label: 'Edit container yml' },\n { key: 'discovery', label: 'Discovery' },\n { key: 'tooling', label: 'Tooling' },\n];\n\nfunction resolveArgs(\n argsDef: Record<string, unknown> | undefined,\n): ResolvedArg[] {\n if (!argsDef) return [];\n const out: ResolvedArg[] = [];\n for (const [name, defRaw] of Object.entries(argsDef)) {\n const def = (defRaw ?? {}) as Partial<ResolvedArg>;\n out.push({\n name,\n type: (def.type as ResolvedArg['type']) ?? 'string',\n required: def.required,\n description: def.description,\n default: def.default,\n alias: def.alias,\n valueHint: def.valueHint,\n });\n }\n return out;\n}\n\nfunction renderValueHint(arg: ResolvedArg): string {\n if (arg.type === 'boolean') return '';\n const hint = arg.valueHint ?? arg.name;\n return `=<${hint}>`;\n}\n\nfunction renderArgDescription(arg: ResolvedArg, isRequired: boolean): string {\n const parts: string[] = [];\n if (arg.description) parts.push(arg.description);\n if (isRequired) parts.push(grey('(Required)'));\n if (arg.default !== undefined && arg.type !== 'boolean') {\n parts.push(grey(`(Default: ${JSON.stringify(arg.default)})`));\n }\n return parts.join(' ');\n}\n\n// Strip ANSI escape sequences so column-padding measurements use\n// the visible width instead of the raw character count.\n// eslint-disable-next-line no-control-regex\nconst ANSI_RE = /\\x1b\\[[0-9;]*m/g;\n\nfunction visibleLen(s: string): number {\n return s.replace(ANSI_RE, '').length;\n}\n\nfunction terminalWidth(): number {\n return process.stdout.columns && process.stdout.columns > 40\n ? process.stdout.columns\n : 100;\n}\n\n/**\n * Wrap `text` (which may contain ANSI codes) to fit `width` columns,\n * with `continuationIndent` prepended to every wrapped line after\n * the first. Word-aware: breaks at spaces, falls back to hard breaks\n * only for individual tokens longer than `width`.\n */\nfunction wrapText(\n text: string,\n width: number,\n continuationIndent: string,\n): string {\n if (visibleLen(text) <= width) return text;\n // `width` is the budget for the actual text on each line — it does\n // not include the continuation indent (caller already accounted for\n // it when computing width). Continuation-line indent gets prefixed\n // at join time, so every line gets the same text-column budget.\n const words = text.split(/(\\s+)/);\n const lines: string[] = [];\n let current = '';\n for (const w of words) {\n if (visibleLen(current) + visibleLen(w) <= width) {\n current += w;\n continue;\n }\n if (current.length > 0) lines.push(current.replace(/\\s+$/, ''));\n current = w.replace(/^\\s+/, '');\n }\n if (current.length > 0) lines.push(current.replace(/\\s+$/, ''));\n return lines.map((l, i) => (i === 0 ? l : continuationIndent + l)).join('\\n');\n}\n\n/**\n * Render a left-aligned label column next to wrapped descriptions.\n * Column gutter is four spaces. Description wraps within the\n * remaining terminal width.\n *\n * - `fixedLabelWidth`: use this label-column width instead of the\n * per-call maximum. Lets several tables (e.g. every COMMANDS\n * group) share one alignment column so descriptions line up\n * across sections, not just within one.\n * - `rowGap`: insert a blank line between rows so multi-line\n * descriptions don't run together.\n */\nfunction alignTable(\n rows: Array<[string, string]>,\n indent: string,\n opts: { fixedLabelWidth?: number; rowGap?: boolean } = {},\n): string {\n if (rows.length === 0) return '';\n const labelWidth =\n opts.fixedLabelWidth ?? Math.max(...rows.map((r) => visibleLen(r[0])));\n const gutter = ' ';\n const descWidth =\n terminalWidth() - indent.length - labelWidth - gutter.length;\n const continuationIndent = ' '.repeat(\n indent.length + labelWidth + gutter.length,\n );\n return rows\n .map(([left, right]) => {\n const pad = ' '.repeat(Math.max(0, labelWidth - visibleLen(left)));\n const wrapped = wrapText(right, descWidth, continuationIndent);\n return `${indent}${left}${pad}${gutter}${wrapped}`;\n })\n .join(opts.rowGap ? '\\n\\n' : '\\n');\n}\n\ninterface SubCommandEntry {\n name: string;\n description: string;\n group: string;\n}\n\nfunction collectSubCommands(cmd: CommandDef): SubCommandEntry[] {\n const subs = (cmd.subCommands ?? {}) as Record<string, CommandDef>;\n const out: SubCommandEntry[] = [];\n for (const [name, sub] of Object.entries(subs)) {\n const meta = (sub?.meta ?? {}) as {\n hidden?: boolean;\n description?: string;\n group?: string;\n };\n if (meta.hidden) continue;\n out.push({\n name,\n description: meta.description ?? '',\n group: meta.group ?? 'other',\n });\n }\n return out;\n}\n\nfunction renderCommandsBlock(entries: SubCommandEntry[]): string[] {\n if (entries.length === 0) return [];\n const lines: string[] = [];\n lines.push(underline(bold('COMMANDS')));\n\n // Group entries while preserving GROUPS' declared order. Anything\n // tagged with an unknown group (or no group) falls into \"Other\"\n // and renders last.\n const byGroup = new Map<string, SubCommandEntry[]>();\n for (const entry of entries) {\n const arr = byGroup.get(entry.group) ?? [];\n arr.push(entry);\n byGroup.set(entry.group, arr);\n }\n\n // One label-column width across ALL groups so every command's\n // description starts at the same column, not just within a group.\n const labelWidth = Math.max(...entries.map((e) => visibleLen(cyan(e.name))));\n\n const renderSection = (label: string, items: SubCommandEntry[]) => {\n if (items.length === 0) return;\n lines.push('');\n // Group label is left-aligned, underlined, in grey — distinct\n // from the section headers above (which are bold+underlined+white)\n // through colour + weight. Blank line after gives the items room\n // to breathe; cyan command labels do the visual separation from\n // the heading without needing an indent, so commands go flush\n // left and the descriptions get the full terminal width.\n lines.push(underline(grey(label)));\n lines.push('');\n const rows: Array<[string, string]> = items.map((e) => [\n cyan(e.name),\n e.description,\n ]);\n // Shared label width + a blank line between entries so the wrapped\n // multi-line descriptions stay readable.\n lines.push(\n alignTable(rows, '', { fixedLabelWidth: labelWidth, rowGap: true }),\n );\n };\n\n for (const { key, label } of GROUPS) {\n renderSection(label, byGroup.get(key) ?? []);\n byGroup.delete(key);\n }\n // Anything left over (ungrouped or unknown-group) lands in a\n // catch-all section so nothing silently disappears.\n for (const [groupKey, items] of byGroup) {\n const label = groupKey === 'other' ? 'Other' : groupKey;\n renderSection(label, items);\n }\n\n lines.push('');\n return lines;\n}\n\nexport function renderUsageBlock(\n cmd: CommandDef,\n commandPath: string[],\n): string {\n const meta = (cmd.meta ?? {}) as {\n name?: string;\n description?: string;\n version?: string;\n };\n const args = resolveArgs((cmd.args ?? {}) as Record<string, unknown>);\n const subCommandEntries = collectSubCommands(cmd);\n\n const fullName = commandPath.join(' ') || meta.name || 'monoceros';\n\n const positionals = args.filter((a) => a.type === 'positional');\n const flags = args.filter((a) => a.type !== 'positional');\n\n // USAGE line: positionals come first, then [OPTIONS]. When the\n // command has subcommands, render a single `<command>` placeholder\n // instead of a pipe-separated list — anything more than a couple\n // of subcommands makes the pipe list unreadable, and the COMMANDS\n // block below carries the actual menu.\n const usageTokens: string[] = [];\n for (const p of positionals) {\n const isRequired = p.required !== false && p.default === undefined;\n const t = p.name.toUpperCase();\n usageTokens.push(isRequired ? `<${t}>` : `[${t}]`);\n }\n if (subCommandEntries.length > 0) usageTokens.push('<command>');\n if (flags.length > 0) usageTokens.push('[OPTIONS]');\n\n const lines: string[] = [];\n const version = meta.version;\n const header = `${meta.description ?? ''} (${fullName}${version ? ` v${version}` : ''})`;\n lines.push(grey(wrapText(header, terminalWidth(), '')));\n lines.push('');\n lines.push(\n `${underline(bold('USAGE'))} ${cyan([fullName, ...usageTokens].join(' '))}`,\n );\n lines.push('');\n\n if (positionals.length > 0) {\n lines.push(underline(bold('ARGUMENTS')));\n lines.push('');\n const rows: Array<[string, string]> = positionals.map((p) => {\n const isRequired = p.required !== false && p.default === undefined;\n return [cyan(p.name.toUpperCase()), renderArgDescription(p, isRequired)];\n });\n lines.push(alignTable(rows, ' '));\n lines.push('');\n }\n\n if (flags.length > 0) {\n lines.push(underline(bold('OPTIONS')));\n lines.push('');\n const rows: Array<[string, string]> = flags.map((f) => {\n const isRequired = f.required === true && f.default === undefined;\n const aliases = (\n Array.isArray(f.alias) ? f.alias : f.alias ? [f.alias] : []\n ).map((a) => `-${a}`);\n const label = [...aliases, `--${f.name}`].join(', ') + renderValueHint(f);\n return [cyan(label), renderArgDescription(f, isRequired)];\n });\n lines.push(alignTable(rows, ' '));\n lines.push('');\n }\n\n if (subCommandEntries.length > 0) {\n for (const line of renderCommandsBlock(subCommandEntries)) {\n lines.push(line);\n }\n lines.push(\n `Use ${cyan(`${fullName} <command> --help`)} for more information about a command.`,\n );\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Detect `--help` / `-h` somewhere in argv that's *not* preceded by\n * a separator `--`. Returns the command path so the caller can render\n * the right subcommand's help.\n *\n * Returns `null` when help wasn't requested at all.\n */\nexport function detectHelpRequest(\n argv: string[],\n main: CommandDef,\n): { path: string[]; cmd: CommandDef } | null {\n const helpIdx = argv.findIndex((a) => a === '--help' || a === '-h');\n const separatorIdx = argv.indexOf('--');\n if (helpIdx === -1) return null;\n if (separatorIdx !== -1 && separatorIdx < helpIdx) return null;\n\n // Walk subcommands by matching argv tokens (in order, before --)\n // against the current command's `subCommands` map.\n const path: string[] = [];\n const tokens = argv.slice(\n 0,\n separatorIdx === -1 ? argv.length : separatorIdx,\n );\n let cursor: CommandDef = main;\n const mainName = ((main.meta ?? {}) as { name?: string }).name ?? 'monoceros';\n path.push(mainName);\n for (const tok of tokens) {\n if (tok.startsWith('-')) continue;\n const subs = (cursor.subCommands ?? {}) as Record<string, CommandDef>;\n if (tok in subs) {\n cursor = subs[tok]!;\n path.push(tok);\n continue;\n }\n // Token isn't a subcommand name — stop walking. Any further\n // tokens are positionals/values for the current command, not\n // routing hints.\n break;\n }\n return { path, cmd: cursor };\n}\n\n/**\n * If argv requests --help, print our own usage block and tell the\n * caller to exit. Returns true when help was rendered.\n */\nexport async function maybeRenderHelp(\n argv: string[],\n main: CommandDef,\n): Promise<boolean> {\n const hit = detectHelpRequest(argv, main);\n if (!hit) return false;\n // Resolve cmd's lazy fields (citty allows them to be functions)\n // before we render. We don't currently use lazy fields, so a\n // simple pass-through suffices.\n process.stdout.write(renderUsageBlock(hit.cmd, hit.path) + '\\n');\n return true;\n}\n","/**\n * Splits the user-args at the first `--` marker.\n *\n * `monoceros run -- monoceros-plugin --help` should hand `--help` to\n * `monoceros-plugin` inside the container, not trigger citty's eager\n * `--help` parser on the outer `monoceros run`. Citty parses `--help`\n * and `--version` before our subcommand handlers run, so the only\n * reliable fix is to strip everything after `--` from `process.argv`\n * before `runMain()` ever sees it.\n *\n * `splitInnerArgs` is the pure helper.\n * `consumeInnerArgsFromProcessArgv` is the side-effecting glue called\n * from `bin.ts`. `getInnerArgs()` is read by `runCommand`.\n */\n\nlet innerArgs: readonly string[] = [];\n\nexport function splitInnerArgs(userArgs: readonly string[]): {\n outerArgs: string[];\n innerArgs: string[];\n} {\n const dashIdx = userArgs.indexOf('--');\n if (dashIdx === -1) {\n return { outerArgs: [...userArgs], innerArgs: [] };\n }\n return {\n outerArgs: userArgs.slice(0, dashIdx),\n innerArgs: userArgs.slice(dashIdx + 1),\n };\n}\n\nexport function consumeInnerArgsFromProcessArgv(): void {\n // process.argv[0] = node, [1] = script path, [2..] = user args\n const userArgs = process.argv.slice(2);\n const split = splitInnerArgs(userArgs);\n process.argv = [...process.argv.slice(0, 2), ...split.outerArgs];\n innerArgs = split.innerArgs;\n}\n\nexport function getInnerArgs(): readonly string[] {\n return innerArgs;\n}\n\n/** Test seam: lets unit tests set inner args without touching process.argv. */\nexport function setInnerArgsForTesting(args: readonly string[]): void {\n innerArgs = args;\n}\n","import { defineCommand } from 'citty';\nimport { addAptPackagesCommand } from './commands/add-apt-packages.js';\nimport { addFeatureCommand } from './commands/add-feature.js';\nimport { addFromUrlCommand } from './commands/add-from-url.js';\nimport { addRepoCommand } from './commands/add-repo.js';\nimport { addLanguageCommand } from './commands/add-language.js';\nimport { addPortCommand } from './commands/add-port.js';\nimport { addServiceCommand } from './commands/add-service.js';\nimport { applyCommand } from './commands/apply.js';\nimport { completionCommand } from './commands/completion.js';\nimport { __completeCommand } from './commands/__complete.js';\nimport { initCommand } from './commands/init.js';\nimport { listComponentsCommand } from './commands/list-components.js';\nimport { logsCommand } from './commands/logs.js';\nimport { portCommand } from './commands/port.js';\nimport { removeAptPackagesCommand } from './commands/remove-apt-packages.js';\nimport { removeFeatureCommand } from './commands/remove-feature.js';\nimport { removeCommand } from './commands/remove.js';\nimport { restoreCommand } from './commands/restore.js';\nimport { removeFromUrlCommand } from './commands/remove-from-url.js';\nimport { removeLanguageCommand } from './commands/remove-language.js';\nimport { removePortCommand } from './commands/remove-port.js';\nimport { removeRepoCommand } from './commands/remove-repo.js';\nimport { removeServiceCommand } from './commands/remove-service.js';\nimport { runCommand } from './commands/run.js';\nimport { shellCommand } from './commands/shell.js';\nimport { startCommand } from './commands/start.js';\nimport { statusCommand } from './commands/status.js';\nimport { stopCommand } from './commands/stop.js';\nimport { tunnelCommand } from './commands/tunnel.js';\nimport { CLI_VERSION } from './version.js';\n\nexport const main = defineCommand({\n meta: {\n name: 'monoceros',\n version: CLI_VERSION,\n description:\n 'Monoceros workbench — local, sandboxed AI-coding environment for solution builders.',\n },\n subCommands: {\n init: initCommand,\n 'list-components': listComponentsCommand,\n shell: shellCommand,\n run: runCommand,\n logs: logsCommand,\n start: startCommand,\n stop: stopCommand,\n status: statusCommand,\n apply: applyCommand,\n remove: removeCommand,\n restore: restoreCommand,\n 'add-service': addServiceCommand,\n 'add-language': addLanguageCommand,\n 'add-apt-packages': addAptPackagesCommand,\n 'add-feature': addFeatureCommand,\n 'add-from-url': addFromUrlCommand,\n 'add-repo': addRepoCommand,\n 'add-port': addPortCommand,\n 'remove-service': removeServiceCommand,\n 'remove-language': removeLanguageCommand,\n 'remove-apt-packages': removeAptPackagesCommand,\n 'remove-feature': removeFeatureCommand,\n 'remove-from-url': removeFromUrlCommand,\n 'remove-repo': removeRepoCommand,\n 'remove-port': removePortCommand,\n port: portCommand,\n tunnel: tunnelCommand,\n completion: completionCommand,\n __complete: __completeCommand,\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { getInnerArgs } from '../inner-args.js';\nimport { runAddAptPackages } from '../modify/index.js';\n\nexport const addAptPackagesCommand = defineCommand({\n meta: {\n name: 'add-apt-packages',\n group: 'edit',\n description:\n 'Add Debian/Ubuntu apt packages to the container config. Pass package names after `--` (e.g. `monoceros add-apt-packages sandbox -- make openssh-client jq`). Idempotent. No curated whitelist — invalid names surface as apt errors at container build time.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n const packages = [...getInnerArgs()];\n if (packages.length === 0) {\n consola.error(\n 'No package names given. Usage: `monoceros add-apt-packages <containername> [--yes] -- <pkg> [<pkg> …]`.',\n );\n process.exit(1);\n }\n try {\n const result = await runAddAptPackages({\n name: args.name,\n packages,\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { promises as fs } from 'node:fs';\nimport { consola } from 'consola';\nimport { createPatch } from 'diff';\nimport path from 'node:path';\nimport type { Document } from 'yaml';\nimport { parseConfig, readConfig, stringifyConfig } from '../config/io.js';\nimport {\n containerConfigPath,\n containerConfigsDir,\n containerDir,\n containerEnvPath,\n monocerosHome as defaultMonocerosHome,\n} from '../config/paths.js';\nimport {\n ensureEnvGitignored,\n ensureEnvVars,\n hasVarPlaceholder,\n GIT_IDENTITY_VAR,\n} from '../config/env-file.js';\nimport { featureOptionHints } from '../init/feature-doc.js';\nimport { loadFeatureManifestSummary } from '../init/manifest.js';\nimport {\n collectGitCredentials,\n resolveProvider,\n type CredentialsSpawn,\n} from '../devcontainer/credentials.js';\nimport {\n findRunningContainerByLocalFolder,\n realContainerExec,\n type ContainerExec,\n type DockerLookupExec,\n} from '../devcontainer/locate-running.js';\nimport { proxyHostPort, readMonocerosConfig } from '../config/global.js';\nimport {\n KNOWN_PROVIDER_HOSTS,\n PROVIDER_VALUES,\n REGEX,\n isValidEmail,\n portNumber,\n type RepoProvider,\n} from '../config/schema.js';\nimport { loadComponentCatalog } from '../init/components.js';\nimport {\n ensureProxy,\n maybeStopProxy,\n type DockerExec as ProxyDockerExec,\n} from '../proxy/index.js';\nimport {\n proxyUrlsFor,\n removeDynamicConfig,\n writeDynamicConfig,\n} from '../proxy/dynamic.js';\nimport { preflightHostPort } from '../proxy/port-check.js';\nimport {\n BUILTIN_LANGUAGES,\n LANGUAGE_CATALOG,\n curatedServiceEnvDefaults,\n deriveServiceName,\n expandCuratedService,\n isCuratedService,\n knownLanguages,\n} from '../create/catalog.js';\nimport {\n renderServiceObjectBody,\n renderCustomService,\n customServiceHint,\n} from '../init/service-doc.js';\nimport { deriveRepoName } from '../create/scaffold.js';\nimport type { FeatureOptions, RepoEntry } from '../create/types.js';\nimport {\n addAptPackagesToDoc,\n addFeatureToDoc,\n addInstallUrlToDoc,\n addLanguageToDoc,\n addPortsToDoc,\n addRepoToDoc,\n addServiceEntryToDoc,\n ensureContainerGitUserPlaceholder,\n relocateLeakedSectionComments,\n removeAptPackagesFromDoc,\n removeFeatureFromDoc,\n removeInstallUrlFromDoc,\n removeLanguageFromDoc,\n removePortsFromDoc,\n removeRepoFromDoc,\n removeServiceFromDoc,\n setDefaultPortInDoc,\n} from './yml.js';\n\n/**\n * `monoceros add-*` / `monoceros remove-*` — edit the yml at\n * `<MONOCEROS_HOME>/container-configs/<name>.yml` for one container.\n *\n * No cwd magic. The first positional argument is always the container\n * name; the command looks up the yml via convention. Comment-preserving\n * AST mutation; the builder then runs `monoceros apply <name>` to\n * materialize.\n */\n\nexport interface ModifyLogger {\n info: (message: string) => void;\n success: (message: string) => void;\n warn: (message: string) => void;\n}\n\nexport type ConfirmFn = (prompt: string) => Promise<boolean>;\n\nexport interface ModifyOptions {\n /** Container name — resolves to `<home>/container-configs/<name>.yml`. */\n name: string;\n yes?: boolean;\n logger?: ModifyLogger;\n output?: (line: string) => void;\n confirm?: ConfirmFn;\n /** Override the resolved MONOCEROS_HOME. Tests inject a tmpdir. */\n monocerosHome?: string;\n}\n\nexport interface AddLanguageInput extends ModifyOptions {\n language: string;\n}\nexport interface AddServiceInput extends ModifyOptions {\n service: string;\n /**\n * Override the compose service name. Required to add the same image\n * more than once (two postgres servers → `--as postgres-app` /\n * `--as postgres-analytics`) and to disambiguate two custom images\n * that derive the same name.\n */\n as?: string;\n}\nexport interface AddAptPackagesInput extends ModifyOptions {\n packages: string[];\n}\nexport interface AddFeatureInput extends ModifyOptions {\n ref: string;\n options?: FeatureOptions;\n}\nexport interface AddFromUrlInput extends ModifyOptions {\n url: string;\n}\nexport interface AddRepoInput extends ModifyOptions {\n url: string;\n /**\n * Explicit destination path under `projects/`. Subfolders allowed\n * via `/` (e.g. `apps/web`). When omitted, the URL-derived single-\n * segment default is used (`https://.../foo.git` → `foo` →\n * `projects/foo/`).\n */\n path?: string;\n /**\n * Optional per-repo git committer identity override. Both name and\n * email must be set together; one alone is a usage error. Falls\n * back to the container-level `git.user` (which itself falls back\n * to the host's `git config --global`) when omitted.\n */\n gitName?: string;\n gitEmail?: string;\n /**\n * Git provider hint. Required when the URL host is not one of the\n * three canonical ones (github.com / gitlab.com / bitbucket.org);\n * optional otherwise. Validated against `PROVIDER_VALUES`.\n */\n provider?: string;\n /**\n * Test injection points for the on-the-fly-clone path (the part\n * that runs after the yml mutation when the container is up).\n */\n containerLookupDocker?: DockerLookupExec;\n containerExec?: ContainerExec;\n credentialsSpawn?: CredentialsSpawn;\n}\n\nexport interface RemoveLanguageInput extends ModifyOptions {\n language: string;\n}\nexport interface RemoveServiceInput extends ModifyOptions {\n service: string;\n}\nexport interface RemoveAptPackagesInput extends ModifyOptions {\n packages: string[];\n}\nexport interface RemoveFeatureInput extends ModifyOptions {\n ref: string;\n}\nexport interface RemoveFromUrlInput extends ModifyOptions {\n url: string;\n}\nexport interface RemoveRepoInput extends ModifyOptions {\n /** url or (effective) name — `monoceros remove-repo` accepts either. */\n target: string;\n}\n\nexport interface AddPortInput extends ModifyOptions {\n ports: number[];\n /**\n * When true, the (single) port in `ports` is moved to / inserted at\n * the front of `routing.ports` — making it the bare\n * `<name>.localhost` default route. Only valid with exactly one\n * port in the args; multiple ports + `asDefault` is a usage error.\n */\n asDefault?: boolean;\n /** Override the docker exec used by the Traefik proxy lifecycle. */\n proxyDocker?: ProxyDockerExec;\n}\nexport interface RemovePortInput extends ModifyOptions {\n ports: number[];\n /** Override the docker exec used by the Traefik proxy lifecycle. */\n proxyDocker?: ProxyDockerExec;\n}\n\nexport type ModifyResult =\n | { status: 'no-change' }\n | { status: 'updated'; changedPaths: string[] }\n | { status: 'aborted' };\n\ntype YmlMutator = (doc: Document) => boolean;\n\n// ─── add-* ────────────────────────────────────────────────────────\n\nexport function runAddLanguage(input: AddLanguageInput): Promise<ModifyResult> {\n if (\n !BUILTIN_LANGUAGES.has(input.language) &&\n !LANGUAGE_CATALOG[input.language]\n ) {\n throw new Error(\n `Unknown language: ${input.language}. Known: ${knownLanguages().join(', ')}.`,\n );\n }\n return mutate(input, (doc) => addLanguageToDoc(doc, input.language));\n}\n\nexport async function runAddService(\n input: AddServiceInput,\n): Promise<ModifyResult> {\n // Curated catalog name → expand to a full active object block.\n // Anything else → treat the argument as an image, derive the service\n // name from it, and drop in a commented scaffold for the fields\n // Monoceros can't know.\n const arg = input.service;\n const curated = isCuratedService(arg);\n\n // `--as` overrides the compose service name (default: the curated\n // name, or one derived from the image). Validate the override here so\n // the builder gets a focused message rather than a schema round-trip\n // error. Mirrors the schema's SERVICE_NAME_RE.\n if (input.as !== undefined && !/^[a-z0-9][a-z0-9_-]*$/.test(input.as)) {\n throw new Error(\n `Invalid --as name ${JSON.stringify(input.as)}. Use lowercase letters, digits, '_' or '-' (must start with a letter or digit).`,\n );\n }\n const name = input.as ?? (curated ? arg : deriveServiceName(arg));\n const image = curated ? expandCuratedService(arg).image : arg;\n // Render the block under the resolved name. For curated services the\n // expansion carries the catalog name, so override it before rendering.\n const custom = curated ? null : renderCustomService(name, arg);\n const bodyLines = curated\n ? renderServiceObjectBody({ ...expandCuratedService(arg), name })\n : custom!.bodyLines;\n const scaffoldComment = curated ? undefined : custom!.comment;\n\n const result = await mutate(input, (doc) => {\n const r = addServiceEntryToDoc(\n doc,\n name,\n image,\n bodyLines,\n scaffoldComment,\n );\n if (r.outcome === 'conflict') {\n throw new Error(\n `A service named '${name}' already exists with a different image (${r.existingImage}). ` +\n `Add it under a different name with \\`--as <name>\\`, or remove the existing one first ` +\n `(\\`monoceros remove-service ${input.name} ${name}\\`).`,\n );\n }\n return r.outcome === 'added';\n });\n\n // Curated service → seed its env dev-defaults into <name>.env (the\n // same ${KEY} placeholders the expanded block carries), mirroring\n // init and add-feature. Keys are image-dictated (POSTGRES_USER, …),\n // so --as renaming the service doesn't change them. Custom images\n // get the fill-in-the-scaffold hint instead — Monoceros can't know\n // their vars.\n if (result.status === 'updated') {\n if (curated) {\n const defaults = curatedServiceEnvDefaults(arg);\n if (Object.keys(defaults).length > 0) {\n const home = input.monocerosHome ?? defaultMonocerosHome();\n await ensureEnvGitignored(containerConfigsDir(home));\n const seeded = await ensureEnvVars(\n containerEnvPath(input.name, home),\n input.name,\n defaults,\n );\n if (seeded.added.length > 0) {\n (input.logger ?? defaultLogger()).info(\n `Seeded ${seeded.added.join(', ')} into ${input.name}.env (dev-defaults — change them there if needed).`,\n );\n }\n }\n } else {\n (input.logger ?? defaultLogger()).info(customServiceHint(name));\n }\n }\n return result;\n}\n\nexport function runAddAptPackages(\n input: AddAptPackagesInput,\n): Promise<ModifyResult> {\n if (input.packages.length === 0) {\n throw new Error(\n 'No package names given. Usage: monoceros add-apt-packages <containername> -- <pkg> [<pkg> …].',\n );\n }\n return mutate(input, (doc) => addAptPackagesToDoc(doc, input.packages));\n}\n\nexport async function runAddRepo(input: AddRepoInput): Promise<ModifyResult> {\n const url = input.url.trim();\n if (url.length === 0) {\n throw new Error(\n 'Missing repo URL. Usage: monoceros add-repo <containername> <url>.',\n );\n }\n const path = (input.path ?? deriveRepoName(url)).trim();\n // --git-name and --git-email come as a pair. Reject half-set input\n // loudly instead of silently dropping it.\n const hasName =\n typeof input.gitName === 'string' && input.gitName.trim().length > 0;\n const hasEmail =\n typeof input.gitEmail === 'string' && input.gitEmail.trim().length > 0;\n if (hasName !== hasEmail) {\n throw new Error(\n '--git-name and --git-email must be set together. Pass both, or neither.',\n );\n }\n // Validate the email eagerly at the flag entry — the schema defers\n // email format to apply (to allow `${VAR}` placeholders from the\n // hand-edited yml), so a typo'd literal would otherwise only surface\n // at apply. A `${VAR}` placeholder is allowed through here too, in\n // case the builder wants to manage the value in <name>.env.\n if (hasEmail) {\n const email = input.gitEmail!.trim();\n if (!isValidEmail(email) && !hasVarPlaceholder(email)) {\n throw new Error(\n `Invalid --git-email '${email}': must be a valid email or a \\${VAR} placeholder resolved from <name>.env.`,\n );\n }\n }\n // --provider validation:\n // - host is canonical (github.com / gitlab.com / bitbucket.org):\n // * no --provider → fine, auto-detected at apply time\n // * --provider matches canonical → accepted, written to yml\n // (harmless; round-trip stays clean)\n // * --provider contradicts canonical → reject loudly\n // - host is non-canonical:\n // * --provider given (valid enum) → write it\n // * --provider missing → reject; the apply pre-flight would\n // fail anyway, fail at add-repo time for a better signal\n // * --provider invalid value → reject with allowed list\n const explicitProvider = normalizeProvider(input.provider);\n let host: string | undefined;\n try {\n host = url.startsWith('https://') ? new URL(url).hostname : undefined;\n } catch {\n host = undefined;\n }\n const canonical = host ? KNOWN_PROVIDER_HOSTS[host.toLowerCase()] : undefined;\n if (host && !canonical && !explicitProvider) {\n throw new Error(\n `Host '${host}' is not a canonical Git provider Monoceros can auto-detect (github.com / gitlab.com / bitbucket.org). Pass --provider=github|gitlab|bitbucket so the credential-helper hints know which CLI to suggest.`,\n );\n }\n if (canonical && explicitProvider && explicitProvider !== canonical) {\n throw new Error(\n `--provider=${explicitProvider} contradicts host '${host}' (auto-detected as ${canonical}). Drop --provider for canonical hosts, or fix the value.`,\n );\n }\n // For canonical hosts we don't persist `provider:` in the yml even\n // when the flag was passed (matches what auto-detection would do\n // and keeps the yml minimal). Non-canonical hosts: write the\n // explicit value as-is.\n const providerToWrite =\n !canonical && explicitProvider ? explicitProvider : undefined;\n const entry: RepoEntry = {\n url,\n path,\n ...(hasName && hasEmail\n ? {\n gitUser: {\n name: input.gitName!.trim(),\n email: input.gitEmail!.trim(),\n },\n }\n : {}),\n ...(providerToWrite ? { provider: providerToWrite } : {}),\n };\n // When a NEW repo is added and the container has no `git.user` yet,\n // scaffold a container-level identity with `${VAR}` placeholders (and\n // seed the blank keys below) — same env-managed default `init`\n // produces. Rides along in the same diff. An existing `git.user`\n // (literal or placeholder) is left untouched.\n let gitUserScaffolded = false;\n const result = await mutate(input, (doc) => {\n const repoAdded = addRepoToDoc(doc, entry);\n if (repoAdded) gitUserScaffolded = ensureContainerGitUserPlaceholder(doc);\n return repoAdded;\n });\n if (result.status === 'updated' && gitUserScaffolded) {\n const home = input.monocerosHome ?? defaultMonocerosHome();\n await ensureEnvGitignored(containerConfigsDir(home));\n await ensureEnvVars(containerEnvPath(input.name, home), input.name, [\n GIT_IDENTITY_VAR.name,\n GIT_IDENTITY_VAR.email,\n ]);\n (input.logger ?? defaultLogger()).info(\n `Added a container git.user with \\${${GIT_IDENTITY_VAR.name}}/\\${${GIT_IDENTITY_VAR.email}} placeholders and seeded ${input.name}.env — fill them or leave blank to use your global git identity.`,\n );\n }\n // On-the-fly clone path: if the yml change took AND the container\n // is currently running, clone the repo directly into the\n // container so the builder doesn't have to `monoceros apply`\n // afterwards. Soft-fail with a warn — failures here never roll\n // back the yml write. See ADR 0007's add-port symmetry.\n if (result.status === 'updated') {\n await tryCloneInRunningContainer(input, entry);\n }\n return result;\n}\n\n/**\n * Best-effort: if the container is running, fetch HTTPS credentials\n * for the repo host, then `docker exec git clone …` directly into\n * `/workspaces/<name>/projects/<path>/`. Skips silently when:\n *\n * - the container isn't running (typical case — yml-only is fine,\n * `monoceros apply` will clone on next bring-up)\n * - the destination folder already exists (idempotent — matches\n * post-create.sh's \"skip clone if dir exists\" rule)\n *\n * Soft-fails with a warn on any error in the clone path. The yml\n * mutation is already persisted; the next `monoceros apply` will\n * retry. Tests inject the docker / credentials spawns; production\n * uses the real ones.\n */\nasync function tryCloneInRunningContainer(\n input: AddRepoInput,\n entry: RepoEntry,\n): Promise<void> {\n const home = input.monocerosHome ?? defaultMonocerosHome();\n const root = containerDir(input.name, home);\n const logger = input.logger ?? defaultLogger();\n\n let containerId: string | null;\n try {\n containerId = await findRunningContainerByLocalFolder(root, {\n ...(input.containerLookupDocker\n ? { docker: input.containerLookupDocker }\n : {}),\n });\n } catch (err) {\n logger.warn(\n `Could not check whether the container is running: ${err instanceof Error ? err.message : String(err)}. The yml is updated — run \\`monoceros apply ${input.name}\\` to clone.`,\n );\n return;\n }\n if (!containerId) {\n logger.info(\n `Container not running — yml updated only. Clone happens on \\`monoceros apply ${input.name}\\`.`,\n );\n return;\n }\n\n // Credential fetch for the URL's host. Same mechanism apply uses\n // (host-side `git credential fill`), writing into the bind-mounted\n // `.monoceros/git-credentials` file so the in-container clone can\n // pick it up via the credential.helper that post-create already\n // wired.\n let urlHost: string;\n try {\n urlHost = new URL(entry.url).hostname;\n } catch {\n logger.warn(\n `Cannot parse URL host from ${entry.url}. The yml is updated — clone manually inside the container or rerun with a fixed URL.`,\n );\n return;\n }\n const provider = resolveProvider(urlHost, entry.provider);\n if (provider === 'unknown') {\n logger.warn(\n `Could not resolve provider for host ${urlHost}. The yml is updated; clone happens at the next \\`monoceros apply\\` if you set the provider.`,\n );\n return;\n }\n try {\n const credsResult = await collectGitCredentials(\n root,\n [{ host: urlHost, provider }],\n {\n ...(input.credentialsSpawn ? { spawn: input.credentialsSpawn } : {}),\n logger: { info: () => {}, warn: (m) => logger.warn(m) },\n },\n );\n const status = credsResult.perHost.find((h) => h.host === urlHost);\n if (!status || status.status !== 'ok') {\n const detail = status?.detail ? `: ${status.detail}` : '';\n logger.warn(\n `No HTTPS credentials available for ${urlHost}${detail}. The yml is updated; set up credentials (e.g. \\`gh auth login\\`) and re-run \\`monoceros apply ${input.name}\\` or rerun this add-repo.`,\n );\n return;\n }\n } catch (err) {\n logger.warn(\n `Credential fetch for ${urlHost} failed: ${err instanceof Error ? err.message : String(err)}. The yml is updated.`,\n );\n return;\n }\n\n // The clone itself. mkdir -p ensures nested parents exist. The\n // outer `[ -d <target> ] && exit 0` short-circuit matches the\n // idempotency post-create.sh has — re-running add-repo against the\n // same URL is a yml no-op anyway, but if the folder somehow\n // exists without the yml entry we still don't overwrite.\n //\n // `git -c credential.helper=…` sets the helper INLINE just for\n // this clone, instead of relying on `git config --global` having\n // been set by post-create.sh. That matters because post-create\n // runs once at container up, not on every add-repo — a container\n // that started life without any HTTPS repo wouldn't have a\n // credential.helper configured at all. Inline-setting keeps\n // add-repo self-contained and doesn't mutate the container's\n // global git config as a side effect.\n const containerName = input.name;\n const targetRel = `projects/${entry.path}`;\n const parentRel = entry.path.includes('/')\n ? `projects/${entry.path.split('/').slice(0, -1).join('/')}`\n : 'projects';\n const credentialsFile = `/workspaces/${containerName}/.monoceros/git-credentials`;\n const credentialHelper = `store --file=${credentialsFile}`;\n const script = [\n `set -eu`,\n `cd /workspaces/${containerName}`,\n `if [ -d ${shquote(targetRel)} ]; then`,\n ` echo \"[add-repo] ${targetRel} already exists — skipping clone.\"`,\n ` exit 0`,\n `fi`,\n `mkdir -p ${shquote(parentRel)}`,\n `git -c ${shquote(`credential.helper=${credentialHelper}`)} clone ${shquote(entry.url)} ${shquote(targetRel)}`,\n ];\n if (entry.gitUser) {\n script.push(\n `git -C ${shquote(targetRel)} config user.name ${shquote(entry.gitUser.name)}`,\n `git -C ${shquote(targetRel)} config user.email ${shquote(entry.gitUser.email)}`,\n );\n }\n const execFn = input.containerExec ?? realContainerExec;\n let exit;\n try {\n exit = await execFn(containerId, ['bash', '-c', script.join('\\n')]);\n } catch (err) {\n logger.warn(\n `In-container clone for ${entry.url} failed: ${err instanceof Error ? err.message : String(err)}. The yml is updated; \\`monoceros apply ${input.name}\\` retries.`,\n );\n return;\n }\n if (exit.exitCode !== 0) {\n logger.warn(\n `In-container clone for ${entry.url} exited ${exit.exitCode}. The yml is updated; \\`monoceros apply ${input.name}\\` retries.`,\n );\n return;\n }\n logger.info(\n `Cloned ${entry.url} into /workspaces/${containerName}/${targetRel} inside the running container.`,\n );\n void path; // path import is reserved for future relative-path work\n}\n\n/**\n * Minimal shell-quote — single-quotes the value, escaping any\n * embedded single-quote via `'\\\\''`. The clone script runs inside a\n * `bash -c` invocation, so input that came from the yml (URLs,\n * paths, identity names) must be safely quoted to avoid trivial\n * injection or accidental shell-meta interpretation.\n */\nfunction shquote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\nfunction normalizeProvider(raw: string | undefined): RepoProvider | undefined {\n if (typeof raw !== 'string') return undefined;\n const trimmed = raw.trim();\n if (trimmed.length === 0) return undefined;\n const lowered = trimmed.toLowerCase() as RepoProvider;\n if (!(PROVIDER_VALUES as readonly string[]).includes(lowered)) {\n throw new Error(\n `Invalid --provider value: ${JSON.stringify(raw)}. Allowed: ${PROVIDER_VALUES.join(', ')}.`,\n );\n }\n return lowered;\n}\n\nexport function runAddFromUrl(input: AddFromUrlInput): Promise<ModifyResult> {\n const url = input.url.trim();\n if (url.length === 0) {\n throw new Error(\n 'Missing URL. Usage: monoceros add-from-url <containername> <url>.',\n );\n }\n return mutate(input, (doc) => addInstallUrlToDoc(doc, url));\n}\n\nexport async function runAddPort(input: AddPortInput): Promise<ModifyResult> {\n if (input.ports.length === 0) {\n throw new Error(\n 'No ports given. Usage: monoceros add-port <containername> -- <port> [<port> …].',\n );\n }\n const ports = normalizePorts(input.ports);\n if (input.asDefault && ports.length > 1) {\n throw new Error(\n `--default takes exactly one port. Got: ${ports.join(', ')}. Run add-port once with --default for the new default, then again (without --default) for the rest.`,\n );\n }\n const result = await mutate(input, (doc) => {\n if (input.asDefault) {\n // --default semantics: ensure the port exists AND sits at index\n // 0. setDefaultPortInDoc covers both (insert-or-move).\n return setDefaultPortInDoc(doc, ports[0]!);\n }\n return addPortsToDoc(doc, ports);\n });\n // Hot-reload path: when the yml actually changed, push the new\n // route set to the Traefik dynamic-config directory and make sure\n // the proxy is up. The yml is the source of truth — we re-read it\n // so the dynamic config reflects the FULL port list (including\n // entries that pre-existed this `add-port` call), not just the\n // delta. Proxy failures surface as warns but never roll back the\n // yml write. See ADR 0007.\n if (result.status === 'updated') {\n await syncPortsToProxy(input);\n }\n return result;\n}\n\n/**\n * Validate each entry as an integer in [1, 65535] and dedupe — same\n * port listed twice in the CLI args is treated as one. Throws on\n * any non-integer or out-of-range value with the offending input\n * verbatim so the builder can fix the typo.\n */\nfunction normalizePorts(raw: readonly (number | string)[]): number[] {\n const result: number[] = [];\n const seen = new Set<number>();\n for (const item of raw) {\n const n = typeof item === 'number' ? item : Number(item);\n if (!Number.isInteger(n) || n < 1 || n > 65535) {\n throw new Error(\n `Invalid port: ${JSON.stringify(item)}. Expected an integer between 1 and 65535.`,\n );\n }\n if (seen.has(n)) continue;\n seen.add(n);\n result.push(n);\n }\n return result;\n}\n\nexport async function runAddFeature(\n input: AddFeatureInput,\n): Promise<ModifyResult> {\n const raw = input.ref.trim();\n if (raw.length === 0) {\n throw new Error(\n 'Missing feature ref. Usage: monoceros add-feature <containername> <feature>.',\n );\n }\n const resolved = await resolveFeatureRefOrShortname(raw);\n // User-supplied `-- key=value` options override the catalog-driven\n // defaults that come with a short name. For a full OCI ref the\n // resolver returns no defaults, so this is just `input.options`.\n const merged: FeatureOptions = {\n ...resolved.defaultOptions,\n ...(input.options ?? {}),\n };\n // Hand the user's typed form (short-name or full ref) to the AST\n // mutator so any error message it produces echoes the form the\n // builder is using rather than always the resolved OCI ref.\n const result = await mutate(input, (doc) =>\n addFeatureToDoc(doc, resolved.ref, merged, raw),\n );\n\n // Seed the feature's credential vars into <name>.env (the same\n // ${VAR} placeholders addFeatureToDoc just wrote into the yml), so\n // the builder only fills values. Skips keys already set with an\n // active `-- key=value`. Mirrors init; remove-feature does NOT touch\n // the env file.\n if (result.status === 'updated') {\n const summary = loadFeatureManifestSummary(resolved.ref);\n const vars = featureOptionHints(\n summary,\n resolved.ref,\n Object.keys(merged),\n ).map((h) => h.envVar);\n if (vars.length > 0) {\n const home = input.monocerosHome ?? defaultMonocerosHome();\n await ensureEnvGitignored(containerConfigsDir(home));\n const seeded = await ensureEnvVars(\n containerEnvPath(input.name, home),\n input.name,\n vars,\n );\n if (seeded.added.length > 0) {\n (input.logger ?? defaultLogger()).info(\n `Seeded ${seeded.added.join(', ')} into ${input.name}.env — fill in the values.`,\n );\n }\n }\n }\n return result;\n}\n\n/**\n * Accept either a full OCI feature ref (`ghcr.io/.../foo:1`) or a\n * catalog short-name (`atlassian`, `atlassian/twg`, `claude`, …).\n *\n * Short names map to the matching component's `contributes.features`\n * entry; the entry's `options` (if any) become the default option\n * values the caller's `--` overrides apply on top of. Unknown short\n * names produce an error that lists the available features.\n */\nasync function resolveFeatureRefOrShortname(input: string): Promise<{\n ref: string;\n defaultOptions: FeatureOptions;\n}> {\n if (REGEX.featureRef.test(input)) {\n return { ref: input, defaultOptions: {} };\n }\n const catalog = await loadComponentCatalog();\n const component = catalog.get(input);\n if (!component) {\n const featureShorts = [...catalog.values()]\n .filter((c) => c.file.category === 'feature')\n .map((c) => c.name)\n .sort();\n const knownList =\n featureShorts.length > 0 ? featureShorts.join(', ') : '(none)';\n throw new Error(\n `Unknown feature: ${JSON.stringify(input)}. ` +\n `Pass either a catalog short-name (one of: ${knownList}) ` +\n `or a full OCI ref like ` +\n `'ghcr.io/getmonoceros/monoceros-features/<name>:<tag>'.`,\n );\n }\n if (component.file.category !== 'feature') {\n throw new Error(\n `'${input}' is a ${component.file.category}, not a feature. ` +\n `Use 'monoceros add-${component.file.category} <name> ${input}' instead.`,\n );\n }\n const features = component.file.contributes.features ?? [];\n if (features.length === 0) {\n throw new Error(\n `Catalog entry '${input}' contributes no feature ref — bug or stale catalog.`,\n );\n }\n if (features.length > 1) {\n // Practically: Monoceros's own catalog has one ref per feature\n // component. A multi-ref short-name would be ambiguous because\n // `add-feature` only adds one ref at a time.\n throw new Error(\n `'${input}' bundles multiple feature refs (${features\n .map((f) => f.ref)\n .join(\n ', ',\n )}). add-feature handles one at a time — pass the OCI ref directly.`,\n );\n }\n const [first] = features;\n return {\n ref: first!.ref,\n defaultOptions: { ...(first!.options ?? {}) },\n };\n}\n\n// ─── remove-* ─────────────────────────────────────────────────────\n\nexport function runRemoveLanguage(\n input: RemoveLanguageInput,\n): Promise<ModifyResult> {\n return mutate(input, (doc) => removeLanguageFromDoc(doc, input.language));\n}\n\nexport function runRemoveService(\n input: RemoveServiceInput,\n): Promise<ModifyResult> {\n return mutate(input, (doc) => removeServiceFromDoc(doc, input.service));\n}\n\nexport function runRemoveAptPackages(\n input: RemoveAptPackagesInput,\n): Promise<ModifyResult> {\n if (input.packages.length === 0) {\n throw new Error(\n 'No package names given. Usage: monoceros remove-apt-packages <containername> -- <pkg> [<pkg> …].',\n );\n }\n return mutate(input, (doc) => removeAptPackagesFromDoc(doc, input.packages));\n}\n\nexport async function runRemoveFeature(\n input: RemoveFeatureInput,\n): Promise<ModifyResult> {\n const raw = input.ref.trim();\n if (raw.length === 0) {\n throw new Error(\n 'Missing feature ref. Usage: monoceros remove-feature <containername> <feature>.',\n );\n }\n // Same short-name → ref resolution as `add-feature`. Without this\n // the suggestion `monoceros remove-feature atlassian` we print\n // elsewhere wouldn't actually work, only the full OCI form.\n const resolved = await resolveFeatureRefOrShortname(raw);\n return mutate(input, (doc) => removeFeatureFromDoc(doc, resolved.ref));\n}\n\nexport function runRemoveFromUrl(\n input: RemoveFromUrlInput,\n): Promise<ModifyResult> {\n const url = input.url.trim();\n if (url.length === 0) {\n throw new Error(\n 'Missing URL. Usage: monoceros remove-from-url <containername> <url>.',\n );\n }\n return mutate(input, (doc) => removeInstallUrlFromDoc(doc, url));\n}\n\nexport async function runRemovePort(\n input: RemovePortInput,\n): Promise<ModifyResult> {\n if (input.ports.length === 0) {\n throw new Error(\n 'No ports given. Usage: monoceros remove-port <containername> -- <port> [<port> …].',\n );\n }\n const ports = normalizePorts(input.ports);\n const result = await mutate(input, (doc) => removePortsFromDoc(doc, ports));\n // Hot-reload path: same state-driven sync as add-port. When the\n // last port is gone the dynamic-config file is dropped and the\n // Traefik singleton is offered up for teardown via maybeStopProxy\n // (which no-ops if any other container is still attached). See\n // ADR 0007.\n if (result.status === 'updated') {\n await syncPortsToProxy(input);\n }\n return result;\n}\n\nexport function runRemoveRepo(input: RemoveRepoInput): Promise<ModifyResult> {\n const target = input.target.trim();\n if (target.length === 0) {\n throw new Error(\n 'Missing repo identifier. Usage: monoceros remove-repo <containername> <url-or-name>.',\n );\n }\n return mutate(input, (doc) => removeRepoFromDoc(doc, target));\n}\n\n// ─── core mutate skeleton ─────────────────────────────────────────\n\nasync function mutate(\n opts: ModifyOptions,\n apply: YmlMutator,\n): Promise<ModifyResult> {\n if (!REGEX.solutionName.test(opts.name)) {\n throw new Error(\n `Invalid container name: ${JSON.stringify(opts.name)}. Use letters, digits, '.', '_' or '-'.`,\n );\n }\n const home = opts.monocerosHome ?? defaultMonocerosHome();\n const ymlPath = containerConfigPath(opts.name, home);\n const logger = opts.logger ?? defaultLogger();\n\n let oldText: string;\n try {\n oldText = await fs.readFile(ymlPath, 'utf8');\n } catch {\n throw new Error(\n `No such config: ${ymlPath}. Run \\`monoceros init <template> ${opts.name}\\` first.`,\n );\n }\n\n const parsed = parseConfig(oldText, ymlPath);\n const changed = apply(parsed.doc);\n\n if (!changed) {\n logger.info('No changes — yml is already in the desired state.');\n return { status: 'no-change' };\n }\n\n // Centralised post-mutation comment fixup. yaml-lib's parser\n // sometimes attaches a column-0 comment block that visually belongs\n // to the NEXT top-level pair (e.g. the `# Container ports exposed…`\n // header above `routing:`) to the previous pair's deepest trailing\n // node instead. On re-emit via the AST, the comment then drifts\n // into the previous section. We run the relocator once here so\n // every add-*/remove-* mutator gets the fix for free — without it,\n // a sequence like `init` → `add-feature` rearranges the routing /\n // repos section headers into the features block above.\n relocateLeakedSectionComments(parsed.doc);\n\n // Re-validate via a round-trip so schema violations introduced by\n // the mutation surface here with the regular field-path error, not\n // later at apply time.\n const newText = stringifyConfig(parsed.doc);\n parseConfig(newText, ymlPath);\n\n const out = opts.output ?? ((line) => process.stdout.write(line + '\\n'));\n out(createPatch(ymlPath, oldText, newText, 'before', 'after'));\n\n if (!opts.yes) {\n const confirm = opts.confirm ?? defaultConfirm;\n const ok = await confirm('Apply these changes to the yml?');\n if (!ok) {\n logger.warn('Aborted by user. The yml was not modified.');\n return { status: 'aborted' };\n }\n }\n\n await fs.writeFile(ymlPath, newText, 'utf8');\n logger.success(`Updated ${ymlPath}.`);\n logger.info(\n `Run \\`monoceros apply ${opts.name}\\` to rebuild the dev-container and pick up the change.`,\n );\n return { status: 'updated', changedPaths: [ymlPath] };\n}\n\nfunction defaultLogger(): ModifyLogger {\n return {\n info: (m) => consola.info(m),\n success: (m) => consola.success(m),\n warn: (m) => consola.warn(m),\n };\n}\n\nconst defaultConfirm: ConfirmFn = async (message) => {\n const result = await consola.prompt(message, {\n type: 'confirm',\n initial: false,\n });\n return result === true;\n};\n\n/**\n * State-driven sync between the yml's `ports:` and Traefik's\n * dynamic-config directory + proxy lifecycle. Called from\n * `runAddPort` / `runRemovePort` after a successful yml change.\n *\n * - ports non-empty → write `<home>/traefik/dynamic/<name>.yml`\n * and call `ensureProxy()` (idempotent — no-op when Traefik is\n * already up).\n * - ports empty → remove the file and call `maybeStopProxy()`\n * (no-op when other containers still depend on the proxy).\n *\n * Any proxy or filesystem failure is surfaced as a warn but never\n * rolls back the yml write. The yml is the source of truth; proxy\n * state is derived and self-healing on the next apply/start.\n */\nasync function syncPortsToProxy(\n input: AddPortInput | RemovePortInput,\n): Promise<void> {\n const home = input.monocerosHome ?? defaultMonocerosHome();\n const ymlPath = containerConfigPath(input.name, home);\n const logger = input.logger ?? defaultLogger();\n\n let allPorts: number[];\n try {\n const parsed = await readConfig(ymlPath);\n allPorts = (parsed.config.routing?.ports ?? []).map(portNumber);\n } catch (err) {\n logger.warn(\n `Could not re-read yml after edit to sync Traefik routes: ${err instanceof Error ? err.message : String(err)}. The yml is correct; \\`monoceros apply ${input.name}\\` will rebuild the routes.`,\n );\n return;\n }\n\n // Effective host port for the Traefik singleton — falls back to 80\n // when monoceros-config.yml has no `routing.hostPort`. Read once per\n // sync so we have the right value for both ensureProxy and the URLs\n // we print back.\n let hostPort = 80;\n try {\n const globalConfig = await readMonocerosConfig({ monocerosHome: home });\n hostPort = proxyHostPort(globalConfig);\n } catch {\n // Bad monoceros-config.yml is the user's problem to fix; don't\n // strand the sync over it. Default 80 is the right fallback.\n }\n\n // Pre-flight outside the warn-only try/catch: a held host port is\n // a hard-fail (the route would never come up otherwise), and the\n // builder needs the actionable message verbatim. The yml is\n // already updated at this point — that's fine, it's the source of\n // truth and the next apply heals once the conflict is resolved.\n if (allPorts.length > 0) {\n await preflightHostPort(hostPort, {\n ...(input.proxyDocker ? { docker: input.proxyDocker } : {}),\n });\n }\n\n try {\n if (allPorts.length > 0) {\n await writeDynamicConfig(input.name, allPorts, { monocerosHome: home });\n await ensureProxy({\n monocerosHome: home,\n hostPort,\n ...(input.proxyDocker ? { docker: input.proxyDocker } : {}),\n logger: { info: (m) => logger.info(m), warn: (m) => logger.warn(m) },\n });\n const urls = proxyUrlsFor(input.name, allPorts, hostPort);\n const lines = urls.map((u) => {\n const tag = u.isDefault ? ' (default)' : '';\n return ` ${u.url}${tag}`;\n });\n logger.info(`Traefik routes refreshed:\\n${lines.join('\\n')}`);\n } else {\n await removeDynamicConfig(input.name, { monocerosHome: home });\n await maybeStopProxy({\n monocerosHome: home,\n ...(input.proxyDocker ? { docker: input.proxyDocker } : {}),\n logger: { info: (m) => logger.info(m), warn: (m) => logger.warn(m) },\n });\n }\n } catch (err) {\n logger.warn(\n `Could not sync Traefik routes after yml edit: ${err instanceof Error ? err.message : String(err)}. The yml is correct; \\`monoceros apply ${input.name}\\` will rebuild the routes.`,\n );\n }\n}\n","import { promises as fs } from 'node:fs';\nimport { Document, parseDocument } from 'yaml';\nimport { type SolutionConfig, validateConfig } from './schema.js';\n\n/**\n * A parsed solution-config yml plus its AST. `config` is the validated\n * plain-JS view (used to drive the apply pipeline); `doc` is the\n * `yaml.Document` (used by mutation helpers so comments and ordering\n * survive a round-trip).\n */\nexport interface ParsedConfig {\n config: SolutionConfig;\n doc: Document.Parsed;\n /** Source path or `<inline>` for an in-memory parse. Used in errors. */\n source: string;\n}\n\n/**\n * Parse a yml string and validate against the schema. Throws on\n * yaml syntax errors and on schema violations. The returned `doc`\n * preserves comments and node ordering — pass it to mutation helpers\n * (`addRepoToDoc`, …) and `stringifyConfig` so the builder's hand-\n * written comments survive `monoceros add-*`.\n */\nexport function parseConfig(\n yamlText: string,\n source = '<inline>',\n): ParsedConfig {\n const doc = parseDocument(yamlText, { prettyErrors: true });\n if (doc.errors.length > 0) {\n const first = doc.errors[0]!;\n throw new Error(`yaml parse error in ${source}: ${first.message}`);\n }\n const config = validateConfig(doc.toJS());\n return { config, doc, source };\n}\n\nexport async function readConfig(filePath: string): Promise<ParsedConfig> {\n const text = await fs.readFile(filePath, 'utf8');\n return parseConfig(text, filePath);\n}\n\n/** Serialize a Document back to yaml. */\nexport function stringifyConfig(doc: Document): string {\n return String(doc);\n}\n\nexport async function writeConfig(\n filePath: string,\n doc: Document,\n): Promise<void> {\n await fs.writeFile(filePath, stringifyConfig(doc), 'utf8');\n}\n\n/**\n * Build a fresh Document from a plain-JS config object. Used when\n * generating a yml from a template (no source comments to preserve)\n * or when migrating an existing stack.json. The resulting Document\n * is suitable for further mutation + `stringifyConfig`.\n *\n * The output is stable: keys appear in the canonical order defined\n * by `KEY_ORDER` below, so two configs with the same content yield\n * byte-identical yaml.\n */\nexport function createDoc(config: SolutionConfig): Document {\n const ordered: Record<string, unknown> = {};\n for (const key of KEY_ORDER) {\n if (key in config) {\n const value = (config as unknown as Record<string, unknown>)[key];\n if (isEmptyContainer(value)) continue;\n ordered[key] = value;\n }\n }\n const doc = new Document(ordered);\n return doc;\n}\n\n/**\n * Canonical key order in generated yaml. Matches the example skeleton\n * in `docs/backlog.md`. Hand-edited yml does not have to follow this\n * order (parser accepts any) — but anything `createDoc` writes does.\n */\nconst KEY_ORDER = [\n 'schemaVersion',\n 'name',\n 'languages',\n 'aptPackages',\n 'features',\n 'installUrls',\n 'services',\n 'repos',\n 'routing',\n 'externalServices',\n 'git',\n] as const;\n\nfunction isEmptyContainer(value: unknown): boolean {\n if (Array.isArray(value)) return value.length === 0;\n if (value && typeof value === 'object') {\n return Object.keys(value as Record<string, unknown>).length === 0;\n }\n return false;\n}\n","import { z } from 'zod';\n\n/**\n * Shape validation for a Monoceros solution-config yml. Catalog\n * validation (which languages/services actually exist) happens\n * separately in `apply`, against `create/catalog.ts` — that keeps the\n * schema decoupled from the catalog and lets the schema live without\n * pulling the whole devcontainer scaffold module in.\n *\n * Schema mirrors the StackFile shape from `create/types.ts` except:\n *\n * - `features` is an **array** of `{ ref, options }` entries (yml is\n * edited by humans and arrays diff/comment better than maps).\n * The apply step converts to the Record shape `devcontainer.json`\n * expects.\n *\n * - `externalServices.postgres` carries what `CreateOptions.postgresUrl`\n * does today.\n *\n * - `git.user.{name,email}` carries the host-captured identity so the\n * yml-as-profile is self-contained when shared across containers.\n * Optional — falls back to host-side `git config --global --get` +\n * `.monoceros/gitconfig` at apply time, same as today.\n */\n\nconst SOLUTION_NAME_RE = /^[A-Za-z0-9._-]+$/;\nconst APT_PACKAGE_NAME_RE = /^[a-z0-9][a-z0-9.+-]*$/;\n// Feature refs are OCI-style:\n// <registry>/<namespace>/<feature>:<tag>\n// e.g. ghcr.io/devcontainers/features/python:1\n// ghcr.io/getmonoceros/monoceros-features/claude-code:1\nconst FEATURE_REF_RE = /^[a-z0-9.-]+(\\/[a-z0-9._-]+)+:[a-z0-9._-]+$/;\nconst INSTALL_URL_RE = /^https:\\/\\/[A-Za-z0-9.\\-_~/:?#[\\]@!&'()*+,;=%]+$/;\n// Repo URLs are HTTPS-only by design. SSH-style URLs (git@host:...,\n// ssh://...) are explicitly out of scope — see ADR 0006 for the\n// reasoning. The schema rejects them at parse time with a clear\n// message rather than letting them through and failing opaquely\n// during the clone in post-create.sh.\nconst REPO_URL_RE = /^https:\\/\\/[A-Za-z0-9@:/+_~.#=&?-]+$/;\n// Path under `projects/`. Allows nested subfolders via `/` (e.g.\n// `apps/web`, `monorepo/libs/shared`). The regex enforces:\n// - non-empty\n// - segments use [A-Za-z0-9._-] (same charset as a leaf folder name)\n// - no leading `/`, no trailing `/`, no consecutive `//`\n// A separate refine rejects `.` / `..` segments — those would either\n// be no-ops or escape `projects/`, neither belongs in a checked-in\n// container yml.\nconst REPO_PATH_RE = /^[A-Za-z0-9._-]+(\\/[A-Za-z0-9._-]+)*$/;\nconst POSTGRES_URL_RE = /^postgres(ql)?:\\/\\//;\n\nexport const REGEX = {\n solutionName: SOLUTION_NAME_RE,\n aptPackage: APT_PACKAGE_NAME_RE,\n featureRef: FEATURE_REF_RE,\n installUrl: INSTALL_URL_RE,\n repoUrl: REPO_URL_RE,\n repoPath: REPO_PATH_RE,\n postgresUrl: POSTGRES_URL_RE,\n};\n\n/**\n * The providers Monoceros knows how to render setup hints for.\n *\n * Canonical SaaS hostnames (`github.com` / `gitlab.com` /\n * `bitbucket.org`) auto-detect to their provider. Everything else\n * — self-hosted GitLab, GitHub Enterprise, Bitbucket Data Center,\n * Gitea / Forgejo — must declare `provider:` explicitly. Gitea has\n * no canonical SaaS host (gitea.com is a demo, not a SaaS), so any\n * `provider: gitea` entry is by definition self-hosted.\n *\n * Forgejo (the community fork of Gitea) shares Gitea's API, UI, and\n * auth flow — we bundle it under `provider: gitea` rather than\n * carrying a separate enum value.\n */\nexport const PROVIDER_VALUES = [\n 'github',\n 'gitlab',\n 'bitbucket',\n 'gitea',\n] as const;\nexport type RepoProvider = (typeof PROVIDER_VALUES)[number];\n\n/**\n * Hostnames whose provider is implicit — no `provider:` field needed\n * in the yml. Everything else (self-hosted GitLab on `git.firma.de`,\n * Gitea instances, …) requires an explicit declaration; the apply\n * pre-flight enforces that.\n */\nexport const KNOWN_PROVIDER_HOSTS: Readonly<Record<string, RepoProvider>> = {\n 'github.com': 'github',\n 'gitlab.com': 'gitlab',\n 'bitbucket.org': 'bitbucket',\n};\n\n/** Current schema version. Bumped only on breaking yml changes. */\nexport const CONFIG_SCHEMA_VERSION = 1 as const;\n\n// Feature option values are string | number | boolean. We also\n// accept `null` (yaml's parse result for a bare `key:`) and transform\n// it to the empty string — that way an init-rendered options block\n// like `apiKey:` parses cleanly and ends up equivalent to the feature\n// option's documented default (which for string fields is `\"\"`).\nexport const FeatureOptionValueSchema = z\n .union([z.string(), z.number(), z.boolean(), z.null()])\n .transform((v) => (v === null ? '' : v));\n\nexport const FeatureEntrySchema = z.object({\n ref: z\n .string()\n .regex(\n FEATURE_REF_RE,\n \"Invalid feature ref. Expected an OCI-image-style ref like 'ghcr.io/devcontainers/features/<name>:<tag>'.\",\n ),\n options: z.record(z.string(), FeatureOptionValueSchema).optional(),\n});\n\n/**\n * Git identity block.\n *\n * Both fields are nullable + accept the empty string. yaml allows\n * a bare `name:` (parsed as null) as a placeholder for \"not set\n * yet — apply, please ask me\". The resolution logic treats null /\n * empty as \"unset\" and walks the precedence chain further.\n *\n * The schema validates only the SHAPE (non-empty string). The email\n * FORMAT is deliberately NOT checked here: a value may be a `${VAR}`\n * placeholder resolved from `<name>.env` at apply time, and you can't\n * validate the format of a value you haven't resolved yet. The format\n * check moves to apply, after interpolation (see `isValidEmail` +\n * apply/index.ts). The flag entry points (`add-repo --git-email`)\n * validate eagerly there instead.\n */\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\n/** Whether a (resolved) string is a well-formed email address. */\nexport function isValidEmail(value: string): boolean {\n return EMAIL_RE.test(value);\n}\n\nexport const GitUserSchema = z.object({\n name: z\n .union([z.literal(''), z.null(), z.string().min(1)])\n .nullish()\n .transform((v) => (typeof v === 'string' && v.length > 0 ? v : undefined)),\n email: z\n .union([z.literal(''), z.null(), z.string().min(1)])\n .nullish()\n .transform((v) => (typeof v === 'string' && v.length > 0 ? v : undefined)),\n});\n\nexport const RepoEntrySchema = z.object({\n url: z\n .string()\n .regex(\n REPO_URL_RE,\n 'Invalid repo URL. Only HTTPS URLs are supported (https://...). SSH-style URLs (git@host:..., ssh://...) are not supported.',\n ),\n path: z\n .string()\n .regex(\n REPO_PATH_RE,\n \"Invalid repo path. Use letters/digits/'._-', forward slashes for nested folders, no leading or trailing slash.\",\n )\n .refine(\n (p) => !p.split('/').some((seg) => seg === '..' || seg === '.'),\n 'Repo path segments cannot be \".\" or \"..\".',\n )\n .optional(),\n // Per-repo git identity override. Falls back to the container-level\n // `git.user` (which itself falls back to the host's\n // `git config --global` at apply time). Useful when a single\n // container clones multiple repos that need different committer\n // identities — e.g. work GitHub org vs personal projects.\n git: z\n .object({\n user: GitUserSchema.optional(),\n })\n .optional(),\n // Provider hint for the pre-flight credential check. For the three\n // canonical hosts (github.com / gitlab.com / bitbucket.org) the\n // provider is auto-detected and this field is unnecessary. For any\n // other host (self-hosted GitLab on a custom domain, Gitea, …) the\n // builder MUST declare the provider so apply can suggest the right\n // CLI setup (`glab auth login --hostname <host>` etc.) when\n // credentials are missing. Enforced at apply pre-flight, not at\n // parse time — see ADR 0006.\n provider: z.enum(PROVIDER_VALUES).optional(),\n});\n\n/**\n * A single entry under `ports:`. Two forms accepted:\n *\n * ports:\n * - 3000 # short form: just the port number\n * - port: 9229 # long form, leaves room for future fields\n * # (protocol, path-prefix, entrypoint …)\n *\n * Today both forms carry the same information. The long form exists\n * so additive extensions (TLS entrypoint, path-based routing) don't\n * require a schema break. See ADR 0007.\n */\nexport const PortEntrySchema = z.union([\n z\n .number()\n .int()\n .min(1, 'Port must be ≥ 1.')\n .max(65535, 'Port must be ≤ 65535.'),\n z.object({\n port: z\n .number()\n .int()\n .min(1, 'Port must be ≥ 1.')\n .max(65535, 'Port must be ≤ 65535.'),\n }),\n]);\n\n/**\n * Routing block — everything Monoceros uses to expose container ports\n * to the host through the shared Traefik singleton.\n *\n * - `ports`: container-internal ports the builder wants reachable\n * via `<name>.localhost` / `<name>-<port>.localhost`. Short form\n * `3000` or long form `{ port: 3000 }` are both accepted; mutators\n * write the short form by default. First port doubles as the\n * default route under the bare `<name>.localhost`.\n *\n * - `vscodeAutoForward`: whether VS Code's Dev-Containers extension\n * should also auto-forward ports on top of Traefik. Default\n * `false`. Set to `true` only if VS Code's port panel should be\n * the primary entry rather than `<name>.localhost`.\n *\n * Host-port for the Traefik singleton itself is global (one Traefik\n * per machine), not per container — it lives in `monoceros-config.yml`\n * under `routing.hostPort`. See ADR 0007.\n */\nexport const RoutingSchema = z.object({\n ports: z.array(PortEntrySchema).default([]),\n vscodeAutoForward: z.boolean().optional(),\n});\n\n// ── Services ────────────────────────────────────────────────────────\n//\n// A `services:` entry is always an object: `image` is required; env,\n// volumes, port, healthcheck, restart and command are opt-in.\n//\n// There is deliberately ONE form. The curated catalog (postgres / mysql\n// / redis) is init-sugar: `monoceros init --with-services=postgres` and\n// `monoceros add-service <name> postgres` EXPAND the catalog name into a\n// full, editable object block. The bare-string form is NOT accepted in\n// the yml — a single shape keeps the model and the docs unambiguous.\n// See docs/backlog.md (init + service redesign).\n\n// Compose service name: becomes the docker compose service key, the\n// in-network DNS alias other services dial it by, and the per-service\n// data dir name under container/<name>/data/<svc>/.\nconst SERVICE_NAME_RE = /^[a-z0-9][a-z0-9_-]*$/;\n\n// env values: yaml may parse a value as string/number/bool, or a bare\n// `KEY:` as null. Coerce everything to a string for compose output;\n// null → \"\" (an explicitly empty env var). `${VAR}` references survive\n// verbatim here — they are resolved against `<name>.env` at apply time.\nconst ServiceEnvValueSchema = z\n .union([z.string(), z.number(), z.boolean(), z.null()])\n .transform((v) => (v === null ? '' : String(v)));\n\nexport const ServiceHealthcheckSchema = z.object({\n // Compose accepts both forms and they differ semantically:\n // - string → run via the shell (CMD-SHELL)\n // - [\"CMD\", …] → exec the args directly, no shell\n // - [\"CMD-SHELL\", …]\n // We accept either and render it back faithfully.\n test: z.union([\n z.string().min(1, 'Healthcheck test must not be empty.'),\n z\n .array(z.string().min(1))\n .min(1, 'Healthcheck test array must not be empty.'),\n ]),\n interval: z.string().optional(),\n timeout: z.string().optional(),\n retries: z.number().int().min(1).optional(),\n startPeriod: z.string().optional(),\n});\n\nexport const SERVICE_RESTART_VALUES = [\n 'no',\n 'always',\n 'on-failure',\n 'unless-stopped',\n] as const;\n\n// A volume entry is `src:dest[:mode]`.\n//\n// - `src` is either the `data` shorthand (→ the per-service\n// bind-mounted data dir under container/<name>/data/<name>/) or a\n// path relative to the container root (`projects/app/init.sql`,\n// `./config/x`). `dest` is an absolute in-container path.\n// - Docker **named volumes** (a bare token like `rustfs_data`) are NOT\n// supported — Monoceros binds to the host disk so content is part of\n// backups (ADR 0003). A bare single token is rejected with a hint to\n// use `data:` or an explicit relative path, because that's the most\n// common compose-port mistake.\n// - Absolute host sources and `..` escapes are rejected.\nfunction isValidServiceVolume(spec: string): boolean {\n const parts = spec.split(':');\n if (parts.length < 2 || parts.length > 3) return false;\n const [src, dest, mode] = parts;\n if (!src || !dest) return false;\n if (!dest.startsWith('/')) return false; // container target must be absolute\n if (mode !== undefined && !/^(ro|rw|cached|delegated|z|Z)$/.test(mode)) {\n return false;\n }\n if (src === 'data') return true;\n if (src.startsWith('/')) return false; // absolute host source\n // A bare token (no `./`, no `/`) is almost always a leftover docker\n // named volume — reject it rather than silently bind-mounting a junk\n // host dir of that name.\n const looksLikePath = src.startsWith('./') || src.includes('/');\n if (!looksLikePath) return false;\n const normalized = src.startsWith('./') ? src.slice(2) : src;\n if (normalized.split('/').some((s) => s === '..' || s === '.')) return false;\n return true;\n}\n\nexport const ServiceObjectSchema = z.object({\n name: z\n .string()\n .regex(\n SERVICE_NAME_RE,\n \"Invalid service name. Use lowercase letters, digits, '_' or '-' (must start with a letter or digit).\",\n ),\n image: z.string().min(1, 'Service image must not be empty.'),\n // In-container port the service listens on. Used by\n // `monoceros tunnel <name> <service>` to forward without an explicit\n // port argument. NOT a host port mapping — host exposure goes through\n // routing.ports (Traefik) or `monoceros tunnel`.\n port: z.number().int().min(1, 'Port must be ≥ 1.').max(65535).optional(),\n env: z.record(z.string(), ServiceEnvValueSchema).optional(),\n volumes: z\n .array(\n z\n .string()\n .refine(\n isValidServiceVolume,\n \"Invalid volume. Use 'data:/container/path' for the per-service persistent dir, or a relative host path ('projects/app/init.sql:/...:ro', './config:/...'). Docker named volumes (a bare name like 'rustfs_data') are not supported; absolute host paths and '..' are rejected.\",\n ),\n )\n .optional(),\n healthcheck: ServiceHealthcheckSchema.optional(),\n restart: z.enum(SERVICE_RESTART_VALUES).optional(),\n command: z.string().optional(),\n});\n\nexport const ExternalServicesSchema = z.object({\n postgres: z\n .string()\n .regex(\n POSTGRES_URL_RE,\n \"Postgres URL must start with 'postgres://' or 'postgresql://'\",\n )\n .optional(),\n});\n\nexport const SolutionConfigSchema = z.object({\n schemaVersion: z.literal(CONFIG_SCHEMA_VERSION),\n name: z\n .string()\n .regex(\n SOLUTION_NAME_RE,\n \"Invalid solution name. Use letters, digits, '.', '_' or '-'.\",\n ),\n languages: z.array(z.string().min(1)).default([]),\n aptPackages: z\n .array(\n z\n .string()\n .regex(\n APT_PACKAGE_NAME_RE,\n \"Invalid apt package name. Expected lowercase alphanumeric plus '.+-'.\",\n ),\n )\n .default([]),\n features: z.array(FeatureEntrySchema).default([]),\n installUrls: z\n .array(\n z\n .string()\n .regex(\n INSTALL_URL_RE,\n \"Invalid install URL. Must start with 'https://' and contain only URL-safe characters (no shell metacharacters).\",\n ),\n )\n .default([]),\n services: z.array(ServiceObjectSchema).default([]),\n repos: z.array(RepoEntrySchema).default([]),\n routing: RoutingSchema.optional(),\n externalServices: ExternalServicesSchema.default({}),\n git: z\n .object({\n user: GitUserSchema.optional(),\n })\n .optional(),\n});\n\nexport type SolutionConfig = z.infer<typeof SolutionConfigSchema>;\nexport type FeatureEntry = z.infer<typeof FeatureEntrySchema>;\nexport type ServiceObject = z.infer<typeof ServiceObjectSchema>;\nexport type ServiceHealthcheck = z.infer<typeof ServiceHealthcheckSchema>;\nexport type RepoEntry = z.infer<typeof RepoEntrySchema>;\nexport type GitUser = z.infer<typeof GitUserSchema>;\nexport type ExternalServices = z.infer<typeof ExternalServicesSchema>;\nexport type PortEntry = z.infer<typeof PortEntrySchema>;\nexport type Routing = z.infer<typeof RoutingSchema>;\n\n/** Resolve a `PortEntry` (short or long form) to a plain port number. */\nexport function portNumber(entry: PortEntry): number {\n return typeof entry === 'number' ? entry : entry.port;\n}\n\n/**\n * Validate parsed yml (e.g. from `doc.toJS()`) against the schema. On\n * failure, throws an Error whose message lists every issue with its\n * dotted path — the apply step prints that verbatim, so the builder\n * sees exactly which yml field is wrong.\n */\nexport function validateConfig(input: unknown): SolutionConfig {\n const result = SolutionConfigSchema.safeParse(input);\n if (!result.success) {\n const issues = result.error.issues\n .map((issue) => {\n const where = issue.path.length > 0 ? issue.path.join('.') : '(root)';\n return ` - ${where}: ${issue.message}`;\n })\n .join('\\n');\n throw new Error(`Invalid solution config:\\n${issues}`);\n }\n return result.data;\n}\n","import { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport { fileURLToPath } from 'node:url';\n\n/**\n * Path helpers for the M2.5 Phase 3 yml-profile model.\n *\n * Two distinct roots:\n *\n * - `workbenchRoot()` — where the **CLI bundle** lives. In dev that's\n * the monoceros-workbench checkout (so `templates/yml/` is reachable\n * and the workbench can be bind-mounted into generated containers\n * as `/opt/monoceros-workbench`). In prod (post-M4) this is the\n * installed package directory.\n *\n * - `monocerosHome()` — where **user data** lives: container-configs,\n * materialized containers, the global `monoceros-config.yml`.\n *\n * The two used to be conflated under one `workbenchRoot()`; splitting\n * them is what lets `monoceros apply <name>` resolve a fixed\n * `<MONOCEROS_HOME>/container/<name>/` location without any cwd\n * magic, while the CLI itself still knows where its bundled templates\n * live.\n *\n * Layout under `<MONOCEROS_HOME>/`:\n * container-configs/<name>.yml ← yml-Profile (`monoceros init`)\n * container/<name>/ ← materialized dev-containers\n * monoceros-config.yml ← optional, user-edited defaults\n * monoceros-config.sample.yml ← marker (in dev) + template (in prod)\n */\n\nconst MONOCEROS_HOME_MARKER = 'monoceros-config.sample.yml';\nconst WORKBENCH_MARKER = path.join('templates', 'components', 'README.md');\nconst CHECKOUT_MARKER = 'pnpm-workspace.yaml';\n\nlet cachedWorkbenchRoot: string | null = null;\nlet cachedMonocerosHome: string | null = null;\nlet cachedCheckoutRoot: string | null | undefined = undefined;\n\n/**\n * Walk upwards from this module until we find the workbench checkout's\n * marker (`templates/components/README.md`). In dev that hits the\n * workbench root reliably; in production the file does not exist\n * outside the shipped CLI package, so callers that need a workbench\n * root for dev-only purposes (bind-mounting `/opt/monoceros-workbench`)\n * get a clear error.\n */\nexport function workbenchRoot(): string {\n if (cachedWorkbenchRoot) return cachedWorkbenchRoot;\n let dir = path.dirname(fileURLToPath(import.meta.url));\n while (true) {\n if (existsSync(path.join(dir, WORKBENCH_MARKER))) {\n cachedWorkbenchRoot = dir;\n return dir;\n }\n const parent = path.dirname(dir);\n if (parent === dir) {\n throw new Error(\n `Could not locate the monoceros workbench checkout (no ${WORKBENCH_MARKER} found by walking up). Run the CLI from a workbench checkout.`,\n );\n }\n dir = parent;\n }\n}\n\n/**\n * Resolve `MONOCEROS_HOME` (where user data lives):\n *\n * 1. Honor the `MONOCEROS_HOME` env-var if set.\n * 2. Walk upwards from this module and accept the first\n * `<dir>/.local/monoceros-config.sample.yml` we find; the\n * containing `<dir>/.local` is treated as the home. This is the\n * dev-workbench detection path.\n * 3. Fall back to `~/.monoceros`.\n *\n * Caches the result for the lifetime of the process — flip `force` to\n * recompute (tests do this between cases).\n */\nexport function monocerosHome(opts: { force?: boolean } = {}): string {\n if (!opts.force && cachedMonocerosHome) return cachedMonocerosHome;\n\n const fromEnv = process.env.MONOCEROS_HOME;\n if (fromEnv && fromEnv.length > 0) {\n cachedMonocerosHome = path.resolve(fromEnv);\n return cachedMonocerosHome;\n }\n\n let dir = path.dirname(fileURLToPath(import.meta.url));\n while (true) {\n const candidate = path.join(dir, '.local');\n if (existsSync(path.join(candidate, MONOCEROS_HOME_MARKER))) {\n cachedMonocerosHome = candidate;\n return candidate;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n\n cachedMonocerosHome = path.join(os.homedir(), '.monoceros');\n return cachedMonocerosHome;\n}\n\n/**\n * Walk upwards from this module to find the workbench checkout root,\n * marked by `pnpm-workspace.yaml`. Distinct from `workbenchRoot()`:\n *\n * - `workbenchRoot()` returns where the **CLI bundle** lives —\n * `packages/cli/` in dev, the installed package directory in prod.\n * - `workbenchCheckoutRoot()` returns where the **full workbench\n * checkout** lives. Only meaningful in dev; returns `null` in\n * prod (the marker doesn't ship with the npm package).\n *\n * Used by features like the dev-only local-source-fallback in\n * `resolveFeatures`, where we want to look at `images/features/<name>/`\n * at the checkout root — not inside the CLI package, where that\n * directory deliberately doesn't exist.\n */\nexport function workbenchCheckoutRoot(): string | null {\n if (cachedCheckoutRoot !== undefined) return cachedCheckoutRoot;\n let dir = path.dirname(fileURLToPath(import.meta.url));\n while (true) {\n if (existsSync(path.join(dir, CHECKOUT_MARKER))) {\n cachedCheckoutRoot = dir;\n return dir;\n }\n const parent = path.dirname(dir);\n if (parent === dir) {\n cachedCheckoutRoot = null;\n return null;\n }\n dir = parent;\n }\n}\n\n/** Reset cached lookups. Test-only. */\nexport function _resetPathCachesForTests(): void {\n cachedWorkbenchRoot = null;\n cachedMonocerosHome = null;\n cachedCheckoutRoot = undefined;\n}\n\n// ─── CLI-bundle paths (templates) ─────────────────────────────────\n\n/**\n * `templates/components/` — the components catalog used by\n * `monoceros init`. Each file under this directory is a small yml\n * snippet describing one composable component (a language, a\n * service, or a feature). See `templates/components/README.md`.\n */\nexport function componentsDir(root: string = workbenchRoot()): string {\n return path.join(root, 'templates', 'components');\n}\n\n/**\n * `features/` (inside the CLI bundle) — npm-shipped copies of the\n * Monoceros feature manifests (`devcontainer-feature.json`). Built\n * by `pnpm manifests:sync` from `images/features/<name>/` and\n * included in the published tarball via the `files` field. The\n * init generator's hint loader looks here as the production\n * fallback when the workbench checkout isn't available.\n */\nexport function bundledFeaturesDir(root: string = workbenchRoot()): string {\n return path.join(root, 'features');\n}\n\n// ─── User-home paths (configs, containers, global config) ────────\n\nexport function containerConfigsDir(home: string = monocerosHome()): string {\n return path.join(home, 'container-configs');\n}\n\nexport function containerConfigPath(\n name: string,\n home: string = monocerosHome(),\n): string {\n return path.join(containerConfigsDir(home), `${name}.yml`);\n}\n\n/**\n * Per-container env file holding values for `${VAR}` references in the\n * yml (service secrets etc.). Lives beside `<name>.yml`, gitignored.\n */\nexport function containerEnvPath(\n name: string,\n home: string = monocerosHome(),\n): string {\n return path.join(containerConfigsDir(home), `${name}.env`);\n}\n\nexport function containersDir(home: string = monocerosHome()): string {\n return path.join(home, 'container');\n}\n\nexport function containerDir(\n name: string,\n home: string = monocerosHome(),\n): string {\n return path.join(containersDir(home), name);\n}\n\nexport function monocerosConfigPath(home: string = monocerosHome()): string {\n return path.join(home, 'monoceros-config.yml');\n}\n\n// ─── User-facing path formatting ─────────────────────────────────\n\n/**\n * Format an absolute path for printing in CLI output: collapses a\n * `$HOME` prefix to `~` so messages stay short without losing\n * information. Non-home paths pass through verbatim.\n *\n * prettyPath('/Users/x/.monoceros/container-configs/hello.yml')\n * → '~/.monoceros/container-configs/hello.yml'\n *\n * Use this whenever a log line tells the user where something\n * landed on disk — `monoceros init`, `apply`, `remove`, `restore`\n * all rely on it so users see one consistent format.\n */\nexport function prettyPath(p: string): string {\n const home = os.homedir();\n if (!home) return p;\n if (p === home) return '~';\n const prefix = home.endsWith(path.sep) ? home : home + path.sep;\n if (p.startsWith(prefix)) {\n return '~' + path.sep + p.slice(prefix.length);\n }\n return p;\n}\n","import { existsSync, readFileSync, promises as fsp } from 'node:fs';\nimport path from 'node:path';\nimport type { ResolvedService } from '../create/types.js';\n\n/**\n * Per-container secret/value source. Lives beside the yml profile as\n * `container-configs/<name>.env`, gitignored, and supplies the values\n * for `${VAR}` references in the yml (today: service env values and\n * service commands). Keeping secrets out of the yml — which is meant to\n * be shareable/committable — is the whole point; the threat model is\n * \"don't commit credentials to git\", not \"no plaintext on disk\".\n *\n * See docs/backlog.md (init + service redesign) for the design and the\n * parked `cmd:`-resolver follow-up.\n */\n\n// KEY=VALUE, optional leading `export`, `#` comments, surrounding\n// single/double quotes stripped. Intentionally minimal — this is a\n// dev-time value file, not a full dotenv grammar (no multi-line values,\n// no `${}` expansion *within* the env file itself).\nconst ENV_LINE_RE = /^\\s*(?:export\\s+)?([A-Za-z_][A-Za-z0-9_]*)\\s*=(.*)$/;\n\nexport function parseEnvFile(content: string): Record<string, string> {\n const out: Record<string, string> = {};\n for (const raw of content.split(/\\r?\\n/)) {\n const trimmed = raw.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const m = ENV_LINE_RE.exec(raw);\n if (!m) continue;\n const key = m[1]!;\n let val = m[2]!.trim();\n if (\n val.length >= 2 &&\n ((val.startsWith('\"') && val.endsWith('\"')) ||\n (val.startsWith(\"'\") && val.endsWith(\"'\")))\n ) {\n val = val.slice(1, -1);\n }\n out[key] = val;\n }\n return out;\n}\n\n/** Read + parse `<name>.env`. Returns `{}` when the file is absent. */\nexport function readEnvFile(envPath: string): Record<string, string> {\n if (!existsSync(envPath)) return {};\n return parseEnvFile(readFileSync(envPath, 'utf8'));\n}\n\n/**\n * Ensure `<container-configs>/.gitignore` excludes `*.env`. A builder\n * may version-control their MONOCEROS_HOME / container-configs to share\n * yml profiles across machines (CLAUDE.md: \"Synchronisation ist eine\n * Frage von git-Repos\"); the per-container env files carry the secrets\n * those yml's reference and must never ride along. Idempotent: leaves an\n * existing pattern + any builder-added rules untouched.\n */\nexport async function ensureEnvGitignored(configsDir: string): Promise<void> {\n const gitignorePath = path.join(configsDir, '.gitignore');\n const pattern = '*.env';\n let existing = '';\n if (existsSync(gitignorePath)) {\n existing = readFileSync(gitignorePath, 'utf8');\n const lines = existing.split(/\\r?\\n/).map((l) => l.trim());\n if (lines.includes(pattern)) return;\n }\n const prefix = existing.length > 0 && !existing.endsWith('\\n') ? '\\n' : '';\n const header =\n existing.length === 0\n ? '# Per-container env files hold the secrets behind the yml ${VAR}\\n# references. Never commit them.\\n'\n : '';\n await fsp.appendFile(gitignorePath, `${prefix}${header}${pattern}\\n`);\n}\n\n// `${VAR}` only — the explicit-brace form. A bare `$VAR` is left alone\n// so a literal env value that happens to contain `$` (a generated\n// password, a shell snippet in `command`) survives untouched.\nconst VAR_RE = /\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g;\n\nexport interface InterpolateResult {\n value: string;\n /** Names referenced by the input that were absent from `vars`. */\n missing: string[];\n}\n\nexport function interpolate(\n value: string,\n vars: Record<string, string>,\n): InterpolateResult {\n const missing: string[] = [];\n const out = value.replace(VAR_RE, (_match, name: string) => {\n if (Object.prototype.hasOwnProperty.call(vars, name)) return vars[name]!;\n missing.push(name);\n return _match; // leave the literal `${VAR}` so the failure is visible\n });\n return { value: out, missing };\n}\n\nexport interface MissingVar {\n /** Dotted path to the field, e.g. `services.postgres.env.POSTGRES_PASSWORD`\n * or `features.ghcr.io/…:1.apiKey`. */\n location: string;\n name: string;\n}\n\nexport interface InterpolateServicesResult {\n services: ResolvedService[];\n missing: MissingVar[];\n}\n\n/**\n * Substitute `${VAR}` across every string field of each service —\n * image, env values, volume specs, command and the healthcheck test /\n * timing strings. Collects every unresolved reference (across all\n * services and fields) so the caller can fail the apply once with a\n * complete list rather than one var at a time.\n */\nexport function interpolateServices(\n services: ResolvedService[],\n vars: Record<string, string>,\n): InterpolateServicesResult {\n const missing: MissingVar[] = [];\n const resolved = services.map((svc) => {\n const interp = (raw: string, field: string): string => {\n const r = interpolate(raw, vars);\n for (const name of r.missing) {\n missing.push({ location: `services.${svc.name}.${field}`, name });\n }\n return r.value;\n };\n\n const next: ResolvedService = {\n ...svc,\n image: interp(svc.image, 'image'),\n env: Object.fromEntries(\n Object.entries(svc.env).map(([k, v]) => [k, interp(v, `env.${k}`)]),\n ),\n volumes: svc.volumes.map((v, i) => interp(v, `volumes[${i}]`)),\n };\n if (svc.command !== undefined) {\n next.command = interp(svc.command, 'command');\n }\n if (svc.healthcheck) {\n const hc = svc.healthcheck;\n next.healthcheck = {\n ...hc,\n test: Array.isArray(hc.test)\n ? hc.test.map((t, i) => interp(t, `healthcheck.test[${i}]`))\n : interp(hc.test, 'healthcheck.test'),\n ...(hc.interval !== undefined\n ? { interval: interp(hc.interval, 'healthcheck.interval') }\n : {}),\n ...(hc.timeout !== undefined\n ? { timeout: interp(hc.timeout, 'healthcheck.timeout') }\n : {}),\n ...(hc.startPeriod !== undefined\n ? { startPeriod: interp(hc.startPeriod, 'healthcheck.startPeriod') }\n : {}),\n };\n }\n return next;\n });\n return { services: resolved, missing };\n}\n\n/**\n * Env var names for the scaffolded container git-identity placeholders.\n * Single source for: the `${VAR}` rendered into the yml (init generator /\n * add-repo) and the keys seeded blank into `<name>.env`. Resolved at\n * apply time; blank → the identity cascade fills it.\n */\nexport const GIT_IDENTITY_VAR = {\n name: 'GIT_USER_NAME',\n email: 'GIT_USER_EMAIL',\n} as const;\n\n/** True if the string contains at least one `${VAR}` reference. */\nexport function hasVarPlaceholder(value: string): boolean {\n // Local non-global regex — VAR_RE carries the `g` flag and `.test`\n // on it is stateful (lastIndex), which would make repeated calls flaky.\n return /\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}/.test(value);\n}\n\nexport interface ResolvedGitUserField {\n /**\n * The usable, non-empty resolved value, or undefined when the field\n * has NO usable value — meaning the caller should climb the identity\n * cascade. \"No usable value\" collapses three cases that are all\n * equivalent for git identity:\n * - the field was absent,\n * - it referenced a `${VAR}` missing from the env (`${X}` survives), or\n * - it resolved to empty / whitespace (a seeded-but-blank `X=` in\n * `<name>.env`).\n * This mirrors how the schema already treats an empty *literal* as\n * \"unset\" — an empty `${VAR}` value must behave the same.\n */\n value?: string;\n}\n\nexport interface ResolvedGitUser {\n name: ResolvedGitUserField;\n email: ResolvedGitUserField;\n}\n\n/**\n * Resolve `${VAR}` in a git identity's name + email against the env\n * file, per field. Unlike services/features, an unresolved/empty value\n * is NOT an error: the caller treats a missing `value` as \"climb the\n * cascade\" (monoceros-config defaults → host → prompt). The email\n * FORMAT of a present value is the caller's check (see `isValidEmail`).\n */\nexport function resolveGitUserFields(\n user: { name?: string; email?: string },\n vars: Record<string, string>,\n): ResolvedGitUser {\n const resolve = (raw: string | undefined): ResolvedGitUserField => {\n if (raw === undefined) return {};\n const r = interpolate(raw, vars);\n // A missing var leaves the literal `${...}` in the value; treat that\n // and an empty/whitespace resolution alike — no usable value.\n if (r.missing.length > 0) return {};\n const trimmed = r.value.trim();\n return trimmed.length > 0 ? { value: trimmed } : {};\n };\n return { name: resolve(user.name), email: resolve(user.email) };\n}\n\nexport interface InterpolateFeaturesResult {\n features: Record<string, Record<string, string | number | boolean>>;\n missing: MissingVar[];\n}\n\n/**\n * Substitute `${VAR}` in feature option *string* values (non-string\n * options pass through). This is what lets credentials like\n * `apiKey: ${ANTHROPIC_API_KEY}` live in `<name>.env` instead of in the\n * shareable yml. Keyed by feature ref → option map, matching the\n * `CreateOptions.features` shape.\n */\nexport function interpolateFeatures(\n features: Record<string, Record<string, string | number | boolean>>,\n vars: Record<string, string>,\n): InterpolateFeaturesResult {\n const missing: MissingVar[] = [];\n const out: Record<string, Record<string, string | number | boolean>> = {};\n for (const [ref, options] of Object.entries(features)) {\n const next: Record<string, string | number | boolean> = {};\n for (const [key, value] of Object.entries(options)) {\n if (typeof value !== 'string') {\n next[key] = value;\n continue;\n }\n const r = interpolate(value, vars);\n for (const name of r.missing) {\n missing.push({ location: `features.${ref}.${key}`, name });\n }\n next[key] = r.value;\n }\n out[ref] = next;\n }\n return { features: out, missing };\n}\n\n/**\n * Short, builder-facing header for a fresh `<name>.env`. Explains what\n * the file is for; no real keys (a freshly-generated yml has no active\n * `${VAR}` yet — curated services use literal dev-defaults).\n */\nexport function buildEnvStub(name: string): string {\n return `# Secrets and values for \\${VAR} references in ${name}.yml.\\n`;\n}\n\nexport interface EnsureEnvVarsResult {\n /** True when the env file did not exist and was created. */\n created: boolean;\n /** Var keys that were appended (absent before). */\n added: string[];\n}\n\n/**\n * Upsert `<name>.env`: create it with the header stub if absent, and\n * append a line for every requested var that isn't already present.\n * Never overwrites existing keys or values.\n *\n * `vars` takes two forms:\n * - `string[]` → seed each as `KEY=` (blank). Used by `add-feature`\n * / `init` for credential placeholders the builder must fill.\n * - `Record<KEY, default>` → seed each as `KEY=<default>`. Used for\n * curated services, which ship working dev-defaults the builder can\n * keep as-is or change in one place.\n */\nexport async function ensureEnvVars(\n envPath: string,\n name: string,\n vars: readonly string[] | Readonly<Record<string, string>>,\n): Promise<EnsureEnvVarsResult> {\n const entries: Array<[string, string]> = Array.isArray(vars)\n ? vars.map((v) => [v, ''])\n : Object.entries(vars);\n const exists = existsSync(envPath);\n let content = exists ? readFileSync(envPath, 'utf8') : buildEnvStub(name);\n const present = new Set(Object.keys(parseEnvFile(content)));\n const seen = new Set<string>();\n const toAdd = entries.filter(([k]) => {\n if (present.has(k) || seen.has(k)) return false;\n seen.add(k);\n return true;\n });\n const added = toAdd.map(([k]) => k);\n if (!exists || added.length > 0) {\n if (content.length > 0 && !content.endsWith('\\n')) content += '\\n';\n for (const [k, v] of toAdd) content += `${k}=${v}\\n`;\n await fsp.mkdir(path.dirname(envPath), { recursive: true });\n await fsp.writeFile(envPath, content);\n }\n return { created: !exists, added };\n}\n\n/**\n * Format an actionable error for unresolved `${VAR}` references — names\n * the missing vars, where they're referenced, and the env file the\n * builder should define them in.\n */\nexport function formatMissingVarsError(\n missing: MissingVar[],\n envPathPretty: string,\n): string {\n const lines = missing.map((m) => ` - \\${${m.name}} (${m.location})`);\n const uniqueNames = [...new Set(missing.map((m) => m.name))];\n return (\n `Unresolved \\${VAR} references in the container yml:\\n${lines.join('\\n')}\\n\\n` +\n `Define them in ${envPathPretty}, e.g.\\n` +\n uniqueNames.map((n) => ` ${n}=<value>`).join('\\n')\n );\n}\n","import type { FeatureManifestSummary } from './manifest.js';\n\n/**\n * Shared per-feature header builder. Both `init`'s yml generator and\n * `add-feature`'s AST mutator consume this: the generator emits each\n * line as a `# `-prefixed string in a line-array, the mutator joins\n * the lines into a yaml-lib `commentBefore` string on the new\n * feature pair.\n *\n * Returns the wrapped paragraph lines WITHOUT a `#` prefix or leading\n * space — the consumer adds whichever convention applies (`# Foo` for\n * the generator, ` Foo` for yaml-lib's stored-after-`#` form).\n *\n * Format mirrors `monoceros-config.sample.yml`'s per-feature blocks:\n * - `<Name> — <description>` (one paragraph, wrapped)\n * - `<usageNote>` (one paragraph per note, wrapped)\n * - `Options: <key> (<short-desc>), …` (wrapped)\n * - `See <documentationURL> for further information.`\n *\n * An empty / unknown manifest summary returns `[]` — the caller emits\n * just the `- ref:` line without prose. Same fallback shape as the\n * generator's documented-mode third-party path.\n */\nexport function buildFeatureHeaderLines(\n summary: FeatureManifestSummary | undefined,\n width: number,\n): string[] {\n const paragraphs = buildHeaderParagraphs(summary);\n const wrapped: string[] = [];\n for (const para of paragraphs) {\n for (const line of wrapToComment(para, width)) {\n wrapped.push(line);\n }\n }\n return wrapped;\n}\n\n/**\n * Same as `buildFeatureHeaderLines` but each line gets a leading\n * single space, matching yaml-lib's `commentBefore` storage\n * convention (which prepends `#` and prints the body verbatim). Use\n * when setting `commentBefore` on a Scalar or Pair node.\n */\nexport function buildFeatureHeaderCommentBefore(\n summary: FeatureManifestSummary | undefined,\n width: number,\n): string {\n const lines = buildFeatureHeaderLines(summary, width);\n return lines.map((l) => ` ${l}`).join('\\n');\n}\n\nfunction buildHeaderParagraphs(\n summary: FeatureManifestSummary | undefined,\n): string[] {\n if (!summary) return [];\n const out: string[] = [];\n const tagline = summary.name?.trim();\n const description = summary.description?.trim();\n if (tagline && description) {\n out.push(`${tagline} — ${description}`);\n } else if (tagline) {\n out.push(tagline);\n } else if (description) {\n out.push(description);\n }\n for (const note of summary.usageNotes) {\n const trimmed = note.trim();\n if (trimmed.length > 0) out.push(trimmed);\n }\n if (summary.optionHints.length > 0) {\n const parts = summary.optionHints.map((key) => {\n const desc = summary.optionDescriptions[key];\n const short = desc ? shortenOptionDescription(desc) : undefined;\n return short ? `${key} (${short})` : key;\n });\n out.push(`Options: ${parts.join(', ')}.`);\n }\n if (summary.documentationURL) {\n out.push(`See ${summary.documentationURL} for further information.`);\n }\n return out;\n}\n\n/**\n * Trim a per-option `description` to a parenthetical hint — first\n * sentence, trailing punctuation stripped. Length cap is intentionally\n * absent: feature manifests are expected to keep descriptions terse;\n * the wrap function downstream handles line breaks naturally.\n */\nfunction shortenOptionDescription(desc: string): string {\n const firstSentence = desc.split(/(?<=[.!?])\\s+/)[0]?.trim() ?? desc.trim();\n return firstSentence.replace(/[.!?]+$/, '').trim();\n}\n\n/**\n * Word-wrap a single paragraph of plain text to `width` columns. The\n * returned strings do NOT include any prefix — the caller is expected\n * to prepend a comment marker (`# `) and indent. Long words that\n * exceed `width` are emitted on their own line rather than split\n * mid-word.\n */\nexport function wrapToComment(text: string, width: number): string[] {\n const words = text.split(/\\s+/).filter((w) => w.length > 0);\n if (words.length === 0) return [''];\n const usable = Math.max(width, 20);\n const lines: string[] = [];\n let current = '';\n for (const w of words) {\n if (current.length === 0) {\n current = w;\n continue;\n }\n if (current.length + 1 + w.length <= usable) {\n current += ' ' + w;\n } else {\n lines.push(current);\n current = w;\n }\n }\n if (current.length > 0) lines.push(current);\n return lines;\n}\n\n/** Default comment width matching the generator's. */\nexport const FEATURE_HEADER_WIDTH = 76 - 2; // COMMENT_WIDTH - \"# \" prefix\n\n/**\n * Derive the `<name>.env` variable name for a feature option, used as\n * the `${VAR}` placeholder in the yml and the seeded key in the env\n * file. Generic rule `<FEATURE_ID>_<OPTION>`, applied uniformly:\n * atlassian:1 + apiToken → ATLASSIAN_API_TOKEN\n * claude-code:1 + apiKey → CLAUDE_CODE_API_KEY\n * github-cli:1 + bitbucketToken→ GITHUB_CLI_BITBUCKET_TOKEN\n *\n * It is a monoceros-side placeholder key — NOT the env var the tool\n * itself reads (the value is passed as the feature's option), so a\n * predictable derived name is honest and avoids per-feature special\n * cases.\n */\nexport function featureOptionVarName(ref: string, optionKey: string): string {\n const leaf = ref.split('/').pop() ?? ref;\n const id = leaf.split('@')[0]!.split(':')[0]!;\n const idSnake = id.replace(/[^A-Za-z0-9]+/g, '_').toUpperCase();\n const optSnake = optionKey\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/[^A-Za-z0-9]+/g, '_')\n .toUpperCase();\n return `${idSnake}_${optSnake}`;\n}\n\nexport interface FeatureOptionHint {\n /** Option key, e.g. `apiToken`. */\n key: string;\n /** Derived env var name, e.g. `ATLASSIAN_API_TOKEN`. */\n envVar: string;\n /** yml placeholder, e.g. `${ATLASSIAN_API_TOKEN}`. */\n placeholder: string;\n}\n\n/**\n * The credential-bearing option hints for a feature (from the manifest's\n * `x-monoceros.optionHints`), minus any keys already set with an active\n * value. Shared by the init generator (renders `${VAR}` hint lines) and\n * `add-feature` (renders the same as a node comment) and the `.env`\n * seeding (uses `envVar`). Empty for unknown/third-party refs (no\n * manifest → no hints).\n */\nexport function featureOptionHints(\n summary: FeatureManifestSummary | undefined,\n ref: string,\n activeKeys: readonly string[] = [],\n): FeatureOptionHint[] {\n return (summary?.optionHints ?? [])\n .filter((key) => !activeKeys.includes(key))\n .map((key) => {\n const envVar = featureOptionVarName(ref, key);\n return { key, envVar, placeholder: `\\${${envVar}}` };\n });\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { bundledFeaturesDir, workbenchCheckoutRoot } from '../config/paths.js';\nimport { matchMonocerosFeature } from '../util/ref.js';\n\n/**\n * Loader for the parts of a Monoceros devcontainer-feature manifest\n * that init's yml-generator wants to surface as inline guidance:\n *\n * - `name` / `description` — the feature's tagline / prose, copied\n * verbatim from the standard devcontainer-feature.json top-level\n * fields. The generator builds the header comment block from\n * these — no fallback prose lives in the generator itself.\n * - `documentationURL` — copied verbatim. The generator emits a\n * \"See <url> for further information.\" line when this is a real\n * URL. Empty / missing / literal \"tbd\" → line omitted.\n * - `optionHints` — names of feature options the generator emits\n * as commented lines under the rendered `options:` block, so the\n * builder sees what's settable without opening the docs.\n * - `optionDescriptions` — per-option `description` from the\n * manifest. The generator weaves these into the per-feature\n * \"Options: …\" summary comment.\n * - `usageNotes` — free-text per-feature paragraphs from\n * `x-monoceros.usageNotes`. Concatenated into the header prose\n * after `description`.\n *\n * Only Monoceros-owned refs\n * (`ghcr.io/getmonoceros/monoceros-features/<name>:<tag>`) are\n * resolved — for third-party features we don't have the manifest on\n * disk at init time. The fallback is \"no hints\", which is right:\n * we don't speculate about other people's feature options.\n *\n * Manifest lookup order:\n * 1. Workbench checkout — `<checkoutRoot>/images/features/<name>/`.\n * Dev edits to the source-of-truth manifest are visible\n * immediately, no rebuild step required.\n * 2. CLI bundle — `<workbenchRoot>/features/<name>/`. Populated\n * by `pnpm manifests:sync` (runs as `prebuild`), shipped in\n * the npm tarball. Production fallback for builders without a\n * workbench checkout.\n *\n * Both paths missing → `undefined` and init renders without hints.\n * Never throws.\n */\n\nexport interface FeatureManifestSummary {\n /** `name` field — short product/tagline. Empty string when unset. */\n name: string;\n /** `description` field — multi-sentence prose. Empty string when unset. */\n description: string;\n /**\n * `documentationURL` — only set when it's a real URL.\n * `tbd` / `TBD` / empty / unset → undefined. The generator uses\n * this to suppress the \"See <url>…\" line when no real docs exist\n * yet, so the file doesn't fill with placeholders.\n */\n documentationURL: string | undefined;\n /** Names of options to render as commented hints in the init output. */\n optionHints: string[];\n /** `description` from each option in the manifest, keyed by name. */\n optionDescriptions: Record<string, string>;\n /**\n * ALL option keys the manifest declares, in declaration order. Used\n * by shell completion for `add-feature -- key=value`: the builder\n * can set any option, not just the subset surfaced as hints in\n * init's commented-out block.\n */\n optionNames: string[];\n /**\n * `type` from each option in the manifest, keyed by name. Limited\n * to `'string'` / `'boolean'` today — what the devcontainer-feature\n * spec supports. Used by completion to suggest `true`/`false` after\n * `--<bool-key>=`.\n */\n optionTypes: Record<string, 'string' | 'boolean'>;\n /** Free-text per-feature notes rendered above the `- ref:` line. */\n usageNotes: string[];\n}\n\ninterface RawManifestOption {\n type?: unknown;\n description?: unknown;\n}\n\ninterface RawManifest {\n name?: string;\n description?: string;\n documentationURL?: string;\n options?: Record<string, RawManifestOption>;\n 'x-monoceros'?: {\n optionHints?: unknown;\n usageNotes?: unknown;\n };\n}\n\nfunction resolveManifestPath(\n name: string,\n checkoutRoot: string | null,\n): string | null {\n if (checkoutRoot) {\n const checkoutPath = path.join(\n checkoutRoot,\n 'images',\n 'features',\n name,\n 'devcontainer-feature.json',\n );\n if (existsSync(checkoutPath)) return checkoutPath;\n }\n const bundlePath = path.join(\n bundledFeaturesDir(),\n name,\n 'devcontainer-feature.json',\n );\n if (existsSync(bundlePath)) return bundlePath;\n return null;\n}\n\nexport function loadFeatureManifestSummary(\n ref: string,\n checkoutRoot: string | null = workbenchCheckoutRoot(),\n): FeatureManifestSummary | undefined {\n const match = matchMonocerosFeature(ref);\n if (!match) return undefined;\n const manifestPath = resolveManifestPath(match.name, checkoutRoot);\n if (!manifestPath) return undefined;\n try {\n const text = readFileSync(manifestPath, 'utf8');\n const parsed = JSON.parse(text) as RawManifest;\n\n const rawHints = parsed['x-monoceros']?.optionHints;\n const optionHints = Array.isArray(rawHints)\n ? rawHints.filter(\n (x): x is string => typeof x === 'string' && x.length > 0,\n )\n : [];\n\n const rawNotes = parsed['x-monoceros']?.usageNotes;\n const usageNotes = Array.isArray(rawNotes)\n ? rawNotes.filter(\n (x): x is string => typeof x === 'string' && x.length > 0,\n )\n : [];\n\n const optionDescriptions: Record<string, string> = {};\n const optionTypes: Record<string, 'string' | 'boolean'> = {};\n const optionNames: string[] = [];\n if (parsed.options) {\n for (const [key, opt] of Object.entries(parsed.options)) {\n if (!opt || typeof opt !== 'object') continue;\n optionNames.push(key);\n if (typeof opt.description === 'string' && opt.description.length > 0) {\n optionDescriptions[key] = opt.description;\n }\n if (opt.type === 'boolean') {\n optionTypes[key] = 'boolean';\n } else if (opt.type === 'string') {\n optionTypes[key] = 'string';\n }\n }\n }\n\n const name = typeof parsed.name === 'string' ? parsed.name : '';\n const description =\n typeof parsed.description === 'string' ? parsed.description : '';\n const rawUrl =\n typeof parsed.documentationURL === 'string'\n ? parsed.documentationURL.trim()\n : '';\n const documentationURL =\n rawUrl.length > 0 && rawUrl.toLowerCase() !== 'tbd' ? rawUrl : undefined;\n\n return {\n name,\n description,\n documentationURL,\n optionHints,\n optionDescriptions,\n optionNames,\n optionTypes,\n usageNotes,\n };\n } catch {\n return undefined;\n }\n}\n","/**\n * Shape detection for OCI refs that point at Monoceros-owned\n * devcontainer features. Two regexes live here:\n *\n * - `MONOCEROS_FEATURE_RE` matches the current ref shape\n * (`ghcr.io/getmonoceros/monoceros-features/<name>:<tag>`),\n * which is what `monoceros init` writes and what GHCR serves.\n *\n * - `DEPRECATED_MONOCEROS_FEATURE_RE` matches the legacy shape\n * (`ghcr.io/monoceros/features/<name>:<tag>`) that was used\n * before the M4 cut. We keep this only to emit a migration\n * warning at `apply` time when a builder's yml still carries\n * the old ref.\n */\n\nconst FEATURE_NAME_CHARSET = '[a-z0-9._-]+';\nconst FEATURE_TAG_CHARSET = '[a-z0-9._-]+';\n\nexport const MONOCEROS_FEATURE_RE = new RegExp(\n `^ghcr\\\\.io/getmonoceros/monoceros-features/(${FEATURE_NAME_CHARSET}):${FEATURE_TAG_CHARSET}$`,\n);\n\nexport const DEPRECATED_MONOCEROS_FEATURE_RE = new RegExp(\n `^ghcr\\\\.io/monoceros/features/(${FEATURE_NAME_CHARSET}):(${FEATURE_TAG_CHARSET})$`,\n);\n\n/**\n * Extract `{ name }` from a current-shape Monoceros feature ref,\n * or `null` for anything else (third-party features, deprecated\n * shape, malformed input).\n */\nexport function matchMonocerosFeature(ref: string): { name: string } | null {\n const match = MONOCEROS_FEATURE_RE.exec(ref);\n if (!match) return null;\n return { name: match[1]! };\n}\n\n/**\n * Translate a legacy-shape ref into the current-shape ref it would\n * map to (`ghcr.io/monoceros/features/<name>:<tag>` →\n * `ghcr.io/getmonoceros/monoceros-features/<name>:<tag>`). Returns\n * `null` for refs that don't match the legacy shape, so callers can\n * use it as a \"should I warn?\" check.\n */\nexport function migrateDeprecatedFeatureRef(ref: string): string | null {\n const match = DEPRECATED_MONOCEROS_FEATURE_RE.exec(ref);\n if (!match) return null;\n const name = match[1]!;\n const tag = match[2]!;\n return `ghcr.io/getmonoceros/monoceros-features/${name}:${tag}`;\n}\n","import { spawn } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { RepoEntry } from '../create/types.js';\nimport { KNOWN_PROVIDER_HOSTS, type RepoProvider } from '../config/schema.js';\nimport { cyan, dim } from '../util/format.js';\n\n/**\n * Spawn signature for `git credential fill`: takes the credential-\n * protocol input on stdin, returns the helper's response on stdout\n * plus the process exit code. Injected by tests.\n */\nexport type CredentialsSpawn = (\n input: string,\n) => Promise<{ stdout: string; exitCode: number }>;\n\nconst realGitCredentialFill: CredentialsSpawn = (input) => {\n return new Promise((resolve, reject) => {\n // GIT_TERMINAL_PROMPT=0 disables git's interactive\n // username/password fallback. Without this, when no credential\n // helper has an entry for the host, `git credential fill` would\n // open /dev/tty and prompt the user — which hangs apply\n // indefinitely because the parent process is running non-\n // interactively. With the env var set, git returns whatever\n // the helpers produced (possibly empty) and exits cleanly,\n // letting our pre-flight detect \"no credentials\" reliably.\n //\n // We deliberately do NOT also set GIT_ASKPASS='' / SSH_ASKPASS=''.\n // Empty string is interpreted differently across git versions, and\n // — concretely observed on Windows + Git Credential Manager — it\n // tickles a path where GCM's `store` silently no-ops after a\n // successful OAuth flow. The credential helper IS the right tool\n // for non-interactive credential resolution; the terminal-prompt\n // gate above already takes care of the hang scenario this was\n // meant to guard against.\n const child = spawn('git', ['credential', 'fill'], {\n stdio: ['pipe', 'pipe', 'inherit'],\n env: {\n ...process.env,\n GIT_TERMINAL_PROMPT: '0',\n },\n });\n let stdout = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.on('error', reject);\n child.on('exit', (code) => resolve({ stdout, exitCode: code ?? 0 }));\n child.stdin.write(input);\n child.stdin.end();\n });\n};\n\n/**\n * Tell git's configured credential helpers to persist a credential.\n *\n * Why this exists: `git credential fill` returns credentials but never\n * tells the helper to save them — that step normally happens AFTER git\n * has used the credential successfully against a remote, when git\n * itself calls `git credential approve`. Our pre-flight uses `fill`\n * for a lookup-only check, so the helper's `store` is never reached\n * by the natural git flow, and the OAuth-acquired token GCM returned\n * gets thrown away. Next apply: browser dialog again.\n *\n * Calling `approve` explicitly after a successful `fill` closes the\n * loop: GCM (and gh's helper, and the Atlassian one) write the\n * credential to their persistent store on this call, so subsequent\n * applies (and the in-container clone) find it cached. Idempotent —\n * approve on an already-stored credential is a no-op.\n */\nexport type CredentialsApprove = (input: string) => Promise<void>;\n\nconst realGitCredentialApprove: CredentialsApprove = (input) => {\n return new Promise((resolve, reject) => {\n const child = spawn('git', ['credential', 'approve'], {\n stdio: ['pipe', 'ignore', 'inherit'],\n env: {\n ...process.env,\n GIT_TERMINAL_PROMPT: '0',\n },\n });\n child.on('error', reject);\n child.on('exit', () => resolve()); // best-effort, non-zero is non-fatal\n child.stdin.write(input);\n child.stdin.end();\n });\n};\n\n/**\n * Resolve a host's provider:\n * - canonical hosts (github.com / gitlab.com / bitbucket.org) →\n * their fixed provider, ignoring any explicit hint (the canonical\n * mapping is the source of truth)\n * - any other host → the explicit hint if given, else 'unknown'\n *\n * Returning 'unknown' triggers the apply pre-flight error that asks\n * the builder to set `provider:` in the yml. We deliberately never\n * guess from hostname patterns (\"starts with `gitlab.`\" etc.) —\n * those produced wrong results for corporate domains like\n * `git.firma.de` and silently fell through to the generic hint.\n */\nexport type ResolvedProvider = RepoProvider | 'unknown';\n\nexport function resolveProvider(\n host: string,\n explicit?: RepoProvider,\n): ResolvedProvider {\n const canonical = KNOWN_PROVIDER_HOSTS[host.toLowerCase()];\n if (canonical) return canonical;\n return explicit ?? 'unknown';\n}\n\nexport interface HostWithProvider {\n host: string;\n provider: ResolvedProvider;\n}\n\n/**\n * Reduce a repo list to one entry per host, carrying along the\n * resolved provider so the pre-flight check can render the right\n * setup hint (or error out with \"set provider:\" for unknowns).\n *\n * If the same host appears with conflicting provider declarations\n * across multiple repo entries, the first one wins — the apply\n * pre-flight surfaces the conflict as a separate diagnostic before\n * we get here in normal flow. (Schema-level dedup would lock us in\n * before the builder ever sees the warning.)\n */\nfunction uniqueHttpsHosts(repos: readonly RepoEntry[]): HostWithProvider[] {\n const byHost = new Map<string, HostWithProvider>();\n for (const repo of repos) {\n if (!repo.url.startsWith('https://')) continue;\n let host: string;\n try {\n host = new URL(repo.url).hostname;\n } catch {\n // Skip malformed URLs — validateOptions catches them at the\n // add-repo step, so reaching this in production means a stack\n // file was hand-edited. Don't fail the whole apply for it.\n continue;\n }\n if (byHost.has(host)) continue;\n byHost.set(host, { host, provider: resolveProvider(host, repo.provider) });\n }\n return [...byHost.values()];\n}\n\n/**\n * Render a provider-specific install command, filtered to the host\n * OS. Returns the relevant line for the current platform — macOS gets\n * the brew command, Windows gets the winget command, Linux falls back\n * to a docs link unless the provider officially recommends a uniform\n * Linux command (then `linuxBrew` is set and used). Callers embed the\n * resulting single line into a setup-instructions block.\n */\n/**\n * Official Homebrew install one-liner (from https://brew.sh). Shown\n * as the first cyan line in any brew-based provider hint so users\n * who don't have Homebrew yet aren't stuck staring at `brew: command\n * not found`. Users who DO have Homebrew can skip the line — we say\n * so right below in dim text.\n */\nconst BREW_INSTALL_COMMAND =\n '/bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"';\n\nfunction installCommandForOS(opts: {\n brew: string;\n /**\n * Linux install command when the provider's docs officially\n * recommend a single uniform path (e.g. GitLab's glab CLI ships\n * Homebrew as the supported Linux install method). When omitted,\n * Linux gets a docs link instead because distro packaging is too\n * heterogeneous to pick a winner. Windows users hit this branch\n * too — Monoceros on Windows runs inside WSL, so the host IS Linux.\n */\n linuxBrew?: string;\n linuxDocsUrl: string;\n}): string {\n const withBrewBootstrap = (cmd: string): string =>\n [\n '',\n cyan(BREW_INSTALL_COMMAND),\n cyan(cmd),\n '',\n dim('(Skip the first line if you already have Homebrew.)'),\n ].join('\\n');\n if (process.platform === 'darwin') return withBrewBootstrap(opts.brew);\n // Linux + WSL (Windows runs Monoceros inside WSL as of 1.12).\n if (opts.linuxBrew) return withBrewBootstrap(opts.linuxBrew);\n return `See ${opts.linuxDocsUrl} for package instructions.`;\n}\n\n/**\n * Provider-specific setup hint per host. Used in the pre-flight\n * error message when `git credential fill` returns nothing for a\n * host. Shows only the install command for the current host OS —\n * less visual noise, no \"is this me?\" guesswork for the builder.\n *\n * Provider is resolved upstream (canonical-host lookup or explicit\n * yml field). This function NEVER guesses from hostname patterns;\n * see `resolveProvider` for the rationale.\n */\nexport function providerSetupHint(\n host: string,\n provider: RepoProvider,\n): {\n /** Short title for the host, formatted as \"host — Provider\". */\n title: string;\n /** Multiline body, left-aligned, no leading indentation. */\n body: string;\n} {\n if (provider === 'github') {\n // `--hostname` is only needed for self-hosted GitHub Enterprise\n // Server. For github.com (SaaS) gh defaults to that host, so we\n // omit the flag. Both `gh auth login` and `gh auth setup-git`\n // accept --hostname with identical semantics — verified against\n // https://cli.github.com/manual/gh_auth_login and\n // https://cli.github.com/manual/gh_auth_setup-git .\n const isSaas = host.toLowerCase() === 'github.com';\n const hostArg = isSaas ? '' : ` --hostname ${host}`;\n // GitHub CLI publishes a Linuxbrew formula alongside the macOS\n // one (https://github.com/cli/cli/blob/trunk/docs/install_linux.md\n // lists Homebrew under \"Linux & WSL\"), so the same brew command\n // works on macOS, Linux and WSL.\n const install = installCommandForOS({\n brew: 'brew install gh',\n linuxBrew: 'brew install gh',\n linuxDocsUrl: 'https://github.com/cli/cli#installation',\n });\n return {\n title: `${host} — GitHub`,\n body: [\n 'Install the GitHub CLI:',\n install,\n '',\n 'Then run once:',\n cyan(`gh auth login${hostArg}`),\n cyan(`gh auth setup-git${hostArg}`),\n '',\n '`gh auth login` walks through OAuth in your browser.',\n '`gh auth setup-git` wires gh into git as a credential helper.',\n ].join('\\n'),\n };\n }\n if (provider === 'gitlab') {\n // `--hostname` is only needed for self-hosted GitLab. For\n // gitlab.com glab defaults to the SaaS host, so we omit the flag.\n const isSaas = host.toLowerCase() === 'gitlab.com';\n const hostArg = isSaas ? '' : ` --hostname ${host}`;\n // GitLab's official install docs (https://gitlab.com/gitlab-org/\n // cli/-/blob/main/docs/installation_options.md) state that\n // Homebrew is \"the officially supported installation method for\n // Linux\" — same brew command on macOS, Linux and WSL.\n const install = installCommandForOS({\n brew: 'brew install glab',\n linuxBrew: 'brew install glab',\n linuxDocsUrl: 'https://gitlab.com/gitlab-org/cli#installation',\n });\n return {\n title: `${host} — GitLab`,\n body: [\n 'Install the GitLab CLI (glab):',\n install,\n '',\n 'Then run once:',\n cyan(`glab auth login${hostArg}`),\n '',\n 'Choose `HTTPS` when asked for git-protocol, then accept',\n '\"Authenticate Git with your GitLab credentials\" — glab',\n 'configures itself as the git credential helper.',\n ].join('\\n'),\n };\n }\n if (provider === 'bitbucket') {\n // Bitbucket has no first-party CLI for git-credentials (no\n // `bb auth login` equivalent to gh/glab), so this is a manual\n // one-time setup either way. The Cloud and Data-Center variants\n // differ in where you get the token and what the username field\n // expects — same pattern as the github / gitlab branches above\n // (canonical SaaS host vs. self-hosted).\n const isCloud = host.toLowerCase() === 'bitbucket.org';\n if (isCloud) {\n return {\n title: `${host} — Bitbucket Cloud`,\n body: [\n 'Bitbucket has no first-party CLI for git-credentials, so this',\n 'is a manual one-time setup. Generate an Atlassian API token at',\n 'https://id.atlassian.com/manage-profile/security/api-tokens',\n '',\n 'Then store it via your OS credential helper:',\n cyan(\n `git credential approve <<< $'protocol=https\\\\nhost=${host}\\\\nusername=<your-atlassian-email>\\\\npassword=<token>\\\\n'`,\n ),\n ].join('\\n'),\n };\n }\n return {\n title: `${host} — Bitbucket Data Center`,\n body: [\n 'Bitbucket has no first-party CLI for git-credentials, so this',\n 'is a manual one-time setup. Generate a personal HTTP access',\n `token in your Bitbucket UI: profile picture (top right on ${host})`,\n '→ Manage account → HTTP access tokens → Create token. Give it',\n 'at least repo-read + repo-write scopes for the repos you need.',\n '',\n 'Then store it via your OS credential helper:',\n cyan(\n `git credential approve <<< $'protocol=https\\\\nhost=${host}\\\\nusername=<your-bitbucket-username>\\\\npassword=<token>\\\\n'`,\n ),\n ].join('\\n'),\n };\n }\n // provider === 'gitea' — Gitea is always self-hosted (gitea.com is\n // a demo / sandbox, not a SaaS), so there's no canonical-host\n // branch. The `tea` CLI exists but logs into its own config and\n // doesn't register as a git credential helper (verified against\n // https://gitea.com/gitea/tea), so we point at the UI flow + a\n // direct `git credential approve` — same pattern as Bitbucket\n // Data Center. Forgejo (the Gitea fork) shares this flow exactly.\n return {\n title: `${host} — Gitea`,\n body: [\n 'Gitea has no first-party CLI helper for git-credentials (the',\n '`tea` CLI logs into its own config, not into your git credential',\n 'helper), so this is a manual one-time setup. Generate an access',\n `token in your Gitea UI: profile picture (top right on ${host}) →`,\n 'Settings → Applications → \"Generate New Token\". Give it at',\n 'least the `read:repository` scope (add `write:repository` if you',\n 'need push from the container).',\n '',\n 'Then store it via your OS credential helper:',\n cyan(\n `git credential approve <<< $'protocol=https\\\\nhost=${host}\\\\nusername=<your-gitea-username>\\\\npassword=<token>\\\\n'`,\n ),\n ].join('\\n'),\n };\n}\n\ninterface ParsedCreds {\n username?: string;\n password?: string;\n}\n\nfunction parseCredentialFillOutput(output: string): ParsedCreds {\n const result: ParsedCreds = {};\n for (const line of output.split('\\n')) {\n const eqIdx = line.indexOf('=');\n if (eqIdx <= 0) continue;\n const key = line.slice(0, eqIdx);\n const value = line.slice(eqIdx + 1);\n if (key === 'username') result.username = value;\n if (key === 'password') result.password = value;\n }\n return result;\n}\n\nfunction formatCredentialLine(\n host: string,\n username: string,\n password: string,\n): string {\n // Both fields percent-encoded so a `@`, `:`, or `/` in the token\n // doesn't break URL parsing inside git's `store` helper.\n const encUser = encodeURIComponent(username);\n const encPass = encodeURIComponent(password);\n return `https://${encUser}:${encPass}@${host}`;\n}\n\nexport interface CollectCredentialsOptions {\n spawn?: CredentialsSpawn;\n /**\n * Approve callback — called once per host after a successful\n * `fill`, with the full credential-protocol payload (incl. password).\n * Tells the host's credential helper to persist the credential.\n * Defaults to `git credential approve`. Tests inject a stub that\n * records calls without spawning git.\n */\n approve?: CredentialsApprove;\n logger?: { info: (msg: string) => void; warn: (msg: string) => void };\n}\n\nexport interface HostCredentialStatus {\n host: string;\n /**\n * Resolved provider for this host — canonical lookup for the three\n * known hosts, explicit yml hint for anything else. Carried into\n * the failure message so `formatMissingCredentialsError` can render\n * the right setup block without re-resolving.\n */\n provider: RepoProvider;\n /** 'ok' when username+password came back from `git credential fill`. */\n status: 'ok' | 'no-credentials' | 'spawn-error' | 'non-zero-exit';\n /** Diagnostic text — empty when status is 'ok'. */\n detail: string;\n}\n\nexport interface CollectCredentialsResult {\n /** Hosts for which credentials were successfully written. */\n hostsWritten: number;\n /** Hosts for which `git credential fill` failed or returned no creds. */\n hostsSkipped: number;\n /** Per-host status (in input order). */\n perHost: HostCredentialStatus[];\n /** Absolute path to the written credentials file (always written, possibly empty). */\n credentialsPath: string;\n}\n\n/**\n * For each unique HTTPS host across the dev-container's repos, ask the\n * host-side git for credentials and write them to\n * `<devContainerRoot>/.monoceros/git-credentials`. The container's\n * post-create.sh configures git to read from that file via `store`\n * credential helper.\n *\n * Host-side `git credential fill` consults whatever helper the host\n * has configured (osxkeychain on macOS, manager on Windows, libsecret\n * on Linux). If a helper has the cached credentials, returns silent.\n * If not, the helper prompts the builder via its native UI\n * (Keychain-popup, GCM-window, terminal-prompt). That's the intended\n * UX — Monoceros never prompts directly, the host's helper does.\n *\n * Always writes the file (possibly empty) so the bind-mount target\n * exists in the container. A host that returns no credentials simply\n * yields a credentials file with no matching entries, and the in-\n * container `git clone` falls back to whatever default git would do\n * (which is to prompt — and there we lose, but the diagnostic is\n * clear).\n */\nexport async function collectGitCredentials(\n devContainerRoot: string,\n hosts: readonly HostWithProvider[],\n options: CollectCredentialsOptions = {},\n): Promise<CollectCredentialsResult> {\n const credsDir = path.join(devContainerRoot, '.monoceros');\n const credentialsPath = path.join(credsDir, 'git-credentials');\n\n const spawnFn = options.spawn ?? realGitCredentialFill;\n const approveFn = options.approve ?? realGitCredentialApprove;\n const logger = options.logger ?? { info: () => {}, warn: () => {} };\n\n // Callers must filter out 'unknown' providers before invoking this\n // function — those should fail the apply pre-flight earlier with a\n // \"set provider:\" error, never reach the credential helper. We\n // narrow the type here for the renderer's sake.\n const lines: string[] = [];\n const perHost: HostCredentialStatus[] = [];\n for (const { host, provider } of hosts) {\n if (provider === 'unknown') {\n // Defensive: should not happen — pre-flight is supposed to\n // bail before this. Record it anyway with no-credentials so\n // the caller doesn't see a partial success.\n perHost.push({\n host,\n provider: 'github', // placeholder — never rendered because pre-flight already bailed\n status: 'no-credentials',\n detail: 'provider not declared (internal: should not reach here)',\n });\n continue;\n }\n logger.info(`Fetching credentials for ${host} from host git…`);\n const input = `protocol=https\\nhost=${host}\\n\\n`;\n let result;\n try {\n result = await spawnFn(input);\n } catch (err) {\n // No logger.warn here — the caller (apply pre-flight) renders\n // a consolidated, provider-specific error message per failing\n // host. A separate WARN line per host would just add visual\n // noise above the actionable error.\n const detail = err instanceof Error ? err.message : String(err);\n perHost.push({ host, provider, status: 'spawn-error', detail });\n continue;\n }\n if (result.exitCode !== 0) {\n perHost.push({\n host,\n provider,\n status: 'non-zero-exit',\n detail: `exit code ${result.exitCode}`,\n });\n continue;\n }\n const { username, password } = parseCredentialFillOutput(result.stdout);\n if (!username || !password) {\n perHost.push({\n host,\n provider,\n status: 'no-credentials',\n detail: 'host credential helper returned no username/password',\n });\n continue;\n }\n lines.push(formatCredentialLine(host, username, password));\n perHost.push({ host, provider, status: 'ok', detail: '' });\n\n // Tell the host credential helper to persist the credential we\n // just received. `git credential fill` itself never triggers a\n // helper `store` — git only does that automatically after using\n // a credential successfully on a real remote operation. Without\n // this explicit approve, an OAuth flow that GCM kicked off on\n // first apply returns the token to us, but GCM never writes it\n // to the Windows Credential Manager. Result: every subsequent\n // apply pops a fresh browser auth dialog. Best-effort — non-zero\n // from approve is non-fatal; we still wrote the in-container\n // credentials file, which is what apply actually relies on.\n const approveInput = `protocol=https\\nhost=${host}\\nusername=${username}\\npassword=${password}\\n\\n`;\n try {\n await approveFn(approveInput);\n } catch {\n /* best-effort, don't block apply on credential-store hiccups */\n }\n }\n\n await fs.mkdir(credsDir, { recursive: true });\n await fs.writeFile(\n credentialsPath,\n lines.join('\\n') + (lines.length > 0 ? '\\n' : ''),\n {\n mode: 0o600,\n },\n );\n\n return {\n hostsWritten: lines.length,\n hostsSkipped: perHost.filter((p) => p.status !== 'ok').length,\n perHost,\n credentialsPath,\n };\n}\n\n/**\n * Expose `uniqueHttpsHosts` for callers that need the host list\n * directly (apply uses it to build the pre-flight check input).\n */\nexport { uniqueHttpsHosts };\n\n/**\n * Build the multi-host pre-flight error message that gets thrown when\n * apply discovers missing credentials. Header inlines the provider\n * for single-host cases; body is left-aligned setup instructions.\n *\n * Format:\n *\n * Missing Git credentials: <host> — <Provider>\n *\n * <setup instructions, left-aligned, multi-line>\n *\n * Then re-run `monoceros apply`.\n *\n * For multi-host failures, each block is separated by a blank line\n * and gets its own provider title.\n */\nexport function formatMissingCredentialsError(\n missing: readonly HostCredentialStatus[],\n): string {\n if (missing.length === 1) {\n const m = missing[0]!;\n const hint = providerSetupHint(m.host, m.provider);\n return [\n `Missing Git credentials: ${hint.title}`,\n '',\n hint.body,\n '',\n `Then re-run ${cyan('monoceros apply')}.`,\n ].join('\\n');\n }\n const lines: string[] = [\n `Missing Git credentials for ${missing.length} hosts:`,\n '',\n ];\n for (const m of missing) {\n const hint = providerSetupHint(m.host, m.provider);\n lines.push(hint.title);\n lines.push('');\n lines.push(hint.body);\n lines.push('');\n }\n lines.push(`Then re-run ${cyan('monoceros apply')}.`);\n return lines.join('\\n');\n}\n\n/**\n * Build the pre-flight error for repos whose host has no provider\n * declared and isn't one of the canonical ones (github.com /\n * gitlab.com / bitbucket.org). The builder needs to add a\n * `provider:` field to the yml before apply can continue.\n */\nexport function formatUnknownProviderError(hosts: readonly string[]): string {\n const sorted = [...new Set(hosts)].sort();\n const lines: string[] = [\n sorted.length === 1\n ? `Unknown Git provider for host ${sorted[0]!}.`\n : `Unknown Git provider for ${sorted.length} hosts: ${sorted.join(', ')}.`,\n '',\n 'Monoceros auto-detects only github.com / gitlab.com / bitbucket.org.',\n 'For any other host (self-hosted GitLab, Gitea, Bitbucket Server, …)',\n 'declare the provider explicitly in the yml. Edit the repo entry:',\n '',\n cyan(' repos:'),\n cyan(` - url: https://${sorted[0]!}/…`),\n cyan(' provider: gitlab # or: github, bitbucket, gitea'),\n '',\n `Or re-add with ${cyan('monoceros add-repo <name> <url> --provider=<github|gitlab|bitbucket|gitea>')}.`,\n ];\n return lines.join('\\n');\n}\n\n// Exported for tests.\nexport const _internals = {\n uniqueHttpsHosts,\n parseCredentialFillOutput,\n formatCredentialLine,\n};\n","// Shared terminal-formatting helpers for CLI output. Same palette\n// as install.sh and packages/cli/src/help.ts:\n//\n// cyan = identifiers you type (commands, refs, component names)\n// grey = supplementary metadata (paths, version notes, hints)\n// bold+und. = structural section markers\n//\n// Status semantics (green ✓, red ✗, yellow !) live in consola for\n// log-level lines and aren't duplicated here.\n//\n// Two flavours of consumer:\n// - status output (install.sh, apply) goes to stderr — use the\n// top-level helpers below; they gate on process.stderr.isTTY.\n// - data output (list-components) goes to stdout — use\n// `colorsFor(process.stdout)` to get the same helpers gated\n// against the right stream, so colours drop out cleanly when\n// the user pipes the output into grep/less/etc.\n\nconst ESC = '\\x1b[';\nconst ANSI_BOLD = `${ESC}1m`;\nconst ANSI_UNDERLINE = `${ESC}4m`;\nconst ANSI_CYAN = `${ESC}36m`;\nconst ANSI_GREY = `${ESC}90m`;\nconst ANSI_RESET = `${ESC}0m`;\n\n// eslint-disable-next-line no-control-regex\nconst ANSI_RE = /\\x1b\\[[0-9;]*m/g;\n\n/**\n * Visible character count, ANSI escape sequences stripped. Used\n * for column-padding so coloured labels still line up.\n */\nexport function visibleLen(s: string): number {\n return s.replace(ANSI_RE, '').length;\n}\n\nexport interface Palette {\n bold: (s: string) => string;\n underline: (s: string) => string;\n cyan: (s: string) => string;\n dim: (s: string) => string;\n /**\n * Section marker — bold + underlined with a `▸` chevron prefix.\n * Same visual treatment as install.sh's section headers.\n */\n sectionLine: (label: string) => string;\n}\n\nfunction makeWrap(isTty: boolean): (s: string, ...codes: string[]) => string {\n return (s, ...codes) => (isTty ? codes.join('') + s + ANSI_RESET : s);\n}\n\nfunction makePalette(isTty: boolean): Palette {\n const wrap = makeWrap(isTty);\n return {\n bold: (s) => wrap(s, ANSI_BOLD),\n underline: (s) => wrap(s, ANSI_UNDERLINE),\n cyan: (s) => wrap(s, ANSI_CYAN),\n dim: (s) => wrap(s, ANSI_GREY),\n sectionLine: (label) => wrap(`▸ ${label}`, ANSI_BOLD, ANSI_UNDERLINE),\n };\n}\n\n/**\n * Resolve a stream-specific palette. Pass `process.stdout` for\n * commands whose payload goes to stdout (so colours drop out when\n * piped); `process.stderr` for status output that stays on stderr\n * regardless of stdout's destination.\n */\nexport function colorsFor(stream: NodeJS.WriteStream): Palette {\n return makePalette(stream.isTTY ?? false);\n}\n\n// Top-level convenience helpers — gated on stderr, matching the\n// install-/apply-style status output that's always written to\n// stderr. Existing call sites keep working unchanged.\nconst stderrPalette = makePalette(process.stderr.isTTY ?? false);\nexport const bold = stderrPalette.bold;\nexport const underline = stderrPalette.underline;\nexport const cyan = stderrPalette.cyan;\nexport const dim = stderrPalette.dim;\nexport const sectionLine = stderrPalette.sectionLine;\n","import { spawn } from 'node:child_process';\n\n/**\n * Find the running docker container that hosts a Monoceros\n * dev-container. The lookup is by the `devcontainer.local_folder`\n * label devcontainer-cli attaches at `up` time — that label is the\n * absolute host path of the materialized container directory, which\n * is the only stable handle (the container name itself is random for\n * image-mode, e.g. `thirsty_bartik`).\n *\n * Returns the container id (12-char prefix as docker prints it) or\n * `null` when nothing matches. `null` is the normal \"container not\n * up yet\" signal; callers should treat it as \"fall back to yml-only,\n * suggest `monoceros apply`\".\n */\n\nexport interface RunningContainerLookupOptions {\n /** Override the docker exec used to query — tests inject a fake. */\n docker?: DockerLookupExec;\n}\n\nexport type DockerLookupExec = (\n args: readonly string[],\n) => Promise<{ stdout: string; stderr: string; exitCode: number }>;\n\nconst realDockerLookup: DockerLookupExec = (args) => {\n return new Promise((resolve, reject) => {\n const child = spawn('docker', args as string[], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n let stdout = '';\n let stderr = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n child.on('error', reject);\n child.on('exit', (code) =>\n resolve({ stdout, stderr, exitCode: code ?? 0 }),\n );\n });\n};\n\n/**\n * Look up the running container by its `devcontainer.local_folder`\n * label. `containerPath` must be the absolute path of the\n * materialized container dir (e.g. `~/.monoceros/container/sandbox`).\n */\nexport async function findRunningContainerByLocalFolder(\n containerPath: string,\n opts: RunningContainerLookupOptions = {},\n): Promise<string | null> {\n const docker = opts.docker ?? realDockerLookup;\n const result = await docker([\n 'ps',\n '-q',\n '--filter',\n `label=devcontainer.local_folder=${containerPath}`,\n '--filter',\n 'status=running',\n ]);\n if (result.exitCode !== 0) return null;\n const ids = result.stdout\n .split('\\n')\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n return ids[0] ?? null;\n}\n\n/**\n * `docker exec` wrapper for in-container commands triggered by\n * `add-repo --clone-now`. Streams stdout/stderr to the parent\n * process by default so a `git clone` progress bar shows up live —\n * the spawn override lets tests capture instead.\n */\nexport type ContainerExec = (\n containerId: string,\n argv: readonly string[],\n) => Promise<{ exitCode: number; stdout?: string; stderr?: string }>;\n\nexport const realContainerExec: ContainerExec = (containerId, argv) => {\n return new Promise((resolve, reject) => {\n const child = spawn('docker', ['exec', containerId, ...argv], {\n // Inherit stdio so live git output reaches the user.\n stdio: ['ignore', 'inherit', 'inherit'],\n });\n child.on('error', reject);\n child.on('exit', (code) => resolve({ exitCode: code ?? 0 }));\n });\n};\n","import { promises as fs } from 'node:fs';\nimport { z } from 'zod';\nimport { isMap, Pair, parseDocument, Scalar, YAMLMap } from 'yaml';\nimport type { Document } from 'yaml';\nimport {\n FeatureOptionValueSchema,\n GitUserSchema,\n REGEX,\n isValidEmail,\n} from './schema.js';\nimport { monocerosConfigPath, monocerosHome } from './paths.js';\n\n/**\n * `<MONOCEROS_HOME>/monoceros-config.yml` — optional builder-owned\n * defaults that apply across every container materialized through\n * this home. Today the only field is git identity; future fields\n * (default editor, Claude auth profile, ...) plug into the same\n * structure.\n *\n * Schema is permissive: missing top-level keys are fine, the file\n * itself is optional. Schema violations surface as a hard error\n * (better to refuse than silently ignore a typo'd key the builder\n * thought was effective).\n */\n\nconst SCHEMA_VERSION = 1 as const;\n\n/**\n * `defaults.features` — map of devcontainer feature ref to a default\n * option object. When a container yml references the same feature ref\n * without overriding a specific option, the value from here is used.\n * Per-container options always win.\n *\n * Typical use: stash the Atlassian apiToken / Anthropic apiKey here\n * once globally instead of repeating them in every container yml.\n */\nexport const MonocerosConfigSchema = z.object({\n schemaVersion: z.literal(SCHEMA_VERSION),\n // .nullish() (= .optional().nullable()) on defaults so the shipped\n // sample yml — where `defaults:` is uncommented but every sub-block\n // is commented out — parses cleanly. YAML produces `defaults: null`\n // in that case; without .nullish() the schema would reject it and\n // we'd be back to forcing builders to comment-juggle three lines.\n defaults: z\n .object({\n // .nullish() (not just .optional()) so the sample yml can leave\n // `git:` uncommented as a category marker — YAML produces\n // `git: null` for an empty mapping, which zod's plain\n // `.optional()` would reject.\n git: z\n .object({\n // Strict email here: monoceros-config defaults are not tied to\n // any container `<name>.env`, so `${VAR}` placeholders make no\n // sense and the format can (and should) be validated at load\n // time — unlike the container/repo `git.user`, which defers to\n // apply after interpolation.\n user: GitUserSchema.optional().refine(\n (u) => u?.email === undefined || isValidEmail(u.email),\n { message: 'Invalid email in defaults.git.user', path: ['email'] },\n ),\n })\n .nullish(),\n // .nullish() for the same reason as `git` — the sample keeps\n // `features:` uncommented as a category marker.\n features: z\n .record(\n z\n .string()\n .regex(\n REGEX.featureRef,\n \"Invalid feature ref. Expected an OCI-image-style ref like 'ghcr.io/getmonoceros/monoceros-features/<name>:<tag>'.\",\n ),\n z.record(z.string(), FeatureOptionValueSchema),\n )\n .nullish(),\n })\n .nullish(),\n // Machine-global routing settings — one Traefik per builder, so\n // host-port and similar live here rather than in any container yml.\n // See ADR 0007.\n routing: z\n .object({\n hostPort: z\n .number()\n .int()\n .min(1)\n .max(65535)\n .optional()\n .describe(\n 'Host port the Traefik singleton binds. Default 80. Set this when 80 is held by another service on your machine — URLs then become http://<name>.localhost:<port>/.',\n ),\n })\n .nullish(),\n});\n\nexport type MonocerosConfig = z.infer<typeof MonocerosConfigSchema>;\n\nexport interface ReadMonocerosConfigOptions {\n /** Override of the user-data home. Tests inject a tmpdir. */\n monocerosHome?: string;\n}\n\n/**\n * Read `<home>/monoceros-config.yml`. Returns `undefined` if the file\n * isn't there (the normal case for a fresh setup). Throws on a parse\n * or schema error — the builder explicitly created the file, so a\n * silent ignore would be worse than a loud abort.\n */\nexport async function readMonocerosConfig(\n opts: ReadMonocerosConfigOptions = {},\n): Promise<MonocerosConfig | undefined> {\n const home = opts.monocerosHome ?? monocerosHome();\n const filePath = monocerosConfigPath(home);\n let text: string;\n try {\n text = await fs.readFile(filePath, 'utf8');\n } catch {\n return undefined;\n }\n const doc = parseDocument(text, { prettyErrors: true });\n if (doc.errors.length > 0) {\n throw new Error(\n `yaml parse error in ${filePath}: ${doc.errors[0]!.message}`,\n );\n }\n const result = MonocerosConfigSchema.safeParse(doc.toJS());\n if (!result.success) {\n const issues = result.error.issues\n .map((issue) => {\n const where = issue.path.length > 0 ? issue.path.join('.') : '(root)';\n return ` - ${where}: ${issue.message}`;\n })\n .join('\\n');\n throw new Error(\n `Invalid ${filePath}:\\n${issues}\\n\\nSee ${filePath.replace(\n /\\.yml$/,\n '.sample.yml',\n )} for a valid example.`,\n );\n }\n return result.data;\n}\n\n/** Default Traefik host port when `routing.hostPort` is unset. */\nexport const DEFAULT_PROXY_HOST_PORT = 80;\n\n/**\n * Effective host port the Traefik singleton should bind. Falls back\n * to `DEFAULT_PROXY_HOST_PORT` (80) when the global config or its\n * `routing.hostPort` field is absent.\n */\nexport function proxyHostPort(config?: MonocerosConfig | undefined): number {\n return config?.routing?.hostPort ?? DEFAULT_PROXY_HOST_PORT;\n}\n\nexport interface WriteGlobalDefaultGitUserResult {\n /** Absolute path to the file that was written. */\n filePath: string;\n /** True when a brand-new file was created (no monoceros-config.yml before). */\n created: boolean;\n /** True when an existing defaults.git.user was already set and we left it alone. */\n alreadySet: boolean;\n}\n\n/**\n * Persist `defaults.git.user` in `<MONOCEROS_HOME>/monoceros-config.yml`.\n *\n * Behaviour:\n *\n * - File missing → create with the minimum shape (`schemaVersion: 1`\n * + `defaults.git.user`). `monoceros-config.sample.yml` carries\n * the canonical documentation; we don't reproduce it here.\n * - File present, no `defaults.git.user` → fill it in,\n * comment-preserving (the rest of the file stays untouched).\n * - File present, `defaults.git.user` already set → leave as-is,\n * report `alreadySet: true`. The caller decides whether to warn\n * or fall back to a per-container override.\n *\n * The caller (apply / init identity flow) decides when to call this\n * — typically only when the builder explicitly chose `g` (global) or\n * `b` (both) in the scope prompt.\n */\nexport async function writeGlobalDefaultGitUser(\n user: { name: string; email: string },\n opts: { monocerosHome?: string } = {},\n): Promise<WriteGlobalDefaultGitUserResult> {\n const home = opts.monocerosHome ?? monocerosHome();\n const filePath = monocerosConfigPath(home);\n\n let text: string | undefined;\n try {\n text = await fs.readFile(filePath, 'utf8');\n } catch {\n text = undefined;\n }\n\n // Brand-new file → write the minimal shape that mirrors the\n // shipped sample's structure (so a later auto-write can navigate\n // the same paths).\n if (text === undefined) {\n const fresh = [\n '# Optional — global defaults for monoceros containers.',\n '',\n 'schemaVersion: 1',\n '',\n 'defaults:',\n ' git:',\n ' user:',\n ` name: ${user.name}`,\n ` email: ${user.email}`,\n '',\n ].join('\\n');\n await fs.mkdir(home, { recursive: true });\n await fs.writeFile(filePath, fresh, 'utf8');\n return { filePath, created: true, alreadySet: false };\n }\n\n // Existing file: operate via the yaml AST. With the simplified\n // sample (no nested commented sub-blocks under the active keys),\n // AST set-at-path is the right tool — the library can no longer\n // attach unrelated comments to the wrong node because there are\n // no fragmented comment runs between sibling maps.\n //\n // We ensure each level of the path (`defaults` → `git` → `user`)\n // exists as a YAMLMap, then set the two scalar fields. Pre-existing\n // values that are non-empty mean \"already set\" and we leave them\n // alone.\n const doc = parseDocument(text, { prettyErrors: true });\n if (doc.errors.length > 0) {\n throw new Error(\n `yaml parse error in ${filePath}: ${doc.errors[0]!.message}`,\n );\n }\n\n const defaultsMap = ensureMap(doc, 'defaults');\n // `git` is placed at the FRONT of `defaults` when newly created —\n // mirrors the shipped-sample layout (`defaults.git` before\n // `defaults.features`). A pre-existing `git:` keeps its position.\n const gitMap = ensureSubMapAtTop(defaultsMap, 'git');\n const userMap = ensureSubMap(gitMap, 'user');\n\n const existingName = userMap.get('name');\n const existingEmail = userMap.get('email');\n if (\n typeof existingName === 'string' &&\n existingName.length > 0 &&\n typeof existingEmail === 'string' &&\n existingEmail.length > 0\n ) {\n return { filePath, created: false, alreadySet: true };\n }\n\n // yaml's parser sometimes attaches a comment that visually belongs to\n // the NEXT outer-level sibling (e.g. `# Feature credentials & options.`\n // sitting above `features:`) to the trailing leaf scalar instead\n // (here: `email:`'s value). If we just `.set()`, that orphaned\n // comment renders right after our new value, producing chaos like:\n // email:\n //\n // a@example.com # Feature credentials & options.\n //\n // Relocate any such leaked comment to the next sibling of `git` under\n // `defaults` (the position it visually belongs to) before writing.\n relocateLeakedLeafComments(userMap, defaultsMap, 'git');\n\n userMap.set('name', user.name);\n userMap.set('email', user.email);\n const newText = String(doc);\n\n await fs.writeFile(filePath, newText, 'utf8');\n return { filePath, created: false, alreadySet: false };\n}\n\n/**\n * Get the document's top-level `<key>` as a YAMLMap, creating it\n * (and replacing a null/scalar value with a fresh map) if needed.\n * Used by writeGlobalDefaultGitUser to navigate to the persistence\n * point without crashing on yml shapes like `defaults:` (parsed as\n * null) or a missing top-level key.\n */\nfunction ensureMap(doc: Document, key: string): YAMLMap {\n const node = doc.get(key, true);\n if (node && isMap(node)) return node;\n const m = new YAMLMap();\n doc.set(key, m);\n return m;\n}\n\n/** Same as ensureMap but for a sub-key under an existing YAMLMap. */\nfunction ensureSubMap(parent: YAMLMap, key: string): YAMLMap {\n const node = parent.get(key, true);\n if (node && isMap(node)) return node;\n const m = new YAMLMap();\n parent.set(key, m);\n return m;\n}\n\n/**\n * Variant of `ensureSubMap` that inserts a *new* key at the FRONT of\n * `parent.items` rather than appending. Used for `defaults.git` so the\n * new block lands where the shipped sample places it (above\n * `features`) and not at the end after every other entry. A\n * pre-existing key keeps its position untouched.\n *\n * Before inserting, transfers any `commentBefore` that yaml-lib\n * attached to `parent` itself (this happens when the source's\n * leading comment-block ABOVE parent's first child was associated\n * with the map node rather than the first Pair) over to that first\n * child's key — otherwise the comment would visually re-attach to\n * our newly-front-inserted pair and mislabel it.\n */\nfunction ensureSubMapAtTop(parent: YAMLMap, key: string): YAMLMap {\n const node = parent.get(key, true);\n if (node && isMap(node)) return node;\n\n type CommentNode = {\n commentBefore?: string | null;\n spaceBefore?: boolean;\n };\n const parentMaybe = parent as CommentNode;\n const newKey = new Scalar(key);\n\n // yaml-lib often parks the comment-block above parent's first\n // child on the map node itself, not on the Pair. When we unshift\n // a new front Pair we have to redistribute that block: the first\n // paragraph (everything up to a blank-line separator) describes\n // what we're inserting, so it travels with the new key; the rest\n // continues to describe the old first child.\n //\n // Before redistributing we strip any commented-out skeleton of the\n // same key (`# git: / # user: / # name: …`) — that's the\n // placeholder a builder leaves behind when they un-commented prose\n // but not the structural keys. We're about to write a real active\n // block; the placeholder would otherwise sit right next to it as\n // dead text.\n if (\n parent.items.length > 0 &&\n typeof parentMaybe.commentBefore === 'string' &&\n parentMaybe.commentBefore.length > 0\n ) {\n const cleaned = stripCommentedKeySkeleton(parentMaybe.commentBefore, key);\n const blankMatch = cleaned.match(/\\n[ \\t]*\\n/);\n let head: string;\n let tail: string;\n if (blankMatch && blankMatch.index !== undefined) {\n head = cleaned.slice(0, blankMatch.index);\n tail = cleaned.slice(blankMatch.index + blankMatch[0].length);\n } else {\n head = cleaned;\n tail = '';\n }\n if (head.length > 0) {\n newKey.commentBefore = head;\n if (parentMaybe.spaceBefore) newKey.spaceBefore = true;\n }\n if (tail.length > 0) {\n const firstKey = parent.items[0]!.key as CommentNode | null;\n if (firstKey && typeof firstKey === 'object') {\n const existing = firstKey.commentBefore ?? '';\n firstKey.commentBefore = existing ? `${tail}\\n${existing}` : tail;\n firstKey.spaceBefore = true;\n }\n }\n parentMaybe.commentBefore = null;\n parentMaybe.spaceBefore = false;\n }\n\n const m = new YAMLMap();\n const pair = new Pair(newKey, m);\n parent.items.unshift(pair);\n return m;\n}\n\n/**\n * Remove a commented-out skeleton of `key` (and its indented children)\n * from a yaml-lib `commentBefore` body.\n *\n * yaml-lib stores comment bodies stripped of the leading `#`: the\n * source `# user:` becomes ` user:` in the comment string. So a\n * placeholder like\n *\n * # git:\n * # user:\n * # name: \"T\"\n * # email: \"h@k.de\"\n *\n * lives in the comment body as four lines, the first ` git:` (single\n * leading space, the post-`#` convention) and the next three with\n * more leading spaces (the children of the commented map).\n *\n * Detection: a line matching ` <key>:` exactly, followed by zero or\n * more lines whose leading whitespace is strictly deeper than that\n * line's. Splice all of those out. Multiple skeletons (rare but\n * possible) get stripped in sequence.\n */\nfunction stripCommentedKeySkeleton(commentBody: string, key: string): string {\n const lines = commentBody.split('\\n');\n const headRe = new RegExp(`^ ${escapeRegExp(key)}:\\\\s*$`);\n const out: string[] = [];\n let i = 0;\n while (i < lines.length) {\n const line = lines[i]!;\n if (headRe.test(line)) {\n // Skip the header and all following indented (deeper than\n // one space) lines — those are the commented map's children.\n i++;\n while (i < lines.length && /^ {2,}\\S/.test(lines[i]!)) {\n i++;\n }\n continue;\n }\n out.push(line);\n i++;\n }\n return out.join('\\n');\n}\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Walk `leafMap`'s pair values. For any value scalar carrying a comment\n * (yaml's parser attaches the next outer-level pair's leading comment\n * to the previous level's trailing scalar when the indent drops by\n * more than one), move that comment to the `commentBefore` of the next\n * sibling of `ancestorKey` in `parent`. Also clear `spaceBefore` on\n * the leaf and set it on the relocation target so the blank line\n * re-appears in the right place.\n *\n * No-op when there's no leaked comment or no next sibling to host it.\n */\nfunction relocateLeakedLeafComments(\n leafMap: YAMLMap,\n parent: YAMLMap,\n ancestorKey: string,\n): void {\n const items = parent.items;\n const ancestorIdx = items.findIndex((p) => {\n const k = p.key as { value?: unknown } | string | null;\n const v = typeof k === 'string' ? k : (k?.value ?? null);\n return v === ancestorKey;\n });\n if (ancestorIdx < 0 || ancestorIdx + 1 >= items.length) return;\n const target = items[ancestorIdx + 1]!;\n type CommentNode = {\n comment?: string | null;\n commentBefore?: string | null;\n spaceBefore?: boolean;\n };\n for (const pair of leafMap.items) {\n const value = pair.value as CommentNode | null;\n if (!value || typeof value !== 'object') continue;\n const leakedComment = value.comment;\n const leakedSpace = value.spaceBefore;\n if (!leakedComment && !leakedSpace) continue;\n if (leakedComment) {\n const targetKey = target.key as CommentNode | null;\n if (targetKey && typeof targetKey === 'object') {\n const existing = targetKey.commentBefore ?? '';\n targetKey.commentBefore = existing\n ? `${leakedComment}\\n${existing}`\n : leakedComment;\n if (leakedSpace) targetKey.spaceBefore = true;\n }\n value.comment = null;\n }\n if (leakedSpace) value.spaceBefore = false;\n }\n}\n","import { existsSync, promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { z } from 'zod';\nimport { parse as parseYaml } from 'yaml';\nimport { componentsDir as defaultComponentsDir } from '../config/paths.js';\nimport { FeatureOptionValueSchema, REGEX } from '../config/schema.js';\n\n/**\n * Components catalog — small, composable yml snippets that\n * `monoceros init` can merge into a container config.\n *\n * Each file under `templates/components/` is one component:\n *\n * - `templates/components/node.yml` → component name `node`\n * - `templates/components/atlassian/twg.yml` → component name\n * `atlassian/twg`\n *\n * Sub-components live inside a directory whose name matches a parent\n * component (and which may itself have a top-level `<group>.yml`,\n * e.g. `atlassian.yml` for the \"both tools\" preset). The convention\n * is: a sub-component sets every sibling boolean option explicitly\n * (`true` for its own feature, `false` for the others), and the\n * merge applies OR-semantics on booleans so combining\n * `--with-features=atlassian/rovodev,atlassian/twg` correctly yields both\n * `true`. See `templates/components/README.md` for the full design.\n */\n\nconst CategorySchema = z.enum(['language', 'service', 'feature']);\nexport type ComponentCategory = z.infer<typeof CategorySchema>;\n\nconst FeatureContributionSchema = z.object({\n ref: z.string().regex(REGEX.featureRef),\n options: z.record(z.string(), FeatureOptionValueSchema).optional(),\n});\n\n/**\n * Shape validation for one component file. The contributes section is\n * deliberately narrow — exactly one of languages/services/features may\n * be set, and it must line up with the declared category.\n */\nconst ComponentFileSchema = z\n .object({\n displayName: z.string().min(1),\n description: z.string().min(1),\n category: CategorySchema,\n contributes: z.object({\n languages: z.array(z.string().min(1)).optional(),\n services: z.array(z.string().min(1)).optional(),\n features: z.array(FeatureContributionSchema).optional(),\n }),\n })\n .superRefine((data, ctx) => {\n const c = data.contributes;\n const filled = [\n c.languages && c.languages.length > 0 ? 'languages' : null,\n c.services && c.services.length > 0 ? 'services' : null,\n c.features && c.features.length > 0 ? 'features' : null,\n ].filter((x): x is string => x !== null);\n\n if (filled.length === 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message:\n 'contributes must set at least one of languages/services/features',\n });\n return;\n }\n if (filled.length > 1) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: `contributes must set exactly one of languages/services/features, got: ${filled.join(', ')}`,\n });\n return;\n }\n const expected =\n data.category === 'language'\n ? 'languages'\n : data.category === 'service'\n ? 'services'\n : 'features';\n if (filled[0] !== expected) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: `category '${data.category}' requires contributes.${expected}, got contributes.${filled[0]}`,\n });\n }\n });\n\nexport type ComponentFile = z.infer<typeof ComponentFileSchema>;\n\nexport interface Component {\n /** Catalog name, e.g. `node`, `atlassian/twg`. Always slash-form. */\n name: string;\n /** Absolute filesystem path of the source yml — useful for errors. */\n sourcePath: string;\n file: ComponentFile;\n}\n\n/**\n * Walk `templates/components/` recursively, parse every `.yml` file,\n * validate it, return as a name-keyed map. README files and other\n * non-yml files are silently skipped.\n *\n * Throws on the first invalid component file with a path-anchored\n * error — better to refuse than to load an inconsistent catalog.\n */\nexport async function loadComponentCatalog(\n rootDir: string = defaultComponentsDir(),\n): Promise<Map<string, Component>> {\n if (!existsSync(rootDir)) {\n return new Map();\n }\n const out = new Map<string, Component>();\n await walk(rootDir, rootDir, out);\n return out;\n}\n\nasync function walk(\n baseDir: string,\n currentDir: string,\n out: Map<string, Component>,\n): Promise<void> {\n const entries = await fs.readdir(currentDir, { withFileTypes: true });\n for (const entry of entries) {\n const full = path.join(currentDir, entry.name);\n if (entry.isDirectory()) {\n await walk(baseDir, full, out);\n continue;\n }\n if (!entry.isFile() || !entry.name.endsWith('.yml')) continue;\n const relative = path.relative(baseDir, full);\n const name = relative\n .replace(/\\.yml$/, '')\n .split(path.sep)\n .join('/');\n const text = await fs.readFile(full, 'utf8');\n let raw: unknown;\n try {\n raw = parseYaml(text);\n } catch (err) {\n throw new Error(\n `Failed to parse component ${name} (${full}): ${(err as Error).message}`,\n );\n }\n const parsed = ComponentFileSchema.safeParse(raw);\n if (!parsed.success) {\n const issues = parsed.error.issues\n .map((issue) => {\n const where = issue.path.length > 0 ? issue.path.join('.') : '(root)';\n return ` - ${where}: ${issue.message}`;\n })\n .join('\\n');\n throw new Error(`Invalid component ${name} (${full}):\\n${issues}`);\n }\n out.set(name, { name, sourcePath: full, file: parsed.data });\n }\n}\n\n/**\n * A `SolutionConfig`-shaped fragment produced by merging the\n * `contributes` of one or more components. Caller wraps this into\n * a full config (adds schemaVersion + name) before writing the yml.\n */\nexport interface MergedComponents {\n languages: string[];\n services: string[];\n features: Array<{\n ref: string;\n options: Record<string, string | number | boolean>;\n }>;\n}\n\n/**\n * Merge the contributions of the given components into a single\n * fragment.\n *\n * Rules:\n * - `languages`/`services`: concat + dedupe (insertion order kept\n * stable; first occurrence wins).\n * - `features`: deduped by `ref`. When two components contribute\n * the same ref, their options are merged with the per-key rules\n * below.\n * - Per-key feature option merge:\n * - booleans: OR (true wins)\n * - strings + numbers: later component overrides (rare in\n * practice — components should set activation flags, not\n * credentials; credentials come from monoceros-config.yml\n * defaults.features or the user editing the yml directly).\n *\n * The OR-merge for booleans is what makes\n * `--with-features=atlassian/rovodev,atlassian/twg` yield both `true` even\n * though each sub-component sets the sibling flag to `false`.\n */\n/**\n * One entry of the resolved-components list. The optional `version`\n * is the `<name>:<version>` suffix from the CLI flag; today it\n * only applies to language components (we append it to each\n * contributed language string so the scaffold passes it as the\n * upstream feature's `version` option). For other categories,\n * providing a version is a builder error and resolveComponents\n * rejects it up front.\n */\nexport interface ResolvedComponent {\n component: Component;\n version?: string;\n}\n\nexport function mergeComponents(\n resolved: Array<Component | ResolvedComponent>,\n): MergedComponents {\n const languages: string[] = [];\n const services: string[] = [];\n const featureByRef = new Map<\n string,\n { ref: string; options: Record<string, string | number | boolean> }\n >();\n\n for (const entry of resolved) {\n const c = isResolvedComponent(entry) ? entry.component : entry;\n const version = isResolvedComponent(entry) ? entry.version : undefined;\n const ct = c.file.contributes;\n for (const lang of ct.languages ?? []) {\n // Language components can carry a `:version` suffix from the\n // CLI. We emit `<lang>:<version>` in the final yml; the\n // scaffold parses it back to the upstream feature's\n // `version` option at apply time.\n const value = version !== undefined ? `${lang}:${version}` : lang;\n if (!languages.includes(value)) languages.push(value);\n }\n for (const svc of ct.services ?? []) {\n if (!services.includes(svc)) services.push(svc);\n }\n for (const f of ct.features ?? []) {\n const existing = featureByRef.get(f.ref);\n if (!existing) {\n featureByRef.set(f.ref, {\n ref: f.ref,\n options: { ...(f.options ?? {}) },\n });\n continue;\n }\n existing.options = mergeFeatureOptions(existing.options, f.options ?? {});\n }\n }\n\n return {\n languages,\n services,\n features: [...featureByRef.values()],\n };\n}\n\nfunction isResolvedComponent(\n x: Component | ResolvedComponent,\n): x is ResolvedComponent {\n return 'component' in x;\n}\n\nexport function mergeFeatureOptions(\n a: Record<string, string | number | boolean>,\n b: Record<string, string | number | boolean>,\n): Record<string, string | number | boolean> {\n const result = { ...a };\n for (const [key, valueB] of Object.entries(b)) {\n const valueA = result[key];\n if (typeof valueA === 'boolean' && typeof valueB === 'boolean') {\n result[key] = valueA || valueB;\n continue;\n }\n result[key] = valueB;\n }\n return result;\n}\n\n/**\n * Resolve `--with-*` names against the catalog. Accepts plain\n * names (`node`) and language-version pairs (`node:20`). Splits\n * the `:version` off, looks up the bare name in the catalog, and\n * carries the version forward only for language components — a\n * version on any other category is rejected with a clear error.\n *\n * Throws with the full list of unknown names so the builder fixes\n * them all at once rather than running into them one at a time.\n */\nexport function resolveComponents(\n catalog: Map<string, Component>,\n names: string[],\n): ResolvedComponent[] {\n const unknown: string[] = [];\n const out: ResolvedComponent[] = [];\n for (const raw of names) {\n const colon = raw.indexOf(':');\n const name = colon === -1 ? raw : raw.slice(0, colon);\n const version = colon === -1 ? undefined : raw.slice(colon + 1);\n\n const c = catalog.get(name);\n if (!c) {\n // The unknown-name message reports the form the user typed\n // (including the :version) so it's easy to spot the typo.\n unknown.push(raw);\n continue;\n }\n if (version !== undefined && c.file.category !== 'language') {\n throw new Error(\n `Component '${name}' is a ${c.file.category}, not a language — a ':${version}' suffix has no meaning here.`,\n );\n }\n out.push({ component: c, ...(version !== undefined ? { version } : {}) });\n }\n if (unknown.length > 0) {\n const available = [...catalog.keys()].sort();\n throw new Error(\n `Unknown component${unknown.length > 1 ? 's' : ''}: ${unknown.join(', ')}.\\n` +\n `Available: ${available.join(', ')}.`,\n );\n }\n return out;\n}\n","import { spawn } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { monocerosHome as defaultMonocerosHome } from '../config/paths.js';\n\n/**\n * Lifecycle of the shared Traefik singleton that fronts every dev-\n * container declaring `ports:` in its yml. See ADR 0007.\n *\n * Two functions externally:\n *\n * - `ensureProxy()` — idempotent; ensures the `monoceros-proxy`\n * docker network exists, the `<MONOCEROS_HOME>/traefik/dynamic/`\n * directory exists (and is user-owned, so subsequent file writes\n * don't fight a root-mkdir from docker), and a running container\n * named `monoceros-proxy` is up. Called from `apply`/`start` when\n * the container's yml declares at least one port.\n *\n * - `maybeStopProxy()` — counts the non-proxy containers attached to\n * the `monoceros-proxy` network. Zero ⇒ stop the singleton and\n * drop the network. Anything else ⇒ no-op. Called from\n * `stop`/`remove`.\n *\n * Test extension point: every docker invocation runs through the\n * `DockerExec` shape, which `ensureProxy`/`maybeStopProxy` accept as\n * an optional override. Tests inject a fake that records args and\n * returns canned stdout / exit codes.\n */\n\n/** Container name AND network name. Docker namespaces them separately. */\nexport const PROXY_CONTAINER_NAME = 'monoceros-proxy';\nexport const PROXY_NETWORK_NAME = 'monoceros-proxy';\n\n/** Traefik release we pin against. Bump deliberately, not floating. */\nexport const TRAEFIK_IMAGE = 'traefik:v3.3';\n\nexport interface DockerResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\nexport type DockerExec = (args: string[]) => Promise<DockerResult>;\n\n/**\n * Default docker invocation — exported so other modules in the proxy/\n * family (port-check, …) can share the same spawn semantics without\n * each having to re-implement child-process bookkeeping. Tests inject\n * their own `DockerExec` and never hit this path.\n */\nexport const defaultDockerExec: DockerExec = (args) => {\n return new Promise((resolve, reject) => {\n const child = spawn('docker', args, {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n let stdout = '';\n let stderr = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n child.on('error', reject);\n child.on('exit', (code) =>\n resolve({ stdout, stderr, exitCode: code ?? 0 }),\n );\n });\n};\n\nconst realDocker: DockerExec = defaultDockerExec;\n\nexport interface ProxyLogger {\n info: (message: string) => void;\n warn?: (message: string) => void;\n}\n\nexport interface ProxyOptions {\n /** Override the docker spawn shape (tests inject a fake). */\n docker?: DockerExec;\n /** Override the resolved MONOCEROS_HOME (tests inject a tmpdir). */\n monocerosHome?: string;\n /**\n * Host port Traefik binds. Read from `monoceros-config.yml`'s\n * `routing.hostPort` by callers; the proxy module itself just gets\n * a number and uses it for the `-p` mapping. Defaults to 80 (see\n * `config/global.ts → DEFAULT_PROXY_HOST_PORT`) when omitted.\n */\n hostPort?: number;\n logger?: ProxyLogger;\n}\n\n/** `<MONOCEROS_HOME>/traefik/dynamic/` — Traefik file-provider directory. */\nexport function proxyDynamicDir(home?: string): string {\n return path.join(home ?? defaultMonocerosHome(), 'traefik', 'dynamic');\n}\n\n/**\n * Bring the singleton + network up if they aren't already.\n *\n * Steps (all idempotent):\n * 1. mkdir -p on the dynamic-config dir (user-owned).\n * 2. `docker network create monoceros-proxy` if missing.\n * 3. If the container exists but is stopped → `docker start`.\n * If it doesn't exist → `docker run -d` with the canonical args.\n * If it's already running → no-op.\n *\n * Throws with a docker-cli-flavored error message on the first\n * failure. Callers that want to soft-fail (e.g. apply continuing\n * without proxy) must catch.\n */\nexport async function ensureProxy(opts: ProxyOptions = {}): Promise<void> {\n const docker = opts.docker ?? realDocker;\n const dyn = proxyDynamicDir(opts.monocerosHome);\n await fs.mkdir(dyn, { recursive: true });\n\n // Network. `inspect` exits 1 when missing (no JSON parse needed).\n const netInspect = await docker(['network', 'inspect', PROXY_NETWORK_NAME]);\n if (netInspect.exitCode !== 0) {\n const create = await docker(['network', 'create', PROXY_NETWORK_NAME]);\n if (create.exitCode !== 0) {\n throw new Error(\n `Could not create docker network ${PROXY_NETWORK_NAME}: ${create.stderr.trim() || `exit ${create.exitCode}`}`,\n );\n }\n }\n\n // Container. The Go-template format is `true`/`false` for the\n // boolean Running flag — easier to compare than parsing JSON.\n const state = await docker([\n 'inspect',\n '--format',\n '{{.State.Running}}',\n PROXY_CONTAINER_NAME,\n ]);\n if (state.exitCode === 0) {\n if (state.stdout.trim() === 'true') return; // already up\n const start = await docker(['start', PROXY_CONTAINER_NAME]);\n if (start.exitCode !== 0) {\n throw new Error(\n `Could not start existing ${PROXY_CONTAINER_NAME} container: ${start.stderr.trim() || `exit ${start.exitCode}`}`,\n );\n }\n return;\n }\n\n // Fresh container. The Traefik args declare a single HTTP entrypoint\n // `web` on :80 and turn on the file provider with watch=true so\n // dynamic-config writes propagate without a Traefik restart. The\n // docker provider is explicitly off — we route via file-provider\n // only, so container labels can't accidentally publish a route.\n // Default 80 — kept as a literal here to avoid a back-reference into\n // config/global.ts. The authoritative value (and the merge logic\n // with `monoceros-config.yml`) lives in config/global.ts.\n const hostPort = opts.hostPort ?? 80;\n const run = await docker([\n 'run',\n '-d',\n '--name',\n PROXY_CONTAINER_NAME,\n '--network',\n PROXY_NETWORK_NAME,\n '-p',\n `${hostPort}:80`,\n '-v',\n `${dyn}:/etc/traefik/dynamic:ro`,\n '--label',\n 'monoceros.role=proxy',\n TRAEFIK_IMAGE,\n '--entrypoints.web.address=:80',\n '--providers.file.directory=/etc/traefik/dynamic',\n '--providers.file.watch=true',\n '--providers.docker=false',\n '--api.dashboard=false',\n '--log.level=INFO',\n ]);\n if (run.exitCode !== 0) {\n throw new Error(\n `Could not start ${PROXY_CONTAINER_NAME}: ${run.stderr.trim() || `exit ${run.exitCode}`}`,\n );\n }\n opts.logger?.info(\n `Started ${PROXY_CONTAINER_NAME} (Traefik on :${hostPort}).`,\n );\n}\n\n/**\n * Stop and drop the singleton + network IFF no other container is\n * still attached. Safe to call from any lifecycle exit (`stop`,\n * `remove`, last `remove-port`). No-ops gracefully when:\n *\n * - the network doesn't exist (nothing to do)\n * - another devcontainer is still attached\n * - the container is already gone\n */\nexport async function maybeStopProxy(opts: ProxyOptions = {}): Promise<void> {\n const docker = opts.docker ?? realDocker;\n const logger = opts.logger;\n\n // Names of every container attached to the network. The Go-template\n // is one name per line. Empty stdout (or just newlines) means no\n // container at all is in the network.\n const inspect = await docker([\n 'network',\n 'inspect',\n PROXY_NETWORK_NAME,\n '--format',\n '{{range $k, $v := .Containers}}{{$v.Name}}\\n{{end}}',\n ]);\n if (inspect.exitCode !== 0) {\n // Network is gone or daemon unreachable — nothing to clean up.\n return;\n }\n const others = inspect.stdout\n .split('\\n')\n .map((n) => n.trim())\n .filter((n) => n.length > 0 && n !== PROXY_CONTAINER_NAME);\n if (others.length > 0) return; // other consumers still depend on it\n\n // Stop+rm the singleton. `rm -f` does both even if it's still up,\n // and shrugs at a missing container, which makes the call resilient\n // to manual `docker rm` from outside Monoceros.\n await docker(['rm', '-f', PROXY_CONTAINER_NAME]);\n\n // Drop the network. `network rm` errors when other containers are\n // still attached — we already filtered for that, so a non-zero exit\n // here is genuinely something else (e.g. permission denied). We log\n // the warn but don't throw; the next ensureProxy() recreates anyway.\n const netRm = await docker(['network', 'rm', PROXY_NETWORK_NAME]);\n if (netRm.exitCode !== 0) {\n logger?.warn?.(\n `Could not remove docker network ${PROXY_NETWORK_NAME}: ${netRm.stderr.trim() || `exit ${netRm.exitCode}`}`,\n );\n return;\n }\n logger?.info(\n `Stopped ${PROXY_CONTAINER_NAME} (no dev-containers with ports left).`,\n );\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { proxyDynamicDir } from './index.js';\n\n/**\n * Traefik file-provider dynamic configuration: one yml per container\n * under `<MONOCEROS_HOME>/traefik/dynamic/<name>.yml`. Traefik picks\n * up file changes within ~100 ms via `--providers.file.watch=true`,\n * so add-port / remove-port effects land hot without a restart of\n * either Traefik or the dev-container. See ADR 0007.\n *\n * Hostname layout (`*.localhost` resolves to 127.0.0.1 by RFC 6761):\n *\n * - `<name>.localhost` → the *first* declared port (default)\n * - `<name>-<port>.localhost` → that specific internal port\n *\n * Backend URL is always `http://<name>:<port>` — the container joins\n * the `monoceros-proxy` network under the yml name (set via\n * `--network-alias` for image-mode and a `networks.monoceros-proxy.aliases:`\n * entry for compose-mode in `create/scaffold.ts`).\n */\n\nexport interface DynamicConfigOptions {\n /** Override the resolved MONOCEROS_HOME (tests inject a tmpdir). */\n monocerosHome?: string;\n}\n\n/** Path the dynamic config for one container lives at. */\nexport function dynamicConfigPath(\n name: string,\n opts: DynamicConfigOptions = {},\n): string {\n return path.join(proxyDynamicDir(opts.monocerosHome), `${name}.yml`);\n}\n\n/**\n * Write the per-container dynamic config. The directory under\n * `<MONOCEROS_HOME>/traefik/dynamic/` is created if needed (matches\n * what ensureProxy does — calling either before the other is fine).\n *\n * Idempotent: a second call with the same `(name, ports)` produces a\n * byte-identical file. Returns the absolute path that was written.\n *\n * `ports` must be non-empty; the caller (add-port, apply) is\n * responsible for routing empty-list cases through\n * `removeDynamicConfig`.\n */\nexport async function writeDynamicConfig(\n name: string,\n ports: number[],\n opts: DynamicConfigOptions = {},\n): Promise<string> {\n if (ports.length === 0) {\n throw new Error(\n `writeDynamicConfig requires at least one port. For empty port lists, call removeDynamicConfig(${JSON.stringify(name)}).`,\n );\n }\n const dir = proxyDynamicDir(opts.monocerosHome);\n await fs.mkdir(dir, { recursive: true });\n const file = path.join(dir, `${name}.yml`);\n await fs.writeFile(file, renderDynamicConfig(name, ports), 'utf8');\n return file;\n}\n\n/** Drop the dynamic config for `name`. No-op when the file is absent. */\nexport async function removeDynamicConfig(\n name: string,\n opts: DynamicConfigOptions = {},\n): Promise<void> {\n const file = path.join(proxyDynamicDir(opts.monocerosHome), `${name}.yml`);\n await fs.rm(file, { force: true });\n}\n\n/**\n * Render the yml body. Pure function — no I/O — so unit tests can\n * snapshot the output cheaply, and `writeDynamicConfig` is the only\n * one that touches the filesystem.\n *\n * First port doubles as the container's default URL via\n * `<name>.localhost`; every port also gets its explicit\n * `<name>-<port>.localhost` route. Both routers point at the same\n * load-balancer service backend.\n */\nexport function renderDynamicConfig(name: string, ports: number[]): string {\n const lines: string[] = [];\n lines.push('# Generated by Monoceros — do not edit by hand.');\n lines.push(`# Container: ${name}`);\n lines.push(`# Ports: ${ports.join(', ')}`);\n lines.push('# Traefik file-provider re-reads this on change (~100 ms);');\n lines.push(\n '# to change routing, edit container-configs/' + name + '.yml or use',\n );\n lines.push('# `monoceros add-port` / `monoceros remove-port`.');\n lines.push('http:');\n lines.push(' routers:');\n ports.forEach((port, idx) => {\n const router = `${name}-${port}`;\n const hostExplicit = `${name}-${port}.localhost`;\n // The first declared port doubles as the container default; its\n // router matches both the explicit `<name>-<port>.localhost` and\n // the bare `<name>.localhost`. The others match only their\n // explicit hostname.\n const rule =\n idx === 0\n ? `\"Host(\\`${name}.localhost\\`) || Host(\\`${hostExplicit}\\`)\"`\n : `\"Host(\\`${hostExplicit}\\`)\"`;\n lines.push(` ${router}:`);\n lines.push(` rule: ${rule}`);\n lines.push(` service: ${router}`);\n lines.push(' entryPoints:');\n lines.push(' - web');\n });\n lines.push(' services:');\n for (const port of ports) {\n const svc = `${name}-${port}`;\n lines.push(` ${svc}:`);\n lines.push(' loadBalancer:');\n lines.push(' servers:');\n lines.push(` - url: \"http://${name}:${port}\"`);\n }\n return lines.join('\\n') + '\\n';\n}\n\n/**\n * URLs the proxy exposes for a container. Pure derivation — used by\n * the `monoceros port` command and by `add-port` to print the\n * resulting routes after a write. `hostPort` (default 80) gets\n * suffixed to the URL only when it's non-default — the URLs stay\n * clean for the common case.\n */\nexport interface ProxyUrl {\n /** Internal container port. */\n port: number;\n /** `http://<name>-<port>.localhost[:<hostPort>]`. */\n url: string;\n /** True for the first port: also reachable at `<name>.localhost`. */\n isDefault: boolean;\n}\n\nexport function proxyUrlsFor(\n name: string,\n ports: number[],\n hostPort: number = 80,\n): ProxyUrl[] {\n const portSuffix = hostPort === 80 ? '' : `:${hostPort}`;\n return ports.map((port, idx) => ({\n port,\n url: `http://${name}-${port}.localhost${portSuffix}`,\n isDefault: idx === 0,\n }));\n}\n","import { Socket } from 'node:net';\nimport {\n PROXY_CONTAINER_NAME,\n defaultDockerExec,\n type DockerExec,\n type ProxyLogger,\n} from './index.js';\n\n/**\n * Pre-flight check on the host port the Traefik singleton would bind.\n *\n * Why this exists: `docker run -p <port>:80` fails with a cryptic\n * `bind: address already in use` line buried inside docker's own\n * stderr, and the builder then has no idea (a) which port, (b) who's\n * holding it, (c) how to choose a different one. Catching this before\n * the docker call lets us point at the exact remedy.\n *\n * Two paths:\n *\n * 1. If the `monoceros-proxy` container is already running, the\n * port is \"in use\" by us — nothing to check. Skip silently.\n *\n * 2. Otherwise, try to TCP-connect to `127.0.0.1:<port>`. Something\n * accepting the connection ⇒ port is taken; ECONNREFUSED ⇒\n * nobody's listening and the port is (probably) free for Docker.\n *\n * We deliberately do NOT try to bind the port ourselves. On Linux,\n * binding ports <1024 requires CAP_NET_BIND_SERVICE — which the\n * unprivileged Node process running monoceros doesn't have, even when\n * the docker daemon does. The bind probe would EACCES with our own\n * lack of privilege, not with someone actually holding the port. The\n * connect probe sidesteps that: connects don't need a privileged port.\n *\n * Trade-off: connect catches anything that's actively LISTEN'ing on\n * 127.0.0.1 or 0.0.0.0 (system nginx, Pi-hole, …) — the cases that\n * realistically conflict with Docker's `-p 80:80`. If something binds\n * only on a specific external interface (192.168.x.x:80) and refuses\n * loopback, the connect probe sees ECONNREFUSED and lets Docker\n * surface its own error — which then carries our actionable hint via\n * the error-message wrapping in apply/.\n *\n * The probe is plumbed through `PortProbe` so tests can inject a stub.\n */\n\nexport type PortProbe = (port: number) => Promise<PortProbeResult>;\n\nexport type PortProbeResult =\n | { ok: true }\n | { ok: false; code: string; message: string };\n\nconst CONNECT_TIMEOUT_MS = 750;\n\nconst realPortProbe: PortProbe = (port) => {\n return new Promise((resolve) => {\n const socket = new Socket();\n let settled = false;\n const settle = (result: PortProbeResult) => {\n if (settled) return;\n settled = true;\n socket.destroy();\n resolve(result);\n };\n socket.setTimeout(CONNECT_TIMEOUT_MS);\n socket.once('connect', () => {\n // Something accepted our connection → the port is held.\n settle({\n ok: false,\n code: 'EADDRINUSE',\n message: `another process is listening on ${port}`,\n });\n });\n socket.once('timeout', () => {\n // No SYN-ACK within the timeout. Could be a firewalled bind or\n // a daemon not on loopback; treat as \"probably free\" and let\n // Docker speak up if it disagrees.\n settle({ ok: true });\n });\n socket.once('error', (err: NodeJS.ErrnoException) => {\n const code = err.code ?? 'UNKNOWN';\n if (code === 'ECONNREFUSED') {\n // Nobody listening — the typical \"port is free\" signal.\n settle({ ok: true });\n } else {\n // Other errors (EHOSTUNREACH, ENETDOWN, …) aren't our bind\n // story. Don't pretend we know — surface verbatim so the\n // builder sees what their network is doing.\n settle({\n ok: false,\n code,\n message: err.message,\n });\n }\n });\n socket.connect(port, '127.0.0.1');\n });\n};\n\nexport interface PreflightHostPortOptions {\n /**\n * Override the docker exec used to check whether monoceros-proxy is\n * already running. Tests inject a fake.\n */\n docker?: DockerExec;\n /** Override the bind probe. Tests inject a fake. */\n portProbe?: PortProbe;\n logger?: ProxyLogger;\n}\n\n/**\n * Ensure `hostPort` is bindable, OR explain in detail why it isn't.\n *\n * - Returns silently when the port is bindable (or already held by\n * the monoceros-proxy container).\n * - Throws an Error with a formatted, actionable message otherwise.\n *\n * The caller (apply / start / add-port hot-path) is expected to\n * `try { await preflightHostPort(...) } catch { print + exit }` —\n * i.e. abort the command cleanly with the rendered hint, not let\n * the docker `bind: address already in use` slip through.\n */\nexport async function preflightHostPort(\n hostPort: number,\n opts: PreflightHostPortOptions = {},\n): Promise<void> {\n // Is monoceros-proxy itself the current holder? If so, ensureProxy\n // will be a no-op and the port-check has nothing to tell us.\n // ALWAYS run this check (not just when opts.docker is overridden) —\n // otherwise the bind probe would fail on Traefik's own port and\n // the builder would see \"port 80 held by another process\" pointing\n // at our own running container.\n const docker = opts.docker ?? defaultDockerExec;\n const inspect = await docker([\n 'inspect',\n '--format',\n '{{.State.Running}}',\n PROXY_CONTAINER_NAME,\n ]);\n if (inspect.exitCode === 0 && inspect.stdout.trim() === 'true') {\n return;\n }\n\n const probe = opts.portProbe ?? realPortProbe;\n const result = await probe(hostPort);\n if (result.ok) return;\n\n // EADDRINUSE is the case we want to dress up nicely. Anything else\n // (EACCES on Linux without CAP_NET_BIND_SERVICE for unprivileged\n // ports < 1024, etc.) gets the message verbatim but framed the\n // same way — the remedy is the same: pick a different port.\n throw new Error(\n formatHostPortHeldError(hostPort, result.code, result.message),\n );\n}\n\nexport function formatHostPortHeldError(\n hostPort: number,\n code: string,\n systemMessage: string,\n): string {\n const isInUse = code === 'EADDRINUSE';\n const lines: string[] = [];\n if (isInUse) {\n lines.push(`Host port ${hostPort} is already in use by another process.`);\n lines.push('');\n lines.push(`Monoceros needs that port for its Traefik proxy (the thing`);\n lines.push(`that routes <name>.localhost / <name>-<port>.localhost to`);\n lines.push(`your dev-container). Two ways out:`);\n lines.push('');\n lines.push(' 1) Recommended: free the port.');\n lines.push(' Identify the process holding it:');\n lines.push(` sudo lsof -iTCP:${hostPort} -sTCP:LISTEN -n -P`);\n lines.push(` # or: sudo ss -tlnp | grep \":${hostPort}\\\\b\"`);\n lines.push(' Then stop or reconfigure that service.');\n lines.push('');\n lines.push(' 2) Move Monoceros off port 80. Edit (or create)');\n lines.push(' ~/.monoceros/monoceros-config.yml and add:');\n lines.push('');\n lines.push(' schemaVersion: 1');\n lines.push(' routing:');\n lines.push(' hostPort: 8080 # any free port');\n lines.push('');\n lines.push(' URLs will become http://<name>.localhost:8080/.');\n lines.push('');\n lines.push(`Aborting — re-run after the conflict is resolved.`);\n } else {\n lines.push(`Cannot reach host port ${hostPort}: ${systemMessage}`);\n lines.push('');\n lines.push(`This is not the typical \"port already in use\" case —`);\n lines.push(`Monoceros's pre-flight uses a TCP-connect probe (not a`);\n lines.push(`bind), so EACCES / privileged-port errors normally don't`);\n lines.push(`appear here. Most likely something on your host network`);\n lines.push(`stack (firewall, network namespace, …) is interfering with`);\n lines.push(`loopback connects.`);\n lines.push('');\n lines.push('Workaround: move Monoceros off this port by setting');\n lines.push('`routing.hostPort` in ~/.monoceros/monoceros-config.yml.');\n lines.push('');\n lines.push(`Aborting — re-run after the issue is resolved.`);\n }\n return lines.join('\\n');\n}\n","// Catalogs of supported language toolchains and backing services for\n// the yml profile. Curated whitelists keep the surface small and\n// reviewable; unknown values are rejected up front rather than passed\n// through to devcontainer / compose.\n\nimport type { ServiceHealthcheck, ServiceObject } from '../config/schema.js';\nimport type { ResolvedService } from './types.js';\n\n// Monoceros runtime image — thin layer on top of Microsoft's\n// typescript-node base (see images/runtime/Dockerfile). The default\n// points at the floating major tag on GHCR, so an `apply` from a\n// fresh install pulls a published image without further setup.\n//\n// Contributors who are iterating on the runtime image itself\n// (`pnpm image:build` → `monoceros-runtime:dev`) can override this\n// via the `MONOCEROS_BASE_IMAGE_OVERRIDE` env var to point at their\n// local tag without editing source. Empty or whitespace-only values\n// are ignored so an accidentally-set-blank var doesn't break apply.\nconst DEFAULT_BASE_IMAGE = 'ghcr.io/getmonoceros/monoceros-runtime:1';\nconst override = process.env.MONOCEROS_BASE_IMAGE_OVERRIDE?.trim();\nexport const BASE_IMAGE =\n override && override.length > 0 ? override : DEFAULT_BASE_IMAGE;\n\nexport interface LanguageEntry {\n id: string;\n feature: string;\n}\n\n// `node` is included in the base runtime image, so the bare entry\n// `languages: [node]` is accepted as input but installs nothing\n// extra. Versioned node — `node:20` — bypasses the builtin set and\n// goes through the upstream feature like the other languages,\n// because the base image's node version (22) isn't selectable\n// otherwise.\nexport const BUILTIN_LANGUAGES = new Set(['node']);\n\nexport const LANGUAGE_CATALOG: Readonly<Record<string, LanguageEntry>> = {\n node: { id: 'node', feature: 'ghcr.io/devcontainers/features/node:1' },\n python: { id: 'python', feature: 'ghcr.io/devcontainers/features/python:1' },\n java: { id: 'java', feature: 'ghcr.io/devcontainers/features/java:1' },\n go: { id: 'go', feature: 'ghcr.io/devcontainers/features/go:1' },\n rust: { id: 'rust', feature: 'ghcr.io/devcontainers/features/rust:1' },\n dotnet: { id: 'dotnet', feature: 'ghcr.io/devcontainers/features/dotnet:2' },\n};\n\n/**\n * Language entries in a container yml may carry an optional\n * version suffix: `java:17`, `node:20`. The suffix is anything\n * the upstream devcontainer feature accepts as its `version`\n * option (typically `latest`, a major like `17`, or an exact\n * semver like `3.12.1`).\n */\nexport const LANGUAGE_SPEC_RE = /^([a-z][a-z0-9-]*)(?::([A-Za-z0-9._-]+))?$/;\n\nexport interface LanguageSpec {\n name: string;\n version?: string;\n}\n\n/**\n * Split a yml language entry into name + optional version. Returns\n * `null` when the input is not a valid language spec. Callers use\n * that null to surface a schema error.\n */\nexport function parseLanguageSpec(spec: string): LanguageSpec | null {\n const m = LANGUAGE_SPEC_RE.exec(spec);\n if (!m) return null;\n return { name: m[1]!, ...(m[2] !== undefined ? { version: m[2] } : {}) };\n}\n\nexport interface ServiceEntry {\n id: string;\n image: string;\n /**\n * Literal dev-default values for the service's env vars. These are\n * rendered as `${KEY}` *placeholders* into the yml (expandCuratedService)\n * and seeded as `KEY=<default>` into `<name>.env` (curatedServiceEnvDefaults),\n * so the yml is shareable without baking credentials in while the\n * connection string stays predictable out of the box.\n */\n env?: Readonly<Record<string, string>>;\n /**\n * Readiness probe. Curated services ship one so the workspace's\n * `depends_on` gates on `service_healthy` (actually accepting\n * connections) rather than just `service_started`. `${VAR}` in the\n * test resolves from `<name>.env` at apply time like any other field.\n */\n healthcheck?: ServiceHealthcheck;\n /**\n * Container-side mount target for the service's persistent data.\n * Monoceros bind-mounts this onto `<container-dir>/data/<id>/` on\n * the host so DB content is visible in the host filesystem\n * (browsable, backupable, removable with the usual tools instead\n * of `docker volume ...`). See ADR 0003 for the per-container\n * state-model the data dir slots into.\n */\n dataMount?: string;\n /**\n * Default in-container port the service listens on. Used by\n * `monoceros tunnel <name> <service>` to resolve the service-name\n * to a port without an extra CLI argument. See ADR 0009.\n */\n defaultPort: number;\n}\n\n// The `monoceros` user/password/db below are deliberate dev-only\n// defaults, not secrets. A curated service renders its env as `${KEY}`\n// placeholders into the yml and seeds these literals into the gitignored\n// `<name>.env`, so the yml stays shareable while the connection string\n// is predictable out of the box — any builder running this workbench\n// knows it at a glance:\n//\n// postgresql://monoceros:monoceros@postgres:5432/monoceros\n// mysql://monoceros:monoceros@mysql:3306/monoceros\n//\n// To use a real password, change the value in `<name>.env` (it never\n// leaves the host, never rides along when the yml is shared). Because\n// the default isn't a secret, the secret-masking layer\n// (util/mask-secrets.ts) doesn't and shouldn't mask it.\nexport const SERVICE_CATALOG: Readonly<Record<string, ServiceEntry>> = {\n postgres: {\n id: 'postgres',\n image: 'postgres:18',\n env: {\n POSTGRES_USER: 'monoceros',\n POSTGRES_PASSWORD: 'monoceros',\n POSTGRES_DB: 'monoceros',\n },\n healthcheck: {\n test: [\n 'CMD',\n 'pg_isready',\n '-U',\n '${POSTGRES_USER}',\n '-d',\n '${POSTGRES_DB}',\n ],\n interval: '10s',\n timeout: '5s',\n retries: 5,\n },\n // Postgres 18+ stores data under /var/lib/postgresql/<major>/, so\n // the recommended mount is the parent directory; pre-18 used\n // /var/lib/postgresql/data directly. See\n // https://github.com/docker-library/postgres/pull/1259.\n dataMount: '/var/lib/postgresql',\n defaultPort: 5432,\n },\n mysql: {\n id: 'mysql',\n image: 'mysql:8',\n env: {\n MYSQL_ROOT_PASSWORD: 'monoceros',\n MYSQL_DATABASE: 'monoceros',\n },\n healthcheck: {\n test: [\n 'CMD',\n 'mysqladmin',\n 'ping',\n '-h',\n '127.0.0.1',\n '-u',\n 'root',\n '-p${MYSQL_ROOT_PASSWORD}',\n ],\n interval: '10s',\n timeout: '5s',\n retries: 5,\n },\n dataMount: '/var/lib/mysql',\n defaultPort: 3306,\n },\n redis: {\n id: 'redis',\n image: 'redis:8',\n healthcheck: {\n test: ['CMD', 'redis-cli', 'ping'],\n interval: '10s',\n timeout: '5s',\n retries: 5,\n },\n dataMount: '/data',\n defaultPort: 6379,\n },\n};\n\nexport function knownLanguages(): string[] {\n return [...BUILTIN_LANGUAGES, ...Object.keys(LANGUAGE_CATALOG)].sort();\n}\n\nexport function knownServices(): string[] {\n return Object.keys(SERVICE_CATALOG).sort();\n}\n\n/**\n * Normalize a `services:` object to a `ResolvedService` — fills the two\n * fields the scaffold treats as always-present (env, volumes) with their\n * empty defaults. `${VAR}` references in env/command pass through\n * untouched; they're resolved against `<name>.env` at apply time\n * (config/env-file.ts).\n */\nexport function resolveService(entry: ServiceObject): ResolvedService {\n return {\n name: entry.name,\n image: entry.image,\n ...(entry.port !== undefined ? { port: entry.port } : {}),\n env: entry.env ? { ...entry.env } : {},\n volumes: entry.volumes ? [...entry.volumes] : [],\n ...(entry.healthcheck ? { healthcheck: entry.healthcheck } : {}),\n ...(entry.restart ? { restart: entry.restart } : {}),\n ...(entry.command ? { command: entry.command } : {}),\n };\n}\n\n/** Whether `name` is a known curated catalog service. */\nexport function isCuratedService(name: string): boolean {\n return Object.prototype.hasOwnProperty.call(SERVICE_CATALOG, name);\n}\n\n/**\n * Expand a curated catalog name into a full `ServiceObject` — the\n * init-sugar form written into the yml so the builder sees (and can\n * edit) every field. Env values render as `${KEY}` placeholders (their\n * literal defaults are seeded into `<name>.env` via\n * `curatedServiceEnvDefaults`), so the yml is shareable without baking\n * credentials in. Throws if `name` isn't curated.\n */\nexport function expandCuratedService(name: string): ServiceObject {\n const def = SERVICE_CATALOG[name];\n if (!def) {\n throw new Error(\n `Unknown service '${name}'. Known catalog services: ${knownServices().join(', ')}.`,\n );\n }\n return {\n name: def.id,\n image: def.image,\n port: def.defaultPort,\n ...(def.env\n ? {\n env: Object.fromEntries(\n Object.keys(def.env).map((k) => [k, `\\${${k}}`]),\n ),\n }\n : {}),\n ...(def.dataMount ? { volumes: [`data:${def.dataMount}`] } : {}),\n ...(def.healthcheck ? { healthcheck: def.healthcheck } : {}),\n restart: 'unless-stopped',\n };\n}\n\n/**\n * The literal `KEY=<default>` values to seed into `<name>.env` for a\n * curated service's `${KEY}` env placeholders — the same dev-defaults\n * the catalog declares. Empty for services without env (redis).\n * `init` and `add-service` upsert these so the builder gets a working\n * container without filling anything, yet can change a value (e.g. a\n * real password) in one gitignored place. Returns `{}` for non-curated\n * names.\n */\nexport function curatedServiceEnvDefaults(\n name: string,\n): Record<string, string> {\n const def = SERVICE_CATALOG[name];\n return def?.env ? { ...def.env } : {};\n}\n\n/**\n * Derive a compose service name from an image ref. Takes the last\n * path segment, strips the tag/digest, lowercases and sanitises:\n * rustfs/rustfs:latest → rustfs\n * postgres:16-alpine → postgres\n * ghcr.io/foo/bar:1 → bar\n * ghcr.io:5000/x/app → app\n */\nexport function deriveServiceName(image: string): string {\n const lastSegment = image.split('/').pop() ?? image;\n const noTag = lastSegment.split('@')[0]!.split(':')[0]!;\n return noTag.toLowerCase().replace(/[^a-z0-9_-]/g, '-');\n}\n","import type { ServiceObject } from '../config/schema.js';\n\n/**\n * Renders a `services:` entry as YAML lines for the **map body** at\n * column 0 (no leading `- `). Two consumers share this:\n * - the init generator indents the body into a sequence item;\n * - `add-service` parses the body into a node and appends it to the\n * services seq (comments and all).\n *\n * Curated services render fully active (every catalog default visible\n * and editable). Custom images render `name` + `image` active plus a\n * commented scaffold of the optional fields — Monoceros can't know a\n * non-curated image's env/ports/volumes, so it shows the knobs rather\n * than guessing.\n */\n\n/** Full active map body for a known ServiceObject (curated, expanded). */\nexport function renderServiceObjectBody(svc: ServiceObject): string[] {\n const lines: string[] = [`name: ${svc.name}`, `image: ${svc.image}`];\n if (svc.port !== undefined) lines.push(`port: ${svc.port}`);\n if (svc.env && Object.keys(svc.env).length > 0) {\n lines.push('env:');\n for (const [k, v] of Object.entries(svc.env)) {\n lines.push(` ${k}: ${v}`);\n }\n }\n if (svc.volumes && svc.volumes.length > 0) {\n lines.push('volumes:');\n for (const vol of svc.volumes) lines.push(` - ${vol}`);\n }\n if (svc.restart) lines.push(`restart: ${svc.restart}`);\n if (svc.command !== undefined) lines.push(`command: ${svc.command}`);\n if (svc.healthcheck) {\n lines.push('healthcheck:');\n const test = svc.healthcheck.test;\n lines.push(\n Array.isArray(test)\n ? ` test: [${test.map((t) => JSON.stringify(t)).join(', ')}]`\n : ` test: ${test}`,\n );\n if (svc.healthcheck.interval)\n lines.push(` interval: ${svc.healthcheck.interval}`);\n if (svc.healthcheck.timeout)\n lines.push(` timeout: ${svc.healthcheck.timeout}`);\n if (svc.healthcheck.retries !== undefined)\n lines.push(` retries: ${svc.healthcheck.retries}`);\n if (svc.healthcheck.startPeriod)\n lines.push(` startPeriod: ${svc.healthcheck.startPeriod}`);\n }\n return lines;\n}\n\n/**\n * A custom (non-curated) image entry: `name` + `image` active, the rest\n * as a commented scaffold so the builder sees the fields Monoceros can't\n * infer. Returns the active body lines plus the scaffold as a YAML\n * `comment` string (no leading `#` — the serializer adds it; attaching\n * it as `node.comment` is the only way the comment survives being moved\n * into the services sequence).\n */\nexport function renderCustomService(\n name: string,\n image: string,\n): { bodyLines: string[]; comment: string } {\n const bodyLines = [`name: ${name}`, `image: ${image}`];\n const comment = [\n ' port: 8080 # in-container port → `monoceros tunnel`',\n ' env: # values resolved from <name>.env',\n ' KEY: ${SOME_VAR}',\n ' volumes:',\n ` - data:/data # persistent host bind-mount under data/${name}`,\n ' - rel/host/path:/in/container:ro',\n ' healthcheck:',\n ' test: curl -f http://localhost:8080/health',\n ' restart: unless-stopped',\n ].join('\\n');\n return { bodyLines, comment };\n}\n\n/**\n * One-line builder-facing hint printed after a custom service is added,\n * pointing at the commented scaffold the builder needs to fill in.\n */\nexport function customServiceHint(name: string): string {\n return (\n `'${name}' is a custom image — Monoceros doesn't know its env, ports or volumes. ` +\n `Review the commented block under services[].${name} in the yml and fill in what the image needs.`\n );\n}\n","import { existsSync, readFileSync, promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { workbenchCheckoutRoot } from '../config/paths.js';\nimport { matchMonocerosFeature } from '../util/ref.js';\nimport {\n BASE_IMAGE,\n BUILTIN_LANGUAGES,\n LANGUAGE_CATALOG,\n knownLanguages,\n parseLanguageSpec,\n} from './catalog.js';\nimport type { CreateOptions } from './types.js';\n\n// Debian/Ubuntu apt package name rules: start with alphanumeric, then\n// alphanumerics + `.+-` are allowed. We intentionally don't allow shell\n// metacharacters (`;`, `&`, `|`, `$`, `(`, …) so a typo can't smuggle\n// arbitrary shell into the apt-packages feature config.\nconst APT_PACKAGE_NAME_RE = /^[a-z0-9][a-z0-9.+-]*$/;\n\n// Devcontainer feature refs are OCI-style:\n// <registry>/<namespace>/<feature>:<tag>\n// e.g. ghcr.io/devcontainers/features/python:1\n// ghcr.io/getmonoceros/monoceros-features/claude-code:1\nconst FEATURE_REF_RE = /^[a-z0-9.-]+(\\/[a-z0-9._-]+)+:[a-z0-9._-]+$/;\n\n// Install URLs must be https:// (no plain http, no other schemes) and\n// contain only URL-safe characters. We deliberately reject shell\n// metacharacters even inside a query string — the URL is embedded into\n// a generated bash script, and a stray `$` or backtick would be a\n// shell-injection vector.\nconst INSTALL_URL_RE = /^https:\\/\\/[A-Za-z0-9.\\-_~/:?#[\\]@!&'()*+,;=%]+$/;\n\n// Git URLs: covers HTTPS, SSH (`git@host:path/repo.git`), and\n// `ssh://`/`git://` schemes. Permissive but no shell metacharacters.\nconst REPO_URL_RE = /^[A-Za-z0-9@:/+_~.#=&?-]+$/;\n\n// Repo destination = path under `projects/`. Allows nested subfolders\n// (`apps/web`) via `/`; segments use `[A-Za-z0-9._-]` (same charset as\n// a leaf folder name). `.` / `..` segments are rejected separately\n// because the regex alone allows pure-dot segments.\nconst REPO_PATH_RE = /^[A-Za-z0-9._-]+(\\/[A-Za-z0-9._-]+)*$/;\n\n/**\n * Derive a repo name from its URL.\n *\n * `git@github.com:foo/bar.git` → `bar`\n * `https://github.com/foo/bar.git` → `bar`\n * `https://github.com/foo/bar` → `bar`\n * `ssh://git@host:22/foo/bar.git` → `bar`\n */\nexport function deriveRepoName(url: string): string {\n const lastSep = Math.max(url.lastIndexOf('/'), url.lastIndexOf(':'));\n const tail = url.slice(lastSep + 1);\n return tail.replace(/\\.git$/, '');\n}\n\nexport function validateOptions(opts: CreateOptions): void {\n if (!opts.name || !/^[a-zA-Z0-9._-]+$/.test(opts.name)) {\n throw new Error(\n `Invalid solution name: ${JSON.stringify(opts.name)}. Use letters, digits, '.', '_' or '-'.`,\n );\n }\n for (const langSpec of opts.languages) {\n const parsed = parseLanguageSpec(langSpec);\n if (!parsed) {\n throw new Error(\n `Invalid language spec: ${JSON.stringify(langSpec)}. Expected '<name>' or '<name>:<version>'.`,\n );\n }\n if (!BUILTIN_LANGUAGES.has(parsed.name) && !LANGUAGE_CATALOG[parsed.name]) {\n throw new Error(\n `Unknown language: ${parsed.name}. Known: ${knownLanguages().join(', ')}.`,\n );\n }\n }\n // Services arrive here already resolved (curated strings expanded\n // against the catalog, objects taken as-is — see resolveService).\n // What's left to enforce are the cross-service invariants the schema\n // can't see: each name is unique and none collides with the reserved\n // `workspace` compose service.\n const seenServiceNames = new Set<string>();\n for (const svc of opts.services) {\n if (!svc.image) {\n throw new Error(\n `Service ${JSON.stringify(svc.name)} has no image. Every service needs an 'image:'.`,\n );\n }\n if (svc.name === 'workspace') {\n throw new Error(\n `Invalid service name 'workspace': it collides with the reserved devcontainer workspace service. Pick another name.`,\n );\n }\n if (seenServiceNames.has(svc.name)) {\n throw new Error(\n `Duplicate service name: ${JSON.stringify(svc.name)}. Each services[] entry must have a unique name.`,\n );\n }\n seenServiceNames.add(svc.name);\n }\n for (const pkg of opts.aptPackages ?? []) {\n if (!APT_PACKAGE_NAME_RE.test(pkg)) {\n throw new Error(\n `Invalid apt package name: ${JSON.stringify(pkg)}. Expected lowercase alphanumeric plus '.+-'.`,\n );\n }\n }\n for (const ref of Object.keys(opts.features ?? {})) {\n if (!FEATURE_REF_RE.test(ref)) {\n throw new Error(\n `Invalid devcontainer feature ref: ${JSON.stringify(ref)}. Expected OCI-image-style ref like 'ghcr.io/devcontainers/features/<name>:<tag>'.`,\n );\n }\n }\n for (const url of opts.installUrls ?? []) {\n if (!INSTALL_URL_RE.test(url)) {\n throw new Error(\n `Invalid install URL: ${JSON.stringify(url)}. Must start with 'https://' and contain only URL-safe characters (no shell metacharacters).`,\n );\n }\n }\n const seenRepoPaths = new Set<string>();\n for (const repo of opts.repos ?? []) {\n if (!REPO_URL_RE.test(repo.url)) {\n throw new Error(\n `Invalid repo URL: ${JSON.stringify(repo.url)}. Use HTTPS or SSH/git@ form; no shell metacharacters.`,\n );\n }\n if (!REPO_PATH_RE.test(repo.path)) {\n throw new Error(\n `Invalid repo path: ${JSON.stringify(repo.path)}. Use letters/digits/'._-', forward slashes for nested folders, no leading or trailing slash.`,\n );\n }\n if (repo.path.split('/').some((seg) => seg === '..' || seg === '.')) {\n throw new Error(\n `Invalid repo path: ${JSON.stringify(repo.path)}. Path segments cannot be \".\" or \"..\".`,\n );\n }\n if (seenRepoPaths.has(repo.path)) {\n throw new Error(\n `Duplicate repo path: ${JSON.stringify(repo.path)}. Each projects/<path> folder must be unique — pass --path to disambiguate.`,\n );\n }\n seenRepoPaths.add(repo.path);\n }\n}\n\n// Normalize: dedupe + sort + drop postgres from compose services when an\n// external --postgres-url is provided.\nexport function normalizeOptions(opts: CreateOptions): CreateOptions {\n const languages = [...new Set(opts.languages)].sort();\n // Dedupe services by name (last write wins) and sort by name so the\n // generated compose/devcontainer output is deterministic regardless\n // of yml order. An external `--postgres-url` drops a curated postgres\n // service — the workspace talks to the external DB instead.\n const serviceByName = new Map<string, (typeof opts.services)[number]>();\n for (const svc of opts.services) {\n if (opts.postgresUrl && svc.name === 'postgres') continue;\n serviceByName.set(svc.name, svc);\n }\n const services = [...serviceByName.values()].sort((a, b) =>\n a.name.localeCompare(b.name),\n );\n const aptPackages = [...new Set(opts.aptPackages ?? [])].sort();\n // Sort feature refs alphabetically so devcontainer.json + stack.json\n // output is deterministic regardless of insertion order.\n const features = opts.features\n ? Object.fromEntries(\n Object.entries(opts.features).sort(([a], [b]) => a.localeCompare(b)),\n )\n : undefined;\n // Install URLs preserve insertion order (installs may depend on each\n // other), but we deduplicate to keep stack.json stable across re-adds.\n const installUrls = opts.installUrls\n ? [...new Set(opts.installUrls)]\n : undefined;\n // Repos: preserve insertion order, dedupe by (url, name, branch)\n // signature — same triple twice is a no-op, different triples\n // coexist. (Same name with different URL is a validation error\n // in validateOptions, not silently merged here.)\n const repos = opts.repos\n ? Array.from(\n new Map(opts.repos.map((r) => [`${r.url}\u0001${r.path}`, r])).values(),\n )\n : undefined;\n // Ports: preserve insertion order — the first entry doubles as the\n // default route under `<name>.localhost`, so reordering would\n // silently change which app the bare hostname points at. Dedupe to\n // keep the dynamic config and `forwardPorts` deterministic.\n const ports = opts.ports ? [...new Set(opts.ports)] : undefined;\n return {\n name: opts.name,\n languages,\n services,\n postgresUrl: opts.postgresUrl,\n ...(aptPackages.length > 0 ? { aptPackages } : {}),\n ...(features && Object.keys(features).length > 0 ? { features } : {}),\n ...(installUrls && installUrls.length > 0 ? { installUrls } : {}),\n ...(repos && repos.length > 0 ? { repos } : {}),\n ...(ports && ports.length > 0 ? { ports } : {}),\n ...(opts.vscodeAutoForward !== undefined\n ? { vscodeAutoForward: opts.vscodeAutoForward }\n : {}),\n };\n}\n\nexport function needsCompose(opts: CreateOptions): boolean {\n return opts.services.length > 0;\n}\n\ninterface DevcontainerImageMode {\n name: string;\n image: string;\n remoteUser: string;\n // Scaffold-level mounts: only the SSH-agent forward for git auth when\n // the yml lists repos. Tool-specific mounts (e.g. ~/.claude for the\n // claude-code feature) come from the feature's own manifest, not from\n // here.\n mounts?: string[];\n // Bind mount that puts the host's container folder onto a known\n // path inside the container. Pairs with `workspaceFolder` below.\n // Always emitted; the host-side source uses devcontainer-cli's\n // `${localWorkspaceFolder}` variable so the tooling expands it.\n workspaceMount?: string;\n // Where the workspace lives inside the container. VS Code's Dev\n // Containers extension uses this to translate host-side paths\n // (from .code-workspace files, \"Open Folder in Container\", …) to\n // their container counterpart. Without it, VS Code passes the raw\n // host path through and aborts because that path doesn't exist\n // inside the container.\n workspaceFolder?: string;\n // Required so the runtime image's entrypoint can install iptables\n // rules if MONOCEROS_EGRESS=enforce is set. Default mode is `off`\n // (see ADR 0002) so the cap is harmless when unused.\n runArgs: string[];\n forwardPorts: number[];\n postCreateCommand: string;\n features?: Record<string, Record<string, unknown>>;\n // Env vars injected into the workspace container at start time\n // (inherited by postCreateCommand). Used by add-repo to wire the\n // forwarded SSH-agent socket and a permissive SSH host-key policy.\n containerEnv?: Record<string, string>;\n // VS Code-specific overrides written into the materialized\n // devcontainer.json. Today only carries `remote.autoForwardPorts`\n // (toggled by `ide.vscodeAutoForward` from the yml). Future\n // feature/yml fields can extend the shape additively.\n customizations?: DevcontainerCustomizations;\n}\n\ninterface DevcontainerComposeMode {\n name: string;\n dockerComposeFile: string;\n service: string;\n // Without runServices, `devcontainer up` only brings up the named service.\n // Listing the auxiliary services here ensures postgres/redis/… come up\n // alongside the workspace container.\n runServices?: string[];\n workspaceFolder: string;\n remoteUser: string;\n forwardPorts: number[];\n postCreateCommand: string;\n features?: Record<string, Record<string, unknown>>;\n customizations?: DevcontainerCustomizations;\n}\n\ninterface DevcontainerCustomizations {\n vscode?: {\n settings?: Record<string, unknown>;\n extensions?: string[];\n };\n}\n\n/**\n * The host docker daemon's mode — passed in by `apply` after a\n * `docker info` probe. Drives whether we emit `idmap` on bind\n * mounts. See `devcontainer/docker-mode.ts` for the rationale.\n */\nexport type DockerMode = 'rootful' | 'rootless';\n\n// Repo auth note: Monoceros supports HTTPS-only repo URLs (see ADR\n// 0006). The host's git credential helper provides the username/token\n// per host (osxkeychain on macOS, libsecret on Linux, wincred on\n// Windows, plus `gh auth setup-git` for GitHub specifically), the\n// apply pipeline writes them to <container-dir>/.monoceros/git-\n// credentials, and post-create.sh wires `git config --global\n// credential.helper \"store --file=…\"` so the container reads from\n// the same file. SSH-agent forwarding, multi-key wiring, host-OS\n// platform-specific socket paths — all that complexity stays out.\n\nexport type DevcontainerJson = DevcontainerImageMode | DevcontainerComposeMode;\n\n/**\n * Per-feature plan for the container build.\n *\n * - `devcontainerKey` — the key used in `devcontainer.json → features`.\n * - `localSourceDir` / `localName` — set when the workbench has the\n * feature on disk. `writeScaffold` copies the directory into\n * `<container>/.devcontainer/features/<name>/` and uses the\n * relative path `./features/<name>` in devcontainer.json.\n * (devcontainer-cli accepts relative paths from `.devcontainer/`\n * but rejects absolute filesystem paths to local features.)\n */\ninterface ResolvedFeature {\n devcontainerKey: string;\n options: Record<string, unknown>;\n localSourceDir?: string;\n localName?: string;\n /**\n * Subdirectories of `/home/node/` that this feature wants to\n * persist across container rebuilds. Each entry is bind-mounted\n * from `<container-dir>/home/<path>` into `/home/node/<path>` and\n * pre-created as an empty directory on the host. Read from the\n * feature manifest's `x-monoceros.persistentHomePaths` array.\n */\n persistentHomePaths: string[];\n /**\n * Like `persistentHomePaths`, but for individual **files** rather\n * than directories. Necessary for tools that keep state in a\n * dotfile next to (not inside) their config directory — e.g.\n * Claude Code's `~/.claude.json` lives alongside `~/.claude/`.\n *\n * Each entry can be a bare path string (file pre-created empty)\n * or `{ path, initialContent }` so a feature author can seed\n * valid initial content. The latter is needed for tools that\n * refuse to parse an empty file: Claude Code, for instance, errors\n * on an empty `.claude.json` and demands at least `{}`. Read from\n * the feature manifest's `x-monoceros.persistentHomeFiles` array.\n */\n persistentHomeFiles: PersistentHomeFile[];\n}\n\ninterface PersistentHomeFile {\n path: string;\n initialContent: string;\n}\n\n/**\n * Compute the feature list for `opts`. Detects Monoceros-owned refs\n * (`ghcr.io/getmonoceros/monoceros-features/<name>:<tag>`) and, if\n * the workbench has the feature on disk, rewrites the key to\n * `./features/<name>` and records the source for the copy step.\n *\n * Third-party refs and Monoceros refs without a local source pass\n * through verbatim — devcontainer-cli pulls them from the registry.\n */\nexport function resolveFeatures(opts: CreateOptions): ResolvedFeature[] {\n const resolved: ResolvedFeature[] = [];\n\n for (const langSpec of opts.languages) {\n const parsed = parseLanguageSpec(langSpec);\n if (!parsed) continue;\n // Builtin only applies to the bare `node` (no version) — the\n // base image's node 22 isn't pinnable, so any `node:<version>`\n // has to go through the upstream feature like everything else.\n if (BUILTIN_LANGUAGES.has(parsed.name) && parsed.version === undefined) {\n continue;\n }\n const entry = LANGUAGE_CATALOG[parsed.name];\n if (!entry) continue;\n const options: Record<string, string> = {};\n if (parsed.version !== undefined) options.version = parsed.version;\n resolved.push({\n devcontainerKey: entry.feature,\n options,\n persistentHomePaths: [],\n persistentHomeFiles: [],\n });\n }\n if (opts.aptPackages && opts.aptPackages.length > 0) {\n resolved.push({\n devcontainerKey: 'ghcr.io/devcontainers-contrib/features/apt-packages:1',\n options: { packages: opts.aptPackages.join(',') },\n persistentHomePaths: [],\n persistentHomeFiles: [],\n });\n }\n if (opts.features) {\n for (const [rawRef, options] of Object.entries(opts.features)) {\n const match = matchMonocerosFeature(rawRef);\n if (match) {\n const name = match.name;\n // Dev-only fallback: when the CLI is run from a workbench\n // checkout, prefer the on-disk copy under `images/features/`\n // so feature edits are testable without a publish. In prod\n // (npm-installed), `workbenchCheckoutRoot()` returns null\n // and we fall through to the GHCR-ref passthrough.\n const checkout = workbenchCheckoutRoot();\n const localSourceDir = checkout\n ? path.join(checkout, 'images', 'features', name)\n : null;\n if (localSourceDir && existsSync(localSourceDir)) {\n const { paths, files } = readPersistentHomeEntries(localSourceDir);\n resolved.push({\n devcontainerKey: `./features/${name}`,\n options,\n localSourceDir,\n localName: name,\n persistentHomePaths: paths,\n persistentHomeFiles: files,\n });\n continue;\n }\n }\n resolved.push({\n devcontainerKey: rawRef,\n options,\n persistentHomePaths: [],\n persistentHomeFiles: [],\n });\n }\n }\n return resolved;\n}\n\n/**\n * Read `x-monoceros.persistentHomePaths` and\n * `x-monoceros.persistentHomeFiles` from a feature's manifest on\n * disk. Returns `{paths: [], files: []}` when the manifest doesn't\n * exist, can't be parsed, or the fields are missing — always\n * best-effort, never throws. Both arrays are validated to contain\n * only safe relative subpaths (no `..`, no absolute, no shell\n * metacharacters); anything else is silently dropped, since a bad\n * value here is a feature-author bug, not something a builder can fix.\n */\nfunction readPersistentHomeEntries(localSourceDir: string): {\n paths: string[];\n files: PersistentHomeFile[];\n} {\n const manifestPath = path.join(localSourceDir, 'devcontainer-feature.json');\n try {\n const text = readFileSync(manifestPath, 'utf8');\n const parsed = JSON.parse(text) as {\n 'x-monoceros'?: {\n persistentHomePaths?: unknown;\n persistentHomeFiles?: unknown;\n };\n };\n return {\n paths: filterSubpaths(parsed['x-monoceros']?.persistentHomePaths),\n files: filterFileEntries(parsed['x-monoceros']?.persistentHomeFiles),\n };\n } catch {\n return { paths: [], files: [] };\n }\n}\n\nfunction filterSubpaths(raw: unknown): string[] {\n if (!Array.isArray(raw)) return [];\n return raw.filter(\n (p): p is string =>\n typeof p === 'string' &&\n p.length > 0 &&\n !p.startsWith('/') &&\n !p.includes('..') &&\n HOME_SUBPATH_RE.test(p),\n );\n}\n\n/**\n * Accept either bare strings or `{path, initialContent}` objects in\n * `persistentHomeFiles`. Bare string is shorthand for \"create an\n * empty file\"; the object form lets feature authors provide initial\n * content (e.g. `{}` for a JSON config that the tool refuses to\n * parse when empty).\n */\nfunction filterFileEntries(raw: unknown): PersistentHomeFile[] {\n if (!Array.isArray(raw)) return [];\n const result: PersistentHomeFile[] = [];\n for (const entry of raw) {\n if (typeof entry === 'string') {\n if (isValidHomeSubpath(entry)) {\n result.push({ path: entry, initialContent: '' });\n }\n continue;\n }\n if (\n entry !== null &&\n typeof entry === 'object' &&\n 'path' in entry &&\n typeof (entry as { path: unknown }).path === 'string'\n ) {\n const e = entry as { path: string; initialContent?: unknown };\n if (!isValidHomeSubpath(e.path)) continue;\n const initialContent =\n typeof e.initialContent === 'string' ? e.initialContent : '';\n result.push({ path: e.path, initialContent });\n }\n }\n return result;\n}\n\nfunction isValidHomeSubpath(p: string): boolean {\n return (\n p.length > 0 &&\n !p.startsWith('/') &&\n !p.includes('..') &&\n HOME_SUBPATH_RE.test(p)\n );\n}\n\n// Home subpaths: dot-prefixed dirs and config-like sub-dirs.\n// Restrictive on purpose — only `.foo`, `.foo/bar`, `foo`, `foo/bar`,\n// no whitespace, no shell metacharacters.\nconst HOME_SUBPATH_RE = /^[A-Za-z0-9._-]+(\\/[A-Za-z0-9._-]+)*$/;\n\nexport function buildDevcontainerJson(\n opts: CreateOptions,\n dockerMode: DockerMode = 'rootful',\n): DevcontainerJson {\n const resolvedFeatures = resolveFeatures(opts);\n const features: Record<string, Record<string, unknown>> = {};\n for (const f of resolvedFeatures) {\n features[f.devcontainerKey] = f.options;\n }\n\n const featuresField =\n Object.keys(features).length > 0 ? { features } : undefined;\n\n // Rootless-Docker bind-mount handling is currently a TODO. Earlier\n // attempts (1.6.3 / 1.6.5) appended `,idmap` / `,idmap=true` to the\n // mount string in the belief Docker supports idmapped mounts via\n // `--mount`. It doesn't — verified against the official docs at\n // https://docs.docker.com/engine/storage/bind-mounts/ — there is\n // no `idmap` key in the `--mount` syntax. Podman supports it,\n // Docker presently doesn't expose the kernel feature on the CLI.\n //\n // For now we emit the same mount strings regardless of dockerMode.\n // That leaves the rootless UID-shift problem (host pre-created\n // dirs appear as root in container; container-written files end\n // up at shifted UIDs on the host) unsolved — separate fix needed,\n // most likely via remoteUser=root in rootless mode so the\n // container's \"root\" maps to the host workspace owner. The\n // dockerMode parameter stays plumbed in so the next attempt can\n // diverge cleanly.\n void dockerMode;\n const idmapSuffix = '';\n\n // Bind-mounts for per-feature persistent home entries. Source on\n // the host is `<container-dir>/home/<subpath>` (under the\n // localWorkspaceFolder); target inside the container is the same\n // subpath under `/home/node/`. Files and directories both go through\n // the same `type=bind` syntax — docker decides from the source's\n // on-disk type. We pre-create both kinds in writeScaffold so the\n // owner matches the host user (otherwise docker auto-creates as\n // root on Linux, breaking writes inside the container) and so a\n // requested **file** bind doesn't get spawned as a directory.\n const homeMounts: string[] = [];\n for (const f of resolvedFeatures) {\n const allSubs = [\n ...f.persistentHomePaths,\n ...f.persistentHomeFiles.map((entry) => entry.path),\n ];\n for (const sub of allSubs) {\n homeMounts.push(\n `source=\\${localWorkspaceFolder}/home/${sub},target=/home/node/${sub},type=bind${idmapSuffix}`,\n );\n }\n }\n\n // VS Code customizations — currently only the `remote.autoForwardPorts`\n // toggle when ports are declared. The default is `false` (Traefik is\n // the single source of truth for external URLs — VS Code's parallel\n // port-forward would be a confusing second URL for the same app).\n // Builders can flip it via `ide.vscodeAutoForward: true` in the\n // yml. See ADR 0007. Other extension hints belong with the feature\n // that needs them (e.g. the claude-code feature recommends\n // `anthropic.claude-code`).\n const ports = opts.ports ?? [];\n const customizationsField =\n ports.length > 0\n ? {\n customizations: {\n vscode: {\n settings: {\n 'remote.autoForwardPorts': opts.vscodeAutoForward ?? false,\n },\n },\n },\n }\n : undefined;\n\n if (needsCompose(opts)) {\n // Compose-mode: per-feature persistent home mounts go onto the\n // workspace service in compose.yaml (see buildComposeYaml). The\n // devcontainer.json just references compose. Network membership\n // (`monoceros-proxy`) lives in compose.yaml's `networks:` block,\n // not here.\n return {\n name: opts.name,\n dockerComposeFile: 'compose.yaml',\n service: 'workspace',\n ...(opts.services.length > 0\n ? { runServices: opts.services.map((s) => s.name) }\n : {}),\n workspaceFolder: `/workspaces/${opts.name}`,\n remoteUser: 'node',\n forwardPorts: ports,\n postCreateCommand: '.devcontainer/post-create.sh',\n ...(featuresField ?? {}),\n ...(customizationsField ?? {}),\n };\n }\n\n // Image-mode mounts: per-feature persistent-home binds.\n const mounts: string[] = [...homeMounts];\n const mountsField = mounts.length > 0 ? { mounts } : {};\n\n // Image-mode workspaces: pin both `workspaceMount` AND\n // `workspaceFolder` explicitly so VS Code's Dev Containers\n // extension knows how the host folder maps into the container.\n //\n // Without these two, \"Open Folder in Container\" / \"Open Workspace\n // in Container\" on a `.code-workspace` falls back to passing the\n // raw host path (e.g. `/Users/.../.local/container/sandbox`) as\n // the container-side workspace path. The container of course has\n // no such directory and VS Code aborts with \"Arbeitsbereich nicht\n // vorhanden\" / \"Workspace does not exist\". Setting workspaceFolder\n // tells VS Code where the workspace lives inside the container\n // (matches what we already do for compose-mode); workspaceMount\n // pins the bind that puts the host folder there.\n //\n // Source path uses `${localWorkspaceFolder}` — devcontainer-cli\n // expands it to the host folder containing the .devcontainer/, no\n // hand-substitution needed on our side.\n const workspaceMountField = {\n workspaceMount: `source=\\${localWorkspaceFolder},target=/workspaces/${opts.name},type=bind,consistency=cached`,\n workspaceFolder: `/workspaces/${opts.name}`,\n };\n\n // Image-mode: when ports are declared, hook the container into the\n // `monoceros-proxy` network so the Traefik singleton can reach it\n // by yml name (`http://<name>:<port>`). `--network` replaces\n // docker's default bridge — for image-mode that's the only network\n // in play, so swapping is fine. ensureProxy() (called from\n // apply/start) creates the network before this `runArgs` value is\n // used.\n //\n // `--network-alias` pins a stable DNS name on the network: by\n // default devcontainer-cli labels image-mode containers with random\n // names like `thirsty_bartik`, which would make Traefik's backend\n // URL non-deterministic. With the alias we know the route in the\n // dynamic config can always point at `http://<name>:<port>`.\n const runArgs = ['--cap-add=NET_ADMIN'];\n if (ports.length > 0) {\n runArgs.push('--network=monoceros-proxy');\n runArgs.push(`--network-alias=${opts.name}`);\n }\n\n return {\n name: opts.name,\n image: BASE_IMAGE,\n remoteUser: 'node',\n ...workspaceMountField,\n ...mountsField,\n runArgs,\n forwardPorts: ports,\n postCreateCommand: '.devcontainer/post-create.sh',\n ...(featuresField ?? {}),\n ...(customizationsField ?? {}),\n };\n}\n\n// Double-quote a YAML scalar, escaping the chars that matter inside a\n// double-quoted YAML string. Always quoting keeps arbitrary service env\n// values (passwords with `:` / `#` / spaces, interpolated secrets, shell\n// commands) safe without pulling in a YAML serializer.\nexport function composeScalar(value: string): string {\n const escaped = value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\t/g, '\\\\t');\n return `\"${escaped}\"`;\n}\n\n// Rewrite a service volume's source segment to a path relative to the\n// `.devcontainer/` directory (where compose runs). The `data` shorthand\n// maps to the per-service bind-mounted data dir under `../data/<name>`;\n// any other (relative, validated) host path is prefixed with `../` to\n// reach up to the container root. The destination + mode segments pass\n// through verbatim.\nexport function composeVolumeSource(spec: string, serviceName: string): string {\n const parts = spec.split(':');\n const src = parts[0]!;\n const rest = parts.slice(1).join(':');\n if (src === 'data') return `../data/${serviceName}:${rest}`;\n // Host-relative source: strip a leading `./` (compose habit) so the\n // `../` prefix that walks up to the container root stays clean.\n const relative = src.startsWith('./') ? src.slice(2) : src;\n return `../${relative}:${rest}`;\n}\n\n// Hand-rolled YAML for compose.yaml. The shape is narrow enough that\n// avoiding a YAML dependency outweighs the cost of careful indentation.\n//\n// `dockerMode` is plumbed in for symmetry with buildDevcontainerJson\n// and future rootless-specific tweaks, but currently unused (see the\n// TODO in buildDevcontainerJson re: docker not exposing idmap on\n// `--mount`).\nexport function buildComposeYaml(\n opts: CreateOptions,\n dockerMode: DockerMode = 'rootful',\n): string {\n void dockerMode;\n const hasPorts = (opts.ports?.length ?? 0) > 0;\n const lines: string[] = ['services:'];\n\n lines.push(' workspace:');\n lines.push(` image: ${BASE_IMAGE}`);\n lines.push(\" command: 'sleep infinity'\");\n // No `user:` directive here — the runtime image's entrypoint runs as\n // root to set up iptables, then drops to the `node` user via gosu\n // before exec'ing the command. NET_ADMIN is required for that\n // iptables setup; see ADR 0002.\n lines.push(' cap_add:');\n lines.push(' - NET_ADMIN');\n if (hasPorts) {\n // Workspace joins both the compose-default network (so it can\n // reach postgres/redis/… that share the project) and the\n // monoceros-proxy network (so Traefik can route to it). Use the\n // long form so we can pin a stable DNS alias on monoceros-proxy:\n // without the alias every compose-mode container would show up\n // as `workspace` (compose service name) and collide between\n // multiple monoceros containers. The alias is the yml name; the\n // dynamic config writes routes against `http://<name>:<port>`.\n // See ADR 0007.\n lines.push(' networks:');\n lines.push(' default: {}');\n lines.push(' monoceros-proxy:');\n lines.push(' aliases:');\n lines.push(` - ${opts.name}`);\n }\n lines.push(' volumes:');\n lines.push(` - ..:/workspaces/${opts.name}:cached`);\n // Per-feature persistent home subpaths (dirs and files alike).\n // Paths inside compose.yaml are relative to the .devcontainer/\n // directory; `..` walks up to the container root, where `home/`\n // lives. Docker reads the host-side inode type to decide whether\n // the mount target inside the container is a file or a directory.\n const resolvedFeatures = resolveFeatures(opts);\n for (const f of resolvedFeatures) {\n const allSubs = [\n ...f.persistentHomePaths,\n ...f.persistentHomeFiles.map((entry) => entry.path),\n ];\n for (const sub of allSubs) {\n lines.push(` - ../home/${sub}:/home/node/${sub}`);\n }\n }\n for (const svc of opts.services) {\n // `${VAR}` env values were already resolved against <name>.env in\n // apply, so everything here is a literal. Per-service data dirs are\n // bind-mounted from the host (`data:` volume shorthand → ../data/<name>)\n // so DB content is visible at `<container-dir>/data/<name>/` and is\n // part of remove-backups. See ADR 0003. Pre-created in writeScaffold\n // so docker doesn't auto-mkdir them as root.\n lines.push(` ${svc.name}:`);\n lines.push(` image: ${svc.image}`);\n if (svc.restart) {\n lines.push(` restart: ${svc.restart}`);\n }\n if (svc.command !== undefined) {\n lines.push(` command: ${composeScalar(svc.command)}`);\n }\n const envKeys = Object.keys(svc.env);\n if (envKeys.length > 0) {\n lines.push(' environment:');\n for (const k of envKeys) {\n lines.push(` ${k}: ${composeScalar(svc.env[k]!)}`);\n }\n }\n if (svc.volumes.length > 0) {\n lines.push(' volumes:');\n for (const vol of svc.volumes) {\n lines.push(` - ${composeVolumeSource(vol, svc.name)}`);\n }\n }\n if (svc.healthcheck) {\n const hc = svc.healthcheck;\n lines.push(' healthcheck:');\n if (Array.isArray(hc.test)) {\n // Compose exec-form: a flow sequence of quoted args.\n lines.push(` test: [${hc.test.map(composeScalar).join(', ')}]`);\n } else {\n lines.push(` test: ${composeScalar(hc.test)}`);\n }\n if (hc.interval) lines.push(` interval: ${hc.interval}`);\n if (hc.timeout) lines.push(` timeout: ${hc.timeout}`);\n if (hc.retries !== undefined) lines.push(` retries: ${hc.retries}`);\n if (hc.startPeriod) {\n lines.push(` start_period: ${hc.startPeriod}`);\n }\n }\n }\n\n if (hasPorts) {\n // `external: true` tells compose that `monoceros-proxy` is managed\n // outside this stack (Monoceros's proxy module creates it via\n // `docker network create`). Without this declaration compose would\n // try to create its own scoped network with the same name and\n // collide.\n lines.push('networks:');\n lines.push(' monoceros-proxy:');\n lines.push(' external: true');\n }\n\n return lines.join('\\n') + '\\n';\n}\n\ninterface CodeWorkspaceFolder {\n path: string;\n name?: string;\n}\n\ninterface CodeWorkspaceFile {\n folders: CodeWorkspaceFolder[];\n}\n\n/**\n * The `<name>.code-workspace` file VS Code uses to open the solution as\n * a multi-root workspace. The first entry is `.` so the workspace root\n * (with its system dotfolders) stays visible in the Explorer. Each\n * repo added via `monoceros add-repo` appears as a sibling root\n * pointing at `projects/<name>/`.\n */\nexport function buildCodeWorkspaceJson(opts: CreateOptions): CodeWorkspaceFile {\n const folders: CodeWorkspaceFolder[] = [{ path: '.' }];\n // Sort repos by path so the Explorer order is deterministic and\n // doesn't depend on insertion order. (Clone order in post-create\n // stays as-added so deps still work.)\n const sortedRepos = [...(opts.repos ?? [])].sort((a, b) =>\n a.path.localeCompare(b.path),\n );\n for (const repo of sortedRepos) {\n // The folder's display label is the leaf segment of the path\n // (the deepest folder name). VS Code shows it in the Explorer\n // tree; for nested clones (`apps/web`) we want `web`, not the\n // whole path.\n const label = repo.path.split('/').pop() ?? repo.path;\n folders.push({ path: `projects/${repo.path}`, name: label });\n }\n return { folders };\n}\n\n/**\n * Merge a generator-produced workspace into whatever the builder may\n * have hand-edited into the on-disk file. The `.code-workspace` is\n * conceptually a builder artifact — VS Code lets people add local\n * folders to it, drop in `settings:` / `extensions:` blocks, reorder\n * roots, etc. A blind overwrite on every `apply` would silently nuke\n * all of that.\n *\n * Merge rules (favour-builder):\n *\n * - Every folder the builder has in their `folders[]` stays, in\n * the same order. We don't touch labels or paths the user\n * already wrote.\n * - Any folder from the generator that ISN'T present in the\n * builder's `folders[]` (matched by `path`) is appended at the\n * end. That covers the typical case \"I just added a new repo via\n * `monoceros add-repo` and want it to show up automatically\".\n * - Folders that exist in the builder file but no longer come from\n * the generator (e.g. yml repo removed) are NOT dropped — the\n * builder may have kept the folder around on purpose. Cleanup\n * is a manual edit.\n * - Top-level fields other than `folders` (e.g. `settings`,\n * `extensions`, `launch`, `tasks`, `remoteAuthority`) carry\n * through verbatim.\n * - If `existing` is null / undefined / unparseable / missing\n * `folders`, the generator output is taken as-is.\n *\n * Pure function — no I/O. `writeScaffold` is the one site that\n * reads + writes; this just transforms.\n */\nexport function mergeCodeWorkspace(\n existing: unknown,\n generated: CodeWorkspaceFile,\n): Record<string, unknown> {\n // Bail out to the generator when the existing file isn't a sane\n // workspace document. We could try to repair partial shapes but\n // the simpler invariant is: a hand-edit that breaks JSON or drops\n // `folders` is the builder's problem to fix.\n if (\n !existing ||\n typeof existing !== 'object' ||\n Array.isArray(existing) ||\n !Array.isArray((existing as { folders?: unknown }).folders)\n ) {\n return { ...generated };\n }\n const existingObj = existing as Record<string, unknown>;\n const existingFolders = existingObj.folders as CodeWorkspaceFolder[];\n const existingPaths = new Set(\n existingFolders\n .map((f) => (f && typeof f === 'object' ? f.path : undefined))\n .filter((p): p is string => typeof p === 'string'),\n );\n\n // Preserve builder-side folders verbatim; append generator-only ones.\n const merged: CodeWorkspaceFolder[] = [...existingFolders];\n for (const g of generated.folders) {\n if (!existingPaths.has(g.path)) merged.push(g);\n }\n\n // Top-level pass-through: keep all builder-set keys, overwrite\n // only `folders`. Order: builder's keys first (to keep their\n // structure recognizable on round-trip), then folders.\n const out: Record<string, unknown> = { ...existingObj };\n out.folders = merged;\n return out;\n}\n\n/**\n * Generate the `post-create.sh` content for a solution. Base sections\n * (git include + pnpm install) are fixed. The `installUrls` and\n * `repos` sections are appended only when those yml fields are\n * populated.\n */\nexport function buildPostCreateScript(opts: CreateOptions): string {\n const lines: string[] = [\n '#!/usr/bin/env bash',\n 'set -euo pipefail',\n '',\n '# Inherit host-side git identity (user.name / user.email) captured',\n '# into .monoceros/gitconfig by `monoceros apply`. Container-local',\n \"# git config loads first; the include below merges the host's\",\n '# identity values in.',\n `git config --global include.path \"/workspaces/${opts.name}/.monoceros/gitconfig\"`,\n '',\n '# Per-feature post-create hooks. Each Monoceros-curated feature',\n '# may drop a script into /usr/local/share/monoceros/post-create.d/',\n '# during its install.sh — typical job is a non-interactive login',\n '# against bind-mounted state under /home/node, using the option',\n '# values the feature received as env vars at install time. Scripts',\n '# run in lexicographic order, each in its own subshell, and a',\n '# failure aborts post-create (set -e is in effect).',\n 'if [ -d /usr/local/share/monoceros/post-create.d ]; then',\n ' for hook in /usr/local/share/monoceros/post-create.d/*.sh; do',\n ' [ -f \"$hook\" ] || continue',\n ' echo \"→ post-create hook: $(basename \"$hook\")\"',\n ' bash \"$hook\"',\n ' done',\n 'fi',\n '',\n '# Bring up Node dependencies if the workspace has a package.json.',\n 'if [ -f package.json ]; then',\n ' pnpm install',\n 'fi',\n ];\n\n if (opts.installUrls && opts.installUrls.length > 0) {\n lines.push(\n '',\n '# Custom install URLs added via `monoceros add-from-url`. Each is',\n '# fetched and piped to `sh` on every container rebuild. URLs run',\n '# in insertion order so later installs can build on earlier ones.',\n '#',\n '# Why `sh` (not `bash`): most install scripts target POSIX `sh`',\n '# and some (starship, rustup, …) explicitly refuse to run under',\n '# `bash`. Outer `set -o pipefail` in this script makes a curl',\n '# failure abort the post-create as expected.',\n `echo \"→ Running ${opts.installUrls.length} install URL(s) added via add-from-url…\"`,\n );\n for (const url of opts.installUrls) {\n lines.push(`echo \"→ ${url}\"`, `curl -fsSL \"${url}\" | sh`);\n }\n }\n\n if (opts.repos && opts.repos.length > 0) {\n const hasHttpsRepo = opts.repos.some((r) => r.url.startsWith('https://'));\n if (hasHttpsRepo) {\n lines.push(\n '',\n '# Wire git to the per-dev-container credentials file populated',\n '# by `monoceros apply` (via `git credential fill` on the host).',\n '# Path uses the workspace bind-mount so the file is reachable',\n '# from inside the container.',\n `git config --global credential.helper \"store --file=/workspaces/${opts.name}/.monoceros/git-credentials\"`,\n );\n }\n lines.push(\n '',\n '# Repos managed by `monoceros add-repo`. Each entry is cloned',\n '# into `projects/<path>/` if (and only if) the directory does',\n '# not exist yet. Existing project subfolders are left alone so',\n '# local changes survive `monoceros apply` rebuilds. Nested',\n '# `<path>` (e.g. apps/web) is created via `mkdir -p` before the',\n '# clone so the parent directories exist.',\n 'mkdir -p projects',\n );\n for (const repo of opts.repos) {\n // For nested paths (`apps/web`), make sure the parent dir\n // exists before git clone — otherwise git fails with \"could\n // not create work tree dir\".\n const parent = repo.path.includes('/')\n ? repo.path.slice(0, repo.path.lastIndexOf('/'))\n : null;\n if (parent) {\n lines.push(`mkdir -p \"projects/${parent}\"`);\n }\n lines.push(\n `if [ ! -d \"projects/${repo.path}\" ]; then`,\n ` echo \"→ Cloning ${repo.path} from ${repo.url}…\"`,\n ` git clone \"${repo.url}\" \"projects/${repo.path}\"`,\n `else`,\n ` echo \"→ projects/${repo.path} already exists, skipping clone\"`,\n `fi`,\n );\n // Per-repo git identity override: set user.name/email inside\n // the cloned repo, so commits from THIS repo go out under the\n // override identity. Idempotent — git config overwrites the\n // value each run, no duplicate accumulation. Falls outside the\n // `if [ ! -d ... ]` clone-guard so an explicit yml update of\n // gitUser also takes effect on re-apply against an existing\n // clone.\n if (repo.gitUser) {\n const safeName = repo.gitUser.name.replace(/\"/g, '\\\\\"');\n const safeEmail = repo.gitUser.email.replace(/\"/g, '\\\\\"');\n lines.push(\n `git -C \"projects/${repo.path}\" config user.name \"${safeName}\"`,\n `git -C \"projects/${repo.path}\" config user.email \"${safeEmail}\"`,\n );\n }\n }\n }\n\n return lines.join('\\n') + '\\n';\n}\n\nexport async function writePostCreateScript(\n devcontainerDir: string,\n opts: CreateOptions,\n): Promise<void> {\n const dest = path.join(devcontainerDir, 'post-create.sh');\n await fs.writeFile(dest, buildPostCreateScript(opts));\n await fs.chmod(dest, 0o755);\n}\n\n/**\n * Materialize the full devcontainer scaffold for `opts` into\n * `targetDir`. Idempotent overwrite — re-running with different opts\n * produces the new scaffold and overwrites any older files.\n *\n * Writes:\n * - `.devcontainer/devcontainer.json`\n * - `.devcontainer/post-create.sh`\n * - `.devcontainer/compose.yaml` (only when services are configured)\n * - `.monoceros/.gitignore`\n * - `projects/.gitkeep`\n * - `<name>.code-workspace`\n *\n * Does NOT write `README.md` — the README is a once-only stub that\n * `runCreate` produces but `runApplyFromYml` should leave alone (the\n * builder may have edited it).\n *\n * Caller is responsible for `validateOptions(opts)` and\n * `normalizeOptions(opts)`; this function trusts the input.\n */\nexport async function writeScaffold(\n opts: CreateOptions,\n targetDir: string,\n scaffoldOpts: { dockerMode?: DockerMode } = {},\n): Promise<void> {\n const dockerMode: DockerMode = scaffoldOpts.dockerMode ?? 'rootful';\n const devcontainerDir = path.join(targetDir, '.devcontainer');\n const monocerosDir = path.join(targetDir, '.monoceros');\n const projectsDir = path.join(targetDir, 'projects');\n const homeDir = path.join(targetDir, 'home');\n const dataDir = path.join(targetDir, 'data');\n await fs.mkdir(devcontainerDir, { recursive: true });\n await fs.mkdir(monocerosDir, { recursive: true });\n await fs.mkdir(projectsDir, { recursive: true });\n await fs.mkdir(homeDir, { recursive: true });\n if (needsCompose(opts)) {\n await fs.mkdir(dataDir, { recursive: true });\n // Pre-create one subdir per service that uses the `data:` volume\n // shorthand, so docker bind-mounts onto an existing host path (and\n // doesn't auto-mkdir as root, which breaks postgres/mysql first-run\n // on Linux).\n for (const svc of opts.services) {\n const hasDataVolume = svc.volumes.some((v) => v.split(':')[0] === 'data');\n if (hasDataVolume) {\n await fs.mkdir(path.join(dataDir, svc.name), { recursive: true });\n }\n }\n }\n\n // Container-root `.gitignore`. Excludes the directories that hold\n // builder-private or container-runtime state:\n // - `home/` — logins, sessions, secrets baked into tool\n // config files\n // - `.monoceros/` — git-credentials captured from the host\n // credential helper, machine-local gitconfig\n // - `data/` — DB data the compose services write at\n // runtime (postgres/mysql/redis), often big,\n // always container-specific\n // Inside `projects/<repo>/` builders have their own `.git` and\n // any wrapping git operation should be at that level, not at the\n // container root — but a stray `git init` at the root is exactly\n // the accident this .gitignore protects against.\n const containerGitignore = path.join(targetDir, '.gitignore');\n await fs.writeFile(containerGitignore, '/home/\\n/.monoceros/\\n/data/\\n');\n\n // `.gitkeep` so `projects/` survives a fresh git clone before any\n // sub-project has been added.\n const gitkeep = path.join(projectsDir, '.gitkeep');\n if (!existsSync(gitkeep)) {\n await fs.writeFile(gitkeep, '');\n }\n\n // `.monoceros/.gitignore` keeps per-builder runtime state out of any\n // wrapping git repo. Always overwrite — content is fixed.\n await fs.writeFile(\n path.join(monocerosDir, '.gitignore'),\n 'git-credentials*\\ngitconfig\\n',\n );\n\n const devcontainerJson = buildDevcontainerJson(opts, dockerMode);\n await fs.writeFile(\n path.join(devcontainerDir, 'devcontainer.json'),\n JSON.stringify(devcontainerJson, null, 2) + '\\n',\n );\n\n // Copy any Monoceros-owned features that the workbench has on disk\n // into `<devcontainerDir>/features/<name>/`. The devcontainer.json\n // references them via the relative path `./features/<name>` — the\n // devcontainer-cli accepts relative paths from the `.devcontainer/`\n // directory but rejects absolute filesystem paths to local features.\n //\n // We always rebuild the whole `features/` directory: drop the old\n // copy and recreate from current sources, so a feature that was\n // removed from the yml doesn't linger as stale on-disk content that\n // devcontainer-cli would still see.\n const featuresDir = path.join(devcontainerDir, 'features');\n if (existsSync(featuresDir)) {\n await fs.rm(featuresDir, { recursive: true, force: true });\n }\n const resolvedFeatures = resolveFeatures(opts);\n for (const f of resolvedFeatures) {\n if (!f.localSourceDir || !f.localName) continue;\n const dest = path.join(featuresDir, f.localName);\n await fs.mkdir(dest, { recursive: true });\n await fs.cp(f.localSourceDir, dest, { recursive: true });\n }\n\n // Pre-create persistent home entries so docker doesn't auto-mkdir\n // them as root at container start. We only ensure existence; any\n // existing content survives, which is the whole point — apply\n // never touches `home/<sub>` once it's there. Directories get\n // mkdir; files get an empty touch (only when missing — already-\n // populated files like a complete .claude.json must not be\n // truncated on re-apply).\n for (const f of resolvedFeatures) {\n for (const sub of f.persistentHomePaths) {\n await fs.mkdir(path.join(homeDir, sub), { recursive: true });\n }\n for (const entry of f.persistentHomeFiles) {\n const filePath = path.join(homeDir, entry.path);\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n if (!existsSync(filePath)) {\n // Seed with the feature-author's initial content (defaults\n // to empty). For JSON configs this should be at least `{}`\n // so the tool doesn't choke on an unparseable empty file.\n await fs.writeFile(filePath, entry.initialContent);\n }\n }\n }\n\n await writePostCreateScript(devcontainerDir, opts);\n\n const composePath = path.join(devcontainerDir, 'compose.yaml');\n if (needsCompose(opts)) {\n await fs.writeFile(composePath, buildComposeYaml(opts, dockerMode));\n } else if (existsSync(composePath)) {\n // Services dropped from the yml — clean up the now-stale file so a\n // later `monoceros start` doesn't pick it up.\n await fs.rm(composePath);\n }\n\n // `.code-workspace` is a builder artifact, not a pure generator\n // output — VS Code lets people drop local folders, settings,\n // extensions etc. into it. Read what's there, merge with what the\n // generator produces, write back. See mergeCodeWorkspace for the\n // exact rules.\n const workspacePath = path.join(targetDir, `${opts.name}.code-workspace`);\n let existingWorkspace: unknown;\n try {\n const raw = await fs.readFile(workspacePath, 'utf8');\n existingWorkspace = JSON.parse(raw);\n } catch {\n // ENOENT (first apply) or parse error — fall through to the\n // generator output. mergeCodeWorkspace handles both via the\n // null-existing branch.\n existingWorkspace = undefined;\n }\n const generated = buildCodeWorkspaceJson(opts);\n const merged = mergeCodeWorkspace(existingWorkspace, generated);\n await fs.writeFile(workspacePath, JSON.stringify(merged, null, 2) + '\\n');\n}\n","import {\n type Document,\n isMap,\n isScalar,\n isSeq,\n Pair,\n parseDocument,\n Scalar,\n YAMLMap,\n YAMLSeq,\n} from 'yaml';\nimport type { FeatureOptions, RepoEntry } from '../create/types.js';\nimport { deriveRepoName } from '../create/scaffold.js';\nimport { loadFeatureManifestSummary } from '../init/manifest.js';\nimport {\n buildFeatureHeaderCommentBefore,\n featureOptionHints,\n FEATURE_HEADER_WIDTH,\n} from '../init/feature-doc.js';\nimport { GIT_IDENTITY_VAR } from '../config/env-file.js';\n\n/**\n * AST-level mutators for solution-config yml. Each function:\n * - takes a yaml.Document obtained from `parseConfig`\n * - mutates it in place if the operation introduces a change\n * - returns `true` iff the doc was changed, `false` for a no-op\n * (matches the `mutate()` skeleton's \"no-change\" branch)\n *\n * All mutators preserve every comment and blank line in the surrounding\n * yml — that's the whole point of the AST approach over a plain\n * `toJS → edit → toString` cycle.\n *\n * Validation is NOT done here. The caller is expected to re-validate\n * the doc (via `parseConfig(stringifyConfig(doc))`) before persisting\n * — that surfaces schema violations with the regular field-path-aware\n * error message.\n */\n\n/** Ensure `doc[key]` is a sequence and return it. */\nfunction ensureSeq(doc: Document, key: string): YAMLSeq {\n const existing = doc.get(key, true);\n if (existing && isSeq(existing)) return existing;\n const seq = new YAMLSeq();\n doc.set(key, seq);\n return seq;\n}\n\n/** Drop `doc[key]` when its sequence is empty. */\nfunction pruneEmptySeq(doc: Document, key: string): void {\n const node = doc.get(key, true);\n if (node && isSeq(node) && node.items.length === 0) {\n doc.delete(key);\n }\n}\n\n/** Compare a scalar item's value (handles both Scalar nodes and plain JS). */\nfunction scalarValue(item: unknown): unknown {\n return isScalar(item) ? item.value : item;\n}\n\nexport function addLanguageToDoc(doc: Document, lang: string): boolean {\n const seq = ensureSeq(doc, 'languages');\n if (seq.items.some((i) => scalarValue(i) === lang)) return false;\n seq.add(lang);\n return true;\n}\n\n/** Find the services[] item whose `name:` equals `name`. */\nfunction findServiceItem(seq: YAMLSeq, name: string): YAMLMap | undefined {\n for (const item of seq.items) {\n if (isMap(item) && item.get('name') === name) return item;\n }\n return undefined;\n}\n\nexport type AddServiceOutcome =\n | { outcome: 'added' }\n | { outcome: 'exists' }\n | { outcome: 'conflict'; existingImage: string };\n\n/**\n * Add a service entry built from pre-rendered map-body lines (see\n * init/service-doc.ts). Idempotent by `name`:\n * - no entry with that name → append (parsing the body so comments in\n * a custom scaffold survive), report `added`.\n * - an entry with that name + the same image → `exists` (no-op,\n * preserves any builder edits to that block).\n * - an entry with that name + a different image → `conflict` (caller\n * turns this into an actionable error).\n */\nexport function addServiceEntryToDoc(\n doc: Document,\n name: string,\n image: string,\n bodyLines: string[],\n scaffoldComment?: string,\n): AddServiceOutcome {\n const seq = ensureSeq(doc, 'services');\n const existing = findServiceItem(seq, name);\n if (existing) {\n const existingImage = existing.get('image');\n if (existingImage === image) return { outcome: 'exists' };\n return { outcome: 'conflict', existingImage: String(existingImage) };\n }\n const node = parseDocument(bodyLines.join('\\n')).contents as YAMLMap;\n // The commented scaffold (custom images) rides as the node's trailing\n // `comment` — comments parsed inside the body string would be dropped\n // when the map is moved into the sequence, but a node `.comment`\n // survives and renders each line under the item, prefixed with `#`.\n if (scaffoldComment) node.comment = scaffoldComment;\n seq.add(node);\n return { outcome: 'added' };\n}\n\nexport function addAptPackagesToDoc(\n doc: Document,\n packages: string[],\n): boolean {\n const seq = ensureSeq(doc, 'aptPackages');\n let changed = false;\n for (const pkg of packages) {\n if (seq.items.some((i) => scalarValue(i) === pkg)) continue;\n seq.add(pkg);\n changed = true;\n }\n return changed;\n}\n\n/**\n * Set (or replace) the container-level `git.user` block. Used by the\n * apply / init identity prompt when the builder chose \"save in this\n * container's yml\" (scope `c` or `b`).\n *\n * Idempotent on identical input: same name + email → no doc change,\n * return false. Different values → in-place overwrite, return true.\n *\n * When `git` is newly created (didn't exist before this call) the\n * block lands right after `name:` at the top of the document — same\n * \"identity is the first thing under the container's own intro\"\n * layout used by monoceros-config's `defaults.git`. A pre-existing\n * `git:` key keeps its position; the builder's manual reorderings\n * are respected.\n *\n * Comment-preserving as usual for our AST mutators.\n */\nexport function setContainerGitUserInDoc(\n doc: Document,\n user: { name: string; email: string },\n): boolean {\n const gitNode = doc.get('git', true);\n let gitMap: YAMLMap;\n let createdNew = false;\n if (gitNode && isMap(gitNode)) {\n gitMap = gitNode;\n } else {\n gitMap = new YAMLMap();\n insertTopLevelAfterName(doc, 'git', gitMap, GIT_USER_HEADER_COMMENT);\n createdNew = true;\n }\n const userNode = gitMap.get('user', true);\n let userMap: YAMLMap;\n if (userNode && isMap(userNode)) {\n userMap = userNode;\n } else {\n userMap = new YAMLMap();\n gitMap.set('user', userMap);\n }\n const currentName = userMap.get('name');\n const currentEmail = userMap.get('email');\n if (!createdNew && currentName === user.name && currentEmail === user.email) {\n return false;\n }\n userMap.set('name', user.name);\n userMap.set('email', user.email);\n relocateLeakedSectionComments(doc);\n return true;\n}\n\n/**\n * Add a container-level `git.user` with `${VAR}` placeholders IF no\n * `git.user` exists yet. Used by `add-repo` so the first repo gets the\n * same env-managed identity scaffold `init` produces. Leaves any\n * existing `git.user` (literal or placeholder) untouched. Returns true\n * when it added the block.\n */\nexport function ensureContainerGitUserPlaceholder(doc: Document): boolean {\n const gitNode = doc.get('git', true);\n if (gitNode && isMap(gitNode)) {\n const userNode = gitNode.get('user', true);\n if (userNode && isMap(userNode)) return false;\n }\n return setContainerGitUserInDoc(doc, {\n name: `\\${${GIT_IDENTITY_VAR.name}}`,\n email: `\\${${GIT_IDENTITY_VAR.email}}`,\n });\n}\n\n/**\n * yaml-lib's parser will sometimes attach a column-0 comment block\n * sitting between two top-level keys (e.g. the `# Repos cloned…`\n * header above `repos:`) to the previous top-level pair's deepest\n * trailing node rather than to the next pair's `commentBefore`. On\n * re-emit via the AST writers (setContainerGitUserInDoc et al.) the\n * comment then comes out indented under the previous section instead\n * of standing at column 0 above the next section — visually broken.\n *\n * This walks the document, finds such leaked comments on the LAST\n * leaf of each top-level section, and moves them to the\n * `commentBefore` of the NEXT top-level pair (where they visually\n * belong).\n *\n * Safe to call after any AST mutation; idempotent — already-correctly-\n * placed comments aren't touched.\n */\nexport function relocateLeakedSectionComments(doc: Document): void {\n const root = doc.contents;\n if (!root || !isMap(root)) return;\n const items = root.items;\n for (let i = 0; i < items.length - 1; i++) {\n const here = items[i]!;\n const next = items[i + 1]!;\n const leak = takeTrailingLeafComment(here.value);\n if (!leak) continue;\n const nextKey = next.key as {\n commentBefore?: string | null;\n spaceBefore?: boolean;\n } | null;\n if (!nextKey || typeof nextKey !== 'object') continue;\n const existing = nextKey.commentBefore ?? '';\n nextKey.commentBefore = existing ? `${leak}\\n${existing}` : leak;\n nextKey.spaceBefore = true;\n }\n}\n\n/**\n * If `node` is a container whose deepest trailing element carries a\n * comment block that includes a yaml-lib \"blank line separator\" (a\n * `\\n\\n` inside the `comment` string — that's how yaml stores a\n * source-level blank line between two trailing comment runs), strip\n * the post-separator portion and return it. Everything before the\n * blank line is a legitimate inline hint that belongs to the leaf;\n * everything after is the next section's leaked header.\n *\n * Returns `null` when there's no blank-line separator — in that case\n * the trailing comment is all legitimate inline content, leave it.\n */\nfunction takeTrailingLeafComment(node: unknown): string | null {\n if (!node) return null;\n type CommentNode = {\n comment?: string | null;\n spaceBefore?: boolean;\n };\n // First, check this node's own trailing comment for a leak.\n const c = node as CommentNode;\n if (typeof c.comment === 'string' && c.comment.length > 0) {\n const blankMatch = c.comment.match(/\\n[ \\t]*\\n/);\n if (blankMatch && blankMatch.index !== undefined) {\n // Strip ONLY the blank-line separator. The character that\n // follows is the leading single space yaml-lib uses between\n // `#` and the comment text — preserve it, otherwise the\n // relocated block emits as `#Foo` instead of `# Foo`.\n const tail = c.comment.slice(blankMatch.index + blankMatch[0].length);\n c.comment = c.comment.slice(0, blankMatch.index);\n if (tail.length > 0) return tail;\n }\n }\n // Recurse into children — last-first across both maps and seqs, so\n // we find the DEEPEST leak first (yaml-lib's parser pushes leaked\n // comments as deep as it can). When a seq has multiple items and\n // the leak sits on an EARLIER one (because subsequent items were\n // added by a later mutation), we walk back through the siblings\n // until we find it.\n if (isMap(node) && node.items.length > 0) {\n for (let i = node.items.length - 1; i >= 0; i--) {\n const value = (node.items[i] as { value?: unknown }).value;\n const found = takeTrailingLeafComment(value);\n if (found) return found;\n }\n }\n if (isSeq(node) && node.items.length > 0) {\n for (let i = node.items.length - 1; i >= 0; i--) {\n const found = takeTrailingLeafComment(node.items[i]);\n if (found) return found;\n }\n }\n return null;\n}\n\n/**\n * Insert a new top-level key into the document, positioned right\n * after `name:` (or at index 1 if `name:` isn't there). Used so newly-\n * persisted `git:` lands at the top of the yml where the builder\n * expects to find it — mirrors the `defaults.git.user` placement in\n * monoceros-config.sample.yml.\n *\n * If `comment` is given, it's attached as the new pair's\n * `commentBefore` so the section gets the same explanatory line the\n * other sections carry.\n */\nfunction insertTopLevelAfterName(\n doc: Document,\n key: string,\n value: YAMLMap,\n comment: string | undefined,\n): void {\n const root = doc.contents;\n if (!root || !isMap(root)) {\n // Document with no top-level map (shouldn't happen for a real\n // solution-config) — fall back to plain set, which appends.\n doc.set(key, value);\n return;\n }\n // Wrap the key in a Scalar so we can attach commentBefore + spaceBefore\n // to it. A plain string key (which is what Pair accepts as a shorthand)\n // doesn't have those fields.\n const keyScalar = new Scalar(key);\n if (comment) {\n keyScalar.commentBefore = comment;\n keyScalar.spaceBefore = true;\n }\n const pair = new Pair(keyScalar, value);\n const nameIdx = root.items.findIndex((p) => {\n const k = p.key as { value?: unknown } | string | null;\n return (typeof k === 'string' ? k : (k?.value ?? null)) === 'name';\n });\n const insertAt = nameIdx >= 0 ? nameIdx + 1 : Math.min(1, root.items.length);\n root.items.splice(insertAt, 0, pair);\n}\n\nconst GIT_USER_HEADER_COMMENT = [\n ' Git committer identity for this container. Overrides',\n \" monoceros-config.yml's defaults.git.user. Applies to every repo\",\n ' below unless that repo declares its own `git.user` override.',\n].join('\\n');\n\n/**\n * Read the port number from a `routing.ports:` entry — handles both\n * the short form (`- 3000`) and the long form (`- port: 3000`).\n * Returns `null` for malformed entries (the schema catches them, but\n * the mutator is defensive).\n */\nfunction portOfItem(item: unknown): number | null {\n const scalar = scalarValue(item);\n if (typeof scalar === 'number' && Number.isInteger(scalar)) {\n return scalar;\n }\n if (isMap(item)) {\n const p = item.get('port');\n if (typeof p === 'number' && Number.isInteger(p)) return p;\n }\n return null;\n}\n\n/** Ensure `routing` is a map and return it (created if absent). */\nfunction ensureRoutingMap(doc: Document): YAMLMap {\n const existing = doc.get('routing', true);\n if (existing && isMap(existing)) return existing;\n const map = new YAMLMap();\n doc.set('routing', map);\n return map;\n}\n\n/**\n * Move a port to position 0 in the `routing.ports` sequence (or add\n * it there if it isn't already in the list). The first entry doubles\n * as the bare `<name>.localhost` default route in the Traefik dynamic\n * config, so this is how the builder picks which app the bare URL\n * points at.\n *\n * Returns `true` if anything changed. Idempotent: when the port is\n * already at index 0, the call is a no-op.\n */\nexport function setDefaultPortInDoc(doc: Document, port: number): boolean {\n const routing = ensureRoutingMap(doc);\n const existing = routing.get('ports', true);\n let seq: YAMLSeq;\n if (existing && isSeq(existing)) {\n seq = existing;\n } else {\n seq = new YAMLSeq();\n routing.set('ports', seq);\n }\n const currentIdx = seq.items.findIndex((i) => portOfItem(i) === port);\n if (currentIdx === 0) return false;\n if (currentIdx > 0) {\n // Splice out preserves the node — comments attached to the entry\n // ride along to the new position. Then unshift back at index 0.\n const [item] = seq.items.splice(currentIdx, 1);\n seq.items.unshift(item);\n return true;\n }\n // Not in the list yet — insert at the front.\n seq.items.unshift(port);\n return true;\n}\n\n/**\n * Add (or no-op) one or more ports to `routing.ports`. Comparison is\n * by port number, so a long-form entry (`- port: 3000`) matches a\n * short-form input (`3000`) and vice versa — that keeps `add-port`\n * idempotent against either form the builder may have written by\n * hand.\n *\n * Writes the short form for new entries (lowest-noise yml). To get\n * the long form, the builder edits the yml directly — relevant once\n * the long form carries additional fields (TLS entrypoint, path\n * prefix … see ADR 0007).\n */\nexport function addPortsToDoc(doc: Document, ports: number[]): boolean {\n const routing = ensureRoutingMap(doc);\n const existing = routing.get('ports', true);\n let seq: YAMLSeq;\n if (existing && isSeq(existing)) {\n seq = existing;\n } else {\n seq = new YAMLSeq();\n routing.set('ports', seq);\n }\n let changed = false;\n for (const port of ports) {\n if (seq.items.some((i) => portOfItem(i) === port)) continue;\n seq.add(port);\n changed = true;\n }\n // No prune here — a non-empty `routing.ports` is the whole point of\n // add-port. If `routing` was freshly created with only this `ports:`\n // field, leaving it bare is fine; future fields (vscodeAutoForward\n // etc.) attach to the same map.\n return changed;\n}\n\n/**\n * Remove one or more ports from `routing.ports`. Matches both short\n * and long form. Idempotent — ports not present are skipped silently,\n * the return reflects whether any actual removal happened. When the\n * port list is empty after removal, the `ports:` key is pruned. If\n * `routing` becomes completely empty (no other sub-keys), the whole\n * block is dropped too — symmetric to how other sequence-emptying\n * mutators behave.\n */\nexport function removePortsFromDoc(doc: Document, ports: number[]): boolean {\n const routing = doc.get('routing', true);\n if (!routing || !isMap(routing)) return false;\n const seq = routing.get('ports', true);\n if (!seq || !isSeq(seq)) return false;\n const targets = new Set(ports);\n let changed = false;\n for (let i = seq.items.length - 1; i >= 0; i--) {\n const p = portOfItem(seq.items[i]);\n if (p !== null && targets.has(p)) {\n seq.items.splice(i, 1);\n changed = true;\n }\n }\n if (changed) {\n if (seq.items.length === 0) routing.delete('ports');\n if (routing.items.length === 0) doc.delete('routing');\n }\n return changed;\n}\n\nexport function addInstallUrlToDoc(doc: Document, url: string): boolean {\n const seq = ensureSeq(doc, 'installUrls');\n if (seq.items.some((i) => scalarValue(i) === url)) return false;\n seq.add(url);\n return true;\n}\n\n/**\n * Add (or no-op) a devcontainer feature entry. Mirrors the legacy\n * `add-feature` semantics: re-adding the same ref with different\n * options is an explicit error (the builder must remove + re-add to\n * change options); same ref + same options is a no-op.\n *\n * `displayName` is what the builder typed on the command line —\n * either a short-name (`atlassian` / `atlassian/twg`) or the full\n * OCI ref. Used in error messages so the suggestion to run\n * `monoceros remove-feature <X>` echoes the form they're familiar\n * with rather than the always-the-full-ref form. Defaults to `ref`\n * when omitted.\n */\nexport function addFeatureToDoc(\n doc: Document,\n ref: string,\n options: FeatureOptions = {},\n displayName?: string,\n): boolean {\n const seq = ensureSeq(doc, 'features');\n const label = displayName ?? ref;\n for (const item of seq.items) {\n if (!isMap(item)) continue;\n const itemRef = item.get('ref');\n if (itemRef !== ref) continue;\n // Same ref: check options equality. Use the live doc's toJS so the\n // sub-map's scalars resolve to plain values; passing doc as the\n // schema/context is required by yaml@2.\n const itemJs = item.toJS(doc) as { options?: FeatureOptions };\n const existingJs = itemJs.options ?? {};\n if (JSON.stringify(existingJs) === JSON.stringify(options)) {\n return false;\n }\n throw new Error(\n `Feature ${label} is already configured with different options. Remove it first (\\`monoceros remove-feature ${label}\\`) before re-adding.`,\n );\n }\n const entry = new YAMLMap();\n entry.set('ref', ref);\n if (Object.keys(options).length > 0) {\n entry.set('options', options);\n }\n // Manifest-driven per-feature header block (tagline + description,\n // options summary, documentationURL) — the same prose the init\n // generator emits. Attached as commentBefore on the sequence ITEM\n // (the entry map itself) so yaml-lib renders it as a block ABOVE\n // the dash:\n //\n // # Atlassian — …\n // # Options: …\n // - ref: ghcr.io/…/atlassian:1\n //\n // Attaching to the inner `ref` key instead would land the comment\n // INSIDE the dash block (`- # Atlassian` on one line) — valid yaml\n // but visually inconsistent with what `init` produces. Unknown /\n // third-party refs produce no summary → no header → bare `- ref:`.\n const summary = loadFeatureManifestSummary(ref);\n const headerBefore = buildFeatureHeaderCommentBefore(\n summary,\n FEATURE_HEADER_WIDTH,\n );\n if (headerBefore.length > 0) {\n (entry as { commentBefore?: string }).commentBefore = headerBefore;\n (entry as { spaceBefore?: boolean }).spaceBefore = true;\n }\n // Credential option hints as a commented `${VAR}` skeleton below the\n // `- ref:` — same placeholders init renders, and the matching env vars\n // are seeded into <name>.env by runAddFeature. As a node `.comment`\n // (the only attachment that survives the move into the sequence),\n // serialized with a `# ` prefix per line.\n const hints = featureOptionHints(summary, ref, Object.keys(options));\n if (hints.length > 0) {\n const commentLines = [' options:'];\n for (const h of hints) commentLines.push(` ${h.key}: ${h.placeholder}`);\n (entry as { comment?: string }).comment = commentLines.join('\\n');\n }\n seq.add(entry);\n return true;\n}\n\n/**\n * Add (or no-op) a repo entry to the `repos:` sequence.\n *\n * Idempotency: if an existing entry has the same URL AND the same\n * effective path AND the same gitUser, this is a no-op (returns\n * false). \"Effective path\" means the explicit `path:` value if set,\n * or the URL-derived single-segment default otherwise. Same URL\n * with a different path is intentionally allowed — that's the \"I\n * want two clones of the same repo into different folders\" case.\n *\n * `gitUser` is an optional per-repo override of the container-level\n * git.user. When set, persisted as a `git.user` nested map; falls\n * back to the container default at apply time when omitted.\n *\n * Branches are not part of this model. Switching branches is a\n * `git checkout` inside the container, not a yml-level concern.\n */\nexport function addRepoToDoc(doc: Document, repo: RepoEntry): boolean {\n const seq = ensureSeq(doc, 'repos');\n for (const item of seq.items) {\n if (!isMap(item)) continue;\n const url = item.get('url');\n if (url !== repo.url) continue;\n const existingPath = item.get('path');\n const effectivePath =\n typeof existingPath === 'string'\n ? existingPath\n : deriveRepoName(url as string);\n if (effectivePath !== repo.path) continue;\n // Same url + same path. Check gitUser + provider equivalence too\n // so an entry that adds/changes either field is treated as an\n // update, not silently ignored.\n const existingGit = item.get('git', true);\n const existingUser =\n existingGit && isMap(existingGit) ? existingGit.get('user', true) : null;\n const existingName =\n existingUser && isMap(existingUser) ? existingUser.get('name') : null;\n const existingEmail =\n existingUser && isMap(existingUser) ? existingUser.get('email') : null;\n const existingGitUser =\n typeof existingName === 'string' && typeof existingEmail === 'string'\n ? { name: existingName, email: existingEmail }\n : undefined;\n const sameGitUser =\n (existingGitUser?.name ?? null) === (repo.gitUser?.name ?? null) &&\n (existingGitUser?.email ?? null) === (repo.gitUser?.email ?? null);\n const existingProvider = item.get('provider');\n const sameProvider =\n (typeof existingProvider === 'string' ? existingProvider : null) ===\n (repo.provider ?? null);\n if (sameGitUser && sameProvider) {\n return false;\n }\n // Different gitUser or provider → update in place instead of\n // appending a duplicate. Re-running add-repo with new values is\n // the natural way to change either field.\n if (repo.gitUser) {\n const gitMap = new YAMLMap();\n const userMap = new YAMLMap();\n userMap.set('name', repo.gitUser.name);\n userMap.set('email', repo.gitUser.email);\n gitMap.set('user', userMap);\n item.set('git', gitMap);\n } else {\n item.delete('git');\n }\n if (repo.provider) {\n item.set('provider', repo.provider);\n } else {\n item.delete('provider');\n }\n // The `mutate()` wrapper relocates leaked section comments\n // post-mutation — no per-call invocation needed here.\n return true;\n }\n const entry = new YAMLMap();\n entry.set('url', repo.url);\n // Only persist `path` when it differs from the URL-derived default.\n // Keeps the yml minimal — the apply pipeline re-derives at runtime.\n const persistPath = repo.path !== deriveRepoName(repo.url);\n if (persistPath) {\n entry.set('path', repo.path);\n }\n if (repo.gitUser) {\n const gitMap = new YAMLMap();\n const userMap = new YAMLMap();\n userMap.set('name', repo.gitUser.name);\n userMap.set('email', repo.gitUser.email);\n gitMap.set('user', userMap);\n entry.set('git', gitMap);\n }\n if (repo.provider) {\n entry.set('provider', repo.provider);\n }\n // Surface the optional fields the caller did NOT pass as commented\n // hints right under the entry — same single-`#`-depth shape the\n // generator emits in composed mode (`# path: / # provider: / …`).\n // Without these the builder can't see at a glance what else they\n // could set without re-reading the docs.\n const hintLines: string[] = [];\n if (!persistPath) hintLines.push(' path:');\n if (!repo.provider) hintLines.push(' provider:');\n if (!repo.gitUser) {\n hintLines.push(' git:');\n hintLines.push(' user:');\n hintLines.push(' name:');\n hintLines.push(' email:');\n }\n if (hintLines.length > 0) {\n (entry as { comment?: string }).comment = hintLines.join('\\n');\n }\n seq.add(entry);\n // Section-comment relocation runs in `mutate()` post-apply.\n return true;\n}\n\n/**\n * Remove helpers — symmetric to add, used by Task 6's `remove-*`\n * commands. Returning false when the target isn't present makes them\n * idempotent (caller logs \"no-change\" instead of erroring).\n */\nexport function removeLanguageFromDoc(doc: Document, lang: string): boolean {\n return removeScalarFromSeq(doc, 'languages', lang);\n}\n\nexport function removeServiceFromDoc(doc: Document, service: string): boolean {\n const node = doc.get('services', true);\n if (!node || !isSeq(node)) return false;\n const idx = node.items.findIndex(\n (i) => isMap(i) && i.get('name') === service,\n );\n if (idx === -1) return false;\n node.items.splice(idx, 1);\n pruneEmptySeq(doc, 'services');\n return true;\n}\n\nexport function removeAptPackageFromDoc(doc: Document, pkg: string): boolean {\n return removeScalarFromSeq(doc, 'aptPackages', pkg);\n}\n\n/** Plural form — for `monoceros remove-apt-packages a b c`. */\nexport function removeAptPackagesFromDoc(\n doc: Document,\n packages: string[],\n): boolean {\n let changed = false;\n for (const pkg of packages) {\n if (removeAptPackageFromDoc(doc, pkg)) changed = true;\n }\n return changed;\n}\n\nexport function removeInstallUrlFromDoc(doc: Document, url: string): boolean {\n return removeScalarFromSeq(doc, 'installUrls', url);\n}\n\nexport function removeFeatureFromDoc(doc: Document, ref: string): boolean {\n const seq = doc.get('features', true);\n if (!seq || !isSeq(seq)) return false;\n const idx = seq.items.findIndex((i) => isMap(i) && i.get('ref') === ref);\n if (idx < 0) return false;\n\n // yaml-lib parks the header comment block that visually precedes\n // entry[idx] as the trailing `.comment` of the PREVIOUS sequence\n // item, separated from that item's own inline hints by a `\\n\\n`.\n // Splicing the entry doesn't touch the previous sibling, so the\n // header lines would survive in the previous entry's trailing\n // comment and re-emit as orphaned column-2 prose under features.\n // Strip the post-`\\n\\n` tail from the previous item's comment\n // before we splice — symmetric to how relocateLeakedSectionComments\n // moves the routing-section header forward.\n if (idx > 0) {\n const prev = seq.items[idx - 1] as { comment?: string | null } | null;\n if (prev && typeof prev.comment === 'string' && prev.comment.length > 0) {\n const blank = prev.comment.match(/\\n[ \\t]*\\n/);\n if (blank && blank.index !== undefined) {\n prev.comment = prev.comment.slice(0, blank.index);\n }\n }\n }\n\n seq.items.splice(idx, 1);\n pruneEmptySeq(doc, 'features');\n return true;\n}\n\n/**\n * Remove a repo by either its url or its (effective) path. Symmetry\n * to add-repo: `monoceros remove-repo <url-or-path>` matches either\n * field. For nested paths the full path is the match key\n * (`remove-repo apps/web`), not the leaf segment.\n */\nexport function removeRepoFromDoc(doc: Document, urlOrPath: string): boolean {\n const seq = doc.get('repos', true);\n if (!seq || !isSeq(seq)) return false;\n const idx = seq.items.findIndex((item) => {\n if (!isMap(item)) return false;\n const url = item.get('url');\n if (url === urlOrPath) return true;\n const path = item.get('path');\n const effectivePath =\n typeof path === 'string'\n ? path\n : typeof url === 'string'\n ? deriveRepoName(url)\n : undefined;\n return effectivePath === urlOrPath;\n });\n if (idx < 0) return false;\n seq.items.splice(idx, 1);\n pruneEmptySeq(doc, 'repos');\n return true;\n}\n\nfunction removeScalarFromSeq(\n doc: Document,\n key: string,\n value: string,\n): boolean {\n const seq = doc.get(key, true);\n if (!seq || !isSeq(seq)) return false;\n const idx = seq.items.findIndex((i) => scalarValue(i) === value);\n if (idx < 0) return false;\n seq.items.splice(idx, 1);\n pruneEmptySeq(doc, key);\n return true;\n}\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport type { FeatureOptions } from '../create/types.js';\nimport { getInnerArgs } from '../inner-args.js';\nimport { runAddFeature } from '../modify/index.js';\n\nexport const addFeatureCommand = defineCommand({\n meta: {\n name: 'add-feature',\n group: 'edit',\n description:\n 'Add a devcontainer feature by ref to the container config. Options follow `--` as `key=value` pairs. Idempotent (same ref + same options is a no-op). Adding the same ref with different options is an error.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n ref: {\n type: 'positional',\n description:\n 'Feature to add. Either a Monoceros catalog short-name (e.g. `atlassian`, `atlassian/twg`, `claude` — see `monoceros list-components`) or a full OCI feature ref (e.g. `ghcr.io/devcontainers/features/docker-in-docker:2`). The short-name brings its catalog-defined default options; `-- key=value` overrides them.',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n let options: FeatureOptions;\n try {\n options = parseOptionsAfterDashes(getInnerArgs());\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n try {\n const result = await runAddFeature({\n name: args.name,\n ref: args.ref,\n options,\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n\n/**\n * Parse `key=value` tokens (one per arg) into a feature options hash.\n * Coerces `true`/`false` to booleans and pure-integer strings to\n * numbers; everything else stays a string.\n */\nfunction parseOptionsAfterDashes(tokens: readonly string[]): FeatureOptions {\n const result: FeatureOptions = {};\n for (const token of tokens) {\n const eqIdx = token.indexOf('=');\n if (eqIdx <= 0) {\n throw new Error(\n `Invalid option: ${JSON.stringify(token)}. Expected key=value (e.g. version=latest).`,\n );\n }\n const key = token.slice(0, eqIdx);\n const raw = token.slice(eqIdx + 1);\n result[key] = coerce(raw);\n }\n return result;\n}\n\nfunction coerce(value: string): string | number | boolean {\n if (value === 'true') return true;\n if (value === 'false') return false;\n if (/^-?\\d+$/.test(value)) {\n const n = Number(value);\n if (Number.isSafeInteger(n)) return n;\n }\n return value;\n}\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runAddFromUrl } from '../modify/index.js';\n\nexport const addFromUrlCommand = defineCommand({\n meta: {\n name: 'add-from-url',\n group: 'edit',\n description:\n 'Add an https:// install URL to the container config. The URL gets piped to sh on every container rebuild. Loudly warns about remote-code execution before persisting. Idempotent.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n url: {\n type: 'positional',\n description:\n 'https:// URL of an install script (e.g. https://starship.rs/install.sh).',\n required: true,\n },\n yes: {\n type: 'boolean',\n description:\n 'Skip the security warning + diff confirm. Use only in scripts where you have already audited the URL.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n if (!args.yes) {\n printSecurityWarning(args.url);\n }\n try {\n const result = await runAddFromUrl({\n name: args.name,\n url: args.url,\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n\nfunction printSecurityWarning(url: string): void {\n const w = (line: string) => process.stderr.write(line + '\\n');\n w('');\n w('⚠️ SECURITY WARNING — `monoceros add-from-url`');\n w('');\n w(` URL: ${url}`);\n w('');\n w(' This URL will be fetched and piped to sh on every container rebuild.');\n w(\n ' Remote-code execution against a URL you do not control is a supply-chain',\n );\n w(\n ' risk: the maintainer could change the script tomorrow and your container',\n );\n w(' would silently run the new payload.');\n w('');\n w(' Before confirming below:');\n w(' 1. Open the URL in a browser, read what the script does.');\n w(\n ' 2. Verify the maintainer is who you think they are (HTTPS cert, repo).',\n );\n w(' 3. Ideally, vendor the install steps as `add-apt-packages` or');\n w(\n ' `add-feature` instead — those reference signed/versioned artifacts.',\n );\n w('');\n}\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runAddRepo } from '../modify/index.js';\n\nexport const addRepoCommand = defineCommand({\n meta: {\n name: 'add-repo',\n group: 'edit',\n description:\n 'Add a git repo to the container config. Cloned into projects/<path>/ on container build. Idempotent — existing project subfolders are left alone. Destination path derived from URL by default; override with --path (supports nested subfolders like apps/web). Branches/PRs are git-level concerns: clone, then `git checkout` inside the container.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n url: {\n type: 'positional',\n description:\n 'Git URL (HTTPS or SSH/git@ form). E.g. https://github.com/foo/bar.git, git@github.com:foo/bar.git.',\n required: true,\n },\n path: {\n type: 'string',\n description:\n 'Destination under projects/. Subfolders via `/` (e.g. apps/web). Default: URL-derived single segment (bar.git → bar).',\n },\n 'git-name': {\n type: 'string',\n description:\n 'Per-repo git committer name. Overrides the container-level git.user.name for this repo only. Pair with --git-email.',\n },\n 'git-email': {\n type: 'string',\n description:\n 'Per-repo git committer email. Overrides the container-level git.user.email for this repo only. Pair with --git-name.',\n },\n provider: {\n type: 'string',\n description:\n 'Git provider for credential-helper guidance: github | gitlab | bitbucket. Required when the URL host is not github.com, gitlab.com, or bitbucket.org — Monoceros uses this to suggest the right CLI (gh / glab / Atlassian token) on missing credentials.',\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n try {\n const result = await runAddRepo({\n name: args.name,\n url: args.url,\n ...(typeof args.path === 'string' ? { path: args.path } : {}),\n ...(typeof args['git-name'] === 'string'\n ? { gitName: args['git-name'] }\n : {}),\n ...(typeof args['git-email'] === 'string'\n ? { gitEmail: args['git-email'] }\n : {}),\n ...(typeof args.provider === 'string'\n ? { provider: args.provider }\n : {}),\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runAddLanguage } from '../modify/index.js';\n\nexport const addLanguageCommand = defineCommand({\n meta: {\n name: 'add-language',\n group: 'edit',\n description:\n 'Add a language toolchain (devcontainer feature) to the container config. Idempotent, prints a diff before writing.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n language: {\n type: 'positional',\n description:\n 'Language identifier from the feature whitelist (e.g. python, java, rust).',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n try {\n const result = await runAddLanguage({\n name: args.name,\n language: args.language,\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { getInnerArgs } from '../inner-args.js';\nimport { runAddPort } from '../modify/index.js';\n\nexport const addPortCommand = defineCommand({\n meta: {\n name: 'add-port',\n group: 'edit',\n description:\n 'Add one or more ports to the container config so they become reachable from the host via Traefik (`<container>.localhost` / `<container>-<port>.localhost`). Pass port numbers after `--` (e.g. `monoceros add-port sandbox -- 3000 5173 6006`). Idempotent. Persisted in the yml so later `monoceros apply` runs restore the routes. Pass `--default` together with a single port to make it the bare `<container>.localhost` route — the port is inserted at position 0 (or moved there if it already exists).',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n default: {\n type: 'boolean',\n description:\n 'Make the (single) port the new default route at `<container>.localhost`. Inserts the port at position 0 of `routing.ports`, or moves it there if it already exists. Errors when more than one port is passed.',\n default: false,\n },\n },\n async run({ args }) {\n const tokens = [...getInnerArgs()];\n if (tokens.length === 0) {\n consola.error(\n 'No ports given. Usage: `monoceros add-port <containername> [--yes] [--default] -- <port> [<port> …]`.',\n );\n process.exit(1);\n }\n try {\n const result = await runAddPort({\n name: args.name,\n ports: tokens.map(coerceToken),\n yes: args.yes,\n asDefault: args.default,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n\n/**\n * Surface non-integer CLI tokens via the same error path as out-of-range\n * ports — `runAddPort` validates the numeric value, but we need to get\n * there with a number-or-string for the message to read naturally.\n */\nfunction coerceToken(raw: string): number {\n const n = Number(raw);\n return Number.isFinite(n) ? n : (raw as unknown as number);\n}\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runAddService } from '../modify/index.js';\n\nexport const addServiceCommand = defineCommand({\n meta: {\n name: 'add-service',\n group: 'edit',\n description:\n 'Add a backing service to the container config. A curated name (postgres, mysql, redis) expands to a full editable block; any other image (e.g. rustfs/rustfs:latest) drops in name + image plus a commented scaffold. Idempotent, prints a diff before writing.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n service: {\n type: 'positional',\n description:\n 'Curated name (postgres, mysql, redis) or any image ref (e.g. rustfs/rustfs:latest).',\n required: true,\n },\n as: {\n type: 'string',\n description:\n 'Override the service name (the compose service / DNS name / data dir). Lets you add the same image more than once — e.g. two postgres servers as postgres-app and postgres-analytics.',\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n try {\n const result = await runAddService({\n name: args.name,\n service: args.service,\n ...(args.as ? { as: args.as } : {}),\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { runApply } from '../apply/index.js';\nimport { CLI_VERSION } from '../version.js';\nimport { dispatch } from './_dispatch.js';\n\n/**\n * `monoceros apply <name>` — materialize the yml at\n * `<MONOCEROS_HOME>/container-configs/<name>.yml` into\n * `<MONOCEROS_HOME>/container/<name>/` and bring the container up.\n *\n * The target location is fixed by convention. cwd is irrelevant. No\n * `--path` override — one config maps to exactly one container\n * directory, and that's the whole mental model.\n */\nexport const applyCommand = defineCommand({\n meta: {\n name: 'apply',\n group: 'lifecycle',\n description:\n 'Materialize a container config into $MONOCEROS_HOME/container/<name>/ and bring the dev-container up. Close any VS Code Remote Containers session for the target first — the extension auto-recreates and races with apply.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Config name. Resolves to $MONOCEROS_HOME/container-configs/<name>.yml.',\n required: true,\n },\n },\n run({ args }) {\n return dispatch(async () => {\n const result = await runApply({\n name: args.name,\n cliVersion: CLI_VERSION,\n });\n return result.containerExitCode;\n });\n },\n});\n","import { existsSync, promises as fs } from 'node:fs';\nimport { consola } from 'consola';\nimport {\n type MonocerosConfig,\n proxyHostPort,\n readMonocerosConfig,\n} from '../config/global.js';\nimport { parseConfig, readConfig, stringifyConfig } from '../config/io.js';\nimport {\n containerConfigPath,\n containerConfigsDir,\n containerDir,\n containerEnvPath,\n monocerosHome as defaultMonocerosHome,\n prettyPath,\n} from '../config/paths.js';\nimport {\n readEnvFile,\n interpolateServices,\n interpolateFeatures,\n formatMissingVarsError,\n ensureEnvGitignored,\n resolveGitUserFields,\n} from '../config/env-file.js';\nimport { REGEX, isValidEmail } from '../config/schema.js';\nimport {\n buildStateFile,\n readStateFile,\n writeStateFile,\n} from '../config/state.js';\nimport type { SolutionConfig } from '../config/schema.js';\nimport { solutionConfigToCreateOptions } from '../config/transform.js';\nimport {\n needsCompose,\n normalizeOptions,\n validateOptions,\n writeScaffold,\n} from '../create/scaffold.js';\nimport { cyan, dim, sectionLine } from '../util/format.js';\nimport { migrateDeprecatedFeatureRef } from '../util/ref.js';\nimport { type DockerExec, runContainerCycle } from '../devcontainer/compose.js';\nimport {\n type CredentialsSpawn,\n collectGitCredentials,\n uniqueHttpsHosts,\n formatMissingCredentialsError,\n formatUnknownProviderError,\n} from '../devcontainer/credentials.js';\nimport {\n type DockerInfoSpawn,\n detectDockerMode,\n formatRootlessNotSupportedError,\n} from '../devcontainer/docker-mode.js';\nimport { type DevcontainerSpawn } from '../devcontainer/cli.js';\nimport {\n ensureProxy,\n type DockerExec as ProxyDockerExec,\n} from '../proxy/index.js';\nimport { removeDynamicConfig, writeDynamicConfig } from '../proxy/dynamic.js';\nimport { preflightHostPort } from '../proxy/port-check.js';\nimport {\n collectGitIdentity,\n type IdentityPrompt,\n type IdentityScopePrompt,\n type IdentitySpawn,\n} from '../devcontainer/identity.js';\nimport { writeGlobalDefaultGitUser } from '../config/global.js';\nimport { setContainerGitUserInDoc } from '../modify/yml.js';\n\n/**\n * `monoceros apply <name>` — read the yml at\n * `<MONOCEROS_HOME>/container-configs/<name>.yml`, materialize the\n * devcontainer scaffold at `<MONOCEROS_HOME>/container/<name>/`, write\n * `.monoceros/state.json` with `origin: <name>`, then teardown + bring\n * the container up.\n *\n * The target location is determined by convention, not by cwd or an\n * explicit path argument. That's deliberate: a config is the source of\n * truth, the container directory mirrors it 1:1, and the builder never\n * has to remember \"which directory was sandbox materialized into\".\n *\n * Idempotent: re-running picks up the current yml, overwrites scaffold\n * files, restarts the container.\n *\n * Refuses to materialize into a non-empty directory whose state.json\n * points at a different origin — protects against accidental clobber\n * if a builder somehow seeded `<MONOCEROS_HOME>/container/<name>/`\n * outside of this command.\n */\n\nexport interface RunApplyOptions {\n /** Config name — resolves to `<home>/container-configs/<name>.yml`. */\n name: string;\n cliVersion: string;\n /** Override of the user-data home. Tests inject a tmpdir. */\n monocerosHome?: string;\n now?: Date;\n logger?: {\n info: (msg: string) => void;\n success: (msg: string) => void;\n warn?: (msg: string) => void;\n /**\n * Print a structural section marker (`▸ Configuration` etc).\n * Optional — tests typically pass a silent logger without one,\n * in which case section markers are no-ops.\n */\n section?: (label: string) => void;\n };\n dockerExec?: DockerExec;\n devcontainerSpawn?: DevcontainerSpawn;\n credentialsSpawn?: CredentialsSpawn;\n dockerInfoSpawn?: DockerInfoSpawn;\n identitySpawn?: IdentitySpawn;\n identityPrompt?: IdentityPrompt;\n identityScopePrompt?: IdentityScopePrompt;\n /** Override the docker exec used by the Traefik proxy lifecycle. */\n proxyDocker?: ProxyDockerExec;\n}\n\nexport interface RunApplyResult {\n /** Absolute path to the materialized container directory. */\n targetDir: string;\n /** Absolute path to the source yml. */\n configPath: string;\n /** Exit code of the trailing `devcontainer up` step. */\n containerExitCode: number;\n}\n\nexport async function runApply(opts: RunApplyOptions): Promise<RunApplyResult> {\n const home = opts.monocerosHome ?? defaultMonocerosHome();\n const logger = opts.logger ?? {\n info: (msg) => consola.info(msg),\n success: (msg) => consola.success(msg),\n warn: (msg) => consola.warn(msg),\n // Default section renderer: empty line, bold-underlined \"▸ Label\",\n // empty line. Mirrors install.sh's section visuals.\n section: (label) => process.stderr.write(`\\n${sectionLine(label)}\\n\\n`),\n };\n const section = (label: string) => logger.section?.(label);\n\n if (!REGEX.solutionName.test(opts.name)) {\n throw new Error(\n `Invalid config name: ${JSON.stringify(opts.name)}. Use letters, digits, '.', '_' or '-'.`,\n );\n }\n\n const ymlPath = containerConfigPath(opts.name, home);\n if (!existsSync(ymlPath)) {\n throw new Error(\n `No such config: ${ymlPath}. Run \\`monoceros init <template> ${opts.name}\\` first.`,\n );\n }\n\n const targetDir = containerDir(opts.name, home);\n await assertSafeTargetDir(targetDir, opts.name);\n\n // ── Configuration ────────────────────────────────────────────\n section('Configuration');\n\n const parsed = await readConfig(ymlPath);\n // Read global defaults early — feature option defaults from\n // `monoceros-config.yml` need to be merged before scaffold codegen,\n // and the git identity logic later in this function also needs the\n // global config.\n const globalConfig = await readMonocerosConfig({ monocerosHome: home });\n\n // Pre-M4 the canonical feature namespace was\n // `ghcr.io/monoceros/features/…`. After the M4 cut it moved to\n // `ghcr.io/getmonoceros/monoceros-features/…`. Old refs still\n // structurally parse as third-party refs, so apply would silently\n // try to pull them from GHCR and 404. Warn loudly and tell the\n // builder what to write instead — but don't rewrite their yml or\n // fail the apply, so they stay in control of the migration.\n warnOnDeprecatedFeatureRefs(parsed.config.features, globalConfig, logger);\n\n // Shape validation happened in readConfig; catalog validation\n // (which language/service exists) happens here against\n // create/scaffold's known set.\n const createOpts = normalizeOptions(\n solutionConfigToCreateOptions(\n parsed.config,\n globalConfig?.defaults?.features ?? {},\n ),\n );\n\n // Resolve `${VAR}` references against the per-container env file\n // (container-configs/<name>.env) — in service fields AND feature\n // option values. That's what lets credentials (DB passwords,\n // `apiKey`/`apiToken`) live in the gitignored env file instead of the\n // shareable yml. An unresolved reference is a hard error (a silently-\n // empty secret fails far more opaquely later).\n const envPath = containerEnvPath(opts.name, home);\n await ensureEnvGitignored(containerConfigsDir(home));\n const envVars = readEnvFile(envPath);\n const interpServices = interpolateServices(createOpts.services, envVars);\n const interpFeatures = interpolateFeatures(\n createOpts.features ?? {},\n envVars,\n );\n const missingVars = [...interpServices.missing, ...interpFeatures.missing];\n if (missingVars.length > 0) {\n throw new Error(formatMissingVarsError(missingVars, prettyPath(envPath)));\n }\n createOpts.services = interpServices.services;\n if (createOpts.features) createOpts.features = interpFeatures.features;\n\n // Resolve `${VAR}` in git identities — the container-level `git.user`\n // and each repo's `git.user` — against the same env file. UNLIKE\n // services/features, a missing var is NOT an error here: the identity\n // falls through to the existing cascade (monoceros-config defaults →\n // host → prompt). Only a fully-resolved-but-malformed email is a hard\n // error, checked now that the actual value is known (the schema defers\n // email format to apply on purpose).\n //\n // - container `git.user`: per field. A resolved field is used; an\n // unresolved one is dropped so the cascade fills it (the cascade\n // already resolves name/email independently).\n // - repo `git.user`: all-or-nothing. A single missing var drops the\n // whole per-repo override, so the repo inherits the container\n // identity (.monoceros/gitconfig) — no Frankenstein name-from-env +\n // email-from-cascade.\n const gitUserErrors: string[] = [];\n let containerGitOverride: { name?: string; email?: string } | undefined;\n if (parsed.config.git?.user) {\n const f = resolveGitUserFields(parsed.config.git.user, envVars);\n if (f.email.value !== undefined && !isValidEmail(f.email.value)) {\n gitUserErrors.push(\n `git.user.email resolved to \"${f.email.value}\", which is not a valid email`,\n );\n }\n const override = {\n ...(f.name.value !== undefined ? { name: f.name.value } : {}),\n ...(f.email.value !== undefined ? { email: f.email.value } : {}),\n };\n if (Object.keys(override).length > 0) containerGitOverride = override;\n }\n for (const repo of createOpts.repos ?? []) {\n if (!repo.gitUser) continue;\n const f = resolveGitUserFields(repo.gitUser, envVars);\n if (f.name.value === undefined || f.email.value === undefined) {\n // All-or-nothing: a field with no usable value (missing/empty var)\n // drops the whole per-repo override → the repo inherits the\n // container identity, which itself climbs the cascade.\n delete repo.gitUser;\n continue;\n }\n if (!isValidEmail(f.email.value)) {\n gitUserErrors.push(\n `repos[${repo.path}].git.user.email resolved to \"${f.email.value}\", which is not a valid email`,\n );\n continue;\n }\n repo.gitUser = { name: f.name.value, email: f.email.value };\n }\n if (gitUserErrors.length > 0) {\n throw new Error(\n `Invalid git identity after resolving ${prettyPath(envPath)}:\\n` +\n gitUserErrors.map((e) => ` - ${e}`).join('\\n') +\n `\\n\\nFix the value in the env file (or the yml).`,\n );\n }\n\n validateOptions(createOpts);\n logger.success(`yml validated ${dim(`(${prettyPath(ymlPath)})`)}`);\n\n // Refresh host git identity and HTTPS credentials before the\n // container teardown so they're in place when post-create.sh runs.\n // Identity resolution priority: yml override → monoceros-config.yml\n // defaults → host global → persisted .monoceros/gitconfig → prompt.\n //\n // Skip identity collection entirely when there's no obvious reason\n // to need one: no repos to clone, no explicit yml.git.user, no\n // defaults.git.user. Without those, asking the builder for a\n // committer identity is pure friction — they didn't ask for git\n // and might just want a sandbox container. They can `monoceros\n // add-repo` later, at which point the next apply re-evaluates and\n // collects identity then.\n const hasRepos = (createOpts.repos ?? []).length > 0;\n const hasContainerGitUser = parsed.config.git?.user !== undefined;\n const hasDefaultGitUser = globalConfig?.defaults?.git?.user !== undefined;\n const idLogger = {\n info: logger.info,\n warn: logger.warn ?? logger.info,\n };\n if (hasRepos || hasContainerGitUser || hasDefaultGitUser) {\n const identity = await collectGitIdentity(targetDir, {\n ...(opts.identitySpawn ? { spawn: opts.identitySpawn } : {}),\n ...(opts.identityPrompt ? { prompt: opts.identityPrompt } : {}),\n ...(opts.identityScopePrompt\n ? { scopePrompt: opts.identityScopePrompt }\n : {}),\n ...(containerGitOverride\n ? { containerOverride: containerGitOverride }\n : {}),\n ...(globalConfig?.defaults?.git?.user\n ? { defaults: globalConfig.defaults.git.user }\n : {}),\n logger: idLogger,\n });\n\n // Persist a freshly-prompted identity to whichever scope the\n // builder picked. Scope `g` writes monoceros-config.yml's\n // `defaults.git.user`; `c` writes this container yml's\n // `git.user`; `b` does both. The `.monoceros/gitconfig` file\n // collectGitIdentity already wrote stays the in-container\n // mechanism — these writes are about making the value\n // recoverable on the next apply / next container without\n // re-prompting.\n if (identity.prompted) {\n await persistPromptedIdentity(identity.prompted, ymlPath, home, logger);\n }\n }\n // Pre-fetch HTTPS credentials for every unique host derived from\n // the declared repos. Pre-flight: if any host returns no credentials,\n // fail fast with provider-specific setup hints — much more\n // actionable than letting the in-container `git clone` later die\n // with \"could not read Username\".\n //\n // First pass: reject hosts whose provider couldn't be resolved\n // (non-canonical host without an explicit `provider:` in the yml).\n // Those produce a separate \"set provider:\" error message — much\n // more useful than a generic \"no credentials\" hint because the\n // builder might actually have credentials in their helper, but we\n // wouldn't know which CLI to suggest.\n const hostsToFetch = uniqueHttpsHosts(createOpts.repos ?? []);\n const unknownProviderHosts = hostsToFetch\n .filter((h) => h.provider === 'unknown')\n .map((h) => h.host);\n if (unknownProviderHosts.length > 0) {\n throw new Error(formatUnknownProviderError(unknownProviderHosts));\n }\n if (hostsToFetch.length > 0) {\n const credResult = await collectGitCredentials(targetDir, hostsToFetch, {\n ...(opts.credentialsSpawn ? { spawn: opts.credentialsSpawn } : {}),\n logger: idLogger,\n });\n const missing = credResult.perHost.filter((p) => p.status !== 'ok');\n if (missing.length > 0) {\n throw new Error(formatMissingCredentialsError(missing));\n }\n }\n\n // NOTE: repos are cloned IN the container (post-create.sh), using the\n // container's network + the mounted credential helper. We deliberately\n // do NOT probe or clone repos host-side: the host's network/credential\n // context isn't the container's (a host may fail to resolve a remote\n // the container reaches fine), so host-side gating produced spurious\n // pre-flight failures across platforms. The in-container clone is the\n // single source of truth and reports a real error if a repo genuinely\n // can't be reached. (The host-side clone added in ADR 0012 — for\n // service bind-mounts of repo files like init.sql — was reverted; that\n // ordering needs a container-side solution instead.)\n\n // ── Scaffold ─────────────────────────────────────────────────\n section('Scaffold');\n\n // Probe the host docker daemon. Two purposes today:\n // - Refuse to apply on rootless Docker, which doesn't work with\n // our bind-mount model (host/container file ownership doesn't\n // line up; Docker doesn't expose the `idmap` mount option that\n // would fix this). The refusal lands before any docker build\n // or container start, so the builder gets a clear actionable\n // message instead of permission-denied surprises mid-clone.\n // - Plumb the mode through to scaffold for any future mode-\n // dependent code paths (parameter is currently unused after\n // the idmap revert — kept so the wiring is in place).\n const dockerMode = await detectDockerMode({\n ...(opts.dockerInfoSpawn ? { spawn: opts.dockerInfoSpawn } : {}),\n });\n if (dockerMode === 'rootless') {\n throw new Error(formatRootlessNotSupportedError());\n }\n\n await fs.mkdir(targetDir, { recursive: true });\n await writeScaffold(createOpts, targetDir, { dockerMode });\n await writeStateFile(\n targetDir,\n buildStateFile({\n origin: opts.name,\n cliVersion: opts.cliVersion,\n ...(opts.now ? { now: opts.now } : {}),\n }),\n );\n logger.success(`materialized into ${prettyPath(targetDir)}`);\n\n // Repos are cloned in-container by post-create.sh (see the NOTE above\n // the Scaffold section) — no host-side clone here.\n\n // ── Container ────────────────────────────────────────────────\n section('Container');\n\n // Pre-announce the feature list so the builder knows what's about\n // to be installed before devcontainer-cli's stream takes over.\n // Empty list = base-image-only container, no features section needed.\n const featureRefs = parsed.config.features.map((f) => f.ref);\n if (featureRefs.length > 0) {\n logger.info(`Features: ${featureRefs.map((r) => cyan(r)).join(', ')}`);\n }\n\n // First-apply UX: devcontainer-cli's upstream output prints\n // `Error fetching image details: No manifest found for …` for\n // multi-arch GHCR images, then sits silent for ~1 min while\n // Docker actually pulls the runtime image. Both are non-fatal —\n // the docker buildx step right after consumes the image just\n // fine. Flag in dim grey so it reads as ambient context.\n logger.info(\n dim(\n 'Pulling runtime image and building feature layers. First apply takes ~1–2 min (Docker downloads the multi-arch base); subsequent applies are cached and fast. devcontainer-cli may log a \"No manifest found\" line — harmless, the pull continues.',\n ),\n );\n\n // Bring up the shared Traefik singleton ahead of the devcontainer\n // when the yml declares ports, and refresh the dynamic config so\n // the routes match whatever the yml currently says. `ensureProxy`\n // and `writeDynamicConfig` are both idempotent; a second\n // devcontainer that also wants Traefik just joins the already-\n // running proxy. See ADR 0007.\n const ports = createOpts.ports ?? [];\n const hasPorts = ports.length > 0;\n if (hasPorts) {\n // Pre-flight: bail with an actionable hint before `docker run`\n // tries to bind a held port. Throws on conflict — the message\n // names the routing.hostPort escape hatch and asks the builder\n // to either free the port or set a different one.\n await preflightHostPort(proxyHostPort(globalConfig), {\n ...(opts.proxyDocker ? { docker: opts.proxyDocker } : {}),\n });\n }\n\n try {\n if (hasPorts) {\n await writeDynamicConfig(opts.name, ports, { monocerosHome: home });\n await ensureProxy({\n ...(opts.proxyDocker ? { docker: opts.proxyDocker } : {}),\n monocerosHome: home,\n hostPort: proxyHostPort(globalConfig),\n logger,\n });\n } else {\n // `ports:` is empty (or was removed since the last apply) —\n // drop any stale dynamic-config file. Filesystem only; the\n // proxy itself is offered for teardown by stop/remove, not\n // here (apply ends with the container up, not stopped).\n await removeDynamicConfig(opts.name, { monocerosHome: home });\n }\n } catch (err) {\n // Don't strand the apply if Traefik bookkeeping fails — surface\n // as a warn and keep going. The devcontainer itself is still\n // usable; the builder loses only the `<name>.localhost` routing,\n // which the next apply / `add-port` will retry.\n logger.warn?.(\n `Could not sync Traefik routes: ${err instanceof Error ? err.message : String(err)}. The container will start, but \\`<name>.localhost\\` routing may not work until the next \\`monoceros apply\\`.`,\n );\n }\n\n const exitCode = await runContainerCycle(targetDir, {\n hasCompose: needsCompose(createOpts),\n ...(opts.dockerExec !== undefined ? { dockerExec: opts.dockerExec } : {}),\n ...(opts.devcontainerSpawn !== undefined\n ? { devcontainerSpawn: opts.devcontainerSpawn }\n : {}),\n logger,\n });\n\n // ── Next steps ───────────────────────────────────────────────\n // Only print the wrap-up on a successful container start;\n // otherwise the failing devcontainer-cli output is the relevant\n // signal and a cheery \"shell into it!\" line would be misleading.\n if (exitCode === 0) {\n section('Next steps');\n logger.info(` ${cyan(`monoceros shell ${opts.name}`)}`);\n }\n\n return { targetDir, configPath: ymlPath, containerExitCode: exitCode };\n}\n\n/**\n * `<MONOCEROS_HOME>/container/<name>/` is safe to (re-)materialize iff:\n * - it doesn't exist or is empty (fresh apply), OR\n * - it already carries `.monoceros/state.json` with the same origin\n * (re-apply against the same yml), OR\n * - the only top-level entry is `.monoceros/` and there's no\n * state.json — that's a partial-apply remnant: pre-flight wrote\n * `gitconfig` / `git-credentials` into `.monoceros/` before\n * something (reachability failure, Ctrl-C, expired token, …)\n * aborted the apply ahead of `writeStateFile`. We own\n * `.monoceros/`, so re-running is safe.\n *\n * Anything else — state.json with a different origin, or files\n * outside `.monoceros/` that aren't ours — stays an error so we\n * don't clobber unrelated work.\n */\nasync function assertSafeTargetDir(\n targetDir: string,\n expectedOrigin: string,\n): Promise<void> {\n if (!existsSync(targetDir)) return;\n const entries = await fs.readdir(targetDir);\n if (entries.length === 0) return;\n\n const state = await readStateFile(targetDir);\n if (state) {\n if (state.origin !== expectedOrigin) {\n throw new Error(\n `${targetDir} is already materialized from config '${state.origin}', not '${expectedOrigin}'. Delete the directory to re-target, or run \\`monoceros apply ${state.origin}\\`.`,\n );\n }\n return; // safe: re-apply same origin\n }\n\n // No state.json. If the only top-level entry is `.monoceros/`, this\n // is a partial-apply remnant from a failed earlier run — pre-flight\n // writes `.monoceros/gitconfig` and `.monoceros/git-credentials`\n // BEFORE the scaffold + state.json sequence, so a mid-apply abort\n // leaves exactly this shape behind. Treat it as recoverable.\n if (entries.length === 1 && entries[0] === '.monoceros') {\n return;\n }\n\n throw new Error(\n `Refusing to materialize into non-empty directory ${targetDir} (no Monoceros state.json found, and the directory has files we don't recognise). Delete the directory before re-running.`,\n );\n}\n\ninterface MigrationLogger {\n warn?: (msg: string) => void;\n info: (msg: string) => void;\n}\n\nfunction warnOnDeprecatedFeatureRefs(\n containerFeatures: SolutionConfig['features'],\n globalConfig: MonocerosConfig | undefined,\n logger: MigrationLogger,\n): void {\n const warn = logger.warn ?? logger.info;\n const seen = new Set<string>();\n const emit = (oldRef: string, source: string) => {\n if (seen.has(oldRef)) return;\n seen.add(oldRef);\n const newRef = migrateDeprecatedFeatureRef(oldRef);\n if (!newRef) return;\n warn(\n `Deprecated feature ref in ${source}: '${oldRef}'. ` +\n `Replace with '${newRef}' — the old namespace is no longer published. ` +\n `See docs/MIGRATION-M4.md for a sed snippet.`,\n );\n };\n\n for (const entry of containerFeatures) {\n emit(entry.ref, 'container yml');\n }\n const globalDefaults = globalConfig?.defaults?.features;\n if (globalDefaults) {\n for (const ref of Object.keys(globalDefaults)) {\n emit(ref, 'monoceros-config.yml');\n }\n }\n}\n\n/**\n * Persist an identity that came from the interactive prompt. Called\n * with the builder's scope pick (`g`/`c`/`b`); logs to the apply\n * stream where the values landed (or why they couldn't, e.g. global\n * default was already set and we left it alone).\n *\n * Pulled out of runApply for readability — runApply already carries\n * a lot of pre-flight ceremony, and this is a self-contained\n * persistence step.\n */\nasync function persistPromptedIdentity(\n prompted: { name: string; email: string; scope: 'g' | 'c' | 'b' },\n ymlPath: string,\n home: string,\n logger: {\n info: (msg: string) => void;\n warn?: (msg: string) => void;\n },\n): Promise<void> {\n const wantGlobal = prompted.scope === 'g' || prompted.scope === 'b';\n const wantContainer = prompted.scope === 'c' || prompted.scope === 'b';\n\n if (wantGlobal) {\n try {\n const result = await writeGlobalDefaultGitUser(\n { name: prompted.name, email: prompted.email },\n { monocerosHome: home },\n );\n if (result.alreadySet) {\n logger.warn?.(\n `monoceros-config.yml already has a defaults.git.user — left it alone. To replace, edit ${prettyPath(result.filePath)} by hand.`,\n );\n } else if (result.created) {\n logger.info(\n `Saved identity globally — created ${prettyPath(result.filePath)} with defaults.git.user.`,\n );\n } else {\n logger.info(\n `Saved identity globally — wrote defaults.git.user into ${prettyPath(result.filePath)}.`,\n );\n }\n } catch (err) {\n logger.warn?.(\n `Could not persist identity to monoceros-config.yml: ${err instanceof Error ? err.message : String(err)}. The values are still active for this apply via .monoceros/gitconfig.`,\n );\n }\n }\n\n if (wantContainer) {\n try {\n const text = await fs.readFile(ymlPath, 'utf8');\n const parsed = parseConfig(text, ymlPath);\n const changed = setContainerGitUserInDoc(parsed.doc, {\n name: prompted.name,\n email: prompted.email,\n });\n if (changed) {\n const out = stringifyConfig(parsed.doc);\n await fs.writeFile(ymlPath, out, 'utf8');\n logger.info(\n `Saved identity in this container — wrote git.user into ${prettyPath(ymlPath)}.`,\n );\n }\n } catch (err) {\n logger.warn?.(\n `Could not persist identity to ${prettyPath(ymlPath)}: ${err instanceof Error ? err.message : String(err)}. The values are still active for this apply via .monoceros/gitconfig.`,\n );\n }\n }\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { CONFIG_SCHEMA_VERSION } from './schema.js';\n\n/**\n * `.monoceros/state.json` — the Phase-3 replacement for `stack.json`.\n *\n * The yml at `.local/container-configs/<origin>.yml` is the source\n * of truth. `state.json` is a back-reference: it tells `monoceros\n * apply` (no args) which yml to read and re-apply for this dev-\n * container. Other fields (appliedAt, cliVersion) are pure diagnostics\n * for `monoceros status` and ad-hoc debugging.\n *\n * `materializedAt` is the timestamp of the most recent apply, NOT\n * the create. There is no `createdAt` because the yml is the\n * lifecycle anchor now — multiple dev-containers can share one yml,\n * and each has its own state.json timeline.\n */\nexport interface StateFile {\n schemaVersion: typeof CONFIG_SCHEMA_VERSION;\n /** Config name the yml is stored under (`<origin>.yml`). */\n origin: string;\n /** Monoceros CLI version that wrote this state.json. */\n monocerosCliVersion: string;\n /** ISO-8601 timestamp of the most recent apply. */\n materializedAt: string;\n}\n\nexport function buildStateFile(opts: {\n origin: string;\n cliVersion: string;\n now?: Date;\n}): StateFile {\n return {\n schemaVersion: CONFIG_SCHEMA_VERSION,\n origin: opts.origin,\n monocerosCliVersion: opts.cliVersion,\n materializedAt: (opts.now ?? new Date()).toISOString(),\n };\n}\n\nexport function stateFilePath(targetDir: string): string {\n return path.join(targetDir, '.monoceros', 'state.json');\n}\n\nexport async function readStateFile(\n targetDir: string,\n): Promise<StateFile | undefined> {\n try {\n const content = await fs.readFile(stateFilePath(targetDir), 'utf8');\n return JSON.parse(content) as StateFile;\n } catch {\n return undefined;\n }\n}\n\nexport async function writeStateFile(\n targetDir: string,\n state: StateFile,\n): Promise<void> {\n const monocerosDir = path.join(targetDir, '.monoceros');\n await fs.mkdir(monocerosDir, { recursive: true });\n await fs.writeFile(\n stateFilePath(targetDir),\n JSON.stringify(state, null, 2) + '\\n',\n );\n}\n","import { resolveService } from '../create/catalog.js';\nimport { deriveRepoName } from '../create/scaffold.js';\nimport type { CreateOptions, FeatureOptions } from '../create/types.js';\nimport { portNumber, type SolutionConfig } from './schema.js';\n\n/**\n * Translate a yml-shaped `SolutionConfig` into the `CreateOptions`\n * shape the existing scaffolders (devcontainer.json, compose.yaml,\n * post-create.sh) consume.\n *\n * The big shape mismatch is `features`:\n * - yml: `[{ ref, options }, …]` (array; humans edit/comment this)\n * - CreateOptions: `Record<ref, options>` (devcontainer.json shape)\n * We dedupe by `ref` and keep the LAST occurrence — same rule the\n * existing `add-feature` command uses when a builder re-adds with new\n * options.\n *\n * `externalServices.postgres` → `postgresUrl` (the CreateOptions\n * field name predates the yml schema; rename would touch every M1\n * caller, so the translator absorbs the diff here).\n *\n * `featureDefaults` (optional) — `defaults.features` from\n * `monoceros-config.yml`. Per-container options always override these;\n * keys not set per-container fall back to the default. A feature ref\n * that exists only in `featureDefaults` (not in the container yml)\n * does NOT get included — the container yml is what decides whether\n * a feature is active at all; the defaults only fill in option values.\n */\nexport function solutionConfigToCreateOptions(\n config: SolutionConfig,\n featureDefaults: Record<string, FeatureOptions> = {},\n): CreateOptions {\n const featureRecord: Record<string, FeatureOptions> = {};\n for (const entry of config.features) {\n const defaults = featureDefaults[entry.ref] ?? {};\n // Per-container options override defaults, EXCEPT when the\n // container value is the empty string. A bare `apiKey:` in the\n // yml parses to null and the schema relaxes that to `\"\"`; the\n // init-generator also writes hint keys without a value. In both\n // cases the builder's intent is \"leave this unset, fall through\n // to the global default\" — not \"explicitly clear the default\".\n // Skip empty strings so the merge respects that.\n const containerOpts = Object.fromEntries(\n Object.entries(entry.options ?? {}).filter(([, v]) => v !== ''),\n );\n featureRecord[entry.ref] = { ...defaults, ...containerOpts };\n }\n\n const result: CreateOptions = {\n name: config.name,\n languages: [...config.languages],\n // Normalize every services[] entry (curated string or explicit\n // object) to the canonical ResolvedService shape. `${VAR}` values\n // survive untouched here — apply interpolates them against\n // <name>.env afterwards.\n services: config.services.map(resolveService),\n };\n\n if (config.externalServices.postgres !== undefined) {\n result.postgresUrl = config.externalServices.postgres;\n }\n if (config.aptPackages.length > 0) {\n result.aptPackages = [...config.aptPackages];\n }\n if (Object.keys(featureRecord).length > 0) {\n result.features = featureRecord;\n }\n if (config.installUrls.length > 0) {\n result.installUrls = [...config.installUrls];\n }\n if (config.repos.length > 0) {\n result.repos = config.repos.map((r) => ({\n url: r.url,\n // `path` is optional in the yml; CreateOptions requires it.\n // When the yml omits `path`, fall back to the URL-derived\n // single-segment default (`https://.../foo.git` → `foo`),\n // which lands the clone at `projects/foo/`.\n path: r.path ?? deriveRepoName(r.url),\n // gitUser is forwarded only when BOTH name + email are set.\n // The relaxed GitUserSchema accepts nullable / empty strings\n // (so a yml placeholder `name:` parses without error), so we\n // re-check here before downstream code, which expects both\n // values to be non-empty.\n ...(r.git?.user?.name && r.git.user.email\n ? { gitUser: { name: r.git.user.name, email: r.git.user.email } }\n : {}),\n ...(r.provider ? { provider: r.provider } : {}),\n }));\n }\n const routingPorts = config.routing?.ports ?? [];\n if (routingPorts.length > 0) {\n // Collapse both yml forms (`- 3000` and `- port: 9229`) to a flat\n // number array. Dedupe by port number — repeated entries in the\n // yml would otherwise show up twice in `forwardPorts` and in the\n // Traefik route set.\n const seen = new Set<number>();\n const ports: number[] = [];\n for (const entry of routingPorts) {\n const n = portNumber(entry);\n if (seen.has(n)) continue;\n seen.add(n);\n ports.push(n);\n }\n result.ports = ports;\n }\n if (config.routing?.vscodeAutoForward !== undefined) {\n result.vscodeAutoForward = config.routing.vscodeAutoForward;\n }\n return result;\n}\n","import { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport { consola } from 'consola';\nimport { type DockerExec } from '../proxy/index.js';\nimport { createSecretMaskStream } from '../util/mask-secrets.js';\nimport { spawnDevcontainer, type DevcontainerSpawn } from './cli.js';\n\nexport { type DockerExec, type DockerResult } from '../proxy/index.js';\n\nexport type ComposeSpawn = (args: string[], cwd: string) => Promise<number>;\n\n// Default spawn: shells out to `docker compose` (the v2 docker\n// subcommand). Stdout/stderr are streamed through a secret masker\n// (see util/mask-secrets.ts) so feature option dumps, ENV-printouts\n// and similar do not leak Atlassian/GitHub/Anthropic tokens onto\n// the host terminal.\nexport const spawnDockerCompose: ComposeSpawn = (args, cwd) => {\n return new Promise((resolve, reject) => {\n const child = spawn('docker', ['compose', ...args], {\n cwd,\n stdio: ['inherit', 'pipe', 'pipe'],\n });\n child.stdout?.pipe(createSecretMaskStream()).pipe(process.stdout);\n child.stderr?.pipe(createSecretMaskStream()).pipe(process.stderr);\n child.on('error', reject);\n child.on('exit', (code) => resolve(code ?? 0));\n });\n};\n\n// Direct invocation of `docker <args>` with no shell wrapper.\n// BOTH stdout and stderr are buffered, never live-streamed: the\n// cleanup pipeline routinely runs docker calls that are expected to\n// fail (e.g. `docker network rm <name>` for image-mode containers\n// that never had a project network — \"network not found\" is the\n// happy path). Live streaming would leak that noise to the user.\n// Callers print buffered stderr on the failure paths they actually\n// care about.\n//\n// Why no bash. The old cleanup path piped a script through\n// `bash -c <script>` which on Windows is typically WSL's bash via\n// the C:\\Users\\…\\WindowsApps\\bash.exe launcher. Quoting a label\n// value with backslashes (`c:\\Users\\…\\.monoceros\\…`) survives PS,\n// CreateProcess, WSL launcher, and bash's own parser only to come\n// out the other end mangled when handed to docker, and the label\n// filter then silently matches nothing. Going through Node spawn\n// directly removes every one of those layers.\n//\n// Shape matches `DockerExec` re-exported above (originally from\n// proxy/index.ts) so tests can swap in the same fake across both the\n// proxy lifecycle and the cleanup pipelines.\nexport const spawnDocker: DockerExec = (args) => {\n return new Promise((resolve, reject) => {\n const child = spawn('docker', args, {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n let stdout = '';\n let stderr = '';\n child.stdout?.on('data', (chunk: Buffer) => {\n stdout += chunk.toString('utf8');\n });\n child.stderr?.on('data', (chunk: Buffer) => {\n stderr += chunk.toString('utf8');\n });\n child.on('error', reject);\n child.on('exit', (code) =>\n resolve({ exitCode: code ?? 0, stdout, stderr }),\n );\n });\n};\n\n/**\n * Collect container IDs matching ANY of the given docker `--filter`\n * values, deduplicated. Tolerates per-filter failures (treats them as\n * empty) so a malformed/unsupported filter doesn't take the whole\n * cleanup down.\n *\n * Each `filter` is the literal value passed after `--filter`, e.g.\n * `label=com.docker.compose.project=foo` or `name=^foo-`.\n */\nexport async function findContainerIds(\n filters: readonly string[],\n exec: DockerExec = spawnDocker,\n): Promise<string[]> {\n const ids = new Set<string>();\n for (const filter of filters) {\n const result = await exec(['ps', '-aq', '--filter', filter]);\n if (result.exitCode !== 0) continue;\n for (const line of result.stdout.split(/\\r?\\n/)) {\n const id = line.trim();\n if (id) ids.add(id);\n }\n }\n return [...ids];\n}\n\nexport interface CleanupDockerObjectsOptions {\n /** Display name in log lines (`[cleanup] tearing down docker project <projectName>…`). */\n projectName: string;\n /** Docker `--filter` values; containers matching ANY are removed. */\n filters: readonly string[];\n /** Optional network name to `docker network rm` after container removal. Failure is ignored (network may not exist). */\n network?: string;\n /** `[cleanup] …` prefix on log lines. Defaults to `cleanup`; `remove/index.ts` uses `remove` to match the existing on-screen tag. */\n logTag?: string;\n logger: { info: (message: string) => void };\n exec?: DockerExec;\n}\n\nexport interface CleanupDockerObjectsResult {\n exitCode: number;\n removedIds: string[];\n}\n\n/**\n * Replacement for the previous `bash -c '…'` cleanup script in\n * `remove` and the apply pre-cleanup. Drives docker directly via\n * Node spawn (`spawnDocker`) so backslash-bearing Windows label\n * values reach docker unmangled.\n *\n * `exitCode` is 0 unless `docker rm -f` itself returned non-zero;\n * per-filter `docker ps` failures are tolerated silently. Use\n * {@link findContainerIds} afterwards if you need to verify the\n * tear-down actually emptied the project.\n */\nexport async function cleanupDockerObjects(\n opts: CleanupDockerObjectsOptions,\n): Promise<CleanupDockerObjectsResult> {\n const exec = opts.exec ?? spawnDocker;\n const tag = opts.logTag ?? 'cleanup';\n opts.logger.info(`[${tag}] tearing down docker project ${opts.projectName}…`);\n\n const ids = await findContainerIds(opts.filters, exec);\n\n let rmExit = 0;\n if (ids.length > 0) {\n opts.logger.info(`[${tag}] removing containers: ${ids.join(' ')}`);\n const rmResult = await exec(['rm', '-f', ...ids]);\n rmExit = rmResult.exitCode;\n if (rmExit !== 0 && rmResult.stderr.trim()) {\n // Real failure path — surface what docker said so the builder\n // doesn't see a bare non-zero exit with no explanation.\n opts.logger.info(`[${tag}] ${rmResult.stderr.trim()}`);\n }\n } else {\n opts.logger.info(`[${tag}] no containers found`);\n }\n\n if (opts.network) {\n const netResult = await exec(['network', 'rm', opts.network]);\n if (netResult.exitCode === 0) {\n opts.logger.info(`[${tag}] network ${opts.network} removed`);\n }\n // Otherwise: silent. The common failure here is \"network not\n // found\" because image-mode devcontainers (no compose) never\n // created one — expected, not actionable, kept the bash version\n // quiet with `2>/dev/null`. A real docker error (daemon down)\n // already showed up on the earlier ps/rm calls.\n }\n\n opts.logger.info(`[${tag}] docker cleanup done`);\n return { exitCode: rmExit, removedIds: ids };\n}\n\ninterface ResolvedCompose {\n composeFile: string;\n projectName: string;\n}\n\n// Match the project name `@devcontainers/cli` derives when it brings a\n// compose-mode devcontainer up: `<root-basename>_devcontainer`.\n// Aligning here means `monoceros start/stop/status/logs` and the\n// implicit `devcontainer up` from `monoceros run/shell` act on the\n// same compose project — without it docker would create two parallel\n// stacks.\nexport function composeProjectName(root: string): string {\n return `${path.basename(root)}_devcontainer`;\n}\n\n/**\n * Resolve compose-mode metadata for the container rooted at `root`.\n * `root` is `<MONOCEROS_HOME>/container/<name>/` and must already\n * exist with a `.devcontainer/compose.yaml` inside. The compose-only\n * lifecycle commands (`start/stop/status/logs/down`) error when the\n * file is missing.\n */\nexport function resolveCompose(root: string): ResolvedCompose {\n if (!existsSync(path.join(root, '.devcontainer'))) {\n throw new Error(\n `No .devcontainer/ at ${root}. Run \\`monoceros apply <name>\\` first.`,\n );\n }\n const composeFile = path.join(root, '.devcontainer', 'compose.yaml');\n if (!existsSync(composeFile)) {\n throw new Error(\n `No compose.yaml at ${composeFile}. \\`start\\` / \\`stop\\` / \\`status\\` / \\`logs\\` require services configured via \\`monoceros add-service <name> <svc>\\`. Use \\`monoceros shell <name>\\` to enter the container directly.`,\n );\n }\n return { composeFile, projectName: composeProjectName(root) };\n}\n\nexport interface ComposeActionOptions {\n root: string;\n service?: string;\n spawn?: ComposeSpawn;\n}\n\nasync function runComposeAction(\n buildSubArgs: (service: string | undefined) => string[],\n opts: ComposeActionOptions,\n): Promise<number> {\n const { composeFile, projectName } = resolveCompose(opts.root);\n const spawnFn = opts.spawn ?? spawnDockerCompose;\n const subArgs = buildSubArgs(opts.service);\n return spawnFn(['-f', composeFile, '-p', projectName, ...subArgs], opts.root);\n}\n\nexport interface StartOptions {\n root: string;\n spawn?: DevcontainerSpawn;\n logger?: { info: (message: string) => void };\n}\n\n// `monoceros start` delegates to `devcontainer up` rather than to\n// `docker compose up -d`. The detour through @devcontainers/cli matters\n// because:\n// - it labels the workspace container with `devcontainer.local_folder`\n// so subsequent `devcontainer exec` (from `monoceros run/shell`) can\n// find the container by workspace path,\n// - it applies devcontainer features (which docker compose ignores), and\n// - it triggers the postCreateCommand once.\n// The auxiliary services come up alongside because the generated\n// devcontainer.json lists them under `runServices`.\nexport async function runStart(opts: StartOptions): Promise<number> {\n resolveCompose(opts.root); // throws if no compose.yaml\n const logger = opts.logger ?? { info: (msg) => consola.info(msg) };\n const spawnFn = opts.spawn ?? spawnDevcontainer;\n logger.info(`Bringing devcontainer up at ${opts.root}…`);\n return spawnFn(\n ['up', '--workspace-folder', opts.root, '--mount-workspace-git-root=false'],\n opts.root,\n );\n}\n\nexport interface RunContainerCycleOptions {\n hasCompose: boolean;\n /**\n * Inject a fake docker exec for tests. Replaces the previous\n * `cleanupSpawn: ComposeSpawn` which fed a bash script to\n * `bash -c`; we now drive docker directly via Node spawn, so the\n * shell layer (and its Windows quoting failures) is out of the\n * picture.\n */\n dockerExec?: DockerExec;\n devcontainerSpawn?: DevcontainerSpawn;\n logger: {\n info: (message: string) => void;\n warn?: (message: string) => void;\n };\n}\n\n/**\n * Container teardown + up for a devcontainer rooted at `root`.\n * Used by `runApply` (apply/index.ts) after writing the scaffold.\n */\nexport async function runContainerCycle(\n root: string,\n opts: RunContainerCycleOptions,\n): Promise<number> {\n const { hasCompose, logger } = opts;\n\n if (hasCompose) {\n const projectName = composeProjectName(root);\n logger.info(\n `Force-removing existing ${projectName} containers (volumes preserved)…`,\n );\n // Two-step removal so a container with stale/missing labels still\n // gets caught:\n // - by docker-compose project label\n // - by container-name prefix `<project>-*`\n // After removal we re-query: if anything remains, VS Code's Remote\n // Containers extension is the likely culprit (auto-recreates on\n // container loss); we abort with a clear hint rather than letting\n // `devcontainer up` collide.\n const exec = opts.dockerExec ?? spawnDocker;\n const filters = [\n `label=com.docker.compose.project=${projectName}`,\n `name=^${projectName}-`,\n ];\n const { exitCode: rmExit } = await cleanupDockerObjects({\n projectName,\n filters,\n network: `${projectName}_default`,\n logger,\n exec,\n });\n if (rmExit !== 0) return rmExit;\n\n const remaining = await findContainerIds(filters, exec);\n if (remaining.length > 0) {\n const warn = logger.warn ?? logger.info;\n warn(\n `ERROR: containers under project ${projectName} reappeared after removal.\\n` +\n `This typically means VS Code's Remote Containers extension is connected\\n` +\n `to this devcontainer and auto-recreated it. Close the dev container\\n` +\n `session in VS Code (Cmd+Shift+P → 'Dev Containers: Close Remote Connection')\\n` +\n `and retry \\`monoceros apply\\`.`,\n );\n return 1;\n }\n\n return runStart({\n root,\n ...(opts.devcontainerSpawn ? { spawn: opts.devcontainerSpawn } : {}),\n logger,\n });\n }\n\n logger.info(`Recreating image-mode devcontainer at ${root}…`);\n const spawnFn = opts.devcontainerSpawn ?? spawnDevcontainer;\n return spawnFn(\n [\n 'up',\n '--workspace-folder',\n root,\n '--mount-workspace-git-root=false',\n '--remove-existing-container',\n ],\n root,\n );\n}\n\nexport function runStop(opts: ComposeActionOptions): Promise<number> {\n return runComposeAction(\n (service) => ['stop', ...(service ? [service] : [])],\n opts,\n );\n}\n\nexport function runStatus(opts: ComposeActionOptions): Promise<number> {\n return runComposeAction(\n (service) => ['ps', ...(service ? [service] : [])],\n opts,\n );\n}\n\nexport interface LogsOptions extends ComposeActionOptions {\n follow?: boolean;\n}\n\nexport function runLogs(opts: LogsOptions): Promise<number> {\n const follow = opts.follow ?? true;\n return runComposeAction(\n (service) => [\n 'logs',\n ...(follow ? ['-f'] : []),\n ...(service ? [service] : []),\n ],\n opts,\n );\n}\n","import { Transform, type TransformCallback } from 'node:stream';\n\n/**\n * Mask known token-shaped strings in arbitrary text.\n *\n * Devcontainer-cli and docker compose stream the build/up output\n * straight to stdout. They include the feature options (Atlassian\n * apiToken, GitHub PAT, Anthropic apiKey, …) verbatim, which leaks\n * real, per-builder secrets onto the user's terminal and into any\n * captured CI log.\n *\n * The fix is a regex sweep on each output line: when a token\n * matches a known prefix shape, replace its middle with `…` and\n * keep the prefix + last 6 characters so the value is still\n * recognizable for debugging (\"did the right token get loaded?\")\n * without exposing the secret.\n *\n * What's **not** masked here, by design:\n *\n * - The literal `monoceros` user/password baked into the compose\n * service catalog (postgres, mysql). It's a documented dev-\n * convention, identical on every Monoceros container, openly\n * listed in `create/catalog.ts` and the components README. Not\n * a secret. Masking it would just make the connection string\n * harder to spot for the builder.\n * - Anything that looks \"password-shaped\" via a key= pattern.\n * Risk of false positives outweighs cosmetic benefit when the\n * value isn't actually sensitive (see also ADR-style note in\n * `create/catalog.ts`).\n */\n\ninterface SecretPattern {\n /** Short label for the pattern, useful in debugging. */\n name: string;\n /** Match shape. Must be a /g regex so all occurrences get replaced. */\n re: RegExp;\n}\n\n// Order doesn't matter — patterns are disjoint by prefix.\nconst PATTERNS: SecretPattern[] = [\n // Atlassian Cloud API token. Starts with literal `ATATT3xFf` plus\n // a long URL-safe-base64 tail. Tightened to that prefix to avoid\n // matching unrelated all-caps words.\n { name: 'atlassian-api', re: /ATATT3xFf[A-Za-z0-9+/=_-]{20,}/g },\n // Bitbucket Cloud app password.\n { name: 'bitbucket-app', re: /ATBB[A-Za-z0-9+/=_-]{20,}/g },\n // GitHub PAT (classic), OAuth, user, server, refresh — all share\n // the `gh<lower-letter>_<base62>` shape per GitHub's token format.\n { name: 'github-token', re: /gh[a-z]_[A-Za-z0-9]{20,}/g },\n // GitHub fine-grained PAT.\n { name: 'github-pat', re: /github_pat_[A-Za-z0-9_]{20,}/g },\n // Anthropic API key.\n { name: 'anthropic-api', re: /sk-ant-[A-Za-z0-9_-]{20,}/g },\n];\n\n/**\n * Replace token-shaped substrings with a masked form. Idempotent\n * for already-masked strings (the elision character isn't part of\n * any pattern's alphabet).\n */\nexport function maskSecrets(text: string): string {\n let result = text;\n for (const { re } of PATTERNS) {\n result = result.replace(re, maskOne);\n }\n return result;\n}\n\nfunction maskOne(token: string): string {\n if (token.length <= 12) return token;\n return `${token.slice(0, 5)}…${token.slice(-6)}`;\n}\n\n/**\n * Transform stream that runs every chunk through `maskSecrets`.\n *\n * Tokens can in theory straddle a chunk boundary if the upstream\n * writer flushes mid-token, leaving an unmasked tail. Mitigation:\n * the transform holds back the last line of every chunk until a\n * newline arrives, since real Docker / devcontainer-cli output is\n * line-oriented and tokens don't contain newlines. On final flush\n * any leftover buffer is masked and emitted.\n */\nexport function createSecretMaskStream(): Transform {\n let buffer = '';\n return new Transform({\n decodeStrings: true,\n transform(chunk: Buffer | string, _enc, cb: TransformCallback): void {\n const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');\n buffer += text;\n const lastNewline = buffer.lastIndexOf('\\n');\n if (lastNewline === -1) {\n // No complete line yet — keep buffering. We'd rather hold\n // back partial output briefly than emit half a token.\n cb(null);\n return;\n }\n const flushable = buffer.slice(0, lastNewline + 1);\n buffer = buffer.slice(lastNewline + 1);\n cb(null, maskSecrets(flushable));\n },\n flush(cb: TransformCallback): void {\n if (buffer.length > 0) {\n const tail = maskSecrets(buffer);\n buffer = '';\n cb(null, tail);\n return;\n }\n cb(null);\n },\n });\n}\n","import { spawn } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { createSecretMaskStream, maskSecrets } from '../util/mask-secrets.js';\nimport {\n createRuntimePullHintStream,\n type PullHintState,\n} from './runtime-pull-hint.js';\n\nconst require_ = createRequire(import.meta.url);\n\nlet cachedBinaryPath: string | null = null;\n\n// Resolve the absolute path to the `@devcontainers/cli` JS entry point. We\n// invoke it via `node <path>` rather than relying on a `.bin/` shim being on\n// PATH, so the CLI works regardless of how the user installed the workbench.\nexport function devcontainerCliPath(): string {\n if (cachedBinaryPath) return cachedBinaryPath;\n const pkgJsonPath = require_.resolve('@devcontainers/cli/package.json');\n const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8')) as {\n bin?: string | Record<string, string>;\n };\n const binEntry =\n typeof pkg.bin === 'string' ? pkg.bin : (pkg.bin?.devcontainer ?? '');\n if (!binEntry) {\n throw new Error('Could not resolve @devcontainers/cli bin entry.');\n }\n cachedBinaryPath = path.resolve(path.dirname(pkgJsonPath), binEntry);\n return cachedBinaryPath;\n}\n\nexport interface DevcontainerSpawnOptions {\n // When true, capture stdout and stderr instead of inheriting them.\n // The buffered output is only flushed (to stderr) if the process exits\n // non-zero, so successful no-op invocations stay silent. Use this for\n // intermediate steps like the implicit `up` that `monoceros run` does\n // before `exec`; leave it unset for explicit lifecycle calls\n // (`monoceros start`) and for the final exec where the user expects to\n // see output.\n quiet?: boolean;\n // When true, hand the child process direct stdio. Pure `inherit` —\n // no piping, no secret masking, no buffering. Required for any\n // interactive use case (`monoceros shell`, the `exec` step of\n // `monoceros run`): bash detects a non-TTY stdin/stdout and exits\n // immediately, which makes a stdio-pipe approach a non-starter.\n // The build/log paths in apply and start still go through the\n // masked-pipe path, where there's no TTY at stake.\n interactive?: boolean;\n}\n\nexport type DevcontainerSpawn = (\n args: string[],\n cwd: string,\n options?: DevcontainerSpawnOptions,\n) => Promise<number>;\n\n// Default spawn implementation: runs the @devcontainers/cli binary\n// directly. Both stdout and stderr are streamed through a secret\n// masker (see util/mask-secrets.ts) so that feature options like\n// Atlassian apiTokens or GitHub PATs do not leak verbatim to the\n// terminal when devcontainer-cli logs the build args / feature\n// option dumps. With `{ quiet: true }` output is buffered (and\n// masked) and only flushed on a non-zero exit.\nexport const spawnDevcontainer: DevcontainerSpawn = (\n args,\n cwd,\n options = {},\n) => {\n const binPath = devcontainerCliPath();\n return new Promise((resolve, reject) => {\n if (options.interactive) {\n // Direct inherit — required so the child binary sees a real\n // TTY on stdin/stdout/stderr. Secret masking is irrelevant\n // here (the builder is running an interactive command;\n // build-time option dumps don't fire on this path).\n const child = spawn(process.execPath, [binPath, ...args], {\n cwd,\n stdio: 'inherit',\n });\n child.on('error', reject);\n child.on('exit', (code) => resolve(code ?? 0));\n return;\n }\n const child = spawn(process.execPath, [binPath, ...args], {\n cwd,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n if (options.quiet) {\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n child.stdout?.on('data', (chunk: Buffer) => stdoutChunks.push(chunk));\n child.stderr?.on('data', (chunk: Buffer) => stderrChunks.push(chunk));\n child.on('error', reject);\n child.on('exit', (code) => {\n const exitCode = code ?? 0;\n if (exitCode !== 0) {\n process.stderr.write(\n maskSecrets(Buffer.concat(stderrChunks).toString('utf8')),\n );\n process.stderr.write(\n maskSecrets(Buffer.concat(stdoutChunks).toString('utf8')),\n );\n }\n resolve(exitCode);\n });\n return;\n }\n // Shared so the \"downloading runtime image…\" hint fires once even\n // though devcontainer-cli may log the manifest line on either stream.\n const pullHint: PullHintState = { hinted: false };\n child.stdout\n ?.pipe(createSecretMaskStream())\n .pipe(createRuntimePullHintStream(pullHint))\n .pipe(process.stdout);\n child.stderr\n ?.pipe(createSecretMaskStream())\n .pipe(createRuntimePullHintStream(pullHint))\n .pipe(process.stderr);\n child.on('error', reject);\n child.on('exit', (code) => resolve(code ?? 0));\n });\n};\n","import { Transform, type TransformCallback } from 'node:stream';\nimport { dim } from '../util/format.js';\n\n/**\n * devcontainer-cli logs this line when it probes the multi-arch GHCR\n * manifest for our runtime image before pulling it. It reads as an\n * \"Error\", but it's harmless — the buildx pull right after consumes\n * the image fine. The catch: on a cold cache Docker then downloads the\n * base for ~1–2 min with NO further output. Left unannotated, the\n * \"Error … No manifest found\" line is the LAST thing on screen before\n * that silence, so builders reasonably conclude the apply broke.\n */\nexport const RUNTIME_PULL_MARKER =\n 'No manifest found for ghcr.io/getmonoceros/monoceros-runtime';\n\n// User-visible string. ASCII-only on purpose: on Windows PS 5.1 conhost\n// (default codepage OEM/ANSI), multi-byte glyphs render as `?`. Same\n// reason install.ps1 uses ASCII glyphs for its Ok/Warn/Fail markers.\nexport const RUNTIME_PULL_HINT =\n 'Downloading the Monoceros runtime image now -- expected on first apply, ' +\n 'takes ~1-2 min (Docker pulls the multi-arch base with no progress ' +\n 'output). The \"No manifest found\" line above is harmless. Please wait...';\n\nexport interface PullHintState {\n hinted: boolean;\n}\n\n/**\n * Line-oriented transform that passes devcontainer-cli output through\n * untouched and, the first time it sees {@link RUNTIME_PULL_MARKER},\n * appends {@link RUNTIME_PULL_HINT} on its own line right after.\n *\n * `state` is shared across the stdout and stderr instances so the hint\n * fires exactly once regardless of which stream devcontainer-cli logged\n * the marker on. Sits after the secret-mask stream in the pipe chain.\n */\nexport function createRuntimePullHintStream(state: PullHintState): Transform {\n let buffer = '';\n const appendHintIfMarker = (block: string): string => {\n if (state.hinted || !block.includes(RUNTIME_PULL_MARKER)) return block;\n state.hinted = true;\n return `${block}${dim(`(i) ${RUNTIME_PULL_HINT}`)}\\n`;\n };\n return new Transform({\n decodeStrings: true,\n transform(chunk: Buffer | string, _enc, cb: TransformCallback): void {\n const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');\n buffer += text;\n const lastNewline = buffer.lastIndexOf('\\n');\n if (lastNewline === -1) {\n cb(null);\n return;\n }\n const flushable = buffer.slice(0, lastNewline + 1);\n buffer = buffer.slice(lastNewline + 1);\n cb(null, appendHintIfMarker(flushable));\n },\n flush(cb: TransformCallback): void {\n if (buffer.length === 0) {\n cb(null);\n return;\n }\n const tail = buffer;\n buffer = '';\n cb(null, appendHintIfMarker(tail));\n },\n });\n}\n","import { spawn } from 'node:child_process';\nimport { cyan } from '../util/format.js';\n\n/**\n * Whether the host's docker daemon runs as root (rootful) or under a\n * user namespace (rootless). The mode decides whether bind-mounts\n * need the `idmap` option.\n *\n * Why this matters: in rootless Docker the host user's UID is mapped\n * to container UID 0 (root), and the container's non-root user\n * (uid 1000 inside) lives at a shifted host UID (uid 65536+1000+ via\n * /etc/subuid). Without `idmap`, files written by either side end up\n * with the wrong ownership on the other — host can't read what the\n * container created, container's node user can't write into what the\n * host pre-created (which is what bit M5 testing on Ubuntu rootless).\n *\n * The Linux kernel supports `idmap` as a bind-mount option since 5.12;\n * Docker exposes it since 25.x. Ubuntu 24.04 and other modern distros\n * are well past both. Older kernels (RHEL 8 with 4.18) would fail the\n * mount with an \"unsupported\" error — accepted trade-off, the error\n * surfaces clearly.\n *\n * On macOS / Windows Docker Desktop, idmap is a no-op at best and a\n * mount-error at worst because those platforms use their own\n * file-sharing layer (VirtioFS / WSL2 + Plan9) instead of native\n * Linux bind mounts. We MUST only emit idmap when the daemon is\n * actually rootless on Linux — otherwise we'd break the working\n * Mac/Windows cases.\n */\nexport type DockerMode = 'rootful' | 'rootless';\n\n/**\n * Spawn signature for `docker info`. Returns stdout + exit code.\n * Injected by tests.\n */\nexport type DockerInfoSpawn = () => Promise<{\n stdout: string;\n exitCode: number;\n}>;\n\nconst realDockerInfo: DockerInfoSpawn = () => {\n return new Promise((resolve, reject) => {\n // `--format '{{json .SecurityOptions}}'` returns a JSON array like\n // `[\"name=seccomp,profile=builtin\",\"name=rootless\"]` on rootless,\n // or `[\"name=seccomp,profile=builtin\"]` on rootful. Cheaper to\n // parse than the full human-readable `docker info` output.\n const child = spawn(\n 'docker',\n ['info', '--format', '{{json .SecurityOptions}}'],\n {\n stdio: ['ignore', 'pipe', 'inherit'],\n },\n );\n let stdout = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.on('error', reject);\n child.on('exit', (code) => resolve({ stdout, exitCode: code ?? 0 }));\n });\n};\n\n/**\n * Probe the host docker daemon and return its mode. Defaults to\n * `'rootful'` whenever we can't reliably determine otherwise — the\n * downstream `docker run` would surface a clearer error if the\n * daemon is unreachable, so we don't pre-emptively fail here.\n */\nexport async function detectDockerMode(\n options: { spawn?: DockerInfoSpawn } = {},\n): Promise<DockerMode> {\n const spawnFn = options.spawn ?? realDockerInfo;\n try {\n const result = await spawnFn();\n if (result.exitCode !== 0) return 'rootful';\n // Match both the bare `rootless` token and the modern\n // `name=rootless` form. Case-insensitive to be defensive against\n // future docker output tweaks.\n return /\\brootless\\b/i.test(result.stdout) ? 'rootless' : 'rootful';\n } catch {\n return 'rootful';\n }\n}\n\n/**\n * Builder-facing error rendered when we detect rootless Docker.\n * Kept user-friendly on purpose — no \"UID shift\" / \"subuid\" jargon.\n * Frames the consequence (\"files you create in the container can't\n * be edited from your host\") rather than the cause, and gives the\n * exact switch-to-rootful command block. install.sh duplicates this\n * text in bash (deliberately — we can't share strings across\n * runtimes without ceremony).\n */\nexport function formatRootlessNotSupportedError(): string {\n return [\n `Monoceros requires Docker in \"rootful\" mode.`,\n ``,\n `You're running Docker in \"rootless\" mode right now. That setup`,\n `runs the daemon without root privileges — sounds safer, but it`,\n `remaps user IDs between your host and the container in a way`,\n `that prevents the container from writing into the directories`,\n `Monoceros mounts into it. Cloning your repos, running`,\n `\\`npm install\\`, building — all fail with permission errors at`,\n `the first attempt.`,\n ``,\n `To fix, switch back to standard rootful Docker:`,\n ``,\n cyan(\n ` systemctl --user stop docker.service docker.socket 2>/dev/null || true`,\n ),\n cyan(` dockerd-rootless-setuptool.sh uninstall`),\n cyan(` rootlesskit rm -rf ~/.local/share/docker`),\n cyan(` unset DOCKER_HOST DOCKER_CONTEXT`),\n cyan(` sudo systemctl enable --now docker`),\n cyan(` sudo usermod -aG docker $USER`),\n ``,\n `If you added DOCKER_HOST or DOCKER_CONTEXT to ~/.bashrc /`,\n `~/.profile (the rootless setup may have suggested it), remove`,\n `those lines too — the 'unset' above only affects your current`,\n `shell. Otherwise new terminals keep pointing at the rootless`,\n `socket and Monoceros's auto-recovery has nothing to fall back to.`,\n ``,\n `Then re-run.`,\n ].join('\\n');\n}\n","import { spawn } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { consola } from 'consola';\n\n/**\n * Spawn signature for `git config --global --get <key>`: takes the\n * key, returns stdout (trimmed) and exit code. Exit code 1 with empty\n * stdout means \"no value set\" — that's how git signals an unset key.\n * Injected by tests.\n */\nexport type IdentitySpawn = (\n key: string,\n) => Promise<{ value: string; exitCode: number }>;\n\n/**\n * Async prompt for a single identity key. Used as a fallback when the\n * host has no `--global` identity and `.monoceros/gitconfig` has no\n * persisted value from an earlier run. Returns the entered value or\n * `undefined` if the builder skips.\n */\nexport type IdentityPrompt = (\n key: 'user.name' | 'user.email',\n) => Promise<string | undefined>;\n\n/**\n * Persistence target the builder chose for a freshly-prompted or\n * previously-persisted identity. `'g'` writes to\n * `~/.monoceros/monoceros-config.yml` (global default for every\n * container), `'c'` writes to the container yml's `git.user`, `'b'`\n * does both, `'n'` skips persistence (keep using whatever transient\n * source we have — typically `.monoceros/gitconfig` from a prior\n * apply). The caller (apply / init) does the actual yml writes —\n * collectGitIdentity just surfaces what the builder picked so the\n * caller can act on it.\n */\nexport type IdentityScope = 'g' | 'c' | 'b' | 'n';\n\nexport type IdentityScopePrompt = (\n ctx: IdentityScopePromptContext,\n) => Promise<IdentityScope | undefined>;\n\n/**\n * Context passed to the scope prompt so the implementation can show\n * the builder what's going on — `'prompt'` after a fresh\n * name/email entry vs. `'persisted'` after recovering values from\n * `.monoceros/gitconfig`. The default consola prompt renders the\n * actual name/email so the builder sees what they'd be persisting.\n */\nexport interface IdentityScopePromptContext {\n reason: 'prompt' | 'persisted';\n name: string;\n email: string;\n}\n\nconst realGitConfigGet: IdentitySpawn = (key) => {\n return new Promise((resolve, reject) => {\n const child = spawn('git', ['config', '--global', '--get', key], {\n stdio: ['ignore', 'pipe', 'inherit'],\n });\n let stdout = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.on('error', reject);\n child.on('exit', (code) =>\n resolve({ value: stdout.trim(), exitCode: code ?? 0 }),\n );\n });\n};\n\nconst realIdentityPrompt: IdentityPrompt = async (key) => {\n // Non-interactive (CI, scripts): never hang waiting for input. The\n // identity stays unset; builder fixes it later by setting host\n // `git config --global` or editing `.monoceros/gitconfig` directly.\n if (!process.stdin.isTTY || !process.stdout.isTTY) {\n return undefined;\n }\n const label =\n key === 'user.name'\n ? 'Git user.name for this dev container (full name)'\n : 'Git user.email for this dev container';\n const value = await consola.prompt(`${label}:`, { type: 'text' });\n if (typeof value !== 'string') return undefined;\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n};\n\nconst realScopePrompt: IdentityScopePrompt = async (ctx) => {\n if (!process.stdin.isTTY || !process.stdout.isTTY) {\n // Non-interactive: default to `g` (global) on a fresh prompt\n // entry (the values are new, we want them remembered); default\n // to `n` on the persisted-recovery path (the values were\n // already in .monoceros/gitconfig and a script that hits this\n // path probably doesn't want a silent rewrite of monoceros-config).\n return ctx.reason === 'prompt' ? 'g' : 'n';\n }\n const heading =\n ctx.reason === 'persisted'\n ? `Found identity in .monoceros/gitconfig: ${ctx.name} <${ctx.email}>. Promote where?`\n : 'Save this identity where?';\n const choice = await consola.prompt(heading, {\n type: 'select',\n options: [\n {\n label: 'Globally — every container uses it as default',\n value: 'g',\n },\n {\n label: 'In this container only',\n value: 'c',\n },\n {\n label: 'Both — global default plus container-level entry',\n value: 'b',\n },\n {\n label: 'Keep as-is — do not write to monoceros-config or yml',\n value: 'n',\n },\n ],\n initial: 'g',\n });\n if (choice === 'g' || choice === 'c' || choice === 'b' || choice === 'n') {\n return choice;\n }\n return undefined;\n};\n\nexport interface CollectIdentityOptions {\n spawn?: IdentitySpawn;\n /**\n * Fallback prompt when the host has no `--global` identity and\n * `.monoceros/gitconfig` has no persisted value either. Tests inject\n * a canned answer; production uses an interactive `consola.prompt`\n * that auto-skips in non-interactive contexts.\n */\n prompt?: IdentityPrompt;\n /**\n * Asked AFTER an interactive identity prompt succeeded: where to\n * persist (global monoceros-config, container yml, both). Result\n * lands in `CollectIdentityResult.promptedScope` for the caller to\n * act on (apply / init handle the actual yml writes).\n */\n scopePrompt?: IdentityScopePrompt;\n /**\n * Per-container override from the container's yml `git.user`. Wins\n * over everything else (host global, workbench-wide defaults,\n * persisted state, interactive prompt).\n */\n containerOverride?: { name?: string; email?: string };\n /**\n * Workbench-wide defaults from `<MONOCEROS_HOME>/monoceros-config.yml`\n * `defaults.git.user`. Wins over host global git config (the\n * monoceros-config.yml is an explicit builder choice for Monoceros\n * containers; host global is the catch-all default), loses to the\n * per-container override.\n */\n defaults?: { name?: string; email?: string };\n logger?: { info: (msg: string) => void; warn: (msg: string) => void };\n}\n\nexport interface CollectIdentityResult {\n name?: string;\n email?: string;\n gitconfigPath: string;\n /**\n * Set ONLY when the identity should be persisted somewhere new —\n * the builder picked one of the persistence scopes (`g`/`c`/`b`)\n * either after a fresh prompt or after we offered to promote\n * recovered persisted values to monoceros-config. The caller uses\n * this to decide which yml(s) to write.\n *\n * `name` / `email` carry the values to persist so the caller\n * doesn't have to re-fish them out of the result fields above.\n * `'n'` (skip) is filtered out before this surfaces — the field\n * stays `undefined` in that case.\n */\n prompted?: {\n name: string;\n email: string;\n scope: 'g' | 'c' | 'b';\n };\n}\n\n/**\n * Extract `user.name` and `user.email` from the host's global git\n * config, write them as `<devContainerRoot>/.monoceros/gitconfig` for\n * the container to include. Done both at `monoceros create` time (so\n * the first `start` has identity) and at every `monoceros apply` (so\n * host changes propagate in).\n *\n * Always writes the file, even when host has nothing set — keeps the\n * include.path target valid (git silently ignores missing files, but\n * present-but-empty is more deterministic).\n *\n * Returns the captured values; the caller can use them for logging.\n * Missing values surface as `undefined`, plus a warn log line.\n */\n/**\n * Resolve an identity by walking the precedence chain (override →\n * defaults → host → persisted → prompt). Pure as far as Monoceros\n * state goes: doesn't write the `.monoceros/gitconfig` file —\n * `collectGitIdentity` is the wrapper that does.\n *\n * Used from `init` when a `--with-repo` flag means the builder needs\n * an identity before any container exists yet (and so before\n * `.monoceros/gitconfig` has a target path). Persistence to\n * monoceros-config or container yml is the caller's job either way.\n */\nexport async function resolveIdentityWithPrompt(\n options: CollectIdentityOptions & {\n persistedValues?: { name?: string; email?: string };\n } = {},\n): Promise<{\n name?: string;\n email?: string;\n prompted?: { name: string; email: string; scope: 'g' | 'c' | 'b' };\n}> {\n const spawnFn = options.spawn ?? realGitConfigGet;\n const promptFn = options.prompt ?? realIdentityPrompt;\n const scopePromptFn = options.scopePrompt ?? realScopePrompt;\n const logger = options.logger ?? { info: () => {}, warn: () => {} };\n const persisted = options.persistedValues ?? {};\n\n const name = await resolveKey('user.name', {\n override: options.containerOverride?.name,\n defaultValue: options.defaults?.name,\n spawnFn,\n persistedValue: persisted.name,\n promptFn,\n logger,\n });\n const email = await resolveKey('user.email', {\n override: options.containerOverride?.email,\n defaultValue: options.defaults?.email,\n spawnFn,\n persistedValue: persisted.email,\n promptFn,\n logger,\n });\n\n // Scope-prompt triggers when both keys came from the same\n // \"promotable\" source AND the identity hasn't already been\n // canonicalised in the yml ladder (container override / global\n // defaults).\n //\n // - `prompt`: fresh entry → ask where to persist (default `g`)\n // - `persisted`: recovered from .monoceros/gitconfig of a prior\n // apply → ask whether to promote to monoceros-config now that\n // the global defaults are empty (default `n` on non-TTY)\n //\n // `host` source is treated as \"the builder's machine-wide default,\n // not ours to promote\" and never triggers the prompt — host\n // changes can drift while Monoceros sleeps.\n const alreadyCanonical =\n !!options.containerOverride?.name ||\n !!options.containerOverride?.email ||\n !!options.defaults?.name ||\n !!options.defaults?.email;\n const promptableSources: ReadonlyArray<IdentitySource> = [\n 'prompt',\n 'persisted',\n ];\n const bothPromotable =\n name?.source !== undefined &&\n email?.source !== undefined &&\n promptableSources.includes(name.source) &&\n promptableSources.includes(email.source) &&\n name.source === email.source;\n\n let promptedScope: IdentityScope | undefined;\n if (!alreadyCanonical && bothPromotable && name?.value && email?.value) {\n promptedScope = await scopePromptFn({\n reason: name.source as 'prompt' | 'persisted',\n name: name.value,\n email: email.value,\n });\n }\n\n return {\n ...(name?.value !== undefined ? { name: name.value } : {}),\n ...(email?.value !== undefined ? { email: email.value } : {}),\n // Only surface `prompted` when the scope is a persistence target\n // (`g`/`c`/`b`). `'n'` means \"do nothing\" — no point passing it\n // to the caller as a \"go persist\" signal.\n ...(promptedScope && promptedScope !== 'n' && name?.value && email?.value\n ? {\n prompted: {\n name: name.value,\n email: email.value,\n scope: promptedScope,\n },\n }\n : {}),\n };\n}\n\nexport async function collectGitIdentity(\n devContainerRoot: string,\n options: CollectIdentityOptions = {},\n): Promise<CollectIdentityResult> {\n const gitconfigDir = path.join(devContainerRoot, '.monoceros');\n const gitconfigPath = path.join(gitconfigDir, 'gitconfig');\n const logger = options.logger ?? { info: () => {}, warn: () => {} };\n\n const existing = await readExistingGitconfig(gitconfigPath);\n\n const resolved = await resolveIdentityWithPrompt({\n ...options,\n persistedValues: existing,\n logger,\n });\n\n const lines: string[] = ['[user]'];\n if (resolved.name !== undefined) lines.push(`\\tname = ${resolved.name}`);\n if (resolved.email !== undefined) lines.push(`\\temail = ${resolved.email}`);\n\n await fs.mkdir(gitconfigDir, { recursive: true });\n await fs.writeFile(gitconfigPath, lines.join('\\n') + '\\n');\n\n return {\n ...(resolved.name !== undefined ? { name: resolved.name } : {}),\n ...(resolved.email !== undefined ? { email: resolved.email } : {}),\n gitconfigPath,\n ...(resolved.prompted ? { prompted: resolved.prompted } : {}),\n };\n}\n\ninterface ResolveKeyOpts {\n override?: string;\n defaultValue?: string;\n spawnFn: IdentitySpawn;\n persistedValue?: string;\n promptFn: IdentityPrompt;\n logger: { warn: (msg: string) => void };\n}\n\ntype IdentitySource =\n | 'container'\n | 'defaults'\n | 'host'\n | 'persisted'\n | 'prompt';\n\ninterface ResolvedKey {\n value: string;\n source: IdentitySource;\n}\n\nasync function resolveKey(\n key: 'user.name' | 'user.email',\n opts: ResolveKeyOpts,\n): Promise<ResolvedKey | undefined> {\n if (opts.override !== undefined && opts.override.length > 0) {\n return { value: opts.override, source: 'container' };\n }\n if (opts.defaultValue !== undefined && opts.defaultValue.length > 0) {\n return { value: opts.defaultValue, source: 'defaults' };\n }\n const hostValue = await readKeyFromHost(opts.spawnFn, key, opts.logger);\n if (hostValue !== undefined) return { value: hostValue, source: 'host' };\n if (opts.persistedValue !== undefined && opts.persistedValue.length > 0) {\n return { value: opts.persistedValue, source: 'persisted' };\n }\n const prompted = await opts.promptFn(key);\n if (prompted !== undefined) return { value: prompted, source: 'prompt' };\n opts.logger.warn(\n `No ${key} resolvable (yml override, monoceros-config.yml defaults, host \\`git config --global\\`, persisted .monoceros/gitconfig, prompt). Container git will have no ${key} until set explicitly.`,\n );\n return undefined;\n}\n\nasync function readKeyFromHost(\n spawnFn: IdentitySpawn,\n key: string,\n logger: { warn: (msg: string) => void },\n): Promise<string | undefined> {\n try {\n const result = await spawnFn(key);\n if (result.exitCode === 0 && result.value.length > 0) {\n return result.value;\n }\n return undefined;\n } catch (err) {\n logger.warn(\n `Host git not runnable (${err instanceof Error ? err.message : String(err)}); identity not captured.`,\n );\n return undefined;\n }\n}\n\nasync function readExistingGitconfig(\n filePath: string,\n): Promise<{ name?: string; email?: string }> {\n try {\n const content = await fs.readFile(filePath, 'utf8');\n const result: { name?: string; email?: string } = {};\n const nameMatch = /^\\s*name\\s*=\\s*(.+?)\\s*$/m.exec(content);\n const emailMatch = /^\\s*email\\s*=\\s*(.+?)\\s*$/m.exec(content);\n if (nameMatch?.[1]) result.name = nameMatch[1];\n if (emailMatch?.[1]) result.email = emailMatch[1];\n return result;\n } catch {\n return {};\n }\n}\n","// Single source of truth for the CLI version: `packages/cli/package.json`.\n//\n// At build time tsup (`tsup.config.ts`) reads `package.json.version` and\n// substitutes the `__CLI_VERSION__` placeholder below. So bumping the\n// version means editing exactly one file — package.json — and rebuilding.\n//\n// In dev (vitest, tsc) the placeholder isn't replaced; the fallback\n// `'dev'` kicks in. Tests don't depend on the exact version string.\n\ndeclare const __CLI_VERSION__: string;\n\nexport const CLI_VERSION =\n typeof __CLI_VERSION__ === 'string' ? __CLI_VERSION__ : 'dev';\n","import { consola } from 'consola';\n\n// Shared exit-code dispatcher: runs the orchestrator, propagates its\n// exit code, and turns thrown errors into a clean console message + exit 1.\nexport async function dispatch(runner: () => Promise<number>): Promise<never> {\n try {\n const exitCode = await runner();\n process.exit(exitCode);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n}\n","import { defineCommand } from 'citty';\n\n/**\n * `monoceros completion <shell>` — prints a shell-completion script\n * for bash, zsh or PowerShell to stdout. The user pipes the output\n * into a file their shell sources at startup.\n *\n * Architecture: the printed script is a THIN wrapper. The actual\n * completion logic lives in the CLI itself behind\n * `monoceros __complete --line \"<buffer>\" --point <N>`, which reads\n * the cursor's view of the line and returns one candidate per line\n * on stdout. The shell script's only job is:\n *\n * 1. Capture the current line + cursor position from the shell's\n * completion variables (COMP_LINE/COMP_POINT, BUFFER/CURSOR,\n * $commandAst/$cursorPosition).\n * 2. Pipe them to `monoceros __complete`.\n * 3. Hand the resulting lines back to the shell's completion\n * mechanism.\n *\n * That keeps the SoT in citty's command definitions + the spec table\n * in `completion/resolve.ts`. Adding a new command or flag means\n * extending the resolver, not editing per-shell scripts.\n */\n\nconst SHELLS = ['bash', 'zsh', 'pwsh'] as const;\ntype Shell = (typeof SHELLS)[number];\n\nexport function renderCompletionScript(shell: Shell): string {\n if (shell === 'bash') {\n return [\n '# bash completion for monoceros',\n '# install: source this file from .bashrc, e.g.',\n '# monoceros completion bash > ~/.bash_completion.d/monoceros',\n '# echo \"source ~/.bash_completion.d/monoceros\" >> ~/.bashrc',\n '#',\n '# The work is done by `monoceros __complete --line --point`; this',\n '# shell wrapper only forwards the cursor view.',\n '',\n '_monoceros() {',\n \" local IFS=$'\\\\n'\",\n ' local candidates',\n ' candidates=$(monoceros __complete --line \"$COMP_LINE\" --point \"$COMP_POINT\" 2>/dev/null)',\n ' local cur=\"${COMP_WORDS[COMP_CWORD]}\"',\n ' COMPREPLY=( $(compgen -W \"$candidates\" -- \"$cur\") )',\n ' # Suppress the trailing space when bash narrowed the candidate',\n ' # set to a single token that ends with `=` — those are value-',\n ' # flags (`--with-features=`, `--with-ports=`, …) where the user types the',\n ' # value immediately after.',\n ' if [[ ${#COMPREPLY[@]} -eq 1 && \"${COMPREPLY[0]}\" == *= ]]; then',\n ' compopt -o nospace',\n ' fi',\n '}',\n 'complete -F _monoceros monoceros',\n '',\n ].join('\\n');\n }\n\n if (shell === 'pwsh') {\n return [\n '# PowerShell completion for monoceros',\n '# install: dot-source this file from your $PROFILE, e.g.',\n '# monoceros completion pwsh > $HOME/.config/monoceros/completion.ps1',\n \"# Add-Content $PROFILE '. $HOME/.config/monoceros/completion.ps1'\",\n '#',\n '# The work is done by `monoceros __complete --line --point`; this',\n '# shell wrapper only forwards the cursor view.',\n '',\n 'Register-ArgumentCompleter -Native -CommandName monoceros -ScriptBlock {',\n ' param($wordToComplete, $commandAst, $cursorPosition)',\n ' $line = $commandAst.Extent.Text',\n ' $point = $cursorPosition - $commandAst.Extent.StartOffset',\n ' if ($point -lt 0) { $point = 0 }',\n ' $raw = & monoceros __complete --line $line --point $point 2>$null',\n ' if (-not $raw) { return }',\n ' $raw -split \"`n\" |',\n ' Where-Object { $_.Length -gt 0 -and $_ -like \"$wordToComplete*\" } |',\n ' ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, \"ParameterValue\", $_) }',\n '}',\n '',\n ].join('\\n');\n }\n\n // zsh\n return [\n '#compdef monoceros',\n '# zsh completion for monoceros',\n '# install: drop this file somewhere on your $fpath as `_monoceros`,',\n '# then start a new shell (or run `compinit`). Example:',\n '# monoceros completion zsh > \"${fpath[1]}/_monoceros\"',\n '#',\n '# The work is done by `monoceros __complete --line --point`; this',\n '# shell wrapper only forwards the cursor view.',\n '',\n '_monoceros() {',\n ' local line=\"$BUFFER\"',\n ' local point=\"$CURSOR\"',\n ' local -a candidates with_eq without_eq',\n ' candidates=(\"${(@f)$(monoceros __complete --line \"$line\" --point \"$point\" 2>/dev/null)}\")',\n ' candidates=(\"${(@)candidates:#}\")',\n ' # Split candidates into \"ends with `=`\" (value-flags — no suffix',\n ' # space wanted because the user types the value immediately) and',\n ' # \"everything else\" (positional values, boolean flags — default',\n ' # space behaviour).',\n ' for cand in \"${candidates[@]}\"; do',\n ' if [[ \"$cand\" == *= ]]; then',\n ' with_eq+=(\"$cand\")',\n ' else',\n ' without_eq+=(\"$cand\")',\n ' fi',\n ' done',\n ' (( ${#with_eq[@]} )) && compadd -S \\'\\' -- \"${with_eq[@]}\"',\n ' (( ${#without_eq[@]} )) && compadd -- \"${without_eq[@]}\"',\n '}',\n '',\n '_monoceros \"$@\"',\n '',\n ].join('\\n');\n}\n\nexport const completionCommand = defineCommand({\n meta: {\n name: 'completion',\n group: 'tooling',\n // Hidden from `monoceros --help`: the install scripts wire up\n // completion automatically; manual setup is documented in\n // docs/commands/completion.md. Still runnable directly.\n hidden: true,\n description:\n 'Print a shell completion script for bash, zsh or PowerShell to stdout. Pipe the output into a file your shell loads at startup. The install scripts (install.sh / install.ps1) call this automatically.',\n },\n args: {\n shell: {\n type: 'positional',\n description: \"Target shell. One of: 'bash', 'zsh', 'pwsh'.\",\n required: true,\n },\n },\n run({ args }) {\n const shell = args.shell as string;\n if (shell !== 'bash' && shell !== 'zsh' && shell !== 'pwsh') {\n process.stderr.write(\n `Unknown shell: ${JSON.stringify(shell)}. Supported: ${SHELLS.join(', ')}.\\n`,\n );\n process.exit(2);\n }\n process.stdout.write(renderCompletionScript(shell));\n },\n});\n\nexport const COMPLETION_SHELLS = SHELLS;\n","import { defineCommand } from 'citty';\nimport { resolveCompletions } from '../completion/resolve.js';\n\n/**\n * Internal CLI entrypoint shell-completion scripts call. NOT part of\n * the user-facing surface — the leading `__` keeps it out of typical\n * help listings. Generated bash / zsh / pwsh wrappers (see\n * `commands/completion.ts`) call this with the current command-line\n * and cursor position, and emit each line of stdout as a candidate\n * completion.\n *\n * Contract (must stay stable; the shipped wrappers depend on it):\n *\n * monoceros __complete --line \"<full line>\" --point <N>\n *\n * - Exit 0 always (errors → empty output; completion is a comfort\n * feature, never a fatal one).\n * - Output: one candidate per line, no decorations, no escapes.\n * - Comma-separated value flags (`--with-features=github,claude`) get\n * returned with the leading prefix already attached so the shell inserts\n * the full token in place — see resolveValues in resolve.ts.\n */\n\nexport const __completeCommand = defineCommand({\n meta: {\n name: '__complete',\n group: 'internal',\n // Internal plumbing for the shell wrappers — never shown in help.\n hidden: true,\n description:\n 'Internal — shell completion engine. Used by the wrappers emitted by `monoceros completion <shell>`. Output one candidate completion per line.',\n },\n args: {\n line: {\n type: 'string',\n description:\n 'Full command line buffer up to (and possibly past) the cursor.',\n default: '',\n },\n point: {\n type: 'string',\n description:\n 'Byte offset of the cursor within --line. Default: end of line.',\n default: '',\n },\n },\n async run({ args }) {\n const line = String(args.line ?? '');\n const point =\n args.point && String(args.point).length > 0\n ? Number.parseInt(String(args.point), 10)\n : line.length;\n let candidates: string[];\n try {\n candidates = await resolveCompletions(\n line,\n Number.isFinite(point) ? point : line.length,\n );\n } catch {\n // Never fail the shell's completion request — silent empty\n // suggestion is the worst it should ever be.\n candidates = [];\n }\n if (candidates.length > 0) {\n process.stdout.write(candidates.join('\\n') + '\\n');\n }\n },\n});\n","import { existsSync, promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { monocerosHome } from '../config/paths.js';\nimport { loadComponentCatalog } from '../init/components.js';\nimport { loadFeatureManifestSummary } from '../init/manifest.js';\nimport { knownLanguages, knownServices } from '../create/catalog.js';\nimport { PROVIDER_VALUES, REGEX } from '../config/schema.js';\n\n/**\n * Shell-agnostic completion engine. Called by the `__complete`\n * internal CLI command and (in tests) directly via `resolveCompletions`.\n *\n * The caller hands us the full command-line buffer (`line`) plus the\n * cursor's byte offset within it (`point`). We tokenize the prefix,\n * figure out which positional / flag is being completed, and return\n * a list of candidate completions matching the current token fragment.\n *\n * Tokenization rules:\n * - Whitespace separates tokens.\n * - Double or single quotes group a run including whitespace; we\n * return the un-quoted content as the token (the shell glue\n * re-quotes when emitting).\n * - A `--key=value` form is one token; the equals sign isn't a\n * separator. Inside `--with-features=github,claude` we treat the part\n * after `=` as the current value-fragment for the `--with-features` flag.\n *\n * Per-command suggestions live in `COMMAND_SPECS`. Adding a new\n * command means adding (or extending) one entry there — the engine\n * dispatches automatically.\n *\n * Source of truth is citty's command definitions; the spec table is\n * a thin mirror keyed by command name. A test (`completion.test.ts`)\n * pins the spec keys against the live command list.\n */\n\n// ─── Public surface ───────────────────────────────────────────────\n\nexport interface ResolveOptions {\n /** Override of MONOCEROS_HOME (tests). */\n monocerosHome?: string;\n}\n\nexport async function resolveCompletions(\n line: string,\n point: number,\n opts: ResolveOptions = {},\n): Promise<string[]> {\n const { prev, current } = parseCompletionLine(line, point);\n const ctx: Ctx = { prev, current, opts };\n // prev[0] is the program name (usually \"monoceros\"); skip it.\n const head = prev[0];\n const afterProgram = head === undefined ? [] : prev.slice(1);\n // No subcommand typed yet → suggest the subcommand list.\n if (afterProgram.length === 0) {\n return filterPrefix(ALL_COMMANDS, current);\n }\n const command = afterProgram[0]!;\n const spec = COMMAND_SPECS[command];\n if (!spec) {\n // Unknown subcommand. We've already entered position 2+, but the\n // command isn't one we know — no useful suggestions.\n return [];\n }\n // Tokens that belong to the command's args (past `monoceros <cmd>`).\n const argTokens = afterProgram.slice(1);\n return dispatchCommand(spec, argTokens, ctx);\n}\n\n// ─── Tokenizer + cursor-context parser ────────────────────────────\n\ninterface CompletionContext {\n /** Tokens to the LEFT of the one being completed. */\n prev: string[];\n /** The token currently under the cursor (or `''` at a fresh word). */\n current: string;\n}\n\ninterface Ctx extends CompletionContext {\n opts: ResolveOptions;\n}\n\nexport function parseCompletionLine(\n line: string,\n point: number,\n): CompletionContext {\n const before = line.slice(0, Math.max(0, Math.min(point, line.length)));\n const tokens = tokenize(before);\n // If the cursor sits right after whitespace, the user is starting a\n // fresh token — current is empty, all preceding tokens are \"prev\".\n const lastChar = before.length > 0 ? before[before.length - 1]! : '';\n if (tokens.length === 0 || isShellWhitespace(lastChar)) {\n return { prev: tokens, current: '' };\n }\n return {\n prev: tokens.slice(0, -1),\n current: tokens[tokens.length - 1]!,\n };\n}\n\nfunction tokenize(text: string): string[] {\n const out: string[] = [];\n let i = 0;\n while (i < text.length) {\n while (i < text.length && isShellWhitespace(text[i]!)) i++;\n if (i >= text.length) break;\n let token = '';\n let quote: '\"' | \"'\" | null = null;\n while (i < text.length) {\n const ch = text[i]!;\n if (quote === null && isShellWhitespace(ch)) break;\n if (quote === null && (ch === '\"' || ch === \"'\")) {\n quote = ch;\n i++;\n continue;\n }\n if (quote !== null && ch === quote) {\n quote = null;\n i++;\n continue;\n }\n token += ch;\n i++;\n }\n out.push(token);\n }\n return out;\n}\n\nfunction isShellWhitespace(ch: string): boolean {\n return ch === ' ' || ch === '\\t';\n}\n\n// ─── Per-command dispatch ─────────────────────────────────────────\n\ntype ValueSource = (ctx: Ctx) => Promise<string[]> | string[];\n\ninterface FlagSpec {\n /** `boolean` = no value; `value` = `--flag <X>` or `--flag=<X>`. */\n type: 'boolean' | 'value';\n /** Short forms, e.g. `['-y']`. */\n aliases?: string[];\n /** Value suggestions for `value`-typed flags. Optional → freeform. */\n values?: ValueSource;\n}\n\ninterface CommandSpec {\n /**\n * Suggestion sources for positional args, indexed by position\n * (0 = arg right after the command name). Missing entries mean\n * the slot exists but has no completion source (e.g. `init`'s\n * fresh-name positional — we don't suggest existing container\n * names there, that would invite collisions).\n */\n positionals?: ValueSource[];\n /**\n * How many positionals the command expects. Defaults to\n * `positionals.length`. Set this explicitly when the command has\n * MORE positional slots than entries in `positionals` (= \"this\n * slot exists but has no suggestion source\"). Once the cursor sits\n * past `positionalCount`, completion falls back to flag names so\n * the builder discovers `--with-*` / `--yes` etc. via Tab.\n */\n positionalCount?: number;\n /** Flag table. Keys include the leading `--`. */\n flags?: Record<string, FlagSpec>;\n /**\n * Suggestion source for tokens after `--` (inner args). Used by\n * `add-feature -- key=value` and `add-apt-packages -- pkg pkg …`.\n */\n innerArgs?: ValueSource;\n}\n\nfunction dispatchCommand(\n spec: CommandSpec,\n argTokens: string[],\n ctx: Ctx,\n): Promise<string[]> | string[] {\n // Split argTokens at the `--` separator: tokens after it are inner\n // args (consumed by `innerArgs`); tokens before it are positionals\n // and flags consumed by `resolvePreDash`. We only need preDash —\n // the post-dash tokens are read directly off ctx.prev by per-command\n // innerArgs sources that care (see listFeatureOptionInnerArgs).\n const dashDashIdx = argTokens.indexOf('--');\n const preDash = dashDashIdx < 0 ? argTokens : argTokens.slice(0, dashDashIdx);\n const inPostDash = dashDashIdx >= 0;\n\n if (inPostDash && spec.innerArgs) {\n return resolveValues(spec.innerArgs, ctx, ctx.current);\n }\n\n // Pre-dash: figure out whether `current` is a value for a flag we\n // started typing, or a fresh positional / flag-name slot.\n return resolvePreDash(spec, preDash, ctx);\n}\n\nasync function resolvePreDash(\n spec: CommandSpec,\n preDash: string[],\n ctx: Ctx,\n): Promise<string[]> {\n const current = ctx.current;\n\n // Case A: current looks like `--flag=…` → suggest values for that flag.\n if (current.startsWith('--') && current.includes('=')) {\n const eqIdx = current.indexOf('=');\n const flagName = current.slice(0, eqIdx);\n const valueFragment = current.slice(eqIdx + 1);\n const flag = spec.flags?.[flagName];\n if (!flag || flag.type !== 'value' || !flag.values) return [];\n const completions = await resolveValues(flag.values, ctx, valueFragment);\n // Re-emit with the `--flag=` prefix attached so the shell inserts\n // the full token in place.\n return completions.map((v) => `${flagName}=${v}`);\n }\n\n // Case B: current starts with `-` (incomplete flag name).\n if (current.startsWith('-')) {\n return listFlagNames(spec.flags ?? {}, current);\n }\n\n // Case C: previous token was `--flag` (no `=`) expecting a value.\n const lastPrev = preDash[preDash.length - 1];\n if (lastPrev && lastPrev.startsWith('--') && !lastPrev.includes('=')) {\n const flag = spec.flags?.[lastPrev];\n if (flag && flag.type === 'value' && flag.values) {\n return resolveValues(flag.values, ctx, current);\n }\n }\n\n // Case D: positional. Count how many positionals have been completed\n // already — that's any preDash token that isn't itself a flag or a\n // flag-value pair we passed through.\n const positionalIdx = countCompletedPositionals(preDash, spec.flags ?? {});\n const positionals = spec.positionals ?? [];\n const expectedPositionalCount = spec.positionalCount ?? positionals.length;\n\n // Still inside a defined positional slot → use its source.\n if (positionalIdx < positionals.length) {\n const positional = positionals[positionalIdx];\n if (positional) return resolveValues(positional, ctx, current);\n }\n // Past all expected positionals → surface available flags so Tab\n // discovers them without the builder having to know they exist\n // (and without having to start with a `-`).\n if (positionalIdx >= expectedPositionalCount) {\n return listFlagNames(spec.flags ?? {}, current);\n }\n // Slot is expected but has no completion source (e.g. `init`'s\n // fresh-name positional, `restore`'s backup-path). Don't suggest\n // anything — let the shell fall through to its built-in handling.\n return [];\n}\n\n/**\n * Flag-name suggestion list filtered against the partial token under\n * the cursor. Includes long names (`--with-features`) and short aliases (`-y`).\n *\n * Value-flags are emitted WITH a trailing `=` (`--with-features=`) so the shell\n * wrappers can use `compopt -o nospace` (bash) / `compadd -S ''` (zsh)\n * to suppress the auto-added trailing space — without that, picking\n * `--with-ports` via Tab and typing `=3000` afterwards produces the\n * broken `--with-ports =3000` (space between flag and value).\n *\n * Boolean flags (no value expected) stay as bare names so the shell's\n * normal trailing-space behaviour applies — after `--yes` you really\n * do want a space.\n */\nfunction listFlagNames(\n flags: Record<string, FlagSpec>,\n fragment: string,\n): string[] {\n const names: string[] = [];\n for (const [name, spec] of Object.entries(flags)) {\n names.push(spec.type === 'value' ? `${name}=` : name);\n for (const alias of spec.aliases ?? []) names.push(alias);\n }\n return filterPrefix(names, fragment);\n}\n\nfunction countCompletedPositionals(\n preDash: string[],\n flags: Record<string, FlagSpec>,\n): number {\n let count = 0;\n for (let i = 0; i < preDash.length; i++) {\n const t = preDash[i]!;\n if (t.startsWith('--') && t.includes('=')) continue; // `--flag=value` — a flag, not a positional\n if (t.startsWith('-')) {\n // Boolean flag → 1 token, value flag → 2 tokens (consume next).\n const flag = flags[t] ?? aliasFlag(flags, t);\n if (flag && flag.type === 'value' && i + 1 < preDash.length) {\n i++; // skip the value\n }\n continue;\n }\n count++;\n }\n return count;\n}\n\nfunction aliasFlag(\n flags: Record<string, FlagSpec>,\n token: string,\n): FlagSpec | undefined {\n for (const f of Object.values(flags)) {\n if (f.aliases?.includes(token)) return f;\n }\n return undefined;\n}\n\nasync function resolveValues(\n source: ValueSource,\n ctx: Ctx,\n fragment: string,\n): Promise<string[]> {\n const values = await source(ctx);\n // For comma-separated value flags (`--with-features=github,claude`) the\n // user may have typed `github,cl` — the fragment to filter against is the\n // part AFTER the last comma.\n const commaIdx = fragment.lastIndexOf(',');\n if (commaIdx < 0) {\n return filterPrefix(values, fragment);\n }\n const prefix = fragment.slice(0, commaIdx + 1);\n const tail = fragment.slice(commaIdx + 1);\n return filterPrefix(values, tail).map((v) => `${prefix}${v}`);\n}\n\nfunction filterPrefix(values: readonly string[], fragment: string): string[] {\n if (fragment.length === 0) return [...values];\n return values.filter((v) => v.startsWith(fragment));\n}\n\n// ─── Value sources ────────────────────────────────────────────────\n\nasync function listContainerNames(ctx: Ctx): Promise<string[]> {\n const home = ctx.opts.monocerosHome ?? monocerosHome();\n const dir = path.join(home, 'container-configs');\n if (!existsSync(dir)) return [];\n const entries = await fs.readdir(dir);\n return entries\n .filter((e) => e.endsWith('.yml'))\n .map((e) => e.slice(0, -'.yml'.length))\n .sort();\n}\n\nasync function listFeatureComponents(): Promise<string[]> {\n const catalog = await loadComponentCatalog();\n return [...catalog.values()]\n .filter((c) => c.file.category === 'feature')\n .map((c) => c.name)\n .sort();\n}\n\nfunction listLanguageNames(): string[] {\n return knownLanguages().sort();\n}\n\nfunction listServiceNames(): string[] {\n return knownServices().sort();\n}\n\nfunction listProviders(): string[] {\n return [...PROVIDER_VALUES];\n}\n\nfunction listShellNames(): string[] {\n return ['bash', 'zsh', 'pwsh'];\n}\n\n/**\n * Inner-arg completion for `monoceros add-feature <name> <feature> --\n * key=value …`. The `<feature>` token can be either a catalog short\n * name (`atlassian`, `atlassian/twg`) or a full OCI ref; in both cases\n * we resolve to the feature manifest and return the option keys.\n *\n * Behaviour with the current token:\n * - Token is empty or contains no `=` → suggest the option NAMES\n * (filtered against the partial token prefix).\n * - Token is `<key>=<fragment>` AND the key is a boolean → suggest\n * `<key>=true` / `<key>=false` matching `<fragment>`. For string\n * options we have no useful default suggestion list (it's\n * freeform credentials / URLs).\n * - Already-typed `key=value` pairs (before the current token) drop\n * the same `key=` from the suggestion list so the builder doesn't\n * get duplicates.\n */\nasync function listFeatureOptionInnerArgs(ctx: Ctx): Promise<string[]> {\n // Locate the feature token. ctx.prev[0] is the program name, [1] is\n // `add-feature`, [2] is the container name, [3] is the feature.\n // The user could have added flags like `--yes` before the feature,\n // but flag tokens always start with `-`. So the feature is the\n // SECOND non-flag positional after the command.\n const after = ctx.prev.slice(2); // drop \"monoceros\", \"add-feature\"\n let positionalCount = 0;\n let featureToken: string | undefined;\n for (let i = 0; i < after.length; i++) {\n const t = after[i]!;\n if (t === '--') break; // stop at the inner-args separator\n if (t.startsWith('-')) continue; // flag\n positionalCount++;\n if (positionalCount === 2) {\n featureToken = t;\n break;\n }\n }\n if (!featureToken) return [];\n const ref = await resolveFeatureRefForCompletion(featureToken);\n if (!ref) return [];\n const summary = loadFeatureManifestSummary(ref);\n if (!summary) return [];\n\n // Which keys has the builder already set in earlier inner-args?\n const dashDash = ctx.prev.indexOf('--');\n const innerSoFar = dashDash >= 0 ? ctx.prev.slice(dashDash + 1) : [];\n const usedKeys = new Set<string>();\n for (const t of innerSoFar) {\n const eq = t.indexOf('=');\n if (eq > 0) usedKeys.add(t.slice(0, eq));\n }\n\n // Current token: `key=value` or just `key`?\n const eqIdx = ctx.current.indexOf('=');\n if (eqIdx >= 0) {\n const key = ctx.current.slice(0, eqIdx);\n const valueFragment = ctx.current.slice(eqIdx + 1);\n const type = summary.optionTypes[key];\n if (type === 'boolean') {\n return ['true', 'false']\n .filter((v) => v.startsWith(valueFragment))\n .map((v) => `${key}=${v}`);\n }\n // String options: no useful suggestion list. Return [] so the\n // shell falls back to its own filename / nothing handling.\n return [];\n }\n\n // Plain key fragment — suggest the still-unused option keys WITH a\n // trailing `=` so the shell wrappers' nospace logic kicks in (same\n // shape as `--with-features=` etc. on the flag side). Without that, the\n // user gets `instance =foo` instead of `instance=foo` after Tab +\n // manual `=foo`.\n return summary.optionNames\n .filter((n) => !usedKeys.has(n))\n .map((n) => `${n}=`);\n}\n\n/**\n * Bridge between the short-name / full-ref formats the user types\n * for `add-feature <feature>` and the OCI ref the manifest loader\n * needs. A no-op for full OCI refs; a single catalog lookup for short\n * names. Returns `undefined` for unknown short-names (no completions).\n */\nasync function resolveFeatureRefForCompletion(\n token: string,\n): Promise<string | undefined> {\n if (REGEX.featureRef.test(token)) return token;\n const catalog = await loadComponentCatalog();\n const c = catalog.get(token);\n if (!c || c.file.category !== 'feature') return undefined;\n const f = c.file.contributes.features?.[0];\n return f?.ref;\n}\n\n// ─── Static command list (mirrors completion.ts) ──────────────────\n\nconst ALL_COMMANDS = [\n 'init',\n 'list-components',\n 'shell',\n 'run',\n 'logs',\n 'start',\n 'stop',\n 'status',\n 'apply',\n 'remove',\n 'restore',\n 'add-service',\n 'add-language',\n 'add-apt-packages',\n 'add-feature',\n 'add-from-url',\n 'add-repo',\n 'add-port',\n 'remove-service',\n 'remove-language',\n 'remove-apt-packages',\n 'remove-feature',\n 'remove-from-url',\n 'remove-repo',\n 'remove-port',\n 'port',\n 'tunnel',\n 'completion',\n] as const;\n\n// ─── Command specs ────────────────────────────────────────────────\n\nconst containerName: ValueSource = (ctx) => listContainerNames(ctx);\n\nconst COMMAND_SPECS: Record<string, CommandSpec> = {\n init: {\n // First positional is a FRESH name → no suggestion source, but\n // the slot exists. Once the cursor is past it (after the name +\n // space), `--with` / `--with-repo` / `--with-ports` surface as\n // flag suggestions.\n positionalCount: 1,\n flags: {\n '--with-languages': { type: 'value', values: () => listLanguageNames() },\n '--with-features': {\n type: 'value',\n values: () => listFeatureComponents(),\n },\n '--with-services': { type: 'value', values: () => listServiceNames() },\n '--with-apt-packages': { type: 'value' },\n '--with-repos': { type: 'value' },\n '--with-ports': { type: 'value' },\n },\n },\n apply: {\n positionals: [containerName],\n flags: { '--yes': { type: 'boolean', aliases: ['-y'] } },\n },\n remove: {\n positionals: [containerName],\n flags: {\n '--yes': { type: 'boolean', aliases: ['-y'] },\n '--no-backup': { type: 'boolean' },\n },\n },\n shell: { positionals: [containerName] },\n run: { positionals: [containerName] },\n logs: { positionals: [containerName] },\n start: { positionals: [containerName] },\n stop: { positionals: [containerName] },\n status: { positionals: [containerName] },\n 'add-language': {\n positionals: [containerName, () => listLanguageNames()],\n },\n 'add-service': {\n positionals: [containerName, () => listServiceNames()],\n },\n 'add-apt-packages': {\n positionals: [containerName],\n innerArgs: () => [], // freeform package names — no useful suggestion list\n },\n 'add-feature': {\n positionals: [containerName, () => listFeatureComponents()],\n flags: { '--yes': { type: 'boolean', aliases: ['-y'] } },\n innerArgs: (ctx) => listFeatureOptionInnerArgs(ctx),\n },\n 'add-from-url': { positionals: [containerName] },\n 'add-repo': {\n positionals: [containerName],\n flags: {\n '--path': { type: 'value' },\n '--git-name': { type: 'value' },\n '--git-email': { type: 'value' },\n '--provider': { type: 'value', values: () => listProviders() },\n '--yes': { type: 'boolean', aliases: ['-y'] },\n },\n },\n 'add-port': {\n positionals: [containerName],\n flags: {\n '--default': { type: 'boolean' },\n '--yes': { type: 'boolean', aliases: ['-y'] },\n },\n innerArgs: () => [],\n },\n 'remove-language': {\n positionals: [containerName, () => listLanguageNames()],\n },\n 'remove-service': {\n positionals: [containerName, () => listServiceNames()],\n },\n 'remove-apt-packages': {\n positionals: [containerName],\n innerArgs: () => [],\n },\n 'remove-feature': {\n positionals: [containerName, () => listFeatureComponents()],\n flags: { '--yes': { type: 'boolean', aliases: ['-y'] } },\n },\n 'remove-from-url': { positionals: [containerName] },\n 'remove-repo': { positionals: [containerName] },\n 'remove-port': {\n positionals: [containerName],\n flags: { '--yes': { type: 'boolean', aliases: ['-y'] } },\n innerArgs: () => [],\n },\n port: {\n positionals: [containerName],\n flags: { '--default': { type: 'boolean' } },\n innerArgs: () => [],\n },\n tunnel: {\n positionals: [containerName, () => listServiceNames()],\n flags: {\n '--local-port': { type: 'value' },\n '--local-address': { type: 'value' },\n },\n },\n completion: {\n positionals: [() => listShellNames()],\n },\n 'list-components': {},\n restore: {\n // First positional is a backup-path; no value suggestions today\n // (could plug filesystem completion later). Slot still exists so\n // Tab is silent inside it rather than offering flags prematurely.\n positionalCount: 1,\n },\n};\n\n/** Exposed for tests so the command list stays in sync with main.ts. */\nexport const COMPLETION_ALL_COMMANDS = ALL_COMMANDS;\nexport const COMPLETION_COMMAND_SPEC_KEYS = Object.keys(COMMAND_SPECS);\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runInit } from '../init/index.js';\n\nexport const initCommand = defineCommand({\n meta: {\n name: 'init',\n group: 'lifecycle',\n description:\n 'Create a fresh container-config yml at <MONOCEROS_HOME>/container-configs/<name>.yml. Without any --with-* flag, the file is a documented default with every component commented out. With --with-languages / --with-features / --with-services / --with-apt-packages, the named pieces are composed into an active, immediately-applyable yml. Then run `monoceros apply <name>`.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Config name. The yml lands at <MONOCEROS_HOME>/container-configs/<name>.yml and becomes the source-of-truth for `monoceros apply <name>`.',\n required: true,\n },\n 'with-languages': {\n type: 'string',\n description:\n 'Language runtimes to install, comma-separated or repeated, e.g. --with-languages=java,node. Optional :version (java:17). Curated catalog only — see `monoceros list-components`.',\n required: false,\n },\n 'with-features': {\n type: 'string',\n description:\n 'Features (AI tools, language CLIs, …), comma-separated or repeated. Catalog short name (claude, atlassian/twg) or a full OCI ref (ghcr.io/foo/bar:1).',\n required: false,\n },\n 'with-services': {\n type: 'string',\n description:\n 'Backing services, comma-separated or repeated. Curated name (postgres, mysql, redis) → full editable block; any other image (rustfs/rustfs:latest) → name + image + commented scaffold.',\n required: false,\n },\n 'with-apt-packages': {\n type: 'string',\n description:\n 'Debian/Ubuntu apt packages to install, comma-separated or repeated, e.g. --with-apt-packages=openssl,make. No curated list.',\n required: false,\n },\n 'with-repos': {\n type: 'string',\n description:\n 'Git URLs to clone into projects/ on first apply, comma-separated or repeated. Folder name derived from URL (foo.git → projects/foo/); use `monoceros add-repo --path=...` post-init for subfolder paths. Canonical hosts only (github.com / gitlab.com / bitbucket.org).',\n required: false,\n },\n 'with-ports': {\n type: 'string',\n description:\n 'Comma-separated list of container-internal ports to expose via Traefik, e.g. --with-ports=3000,5173,6006. First entry doubles as http://<name>.localhost (default route). Equivalent to `monoceros add-port` after init. Each must be an integer in 1–65535.',\n required: false,\n },\n },\n async run({ args, rawArgs }) {\n try {\n const languages = collectListFlag('--with-languages', rawArgs);\n const features = collectListFlag('--with-features', rawArgs);\n const services = collectListFlag('--with-services', rawArgs);\n const aptPackages = collectListFlag('--with-apt-packages', rawArgs);\n const repos = collectListFlag('--with-repos', rawArgs);\n const ports = collectWithPortsList(args['with-ports'], rawArgs);\n await runInit({\n name: args.name,\n ...(languages.length > 0 ? { languages } : {}),\n ...(features.length > 0 ? { features } : {}),\n ...(services.length > 0 ? { services } : {}),\n ...(aptPackages.length > 0 ? { aptPackages } : {}),\n ...(repos.length > 0 ? { withRepo: repos } : {}),\n ...(ports && ports.length > 0 ? { withPorts: ports } : {}),\n });\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n\n/**\n * Collect every value for a repeatable comma-list flag from rawArgs.\n * Handles all three shapes the shell can produce:\n * --flag=a,b → ['a','b']\n * --flag a b → ['a','b']\n * --flag=a, b, c → ['a','b','c'] (shell strips spaces, rest float)\n * citty only keeps the last occurrence of a repeated flag, so we walk\n * rawArgs directly. Returns trimmed, comma-split, non-empty pieces in\n * order of appearance.\n */\nexport function collectListFlag(flag: string, rawArgs: string[]): string[] {\n const eq = `${flag}=`;\n const pieces: string[] = [];\n for (let i = 0; i < rawArgs.length; i += 1) {\n const t = rawArgs[i]!;\n let scanStart = -1;\n if (t === flag) {\n scanStart = i + 1;\n } else if (t.startsWith(eq)) {\n pieces.push(t.slice(eq.length));\n scanStart = i + 1;\n }\n if (scanStart < 0) continue;\n let j = scanStart;\n while (j < rawArgs.length) {\n const u = rawArgs[j]!;\n if (u.startsWith('-')) break;\n pieces.push(u);\n j += 1;\n }\n i = j - 1;\n }\n return pieces\n .flatMap((s) => s.split(','))\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n}\n\n/**\n * Collect ports from every `--with-ports` occurrence (both `=value`\n * and two-token forms) plus the shell-tokenization fallback for\n * `--with-ports=3000, 5173, 6006` where the shell strips the spaces\n * and leaves bare value tokens after the flag.\n *\n * We walk rawArgs only and ignore the args['with-ports'] value —\n * citty drops earlier occurrences when the flag is repeated, but\n * rawArgs has them all in order.\n *\n * Validation (integer, 1..65535) lives in runInit so the same error\n * surface covers both the CLI and direct runInit callers.\n */\nexport function collectWithPortsList(\n _withPortsArg: string | undefined,\n rawArgs: string[],\n): number[] | undefined {\n const pieces: string[] = [];\n for (let i = 0; i < rawArgs.length; i += 1) {\n const t = rawArgs[i]!;\n let scanStart = -1;\n if (t === '--with-ports') {\n scanStart = i + 1; // value sits after the flag, picked up below\n } else if (t.startsWith('--with-ports=')) {\n pieces.push(t.slice('--with-ports='.length));\n scanStart = i + 1;\n }\n if (scanStart < 0) continue;\n // Sweep subsequent non-flag tokens — covers both the two-token\n // form (`--with-ports VALUE`) and the shell-tokenized\n // `--with-ports=3000, 5173, 6006` case where 5173/6006 land as\n // bare tokens after the flag.\n let j = scanStart;\n while (j < rawArgs.length) {\n const u = rawArgs[j]!;\n if (u.startsWith('-')) break;\n pieces.push(u);\n j += 1;\n }\n // Skip everything we just consumed; the outer i++ resumes at j.\n i = j - 1;\n }\n const parts = pieces\n .flatMap((s) => s.split(','))\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n if (parts.length === 0) return undefined;\n const out: number[] = [];\n for (const p of parts) {\n const n = Number(p);\n if (!Number.isInteger(n) || n < 1 || n > 65535) {\n throw new Error(\n `Invalid port in --with-ports: ${JSON.stringify(p)}. Expected integers between 1 and 65535, comma-separated.`,\n );\n }\n out.push(n);\n }\n return out;\n}\n","import { existsSync, promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { consola } from 'consola';\nimport {\n containerConfigPath,\n containerConfigsDir,\n containerEnvPath,\n monocerosHome as defaultMonocerosHome,\n workbenchRoot as defaultWorkbenchRoot,\n workbenchCheckoutRoot,\n componentsDir as defaultComponentsDir,\n} from '../config/paths.js';\nimport {\n ensureEnvGitignored,\n ensureEnvVars,\n GIT_IDENTITY_VAR,\n} from '../config/env-file.js';\nimport { featureOptionHints } from './feature-doc.js';\nimport { KNOWN_PROVIDER_HOSTS, REGEX } from '../config/schema.js';\nimport { loadComponentCatalog, mergeFeatureOptions } from './components.js';\nimport type { Component } from './components.js';\nimport {\n generateComposedYml,\n generateDocumentedYml,\n type ComposedInit,\n type InitService,\n} from './generator.js';\nimport { loadFeatureManifestSummary } from './manifest.js';\nimport {\n curatedServiceEnvDefaults,\n deriveServiceName,\n isCuratedService,\n knownLanguages,\n parseLanguageSpec,\n} from '../create/catalog.js';\n\n/**\n * `monoceros init <name> [--with-languages=… --with-features=… …]` —\n * produce a fresh container-config yml at\n * `<MONOCEROS_HOME>/container-configs/<name>.yml`.\n *\n * Two modes:\n *\n * - With any per-category flag (`--with-languages=node`,\n * `--with-services=postgres`, `--with-features=github,claude`, …;\n * each takes a comma-list): the listed components are merged and\n * the result written as an active, immediately-applyable yml.\n * Per-feature option hints (auth/credentials from the feature\n * manifest) appear as commented lines next to the active options\n * so the builder can see what's available without leaving the file.\n *\n * - Without any `--with-*` flag: a documented-default yml is written. Every\n * section is commented out, every catalog component appears as\n * a suggestion with prose describing what it adds. Builder\n * un-comments what they want, then `monoceros apply <name>`.\n *\n * Errors loudly if:\n *\n * - the target config already exists (delete it first if you want\n * to start over — protects hand-edits)\n * - a `--with-*` name is not in the catalog (the error message\n * lists what *is* available)\n * - the chosen container name is shape-invalid\n */\n\nexport interface RunInitOptions {\n name: string;\n /**\n * Explicit per-category inputs (from `--with-languages`,\n * `--with-features`, `--with-services`, `--with-apt-packages`).\n * When ALL of these are empty/undefined → documented mode (every\n * catalog component commented out). When any is set → composed mode.\n *\n * - `languages`: curated runtime names, optional `:version`\n * (`java:17`). Validated against the language catalog.\n * - `features`: curated short names (`claude`, `atlassian/twg`) OR\n * full OCI refs (`ghcr.io/foo/bar:1`).\n * - `services`: curated names (`postgres`) → expanded block, OR any\n * image (`rustfs/rustfs:latest`) → name+image + commented scaffold.\n * - `aptPackages`: arbitrary apt package names (no catalog).\n */\n languages?: string[];\n features?: string[];\n services?: string[];\n aptPackages?: string[];\n /**\n * Git URLs to clone into `projects/` on the first apply. Each URL\n * lands at `projects/<URL-derived-leaf>/` (e.g.\n * `https://.../foo.git` → `projects/foo/`). For nested destination\n * paths (`projects/apps/web/`) use `monoceros add-repo --path=...`\n * post-init — the init flag intentionally keeps the syntax minimal.\n */\n withRepo?: string[];\n /**\n * Container-internal ports to pre-seed in `routing.ports`. First\n * entry doubles as the bare `<name>.localhost` default route in\n * Traefik. Equivalent to running `monoceros add-port` after init.\n * Each must be an integer in [1, 65535]; invalid values raise a\n * usage error before the yml is written.\n */\n withPorts?: number[];\n /** Override of the CLI-bundle root that holds `templates/components/`. */\n workbenchRoot?: string;\n /** Override of the user-data home that owns `container-configs/`. */\n monocerosHome?: string;\n logger?: {\n success: (msg: string) => void;\n info: (msg: string) => void;\n };\n}\n\nexport interface RunInitResult {\n configPath: string;\n /** True when the documented-default mode was used. */\n documented: boolean;\n}\n\nexport async function runInit(opts: RunInitOptions): Promise<RunInitResult> {\n const workbench = opts.workbenchRoot ?? defaultWorkbenchRoot();\n const home = opts.monocerosHome ?? defaultMonocerosHome();\n const logger = opts.logger ?? {\n success: (msg) => consola.success(msg),\n info: (msg) => consola.info(msg),\n };\n\n if (!REGEX.solutionName.test(opts.name)) {\n throw new Error(\n `Invalid config name: ${JSON.stringify(opts.name)}. Use letters, digits, '.', '_' or '-'.`,\n );\n }\n\n const dest = containerConfigPath(opts.name, home);\n if (existsSync(dest)) {\n throw new Error(\n `Config already exists: ${dest}. Delete it manually before re-running \\`monoceros init\\` — this protects any hand-edits.`,\n );\n }\n\n const catalog = await loadComponentCatalog(defaultComponentsDir(workbench));\n if (catalog.size === 0) {\n throw new Error(\n `No components found under ${defaultComponentsDir(workbench)}. The workbench checkout is incomplete.`,\n );\n }\n\n // Feature manifests live at the workbench-checkout root, not in\n // the CLI bundle. In tests the fixture sets `workbenchRoot` to a\n // dir that happens to hold both the templates *and* an\n // `images/features/` tree; honour that override. In real use we\n // fall back to `workbenchCheckoutRoot()` which returns null when\n // the CLI is run from an npm install — manifest lookups then\n // return undefined and init renders without optionHints.\n const checkoutRoot = opts.workbenchRoot ?? workbenchCheckoutRoot();\n const lookup = (ref: string) => loadFeatureManifestSummary(ref, checkoutRoot);\n\n // --with-repo URL validation: only canonical hosts. Non-canonical\n // hosts (self-hosted GitLab, Gitea, …) need `provider:` in the yml,\n // and init has no --provider flag, so the builder takes the\n // `monoceros init` + `monoceros add-repo … --provider=…` path\n // instead.\n // Dedupe input URLs (preserve insertion order) — same URL passed\n // twice from the CLI is a no-op, matching how `monoceros add-repo`\n // treats the second-add case.\n const reposRaw = (opts.withRepo ?? [])\n .map((u) => u.trim())\n .filter((u) => u.length > 0);\n const repos: string[] = [];\n const seenRepoUrls = new Set<string>();\n for (const url of reposRaw) {\n if (seenRepoUrls.has(url)) continue;\n seenRepoUrls.add(url);\n repos.push(url);\n }\n if (repos.length > 0) {\n const offending: string[] = [];\n for (const url of repos) {\n let host: string | undefined;\n try {\n host = url.startsWith('https://') ? new URL(url).hostname : undefined;\n } catch {\n host = undefined;\n }\n if (!host || !KNOWN_PROVIDER_HOSTS[host.toLowerCase()]) {\n offending.push(url);\n }\n }\n if (offending.length > 0) {\n throw new Error(\n [\n `--with-repo only supports github.com / gitlab.com / bitbucket.org URLs.`,\n `These are not canonical: ${offending.join(', ')}`,\n `For other hosts, run \\`monoceros init <name>\\` first, then`,\n `\\`monoceros add-repo <name> <url> --provider=github|gitlab|bitbucket\\`.`,\n ].join('\\n'),\n );\n }\n }\n\n // --with-ports validation: integer 1..65535, dedupe preserving\n // insertion order (first entry = the default route — collapsing two\n // mentions of 3000 to a single entry keeps that semantics\n // unambiguous).\n const portsRaw = opts.withPorts ?? [];\n const ports: number[] = [];\n const seenPorts = new Set<number>();\n for (const raw of portsRaw) {\n if (!Number.isInteger(raw) || raw < 1 || raw > 65535) {\n throw new Error(\n `Invalid port in --with-ports: ${JSON.stringify(raw)}. Expected integers between 1 and 65535.`,\n );\n }\n if (seenPorts.has(raw)) continue;\n seenPorts.add(raw);\n ports.push(raw);\n }\n\n // Identity is NOT resolved at init. When repos are present, the\n // generators render a container-level `git.user` with `${VAR}`\n // placeholders and we seed the matching blank keys into `<name>.env`\n // (below). Identity then resolves at apply time from that env file,\n // falling through the cascade (monoceros-config defaults → host →\n // prompt) when the keys are left blank — no init-time prompt.\n\n // Both generators take the URL + port lists directly — no AST\n // round-trip after the fact. That lets each generator decide how\n // to render the routing/repos block (commented hints in documented\n // mode, active entries in composed mode), keeping the \"all\n // available options visible\" rule consistent across sections.\n let text: string;\n const composed = resolveComposedInit(catalog, {\n languages: opts.languages ?? [],\n features: opts.features ?? [],\n services: opts.services ?? [],\n aptPackages: opts.aptPackages ?? [],\n });\n const anyComposed =\n composed.languages.length > 0 ||\n composed.features.length > 0 ||\n composed.services.length > 0 ||\n composed.aptPackages.length > 0;\n if (!anyComposed) {\n text = generateDocumentedYml(opts.name, catalog, lookup, repos, ports);\n } else {\n text = generateComposedYml(opts.name, composed, lookup, repos, ports);\n }\n\n await fs.mkdir(containerConfigsDir(home), { recursive: true });\n await ensureEnvGitignored(containerConfigsDir(home));\n await fs.writeFile(dest, text, 'utf8');\n\n // Scaffold the gitignored `<name>.env`: create it with the header\n // stub, then seed the `${VAR}` references the composed yml carries —\n // feature credential placeholders as blank `VAR=` keys (builder fills\n // them) and curated-service env vars with their dev-defaults\n // (`POSTGRES_USER=monoceros`, …; builder can keep or change them).\n // Upsert — never overwrites an existing env file's keys (e.g. one\n // from `restore`). Service defaults win over feature blanks on the\n // (unlikely) key collision.\n const envPath = containerEnvPath(opts.name, home);\n const seedVars: Record<string, string> = {};\n for (const f of composed.features) {\n for (const h of featureOptionHints(\n lookup(f.ref),\n f.ref,\n Object.keys(f.options ?? {}),\n )) {\n if (!(h.envVar in seedVars)) seedVars[h.envVar] = '';\n }\n }\n for (const svc of composed.services) {\n if (svc.kind === 'curated') {\n Object.assign(seedVars, curatedServiceEnvDefaults(svc.name));\n }\n }\n // When repos are present, the yml carries a container-level\n // `git.user: ${GIT_USER_NAME}/${GIT_USER_EMAIL}` — seed the matching\n // keys BLANK so the builder either fills them or leaves them empty\n // (→ apply climbs the identity cascade). Blank, not host-derived: the\n // builder asked for a shareable, env-managed identity.\n if (repos.length > 0) {\n seedVars[GIT_IDENTITY_VAR.name] = '';\n seedVars[GIT_IDENTITY_VAR.email] = '';\n }\n await ensureEnvVars(envPath, opts.name, seedVars);\n\n const documented = !anyComposed;\n // Paths relative to MONOCEROS_HOME keep the line readable (the dev\n // .local home is deep under the project root).\n const ymlRel = path.relative(home, dest);\n const envRel = path.relative(home, envPath);\n if (documented) {\n logger.success(`Wrote documented default to ${ymlRel} and ${envRel}.`);\n logger.info(\n `Un-comment what you need, then \\`monoceros apply ${opts.name}\\`.`,\n );\n } else {\n logger.success(`Composed into ${ymlRel} and ${envRel}.`);\n logger.info(\n `Edit the files if you need to tweak, then \\`monoceros apply ${opts.name}\\`.`,\n );\n }\n\n return { configPath: dest, documented };\n}\n\n// ───── Composed-mode input resolution ─────────────────────────────\n\n/**\n * Resolve the raw `--with-*` lists into the categorized, validated\n * shape the composed generator consumes. Curated vs. arbitrary handling\n * lives here:\n * - languages → validated against the language catalog (`:version` ok)\n * - features → catalog short name OR full OCI ref\n * - services → curated name (expanded) OR any image (scaffolded)\n * - aptPackages → arbitrary names (shape-checked only)\n */\nfunction resolveComposedInit(\n catalog: Map<string, Component>,\n raw: {\n languages: string[];\n features: string[];\n services: string[];\n aptPackages: string[];\n },\n): ComposedInit {\n return {\n languages: resolveInitLanguages(raw.languages),\n aptPackages: resolveInitAptPackages(raw.aptPackages),\n services: resolveInitServices(raw.services),\n features: resolveInitFeatures(catalog, raw.features),\n };\n}\n\nfunction resolveInitLanguages(entries: string[]): string[] {\n const known = new Set(knownLanguages());\n const out: string[] = [];\n const seen = new Set<string>();\n const unknown: string[] = [];\n for (const raw of entries) {\n const e = raw.trim();\n if (!e || seen.has(e)) continue;\n const spec = parseLanguageSpec(e);\n if (!spec || !known.has(spec.name)) {\n unknown.push(e);\n continue;\n }\n seen.add(e);\n out.push(e);\n }\n if (unknown.length > 0) {\n throw new Error(\n `Unknown language${unknown.length > 1 ? 's' : ''}: ${unknown.join(', ')}. ` +\n `Known: ${knownLanguages().join(', ')}.`,\n );\n }\n return out;\n}\n\nfunction resolveInitAptPackages(entries: string[]): string[] {\n const out: string[] = [];\n const seen = new Set<string>();\n const bad: string[] = [];\n for (const raw of entries) {\n const e = raw.trim();\n if (!e || seen.has(e)) continue;\n if (!REGEX.aptPackage.test(e)) {\n bad.push(e);\n continue;\n }\n seen.add(e);\n out.push(e);\n }\n if (bad.length > 0) {\n throw new Error(\n `Invalid apt package name${bad.length > 1 ? 's' : ''}: ${bad.join(', ')}. ` +\n `Expected lowercase alphanumeric plus '.+-'.`,\n );\n }\n return out;\n}\n\nfunction resolveInitServices(entries: string[]): InitService[] {\n const out: InitService[] = [];\n const byName = new Map<string, InitService>();\n for (const raw of entries) {\n const e = raw.trim();\n if (!e) continue;\n const svc: InitService = isCuratedService(e)\n ? { kind: 'curated', name: e }\n : { kind: 'custom', name: deriveServiceName(e), image: e };\n const existing = byName.get(svc.name);\n if (existing) {\n // Same entry twice → no-op; a genuine name clash → error.\n if (existing.kind === svc.kind && existing.image === svc.image) continue;\n throw new Error(\n `Two --with-services entries resolve to the service name '${svc.name}'. ` +\n `Add one after init with \\`monoceros add-service ${'<name>'} <image> --as=<other>\\`.`,\n );\n }\n byName.set(svc.name, svc);\n out.push(svc);\n }\n return out;\n}\n\nfunction resolveInitFeatures(\n catalog: Map<string, Component>,\n entries: string[],\n): Array<{ ref: string; options: Record<string, string | number | boolean> }> {\n const byRef = new Map<\n string,\n { ref: string; options: Record<string, string | number | boolean> }\n >();\n const unknown: string[] = [];\n for (const raw of entries) {\n const e = raw.trim();\n if (!e) continue;\n if (REGEX.featureRef.test(e)) {\n if (!byRef.has(e)) byRef.set(e, { ref: e, options: {} });\n continue;\n }\n const c = catalog.get(e);\n if (!c || c.file.category !== 'feature') {\n unknown.push(e);\n continue;\n }\n for (const f of c.file.contributes.features ?? []) {\n const existing = byRef.get(f.ref);\n if (!existing) {\n byRef.set(f.ref, { ref: f.ref, options: { ...(f.options ?? {}) } });\n } else {\n existing.options = mergeFeatureOptions(\n existing.options,\n f.options ?? {},\n );\n }\n }\n }\n if (unknown.length > 0) {\n const featureNames = [...catalog.values()]\n .filter((c) => c.file.category === 'feature')\n .map((c) => c.name)\n .sort();\n throw new Error(\n `Unknown feature${unknown.length > 1 ? 's' : ''}: ${unknown.join(', ')}.\\n` +\n `Use a catalog short name (${featureNames.join(', ')}) or a full OCI ref (ghcr.io/…/<name>:<tag>).`,\n );\n }\n return [...byRef.values()];\n}\n","import type { Component } from './components.js';\nimport type { FeatureManifestSummary } from './manifest.js';\nimport {\n buildFeatureHeaderLines,\n featureOptionHints,\n wrapToComment as sharedWrapToComment,\n} from './feature-doc.js';\nimport { expandCuratedService } from '../create/catalog.js';\nimport { renderCustomService, renderServiceObjectBody } from './service-doc.js';\nimport { GIT_IDENTITY_VAR } from '../config/env-file.js';\n\n/**\n * Renderer for the container yml that `monoceros init` produces.\n *\n * Style rules (the file the builder sees, not the code):\n *\n * - Every section carries a short user-facing header comment that\n * explains WHY the section exists, not how it's wired internally.\n * One to four lines, builder-vocabulary.\n *\n * - One `#` depth — never `# # foo`. Builder strips one `#` per line\n * of a commented block to activate it.\n *\n * - No trailing `# explanations` after a value. Per-feature option\n * text lives in the feature manifest and surfaces as a wrapped\n * header block above the matching `- ref:` line.\n *\n * Per-feature header text is pulled straight from the feature manifest\n * (`name`, `description`, `options.<key>.description`,\n * `documentationURL`, `x-monoceros.usageNotes`). The generator carries\n * no fallback prose — gaps in the manifest are visible gaps in the\n * generated yml, which is the right incentive.\n */\n\nexport type ManifestLookup = (\n ref: string,\n) => FeatureManifestSummary | undefined;\n\nconst SCHEMA_HEADER_ACTIVE =\n '# Solution-config — describes what should be inside your dev-container.\\n# Edit any section, then run `monoceros apply <name>` to (re-)build.';\nconst SCHEMA_HEADER_DOCUMENTED =\n '# Solution-config — describes what should be inside your dev-container.\\n# Every section is commented out by default; un-comment what you need\\n# (strip one `#` per line of the block), then run `monoceros apply <name>`.';\n\n// Soft target for wrapped comment lines. Keeps the rendered yml\n// readable in a standard editor without horizontal scrolling.\nconst COMMENT_WIDTH = 76;\n\n/**\n * Render the active-mode yml for the given components.\n */\n/** A service the builder asked for via `--with-services`. */\nexport interface InitService {\n /** `curated` → expand via SERVICE_CATALOG; `custom` → name + image + scaffold. */\n kind: 'curated' | 'custom';\n /** Compose service name (curated id, or derived from the image). */\n name: string;\n /** Image ref — only for `custom` services. */\n image?: string;\n}\n\n/** Resolved, categorized inputs for the composed-mode generator. */\nexport interface ComposedInit {\n languages: readonly string[];\n aptPackages: readonly string[];\n services: readonly InitService[];\n features: readonly RenderableFeature[];\n}\n\nexport function generateComposedYml(\n name: string,\n composed: ComposedInit,\n lookupManifest: ManifestLookup,\n repoUrls: readonly string[] = [],\n ports: readonly number[] = [],\n): string {\n const lines: string[] = [];\n pushHeader(lines, SCHEMA_HEADER_ACTIVE, name);\n lines.push('');\n lines.push('schemaVersion: 1');\n lines.push(`name: ${name}`);\n lines.push('');\n\n if (composed.languages.length > 0) {\n pushSectionHeader(lines, LANGUAGES_HEADER, /* commented */ false);\n lines.push('languages:');\n for (const lang of composed.languages) lines.push(` - ${lang}`);\n lines.push('');\n }\n if (composed.aptPackages.length > 0) {\n pushSectionHeader(lines, APT_PACKAGES_HEADER, /* commented */ false);\n lines.push('aptPackages:');\n for (const pkg of composed.aptPackages) lines.push(` - ${pkg}`);\n lines.push('');\n }\n if (composed.services.length > 0) {\n pushSectionHeader(lines, SERVICES_HEADER, /* commented */ false);\n lines.push('services:');\n for (const svc of composed.services) pushServiceEntry(lines, svc);\n lines.push('');\n }\n if (composed.features.length > 0) {\n pushSectionHeader(lines, FEATURES_HEADER_ACTIVE, /* commented */ false);\n lines.push('features:');\n for (const f of composed.features) {\n lines.push('');\n renderFeatureBlock(\n lines,\n f,\n lookupManifest(f.ref),\n /* commented */ false,\n );\n }\n lines.push('');\n }\n if (repoUrls.length > 0) {\n // Container-level identity first (placeholders + .env seed); repos\n // inherit it. The per-repo `git.user` below stays commented as the\n // override hint for the work-vs-personal case.\n pushGitIdentityBlock(lines);\n pushSectionHeader(lines, REPOS_HEADER, /* commented */ false);\n lines.push('repos:');\n for (const url of repoUrls) {\n lines.push(` - url: ${url}`);\n // Optional per-repo fields as commented hints (single-`#`).\n // Builder strips one `#` per line to set a path, declare a\n // provider, or override the container-level git.user for this\n // repo.\n lines.push(' # path:');\n lines.push(' # provider:');\n lines.push(' # git:');\n lines.push(' # user:');\n lines.push(' # name:');\n lines.push(' # email:');\n }\n lines.push('');\n }\n if (ports.length > 0) {\n pushSectionHeader(lines, routingHeader(name), /* commented */ false);\n lines.push('routing:');\n lines.push(' ports:');\n for (const port of ports) {\n lines.push(` - ${port}`);\n }\n lines.push(' # vscodeAutoForward: false');\n lines.push('');\n }\n\n return ensureTrailingNewline(lines.join('\\n'));\n}\n\n/**\n * Render the documented-default yml: every section commented out at\n * single-`#` depth, with a user-facing header above each section.\n */\nexport function generateDocumentedYml(\n name: string,\n catalog: Map<string, Component>,\n lookupManifest: ManifestLookup,\n repoUrls: readonly string[] = [],\n ports: readonly number[] = [],\n): string {\n const byCategory = groupByCategory(catalog);\n const lines: string[] = [];\n pushHeader(lines, SCHEMA_HEADER_DOCUMENTED, name);\n lines.push('');\n lines.push('schemaVersion: 1');\n lines.push(`name: ${name}`);\n lines.push('');\n\n if (byCategory.language.length > 0) {\n pushSectionHeader(lines, LANGUAGES_HEADER, /* commented */ true);\n lines.push('# languages:');\n for (const c of byCategory.language) {\n for (const lang of c.file.contributes.languages ?? []) {\n lines.push(`# - ${lang}`);\n }\n }\n lines.push('');\n }\n if (byCategory.service.length > 0) {\n pushSectionHeader(lines, SERVICES_HEADER, /* commented */ true);\n lines.push('# services:');\n for (const c of byCategory.service) {\n for (const svc of c.file.contributes.services ?? []) {\n const body = renderServiceObjectBody(expandCuratedService(svc));\n lines.push(`# - ${body[0]}`);\n for (const line of body.slice(1)) lines.push(`# ${line}`);\n }\n }\n lines.push('');\n }\n if (byCategory.feature.length > 0) {\n pushSectionHeader(lines, FEATURES_HEADER_DOCUMENTED, /* commented */ true);\n lines.push('# features:');\n\n const renderedRefs = new Set<string>();\n const topLevel = byCategory.feature.filter((c) => !c.name.includes('/'));\n for (const c of topLevel) {\n for (const f of c.file.contributes.features ?? []) {\n if (renderedRefs.has(f.ref)) continue;\n renderedRefs.add(f.ref);\n lines.push('#');\n renderFeatureBlock(\n lines,\n f,\n lookupManifest(f.ref),\n /* commented */ true,\n );\n }\n }\n for (const c of byCategory.feature) {\n if (!c.name.includes('/')) continue;\n for (const f of c.file.contributes.features ?? []) {\n if (renderedRefs.has(f.ref)) continue;\n renderedRefs.add(f.ref);\n lines.push('#');\n renderFeatureBlock(\n lines,\n f,\n lookupManifest(f.ref),\n /* commented */ true,\n );\n }\n }\n lines.push('');\n }\n\n if (repoUrls.length > 0) {\n pushGitIdentityBlock(lines);\n pushSectionHeader(lines, REPOS_HEADER, /* commented */ false);\n lines.push('repos:');\n for (const url of repoUrls) {\n lines.push(` - url: ${url}`);\n }\n lines.push('');\n } else {\n pushSectionHeader(lines, REPOS_HEADER, /* commented */ true);\n lines.push('# repos:');\n lines.push('# - url: https://github.com/<org>/<repo>.git');\n lines.push('# path: <folder>');\n lines.push('# provider: github');\n lines.push('# git:');\n lines.push('# user:');\n lines.push('# name: Your Name');\n lines.push('# email: you@example.com');\n lines.push('');\n }\n\n if (ports.length > 0) {\n pushSectionHeader(lines, routingHeader(name), /* commented */ false);\n lines.push('routing:');\n lines.push(' ports:');\n for (const port of ports) {\n lines.push(` - ${port}`);\n }\n lines.push(' # vscodeAutoForward: false');\n lines.push('');\n } else {\n pushSectionHeader(lines, routingHeader(name), /* commented */ true);\n lines.push('# routing:');\n lines.push('# ports:');\n lines.push('# - 3000');\n lines.push('# - 5173');\n lines.push('# vscodeAutoForward: false');\n lines.push('');\n }\n\n return ensureTrailingNewline(lines.join('\\n'));\n}\n\n// ───── Section header text ────────────────────────────────────────\n\nconst LANGUAGES_HEADER =\n 'Language runtimes installed inside the dev-container. Pick the ones your projects build against. The catalog of available runtimes is shown by `monoceros list-components`.';\n\nconst SERVICES_HEADER =\n 'Sibling containers that run alongside the dev-container (databases, caches, message queues, …). Each service is reachable from inside the dev-container by its name as hostname (e.g. `postgres://postgres:5432`). Activating any service switches the container to docker-compose mode automatically.';\n\nconst APT_PACKAGES_HEADER =\n 'Debian/Ubuntu apt packages installed in the dev-container at build time. No curated list — any apt package name works; an invalid name surfaces as an apt error during build.';\n\n// Render one composed-mode service entry as a `services:` sequence item.\n// Curated services expand to the full catalog block; custom images get\n// name + image active plus the commented field scaffold.\nfunction pushServiceEntry(out: string[], svc: InitService): void {\n if (svc.kind === 'custom') {\n const { bodyLines, comment } = renderCustomService(\n svc.name,\n svc.image ?? '',\n );\n out.push(` - ${bodyLines[0]}`);\n for (const line of bodyLines.slice(1)) out.push(` ${line}`);\n for (const cl of comment.split('\\n')) out.push(` #${cl}`);\n return;\n }\n const body = renderServiceObjectBody(expandCuratedService(svc.name));\n out.push(` - ${body[0]}`);\n for (const line of body.slice(1)) out.push(` ${line}`);\n}\n\nconst FEATURES_HEADER_ACTIVE =\n 'A Monoceros dev-container is shaped by features — pluggable units that drop tooling (AI assistants, language CLIs, cloud SDKs, …) into the container and bring their own options. The features active for this container are listed below; adjust their options as needed. Shared credentials used across containers belong in monoceros-config.yml under `defaults.features.<ref>` rather than here. Full catalog: `monoceros list-components`.';\n\nconst FEATURES_HEADER_DOCUMENTED =\n 'A Monoceros dev-container is shaped by features — pluggable units that drop tooling (AI assistants, language CLIs, cloud SDKs, …) into the container and bring their own options. Un-comment the blocks below for the features you want active. Shared credentials used across containers belong in monoceros-config.yml under `defaults.features.<ref>` rather than here. Full catalog: `monoceros list-components`.';\n\nconst REPOS_HEADER =\n 'Git repositories cloned into `projects/` on container start-up. HTTPS URLs only. The provider is auto-detected for github.com / gitlab.com / bitbucket.org; for any other host (self-hosted GitLab, Gitea, …) declare `provider:` explicitly. Add more later with `monoceros add-repo`.';\n\nconst GIT_IDENTITY_HEADER =\n 'Git committer identity for commits made inside the container. The ${VAR} values resolve from <name>.env at apply time — fill them there, or leave them blank to fall back to your global git config (or a one-time prompt). Override per repo under repos[].git.user.';\n\n// Top-level `git.user` block with `${VAR}` placeholders. Rendered\n// whenever the container has repos: the identity then lives in the\n// gitignored <name>.env (seeded blank by init/add-repo), keeping the\n// shareable yml free of personal data while staying obvious.\nfunction pushGitIdentityBlock(lines: string[]): void {\n pushSectionHeader(lines, GIT_IDENTITY_HEADER, /* commented */ false);\n lines.push('git:');\n lines.push(' user:');\n lines.push(` name: \\${${GIT_IDENTITY_VAR.name}}`);\n lines.push(` email: \\${${GIT_IDENTITY_VAR.email}}`);\n lines.push('');\n}\n\nfunction routingHeader(name: string): string {\n return `Container ports exposed to the host through Traefik. Reach them in your browser as ${name}-<port>.localhost (e.g. ${name}-3000.localhost). The first entry is the default route and is also reachable as the bare ${name}.localhost. Manage the list with \\`monoceros add-port\\`.`;\n}\n\n// ───── Per-feature rendering ──────────────────────────────────────\n\ninterface RenderableFeature {\n ref: string;\n options?: Record<string, string | number | boolean>;\n}\n\n/**\n * Render one feature entry. Header comment block (from manifest) +\n * the `- ref:` / `options:` yaml. Commented (`commented: true`) means\n * every line carries one `#` prefix — builder strips it to activate.\n *\n * Format (both modes), `<lp>` = line prefix (`# ` when commented, ``\n * when active), `<ip>` = inside-options-prefix (`# ` commented,\n * ` ` active):\n *\n * <lp># <feature name and prose, wrapped>\n * <lp># Options: opt1 (desc), opt2 (desc), …\n * <lp># See <documentationURL> for further information.\n * <lp> - ref: <ref>\n * <lp> options:\n * <lp> <opt>: <value>\n */\nfunction renderFeatureBlock(\n out: string[],\n feature: RenderableFeature,\n summary: FeatureManifestSummary | undefined,\n commented: boolean,\n): void {\n // Header lines are ALWAYS plain `#` comments at single depth —\n // never double `# #`. In documented mode the yaml lines below get\n // a `# ` prefix that the builder strips to activate; in active\n // mode they have no prefix. Either way the header comments stay\n // as-is, because they ARE the documentation that should live in\n // the file forever.\n const yamlPrefix = commented ? '# ' : '';\n\n // Header lines come from the shared feature-doc builder so\n // `add-feature`'s AST writer can emit the exact same prose block.\n for (const wl of buildFeatureHeaderLines(summary, COMMENT_WIDTH - 2)) {\n out.push(`# ${wl}`.trimEnd());\n }\n\n // The `- ref:` block. Indented two spaces under `features:`.\n out.push(`${yamlPrefix} - ref: ${feature.ref}`);\n\n const options = feature.options ?? {};\n const activeKeys = Object.entries(options);\n // Hint keys carry a `${VAR}` placeholder so the builder sees exactly\n // which env var to fill (and `init` / `add-feature` seed the same var\n // into <name>.env). Derivation is shared via featureOptionHints.\n const hints = featureOptionHints(summary, feature.ref, Object.keys(options));\n\n if (activeKeys.length === 0 && hints.length === 0) return;\n\n if (commented) {\n // Documented mode: the whole feature block is single-`#`\n // commented at outer depth. The options skeleton lives INSIDE\n // that outer comment — no extra inner `#`, otherwise we'd\n // re-introduce `# # foo` nesting that the builder rightly\n // objected to. After stripping one `#` per line, the active\n // form has the hint keys as bare-null values which the\n // transform treats as \"fall through to default\" (see\n // `solutionConfigToCreateOptions`).\n out.push(`${yamlPrefix} options:`);\n for (const [key, value] of activeKeys) {\n out.push(`${yamlPrefix} ${key}: ${renderScalarValue(value)}`);\n }\n for (const hint of hints) {\n out.push(`${yamlPrefix} ${hint.key}: ${hint.placeholder}`);\n }\n return;\n }\n\n // Composed mode: `- ref:` is active. Active option values (from\n // explicit --with-options, when that lands) go under an active\n // `options:` block. Remaining hints stay in a single-`#`\n // commented block under the ref — never active-empty, because\n // bare-null values attract yaml-lib's trailing-comment-stealing\n // on round-trip via the AST writers (apply / add-port / …) and\n // would silently override monoceros-config defaults with `\"\"`.\n if (activeKeys.length > 0) {\n out.push(` options:`);\n for (const [key, value] of activeKeys) {\n out.push(` ${key}: ${renderScalarValue(value)}`);\n }\n }\n if (hints.length > 0) {\n out.push(` # options:`);\n for (const hint of hints) {\n out.push(` # ${hint.key}: ${hint.placeholder}`);\n }\n }\n}\n\n// ───── Misc helpers ───────────────────────────────────────────────\n\nfunction pushHeader(out: string[], header: string, name: string): void {\n for (const line of header.replace(/<name>/g, name).split('\\n')) {\n out.push(line);\n }\n}\n\nfunction pushSectionHeader(\n out: string[],\n text: string,\n _commented: boolean,\n): void {\n // All section headers are themselves `#`-comments regardless of\n // whether the section body is commented or active. The `_commented`\n // flag is kept for symmetry with future per-mode wording but is\n // unused today — the body's commented-ness is encoded in the body\n // lines, not in the header.\n void _commented;\n const wrapped = sharedWrapToComment(text, COMMENT_WIDTH - 2);\n for (const wl of wrapped) {\n out.push(`# ${wl}`.trimEnd());\n }\n}\n\nfunction renderScalarValue(value: string | number | boolean): string {\n if (typeof value === 'string') {\n return /^[A-Za-z_][A-Za-z0-9._-]*$/.test(value)\n ? value\n : JSON.stringify(value);\n }\n return String(value);\n}\n\nfunction groupByCategory(catalog: Map<string, Component>): {\n language: Component[];\n service: Component[];\n feature: Component[];\n} {\n const out: ReturnType<typeof groupByCategory> = {\n language: [],\n service: [],\n feature: [],\n };\n const sorted = [...catalog.values()].sort((a, b) =>\n a.name.localeCompare(b.name),\n );\n for (const c of sorted) {\n out[c.file.category].push(c);\n }\n return out;\n}\n\nfunction ensureTrailingNewline(s: string): string {\n return s.endsWith('\\n') ? s : s + '\\n';\n}\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { loadComponentCatalog } from '../init/components.js';\nimport { colorsFor } from '../util/format.js';\n\n// Category-key → human-readable section label. Same order is used\n// for rendering — languages first (most common), services next,\n// features last.\nconst CATEGORY_LABELS = {\n language: 'Languages',\n service: 'Services',\n feature: 'Features',\n} as const;\nconst CATEGORY_ORDER: ReadonlyArray<keyof typeof CATEGORY_LABELS> = [\n 'language',\n 'service',\n 'feature',\n];\n\nexport const listComponentsCommand = defineCommand({\n meta: {\n name: 'list-components',\n group: 'discovery',\n description:\n 'Print the components catalog used by `monoceros init --with-languages=… / --with-services=… / --with-features=…`, grouped by category (Languages, Services, Features). Component names render in cyan, descriptions in default colour; when piped, the formatting drops out and lines become `name<TAB>description` for grep/awk-friendly consumption.',\n },\n args: {},\n async run() {\n try {\n const catalog = await loadComponentCatalog();\n if (catalog.size === 0) {\n consola.warn(\n 'No components found. The workbench checkout looks incomplete.',\n );\n process.exit(0);\n }\n\n const fmt = colorsFor(process.stdout);\n const isTty = process.stdout.isTTY ?? false;\n\n // Group entries by category for sectioned rendering.\n const byCategory = new Map<\n string,\n Array<{ name: string; desc: string }>\n >();\n for (const c of catalog.values()) {\n const list = byCategory.get(c.file.category) ?? [];\n list.push({ name: c.name, desc: c.file.displayName });\n byCategory.set(c.file.category, list);\n }\n for (const list of byCategory.values()) {\n list.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n // Piped (non-TTY) output: stay machine-friendly with the\n // historical `name<TAB>description` shape, one category at a\n // time. No ANSI, no alignment padding — grep/awk consumers\n // want predictable columns.\n if (!isTty) {\n let first = true;\n for (const cat of CATEGORY_ORDER) {\n const items = byCategory.get(cat);\n if (!items || items.length === 0) continue;\n if (!first) process.stdout.write('\\n');\n first = false;\n process.stdout.write(`# ${cat}\\n`);\n for (const { name, desc } of items) {\n process.stdout.write(`${name}\\t${desc}\\n`);\n }\n }\n process.exit(0);\n }\n\n // Interactive (TTY) output: section headers + aligned\n // columns, same visual vocabulary as the help renderer and\n // the apply/install structured output. Cyan name column\n // padded to the widest entry in its section so the\n // description column lines up.\n let first = true;\n for (const cat of CATEGORY_ORDER) {\n const items = byCategory.get(cat);\n if (!items || items.length === 0) continue;\n if (!first) process.stdout.write('\\n');\n first = false;\n process.stdout.write(`${fmt.sectionLine(CATEGORY_LABELS[cat])}\\n\\n`);\n const nameWidth = Math.max(...items.map((i) => i.name.length));\n const gutter = 2;\n for (const { name, desc } of items) {\n const pad = ' '.repeat(nameWidth - name.length + gutter);\n process.stdout.write(` ${fmt.cyan(name)}${pad}${desc}\\n`);\n }\n }\n process.exit(0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { containerDir } from '../config/paths.js';\nimport { runLogs } from '../devcontainer/compose.js';\nimport { dispatch } from './_dispatch.js';\n\nexport const logsCommand = defineCommand({\n meta: {\n name: 'logs',\n group: 'run',\n description:\n 'Tail logs from the compose services of the named dev-container. Pass --no-follow for a one-shot dump.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n service: {\n type: 'string',\n description:\n 'Restrict to a single compose service (e.g. postgres). Defaults to all.',\n },\n follow: {\n type: 'boolean',\n description:\n 'Follow log output (default: true). Use --no-follow to disable.',\n alias: ['f'],\n default: true,\n },\n },\n run({ args }) {\n return dispatch(() =>\n runLogs({\n root: containerDir(args.name),\n ...(typeof args.service === 'string' ? { service: args.service } : {}),\n follow: args.follow,\n }),\n );\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { proxyHostPort, readMonocerosConfig } from '../config/global.js';\nimport { readConfig } from '../config/io.js';\nimport { containerConfigPath } from '../config/paths.js';\nimport { portNumber } from '../config/schema.js';\nimport { proxyUrlsFor } from '../proxy/dynamic.js';\nimport { colorsFor } from '../util/format.js';\n\nexport interface RunPortListingOptions {\n name: string;\n /** Override the resolved MONOCEROS_HOME (tests inject a tmpdir). */\n monocerosHome?: string;\n /** Where the table is printed. Defaults to process.stdout. */\n out?: NodeJS.WriteStream;\n /** Where the \"no ports declared\" hint goes. Defaults to consola. */\n info?: (message: string) => void;\n}\n\n/**\n * Render the port-listing table for one container. Pure I/O — no\n * `process.exit`, returns the intended exit code so the CLI wrapper\n * can stay thin.\n *\n * 0 → printed the table or the \"no ports declared\" hint\n * 1 → unrecoverable failure (yml missing, parse error, …)\n */\nexport async function runPortListing(\n opts: RunPortListingOptions,\n): Promise<number> {\n const out = opts.out ?? process.stdout;\n const info = opts.info ?? ((m) => consola.info(m));\n\n const parsed = await readConfig(\n containerConfigPath(opts.name, opts.monocerosHome),\n );\n const portEntries = parsed.config.routing?.ports ?? [];\n if (portEntries.length === 0) {\n info(\n `No ports declared in ${opts.name}.yml. Run \\`monoceros add-port ${opts.name} -- <port>\\` to expose one.`,\n );\n return 0;\n }\n const ports = portEntries.map(portNumber);\n const globalConfig = await readMonocerosConfig({\n ...(opts.monocerosHome ? { monocerosHome: opts.monocerosHome } : {}),\n });\n const hostPort = proxyHostPort(globalConfig);\n const urls = proxyUrlsFor(opts.name, ports, hostPort);\n\n const isTty = out.isTTY ?? false;\n const fmt = colorsFor(out);\n\n // The first port doubles as the default `<name>.localhost` route.\n // Emit that as an explicit extra row so the builder sees both URLs\n // alongside the explicit port mapping for the first entry.\n const portSuffix = hostPort === 80 ? '' : `:${hostPort}`;\n const rows: Array<{ port: number; url: string; tag: string }> = [];\n rows.push({\n port: urls[0]!.port,\n url: `http://${opts.name}.localhost${portSuffix}`,\n tag: 'default',\n });\n for (const u of urls) {\n rows.push({ port: u.port, url: u.url, tag: '' });\n }\n\n if (!isTty) {\n for (const r of rows) {\n out.write(`${r.port}\\t${r.url}\\t${r.tag}\\n`);\n }\n return 0;\n }\n\n // TTY: aligned three-column table, port cyan, url default, tag dim.\n const portWidth = Math.max(...rows.map((r) => String(r.port).length));\n const urlWidth = Math.max(...rows.map((r) => r.url.length));\n const gutter = 2;\n for (const r of rows) {\n const portStr = String(r.port).padStart(portWidth);\n const urlPad = ' '.repeat(urlWidth - r.url.length + gutter);\n const tag = r.tag ? fmt.dim(`(${r.tag})`) : '';\n out.write(` ${fmt.cyan(portStr)} → ${r.url}${urlPad}${tag}\\n`);\n }\n return 0;\n}\n\nexport const portCommand = defineCommand({\n meta: {\n name: 'port',\n group: 'discovery',\n description:\n 'List the Traefik URLs for a container. Reads ports from `routing.ports` in the container yml and the host port from `routing.hostPort` in monoceros-config.yml (default 80). When piped, drops formatting and emits `port<TAB>url<TAB>tag` per line for grep/awk consumption.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n },\n async run({ args }) {\n try {\n const code = await runPortListing({ name: args.name });\n process.exit(code);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { getInnerArgs } from '../inner-args.js';\nimport { runRemoveAptPackages } from '../modify/index.js';\n\nexport const removeAptPackagesCommand = defineCommand({\n meta: {\n name: 'remove-apt-packages',\n group: 'edit',\n description:\n 'Remove apt packages from the container config. Pass package names after `--` (e.g. `monoceros remove-apt-packages sandbox -- make jq`). Idempotent, prints a diff before writing.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n const packages = [...getInnerArgs()];\n if (packages.length === 0) {\n consola.error(\n 'No package names given. Usage: `monoceros remove-apt-packages <containername> [--yes] -- <pkg> [<pkg> …]`.',\n );\n process.exit(1);\n }\n try {\n const result = await runRemoveAptPackages({\n name: args.name,\n packages,\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runRemoveFeature } from '../modify/index.js';\n\nexport const removeFeatureCommand = defineCommand({\n meta: {\n name: 'remove-feature',\n group: 'edit',\n description:\n 'Remove a devcontainer feature from the container config. Accepts either a Monoceros catalog short-name (e.g. `atlassian`, `claude`) or a full OCI ref. Idempotent, prints a diff before writing.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n ref: {\n type: 'positional',\n description:\n 'Feature to remove. Either a Monoceros catalog short-name (e.g. `atlassian`, `atlassian/twg`, `claude` — see `monoceros list-components`) or a full OCI feature ref (e.g. `ghcr.io/devcontainers/features/docker-in-docker:2`).',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n try {\n const result = await runRemoveFeature({\n name: args.name,\n ref: args.ref,\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { createInterface } from 'node:readline/promises';\nimport { runRemove } from '../remove/index.js';\n\nexport const removeCommand = defineCommand({\n meta: {\n name: 'remove',\n group: 'lifecycle',\n description:\n 'Wipe everything belonging to a container: stop and remove the docker objects, back up the container-configs yml + container directory (incl. home/, projects/, data/), then delete them from disk. Shared docker images stay. By default the destructive step is confirmed interactively; pass -y to skip.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n backup: {\n type: 'boolean',\n // citty turns a default-true boolean automatically into a\n // `--no-X` flag for negation, so the builder gets the natural\n // `monoceros remove <name> --no-backup` form without us\n // needing to special-case the parsing. Defining the arg as\n // `no-backup` directly conflicts with citty's prefix logic\n // and silently fails to bind, so we always go through the\n // positive form.\n description:\n 'Write a backup of <container-dir> and the yml under container-backups/ before deleting. Default on; use `--no-backup` to skip.',\n default: true,\n },\n yes: {\n type: 'boolean',\n alias: 'y',\n description:\n 'Skip the interactive confirmation prompt. Useful in scripts.',\n default: false,\n },\n },\n async run({ args }) {\n try {\n const noBackup = args.backup === false;\n const skipPrompt = args.yes === true;\n\n if (!skipPrompt) {\n const warning = noBackup\n ? `About to remove '${args.name}' WITHOUT a backup. Docker objects, container-configs entry, and container directory will all be deleted.`\n : `About to remove '${args.name}'. A backup will be written to container-backups/ first, then docker objects, container-configs entry, and container directory will all be deleted.`;\n consola.warn(warning);\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n const answer = await rl.question('Continue? [y/N] ');\n rl.close();\n if (!/^y(es)?$/i.test(answer.trim())) {\n consola.info('Aborted. Nothing changed.');\n process.exit(0);\n }\n }\n\n await runRemove({\n name: args.name,\n ...(noBackup ? { noBackup: true } : {}),\n });\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { existsSync, promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { consola } from 'consola';\nimport {\n containerConfigPath,\n containerDir,\n containerEnvPath,\n monocerosHome as defaultMonocerosHome,\n prettyPath,\n} from '../config/paths.js';\nimport { REGEX } from '../config/schema.js';\nimport {\n cleanupDockerObjects,\n composeProjectName,\n spawnDocker,\n type DockerExec,\n} from '../devcontainer/compose.js';\nimport { maybeStopProxy } from '../proxy/index.js';\nimport { removeDynamicConfig } from '../proxy/dynamic.js';\n\n/**\n * `monoceros remove <name>` — wipe everything belonging to one\n * container.\n *\n * What \"everything\" means in practice (in this order):\n *\n * 1. Stop and remove docker objects scoped to the container:\n * - compose containers (label `com.docker.compose.project=<project>`)\n * - any image-mode container matching `vsc-<name>-*`\n * - the project network `<project>_default`\n * Named docker volumes are no longer used as of fea2b3f (DB data\n * is bind-mounted onto `<container-dir>/data/<svc>/`), so they\n * go away with the directory delete below.\n *\n * 2. Optionally back up the host-side state:\n * - `container-configs/<name>.yml`\n * - `container/<name>/` (entire scaffold incl. `home/`,\n * `projects/`, `.monoceros/`, `data/`)\n * Lands at `container-backups/<name>-<timestamp>/`. Plain\n * directory copy — readable with normal filesystem tools.\n *\n * 3. Delete the host-side state.\n *\n * Shared docker images (`monoceros-runtime:dev`, feature build\n * images, postgres/mysql/redis base images) are NOT removed — they\n * are shared across containers and pruning them is a separate\n * operation the builder can do with `docker image prune` when they\n * actually want to free that disk.\n */\n\nexport interface RunRemoveOptions {\n name: string;\n /** When true, skip the backup step. */\n noBackup?: boolean;\n /** Override of the user-data home. Tests inject a tmpdir. */\n monocerosHome?: string;\n /** Override the timestamp embedded in the backup directory name. */\n now?: Date;\n /**\n * Docker exec for the cleanup pipeline (ps/rm/network/run). Tests\n * inject a stub. Replaces the previous `dockerSpawn: ComposeSpawn`\n * which drove a bash script — direct docker spawn dodges the\n * Windows quoting issues on backslash-bearing label values.\n */\n dockerExec?: DockerExec;\n /** Override the docker exec used by the Traefik proxy lifecycle. */\n proxyDocker?: DockerExec;\n logger?: {\n info: (msg: string) => void;\n success: (msg: string) => void;\n warn?: (msg: string) => void;\n };\n}\n\nexport interface RunRemoveResult {\n /** Path the yml was at before deletion, or `null` if it didn't exist. */\n configPath: string | null;\n /** Path the container scaffold was at before deletion, or `null`. */\n containerPath: string | null;\n /** Directory of the backup, or `null` when --no-backup was passed. */\n backupPath: string | null;\n /** Exit code of the docker cleanup step (0 on success). */\n dockerExitCode: number;\n}\n\nexport async function runRemove(\n opts: RunRemoveOptions,\n): Promise<RunRemoveResult> {\n const home = opts.monocerosHome ?? defaultMonocerosHome();\n const logger = opts.logger ?? {\n info: (msg) => consola.info(msg),\n success: (msg) => consola.success(msg),\n warn: (msg) => consola.warn(msg),\n };\n\n if (!REGEX.solutionName.test(opts.name)) {\n throw new Error(\n `Invalid config name: ${JSON.stringify(opts.name)}. Use letters, digits, '.', '_' or '-'.`,\n );\n }\n\n const ymlPath = containerConfigPath(opts.name, home);\n const envPath = containerEnvPath(opts.name, home);\n const containerPath = containerDir(opts.name, home);\n const hasYml = existsSync(ymlPath);\n const hasEnv = existsSync(envPath);\n const hasContainer = existsSync(containerPath);\n\n if (!hasYml && !hasContainer) {\n throw new Error(\n `Nothing to remove for '${opts.name}': neither ${ymlPath} nor ${containerPath} exists.`,\n );\n }\n\n // ── Step 1: stop + remove docker objects ────────────────────────\n // Four overlapping filters because devcontainer-cli ranges over\n // multiple naming/labeling schemes depending on container mode:\n // 1. compose-mode containers carry the compose project label\n // 2. image-mode + feature-build intermediates carry the\n // devcontainer.local_folder label — the most reliable anchor,\n // because @devcontainers/cli lets Docker assign random names\n // like 'kind_cerf' that neither name-prefix filter catches.\n // 3. container-name prefix as a fallback for half-broken state\n // 4. deterministic `vsc-<name>-` prefix from older\n // devcontainer-cli versions\n // All four are union'd, deduplicated, and `docker rm -f`-ed\n // together via cleanupDockerObjects() (direct Node spawn of docker,\n // no shell wrapper).\n const projectName = composeProjectName(containerPath);\n const dockerExec = opts.dockerExec ?? spawnDocker;\n const { exitCode: dockerExitCode } = await cleanupDockerObjects({\n projectName,\n filters: [\n `label=com.docker.compose.project=${projectName}`,\n `label=devcontainer.local_folder=${containerPath}`,\n `name=^${projectName}-`,\n `name=^vsc-${opts.name}-`,\n ],\n network: `${projectName}_default`,\n logTag: 'remove',\n logger,\n exec: dockerExec,\n });\n\n // ── Step 2: optional backup ────────────────────────────────────\n let backupPath: string | null = null;\n if (!opts.noBackup && (hasYml || hasContainer)) {\n const ts = (opts.now ?? new Date()).toISOString().replace(/[:.]/g, '-');\n backupPath = path.join(home, 'container-backups', `${opts.name}-${ts}`);\n await fs.mkdir(backupPath, { recursive: true });\n if (hasYml) {\n await fs.copyFile(ymlPath, path.join(backupPath, `${opts.name}.yml`));\n }\n // The per-container env file holds the values behind the yml's\n // `${VAR}` references (secrets). It must travel with the backup, or\n // a restore would bring back a yml that can't be applied.\n if (hasEnv) {\n await fs.copyFile(envPath, path.join(backupPath, `${opts.name}.env`));\n }\n if (hasContainer) {\n await fs.cp(containerPath, path.join(backupPath, 'container'), {\n recursive: true,\n });\n }\n logger.info(`Backup written to ${prettyPath(backupPath)}.`);\n }\n\n // ── Step 3: delete host-side state ─────────────────────────────\n if (hasYml) {\n await fs.rm(ymlPath, { force: true });\n }\n if (hasEnv) {\n await fs.rm(envPath, { force: true });\n }\n if (hasContainer) {\n try {\n await fs.rm(containerPath, { recursive: true, force: true });\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== 'EACCES' && code !== 'EPERM') {\n throw err;\n }\n // Linux + rootful Docker quirk: bind-mounted service data\n // dirs (postgres, mysql, …) end up owned by the container\n // process's UID — root for the official postgres image. The\n // unprivileged monoceros process can't unlink them.\n //\n // Fall back to docker as the cleanup actor: alpine runs as\n // root, mounts the target dir, deletes everything inside.\n // After that the host-side rm clears the now-empty parent.\n //\n // macOS / Docker Desktop / rootless Docker never hit this\n // branch — the happy fs.rm above succeeds because files are\n // user-owned through the VM / userns layer.\n logger.info(\n `[remove] host-side rm hit ${code} on ${prettyPath(containerPath)}; using a throw-away alpine container to clean root-owned files…`,\n );\n const { exitCode: exit } = await dockerExec([\n 'run',\n '--rm',\n '-v',\n `${containerPath}:/target`,\n 'alpine:3.21',\n 'find',\n '/target',\n '-mindepth',\n '1',\n '-delete',\n ]);\n if (exit !== 0) {\n throw new Error(\n `docker-based cleanup of ${containerPath} exited ${exit}. Inspect with \\`sudo ls -la ${containerPath}\\` and clean manually.`,\n );\n }\n await fs.rm(containerPath, { recursive: true, force: true });\n }\n }\n\n logger.success(\n `Removed '${opts.name}': docker objects gone, container-configs entry deleted, container directory deleted.`,\n );\n if (!backupPath) {\n logger.warn?.(\n 'No backup created (--no-backup). The host-side state is gone for good.',\n );\n }\n\n // Drop the container's Traefik dynamic-config file so a future\n // container with the same yml-name (re-init after remove) starts\n // with a clean slate. No-op when the file is absent.\n try {\n await removeDynamicConfig(opts.name, { monocerosHome: home });\n } catch (err) {\n logger.warn?.(\n `Could not remove Traefik dynamic config for ${opts.name}: ${err instanceof Error ? err.message : String(err)}. Ignored.`,\n );\n }\n\n // Tear down the Traefik singleton if this was the last container\n // attached to its network. See ADR 0007 (variant A — stop and\n // remove are treated identically).\n try {\n await maybeStopProxy({\n ...(opts.proxyDocker ? { docker: opts.proxyDocker } : {}),\n monocerosHome: home,\n logger: { info: (msg) => logger.info(msg), warn: logger.warn },\n });\n } catch (err) {\n logger.warn?.(\n `Could not tear down the Traefik proxy: ${err instanceof Error ? err.message : String(err)}. Ignored.`,\n );\n }\n\n return {\n configPath: hasYml ? ymlPath : null,\n containerPath: hasContainer ? containerPath : null,\n backupPath,\n dockerExitCode,\n };\n}\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runRestore } from '../restore/index.js';\n\nexport const restoreCommand = defineCommand({\n meta: {\n name: 'restore',\n group: 'lifecycle',\n description:\n \"Restore a container's host-side state from a backup written by `monoceros remove`. Copies the yml and the container directory back into $MONOCEROS_HOME. Refuses to overwrite an existing config or container — remove the in-place container first if you need to clobber. Run `monoceros apply <name>` afterwards to bring it back up.\",\n },\n args: {\n 'backup-path': {\n type: 'positional',\n description:\n 'Path to a backup directory (typically `<MONOCEROS_HOME>/container-backups/<name>-<timestamp>/`).',\n required: true,\n },\n },\n async run({ args }) {\n try {\n await runRestore({ backupPath: args['backup-path'] });\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { existsSync, promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { consola } from 'consola';\nimport {\n containerConfigPath,\n containerConfigsDir,\n containerDir,\n containerEnvPath,\n monocerosHome as defaultMonocerosHome,\n prettyPath,\n} from '../config/paths.js';\n\n/**\n * `monoceros restore <backup-path>` — re-instantiate the host-side\n * state of a previously-removed container from a backup written by\n * `monoceros remove`.\n *\n * Backup layout (produced by runRemove):\n *\n * <backup>/<name>.yml ← container-configs source\n * <backup>/container/ ← full container scaffold\n * (home/, projects/, data/, .monoceros/, …)\n *\n * Restore copies both back into `$MONOCEROS_HOME`:\n *\n * $MONOCEROS_HOME/container-configs/<name>.yml\n * $MONOCEROS_HOME/container/<name>/\n *\n * Refuses to clobber an existing config or container dir — the\n * builder must remove the in-place container first (or pick a\n * different target name).\n *\n * Restore does NOT recreate the docker objects: builder runs\n * `monoceros apply <name>` afterwards. That keeps restore a\n * pure filesystem operation, safe to dry-run, with no side-\n * effects on the docker daemon.\n */\n\nexport interface RunRestoreOptions {\n /** Path to a `<MONOCEROS_HOME>/container-backups/<name>-<ts>/` dir. */\n backupPath: string;\n /** Override of the user-data home. Tests inject a tmpdir. */\n monocerosHome?: string;\n logger?: {\n info: (msg: string) => void;\n success: (msg: string) => void;\n };\n}\n\nexport interface RunRestoreResult {\n /** Container name detected from the backup contents. */\n name: string;\n /** Where the yml was restored to. */\n configPath: string;\n /** Where the container directory was restored to (or `null` when\n * the backup didn't carry one — e.g. a remove that ran before any\n * apply had materialized the container dir). */\n containerPath: string | null;\n}\n\nexport async function runRestore(\n opts: RunRestoreOptions,\n): Promise<RunRestoreResult> {\n const home = opts.monocerosHome ?? defaultMonocerosHome();\n const logger = opts.logger ?? {\n info: (msg) => consola.info(msg),\n success: (msg) => consola.success(msg),\n };\n\n const backup = path.resolve(opts.backupPath);\n if (!existsSync(backup)) {\n throw new Error(`Backup not found: ${backup}.`);\n }\n const stat = await fs.stat(backup);\n if (!stat.isDirectory()) {\n throw new Error(`Backup path is not a directory: ${backup}.`);\n }\n\n // Detect the container name from the single `.yml` file in the\n // backup root. runRemove writes `<name>.yml`; we don't depend on\n // the backup-directory name (`<name>-<timestamp>`) because the\n // builder might have renamed/moved the backup folder.\n const entries = await fs.readdir(backup);\n const ymlFiles = entries.filter((f) => f.endsWith('.yml'));\n if (ymlFiles.length === 0) {\n throw new Error(\n `Backup at ${backup} doesn't contain a *.yml — expected a single config file at the root.`,\n );\n }\n if (ymlFiles.length > 1) {\n throw new Error(\n `Backup at ${backup} contains multiple .yml files (${ymlFiles.join(', ')}). Expected exactly one.`,\n );\n }\n const ymlFile = ymlFiles[0]!;\n const name = ymlFile.replace(/\\.yml$/, '');\n\n const containerInBackup = path.join(backup, 'container');\n const hasContainer = existsSync(containerInBackup);\n\n // The env file (values behind the yml's `${VAR}` references) is\n // restored alongside the yml when the backup carries one.\n const envInBackup = path.join(backup, `${name}.env`);\n const hasEnv = existsSync(envInBackup);\n\n // Refuse to overwrite live state.\n const destYml = containerConfigPath(name, home);\n const destContainer = containerDir(name, home);\n if (existsSync(destYml)) {\n throw new Error(\n `Refusing to restore: ${destYml} already exists. Remove the current container first (\\`monoceros remove ${name}\\`) or rename the existing config.`,\n );\n }\n if (hasContainer && existsSync(destContainer)) {\n throw new Error(\n `Refusing to restore: ${destContainer} already exists. Remove the current container first (\\`monoceros remove ${name}\\`).`,\n );\n }\n\n // Copy back into place.\n await fs.mkdir(containerConfigsDir(home), { recursive: true });\n await fs.copyFile(path.join(backup, ymlFile), destYml);\n if (hasEnv) {\n await fs.copyFile(envInBackup, containerEnvPath(name, home));\n }\n if (hasContainer) {\n await fs.cp(containerInBackup, destContainer, { recursive: true });\n }\n\n logger.success(`Restored '${name}' from ${prettyPath(backup)}.`);\n logger.info(\n `Run \\`monoceros apply ${name}\\` to bring the container back up.`,\n );\n\n return {\n name,\n configPath: destYml,\n containerPath: hasContainer ? destContainer : null,\n };\n}\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runRemoveFromUrl } from '../modify/index.js';\n\nexport const removeFromUrlCommand = defineCommand({\n meta: {\n name: 'remove-from-url',\n group: 'edit',\n description:\n 'Remove a previously-added install URL from the container config. Idempotent, prints a diff before writing. The URL is dropped from post-create.sh on the next `monoceros apply`.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n url: {\n type: 'positional',\n description: 'Install URL to remove (must match the original exactly).',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n try {\n const result = await runRemoveFromUrl({\n name: args.name,\n url: args.url,\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runRemoveLanguage } from '../modify/index.js';\n\nexport const removeLanguageCommand = defineCommand({\n meta: {\n name: 'remove-language',\n group: 'edit',\n description:\n 'Remove a language toolchain from the container config. Idempotent, prints a diff before writing.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n language: {\n type: 'positional',\n description: 'Language identifier (e.g. python, java, rust).',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n try {\n const result = await runRemoveLanguage({\n name: args.name,\n language: args.language,\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { getInnerArgs } from '../inner-args.js';\nimport { runRemovePort } from '../modify/index.js';\n\nexport const removePortCommand = defineCommand({\n meta: {\n name: 'remove-port',\n group: 'edit',\n description:\n 'Remove one or more ports from the container config. Pass port numbers after `--` (e.g. `monoceros remove-port sandbox -- 3000 5173`). Idempotent — ports not present are skipped silently.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n const tokens = [...getInnerArgs()];\n if (tokens.length === 0) {\n consola.error(\n 'No ports given. Usage: `monoceros remove-port <containername> [--yes] -- <port> [<port> …]`.',\n );\n process.exit(1);\n }\n try {\n const result = await runRemovePort({\n name: args.name,\n ports: tokens.map(coerceToken),\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n\nfunction coerceToken(raw: string): number {\n const n = Number(raw);\n return Number.isFinite(n) ? n : (raw as unknown as number);\n}\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runRemoveRepo } from '../modify/index.js';\n\nexport const removeRepoCommand = defineCommand({\n meta: {\n name: 'remove-repo',\n group: 'edit',\n description:\n 'Remove a repo from the container config (matches by URL or by its projects/<folder> name). Does NOT delete the existing projects/<folder> directory — local edits are preserved; clean it up manually.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n target: {\n type: 'positional',\n description: 'Repo URL or its projects/<folder> name. Either works.',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n try {\n const result = await runRemoveRepo({\n name: args.name,\n target: args.target,\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runRemoveService } from '../modify/index.js';\n\nexport const removeServiceCommand = defineCommand({\n meta: {\n name: 'remove-service',\n group: 'edit',\n description:\n 'Remove a compose service from the container config. Idempotent, prints a diff before writing. Note: data volumes (e.g. postgres-data) are NOT cleaned up automatically.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n service: {\n type: 'positional',\n description: 'Service identifier (e.g. postgres, redis).',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n try {\n const result = await runRemoveService({\n name: args.name,\n service: args.service,\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { containerDir } from '../config/paths.js';\nimport { runInContainer } from '../devcontainer/run.js';\nimport { getInnerArgs } from '../inner-args.js';\n\nexport const runCommand = defineCommand({\n meta: {\n name: 'run',\n group: 'run',\n description:\n 'Run a one-off command inside the named dev-container. Use `--` to separate monoceros flags from the inner command.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n },\n async run({ args }) {\n const command = [...getInnerArgs()];\n if (command.length === 0) {\n consola.error(\n 'No command provided. Usage: `monoceros run <containername> -- <cmd> [args…]`.',\n );\n process.exit(1);\n }\n try {\n const exitCode = await runInContainer({\n root: containerDir(args.name),\n command,\n });\n process.exit(exitCode);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport { spawnDevcontainer, type DevcontainerSpawn } from './cli.js';\n\nexport interface ShellLogger {\n info: (message: string) => void;\n}\n\nexport interface RunShellOptions {\n /** Container root: `<MONOCEROS_HOME>/container/<name>/`. */\n root: string;\n spawn?: DevcontainerSpawn;\n}\n\nexport async function runShell(opts: RunShellOptions): Promise<number> {\n assertContainerExists(opts.root);\n const spawnFn = opts.spawn ?? spawnDevcontainer;\n\n const upCode = await spawnFn(\n ['up', '--workspace-folder', opts.root, '--mount-workspace-git-root=false'],\n opts.root,\n { quiet: true },\n );\n if (upCode !== 0) return upCode;\n\n return spawnFn(\n [\n 'exec',\n '--workspace-folder',\n opts.root,\n '--mount-workspace-git-root=false',\n 'bash',\n ],\n opts.root,\n { interactive: true },\n );\n}\n\nexport function assertContainerExists(root: string): void {\n if (!existsSync(path.join(root, '.devcontainer'))) {\n throw new Error(\n `No .devcontainer/ at ${root}. Run \\`monoceros apply <name>\\` first.`,\n );\n }\n}\n","import { spawnDevcontainer, type DevcontainerSpawn } from './cli.js';\nimport { assertContainerExists } from './shell.js';\n\nexport interface RunInContainerOptions {\n /** Container root: `<MONOCEROS_HOME>/container/<name>/`. */\n root: string;\n command: string[];\n spawn?: DevcontainerSpawn;\n}\n\n// Run a one-off command inside the named container. Brings the\n// container up if needed (silently — only the inner command's stdio is\n// passed through), then forwards the command verbatim to\n// `devcontainer exec`. The inner command's exit code is propagated.\nexport async function runInContainer(\n opts: RunInContainerOptions,\n): Promise<number> {\n if (opts.command.length === 0) {\n throw new Error(\n 'No command provided. Usage: `monoceros run <containername> -- <cmd> [args…]`.',\n );\n }\n assertContainerExists(opts.root);\n const spawnFn = opts.spawn ?? spawnDevcontainer;\n\n const upCode = await spawnFn(\n ['up', '--workspace-folder', opts.root, '--mount-workspace-git-root=false'],\n opts.root,\n { quiet: true },\n );\n if (upCode !== 0) return upCode;\n\n return spawnFn(\n [\n 'exec',\n '--workspace-folder',\n opts.root,\n '--mount-workspace-git-root=false',\n ...opts.command,\n ],\n opts.root,\n { interactive: true },\n );\n}\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { containerDir } from '../config/paths.js';\nimport { runShell } from '../devcontainer/shell.js';\n\nexport const shellCommand = defineCommand({\n meta: {\n name: 'shell',\n group: 'run',\n description:\n 'Open an interactive bash session inside the named dev-container.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n },\n async run({ args }) {\n try {\n const exitCode = await runShell({ root: containerDir(args.name) });\n process.exit(exitCode);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { proxyHostPort, readMonocerosConfig } from '../config/global.js';\nimport { readConfig } from '../config/io.js';\nimport { containerConfigPath, containerDir } from '../config/paths.js';\nimport { runStart } from '../devcontainer/compose.js';\nimport { ensureProxy } from '../proxy/index.js';\nimport { preflightHostPort } from '../proxy/port-check.js';\nimport { dispatch } from './_dispatch.js';\n\nexport const startCommand = defineCommand({\n meta: {\n name: 'start',\n group: 'run',\n description:\n 'Bring the named dev-container up via `devcontainer up` (workspace + runServices, postCreate, features).',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n },\n run({ args }) {\n return dispatch(async () => {\n // Re-establish the Traefik singleton before bringing the\n // container up when the yml declares ports. The pre-flight\n // host-port check fails hard with an actionable hint if port\n // 80 (or the configured `routing.hostPort`) is held by\n // somebody else; ensureProxy itself is idempotent and safe to\n // call when the proxy is already up. See ADR 0007.\n let needsProxy = false;\n let hostPort = 80;\n try {\n const parsed = await readConfig(containerConfigPath(args.name));\n if ((parsed.config.routing?.ports ?? []).length > 0) {\n needsProxy = true;\n const global = await readMonocerosConfig();\n hostPort = proxyHostPort(global);\n }\n } catch (err) {\n consola.warn(\n `Could not read container yml ahead of start: ${err instanceof Error ? err.message : String(err)}. Skipping Traefik pre-flight.`,\n );\n }\n if (needsProxy) {\n await preflightHostPort(hostPort);\n await ensureProxy({ hostPort });\n }\n return runStart({ root: containerDir(args.name) });\n });\n },\n});\n","import { defineCommand } from 'citty';\nimport { containerDir } from '../config/paths.js';\nimport { runStatus } from '../devcontainer/compose.js';\nimport { dispatch } from './_dispatch.js';\n\nexport const statusCommand = defineCommand({\n meta: {\n name: 'status',\n group: 'run',\n description:\n 'Show whether the compose services for the named dev-container are running.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n service: {\n type: 'string',\n description:\n 'Restrict to a single compose service (e.g. postgres). Defaults to all.',\n },\n },\n run({ args }) {\n return dispatch(() =>\n runStatus({\n root: containerDir(args.name),\n ...(typeof args.service === 'string' ? { service: args.service } : {}),\n }),\n );\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { containerDir } from '../config/paths.js';\nimport { runStop } from '../devcontainer/compose.js';\nimport { maybeStopProxy } from '../proxy/index.js';\nimport { dispatch } from './_dispatch.js';\n\nexport const stopCommand = defineCommand({\n meta: {\n name: 'stop',\n group: 'run',\n description:\n 'Stop the compose services for the named dev-container. Volumes are preserved.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n service: {\n type: 'string',\n description:\n 'Restrict to a single compose service (e.g. postgres). Defaults to all.',\n },\n },\n run({ args }) {\n return dispatch(async () => {\n const exit = await runStop({\n root: containerDir(args.name),\n ...(typeof args.service === 'string' ? { service: args.service } : {}),\n });\n // Tear down the Traefik singleton if this was the last container\n // depending on it. Cheap idempotent call — no-ops when the proxy\n // network is already gone or other containers are still attached.\n // See ADR 0007 (variant A: stop and remove treated identically).\n try {\n await maybeStopProxy({\n logger: { info: (msg) => consola.info(msg) },\n });\n } catch (err) {\n consola.warn(\n `Could not tear down the Traefik proxy: ${err instanceof Error ? err.message : String(err)}. Ignored.`,\n );\n }\n return exit;\n });\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runTunnel } from '../tunnel/run.js';\n\nexport const tunnelCommand = defineCommand({\n meta: {\n name: 'tunnel',\n group: 'discovery',\n description:\n 'Open a TCP tunnel from the host to a service or port inside the container. Foreground process — Ctrl+C closes the tunnel. Pass a service name (e.g. `postgres`, `mysql`, `redis`) from the container yml or a bare in-container port number. See ADR 0009.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n target: {\n type: 'positional',\n description:\n 'Service name from the container yml (e.g. `postgres`), `service:port` for an explicit in-container port (e.g. `rustfs:9001`), or a bare in-container port number → workspace (e.g. `8080`).',\n required: true,\n },\n 'local-port': {\n type: 'string',\n description:\n 'Host port the tunnel listens on. Default: same as the internal port (e.g. postgres → 5432). Pass a different value when the default is busy.',\n },\n 'local-address': {\n type: 'string',\n description:\n 'Host interface the tunnel binds to. Default: 127.0.0.1 (loopback only — same machine). Pass 0.0.0.0 to expose on all interfaces (LAN, other devices on the same network).',\n },\n },\n async run({ args }) {\n try {\n const localPort = parseLocalPort(args['local-port']);\n const exitCode = await runTunnel({\n name: args.name,\n target: args.target,\n ...(localPort !== undefined ? { localPort } : {}),\n ...(args['local-address']\n ? { localAddress: args['local-address'] }\n : {}),\n });\n process.exit(exitCode);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n\nfunction parseLocalPort(raw: string | undefined): number | undefined {\n if (raw === undefined) return undefined;\n const n = Number(raw);\n if (!Number.isInteger(n) || n <= 0 || n >= 65536) {\n throw new Error(\n `Invalid --local-port '${raw}': must be an integer between 1 and 65535.`,\n );\n }\n return n;\n}\n","import { spawn, type ChildProcess } from 'node:child_process';\nimport { consola } from 'consola';\nimport {\n resolveTunnelTarget,\n type ResolveOptions,\n type ResolvedTarget,\n} from './resolve.js';\nimport { preflightLocalPort } from './port-check.js';\n\nexport const SOCAT_IMAGE = 'alpine/socat:1.8.0.3';\n\nexport interface RunTunnelOptions {\n name: string;\n target: string;\n localPort?: number;\n localAddress?: string;\n monocerosHome?: string;\n /** Override the docker spawn (tests inject a fake). */\n dockerSpawn?: DockerSpawn;\n /** Override target resolution (tests inject a fake). */\n resolve?: (opts: ResolveOptions) => Promise<ResolvedTarget>;\n /** Override local-port pre-flight (tests inject a fake). */\n preflight?: typeof preflightLocalPort;\n /** Override the SIGINT install — tests skip it so the suite isn't muted. */\n installSignalHandler?: (handler: () => void) => () => void;\n logger?: TunnelLogger;\n}\n\nexport interface TunnelLogger {\n info: (message: string) => void;\n warn?: (message: string) => void;\n}\n\n/**\n * Spawn `docker run alpine/socat …` in the foreground. Returns the\n * child's exit code (0 on Ctrl+C/clean shutdown, non-zero on error).\n *\n * Why a custom DockerSpawn instead of the proxy/DockerExec used\n * elsewhere: tunnel is a long-running foreground process, not a one-\n * shot inspect. We need stdio: 'inherit' so the user sees socat's\n * own log lines and Ctrl+C reaches docker via the terminal's process\n * group. The DockerExec shape buffers stdout/stderr — wrong shape\n * for this use.\n */\nexport type DockerSpawn = (args: string[]) => DockerSpawnHandle;\n\nexport interface DockerSpawnHandle {\n /** Resolves with the child's exit code after the process exits. */\n exited: Promise<number>;\n /** Forward a signal to the child. No-op if already exited. */\n kill: (signal: NodeJS.Signals) => void;\n}\n\nconst defaultDockerSpawn: DockerSpawn = (args) => {\n const child: ChildProcess = spawn('docker', args, {\n stdio: 'inherit',\n });\n const exited = new Promise<number>((resolve, reject) => {\n child.on('error', reject);\n child.on('exit', (code, signal) => {\n if (typeof code === 'number') resolve(code);\n else if (signal) resolve(128 + signalNumber(signal));\n else resolve(0);\n });\n });\n return {\n exited,\n kill: (signal) => {\n try {\n child.kill(signal);\n } catch {\n /* already exited */\n }\n },\n };\n};\n\nfunction signalNumber(signal: NodeJS.Signals): number {\n // Sufficient subset — anything else collapses to \"1\".\n switch (signal) {\n case 'SIGINT':\n return 2;\n case 'SIGTERM':\n return 15;\n default:\n return 1;\n }\n}\n\n/**\n * Default SIGINT install: swallow the first SIGINT so Node doesn't\n * tear down ahead of the docker child. The terminal's process-group\n * SIGINT still reaches docker run, which catches it and triggers\n * container teardown. We just wait for `exited`.\n */\nconst installSigintDefault = (handler: () => void): (() => void) => {\n process.on('SIGINT', handler);\n return () => process.off('SIGINT', handler);\n};\n\nconst DEFAULT_LOCAL_ADDRESS = '127.0.0.1';\n\nexport async function runTunnel(opts: RunTunnelOptions): Promise<number> {\n const log: TunnelLogger = opts.logger ?? {\n info: (m) => consola.info(m),\n warn: (m) => consola.warn(m),\n };\n\n const resolve = opts.resolve ?? resolveTunnelTarget;\n const resolveArgs: ResolveOptions = {\n name: opts.name,\n target: opts.target,\n ...(opts.monocerosHome !== undefined\n ? { monocerosHome: opts.monocerosHome }\n : {}),\n };\n const resolved = await resolve(resolveArgs);\n\n const localPort = opts.localPort ?? resolved.internalPort;\n const localAddress = opts.localAddress ?? DEFAULT_LOCAL_ADDRESS;\n validateLocalAddress(localAddress);\n\n const preflight = opts.preflight ?? preflightLocalPort;\n await preflight({ port: localPort, address: localAddress });\n\n const dockerSpawn = opts.dockerSpawn ?? defaultDockerSpawn;\n const installSignalHandler =\n opts.installSignalHandler ?? installSigintDefault;\n\n const dockerArgs = buildDockerArgs({\n localAddress,\n localPort,\n internalPort: resolved.internalPort,\n network: resolved.network,\n targetHost: resolved.targetHost,\n });\n\n log.info(\n `Tunnel: ${localAddress}:${localPort} → ${resolved.display}:${resolved.internalPort} (Ctrl+C to stop)`,\n );\n\n const handle = dockerSpawn(dockerArgs);\n const uninstall = installSignalHandler(() => {\n // Swallow — let docker run handle the signal via the terminal's\n // process group. We just wait for `exited`.\n });\n try {\n const exitCode = await handle.exited;\n // docker run reports 130 on SIGINT (128 + 2). Treat that as a\n // clean user-initiated stop, not an error.\n if (exitCode === 130) return 0;\n return exitCode;\n } finally {\n uninstall();\n }\n}\n\nexport interface BuildDockerArgsInput {\n localAddress: string;\n localPort: number;\n internalPort: number;\n network: string;\n targetHost: string;\n}\n\nexport function buildDockerArgs(input: BuildDockerArgsInput): string[] {\n return [\n 'run',\n '--rm',\n '-i',\n `--network=${input.network}`,\n '-p',\n `${input.localAddress}:${input.localPort}:${input.internalPort}`,\n SOCAT_IMAGE,\n `TCP-LISTEN:${input.internalPort},fork,reuseaddr`,\n `TCP:${input.targetHost}:${input.internalPort}`,\n ];\n}\n\nconst IPV4_RE = /^(\\d{1,3}\\.){3}\\d{1,3}$/;\n\nfunction validateLocalAddress(addr: string): void {\n // Two common forms we want to accept verbatim: dotted-quad IPv4\n // (including 127.0.0.1 and 0.0.0.0) and the docker-recognised\n // localhost alias. Anything else is rejected — IPv6 isn't supported\n // by the `-p` flag form we emit, and arbitrary hostnames here are\n // a foot-gun (docker won't resolve them for `-p` mappings).\n if (addr === 'localhost') return;\n if (IPV4_RE.test(addr)) {\n for (const part of addr.split('.')) {\n const n = Number(part);\n if (n < 0 || n > 255) {\n throw new Error(\n `Invalid --local-address '${addr}': each dotted-quad octet must be 0-255.`,\n );\n }\n }\n return;\n }\n throw new Error(\n `Invalid --local-address '${addr}'. Use 127.0.0.1 (default), 0.0.0.0, or a specific IPv4 address.`,\n );\n}\n","import { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport { containerConfigPath, containerDir } from '../config/paths.js';\nimport { readConfig } from '../config/io.js';\nimport { composeProjectName } from '../devcontainer/compose.js';\nimport { resolveService } from '../create/catalog.js';\nimport type { ResolvedService } from '../create/types.js';\nimport {\n defaultDockerExec,\n PROXY_NETWORK_NAME,\n type DockerExec,\n} from '../proxy/index.js';\nimport type { SolutionConfig } from '../config/schema.js';\n\n/**\n * Resolved tunnel target — exactly what `docker run --network=… …\n * TCP:<host>:<port>` needs. The caller (run.ts) doesn't have to know\n * about compose-vs-image-mode or how IP lookups work; that's all\n * compressed into these three fields.\n */\nexport interface ResolvedTarget {\n /** Docker network the socat sidecar will join. */\n network: string;\n /** DNS name or IP address the socat sidecar forwards to. */\n targetHost: string;\n /** In-container port the target service listens on. */\n internalPort: number;\n /** Pretty label for the startup banner (\"hello/postgres\" or \"hello:8080\"). */\n display: string;\n}\n\nexport interface ResolveOptions {\n /** Container name (yml in $MONOCEROS_HOME/container-configs/). */\n name: string;\n /** The raw second positional from the CLI — service name or port string. */\n target: string;\n /** Override the resolved MONOCEROS_HOME (tests inject a tmpdir). */\n monocerosHome?: string;\n /** Docker exec (tests inject a fake). */\n docker?: DockerExec;\n}\n\n/**\n * Resolve `monoceros tunnel <name> <target>` to a concrete docker\n * network + target host + internal port. Throws Error with an\n * actionable message on any unresolvable case (unknown service,\n * non-numeric port, container not running, …) — the caller surfaces\n * the message and exits 1.\n *\n * See ADR 0009 for the full topology table.\n */\nexport async function resolveTunnelTarget(\n opts: ResolveOptions,\n): Promise<ResolvedTarget> {\n const ymlPath = containerConfigPath(opts.name, opts.monocerosHome);\n if (!existsSync(ymlPath)) {\n throw new Error(\n `No yml profile for '${opts.name}' at ${ymlPath}. Run \\`monoceros init ${opts.name}\\` first.`,\n );\n }\n const parsed = await readConfig(ymlPath);\n const config = parsed.config;\n\n const containerRoot = containerDir(opts.name, opts.monocerosHome);\n if (!existsSync(containerRoot)) {\n throw new Error(\n `Container '${opts.name}' is not materialised at ${containerRoot}. Run \\`monoceros apply ${opts.name}\\` first.`,\n );\n }\n\n const composePath = path.join(containerRoot, '.devcontainer', 'compose.yaml');\n const isCompose = existsSync(composePath);\n\n const parsedTarget = parseTargetArg(opts.target, config);\n const docker = opts.docker ?? defaultDockerExec;\n\n if (isCompose) {\n return resolveCompose({\n name: opts.name,\n containerRoot,\n parsedTarget,\n });\n }\n return resolveImageMode({\n name: opts.name,\n containerRoot,\n parsedTarget,\n config,\n docker,\n });\n}\n\ninterface ParsedService {\n kind: 'service';\n service: string;\n port: number;\n}\n\ninterface ParsedPort {\n kind: 'port';\n port: number;\n}\n\ntype ParsedTarget = ParsedService | ParsedPort;\n\nfunction parseTargetArg(raw: string, config: SolutionConfig): ParsedTarget {\n // Explicit `<service>:<port>` — forward a configured service on a\n // specific in-container port. Lets you reach a second port (e.g. a\n // console UI on 9001) even if the service declares a different / no\n // `port:`. Service names never contain ':' (schema), so the colon\n // unambiguously splits name from port.\n const colon = raw.indexOf(':');\n if (colon > 0) {\n const name = raw.slice(0, colon);\n const port = Number(raw.slice(colon + 1));\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new Error(\n `Invalid target '${raw}'. Use <service>:<port> with a numeric port (1–65535), a bare port number, or a configured service name.`,\n );\n }\n findConfiguredService(config, name); // throws if not configured\n return { kind: 'service', service: name, port };\n }\n\n const asNumber = Number(raw);\n if (Number.isInteger(asNumber) && asNumber > 0 && asNumber < 65536) {\n return { kind: 'port', port: asNumber };\n }\n\n // Bare service name. The port comes from the resolved entry — an\n // object's `port:` or a curated service's catalog default.\n const match = findConfiguredService(config, raw);\n if (match.port === undefined) {\n throw new Error(\n `Service '${raw}' declares no port, so tunnel can't know what to forward. ` +\n `Add \\`port: <n>\\` to the service in the yml and re-apply, or pass one explicitly: ` +\n `\\`monoceros tunnel <name> ${raw}:<port>\\`.`,\n );\n }\n return { kind: 'service', service: raw, port: match.port };\n}\n\n/**\n * Resolve a service name against the container yml (curated string or\n * explicit object both count). Throws an actionable error listing the\n * configured services when the name isn't one of them.\n */\nfunction findConfiguredService(\n config: SolutionConfig,\n name: string,\n): ResolvedService {\n const services = config.services.map(resolveService);\n const match = services.find((s) => s.name === name);\n if (!match) {\n const names = services.map((s) => s.name);\n const list = names.length > 0 ? names.join(', ') : '(none configured)';\n throw new Error(\n `Service '${name}' is not configured in this container's yml. Configured services: ${list}. Or pass a port number (e.g. \\`monoceros tunnel <name> 8080\\`).`,\n );\n }\n return match;\n}\n\nfunction resolveCompose(args: {\n name: string;\n containerRoot: string;\n parsedTarget: ParsedTarget;\n}): ResolvedTarget {\n const network = `${composeProjectName(args.containerRoot)}_default`;\n if (args.parsedTarget.kind === 'service') {\n return {\n network,\n targetHost: args.parsedTarget.service,\n internalPort: args.parsedTarget.port,\n display: `${args.name}/${args.parsedTarget.service}:${args.parsedTarget.port}`,\n };\n }\n return {\n network,\n targetHost: 'workspace',\n internalPort: args.parsedTarget.port,\n display: `${args.name}:${args.parsedTarget.port}`,\n };\n}\n\nasync function resolveImageMode(args: {\n name: string;\n containerRoot: string;\n parsedTarget: ParsedTarget;\n config: SolutionConfig;\n docker: DockerExec;\n}): Promise<ResolvedTarget> {\n if (args.parsedTarget.kind === 'service') {\n // Services live in compose; if the container is image-mode, the\n // declared service is nonsense — refuse early instead of letting\n // socat hang on a name that nowhere resolves.\n throw new Error(\n `Service '${args.parsedTarget.service}' is declared in the yml but '${args.name}' is image-mode (no compose.yaml). Services need compose mode — re-apply with at least one \\`services:\\` entry to get a compose setup.`,\n );\n }\n\n // Image-mode + port: prefer monoceros-proxy network when the yml\n // declares routing.ports (the container then has a stable alias on\n // it). Fall back to the container's bridge IP otherwise.\n const ports = args.config.routing?.ports ?? [];\n if (ports.length > 0) {\n return {\n network: PROXY_NETWORK_NAME,\n targetHost: args.name,\n internalPort: args.parsedTarget.port,\n display: `${args.name}:${args.parsedTarget.port}`,\n };\n }\n\n const { network, ip } = await lookupContainerNetwork({\n containerRoot: args.containerRoot,\n docker: args.docker,\n });\n return {\n network,\n targetHost: ip,\n internalPort: args.parsedTarget.port,\n display: `${args.name}:${args.parsedTarget.port}`,\n };\n}\n\ninterface NetworkLookup {\n network: string;\n ip: string;\n}\n\n/**\n * For image-mode containers without `routing.ports`: find the\n * container's running ID by its `devcontainer.local_folder` label\n * (the same anchor `monoceros remove` uses), then pick the first\n * network with a usable IP and return both. Socat joins that network\n * and dials the IP directly — no DNS available on the default bridge.\n *\n * Restarts of the target invalidate the IP and break the tunnel; the\n * builder reruns `monoceros tunnel`. Acceptable for the ad-hoc use\n * case this resolver covers (see ADR 0009 — image-mode-without-ports\n * is the \"notlösung\" path).\n */\nasync function lookupContainerNetwork(args: {\n containerRoot: string;\n docker: DockerExec;\n}): Promise<NetworkLookup> {\n const psResult = await args.docker([\n 'ps',\n '-q',\n '--filter',\n `label=devcontainer.local_folder=${args.containerRoot}`,\n ]);\n if (psResult.exitCode !== 0) {\n throw new Error(\n `docker ps failed: ${psResult.stderr.trim() || `exit ${psResult.exitCode}`}`,\n );\n }\n const containerId = psResult.stdout.trim().split('\\n')[0]?.trim();\n if (!containerId) {\n throw new Error(\n `No running container for '${args.containerRoot}'. Start it with \\`monoceros start <name>\\` (or open a shell with \\`monoceros shell <name>\\`) and retry.`,\n );\n }\n const inspect = await args.docker([\n 'inspect',\n '--format',\n '{{json .NetworkSettings.Networks}}',\n containerId,\n ]);\n if (inspect.exitCode !== 0) {\n throw new Error(\n `docker inspect failed: ${inspect.stderr.trim() || `exit ${inspect.exitCode}`}`,\n );\n }\n let networks: Record<string, { IPAddress?: string }> | null = null;\n try {\n networks = JSON.parse(inspect.stdout) as Record<\n string,\n { IPAddress?: string }\n >;\n } catch {\n throw new Error(\n `Unexpected docker inspect output: ${inspect.stdout.slice(0, 200)}`,\n );\n }\n if (!networks) {\n throw new Error(\n `Container ${containerId} reports no networks. Restart it and retry.`,\n );\n }\n for (const [name, settings] of Object.entries(networks)) {\n if (settings.IPAddress && settings.IPAddress.length > 0) {\n return { network: name, ip: settings.IPAddress };\n }\n }\n throw new Error(\n `Container ${containerId} has no network with a reachable IP. Restart it and retry.`,\n );\n}\n","import { Socket } from 'node:net';\n\n/**\n * TCP-connect probe for the *local* host port the tunnel sidecar will\n * bind. Mirrors `proxy/port-check.ts` — same reasoning (bind probes\n * trip EACCES on privileged ports under unprivileged Node; connect\n * probes don't) — but stripped down: tunnels have no \"already-mine\"\n * skip case (every invocation is a fresh sidecar).\n *\n * Throws Error with an actionable message when something's listening\n * on `port`. The message names the port and points at `--local-port`\n * as the override.\n */\n\nexport type PortProbe = (\n port: number,\n address: string,\n) => Promise<PortProbeResult>;\n\nexport type PortProbeResult =\n | { ok: true }\n | { ok: false; code: string; message: string };\n\nconst CONNECT_TIMEOUT_MS = 750;\n\nconst realPortProbe: PortProbe = (port, address) => {\n // For 0.0.0.0 (any-interface) bindings, probing loopback is the\n // realistic conflict surface — anything LISTEN'ing on 0.0.0.0 or\n // 127.0.0.1 will collide with our `-p 0.0.0.0:<port>:…` mapping.\n const probeHost = address === '0.0.0.0' ? '127.0.0.1' : address;\n return new Promise((resolve) => {\n const socket = new Socket();\n let settled = false;\n const settle = (result: PortProbeResult) => {\n if (settled) return;\n settled = true;\n socket.destroy();\n resolve(result);\n };\n socket.setTimeout(CONNECT_TIMEOUT_MS);\n socket.once('connect', () => {\n settle({\n ok: false,\n code: 'EADDRINUSE',\n message: `another process is listening on ${port}`,\n });\n });\n socket.once('timeout', () => {\n settle({ ok: true });\n });\n socket.once('error', (err: NodeJS.ErrnoException) => {\n const code = err.code ?? 'UNKNOWN';\n if (code === 'ECONNREFUSED') {\n settle({ ok: true });\n } else {\n settle({ ok: false, code, message: err.message });\n }\n });\n socket.connect(port, probeHost);\n });\n};\n\nexport interface PreflightLocalPortOptions {\n port: number;\n address: string;\n /** Tests inject a stub probe. */\n probe?: PortProbe;\n}\n\nexport async function preflightLocalPort(\n opts: PreflightLocalPortOptions,\n): Promise<void> {\n const probe = opts.probe ?? realPortProbe;\n const result = await probe(opts.port, opts.address);\n if (result.ok) return;\n throw new Error(formatLocalPortHeldError(opts.port, opts.address, result));\n}\n\nfunction formatLocalPortHeldError(\n port: number,\n address: string,\n result: Extract<PortProbeResult, { ok: false }>,\n): string {\n const lines: string[] = [];\n if (result.code === 'EADDRINUSE') {\n lines.push(`Local port ${port} on ${address} is already in use.`);\n lines.push('');\n lines.push('Identify the holder, then either stop it or pick a different');\n lines.push('port for the tunnel:');\n lines.push('');\n lines.push(` sudo lsof -iTCP:${port} -sTCP:LISTEN -n -P`);\n lines.push(` # or: sudo ss -tlnp | grep \":${port}\\\\b\"`);\n lines.push('');\n lines.push('Re-run with an explicit local port:');\n lines.push(` monoceros tunnel … --local-port=${port + 1}`);\n } else {\n lines.push(\n `Cannot probe local port ${port} on ${address}: ${result.message}`,\n );\n lines.push('');\n lines.push(\n 'Most likely the host network stack (firewall, namespace) is interfering.',\n );\n lines.push('Try a different local port via `--local-port=<n>`.');\n }\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["entry","main","path","defineCommand","consola","fs","path","entry","existsSync","path","existsSync","readFileSync","path","path","existsSync","readFileSync","fs","path","ANSI_BOLD","ANSI_UNDERLINE","ANSI_CYAN","ANSI_GREY","ANSI_RESET","isTty","ANSI_RESET","ANSI_BOLD","ANSI_UNDERLINE","ANSI_CYAN","ANSI_GREY","bold","underline","cyan","cyan","path","fs","cyan","spawn","fs","z","parseDocument","z","fs","parseDocument","existsSync","fs","path","z","z","existsSync","fs","entry","path","spawn","fs","path","spawn","path","fs","fs","path","fs","path","entry","existsSync","readFileSync","fs","path","APT_PACKAGE_NAME_RE","FEATURE_REF_RE","INSTALL_URL_RE","REPO_URL_RE","REPO_PATH_RE","entry","path","existsSync","readFileSync","fs","isMap","Pair","parseDocument","Scalar","YAMLMap","isMap","parseDocument","YAMLMap","Scalar","Pair","entry","path","path","entry","containerName","fs","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","existsSync","fs","consola","fs","path","path","fs","entry","spawn","existsSync","path","consola","spawn","readFileSync","path","Transform","Transform","readFileSync","path","child","spawn","spawn","path","existsSync","consola","spawn","spawn","cyan","spawn","fs","path","consola","consola","existsSync","override","fs","cyan","entry","consola","defineCommand","defineCommand","defineCommand","existsSync","fs","path","path","existsSync","fs","defineCommand","defineCommand","consola","existsSync","fs","path","consola","consola","existsSync","fs","path","defineCommand","consola","defineCommand","consola","defineCommand","consola","isTty","first","defineCommand","defineCommand","defineCommand","consola","consola","isTty","defineCommand","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","existsSync","fs","path","consola","consola","existsSync","path","fs","defineCommand","consola","defineCommand","consola","existsSync","fs","path","consola","consola","path","existsSync","fs","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","coerceToken","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","existsSync","path","existsSync","path","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","defineCommand","defineCommand","consola","defineCommand","consola","defineCommand","consola","spawn","consola","existsSync","path","existsSync","path","resolveCompose","Socket","CONNECT_TIMEOUT_MS","realPortProbe","spawn","consola","resolveArgs","defineCommand","consola","defineCommand"]}
1
+ {"version":3,"sources":["../src/bin.ts","../src/devcontainer/docker-group-bootstrap.ts","../src/help.ts","../src/inner-args.ts","../src/main.ts","../src/commands/add-apt-packages.ts","../src/modify/index.ts","../src/config/io.ts","../src/config/schema.ts","../src/config/paths.ts","../src/config/env-file.ts","../src/init/feature-doc.ts","../src/init/manifest.ts","../src/util/ref.ts","../src/devcontainer/credentials.ts","../src/util/format.ts","../src/devcontainer/locate-running.ts","../src/config/global.ts","../src/init/components.ts","../src/proxy/index.ts","../src/proxy/dynamic.ts","../src/proxy/port-check.ts","../src/create/catalog.ts","../src/init/service-doc.ts","../src/create/scaffold.ts","../src/modify/yml.ts","../src/commands/add-feature.ts","../src/commands/add-from-url.ts","../src/commands/add-repo.ts","../src/commands/add-language.ts","../src/commands/add-port.ts","../src/commands/add-service.ts","../src/commands/apply.ts","../src/apply/index.ts","../src/config/state.ts","../src/config/transform.ts","../src/apply/apply-log.ts","../src/apply/apply-progress.ts","../src/apply/apply-summary.ts","../src/devcontainer/compose.ts","../src/util/mask-secrets.ts","../src/devcontainer/cli.ts","../src/devcontainer/runtime-pull-hint.ts","../src/devcontainer/docker-mode.ts","../src/devcontainer/identity.ts","../src/version.ts","../src/commands/_dispatch.ts","../src/commands/completion.ts","../src/commands/__complete.ts","../src/completion/resolve.ts","../src/commands/init.ts","../src/init/index.ts","../src/init/generator.ts","../src/commands/list-components.ts","../src/commands/logs.ts","../src/commands/port.ts","../src/commands/remove-apt-packages.ts","../src/commands/remove-feature.ts","../src/commands/remove.ts","../src/remove/index.ts","../src/commands/restore.ts","../src/restore/index.ts","../src/commands/remove-from-url.ts","../src/commands/remove-language.ts","../src/commands/remove-port.ts","../src/commands/remove-repo.ts","../src/commands/remove-service.ts","../src/commands/run.ts","../src/devcontainer/shell.ts","../src/devcontainer/run.ts","../src/commands/shell.ts","../src/commands/start.ts","../src/commands/status.ts","../src/commands/stop.ts","../src/commands/tunnel.ts","../src/tunnel/run.ts","../src/tunnel/resolve.ts","../src/tunnel/port-check.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { runMain } from 'citty';\nimport { bootstrapDockerGroup } from './devcontainer/docker-group-bootstrap.js';\nimport { maybeRenderHelp } from './help.js';\nimport { consumeInnerArgsFromProcessArgv } from './inner-args.js';\nimport { main } from './main.js';\n\n// On Linux: transparently re-exec under the docker group if the\n// current shell hasn't loaded it yet (typical after a fresh\n// `usermod -aG docker $USER` until the GNOME/KDE session is logged\n// out + back in). See docker-group-bootstrap.ts for the rationale.\n// No-op on macOS/Windows and when docker access already works.\n//\n// Runs BEFORE argv munging / help / runMain because if a re-exec\n// fires, we want the child process to receive the original argv\n// verbatim — let consumeInnerArgsFromProcessArgv / citty / help do\n// their work in the re-exec'd process, not in the about-to-die\n// parent.\nbootstrapDockerGroup();\n\n// Pull everything after `--` out of argv before citty starts parsing.\n// Otherwise citty's eager --help/--version handling shadows the inner\n// command (e.g. `monoceros run -- foo --help` would show monoceros run's\n// own help, not foo's).\nconsumeInnerArgsFromProcessArgv();\n\nasync function entry(): Promise<void> {\n // We render `--help` ourselves so the USAGE line shows positional\n // arguments *before* `[OPTIONS]`, matching the\n // `monoceros <command> <containername> [<args> …]` convention. Citty's\n // built-in renderer puts `[OPTIONS]` first which is the opposite. When\n // help was rendered, exit before handing off to citty so its own help\n // doesn't double up.\n if (await maybeRenderHelp(process.argv.slice(2), main)) {\n return;\n }\n await runMain(main);\n}\n\nentry().catch((err: unknown) => {\n // runMain handles its own errors; this catch is a safety net for the\n // help path.\n console.error(\n err instanceof Error ? (err.stack ?? err.message) : String(err),\n );\n process.exit(1);\n});\n","import { spawnSync } from 'node:child_process';\nimport { userInfo } from 'node:os';\n\n/**\n * Transparent recovery for the \"Linux + fresh usermod + same desktop\n * session\" trap.\n *\n * After `sudo usermod -aG docker $USER`, the user IS in /etc/group's\n * docker line — but the running desktop session loaded its group list\n * at GNOME/KDE login time and has no way to refresh. Every shell\n * spawned from that session inherits the stale list, so `docker info`\n * fails with \"permission denied while connecting to docker.sock\"\n * until the user either runs `newgrp docker` (per-shell), opens a\n * fresh login session (`su -l`, `ssh localhost`), or logs out of the\n * desktop entirely.\n *\n * That ceremony is real, well-known, and intentional (Linux process\n * credentials can't be live-updated for security reasons). But making\n * the *user* deal with it for every `monoceros …` invocation in a\n * fresh terminal tab is bad UX.\n *\n * This helper sidesteps it for monoceros's own process tree:\n *\n * 1. Probe `docker info`. If it works, no-op — we're good.\n * 2. If it fails AND the user is in /etc/group's docker line\n * (= `usermod` already ran successfully), re-exec ourselves\n * via `sg docker -c \"node …\"`. `sg` is the shadow-utils helper\n * that runs a single command under a different primary group;\n * it reads /etc/group fresh so the docker membership applies.\n * 3. If the user is NOT in /etc/group's docker line, return — the\n * caller will hit the docker failure on its own and surface a\n * \"run usermod\" error that's actually actionable.\n *\n * The re-exec is invisible to the user: bash sees a single\n * `monoceros …` command, history captures one line, ↑ arrow returns\n * the exact command. The sg sub-process lives only as long as\n * monoceros's own run, then exits.\n *\n * Linux-only. macOS / Windows Docker Desktop use different access\n * mechanisms (CLI-helper, named pipes) where group membership\n * doesn't apply — we early-return.\n */\n\nconst REEXEC_MARKER = 'MONOCEROS_DOCKER_GROUP_REEXEC';\n\n/**\n * Probes docker access and, if needed, transparently re-execs the\n * current node process under the docker group via `sg docker`. Never\n * returns if a re-exec fired (calls `process.exit` with the sg\n * sub-process's exit code).\n *\n * Returns when no recovery was needed or possible. The caller should\n * proceed with normal execution; downstream docker calls will either\n * work (recovery succeeded) or fail with the original permission\n * error (recovery wasn't applicable — typically usermod wasn't run).\n */\nexport function bootstrapDockerGroup(\n opts: {\n /** Override spawn behavior — tests inject deterministic responses. */\n runProbe?: (cmd: string, args: readonly string[]) => number;\n /** Tests inject the re-exec without actually fork+exec'ing. */\n reexec?: (argv: readonly string[]) => number;\n /** Override $USER detection. Tests inject a stable value. */\n username?: string;\n /** Override process.platform. Tests inject 'linux'. */\n platform?: NodeJS.Platform;\n /** Override env-marker check. Tests inject either undefined or '1'. */\n marker?: string;\n } = {},\n): void {\n const platform = opts.platform ?? process.platform;\n if (platform !== 'linux') return;\n\n const marker = opts.marker ?? process.env[REEXEC_MARKER];\n if (marker === '1') return; // already re-exec'd, don't loop\n\n const probe = opts.runProbe ?? defaultProbe;\n\n // `docker --version` doesn't touch the daemon — it's a client-only\n // call. If it fails, docker isn't installed at all. The downstream\n // error path will tell the user to install docker; we have nothing\n // to recover from.\n if (probe('docker', ['--version']) !== 0) return;\n\n // `docker info` does talk to the daemon. exit 0 = access works,\n // non-zero = either daemon down or permission denied. Either way\n // we have nothing to gain by trying sg — re-exec'ing under docker\n // group won't fix a stopped daemon.\n if (probe('docker', ['info']) === 0) return;\n\n // Daemon unreachable. Is this the \"usermod already ran\" case?\n const username = opts.username ?? userInfo().username;\n if (!isInDockerGroupViaEtcGroup(username, probe)) return;\n\n // Re-exec via sg. Use the exact argv that started us so the new\n // process is indistinguishable from the original invocation.\n const reexec = opts.reexec ?? defaultReexec;\n const exitCode = reexec(process.argv);\n process.exit(exitCode);\n}\n\n/**\n * Read /etc/group via getent and check whether `username` appears in\n * the docker line's member list. We use `getent` rather than parsing\n * /etc/group directly so NSS-backed group sources (LDAP, sssd, …)\n * are honored too. Returns false on any failure — the caller treats\n * \"we couldn't confirm membership\" the same as \"not a member\", which\n * is the safe default (no re-exec attempt → original error surfaces).\n */\nfunction isInDockerGroupViaEtcGroup(\n username: string,\n probe: (cmd: string, args: readonly string[]) => number,\n): boolean {\n // We need stdout, not just exit code, so the probe interface\n // doesn't suffice. Run getent directly.\n const result = spawnSync('getent', ['group', 'docker'], {\n encoding: 'utf8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n void probe; // signature parity with the rest of the helper\n if (result.status !== 0) return false;\n // getent output: `docker:x:984:user1,user2,parallels`\n // We want the comma-separated member list after the third colon.\n const fields = result.stdout.split(':');\n if (fields.length < 4) return false;\n const members = (fields[3] ?? '')\n .trim()\n .split(',')\n .map((m) => m.trim())\n .filter(Boolean);\n return members.includes(username);\n}\n\nfunction defaultProbe(cmd: string, args: readonly string[]): number {\n const result = spawnSync(cmd, [...args], { stdio: 'ignore' });\n return result.status ?? 1;\n}\n\nfunction defaultReexec(argv: readonly string[]): number {\n // argv[0] is the node binary, argv[1] is our bin.ts entry, argv[2+]\n // are the user-supplied flags/positionals. `sg docker -c \"…\"` needs\n // a single shell-quoted command string.\n const quoted = argv.map(shellQuote).join(' ');\n const env = { ...process.env, [REEXEC_MARKER]: '1' };\n const result = spawnSync('sg', ['docker', '-c', quoted], {\n stdio: 'inherit',\n env,\n });\n return result.status ?? 1;\n}\n\n/**\n * Single-quote-wrap an argv element so it survives `sg docker -c \"…\"`.\n * The wrapping pattern `'a'\\''b'` is the standard \"literal single\n * quotes inside a single-quoted string\" trick — close the quote,\n * escape a literal quote, reopen.\n */\nfunction shellQuote(arg: string): string {\n // Safe characters that don't need quoting at all.\n if (/^[\\w./@:=,+-]+$/.test(arg)) return arg;\n return `'${arg.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n// Exported for tests.\nexport const _internals = { isInDockerGroupViaEtcGroup, shellQuote };\n","import type { CommandDef } from 'citty';\n\n/**\n * Custom help renderer. Citty's built-in `renderUsage` has two\n * issues for Monoceros:\n *\n * 1. It puts `[OPTIONS]` *before* the positional arguments in the\n * USAGE line. We want positionals first, matching the\n * `monoceros <command> <containername> [<args> …]` shape\n * documented in konzept.md and docs/commands/README.md.\n * 2. It lists all subcommands in a flat pipe-separated USAGE line\n * (`monoceros init|shell|run|…`) and a flat COMMANDS block.\n * With 20+ commands that becomes unreadable.\n *\n * This module checks for `--help` / `-h` in argv before citty gets a\n * chance to print its own help. When triggered, it resolves the\n * matching subcommand and prints our own block: positional-first\n * USAGE, COMMANDS grouped by `meta.group`, and descriptions wrapped\n * to terminal width.\n */\n\ninterface ResolvedArg {\n name: string;\n type: 'positional' | 'string' | 'boolean' | 'number' | 'enum';\n required?: boolean;\n description?: string;\n default?: unknown;\n alias?: string | string[];\n valueHint?: string;\n}\n\nconst ANSI_BOLD = '\\x1b[1m';\nconst ANSI_UNDERLINE = '\\x1b[4m';\nconst ANSI_CYAN = '\\x1b[36m';\nconst ANSI_GREY = '\\x1b[90m';\nconst ANSI_RESET = '\\x1b[0m';\n\nfunction isTty(): boolean {\n return process.stdout.isTTY ?? false;\n}\n\nfunction color(text: string, ...codes: string[]): string {\n if (!isTty()) return text;\n return codes.join('') + text + ANSI_RESET;\n}\n\nconst bold = (s: string) => color(s, ANSI_BOLD);\nconst underline = (s: string) => color(s, ANSI_UNDERLINE);\nconst cyan = (s: string) => color(s, ANSI_CYAN);\nconst grey = (s: string) => color(s, ANSI_GREY);\n\n/**\n * Ordered list of command-group keys with a human-readable label.\n * Anything a command file tags via `meta.group` lands in the\n * matching bucket; anything ungrouped falls through to \"Other\".\n * The render order follows this array.\n */\nconst GROUPS: ReadonlyArray<{ key: string; label: string }> = [\n { key: 'lifecycle', label: 'Container lifecycle' },\n { key: 'run', label: 'Run + inspect' },\n { key: 'edit', label: 'Edit container yml' },\n { key: 'discovery', label: 'Discovery' },\n { key: 'tooling', label: 'Tooling' },\n];\n\nfunction resolveArgs(\n argsDef: Record<string, unknown> | undefined,\n): ResolvedArg[] {\n if (!argsDef) return [];\n const out: ResolvedArg[] = [];\n for (const [name, defRaw] of Object.entries(argsDef)) {\n const def = (defRaw ?? {}) as Partial<ResolvedArg>;\n out.push({\n name,\n type: (def.type as ResolvedArg['type']) ?? 'string',\n required: def.required,\n description: def.description,\n default: def.default,\n alias: def.alias,\n valueHint: def.valueHint,\n });\n }\n return out;\n}\n\nfunction renderValueHint(arg: ResolvedArg): string {\n if (arg.type === 'boolean') return '';\n const hint = arg.valueHint ?? arg.name;\n return `=<${hint}>`;\n}\n\nfunction renderArgDescription(arg: ResolvedArg, isRequired: boolean): string {\n const parts: string[] = [];\n if (arg.description) parts.push(arg.description);\n if (isRequired) parts.push(grey('(Required)'));\n if (arg.default !== undefined && arg.type !== 'boolean') {\n parts.push(grey(`(Default: ${JSON.stringify(arg.default)})`));\n }\n return parts.join(' ');\n}\n\n// Strip ANSI escape sequences so column-padding measurements use\n// the visible width instead of the raw character count.\n// eslint-disable-next-line no-control-regex\nconst ANSI_RE = /\\x1b\\[[0-9;]*m/g;\n\nfunction visibleLen(s: string): number {\n return s.replace(ANSI_RE, '').length;\n}\n\nfunction terminalWidth(): number {\n return process.stdout.columns && process.stdout.columns > 40\n ? process.stdout.columns\n : 100;\n}\n\n/**\n * Wrap `text` (which may contain ANSI codes) to fit `width` columns,\n * with `continuationIndent` prepended to every wrapped line after\n * the first. Word-aware: breaks at spaces, falls back to hard breaks\n * only for individual tokens longer than `width`.\n */\nfunction wrapText(\n text: string,\n width: number,\n continuationIndent: string,\n): string {\n if (visibleLen(text) <= width) return text;\n // `width` is the budget for the actual text on each line — it does\n // not include the continuation indent (caller already accounted for\n // it when computing width). Continuation-line indent gets prefixed\n // at join time, so every line gets the same text-column budget.\n const words = text.split(/(\\s+)/);\n const lines: string[] = [];\n let current = '';\n for (const w of words) {\n if (visibleLen(current) + visibleLen(w) <= width) {\n current += w;\n continue;\n }\n if (current.length > 0) lines.push(current.replace(/\\s+$/, ''));\n current = w.replace(/^\\s+/, '');\n }\n if (current.length > 0) lines.push(current.replace(/\\s+$/, ''));\n return lines.map((l, i) => (i === 0 ? l : continuationIndent + l)).join('\\n');\n}\n\n/**\n * Render a left-aligned label column next to wrapped descriptions.\n * Column gutter is four spaces. Description wraps within the\n * remaining terminal width.\n *\n * - `fixedLabelWidth`: use this label-column width instead of the\n * per-call maximum. Lets several tables (e.g. every COMMANDS\n * group) share one alignment column so descriptions line up\n * across sections, not just within one.\n * - `rowGap`: insert a blank line between rows so multi-line\n * descriptions don't run together.\n */\nfunction alignTable(\n rows: Array<[string, string]>,\n indent: string,\n opts: { fixedLabelWidth?: number; rowGap?: boolean } = {},\n): string {\n if (rows.length === 0) return '';\n const labelWidth =\n opts.fixedLabelWidth ?? Math.max(...rows.map((r) => visibleLen(r[0])));\n const gutter = ' ';\n const descWidth =\n terminalWidth() - indent.length - labelWidth - gutter.length;\n const continuationIndent = ' '.repeat(\n indent.length + labelWidth + gutter.length,\n );\n return rows\n .map(([left, right]) => {\n const pad = ' '.repeat(Math.max(0, labelWidth - visibleLen(left)));\n const wrapped = wrapText(right, descWidth, continuationIndent);\n return `${indent}${left}${pad}${gutter}${wrapped}`;\n })\n .join(opts.rowGap ? '\\n\\n' : '\\n');\n}\n\ninterface SubCommandEntry {\n name: string;\n description: string;\n group: string;\n}\n\nfunction collectSubCommands(cmd: CommandDef): SubCommandEntry[] {\n const subs = (cmd.subCommands ?? {}) as Record<string, CommandDef>;\n const out: SubCommandEntry[] = [];\n for (const [name, sub] of Object.entries(subs)) {\n const meta = (sub?.meta ?? {}) as {\n hidden?: boolean;\n description?: string;\n group?: string;\n };\n if (meta.hidden) continue;\n out.push({\n name,\n description: meta.description ?? '',\n group: meta.group ?? 'other',\n });\n }\n return out;\n}\n\nfunction renderCommandsBlock(entries: SubCommandEntry[]): string[] {\n if (entries.length === 0) return [];\n const lines: string[] = [];\n lines.push(underline(bold('COMMANDS')));\n\n // Group entries while preserving GROUPS' declared order. Anything\n // tagged with an unknown group (or no group) falls into \"Other\"\n // and renders last.\n const byGroup = new Map<string, SubCommandEntry[]>();\n for (const entry of entries) {\n const arr = byGroup.get(entry.group) ?? [];\n arr.push(entry);\n byGroup.set(entry.group, arr);\n }\n\n // One label-column width across ALL groups so every command's\n // description starts at the same column, not just within a group.\n const labelWidth = Math.max(...entries.map((e) => visibleLen(cyan(e.name))));\n\n const renderSection = (label: string, items: SubCommandEntry[]) => {\n if (items.length === 0) return;\n lines.push('');\n // Group label is left-aligned, underlined, in grey — distinct\n // from the section headers above (which are bold+underlined+white)\n // through colour + weight. Blank line after gives the items room\n // to breathe; cyan command labels do the visual separation from\n // the heading without needing an indent, so commands go flush\n // left and the descriptions get the full terminal width.\n lines.push(underline(grey(label)));\n lines.push('');\n const rows: Array<[string, string]> = items.map((e) => [\n cyan(e.name),\n e.description,\n ]);\n // Shared label width + a blank line between entries so the wrapped\n // multi-line descriptions stay readable.\n lines.push(\n alignTable(rows, '', { fixedLabelWidth: labelWidth, rowGap: true }),\n );\n };\n\n for (const { key, label } of GROUPS) {\n renderSection(label, byGroup.get(key) ?? []);\n byGroup.delete(key);\n }\n // Anything left over (ungrouped or unknown-group) lands in a\n // catch-all section so nothing silently disappears.\n for (const [groupKey, items] of byGroup) {\n const label = groupKey === 'other' ? 'Other' : groupKey;\n renderSection(label, items);\n }\n\n lines.push('');\n return lines;\n}\n\nexport function renderUsageBlock(\n cmd: CommandDef,\n commandPath: string[],\n): string {\n const meta = (cmd.meta ?? {}) as {\n name?: string;\n description?: string;\n version?: string;\n };\n const args = resolveArgs((cmd.args ?? {}) as Record<string, unknown>);\n const subCommandEntries = collectSubCommands(cmd);\n\n const fullName = commandPath.join(' ') || meta.name || 'monoceros';\n\n const positionals = args.filter((a) => a.type === 'positional');\n const flags = args.filter((a) => a.type !== 'positional');\n\n // USAGE line: positionals come first, then [OPTIONS]. When the\n // command has subcommands, render a single `<command>` placeholder\n // instead of a pipe-separated list — anything more than a couple\n // of subcommands makes the pipe list unreadable, and the COMMANDS\n // block below carries the actual menu.\n const usageTokens: string[] = [];\n for (const p of positionals) {\n const isRequired = p.required !== false && p.default === undefined;\n const t = p.name.toUpperCase();\n usageTokens.push(isRequired ? `<${t}>` : `[${t}]`);\n }\n if (subCommandEntries.length > 0) usageTokens.push('<command>');\n if (flags.length > 0) usageTokens.push('[OPTIONS]');\n\n const lines: string[] = [];\n const version = meta.version;\n const header = `${meta.description ?? ''} (${fullName}${version ? ` v${version}` : ''})`;\n lines.push(grey(wrapText(header, terminalWidth(), '')));\n lines.push('');\n lines.push(\n `${underline(bold('USAGE'))} ${cyan([fullName, ...usageTokens].join(' '))}`,\n );\n lines.push('');\n\n if (positionals.length > 0) {\n lines.push(underline(bold('ARGUMENTS')));\n lines.push('');\n const rows: Array<[string, string]> = positionals.map((p) => {\n const isRequired = p.required !== false && p.default === undefined;\n return [cyan(p.name.toUpperCase()), renderArgDescription(p, isRequired)];\n });\n lines.push(alignTable(rows, ' '));\n lines.push('');\n }\n\n if (flags.length > 0) {\n lines.push(underline(bold('OPTIONS')));\n lines.push('');\n const rows: Array<[string, string]> = flags.map((f) => {\n const isRequired = f.required === true && f.default === undefined;\n const aliases = (\n Array.isArray(f.alias) ? f.alias : f.alias ? [f.alias] : []\n ).map((a) => `-${a}`);\n const label = [...aliases, `--${f.name}`].join(', ') + renderValueHint(f);\n return [cyan(label), renderArgDescription(f, isRequired)];\n });\n lines.push(alignTable(rows, ' '));\n lines.push('');\n }\n\n if (subCommandEntries.length > 0) {\n for (const line of renderCommandsBlock(subCommandEntries)) {\n lines.push(line);\n }\n lines.push(\n `Use ${cyan(`${fullName} <command> --help`)} for more information about a command.`,\n );\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Detect `--help` / `-h` somewhere in argv that's *not* preceded by\n * a separator `--`. Returns the command path so the caller can render\n * the right subcommand's help.\n *\n * Returns `null` when help wasn't requested at all.\n */\nexport function detectHelpRequest(\n argv: string[],\n main: CommandDef,\n): { path: string[]; cmd: CommandDef } | null {\n const helpIdx = argv.findIndex((a) => a === '--help' || a === '-h');\n const separatorIdx = argv.indexOf('--');\n if (helpIdx === -1) return null;\n if (separatorIdx !== -1 && separatorIdx < helpIdx) return null;\n\n // Walk subcommands by matching argv tokens (in order, before --)\n // against the current command's `subCommands` map.\n const path: string[] = [];\n const tokens = argv.slice(\n 0,\n separatorIdx === -1 ? argv.length : separatorIdx,\n );\n let cursor: CommandDef = main;\n const mainName = ((main.meta ?? {}) as { name?: string }).name ?? 'monoceros';\n path.push(mainName);\n for (const tok of tokens) {\n if (tok.startsWith('-')) continue;\n const subs = (cursor.subCommands ?? {}) as Record<string, CommandDef>;\n if (tok in subs) {\n cursor = subs[tok]!;\n path.push(tok);\n continue;\n }\n // Token isn't a subcommand name — stop walking. Any further\n // tokens are positionals/values for the current command, not\n // routing hints.\n break;\n }\n return { path, cmd: cursor };\n}\n\n/**\n * If argv requests --help, print our own usage block and tell the\n * caller to exit. Returns true when help was rendered.\n */\nexport async function maybeRenderHelp(\n argv: string[],\n main: CommandDef,\n): Promise<boolean> {\n const hit = detectHelpRequest(argv, main);\n if (!hit) return false;\n // Resolve cmd's lazy fields (citty allows them to be functions)\n // before we render. We don't currently use lazy fields, so a\n // simple pass-through suffices.\n process.stdout.write(renderUsageBlock(hit.cmd, hit.path) + '\\n');\n return true;\n}\n","/**\n * Splits the user-args at the first `--` marker.\n *\n * `monoceros run -- monoceros-plugin --help` should hand `--help` to\n * `monoceros-plugin` inside the container, not trigger citty's eager\n * `--help` parser on the outer `monoceros run`. Citty parses `--help`\n * and `--version` before our subcommand handlers run, so the only\n * reliable fix is to strip everything after `--` from `process.argv`\n * before `runMain()` ever sees it.\n *\n * `splitInnerArgs` is the pure helper.\n * `consumeInnerArgsFromProcessArgv` is the side-effecting glue called\n * from `bin.ts`. `getInnerArgs()` is read by `runCommand`.\n */\n\nlet innerArgs: readonly string[] = [];\n\nexport function splitInnerArgs(userArgs: readonly string[]): {\n outerArgs: string[];\n innerArgs: string[];\n} {\n const dashIdx = userArgs.indexOf('--');\n if (dashIdx === -1) {\n return { outerArgs: [...userArgs], innerArgs: [] };\n }\n return {\n outerArgs: userArgs.slice(0, dashIdx),\n innerArgs: userArgs.slice(dashIdx + 1),\n };\n}\n\nexport function consumeInnerArgsFromProcessArgv(): void {\n // process.argv[0] = node, [1] = script path, [2..] = user args\n const userArgs = process.argv.slice(2);\n const split = splitInnerArgs(userArgs);\n process.argv = [...process.argv.slice(0, 2), ...split.outerArgs];\n innerArgs = split.innerArgs;\n}\n\nexport function getInnerArgs(): readonly string[] {\n return innerArgs;\n}\n\n/** Test seam: lets unit tests set inner args without touching process.argv. */\nexport function setInnerArgsForTesting(args: readonly string[]): void {\n innerArgs = args;\n}\n","import { defineCommand } from 'citty';\nimport { addAptPackagesCommand } from './commands/add-apt-packages.js';\nimport { addFeatureCommand } from './commands/add-feature.js';\nimport { addFromUrlCommand } from './commands/add-from-url.js';\nimport { addRepoCommand } from './commands/add-repo.js';\nimport { addLanguageCommand } from './commands/add-language.js';\nimport { addPortCommand } from './commands/add-port.js';\nimport { addServiceCommand } from './commands/add-service.js';\nimport { applyCommand } from './commands/apply.js';\nimport { completionCommand } from './commands/completion.js';\nimport { __completeCommand } from './commands/__complete.js';\nimport { initCommand } from './commands/init.js';\nimport { listComponentsCommand } from './commands/list-components.js';\nimport { logsCommand } from './commands/logs.js';\nimport { portCommand } from './commands/port.js';\nimport { removeAptPackagesCommand } from './commands/remove-apt-packages.js';\nimport { removeFeatureCommand } from './commands/remove-feature.js';\nimport { removeCommand } from './commands/remove.js';\nimport { restoreCommand } from './commands/restore.js';\nimport { removeFromUrlCommand } from './commands/remove-from-url.js';\nimport { removeLanguageCommand } from './commands/remove-language.js';\nimport { removePortCommand } from './commands/remove-port.js';\nimport { removeRepoCommand } from './commands/remove-repo.js';\nimport { removeServiceCommand } from './commands/remove-service.js';\nimport { runCommand } from './commands/run.js';\nimport { shellCommand } from './commands/shell.js';\nimport { startCommand } from './commands/start.js';\nimport { statusCommand } from './commands/status.js';\nimport { stopCommand } from './commands/stop.js';\nimport { tunnelCommand } from './commands/tunnel.js';\nimport { CLI_VERSION } from './version.js';\n\nexport const main = defineCommand({\n meta: {\n name: 'monoceros',\n version: CLI_VERSION,\n description:\n 'Monoceros workbench — local, sandboxed AI-coding environment for solution builders.',\n },\n subCommands: {\n init: initCommand,\n 'list-components': listComponentsCommand,\n shell: shellCommand,\n run: runCommand,\n logs: logsCommand,\n start: startCommand,\n stop: stopCommand,\n status: statusCommand,\n apply: applyCommand,\n remove: removeCommand,\n restore: restoreCommand,\n 'add-service': addServiceCommand,\n 'add-language': addLanguageCommand,\n 'add-apt-packages': addAptPackagesCommand,\n 'add-feature': addFeatureCommand,\n 'add-from-url': addFromUrlCommand,\n 'add-repo': addRepoCommand,\n 'add-port': addPortCommand,\n 'remove-service': removeServiceCommand,\n 'remove-language': removeLanguageCommand,\n 'remove-apt-packages': removeAptPackagesCommand,\n 'remove-feature': removeFeatureCommand,\n 'remove-from-url': removeFromUrlCommand,\n 'remove-repo': removeRepoCommand,\n 'remove-port': removePortCommand,\n port: portCommand,\n tunnel: tunnelCommand,\n completion: completionCommand,\n __complete: __completeCommand,\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { getInnerArgs } from '../inner-args.js';\nimport { runAddAptPackages } from '../modify/index.js';\n\nexport const addAptPackagesCommand = defineCommand({\n meta: {\n name: 'add-apt-packages',\n group: 'edit',\n description:\n 'Add Debian/Ubuntu apt packages to the container config. Pass package names after `--` (e.g. `monoceros add-apt-packages sandbox -- make openssh-client jq`). Idempotent. No curated whitelist — invalid names surface as apt errors at container build time.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n const packages = [...getInnerArgs()];\n if (packages.length === 0) {\n consola.error(\n 'No package names given. Usage: `monoceros add-apt-packages <containername> [--yes] -- <pkg> [<pkg> …]`.',\n );\n process.exit(1);\n }\n try {\n const result = await runAddAptPackages({\n name: args.name,\n packages,\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { promises as fs } from 'node:fs';\nimport { consola } from 'consola';\nimport { createPatch } from 'diff';\nimport path from 'node:path';\nimport type { Document } from 'yaml';\nimport { parseConfig, readConfig, stringifyConfig } from '../config/io.js';\nimport {\n containerConfigPath,\n containerConfigsDir,\n containerDir,\n containerEnvPath,\n monocerosHome as defaultMonocerosHome,\n} from '../config/paths.js';\nimport {\n ensureEnvGitignored,\n ensureEnvVars,\n hasVarPlaceholder,\n GIT_IDENTITY_VAR,\n} from '../config/env-file.js';\nimport { featureOptionHints } from '../init/feature-doc.js';\nimport { loadFeatureManifestSummary } from '../init/manifest.js';\nimport {\n collectGitCredentials,\n resolveProvider,\n type CredentialsSpawn,\n} from '../devcontainer/credentials.js';\nimport {\n findRunningContainerByLocalFolder,\n realContainerExec,\n type ContainerExec,\n type DockerLookupExec,\n} from '../devcontainer/locate-running.js';\nimport { proxyHostPort, readMonocerosConfig } from '../config/global.js';\nimport {\n KNOWN_PROVIDER_HOSTS,\n PROVIDER_VALUES,\n REGEX,\n isValidEmail,\n portNumber,\n type RepoProvider,\n} from '../config/schema.js';\nimport { loadComponentCatalog } from '../init/components.js';\nimport {\n ensureProxy,\n maybeStopProxy,\n type DockerExec as ProxyDockerExec,\n} from '../proxy/index.js';\nimport {\n proxyUrlsFor,\n removeDynamicConfig,\n writeDynamicConfig,\n} from '../proxy/dynamic.js';\nimport { preflightHostPort } from '../proxy/port-check.js';\nimport {\n BUILTIN_LANGUAGES,\n LANGUAGE_CATALOG,\n curatedServiceEnvDefaults,\n deriveServiceName,\n expandCuratedService,\n isCuratedService,\n knownLanguages,\n} from '../create/catalog.js';\nimport {\n renderServiceObjectBody,\n renderCustomService,\n customServiceHint,\n} from '../init/service-doc.js';\nimport { deriveRepoName } from '../create/scaffold.js';\nimport type { FeatureOptions, RepoEntry } from '../create/types.js';\nimport {\n addAptPackagesToDoc,\n addFeatureToDoc,\n addInstallUrlToDoc,\n addLanguageToDoc,\n addPortsToDoc,\n addRepoToDoc,\n addServiceEntryToDoc,\n ensureContainerGitUserPlaceholder,\n relocateLeakedSectionComments,\n removeAptPackagesFromDoc,\n removeFeatureFromDoc,\n removeInstallUrlFromDoc,\n removeLanguageFromDoc,\n removePortsFromDoc,\n removeRepoFromDoc,\n removeServiceFromDoc,\n setDefaultPortInDoc,\n} from './yml.js';\n\n/**\n * `monoceros add-*` / `monoceros remove-*` — edit the yml at\n * `<MONOCEROS_HOME>/container-configs/<name>.yml` for one container.\n *\n * No cwd magic. The first positional argument is always the container\n * name; the command looks up the yml via convention. Comment-preserving\n * AST mutation; the builder then runs `monoceros apply <name>` to\n * materialize.\n */\n\nexport interface ModifyLogger {\n info: (message: string) => void;\n success: (message: string) => void;\n warn: (message: string) => void;\n}\n\nexport type ConfirmFn = (prompt: string) => Promise<boolean>;\n\nexport interface ModifyOptions {\n /** Container name — resolves to `<home>/container-configs/<name>.yml`. */\n name: string;\n yes?: boolean;\n logger?: ModifyLogger;\n output?: (line: string) => void;\n confirm?: ConfirmFn;\n /** Override the resolved MONOCEROS_HOME. Tests inject a tmpdir. */\n monocerosHome?: string;\n}\n\nexport interface AddLanguageInput extends ModifyOptions {\n language: string;\n}\nexport interface AddServiceInput extends ModifyOptions {\n service: string;\n /**\n * Override the compose service name. Required to add the same image\n * more than once (two postgres servers → `--as postgres-app` /\n * `--as postgres-analytics`) and to disambiguate two custom images\n * that derive the same name.\n */\n as?: string;\n}\nexport interface AddAptPackagesInput extends ModifyOptions {\n packages: string[];\n}\nexport interface AddFeatureInput extends ModifyOptions {\n ref: string;\n options?: FeatureOptions;\n}\nexport interface AddFromUrlInput extends ModifyOptions {\n url: string;\n}\nexport interface AddRepoInput extends ModifyOptions {\n url: string;\n /**\n * Explicit destination path under `projects/`. Subfolders allowed\n * via `/` (e.g. `apps/web`). When omitted, the URL-derived single-\n * segment default is used (`https://.../foo.git` → `foo` →\n * `projects/foo/`).\n */\n path?: string;\n /**\n * Optional per-repo git committer identity override. Both name and\n * email must be set together; one alone is a usage error. Falls\n * back to the container-level `git.user` (which itself falls back\n * to the host's `git config --global`) when omitted.\n */\n gitName?: string;\n gitEmail?: string;\n /**\n * Git provider hint. Required when the URL host is not one of the\n * three canonical ones (github.com / gitlab.com / bitbucket.org);\n * optional otherwise. Validated against `PROVIDER_VALUES`.\n */\n provider?: string;\n /**\n * Test injection points for the on-the-fly-clone path (the part\n * that runs after the yml mutation when the container is up).\n */\n containerLookupDocker?: DockerLookupExec;\n containerExec?: ContainerExec;\n credentialsSpawn?: CredentialsSpawn;\n}\n\nexport interface RemoveLanguageInput extends ModifyOptions {\n language: string;\n}\nexport interface RemoveServiceInput extends ModifyOptions {\n service: string;\n}\nexport interface RemoveAptPackagesInput extends ModifyOptions {\n packages: string[];\n}\nexport interface RemoveFeatureInput extends ModifyOptions {\n ref: string;\n}\nexport interface RemoveFromUrlInput extends ModifyOptions {\n url: string;\n}\nexport interface RemoveRepoInput extends ModifyOptions {\n /** url or (effective) name — `monoceros remove-repo` accepts either. */\n target: string;\n}\n\nexport interface AddPortInput extends ModifyOptions {\n ports: number[];\n /**\n * When true, the (single) port in `ports` is moved to / inserted at\n * the front of `routing.ports` — making it the bare\n * `<name>.localhost` default route. Only valid with exactly one\n * port in the args; multiple ports + `asDefault` is a usage error.\n */\n asDefault?: boolean;\n /** Override the docker exec used by the Traefik proxy lifecycle. */\n proxyDocker?: ProxyDockerExec;\n}\nexport interface RemovePortInput extends ModifyOptions {\n ports: number[];\n /** Override the docker exec used by the Traefik proxy lifecycle. */\n proxyDocker?: ProxyDockerExec;\n}\n\nexport type ModifyResult =\n | { status: 'no-change' }\n | { status: 'updated'; changedPaths: string[] }\n | { status: 'aborted' };\n\ntype YmlMutator = (doc: Document) => boolean;\n\n// ─── add-* ────────────────────────────────────────────────────────\n\nexport function runAddLanguage(input: AddLanguageInput): Promise<ModifyResult> {\n if (\n !BUILTIN_LANGUAGES.has(input.language) &&\n !LANGUAGE_CATALOG[input.language]\n ) {\n throw new Error(\n `Unknown language: ${input.language}. Known: ${knownLanguages().join(', ')}.`,\n );\n }\n return mutate(input, (doc) => addLanguageToDoc(doc, input.language));\n}\n\nexport async function runAddService(\n input: AddServiceInput,\n): Promise<ModifyResult> {\n // Curated catalog name → expand to a full active object block.\n // Anything else → treat the argument as an image, derive the service\n // name from it, and drop in a commented scaffold for the fields\n // Monoceros can't know.\n const arg = input.service;\n const curated = isCuratedService(arg);\n\n // `--as` overrides the compose service name (default: the curated\n // name, or one derived from the image). Validate the override here so\n // the builder gets a focused message rather than a schema round-trip\n // error. Mirrors the schema's SERVICE_NAME_RE.\n if (input.as !== undefined && !/^[a-z0-9][a-z0-9_-]*$/.test(input.as)) {\n throw new Error(\n `Invalid --as name ${JSON.stringify(input.as)}. Use lowercase letters, digits, '_' or '-' (must start with a letter or digit).`,\n );\n }\n const name = input.as ?? (curated ? arg : deriveServiceName(arg));\n const image = curated ? expandCuratedService(arg).image : arg;\n // Render the block under the resolved name. For curated services the\n // expansion carries the catalog name, so override it before rendering.\n const custom = curated ? null : renderCustomService(name, arg);\n const bodyLines = curated\n ? renderServiceObjectBody({ ...expandCuratedService(arg), name })\n : custom!.bodyLines;\n const scaffoldComment = curated ? undefined : custom!.comment;\n\n const result = await mutate(input, (doc) => {\n const r = addServiceEntryToDoc(\n doc,\n name,\n image,\n bodyLines,\n scaffoldComment,\n );\n if (r.outcome === 'conflict') {\n throw new Error(\n `A service named '${name}' already exists with a different image (${r.existingImage}). ` +\n `Add it under a different name with \\`--as <name>\\`, or remove the existing one first ` +\n `(\\`monoceros remove-service ${input.name} ${name}\\`).`,\n );\n }\n return r.outcome === 'added';\n });\n\n // Curated service → seed its env dev-defaults into <name>.env (the\n // same ${KEY} placeholders the expanded block carries), mirroring\n // init and add-feature. Keys are image-dictated (POSTGRES_USER, …),\n // so --as renaming the service doesn't change them. Custom images\n // get the fill-in-the-scaffold hint instead — Monoceros can't know\n // their vars.\n if (result.status === 'updated') {\n if (curated) {\n const defaults = curatedServiceEnvDefaults(arg);\n if (Object.keys(defaults).length > 0) {\n const home = input.monocerosHome ?? defaultMonocerosHome();\n await ensureEnvGitignored(containerConfigsDir(home));\n const seeded = await ensureEnvVars(\n containerEnvPath(input.name, home),\n input.name,\n defaults,\n );\n if (seeded.added.length > 0) {\n (input.logger ?? defaultLogger()).info(\n `Seeded ${seeded.added.join(', ')} into ${input.name}.env (dev-defaults — change them there if needed).`,\n );\n }\n }\n } else {\n (input.logger ?? defaultLogger()).info(customServiceHint(name));\n }\n }\n return result;\n}\n\nexport function runAddAptPackages(\n input: AddAptPackagesInput,\n): Promise<ModifyResult> {\n if (input.packages.length === 0) {\n throw new Error(\n 'No package names given. Usage: monoceros add-apt-packages <containername> -- <pkg> [<pkg> …].',\n );\n }\n return mutate(input, (doc) => addAptPackagesToDoc(doc, input.packages));\n}\n\nexport async function runAddRepo(input: AddRepoInput): Promise<ModifyResult> {\n const url = input.url.trim();\n if (url.length === 0) {\n throw new Error(\n 'Missing repo URL. Usage: monoceros add-repo <containername> <url>.',\n );\n }\n const path = (input.path ?? deriveRepoName(url)).trim();\n // --git-name and --git-email come as a pair. Reject half-set input\n // loudly instead of silently dropping it.\n const hasName =\n typeof input.gitName === 'string' && input.gitName.trim().length > 0;\n const hasEmail =\n typeof input.gitEmail === 'string' && input.gitEmail.trim().length > 0;\n if (hasName !== hasEmail) {\n throw new Error(\n '--git-name and --git-email must be set together. Pass both, or neither.',\n );\n }\n // Validate the email eagerly at the flag entry — the schema defers\n // email format to apply (to allow `${VAR}` placeholders from the\n // hand-edited yml), so a typo'd literal would otherwise only surface\n // at apply. A `${VAR}` placeholder is allowed through here too, in\n // case the builder wants to manage the value in <name>.env.\n if (hasEmail) {\n const email = input.gitEmail!.trim();\n if (!isValidEmail(email) && !hasVarPlaceholder(email)) {\n throw new Error(\n `Invalid --git-email '${email}': must be a valid email or a \\${VAR} placeholder resolved from <name>.env.`,\n );\n }\n }\n // --provider validation:\n // - host is canonical (github.com / gitlab.com / bitbucket.org):\n // * no --provider → fine, auto-detected at apply time\n // * --provider matches canonical → accepted, written to yml\n // (harmless; round-trip stays clean)\n // * --provider contradicts canonical → reject loudly\n // - host is non-canonical:\n // * --provider given (valid enum) → write it\n // * --provider missing → reject; the apply pre-flight would\n // fail anyway, fail at add-repo time for a better signal\n // * --provider invalid value → reject with allowed list\n const explicitProvider = normalizeProvider(input.provider);\n let host: string | undefined;\n try {\n host = url.startsWith('https://') ? new URL(url).hostname : undefined;\n } catch {\n host = undefined;\n }\n const canonical = host ? KNOWN_PROVIDER_HOSTS[host.toLowerCase()] : undefined;\n if (host && !canonical && !explicitProvider) {\n throw new Error(\n `Host '${host}' is not a canonical Git provider Monoceros can auto-detect (github.com / gitlab.com / bitbucket.org). Pass --provider=github|gitlab|bitbucket so the credential-helper hints know which CLI to suggest.`,\n );\n }\n if (canonical && explicitProvider && explicitProvider !== canonical) {\n throw new Error(\n `--provider=${explicitProvider} contradicts host '${host}' (auto-detected as ${canonical}). Drop --provider for canonical hosts, or fix the value.`,\n );\n }\n // For canonical hosts we don't persist `provider:` in the yml even\n // when the flag was passed (matches what auto-detection would do\n // and keeps the yml minimal). Non-canonical hosts: write the\n // explicit value as-is.\n const providerToWrite =\n !canonical && explicitProvider ? explicitProvider : undefined;\n const entry: RepoEntry = {\n url,\n path,\n ...(hasName && hasEmail\n ? {\n gitUser: {\n name: input.gitName!.trim(),\n email: input.gitEmail!.trim(),\n },\n }\n : {}),\n ...(providerToWrite ? { provider: providerToWrite } : {}),\n };\n // When a NEW repo is added and the container has no `git.user` yet,\n // scaffold a container-level identity with `${VAR}` placeholders (and\n // seed the blank keys below) — same env-managed default `init`\n // produces. Rides along in the same diff. An existing `git.user`\n // (literal or placeholder) is left untouched.\n let gitUserScaffolded = false;\n const result = await mutate(input, (doc) => {\n const repoAdded = addRepoToDoc(doc, entry);\n if (repoAdded) gitUserScaffolded = ensureContainerGitUserPlaceholder(doc);\n return repoAdded;\n });\n if (result.status === 'updated' && gitUserScaffolded) {\n const home = input.monocerosHome ?? defaultMonocerosHome();\n await ensureEnvGitignored(containerConfigsDir(home));\n await ensureEnvVars(containerEnvPath(input.name, home), input.name, [\n GIT_IDENTITY_VAR.name,\n GIT_IDENTITY_VAR.email,\n ]);\n (input.logger ?? defaultLogger()).info(\n `Added a container git.user with \\${${GIT_IDENTITY_VAR.name}}/\\${${GIT_IDENTITY_VAR.email}} placeholders and seeded ${input.name}.env — fill them or leave blank to use your global git identity.`,\n );\n }\n // On-the-fly clone path: if the yml change took AND the container\n // is currently running, clone the repo directly into the\n // container so the builder doesn't have to `monoceros apply`\n // afterwards. Soft-fail with a warn — failures here never roll\n // back the yml write. See ADR 0007's add-port symmetry.\n if (result.status === 'updated') {\n await tryCloneInRunningContainer(input, entry);\n }\n return result;\n}\n\n/**\n * Best-effort: if the container is running, fetch HTTPS credentials\n * for the repo host, then `docker exec git clone …` directly into\n * `/workspaces/<name>/projects/<path>/`. Skips silently when:\n *\n * - the container isn't running (typical case — yml-only is fine,\n * `monoceros apply` will clone on next bring-up)\n * - the destination folder already exists (idempotent — matches\n * post-create.sh's \"skip clone if dir exists\" rule)\n *\n * Soft-fails with a warn on any error in the clone path. The yml\n * mutation is already persisted; the next `monoceros apply` will\n * retry. Tests inject the docker / credentials spawns; production\n * uses the real ones.\n */\nasync function tryCloneInRunningContainer(\n input: AddRepoInput,\n entry: RepoEntry,\n): Promise<void> {\n const home = input.monocerosHome ?? defaultMonocerosHome();\n const root = containerDir(input.name, home);\n const logger = input.logger ?? defaultLogger();\n\n let containerId: string | null;\n try {\n containerId = await findRunningContainerByLocalFolder(root, {\n ...(input.containerLookupDocker\n ? { docker: input.containerLookupDocker }\n : {}),\n });\n } catch (err) {\n logger.warn(\n `Could not check whether the container is running: ${err instanceof Error ? err.message : String(err)}. The yml is updated — run \\`monoceros apply ${input.name}\\` to clone.`,\n );\n return;\n }\n if (!containerId) {\n logger.info(\n `Container not running — yml updated only. Clone happens on \\`monoceros apply ${input.name}\\`.`,\n );\n return;\n }\n\n // Credential fetch for the URL's host. Same mechanism apply uses\n // (host-side `git credential fill`), writing into the bind-mounted\n // `.monoceros/git-credentials` file so the in-container clone can\n // pick it up via the credential.helper that post-create already\n // wired.\n let urlHost: string;\n try {\n urlHost = new URL(entry.url).hostname;\n } catch {\n logger.warn(\n `Cannot parse URL host from ${entry.url}. The yml is updated — clone manually inside the container or rerun with a fixed URL.`,\n );\n return;\n }\n const provider = resolveProvider(urlHost, entry.provider);\n if (provider === 'unknown') {\n logger.warn(\n `Could not resolve provider for host ${urlHost}. The yml is updated; clone happens at the next \\`monoceros apply\\` if you set the provider.`,\n );\n return;\n }\n try {\n const credsResult = await collectGitCredentials(\n root,\n [{ host: urlHost, provider }],\n {\n ...(input.credentialsSpawn ? { spawn: input.credentialsSpawn } : {}),\n logger: { info: () => {}, warn: (m) => logger.warn(m) },\n },\n );\n const status = credsResult.perHost.find((h) => h.host === urlHost);\n if (!status || status.status !== 'ok') {\n const detail = status?.detail ? `: ${status.detail}` : '';\n logger.warn(\n `No HTTPS credentials available for ${urlHost}${detail}. The yml is updated; set up credentials (e.g. \\`gh auth login\\`) and re-run \\`monoceros apply ${input.name}\\` or rerun this add-repo.`,\n );\n return;\n }\n } catch (err) {\n logger.warn(\n `Credential fetch for ${urlHost} failed: ${err instanceof Error ? err.message : String(err)}. The yml is updated.`,\n );\n return;\n }\n\n // The clone itself. mkdir -p ensures nested parents exist. The\n // outer `[ -d <target> ] && exit 0` short-circuit matches the\n // idempotency post-create.sh has — re-running add-repo against the\n // same URL is a yml no-op anyway, but if the folder somehow\n // exists without the yml entry we still don't overwrite.\n //\n // `git -c credential.helper=…` sets the helper INLINE just for\n // this clone, instead of relying on `git config --global` having\n // been set by post-create.sh. That matters because post-create\n // runs once at container up, not on every add-repo — a container\n // that started life without any HTTPS repo wouldn't have a\n // credential.helper configured at all. Inline-setting keeps\n // add-repo self-contained and doesn't mutate the container's\n // global git config as a side effect.\n const containerName = input.name;\n const targetRel = `projects/${entry.path}`;\n const parentRel = entry.path.includes('/')\n ? `projects/${entry.path.split('/').slice(0, -1).join('/')}`\n : 'projects';\n const credentialsFile = `/workspaces/${containerName}/.monoceros/git-credentials`;\n const credentialHelper = `store --file=${credentialsFile}`;\n const script = [\n `set -eu`,\n `cd /workspaces/${containerName}`,\n `if [ -d ${shquote(targetRel)} ]; then`,\n ` echo \"[add-repo] ${targetRel} already exists — skipping clone.\"`,\n ` exit 0`,\n `fi`,\n `mkdir -p ${shquote(parentRel)}`,\n `git -c ${shquote(`credential.helper=${credentialHelper}`)} clone ${shquote(entry.url)} ${shquote(targetRel)}`,\n ];\n if (entry.gitUser) {\n script.push(\n `git -C ${shquote(targetRel)} config user.name ${shquote(entry.gitUser.name)}`,\n `git -C ${shquote(targetRel)} config user.email ${shquote(entry.gitUser.email)}`,\n );\n }\n const execFn = input.containerExec ?? realContainerExec;\n let exit;\n try {\n exit = await execFn(containerId, ['bash', '-c', script.join('\\n')]);\n } catch (err) {\n logger.warn(\n `In-container clone for ${entry.url} failed: ${err instanceof Error ? err.message : String(err)}. The yml is updated; \\`monoceros apply ${input.name}\\` retries.`,\n );\n return;\n }\n if (exit.exitCode !== 0) {\n logger.warn(\n `In-container clone for ${entry.url} exited ${exit.exitCode}. The yml is updated; \\`monoceros apply ${input.name}\\` retries.`,\n );\n return;\n }\n logger.info(\n `Cloned ${entry.url} into /workspaces/${containerName}/${targetRel} inside the running container.`,\n );\n void path; // path import is reserved for future relative-path work\n}\n\n/**\n * Minimal shell-quote — single-quotes the value, escaping any\n * embedded single-quote via `'\\\\''`. The clone script runs inside a\n * `bash -c` invocation, so input that came from the yml (URLs,\n * paths, identity names) must be safely quoted to avoid trivial\n * injection or accidental shell-meta interpretation.\n */\nfunction shquote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\nfunction normalizeProvider(raw: string | undefined): RepoProvider | undefined {\n if (typeof raw !== 'string') return undefined;\n const trimmed = raw.trim();\n if (trimmed.length === 0) return undefined;\n const lowered = trimmed.toLowerCase() as RepoProvider;\n if (!(PROVIDER_VALUES as readonly string[]).includes(lowered)) {\n throw new Error(\n `Invalid --provider value: ${JSON.stringify(raw)}. Allowed: ${PROVIDER_VALUES.join(', ')}.`,\n );\n }\n return lowered;\n}\n\nexport function runAddFromUrl(input: AddFromUrlInput): Promise<ModifyResult> {\n const url = input.url.trim();\n if (url.length === 0) {\n throw new Error(\n 'Missing URL. Usage: monoceros add-from-url <containername> <url>.',\n );\n }\n return mutate(input, (doc) => addInstallUrlToDoc(doc, url));\n}\n\nexport async function runAddPort(input: AddPortInput): Promise<ModifyResult> {\n if (input.ports.length === 0) {\n throw new Error(\n 'No ports given. Usage: monoceros add-port <containername> -- <port> [<port> …].',\n );\n }\n const ports = normalizePorts(input.ports);\n if (input.asDefault && ports.length > 1) {\n throw new Error(\n `--default takes exactly one port. Got: ${ports.join(', ')}. Run add-port once with --default for the new default, then again (without --default) for the rest.`,\n );\n }\n const result = await mutate(input, (doc) => {\n if (input.asDefault) {\n // --default semantics: ensure the port exists AND sits at index\n // 0. setDefaultPortInDoc covers both (insert-or-move).\n return setDefaultPortInDoc(doc, ports[0]!);\n }\n return addPortsToDoc(doc, ports);\n });\n // Hot-reload path: when the yml actually changed, push the new\n // route set to the Traefik dynamic-config directory and make sure\n // the proxy is up. The yml is the source of truth — we re-read it\n // so the dynamic config reflects the FULL port list (including\n // entries that pre-existed this `add-port` call), not just the\n // delta. Proxy failures surface as warns but never roll back the\n // yml write. See ADR 0007.\n if (result.status === 'updated') {\n await syncPortsToProxy(input);\n }\n return result;\n}\n\n/**\n * Validate each entry as an integer in [1, 65535] and dedupe — same\n * port listed twice in the CLI args is treated as one. Throws on\n * any non-integer or out-of-range value with the offending input\n * verbatim so the builder can fix the typo.\n */\nfunction normalizePorts(raw: readonly (number | string)[]): number[] {\n const result: number[] = [];\n const seen = new Set<number>();\n for (const item of raw) {\n const n = typeof item === 'number' ? item : Number(item);\n if (!Number.isInteger(n) || n < 1 || n > 65535) {\n throw new Error(\n `Invalid port: ${JSON.stringify(item)}. Expected an integer between 1 and 65535.`,\n );\n }\n if (seen.has(n)) continue;\n seen.add(n);\n result.push(n);\n }\n return result;\n}\n\nexport async function runAddFeature(\n input: AddFeatureInput,\n): Promise<ModifyResult> {\n const raw = input.ref.trim();\n if (raw.length === 0) {\n throw new Error(\n 'Missing feature ref. Usage: monoceros add-feature <containername> <feature>.',\n );\n }\n const resolved = await resolveFeatureRefOrShortname(raw);\n // User-supplied `-- key=value` options override the catalog-driven\n // defaults that come with a short name. For a full OCI ref the\n // resolver returns no defaults, so this is just `input.options`.\n const merged: FeatureOptions = {\n ...resolved.defaultOptions,\n ...(input.options ?? {}),\n };\n // Hand the user's typed form (short-name or full ref) to the AST\n // mutator so any error message it produces echoes the form the\n // builder is using rather than always the resolved OCI ref.\n const result = await mutate(input, (doc) =>\n addFeatureToDoc(doc, resolved.ref, merged, raw),\n );\n\n // Seed the feature's credential vars into <name>.env (the same\n // ${VAR} placeholders addFeatureToDoc just wrote into the yml), so\n // the builder only fills values. Skips keys already set with an\n // active `-- key=value`. Mirrors init; remove-feature does NOT touch\n // the env file.\n if (result.status === 'updated') {\n const summary = loadFeatureManifestSummary(resolved.ref);\n const vars = featureOptionHints(\n summary,\n resolved.ref,\n Object.keys(merged),\n ).map((h) => h.envVar);\n if (vars.length > 0) {\n const home = input.monocerosHome ?? defaultMonocerosHome();\n await ensureEnvGitignored(containerConfigsDir(home));\n const seeded = await ensureEnvVars(\n containerEnvPath(input.name, home),\n input.name,\n vars,\n );\n if (seeded.added.length > 0) {\n (input.logger ?? defaultLogger()).info(\n `Seeded ${seeded.added.join(', ')} into ${input.name}.env — fill in the values.`,\n );\n }\n }\n }\n return result;\n}\n\n/**\n * Accept either a full OCI feature ref (`ghcr.io/.../foo:1`) or a\n * catalog short-name (`atlassian`, `atlassian/twg`, `claude`, …).\n *\n * Short names map to the matching component's `contributes.features`\n * entry; the entry's `options` (if any) become the default option\n * values the caller's `--` overrides apply on top of. Unknown short\n * names produce an error that lists the available features.\n */\nasync function resolveFeatureRefOrShortname(input: string): Promise<{\n ref: string;\n defaultOptions: FeatureOptions;\n}> {\n if (REGEX.featureRef.test(input)) {\n return { ref: input, defaultOptions: {} };\n }\n const catalog = await loadComponentCatalog();\n const component = catalog.get(input);\n if (!component) {\n const featureShorts = [...catalog.values()]\n .filter((c) => c.file.category === 'feature')\n .map((c) => c.name)\n .sort();\n const knownList =\n featureShorts.length > 0 ? featureShorts.join(', ') : '(none)';\n throw new Error(\n `Unknown feature: ${JSON.stringify(input)}. ` +\n `Pass either a catalog short-name (one of: ${knownList}) ` +\n `or a full OCI ref like ` +\n `'ghcr.io/getmonoceros/monoceros-features/<name>:<tag>'.`,\n );\n }\n if (component.file.category !== 'feature') {\n throw new Error(\n `'${input}' is a ${component.file.category}, not a feature. ` +\n `Use 'monoceros add-${component.file.category} <name> ${input}' instead.`,\n );\n }\n const features = component.file.contributes.features ?? [];\n if (features.length === 0) {\n throw new Error(\n `Catalog entry '${input}' contributes no feature ref — bug or stale catalog.`,\n );\n }\n if (features.length > 1) {\n // Practically: Monoceros's own catalog has one ref per feature\n // component. A multi-ref short-name would be ambiguous because\n // `add-feature` only adds one ref at a time.\n throw new Error(\n `'${input}' bundles multiple feature refs (${features\n .map((f) => f.ref)\n .join(\n ', ',\n )}). add-feature handles one at a time — pass the OCI ref directly.`,\n );\n }\n const [first] = features;\n return {\n ref: first!.ref,\n defaultOptions: { ...(first!.options ?? {}) },\n };\n}\n\n// ─── remove-* ─────────────────────────────────────────────────────\n\nexport function runRemoveLanguage(\n input: RemoveLanguageInput,\n): Promise<ModifyResult> {\n return mutate(input, (doc) => removeLanguageFromDoc(doc, input.language));\n}\n\nexport function runRemoveService(\n input: RemoveServiceInput,\n): Promise<ModifyResult> {\n return mutate(input, (doc) => removeServiceFromDoc(doc, input.service));\n}\n\nexport function runRemoveAptPackages(\n input: RemoveAptPackagesInput,\n): Promise<ModifyResult> {\n if (input.packages.length === 0) {\n throw new Error(\n 'No package names given. Usage: monoceros remove-apt-packages <containername> -- <pkg> [<pkg> …].',\n );\n }\n return mutate(input, (doc) => removeAptPackagesFromDoc(doc, input.packages));\n}\n\nexport async function runRemoveFeature(\n input: RemoveFeatureInput,\n): Promise<ModifyResult> {\n const raw = input.ref.trim();\n if (raw.length === 0) {\n throw new Error(\n 'Missing feature ref. Usage: monoceros remove-feature <containername> <feature>.',\n );\n }\n // Same short-name → ref resolution as `add-feature`. Without this\n // the suggestion `monoceros remove-feature atlassian` we print\n // elsewhere wouldn't actually work, only the full OCI form.\n const resolved = await resolveFeatureRefOrShortname(raw);\n return mutate(input, (doc) => removeFeatureFromDoc(doc, resolved.ref));\n}\n\nexport function runRemoveFromUrl(\n input: RemoveFromUrlInput,\n): Promise<ModifyResult> {\n const url = input.url.trim();\n if (url.length === 0) {\n throw new Error(\n 'Missing URL. Usage: monoceros remove-from-url <containername> <url>.',\n );\n }\n return mutate(input, (doc) => removeInstallUrlFromDoc(doc, url));\n}\n\nexport async function runRemovePort(\n input: RemovePortInput,\n): Promise<ModifyResult> {\n if (input.ports.length === 0) {\n throw new Error(\n 'No ports given. Usage: monoceros remove-port <containername> -- <port> [<port> …].',\n );\n }\n const ports = normalizePorts(input.ports);\n const result = await mutate(input, (doc) => removePortsFromDoc(doc, ports));\n // Hot-reload path: same state-driven sync as add-port. When the\n // last port is gone the dynamic-config file is dropped and the\n // Traefik singleton is offered up for teardown via maybeStopProxy\n // (which no-ops if any other container is still attached). See\n // ADR 0007.\n if (result.status === 'updated') {\n await syncPortsToProxy(input);\n }\n return result;\n}\n\nexport function runRemoveRepo(input: RemoveRepoInput): Promise<ModifyResult> {\n const target = input.target.trim();\n if (target.length === 0) {\n throw new Error(\n 'Missing repo identifier. Usage: monoceros remove-repo <containername> <url-or-name>.',\n );\n }\n return mutate(input, (doc) => removeRepoFromDoc(doc, target));\n}\n\n// ─── core mutate skeleton ─────────────────────────────────────────\n\nasync function mutate(\n opts: ModifyOptions,\n apply: YmlMutator,\n): Promise<ModifyResult> {\n if (!REGEX.solutionName.test(opts.name)) {\n throw new Error(\n `Invalid container name: ${JSON.stringify(opts.name)}. Use letters, digits, '.', '_' or '-'.`,\n );\n }\n const home = opts.monocerosHome ?? defaultMonocerosHome();\n const ymlPath = containerConfigPath(opts.name, home);\n const logger = opts.logger ?? defaultLogger();\n\n let oldText: string;\n try {\n oldText = await fs.readFile(ymlPath, 'utf8');\n } catch {\n throw new Error(\n `No such config: ${ymlPath}. Run \\`monoceros init <template> ${opts.name}\\` first.`,\n );\n }\n\n const parsed = parseConfig(oldText, ymlPath);\n const changed = apply(parsed.doc);\n\n if (!changed) {\n logger.info('No changes — yml is already in the desired state.');\n return { status: 'no-change' };\n }\n\n // Centralised post-mutation comment fixup. yaml-lib's parser\n // sometimes attaches a column-0 comment block that visually belongs\n // to the NEXT top-level pair (e.g. the `# Container ports exposed…`\n // header above `routing:`) to the previous pair's deepest trailing\n // node instead. On re-emit via the AST, the comment then drifts\n // into the previous section. We run the relocator once here so\n // every add-*/remove-* mutator gets the fix for free — without it,\n // a sequence like `init` → `add-feature` rearranges the routing /\n // repos section headers into the features block above.\n relocateLeakedSectionComments(parsed.doc);\n\n // Re-validate via a round-trip so schema violations introduced by\n // the mutation surface here with the regular field-path error, not\n // later at apply time.\n const newText = stringifyConfig(parsed.doc);\n parseConfig(newText, ymlPath);\n\n const out = opts.output ?? ((line) => process.stdout.write(line + '\\n'));\n out(createPatch(ymlPath, oldText, newText, 'before', 'after'));\n\n if (!opts.yes) {\n const confirm = opts.confirm ?? defaultConfirm;\n const ok = await confirm('Apply these changes to the yml?');\n if (!ok) {\n logger.warn('Aborted by user. The yml was not modified.');\n return { status: 'aborted' };\n }\n }\n\n await fs.writeFile(ymlPath, newText, 'utf8');\n logger.success(`Updated ${ymlPath}.`);\n logger.info(\n `Run \\`monoceros apply ${opts.name}\\` to rebuild the dev-container and pick up the change.`,\n );\n return { status: 'updated', changedPaths: [ymlPath] };\n}\n\nfunction defaultLogger(): ModifyLogger {\n return {\n info: (m) => consola.info(m),\n success: (m) => consola.success(m),\n warn: (m) => consola.warn(m),\n };\n}\n\nconst defaultConfirm: ConfirmFn = async (message) => {\n const result = await consola.prompt(message, {\n type: 'confirm',\n initial: false,\n });\n return result === true;\n};\n\n/**\n * State-driven sync between the yml's `ports:` and Traefik's\n * dynamic-config directory + proxy lifecycle. Called from\n * `runAddPort` / `runRemovePort` after a successful yml change.\n *\n * - ports non-empty → write `<home>/traefik/dynamic/<name>.yml`\n * and call `ensureProxy()` (idempotent — no-op when Traefik is\n * already up).\n * - ports empty → remove the file and call `maybeStopProxy()`\n * (no-op when other containers still depend on the proxy).\n *\n * Any proxy or filesystem failure is surfaced as a warn but never\n * rolls back the yml write. The yml is the source of truth; proxy\n * state is derived and self-healing on the next apply/start.\n */\nasync function syncPortsToProxy(\n input: AddPortInput | RemovePortInput,\n): Promise<void> {\n const home = input.monocerosHome ?? defaultMonocerosHome();\n const ymlPath = containerConfigPath(input.name, home);\n const logger = input.logger ?? defaultLogger();\n\n let allPorts: number[];\n try {\n const parsed = await readConfig(ymlPath);\n allPorts = (parsed.config.routing?.ports ?? []).map(portNumber);\n } catch (err) {\n logger.warn(\n `Could not re-read yml after edit to sync Traefik routes: ${err instanceof Error ? err.message : String(err)}. The yml is correct; \\`monoceros apply ${input.name}\\` will rebuild the routes.`,\n );\n return;\n }\n\n // Effective host port for the Traefik singleton — falls back to 80\n // when monoceros-config.yml has no `routing.hostPort`. Read once per\n // sync so we have the right value for both ensureProxy and the URLs\n // we print back.\n let hostPort = 80;\n try {\n const globalConfig = await readMonocerosConfig({ monocerosHome: home });\n hostPort = proxyHostPort(globalConfig);\n } catch {\n // Bad monoceros-config.yml is the user's problem to fix; don't\n // strand the sync over it. Default 80 is the right fallback.\n }\n\n // Pre-flight outside the warn-only try/catch: a held host port is\n // a hard-fail (the route would never come up otherwise), and the\n // builder needs the actionable message verbatim. The yml is\n // already updated at this point — that's fine, it's the source of\n // truth and the next apply heals once the conflict is resolved.\n if (allPorts.length > 0) {\n await preflightHostPort(hostPort, {\n ...(input.proxyDocker ? { docker: input.proxyDocker } : {}),\n });\n }\n\n try {\n if (allPorts.length > 0) {\n await writeDynamicConfig(input.name, allPorts, { monocerosHome: home });\n await ensureProxy({\n monocerosHome: home,\n hostPort,\n ...(input.proxyDocker ? { docker: input.proxyDocker } : {}),\n logger: { info: (m) => logger.info(m), warn: (m) => logger.warn(m) },\n });\n const urls = proxyUrlsFor(input.name, allPorts, hostPort);\n const lines = urls.map((u) => {\n const tag = u.isDefault ? ' (default)' : '';\n return ` ${u.url}${tag}`;\n });\n logger.info(`Traefik routes refreshed:\\n${lines.join('\\n')}`);\n } else {\n await removeDynamicConfig(input.name, { monocerosHome: home });\n await maybeStopProxy({\n monocerosHome: home,\n ...(input.proxyDocker ? { docker: input.proxyDocker } : {}),\n logger: { info: (m) => logger.info(m), warn: (m) => logger.warn(m) },\n });\n }\n } catch (err) {\n logger.warn(\n `Could not sync Traefik routes after yml edit: ${err instanceof Error ? err.message : String(err)}. The yml is correct; \\`monoceros apply ${input.name}\\` will rebuild the routes.`,\n );\n }\n}\n","import { promises as fs } from 'node:fs';\nimport { Document, parseDocument } from 'yaml';\nimport { type SolutionConfig, validateConfig } from './schema.js';\n\n/**\n * A parsed solution-config yml plus its AST. `config` is the validated\n * plain-JS view (used to drive the apply pipeline); `doc` is the\n * `yaml.Document` (used by mutation helpers so comments and ordering\n * survive a round-trip).\n */\nexport interface ParsedConfig {\n config: SolutionConfig;\n doc: Document.Parsed;\n /** Source path or `<inline>` for an in-memory parse. Used in errors. */\n source: string;\n}\n\n/**\n * Parse a yml string and validate against the schema. Throws on\n * yaml syntax errors and on schema violations. The returned `doc`\n * preserves comments and node ordering — pass it to mutation helpers\n * (`addRepoToDoc`, …) and `stringifyConfig` so the builder's hand-\n * written comments survive `monoceros add-*`.\n */\nexport function parseConfig(\n yamlText: string,\n source = '<inline>',\n): ParsedConfig {\n const doc = parseDocument(yamlText, { prettyErrors: true });\n if (doc.errors.length > 0) {\n const first = doc.errors[0]!;\n throw new Error(`yaml parse error in ${source}: ${first.message}`);\n }\n const config = validateConfig(doc.toJS());\n return { config, doc, source };\n}\n\nexport async function readConfig(filePath: string): Promise<ParsedConfig> {\n const text = await fs.readFile(filePath, 'utf8');\n return parseConfig(text, filePath);\n}\n\n/** Serialize a Document back to yaml. */\nexport function stringifyConfig(doc: Document): string {\n return String(doc);\n}\n\nexport async function writeConfig(\n filePath: string,\n doc: Document,\n): Promise<void> {\n await fs.writeFile(filePath, stringifyConfig(doc), 'utf8');\n}\n\n/**\n * Build a fresh Document from a plain-JS config object. Used when\n * generating a yml from a template (no source comments to preserve)\n * or when migrating an existing stack.json. The resulting Document\n * is suitable for further mutation + `stringifyConfig`.\n *\n * The output is stable: keys appear in the canonical order defined\n * by `KEY_ORDER` below, so two configs with the same content yield\n * byte-identical yaml.\n */\nexport function createDoc(config: SolutionConfig): Document {\n const ordered: Record<string, unknown> = {};\n for (const key of KEY_ORDER) {\n if (key in config) {\n const value = (config as unknown as Record<string, unknown>)[key];\n if (isEmptyContainer(value)) continue;\n ordered[key] = value;\n }\n }\n const doc = new Document(ordered);\n return doc;\n}\n\n/**\n * Canonical key order in generated yaml. Matches the example skeleton\n * in `docs/backlog.md`. Hand-edited yml does not have to follow this\n * order (parser accepts any) — but anything `createDoc` writes does.\n */\nconst KEY_ORDER = [\n 'schemaVersion',\n 'name',\n 'languages',\n 'aptPackages',\n 'features',\n 'installUrls',\n 'services',\n 'repos',\n 'routing',\n 'externalServices',\n 'git',\n] as const;\n\nfunction isEmptyContainer(value: unknown): boolean {\n if (Array.isArray(value)) return value.length === 0;\n if (value && typeof value === 'object') {\n return Object.keys(value as Record<string, unknown>).length === 0;\n }\n return false;\n}\n","import { z } from 'zod';\n\n/**\n * Shape validation for a Monoceros solution-config yml. Catalog\n * validation (which languages/services actually exist) happens\n * separately in `apply`, against `create/catalog.ts` — that keeps the\n * schema decoupled from the catalog and lets the schema live without\n * pulling the whole devcontainer scaffold module in.\n *\n * Schema mirrors the StackFile shape from `create/types.ts` except:\n *\n * - `features` is an **array** of `{ ref, options }` entries (yml is\n * edited by humans and arrays diff/comment better than maps).\n * The apply step converts to the Record shape `devcontainer.json`\n * expects.\n *\n * - `externalServices.postgres` carries what `CreateOptions.postgresUrl`\n * does today.\n *\n * - `git.user.{name,email}` carries the host-captured identity so the\n * yml-as-profile is self-contained when shared across containers.\n * Optional — falls back to host-side `git config --global --get` +\n * `.monoceros/gitconfig` at apply time, same as today.\n */\n\nconst SOLUTION_NAME_RE = /^[A-Za-z0-9._-]+$/;\nconst APT_PACKAGE_NAME_RE = /^[a-z0-9][a-z0-9.+-]*$/;\n// Feature refs are OCI-style:\n// <registry>/<namespace>/<feature>:<tag>\n// e.g. ghcr.io/devcontainers/features/python:1\n// ghcr.io/getmonoceros/monoceros-features/claude-code:1\nconst FEATURE_REF_RE = /^[a-z0-9.-]+(\\/[a-z0-9._-]+)+:[a-z0-9._-]+$/;\nconst INSTALL_URL_RE = /^https:\\/\\/[A-Za-z0-9.\\-_~/:?#[\\]@!&'()*+,;=%]+$/;\n// Repo URLs are HTTPS-only by design. SSH-style URLs (git@host:...,\n// ssh://...) are explicitly out of scope — see ADR 0006 for the\n// reasoning. The schema rejects them at parse time with a clear\n// message rather than letting them through and failing opaquely\n// during the clone in post-create.sh.\nconst REPO_URL_RE = /^https:\\/\\/[A-Za-z0-9@:/+_~.#=&?-]+$/;\n// Path under `projects/`. Allows nested subfolders via `/` (e.g.\n// `apps/web`, `monorepo/libs/shared`). The regex enforces:\n// - non-empty\n// - segments use [A-Za-z0-9._-] (same charset as a leaf folder name)\n// - no leading `/`, no trailing `/`, no consecutive `//`\n// A separate refine rejects `.` / `..` segments — those would either\n// be no-ops or escape `projects/`, neither belongs in a checked-in\n// container yml.\nconst REPO_PATH_RE = /^[A-Za-z0-9._-]+(\\/[A-Za-z0-9._-]+)*$/;\nconst POSTGRES_URL_RE = /^postgres(ql)?:\\/\\//;\n\nexport const REGEX = {\n solutionName: SOLUTION_NAME_RE,\n aptPackage: APT_PACKAGE_NAME_RE,\n featureRef: FEATURE_REF_RE,\n installUrl: INSTALL_URL_RE,\n repoUrl: REPO_URL_RE,\n repoPath: REPO_PATH_RE,\n postgresUrl: POSTGRES_URL_RE,\n};\n\n/**\n * The providers Monoceros knows how to render setup hints for.\n *\n * Canonical SaaS hostnames (`github.com` / `gitlab.com` /\n * `bitbucket.org`) auto-detect to their provider. Everything else\n * — self-hosted GitLab, GitHub Enterprise, Bitbucket Data Center,\n * Gitea / Forgejo — must declare `provider:` explicitly. Gitea has\n * no canonical SaaS host (gitea.com is a demo, not a SaaS), so any\n * `provider: gitea` entry is by definition self-hosted.\n *\n * Forgejo (the community fork of Gitea) shares Gitea's API, UI, and\n * auth flow — we bundle it under `provider: gitea` rather than\n * carrying a separate enum value.\n */\nexport const PROVIDER_VALUES = [\n 'github',\n 'gitlab',\n 'bitbucket',\n 'gitea',\n] as const;\nexport type RepoProvider = (typeof PROVIDER_VALUES)[number];\n\n/**\n * Hostnames whose provider is implicit — no `provider:` field needed\n * in the yml. Everything else (self-hosted GitLab on `git.firma.de`,\n * Gitea instances, …) requires an explicit declaration; the apply\n * pre-flight enforces that.\n */\nexport const KNOWN_PROVIDER_HOSTS: Readonly<Record<string, RepoProvider>> = {\n 'github.com': 'github',\n 'gitlab.com': 'gitlab',\n 'bitbucket.org': 'bitbucket',\n};\n\n/** Current schema version. Bumped only on breaking yml changes. */\nexport const CONFIG_SCHEMA_VERSION = 1 as const;\n\n// Feature option values are string | number | boolean. We also\n// accept `null` (yaml's parse result for a bare `key:`) and transform\n// it to the empty string — that way an init-rendered options block\n// like `apiKey:` parses cleanly and ends up equivalent to the feature\n// option's documented default (which for string fields is `\"\"`).\nexport const FeatureOptionValueSchema = z\n .union([z.string(), z.number(), z.boolean(), z.null()])\n .transform((v) => (v === null ? '' : v));\n\nexport const FeatureEntrySchema = z.object({\n ref: z\n .string()\n .regex(\n FEATURE_REF_RE,\n \"Invalid feature ref. Expected an OCI-image-style ref like 'ghcr.io/devcontainers/features/<name>:<tag>'.\",\n ),\n options: z.record(z.string(), FeatureOptionValueSchema).optional(),\n});\n\n/**\n * Git identity block.\n *\n * Both fields are nullable + accept the empty string. yaml allows\n * a bare `name:` (parsed as null) as a placeholder for \"not set\n * yet — apply, please ask me\". The resolution logic treats null /\n * empty as \"unset\" and walks the precedence chain further.\n *\n * The schema validates only the SHAPE (non-empty string). The email\n * FORMAT is deliberately NOT checked here: a value may be a `${VAR}`\n * placeholder resolved from `<name>.env` at apply time, and you can't\n * validate the format of a value you haven't resolved yet. The format\n * check moves to apply, after interpolation (see `isValidEmail` +\n * apply/index.ts). The flag entry points (`add-repo --git-email`)\n * validate eagerly there instead.\n */\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\n/** Whether a (resolved) string is a well-formed email address. */\nexport function isValidEmail(value: string): boolean {\n return EMAIL_RE.test(value);\n}\n\nexport const GitUserSchema = z.object({\n name: z\n .union([z.literal(''), z.null(), z.string().min(1)])\n .nullish()\n .transform((v) => (typeof v === 'string' && v.length > 0 ? v : undefined)),\n email: z\n .union([z.literal(''), z.null(), z.string().min(1)])\n .nullish()\n .transform((v) => (typeof v === 'string' && v.length > 0 ? v : undefined)),\n});\n\nexport const RepoEntrySchema = z.object({\n url: z\n .string()\n .regex(\n REPO_URL_RE,\n 'Invalid repo URL. Only HTTPS URLs are supported (https://...). SSH-style URLs (git@host:..., ssh://...) are not supported.',\n ),\n path: z\n .string()\n .regex(\n REPO_PATH_RE,\n \"Invalid repo path. Use letters/digits/'._-', forward slashes for nested folders, no leading or trailing slash.\",\n )\n .refine(\n (p) => !p.split('/').some((seg) => seg === '..' || seg === '.'),\n 'Repo path segments cannot be \".\" or \"..\".',\n )\n .optional(),\n // Per-repo git identity override. Falls back to the container-level\n // `git.user` (which itself falls back to the host's\n // `git config --global` at apply time). Useful when a single\n // container clones multiple repos that need different committer\n // identities — e.g. work GitHub org vs personal projects.\n git: z\n .object({\n user: GitUserSchema.optional(),\n })\n .optional(),\n // Provider hint for the pre-flight credential check. For the three\n // canonical hosts (github.com / gitlab.com / bitbucket.org) the\n // provider is auto-detected and this field is unnecessary. For any\n // other host (self-hosted GitLab on a custom domain, Gitea, …) the\n // builder MUST declare the provider so apply can suggest the right\n // CLI setup (`glab auth login --hostname <host>` etc.) when\n // credentials are missing. Enforced at apply pre-flight, not at\n // parse time — see ADR 0006.\n provider: z.enum(PROVIDER_VALUES).optional(),\n});\n\n/**\n * A single entry under `ports:`. Two forms accepted:\n *\n * ports:\n * - 3000 # short form: just the port number\n * - port: 9229 # long form, leaves room for future fields\n * # (protocol, path-prefix, entrypoint …)\n *\n * Today both forms carry the same information. The long form exists\n * so additive extensions (TLS entrypoint, path-based routing) don't\n * require a schema break. See ADR 0007.\n */\nexport const PortEntrySchema = z.union([\n z\n .number()\n .int()\n .min(1, 'Port must be ≥ 1.')\n .max(65535, 'Port must be ≤ 65535.'),\n z.object({\n port: z\n .number()\n .int()\n .min(1, 'Port must be ≥ 1.')\n .max(65535, 'Port must be ≤ 65535.'),\n }),\n]);\n\n/**\n * Routing block — everything Monoceros uses to expose container ports\n * to the host through the shared Traefik singleton.\n *\n * - `ports`: container-internal ports the builder wants reachable\n * via `<name>.localhost` / `<name>-<port>.localhost`. Short form\n * `3000` or long form `{ port: 3000 }` are both accepted; mutators\n * write the short form by default. First port doubles as the\n * default route under the bare `<name>.localhost`.\n *\n * - `vscodeAutoForward`: whether VS Code's Dev-Containers extension\n * should also auto-forward ports on top of Traefik. Default\n * `false`. Set to `true` only if VS Code's port panel should be\n * the primary entry rather than `<name>.localhost`.\n *\n * Host-port for the Traefik singleton itself is global (one Traefik\n * per machine), not per container — it lives in `monoceros-config.yml`\n * under `routing.hostPort`. See ADR 0007.\n */\nexport const RoutingSchema = z.object({\n ports: z.array(PortEntrySchema).default([]),\n vscodeAutoForward: z.boolean().optional(),\n});\n\n// ── Services ────────────────────────────────────────────────────────\n//\n// A `services:` entry is always an object: `image` is required; env,\n// volumes, port, healthcheck, restart and command are opt-in.\n//\n// There is deliberately ONE form. The curated catalog (postgres / mysql\n// / redis) is init-sugar: `monoceros init --with-services=postgres` and\n// `monoceros add-service <name> postgres` EXPAND the catalog name into a\n// full, editable object block. The bare-string form is NOT accepted in\n// the yml — a single shape keeps the model and the docs unambiguous.\n// See docs/backlog.md (init + service redesign).\n\n// Compose service name: becomes the docker compose service key, the\n// in-network DNS alias other services dial it by, and the per-service\n// data dir name under container/<name>/data/<svc>/.\nconst SERVICE_NAME_RE = /^[a-z0-9][a-z0-9_-]*$/;\n\n// env values: yaml may parse a value as string/number/bool, or a bare\n// `KEY:` as null. Coerce everything to a string for compose output;\n// null → \"\" (an explicitly empty env var). `${VAR}` references survive\n// verbatim here — they are resolved against `<name>.env` at apply time.\nconst ServiceEnvValueSchema = z\n .union([z.string(), z.number(), z.boolean(), z.null()])\n .transform((v) => (v === null ? '' : String(v)));\n\nexport const ServiceHealthcheckSchema = z.object({\n // Compose accepts both forms and they differ semantically:\n // - string → run via the shell (CMD-SHELL)\n // - [\"CMD\", …] → exec the args directly, no shell\n // - [\"CMD-SHELL\", …]\n // We accept either and render it back faithfully.\n test: z.union([\n z.string().min(1, 'Healthcheck test must not be empty.'),\n z\n .array(z.string().min(1))\n .min(1, 'Healthcheck test array must not be empty.'),\n ]),\n interval: z.string().optional(),\n timeout: z.string().optional(),\n retries: z.number().int().min(1).optional(),\n startPeriod: z.string().optional(),\n});\n\nexport const SERVICE_RESTART_VALUES = [\n 'no',\n 'always',\n 'on-failure',\n 'unless-stopped',\n] as const;\n\n// A volume entry is `src:dest[:mode]`.\n//\n// - `src` is either the `data` shorthand (→ the per-service\n// bind-mounted data dir under container/<name>/data/<name>/) or a\n// path relative to the container root (`projects/app/init.sql`,\n// `./config/x`). `dest` is an absolute in-container path.\n// - Docker **named volumes** (a bare token like `rustfs_data`) are NOT\n// supported — Monoceros binds to the host disk so content is part of\n// backups (ADR 0003). A bare single token is rejected with a hint to\n// use `data:` or an explicit relative path, because that's the most\n// common compose-port mistake.\n// - Absolute host sources and `..` escapes are rejected.\nfunction isValidServiceVolume(spec: string): boolean {\n const parts = spec.split(':');\n if (parts.length < 2 || parts.length > 3) return false;\n const [src, dest, mode] = parts;\n if (!src || !dest) return false;\n if (!dest.startsWith('/')) return false; // container target must be absolute\n if (mode !== undefined && !/^(ro|rw|cached|delegated|z|Z)$/.test(mode)) {\n return false;\n }\n if (src === 'data') return true;\n if (src.startsWith('/')) return false; // absolute host source\n // A bare token (no `./`, no `/`) is almost always a leftover docker\n // named volume — reject it rather than silently bind-mounting a junk\n // host dir of that name.\n const looksLikePath = src.startsWith('./') || src.includes('/');\n if (!looksLikePath) return false;\n const normalized = src.startsWith('./') ? src.slice(2) : src;\n if (normalized.split('/').some((s) => s === '..' || s === '.')) return false;\n return true;\n}\n\nexport const ServiceObjectSchema = z.object({\n name: z\n .string()\n .regex(\n SERVICE_NAME_RE,\n \"Invalid service name. Use lowercase letters, digits, '_' or '-' (must start with a letter or digit).\",\n ),\n image: z.string().min(1, 'Service image must not be empty.'),\n // In-container port the service listens on. Used by\n // `monoceros tunnel <name> <service>` to forward without an explicit\n // port argument. NOT a host port mapping — host exposure goes through\n // routing.ports (Traefik) or `monoceros tunnel`.\n port: z.number().int().min(1, 'Port must be ≥ 1.').max(65535).optional(),\n env: z.record(z.string(), ServiceEnvValueSchema).optional(),\n volumes: z\n .array(\n z\n .string()\n .refine(\n isValidServiceVolume,\n \"Invalid volume. Use 'data:/container/path' for the per-service persistent dir, or a relative host path ('projects/app/init.sql:/...:ro', './config:/...'). Docker named volumes (a bare name like 'rustfs_data') are not supported; absolute host paths and '..' are rejected.\",\n ),\n )\n .optional(),\n healthcheck: ServiceHealthcheckSchema.optional(),\n restart: z.enum(SERVICE_RESTART_VALUES).optional(),\n command: z.string().optional(),\n});\n\nexport const ExternalServicesSchema = z.object({\n postgres: z\n .string()\n .regex(\n POSTGRES_URL_RE,\n \"Postgres URL must start with 'postgres://' or 'postgresql://'\",\n )\n .optional(),\n});\n\nexport const SolutionConfigSchema = z.object({\n schemaVersion: z.literal(CONFIG_SCHEMA_VERSION),\n name: z\n .string()\n .regex(\n SOLUTION_NAME_RE,\n \"Invalid solution name. Use letters, digits, '.', '_' or '-'.\",\n ),\n languages: z.array(z.string().min(1)).default([]),\n aptPackages: z\n .array(\n z\n .string()\n .regex(\n APT_PACKAGE_NAME_RE,\n \"Invalid apt package name. Expected lowercase alphanumeric plus '.+-'.\",\n ),\n )\n .default([]),\n features: z.array(FeatureEntrySchema).default([]),\n installUrls: z\n .array(\n z\n .string()\n .regex(\n INSTALL_URL_RE,\n \"Invalid install URL. Must start with 'https://' and contain only URL-safe characters (no shell metacharacters).\",\n ),\n )\n .default([]),\n services: z.array(ServiceObjectSchema).default([]),\n repos: z.array(RepoEntrySchema).default([]),\n routing: RoutingSchema.optional(),\n externalServices: ExternalServicesSchema.default({}),\n git: z\n .object({\n user: GitUserSchema.optional(),\n })\n .optional(),\n});\n\nexport type SolutionConfig = z.infer<typeof SolutionConfigSchema>;\nexport type FeatureEntry = z.infer<typeof FeatureEntrySchema>;\nexport type ServiceObject = z.infer<typeof ServiceObjectSchema>;\nexport type ServiceHealthcheck = z.infer<typeof ServiceHealthcheckSchema>;\nexport type RepoEntry = z.infer<typeof RepoEntrySchema>;\nexport type GitUser = z.infer<typeof GitUserSchema>;\nexport type ExternalServices = z.infer<typeof ExternalServicesSchema>;\nexport type PortEntry = z.infer<typeof PortEntrySchema>;\nexport type Routing = z.infer<typeof RoutingSchema>;\n\n/** Resolve a `PortEntry` (short or long form) to a plain port number. */\nexport function portNumber(entry: PortEntry): number {\n return typeof entry === 'number' ? entry : entry.port;\n}\n\n/**\n * Validate parsed yml (e.g. from `doc.toJS()`) against the schema. On\n * failure, throws an Error whose message lists every issue with its\n * dotted path — the apply step prints that verbatim, so the builder\n * sees exactly which yml field is wrong.\n */\nexport function validateConfig(input: unknown): SolutionConfig {\n const result = SolutionConfigSchema.safeParse(input);\n if (!result.success) {\n const issues = result.error.issues\n .map((issue) => {\n const where = issue.path.length > 0 ? issue.path.join('.') : '(root)';\n return ` - ${where}: ${issue.message}`;\n })\n .join('\\n');\n throw new Error(`Invalid solution config:\\n${issues}`);\n }\n return result.data;\n}\n","import { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport { fileURLToPath } from 'node:url';\n\n/**\n * Path helpers for the M2.5 Phase 3 yml-profile model.\n *\n * Two distinct roots:\n *\n * - `workbenchRoot()` — where the **CLI bundle** lives. In dev that's\n * the monoceros-workbench checkout (so `templates/yml/` is reachable\n * and the workbench can be bind-mounted into generated containers\n * as `/opt/monoceros-workbench`). In prod (post-M4) this is the\n * installed package directory.\n *\n * - `monocerosHome()` — where **user data** lives: container-configs,\n * materialized containers, the global `monoceros-config.yml`.\n *\n * The two used to be conflated under one `workbenchRoot()`; splitting\n * them is what lets `monoceros apply <name>` resolve a fixed\n * `<MONOCEROS_HOME>/container/<name>/` location without any cwd\n * magic, while the CLI itself still knows where its bundled templates\n * live.\n *\n * Layout under `<MONOCEROS_HOME>/`:\n * container-configs/<name>.yml ← yml-Profile (`monoceros init`)\n * container/<name>/ ← materialized dev-containers\n * monoceros-config.yml ← optional, user-edited defaults\n * monoceros-config.sample.yml ← marker (in dev) + template (in prod)\n */\n\nconst MONOCEROS_HOME_MARKER = 'monoceros-config.sample.yml';\nconst WORKBENCH_MARKER = path.join('templates', 'components', 'README.md');\nconst CHECKOUT_MARKER = 'pnpm-workspace.yaml';\n\nlet cachedWorkbenchRoot: string | null = null;\nlet cachedMonocerosHome: string | null = null;\nlet cachedCheckoutRoot: string | null | undefined = undefined;\n\n/**\n * Walk upwards from this module until we find the workbench checkout's\n * marker (`templates/components/README.md`). In dev that hits the\n * workbench root reliably; in production the file does not exist\n * outside the shipped CLI package, so callers that need a workbench\n * root for dev-only purposes (bind-mounting `/opt/monoceros-workbench`)\n * get a clear error.\n */\nexport function workbenchRoot(): string {\n if (cachedWorkbenchRoot) return cachedWorkbenchRoot;\n let dir = path.dirname(fileURLToPath(import.meta.url));\n while (true) {\n if (existsSync(path.join(dir, WORKBENCH_MARKER))) {\n cachedWorkbenchRoot = dir;\n return dir;\n }\n const parent = path.dirname(dir);\n if (parent === dir) {\n throw new Error(\n `Could not locate the monoceros workbench checkout (no ${WORKBENCH_MARKER} found by walking up). Run the CLI from a workbench checkout.`,\n );\n }\n dir = parent;\n }\n}\n\n/**\n * Resolve `MONOCEROS_HOME` (where user data lives):\n *\n * 1. Honor the `MONOCEROS_HOME` env-var if set.\n * 2. Walk upwards from this module and accept the first\n * `<dir>/.local/monoceros-config.sample.yml` we find; the\n * containing `<dir>/.local` is treated as the home. This is the\n * dev-workbench detection path.\n * 3. Fall back to `~/.monoceros`.\n *\n * Caches the result for the lifetime of the process — flip `force` to\n * recompute (tests do this between cases).\n */\nexport function monocerosHome(opts: { force?: boolean } = {}): string {\n if (!opts.force && cachedMonocerosHome) return cachedMonocerosHome;\n\n const fromEnv = process.env.MONOCEROS_HOME;\n if (fromEnv && fromEnv.length > 0) {\n cachedMonocerosHome = path.resolve(fromEnv);\n return cachedMonocerosHome;\n }\n\n let dir = path.dirname(fileURLToPath(import.meta.url));\n while (true) {\n const candidate = path.join(dir, '.local');\n if (existsSync(path.join(candidate, MONOCEROS_HOME_MARKER))) {\n cachedMonocerosHome = candidate;\n return candidate;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n\n cachedMonocerosHome = path.join(os.homedir(), '.monoceros');\n return cachedMonocerosHome;\n}\n\n/**\n * Walk upwards from this module to find the workbench checkout root,\n * marked by `pnpm-workspace.yaml`. Distinct from `workbenchRoot()`:\n *\n * - `workbenchRoot()` returns where the **CLI bundle** lives —\n * `packages/cli/` in dev, the installed package directory in prod.\n * - `workbenchCheckoutRoot()` returns where the **full workbench\n * checkout** lives. Only meaningful in dev; returns `null` in\n * prod (the marker doesn't ship with the npm package).\n *\n * Used by features like the dev-only local-source-fallback in\n * `resolveFeatures`, where we want to look at `images/features/<name>/`\n * at the checkout root — not inside the CLI package, where that\n * directory deliberately doesn't exist.\n */\nexport function workbenchCheckoutRoot(): string | null {\n if (cachedCheckoutRoot !== undefined) return cachedCheckoutRoot;\n let dir = path.dirname(fileURLToPath(import.meta.url));\n while (true) {\n if (existsSync(path.join(dir, CHECKOUT_MARKER))) {\n cachedCheckoutRoot = dir;\n return dir;\n }\n const parent = path.dirname(dir);\n if (parent === dir) {\n cachedCheckoutRoot = null;\n return null;\n }\n dir = parent;\n }\n}\n\n/** Reset cached lookups. Test-only. */\nexport function _resetPathCachesForTests(): void {\n cachedWorkbenchRoot = null;\n cachedMonocerosHome = null;\n cachedCheckoutRoot = undefined;\n}\n\n// ─── CLI-bundle paths (templates) ─────────────────────────────────\n\n/**\n * `templates/components/` — the components catalog used by\n * `monoceros init`. Each file under this directory is a small yml\n * snippet describing one composable component (a language, a\n * service, or a feature). See `templates/components/README.md`.\n */\nexport function componentsDir(root: string = workbenchRoot()): string {\n return path.join(root, 'templates', 'components');\n}\n\n/**\n * `features/` (inside the CLI bundle) — npm-shipped copies of the\n * Monoceros feature manifests (`devcontainer-feature.json`). Built\n * by `pnpm manifests:sync` from `images/features/<name>/` and\n * included in the published tarball via the `files` field. The\n * init generator's hint loader looks here as the production\n * fallback when the workbench checkout isn't available.\n */\nexport function bundledFeaturesDir(root: string = workbenchRoot()): string {\n return path.join(root, 'features');\n}\n\n// ─── User-home paths (configs, containers, global config) ────────\n\nexport function containerConfigsDir(home: string = monocerosHome()): string {\n return path.join(home, 'container-configs');\n}\n\nexport function containerConfigPath(\n name: string,\n home: string = monocerosHome(),\n): string {\n return path.join(containerConfigsDir(home), `${name}.yml`);\n}\n\n/**\n * Per-container env file holding values for `${VAR}` references in the\n * yml (service secrets etc.). Lives beside `<name>.yml`, gitignored.\n */\nexport function containerEnvPath(\n name: string,\n home: string = monocerosHome(),\n): string {\n return path.join(containerConfigsDir(home), `${name}.env`);\n}\n\nexport function containersDir(home: string = monocerosHome()): string {\n return path.join(home, 'container');\n}\n\nexport function containerDir(\n name: string,\n home: string = monocerosHome(),\n): string {\n return path.join(containersDir(home), name);\n}\n\n/**\n * Per-container log directory — receives `apply-<name>-<ISO>.log` and\n * (future) audit logs for other lifecycle commands. Goes away with\n * `monoceros remove`. See ADR 0013.\n */\nexport function containerLogsDir(\n name: string,\n home: string = monocerosHome(),\n): string {\n return path.join(containerDir(name, home), 'logs');\n}\n\nexport function monocerosConfigPath(home: string = monocerosHome()): string {\n return path.join(home, 'monoceros-config.yml');\n}\n\n// ─── User-facing path formatting ─────────────────────────────────\n\n/**\n * Format an absolute path for printing in CLI output: collapses a\n * `$HOME` prefix to `~` so messages stay short without losing\n * information. Non-home paths pass through verbatim.\n *\n * prettyPath('/Users/x/.monoceros/container-configs/hello.yml')\n * → '~/.monoceros/container-configs/hello.yml'\n *\n * Use this whenever a log line tells the user where something\n * landed on disk — `monoceros init`, `apply`, `remove`, `restore`\n * all rely on it so users see one consistent format.\n */\nexport function prettyPath(p: string): string {\n const home = os.homedir();\n if (!home) return p;\n if (p === home) return '~';\n const prefix = home.endsWith(path.sep) ? home : home + path.sep;\n if (p.startsWith(prefix)) {\n return '~' + path.sep + p.slice(prefix.length);\n }\n return p;\n}\n","import { existsSync, readFileSync, promises as fsp } from 'node:fs';\nimport path from 'node:path';\nimport type { ResolvedService } from '../create/types.js';\n\n/**\n * Per-container secret/value source. Lives beside the yml profile as\n * `container-configs/<name>.env`, gitignored, and supplies the values\n * for `${VAR}` references in the yml (today: service env values and\n * service commands). Keeping secrets out of the yml — which is meant to\n * be shareable/committable — is the whole point; the threat model is\n * \"don't commit credentials to git\", not \"no plaintext on disk\".\n *\n * See docs/backlog.md (init + service redesign) for the design and the\n * parked `cmd:`-resolver follow-up.\n */\n\n// KEY=VALUE, optional leading `export`, `#` comments, surrounding\n// single/double quotes stripped. Intentionally minimal — this is a\n// dev-time value file, not a full dotenv grammar (no multi-line values,\n// no `${}` expansion *within* the env file itself).\nconst ENV_LINE_RE = /^\\s*(?:export\\s+)?([A-Za-z_][A-Za-z0-9_]*)\\s*=(.*)$/;\n\nexport function parseEnvFile(content: string): Record<string, string> {\n const out: Record<string, string> = {};\n for (const raw of content.split(/\\r?\\n/)) {\n const trimmed = raw.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const m = ENV_LINE_RE.exec(raw);\n if (!m) continue;\n const key = m[1]!;\n let val = m[2]!.trim();\n if (\n val.length >= 2 &&\n ((val.startsWith('\"') && val.endsWith('\"')) ||\n (val.startsWith(\"'\") && val.endsWith(\"'\")))\n ) {\n val = val.slice(1, -1);\n }\n out[key] = val;\n }\n return out;\n}\n\n/** Read + parse `<name>.env`. Returns `{}` when the file is absent. */\nexport function readEnvFile(envPath: string): Record<string, string> {\n if (!existsSync(envPath)) return {};\n return parseEnvFile(readFileSync(envPath, 'utf8'));\n}\n\n/**\n * Ensure `<container-configs>/.gitignore` excludes `*.env`. A builder\n * may version-control their MONOCEROS_HOME / container-configs to share\n * yml profiles across machines (CLAUDE.md: \"Synchronisation ist eine\n * Frage von git-Repos\"); the per-container env files carry the secrets\n * those yml's reference and must never ride along. Idempotent: leaves an\n * existing pattern + any builder-added rules untouched.\n */\nexport async function ensureEnvGitignored(configsDir: string): Promise<void> {\n const gitignorePath = path.join(configsDir, '.gitignore');\n const pattern = '*.env';\n let existing = '';\n if (existsSync(gitignorePath)) {\n existing = readFileSync(gitignorePath, 'utf8');\n const lines = existing.split(/\\r?\\n/).map((l) => l.trim());\n if (lines.includes(pattern)) return;\n }\n const prefix = existing.length > 0 && !existing.endsWith('\\n') ? '\\n' : '';\n const header =\n existing.length === 0\n ? '# Per-container env files hold the secrets behind the yml ${VAR}\\n# references. Never commit them.\\n'\n : '';\n await fsp.appendFile(gitignorePath, `${prefix}${header}${pattern}\\n`);\n}\n\n// `${VAR}` only — the explicit-brace form. A bare `$VAR` is left alone\n// so a literal env value that happens to contain `$` (a generated\n// password, a shell snippet in `command`) survives untouched.\nconst VAR_RE = /\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g;\n\nexport interface InterpolateResult {\n value: string;\n /** Names referenced by the input that were absent from `vars`. */\n missing: string[];\n}\n\nexport function interpolate(\n value: string,\n vars: Record<string, string>,\n): InterpolateResult {\n const missing: string[] = [];\n const out = value.replace(VAR_RE, (_match, name: string) => {\n if (Object.prototype.hasOwnProperty.call(vars, name)) return vars[name]!;\n missing.push(name);\n return _match; // leave the literal `${VAR}` so the failure is visible\n });\n return { value: out, missing };\n}\n\nexport interface MissingVar {\n /** Dotted path to the field, e.g. `services.postgres.env.POSTGRES_PASSWORD`\n * or `features.ghcr.io/…:1.apiKey`. */\n location: string;\n name: string;\n}\n\nexport interface InterpolateServicesResult {\n services: ResolvedService[];\n missing: MissingVar[];\n}\n\n/**\n * Substitute `${VAR}` across every string field of each service —\n * image, env values, volume specs, command and the healthcheck test /\n * timing strings. Collects every unresolved reference (across all\n * services and fields) so the caller can fail the apply once with a\n * complete list rather than one var at a time.\n */\nexport function interpolateServices(\n services: ResolvedService[],\n vars: Record<string, string>,\n): InterpolateServicesResult {\n const missing: MissingVar[] = [];\n const resolved = services.map((svc) => {\n const interp = (raw: string, field: string): string => {\n const r = interpolate(raw, vars);\n for (const name of r.missing) {\n missing.push({ location: `services.${svc.name}.${field}`, name });\n }\n return r.value;\n };\n\n const next: ResolvedService = {\n ...svc,\n image: interp(svc.image, 'image'),\n env: Object.fromEntries(\n Object.entries(svc.env).map(([k, v]) => [k, interp(v, `env.${k}`)]),\n ),\n volumes: svc.volumes.map((v, i) => interp(v, `volumes[${i}]`)),\n };\n if (svc.command !== undefined) {\n next.command = interp(svc.command, 'command');\n }\n if (svc.healthcheck) {\n const hc = svc.healthcheck;\n next.healthcheck = {\n ...hc,\n test: Array.isArray(hc.test)\n ? hc.test.map((t, i) => interp(t, `healthcheck.test[${i}]`))\n : interp(hc.test, 'healthcheck.test'),\n ...(hc.interval !== undefined\n ? { interval: interp(hc.interval, 'healthcheck.interval') }\n : {}),\n ...(hc.timeout !== undefined\n ? { timeout: interp(hc.timeout, 'healthcheck.timeout') }\n : {}),\n ...(hc.startPeriod !== undefined\n ? { startPeriod: interp(hc.startPeriod, 'healthcheck.startPeriod') }\n : {}),\n };\n }\n return next;\n });\n return { services: resolved, missing };\n}\n\n/**\n * Env var names for the scaffolded container git-identity placeholders.\n * Single source for: the `${VAR}` rendered into the yml (init generator /\n * add-repo) and the keys seeded blank into `<name>.env`. Resolved at\n * apply time; blank → the identity cascade fills it.\n */\nexport const GIT_IDENTITY_VAR = {\n name: 'GIT_USER_NAME',\n email: 'GIT_USER_EMAIL',\n} as const;\n\n/** True if the string contains at least one `${VAR}` reference. */\nexport function hasVarPlaceholder(value: string): boolean {\n // Local non-global regex — VAR_RE carries the `g` flag and `.test`\n // on it is stateful (lastIndex), which would make repeated calls flaky.\n return /\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}/.test(value);\n}\n\nexport interface ResolvedGitUserField {\n /**\n * The usable, non-empty resolved value, or undefined when the field\n * has NO usable value — meaning the caller should climb the identity\n * cascade. \"No usable value\" collapses three cases that are all\n * equivalent for git identity:\n * - the field was absent,\n * - it referenced a `${VAR}` missing from the env (`${X}` survives), or\n * - it resolved to empty / whitespace (a seeded-but-blank `X=` in\n * `<name>.env`).\n * This mirrors how the schema already treats an empty *literal* as\n * \"unset\" — an empty `${VAR}` value must behave the same.\n */\n value?: string;\n}\n\nexport interface ResolvedGitUser {\n name: ResolvedGitUserField;\n email: ResolvedGitUserField;\n}\n\n/**\n * Resolve `${VAR}` in a git identity's name + email against the env\n * file, per field. Unlike services/features, an unresolved/empty value\n * is NOT an error: the caller treats a missing `value` as \"climb the\n * cascade\" (monoceros-config defaults → host → prompt). The email\n * FORMAT of a present value is the caller's check (see `isValidEmail`).\n */\nexport function resolveGitUserFields(\n user: { name?: string; email?: string },\n vars: Record<string, string>,\n): ResolvedGitUser {\n const resolve = (raw: string | undefined): ResolvedGitUserField => {\n if (raw === undefined) return {};\n const r = interpolate(raw, vars);\n // A missing var leaves the literal `${...}` in the value; treat that\n // and an empty/whitespace resolution alike — no usable value.\n if (r.missing.length > 0) return {};\n const trimmed = r.value.trim();\n return trimmed.length > 0 ? { value: trimmed } : {};\n };\n return { name: resolve(user.name), email: resolve(user.email) };\n}\n\ninterface FeatureLike {\n ref: string;\n options?: Record<string, string | number | boolean>;\n}\n\n/**\n * Resolve `${VAR}` in feature option *string* values against the env\n * file, BEFORE the options are merged with the monoceros-config\n * `defaults.features` cascade (config/transform.ts).\n *\n * Unlike services, an unresolved/empty feature option is NOT an error:\n * a string value that references a missing var, OR resolves to\n * empty/whitespace, becomes `\"\"`. The transform's merge then skips\n * empty-string container options, so the option falls through to the\n * global default (or stays unset — e.g. an empty `apiKey` means the\n * feature uses its OAuth/login path). A resolved non-empty value\n * overrides the default. This is why feature credential placeholders\n * can be rendered ACTIVE in the yml (`apiKey: ${VAR}`) with a blank\n * `.env` seed: blank → unset, filled → used. Non-string options\n * (booleans, numbers) pass through untouched.\n *\n * Must run before the transform merge — at that point `apiKey: ${VAR}`\n * is still a non-empty string and would wrongly override the default.\n */\nexport function interpolateFeatureOptions<T extends FeatureLike>(\n features: readonly T[],\n vars: Record<string, string>,\n): T[] {\n return features.map((f) => {\n if (!f.options) return f;\n const opts: Record<string, string | number | boolean> = {};\n for (const [key, value] of Object.entries(f.options)) {\n if (typeof value !== 'string') {\n opts[key] = value;\n continue;\n }\n const r = interpolate(value, vars);\n // Missing var (leaves the literal `${...}`) or an empty/whitespace\n // resolution → \"\" so the transform's empty-skip inherits the\n // default / leaves it unset. Otherwise the trimmed resolved value.\n opts[key] = r.missing.length > 0 ? '' : r.value.trim();\n }\n return { ...f, options: opts };\n });\n}\n\n/**\n * Short, builder-facing header for a fresh `<name>.env`. Explains what\n * the file is for; no real keys (a freshly-generated yml has no active\n * `${VAR}` yet — curated services use literal dev-defaults).\n */\nexport function buildEnvStub(name: string): string {\n return `# Secrets and values for \\${VAR} references in ${name}.yml.\\n`;\n}\n\nexport interface EnsureEnvVarsResult {\n /** True when the env file did not exist and was created. */\n created: boolean;\n /** Var keys that were appended (absent before). */\n added: string[];\n}\n\n/**\n * Upsert `<name>.env`: create it with the header stub if absent, and\n * append a line for every requested var that isn't already present.\n * Never overwrites existing keys or values.\n *\n * `vars` takes two forms:\n * - `string[]` → seed each as `KEY=` (blank). Used by `add-feature`\n * / `init` for credential placeholders the builder must fill.\n * - `Record<KEY, default>` → seed each as `KEY=<default>`. Used for\n * curated services, which ship working dev-defaults the builder can\n * keep as-is or change in one place.\n */\nexport async function ensureEnvVars(\n envPath: string,\n name: string,\n vars: readonly string[] | Readonly<Record<string, string>>,\n): Promise<EnsureEnvVarsResult> {\n const entries: Array<[string, string]> = Array.isArray(vars)\n ? vars.map((v) => [v, ''])\n : Object.entries(vars);\n const exists = existsSync(envPath);\n let content = exists ? readFileSync(envPath, 'utf8') : buildEnvStub(name);\n const present = new Set(Object.keys(parseEnvFile(content)));\n const seen = new Set<string>();\n const toAdd = entries.filter(([k]) => {\n if (present.has(k) || seen.has(k)) return false;\n seen.add(k);\n return true;\n });\n const added = toAdd.map(([k]) => k);\n if (!exists || added.length > 0) {\n if (content.length > 0 && !content.endsWith('\\n')) content += '\\n';\n for (const [k, v] of toAdd) content += `${k}=${v}\\n`;\n await fsp.mkdir(path.dirname(envPath), { recursive: true });\n await fsp.writeFile(envPath, content);\n }\n return { created: !exists, added };\n}\n\n/**\n * Format an actionable error for unresolved `${VAR}` references — names\n * the missing vars, where they're referenced, and the env file the\n * builder should define them in.\n */\nexport function formatMissingVarsError(\n missing: MissingVar[],\n envPathPretty: string,\n): string {\n const lines = missing.map((m) => ` - \\${${m.name}} (${m.location})`);\n const uniqueNames = [...new Set(missing.map((m) => m.name))];\n return (\n `Unresolved \\${VAR} references in the container yml:\\n${lines.join('\\n')}\\n\\n` +\n `Define them in ${envPathPretty}, e.g.\\n` +\n uniqueNames.map((n) => ` ${n}=<value>`).join('\\n')\n );\n}\n","import type { FeatureManifestSummary } from './manifest.js';\n\n/**\n * Shared per-feature header builder. Both `init`'s yml generator and\n * `add-feature`'s AST mutator consume this: the generator emits each\n * line as a `# `-prefixed string in a line-array, the mutator joins\n * the lines into a yaml-lib `commentBefore` string on the new\n * feature pair.\n *\n * Returns the wrapped paragraph lines WITHOUT a `#` prefix or leading\n * space — the consumer adds whichever convention applies (`# Foo` for\n * the generator, ` Foo` for yaml-lib's stored-after-`#` form).\n *\n * Format mirrors `monoceros-config.sample.yml`'s per-feature blocks:\n * - `<Name> — <description>` (one paragraph, wrapped)\n * - `<usageNote>` (one paragraph per note, wrapped)\n * - `Options: <key> (<short-desc>), …` (wrapped)\n * - `See <documentationURL> for further information.`\n *\n * An empty / unknown manifest summary returns `[]` — the caller emits\n * just the `- ref:` line without prose. Same fallback shape as the\n * generator's documented-mode third-party path.\n */\nexport function buildFeatureHeaderLines(\n summary: FeatureManifestSummary | undefined,\n width: number,\n): string[] {\n const paragraphs = buildHeaderParagraphs(summary);\n const wrapped: string[] = [];\n for (const para of paragraphs) {\n for (const line of wrapToComment(para, width)) {\n wrapped.push(line);\n }\n }\n return wrapped;\n}\n\n/**\n * Same as `buildFeatureHeaderLines` but each line gets a leading\n * single space, matching yaml-lib's `commentBefore` storage\n * convention (which prepends `#` and prints the body verbatim). Use\n * when setting `commentBefore` on a Scalar or Pair node.\n */\nexport function buildFeatureHeaderCommentBefore(\n summary: FeatureManifestSummary | undefined,\n width: number,\n): string {\n const lines = buildFeatureHeaderLines(summary, width);\n return lines.map((l) => ` ${l}`).join('\\n');\n}\n\nfunction buildHeaderParagraphs(\n summary: FeatureManifestSummary | undefined,\n): string[] {\n if (!summary) return [];\n const out: string[] = [];\n const tagline = summary.name?.trim();\n const description = summary.description?.trim();\n if (tagline && description) {\n out.push(`${tagline} — ${description}`);\n } else if (tagline) {\n out.push(tagline);\n } else if (description) {\n out.push(description);\n }\n for (const note of summary.usageNotes) {\n const trimmed = note.trim();\n if (trimmed.length > 0) out.push(trimmed);\n }\n if (summary.optionHints.length > 0) {\n const parts = summary.optionHints.map((key) => {\n const desc = summary.optionDescriptions[key];\n const short = desc ? shortenOptionDescription(desc) : undefined;\n return short ? `${key} (${short})` : key;\n });\n out.push(`Options: ${parts.join(', ')}.`);\n }\n if (summary.documentationURL) {\n out.push(`See ${summary.documentationURL} for further information.`);\n }\n return out;\n}\n\n/**\n * Trim a per-option `description` to a parenthetical hint — first\n * sentence, trailing punctuation stripped. Length cap is intentionally\n * absent: feature manifests are expected to keep descriptions terse;\n * the wrap function downstream handles line breaks naturally.\n */\nfunction shortenOptionDescription(desc: string): string {\n const firstSentence = desc.split(/(?<=[.!?])\\s+/)[0]?.trim() ?? desc.trim();\n return firstSentence.replace(/[.!?]+$/, '').trim();\n}\n\n/**\n * Word-wrap a single paragraph of plain text to `width` columns. The\n * returned strings do NOT include any prefix — the caller is expected\n * to prepend a comment marker (`# `) and indent. Long words that\n * exceed `width` are emitted on their own line rather than split\n * mid-word.\n */\nexport function wrapToComment(text: string, width: number): string[] {\n const words = text.split(/\\s+/).filter((w) => w.length > 0);\n if (words.length === 0) return [''];\n const usable = Math.max(width, 20);\n const lines: string[] = [];\n let current = '';\n for (const w of words) {\n if (current.length === 0) {\n current = w;\n continue;\n }\n if (current.length + 1 + w.length <= usable) {\n current += ' ' + w;\n } else {\n lines.push(current);\n current = w;\n }\n }\n if (current.length > 0) lines.push(current);\n return lines;\n}\n\n/** Default comment width matching the generator's. */\nexport const FEATURE_HEADER_WIDTH = 76 - 2; // COMMENT_WIDTH - \"# \" prefix\n\n/**\n * Derive the `<name>.env` variable name for a feature option, used as\n * the `${VAR}` placeholder in the yml and the seeded key in the env\n * file. Generic rule `<FEATURE_ID>_<OPTION>`, applied uniformly:\n * atlassian:1 + apiToken → ATLASSIAN_API_TOKEN\n * claude-code:1 + apiKey → CLAUDE_CODE_API_KEY\n * github-cli:1 + bitbucketToken→ GITHUB_CLI_BITBUCKET_TOKEN\n *\n * It is a monoceros-side placeholder key — NOT the env var the tool\n * itself reads (the value is passed as the feature's option), so a\n * predictable derived name is honest and avoids per-feature special\n * cases.\n */\nexport function featureOptionVarName(ref: string, optionKey: string): string {\n const leaf = ref.split('/').pop() ?? ref;\n const id = leaf.split('@')[0]!.split(':')[0]!;\n const idSnake = id.replace(/[^A-Za-z0-9]+/g, '_').toUpperCase();\n const optSnake = optionKey\n .replace(/([a-z0-9])([A-Z])/g, '$1_$2')\n .replace(/[^A-Za-z0-9]+/g, '_')\n .toUpperCase();\n return `${idSnake}_${optSnake}`;\n}\n\nexport interface FeatureOptionHint {\n /** Option key, e.g. `apiToken`. */\n key: string;\n /** Derived env var name, e.g. `ATLASSIAN_API_TOKEN`. */\n envVar: string;\n /** yml placeholder, e.g. `${ATLASSIAN_API_TOKEN}`. */\n placeholder: string;\n}\n\n/**\n * The credential-bearing option hints for a feature (from the manifest's\n * `x-monoceros.optionHints`), minus any keys already set with an active\n * value. Shared by the init generator (renders `${VAR}` hint lines) and\n * `add-feature` (renders the same as a node comment) and the `.env`\n * seeding (uses `envVar`). Empty for unknown/third-party refs (no\n * manifest → no hints).\n */\nexport function featureOptionHints(\n summary: FeatureManifestSummary | undefined,\n ref: string,\n activeKeys: readonly string[] = [],\n): FeatureOptionHint[] {\n return (summary?.optionHints ?? [])\n .filter((key) => !activeKeys.includes(key))\n .map((key) => {\n const envVar = featureOptionVarName(ref, key);\n return { key, envVar, placeholder: `\\${${envVar}}` };\n });\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport path from 'node:path';\nimport { bundledFeaturesDir, workbenchCheckoutRoot } from '../config/paths.js';\nimport { matchMonocerosFeature } from '../util/ref.js';\n\n/**\n * Loader for the parts of a Monoceros devcontainer-feature manifest\n * that init's yml-generator wants to surface as inline guidance:\n *\n * - `name` / `description` — the feature's tagline / prose, copied\n * verbatim from the standard devcontainer-feature.json top-level\n * fields. The generator builds the header comment block from\n * these — no fallback prose lives in the generator itself.\n * - `documentationURL` — copied verbatim. The generator emits a\n * \"See <url> for further information.\" line when this is a real\n * URL. Empty / missing / literal \"tbd\" → line omitted.\n * - `optionHints` — names of feature options the generator emits\n * as commented lines under the rendered `options:` block, so the\n * builder sees what's settable without opening the docs.\n * - `optionDescriptions` — per-option `description` from the\n * manifest. The generator weaves these into the per-feature\n * \"Options: …\" summary comment.\n * - `usageNotes` — free-text per-feature paragraphs from\n * `x-monoceros.usageNotes`. Concatenated into the header prose\n * after `description`.\n *\n * Only Monoceros-owned refs\n * (`ghcr.io/getmonoceros/monoceros-features/<name>:<tag>`) are\n * resolved — for third-party features we don't have the manifest on\n * disk at init time. The fallback is \"no hints\", which is right:\n * we don't speculate about other people's feature options.\n *\n * Manifest lookup order:\n * 1. Workbench checkout — `<checkoutRoot>/images/features/<name>/`.\n * Dev edits to the source-of-truth manifest are visible\n * immediately, no rebuild step required.\n * 2. CLI bundle — `<workbenchRoot>/features/<name>/`. Populated\n * by `pnpm manifests:sync` (runs as `prebuild`), shipped in\n * the npm tarball. Production fallback for builders without a\n * workbench checkout.\n *\n * Both paths missing → `undefined` and init renders without hints.\n * Never throws.\n */\n\nexport interface FeatureManifestSummary {\n /** `name` field — short product/tagline. Empty string when unset. */\n name: string;\n /** `description` field — multi-sentence prose. Empty string when unset. */\n description: string;\n /**\n * `documentationURL` — only set when it's a real URL.\n * `tbd` / `TBD` / empty / unset → undefined. The generator uses\n * this to suppress the \"See <url>…\" line when no real docs exist\n * yet, so the file doesn't fill with placeholders.\n */\n documentationURL: string | undefined;\n /** Names of options to render as commented hints in the init output. */\n optionHints: string[];\n /** `description` from each option in the manifest, keyed by name. */\n optionDescriptions: Record<string, string>;\n /**\n * ALL option keys the manifest declares, in declaration order. Used\n * by shell completion for `add-feature -- key=value`: the builder\n * can set any option, not just the subset surfaced as hints in\n * init's commented-out block.\n */\n optionNames: string[];\n /**\n * `type` from each option in the manifest, keyed by name. Limited\n * to `'string'` / `'boolean'` today — what the devcontainer-feature\n * spec supports. Used by completion to suggest `true`/`false` after\n * `--<bool-key>=`.\n */\n optionTypes: Record<string, 'string' | 'boolean'>;\n /** Free-text per-feature notes rendered above the `- ref:` line. */\n usageNotes: string[];\n}\n\ninterface RawManifestOption {\n type?: unknown;\n description?: unknown;\n}\n\ninterface RawManifest {\n name?: string;\n description?: string;\n documentationURL?: string;\n options?: Record<string, RawManifestOption>;\n 'x-monoceros'?: {\n optionHints?: unknown;\n usageNotes?: unknown;\n };\n}\n\nfunction resolveManifestPath(\n name: string,\n checkoutRoot: string | null,\n): string | null {\n if (checkoutRoot) {\n const checkoutPath = path.join(\n checkoutRoot,\n 'images',\n 'features',\n name,\n 'devcontainer-feature.json',\n );\n if (existsSync(checkoutPath)) return checkoutPath;\n }\n const bundlePath = path.join(\n bundledFeaturesDir(),\n name,\n 'devcontainer-feature.json',\n );\n if (existsSync(bundlePath)) return bundlePath;\n return null;\n}\n\nexport function loadFeatureManifestSummary(\n ref: string,\n checkoutRoot: string | null = workbenchCheckoutRoot(),\n): FeatureManifestSummary | undefined {\n const match = matchMonocerosFeature(ref);\n if (!match) return undefined;\n const manifestPath = resolveManifestPath(match.name, checkoutRoot);\n if (!manifestPath) return undefined;\n try {\n const text = readFileSync(manifestPath, 'utf8');\n const parsed = JSON.parse(text) as RawManifest;\n\n const rawHints = parsed['x-monoceros']?.optionHints;\n const optionHints = Array.isArray(rawHints)\n ? rawHints.filter(\n (x): x is string => typeof x === 'string' && x.length > 0,\n )\n : [];\n\n const rawNotes = parsed['x-monoceros']?.usageNotes;\n const usageNotes = Array.isArray(rawNotes)\n ? rawNotes.filter(\n (x): x is string => typeof x === 'string' && x.length > 0,\n )\n : [];\n\n const optionDescriptions: Record<string, string> = {};\n const optionTypes: Record<string, 'string' | 'boolean'> = {};\n const optionNames: string[] = [];\n if (parsed.options) {\n for (const [key, opt] of Object.entries(parsed.options)) {\n if (!opt || typeof opt !== 'object') continue;\n optionNames.push(key);\n if (typeof opt.description === 'string' && opt.description.length > 0) {\n optionDescriptions[key] = opt.description;\n }\n if (opt.type === 'boolean') {\n optionTypes[key] = 'boolean';\n } else if (opt.type === 'string') {\n optionTypes[key] = 'string';\n }\n }\n }\n\n const name = typeof parsed.name === 'string' ? parsed.name : '';\n const description =\n typeof parsed.description === 'string' ? parsed.description : '';\n const rawUrl =\n typeof parsed.documentationURL === 'string'\n ? parsed.documentationURL.trim()\n : '';\n const documentationURL =\n rawUrl.length > 0 && rawUrl.toLowerCase() !== 'tbd' ? rawUrl : undefined;\n\n return {\n name,\n description,\n documentationURL,\n optionHints,\n optionDescriptions,\n optionNames,\n optionTypes,\n usageNotes,\n };\n } catch {\n return undefined;\n }\n}\n","/**\n * Shape detection for OCI refs that point at Monoceros-owned\n * devcontainer features. Two regexes live here:\n *\n * - `MONOCEROS_FEATURE_RE` matches the current ref shape\n * (`ghcr.io/getmonoceros/monoceros-features/<name>:<tag>`),\n * which is what `monoceros init` writes and what GHCR serves.\n *\n * - `DEPRECATED_MONOCEROS_FEATURE_RE` matches the legacy shape\n * (`ghcr.io/monoceros/features/<name>:<tag>`) that was used\n * before the M4 cut. We keep this only to emit a migration\n * warning at `apply` time when a builder's yml still carries\n * the old ref.\n */\n\nconst FEATURE_NAME_CHARSET = '[a-z0-9._-]+';\nconst FEATURE_TAG_CHARSET = '[a-z0-9._-]+';\n\nexport const MONOCEROS_FEATURE_RE = new RegExp(\n `^ghcr\\\\.io/getmonoceros/monoceros-features/(${FEATURE_NAME_CHARSET}):${FEATURE_TAG_CHARSET}$`,\n);\n\nexport const DEPRECATED_MONOCEROS_FEATURE_RE = new RegExp(\n `^ghcr\\\\.io/monoceros/features/(${FEATURE_NAME_CHARSET}):(${FEATURE_TAG_CHARSET})$`,\n);\n\n/**\n * Extract `{ name }` from a current-shape Monoceros feature ref,\n * or `null` for anything else (third-party features, deprecated\n * shape, malformed input).\n */\nexport function matchMonocerosFeature(ref: string): { name: string } | null {\n const match = MONOCEROS_FEATURE_RE.exec(ref);\n if (!match) return null;\n return { name: match[1]! };\n}\n\n/**\n * Translate a legacy-shape ref into the current-shape ref it would\n * map to (`ghcr.io/monoceros/features/<name>:<tag>` →\n * `ghcr.io/getmonoceros/monoceros-features/<name>:<tag>`). Returns\n * `null` for refs that don't match the legacy shape, so callers can\n * use it as a \"should I warn?\" check.\n */\nexport function migrateDeprecatedFeatureRef(ref: string): string | null {\n const match = DEPRECATED_MONOCEROS_FEATURE_RE.exec(ref);\n if (!match) return null;\n const name = match[1]!;\n const tag = match[2]!;\n return `ghcr.io/getmonoceros/monoceros-features/${name}:${tag}`;\n}\n","import { spawn } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { RepoEntry } from '../create/types.js';\nimport { KNOWN_PROVIDER_HOSTS, type RepoProvider } from '../config/schema.js';\nimport { cyan, dim } from '../util/format.js';\n\n/**\n * Spawn signature for `git credential fill`: takes the credential-\n * protocol input on stdin, returns the helper's response on stdout\n * plus the process exit code. Injected by tests.\n */\nexport type CredentialsSpawn = (\n input: string,\n) => Promise<{ stdout: string; exitCode: number }>;\n\nconst realGitCredentialFill: CredentialsSpawn = (input) => {\n return new Promise((resolve, reject) => {\n // GIT_TERMINAL_PROMPT=0 disables git's interactive\n // username/password fallback. Without this, when no credential\n // helper has an entry for the host, `git credential fill` would\n // open /dev/tty and prompt the user — which hangs apply\n // indefinitely because the parent process is running non-\n // interactively. With the env var set, git returns whatever\n // the helpers produced (possibly empty) and exits cleanly,\n // letting our pre-flight detect \"no credentials\" reliably.\n //\n // We deliberately do NOT also set GIT_ASKPASS='' / SSH_ASKPASS=''.\n // Empty string is interpreted differently across git versions, and\n // — concretely observed on Windows + Git Credential Manager — it\n // tickles a path where GCM's `store` silently no-ops after a\n // successful OAuth flow. The credential helper IS the right tool\n // for non-interactive credential resolution; the terminal-prompt\n // gate above already takes care of the hang scenario this was\n // meant to guard against.\n const child = spawn('git', ['credential', 'fill'], {\n stdio: ['pipe', 'pipe', 'inherit'],\n env: {\n ...process.env,\n GIT_TERMINAL_PROMPT: '0',\n },\n });\n let stdout = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.on('error', reject);\n child.on('exit', (code) => resolve({ stdout, exitCode: code ?? 0 }));\n child.stdin.write(input);\n child.stdin.end();\n });\n};\n\n/**\n * Tell git's configured credential helpers to persist a credential.\n *\n * Why this exists: `git credential fill` returns credentials but never\n * tells the helper to save them — that step normally happens AFTER git\n * has used the credential successfully against a remote, when git\n * itself calls `git credential approve`. Our pre-flight uses `fill`\n * for a lookup-only check, so the helper's `store` is never reached\n * by the natural git flow, and the OAuth-acquired token GCM returned\n * gets thrown away. Next apply: browser dialog again.\n *\n * Calling `approve` explicitly after a successful `fill` closes the\n * loop: GCM (and gh's helper, and the Atlassian one) write the\n * credential to their persistent store on this call, so subsequent\n * applies (and the in-container clone) find it cached. Idempotent —\n * approve on an already-stored credential is a no-op.\n */\nexport type CredentialsApprove = (input: string) => Promise<void>;\n\nconst realGitCredentialApprove: CredentialsApprove = (input) => {\n return new Promise((resolve, reject) => {\n const child = spawn('git', ['credential', 'approve'], {\n stdio: ['pipe', 'ignore', 'inherit'],\n env: {\n ...process.env,\n GIT_TERMINAL_PROMPT: '0',\n },\n });\n child.on('error', reject);\n child.on('exit', () => resolve()); // best-effort, non-zero is non-fatal\n child.stdin.write(input);\n child.stdin.end();\n });\n};\n\n/**\n * Resolve a host's provider:\n * - canonical hosts (github.com / gitlab.com / bitbucket.org) →\n * their fixed provider, ignoring any explicit hint (the canonical\n * mapping is the source of truth)\n * - any other host → the explicit hint if given, else 'unknown'\n *\n * Returning 'unknown' triggers the apply pre-flight error that asks\n * the builder to set `provider:` in the yml. We deliberately never\n * guess from hostname patterns (\"starts with `gitlab.`\" etc.) —\n * those produced wrong results for corporate domains like\n * `git.firma.de` and silently fell through to the generic hint.\n */\nexport type ResolvedProvider = RepoProvider | 'unknown';\n\nexport function resolveProvider(\n host: string,\n explicit?: RepoProvider,\n): ResolvedProvider {\n const canonical = KNOWN_PROVIDER_HOSTS[host.toLowerCase()];\n if (canonical) return canonical;\n return explicit ?? 'unknown';\n}\n\nexport interface HostWithProvider {\n host: string;\n provider: ResolvedProvider;\n}\n\n/**\n * Reduce a repo list to one entry per host, carrying along the\n * resolved provider so the pre-flight check can render the right\n * setup hint (or error out with \"set provider:\" for unknowns).\n *\n * If the same host appears with conflicting provider declarations\n * across multiple repo entries, the first one wins — the apply\n * pre-flight surfaces the conflict as a separate diagnostic before\n * we get here in normal flow. (Schema-level dedup would lock us in\n * before the builder ever sees the warning.)\n */\nfunction uniqueHttpsHosts(repos: readonly RepoEntry[]): HostWithProvider[] {\n const byHost = new Map<string, HostWithProvider>();\n for (const repo of repos) {\n if (!repo.url.startsWith('https://')) continue;\n let host: string;\n try {\n host = new URL(repo.url).hostname;\n } catch {\n // Skip malformed URLs — validateOptions catches them at the\n // add-repo step, so reaching this in production means a stack\n // file was hand-edited. Don't fail the whole apply for it.\n continue;\n }\n if (byHost.has(host)) continue;\n byHost.set(host, { host, provider: resolveProvider(host, repo.provider) });\n }\n return [...byHost.values()];\n}\n\n/**\n * Render a provider-specific install command, filtered to the host\n * OS. Returns the relevant line for the current platform — macOS gets\n * the brew command, Windows gets the winget command, Linux falls back\n * to a docs link unless the provider officially recommends a uniform\n * Linux command (then `linuxBrew` is set and used). Callers embed the\n * resulting single line into a setup-instructions block.\n */\n/**\n * Official Homebrew install one-liner (from https://brew.sh). Shown\n * as the first cyan line in any brew-based provider hint so users\n * who don't have Homebrew yet aren't stuck staring at `brew: command\n * not found`. Users who DO have Homebrew can skip the line — we say\n * so right below in dim text.\n */\nconst BREW_INSTALL_COMMAND =\n '/bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"';\n\nfunction installCommandForOS(opts: {\n brew: string;\n /**\n * Linux install command when the provider's docs officially\n * recommend a single uniform path (e.g. GitLab's glab CLI ships\n * Homebrew as the supported Linux install method). When omitted,\n * Linux gets a docs link instead because distro packaging is too\n * heterogeneous to pick a winner. Windows users hit this branch\n * too — Monoceros on Windows runs inside WSL, so the host IS Linux.\n */\n linuxBrew?: string;\n linuxDocsUrl: string;\n}): string {\n const withBrewBootstrap = (cmd: string): string =>\n [\n '',\n cyan(BREW_INSTALL_COMMAND),\n cyan(cmd),\n '',\n dim('(Skip the first line if you already have Homebrew.)'),\n ].join('\\n');\n if (process.platform === 'darwin') return withBrewBootstrap(opts.brew);\n // Linux + WSL (Windows runs Monoceros inside WSL as of 1.12).\n if (opts.linuxBrew) return withBrewBootstrap(opts.linuxBrew);\n return `See ${opts.linuxDocsUrl} for package instructions.`;\n}\n\n/**\n * Provider-specific setup hint per host. Used in the pre-flight\n * error message when `git credential fill` returns nothing for a\n * host. Shows only the install command for the current host OS —\n * less visual noise, no \"is this me?\" guesswork for the builder.\n *\n * Provider is resolved upstream (canonical-host lookup or explicit\n * yml field). This function NEVER guesses from hostname patterns;\n * see `resolveProvider` for the rationale.\n */\nexport function providerSetupHint(\n host: string,\n provider: RepoProvider,\n): {\n /** Short title for the host, formatted as \"host — Provider\". */\n title: string;\n /** Multiline body, left-aligned, no leading indentation. */\n body: string;\n} {\n if (provider === 'github') {\n // `--hostname` is only needed for self-hosted GitHub Enterprise\n // Server. For github.com (SaaS) gh defaults to that host, so we\n // omit the flag. Both `gh auth login` and `gh auth setup-git`\n // accept --hostname with identical semantics — verified against\n // https://cli.github.com/manual/gh_auth_login and\n // https://cli.github.com/manual/gh_auth_setup-git .\n const isSaas = host.toLowerCase() === 'github.com';\n const hostArg = isSaas ? '' : ` --hostname ${host}`;\n // GitHub CLI publishes a Linuxbrew formula alongside the macOS\n // one (https://github.com/cli/cli/blob/trunk/docs/install_linux.md\n // lists Homebrew under \"Linux & WSL\"), so the same brew command\n // works on macOS, Linux and WSL.\n const install = installCommandForOS({\n brew: 'brew install gh',\n linuxBrew: 'brew install gh',\n linuxDocsUrl: 'https://github.com/cli/cli#installation',\n });\n return {\n title: `${host} — GitHub`,\n body: [\n 'Install the GitHub CLI:',\n install,\n '',\n 'Then run once:',\n cyan(`gh auth login${hostArg}`),\n cyan(`gh auth setup-git${hostArg}`),\n '',\n '`gh auth login` walks through OAuth in your browser.',\n '`gh auth setup-git` wires gh into git as a credential helper.',\n ].join('\\n'),\n };\n }\n if (provider === 'gitlab') {\n // `--hostname` is only needed for self-hosted GitLab. For\n // gitlab.com glab defaults to the SaaS host, so we omit the flag.\n const isSaas = host.toLowerCase() === 'gitlab.com';\n const hostArg = isSaas ? '' : ` --hostname ${host}`;\n // GitLab's official install docs (https://gitlab.com/gitlab-org/\n // cli/-/blob/main/docs/installation_options.md) state that\n // Homebrew is \"the officially supported installation method for\n // Linux\" — same brew command on macOS, Linux and WSL.\n const install = installCommandForOS({\n brew: 'brew install glab',\n linuxBrew: 'brew install glab',\n linuxDocsUrl: 'https://gitlab.com/gitlab-org/cli#installation',\n });\n return {\n title: `${host} — GitLab`,\n body: [\n 'Install the GitLab CLI (glab):',\n install,\n '',\n 'Then run once:',\n cyan(`glab auth login${hostArg}`),\n '',\n 'Choose `HTTPS` when asked for git-protocol, then accept',\n '\"Authenticate Git with your GitLab credentials\" — glab',\n 'configures itself as the git credential helper.',\n ].join('\\n'),\n };\n }\n if (provider === 'bitbucket') {\n // Bitbucket has no first-party CLI for git-credentials (no\n // `bb auth login` equivalent to gh/glab), so this is a manual\n // one-time setup either way. The Cloud and Data-Center variants\n // differ in where you get the token and what the username field\n // expects — same pattern as the github / gitlab branches above\n // (canonical SaaS host vs. self-hosted).\n const isCloud = host.toLowerCase() === 'bitbucket.org';\n if (isCloud) {\n return {\n title: `${host} — Bitbucket Cloud`,\n body: [\n 'Bitbucket has no first-party CLI for git-credentials, so this',\n 'is a manual one-time setup. Generate an Atlassian API token at',\n 'https://id.atlassian.com/manage-profile/security/api-tokens',\n '',\n 'Then store it via your OS credential helper:',\n cyan(\n `git credential approve <<< $'protocol=https\\\\nhost=${host}\\\\nusername=<your-atlassian-email>\\\\npassword=<token>\\\\n'`,\n ),\n ].join('\\n'),\n };\n }\n return {\n title: `${host} — Bitbucket Data Center`,\n body: [\n 'Bitbucket has no first-party CLI for git-credentials, so this',\n 'is a manual one-time setup. Generate a personal HTTP access',\n `token in your Bitbucket UI: profile picture (top right on ${host})`,\n '→ Manage account → HTTP access tokens → Create token. Give it',\n 'at least repo-read + repo-write scopes for the repos you need.',\n '',\n 'Then store it via your OS credential helper:',\n cyan(\n `git credential approve <<< $'protocol=https\\\\nhost=${host}\\\\nusername=<your-bitbucket-username>\\\\npassword=<token>\\\\n'`,\n ),\n ].join('\\n'),\n };\n }\n // provider === 'gitea' — Gitea is always self-hosted (gitea.com is\n // a demo / sandbox, not a SaaS), so there's no canonical-host\n // branch. The `tea` CLI exists but logs into its own config and\n // doesn't register as a git credential helper (verified against\n // https://gitea.com/gitea/tea), so we point at the UI flow + a\n // direct `git credential approve` — same pattern as Bitbucket\n // Data Center. Forgejo (the Gitea fork) shares this flow exactly.\n return {\n title: `${host} — Gitea`,\n body: [\n 'Gitea has no first-party CLI helper for git-credentials (the',\n '`tea` CLI logs into its own config, not into your git credential',\n 'helper), so this is a manual one-time setup. Generate an access',\n `token in your Gitea UI: profile picture (top right on ${host}) →`,\n 'Settings → Applications → \"Generate New Token\". Give it at',\n 'least the `read:repository` scope (add `write:repository` if you',\n 'need push from the container).',\n '',\n 'Then store it via your OS credential helper:',\n cyan(\n `git credential approve <<< $'protocol=https\\\\nhost=${host}\\\\nusername=<your-gitea-username>\\\\npassword=<token>\\\\n'`,\n ),\n ].join('\\n'),\n };\n}\n\ninterface ParsedCreds {\n username?: string;\n password?: string;\n}\n\nfunction parseCredentialFillOutput(output: string): ParsedCreds {\n const result: ParsedCreds = {};\n for (const line of output.split('\\n')) {\n const eqIdx = line.indexOf('=');\n if (eqIdx <= 0) continue;\n const key = line.slice(0, eqIdx);\n const value = line.slice(eqIdx + 1);\n if (key === 'username') result.username = value;\n if (key === 'password') result.password = value;\n }\n return result;\n}\n\nfunction formatCredentialLine(\n host: string,\n username: string,\n password: string,\n): string {\n // Both fields percent-encoded so a `@`, `:`, or `/` in the token\n // doesn't break URL parsing inside git's `store` helper.\n const encUser = encodeURIComponent(username);\n const encPass = encodeURIComponent(password);\n return `https://${encUser}:${encPass}@${host}`;\n}\n\nexport interface CollectCredentialsOptions {\n spawn?: CredentialsSpawn;\n /**\n * Approve callback — called once per host after a successful\n * `fill`, with the full credential-protocol payload (incl. password).\n * Tells the host's credential helper to persist the credential.\n * Defaults to `git credential approve`. Tests inject a stub that\n * records calls without spawning git.\n */\n approve?: CredentialsApprove;\n logger?: { info: (msg: string) => void; warn: (msg: string) => void };\n}\n\nexport interface HostCredentialStatus {\n host: string;\n /**\n * Resolved provider for this host — canonical lookup for the three\n * known hosts, explicit yml hint for anything else. Carried into\n * the failure message so `formatMissingCredentialsError` can render\n * the right setup block without re-resolving.\n */\n provider: RepoProvider;\n /** 'ok' when username+password came back from `git credential fill`. */\n status: 'ok' | 'no-credentials' | 'spawn-error' | 'non-zero-exit';\n /** Diagnostic text — empty when status is 'ok'. */\n detail: string;\n}\n\nexport interface CollectCredentialsResult {\n /** Hosts for which credentials were successfully written. */\n hostsWritten: number;\n /** Hosts for which `git credential fill` failed or returned no creds. */\n hostsSkipped: number;\n /** Per-host status (in input order). */\n perHost: HostCredentialStatus[];\n /** Absolute path to the written credentials file (always written, possibly empty). */\n credentialsPath: string;\n}\n\n/**\n * For each unique HTTPS host across the dev-container's repos, ask the\n * host-side git for credentials and write them to\n * `<devContainerRoot>/.monoceros/git-credentials`. The container's\n * post-create.sh configures git to read from that file via `store`\n * credential helper.\n *\n * Host-side `git credential fill` consults whatever helper the host\n * has configured (osxkeychain on macOS, manager on Windows, libsecret\n * on Linux). If a helper has the cached credentials, returns silent.\n * If not, the helper prompts the builder via its native UI\n * (Keychain-popup, GCM-window, terminal-prompt). That's the intended\n * UX — Monoceros never prompts directly, the host's helper does.\n *\n * Always writes the file (possibly empty) so the bind-mount target\n * exists in the container. A host that returns no credentials simply\n * yields a credentials file with no matching entries, and the in-\n * container `git clone` falls back to whatever default git would do\n * (which is to prompt — and there we lose, but the diagnostic is\n * clear).\n */\nexport async function collectGitCredentials(\n devContainerRoot: string,\n hosts: readonly HostWithProvider[],\n options: CollectCredentialsOptions = {},\n): Promise<CollectCredentialsResult> {\n const credsDir = path.join(devContainerRoot, '.monoceros');\n const credentialsPath = path.join(credsDir, 'git-credentials');\n\n const spawnFn = options.spawn ?? realGitCredentialFill;\n const approveFn = options.approve ?? realGitCredentialApprove;\n const logger = options.logger ?? { info: () => {}, warn: () => {} };\n\n // Callers must filter out 'unknown' providers before invoking this\n // function — those should fail the apply pre-flight earlier with a\n // \"set provider:\" error, never reach the credential helper. We\n // narrow the type here for the renderer's sake.\n const lines: string[] = [];\n const perHost: HostCredentialStatus[] = [];\n for (const { host, provider } of hosts) {\n if (provider === 'unknown') {\n // Defensive: should not happen — pre-flight is supposed to\n // bail before this. Record it anyway with no-credentials so\n // the caller doesn't see a partial success.\n perHost.push({\n host,\n provider: 'github', // placeholder — never rendered because pre-flight already bailed\n status: 'no-credentials',\n detail: 'provider not declared (internal: should not reach here)',\n });\n continue;\n }\n logger.info(`Fetching credentials for ${host} from host git…`);\n const input = `protocol=https\\nhost=${host}\\n\\n`;\n let result;\n try {\n result = await spawnFn(input);\n } catch (err) {\n // No logger.warn here — the caller (apply pre-flight) renders\n // a consolidated, provider-specific error message per failing\n // host. A separate WARN line per host would just add visual\n // noise above the actionable error.\n const detail = err instanceof Error ? err.message : String(err);\n perHost.push({ host, provider, status: 'spawn-error', detail });\n continue;\n }\n if (result.exitCode !== 0) {\n perHost.push({\n host,\n provider,\n status: 'non-zero-exit',\n detail: `exit code ${result.exitCode}`,\n });\n continue;\n }\n const { username, password } = parseCredentialFillOutput(result.stdout);\n if (!username || !password) {\n perHost.push({\n host,\n provider,\n status: 'no-credentials',\n detail: 'host credential helper returned no username/password',\n });\n continue;\n }\n lines.push(formatCredentialLine(host, username, password));\n perHost.push({ host, provider, status: 'ok', detail: '' });\n\n // Tell the host credential helper to persist the credential we\n // just received. `git credential fill` itself never triggers a\n // helper `store` — git only does that automatically after using\n // a credential successfully on a real remote operation. Without\n // this explicit approve, an OAuth flow that GCM kicked off on\n // first apply returns the token to us, but GCM never writes it\n // to the Windows Credential Manager. Result: every subsequent\n // apply pops a fresh browser auth dialog. Best-effort — non-zero\n // from approve is non-fatal; we still wrote the in-container\n // credentials file, which is what apply actually relies on.\n const approveInput = `protocol=https\\nhost=${host}\\nusername=${username}\\npassword=${password}\\n\\n`;\n try {\n await approveFn(approveInput);\n } catch {\n /* best-effort, don't block apply on credential-store hiccups */\n }\n }\n\n await fs.mkdir(credsDir, { recursive: true });\n await fs.writeFile(\n credentialsPath,\n lines.join('\\n') + (lines.length > 0 ? '\\n' : ''),\n {\n mode: 0o600,\n },\n );\n\n return {\n hostsWritten: lines.length,\n hostsSkipped: perHost.filter((p) => p.status !== 'ok').length,\n perHost,\n credentialsPath,\n };\n}\n\n/**\n * Expose `uniqueHttpsHosts` for callers that need the host list\n * directly (apply uses it to build the pre-flight check input).\n */\nexport { uniqueHttpsHosts };\n\n/**\n * Build the multi-host pre-flight error message that gets thrown when\n * apply discovers missing credentials. Header inlines the provider\n * for single-host cases; body is left-aligned setup instructions.\n *\n * Format:\n *\n * Missing Git credentials: <host> — <Provider>\n *\n * <setup instructions, left-aligned, multi-line>\n *\n * Then re-run `monoceros apply`.\n *\n * For multi-host failures, each block is separated by a blank line\n * and gets its own provider title.\n */\nexport function formatMissingCredentialsError(\n missing: readonly HostCredentialStatus[],\n): string {\n if (missing.length === 1) {\n const m = missing[0]!;\n const hint = providerSetupHint(m.host, m.provider);\n return [\n `Missing Git credentials: ${hint.title}`,\n '',\n hint.body,\n '',\n `Then re-run ${cyan('monoceros apply')}.`,\n ].join('\\n');\n }\n const lines: string[] = [\n `Missing Git credentials for ${missing.length} hosts:`,\n '',\n ];\n for (const m of missing) {\n const hint = providerSetupHint(m.host, m.provider);\n lines.push(hint.title);\n lines.push('');\n lines.push(hint.body);\n lines.push('');\n }\n lines.push(`Then re-run ${cyan('monoceros apply')}.`);\n return lines.join('\\n');\n}\n\n/**\n * Build the pre-flight error for repos whose host has no provider\n * declared and isn't one of the canonical ones (github.com /\n * gitlab.com / bitbucket.org). The builder needs to add a\n * `provider:` field to the yml before apply can continue.\n */\nexport function formatUnknownProviderError(hosts: readonly string[]): string {\n const sorted = [...new Set(hosts)].sort();\n const lines: string[] = [\n sorted.length === 1\n ? `Unknown Git provider for host ${sorted[0]!}.`\n : `Unknown Git provider for ${sorted.length} hosts: ${sorted.join(', ')}.`,\n '',\n 'Monoceros auto-detects only github.com / gitlab.com / bitbucket.org.',\n 'For any other host (self-hosted GitLab, Gitea, Bitbucket Server, …)',\n 'declare the provider explicitly in the yml. Edit the repo entry:',\n '',\n cyan(' repos:'),\n cyan(` - url: https://${sorted[0]!}/…`),\n cyan(' provider: gitlab # or: github, bitbucket, gitea'),\n '',\n `Or re-add with ${cyan('monoceros add-repo <name> <url> --provider=<github|gitlab|bitbucket|gitea>')}.`,\n ];\n return lines.join('\\n');\n}\n\n// Exported for tests.\nexport const _internals = {\n uniqueHttpsHosts,\n parseCredentialFillOutput,\n formatCredentialLine,\n};\n","// Shared terminal-formatting helpers for CLI output. Same palette\n// as install.sh and packages/cli/src/help.ts:\n//\n// cyan = identifiers you type (commands, refs, component names)\n// grey = supplementary metadata (paths, version notes, hints)\n// bold+und. = structural section markers\n//\n// Status semantics (green ✓, red ✗, yellow !) live in consola for\n// log-level lines and aren't duplicated here.\n//\n// Two flavours of consumer:\n// - status output (install.sh, apply) goes to stderr — use the\n// top-level helpers below; they gate on process.stderr.isTTY.\n// - data output (list-components) goes to stdout — use\n// `colorsFor(process.stdout)` to get the same helpers gated\n// against the right stream, so colours drop out cleanly when\n// the user pipes the output into grep/less/etc.\n\nconst ESC = '\\x1b[';\nconst ANSI_BOLD = `${ESC}1m`;\nconst ANSI_UNDERLINE = `${ESC}4m`;\nconst ANSI_CYAN = `${ESC}36m`;\nconst ANSI_GREY = `${ESC}90m`;\nconst ANSI_RESET = `${ESC}0m`;\n\n// eslint-disable-next-line no-control-regex\nconst ANSI_RE = /\\x1b\\[[0-9;]*m/g;\n\n/**\n * Visible character count, ANSI escape sequences stripped. Used\n * for column-padding so coloured labels still line up.\n */\nexport function visibleLen(s: string): number {\n return s.replace(ANSI_RE, '').length;\n}\n\n/**\n * Remove ANSI colour/style escape sequences from a string. Used by\n * the apply log sink so log files stay readable in plain `cat`.\n */\nexport function stripAnsi(s: string): string {\n return s.replace(ANSI_RE, '');\n}\n\nexport interface Palette {\n bold: (s: string) => string;\n underline: (s: string) => string;\n cyan: (s: string) => string;\n dim: (s: string) => string;\n /**\n * Section marker — bold + underlined with a `▸` chevron prefix.\n * Same visual treatment as install.sh's section headers.\n */\n sectionLine: (label: string) => string;\n}\n\nfunction makeWrap(isTty: boolean): (s: string, ...codes: string[]) => string {\n return (s, ...codes) => (isTty ? codes.join('') + s + ANSI_RESET : s);\n}\n\nfunction makePalette(isTty: boolean): Palette {\n const wrap = makeWrap(isTty);\n return {\n bold: (s) => wrap(s, ANSI_BOLD),\n underline: (s) => wrap(s, ANSI_UNDERLINE),\n cyan: (s) => wrap(s, ANSI_CYAN),\n dim: (s) => wrap(s, ANSI_GREY),\n sectionLine: (label) => wrap(`▸ ${label}`, ANSI_BOLD, ANSI_UNDERLINE),\n };\n}\n\n/**\n * Resolve a stream-specific palette. Pass `process.stdout` for\n * commands whose payload goes to stdout (so colours drop out when\n * piped); `process.stderr` for status output that stays on stderr\n * regardless of stdout's destination.\n */\nexport function colorsFor(stream: NodeJS.WriteStream): Palette {\n return makePalette(stream.isTTY ?? false);\n}\n\n// Top-level convenience helpers — gated on stderr, matching the\n// install-/apply-style status output that's always written to\n// stderr. Existing call sites keep working unchanged.\nconst stderrPalette = makePalette(process.stderr.isTTY ?? false);\nexport const bold = stderrPalette.bold;\nexport const underline = stderrPalette.underline;\nexport const cyan = stderrPalette.cyan;\nexport const dim = stderrPalette.dim;\nexport const sectionLine = stderrPalette.sectionLine;\n","import { spawn } from 'node:child_process';\n\n/**\n * Find the running docker container that hosts a Monoceros\n * dev-container. The lookup is by the `devcontainer.local_folder`\n * label devcontainer-cli attaches at `up` time — that label is the\n * absolute host path of the materialized container directory, which\n * is the only stable handle (the container name itself is random for\n * image-mode, e.g. `thirsty_bartik`).\n *\n * Returns the container id (12-char prefix as docker prints it) or\n * `null` when nothing matches. `null` is the normal \"container not\n * up yet\" signal; callers should treat it as \"fall back to yml-only,\n * suggest `monoceros apply`\".\n */\n\nexport interface RunningContainerLookupOptions {\n /** Override the docker exec used to query — tests inject a fake. */\n docker?: DockerLookupExec;\n}\n\nexport type DockerLookupExec = (\n args: readonly string[],\n) => Promise<{ stdout: string; stderr: string; exitCode: number }>;\n\nconst realDockerLookup: DockerLookupExec = (args) => {\n return new Promise((resolve, reject) => {\n const child = spawn('docker', args as string[], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n let stdout = '';\n let stderr = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n child.on('error', reject);\n child.on('exit', (code) =>\n resolve({ stdout, stderr, exitCode: code ?? 0 }),\n );\n });\n};\n\n/**\n * Look up the running container by its `devcontainer.local_folder`\n * label. `containerPath` must be the absolute path of the\n * materialized container dir (e.g. `~/.monoceros/container/sandbox`).\n */\nexport async function findRunningContainerByLocalFolder(\n containerPath: string,\n opts: RunningContainerLookupOptions = {},\n): Promise<string | null> {\n const docker = opts.docker ?? realDockerLookup;\n const result = await docker([\n 'ps',\n '-q',\n '--filter',\n `label=devcontainer.local_folder=${containerPath}`,\n '--filter',\n 'status=running',\n ]);\n if (result.exitCode !== 0) return null;\n const ids = result.stdout\n .split('\\n')\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n return ids[0] ?? null;\n}\n\n/**\n * `docker exec` wrapper for in-container commands triggered by\n * `add-repo --clone-now`. Streams stdout/stderr to the parent\n * process by default so a `git clone` progress bar shows up live —\n * the spawn override lets tests capture instead.\n */\nexport type ContainerExec = (\n containerId: string,\n argv: readonly string[],\n) => Promise<{ exitCode: number; stdout?: string; stderr?: string }>;\n\nexport const realContainerExec: ContainerExec = (containerId, argv) => {\n return new Promise((resolve, reject) => {\n const child = spawn('docker', ['exec', containerId, ...argv], {\n // Inherit stdio so live git output reaches the user.\n stdio: ['ignore', 'inherit', 'inherit'],\n });\n child.on('error', reject);\n child.on('exit', (code) => resolve({ exitCode: code ?? 0 }));\n });\n};\n","import { promises as fs } from 'node:fs';\nimport { z } from 'zod';\nimport { isMap, Pair, parseDocument, Scalar, YAMLMap } from 'yaml';\nimport type { Document } from 'yaml';\nimport {\n FeatureOptionValueSchema,\n GitUserSchema,\n REGEX,\n isValidEmail,\n} from './schema.js';\nimport { monocerosConfigPath, monocerosHome } from './paths.js';\n\n/**\n * `<MONOCEROS_HOME>/monoceros-config.yml` — optional builder-owned\n * defaults that apply across every container materialized through\n * this home. Today the only field is git identity; future fields\n * (default editor, Claude auth profile, ...) plug into the same\n * structure.\n *\n * Schema is permissive: missing top-level keys are fine, the file\n * itself is optional. Schema violations surface as a hard error\n * (better to refuse than silently ignore a typo'd key the builder\n * thought was effective).\n */\n\nconst SCHEMA_VERSION = 1 as const;\n\n/**\n * `defaults.features` — map of devcontainer feature ref to a default\n * option object. When a container yml references the same feature ref\n * without overriding a specific option, the value from here is used.\n * Per-container options always win.\n *\n * Typical use: stash the Atlassian apiToken / Anthropic apiKey here\n * once globally instead of repeating them in every container yml.\n */\nexport const MonocerosConfigSchema = z.object({\n schemaVersion: z.literal(SCHEMA_VERSION),\n // .nullish() (= .optional().nullable()) on defaults so the shipped\n // sample yml — where `defaults:` is uncommented but every sub-block\n // is commented out — parses cleanly. YAML produces `defaults: null`\n // in that case; without .nullish() the schema would reject it and\n // we'd be back to forcing builders to comment-juggle three lines.\n defaults: z\n .object({\n // .nullish() (not just .optional()) so the sample yml can leave\n // `git:` uncommented as a category marker — YAML produces\n // `git: null` for an empty mapping, which zod's plain\n // `.optional()` would reject.\n git: z\n .object({\n // Strict email here: monoceros-config defaults are not tied to\n // any container `<name>.env`, so `${VAR}` placeholders make no\n // sense and the format can (and should) be validated at load\n // time — unlike the container/repo `git.user`, which defers to\n // apply after interpolation.\n user: GitUserSchema.optional().refine(\n (u) => u?.email === undefined || isValidEmail(u.email),\n { message: 'Invalid email in defaults.git.user', path: ['email'] },\n ),\n })\n .nullish(),\n // .nullish() for the same reason as `git` — the sample keeps\n // `features:` uncommented as a category marker.\n features: z\n .record(\n z\n .string()\n .regex(\n REGEX.featureRef,\n \"Invalid feature ref. Expected an OCI-image-style ref like 'ghcr.io/getmonoceros/monoceros-features/<name>:<tag>'.\",\n ),\n z.record(z.string(), FeatureOptionValueSchema),\n )\n .nullish(),\n })\n .nullish(),\n // Machine-global routing settings — one Traefik per builder, so\n // host-port and similar live here rather than in any container yml.\n // See ADR 0007.\n routing: z\n .object({\n hostPort: z\n .number()\n .int()\n .min(1)\n .max(65535)\n .optional()\n .describe(\n 'Host port the Traefik singleton binds. Default 80. Set this when 80 is held by another service on your machine — URLs then become http://<name>.localhost:<port>/.',\n ),\n })\n .nullish(),\n});\n\nexport type MonocerosConfig = z.infer<typeof MonocerosConfigSchema>;\n\nexport interface ReadMonocerosConfigOptions {\n /** Override of the user-data home. Tests inject a tmpdir. */\n monocerosHome?: string;\n}\n\n/**\n * Read `<home>/monoceros-config.yml`. Returns `undefined` if the file\n * isn't there (the normal case for a fresh setup). Throws on a parse\n * or schema error — the builder explicitly created the file, so a\n * silent ignore would be worse than a loud abort.\n */\nexport async function readMonocerosConfig(\n opts: ReadMonocerosConfigOptions = {},\n): Promise<MonocerosConfig | undefined> {\n const home = opts.monocerosHome ?? monocerosHome();\n const filePath = monocerosConfigPath(home);\n let text: string;\n try {\n text = await fs.readFile(filePath, 'utf8');\n } catch {\n return undefined;\n }\n const doc = parseDocument(text, { prettyErrors: true });\n if (doc.errors.length > 0) {\n throw new Error(\n `yaml parse error in ${filePath}: ${doc.errors[0]!.message}`,\n );\n }\n const result = MonocerosConfigSchema.safeParse(doc.toJS());\n if (!result.success) {\n const issues = result.error.issues\n .map((issue) => {\n const where = issue.path.length > 0 ? issue.path.join('.') : '(root)';\n return ` - ${where}: ${issue.message}`;\n })\n .join('\\n');\n throw new Error(\n `Invalid ${filePath}:\\n${issues}\\n\\nSee ${filePath.replace(\n /\\.yml$/,\n '.sample.yml',\n )} for a valid example.`,\n );\n }\n return result.data;\n}\n\n/** Default Traefik host port when `routing.hostPort` is unset. */\nexport const DEFAULT_PROXY_HOST_PORT = 80;\n\n/**\n * Effective host port the Traefik singleton should bind. Falls back\n * to `DEFAULT_PROXY_HOST_PORT` (80) when the global config or its\n * `routing.hostPort` field is absent.\n */\nexport function proxyHostPort(config?: MonocerosConfig | undefined): number {\n return config?.routing?.hostPort ?? DEFAULT_PROXY_HOST_PORT;\n}\n\nexport interface WriteGlobalDefaultGitUserResult {\n /** Absolute path to the file that was written. */\n filePath: string;\n /** True when a brand-new file was created (no monoceros-config.yml before). */\n created: boolean;\n /** True when an existing defaults.git.user was already set and we left it alone. */\n alreadySet: boolean;\n}\n\n/**\n * Persist `defaults.git.user` in `<MONOCEROS_HOME>/monoceros-config.yml`.\n *\n * Behaviour:\n *\n * - File missing → create with the minimum shape (`schemaVersion: 1`\n * + `defaults.git.user`). `monoceros-config.sample.yml` carries\n * the canonical documentation; we don't reproduce it here.\n * - File present, no `defaults.git.user` → fill it in,\n * comment-preserving (the rest of the file stays untouched).\n * - File present, `defaults.git.user` already set → leave as-is,\n * report `alreadySet: true`. The caller decides whether to warn\n * or fall back to a per-container override.\n *\n * The caller (apply / init identity flow) decides when to call this\n * — typically only when the builder explicitly chose `g` (global) or\n * `b` (both) in the scope prompt.\n */\nexport async function writeGlobalDefaultGitUser(\n user: { name: string; email: string },\n opts: { monocerosHome?: string } = {},\n): Promise<WriteGlobalDefaultGitUserResult> {\n const home = opts.monocerosHome ?? monocerosHome();\n const filePath = monocerosConfigPath(home);\n\n let text: string | undefined;\n try {\n text = await fs.readFile(filePath, 'utf8');\n } catch {\n text = undefined;\n }\n\n // Brand-new file → write the minimal shape that mirrors the\n // shipped sample's structure (so a later auto-write can navigate\n // the same paths).\n if (text === undefined) {\n const fresh = [\n '# Optional — global defaults for monoceros containers.',\n '',\n 'schemaVersion: 1',\n '',\n 'defaults:',\n ' git:',\n ' user:',\n ` name: ${user.name}`,\n ` email: ${user.email}`,\n '',\n ].join('\\n');\n await fs.mkdir(home, { recursive: true });\n await fs.writeFile(filePath, fresh, 'utf8');\n return { filePath, created: true, alreadySet: false };\n }\n\n // Existing file: operate via the yaml AST. With the simplified\n // sample (no nested commented sub-blocks under the active keys),\n // AST set-at-path is the right tool — the library can no longer\n // attach unrelated comments to the wrong node because there are\n // no fragmented comment runs between sibling maps.\n //\n // We ensure each level of the path (`defaults` → `git` → `user`)\n // exists as a YAMLMap, then set the two scalar fields. Pre-existing\n // values that are non-empty mean \"already set\" and we leave them\n // alone.\n const doc = parseDocument(text, { prettyErrors: true });\n if (doc.errors.length > 0) {\n throw new Error(\n `yaml parse error in ${filePath}: ${doc.errors[0]!.message}`,\n );\n }\n\n const defaultsMap = ensureMap(doc, 'defaults');\n // `git` is placed at the FRONT of `defaults` when newly created —\n // mirrors the shipped-sample layout (`defaults.git` before\n // `defaults.features`). A pre-existing `git:` keeps its position.\n const gitMap = ensureSubMapAtTop(defaultsMap, 'git');\n const userMap = ensureSubMap(gitMap, 'user');\n\n const existingName = userMap.get('name');\n const existingEmail = userMap.get('email');\n if (\n typeof existingName === 'string' &&\n existingName.length > 0 &&\n typeof existingEmail === 'string' &&\n existingEmail.length > 0\n ) {\n return { filePath, created: false, alreadySet: true };\n }\n\n // yaml's parser sometimes attaches a comment that visually belongs to\n // the NEXT outer-level sibling (e.g. `# Feature credentials & options.`\n // sitting above `features:`) to the trailing leaf scalar instead\n // (here: `email:`'s value). If we just `.set()`, that orphaned\n // comment renders right after our new value, producing chaos like:\n // email:\n //\n // a@example.com # Feature credentials & options.\n //\n // Relocate any such leaked comment to the next sibling of `git` under\n // `defaults` (the position it visually belongs to) before writing.\n relocateLeakedLeafComments(userMap, defaultsMap, 'git');\n\n userMap.set('name', user.name);\n userMap.set('email', user.email);\n const newText = String(doc);\n\n await fs.writeFile(filePath, newText, 'utf8');\n return { filePath, created: false, alreadySet: false };\n}\n\n/**\n * Get the document's top-level `<key>` as a YAMLMap, creating it\n * (and replacing a null/scalar value with a fresh map) if needed.\n * Used by writeGlobalDefaultGitUser to navigate to the persistence\n * point without crashing on yml shapes like `defaults:` (parsed as\n * null) or a missing top-level key.\n */\nfunction ensureMap(doc: Document, key: string): YAMLMap {\n const node = doc.get(key, true);\n if (node && isMap(node)) return node;\n const m = new YAMLMap();\n doc.set(key, m);\n return m;\n}\n\n/** Same as ensureMap but for a sub-key under an existing YAMLMap. */\nfunction ensureSubMap(parent: YAMLMap, key: string): YAMLMap {\n const node = parent.get(key, true);\n if (node && isMap(node)) return node;\n const m = new YAMLMap();\n parent.set(key, m);\n return m;\n}\n\n/**\n * Variant of `ensureSubMap` that inserts a *new* key at the FRONT of\n * `parent.items` rather than appending. Used for `defaults.git` so the\n * new block lands where the shipped sample places it (above\n * `features`) and not at the end after every other entry. A\n * pre-existing key keeps its position untouched.\n *\n * Before inserting, transfers any `commentBefore` that yaml-lib\n * attached to `parent` itself (this happens when the source's\n * leading comment-block ABOVE parent's first child was associated\n * with the map node rather than the first Pair) over to that first\n * child's key — otherwise the comment would visually re-attach to\n * our newly-front-inserted pair and mislabel it.\n */\nfunction ensureSubMapAtTop(parent: YAMLMap, key: string): YAMLMap {\n const node = parent.get(key, true);\n if (node && isMap(node)) return node;\n\n type CommentNode = {\n commentBefore?: string | null;\n spaceBefore?: boolean;\n };\n const parentMaybe = parent as CommentNode;\n const newKey = new Scalar(key);\n\n // yaml-lib often parks the comment-block above parent's first\n // child on the map node itself, not on the Pair. When we unshift\n // a new front Pair we have to redistribute that block: the first\n // paragraph (everything up to a blank-line separator) describes\n // what we're inserting, so it travels with the new key; the rest\n // continues to describe the old first child.\n //\n // Before redistributing we strip any commented-out skeleton of the\n // same key (`# git: / # user: / # name: …`) — that's the\n // placeholder a builder leaves behind when they un-commented prose\n // but not the structural keys. We're about to write a real active\n // block; the placeholder would otherwise sit right next to it as\n // dead text.\n if (\n parent.items.length > 0 &&\n typeof parentMaybe.commentBefore === 'string' &&\n parentMaybe.commentBefore.length > 0\n ) {\n const cleaned = stripCommentedKeySkeleton(parentMaybe.commentBefore, key);\n const blankMatch = cleaned.match(/\\n[ \\t]*\\n/);\n let head: string;\n let tail: string;\n if (blankMatch && blankMatch.index !== undefined) {\n head = cleaned.slice(0, blankMatch.index);\n tail = cleaned.slice(blankMatch.index + blankMatch[0].length);\n } else {\n head = cleaned;\n tail = '';\n }\n if (head.length > 0) {\n newKey.commentBefore = head;\n if (parentMaybe.spaceBefore) newKey.spaceBefore = true;\n }\n if (tail.length > 0) {\n const firstKey = parent.items[0]!.key as CommentNode | null;\n if (firstKey && typeof firstKey === 'object') {\n const existing = firstKey.commentBefore ?? '';\n firstKey.commentBefore = existing ? `${tail}\\n${existing}` : tail;\n firstKey.spaceBefore = true;\n }\n }\n parentMaybe.commentBefore = null;\n parentMaybe.spaceBefore = false;\n }\n\n const m = new YAMLMap();\n const pair = new Pair(newKey, m);\n parent.items.unshift(pair);\n return m;\n}\n\n/**\n * Remove a commented-out skeleton of `key` (and its indented children)\n * from a yaml-lib `commentBefore` body.\n *\n * yaml-lib stores comment bodies stripped of the leading `#`: the\n * source `# user:` becomes ` user:` in the comment string. So a\n * placeholder like\n *\n * # git:\n * # user:\n * # name: \"T\"\n * # email: \"h@k.de\"\n *\n * lives in the comment body as four lines, the first ` git:` (single\n * leading space, the post-`#` convention) and the next three with\n * more leading spaces (the children of the commented map).\n *\n * Detection: a line matching ` <key>:` exactly, followed by zero or\n * more lines whose leading whitespace is strictly deeper than that\n * line's. Splice all of those out. Multiple skeletons (rare but\n * possible) get stripped in sequence.\n */\nfunction stripCommentedKeySkeleton(commentBody: string, key: string): string {\n const lines = commentBody.split('\\n');\n const headRe = new RegExp(`^ ${escapeRegExp(key)}:\\\\s*$`);\n const out: string[] = [];\n let i = 0;\n while (i < lines.length) {\n const line = lines[i]!;\n if (headRe.test(line)) {\n // Skip the header and all following indented (deeper than\n // one space) lines — those are the commented map's children.\n i++;\n while (i < lines.length && /^ {2,}\\S/.test(lines[i]!)) {\n i++;\n }\n continue;\n }\n out.push(line);\n i++;\n }\n return out.join('\\n');\n}\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Walk `leafMap`'s pair values. For any value scalar carrying a comment\n * (yaml's parser attaches the next outer-level pair's leading comment\n * to the previous level's trailing scalar when the indent drops by\n * more than one), move that comment to the `commentBefore` of the next\n * sibling of `ancestorKey` in `parent`. Also clear `spaceBefore` on\n * the leaf and set it on the relocation target so the blank line\n * re-appears in the right place.\n *\n * No-op when there's no leaked comment or no next sibling to host it.\n */\nfunction relocateLeakedLeafComments(\n leafMap: YAMLMap,\n parent: YAMLMap,\n ancestorKey: string,\n): void {\n const items = parent.items;\n const ancestorIdx = items.findIndex((p) => {\n const k = p.key as { value?: unknown } | string | null;\n const v = typeof k === 'string' ? k : (k?.value ?? null);\n return v === ancestorKey;\n });\n if (ancestorIdx < 0 || ancestorIdx + 1 >= items.length) return;\n const target = items[ancestorIdx + 1]!;\n type CommentNode = {\n comment?: string | null;\n commentBefore?: string | null;\n spaceBefore?: boolean;\n };\n for (const pair of leafMap.items) {\n const value = pair.value as CommentNode | null;\n if (!value || typeof value !== 'object') continue;\n const leakedComment = value.comment;\n const leakedSpace = value.spaceBefore;\n if (!leakedComment && !leakedSpace) continue;\n if (leakedComment) {\n const targetKey = target.key as CommentNode | null;\n if (targetKey && typeof targetKey === 'object') {\n const existing = targetKey.commentBefore ?? '';\n targetKey.commentBefore = existing\n ? `${leakedComment}\\n${existing}`\n : leakedComment;\n if (leakedSpace) targetKey.spaceBefore = true;\n }\n value.comment = null;\n }\n if (leakedSpace) value.spaceBefore = false;\n }\n}\n","import { existsSync, promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { z } from 'zod';\nimport { parse as parseYaml } from 'yaml';\nimport { componentsDir as defaultComponentsDir } from '../config/paths.js';\nimport { FeatureOptionValueSchema, REGEX } from '../config/schema.js';\n\n/**\n * Components catalog — small, composable yml snippets that\n * `monoceros init` can merge into a container config.\n *\n * Each file under `templates/components/` is one component:\n *\n * - `templates/components/node.yml` → component name `node`\n * - `templates/components/atlassian/twg.yml` → component name\n * `atlassian/twg`\n *\n * Sub-components live inside a directory whose name matches a parent\n * component (and which may itself have a top-level `<group>.yml`,\n * e.g. `atlassian.yml` for the \"both tools\" preset). The convention\n * is: a sub-component sets every sibling boolean option explicitly\n * (`true` for its own feature, `false` for the others), and the\n * merge applies OR-semantics on booleans so combining\n * `--with-features=atlassian/rovodev,atlassian/twg` correctly yields both\n * `true`. See `templates/components/README.md` for the full design.\n */\n\nconst CategorySchema = z.enum(['language', 'service', 'feature']);\nexport type ComponentCategory = z.infer<typeof CategorySchema>;\n\nconst FeatureContributionSchema = z.object({\n ref: z.string().regex(REGEX.featureRef),\n options: z.record(z.string(), FeatureOptionValueSchema).optional(),\n});\n\n/**\n * Shape validation for one component file. The contributes section is\n * deliberately narrow — exactly one of languages/services/features may\n * be set, and it must line up with the declared category.\n */\nconst ComponentFileSchema = z\n .object({\n displayName: z.string().min(1),\n description: z.string().min(1),\n category: CategorySchema,\n contributes: z.object({\n languages: z.array(z.string().min(1)).optional(),\n services: z.array(z.string().min(1)).optional(),\n features: z.array(FeatureContributionSchema).optional(),\n }),\n })\n .superRefine((data, ctx) => {\n const c = data.contributes;\n const filled = [\n c.languages && c.languages.length > 0 ? 'languages' : null,\n c.services && c.services.length > 0 ? 'services' : null,\n c.features && c.features.length > 0 ? 'features' : null,\n ].filter((x): x is string => x !== null);\n\n if (filled.length === 0) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message:\n 'contributes must set at least one of languages/services/features',\n });\n return;\n }\n if (filled.length > 1) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: `contributes must set exactly one of languages/services/features, got: ${filled.join(', ')}`,\n });\n return;\n }\n const expected =\n data.category === 'language'\n ? 'languages'\n : data.category === 'service'\n ? 'services'\n : 'features';\n if (filled[0] !== expected) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: `category '${data.category}' requires contributes.${expected}, got contributes.${filled[0]}`,\n });\n }\n });\n\nexport type ComponentFile = z.infer<typeof ComponentFileSchema>;\n\nexport interface Component {\n /** Catalog name, e.g. `node`, `atlassian/twg`. Always slash-form. */\n name: string;\n /** Absolute filesystem path of the source yml — useful for errors. */\n sourcePath: string;\n file: ComponentFile;\n}\n\n/**\n * Walk `templates/components/` recursively, parse every `.yml` file,\n * validate it, return as a name-keyed map. README files and other\n * non-yml files are silently skipped.\n *\n * Throws on the first invalid component file with a path-anchored\n * error — better to refuse than to load an inconsistent catalog.\n */\nexport async function loadComponentCatalog(\n rootDir: string = defaultComponentsDir(),\n): Promise<Map<string, Component>> {\n if (!existsSync(rootDir)) {\n return new Map();\n }\n const out = new Map<string, Component>();\n await walk(rootDir, rootDir, out);\n return out;\n}\n\nasync function walk(\n baseDir: string,\n currentDir: string,\n out: Map<string, Component>,\n): Promise<void> {\n const entries = await fs.readdir(currentDir, { withFileTypes: true });\n for (const entry of entries) {\n const full = path.join(currentDir, entry.name);\n if (entry.isDirectory()) {\n await walk(baseDir, full, out);\n continue;\n }\n if (!entry.isFile() || !entry.name.endsWith('.yml')) continue;\n const relative = path.relative(baseDir, full);\n const name = relative\n .replace(/\\.yml$/, '')\n .split(path.sep)\n .join('/');\n const text = await fs.readFile(full, 'utf8');\n let raw: unknown;\n try {\n raw = parseYaml(text);\n } catch (err) {\n throw new Error(\n `Failed to parse component ${name} (${full}): ${(err as Error).message}`,\n );\n }\n const parsed = ComponentFileSchema.safeParse(raw);\n if (!parsed.success) {\n const issues = parsed.error.issues\n .map((issue) => {\n const where = issue.path.length > 0 ? issue.path.join('.') : '(root)';\n return ` - ${where}: ${issue.message}`;\n })\n .join('\\n');\n throw new Error(`Invalid component ${name} (${full}):\\n${issues}`);\n }\n out.set(name, { name, sourcePath: full, file: parsed.data });\n }\n}\n\n/**\n * A `SolutionConfig`-shaped fragment produced by merging the\n * `contributes` of one or more components. Caller wraps this into\n * a full config (adds schemaVersion + name) before writing the yml.\n */\nexport interface MergedComponents {\n languages: string[];\n services: string[];\n features: Array<{\n ref: string;\n options: Record<string, string | number | boolean>;\n }>;\n}\n\n/**\n * Merge the contributions of the given components into a single\n * fragment.\n *\n * Rules:\n * - `languages`/`services`: concat + dedupe (insertion order kept\n * stable; first occurrence wins).\n * - `features`: deduped by `ref`. When two components contribute\n * the same ref, their options are merged with the per-key rules\n * below.\n * - Per-key feature option merge:\n * - booleans: OR (true wins)\n * - strings + numbers: later component overrides (rare in\n * practice — components should set activation flags, not\n * credentials; credentials come from monoceros-config.yml\n * defaults.features or the user editing the yml directly).\n *\n * The OR-merge for booleans is what makes\n * `--with-features=atlassian/rovodev,atlassian/twg` yield both `true` even\n * though each sub-component sets the sibling flag to `false`.\n */\n/**\n * One entry of the resolved-components list. The optional `version`\n * is the `<name>:<version>` suffix from the CLI flag; today it\n * only applies to language components (we append it to each\n * contributed language string so the scaffold passes it as the\n * upstream feature's `version` option). For other categories,\n * providing a version is a builder error and resolveComponents\n * rejects it up front.\n */\nexport interface ResolvedComponent {\n component: Component;\n version?: string;\n}\n\nexport function mergeComponents(\n resolved: Array<Component | ResolvedComponent>,\n): MergedComponents {\n const languages: string[] = [];\n const services: string[] = [];\n const featureByRef = new Map<\n string,\n { ref: string; options: Record<string, string | number | boolean> }\n >();\n\n for (const entry of resolved) {\n const c = isResolvedComponent(entry) ? entry.component : entry;\n const version = isResolvedComponent(entry) ? entry.version : undefined;\n const ct = c.file.contributes;\n for (const lang of ct.languages ?? []) {\n // Language components can carry a `:version` suffix from the\n // CLI. We emit `<lang>:<version>` in the final yml; the\n // scaffold parses it back to the upstream feature's\n // `version` option at apply time.\n const value = version !== undefined ? `${lang}:${version}` : lang;\n if (!languages.includes(value)) languages.push(value);\n }\n for (const svc of ct.services ?? []) {\n if (!services.includes(svc)) services.push(svc);\n }\n for (const f of ct.features ?? []) {\n const existing = featureByRef.get(f.ref);\n if (!existing) {\n featureByRef.set(f.ref, {\n ref: f.ref,\n options: { ...(f.options ?? {}) },\n });\n continue;\n }\n existing.options = mergeFeatureOptions(existing.options, f.options ?? {});\n }\n }\n\n return {\n languages,\n services,\n features: [...featureByRef.values()],\n };\n}\n\nfunction isResolvedComponent(\n x: Component | ResolvedComponent,\n): x is ResolvedComponent {\n return 'component' in x;\n}\n\nexport function mergeFeatureOptions(\n a: Record<string, string | number | boolean>,\n b: Record<string, string | number | boolean>,\n): Record<string, string | number | boolean> {\n const result = { ...a };\n for (const [key, valueB] of Object.entries(b)) {\n const valueA = result[key];\n if (typeof valueA === 'boolean' && typeof valueB === 'boolean') {\n result[key] = valueA || valueB;\n continue;\n }\n result[key] = valueB;\n }\n return result;\n}\n\n/**\n * Resolve `--with-*` names against the catalog. Accepts plain\n * names (`node`) and language-version pairs (`node:20`). Splits\n * the `:version` off, looks up the bare name in the catalog, and\n * carries the version forward only for language components — a\n * version on any other category is rejected with a clear error.\n *\n * Throws with the full list of unknown names so the builder fixes\n * them all at once rather than running into them one at a time.\n */\nexport function resolveComponents(\n catalog: Map<string, Component>,\n names: string[],\n): ResolvedComponent[] {\n const unknown: string[] = [];\n const out: ResolvedComponent[] = [];\n for (const raw of names) {\n const colon = raw.indexOf(':');\n const name = colon === -1 ? raw : raw.slice(0, colon);\n const version = colon === -1 ? undefined : raw.slice(colon + 1);\n\n const c = catalog.get(name);\n if (!c) {\n // The unknown-name message reports the form the user typed\n // (including the :version) so it's easy to spot the typo.\n unknown.push(raw);\n continue;\n }\n if (version !== undefined && c.file.category !== 'language') {\n throw new Error(\n `Component '${name}' is a ${c.file.category}, not a language — a ':${version}' suffix has no meaning here.`,\n );\n }\n out.push({ component: c, ...(version !== undefined ? { version } : {}) });\n }\n if (unknown.length > 0) {\n const available = [...catalog.keys()].sort();\n throw new Error(\n `Unknown component${unknown.length > 1 ? 's' : ''}: ${unknown.join(', ')}.\\n` +\n `Available: ${available.join(', ')}.`,\n );\n }\n return out;\n}\n","import { spawn } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { monocerosHome as defaultMonocerosHome } from '../config/paths.js';\n\n/**\n * Lifecycle of the shared Traefik singleton that fronts every dev-\n * container declaring `ports:` in its yml. See ADR 0007.\n *\n * Two functions externally:\n *\n * - `ensureProxy()` — idempotent; ensures the `monoceros-proxy`\n * docker network exists, the `<MONOCEROS_HOME>/traefik/dynamic/`\n * directory exists (and is user-owned, so subsequent file writes\n * don't fight a root-mkdir from docker), and a running container\n * named `monoceros-proxy` is up. Called from `apply`/`start` when\n * the container's yml declares at least one port.\n *\n * - `maybeStopProxy()` — counts the non-proxy containers attached to\n * the `monoceros-proxy` network. Zero ⇒ stop the singleton and\n * drop the network. Anything else ⇒ no-op. Called from\n * `stop`/`remove`.\n *\n * Test extension point: every docker invocation runs through the\n * `DockerExec` shape, which `ensureProxy`/`maybeStopProxy` accept as\n * an optional override. Tests inject a fake that records args and\n * returns canned stdout / exit codes.\n */\n\n/** Container name AND network name. Docker namespaces them separately. */\nexport const PROXY_CONTAINER_NAME = 'monoceros-proxy';\nexport const PROXY_NETWORK_NAME = 'monoceros-proxy';\n\n/** Traefik release we pin against. Bump deliberately, not floating. */\nexport const TRAEFIK_IMAGE = 'traefik:v3.3';\n\nexport interface DockerResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n}\n\nexport type DockerExec = (args: string[]) => Promise<DockerResult>;\n\n/**\n * Default docker invocation — exported so other modules in the proxy/\n * family (port-check, …) can share the same spawn semantics without\n * each having to re-implement child-process bookkeeping. Tests inject\n * their own `DockerExec` and never hit this path.\n */\nexport const defaultDockerExec: DockerExec = (args) => {\n return new Promise((resolve, reject) => {\n const child = spawn('docker', args, {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n let stdout = '';\n let stderr = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString();\n });\n child.on('error', reject);\n child.on('exit', (code) =>\n resolve({ stdout, stderr, exitCode: code ?? 0 }),\n );\n });\n};\n\nconst realDocker: DockerExec = defaultDockerExec;\n\nexport interface ProxyLogger {\n info: (message: string) => void;\n warn?: (message: string) => void;\n}\n\nexport interface ProxyOptions {\n /** Override the docker spawn shape (tests inject a fake). */\n docker?: DockerExec;\n /** Override the resolved MONOCEROS_HOME (tests inject a tmpdir). */\n monocerosHome?: string;\n /**\n * Host port Traefik binds. Read from `monoceros-config.yml`'s\n * `routing.hostPort` by callers; the proxy module itself just gets\n * a number and uses it for the `-p` mapping. Defaults to 80 (see\n * `config/global.ts → DEFAULT_PROXY_HOST_PORT`) when omitted.\n */\n hostPort?: number;\n logger?: ProxyLogger;\n}\n\n/** `<MONOCEROS_HOME>/traefik/dynamic/` — Traefik file-provider directory. */\nexport function proxyDynamicDir(home?: string): string {\n return path.join(home ?? defaultMonocerosHome(), 'traefik', 'dynamic');\n}\n\n/**\n * Bring the singleton + network up if they aren't already.\n *\n * Steps (all idempotent):\n * 1. mkdir -p on the dynamic-config dir (user-owned).\n * 2. `docker network create monoceros-proxy` if missing.\n * 3. If the container exists but is stopped → `docker start`.\n * If it doesn't exist → `docker run -d` with the canonical args.\n * If it's already running → no-op.\n *\n * Throws with a docker-cli-flavored error message on the first\n * failure. Callers that want to soft-fail (e.g. apply continuing\n * without proxy) must catch.\n */\nexport async function ensureProxy(opts: ProxyOptions = {}): Promise<void> {\n const docker = opts.docker ?? realDocker;\n const dyn = proxyDynamicDir(opts.monocerosHome);\n await fs.mkdir(dyn, { recursive: true });\n\n // Network. `inspect` exits 1 when missing (no JSON parse needed).\n const netInspect = await docker(['network', 'inspect', PROXY_NETWORK_NAME]);\n if (netInspect.exitCode !== 0) {\n const create = await docker(['network', 'create', PROXY_NETWORK_NAME]);\n if (create.exitCode !== 0) {\n throw new Error(\n `Could not create docker network ${PROXY_NETWORK_NAME}: ${create.stderr.trim() || `exit ${create.exitCode}`}`,\n );\n }\n }\n\n // Container. The Go-template format is `true`/`false` for the\n // boolean Running flag — easier to compare than parsing JSON.\n const state = await docker([\n 'inspect',\n '--format',\n '{{.State.Running}}',\n PROXY_CONTAINER_NAME,\n ]);\n if (state.exitCode === 0) {\n if (state.stdout.trim() === 'true') return; // already up\n const start = await docker(['start', PROXY_CONTAINER_NAME]);\n if (start.exitCode !== 0) {\n throw new Error(\n `Could not start existing ${PROXY_CONTAINER_NAME} container: ${start.stderr.trim() || `exit ${start.exitCode}`}`,\n );\n }\n return;\n }\n\n // Fresh container. The Traefik args declare a single HTTP entrypoint\n // `web` on :80 and turn on the file provider with watch=true so\n // dynamic-config writes propagate without a Traefik restart. The\n // docker provider is explicitly off — we route via file-provider\n // only, so container labels can't accidentally publish a route.\n // Default 80 — kept as a literal here to avoid a back-reference into\n // config/global.ts. The authoritative value (and the merge logic\n // with `monoceros-config.yml`) lives in config/global.ts.\n const hostPort = opts.hostPort ?? 80;\n const run = await docker([\n 'run',\n '-d',\n '--name',\n PROXY_CONTAINER_NAME,\n '--network',\n PROXY_NETWORK_NAME,\n '-p',\n `${hostPort}:80`,\n '-v',\n `${dyn}:/etc/traefik/dynamic:ro`,\n '--label',\n 'monoceros.role=proxy',\n TRAEFIK_IMAGE,\n '--entrypoints.web.address=:80',\n '--providers.file.directory=/etc/traefik/dynamic',\n '--providers.file.watch=true',\n '--providers.docker=false',\n '--api.dashboard=false',\n '--log.level=INFO',\n ]);\n if (run.exitCode !== 0) {\n throw new Error(\n `Could not start ${PROXY_CONTAINER_NAME}: ${run.stderr.trim() || `exit ${run.exitCode}`}`,\n );\n }\n opts.logger?.info(\n `Started ${PROXY_CONTAINER_NAME} (Traefik on :${hostPort}).`,\n );\n}\n\n/**\n * Stop and drop the singleton + network IFF no other container is\n * still attached. Safe to call from any lifecycle exit (`stop`,\n * `remove`, last `remove-port`). No-ops gracefully when:\n *\n * - the network doesn't exist (nothing to do)\n * - another devcontainer is still attached\n * - the container is already gone\n */\nexport async function maybeStopProxy(opts: ProxyOptions = {}): Promise<void> {\n const docker = opts.docker ?? realDocker;\n const logger = opts.logger;\n\n // Names of every container attached to the network. The Go-template\n // is one name per line. Empty stdout (or just newlines) means no\n // container at all is in the network.\n const inspect = await docker([\n 'network',\n 'inspect',\n PROXY_NETWORK_NAME,\n '--format',\n '{{range $k, $v := .Containers}}{{$v.Name}}\\n{{end}}',\n ]);\n if (inspect.exitCode !== 0) {\n // Network is gone or daemon unreachable — nothing to clean up.\n return;\n }\n const others = inspect.stdout\n .split('\\n')\n .map((n) => n.trim())\n .filter((n) => n.length > 0 && n !== PROXY_CONTAINER_NAME);\n if (others.length > 0) return; // other consumers still depend on it\n\n // Stop+rm the singleton. `rm -f` does both even if it's still up,\n // and shrugs at a missing container, which makes the call resilient\n // to manual `docker rm` from outside Monoceros.\n await docker(['rm', '-f', PROXY_CONTAINER_NAME]);\n\n // Drop the network. `network rm` errors when other containers are\n // still attached — we already filtered for that, so a non-zero exit\n // here is genuinely something else (e.g. permission denied). We log\n // the warn but don't throw; the next ensureProxy() recreates anyway.\n const netRm = await docker(['network', 'rm', PROXY_NETWORK_NAME]);\n if (netRm.exitCode !== 0) {\n logger?.warn?.(\n `Could not remove docker network ${PROXY_NETWORK_NAME}: ${netRm.stderr.trim() || `exit ${netRm.exitCode}`}`,\n );\n return;\n }\n logger?.info(\n `Stopped ${PROXY_CONTAINER_NAME} (no dev-containers with ports left).`,\n );\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { proxyDynamicDir } from './index.js';\n\n/**\n * Traefik file-provider dynamic configuration: one yml per container\n * under `<MONOCEROS_HOME>/traefik/dynamic/<name>.yml`. Traefik picks\n * up file changes within ~100 ms via `--providers.file.watch=true`,\n * so add-port / remove-port effects land hot without a restart of\n * either Traefik or the dev-container. See ADR 0007.\n *\n * Hostname layout (`*.localhost` resolves to 127.0.0.1 by RFC 6761):\n *\n * - `<name>.localhost` → the *first* declared port (default)\n * - `<name>-<port>.localhost` → that specific internal port\n *\n * Backend URL is always `http://<name>:<port>` — the container joins\n * the `monoceros-proxy` network under the yml name (set via\n * `--network-alias` for image-mode and a `networks.monoceros-proxy.aliases:`\n * entry for compose-mode in `create/scaffold.ts`).\n */\n\nexport interface DynamicConfigOptions {\n /** Override the resolved MONOCEROS_HOME (tests inject a tmpdir). */\n monocerosHome?: string;\n}\n\n/** Path the dynamic config for one container lives at. */\nexport function dynamicConfigPath(\n name: string,\n opts: DynamicConfigOptions = {},\n): string {\n return path.join(proxyDynamicDir(opts.monocerosHome), `${name}.yml`);\n}\n\n/**\n * Write the per-container dynamic config. The directory under\n * `<MONOCEROS_HOME>/traefik/dynamic/` is created if needed (matches\n * what ensureProxy does — calling either before the other is fine).\n *\n * Idempotent: a second call with the same `(name, ports)` produces a\n * byte-identical file. Returns the absolute path that was written.\n *\n * `ports` must be non-empty; the caller (add-port, apply) is\n * responsible for routing empty-list cases through\n * `removeDynamicConfig`.\n */\nexport async function writeDynamicConfig(\n name: string,\n ports: number[],\n opts: DynamicConfigOptions = {},\n): Promise<string> {\n if (ports.length === 0) {\n throw new Error(\n `writeDynamicConfig requires at least one port. For empty port lists, call removeDynamicConfig(${JSON.stringify(name)}).`,\n );\n }\n const dir = proxyDynamicDir(opts.monocerosHome);\n await fs.mkdir(dir, { recursive: true });\n const file = path.join(dir, `${name}.yml`);\n await fs.writeFile(file, renderDynamicConfig(name, ports), 'utf8');\n return file;\n}\n\n/** Drop the dynamic config for `name`. No-op when the file is absent. */\nexport async function removeDynamicConfig(\n name: string,\n opts: DynamicConfigOptions = {},\n): Promise<void> {\n const file = path.join(proxyDynamicDir(opts.monocerosHome), `${name}.yml`);\n await fs.rm(file, { force: true });\n}\n\n/**\n * Render the yml body. Pure function — no I/O — so unit tests can\n * snapshot the output cheaply, and `writeDynamicConfig` is the only\n * one that touches the filesystem.\n *\n * First port doubles as the container's default URL via\n * `<name>.localhost`; every port also gets its explicit\n * `<name>-<port>.localhost` route. Both routers point at the same\n * load-balancer service backend.\n */\nexport function renderDynamicConfig(name: string, ports: number[]): string {\n const lines: string[] = [];\n lines.push('# Generated by Monoceros — do not edit by hand.');\n lines.push(`# Container: ${name}`);\n lines.push(`# Ports: ${ports.join(', ')}`);\n lines.push('# Traefik file-provider re-reads this on change (~100 ms);');\n lines.push(\n '# to change routing, edit container-configs/' + name + '.yml or use',\n );\n lines.push('# `monoceros add-port` / `monoceros remove-port`.');\n lines.push('http:');\n lines.push(' routers:');\n ports.forEach((port, idx) => {\n const router = `${name}-${port}`;\n const hostExplicit = `${name}-${port}.localhost`;\n // The first declared port doubles as the container default; its\n // router matches both the explicit `<name>-<port>.localhost` and\n // the bare `<name>.localhost`. The others match only their\n // explicit hostname.\n const rule =\n idx === 0\n ? `\"Host(\\`${name}.localhost\\`) || Host(\\`${hostExplicit}\\`)\"`\n : `\"Host(\\`${hostExplicit}\\`)\"`;\n lines.push(` ${router}:`);\n lines.push(` rule: ${rule}`);\n lines.push(` service: ${router}`);\n lines.push(' entryPoints:');\n lines.push(' - web');\n });\n lines.push(' services:');\n for (const port of ports) {\n const svc = `${name}-${port}`;\n lines.push(` ${svc}:`);\n lines.push(' loadBalancer:');\n lines.push(' servers:');\n lines.push(` - url: \"http://${name}:${port}\"`);\n }\n return lines.join('\\n') + '\\n';\n}\n\n/**\n * URLs the proxy exposes for a container. Pure derivation — used by\n * the `monoceros port` command and by `add-port` to print the\n * resulting routes after a write. `hostPort` (default 80) gets\n * suffixed to the URL only when it's non-default — the URLs stay\n * clean for the common case.\n */\nexport interface ProxyUrl {\n /** Internal container port. */\n port: number;\n /** `http://<name>-<port>.localhost[:<hostPort>]`. */\n url: string;\n /** True for the first port: also reachable at `<name>.localhost`. */\n isDefault: boolean;\n}\n\nexport function proxyUrlsFor(\n name: string,\n ports: number[],\n hostPort: number = 80,\n): ProxyUrl[] {\n const portSuffix = hostPort === 80 ? '' : `:${hostPort}`;\n return ports.map((port, idx) => ({\n port,\n url: `http://${name}-${port}.localhost${portSuffix}`,\n isDefault: idx === 0,\n }));\n}\n","import { Socket } from 'node:net';\nimport {\n PROXY_CONTAINER_NAME,\n defaultDockerExec,\n type DockerExec,\n type ProxyLogger,\n} from './index.js';\n\n/**\n * Pre-flight check on the host port the Traefik singleton would bind.\n *\n * Why this exists: `docker run -p <port>:80` fails with a cryptic\n * `bind: address already in use` line buried inside docker's own\n * stderr, and the builder then has no idea (a) which port, (b) who's\n * holding it, (c) how to choose a different one. Catching this before\n * the docker call lets us point at the exact remedy.\n *\n * Two paths:\n *\n * 1. If the `monoceros-proxy` container is already running, the\n * port is \"in use\" by us — nothing to check. Skip silently.\n *\n * 2. Otherwise, try to TCP-connect to `127.0.0.1:<port>`. Something\n * accepting the connection ⇒ port is taken; ECONNREFUSED ⇒\n * nobody's listening and the port is (probably) free for Docker.\n *\n * We deliberately do NOT try to bind the port ourselves. On Linux,\n * binding ports <1024 requires CAP_NET_BIND_SERVICE — which the\n * unprivileged Node process running monoceros doesn't have, even when\n * the docker daemon does. The bind probe would EACCES with our own\n * lack of privilege, not with someone actually holding the port. The\n * connect probe sidesteps that: connects don't need a privileged port.\n *\n * Trade-off: connect catches anything that's actively LISTEN'ing on\n * 127.0.0.1 or 0.0.0.0 (system nginx, Pi-hole, …) — the cases that\n * realistically conflict with Docker's `-p 80:80`. If something binds\n * only on a specific external interface (192.168.x.x:80) and refuses\n * loopback, the connect probe sees ECONNREFUSED and lets Docker\n * surface its own error — which then carries our actionable hint via\n * the error-message wrapping in apply/.\n *\n * The probe is plumbed through `PortProbe` so tests can inject a stub.\n */\n\nexport type PortProbe = (port: number) => Promise<PortProbeResult>;\n\nexport type PortProbeResult =\n | { ok: true }\n | { ok: false; code: string; message: string };\n\nconst CONNECT_TIMEOUT_MS = 750;\n\nconst realPortProbe: PortProbe = (port) => {\n return new Promise((resolve) => {\n const socket = new Socket();\n let settled = false;\n const settle = (result: PortProbeResult) => {\n if (settled) return;\n settled = true;\n socket.destroy();\n resolve(result);\n };\n socket.setTimeout(CONNECT_TIMEOUT_MS);\n socket.once('connect', () => {\n // Something accepted our connection → the port is held.\n settle({\n ok: false,\n code: 'EADDRINUSE',\n message: `another process is listening on ${port}`,\n });\n });\n socket.once('timeout', () => {\n // No SYN-ACK within the timeout. Could be a firewalled bind or\n // a daemon not on loopback; treat as \"probably free\" and let\n // Docker speak up if it disagrees.\n settle({ ok: true });\n });\n socket.once('error', (err: NodeJS.ErrnoException) => {\n const code = err.code ?? 'UNKNOWN';\n if (code === 'ECONNREFUSED') {\n // Nobody listening — the typical \"port is free\" signal.\n settle({ ok: true });\n } else {\n // Other errors (EHOSTUNREACH, ENETDOWN, …) aren't our bind\n // story. Don't pretend we know — surface verbatim so the\n // builder sees what their network is doing.\n settle({\n ok: false,\n code,\n message: err.message,\n });\n }\n });\n socket.connect(port, '127.0.0.1');\n });\n};\n\nexport interface PreflightHostPortOptions {\n /**\n * Override the docker exec used to check whether monoceros-proxy is\n * already running. Tests inject a fake.\n */\n docker?: DockerExec;\n /** Override the bind probe. Tests inject a fake. */\n portProbe?: PortProbe;\n logger?: ProxyLogger;\n}\n\n/**\n * Ensure `hostPort` is bindable, OR explain in detail why it isn't.\n *\n * - Returns silently when the port is bindable (or already held by\n * the monoceros-proxy container).\n * - Throws an Error with a formatted, actionable message otherwise.\n *\n * The caller (apply / start / add-port hot-path) is expected to\n * `try { await preflightHostPort(...) } catch { print + exit }` —\n * i.e. abort the command cleanly with the rendered hint, not let\n * the docker `bind: address already in use` slip through.\n */\nexport async function preflightHostPort(\n hostPort: number,\n opts: PreflightHostPortOptions = {},\n): Promise<void> {\n // Is monoceros-proxy itself the current holder? If so, ensureProxy\n // will be a no-op and the port-check has nothing to tell us.\n // ALWAYS run this check (not just when opts.docker is overridden) —\n // otherwise the bind probe would fail on Traefik's own port and\n // the builder would see \"port 80 held by another process\" pointing\n // at our own running container.\n const docker = opts.docker ?? defaultDockerExec;\n const inspect = await docker([\n 'inspect',\n '--format',\n '{{.State.Running}}',\n PROXY_CONTAINER_NAME,\n ]);\n if (inspect.exitCode === 0 && inspect.stdout.trim() === 'true') {\n return;\n }\n\n const probe = opts.portProbe ?? realPortProbe;\n const result = await probe(hostPort);\n if (result.ok) return;\n\n // EADDRINUSE is the case we want to dress up nicely. Anything else\n // (EACCES on Linux without CAP_NET_BIND_SERVICE for unprivileged\n // ports < 1024, etc.) gets the message verbatim but framed the\n // same way — the remedy is the same: pick a different port.\n throw new Error(\n formatHostPortHeldError(hostPort, result.code, result.message),\n );\n}\n\nexport function formatHostPortHeldError(\n hostPort: number,\n code: string,\n systemMessage: string,\n): string {\n const isInUse = code === 'EADDRINUSE';\n const lines: string[] = [];\n if (isInUse) {\n lines.push(`Host port ${hostPort} is already in use by another process.`);\n lines.push('');\n lines.push(`Monoceros needs that port for its Traefik proxy (the thing`);\n lines.push(`that routes <name>.localhost / <name>-<port>.localhost to`);\n lines.push(`your dev-container). Two ways out:`);\n lines.push('');\n lines.push(' 1) Recommended: free the port.');\n lines.push(' Identify the process holding it:');\n lines.push(` sudo lsof -iTCP:${hostPort} -sTCP:LISTEN -n -P`);\n lines.push(` # or: sudo ss -tlnp | grep \":${hostPort}\\\\b\"`);\n lines.push(' Then stop or reconfigure that service.');\n lines.push('');\n lines.push(' 2) Move Monoceros off port 80. Edit (or create)');\n lines.push(' ~/.monoceros/monoceros-config.yml and add:');\n lines.push('');\n lines.push(' schemaVersion: 1');\n lines.push(' routing:');\n lines.push(' hostPort: 8080 # any free port');\n lines.push('');\n lines.push(' URLs will become http://<name>.localhost:8080/.');\n lines.push('');\n lines.push(`Aborting — re-run after the conflict is resolved.`);\n } else {\n lines.push(`Cannot reach host port ${hostPort}: ${systemMessage}`);\n lines.push('');\n lines.push(`This is not the typical \"port already in use\" case —`);\n lines.push(`Monoceros's pre-flight uses a TCP-connect probe (not a`);\n lines.push(`bind), so EACCES / privileged-port errors normally don't`);\n lines.push(`appear here. Most likely something on your host network`);\n lines.push(`stack (firewall, network namespace, …) is interfering with`);\n lines.push(`loopback connects.`);\n lines.push('');\n lines.push('Workaround: move Monoceros off this port by setting');\n lines.push('`routing.hostPort` in ~/.monoceros/monoceros-config.yml.');\n lines.push('');\n lines.push(`Aborting — re-run after the issue is resolved.`);\n }\n return lines.join('\\n');\n}\n","// Catalogs of supported language toolchains and backing services for\n// the yml profile. Curated whitelists keep the surface small and\n// reviewable; unknown values are rejected up front rather than passed\n// through to devcontainer / compose.\n\nimport type { ServiceHealthcheck, ServiceObject } from '../config/schema.js';\nimport type { ResolvedService } from './types.js';\n\n// Monoceros runtime image — thin layer on top of Microsoft's\n// typescript-node base (see images/runtime/Dockerfile). The default\n// points at the floating major tag on GHCR, so an `apply` from a\n// fresh install pulls a published image without further setup.\n//\n// Contributors who are iterating on the runtime image itself\n// (`pnpm image:build` → `monoceros-runtime:dev`) can override this\n// via the `MONOCEROS_BASE_IMAGE_OVERRIDE` env var to point at their\n// local tag without editing source. Empty or whitespace-only values\n// are ignored so an accidentally-set-blank var doesn't break apply.\nconst DEFAULT_BASE_IMAGE = 'ghcr.io/getmonoceros/monoceros-runtime:1';\nconst override = process.env.MONOCEROS_BASE_IMAGE_OVERRIDE?.trim();\nexport const BASE_IMAGE =\n override && override.length > 0 ? override : DEFAULT_BASE_IMAGE;\n\nexport interface LanguageEntry {\n id: string;\n feature: string;\n}\n\n// `node` is included in the base runtime image, so the bare entry\n// `languages: [node]` is accepted as input but installs nothing\n// extra. Versioned node — `node:20` — bypasses the builtin set and\n// goes through the upstream feature like the other languages,\n// because the base image's node version (22) isn't selectable\n// otherwise.\nexport const BUILTIN_LANGUAGES = new Set(['node']);\n\nexport const LANGUAGE_CATALOG: Readonly<Record<string, LanguageEntry>> = {\n node: { id: 'node', feature: 'ghcr.io/devcontainers/features/node:1' },\n python: { id: 'python', feature: 'ghcr.io/devcontainers/features/python:1' },\n java: { id: 'java', feature: 'ghcr.io/devcontainers/features/java:1' },\n go: { id: 'go', feature: 'ghcr.io/devcontainers/features/go:1' },\n rust: { id: 'rust', feature: 'ghcr.io/devcontainers/features/rust:1' },\n dotnet: { id: 'dotnet', feature: 'ghcr.io/devcontainers/features/dotnet:2' },\n};\n\n/**\n * Language entries in a container yml may carry an optional\n * version suffix: `java:17`, `node:20`. The suffix is anything\n * the upstream devcontainer feature accepts as its `version`\n * option (typically `latest`, a major like `17`, or an exact\n * semver like `3.12.1`).\n */\nexport const LANGUAGE_SPEC_RE = /^([a-z][a-z0-9-]*)(?::([A-Za-z0-9._-]+))?$/;\n\nexport interface LanguageSpec {\n name: string;\n version?: string;\n}\n\n/**\n * Split a yml language entry into name + optional version. Returns\n * `null` when the input is not a valid language spec. Callers use\n * that null to surface a schema error.\n */\nexport function parseLanguageSpec(spec: string): LanguageSpec | null {\n const m = LANGUAGE_SPEC_RE.exec(spec);\n if (!m) return null;\n return { name: m[1]!, ...(m[2] !== undefined ? { version: m[2] } : {}) };\n}\n\nexport interface ServiceEntry {\n id: string;\n image: string;\n /**\n * Literal dev-default values for the service's env vars. These are\n * rendered as `${KEY}` *placeholders* into the yml (expandCuratedService)\n * and seeded as `KEY=<default>` into `<name>.env` (curatedServiceEnvDefaults),\n * so the yml is shareable without baking credentials in while the\n * connection string stays predictable out of the box.\n */\n env?: Readonly<Record<string, string>>;\n /**\n * Readiness probe. Curated services ship one so the workspace's\n * `depends_on` gates on `service_healthy` (actually accepting\n * connections) rather than just `service_started`. `${VAR}` in the\n * test resolves from `<name>.env` at apply time like any other field.\n */\n healthcheck?: ServiceHealthcheck;\n /**\n * Container-side mount target for the service's persistent data.\n * Monoceros bind-mounts this onto `<container-dir>/data/<id>/` on\n * the host so DB content is visible in the host filesystem\n * (browsable, backupable, removable with the usual tools instead\n * of `docker volume ...`). See ADR 0003 for the per-container\n * state-model the data dir slots into.\n */\n dataMount?: string;\n /**\n * Default in-container port the service listens on. Used by\n * `monoceros tunnel <name> <service>` to resolve the service-name\n * to a port without an extra CLI argument. See ADR 0009.\n */\n defaultPort: number;\n}\n\n// The `monoceros` user/password/db below are deliberate dev-only\n// defaults, not secrets. A curated service renders its env as `${KEY}`\n// placeholders into the yml and seeds these literals into the gitignored\n// `<name>.env`, so the yml stays shareable while the connection string\n// is predictable out of the box — any builder running this workbench\n// knows it at a glance:\n//\n// postgresql://monoceros:monoceros@postgres:5432/monoceros\n// mysql://monoceros:monoceros@mysql:3306/monoceros\n//\n// To use a real password, change the value in `<name>.env` (it never\n// leaves the host, never rides along when the yml is shared). Because\n// the default isn't a secret, the secret-masking layer\n// (util/mask-secrets.ts) doesn't and shouldn't mask it.\nexport const SERVICE_CATALOG: Readonly<Record<string, ServiceEntry>> = {\n postgres: {\n id: 'postgres',\n image: 'postgres:18',\n env: {\n POSTGRES_USER: 'monoceros',\n POSTGRES_PASSWORD: 'monoceros',\n POSTGRES_DB: 'monoceros',\n },\n healthcheck: {\n test: [\n 'CMD',\n 'pg_isready',\n '-U',\n '${POSTGRES_USER}',\n '-d',\n '${POSTGRES_DB}',\n ],\n interval: '10s',\n timeout: '5s',\n retries: 5,\n },\n // Postgres 18+ stores data under /var/lib/postgresql/<major>/, so\n // the recommended mount is the parent directory; pre-18 used\n // /var/lib/postgresql/data directly. See\n // https://github.com/docker-library/postgres/pull/1259.\n dataMount: '/var/lib/postgresql',\n defaultPort: 5432,\n },\n mysql: {\n id: 'mysql',\n image: 'mysql:8',\n env: {\n MYSQL_ROOT_PASSWORD: 'monoceros',\n MYSQL_DATABASE: 'monoceros',\n },\n healthcheck: {\n test: [\n 'CMD',\n 'mysqladmin',\n 'ping',\n '-h',\n '127.0.0.1',\n '-u',\n 'root',\n '-p${MYSQL_ROOT_PASSWORD}',\n ],\n interval: '10s',\n timeout: '5s',\n retries: 5,\n },\n dataMount: '/var/lib/mysql',\n defaultPort: 3306,\n },\n redis: {\n id: 'redis',\n image: 'redis:8',\n healthcheck: {\n test: ['CMD', 'redis-cli', 'ping'],\n interval: '10s',\n timeout: '5s',\n retries: 5,\n },\n dataMount: '/data',\n defaultPort: 6379,\n },\n};\n\nexport function knownLanguages(): string[] {\n return [...BUILTIN_LANGUAGES, ...Object.keys(LANGUAGE_CATALOG)].sort();\n}\n\nexport function knownServices(): string[] {\n return Object.keys(SERVICE_CATALOG).sort();\n}\n\n/**\n * Normalize a `services:` object to a `ResolvedService` — fills the two\n * fields the scaffold treats as always-present (env, volumes) with their\n * empty defaults. `${VAR}` references in env/command pass through\n * untouched; they're resolved against `<name>.env` at apply time\n * (config/env-file.ts).\n */\nexport function resolveService(entry: ServiceObject): ResolvedService {\n return {\n name: entry.name,\n image: entry.image,\n ...(entry.port !== undefined ? { port: entry.port } : {}),\n env: entry.env ? { ...entry.env } : {},\n volumes: entry.volumes ? [...entry.volumes] : [],\n ...(entry.healthcheck ? { healthcheck: entry.healthcheck } : {}),\n ...(entry.restart ? { restart: entry.restart } : {}),\n ...(entry.command ? { command: entry.command } : {}),\n };\n}\n\n/** Whether `name` is a known curated catalog service. */\nexport function isCuratedService(name: string): boolean {\n return Object.prototype.hasOwnProperty.call(SERVICE_CATALOG, name);\n}\n\n/**\n * Expand a curated catalog name into a full `ServiceObject` — the\n * init-sugar form written into the yml so the builder sees (and can\n * edit) every field. Env values render as `${KEY}` placeholders (their\n * literal defaults are seeded into `<name>.env` via\n * `curatedServiceEnvDefaults`), so the yml is shareable without baking\n * credentials in. Throws if `name` isn't curated.\n */\nexport function expandCuratedService(name: string): ServiceObject {\n const def = SERVICE_CATALOG[name];\n if (!def) {\n throw new Error(\n `Unknown service '${name}'. Known catalog services: ${knownServices().join(', ')}.`,\n );\n }\n return {\n name: def.id,\n image: def.image,\n port: def.defaultPort,\n ...(def.env\n ? {\n env: Object.fromEntries(\n Object.keys(def.env).map((k) => [k, `\\${${k}}`]),\n ),\n }\n : {}),\n ...(def.dataMount ? { volumes: [`data:${def.dataMount}`] } : {}),\n ...(def.healthcheck ? { healthcheck: def.healthcheck } : {}),\n restart: 'unless-stopped',\n };\n}\n\n/**\n * The literal `KEY=<default>` values to seed into `<name>.env` for a\n * curated service's `${KEY}` env placeholders — the same dev-defaults\n * the catalog declares. Empty for services without env (redis).\n * `init` and `add-service` upsert these so the builder gets a working\n * container without filling anything, yet can change a value (e.g. a\n * real password) in one gitignored place. Returns `{}` for non-curated\n * names.\n */\nexport function curatedServiceEnvDefaults(\n name: string,\n): Record<string, string> {\n const def = SERVICE_CATALOG[name];\n return def?.env ? { ...def.env } : {};\n}\n\n/**\n * Derive a compose service name from an image ref. Takes the last\n * path segment, strips the tag/digest, lowercases and sanitises:\n * rustfs/rustfs:latest → rustfs\n * postgres:16-alpine → postgres\n * ghcr.io/foo/bar:1 → bar\n * ghcr.io:5000/x/app → app\n */\nexport function deriveServiceName(image: string): string {\n const lastSegment = image.split('/').pop() ?? image;\n const noTag = lastSegment.split('@')[0]!.split(':')[0]!;\n return noTag.toLowerCase().replace(/[^a-z0-9_-]/g, '-');\n}\n","import type { ServiceObject } from '../config/schema.js';\n\n/**\n * Renders a `services:` entry as YAML lines for the **map body** at\n * column 0 (no leading `- `). Two consumers share this:\n * - the init generator indents the body into a sequence item;\n * - `add-service` parses the body into a node and appends it to the\n * services seq (comments and all).\n *\n * Curated services render fully active (every catalog default visible\n * and editable). Custom images render `name` + `image` active plus a\n * commented scaffold of the optional fields — Monoceros can't know a\n * non-curated image's env/ports/volumes, so it shows the knobs rather\n * than guessing.\n */\n\n/** Full active map body for a known ServiceObject (curated, expanded). */\nexport function renderServiceObjectBody(svc: ServiceObject): string[] {\n const lines: string[] = [`name: ${svc.name}`, `image: ${svc.image}`];\n if (svc.port !== undefined) lines.push(`port: ${svc.port}`);\n if (svc.env && Object.keys(svc.env).length > 0) {\n lines.push('env:');\n for (const [k, v] of Object.entries(svc.env)) {\n lines.push(` ${k}: ${v}`);\n }\n }\n if (svc.volumes && svc.volumes.length > 0) {\n lines.push('volumes:');\n for (const vol of svc.volumes) lines.push(` - ${vol}`);\n }\n if (svc.restart) lines.push(`restart: ${svc.restart}`);\n if (svc.command !== undefined) lines.push(`command: ${svc.command}`);\n if (svc.healthcheck) {\n lines.push('healthcheck:');\n const test = svc.healthcheck.test;\n lines.push(\n Array.isArray(test)\n ? ` test: [${test.map((t) => JSON.stringify(t)).join(', ')}]`\n : ` test: ${test}`,\n );\n if (svc.healthcheck.interval)\n lines.push(` interval: ${svc.healthcheck.interval}`);\n if (svc.healthcheck.timeout)\n lines.push(` timeout: ${svc.healthcheck.timeout}`);\n if (svc.healthcheck.retries !== undefined)\n lines.push(` retries: ${svc.healthcheck.retries}`);\n if (svc.healthcheck.startPeriod)\n lines.push(` startPeriod: ${svc.healthcheck.startPeriod}`);\n }\n return lines;\n}\n\n/**\n * A custom (non-curated) image entry: `name` + `image` active, the rest\n * as a commented scaffold so the builder sees the fields Monoceros can't\n * infer. Returns the active body lines plus the scaffold as a YAML\n * `comment` string (no leading `#` — the serializer adds it; attaching\n * it as `node.comment` is the only way the comment survives being moved\n * into the services sequence).\n */\nexport function renderCustomService(\n name: string,\n image: string,\n): { bodyLines: string[]; comment: string } {\n const bodyLines = [`name: ${name}`, `image: ${image}`];\n const comment = [\n ' port: 8080 # in-container port → `monoceros tunnel`',\n ' env: # values resolved from <name>.env',\n ' KEY: ${SOME_VAR}',\n ' volumes:',\n ` - data:/data # persistent host bind-mount under data/${name}`,\n ' - rel/host/path:/in/container:ro',\n ' healthcheck:',\n ' test: curl -f http://localhost:8080/health',\n ' restart: unless-stopped',\n ].join('\\n');\n return { bodyLines, comment };\n}\n\n/**\n * One-line builder-facing hint printed after a custom service is added,\n * pointing at the commented scaffold the builder needs to fill in.\n */\nexport function customServiceHint(name: string): string {\n return (\n `'${name}' is a custom image — Monoceros doesn't know its env, ports or volumes. ` +\n `Review the commented block under services[].${name} in the yml and fill in what the image needs.`\n );\n}\n","import { existsSync, readFileSync, promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { workbenchCheckoutRoot } from '../config/paths.js';\nimport { matchMonocerosFeature } from '../util/ref.js';\nimport {\n BASE_IMAGE,\n BUILTIN_LANGUAGES,\n LANGUAGE_CATALOG,\n knownLanguages,\n parseLanguageSpec,\n} from './catalog.js';\nimport type { CreateOptions } from './types.js';\n\n// Debian/Ubuntu apt package name rules: start with alphanumeric, then\n// alphanumerics + `.+-` are allowed. We intentionally don't allow shell\n// metacharacters (`;`, `&`, `|`, `$`, `(`, …) so a typo can't smuggle\n// arbitrary shell into the apt-packages feature config.\nconst APT_PACKAGE_NAME_RE = /^[a-z0-9][a-z0-9.+-]*$/;\n\n// Devcontainer feature refs are OCI-style:\n// <registry>/<namespace>/<feature>:<tag>\n// e.g. ghcr.io/devcontainers/features/python:1\n// ghcr.io/getmonoceros/monoceros-features/claude-code:1\nconst FEATURE_REF_RE = /^[a-z0-9.-]+(\\/[a-z0-9._-]+)+:[a-z0-9._-]+$/;\n\n// Install URLs must be https:// (no plain http, no other schemes) and\n// contain only URL-safe characters. We deliberately reject shell\n// metacharacters even inside a query string — the URL is embedded into\n// a generated bash script, and a stray `$` or backtick would be a\n// shell-injection vector.\nconst INSTALL_URL_RE = /^https:\\/\\/[A-Za-z0-9.\\-_~/:?#[\\]@!&'()*+,;=%]+$/;\n\n// Git URLs: covers HTTPS, SSH (`git@host:path/repo.git`), and\n// `ssh://`/`git://` schemes. Permissive but no shell metacharacters.\nconst REPO_URL_RE = /^[A-Za-z0-9@:/+_~.#=&?-]+$/;\n\n// Repo destination = path under `projects/`. Allows nested subfolders\n// (`apps/web`) via `/`; segments use `[A-Za-z0-9._-]` (same charset as\n// a leaf folder name). `.` / `..` segments are rejected separately\n// because the regex alone allows pure-dot segments.\nconst REPO_PATH_RE = /^[A-Za-z0-9._-]+(\\/[A-Za-z0-9._-]+)*$/;\n\n/**\n * Derive a repo name from its URL.\n *\n * `git@github.com:foo/bar.git` → `bar`\n * `https://github.com/foo/bar.git` → `bar`\n * `https://github.com/foo/bar` → `bar`\n * `ssh://git@host:22/foo/bar.git` → `bar`\n */\nexport function deriveRepoName(url: string): string {\n const lastSep = Math.max(url.lastIndexOf('/'), url.lastIndexOf(':'));\n const tail = url.slice(lastSep + 1);\n return tail.replace(/\\.git$/, '');\n}\n\nexport function validateOptions(opts: CreateOptions): void {\n if (!opts.name || !/^[a-zA-Z0-9._-]+$/.test(opts.name)) {\n throw new Error(\n `Invalid solution name: ${JSON.stringify(opts.name)}. Use letters, digits, '.', '_' or '-'.`,\n );\n }\n for (const langSpec of opts.languages) {\n const parsed = parseLanguageSpec(langSpec);\n if (!parsed) {\n throw new Error(\n `Invalid language spec: ${JSON.stringify(langSpec)}. Expected '<name>' or '<name>:<version>'.`,\n );\n }\n if (!BUILTIN_LANGUAGES.has(parsed.name) && !LANGUAGE_CATALOG[parsed.name]) {\n throw new Error(\n `Unknown language: ${parsed.name}. Known: ${knownLanguages().join(', ')}.`,\n );\n }\n }\n // Services arrive here already resolved (curated strings expanded\n // against the catalog, objects taken as-is — see resolveService).\n // What's left to enforce are the cross-service invariants the schema\n // can't see: each name is unique and none collides with the reserved\n // `workspace` compose service.\n const seenServiceNames = new Set<string>();\n for (const svc of opts.services) {\n if (!svc.image) {\n throw new Error(\n `Service ${JSON.stringify(svc.name)} has no image. Every service needs an 'image:'.`,\n );\n }\n if (svc.name === 'workspace') {\n throw new Error(\n `Invalid service name 'workspace': it collides with the reserved devcontainer workspace service. Pick another name.`,\n );\n }\n if (seenServiceNames.has(svc.name)) {\n throw new Error(\n `Duplicate service name: ${JSON.stringify(svc.name)}. Each services[] entry must have a unique name.`,\n );\n }\n seenServiceNames.add(svc.name);\n }\n for (const pkg of opts.aptPackages ?? []) {\n if (!APT_PACKAGE_NAME_RE.test(pkg)) {\n throw new Error(\n `Invalid apt package name: ${JSON.stringify(pkg)}. Expected lowercase alphanumeric plus '.+-'.`,\n );\n }\n }\n for (const ref of Object.keys(opts.features ?? {})) {\n if (!FEATURE_REF_RE.test(ref)) {\n throw new Error(\n `Invalid devcontainer feature ref: ${JSON.stringify(ref)}. Expected OCI-image-style ref like 'ghcr.io/devcontainers/features/<name>:<tag>'.`,\n );\n }\n }\n for (const url of opts.installUrls ?? []) {\n if (!INSTALL_URL_RE.test(url)) {\n throw new Error(\n `Invalid install URL: ${JSON.stringify(url)}. Must start with 'https://' and contain only URL-safe characters (no shell metacharacters).`,\n );\n }\n }\n const seenRepoPaths = new Set<string>();\n for (const repo of opts.repos ?? []) {\n if (!REPO_URL_RE.test(repo.url)) {\n throw new Error(\n `Invalid repo URL: ${JSON.stringify(repo.url)}. Use HTTPS or SSH/git@ form; no shell metacharacters.`,\n );\n }\n if (!REPO_PATH_RE.test(repo.path)) {\n throw new Error(\n `Invalid repo path: ${JSON.stringify(repo.path)}. Use letters/digits/'._-', forward slashes for nested folders, no leading or trailing slash.`,\n );\n }\n if (repo.path.split('/').some((seg) => seg === '..' || seg === '.')) {\n throw new Error(\n `Invalid repo path: ${JSON.stringify(repo.path)}. Path segments cannot be \".\" or \"..\".`,\n );\n }\n if (seenRepoPaths.has(repo.path)) {\n throw new Error(\n `Duplicate repo path: ${JSON.stringify(repo.path)}. Each projects/<path> folder must be unique — pass --path to disambiguate.`,\n );\n }\n seenRepoPaths.add(repo.path);\n }\n}\n\n// Normalize: dedupe + sort + drop postgres from compose services when an\n// external --postgres-url is provided.\nexport function normalizeOptions(opts: CreateOptions): CreateOptions {\n const languages = [...new Set(opts.languages)].sort();\n // Dedupe services by name (last write wins) and sort by name so the\n // generated compose/devcontainer output is deterministic regardless\n // of yml order. An external `--postgres-url` drops a curated postgres\n // service — the workspace talks to the external DB instead.\n const serviceByName = new Map<string, (typeof opts.services)[number]>();\n for (const svc of opts.services) {\n if (opts.postgresUrl && svc.name === 'postgres') continue;\n serviceByName.set(svc.name, svc);\n }\n const services = [...serviceByName.values()].sort((a, b) =>\n a.name.localeCompare(b.name),\n );\n const aptPackages = [...new Set(opts.aptPackages ?? [])].sort();\n // Sort feature refs alphabetically so devcontainer.json + stack.json\n // output is deterministic regardless of insertion order.\n const features = opts.features\n ? Object.fromEntries(\n Object.entries(opts.features).sort(([a], [b]) => a.localeCompare(b)),\n )\n : undefined;\n // Install URLs preserve insertion order (installs may depend on each\n // other), but we deduplicate to keep stack.json stable across re-adds.\n const installUrls = opts.installUrls\n ? [...new Set(opts.installUrls)]\n : undefined;\n // Repos: preserve insertion order, dedupe by (url, name, branch)\n // signature — same triple twice is a no-op, different triples\n // coexist. (Same name with different URL is a validation error\n // in validateOptions, not silently merged here.)\n const repos = opts.repos\n ? Array.from(\n new Map(opts.repos.map((r) => [`${r.url}\u0001${r.path}`, r])).values(),\n )\n : undefined;\n // Ports: preserve insertion order — the first entry doubles as the\n // default route under `<name>.localhost`, so reordering would\n // silently change which app the bare hostname points at. Dedupe to\n // keep the dynamic config and `forwardPorts` deterministic.\n const ports = opts.ports ? [...new Set(opts.ports)] : undefined;\n return {\n name: opts.name,\n languages,\n services,\n postgresUrl: opts.postgresUrl,\n ...(aptPackages.length > 0 ? { aptPackages } : {}),\n ...(features && Object.keys(features).length > 0 ? { features } : {}),\n ...(installUrls && installUrls.length > 0 ? { installUrls } : {}),\n ...(repos && repos.length > 0 ? { repos } : {}),\n ...(ports && ports.length > 0 ? { ports } : {}),\n ...(opts.vscodeAutoForward !== undefined\n ? { vscodeAutoForward: opts.vscodeAutoForward }\n : {}),\n };\n}\n\nexport function needsCompose(opts: CreateOptions): boolean {\n return opts.services.length > 0;\n}\n\ninterface DevcontainerImageMode {\n name: string;\n image: string;\n remoteUser: string;\n // Scaffold-level mounts: only the SSH-agent forward for git auth when\n // the yml lists repos. Tool-specific mounts (e.g. ~/.claude for the\n // claude-code feature) come from the feature's own manifest, not from\n // here.\n mounts?: string[];\n // Bind mount that puts the host's container folder onto a known\n // path inside the container. Pairs with `workspaceFolder` below.\n // Always emitted; the host-side source uses devcontainer-cli's\n // `${localWorkspaceFolder}` variable so the tooling expands it.\n workspaceMount?: string;\n // Where the workspace lives inside the container. VS Code's Dev\n // Containers extension uses this to translate host-side paths\n // (from .code-workspace files, \"Open Folder in Container\", …) to\n // their container counterpart. Without it, VS Code passes the raw\n // host path through and aborts because that path doesn't exist\n // inside the container.\n workspaceFolder?: string;\n // Required so the runtime image's entrypoint can install iptables\n // rules if MONOCEROS_EGRESS=enforce is set. Default mode is `off`\n // (see ADR 0002) so the cap is harmless when unused.\n runArgs: string[];\n forwardPorts: number[];\n postCreateCommand: string;\n features?: Record<string, Record<string, unknown>>;\n // Env vars injected into the workspace container at start time\n // (inherited by postCreateCommand). Used by add-repo to wire the\n // forwarded SSH-agent socket and a permissive SSH host-key policy.\n containerEnv?: Record<string, string>;\n // VS Code-specific overrides written into the materialized\n // devcontainer.json. Today only carries `remote.autoForwardPorts`\n // (toggled by `ide.vscodeAutoForward` from the yml). Future\n // feature/yml fields can extend the shape additively.\n customizations?: DevcontainerCustomizations;\n}\n\ninterface DevcontainerComposeMode {\n name: string;\n dockerComposeFile: string;\n service: string;\n // Without runServices, `devcontainer up` only brings up the named service.\n // Listing the auxiliary services here ensures postgres/redis/… come up\n // alongside the workspace container.\n runServices?: string[];\n workspaceFolder: string;\n remoteUser: string;\n forwardPorts: number[];\n postCreateCommand: string;\n features?: Record<string, Record<string, unknown>>;\n customizations?: DevcontainerCustomizations;\n}\n\ninterface DevcontainerCustomizations {\n vscode?: {\n settings?: Record<string, unknown>;\n extensions?: string[];\n };\n}\n\n/**\n * The host docker daemon's mode — passed in by `apply` after a\n * `docker info` probe. Drives whether we emit `idmap` on bind\n * mounts. See `devcontainer/docker-mode.ts` for the rationale.\n */\nexport type DockerMode = 'rootful' | 'rootless';\n\n// Repo auth note: Monoceros supports HTTPS-only repo URLs (see ADR\n// 0006). The host's git credential helper provides the username/token\n// per host (osxkeychain on macOS, libsecret on Linux, wincred on\n// Windows, plus `gh auth setup-git` for GitHub specifically), the\n// apply pipeline writes them to <container-dir>/.monoceros/git-\n// credentials, and post-create.sh wires `git config --global\n// credential.helper \"store --file=…\"` so the container reads from\n// the same file. SSH-agent forwarding, multi-key wiring, host-OS\n// platform-specific socket paths — all that complexity stays out.\n\nexport type DevcontainerJson = DevcontainerImageMode | DevcontainerComposeMode;\n\n/**\n * Per-feature plan for the container build.\n *\n * - `devcontainerKey` — the key used in `devcontainer.json → features`.\n * - `localSourceDir` / `localName` — set when the workbench has the\n * feature on disk. `writeScaffold` copies the directory into\n * `<container>/.devcontainer/features/<name>/` and uses the\n * relative path `./features/<name>` in devcontainer.json.\n * (devcontainer-cli accepts relative paths from `.devcontainer/`\n * but rejects absolute filesystem paths to local features.)\n */\ninterface ResolvedFeature {\n devcontainerKey: string;\n options: Record<string, unknown>;\n localSourceDir?: string;\n localName?: string;\n /**\n * Subdirectories of `/home/node/` that this feature wants to\n * persist across container rebuilds. Each entry is bind-mounted\n * from `<container-dir>/home/<path>` into `/home/node/<path>` and\n * pre-created as an empty directory on the host. Read from the\n * feature manifest's `x-monoceros.persistentHomePaths` array.\n */\n persistentHomePaths: string[];\n /**\n * Like `persistentHomePaths`, but for individual **files** rather\n * than directories. Necessary for tools that keep state in a\n * dotfile next to (not inside) their config directory — e.g.\n * Claude Code's `~/.claude.json` lives alongside `~/.claude/`.\n *\n * Each entry can be a bare path string (file pre-created empty)\n * or `{ path, initialContent }` so a feature author can seed\n * valid initial content. The latter is needed for tools that\n * refuse to parse an empty file: Claude Code, for instance, errors\n * on an empty `.claude.json` and demands at least `{}`. Read from\n * the feature manifest's `x-monoceros.persistentHomeFiles` array.\n */\n persistentHomeFiles: PersistentHomeFile[];\n}\n\ninterface PersistentHomeFile {\n path: string;\n initialContent: string;\n}\n\n/**\n * Compute the feature list for `opts`. Detects Monoceros-owned refs\n * (`ghcr.io/getmonoceros/monoceros-features/<name>:<tag>`) and, if\n * the workbench has the feature on disk, rewrites the key to\n * `./features/<name>` and records the source for the copy step.\n *\n * Third-party refs and Monoceros refs without a local source pass\n * through verbatim — devcontainer-cli pulls them from the registry.\n */\nexport function resolveFeatures(opts: CreateOptions): ResolvedFeature[] {\n const resolved: ResolvedFeature[] = [];\n\n for (const langSpec of opts.languages) {\n const parsed = parseLanguageSpec(langSpec);\n if (!parsed) continue;\n // Builtin only applies to the bare `node` (no version) — the\n // base image's node 22 isn't pinnable, so any `node:<version>`\n // has to go through the upstream feature like everything else.\n if (BUILTIN_LANGUAGES.has(parsed.name) && parsed.version === undefined) {\n continue;\n }\n const entry = LANGUAGE_CATALOG[parsed.name];\n if (!entry) continue;\n const options: Record<string, string> = {};\n if (parsed.version !== undefined) options.version = parsed.version;\n resolved.push({\n devcontainerKey: entry.feature,\n options,\n persistentHomePaths: [],\n persistentHomeFiles: [],\n });\n }\n if (opts.aptPackages && opts.aptPackages.length > 0) {\n resolved.push({\n devcontainerKey: 'ghcr.io/devcontainers-contrib/features/apt-packages:1',\n options: { packages: opts.aptPackages.join(',') },\n persistentHomePaths: [],\n persistentHomeFiles: [],\n });\n }\n if (opts.features) {\n for (const [rawRef, options] of Object.entries(opts.features)) {\n const match = matchMonocerosFeature(rawRef);\n if (match) {\n const name = match.name;\n // Dev-only fallback: when the CLI is run from a workbench\n // checkout, prefer the on-disk copy under `images/features/`\n // so feature edits are testable without a publish. In prod\n // (npm-installed), `workbenchCheckoutRoot()` returns null\n // and we fall through to the GHCR-ref passthrough.\n const checkout = workbenchCheckoutRoot();\n const localSourceDir = checkout\n ? path.join(checkout, 'images', 'features', name)\n : null;\n if (localSourceDir && existsSync(localSourceDir)) {\n const { paths, files } = readPersistentHomeEntries(localSourceDir);\n resolved.push({\n devcontainerKey: `./features/${name}`,\n options,\n localSourceDir,\n localName: name,\n persistentHomePaths: paths,\n persistentHomeFiles: files,\n });\n continue;\n }\n }\n resolved.push({\n devcontainerKey: rawRef,\n options,\n persistentHomePaths: [],\n persistentHomeFiles: [],\n });\n }\n }\n return resolved;\n}\n\n/**\n * Read `x-monoceros.persistentHomePaths` and\n * `x-monoceros.persistentHomeFiles` from a feature's manifest on\n * disk. Returns `{paths: [], files: []}` when the manifest doesn't\n * exist, can't be parsed, or the fields are missing — always\n * best-effort, never throws. Both arrays are validated to contain\n * only safe relative subpaths (no `..`, no absolute, no shell\n * metacharacters); anything else is silently dropped, since a bad\n * value here is a feature-author bug, not something a builder can fix.\n */\nfunction readPersistentHomeEntries(localSourceDir: string): {\n paths: string[];\n files: PersistentHomeFile[];\n} {\n const manifestPath = path.join(localSourceDir, 'devcontainer-feature.json');\n try {\n const text = readFileSync(manifestPath, 'utf8');\n const parsed = JSON.parse(text) as {\n 'x-monoceros'?: {\n persistentHomePaths?: unknown;\n persistentHomeFiles?: unknown;\n };\n };\n return {\n paths: filterSubpaths(parsed['x-monoceros']?.persistentHomePaths),\n files: filterFileEntries(parsed['x-monoceros']?.persistentHomeFiles),\n };\n } catch {\n return { paths: [], files: [] };\n }\n}\n\nfunction filterSubpaths(raw: unknown): string[] {\n if (!Array.isArray(raw)) return [];\n return raw.filter(\n (p): p is string =>\n typeof p === 'string' &&\n p.length > 0 &&\n !p.startsWith('/') &&\n !p.includes('..') &&\n HOME_SUBPATH_RE.test(p),\n );\n}\n\n/**\n * Accept either bare strings or `{path, initialContent}` objects in\n * `persistentHomeFiles`. Bare string is shorthand for \"create an\n * empty file\"; the object form lets feature authors provide initial\n * content (e.g. `{}` for a JSON config that the tool refuses to\n * parse when empty).\n */\nfunction filterFileEntries(raw: unknown): PersistentHomeFile[] {\n if (!Array.isArray(raw)) return [];\n const result: PersistentHomeFile[] = [];\n for (const entry of raw) {\n if (typeof entry === 'string') {\n if (isValidHomeSubpath(entry)) {\n result.push({ path: entry, initialContent: '' });\n }\n continue;\n }\n if (\n entry !== null &&\n typeof entry === 'object' &&\n 'path' in entry &&\n typeof (entry as { path: unknown }).path === 'string'\n ) {\n const e = entry as { path: string; initialContent?: unknown };\n if (!isValidHomeSubpath(e.path)) continue;\n const initialContent =\n typeof e.initialContent === 'string' ? e.initialContent : '';\n result.push({ path: e.path, initialContent });\n }\n }\n return result;\n}\n\nfunction isValidHomeSubpath(p: string): boolean {\n return (\n p.length > 0 &&\n !p.startsWith('/') &&\n !p.includes('..') &&\n HOME_SUBPATH_RE.test(p)\n );\n}\n\n// Home subpaths: dot-prefixed dirs and config-like sub-dirs.\n// Restrictive on purpose — only `.foo`, `.foo/bar`, `foo`, `foo/bar`,\n// no whitespace, no shell metacharacters.\nconst HOME_SUBPATH_RE = /^[A-Za-z0-9._-]+(\\/[A-Za-z0-9._-]+)*$/;\n\nexport function buildDevcontainerJson(\n opts: CreateOptions,\n dockerMode: DockerMode = 'rootful',\n): DevcontainerJson {\n const resolvedFeatures = resolveFeatures(opts);\n const features: Record<string, Record<string, unknown>> = {};\n for (const f of resolvedFeatures) {\n features[f.devcontainerKey] = f.options;\n }\n\n const featuresField =\n Object.keys(features).length > 0 ? { features } : undefined;\n\n // Rootless-Docker bind-mount handling is currently a TODO. Earlier\n // attempts (1.6.3 / 1.6.5) appended `,idmap` / `,idmap=true` to the\n // mount string in the belief Docker supports idmapped mounts via\n // `--mount`. It doesn't — verified against the official docs at\n // https://docs.docker.com/engine/storage/bind-mounts/ — there is\n // no `idmap` key in the `--mount` syntax. Podman supports it,\n // Docker presently doesn't expose the kernel feature on the CLI.\n //\n // For now we emit the same mount strings regardless of dockerMode.\n // That leaves the rootless UID-shift problem (host pre-created\n // dirs appear as root in container; container-written files end\n // up at shifted UIDs on the host) unsolved — separate fix needed,\n // most likely via remoteUser=root in rootless mode so the\n // container's \"root\" maps to the host workspace owner. The\n // dockerMode parameter stays plumbed in so the next attempt can\n // diverge cleanly.\n void dockerMode;\n const idmapSuffix = '';\n\n // Bind-mounts for per-feature persistent home entries. Source on\n // the host is `<container-dir>/home/<subpath>` (under the\n // localWorkspaceFolder); target inside the container is the same\n // subpath under `/home/node/`. Files and directories both go through\n // the same `type=bind` syntax — docker decides from the source's\n // on-disk type. We pre-create both kinds in writeScaffold so the\n // owner matches the host user (otherwise docker auto-creates as\n // root on Linux, breaking writes inside the container) and so a\n // requested **file** bind doesn't get spawned as a directory.\n const homeMounts: string[] = [];\n for (const f of resolvedFeatures) {\n const allSubs = [\n ...f.persistentHomePaths,\n ...f.persistentHomeFiles.map((entry) => entry.path),\n ];\n for (const sub of allSubs) {\n homeMounts.push(\n `source=\\${localWorkspaceFolder}/home/${sub},target=/home/node/${sub},type=bind${idmapSuffix}`,\n );\n }\n }\n\n // VS Code customizations — currently only the `remote.autoForwardPorts`\n // toggle when ports are declared. The default is `false` (Traefik is\n // the single source of truth for external URLs — VS Code's parallel\n // port-forward would be a confusing second URL for the same app).\n // Builders can flip it via `ide.vscodeAutoForward: true` in the\n // yml. See ADR 0007. Other extension hints belong with the feature\n // that needs them (e.g. the claude-code feature recommends\n // `anthropic.claude-code`).\n const ports = opts.ports ?? [];\n const customizationsField =\n ports.length > 0\n ? {\n customizations: {\n vscode: {\n settings: {\n 'remote.autoForwardPorts': opts.vscodeAutoForward ?? false,\n },\n },\n },\n }\n : undefined;\n\n if (needsCompose(opts)) {\n // Compose-mode: per-feature persistent home mounts go onto the\n // workspace service in compose.yaml (see buildComposeYaml). The\n // devcontainer.json just references compose. Network membership\n // (`monoceros-proxy`) lives in compose.yaml's `networks:` block,\n // not here.\n return {\n name: opts.name,\n dockerComposeFile: 'compose.yaml',\n service: 'workspace',\n ...(opts.services.length > 0\n ? { runServices: opts.services.map((s) => s.name) }\n : {}),\n workspaceFolder: `/workspaces/${opts.name}`,\n remoteUser: 'node',\n forwardPorts: ports,\n postCreateCommand: '.devcontainer/post-create.sh',\n ...(featuresField ?? {}),\n ...(customizationsField ?? {}),\n };\n }\n\n // Image-mode mounts: per-feature persistent-home binds.\n const mounts: string[] = [...homeMounts];\n const mountsField = mounts.length > 0 ? { mounts } : {};\n\n // Image-mode workspaces: pin both `workspaceMount` AND\n // `workspaceFolder` explicitly so VS Code's Dev Containers\n // extension knows how the host folder maps into the container.\n //\n // Without these two, \"Open Folder in Container\" / \"Open Workspace\n // in Container\" on a `.code-workspace` falls back to passing the\n // raw host path (e.g. `/Users/.../.local/container/sandbox`) as\n // the container-side workspace path. The container of course has\n // no such directory and VS Code aborts with \"Arbeitsbereich nicht\n // vorhanden\" / \"Workspace does not exist\". Setting workspaceFolder\n // tells VS Code where the workspace lives inside the container\n // (matches what we already do for compose-mode); workspaceMount\n // pins the bind that puts the host folder there.\n //\n // Source path uses `${localWorkspaceFolder}` — devcontainer-cli\n // expands it to the host folder containing the .devcontainer/, no\n // hand-substitution needed on our side.\n const workspaceMountField = {\n workspaceMount: `source=\\${localWorkspaceFolder},target=/workspaces/${opts.name},type=bind,consistency=cached`,\n workspaceFolder: `/workspaces/${opts.name}`,\n };\n\n // Image-mode: when ports are declared, hook the container into the\n // `monoceros-proxy` network so the Traefik singleton can reach it\n // by yml name (`http://<name>:<port>`). `--network` replaces\n // docker's default bridge — for image-mode that's the only network\n // in play, so swapping is fine. ensureProxy() (called from\n // apply/start) creates the network before this `runArgs` value is\n // used.\n //\n // `--network-alias` pins a stable DNS name on the network: by\n // default devcontainer-cli labels image-mode containers with random\n // names like `thirsty_bartik`, which would make Traefik's backend\n // URL non-deterministic. With the alias we know the route in the\n // dynamic config can always point at `http://<name>:<port>`.\n const runArgs = ['--cap-add=NET_ADMIN'];\n if (ports.length > 0) {\n runArgs.push('--network=monoceros-proxy');\n runArgs.push(`--network-alias=${opts.name}`);\n }\n\n return {\n name: opts.name,\n image: BASE_IMAGE,\n remoteUser: 'node',\n ...workspaceMountField,\n ...mountsField,\n runArgs,\n forwardPorts: ports,\n postCreateCommand: '.devcontainer/post-create.sh',\n ...(featuresField ?? {}),\n ...(customizationsField ?? {}),\n };\n}\n\n// Double-quote a YAML scalar, escaping the chars that matter inside a\n// double-quoted YAML string. Always quoting keeps arbitrary service env\n// values (passwords with `:` / `#` / spaces, interpolated secrets, shell\n// commands) safe without pulling in a YAML serializer.\nexport function composeScalar(value: string): string {\n const escaped = value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\t/g, '\\\\t');\n return `\"${escaped}\"`;\n}\n\n// Rewrite a service volume's source segment to a path relative to the\n// `.devcontainer/` directory (where compose runs). The `data` shorthand\n// maps to the per-service bind-mounted data dir under `../data/<name>`;\n// any other (relative, validated) host path is prefixed with `../` to\n// reach up to the container root. The destination + mode segments pass\n// through verbatim.\nexport function composeVolumeSource(spec: string, serviceName: string): string {\n const parts = spec.split(':');\n const src = parts[0]!;\n const rest = parts.slice(1).join(':');\n if (src === 'data') return `../data/${serviceName}:${rest}`;\n // Host-relative source: strip a leading `./` (compose habit) so the\n // `../` prefix that walks up to the container root stays clean.\n const relative = src.startsWith('./') ? src.slice(2) : src;\n return `../${relative}:${rest}`;\n}\n\n// Hand-rolled YAML for compose.yaml. The shape is narrow enough that\n// avoiding a YAML dependency outweighs the cost of careful indentation.\n//\n// `dockerMode` is plumbed in for symmetry with buildDevcontainerJson\n// and future rootless-specific tweaks, but currently unused (see the\n// TODO in buildDevcontainerJson re: docker not exposing idmap on\n// `--mount`).\nexport function buildComposeYaml(\n opts: CreateOptions,\n dockerMode: DockerMode = 'rootful',\n): string {\n void dockerMode;\n const hasPorts = (opts.ports?.length ?? 0) > 0;\n const lines: string[] = ['services:'];\n\n lines.push(' workspace:');\n lines.push(` image: ${BASE_IMAGE}`);\n lines.push(\" command: 'sleep infinity'\");\n // No `user:` directive here — the runtime image's entrypoint runs as\n // root to set up iptables, then drops to the `node` user via gosu\n // before exec'ing the command. NET_ADMIN is required for that\n // iptables setup; see ADR 0002.\n lines.push(' cap_add:');\n lines.push(' - NET_ADMIN');\n if (hasPorts) {\n // Workspace joins both the compose-default network (so it can\n // reach postgres/redis/… that share the project) and the\n // monoceros-proxy network (so Traefik can route to it). Use the\n // long form so we can pin a stable DNS alias on monoceros-proxy:\n // without the alias every compose-mode container would show up\n // as `workspace` (compose service name) and collide between\n // multiple monoceros containers. The alias is the yml name; the\n // dynamic config writes routes against `http://<name>:<port>`.\n // See ADR 0007.\n lines.push(' networks:');\n lines.push(' default: {}');\n lines.push(' monoceros-proxy:');\n lines.push(' aliases:');\n lines.push(` - ${opts.name}`);\n }\n lines.push(' volumes:');\n lines.push(` - ..:/workspaces/${opts.name}:cached`);\n // Per-feature persistent home subpaths (dirs and files alike).\n // Paths inside compose.yaml are relative to the .devcontainer/\n // directory; `..` walks up to the container root, where `home/`\n // lives. Docker reads the host-side inode type to decide whether\n // the mount target inside the container is a file or a directory.\n const resolvedFeatures = resolveFeatures(opts);\n for (const f of resolvedFeatures) {\n const allSubs = [\n ...f.persistentHomePaths,\n ...f.persistentHomeFiles.map((entry) => entry.path),\n ];\n for (const sub of allSubs) {\n lines.push(` - ../home/${sub}:/home/node/${sub}`);\n }\n }\n for (const svc of opts.services) {\n // `${VAR}` env values were already resolved against <name>.env in\n // apply, so everything here is a literal. Per-service data dirs are\n // bind-mounted from the host (`data:` volume shorthand → ../data/<name>)\n // so DB content is visible at `<container-dir>/data/<name>/` and is\n // part of remove-backups. See ADR 0003. Pre-created in writeScaffold\n // so docker doesn't auto-mkdir them as root.\n lines.push(` ${svc.name}:`);\n lines.push(` image: ${svc.image}`);\n if (svc.restart) {\n lines.push(` restart: ${svc.restart}`);\n }\n if (svc.command !== undefined) {\n lines.push(` command: ${composeScalar(svc.command)}`);\n }\n const envKeys = Object.keys(svc.env);\n if (envKeys.length > 0) {\n lines.push(' environment:');\n for (const k of envKeys) {\n lines.push(` ${k}: ${composeScalar(svc.env[k]!)}`);\n }\n }\n if (svc.volumes.length > 0) {\n lines.push(' volumes:');\n for (const vol of svc.volumes) {\n lines.push(` - ${composeVolumeSource(vol, svc.name)}`);\n }\n }\n if (svc.healthcheck) {\n const hc = svc.healthcheck;\n lines.push(' healthcheck:');\n if (Array.isArray(hc.test)) {\n // Compose exec-form: a flow sequence of quoted args.\n lines.push(` test: [${hc.test.map(composeScalar).join(', ')}]`);\n } else {\n lines.push(` test: ${composeScalar(hc.test)}`);\n }\n if (hc.interval) lines.push(` interval: ${hc.interval}`);\n if (hc.timeout) lines.push(` timeout: ${hc.timeout}`);\n if (hc.retries !== undefined) lines.push(` retries: ${hc.retries}`);\n if (hc.startPeriod) {\n lines.push(` start_period: ${hc.startPeriod}`);\n }\n }\n }\n\n if (hasPorts) {\n // `external: true` tells compose that `monoceros-proxy` is managed\n // outside this stack (Monoceros's proxy module creates it via\n // `docker network create`). Without this declaration compose would\n // try to create its own scoped network with the same name and\n // collide.\n lines.push('networks:');\n lines.push(' monoceros-proxy:');\n lines.push(' external: true');\n }\n\n return lines.join('\\n') + '\\n';\n}\n\ninterface CodeWorkspaceFolder {\n path: string;\n name?: string;\n}\n\ninterface CodeWorkspaceFile {\n folders: CodeWorkspaceFolder[];\n}\n\n/**\n * The `<name>.code-workspace` file VS Code uses to open the solution as\n * a multi-root workspace. The first entry is `.` so the workspace root\n * (with its system dotfolders) stays visible in the Explorer. Each\n * repo added via `monoceros add-repo` appears as a sibling root\n * pointing at `projects/<name>/`.\n */\nexport function buildCodeWorkspaceJson(opts: CreateOptions): CodeWorkspaceFile {\n const folders: CodeWorkspaceFolder[] = [{ path: '.' }];\n // Sort repos by path so the Explorer order is deterministic and\n // doesn't depend on insertion order. (Clone order in post-create\n // stays as-added so deps still work.)\n const sortedRepos = [...(opts.repos ?? [])].sort((a, b) =>\n a.path.localeCompare(b.path),\n );\n for (const repo of sortedRepos) {\n // The folder's display label is the leaf segment of the path\n // (the deepest folder name). VS Code shows it in the Explorer\n // tree; for nested clones (`apps/web`) we want `web`, not the\n // whole path.\n const label = repo.path.split('/').pop() ?? repo.path;\n folders.push({ path: `projects/${repo.path}`, name: label });\n }\n return { folders };\n}\n\n/**\n * Merge a generator-produced workspace into whatever the builder may\n * have hand-edited into the on-disk file. The `.code-workspace` is\n * conceptually a builder artifact — VS Code lets people add local\n * folders to it, drop in `settings:` / `extensions:` blocks, reorder\n * roots, etc. A blind overwrite on every `apply` would silently nuke\n * all of that.\n *\n * Merge rules (favour-builder):\n *\n * - Every folder the builder has in their `folders[]` stays, in\n * the same order. We don't touch labels or paths the user\n * already wrote.\n * - Any folder from the generator that ISN'T present in the\n * builder's `folders[]` (matched by `path`) is appended at the\n * end. That covers the typical case \"I just added a new repo via\n * `monoceros add-repo` and want it to show up automatically\".\n * - Folders that exist in the builder file but no longer come from\n * the generator (e.g. yml repo removed) are NOT dropped — the\n * builder may have kept the folder around on purpose. Cleanup\n * is a manual edit.\n * - Top-level fields other than `folders` (e.g. `settings`,\n * `extensions`, `launch`, `tasks`, `remoteAuthority`) carry\n * through verbatim.\n * - If `existing` is null / undefined / unparseable / missing\n * `folders`, the generator output is taken as-is.\n *\n * Pure function — no I/O. `writeScaffold` is the one site that\n * reads + writes; this just transforms.\n */\nexport function mergeCodeWorkspace(\n existing: unknown,\n generated: CodeWorkspaceFile,\n): Record<string, unknown> {\n // Bail out to the generator when the existing file isn't a sane\n // workspace document. We could try to repair partial shapes but\n // the simpler invariant is: a hand-edit that breaks JSON or drops\n // `folders` is the builder's problem to fix.\n if (\n !existing ||\n typeof existing !== 'object' ||\n Array.isArray(existing) ||\n !Array.isArray((existing as { folders?: unknown }).folders)\n ) {\n return { ...generated };\n }\n const existingObj = existing as Record<string, unknown>;\n const existingFolders = existingObj.folders as CodeWorkspaceFolder[];\n const existingPaths = new Set(\n existingFolders\n .map((f) => (f && typeof f === 'object' ? f.path : undefined))\n .filter((p): p is string => typeof p === 'string'),\n );\n\n // Preserve builder-side folders verbatim; append generator-only ones.\n const merged: CodeWorkspaceFolder[] = [...existingFolders];\n for (const g of generated.folders) {\n if (!existingPaths.has(g.path)) merged.push(g);\n }\n\n // Top-level pass-through: keep all builder-set keys, overwrite\n // only `folders`. Order: builder's keys first (to keep their\n // structure recognizable on round-trip), then folders.\n const out: Record<string, unknown> = { ...existingObj };\n out.folders = merged;\n return out;\n}\n\n/**\n * Generate the `post-create.sh` content for a solution. Base sections\n * (git include + pnpm install) are fixed. The `installUrls` and\n * `repos` sections are appended only when those yml fields are\n * populated.\n */\nexport function buildPostCreateScript(opts: CreateOptions): string {\n const lines: string[] = [\n '#!/usr/bin/env bash',\n 'set -euo pipefail',\n '',\n '# Inherit host-side git identity (user.name / user.email) captured',\n '# into .monoceros/gitconfig by `monoceros apply`. Container-local',\n \"# git config loads first; the include below merges the host's\",\n '# identity values in.',\n `git config --global include.path \"/workspaces/${opts.name}/.monoceros/gitconfig\"`,\n '',\n '# Per-feature post-create hooks. Each Monoceros-curated feature',\n '# may drop a script into /usr/local/share/monoceros/post-create.d/',\n '# during its install.sh — typical job is a non-interactive login',\n '# against bind-mounted state under /home/node, using the option',\n '# values the feature received as env vars at install time. Scripts',\n '# run in lexicographic order, each in its own subshell, and a',\n '# failure aborts post-create (set -e is in effect).',\n 'if [ -d /usr/local/share/monoceros/post-create.d ]; then',\n ' for hook in /usr/local/share/monoceros/post-create.d/*.sh; do',\n ' [ -f \"$hook\" ] || continue',\n ' echo \"→ post-create hook: $(basename \"$hook\")\"',\n ' bash \"$hook\"',\n ' done',\n 'fi',\n '',\n '# Bring up Node dependencies if the workspace has a package.json.',\n 'if [ -f package.json ]; then',\n ' pnpm install',\n 'fi',\n ];\n\n if (opts.installUrls && opts.installUrls.length > 0) {\n lines.push(\n '',\n '# Custom install URLs added via `monoceros add-from-url`. Each is',\n '# fetched and piped to `sh` on every container rebuild. URLs run',\n '# in insertion order so later installs can build on earlier ones.',\n '#',\n '# Why `sh` (not `bash`): most install scripts target POSIX `sh`',\n '# and some (starship, rustup, …) explicitly refuse to run under',\n '# `bash`. Outer `set -o pipefail` in this script makes a curl',\n '# failure abort the post-create as expected.',\n `echo \"→ Running ${opts.installUrls.length} install URL(s) added via add-from-url…\"`,\n );\n for (const url of opts.installUrls) {\n lines.push(`echo \"→ ${url}\"`, `curl -fsSL \"${url}\" | sh`);\n }\n }\n\n if (opts.repos && opts.repos.length > 0) {\n const hasHttpsRepo = opts.repos.some((r) => r.url.startsWith('https://'));\n if (hasHttpsRepo) {\n lines.push(\n '',\n '# Wire git to the per-dev-container credentials file populated',\n '# by `monoceros apply` (via `git credential fill` on the host).',\n '# Path uses the workspace bind-mount so the file is reachable',\n '# from inside the container.',\n `git config --global credential.helper \"store --file=/workspaces/${opts.name}/.monoceros/git-credentials\"`,\n );\n }\n lines.push(\n '',\n '# Repos managed by `monoceros add-repo`. Each entry is cloned',\n '# into `projects/<path>/` if (and only if) the directory does',\n '# not exist yet. Existing project subfolders are left alone so',\n '# local changes survive `monoceros apply` rebuilds. Nested',\n '# `<path>` (e.g. apps/web) is created via `mkdir -p` before the',\n '# clone so the parent directories exist.',\n 'mkdir -p projects',\n );\n for (const repo of opts.repos) {\n // For nested paths (`apps/web`), make sure the parent dir\n // exists before git clone — otherwise git fails with \"could\n // not create work tree dir\".\n const parent = repo.path.includes('/')\n ? repo.path.slice(0, repo.path.lastIndexOf('/'))\n : null;\n if (parent) {\n lines.push(`mkdir -p \"projects/${parent}\"`);\n }\n lines.push(\n `if [ ! -d \"projects/${repo.path}\" ]; then`,\n ` echo \"→ Cloning ${repo.path} from ${repo.url}…\"`,\n ` git clone \"${repo.url}\" \"projects/${repo.path}\"`,\n `else`,\n ` echo \"→ projects/${repo.path} already exists, skipping clone\"`,\n `fi`,\n );\n // Per-repo git identity override: set user.name/email inside\n // the cloned repo, so commits from THIS repo go out under the\n // override identity. Idempotent — git config overwrites the\n // value each run, no duplicate accumulation. Falls outside the\n // `if [ ! -d ... ]` clone-guard so an explicit yml update of\n // gitUser also takes effect on re-apply against an existing\n // clone.\n if (repo.gitUser) {\n const safeName = repo.gitUser.name.replace(/\"/g, '\\\\\"');\n const safeEmail = repo.gitUser.email.replace(/\"/g, '\\\\\"');\n lines.push(\n `git -C \"projects/${repo.path}\" config user.name \"${safeName}\"`,\n `git -C \"projects/${repo.path}\" config user.email \"${safeEmail}\"`,\n );\n }\n }\n }\n\n return lines.join('\\n') + '\\n';\n}\n\nexport async function writePostCreateScript(\n devcontainerDir: string,\n opts: CreateOptions,\n): Promise<void> {\n const dest = path.join(devcontainerDir, 'post-create.sh');\n await fs.writeFile(dest, buildPostCreateScript(opts));\n await fs.chmod(dest, 0o755);\n}\n\n/**\n * Materialize the full devcontainer scaffold for `opts` into\n * `targetDir`. Idempotent overwrite — re-running with different opts\n * produces the new scaffold and overwrites any older files.\n *\n * Writes:\n * - `.devcontainer/devcontainer.json`\n * - `.devcontainer/post-create.sh`\n * - `.devcontainer/compose.yaml` (only when services are configured)\n * - `.monoceros/.gitignore`\n * - `projects/.gitkeep`\n * - `<name>.code-workspace`\n *\n * Does NOT write `README.md` — the README is a once-only stub that\n * `runCreate` produces but `runApplyFromYml` should leave alone (the\n * builder may have edited it).\n *\n * Caller is responsible for `validateOptions(opts)` and\n * `normalizeOptions(opts)`; this function trusts the input.\n */\nexport async function writeScaffold(\n opts: CreateOptions,\n targetDir: string,\n scaffoldOpts: { dockerMode?: DockerMode } = {},\n): Promise<void> {\n const dockerMode: DockerMode = scaffoldOpts.dockerMode ?? 'rootful';\n const devcontainerDir = path.join(targetDir, '.devcontainer');\n const monocerosDir = path.join(targetDir, '.monoceros');\n const projectsDir = path.join(targetDir, 'projects');\n const homeDir = path.join(targetDir, 'home');\n const dataDir = path.join(targetDir, 'data');\n await fs.mkdir(devcontainerDir, { recursive: true });\n await fs.mkdir(monocerosDir, { recursive: true });\n await fs.mkdir(projectsDir, { recursive: true });\n await fs.mkdir(homeDir, { recursive: true });\n if (needsCompose(opts)) {\n await fs.mkdir(dataDir, { recursive: true });\n // Pre-create one subdir per service that uses the `data:` volume\n // shorthand, so docker bind-mounts onto an existing host path (and\n // doesn't auto-mkdir as root, which breaks postgres/mysql first-run\n // on Linux).\n for (const svc of opts.services) {\n const hasDataVolume = svc.volumes.some((v) => v.split(':')[0] === 'data');\n if (hasDataVolume) {\n await fs.mkdir(path.join(dataDir, svc.name), { recursive: true });\n }\n }\n }\n\n // Container-root `.gitignore`. Excludes the directories that hold\n // builder-private or container-runtime state:\n // - `home/` — logins, sessions, secrets baked into tool\n // config files\n // - `.monoceros/` — git-credentials captured from the host\n // credential helper, machine-local gitconfig\n // - `data/` — DB data the compose services write at\n // runtime (postgres/mysql/redis), often big,\n // always container-specific\n // Inside `projects/<repo>/` builders have their own `.git` and\n // any wrapping git operation should be at that level, not at the\n // container root — but a stray `git init` at the root is exactly\n // the accident this .gitignore protects against.\n const containerGitignore = path.join(targetDir, '.gitignore');\n await fs.writeFile(containerGitignore, '/home/\\n/.monoceros/\\n/data/\\n');\n\n // `.gitkeep` so `projects/` survives a fresh git clone before any\n // sub-project has been added.\n const gitkeep = path.join(projectsDir, '.gitkeep');\n if (!existsSync(gitkeep)) {\n await fs.writeFile(gitkeep, '');\n }\n\n // `.monoceros/.gitignore` keeps per-builder runtime state out of any\n // wrapping git repo. Always overwrite — content is fixed.\n await fs.writeFile(\n path.join(monocerosDir, '.gitignore'),\n 'git-credentials*\\ngitconfig\\n',\n );\n\n const devcontainerJson = buildDevcontainerJson(opts, dockerMode);\n await fs.writeFile(\n path.join(devcontainerDir, 'devcontainer.json'),\n JSON.stringify(devcontainerJson, null, 2) + '\\n',\n );\n\n // Copy any Monoceros-owned features that the workbench has on disk\n // into `<devcontainerDir>/features/<name>/`. The devcontainer.json\n // references them via the relative path `./features/<name>` — the\n // devcontainer-cli accepts relative paths from the `.devcontainer/`\n // directory but rejects absolute filesystem paths to local features.\n //\n // We always rebuild the whole `features/` directory: drop the old\n // copy and recreate from current sources, so a feature that was\n // removed from the yml doesn't linger as stale on-disk content that\n // devcontainer-cli would still see.\n const featuresDir = path.join(devcontainerDir, 'features');\n if (existsSync(featuresDir)) {\n await fs.rm(featuresDir, { recursive: true, force: true });\n }\n const resolvedFeatures = resolveFeatures(opts);\n for (const f of resolvedFeatures) {\n if (!f.localSourceDir || !f.localName) continue;\n const dest = path.join(featuresDir, f.localName);\n await fs.mkdir(dest, { recursive: true });\n await fs.cp(f.localSourceDir, dest, { recursive: true });\n }\n\n // Pre-create persistent home entries so docker doesn't auto-mkdir\n // them as root at container start. We only ensure existence; any\n // existing content survives, which is the whole point — apply\n // never touches `home/<sub>` once it's there. Directories get\n // mkdir; files get an empty touch (only when missing — already-\n // populated files like a complete .claude.json must not be\n // truncated on re-apply).\n for (const f of resolvedFeatures) {\n for (const sub of f.persistentHomePaths) {\n await fs.mkdir(path.join(homeDir, sub), { recursive: true });\n }\n for (const entry of f.persistentHomeFiles) {\n const filePath = path.join(homeDir, entry.path);\n await fs.mkdir(path.dirname(filePath), { recursive: true });\n if (!existsSync(filePath)) {\n // Seed with the feature-author's initial content (defaults\n // to empty). For JSON configs this should be at least `{}`\n // so the tool doesn't choke on an unparseable empty file.\n await fs.writeFile(filePath, entry.initialContent);\n }\n }\n }\n\n await writePostCreateScript(devcontainerDir, opts);\n\n const composePath = path.join(devcontainerDir, 'compose.yaml');\n if (needsCompose(opts)) {\n await fs.writeFile(composePath, buildComposeYaml(opts, dockerMode));\n } else if (existsSync(composePath)) {\n // Services dropped from the yml — clean up the now-stale file so a\n // later `monoceros start` doesn't pick it up.\n await fs.rm(composePath);\n }\n\n // `.code-workspace` is a builder artifact, not a pure generator\n // output — VS Code lets people drop local folders, settings,\n // extensions etc. into it. Read what's there, merge with what the\n // generator produces, write back. See mergeCodeWorkspace for the\n // exact rules.\n const workspacePath = path.join(targetDir, `${opts.name}.code-workspace`);\n let existingWorkspace: unknown;\n try {\n const raw = await fs.readFile(workspacePath, 'utf8');\n existingWorkspace = JSON.parse(raw);\n } catch {\n // ENOENT (first apply) or parse error — fall through to the\n // generator output. mergeCodeWorkspace handles both via the\n // null-existing branch.\n existingWorkspace = undefined;\n }\n const generated = buildCodeWorkspaceJson(opts);\n const merged = mergeCodeWorkspace(existingWorkspace, generated);\n await fs.writeFile(workspacePath, JSON.stringify(merged, null, 2) + '\\n');\n}\n","import {\n type Document,\n isMap,\n isScalar,\n isSeq,\n Pair,\n parseDocument,\n Scalar,\n YAMLMap,\n YAMLSeq,\n} from 'yaml';\nimport type { FeatureOptions, RepoEntry } from '../create/types.js';\nimport { deriveRepoName } from '../create/scaffold.js';\nimport { loadFeatureManifestSummary } from '../init/manifest.js';\nimport {\n buildFeatureHeaderCommentBefore,\n featureOptionHints,\n FEATURE_HEADER_WIDTH,\n} from '../init/feature-doc.js';\nimport { GIT_IDENTITY_VAR } from '../config/env-file.js';\n\n/**\n * AST-level mutators for solution-config yml. Each function:\n * - takes a yaml.Document obtained from `parseConfig`\n * - mutates it in place if the operation introduces a change\n * - returns `true` iff the doc was changed, `false` for a no-op\n * (matches the `mutate()` skeleton's \"no-change\" branch)\n *\n * All mutators preserve every comment and blank line in the surrounding\n * yml — that's the whole point of the AST approach over a plain\n * `toJS → edit → toString` cycle.\n *\n * Validation is NOT done here. The caller is expected to re-validate\n * the doc (via `parseConfig(stringifyConfig(doc))`) before persisting\n * — that surfaces schema violations with the regular field-path-aware\n * error message.\n */\n\n/** Ensure `doc[key]` is a sequence and return it. */\nfunction ensureSeq(doc: Document, key: string): YAMLSeq {\n const existing = doc.get(key, true);\n if (existing && isSeq(existing)) return existing;\n const seq = new YAMLSeq();\n doc.set(key, seq);\n return seq;\n}\n\n/** Drop `doc[key]` when its sequence is empty. */\nfunction pruneEmptySeq(doc: Document, key: string): void {\n const node = doc.get(key, true);\n if (node && isSeq(node) && node.items.length === 0) {\n doc.delete(key);\n }\n}\n\n/** Compare a scalar item's value (handles both Scalar nodes and plain JS). */\nfunction scalarValue(item: unknown): unknown {\n return isScalar(item) ? item.value : item;\n}\n\nexport function addLanguageToDoc(doc: Document, lang: string): boolean {\n const seq = ensureSeq(doc, 'languages');\n if (seq.items.some((i) => scalarValue(i) === lang)) return false;\n seq.add(lang);\n return true;\n}\n\n/** Find the services[] item whose `name:` equals `name`. */\nfunction findServiceItem(seq: YAMLSeq, name: string): YAMLMap | undefined {\n for (const item of seq.items) {\n if (isMap(item) && item.get('name') === name) return item;\n }\n return undefined;\n}\n\nexport type AddServiceOutcome =\n | { outcome: 'added' }\n | { outcome: 'exists' }\n | { outcome: 'conflict'; existingImage: string };\n\n/**\n * Add a service entry built from pre-rendered map-body lines (see\n * init/service-doc.ts). Idempotent by `name`:\n * - no entry with that name → append (parsing the body so comments in\n * a custom scaffold survive), report `added`.\n * - an entry with that name + the same image → `exists` (no-op,\n * preserves any builder edits to that block).\n * - an entry with that name + a different image → `conflict` (caller\n * turns this into an actionable error).\n */\nexport function addServiceEntryToDoc(\n doc: Document,\n name: string,\n image: string,\n bodyLines: string[],\n scaffoldComment?: string,\n): AddServiceOutcome {\n const seq = ensureSeq(doc, 'services');\n const existing = findServiceItem(seq, name);\n if (existing) {\n const existingImage = existing.get('image');\n if (existingImage === image) return { outcome: 'exists' };\n return { outcome: 'conflict', existingImage: String(existingImage) };\n }\n const node = parseDocument(bodyLines.join('\\n')).contents as YAMLMap;\n // The commented scaffold (custom images) rides as the node's trailing\n // `comment` — comments parsed inside the body string would be dropped\n // when the map is moved into the sequence, but a node `.comment`\n // survives and renders each line under the item, prefixed with `#`.\n if (scaffoldComment) node.comment = scaffoldComment;\n seq.add(node);\n return { outcome: 'added' };\n}\n\nexport function addAptPackagesToDoc(\n doc: Document,\n packages: string[],\n): boolean {\n const seq = ensureSeq(doc, 'aptPackages');\n let changed = false;\n for (const pkg of packages) {\n if (seq.items.some((i) => scalarValue(i) === pkg)) continue;\n seq.add(pkg);\n changed = true;\n }\n return changed;\n}\n\n/**\n * Set (or replace) the container-level `git.user` block. Used by the\n * apply / init identity prompt when the builder chose \"save in this\n * container's yml\" (scope `c` or `b`).\n *\n * Idempotent on identical input: same name + email → no doc change,\n * return false. Different values → in-place overwrite, return true.\n *\n * When `git` is newly created (didn't exist before this call) the\n * block lands right after `name:` at the top of the document — same\n * \"identity is the first thing under the container's own intro\"\n * layout used by monoceros-config's `defaults.git`. A pre-existing\n * `git:` key keeps its position; the builder's manual reorderings\n * are respected.\n *\n * Comment-preserving as usual for our AST mutators.\n */\nexport function setContainerGitUserInDoc(\n doc: Document,\n user: { name: string; email: string },\n): boolean {\n const gitNode = doc.get('git', true);\n let gitMap: YAMLMap;\n let createdNew = false;\n if (gitNode && isMap(gitNode)) {\n gitMap = gitNode;\n } else {\n gitMap = new YAMLMap();\n insertTopLevelAfterName(doc, 'git', gitMap, GIT_USER_HEADER_COMMENT);\n createdNew = true;\n }\n const userNode = gitMap.get('user', true);\n let userMap: YAMLMap;\n if (userNode && isMap(userNode)) {\n userMap = userNode;\n } else {\n userMap = new YAMLMap();\n gitMap.set('user', userMap);\n }\n const currentName = userMap.get('name');\n const currentEmail = userMap.get('email');\n if (!createdNew && currentName === user.name && currentEmail === user.email) {\n return false;\n }\n userMap.set('name', user.name);\n userMap.set('email', user.email);\n relocateLeakedSectionComments(doc);\n return true;\n}\n\n/**\n * Add a container-level `git.user` with `${VAR}` placeholders IF no\n * `git.user` exists yet. Used by `add-repo` so the first repo gets the\n * same env-managed identity scaffold `init` produces. Leaves any\n * existing `git.user` (literal or placeholder) untouched. Returns true\n * when it added the block.\n */\nexport function ensureContainerGitUserPlaceholder(doc: Document): boolean {\n const gitNode = doc.get('git', true);\n if (gitNode && isMap(gitNode)) {\n const userNode = gitNode.get('user', true);\n if (userNode && isMap(userNode)) return false;\n }\n return setContainerGitUserInDoc(doc, {\n name: `\\${${GIT_IDENTITY_VAR.name}}`,\n email: `\\${${GIT_IDENTITY_VAR.email}}`,\n });\n}\n\n/**\n * yaml-lib's parser will sometimes attach a column-0 comment block\n * sitting between two top-level keys (e.g. the `# Repos cloned…`\n * header above `repos:`) to the previous top-level pair's deepest\n * trailing node rather than to the next pair's `commentBefore`. On\n * re-emit via the AST writers (setContainerGitUserInDoc et al.) the\n * comment then comes out indented under the previous section instead\n * of standing at column 0 above the next section — visually broken.\n *\n * This walks the document, finds such leaked comments on the LAST\n * leaf of each top-level section, and moves them to the\n * `commentBefore` of the NEXT top-level pair (where they visually\n * belong).\n *\n * Safe to call after any AST mutation; idempotent — already-correctly-\n * placed comments aren't touched.\n */\nexport function relocateLeakedSectionComments(doc: Document): void {\n const root = doc.contents;\n if (!root || !isMap(root)) return;\n const items = root.items;\n for (let i = 0; i < items.length - 1; i++) {\n const here = items[i]!;\n const next = items[i + 1]!;\n const leak = takeTrailingLeafComment(here.value);\n if (!leak) continue;\n const nextKey = next.key as {\n commentBefore?: string | null;\n spaceBefore?: boolean;\n } | null;\n if (!nextKey || typeof nextKey !== 'object') continue;\n const existing = nextKey.commentBefore ?? '';\n nextKey.commentBefore = existing ? `${leak}\\n${existing}` : leak;\n nextKey.spaceBefore = true;\n }\n}\n\n/**\n * If `node` is a container whose deepest trailing element carries a\n * comment block that includes a yaml-lib \"blank line separator\" (a\n * `\\n\\n` inside the `comment` string — that's how yaml stores a\n * source-level blank line between two trailing comment runs), strip\n * the post-separator portion and return it. Everything before the\n * blank line is a legitimate inline hint that belongs to the leaf;\n * everything after is the next section's leaked header.\n *\n * Returns `null` when there's no blank-line separator — in that case\n * the trailing comment is all legitimate inline content, leave it.\n */\nfunction takeTrailingLeafComment(node: unknown): string | null {\n if (!node) return null;\n type CommentNode = {\n comment?: string | null;\n spaceBefore?: boolean;\n };\n // First, check this node's own trailing comment for a leak.\n const c = node as CommentNode;\n if (typeof c.comment === 'string' && c.comment.length > 0) {\n const blankMatch = c.comment.match(/\\n[ \\t]*\\n/);\n if (blankMatch && blankMatch.index !== undefined) {\n // Strip ONLY the blank-line separator. The character that\n // follows is the leading single space yaml-lib uses between\n // `#` and the comment text — preserve it, otherwise the\n // relocated block emits as `#Foo` instead of `# Foo`.\n const tail = c.comment.slice(blankMatch.index + blankMatch[0].length);\n c.comment = c.comment.slice(0, blankMatch.index);\n if (tail.length > 0) return tail;\n }\n }\n // Recurse into children — last-first across both maps and seqs, so\n // we find the DEEPEST leak first (yaml-lib's parser pushes leaked\n // comments as deep as it can). When a seq has multiple items and\n // the leak sits on an EARLIER one (because subsequent items were\n // added by a later mutation), we walk back through the siblings\n // until we find it.\n if (isMap(node) && node.items.length > 0) {\n for (let i = node.items.length - 1; i >= 0; i--) {\n const value = (node.items[i] as { value?: unknown }).value;\n const found = takeTrailingLeafComment(value);\n if (found) return found;\n }\n }\n if (isSeq(node) && node.items.length > 0) {\n for (let i = node.items.length - 1; i >= 0; i--) {\n const found = takeTrailingLeafComment(node.items[i]);\n if (found) return found;\n }\n }\n return null;\n}\n\n/**\n * Insert a new top-level key into the document, positioned right\n * after `name:` (or at index 1 if `name:` isn't there). Used so newly-\n * persisted `git:` lands at the top of the yml where the builder\n * expects to find it — mirrors the `defaults.git.user` placement in\n * monoceros-config.sample.yml.\n *\n * If `comment` is given, it's attached as the new pair's\n * `commentBefore` so the section gets the same explanatory line the\n * other sections carry.\n */\nfunction insertTopLevelAfterName(\n doc: Document,\n key: string,\n value: YAMLMap,\n comment: string | undefined,\n): void {\n const root = doc.contents;\n if (!root || !isMap(root)) {\n // Document with no top-level map (shouldn't happen for a real\n // solution-config) — fall back to plain set, which appends.\n doc.set(key, value);\n return;\n }\n // Wrap the key in a Scalar so we can attach commentBefore + spaceBefore\n // to it. A plain string key (which is what Pair accepts as a shorthand)\n // doesn't have those fields.\n const keyScalar = new Scalar(key);\n if (comment) {\n keyScalar.commentBefore = comment;\n keyScalar.spaceBefore = true;\n }\n const pair = new Pair(keyScalar, value);\n const nameIdx = root.items.findIndex((p) => {\n const k = p.key as { value?: unknown } | string | null;\n return (typeof k === 'string' ? k : (k?.value ?? null)) === 'name';\n });\n const insertAt = nameIdx >= 0 ? nameIdx + 1 : Math.min(1, root.items.length);\n root.items.splice(insertAt, 0, pair);\n}\n\nconst GIT_USER_HEADER_COMMENT = [\n ' Git committer identity for this container. Overrides',\n \" monoceros-config.yml's defaults.git.user. Applies to every repo\",\n ' below unless that repo declares its own `git.user` override.',\n].join('\\n');\n\n/**\n * Read the port number from a `routing.ports:` entry — handles both\n * the short form (`- 3000`) and the long form (`- port: 3000`).\n * Returns `null` for malformed entries (the schema catches them, but\n * the mutator is defensive).\n */\nfunction portOfItem(item: unknown): number | null {\n const scalar = scalarValue(item);\n if (typeof scalar === 'number' && Number.isInteger(scalar)) {\n return scalar;\n }\n if (isMap(item)) {\n const p = item.get('port');\n if (typeof p === 'number' && Number.isInteger(p)) return p;\n }\n return null;\n}\n\n/** Ensure `routing` is a map and return it (created if absent). */\nfunction ensureRoutingMap(doc: Document): YAMLMap {\n const existing = doc.get('routing', true);\n if (existing && isMap(existing)) return existing;\n const map = new YAMLMap();\n doc.set('routing', map);\n return map;\n}\n\n/**\n * Move a port to position 0 in the `routing.ports` sequence (or add\n * it there if it isn't already in the list). The first entry doubles\n * as the bare `<name>.localhost` default route in the Traefik dynamic\n * config, so this is how the builder picks which app the bare URL\n * points at.\n *\n * Returns `true` if anything changed. Idempotent: when the port is\n * already at index 0, the call is a no-op.\n */\nexport function setDefaultPortInDoc(doc: Document, port: number): boolean {\n const routing = ensureRoutingMap(doc);\n const existing = routing.get('ports', true);\n let seq: YAMLSeq;\n if (existing && isSeq(existing)) {\n seq = existing;\n } else {\n seq = new YAMLSeq();\n routing.set('ports', seq);\n }\n const currentIdx = seq.items.findIndex((i) => portOfItem(i) === port);\n if (currentIdx === 0) return false;\n if (currentIdx > 0) {\n // Splice out preserves the node — comments attached to the entry\n // ride along to the new position. Then unshift back at index 0.\n const [item] = seq.items.splice(currentIdx, 1);\n seq.items.unshift(item);\n return true;\n }\n // Not in the list yet — insert at the front.\n seq.items.unshift(port);\n return true;\n}\n\n/**\n * Add (or no-op) one or more ports to `routing.ports`. Comparison is\n * by port number, so a long-form entry (`- port: 3000`) matches a\n * short-form input (`3000`) and vice versa — that keeps `add-port`\n * idempotent against either form the builder may have written by\n * hand.\n *\n * Writes the short form for new entries (lowest-noise yml). To get\n * the long form, the builder edits the yml directly — relevant once\n * the long form carries additional fields (TLS entrypoint, path\n * prefix … see ADR 0007).\n */\nexport function addPortsToDoc(doc: Document, ports: number[]): boolean {\n const routing = ensureRoutingMap(doc);\n const existing = routing.get('ports', true);\n let seq: YAMLSeq;\n if (existing && isSeq(existing)) {\n seq = existing;\n } else {\n seq = new YAMLSeq();\n routing.set('ports', seq);\n }\n let changed = false;\n for (const port of ports) {\n if (seq.items.some((i) => portOfItem(i) === port)) continue;\n seq.add(port);\n changed = true;\n }\n // No prune here — a non-empty `routing.ports` is the whole point of\n // add-port. If `routing` was freshly created with only this `ports:`\n // field, leaving it bare is fine; future fields (vscodeAutoForward\n // etc.) attach to the same map.\n return changed;\n}\n\n/**\n * Remove one or more ports from `routing.ports`. Matches both short\n * and long form. Idempotent — ports not present are skipped silently,\n * the return reflects whether any actual removal happened. When the\n * port list is empty after removal, the `ports:` key is pruned. If\n * `routing` becomes completely empty (no other sub-keys), the whole\n * block is dropped too — symmetric to how other sequence-emptying\n * mutators behave.\n */\nexport function removePortsFromDoc(doc: Document, ports: number[]): boolean {\n const routing = doc.get('routing', true);\n if (!routing || !isMap(routing)) return false;\n const seq = routing.get('ports', true);\n if (!seq || !isSeq(seq)) return false;\n const targets = new Set(ports);\n let changed = false;\n for (let i = seq.items.length - 1; i >= 0; i--) {\n const p = portOfItem(seq.items[i]);\n if (p !== null && targets.has(p)) {\n seq.items.splice(i, 1);\n changed = true;\n }\n }\n if (changed) {\n if (seq.items.length === 0) routing.delete('ports');\n if (routing.items.length === 0) doc.delete('routing');\n }\n return changed;\n}\n\nexport function addInstallUrlToDoc(doc: Document, url: string): boolean {\n const seq = ensureSeq(doc, 'installUrls');\n if (seq.items.some((i) => scalarValue(i) === url)) return false;\n seq.add(url);\n return true;\n}\n\n/**\n * Add (or no-op) a devcontainer feature entry. Mirrors the legacy\n * `add-feature` semantics: re-adding the same ref with different\n * options is an explicit error (the builder must remove + re-add to\n * change options); same ref + same options is a no-op.\n *\n * `displayName` is what the builder typed on the command line —\n * either a short-name (`atlassian` / `atlassian/twg`) or the full\n * OCI ref. Used in error messages so the suggestion to run\n * `monoceros remove-feature <X>` echoes the form they're familiar\n * with rather than the always-the-full-ref form. Defaults to `ref`\n * when omitted.\n */\nexport function addFeatureToDoc(\n doc: Document,\n ref: string,\n options: FeatureOptions = {},\n displayName?: string,\n): boolean {\n const seq = ensureSeq(doc, 'features');\n const label = displayName ?? ref;\n\n // Credential option hints become ACTIVE `${VAR}` placeholders in the\n // options block (not a commented skeleton): an empty/missing `${VAR}`\n // resolves to \"\" at apply and the transform skips it → the\n // monoceros-config default is inherited, or the option stays unset.\n // The matching env vars are seeded blank by runAddFeature. Only keys\n // the caller didn't already set get a placeholder (featureOptionHints\n // filters those out).\n const summary = loadFeatureManifestSummary(ref);\n const hints = featureOptionHints(summary, ref, Object.keys(options));\n const mergedOptions: FeatureOptions = { ...options };\n for (const h of hints) mergedOptions[h.key] = h.placeholder;\n\n for (const item of seq.items) {\n if (!isMap(item)) continue;\n const itemRef = item.get('ref');\n if (itemRef !== ref) continue;\n // Same ref: check options equality. Use the live doc's toJS so the\n // sub-map's scalars resolve to plain values; passing doc as the\n // schema/context is required by yaml@2.\n const itemJs = item.toJS(doc) as { options?: FeatureOptions };\n const existingJs = itemJs.options ?? {};\n if (JSON.stringify(existingJs) === JSON.stringify(mergedOptions)) {\n return false;\n }\n throw new Error(\n `Feature ${label} is already configured with different options. Remove it first (\\`monoceros remove-feature ${label}\\`) before re-adding.`,\n );\n }\n const entry = new YAMLMap();\n entry.set('ref', ref);\n if (Object.keys(mergedOptions).length > 0) {\n entry.set('options', mergedOptions);\n }\n // Manifest-driven per-feature header block (tagline + description,\n // options summary, documentationURL) — the same prose the init\n // generator emits. Attached as commentBefore on the sequence ITEM\n // (the entry map itself) so yaml-lib renders it as a block ABOVE\n // the dash:\n //\n // # Atlassian — …\n // # Options: …\n // - ref: ghcr.io/…/atlassian:1\n //\n // Attaching to the inner `ref` key instead would land the comment\n // INSIDE the dash block (`- # Atlassian` on one line) — valid yaml\n // but visually inconsistent with what `init` produces. Unknown /\n // third-party refs produce no summary → no header → bare `- ref:`.\n const headerBefore = buildFeatureHeaderCommentBefore(\n summary,\n FEATURE_HEADER_WIDTH,\n );\n if (headerBefore.length > 0) {\n (entry as { commentBefore?: string }).commentBefore = headerBefore;\n (entry as { spaceBefore?: boolean }).spaceBefore = true;\n }\n seq.add(entry);\n return true;\n}\n\n/**\n * Add (or no-op) a repo entry to the `repos:` sequence.\n *\n * Idempotency: if an existing entry has the same URL AND the same\n * effective path AND the same gitUser, this is a no-op (returns\n * false). \"Effective path\" means the explicit `path:` value if set,\n * or the URL-derived single-segment default otherwise. Same URL\n * with a different path is intentionally allowed — that's the \"I\n * want two clones of the same repo into different folders\" case.\n *\n * `gitUser` is an optional per-repo override of the container-level\n * git.user. When set, persisted as a `git.user` nested map; falls\n * back to the container default at apply time when omitted.\n *\n * Branches are not part of this model. Switching branches is a\n * `git checkout` inside the container, not a yml-level concern.\n */\nexport function addRepoToDoc(doc: Document, repo: RepoEntry): boolean {\n const seq = ensureSeq(doc, 'repos');\n for (const item of seq.items) {\n if (!isMap(item)) continue;\n const url = item.get('url');\n if (url !== repo.url) continue;\n const existingPath = item.get('path');\n const effectivePath =\n typeof existingPath === 'string'\n ? existingPath\n : deriveRepoName(url as string);\n if (effectivePath !== repo.path) continue;\n // Same url + same path. Check gitUser + provider equivalence too\n // so an entry that adds/changes either field is treated as an\n // update, not silently ignored.\n const existingGit = item.get('git', true);\n const existingUser =\n existingGit && isMap(existingGit) ? existingGit.get('user', true) : null;\n const existingName =\n existingUser && isMap(existingUser) ? existingUser.get('name') : null;\n const existingEmail =\n existingUser && isMap(existingUser) ? existingUser.get('email') : null;\n const existingGitUser =\n typeof existingName === 'string' && typeof existingEmail === 'string'\n ? { name: existingName, email: existingEmail }\n : undefined;\n const sameGitUser =\n (existingGitUser?.name ?? null) === (repo.gitUser?.name ?? null) &&\n (existingGitUser?.email ?? null) === (repo.gitUser?.email ?? null);\n const existingProvider = item.get('provider');\n const sameProvider =\n (typeof existingProvider === 'string' ? existingProvider : null) ===\n (repo.provider ?? null);\n if (sameGitUser && sameProvider) {\n return false;\n }\n // Different gitUser or provider → update in place instead of\n // appending a duplicate. Re-running add-repo with new values is\n // the natural way to change either field.\n if (repo.gitUser) {\n const gitMap = new YAMLMap();\n const userMap = new YAMLMap();\n userMap.set('name', repo.gitUser.name);\n userMap.set('email', repo.gitUser.email);\n gitMap.set('user', userMap);\n item.set('git', gitMap);\n } else {\n item.delete('git');\n }\n if (repo.provider) {\n item.set('provider', repo.provider);\n } else {\n item.delete('provider');\n }\n // The `mutate()` wrapper relocates leaked section comments\n // post-mutation — no per-call invocation needed here.\n return true;\n }\n const entry = new YAMLMap();\n entry.set('url', repo.url);\n // Only persist `path` when it differs from the URL-derived default.\n // Keeps the yml minimal — the apply pipeline re-derives at runtime.\n const persistPath = repo.path !== deriveRepoName(repo.url);\n if (persistPath) {\n entry.set('path', repo.path);\n }\n if (repo.gitUser) {\n const gitMap = new YAMLMap();\n const userMap = new YAMLMap();\n userMap.set('name', repo.gitUser.name);\n userMap.set('email', repo.gitUser.email);\n gitMap.set('user', userMap);\n entry.set('git', gitMap);\n }\n if (repo.provider) {\n entry.set('provider', repo.provider);\n }\n // Surface the optional fields the caller did NOT pass as commented\n // hints right under the entry — same single-`#`-depth shape the\n // generator emits in composed mode (`# path: / # provider: / …`).\n // Without these the builder can't see at a glance what else they\n // could set without re-reading the docs.\n const hintLines: string[] = [];\n if (!persistPath) hintLines.push(' path:');\n if (!repo.provider) hintLines.push(' provider:');\n if (!repo.gitUser) {\n hintLines.push(' git:');\n hintLines.push(' user:');\n hintLines.push(' name:');\n hintLines.push(' email:');\n }\n if (hintLines.length > 0) {\n (entry as { comment?: string }).comment = hintLines.join('\\n');\n }\n seq.add(entry);\n // Section-comment relocation runs in `mutate()` post-apply.\n return true;\n}\n\n/**\n * Remove helpers — symmetric to add, used by Task 6's `remove-*`\n * commands. Returning false when the target isn't present makes them\n * idempotent (caller logs \"no-change\" instead of erroring).\n */\nexport function removeLanguageFromDoc(doc: Document, lang: string): boolean {\n return removeScalarFromSeq(doc, 'languages', lang);\n}\n\nexport function removeServiceFromDoc(doc: Document, service: string): boolean {\n const node = doc.get('services', true);\n if (!node || !isSeq(node)) return false;\n const idx = node.items.findIndex(\n (i) => isMap(i) && i.get('name') === service,\n );\n if (idx === -1) return false;\n node.items.splice(idx, 1);\n pruneEmptySeq(doc, 'services');\n return true;\n}\n\nexport function removeAptPackageFromDoc(doc: Document, pkg: string): boolean {\n return removeScalarFromSeq(doc, 'aptPackages', pkg);\n}\n\n/** Plural form — for `monoceros remove-apt-packages a b c`. */\nexport function removeAptPackagesFromDoc(\n doc: Document,\n packages: string[],\n): boolean {\n let changed = false;\n for (const pkg of packages) {\n if (removeAptPackageFromDoc(doc, pkg)) changed = true;\n }\n return changed;\n}\n\nexport function removeInstallUrlFromDoc(doc: Document, url: string): boolean {\n return removeScalarFromSeq(doc, 'installUrls', url);\n}\n\nexport function removeFeatureFromDoc(doc: Document, ref: string): boolean {\n const seq = doc.get('features', true);\n if (!seq || !isSeq(seq)) return false;\n const idx = seq.items.findIndex((i) => isMap(i) && i.get('ref') === ref);\n if (idx < 0) return false;\n\n // yaml-lib parks the header comment block that visually precedes\n // entry[idx] as the trailing `.comment` of the PREVIOUS sequence\n // item, separated from that item's own inline hints by a `\\n\\n`.\n // Splicing the entry doesn't touch the previous sibling, so the\n // header lines would survive in the previous entry's trailing\n // comment and re-emit as orphaned column-2 prose under features.\n // Strip the post-`\\n\\n` tail from the previous item's comment\n // before we splice — symmetric to how relocateLeakedSectionComments\n // moves the routing-section header forward.\n if (idx > 0) {\n const prev = seq.items[idx - 1] as { comment?: string | null } | null;\n if (prev && typeof prev.comment === 'string' && prev.comment.length > 0) {\n const blank = prev.comment.match(/\\n[ \\t]*\\n/);\n if (blank && blank.index !== undefined) {\n prev.comment = prev.comment.slice(0, blank.index);\n }\n }\n }\n\n seq.items.splice(idx, 1);\n pruneEmptySeq(doc, 'features');\n return true;\n}\n\n/**\n * Remove a repo by either its url or its (effective) path. Symmetry\n * to add-repo: `monoceros remove-repo <url-or-path>` matches either\n * field. For nested paths the full path is the match key\n * (`remove-repo apps/web`), not the leaf segment.\n */\nexport function removeRepoFromDoc(doc: Document, urlOrPath: string): boolean {\n const seq = doc.get('repos', true);\n if (!seq || !isSeq(seq)) return false;\n const idx = seq.items.findIndex((item) => {\n if (!isMap(item)) return false;\n const url = item.get('url');\n if (url === urlOrPath) return true;\n const path = item.get('path');\n const effectivePath =\n typeof path === 'string'\n ? path\n : typeof url === 'string'\n ? deriveRepoName(url)\n : undefined;\n return effectivePath === urlOrPath;\n });\n if (idx < 0) return false;\n seq.items.splice(idx, 1);\n pruneEmptySeq(doc, 'repos');\n return true;\n}\n\nfunction removeScalarFromSeq(\n doc: Document,\n key: string,\n value: string,\n): boolean {\n const seq = doc.get(key, true);\n if (!seq || !isSeq(seq)) return false;\n const idx = seq.items.findIndex((i) => scalarValue(i) === value);\n if (idx < 0) return false;\n seq.items.splice(idx, 1);\n pruneEmptySeq(doc, key);\n return true;\n}\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport type { FeatureOptions } from '../create/types.js';\nimport { getInnerArgs } from '../inner-args.js';\nimport { runAddFeature } from '../modify/index.js';\n\nexport const addFeatureCommand = defineCommand({\n meta: {\n name: 'add-feature',\n group: 'edit',\n description:\n 'Add a devcontainer feature by ref to the container config. Options follow `--` as `key=value` pairs. Idempotent (same ref + same options is a no-op). Adding the same ref with different options is an error.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n ref: {\n type: 'positional',\n description:\n 'Feature to add. Either a Monoceros catalog short-name (e.g. `atlassian`, `atlassian/twg`, `claude` — see `monoceros list-components`) or a full OCI feature ref (e.g. `ghcr.io/devcontainers/features/docker-in-docker:2`). The short-name brings its catalog-defined default options; `-- key=value` overrides them.',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n let options: FeatureOptions;\n try {\n options = parseOptionsAfterDashes(getInnerArgs());\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n try {\n const result = await runAddFeature({\n name: args.name,\n ref: args.ref,\n options,\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n\n/**\n * Parse `key=value` tokens (one per arg) into a feature options hash.\n * Coerces `true`/`false` to booleans and pure-integer strings to\n * numbers; everything else stays a string.\n */\nfunction parseOptionsAfterDashes(tokens: readonly string[]): FeatureOptions {\n const result: FeatureOptions = {};\n for (const token of tokens) {\n const eqIdx = token.indexOf('=');\n if (eqIdx <= 0) {\n throw new Error(\n `Invalid option: ${JSON.stringify(token)}. Expected key=value (e.g. version=latest).`,\n );\n }\n const key = token.slice(0, eqIdx);\n const raw = token.slice(eqIdx + 1);\n result[key] = coerce(raw);\n }\n return result;\n}\n\nfunction coerce(value: string): string | number | boolean {\n if (value === 'true') return true;\n if (value === 'false') return false;\n if (/^-?\\d+$/.test(value)) {\n const n = Number(value);\n if (Number.isSafeInteger(n)) return n;\n }\n return value;\n}\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runAddFromUrl } from '../modify/index.js';\n\nexport const addFromUrlCommand = defineCommand({\n meta: {\n name: 'add-from-url',\n group: 'edit',\n description:\n 'Add an https:// install URL to the container config. The URL gets piped to sh on every container rebuild. Loudly warns about remote-code execution before persisting. Idempotent.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n url: {\n type: 'positional',\n description:\n 'https:// URL of an install script (e.g. https://starship.rs/install.sh).',\n required: true,\n },\n yes: {\n type: 'boolean',\n description:\n 'Skip the security warning + diff confirm. Use only in scripts where you have already audited the URL.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n if (!args.yes) {\n printSecurityWarning(args.url);\n }\n try {\n const result = await runAddFromUrl({\n name: args.name,\n url: args.url,\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n\nfunction printSecurityWarning(url: string): void {\n const w = (line: string) => process.stderr.write(line + '\\n');\n w('');\n w('⚠️ SECURITY WARNING — `monoceros add-from-url`');\n w('');\n w(` URL: ${url}`);\n w('');\n w(' This URL will be fetched and piped to sh on every container rebuild.');\n w(\n ' Remote-code execution against a URL you do not control is a supply-chain',\n );\n w(\n ' risk: the maintainer could change the script tomorrow and your container',\n );\n w(' would silently run the new payload.');\n w('');\n w(' Before confirming below:');\n w(' 1. Open the URL in a browser, read what the script does.');\n w(\n ' 2. Verify the maintainer is who you think they are (HTTPS cert, repo).',\n );\n w(' 3. Ideally, vendor the install steps as `add-apt-packages` or');\n w(\n ' `add-feature` instead — those reference signed/versioned artifacts.',\n );\n w('');\n}\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runAddRepo } from '../modify/index.js';\n\nexport const addRepoCommand = defineCommand({\n meta: {\n name: 'add-repo',\n group: 'edit',\n description:\n 'Add a git repo to the container config. Cloned into projects/<path>/ on container build. Idempotent — existing project subfolders are left alone. Destination path derived from URL by default; override with --path (supports nested subfolders like apps/web). Branches/PRs are git-level concerns: clone, then `git checkout` inside the container.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n url: {\n type: 'positional',\n description:\n 'Git URL (HTTPS or SSH/git@ form). E.g. https://github.com/foo/bar.git, git@github.com:foo/bar.git.',\n required: true,\n },\n path: {\n type: 'string',\n description:\n 'Destination under projects/. Subfolders via `/` (e.g. apps/web). Default: URL-derived single segment (bar.git → bar).',\n },\n 'git-name': {\n type: 'string',\n description:\n 'Per-repo git committer name. Overrides the container-level git.user.name for this repo only. Pair with --git-email.',\n },\n 'git-email': {\n type: 'string',\n description:\n 'Per-repo git committer email. Overrides the container-level git.user.email for this repo only. Pair with --git-name.',\n },\n provider: {\n type: 'string',\n description:\n 'Git provider for credential-helper guidance: github | gitlab | bitbucket. Required when the URL host is not github.com, gitlab.com, or bitbucket.org — Monoceros uses this to suggest the right CLI (gh / glab / Atlassian token) on missing credentials.',\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n try {\n const result = await runAddRepo({\n name: args.name,\n url: args.url,\n ...(typeof args.path === 'string' ? { path: args.path } : {}),\n ...(typeof args['git-name'] === 'string'\n ? { gitName: args['git-name'] }\n : {}),\n ...(typeof args['git-email'] === 'string'\n ? { gitEmail: args['git-email'] }\n : {}),\n ...(typeof args.provider === 'string'\n ? { provider: args.provider }\n : {}),\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runAddLanguage } from '../modify/index.js';\n\nexport const addLanguageCommand = defineCommand({\n meta: {\n name: 'add-language',\n group: 'edit',\n description:\n 'Add a language toolchain (devcontainer feature) to the container config. Idempotent, prints a diff before writing.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n language: {\n type: 'positional',\n description:\n 'Language identifier from the feature whitelist (e.g. python, java, rust).',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n try {\n const result = await runAddLanguage({\n name: args.name,\n language: args.language,\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { getInnerArgs } from '../inner-args.js';\nimport { runAddPort } from '../modify/index.js';\n\nexport const addPortCommand = defineCommand({\n meta: {\n name: 'add-port',\n group: 'edit',\n description:\n 'Add one or more ports to the container config so they become reachable from the host via Traefik (`<container>.localhost` / `<container>-<port>.localhost`). Pass port numbers after `--` (e.g. `monoceros add-port sandbox -- 3000 5173 6006`). Idempotent. Persisted in the yml so later `monoceros apply` runs restore the routes. Pass `--default` together with a single port to make it the bare `<container>.localhost` route — the port is inserted at position 0 (or moved there if it already exists).',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n default: {\n type: 'boolean',\n description:\n 'Make the (single) port the new default route at `<container>.localhost`. Inserts the port at position 0 of `routing.ports`, or moves it there if it already exists. Errors when more than one port is passed.',\n default: false,\n },\n },\n async run({ args }) {\n const tokens = [...getInnerArgs()];\n if (tokens.length === 0) {\n consola.error(\n 'No ports given. Usage: `monoceros add-port <containername> [--yes] [--default] -- <port> [<port> …]`.',\n );\n process.exit(1);\n }\n try {\n const result = await runAddPort({\n name: args.name,\n ports: tokens.map(coerceToken),\n yes: args.yes,\n asDefault: args.default,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n\n/**\n * Surface non-integer CLI tokens via the same error path as out-of-range\n * ports — `runAddPort` validates the numeric value, but we need to get\n * there with a number-or-string for the message to read naturally.\n */\nfunction coerceToken(raw: string): number {\n const n = Number(raw);\n return Number.isFinite(n) ? n : (raw as unknown as number);\n}\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runAddService } from '../modify/index.js';\n\nexport const addServiceCommand = defineCommand({\n meta: {\n name: 'add-service',\n group: 'edit',\n description:\n 'Add a backing service to the container config. A curated name (postgres, mysql, redis) expands to a full editable block; any other image (e.g. rustfs/rustfs:latest) drops in name + image plus a commented scaffold. Idempotent, prints a diff before writing.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n service: {\n type: 'positional',\n description:\n 'Curated name (postgres, mysql, redis) or any image ref (e.g. rustfs/rustfs:latest).',\n required: true,\n },\n as: {\n type: 'string',\n description:\n 'Override the service name (the compose service / DNS name / data dir). Lets you add the same image more than once — e.g. two postgres servers as postgres-app and postgres-analytics.',\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n try {\n const result = await runAddService({\n name: args.name,\n service: args.service,\n ...(args.as ? { as: args.as } : {}),\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { runApply } from '../apply/index.js';\nimport { CLI_VERSION } from '../version.js';\nimport { dispatch } from './_dispatch.js';\n\n/**\n * `monoceros apply <name>` — materialize the yml at\n * `<MONOCEROS_HOME>/container-configs/<name>.yml` into\n * `<MONOCEROS_HOME>/container/<name>/` and bring the container up.\n *\n * The target location is fixed by convention. cwd is irrelevant. No\n * `--path` override — one config maps to exactly one container\n * directory, and that's the whole mental model.\n */\nexport const applyCommand = defineCommand({\n meta: {\n name: 'apply',\n group: 'lifecycle',\n description:\n 'Materialize a container config into $MONOCEROS_HOME/container/<name>/ and bring the dev-container up. Close any VS Code Remote Containers session for the target first — the extension auto-recreates and races with apply.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Config name. Resolves to $MONOCEROS_HOME/container-configs/<name>.yml.',\n required: true,\n },\n verbose: {\n type: 'boolean',\n description:\n 'Stream the raw @devcontainers/cli output to stderr instead of showing a phase spinner. Auto-enabled when stderr is not a TTY.',\n default: false,\n },\n },\n run({ args }) {\n return dispatch(async () => {\n const result = await runApply({\n name: args.name,\n cliVersion: CLI_VERSION,\n verbose: args.verbose,\n });\n return result.containerExitCode;\n });\n },\n});\n","import { existsSync, promises as fs } from 'node:fs';\nimport { consola } from 'consola';\nimport {\n type MonocerosConfig,\n proxyHostPort,\n readMonocerosConfig,\n} from '../config/global.js';\nimport { parseConfig, readConfig, stringifyConfig } from '../config/io.js';\nimport {\n containerConfigPath,\n containerConfigsDir,\n containerDir,\n containerEnvPath,\n monocerosHome as defaultMonocerosHome,\n prettyPath,\n} from '../config/paths.js';\nimport {\n readEnvFile,\n interpolateServices,\n interpolateFeatureOptions,\n formatMissingVarsError,\n ensureEnvGitignored,\n resolveGitUserFields,\n} from '../config/env-file.js';\nimport { REGEX, isValidEmail } from '../config/schema.js';\nimport {\n buildStateFile,\n readStateFile,\n writeStateFile,\n} from '../config/state.js';\nimport type { SolutionConfig } from '../config/schema.js';\nimport { solutionConfigToCreateOptions } from '../config/transform.js';\nimport {\n needsCompose,\n normalizeOptions,\n validateOptions,\n writeScaffold,\n} from '../create/scaffold.js';\nimport { cyan, dim, sectionLine, stripAnsi } from '../util/format.js';\nimport { migrateDeprecatedFeatureRef } from '../util/ref.js';\nimport { createApplyLog, teeApplyLogger } from './apply-log.js';\nimport {\n type ApplyProgress,\n createApplyProgress,\n createSigintAbort,\n logFileOnlyLogger,\n progressTeeLogger,\n} from './apply-progress.js';\nimport { buildApplySummary, formatApplySummary } from './apply-summary.js';\nimport { type DockerExec, runContainerCycle } from '../devcontainer/compose.js';\nimport {\n type CredentialsSpawn,\n collectGitCredentials,\n uniqueHttpsHosts,\n formatMissingCredentialsError,\n formatUnknownProviderError,\n} from '../devcontainer/credentials.js';\nimport {\n type DockerInfoSpawn,\n detectDockerMode,\n formatRootlessNotSupportedError,\n} from '../devcontainer/docker-mode.js';\nimport { type DevcontainerSpawn } from '../devcontainer/cli.js';\nimport {\n ensureProxy,\n type DockerExec as ProxyDockerExec,\n} from '../proxy/index.js';\nimport { removeDynamicConfig, writeDynamicConfig } from '../proxy/dynamic.js';\nimport { preflightHostPort } from '../proxy/port-check.js';\nimport {\n collectGitIdentity,\n type IdentityPrompt,\n type IdentityScopePrompt,\n type IdentitySpawn,\n} from '../devcontainer/identity.js';\nimport { writeGlobalDefaultGitUser } from '../config/global.js';\nimport { setContainerGitUserInDoc } from '../modify/yml.js';\n\n/**\n * `monoceros apply <name>` — read the yml at\n * `<MONOCEROS_HOME>/container-configs/<name>.yml`, materialize the\n * devcontainer scaffold at `<MONOCEROS_HOME>/container/<name>/`, write\n * `.monoceros/state.json` with `origin: <name>`, then teardown + bring\n * the container up.\n *\n * The target location is determined by convention, not by cwd or an\n * explicit path argument. That's deliberate: a config is the source of\n * truth, the container directory mirrors it 1:1, and the builder never\n * has to remember \"which directory was sandbox materialized into\".\n *\n * Idempotent: re-running picks up the current yml, overwrites scaffold\n * files, restarts the container.\n *\n * Refuses to materialize into a non-empty directory whose state.json\n * points at a different origin — protects against accidental clobber\n * if a builder somehow seeded `<MONOCEROS_HOME>/container/<name>/`\n * outside of this command.\n */\n\nexport interface RunApplyOptions {\n /** Config name — resolves to `<home>/container-configs/<name>.yml`. */\n name: string;\n cliVersion: string;\n /** Override of the user-data home. Tests inject a tmpdir. */\n monocerosHome?: string;\n now?: Date;\n /**\n * When true, stream the raw `@devcontainers/cli` output to stderr\n * exactly like before ADR 0013 step 2 — no spinner, no phase\n * detection, full transcript live. Also forced on when stderr is\n * not a TTY (CI, piped output). Defaults to false.\n */\n verbose?: boolean;\n /**\n * Override the stream the spinner writes to. Tests inject an\n * in-memory writable so the progress UI doesn't touch process.stderr.\n */\n progressOut?: NodeJS.WriteStream;\n logger?: {\n info: (msg: string) => void;\n success: (msg: string) => void;\n warn?: (msg: string) => void;\n /**\n * Print a structural section marker (`▸ Configuration` etc).\n * Optional — tests typically pass a silent logger without one,\n * in which case section markers are no-ops.\n */\n section?: (label: string) => void;\n };\n dockerExec?: DockerExec;\n devcontainerSpawn?: DevcontainerSpawn;\n credentialsSpawn?: CredentialsSpawn;\n dockerInfoSpawn?: DockerInfoSpawn;\n identitySpawn?: IdentitySpawn;\n identityPrompt?: IdentityPrompt;\n identityScopePrompt?: IdentityScopePrompt;\n /** Override the docker exec used by the Traefik proxy lifecycle. */\n proxyDocker?: ProxyDockerExec;\n}\n\nexport interface RunApplyResult {\n /** Absolute path to the materialized container directory. */\n targetDir: string;\n /** Absolute path to the source yml. */\n configPath: string;\n /** Exit code of the trailing `devcontainer up` step. */\n containerExitCode: number;\n}\n\nexport async function runApply(opts: RunApplyOptions): Promise<RunApplyResult> {\n const home = opts.monocerosHome ?? defaultMonocerosHome();\n const logger = opts.logger ?? {\n info: (msg) => consola.info(msg),\n success: (msg) => consola.success(msg),\n warn: (msg) => consola.warn(msg),\n // Default section renderer: empty line, bold-underlined \"▸ Label\",\n // empty line. Mirrors install.sh's section visuals.\n section: (label) => process.stderr.write(`\\n${sectionLine(label)}\\n\\n`),\n };\n const section = (label: string) => logger.section?.(label);\n\n if (!REGEX.solutionName.test(opts.name)) {\n throw new Error(\n `Invalid config name: ${JSON.stringify(opts.name)}. Use letters, digits, '.', '_' or '-'.`,\n );\n }\n\n const ymlPath = containerConfigPath(opts.name, home);\n if (!existsSync(ymlPath)) {\n throw new Error(\n `No such config: ${ymlPath}. Run \\`monoceros init <template> ${opts.name}\\` first.`,\n );\n }\n\n const targetDir = containerDir(opts.name, home);\n await assertSafeTargetDir(targetDir, opts.name);\n\n // ── Configuration ────────────────────────────────────────────\n section('Configuration');\n\n const parsed = await readConfig(ymlPath);\n // Read global defaults early — feature option defaults from\n // `monoceros-config.yml` need to be merged before scaffold codegen,\n // and the git identity logic later in this function also needs the\n // global config.\n const globalConfig = await readMonocerosConfig({ monocerosHome: home });\n\n // Pre-M4 the canonical feature namespace was\n // `ghcr.io/monoceros/features/…`. After the M4 cut it moved to\n // `ghcr.io/getmonoceros/monoceros-features/…`. Old refs still\n // structurally parse as third-party refs, so apply would silently\n // try to pull them from GHCR and 404. Warn loudly and tell the\n // builder what to write instead — but don't rewrite their yml or\n // fail the apply, so they stay in control of the migration.\n warnOnDeprecatedFeatureRefs(parsed.config.features, globalConfig, logger);\n\n // Read the per-container env file (container-configs/<name>.env) — the\n // source for `${VAR}` references — BEFORE the transform, because\n // feature options must be resolved before they're merged with the\n // monoceros-config `defaults.features` cascade.\n const envPath = containerEnvPath(opts.name, home);\n await ensureEnvGitignored(containerConfigsDir(home));\n const envVars = readEnvFile(envPath);\n\n // Resolve `${VAR}` in FEATURE options first. A missing/empty value\n // becomes \"\" so the transform's merge skips it → the option falls\n // through to the global default (or stays unset). A filled value\n // overrides. This lets credential placeholders (`apiKey: ${VAR}`) be\n // active in the yml with a blank `.env` seed.\n const resolvedFeatures = interpolateFeatureOptions(\n parsed.config.features,\n envVars,\n );\n\n // Shape validation happened in readConfig; catalog validation\n // (which language/service exists) happens here against\n // create/scaffold's known set.\n const createOpts = normalizeOptions(\n solutionConfigToCreateOptions(\n { ...parsed.config, features: resolvedFeatures },\n globalConfig?.defaults?.features ?? {},\n ),\n );\n\n // Resolve `${VAR}` in SERVICE fields (post-transform — services don't\n // merge with defaults). Unlike features, an unresolved reference here\n // is a hard error: a silently-empty DB password fails far more\n // opaquely later.\n const interpServices = interpolateServices(createOpts.services, envVars);\n if (interpServices.missing.length > 0) {\n throw new Error(\n formatMissingVarsError(interpServices.missing, prettyPath(envPath)),\n );\n }\n createOpts.services = interpServices.services;\n\n // Resolve `${VAR}` in git identities — the container-level `git.user`\n // and each repo's `git.user` — against the same env file. UNLIKE\n // services/features, a missing var is NOT an error here: the identity\n // falls through to the existing cascade (monoceros-config defaults →\n // host → prompt). Only a fully-resolved-but-malformed email is a hard\n // error, checked now that the actual value is known (the schema defers\n // email format to apply on purpose).\n //\n // - container `git.user`: per field. A resolved field is used; an\n // unresolved one is dropped so the cascade fills it (the cascade\n // already resolves name/email independently).\n // - repo `git.user`: all-or-nothing. A single missing var drops the\n // whole per-repo override, so the repo inherits the container\n // identity (.monoceros/gitconfig) — no Frankenstein name-from-env +\n // email-from-cascade.\n const gitUserErrors: string[] = [];\n let containerGitOverride: { name?: string; email?: string } | undefined;\n if (parsed.config.git?.user) {\n const f = resolveGitUserFields(parsed.config.git.user, envVars);\n if (f.email.value !== undefined && !isValidEmail(f.email.value)) {\n gitUserErrors.push(\n `git.user.email resolved to \"${f.email.value}\", which is not a valid email`,\n );\n }\n const override = {\n ...(f.name.value !== undefined ? { name: f.name.value } : {}),\n ...(f.email.value !== undefined ? { email: f.email.value } : {}),\n };\n if (Object.keys(override).length > 0) containerGitOverride = override;\n }\n for (const repo of createOpts.repos ?? []) {\n if (!repo.gitUser) continue;\n const f = resolveGitUserFields(repo.gitUser, envVars);\n if (f.name.value === undefined || f.email.value === undefined) {\n // All-or-nothing: a field with no usable value (missing/empty var)\n // drops the whole per-repo override → the repo inherits the\n // container identity, which itself climbs the cascade.\n delete repo.gitUser;\n continue;\n }\n if (!isValidEmail(f.email.value)) {\n gitUserErrors.push(\n `repos[${repo.path}].git.user.email resolved to \"${f.email.value}\", which is not a valid email`,\n );\n continue;\n }\n repo.gitUser = { name: f.name.value, email: f.email.value };\n }\n if (gitUserErrors.length > 0) {\n throw new Error(\n `Invalid git identity after resolving ${prettyPath(envPath)}:\\n` +\n gitUserErrors.map((e) => ` - ${e}`).join('\\n') +\n `\\n\\nFix the value in the env file (or the yml).`,\n );\n }\n\n validateOptions(createOpts);\n logger.success(`yml validated ${dim(`(${prettyPath(ymlPath)})`)}`);\n\n // Refresh host git identity and HTTPS credentials before the\n // container teardown so they're in place when post-create.sh runs.\n // Identity resolution priority: yml override → monoceros-config.yml\n // defaults → host global → persisted .monoceros/gitconfig → prompt.\n //\n // Skip identity collection entirely when there's no obvious reason\n // to need one: no repos to clone, no explicit yml.git.user, no\n // defaults.git.user. Without those, asking the builder for a\n // committer identity is pure friction — they didn't ask for git\n // and might just want a sandbox container. They can `monoceros\n // add-repo` later, at which point the next apply re-evaluates and\n // collects identity then.\n const hasRepos = (createOpts.repos ?? []).length > 0;\n const hasContainerGitUser = parsed.config.git?.user !== undefined;\n const hasDefaultGitUser = globalConfig?.defaults?.git?.user !== undefined;\n const idLogger = {\n info: logger.info,\n warn: logger.warn ?? logger.info,\n };\n if (hasRepos || hasContainerGitUser || hasDefaultGitUser) {\n const identity = await collectGitIdentity(targetDir, {\n ...(opts.identitySpawn ? { spawn: opts.identitySpawn } : {}),\n ...(opts.identityPrompt ? { prompt: opts.identityPrompt } : {}),\n ...(opts.identityScopePrompt\n ? { scopePrompt: opts.identityScopePrompt }\n : {}),\n ...(containerGitOverride\n ? { containerOverride: containerGitOverride }\n : {}),\n ...(globalConfig?.defaults?.git?.user\n ? { defaults: globalConfig.defaults.git.user }\n : {}),\n logger: idLogger,\n });\n\n // Persist a freshly-prompted identity to whichever scope the\n // builder picked. Scope `g` writes monoceros-config.yml's\n // `defaults.git.user`; `c` writes this container yml's\n // `git.user`; `b` does both. The `.monoceros/gitconfig` file\n // collectGitIdentity already wrote stays the in-container\n // mechanism — these writes are about making the value\n // recoverable on the next apply / next container without\n // re-prompting.\n if (identity.prompted) {\n await persistPromptedIdentity(identity.prompted, ymlPath, home, logger);\n }\n }\n // Pre-fetch HTTPS credentials for every unique host derived from\n // the declared repos. Pre-flight: if any host returns no credentials,\n // fail fast with provider-specific setup hints — much more\n // actionable than letting the in-container `git clone` later die\n // with \"could not read Username\".\n //\n // First pass: reject hosts whose provider couldn't be resolved\n // (non-canonical host without an explicit `provider:` in the yml).\n // Those produce a separate \"set provider:\" error message — much\n // more useful than a generic \"no credentials\" hint because the\n // builder might actually have credentials in their helper, but we\n // wouldn't know which CLI to suggest.\n const hostsToFetch = uniqueHttpsHosts(createOpts.repos ?? []);\n const unknownProviderHosts = hostsToFetch\n .filter((h) => h.provider === 'unknown')\n .map((h) => h.host);\n if (unknownProviderHosts.length > 0) {\n throw new Error(formatUnknownProviderError(unknownProviderHosts));\n }\n if (hostsToFetch.length > 0) {\n const credResult = await collectGitCredentials(targetDir, hostsToFetch, {\n ...(opts.credentialsSpawn ? { spawn: opts.credentialsSpawn } : {}),\n logger: idLogger,\n });\n const missing = credResult.perHost.filter((p) => p.status !== 'ok');\n if (missing.length > 0) {\n throw new Error(formatMissingCredentialsError(missing));\n }\n }\n\n // NOTE: repos are cloned IN the container (post-create.sh), using the\n // container's network + the mounted credential helper. We deliberately\n // do NOT probe or clone repos host-side: the host's network/credential\n // context isn't the container's (a host may fail to resolve a remote\n // the container reaches fine), so host-side gating produced spurious\n // pre-flight failures across platforms. The in-container clone is the\n // single source of truth and reports a real error if a repo genuinely\n // can't be reached. (The host-side clone added in ADR 0012 — for\n // service bind-mounts of repo files like init.sql — was reverted; that\n // ordering needs a container-side solution instead.)\n\n // ── Scaffold ─────────────────────────────────────────────────\n section('Scaffold');\n\n // Probe the host docker daemon. Two purposes today:\n // - Refuse to apply on rootless Docker, which doesn't work with\n // our bind-mount model (host/container file ownership doesn't\n // line up; Docker doesn't expose the `idmap` mount option that\n // would fix this). The refusal lands before any docker build\n // or container start, so the builder gets a clear actionable\n // message instead of permission-denied surprises mid-clone.\n // - Plumb the mode through to scaffold for any future mode-\n // dependent code paths (parameter is currently unused after\n // the idmap revert — kept so the wiring is in place).\n const dockerMode = await detectDockerMode({\n ...(opts.dockerInfoSpawn ? { spawn: opts.dockerInfoSpawn } : {}),\n });\n if (dockerMode === 'rootless') {\n throw new Error(formatRootlessNotSupportedError());\n }\n\n await fs.mkdir(targetDir, { recursive: true });\n await writeScaffold(createOpts, targetDir, { dockerMode });\n await writeStateFile(\n targetDir,\n buildStateFile({\n origin: opts.name,\n cliVersion: opts.cliVersion,\n ...(opts.now ? { now: opts.now } : {}),\n }),\n );\n logger.success(`materialized into ${prettyPath(targetDir)}`);\n\n // Repos are cloned in-container by post-create.sh (see the NOTE above\n // the Scaffold section) — no host-side clone here.\n\n // ── Container ────────────────────────────────────────────────\n section('Container');\n\n // Open the per-apply log file under `<container>/logs/`. From this\n // point on, the wrapped `containerLogger` mirrors info/warn/success\n // into the log alongside the terminal, and `applyLog.sink` is teed\n // into the devcontainer-cli stream via `runContainerCycle`'s\n // `logSink` option. See ADR 0013.\n const applyLog = createApplyLog({\n name: opts.name,\n home,\n cliVersion: opts.cliVersion,\n configPath: ymlPath,\n ...(opts.now ? { now: opts.now } : {}),\n });\n\n // Decide between interactive (spinner) and verbose (raw stream)\n // mode. ADR 0013: spinner is default; `--verbose` and non-TTY\n // environments fall back to the live stream so CI logs and\n // builder-driven debugging stay intact.\n const progressOut = opts.progressOut ?? process.stderr;\n const interactive = (progressOut.isTTY ?? false) && !opts.verbose;\n const progress: ApplyProgress | null = interactive\n ? createApplyProgress({ out: progressOut, interactive: true })\n : null;\n\n // Loggers used inside the container section:\n // - `containerLogger` carries status lines that must surface on\n // screen (Features list, Traefik routing warning). In spinner\n // mode these print above the spinner via println; in verbose\n // mode they go through consola as before.\n // - `internalLogger` is the chatter from compose pre-cleanup. In\n // spinner mode it goes to the log file only — the spinner phase\n // label already conveys \"cleaning up\". In verbose mode it goes\n // to screen too.\n const containerLogger = progress\n ? progressTeeLogger(progress, applyLog.sink)\n : teeApplyLogger(logger, applyLog.sink);\n const internalLogger = progress\n ? logFileOnlyLogger(applyLog.sink)\n : containerLogger;\n\n // SIGINT cleanup. Without a handler, Ctrl+C leaves the spinner's\n // last frame stuck on screen (cursor mid-line, shell prompt glued\n // to it), the log file misses its last write-buffer chunk, and the\n // exit code is whatever Node defaults to. With the handler we stop\n // the spinner, write a final marker into the log, close it cleanly,\n // and exit with 130 (128 + SIGINT, conventional for Ctrl+C).\n //\n // The docker child is left to die from signal propagation — what\n // it leaves behind (half-created container, partial layer) is\n // cleaned up on the next `apply` via `--remove-existing-container`\n // / the compose pre-cleanup, so we do not try to undo it here.\n const onSigint = createSigintAbort({\n progress,\n out: progressOut,\n log: applyLog,\n formatLogPointer: (p) => dim(`log: ${prettyPath(p)}`),\n onExit: () => process.exit(130),\n });\n process.on('SIGINT', onSigint);\n\n let exitCode: number;\n try {\n // First-apply context: devcontainer-cli prints \"Error fetching image\n // details: No manifest found for …\" for multi-arch GHCR images, then\n // sits silent for ~1 min while Docker pulls the runtime image.\n // Both are non-fatal — the buildx step right after consumes the\n // image fine. In spinner mode the phase label (\"starting container…\")\n // covers this, so the warning lives in the log file only. In verbose\n // mode it stays on screen as before.\n const pullWarning =\n 'Pulling runtime image and building feature layers. First apply takes ~1–2 min (Docker downloads the multi-arch base); subsequent applies are cached and fast. devcontainer-cli may log a \"No manifest found\" line — harmless, the pull continues.';\n if (progress) {\n applyLog.stream.write(`# note: ${pullWarning}\\n\\n`);\n } else {\n containerLogger.info(dim(pullWarning));\n }\n\n // Bring up the shared Traefik singleton ahead of the devcontainer\n // when the yml declares ports, and refresh the dynamic config so\n // the routes match whatever the yml currently says. `ensureProxy`\n // and `writeDynamicConfig` are both idempotent; a second\n // devcontainer that also wants Traefik just joins the already-\n // running proxy. See ADR 0007.\n const ports = createOpts.ports ?? [];\n const hasPorts = ports.length > 0;\n if (hasPorts) {\n // Pre-flight: bail with an actionable hint before `docker run`\n // tries to bind a held port. Throws on conflict — the message\n // names the routing.hostPort escape hatch and asks the builder\n // to either free the port or set a different one.\n await preflightHostPort(proxyHostPort(globalConfig), {\n ...(opts.proxyDocker ? { docker: opts.proxyDocker } : {}),\n });\n }\n\n try {\n if (hasPorts) {\n await writeDynamicConfig(opts.name, ports, { monocerosHome: home });\n await ensureProxy({\n ...(opts.proxyDocker ? { docker: opts.proxyDocker } : {}),\n monocerosHome: home,\n hostPort: proxyHostPort(globalConfig),\n logger: containerLogger,\n });\n } else {\n // `ports:` is empty (or was removed since the last apply) —\n // drop any stale dynamic-config file. Filesystem only; the\n // proxy itself is offered for teardown by stop/remove, not\n // here (apply ends with the container up, not stopped).\n await removeDynamicConfig(opts.name, { monocerosHome: home });\n }\n } catch (err) {\n // Don't strand the apply if Traefik bookkeeping fails — surface\n // as a warn and keep going. The devcontainer itself is still\n // usable; the builder loses only the `<name>.localhost` routing,\n // which the next apply / `add-port` will retry.\n containerLogger.warn?.(\n `Could not sync Traefik routes: ${err instanceof Error ? err.message : String(err)}. The container will start, but \\`<name>.localhost\\` routing may not work until the next \\`monoceros apply\\`.`,\n );\n }\n\n // Seed the spinner phase before the actual work so the builder\n // sees an immediate hint of what is happening. The stream-driven\n // triggers in apply-progress.ts take over once devcontainer-cli\n // starts emitting recognizable lines.\n if (progress) {\n progress.setPhase(\n needsCompose(createOpts)\n ? 'cleaning up previous containers…'\n : 'starting container…',\n );\n }\n\n exitCode = await runContainerCycle(targetDir, {\n hasCompose: needsCompose(createOpts),\n ...(opts.dockerExec !== undefined ? { dockerExec: opts.dockerExec } : {}),\n ...(opts.devcontainerSpawn !== undefined\n ? { devcontainerSpawn: opts.devcontainerSpawn }\n : {}),\n logSink: applyLog.sink,\n ...(progress ? { progressSink: progress.streamSink, silent: true } : {}),\n logger: internalLogger,\n });\n\n // Stop the spinner and surface the outcome. In spinner mode the\n // failure path also prints the captured tail (~15 last lines of the\n // devcontainer-cli stream) so the builder sees the actual error\n // without paging through the log file. In verbose mode the stream\n // was already on screen, so there is nothing to replay.\n if (progress) {\n if (exitCode === 0) {\n progress.succeed();\n } else {\n const { tailLines } = progress.fail();\n progressOut.write(`\\n✘ apply failed (exit ${exitCode})\\n\\n`);\n for (const line of tailLines) {\n progressOut.write(` ${line}\\n`);\n }\n if (tailLines.length > 0) progressOut.write('\\n');\n }\n }\n\n // Inventory block on success: shows the builder what their yml just\n // materialized (features, services, languages, repos, ports, apt\n // packages, install URLs). Replaces the cherry-picked \"Features: …\"\n // line that used to print above the spinner. Mirrored into the log\n // file with ANSI escapes stripped so `cat …apply-….log` stays\n // readable.\n if (exitCode === 0) {\n const summaryLines = buildApplySummary(createOpts);\n if (summaryLines.length > 0) {\n const formatted = formatApplySummary(summaryLines);\n progressOut.write(`\\n${formatted}\\n`);\n applyLog.stream.write(`\\n${stripAnsi(formatted)}\\n`);\n }\n }\n\n // Close the log before announcing its path — guarantees the file\n // is fully flushed to disk by the time the builder follows the\n // pointer. The path is printed on both the success and failure\n // path; on failure it is the breadcrumb pointing at the full\n // diagnostic above what fits in the tail.\n //\n // Direct write rather than `logger.info` — the default consola\n // logger prefixes info lines with a timestamp, which collides with\n // the structured look of the section. The leading blank line\n // separates the pointer from the summary block above.\n await applyLog.close();\n progressOut.write(`\\n ${dim(`log: ${prettyPath(applyLog.path)}`)}\\n`);\n\n // ── Next steps ───────────────────────────────────────────────\n // Only print the wrap-up on a successful container start;\n // otherwise the failing devcontainer-cli output is the relevant\n // signal and a cheery \"shell into it!\" line would be misleading.\n if (exitCode === 0) {\n section('Next steps');\n logger.info(` ${cyan(`monoceros shell ${opts.name}`)}`);\n }\n } finally {\n process.off('SIGINT', onSigint);\n }\n\n return { targetDir, configPath: ymlPath, containerExitCode: exitCode };\n}\n\n/**\n * `<MONOCEROS_HOME>/container/<name>/` is safe to (re-)materialize iff:\n * - it doesn't exist or is empty (fresh apply), OR\n * - it already carries `.monoceros/state.json` with the same origin\n * (re-apply against the same yml), OR\n * - the only top-level entry is `.monoceros/` and there's no\n * state.json — that's a partial-apply remnant: pre-flight wrote\n * `gitconfig` / `git-credentials` into `.monoceros/` before\n * something (reachability failure, Ctrl-C, expired token, …)\n * aborted the apply ahead of `writeStateFile`. We own\n * `.monoceros/`, so re-running is safe.\n *\n * Anything else — state.json with a different origin, or files\n * outside `.monoceros/` that aren't ours — stays an error so we\n * don't clobber unrelated work.\n */\nasync function assertSafeTargetDir(\n targetDir: string,\n expectedOrigin: string,\n): Promise<void> {\n if (!existsSync(targetDir)) return;\n const entries = await fs.readdir(targetDir);\n if (entries.length === 0) return;\n\n const state = await readStateFile(targetDir);\n if (state) {\n if (state.origin !== expectedOrigin) {\n throw new Error(\n `${targetDir} is already materialized from config '${state.origin}', not '${expectedOrigin}'. Delete the directory to re-target, or run \\`monoceros apply ${state.origin}\\`.`,\n );\n }\n return; // safe: re-apply same origin\n }\n\n // No state.json. If the only top-level entry is `.monoceros/`, this\n // is a partial-apply remnant from a failed earlier run — pre-flight\n // writes `.monoceros/gitconfig` and `.monoceros/git-credentials`\n // BEFORE the scaffold + state.json sequence, so a mid-apply abort\n // leaves exactly this shape behind. Treat it as recoverable.\n if (entries.length === 1 && entries[0] === '.monoceros') {\n return;\n }\n\n throw new Error(\n `Refusing to materialize into non-empty directory ${targetDir} (no Monoceros state.json found, and the directory has files we don't recognise). Delete the directory before re-running.`,\n );\n}\n\ninterface MigrationLogger {\n warn?: (msg: string) => void;\n info: (msg: string) => void;\n}\n\nfunction warnOnDeprecatedFeatureRefs(\n containerFeatures: SolutionConfig['features'],\n globalConfig: MonocerosConfig | undefined,\n logger: MigrationLogger,\n): void {\n const warn = logger.warn ?? logger.info;\n const seen = new Set<string>();\n const emit = (oldRef: string, source: string) => {\n if (seen.has(oldRef)) return;\n seen.add(oldRef);\n const newRef = migrateDeprecatedFeatureRef(oldRef);\n if (!newRef) return;\n warn(\n `Deprecated feature ref in ${source}: '${oldRef}'. ` +\n `Replace with '${newRef}' — the old namespace is no longer published. ` +\n `See docs/MIGRATION-M4.md for a sed snippet.`,\n );\n };\n\n for (const entry of containerFeatures) {\n emit(entry.ref, 'container yml');\n }\n const globalDefaults = globalConfig?.defaults?.features;\n if (globalDefaults) {\n for (const ref of Object.keys(globalDefaults)) {\n emit(ref, 'monoceros-config.yml');\n }\n }\n}\n\n/**\n * Persist an identity that came from the interactive prompt. Called\n * with the builder's scope pick (`g`/`c`/`b`); logs to the apply\n * stream where the values landed (or why they couldn't, e.g. global\n * default was already set and we left it alone).\n *\n * Pulled out of runApply for readability — runApply already carries\n * a lot of pre-flight ceremony, and this is a self-contained\n * persistence step.\n */\nasync function persistPromptedIdentity(\n prompted: { name: string; email: string; scope: 'g' | 'c' | 'b' },\n ymlPath: string,\n home: string,\n logger: {\n info: (msg: string) => void;\n warn?: (msg: string) => void;\n },\n): Promise<void> {\n const wantGlobal = prompted.scope === 'g' || prompted.scope === 'b';\n const wantContainer = prompted.scope === 'c' || prompted.scope === 'b';\n\n if (wantGlobal) {\n try {\n const result = await writeGlobalDefaultGitUser(\n { name: prompted.name, email: prompted.email },\n { monocerosHome: home },\n );\n if (result.alreadySet) {\n logger.warn?.(\n `monoceros-config.yml already has a defaults.git.user — left it alone. To replace, edit ${prettyPath(result.filePath)} by hand.`,\n );\n } else if (result.created) {\n logger.info(\n `Saved identity globally — created ${prettyPath(result.filePath)} with defaults.git.user.`,\n );\n } else {\n logger.info(\n `Saved identity globally — wrote defaults.git.user into ${prettyPath(result.filePath)}.`,\n );\n }\n } catch (err) {\n logger.warn?.(\n `Could not persist identity to monoceros-config.yml: ${err instanceof Error ? err.message : String(err)}. The values are still active for this apply via .monoceros/gitconfig.`,\n );\n }\n }\n\n if (wantContainer) {\n try {\n const text = await fs.readFile(ymlPath, 'utf8');\n const parsed = parseConfig(text, ymlPath);\n const changed = setContainerGitUserInDoc(parsed.doc, {\n name: prompted.name,\n email: prompted.email,\n });\n if (changed) {\n const out = stringifyConfig(parsed.doc);\n await fs.writeFile(ymlPath, out, 'utf8');\n logger.info(\n `Saved identity in this container — wrote git.user into ${prettyPath(ymlPath)}.`,\n );\n }\n } catch (err) {\n logger.warn?.(\n `Could not persist identity to ${prettyPath(ymlPath)}: ${err instanceof Error ? err.message : String(err)}. The values are still active for this apply via .monoceros/gitconfig.`,\n );\n }\n }\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { CONFIG_SCHEMA_VERSION } from './schema.js';\n\n/**\n * `.monoceros/state.json` — the Phase-3 replacement for `stack.json`.\n *\n * The yml at `.local/container-configs/<origin>.yml` is the source\n * of truth. `state.json` is a back-reference: it tells `monoceros\n * apply` (no args) which yml to read and re-apply for this dev-\n * container. Other fields (appliedAt, cliVersion) are pure diagnostics\n * for `monoceros status` and ad-hoc debugging.\n *\n * `materializedAt` is the timestamp of the most recent apply, NOT\n * the create. There is no `createdAt` because the yml is the\n * lifecycle anchor now — multiple dev-containers can share one yml,\n * and each has its own state.json timeline.\n */\nexport interface StateFile {\n schemaVersion: typeof CONFIG_SCHEMA_VERSION;\n /** Config name the yml is stored under (`<origin>.yml`). */\n origin: string;\n /** Monoceros CLI version that wrote this state.json. */\n monocerosCliVersion: string;\n /** ISO-8601 timestamp of the most recent apply. */\n materializedAt: string;\n}\n\nexport function buildStateFile(opts: {\n origin: string;\n cliVersion: string;\n now?: Date;\n}): StateFile {\n return {\n schemaVersion: CONFIG_SCHEMA_VERSION,\n origin: opts.origin,\n monocerosCliVersion: opts.cliVersion,\n materializedAt: (opts.now ?? new Date()).toISOString(),\n };\n}\n\nexport function stateFilePath(targetDir: string): string {\n return path.join(targetDir, '.monoceros', 'state.json');\n}\n\nexport async function readStateFile(\n targetDir: string,\n): Promise<StateFile | undefined> {\n try {\n const content = await fs.readFile(stateFilePath(targetDir), 'utf8');\n return JSON.parse(content) as StateFile;\n } catch {\n return undefined;\n }\n}\n\nexport async function writeStateFile(\n targetDir: string,\n state: StateFile,\n): Promise<void> {\n const monocerosDir = path.join(targetDir, '.monoceros');\n await fs.mkdir(monocerosDir, { recursive: true });\n await fs.writeFile(\n stateFilePath(targetDir),\n JSON.stringify(state, null, 2) + '\\n',\n );\n}\n","import { resolveService } from '../create/catalog.js';\nimport { deriveRepoName } from '../create/scaffold.js';\nimport type { CreateOptions, FeatureOptions } from '../create/types.js';\nimport { portNumber, type SolutionConfig } from './schema.js';\n\n/**\n * Translate a yml-shaped `SolutionConfig` into the `CreateOptions`\n * shape the existing scaffolders (devcontainer.json, compose.yaml,\n * post-create.sh) consume.\n *\n * The big shape mismatch is `features`:\n * - yml: `[{ ref, options }, …]` (array; humans edit/comment this)\n * - CreateOptions: `Record<ref, options>` (devcontainer.json shape)\n * We dedupe by `ref` and keep the LAST occurrence — same rule the\n * existing `add-feature` command uses when a builder re-adds with new\n * options.\n *\n * `externalServices.postgres` → `postgresUrl` (the CreateOptions\n * field name predates the yml schema; rename would touch every M1\n * caller, so the translator absorbs the diff here).\n *\n * `featureDefaults` (optional) — `defaults.features` from\n * `monoceros-config.yml`. Per-container options always override these;\n * keys not set per-container fall back to the default. A feature ref\n * that exists only in `featureDefaults` (not in the container yml)\n * does NOT get included — the container yml is what decides whether\n * a feature is active at all; the defaults only fill in option values.\n */\nexport function solutionConfigToCreateOptions(\n config: SolutionConfig,\n featureDefaults: Record<string, FeatureOptions> = {},\n): CreateOptions {\n const featureRecord: Record<string, FeatureOptions> = {};\n for (const entry of config.features) {\n const defaults = featureDefaults[entry.ref] ?? {};\n // Per-container options override defaults, EXCEPT when the\n // container value is the empty string. A bare `apiKey:` in the\n // yml parses to null and the schema relaxes that to `\"\"`; the\n // init-generator also writes hint keys without a value. In both\n // cases the builder's intent is \"leave this unset, fall through\n // to the global default\" — not \"explicitly clear the default\".\n // Skip empty strings so the merge respects that.\n const containerOpts = Object.fromEntries(\n Object.entries(entry.options ?? {}).filter(([, v]) => v !== ''),\n );\n featureRecord[entry.ref] = { ...defaults, ...containerOpts };\n }\n\n const result: CreateOptions = {\n name: config.name,\n languages: [...config.languages],\n // Normalize every services[] entry (curated string or explicit\n // object) to the canonical ResolvedService shape. `${VAR}` values\n // survive untouched here — apply interpolates them against\n // <name>.env afterwards.\n services: config.services.map(resolveService),\n };\n\n if (config.externalServices.postgres !== undefined) {\n result.postgresUrl = config.externalServices.postgres;\n }\n if (config.aptPackages.length > 0) {\n result.aptPackages = [...config.aptPackages];\n }\n if (Object.keys(featureRecord).length > 0) {\n result.features = featureRecord;\n }\n if (config.installUrls.length > 0) {\n result.installUrls = [...config.installUrls];\n }\n if (config.repos.length > 0) {\n result.repos = config.repos.map((r) => ({\n url: r.url,\n // `path` is optional in the yml; CreateOptions requires it.\n // When the yml omits `path`, fall back to the URL-derived\n // single-segment default (`https://.../foo.git` → `foo`),\n // which lands the clone at `projects/foo/`.\n path: r.path ?? deriveRepoName(r.url),\n // gitUser is forwarded only when BOTH name + email are set.\n // The relaxed GitUserSchema accepts nullable / empty strings\n // (so a yml placeholder `name:` parses without error), so we\n // re-check here before downstream code, which expects both\n // values to be non-empty.\n ...(r.git?.user?.name && r.git.user.email\n ? { gitUser: { name: r.git.user.name, email: r.git.user.email } }\n : {}),\n ...(r.provider ? { provider: r.provider } : {}),\n }));\n }\n const routingPorts = config.routing?.ports ?? [];\n if (routingPorts.length > 0) {\n // Collapse both yml forms (`- 3000` and `- port: 9229`) to a flat\n // number array. Dedupe by port number — repeated entries in the\n // yml would otherwise show up twice in `forwardPorts` and in the\n // Traefik route set.\n const seen = new Set<number>();\n const ports: number[] = [];\n for (const entry of routingPorts) {\n const n = portNumber(entry);\n if (seen.has(n)) continue;\n seen.add(n);\n ports.push(n);\n }\n result.ports = ports;\n }\n if (config.routing?.vscodeAutoForward !== undefined) {\n result.vscodeAutoForward = config.routing.vscodeAutoForward;\n }\n return result;\n}\n","import { createWriteStream, mkdirSync, type WriteStream } from 'node:fs';\nimport path from 'node:path';\nimport { Writable } from 'node:stream';\nimport { containerLogsDir } from '../config/paths.js';\nimport { stripAnsi } from '../util/format.js';\n\n/**\n * Per-apply log file under `<home>/container/<name>/logs/`.\n *\n * Step 1 of ADR 0013: the terminal still receives the live\n * `@devcontainers/cli` stream and the existing `▸ Container` section\n * lines unchanged. In parallel, a Writable is offered to the\n * devcontainer spawn (`logSink`) and a tee-logger mirrors the\n * `logger.info/.success/.warn` calls into the same file, so the log\n * contains the full apply transcript including the bits the spawn\n * stream does not see (Features list, traefik warnings, compose\n * pre-cleanup).\n *\n * Caller responsibilities:\n * - call `close()` exactly once after the container cycle finishes,\n * on the happy path and on the error path\n * - hand the resulting `path` to the user via a final `ℹ log: <path>`\n * line so the artifact is discoverable\n */\nexport interface ApplyLogOptions {\n name: string;\n home: string;\n cliVersion: string;\n configPath: string;\n now?: Date;\n}\n\nexport interface ApplyLog {\n /** Absolute path to the open log file. */\n path: string;\n /** Underlying write stream — pass as `logSink` to devcontainer spawns. */\n stream: WriteStream;\n /** Plain-text mirror sink for our own status lines (ANSI-stripped). */\n sink: Writable;\n /** Close the file. Safe to call once; subsequent calls are no-ops. */\n close(): Promise<void>;\n}\n\nfunction safeIsoStamp(d: Date): string {\n // ISO with `:` and `.` replaced — colons are invalid in NTFS filenames\n // and `.` before the suffix would imply a doubled extension.\n return d.toISOString().replace(/[:.]/g, '-');\n}\n\nexport function createApplyLog(opts: ApplyLogOptions): ApplyLog {\n const now = opts.now ?? new Date();\n const dir = containerLogsDir(opts.name, opts.home);\n mkdirSync(dir, { recursive: true });\n const file = `apply-${opts.name}-${safeIsoStamp(now)}.log`;\n const fullPath = path.join(dir, file);\n const stream = createWriteStream(fullPath, { flags: 'w' });\n\n // Header — small, human-readable, fixed key=value style so `grep` and\n // future tooling can find the apply context without parsing prose.\n const header = [\n `# monoceros apply log`,\n `# command: monoceros apply ${opts.name}`,\n `# started: ${now.toISOString()}`,\n `# cli-version: ${opts.cliVersion}`,\n `# config: ${opts.configPath}`,\n `# host: ${process.platform}/${process.arch} node ${process.version}`,\n ``,\n ``,\n ].join('\\n');\n stream.write(header);\n\n // ANSI-stripping Writable for our own status lines. devcontainer-cli\n // output passes through `stream` directly (the masked secret pipeline\n // upstream of it doesn't add ANSI; timestamps + JSON are plain text).\n const sink = new Writable({\n write(chunk, _enc, cb): void {\n const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');\n stream.write(stripAnsi(text), cb);\n },\n });\n\n let closed = false;\n return {\n path: fullPath,\n stream,\n sink,\n close: () =>\n new Promise<void>((resolve) => {\n if (closed) {\n resolve();\n return;\n }\n closed = true;\n sink.end(() => {\n stream.end(() => resolve());\n });\n }),\n };\n}\n\n/**\n * Wrap an apply logger so each `info/success/warn` call is also\n * appended to `sink` with a level prefix. Section markers stay on\n * screen only — they are structural for the terminal, the log file\n * gets its own header.\n */\nexport interface TeeableLogger {\n info: (msg: string) => void;\n success: (msg: string) => void;\n warn?: (msg: string) => void;\n section?: (label: string) => void;\n}\n\nexport function teeApplyLogger<L extends TeeableLogger>(\n base: L,\n sink: Writable,\n): L {\n const write = (level: string, msg: string): void => {\n sink.write(`[${level}] ${msg}\\n`);\n };\n const wrapped: TeeableLogger = {\n info: (msg) => {\n base.info(msg);\n write('info', msg);\n },\n success: (msg) => {\n base.success(msg);\n write('ok', msg);\n },\n warn: (msg) => {\n (base.warn ?? base.info)(msg);\n write('warn', msg);\n },\n };\n if (base.section) wrapped.section = base.section.bind(base);\n return wrapped as L;\n}\n","import { Writable } from 'node:stream';\nimport { stripAnsi } from '../util/format.js';\n\n/**\n * Apply-time phase spinner + tail buffer. ADR 0013 step 2.\n *\n * In interactive mode (TTY, no `--verbose`), the raw `@devcontainers/cli`\n * output is suppressed on screen and replaced by a single line: a\n * spinner glyph plus the current phase label. The phase advances when\n * recognizable triggers appear in the stream (see {@link PHASE_TRIGGERS}).\n *\n * The same stream chunks feed a {@link TAIL_LINES}-line ring buffer.\n * On failure the caller pulls that tail out and prints it to stderr,\n * so the builder sees the actual diagnostic instead of an empty\n * \"apply failed\" message — the rest lives in the apply log file.\n *\n * In non-interactive mode the spinner is replaced by plain\n * `> phase…` lines on phase changes — useful when output is piped to\n * a file. The stream itself is NOT echoed there either; the log\n * file is the source of truth.\n */\n\nconst FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\nconst FRAME_INTERVAL_MS = 80;\nconst TAIL_LINES = 15;\n\n/**\n * Stream triggers that advance the spinner label. Order matters — the\n * first match per line wins, so put more specific patterns first.\n * Each pattern is matched against single output lines after ANSI\n * stripping, so any colour/style codes in upstream output do not\n * interfere with detection.\n */\nconst PHASE_TRIGGERS: ReadonlyArray<{ pattern: RegExp; label: string }> = [\n // Compose mode triggers a feature/layer build before the container\n // is created — distinct phase, often the longest single step.\n { pattern: /Start: Run: docker build/i, label: 'building feature layers…' },\n // Image mode jumps straight from \"preparing…\" into the docker run\n // that pulls (if needed) + creates + starts the container.\n { pattern: /Start: Run: docker run/i, label: 'starting container…' },\n { pattern: /Running the postCreateCommand/i, label: 'running postCreate…' },\n];\n\nexport interface ApplyProgressOptions {\n /** Stream to write the spinner to. Usually `process.stderr`. */\n out: NodeJS.WriteStream;\n /**\n * When true, emit the spinner via `\\r\\x1b[K` cursor manipulation.\n * When false, emit a plain `> phase…\\n` line on each phase change.\n * Tests use `false` with an in-memory stream; production picks\n * based on `out.isTTY && !verbose`.\n */\n interactive: boolean;\n /** Override the clock for deterministic elapsed-time formatting. */\n now?: () => number;\n}\n\nexport interface ApplyProgress {\n /** Change the visible phase label. No-op if same as current. */\n setPhase(label: string): void;\n /**\n * Print `line` above the spinner: pause the spinner, write the line\n * with a trailing newline (added if missing), restart the spinner.\n * Use for one-off status lines that must stay visible — `Features: …`\n * or a Traefik-routing warning.\n */\n println(line: string): void;\n /** Stop the spinner and emit `✔ <label>` (elapsed time appended). */\n succeed(label?: string): void;\n /** Stop the spinner; return the captured tail lines. */\n fail(): { tailLines: string[] };\n /**\n * Writable to pass to `spawnDevcontainer` as `progressSink`. Drives\n * phase detection and fills the tail ring buffer.\n */\n readonly streamSink: Writable;\n}\n\nexport function createApplyProgress(opts: ApplyProgressOptions): ApplyProgress {\n const out = opts.out;\n const now = opts.now ?? (() => Date.now());\n const startedAt = now();\n let phase = 'preparing…';\n let frameIdx = 0;\n let timer: NodeJS.Timeout | null = null;\n let stopped = false;\n const tail: string[] = [];\n let lineBuf = '';\n\n const writeSpinner = (): void => {\n if (!opts.interactive || stopped) return;\n out.write(`\\r\\x1b[K${FRAMES[frameIdx]} ${phase}`);\n };\n const clearLine = (): void => {\n if (!opts.interactive) return;\n out.write('\\r\\x1b[K');\n };\n\n const setPhase = (label: string): void => {\n if (phase === label) return;\n phase = label;\n if (opts.interactive) {\n writeSpinner();\n } else {\n out.write(`> ${label}\\n`);\n }\n };\n\n const println = (line: string): void => {\n clearLine();\n const withNewline = line.endsWith('\\n') ? line : `${line}\\n`;\n out.write(withNewline);\n writeSpinner();\n };\n\n const fmtElapsed = (): string => {\n const ms = now() - startedAt;\n const totalSec = Math.max(0, Math.round(ms / 1000));\n const m = Math.floor(totalSec / 60);\n const s = totalSec % 60;\n return m > 0 ? `${m}m ${s}s` : `${s}s`;\n };\n\n const stop = (): void => {\n if (timer) {\n clearInterval(timer);\n timer = null;\n }\n if (!stopped) {\n stopped = true;\n clearLine();\n }\n };\n\n const succeed = (label?: string): void => {\n stop();\n const text = label ?? `container ready (${fmtElapsed()})`;\n out.write(`✔ ${text}\\n`);\n };\n\n const fail = (): { tailLines: string[] } => {\n stop();\n return { tailLines: [...tail] };\n };\n\n const streamSink = new Writable({\n write(chunk, _enc, cb): void {\n const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');\n lineBuf += stripAnsi(text);\n let nl: number;\n while ((nl = lineBuf.indexOf('\\n')) !== -1) {\n const line = lineBuf.slice(0, nl);\n lineBuf = lineBuf.slice(nl + 1);\n if (line.length === 0) continue;\n tail.push(line);\n if (tail.length > TAIL_LINES) tail.shift();\n for (const trig of PHASE_TRIGGERS) {\n if (trig.pattern.test(line)) {\n setPhase(trig.label);\n break;\n }\n }\n }\n cb();\n },\n });\n\n if (opts.interactive) {\n writeSpinner();\n timer = setInterval(() => {\n frameIdx = (frameIdx + 1) % FRAMES.length;\n writeSpinner();\n }, FRAME_INTERVAL_MS);\n // Don't keep the event loop alive just because the spinner is\n // ticking — the apply work itself owns the lifetime.\n timer.unref?.();\n }\n\n return {\n setPhase,\n println,\n succeed,\n fail,\n streamSink,\n };\n}\n\n/**\n * Logger shape compatible with the rest of the apply pipeline that\n * routes info/warn/success through a {@link ApplyProgress} (above the\n * spinner) and into the apply log sink.\n */\nexport function progressTeeLogger(\n progress: ApplyProgress,\n sink: Writable,\n): {\n info: (msg: string) => void;\n success: (msg: string) => void;\n warn: (msg: string) => void;\n} {\n const fileLine = (level: string, msg: string): void => {\n sink.write(`[${level}] ${msg}\\n`);\n };\n return {\n info: (msg) => {\n progress.println(msg);\n fileLine('info', msg);\n },\n success: (msg) => {\n progress.println(`✔ ${msg}`);\n fileLine('ok', msg);\n },\n warn: (msg) => {\n progress.println(`! ${msg}`);\n fileLine('warn', msg);\n },\n };\n}\n\n/**\n * Logger that only writes to the apply log sink. Used in interactive\n * mode for diagnostic chatter from `runContainerCycle`'s compose\n * pre-cleanup ([cleanup] tearing down…, removing containers, etc.) —\n * the spinner phase covers what is happening on screen; the full\n * detail lives in the log file.\n */\nexport function logFileOnlyLogger(sink: Writable): {\n info: (msg: string) => void;\n success: (msg: string) => void;\n warn: (msg: string) => void;\n} {\n const fileLine = (level: string, msg: string): void => {\n sink.write(`[${level}] ${msg}\\n`);\n };\n return {\n info: (msg) => fileLine('info', msg),\n success: (msg) => fileLine('ok', msg),\n warn: (msg) => fileLine('warn', msg),\n };\n}\n\n/**\n * SIGINT cleanup for `monoceros apply`: stop the spinner, mark the\n * log file with an abort marker, close the file, then call `onExit`\n * (which production wires to `process.exit(130)`). Extracted from\n * `runApply` so the abort flow can be exercised without registering\n * a real signal handler.\n *\n * The returned function is the handler itself. Re-entry — a second\n * Ctrl+C while the first cleanup is still running — is guarded so\n * the second press becomes a no-op and lets the runtime handle a\n * hard kill.\n */\nexport interface ApplyAbortDeps {\n progress: ApplyProgress | null;\n /**\n * Where the `⏹ aborted` line and the final log-path pointer are\n * written. Production uses `process.stderr` (or the same stream\n * the spinner uses); tests inject an in-memory writable.\n */\n out: { write: (chunk: string) => void };\n log: {\n stream: { write: (chunk: string) => void };\n close: () => Promise<void>;\n path: string;\n };\n /** Format the final pointer line — kept injectable for ANSI-free assertions. */\n formatLogPointer: (path: string) => string;\n /** Process-terminator. Production passes a function calling `process.exit(130)`. */\n onExit: () => void;\n}\n\nexport function createSigintAbort(deps: ApplyAbortDeps): () => void {\n let aborted = false;\n return (): void => {\n if (aborted) return;\n aborted = true;\n if (deps.progress) deps.progress.fail();\n deps.out.write('\\n⏹ aborted\\n');\n deps.log.stream.write('\\n[abort] SIGINT received\\n');\n void deps.log.close().finally(() => {\n deps.out.write(`\\n ${deps.formatLogPointer(deps.log.path)}\\n`);\n deps.onExit();\n });\n };\n}\n","import type { CreateOptions } from '../create/types.js';\nimport { cyan } from '../util/format.js';\n\n/**\n * Post-apply summary: a label/values block that lists what the\n * builder just materialized into the container — features, services,\n * languages, repos, ports, apt packages, install URLs. Skips sections\n * that are empty.\n *\n * Replaces the pre-spinner \"Features: …\" line that used to print\n * above the progress indicator: that line cherry-picked one yml field\n * arbitrarily; the summary covers everything.\n *\n * Two-step API so the formatter can be unit-tested without an open\n * apply: `buildApplySummary` produces a structured list,\n * `formatApplySummary` renders it.\n */\n\nexport interface SummaryLine {\n label: string;\n values: string[];\n}\n\n/**\n * Last path-or-URL segment, with any `:tag` suffix stripped. Used as\n * a short display name for OCI-style feature refs.\n *\n * `ghcr.io/getmonoceros/monoceros-features/claude-code:1` → `claude-code`\n * `./features/local-thing` → `local-thing`\n */\nfunction shortFeatureName(ref: string): string {\n const withoutTag = ref.replace(/:[^:/@]+$/, '');\n const idx = withoutTag.lastIndexOf('/');\n return idx >= 0 ? withoutTag.slice(idx + 1) : withoutTag;\n}\n\n/** Last non-empty path segment, falling back to the URL when path is bare. */\nfunction shortRepoName(repo: { url: string; path: string }): string {\n const last = repo.path.split('/').filter(Boolean).pop();\n return last && last.length > 0 ? last : repo.url;\n}\n\nexport function buildApplySummary(opts: CreateOptions): SummaryLine[] {\n const lines: SummaryLine[] = [];\n if (opts.languages.length > 0) {\n lines.push({ label: 'Languages', values: opts.languages });\n }\n if (opts.services.length > 0) {\n lines.push({\n label: 'Services',\n values: opts.services.map((s) => s.name),\n });\n }\n if (opts.features && Object.keys(opts.features).length > 0) {\n lines.push({\n label: 'Features',\n values: Object.keys(opts.features).map(shortFeatureName),\n });\n }\n if (opts.repos && opts.repos.length > 0) {\n lines.push({\n label: 'Repositories',\n values: opts.repos.map(shortRepoName),\n });\n }\n if (opts.ports && opts.ports.length > 0) {\n lines.push({ label: 'Ports', values: opts.ports.map(String) });\n }\n if (opts.aptPackages && opts.aptPackages.length > 0) {\n lines.push({ label: 'APT packages', values: opts.aptPackages });\n }\n if (opts.installUrls && opts.installUrls.length > 0) {\n lines.push({ label: 'Install URLs', values: opts.installUrls });\n }\n return lines;\n}\n\n/**\n * Render the summary as a colour-tinted, label-aligned block. The\n * caller is responsible for adding the leading + trailing newlines\n * that frame it within the apply output.\n */\nexport function formatApplySummary(lines: SummaryLine[]): string {\n if (lines.length === 0) return '';\n const labelWidth = Math.max(...lines.map((l) => l.label.length));\n return lines\n .map((l) => ` ${l.label.padEnd(labelWidth)} ${cyan(l.values.join(', '))}`)\n .join('\\n');\n}\n","import { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport { consola } from 'consola';\nimport { type DockerExec } from '../proxy/index.js';\nimport { createSecretMaskStream } from '../util/mask-secrets.js';\nimport { spawnDevcontainer, type DevcontainerSpawn } from './cli.js';\n\nexport { type DockerExec, type DockerResult } from '../proxy/index.js';\n\nexport type ComposeSpawn = (args: string[], cwd: string) => Promise<number>;\n\n// Default spawn: shells out to `docker compose` (the v2 docker\n// subcommand). Stdout/stderr are streamed through a secret masker\n// (see util/mask-secrets.ts) so feature option dumps, ENV-printouts\n// and similar do not leak Atlassian/GitHub/Anthropic tokens onto\n// the host terminal.\nexport const spawnDockerCompose: ComposeSpawn = (args, cwd) => {\n return new Promise((resolve, reject) => {\n const child = spawn('docker', ['compose', ...args], {\n cwd,\n stdio: ['inherit', 'pipe', 'pipe'],\n });\n child.stdout?.pipe(createSecretMaskStream()).pipe(process.stdout);\n child.stderr?.pipe(createSecretMaskStream()).pipe(process.stderr);\n child.on('error', reject);\n child.on('exit', (code) => resolve(code ?? 0));\n });\n};\n\n// Direct invocation of `docker <args>` with no shell wrapper.\n// BOTH stdout and stderr are buffered, never live-streamed: the\n// cleanup pipeline routinely runs docker calls that are expected to\n// fail (e.g. `docker network rm <name>` for image-mode containers\n// that never had a project network — \"network not found\" is the\n// happy path). Live streaming would leak that noise to the user.\n// Callers print buffered stderr on the failure paths they actually\n// care about.\n//\n// Why no bash. The old cleanup path piped a script through\n// `bash -c <script>` which on Windows is typically WSL's bash via\n// the C:\\Users\\…\\WindowsApps\\bash.exe launcher. Quoting a label\n// value with backslashes (`c:\\Users\\…\\.monoceros\\…`) survives PS,\n// CreateProcess, WSL launcher, and bash's own parser only to come\n// out the other end mangled when handed to docker, and the label\n// filter then silently matches nothing. Going through Node spawn\n// directly removes every one of those layers.\n//\n// Shape matches `DockerExec` re-exported above (originally from\n// proxy/index.ts) so tests can swap in the same fake across both the\n// proxy lifecycle and the cleanup pipelines.\nexport const spawnDocker: DockerExec = (args) => {\n return new Promise((resolve, reject) => {\n const child = spawn('docker', args, {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n let stdout = '';\n let stderr = '';\n child.stdout?.on('data', (chunk: Buffer) => {\n stdout += chunk.toString('utf8');\n });\n child.stderr?.on('data', (chunk: Buffer) => {\n stderr += chunk.toString('utf8');\n });\n child.on('error', reject);\n child.on('exit', (code) =>\n resolve({ exitCode: code ?? 0, stdout, stderr }),\n );\n });\n};\n\n/**\n * Collect container IDs matching ANY of the given docker `--filter`\n * values, deduplicated. Tolerates per-filter failures (treats them as\n * empty) so a malformed/unsupported filter doesn't take the whole\n * cleanup down.\n *\n * Each `filter` is the literal value passed after `--filter`, e.g.\n * `label=com.docker.compose.project=foo` or `name=^foo-`.\n */\nexport async function findContainerIds(\n filters: readonly string[],\n exec: DockerExec = spawnDocker,\n): Promise<string[]> {\n const ids = new Set<string>();\n for (const filter of filters) {\n const result = await exec(['ps', '-aq', '--filter', filter]);\n if (result.exitCode !== 0) continue;\n for (const line of result.stdout.split(/\\r?\\n/)) {\n const id = line.trim();\n if (id) ids.add(id);\n }\n }\n return [...ids];\n}\n\nexport interface CleanupDockerObjectsOptions {\n /** Display name in log lines (`[cleanup] tearing down docker project <projectName>…`). */\n projectName: string;\n /** Docker `--filter` values; containers matching ANY are removed. */\n filters: readonly string[];\n /** Optional network name to `docker network rm` after container removal. Failure is ignored (network may not exist). */\n network?: string;\n /** `[cleanup] …` prefix on log lines. Defaults to `cleanup`; `remove/index.ts` uses `remove` to match the existing on-screen tag. */\n logTag?: string;\n logger: { info: (message: string) => void };\n exec?: DockerExec;\n}\n\nexport interface CleanupDockerObjectsResult {\n exitCode: number;\n removedIds: string[];\n}\n\n/**\n * Replacement for the previous `bash -c '…'` cleanup script in\n * `remove` and the apply pre-cleanup. Drives docker directly via\n * Node spawn (`spawnDocker`) so backslash-bearing Windows label\n * values reach docker unmangled.\n *\n * `exitCode` is 0 unless `docker rm -f` itself returned non-zero;\n * per-filter `docker ps` failures are tolerated silently. Use\n * {@link findContainerIds} afterwards if you need to verify the\n * tear-down actually emptied the project.\n */\nexport async function cleanupDockerObjects(\n opts: CleanupDockerObjectsOptions,\n): Promise<CleanupDockerObjectsResult> {\n const exec = opts.exec ?? spawnDocker;\n const tag = opts.logTag ?? 'cleanup';\n opts.logger.info(`[${tag}] tearing down docker project ${opts.projectName}…`);\n\n const ids = await findContainerIds(opts.filters, exec);\n\n let rmExit = 0;\n if (ids.length > 0) {\n opts.logger.info(`[${tag}] removing containers: ${ids.join(' ')}`);\n const rmResult = await exec(['rm', '-f', ...ids]);\n rmExit = rmResult.exitCode;\n if (rmExit !== 0 && rmResult.stderr.trim()) {\n // Real failure path — surface what docker said so the builder\n // doesn't see a bare non-zero exit with no explanation.\n opts.logger.info(`[${tag}] ${rmResult.stderr.trim()}`);\n }\n } else {\n opts.logger.info(`[${tag}] no containers found`);\n }\n\n if (opts.network) {\n const netResult = await exec(['network', 'rm', opts.network]);\n if (netResult.exitCode === 0) {\n opts.logger.info(`[${tag}] network ${opts.network} removed`);\n }\n // Otherwise: silent. The common failure here is \"network not\n // found\" because image-mode devcontainers (no compose) never\n // created one — expected, not actionable, kept the bash version\n // quiet with `2>/dev/null`. A real docker error (daemon down)\n // already showed up on the earlier ps/rm calls.\n }\n\n opts.logger.info(`[${tag}] docker cleanup done`);\n return { exitCode: rmExit, removedIds: ids };\n}\n\ninterface ResolvedCompose {\n composeFile: string;\n projectName: string;\n}\n\n// Match the project name `@devcontainers/cli` derives when it brings a\n// compose-mode devcontainer up: `<root-basename>_devcontainer`.\n// Aligning here means `monoceros start/stop/status/logs` and the\n// implicit `devcontainer up` from `monoceros run/shell` act on the\n// same compose project — without it docker would create two parallel\n// stacks.\nexport function composeProjectName(root: string): string {\n return `${path.basename(root)}_devcontainer`;\n}\n\n/**\n * Resolve compose-mode metadata for the container rooted at `root`.\n * `root` is `<MONOCEROS_HOME>/container/<name>/` and must already\n * exist with a `.devcontainer/compose.yaml` inside. The compose-only\n * lifecycle commands (`start/stop/status/logs/down`) error when the\n * file is missing.\n */\nexport function resolveCompose(root: string): ResolvedCompose {\n if (!existsSync(path.join(root, '.devcontainer'))) {\n throw new Error(\n `No .devcontainer/ at ${root}. Run \\`monoceros apply <name>\\` first.`,\n );\n }\n const composeFile = path.join(root, '.devcontainer', 'compose.yaml');\n if (!existsSync(composeFile)) {\n throw new Error(\n `No compose.yaml at ${composeFile}. \\`start\\` / \\`stop\\` / \\`status\\` / \\`logs\\` require services configured via \\`monoceros add-service <name> <svc>\\`. Use \\`monoceros shell <name>\\` to enter the container directly.`,\n );\n }\n return { composeFile, projectName: composeProjectName(root) };\n}\n\nexport interface ComposeActionOptions {\n root: string;\n service?: string;\n spawn?: ComposeSpawn;\n}\n\nasync function runComposeAction(\n buildSubArgs: (service: string | undefined) => string[],\n opts: ComposeActionOptions,\n): Promise<number> {\n const { composeFile, projectName } = resolveCompose(opts.root);\n const spawnFn = opts.spawn ?? spawnDockerCompose;\n const subArgs = buildSubArgs(opts.service);\n return spawnFn(['-f', composeFile, '-p', projectName, ...subArgs], opts.root);\n}\n\nexport interface StartOptions {\n root: string;\n spawn?: DevcontainerSpawn;\n logger?: { info: (message: string) => void };\n /**\n * Forwarded to {@link DevcontainerSpawnOptions.logSink}. See ADR 0013\n * and apply/apply-log.ts.\n */\n logSink?: NodeJS.WritableStream;\n /** Forwarded to {@link DevcontainerSpawnOptions.progressSink}. */\n progressSink?: NodeJS.WritableStream;\n /** Forwarded to {@link DevcontainerSpawnOptions.silent}. */\n silent?: boolean;\n}\n\n// `monoceros start` delegates to `devcontainer up` rather than to\n// `docker compose up -d`. The detour through @devcontainers/cli matters\n// because:\n// - it labels the workspace container with `devcontainer.local_folder`\n// so subsequent `devcontainer exec` (from `monoceros run/shell`) can\n// find the container by workspace path,\n// - it applies devcontainer features (which docker compose ignores), and\n// - it triggers the postCreateCommand once.\n// The auxiliary services come up alongside because the generated\n// devcontainer.json lists them under `runServices`.\nexport async function runStart(opts: StartOptions): Promise<number> {\n resolveCompose(opts.root); // throws if no compose.yaml\n const logger = opts.logger ?? { info: (msg) => consola.info(msg) };\n const spawnFn = opts.spawn ?? spawnDevcontainer;\n logger.info(`Bringing devcontainer up at ${opts.root}…`);\n return spawnFn(\n ['up', '--workspace-folder', opts.root, '--mount-workspace-git-root=false'],\n opts.root,\n buildSpawnOptions(opts),\n );\n}\n\nfunction buildSpawnOptions(\n opts: Pick<StartOptions, 'logSink' | 'progressSink' | 'silent'>,\n): DevcontainerSpawnOptionsForwarded | undefined {\n const out: DevcontainerSpawnOptionsForwarded = {};\n if (opts.logSink) out.logSink = opts.logSink;\n if (opts.progressSink) out.progressSink = opts.progressSink;\n if (opts.silent) out.silent = true;\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\ninterface DevcontainerSpawnOptionsForwarded {\n logSink?: NodeJS.WritableStream;\n progressSink?: NodeJS.WritableStream;\n silent?: boolean;\n}\n\nexport interface RunContainerCycleOptions {\n hasCompose: boolean;\n /**\n * Inject a fake docker exec for tests. Replaces the previous\n * `cleanupSpawn: ComposeSpawn` which fed a bash script to\n * `bash -c`; we now drive docker directly via Node spawn, so the\n * shell layer (and its Windows quoting failures) is out of the\n * picture.\n */\n dockerExec?: DockerExec;\n devcontainerSpawn?: DevcontainerSpawn;\n /**\n * Forwarded to the underlying `spawnDevcontainer` as\n * {@link DevcontainerSpawnOptions.logSink}. See ADR 0013.\n */\n logSink?: NodeJS.WritableStream;\n /** Forwarded to {@link DevcontainerSpawnOptions.progressSink}. */\n progressSink?: NodeJS.WritableStream;\n /** Forwarded to {@link DevcontainerSpawnOptions.silent}. */\n silent?: boolean;\n logger: {\n info: (message: string) => void;\n warn?: (message: string) => void;\n };\n}\n\n/**\n * Container teardown + up for a devcontainer rooted at `root`.\n * Used by `runApply` (apply/index.ts) after writing the scaffold.\n */\nexport async function runContainerCycle(\n root: string,\n opts: RunContainerCycleOptions,\n): Promise<number> {\n const { hasCompose, logger } = opts;\n\n if (hasCompose) {\n const projectName = composeProjectName(root);\n logger.info(\n `Force-removing existing ${projectName} containers (volumes preserved)…`,\n );\n // Two-step removal so a container with stale/missing labels still\n // gets caught:\n // - by docker-compose project label\n // - by container-name prefix `<project>-*`\n // After removal we re-query: if anything remains, VS Code's Remote\n // Containers extension is the likely culprit (auto-recreates on\n // container loss); we abort with a clear hint rather than letting\n // `devcontainer up` collide.\n const exec = opts.dockerExec ?? spawnDocker;\n const filters = [\n `label=com.docker.compose.project=${projectName}`,\n `name=^${projectName}-`,\n ];\n const { exitCode: rmExit } = await cleanupDockerObjects({\n projectName,\n filters,\n network: `${projectName}_default`,\n logger,\n exec,\n });\n if (rmExit !== 0) return rmExit;\n\n const remaining = await findContainerIds(filters, exec);\n if (remaining.length > 0) {\n const warn = logger.warn ?? logger.info;\n warn(\n `ERROR: containers under project ${projectName} reappeared after removal.\\n` +\n `This typically means VS Code's Remote Containers extension is connected\\n` +\n `to this devcontainer and auto-recreated it. Close the dev container\\n` +\n `session in VS Code (Cmd+Shift+P → 'Dev Containers: Close Remote Connection')\\n` +\n `and retry \\`monoceros apply\\`.`,\n );\n return 1;\n }\n\n return runStart({\n root,\n ...(opts.devcontainerSpawn ? { spawn: opts.devcontainerSpawn } : {}),\n ...(opts.logSink ? { logSink: opts.logSink } : {}),\n ...(opts.progressSink ? { progressSink: opts.progressSink } : {}),\n ...(opts.silent ? { silent: true } : {}),\n logger,\n });\n }\n\n logger.info(`Recreating image-mode devcontainer at ${root}…`);\n const spawnFn = opts.devcontainerSpawn ?? spawnDevcontainer;\n return spawnFn(\n [\n 'up',\n '--workspace-folder',\n root,\n '--mount-workspace-git-root=false',\n '--remove-existing-container',\n ],\n root,\n buildSpawnOptions(opts),\n );\n}\n\nexport function runStop(opts: ComposeActionOptions): Promise<number> {\n return runComposeAction(\n (service) => ['stop', ...(service ? [service] : [])],\n opts,\n );\n}\n\nexport function runStatus(opts: ComposeActionOptions): Promise<number> {\n return runComposeAction(\n (service) => ['ps', ...(service ? [service] : [])],\n opts,\n );\n}\n\nexport interface LogsOptions extends ComposeActionOptions {\n follow?: boolean;\n}\n\nexport function runLogs(opts: LogsOptions): Promise<number> {\n const follow = opts.follow ?? true;\n return runComposeAction(\n (service) => [\n 'logs',\n ...(follow ? ['-f'] : []),\n ...(service ? [service] : []),\n ],\n opts,\n );\n}\n","import { Transform, type TransformCallback } from 'node:stream';\n\n/**\n * Mask known token-shaped strings in arbitrary text.\n *\n * Devcontainer-cli and docker compose stream the build/up output\n * straight to stdout. They include the feature options (Atlassian\n * apiToken, GitHub PAT, Anthropic apiKey, …) verbatim, which leaks\n * real, per-builder secrets onto the user's terminal and into any\n * captured CI log.\n *\n * The fix is a regex sweep on each output line: when a token\n * matches a known prefix shape, replace its middle with `…` and\n * keep the prefix + last 6 characters so the value is still\n * recognizable for debugging (\"did the right token get loaded?\")\n * without exposing the secret.\n *\n * What's **not** masked here, by design:\n *\n * - The literal `monoceros` user/password baked into the compose\n * service catalog (postgres, mysql). It's a documented dev-\n * convention, identical on every Monoceros container, openly\n * listed in `create/catalog.ts` and the components README. Not\n * a secret. Masking it would just make the connection string\n * harder to spot for the builder.\n * - Anything that looks \"password-shaped\" via a key= pattern.\n * Risk of false positives outweighs cosmetic benefit when the\n * value isn't actually sensitive (see also ADR-style note in\n * `create/catalog.ts`).\n */\n\ninterface SecretPattern {\n /** Short label for the pattern, useful in debugging. */\n name: string;\n /** Match shape. Must be a /g regex so all occurrences get replaced. */\n re: RegExp;\n}\n\n// Order doesn't matter — patterns are disjoint by prefix.\nconst PATTERNS: SecretPattern[] = [\n // Atlassian Cloud API token. Starts with literal `ATATT3xFf` plus\n // a long URL-safe-base64 tail. Tightened to that prefix to avoid\n // matching unrelated all-caps words.\n { name: 'atlassian-api', re: /ATATT3xFf[A-Za-z0-9+/=_-]{20,}/g },\n // Bitbucket Cloud app password.\n { name: 'bitbucket-app', re: /ATBB[A-Za-z0-9+/=_-]{20,}/g },\n // GitHub PAT (classic), OAuth, user, server, refresh — all share\n // the `gh<lower-letter>_<base62>` shape per GitHub's token format.\n { name: 'github-token', re: /gh[a-z]_[A-Za-z0-9]{20,}/g },\n // GitHub fine-grained PAT.\n { name: 'github-pat', re: /github_pat_[A-Za-z0-9_]{20,}/g },\n // Anthropic API key.\n { name: 'anthropic-api', re: /sk-ant-[A-Za-z0-9_-]{20,}/g },\n];\n\n/**\n * Replace token-shaped substrings with a masked form. Idempotent\n * for already-masked strings (the elision character isn't part of\n * any pattern's alphabet).\n */\nexport function maskSecrets(text: string): string {\n let result = text;\n for (const { re } of PATTERNS) {\n result = result.replace(re, maskOne);\n }\n return result;\n}\n\nfunction maskOne(token: string): string {\n if (token.length <= 12) return token;\n return `${token.slice(0, 5)}…${token.slice(-6)}`;\n}\n\n/**\n * Transform stream that runs every chunk through `maskSecrets`.\n *\n * Tokens can in theory straddle a chunk boundary if the upstream\n * writer flushes mid-token, leaving an unmasked tail. Mitigation:\n * the transform holds back the last line of every chunk until a\n * newline arrives, since real Docker / devcontainer-cli output is\n * line-oriented and tokens don't contain newlines. On final flush\n * any leftover buffer is masked and emitted.\n */\nexport function createSecretMaskStream(): Transform {\n let buffer = '';\n return new Transform({\n decodeStrings: true,\n transform(chunk: Buffer | string, _enc, cb: TransformCallback): void {\n const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');\n buffer += text;\n const lastNewline = buffer.lastIndexOf('\\n');\n if (lastNewline === -1) {\n // No complete line yet — keep buffering. We'd rather hold\n // back partial output briefly than emit half a token.\n cb(null);\n return;\n }\n const flushable = buffer.slice(0, lastNewline + 1);\n buffer = buffer.slice(lastNewline + 1);\n cb(null, maskSecrets(flushable));\n },\n flush(cb: TransformCallback): void {\n if (buffer.length > 0) {\n const tail = maskSecrets(buffer);\n buffer = '';\n cb(null, tail);\n return;\n }\n cb(null);\n },\n });\n}\n","import { spawn } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport { createSecretMaskStream, maskSecrets } from '../util/mask-secrets.js';\nimport {\n createRuntimePullHintStream,\n type PullHintState,\n} from './runtime-pull-hint.js';\n\nconst require_ = createRequire(import.meta.url);\n\nlet cachedBinaryPath: string | null = null;\n\n// Resolve the absolute path to the `@devcontainers/cli` JS entry point. We\n// invoke it via `node <path>` rather than relying on a `.bin/` shim being on\n// PATH, so the CLI works regardless of how the user installed the workbench.\nexport function devcontainerCliPath(): string {\n if (cachedBinaryPath) return cachedBinaryPath;\n const pkgJsonPath = require_.resolve('@devcontainers/cli/package.json');\n const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8')) as {\n bin?: string | Record<string, string>;\n };\n const binEntry =\n typeof pkg.bin === 'string' ? pkg.bin : (pkg.bin?.devcontainer ?? '');\n if (!binEntry) {\n throw new Error('Could not resolve @devcontainers/cli bin entry.');\n }\n cachedBinaryPath = path.resolve(path.dirname(pkgJsonPath), binEntry);\n return cachedBinaryPath;\n}\n\nexport interface DevcontainerSpawnOptions {\n // When true, capture stdout and stderr instead of inheriting them.\n // The buffered output is only flushed (to stderr) if the process exits\n // non-zero, so successful no-op invocations stay silent. Use this for\n // intermediate steps like the implicit `up` that `monoceros run` does\n // before `exec`; leave it unset for explicit lifecycle calls\n // (`monoceros start`) and for the final exec where the user expects to\n // see output.\n quiet?: boolean;\n // When true, hand the child process direct stdio. Pure `inherit` —\n // no piping, no secret masking, no buffering. Required for any\n // interactive use case (`monoceros shell`, the `exec` step of\n // `monoceros run`): bash detects a non-TTY stdin/stdout and exits\n // immediately, which makes a stdio-pipe approach a non-starter.\n // The build/log paths in apply and start still go through the\n // masked-pipe path, where there's no TTY at stake.\n interactive?: boolean;\n // Optional additional sink that receives a copy of the masked\n // stdout/stderr stream. Used by `apply` to mirror the live\n // devcontainer-cli output into a per-apply log file. The spawn\n // never ends this stream — the caller closes it after the apply\n // wraps up. See ADR 0013 and apply/apply-log.ts.\n logSink?: NodeJS.WritableStream;\n // Optional sink that receives the same masked stream as `logSink`.\n // Used by the spinner / phase detector. See apply/apply-progress.ts.\n progressSink?: NodeJS.WritableStream;\n // When true, do NOT pipe the masked stream to process.stdout/stderr.\n // The output is captured by `logSink` and/or `progressSink` instead.\n // Set in interactive apply mode where the spinner owns the terminal;\n // verbose / non-TTY mode leaves it false so output still streams live.\n silent?: boolean;\n}\n\nexport type DevcontainerSpawn = (\n args: string[],\n cwd: string,\n options?: DevcontainerSpawnOptions,\n) => Promise<number>;\n\n// Default spawn implementation: runs the @devcontainers/cli binary\n// directly. Both stdout and stderr are streamed through a secret\n// masker (see util/mask-secrets.ts) so that feature options like\n// Atlassian apiTokens or GitHub PATs do not leak verbatim to the\n// terminal when devcontainer-cli logs the build args / feature\n// option dumps. With `{ quiet: true }` output is buffered (and\n// masked) and only flushed on a non-zero exit.\nexport const spawnDevcontainer: DevcontainerSpawn = (\n args,\n cwd,\n options = {},\n) => {\n const binPath = devcontainerCliPath();\n return new Promise((resolve, reject) => {\n if (options.interactive) {\n // Direct inherit — required so the child binary sees a real\n // TTY on stdin/stdout/stderr. Secret masking is irrelevant\n // here (the builder is running an interactive command;\n // build-time option dumps don't fire on this path).\n const child = spawn(process.execPath, [binPath, ...args], {\n cwd,\n stdio: 'inherit',\n });\n child.on('error', reject);\n child.on('exit', (code) => resolve(code ?? 0));\n return;\n }\n const child = spawn(process.execPath, [binPath, ...args], {\n cwd,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n if (options.quiet) {\n const stdoutChunks: Buffer[] = [];\n const stderrChunks: Buffer[] = [];\n child.stdout?.on('data', (chunk: Buffer) => stdoutChunks.push(chunk));\n child.stderr?.on('data', (chunk: Buffer) => stderrChunks.push(chunk));\n child.on('error', reject);\n child.on('exit', (code) => {\n const exitCode = code ?? 0;\n if (exitCode !== 0) {\n process.stderr.write(\n maskSecrets(Buffer.concat(stderrChunks).toString('utf8')),\n );\n process.stderr.write(\n maskSecrets(Buffer.concat(stdoutChunks).toString('utf8')),\n );\n }\n resolve(exitCode);\n });\n return;\n }\n // Shared so the \"downloading runtime image…\" hint fires once even\n // though devcontainer-cli may log the manifest line on either stream.\n const pullHint: PullHintState = { hinted: false };\n const stdoutPipe = child.stdout\n ?.pipe(createSecretMaskStream())\n .pipe(createRuntimePullHintStream(pullHint));\n const stderrPipe = child.stderr\n ?.pipe(createSecretMaskStream())\n .pipe(createRuntimePullHintStream(pullHint));\n // Live terminal echo — suppressed when the apply runs in\n // interactive mode (`silent: true`) and the spinner owns the\n // screen. Verbose / non-TTY apply paths leave silent off so the\n // raw stream still surfaces.\n if (!options.silent) {\n stdoutPipe?.pipe(process.stdout);\n stderrPipe?.pipe(process.stderr);\n }\n // Tee both masked streams into the apply log sink. `end: false`\n // so the caller (apply/index.ts) controls when the log file\n // closes — stdout and stderr both feed it, and the file should\n // outlive whichever ends first.\n if (options.logSink) {\n stdoutPipe?.pipe(options.logSink, { end: false });\n stderrPipe?.pipe(options.logSink, { end: false });\n }\n if (options.progressSink) {\n stdoutPipe?.pipe(options.progressSink, { end: false });\n stderrPipe?.pipe(options.progressSink, { end: false });\n }\n child.on('error', reject);\n child.on('exit', (code) => resolve(code ?? 0));\n });\n};\n","import { Transform, type TransformCallback } from 'node:stream';\nimport { dim } from '../util/format.js';\n\n/**\n * devcontainer-cli logs this line when it probes the multi-arch GHCR\n * manifest for our runtime image before pulling it. It reads as an\n * \"Error\", but it's harmless — the buildx pull right after consumes\n * the image fine. The catch: on a cold cache Docker then downloads the\n * base for ~1–2 min with NO further output. Left unannotated, the\n * \"Error … No manifest found\" line is the LAST thing on screen before\n * that silence, so builders reasonably conclude the apply broke.\n */\nexport const RUNTIME_PULL_MARKER =\n 'No manifest found for ghcr.io/getmonoceros/monoceros-runtime';\n\n// User-visible string. ASCII-only on purpose: on Windows PS 5.1 conhost\n// (default codepage OEM/ANSI), multi-byte glyphs render as `?`. Same\n// reason install.ps1 uses ASCII glyphs for its Ok/Warn/Fail markers.\nexport const RUNTIME_PULL_HINT =\n 'Downloading the Monoceros runtime image now -- expected on first apply, ' +\n 'takes ~1-2 min (Docker pulls the multi-arch base with no progress ' +\n 'output). The \"No manifest found\" line above is harmless. Please wait...';\n\nexport interface PullHintState {\n hinted: boolean;\n}\n\n/**\n * Line-oriented transform that passes devcontainer-cli output through\n * untouched and, the first time it sees {@link RUNTIME_PULL_MARKER},\n * appends {@link RUNTIME_PULL_HINT} on its own line right after.\n *\n * `state` is shared across the stdout and stderr instances so the hint\n * fires exactly once regardless of which stream devcontainer-cli logged\n * the marker on. Sits after the secret-mask stream in the pipe chain.\n */\nexport function createRuntimePullHintStream(state: PullHintState): Transform {\n let buffer = '';\n const appendHintIfMarker = (block: string): string => {\n if (state.hinted || !block.includes(RUNTIME_PULL_MARKER)) return block;\n state.hinted = true;\n return `${block}${dim(`(i) ${RUNTIME_PULL_HINT}`)}\\n`;\n };\n return new Transform({\n decodeStrings: true,\n transform(chunk: Buffer | string, _enc, cb: TransformCallback): void {\n const text = typeof chunk === 'string' ? chunk : chunk.toString('utf8');\n buffer += text;\n const lastNewline = buffer.lastIndexOf('\\n');\n if (lastNewline === -1) {\n cb(null);\n return;\n }\n const flushable = buffer.slice(0, lastNewline + 1);\n buffer = buffer.slice(lastNewline + 1);\n cb(null, appendHintIfMarker(flushable));\n },\n flush(cb: TransformCallback): void {\n if (buffer.length === 0) {\n cb(null);\n return;\n }\n const tail = buffer;\n buffer = '';\n cb(null, appendHintIfMarker(tail));\n },\n });\n}\n","import { spawn } from 'node:child_process';\nimport { cyan } from '../util/format.js';\n\n/**\n * Whether the host's docker daemon runs as root (rootful) or under a\n * user namespace (rootless). The mode decides whether bind-mounts\n * need the `idmap` option.\n *\n * Why this matters: in rootless Docker the host user's UID is mapped\n * to container UID 0 (root), and the container's non-root user\n * (uid 1000 inside) lives at a shifted host UID (uid 65536+1000+ via\n * /etc/subuid). Without `idmap`, files written by either side end up\n * with the wrong ownership on the other — host can't read what the\n * container created, container's node user can't write into what the\n * host pre-created (which is what bit M5 testing on Ubuntu rootless).\n *\n * The Linux kernel supports `idmap` as a bind-mount option since 5.12;\n * Docker exposes it since 25.x. Ubuntu 24.04 and other modern distros\n * are well past both. Older kernels (RHEL 8 with 4.18) would fail the\n * mount with an \"unsupported\" error — accepted trade-off, the error\n * surfaces clearly.\n *\n * On macOS / Windows Docker Desktop, idmap is a no-op at best and a\n * mount-error at worst because those platforms use their own\n * file-sharing layer (VirtioFS / WSL2 + Plan9) instead of native\n * Linux bind mounts. We MUST only emit idmap when the daemon is\n * actually rootless on Linux — otherwise we'd break the working\n * Mac/Windows cases.\n */\nexport type DockerMode = 'rootful' | 'rootless';\n\n/**\n * Spawn signature for `docker info`. Returns stdout + exit code.\n * Injected by tests.\n */\nexport type DockerInfoSpawn = () => Promise<{\n stdout: string;\n exitCode: number;\n}>;\n\nconst realDockerInfo: DockerInfoSpawn = () => {\n return new Promise((resolve, reject) => {\n // `--format '{{json .SecurityOptions}}'` returns a JSON array like\n // `[\"name=seccomp,profile=builtin\",\"name=rootless\"]` on rootless,\n // or `[\"name=seccomp,profile=builtin\"]` on rootful. Cheaper to\n // parse than the full human-readable `docker info` output.\n const child = spawn(\n 'docker',\n ['info', '--format', '{{json .SecurityOptions}}'],\n {\n stdio: ['ignore', 'pipe', 'inherit'],\n },\n );\n let stdout = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.on('error', reject);\n child.on('exit', (code) => resolve({ stdout, exitCode: code ?? 0 }));\n });\n};\n\n/**\n * Probe the host docker daemon and return its mode. Defaults to\n * `'rootful'` whenever we can't reliably determine otherwise — the\n * downstream `docker run` would surface a clearer error if the\n * daemon is unreachable, so we don't pre-emptively fail here.\n */\nexport async function detectDockerMode(\n options: { spawn?: DockerInfoSpawn } = {},\n): Promise<DockerMode> {\n const spawnFn = options.spawn ?? realDockerInfo;\n try {\n const result = await spawnFn();\n if (result.exitCode !== 0) return 'rootful';\n // Match both the bare `rootless` token and the modern\n // `name=rootless` form. Case-insensitive to be defensive against\n // future docker output tweaks.\n return /\\brootless\\b/i.test(result.stdout) ? 'rootless' : 'rootful';\n } catch {\n return 'rootful';\n }\n}\n\n/**\n * Builder-facing error rendered when we detect rootless Docker.\n * Kept user-friendly on purpose — no \"UID shift\" / \"subuid\" jargon.\n * Frames the consequence (\"files you create in the container can't\n * be edited from your host\") rather than the cause, and gives the\n * exact switch-to-rootful command block. install.sh duplicates this\n * text in bash (deliberately — we can't share strings across\n * runtimes without ceremony).\n */\nexport function formatRootlessNotSupportedError(): string {\n return [\n `Monoceros requires Docker in \"rootful\" mode.`,\n ``,\n `You're running Docker in \"rootless\" mode right now. That setup`,\n `runs the daemon without root privileges — sounds safer, but it`,\n `remaps user IDs between your host and the container in a way`,\n `that prevents the container from writing into the directories`,\n `Monoceros mounts into it. Cloning your repos, running`,\n `\\`npm install\\`, building — all fail with permission errors at`,\n `the first attempt.`,\n ``,\n `To fix, switch back to standard rootful Docker:`,\n ``,\n cyan(\n ` systemctl --user stop docker.service docker.socket 2>/dev/null || true`,\n ),\n cyan(` dockerd-rootless-setuptool.sh uninstall`),\n cyan(` rootlesskit rm -rf ~/.local/share/docker`),\n cyan(` unset DOCKER_HOST DOCKER_CONTEXT`),\n cyan(` sudo systemctl enable --now docker`),\n cyan(` sudo usermod -aG docker $USER`),\n ``,\n `If you added DOCKER_HOST or DOCKER_CONTEXT to ~/.bashrc /`,\n `~/.profile (the rootless setup may have suggested it), remove`,\n `those lines too — the 'unset' above only affects your current`,\n `shell. Otherwise new terminals keep pointing at the rootless`,\n `socket and Monoceros's auto-recovery has nothing to fall back to.`,\n ``,\n `Then re-run.`,\n ].join('\\n');\n}\n","import { spawn } from 'node:child_process';\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { consola } from 'consola';\n\n/**\n * Spawn signature for `git config --global --get <key>`: takes the\n * key, returns stdout (trimmed) and exit code. Exit code 1 with empty\n * stdout means \"no value set\" — that's how git signals an unset key.\n * Injected by tests.\n */\nexport type IdentitySpawn = (\n key: string,\n) => Promise<{ value: string; exitCode: number }>;\n\n/**\n * Async prompt for a single identity key. Used as a fallback when the\n * host has no `--global` identity and `.monoceros/gitconfig` has no\n * persisted value from an earlier run. Returns the entered value or\n * `undefined` if the builder skips.\n */\nexport type IdentityPrompt = (\n key: 'user.name' | 'user.email',\n) => Promise<string | undefined>;\n\n/**\n * Persistence target the builder chose for a freshly-prompted or\n * previously-persisted identity. `'g'` writes to\n * `~/.monoceros/monoceros-config.yml` (global default for every\n * container), `'c'` writes to the container yml's `git.user`, `'b'`\n * does both, `'n'` skips persistence (keep using whatever transient\n * source we have — typically `.monoceros/gitconfig` from a prior\n * apply). The caller (apply / init) does the actual yml writes —\n * collectGitIdentity just surfaces what the builder picked so the\n * caller can act on it.\n */\nexport type IdentityScope = 'g' | 'c' | 'b' | 'n';\n\nexport type IdentityScopePrompt = (\n ctx: IdentityScopePromptContext,\n) => Promise<IdentityScope | undefined>;\n\n/**\n * Context passed to the scope prompt so the implementation can show\n * the builder what's going on — `'prompt'` after a fresh\n * name/email entry vs. `'persisted'` after recovering values from\n * `.monoceros/gitconfig`. The default consola prompt renders the\n * actual name/email so the builder sees what they'd be persisting.\n */\nexport interface IdentityScopePromptContext {\n reason: 'prompt' | 'persisted';\n name: string;\n email: string;\n}\n\nconst realGitConfigGet: IdentitySpawn = (key) => {\n return new Promise((resolve, reject) => {\n const child = spawn('git', ['config', '--global', '--get', key], {\n stdio: ['ignore', 'pipe', 'inherit'],\n });\n let stdout = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString();\n });\n child.on('error', reject);\n child.on('exit', (code) =>\n resolve({ value: stdout.trim(), exitCode: code ?? 0 }),\n );\n });\n};\n\nconst realIdentityPrompt: IdentityPrompt = async (key) => {\n // Non-interactive (CI, scripts): never hang waiting for input. The\n // identity stays unset; builder fixes it later by setting host\n // `git config --global` or editing `.monoceros/gitconfig` directly.\n if (!process.stdin.isTTY || !process.stdout.isTTY) {\n return undefined;\n }\n const label =\n key === 'user.name'\n ? 'Git user.name for this dev container (full name)'\n : 'Git user.email for this dev container';\n const value = await consola.prompt(`${label}:`, { type: 'text' });\n if (typeof value !== 'string') return undefined;\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n};\n\nconst realScopePrompt: IdentityScopePrompt = async (ctx) => {\n if (!process.stdin.isTTY || !process.stdout.isTTY) {\n // Non-interactive: default to `g` (global) on a fresh prompt\n // entry (the values are new, we want them remembered); default\n // to `n` on the persisted-recovery path (the values were\n // already in .monoceros/gitconfig and a script that hits this\n // path probably doesn't want a silent rewrite of monoceros-config).\n return ctx.reason === 'prompt' ? 'g' : 'n';\n }\n const heading =\n ctx.reason === 'persisted'\n ? `Found identity in .monoceros/gitconfig: ${ctx.name} <${ctx.email}>. Promote where?`\n : 'Save this identity where?';\n const choice = await consola.prompt(heading, {\n type: 'select',\n options: [\n {\n label: 'Globally — every container uses it as default',\n value: 'g',\n },\n {\n label: 'In this container only',\n value: 'c',\n },\n {\n label: 'Both — global default plus container-level entry',\n value: 'b',\n },\n {\n label: 'Keep as-is — do not write to monoceros-config or yml',\n value: 'n',\n },\n ],\n initial: 'g',\n });\n if (choice === 'g' || choice === 'c' || choice === 'b' || choice === 'n') {\n return choice;\n }\n return undefined;\n};\n\nexport interface CollectIdentityOptions {\n spawn?: IdentitySpawn;\n /**\n * Fallback prompt when the host has no `--global` identity and\n * `.monoceros/gitconfig` has no persisted value either. Tests inject\n * a canned answer; production uses an interactive `consola.prompt`\n * that auto-skips in non-interactive contexts.\n */\n prompt?: IdentityPrompt;\n /**\n * Asked AFTER an interactive identity prompt succeeded: where to\n * persist (global monoceros-config, container yml, both). Result\n * lands in `CollectIdentityResult.promptedScope` for the caller to\n * act on (apply / init handle the actual yml writes).\n */\n scopePrompt?: IdentityScopePrompt;\n /**\n * Per-container override from the container's yml `git.user`. Wins\n * over everything else (host global, workbench-wide defaults,\n * persisted state, interactive prompt).\n */\n containerOverride?: { name?: string; email?: string };\n /**\n * Workbench-wide defaults from `<MONOCEROS_HOME>/monoceros-config.yml`\n * `defaults.git.user`. Wins over host global git config (the\n * monoceros-config.yml is an explicit builder choice for Monoceros\n * containers; host global is the catch-all default), loses to the\n * per-container override.\n */\n defaults?: { name?: string; email?: string };\n logger?: { info: (msg: string) => void; warn: (msg: string) => void };\n}\n\nexport interface CollectIdentityResult {\n name?: string;\n email?: string;\n gitconfigPath: string;\n /**\n * Set ONLY when the identity should be persisted somewhere new —\n * the builder picked one of the persistence scopes (`g`/`c`/`b`)\n * either after a fresh prompt or after we offered to promote\n * recovered persisted values to monoceros-config. The caller uses\n * this to decide which yml(s) to write.\n *\n * `name` / `email` carry the values to persist so the caller\n * doesn't have to re-fish them out of the result fields above.\n * `'n'` (skip) is filtered out before this surfaces — the field\n * stays `undefined` in that case.\n */\n prompted?: {\n name: string;\n email: string;\n scope: 'g' | 'c' | 'b';\n };\n}\n\n/**\n * Extract `user.name` and `user.email` from the host's global git\n * config, write them as `<devContainerRoot>/.monoceros/gitconfig` for\n * the container to include. Done both at `monoceros create` time (so\n * the first `start` has identity) and at every `monoceros apply` (so\n * host changes propagate in).\n *\n * Always writes the file, even when host has nothing set — keeps the\n * include.path target valid (git silently ignores missing files, but\n * present-but-empty is more deterministic).\n *\n * Returns the captured values; the caller can use them for logging.\n * Missing values surface as `undefined`, plus a warn log line.\n */\n/**\n * Resolve an identity by walking the precedence chain (override →\n * defaults → host → persisted → prompt). Pure as far as Monoceros\n * state goes: doesn't write the `.monoceros/gitconfig` file —\n * `collectGitIdentity` is the wrapper that does.\n *\n * Used from `init` when a `--with-repo` flag means the builder needs\n * an identity before any container exists yet (and so before\n * `.monoceros/gitconfig` has a target path). Persistence to\n * monoceros-config or container yml is the caller's job either way.\n */\nexport async function resolveIdentityWithPrompt(\n options: CollectIdentityOptions & {\n persistedValues?: { name?: string; email?: string };\n } = {},\n): Promise<{\n name?: string;\n email?: string;\n prompted?: { name: string; email: string; scope: 'g' | 'c' | 'b' };\n}> {\n const spawnFn = options.spawn ?? realGitConfigGet;\n const promptFn = options.prompt ?? realIdentityPrompt;\n const scopePromptFn = options.scopePrompt ?? realScopePrompt;\n const logger = options.logger ?? { info: () => {}, warn: () => {} };\n const persisted = options.persistedValues ?? {};\n\n const name = await resolveKey('user.name', {\n override: options.containerOverride?.name,\n defaultValue: options.defaults?.name,\n spawnFn,\n persistedValue: persisted.name,\n promptFn,\n logger,\n });\n const email = await resolveKey('user.email', {\n override: options.containerOverride?.email,\n defaultValue: options.defaults?.email,\n spawnFn,\n persistedValue: persisted.email,\n promptFn,\n logger,\n });\n\n // Scope-prompt triggers when both keys came from the same\n // \"promotable\" source AND the identity hasn't already been\n // canonicalised in the yml ladder (container override / global\n // defaults).\n //\n // - `prompt`: fresh entry → ask where to persist (default `g`)\n // - `persisted`: recovered from .monoceros/gitconfig of a prior\n // apply → ask whether to promote to monoceros-config now that\n // the global defaults are empty (default `n` on non-TTY)\n //\n // `host` source is treated as \"the builder's machine-wide default,\n // not ours to promote\" and never triggers the prompt — host\n // changes can drift while Monoceros sleeps.\n const alreadyCanonical =\n !!options.containerOverride?.name ||\n !!options.containerOverride?.email ||\n !!options.defaults?.name ||\n !!options.defaults?.email;\n const promptableSources: ReadonlyArray<IdentitySource> = [\n 'prompt',\n 'persisted',\n ];\n const bothPromotable =\n name?.source !== undefined &&\n email?.source !== undefined &&\n promptableSources.includes(name.source) &&\n promptableSources.includes(email.source) &&\n name.source === email.source;\n\n let promptedScope: IdentityScope | undefined;\n if (!alreadyCanonical && bothPromotable && name?.value && email?.value) {\n promptedScope = await scopePromptFn({\n reason: name.source as 'prompt' | 'persisted',\n name: name.value,\n email: email.value,\n });\n }\n\n return {\n ...(name?.value !== undefined ? { name: name.value } : {}),\n ...(email?.value !== undefined ? { email: email.value } : {}),\n // Only surface `prompted` when the scope is a persistence target\n // (`g`/`c`/`b`). `'n'` means \"do nothing\" — no point passing it\n // to the caller as a \"go persist\" signal.\n ...(promptedScope && promptedScope !== 'n' && name?.value && email?.value\n ? {\n prompted: {\n name: name.value,\n email: email.value,\n scope: promptedScope,\n },\n }\n : {}),\n };\n}\n\nexport async function collectGitIdentity(\n devContainerRoot: string,\n options: CollectIdentityOptions = {},\n): Promise<CollectIdentityResult> {\n const gitconfigDir = path.join(devContainerRoot, '.monoceros');\n const gitconfigPath = path.join(gitconfigDir, 'gitconfig');\n const logger = options.logger ?? { info: () => {}, warn: () => {} };\n\n const existing = await readExistingGitconfig(gitconfigPath);\n\n const resolved = await resolveIdentityWithPrompt({\n ...options,\n persistedValues: existing,\n logger,\n });\n\n const lines: string[] = ['[user]'];\n if (resolved.name !== undefined) lines.push(`\\tname = ${resolved.name}`);\n if (resolved.email !== undefined) lines.push(`\\temail = ${resolved.email}`);\n\n await fs.mkdir(gitconfigDir, { recursive: true });\n await fs.writeFile(gitconfigPath, lines.join('\\n') + '\\n');\n\n return {\n ...(resolved.name !== undefined ? { name: resolved.name } : {}),\n ...(resolved.email !== undefined ? { email: resolved.email } : {}),\n gitconfigPath,\n ...(resolved.prompted ? { prompted: resolved.prompted } : {}),\n };\n}\n\ninterface ResolveKeyOpts {\n override?: string;\n defaultValue?: string;\n spawnFn: IdentitySpawn;\n persistedValue?: string;\n promptFn: IdentityPrompt;\n logger: { warn: (msg: string) => void };\n}\n\ntype IdentitySource =\n | 'container'\n | 'defaults'\n | 'host'\n | 'persisted'\n | 'prompt';\n\ninterface ResolvedKey {\n value: string;\n source: IdentitySource;\n}\n\nasync function resolveKey(\n key: 'user.name' | 'user.email',\n opts: ResolveKeyOpts,\n): Promise<ResolvedKey | undefined> {\n if (opts.override !== undefined && opts.override.length > 0) {\n return { value: opts.override, source: 'container' };\n }\n if (opts.defaultValue !== undefined && opts.defaultValue.length > 0) {\n return { value: opts.defaultValue, source: 'defaults' };\n }\n const hostValue = await readKeyFromHost(opts.spawnFn, key, opts.logger);\n if (hostValue !== undefined) return { value: hostValue, source: 'host' };\n if (opts.persistedValue !== undefined && opts.persistedValue.length > 0) {\n return { value: opts.persistedValue, source: 'persisted' };\n }\n const prompted = await opts.promptFn(key);\n if (prompted !== undefined) return { value: prompted, source: 'prompt' };\n opts.logger.warn(\n `No ${key} resolvable (yml override, monoceros-config.yml defaults, host \\`git config --global\\`, persisted .monoceros/gitconfig, prompt). Container git will have no ${key} until set explicitly.`,\n );\n return undefined;\n}\n\nasync function readKeyFromHost(\n spawnFn: IdentitySpawn,\n key: string,\n logger: { warn: (msg: string) => void },\n): Promise<string | undefined> {\n try {\n const result = await spawnFn(key);\n if (result.exitCode === 0 && result.value.length > 0) {\n return result.value;\n }\n return undefined;\n } catch (err) {\n logger.warn(\n `Host git not runnable (${err instanceof Error ? err.message : String(err)}); identity not captured.`,\n );\n return undefined;\n }\n}\n\nasync function readExistingGitconfig(\n filePath: string,\n): Promise<{ name?: string; email?: string }> {\n try {\n const content = await fs.readFile(filePath, 'utf8');\n const result: { name?: string; email?: string } = {};\n const nameMatch = /^\\s*name\\s*=\\s*(.+?)\\s*$/m.exec(content);\n const emailMatch = /^\\s*email\\s*=\\s*(.+?)\\s*$/m.exec(content);\n if (nameMatch?.[1]) result.name = nameMatch[1];\n if (emailMatch?.[1]) result.email = emailMatch[1];\n return result;\n } catch {\n return {};\n }\n}\n","// Single source of truth for the CLI version: `packages/cli/package.json`.\n//\n// At build time tsup (`tsup.config.ts`) reads `package.json.version` and\n// substitutes the `__CLI_VERSION__` placeholder below. So bumping the\n// version means editing exactly one file — package.json — and rebuilding.\n//\n// In dev (vitest, tsc) the placeholder isn't replaced; the fallback\n// `'dev'` kicks in. Tests don't depend on the exact version string.\n\ndeclare const __CLI_VERSION__: string;\n\nexport const CLI_VERSION =\n typeof __CLI_VERSION__ === 'string' ? __CLI_VERSION__ : 'dev';\n","import { consola } from 'consola';\n\n// Shared exit-code dispatcher: runs the orchestrator, propagates its\n// exit code, and turns thrown errors into a clean console message + exit 1.\nexport async function dispatch(runner: () => Promise<number>): Promise<never> {\n try {\n const exitCode = await runner();\n process.exit(exitCode);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n}\n","import { defineCommand } from 'citty';\n\n/**\n * `monoceros completion <shell>` — prints a shell-completion script\n * for bash, zsh or PowerShell to stdout. The user pipes the output\n * into a file their shell sources at startup.\n *\n * Architecture: the printed script is a THIN wrapper. The actual\n * completion logic lives in the CLI itself behind\n * `monoceros __complete --line \"<buffer>\" --point <N>`, which reads\n * the cursor's view of the line and returns one candidate per line\n * on stdout. The shell script's only job is:\n *\n * 1. Capture the current line + cursor position from the shell's\n * completion variables (COMP_LINE/COMP_POINT, BUFFER/CURSOR,\n * $commandAst/$cursorPosition).\n * 2. Pipe them to `monoceros __complete`.\n * 3. Hand the resulting lines back to the shell's completion\n * mechanism.\n *\n * That keeps the SoT in citty's command definitions + the spec table\n * in `completion/resolve.ts`. Adding a new command or flag means\n * extending the resolver, not editing per-shell scripts.\n */\n\nconst SHELLS = ['bash', 'zsh', 'pwsh'] as const;\ntype Shell = (typeof SHELLS)[number];\n\nexport function renderCompletionScript(shell: Shell): string {\n if (shell === 'bash') {\n return [\n '# bash completion for monoceros',\n '# install: source this file from .bashrc, e.g.',\n '# monoceros completion bash > ~/.bash_completion.d/monoceros',\n '# echo \"source ~/.bash_completion.d/monoceros\" >> ~/.bashrc',\n '#',\n '# The work is done by `monoceros __complete --line --point`; this',\n '# shell wrapper only forwards the cursor view.',\n '',\n '_monoceros() {',\n \" local IFS=$'\\\\n'\",\n ' local candidates',\n ' candidates=$(monoceros __complete --line \"$COMP_LINE\" --point \"$COMP_POINT\" 2>/dev/null)',\n ' local cur=\"${COMP_WORDS[COMP_CWORD]}\"',\n ' COMPREPLY=( $(compgen -W \"$candidates\" -- \"$cur\") )',\n ' # Suppress the trailing space when bash narrowed the candidate',\n ' # set to a single token that ends with `=` — those are value-',\n ' # flags (`--with-features=`, `--with-ports=`, …) where the user types the',\n ' # value immediately after.',\n ' if [[ ${#COMPREPLY[@]} -eq 1 && \"${COMPREPLY[0]}\" == *= ]]; then',\n ' compopt -o nospace',\n ' fi',\n '}',\n 'complete -F _monoceros monoceros',\n '',\n ].join('\\n');\n }\n\n if (shell === 'pwsh') {\n return [\n '# PowerShell completion for monoceros',\n '# install: dot-source this file from your $PROFILE, e.g.',\n '# monoceros completion pwsh > $HOME/.config/monoceros/completion.ps1',\n \"# Add-Content $PROFILE '. $HOME/.config/monoceros/completion.ps1'\",\n '#',\n '# The work is done by `monoceros __complete --line --point`; this',\n '# shell wrapper only forwards the cursor view.',\n '',\n 'Register-ArgumentCompleter -Native -CommandName monoceros -ScriptBlock {',\n ' param($wordToComplete, $commandAst, $cursorPosition)',\n ' $line = $commandAst.Extent.Text',\n ' $point = $cursorPosition - $commandAst.Extent.StartOffset',\n ' if ($point -lt 0) { $point = 0 }',\n ' $raw = & monoceros __complete --line $line --point $point 2>$null',\n ' if (-not $raw) { return }',\n ' $raw -split \"`n\" |',\n ' Where-Object { $_.Length -gt 0 -and $_ -like \"$wordToComplete*\" } |',\n ' ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, \"ParameterValue\", $_) }',\n '}',\n '',\n ].join('\\n');\n }\n\n // zsh\n return [\n '#compdef monoceros',\n '# zsh completion for monoceros',\n '# install: drop this file somewhere on your $fpath as `_monoceros`,',\n '# then start a new shell (or run `compinit`). Example:',\n '# monoceros completion zsh > \"${fpath[1]}/_monoceros\"',\n '#',\n '# The work is done by `monoceros __complete --line --point`; this',\n '# shell wrapper only forwards the cursor view.',\n '',\n '_monoceros() {',\n ' local line=\"$BUFFER\"',\n ' local point=\"$CURSOR\"',\n ' local -a candidates with_eq without_eq',\n ' candidates=(\"${(@f)$(monoceros __complete --line \"$line\" --point \"$point\" 2>/dev/null)}\")',\n ' candidates=(\"${(@)candidates:#}\")',\n ' # Split candidates into \"ends with `=`\" (value-flags — no suffix',\n ' # space wanted because the user types the value immediately) and',\n ' # \"everything else\" (positional values, boolean flags — default',\n ' # space behaviour).',\n ' for cand in \"${candidates[@]}\"; do',\n ' if [[ \"$cand\" == *= ]]; then',\n ' with_eq+=(\"$cand\")',\n ' else',\n ' without_eq+=(\"$cand\")',\n ' fi',\n ' done',\n ' (( ${#with_eq[@]} )) && compadd -S \\'\\' -- \"${with_eq[@]}\"',\n ' (( ${#without_eq[@]} )) && compadd -- \"${without_eq[@]}\"',\n '}',\n '',\n '_monoceros \"$@\"',\n '',\n ].join('\\n');\n}\n\nexport const completionCommand = defineCommand({\n meta: {\n name: 'completion',\n group: 'tooling',\n // Hidden from `monoceros --help`: the install scripts wire up\n // completion automatically; manual setup is documented in\n // docs/commands/completion.md. Still runnable directly.\n hidden: true,\n description:\n 'Print a shell completion script for bash, zsh or PowerShell to stdout. Pipe the output into a file your shell loads at startup. The install scripts (install.sh / install.ps1) call this automatically.',\n },\n args: {\n shell: {\n type: 'positional',\n description: \"Target shell. One of: 'bash', 'zsh', 'pwsh'.\",\n required: true,\n },\n },\n run({ args }) {\n const shell = args.shell as string;\n if (shell !== 'bash' && shell !== 'zsh' && shell !== 'pwsh') {\n process.stderr.write(\n `Unknown shell: ${JSON.stringify(shell)}. Supported: ${SHELLS.join(', ')}.\\n`,\n );\n process.exit(2);\n }\n process.stdout.write(renderCompletionScript(shell));\n },\n});\n\nexport const COMPLETION_SHELLS = SHELLS;\n","import { defineCommand } from 'citty';\nimport { resolveCompletions } from '../completion/resolve.js';\n\n/**\n * Internal CLI entrypoint shell-completion scripts call. NOT part of\n * the user-facing surface — the leading `__` keeps it out of typical\n * help listings. Generated bash / zsh / pwsh wrappers (see\n * `commands/completion.ts`) call this with the current command-line\n * and cursor position, and emit each line of stdout as a candidate\n * completion.\n *\n * Contract (must stay stable; the shipped wrappers depend on it):\n *\n * monoceros __complete --line \"<full line>\" --point <N>\n *\n * - Exit 0 always (errors → empty output; completion is a comfort\n * feature, never a fatal one).\n * - Output: one candidate per line, no decorations, no escapes.\n * - Comma-separated value flags (`--with-features=github,claude`) get\n * returned with the leading prefix already attached so the shell inserts\n * the full token in place — see resolveValues in resolve.ts.\n */\n\nexport const __completeCommand = defineCommand({\n meta: {\n name: '__complete',\n group: 'internal',\n // Internal plumbing for the shell wrappers — never shown in help.\n hidden: true,\n description:\n 'Internal — shell completion engine. Used by the wrappers emitted by `monoceros completion <shell>`. Output one candidate completion per line.',\n },\n args: {\n line: {\n type: 'string',\n description:\n 'Full command line buffer up to (and possibly past) the cursor.',\n default: '',\n },\n point: {\n type: 'string',\n description:\n 'Byte offset of the cursor within --line. Default: end of line.',\n default: '',\n },\n },\n async run({ args }) {\n const line = String(args.line ?? '');\n const point =\n args.point && String(args.point).length > 0\n ? Number.parseInt(String(args.point), 10)\n : line.length;\n let candidates: string[];\n try {\n candidates = await resolveCompletions(\n line,\n Number.isFinite(point) ? point : line.length,\n );\n } catch {\n // Never fail the shell's completion request — silent empty\n // suggestion is the worst it should ever be.\n candidates = [];\n }\n if (candidates.length > 0) {\n process.stdout.write(candidates.join('\\n') + '\\n');\n }\n },\n});\n","import { existsSync, promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { monocerosHome } from '../config/paths.js';\nimport { loadComponentCatalog } from '../init/components.js';\nimport { loadFeatureManifestSummary } from '../init/manifest.js';\nimport { knownLanguages, knownServices } from '../create/catalog.js';\nimport { PROVIDER_VALUES, REGEX } from '../config/schema.js';\n\n/**\n * Shell-agnostic completion engine. Called by the `__complete`\n * internal CLI command and (in tests) directly via `resolveCompletions`.\n *\n * The caller hands us the full command-line buffer (`line`) plus the\n * cursor's byte offset within it (`point`). We tokenize the prefix,\n * figure out which positional / flag is being completed, and return\n * a list of candidate completions matching the current token fragment.\n *\n * Tokenization rules:\n * - Whitespace separates tokens.\n * - Double or single quotes group a run including whitespace; we\n * return the un-quoted content as the token (the shell glue\n * re-quotes when emitting).\n * - A `--key=value` form is one token; the equals sign isn't a\n * separator. Inside `--with-features=github,claude` we treat the part\n * after `=` as the current value-fragment for the `--with-features` flag.\n *\n * Per-command suggestions live in `COMMAND_SPECS`. Adding a new\n * command means adding (or extending) one entry there — the engine\n * dispatches automatically.\n *\n * Source of truth is citty's command definitions; the spec table is\n * a thin mirror keyed by command name. A test (`completion.test.ts`)\n * pins the spec keys against the live command list.\n */\n\n// ─── Public surface ───────────────────────────────────────────────\n\nexport interface ResolveOptions {\n /** Override of MONOCEROS_HOME (tests). */\n monocerosHome?: string;\n}\n\nexport async function resolveCompletions(\n line: string,\n point: number,\n opts: ResolveOptions = {},\n): Promise<string[]> {\n const { prev, current } = parseCompletionLine(line, point);\n const ctx: Ctx = { prev, current, opts };\n // prev[0] is the program name (usually \"monoceros\"); skip it.\n const head = prev[0];\n const afterProgram = head === undefined ? [] : prev.slice(1);\n // No subcommand typed yet → suggest the subcommand list.\n if (afterProgram.length === 0) {\n return filterPrefix(ALL_COMMANDS, current);\n }\n const command = afterProgram[0]!;\n const spec = COMMAND_SPECS[command];\n if (!spec) {\n // Unknown subcommand. We've already entered position 2+, but the\n // command isn't one we know — no useful suggestions.\n return [];\n }\n // Tokens that belong to the command's args (past `monoceros <cmd>`).\n const argTokens = afterProgram.slice(1);\n return dispatchCommand(spec, argTokens, ctx);\n}\n\n// ─── Tokenizer + cursor-context parser ────────────────────────────\n\ninterface CompletionContext {\n /** Tokens to the LEFT of the one being completed. */\n prev: string[];\n /** The token currently under the cursor (or `''` at a fresh word). */\n current: string;\n}\n\ninterface Ctx extends CompletionContext {\n opts: ResolveOptions;\n}\n\nexport function parseCompletionLine(\n line: string,\n point: number,\n): CompletionContext {\n const before = line.slice(0, Math.max(0, Math.min(point, line.length)));\n const tokens = tokenize(before);\n // If the cursor sits right after whitespace, the user is starting a\n // fresh token — current is empty, all preceding tokens are \"prev\".\n const lastChar = before.length > 0 ? before[before.length - 1]! : '';\n if (tokens.length === 0 || isShellWhitespace(lastChar)) {\n return { prev: tokens, current: '' };\n }\n return {\n prev: tokens.slice(0, -1),\n current: tokens[tokens.length - 1]!,\n };\n}\n\nfunction tokenize(text: string): string[] {\n const out: string[] = [];\n let i = 0;\n while (i < text.length) {\n while (i < text.length && isShellWhitespace(text[i]!)) i++;\n if (i >= text.length) break;\n let token = '';\n let quote: '\"' | \"'\" | null = null;\n while (i < text.length) {\n const ch = text[i]!;\n if (quote === null && isShellWhitespace(ch)) break;\n if (quote === null && (ch === '\"' || ch === \"'\")) {\n quote = ch;\n i++;\n continue;\n }\n if (quote !== null && ch === quote) {\n quote = null;\n i++;\n continue;\n }\n token += ch;\n i++;\n }\n out.push(token);\n }\n return out;\n}\n\nfunction isShellWhitespace(ch: string): boolean {\n return ch === ' ' || ch === '\\t';\n}\n\n// ─── Per-command dispatch ─────────────────────────────────────────\n\ntype ValueSource = (ctx: Ctx) => Promise<string[]> | string[];\n\ninterface FlagSpec {\n /** `boolean` = no value; `value` = `--flag <X>` or `--flag=<X>`. */\n type: 'boolean' | 'value';\n /** Short forms, e.g. `['-y']`. */\n aliases?: string[];\n /** Value suggestions for `value`-typed flags. Optional → freeform. */\n values?: ValueSource;\n}\n\ninterface CommandSpec {\n /**\n * Suggestion sources for positional args, indexed by position\n * (0 = arg right after the command name). Missing entries mean\n * the slot exists but has no completion source (e.g. `init`'s\n * fresh-name positional — we don't suggest existing container\n * names there, that would invite collisions).\n */\n positionals?: ValueSource[];\n /**\n * How many positionals the command expects. Defaults to\n * `positionals.length`. Set this explicitly when the command has\n * MORE positional slots than entries in `positionals` (= \"this\n * slot exists but has no suggestion source\"). Once the cursor sits\n * past `positionalCount`, completion falls back to flag names so\n * the builder discovers `--with-*` / `--yes` etc. via Tab.\n */\n positionalCount?: number;\n /** Flag table. Keys include the leading `--`. */\n flags?: Record<string, FlagSpec>;\n /**\n * Suggestion source for tokens after `--` (inner args). Used by\n * `add-feature -- key=value` and `add-apt-packages -- pkg pkg …`.\n */\n innerArgs?: ValueSource;\n}\n\nfunction dispatchCommand(\n spec: CommandSpec,\n argTokens: string[],\n ctx: Ctx,\n): Promise<string[]> | string[] {\n // Split argTokens at the `--` separator: tokens after it are inner\n // args (consumed by `innerArgs`); tokens before it are positionals\n // and flags consumed by `resolvePreDash`. We only need preDash —\n // the post-dash tokens are read directly off ctx.prev by per-command\n // innerArgs sources that care (see listFeatureOptionInnerArgs).\n const dashDashIdx = argTokens.indexOf('--');\n const preDash = dashDashIdx < 0 ? argTokens : argTokens.slice(0, dashDashIdx);\n const inPostDash = dashDashIdx >= 0;\n\n if (inPostDash && spec.innerArgs) {\n return resolveValues(spec.innerArgs, ctx, ctx.current);\n }\n\n // Pre-dash: figure out whether `current` is a value for a flag we\n // started typing, or a fresh positional / flag-name slot.\n return resolvePreDash(spec, preDash, ctx);\n}\n\nasync function resolvePreDash(\n spec: CommandSpec,\n preDash: string[],\n ctx: Ctx,\n): Promise<string[]> {\n const current = ctx.current;\n\n // Case A: current looks like `--flag=…` → suggest values for that flag.\n if (current.startsWith('--') && current.includes('=')) {\n const eqIdx = current.indexOf('=');\n const flagName = current.slice(0, eqIdx);\n const valueFragment = current.slice(eqIdx + 1);\n const flag = spec.flags?.[flagName];\n if (!flag || flag.type !== 'value' || !flag.values) return [];\n const completions = await resolveValues(flag.values, ctx, valueFragment);\n // Re-emit with the `--flag=` prefix attached so the shell inserts\n // the full token in place.\n return completions.map((v) => `${flagName}=${v}`);\n }\n\n // Case B: current starts with `-` (incomplete flag name).\n if (current.startsWith('-')) {\n return listFlagNames(spec.flags ?? {}, current);\n }\n\n // Case C: previous token was `--flag` (no `=`) expecting a value.\n const lastPrev = preDash[preDash.length - 1];\n if (lastPrev && lastPrev.startsWith('--') && !lastPrev.includes('=')) {\n const flag = spec.flags?.[lastPrev];\n if (flag && flag.type === 'value' && flag.values) {\n return resolveValues(flag.values, ctx, current);\n }\n }\n\n // Case D: positional. Count how many positionals have been completed\n // already — that's any preDash token that isn't itself a flag or a\n // flag-value pair we passed through.\n const positionalIdx = countCompletedPositionals(preDash, spec.flags ?? {});\n const positionals = spec.positionals ?? [];\n const expectedPositionalCount = spec.positionalCount ?? positionals.length;\n\n // Still inside a defined positional slot → use its source.\n if (positionalIdx < positionals.length) {\n const positional = positionals[positionalIdx];\n if (positional) return resolveValues(positional, ctx, current);\n }\n // Past all expected positionals → surface available flags so Tab\n // discovers them without the builder having to know they exist\n // (and without having to start with a `-`).\n if (positionalIdx >= expectedPositionalCount) {\n return listFlagNames(spec.flags ?? {}, current);\n }\n // Slot is expected but has no completion source (e.g. `init`'s\n // fresh-name positional, `restore`'s backup-path). Don't suggest\n // anything — let the shell fall through to its built-in handling.\n return [];\n}\n\n/**\n * Flag-name suggestion list filtered against the partial token under\n * the cursor. Includes long names (`--with-features`) and short aliases (`-y`).\n *\n * Value-flags are emitted WITH a trailing `=` (`--with-features=`) so the shell\n * wrappers can use `compopt -o nospace` (bash) / `compadd -S ''` (zsh)\n * to suppress the auto-added trailing space — without that, picking\n * `--with-ports` via Tab and typing `=3000` afterwards produces the\n * broken `--with-ports =3000` (space between flag and value).\n *\n * Boolean flags (no value expected) stay as bare names so the shell's\n * normal trailing-space behaviour applies — after `--yes` you really\n * do want a space.\n */\nfunction listFlagNames(\n flags: Record<string, FlagSpec>,\n fragment: string,\n): string[] {\n const names: string[] = [];\n for (const [name, spec] of Object.entries(flags)) {\n names.push(spec.type === 'value' ? `${name}=` : name);\n for (const alias of spec.aliases ?? []) names.push(alias);\n }\n return filterPrefix(names, fragment);\n}\n\nfunction countCompletedPositionals(\n preDash: string[],\n flags: Record<string, FlagSpec>,\n): number {\n let count = 0;\n for (let i = 0; i < preDash.length; i++) {\n const t = preDash[i]!;\n if (t.startsWith('--') && t.includes('=')) continue; // `--flag=value` — a flag, not a positional\n if (t.startsWith('-')) {\n // Boolean flag → 1 token, value flag → 2 tokens (consume next).\n const flag = flags[t] ?? aliasFlag(flags, t);\n if (flag && flag.type === 'value' && i + 1 < preDash.length) {\n i++; // skip the value\n }\n continue;\n }\n count++;\n }\n return count;\n}\n\nfunction aliasFlag(\n flags: Record<string, FlagSpec>,\n token: string,\n): FlagSpec | undefined {\n for (const f of Object.values(flags)) {\n if (f.aliases?.includes(token)) return f;\n }\n return undefined;\n}\n\nasync function resolveValues(\n source: ValueSource,\n ctx: Ctx,\n fragment: string,\n): Promise<string[]> {\n const values = await source(ctx);\n // For comma-separated value flags (`--with-features=github,claude`) the\n // user may have typed `github,cl` — the fragment to filter against is the\n // part AFTER the last comma.\n const commaIdx = fragment.lastIndexOf(',');\n if (commaIdx < 0) {\n return filterPrefix(values, fragment);\n }\n const prefix = fragment.slice(0, commaIdx + 1);\n const tail = fragment.slice(commaIdx + 1);\n return filterPrefix(values, tail).map((v) => `${prefix}${v}`);\n}\n\nfunction filterPrefix(values: readonly string[], fragment: string): string[] {\n if (fragment.length === 0) return [...values];\n return values.filter((v) => v.startsWith(fragment));\n}\n\n// ─── Value sources ────────────────────────────────────────────────\n\nasync function listContainerNames(ctx: Ctx): Promise<string[]> {\n const home = ctx.opts.monocerosHome ?? monocerosHome();\n const dir = path.join(home, 'container-configs');\n if (!existsSync(dir)) return [];\n const entries = await fs.readdir(dir);\n return entries\n .filter((e) => e.endsWith('.yml'))\n .map((e) => e.slice(0, -'.yml'.length))\n .sort();\n}\n\nasync function listFeatureComponents(): Promise<string[]> {\n const catalog = await loadComponentCatalog();\n return [...catalog.values()]\n .filter((c) => c.file.category === 'feature')\n .map((c) => c.name)\n .sort();\n}\n\nfunction listLanguageNames(): string[] {\n return knownLanguages().sort();\n}\n\nfunction listServiceNames(): string[] {\n return knownServices().sort();\n}\n\nfunction listProviders(): string[] {\n return [...PROVIDER_VALUES];\n}\n\nfunction listShellNames(): string[] {\n return ['bash', 'zsh', 'pwsh'];\n}\n\n/**\n * Inner-arg completion for `monoceros add-feature <name> <feature> --\n * key=value …`. The `<feature>` token can be either a catalog short\n * name (`atlassian`, `atlassian/twg`) or a full OCI ref; in both cases\n * we resolve to the feature manifest and return the option keys.\n *\n * Behaviour with the current token:\n * - Token is empty or contains no `=` → suggest the option NAMES\n * (filtered against the partial token prefix).\n * - Token is `<key>=<fragment>` AND the key is a boolean → suggest\n * `<key>=true` / `<key>=false` matching `<fragment>`. For string\n * options we have no useful default suggestion list (it's\n * freeform credentials / URLs).\n * - Already-typed `key=value` pairs (before the current token) drop\n * the same `key=` from the suggestion list so the builder doesn't\n * get duplicates.\n */\nasync function listFeatureOptionInnerArgs(ctx: Ctx): Promise<string[]> {\n // Locate the feature token. ctx.prev[0] is the program name, [1] is\n // `add-feature`, [2] is the container name, [3] is the feature.\n // The user could have added flags like `--yes` before the feature,\n // but flag tokens always start with `-`. So the feature is the\n // SECOND non-flag positional after the command.\n const after = ctx.prev.slice(2); // drop \"monoceros\", \"add-feature\"\n let positionalCount = 0;\n let featureToken: string | undefined;\n for (let i = 0; i < after.length; i++) {\n const t = after[i]!;\n if (t === '--') break; // stop at the inner-args separator\n if (t.startsWith('-')) continue; // flag\n positionalCount++;\n if (positionalCount === 2) {\n featureToken = t;\n break;\n }\n }\n if (!featureToken) return [];\n const ref = await resolveFeatureRefForCompletion(featureToken);\n if (!ref) return [];\n const summary = loadFeatureManifestSummary(ref);\n if (!summary) return [];\n\n // Which keys has the builder already set in earlier inner-args?\n const dashDash = ctx.prev.indexOf('--');\n const innerSoFar = dashDash >= 0 ? ctx.prev.slice(dashDash + 1) : [];\n const usedKeys = new Set<string>();\n for (const t of innerSoFar) {\n const eq = t.indexOf('=');\n if (eq > 0) usedKeys.add(t.slice(0, eq));\n }\n\n // Current token: `key=value` or just `key`?\n const eqIdx = ctx.current.indexOf('=');\n if (eqIdx >= 0) {\n const key = ctx.current.slice(0, eqIdx);\n const valueFragment = ctx.current.slice(eqIdx + 1);\n const type = summary.optionTypes[key];\n if (type === 'boolean') {\n return ['true', 'false']\n .filter((v) => v.startsWith(valueFragment))\n .map((v) => `${key}=${v}`);\n }\n // String options: no useful suggestion list. Return [] so the\n // shell falls back to its own filename / nothing handling.\n return [];\n }\n\n // Plain key fragment — suggest the still-unused option keys WITH a\n // trailing `=` so the shell wrappers' nospace logic kicks in (same\n // shape as `--with-features=` etc. on the flag side). Without that, the\n // user gets `instance =foo` instead of `instance=foo` after Tab +\n // manual `=foo`.\n return summary.optionNames\n .filter((n) => !usedKeys.has(n))\n .map((n) => `${n}=`);\n}\n\n/**\n * Bridge between the short-name / full-ref formats the user types\n * for `add-feature <feature>` and the OCI ref the manifest loader\n * needs. A no-op for full OCI refs; a single catalog lookup for short\n * names. Returns `undefined` for unknown short-names (no completions).\n */\nasync function resolveFeatureRefForCompletion(\n token: string,\n): Promise<string | undefined> {\n if (REGEX.featureRef.test(token)) return token;\n const catalog = await loadComponentCatalog();\n const c = catalog.get(token);\n if (!c || c.file.category !== 'feature') return undefined;\n const f = c.file.contributes.features?.[0];\n return f?.ref;\n}\n\n// ─── Static command list (mirrors completion.ts) ──────────────────\n\nconst ALL_COMMANDS = [\n 'init',\n 'list-components',\n 'shell',\n 'run',\n 'logs',\n 'start',\n 'stop',\n 'status',\n 'apply',\n 'remove',\n 'restore',\n 'add-service',\n 'add-language',\n 'add-apt-packages',\n 'add-feature',\n 'add-from-url',\n 'add-repo',\n 'add-port',\n 'remove-service',\n 'remove-language',\n 'remove-apt-packages',\n 'remove-feature',\n 'remove-from-url',\n 'remove-repo',\n 'remove-port',\n 'port',\n 'tunnel',\n 'completion',\n] as const;\n\n// ─── Command specs ────────────────────────────────────────────────\n\nconst containerName: ValueSource = (ctx) => listContainerNames(ctx);\n\nconst COMMAND_SPECS: Record<string, CommandSpec> = {\n init: {\n // First positional is a FRESH name → no suggestion source, but\n // the slot exists. Once the cursor is past it (after the name +\n // space), `--with` / `--with-repo` / `--with-ports` surface as\n // flag suggestions.\n positionalCount: 1,\n flags: {\n '--with-languages': { type: 'value', values: () => listLanguageNames() },\n '--with-features': {\n type: 'value',\n values: () => listFeatureComponents(),\n },\n '--with-services': { type: 'value', values: () => listServiceNames() },\n '--with-apt-packages': { type: 'value' },\n '--with-repos': { type: 'value' },\n '--with-ports': { type: 'value' },\n },\n },\n apply: {\n positionals: [containerName],\n flags: { '--yes': { type: 'boolean', aliases: ['-y'] } },\n },\n remove: {\n positionals: [containerName],\n flags: {\n '--yes': { type: 'boolean', aliases: ['-y'] },\n '--no-backup': { type: 'boolean' },\n },\n },\n shell: { positionals: [containerName] },\n run: { positionals: [containerName] },\n logs: { positionals: [containerName] },\n start: { positionals: [containerName] },\n stop: { positionals: [containerName] },\n status: { positionals: [containerName] },\n 'add-language': {\n positionals: [containerName, () => listLanguageNames()],\n },\n 'add-service': {\n positionals: [containerName, () => listServiceNames()],\n },\n 'add-apt-packages': {\n positionals: [containerName],\n innerArgs: () => [], // freeform package names — no useful suggestion list\n },\n 'add-feature': {\n positionals: [containerName, () => listFeatureComponents()],\n flags: { '--yes': { type: 'boolean', aliases: ['-y'] } },\n innerArgs: (ctx) => listFeatureOptionInnerArgs(ctx),\n },\n 'add-from-url': { positionals: [containerName] },\n 'add-repo': {\n positionals: [containerName],\n flags: {\n '--path': { type: 'value' },\n '--git-name': { type: 'value' },\n '--git-email': { type: 'value' },\n '--provider': { type: 'value', values: () => listProviders() },\n '--yes': { type: 'boolean', aliases: ['-y'] },\n },\n },\n 'add-port': {\n positionals: [containerName],\n flags: {\n '--default': { type: 'boolean' },\n '--yes': { type: 'boolean', aliases: ['-y'] },\n },\n innerArgs: () => [],\n },\n 'remove-language': {\n positionals: [containerName, () => listLanguageNames()],\n },\n 'remove-service': {\n positionals: [containerName, () => listServiceNames()],\n },\n 'remove-apt-packages': {\n positionals: [containerName],\n innerArgs: () => [],\n },\n 'remove-feature': {\n positionals: [containerName, () => listFeatureComponents()],\n flags: { '--yes': { type: 'boolean', aliases: ['-y'] } },\n },\n 'remove-from-url': { positionals: [containerName] },\n 'remove-repo': { positionals: [containerName] },\n 'remove-port': {\n positionals: [containerName],\n flags: { '--yes': { type: 'boolean', aliases: ['-y'] } },\n innerArgs: () => [],\n },\n port: {\n positionals: [containerName],\n flags: { '--default': { type: 'boolean' } },\n innerArgs: () => [],\n },\n tunnel: {\n positionals: [containerName, () => listServiceNames()],\n flags: {\n '--local-port': { type: 'value' },\n '--local-address': { type: 'value' },\n },\n },\n completion: {\n positionals: [() => listShellNames()],\n },\n 'list-components': {},\n restore: {\n // First positional is a backup-path; no value suggestions today\n // (could plug filesystem completion later). Slot still exists so\n // Tab is silent inside it rather than offering flags prematurely.\n positionalCount: 1,\n },\n};\n\n/** Exposed for tests so the command list stays in sync with main.ts. */\nexport const COMPLETION_ALL_COMMANDS = ALL_COMMANDS;\nexport const COMPLETION_COMMAND_SPEC_KEYS = Object.keys(COMMAND_SPECS);\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runInit } from '../init/index.js';\n\nexport const initCommand = defineCommand({\n meta: {\n name: 'init',\n group: 'lifecycle',\n description:\n 'Create a fresh container-config yml at <MONOCEROS_HOME>/container-configs/<name>.yml. Without any --with-* flag, the file is a documented default with every component commented out. With --with-languages / --with-features / --with-services / --with-apt-packages, the named pieces are composed into an active, immediately-applyable yml. Then run `monoceros apply <name>`.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Config name. The yml lands at <MONOCEROS_HOME>/container-configs/<name>.yml and becomes the source-of-truth for `monoceros apply <name>`.',\n required: true,\n },\n 'with-languages': {\n type: 'string',\n description:\n 'Language runtimes to install, comma-separated or repeated, e.g. --with-languages=java,node. Optional :version (java:17). Curated catalog only — see `monoceros list-components`.',\n required: false,\n },\n 'with-features': {\n type: 'string',\n description:\n 'Features (AI tools, language CLIs, …), comma-separated or repeated. Catalog short name (claude, atlassian/twg) or a full OCI ref (ghcr.io/foo/bar:1).',\n required: false,\n },\n 'with-services': {\n type: 'string',\n description:\n 'Backing services, comma-separated or repeated. Curated name (postgres, mysql, redis) → full editable block; any other image (rustfs/rustfs:latest) → name + image + commented scaffold.',\n required: false,\n },\n 'with-apt-packages': {\n type: 'string',\n description:\n 'Debian/Ubuntu apt packages to install, comma-separated or repeated, e.g. --with-apt-packages=openssl,make. No curated list.',\n required: false,\n },\n 'with-repos': {\n type: 'string',\n description:\n 'Git URLs to clone into projects/ on first apply, comma-separated or repeated. Folder name derived from URL (foo.git → projects/foo/); use `monoceros add-repo --path=...` post-init for subfolder paths. Canonical hosts only (github.com / gitlab.com / bitbucket.org).',\n required: false,\n },\n 'with-ports': {\n type: 'string',\n description:\n 'Comma-separated list of container-internal ports to expose via Traefik, e.g. --with-ports=3000,5173,6006. First entry doubles as http://<name>.localhost (default route). Equivalent to `monoceros add-port` after init. Each must be an integer in 1–65535.',\n required: false,\n },\n },\n async run({ args, rawArgs }) {\n try {\n const languages = collectListFlag('--with-languages', rawArgs);\n const features = collectListFlag('--with-features', rawArgs);\n const services = collectListFlag('--with-services', rawArgs);\n const aptPackages = collectListFlag('--with-apt-packages', rawArgs);\n const repos = collectListFlag('--with-repos', rawArgs);\n const ports = collectWithPortsList(args['with-ports'], rawArgs);\n await runInit({\n name: args.name,\n ...(languages.length > 0 ? { languages } : {}),\n ...(features.length > 0 ? { features } : {}),\n ...(services.length > 0 ? { services } : {}),\n ...(aptPackages.length > 0 ? { aptPackages } : {}),\n ...(repos.length > 0 ? { withRepo: repos } : {}),\n ...(ports && ports.length > 0 ? { withPorts: ports } : {}),\n });\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n\n/**\n * Collect every value for a repeatable comma-list flag from rawArgs.\n * Handles all three shapes the shell can produce:\n * --flag=a,b → ['a','b']\n * --flag a b → ['a','b']\n * --flag=a, b, c → ['a','b','c'] (shell strips spaces, rest float)\n * citty only keeps the last occurrence of a repeated flag, so we walk\n * rawArgs directly. Returns trimmed, comma-split, non-empty pieces in\n * order of appearance.\n */\nexport function collectListFlag(flag: string, rawArgs: string[]): string[] {\n const eq = `${flag}=`;\n const pieces: string[] = [];\n for (let i = 0; i < rawArgs.length; i += 1) {\n const t = rawArgs[i]!;\n let scanStart = -1;\n if (t === flag) {\n scanStart = i + 1;\n } else if (t.startsWith(eq)) {\n pieces.push(t.slice(eq.length));\n scanStart = i + 1;\n }\n if (scanStart < 0) continue;\n let j = scanStart;\n while (j < rawArgs.length) {\n const u = rawArgs[j]!;\n if (u.startsWith('-')) break;\n pieces.push(u);\n j += 1;\n }\n i = j - 1;\n }\n return pieces\n .flatMap((s) => s.split(','))\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n}\n\n/**\n * Collect ports from every `--with-ports` occurrence (both `=value`\n * and two-token forms) plus the shell-tokenization fallback for\n * `--with-ports=3000, 5173, 6006` where the shell strips the spaces\n * and leaves bare value tokens after the flag.\n *\n * We walk rawArgs only and ignore the args['with-ports'] value —\n * citty drops earlier occurrences when the flag is repeated, but\n * rawArgs has them all in order.\n *\n * Validation (integer, 1..65535) lives in runInit so the same error\n * surface covers both the CLI and direct runInit callers.\n */\nexport function collectWithPortsList(\n _withPortsArg: string | undefined,\n rawArgs: string[],\n): number[] | undefined {\n const pieces: string[] = [];\n for (let i = 0; i < rawArgs.length; i += 1) {\n const t = rawArgs[i]!;\n let scanStart = -1;\n if (t === '--with-ports') {\n scanStart = i + 1; // value sits after the flag, picked up below\n } else if (t.startsWith('--with-ports=')) {\n pieces.push(t.slice('--with-ports='.length));\n scanStart = i + 1;\n }\n if (scanStart < 0) continue;\n // Sweep subsequent non-flag tokens — covers both the two-token\n // form (`--with-ports VALUE`) and the shell-tokenized\n // `--with-ports=3000, 5173, 6006` case where 5173/6006 land as\n // bare tokens after the flag.\n let j = scanStart;\n while (j < rawArgs.length) {\n const u = rawArgs[j]!;\n if (u.startsWith('-')) break;\n pieces.push(u);\n j += 1;\n }\n // Skip everything we just consumed; the outer i++ resumes at j.\n i = j - 1;\n }\n const parts = pieces\n .flatMap((s) => s.split(','))\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n if (parts.length === 0) return undefined;\n const out: number[] = [];\n for (const p of parts) {\n const n = Number(p);\n if (!Number.isInteger(n) || n < 1 || n > 65535) {\n throw new Error(\n `Invalid port in --with-ports: ${JSON.stringify(p)}. Expected integers between 1 and 65535, comma-separated.`,\n );\n }\n out.push(n);\n }\n return out;\n}\n","import { existsSync, promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { consola } from 'consola';\nimport {\n containerConfigPath,\n containerConfigsDir,\n containerEnvPath,\n monocerosHome as defaultMonocerosHome,\n workbenchRoot as defaultWorkbenchRoot,\n workbenchCheckoutRoot,\n componentsDir as defaultComponentsDir,\n} from '../config/paths.js';\nimport {\n ensureEnvGitignored,\n ensureEnvVars,\n GIT_IDENTITY_VAR,\n} from '../config/env-file.js';\nimport { featureOptionHints } from './feature-doc.js';\nimport { KNOWN_PROVIDER_HOSTS, REGEX } from '../config/schema.js';\nimport { loadComponentCatalog, mergeFeatureOptions } from './components.js';\nimport type { Component } from './components.js';\nimport {\n generateComposedYml,\n generateDocumentedYml,\n type ComposedInit,\n type InitService,\n} from './generator.js';\nimport { loadFeatureManifestSummary } from './manifest.js';\nimport {\n curatedServiceEnvDefaults,\n deriveServiceName,\n isCuratedService,\n knownLanguages,\n parseLanguageSpec,\n} from '../create/catalog.js';\n\n/**\n * `monoceros init <name> [--with-languages=… --with-features=… …]` —\n * produce a fresh container-config yml at\n * `<MONOCEROS_HOME>/container-configs/<name>.yml`.\n *\n * Two modes:\n *\n * - With any per-category flag (`--with-languages=node`,\n * `--with-services=postgres`, `--with-features=github,claude`, …;\n * each takes a comma-list): the listed components are merged and\n * the result written as an active, immediately-applyable yml.\n * Per-feature option hints (auth/credentials from the feature\n * manifest) appear as commented lines next to the active options\n * so the builder can see what's available without leaving the file.\n *\n * - Without any `--with-*` flag: a documented-default yml is written. Every\n * section is commented out, every catalog component appears as\n * a suggestion with prose describing what it adds. Builder\n * un-comments what they want, then `monoceros apply <name>`.\n *\n * Errors loudly if:\n *\n * - the target config already exists (delete it first if you want\n * to start over — protects hand-edits)\n * - a `--with-*` name is not in the catalog (the error message\n * lists what *is* available)\n * - the chosen container name is shape-invalid\n */\n\nexport interface RunInitOptions {\n name: string;\n /**\n * Explicit per-category inputs (from `--with-languages`,\n * `--with-features`, `--with-services`, `--with-apt-packages`).\n * When ALL of these are empty/undefined → documented mode (every\n * catalog component commented out). When any is set → composed mode.\n *\n * - `languages`: curated runtime names, optional `:version`\n * (`java:17`). Validated against the language catalog.\n * - `features`: curated short names (`claude`, `atlassian/twg`) OR\n * full OCI refs (`ghcr.io/foo/bar:1`).\n * - `services`: curated names (`postgres`) → expanded block, OR any\n * image (`rustfs/rustfs:latest`) → name+image + commented scaffold.\n * - `aptPackages`: arbitrary apt package names (no catalog).\n */\n languages?: string[];\n features?: string[];\n services?: string[];\n aptPackages?: string[];\n /**\n * Git URLs to clone into `projects/` on the first apply. Each URL\n * lands at `projects/<URL-derived-leaf>/` (e.g.\n * `https://.../foo.git` → `projects/foo/`). For nested destination\n * paths (`projects/apps/web/`) use `monoceros add-repo --path=...`\n * post-init — the init flag intentionally keeps the syntax minimal.\n */\n withRepo?: string[];\n /**\n * Container-internal ports to pre-seed in `routing.ports`. First\n * entry doubles as the bare `<name>.localhost` default route in\n * Traefik. Equivalent to running `monoceros add-port` after init.\n * Each must be an integer in [1, 65535]; invalid values raise a\n * usage error before the yml is written.\n */\n withPorts?: number[];\n /** Override of the CLI-bundle root that holds `templates/components/`. */\n workbenchRoot?: string;\n /** Override of the user-data home that owns `container-configs/`. */\n monocerosHome?: string;\n logger?: {\n success: (msg: string) => void;\n info: (msg: string) => void;\n };\n}\n\nexport interface RunInitResult {\n configPath: string;\n /** True when the documented-default mode was used. */\n documented: boolean;\n}\n\nexport async function runInit(opts: RunInitOptions): Promise<RunInitResult> {\n const workbench = opts.workbenchRoot ?? defaultWorkbenchRoot();\n const home = opts.monocerosHome ?? defaultMonocerosHome();\n const logger = opts.logger ?? {\n success: (msg) => consola.success(msg),\n info: (msg) => consola.info(msg),\n };\n\n if (!REGEX.solutionName.test(opts.name)) {\n throw new Error(\n `Invalid config name: ${JSON.stringify(opts.name)}. Use letters, digits, '.', '_' or '-'.`,\n );\n }\n\n const dest = containerConfigPath(opts.name, home);\n if (existsSync(dest)) {\n throw new Error(\n `Config already exists: ${dest}. Delete it manually before re-running \\`monoceros init\\` — this protects any hand-edits.`,\n );\n }\n\n const catalog = await loadComponentCatalog(defaultComponentsDir(workbench));\n if (catalog.size === 0) {\n throw new Error(\n `No components found under ${defaultComponentsDir(workbench)}. The workbench checkout is incomplete.`,\n );\n }\n\n // Feature manifests live at the workbench-checkout root, not in\n // the CLI bundle. In tests the fixture sets `workbenchRoot` to a\n // dir that happens to hold both the templates *and* an\n // `images/features/` tree; honour that override. In real use we\n // fall back to `workbenchCheckoutRoot()` which returns null when\n // the CLI is run from an npm install — manifest lookups then\n // return undefined and init renders without optionHints.\n const checkoutRoot = opts.workbenchRoot ?? workbenchCheckoutRoot();\n const lookup = (ref: string) => loadFeatureManifestSummary(ref, checkoutRoot);\n\n // --with-repo URL validation: only canonical hosts. Non-canonical\n // hosts (self-hosted GitLab, Gitea, …) need `provider:` in the yml,\n // and init has no --provider flag, so the builder takes the\n // `monoceros init` + `monoceros add-repo … --provider=…` path\n // instead.\n // Dedupe input URLs (preserve insertion order) — same URL passed\n // twice from the CLI is a no-op, matching how `monoceros add-repo`\n // treats the second-add case.\n const reposRaw = (opts.withRepo ?? [])\n .map((u) => u.trim())\n .filter((u) => u.length > 0);\n const repos: string[] = [];\n const seenRepoUrls = new Set<string>();\n for (const url of reposRaw) {\n if (seenRepoUrls.has(url)) continue;\n seenRepoUrls.add(url);\n repos.push(url);\n }\n if (repos.length > 0) {\n const offending: string[] = [];\n for (const url of repos) {\n let host: string | undefined;\n try {\n host = url.startsWith('https://') ? new URL(url).hostname : undefined;\n } catch {\n host = undefined;\n }\n if (!host || !KNOWN_PROVIDER_HOSTS[host.toLowerCase()]) {\n offending.push(url);\n }\n }\n if (offending.length > 0) {\n throw new Error(\n [\n `--with-repo only supports github.com / gitlab.com / bitbucket.org URLs.`,\n `These are not canonical: ${offending.join(', ')}`,\n `For other hosts, run \\`monoceros init <name>\\` first, then`,\n `\\`monoceros add-repo <name> <url> --provider=github|gitlab|bitbucket\\`.`,\n ].join('\\n'),\n );\n }\n }\n\n // --with-ports validation: integer 1..65535, dedupe preserving\n // insertion order (first entry = the default route — collapsing two\n // mentions of 3000 to a single entry keeps that semantics\n // unambiguous).\n const portsRaw = opts.withPorts ?? [];\n const ports: number[] = [];\n const seenPorts = new Set<number>();\n for (const raw of portsRaw) {\n if (!Number.isInteger(raw) || raw < 1 || raw > 65535) {\n throw new Error(\n `Invalid port in --with-ports: ${JSON.stringify(raw)}. Expected integers between 1 and 65535.`,\n );\n }\n if (seenPorts.has(raw)) continue;\n seenPorts.add(raw);\n ports.push(raw);\n }\n\n // Identity is NOT resolved at init. When repos are present, the\n // generators render a container-level `git.user` with `${VAR}`\n // placeholders and we seed the matching blank keys into `<name>.env`\n // (below). Identity then resolves at apply time from that env file,\n // falling through the cascade (monoceros-config defaults → host →\n // prompt) when the keys are left blank — no init-time prompt.\n\n // Both generators take the URL + port lists directly — no AST\n // round-trip after the fact. That lets each generator decide how\n // to render the routing/repos block (commented hints in documented\n // mode, active entries in composed mode), keeping the \"all\n // available options visible\" rule consistent across sections.\n let text: string;\n const composed = resolveComposedInit(catalog, {\n languages: opts.languages ?? [],\n features: opts.features ?? [],\n services: opts.services ?? [],\n aptPackages: opts.aptPackages ?? [],\n });\n const anyComposed =\n composed.languages.length > 0 ||\n composed.features.length > 0 ||\n composed.services.length > 0 ||\n composed.aptPackages.length > 0;\n if (!anyComposed) {\n text = generateDocumentedYml(opts.name, catalog, lookup, repos, ports);\n } else {\n text = generateComposedYml(opts.name, composed, lookup, repos, ports);\n }\n\n await fs.mkdir(containerConfigsDir(home), { recursive: true });\n await ensureEnvGitignored(containerConfigsDir(home));\n await fs.writeFile(dest, text, 'utf8');\n\n // Scaffold the gitignored `<name>.env`: create it with the header\n // stub, then seed the `${VAR}` references the composed yml carries —\n // feature credential placeholders as blank `VAR=` keys (builder fills\n // them) and curated-service env vars with their dev-defaults\n // (`POSTGRES_USER=monoceros`, …; builder can keep or change them).\n // Upsert — never overwrites an existing env file's keys (e.g. one\n // from `restore`). Service defaults win over feature blanks on the\n // (unlikely) key collision.\n const envPath = containerEnvPath(opts.name, home);\n const seedVars: Record<string, string> = {};\n for (const f of composed.features) {\n for (const h of featureOptionHints(\n lookup(f.ref),\n f.ref,\n Object.keys(f.options ?? {}),\n )) {\n if (!(h.envVar in seedVars)) seedVars[h.envVar] = '';\n }\n }\n for (const svc of composed.services) {\n if (svc.kind === 'curated') {\n Object.assign(seedVars, curatedServiceEnvDefaults(svc.name));\n }\n }\n // When repos are present, the yml carries a container-level\n // `git.user: ${GIT_USER_NAME}/${GIT_USER_EMAIL}` — seed the matching\n // keys BLANK so the builder either fills them or leaves them empty\n // (→ apply climbs the identity cascade). Blank, not host-derived: the\n // builder asked for a shareable, env-managed identity.\n if (repos.length > 0) {\n seedVars[GIT_IDENTITY_VAR.name] = '';\n seedVars[GIT_IDENTITY_VAR.email] = '';\n }\n await ensureEnvVars(envPath, opts.name, seedVars);\n\n const documented = !anyComposed;\n // Paths relative to MONOCEROS_HOME keep the line readable (the dev\n // .local home is deep under the project root).\n const ymlRel = path.relative(home, dest);\n const envRel = path.relative(home, envPath);\n if (documented) {\n logger.success(`Wrote documented default to ${ymlRel} and ${envRel}.`);\n logger.info(\n `Un-comment what you need, then \\`monoceros apply ${opts.name}\\`.`,\n );\n } else {\n logger.success(`Composed into ${ymlRel} and ${envRel}.`);\n logger.info(\n `Edit the files if you need to tweak, then \\`monoceros apply ${opts.name}\\`.`,\n );\n }\n\n return { configPath: dest, documented };\n}\n\n// ───── Composed-mode input resolution ─────────────────────────────\n\n/**\n * Resolve the raw `--with-*` lists into the categorized, validated\n * shape the composed generator consumes. Curated vs. arbitrary handling\n * lives here:\n * - languages → validated against the language catalog (`:version` ok)\n * - features → catalog short name OR full OCI ref\n * - services → curated name (expanded) OR any image (scaffolded)\n * - aptPackages → arbitrary names (shape-checked only)\n */\nfunction resolveComposedInit(\n catalog: Map<string, Component>,\n raw: {\n languages: string[];\n features: string[];\n services: string[];\n aptPackages: string[];\n },\n): ComposedInit {\n return {\n languages: resolveInitLanguages(raw.languages),\n aptPackages: resolveInitAptPackages(raw.aptPackages),\n services: resolveInitServices(raw.services),\n features: resolveInitFeatures(catalog, raw.features),\n };\n}\n\nfunction resolveInitLanguages(entries: string[]): string[] {\n const known = new Set(knownLanguages());\n const out: string[] = [];\n const seen = new Set<string>();\n const unknown: string[] = [];\n for (const raw of entries) {\n const e = raw.trim();\n if (!e || seen.has(e)) continue;\n const spec = parseLanguageSpec(e);\n if (!spec || !known.has(spec.name)) {\n unknown.push(e);\n continue;\n }\n seen.add(e);\n out.push(e);\n }\n if (unknown.length > 0) {\n throw new Error(\n `Unknown language${unknown.length > 1 ? 's' : ''}: ${unknown.join(', ')}. ` +\n `Known: ${knownLanguages().join(', ')}.`,\n );\n }\n return out;\n}\n\nfunction resolveInitAptPackages(entries: string[]): string[] {\n const out: string[] = [];\n const seen = new Set<string>();\n const bad: string[] = [];\n for (const raw of entries) {\n const e = raw.trim();\n if (!e || seen.has(e)) continue;\n if (!REGEX.aptPackage.test(e)) {\n bad.push(e);\n continue;\n }\n seen.add(e);\n out.push(e);\n }\n if (bad.length > 0) {\n throw new Error(\n `Invalid apt package name${bad.length > 1 ? 's' : ''}: ${bad.join(', ')}. ` +\n `Expected lowercase alphanumeric plus '.+-'.`,\n );\n }\n return out;\n}\n\nfunction resolveInitServices(entries: string[]): InitService[] {\n const out: InitService[] = [];\n const byName = new Map<string, InitService>();\n for (const raw of entries) {\n const e = raw.trim();\n if (!e) continue;\n const svc: InitService = isCuratedService(e)\n ? { kind: 'curated', name: e }\n : { kind: 'custom', name: deriveServiceName(e), image: e };\n const existing = byName.get(svc.name);\n if (existing) {\n // Same entry twice → no-op; a genuine name clash → error.\n if (existing.kind === svc.kind && existing.image === svc.image) continue;\n throw new Error(\n `Two --with-services entries resolve to the service name '${svc.name}'. ` +\n `Add one after init with \\`monoceros add-service ${'<name>'} <image> --as=<other>\\`.`,\n );\n }\n byName.set(svc.name, svc);\n out.push(svc);\n }\n return out;\n}\n\nfunction resolveInitFeatures(\n catalog: Map<string, Component>,\n entries: string[],\n): Array<{ ref: string; options: Record<string, string | number | boolean> }> {\n const byRef = new Map<\n string,\n { ref: string; options: Record<string, string | number | boolean> }\n >();\n const unknown: string[] = [];\n for (const raw of entries) {\n const e = raw.trim();\n if (!e) continue;\n if (REGEX.featureRef.test(e)) {\n if (!byRef.has(e)) byRef.set(e, { ref: e, options: {} });\n continue;\n }\n const c = catalog.get(e);\n if (!c || c.file.category !== 'feature') {\n unknown.push(e);\n continue;\n }\n for (const f of c.file.contributes.features ?? []) {\n const existing = byRef.get(f.ref);\n if (!existing) {\n byRef.set(f.ref, { ref: f.ref, options: { ...(f.options ?? {}) } });\n } else {\n existing.options = mergeFeatureOptions(\n existing.options,\n f.options ?? {},\n );\n }\n }\n }\n if (unknown.length > 0) {\n const featureNames = [...catalog.values()]\n .filter((c) => c.file.category === 'feature')\n .map((c) => c.name)\n .sort();\n throw new Error(\n `Unknown feature${unknown.length > 1 ? 's' : ''}: ${unknown.join(', ')}.\\n` +\n `Use a catalog short name (${featureNames.join(', ')}) or a full OCI ref (ghcr.io/…/<name>:<tag>).`,\n );\n }\n return [...byRef.values()];\n}\n","import type { Component } from './components.js';\nimport type { FeatureManifestSummary } from './manifest.js';\nimport {\n buildFeatureHeaderLines,\n featureOptionHints,\n wrapToComment as sharedWrapToComment,\n} from './feature-doc.js';\nimport { expandCuratedService } from '../create/catalog.js';\nimport { renderCustomService, renderServiceObjectBody } from './service-doc.js';\nimport { GIT_IDENTITY_VAR } from '../config/env-file.js';\n\n/**\n * Renderer for the container yml that `monoceros init` produces.\n *\n * Style rules (the file the builder sees, not the code):\n *\n * - Every section carries a short user-facing header comment that\n * explains WHY the section exists, not how it's wired internally.\n * One to four lines, builder-vocabulary.\n *\n * - One `#` depth — never `# # foo`. Builder strips one `#` per line\n * of a commented block to activate it.\n *\n * - No trailing `# explanations` after a value. Per-feature option\n * text lives in the feature manifest and surfaces as a wrapped\n * header block above the matching `- ref:` line.\n *\n * Per-feature header text is pulled straight from the feature manifest\n * (`name`, `description`, `options.<key>.description`,\n * `documentationURL`, `x-monoceros.usageNotes`). The generator carries\n * no fallback prose — gaps in the manifest are visible gaps in the\n * generated yml, which is the right incentive.\n */\n\nexport type ManifestLookup = (\n ref: string,\n) => FeatureManifestSummary | undefined;\n\nconst SCHEMA_HEADER_ACTIVE =\n '# Solution-config — describes what should be inside your dev-container.\\n# Edit any section, then run `monoceros apply <name>` to (re-)build.';\nconst SCHEMA_HEADER_DOCUMENTED =\n '# Solution-config — describes what should be inside your dev-container.\\n# Every section is commented out by default; un-comment what you need\\n# (strip one `#` per line of the block), then run `monoceros apply <name>`.';\n\n// Soft target for wrapped comment lines. Keeps the rendered yml\n// readable in a standard editor without horizontal scrolling.\nconst COMMENT_WIDTH = 76;\n\n/**\n * Render the active-mode yml for the given components.\n */\n/** A service the builder asked for via `--with-services`. */\nexport interface InitService {\n /** `curated` → expand via SERVICE_CATALOG; `custom` → name + image + scaffold. */\n kind: 'curated' | 'custom';\n /** Compose service name (curated id, or derived from the image). */\n name: string;\n /** Image ref — only for `custom` services. */\n image?: string;\n}\n\n/** Resolved, categorized inputs for the composed-mode generator. */\nexport interface ComposedInit {\n languages: readonly string[];\n aptPackages: readonly string[];\n services: readonly InitService[];\n features: readonly RenderableFeature[];\n}\n\nexport function generateComposedYml(\n name: string,\n composed: ComposedInit,\n lookupManifest: ManifestLookup,\n repoUrls: readonly string[] = [],\n ports: readonly number[] = [],\n): string {\n const lines: string[] = [];\n pushHeader(lines, SCHEMA_HEADER_ACTIVE, name);\n lines.push('');\n lines.push('schemaVersion: 1');\n lines.push(`name: ${name}`);\n lines.push('');\n\n if (composed.languages.length > 0) {\n pushSectionHeader(lines, LANGUAGES_HEADER, /* commented */ false);\n lines.push('languages:');\n for (const lang of composed.languages) lines.push(` - ${lang}`);\n lines.push('');\n }\n if (composed.aptPackages.length > 0) {\n pushSectionHeader(lines, APT_PACKAGES_HEADER, /* commented */ false);\n lines.push('aptPackages:');\n for (const pkg of composed.aptPackages) lines.push(` - ${pkg}`);\n lines.push('');\n }\n if (composed.services.length > 0) {\n pushSectionHeader(lines, SERVICES_HEADER, /* commented */ false);\n lines.push('services:');\n for (const svc of composed.services) pushServiceEntry(lines, svc);\n lines.push('');\n }\n if (composed.features.length > 0) {\n pushSectionHeader(lines, FEATURES_HEADER_ACTIVE, /* commented */ false);\n lines.push('features:');\n for (const f of composed.features) {\n lines.push('');\n renderFeatureBlock(\n lines,\n f,\n lookupManifest(f.ref),\n /* commented */ false,\n );\n }\n lines.push('');\n }\n if (repoUrls.length > 0) {\n // Container-level identity first (placeholders + .env seed); repos\n // inherit it. The per-repo `git.user` below stays commented as the\n // override hint for the work-vs-personal case.\n pushGitIdentityBlock(lines);\n pushSectionHeader(lines, REPOS_HEADER, /* commented */ false);\n lines.push('repos:');\n for (const url of repoUrls) {\n lines.push(` - url: ${url}`);\n // Optional per-repo fields as commented hints (single-`#`).\n // Builder strips one `#` per line to set a path, declare a\n // provider, or override the container-level git.user for this\n // repo.\n lines.push(' # path:');\n lines.push(' # provider:');\n lines.push(' # git:');\n lines.push(' # user:');\n lines.push(' # name:');\n lines.push(' # email:');\n }\n lines.push('');\n }\n if (ports.length > 0) {\n pushSectionHeader(lines, routingHeader(name), /* commented */ false);\n lines.push('routing:');\n lines.push(' ports:');\n for (const port of ports) {\n lines.push(` - ${port}`);\n }\n lines.push(' # vscodeAutoForward: false');\n lines.push('');\n }\n\n return ensureTrailingNewline(lines.join('\\n'));\n}\n\n/**\n * Render the documented-default yml: every section commented out at\n * single-`#` depth, with a user-facing header above each section.\n */\nexport function generateDocumentedYml(\n name: string,\n catalog: Map<string, Component>,\n lookupManifest: ManifestLookup,\n repoUrls: readonly string[] = [],\n ports: readonly number[] = [],\n): string {\n const byCategory = groupByCategory(catalog);\n const lines: string[] = [];\n pushHeader(lines, SCHEMA_HEADER_DOCUMENTED, name);\n lines.push('');\n lines.push('schemaVersion: 1');\n lines.push(`name: ${name}`);\n lines.push('');\n\n if (byCategory.language.length > 0) {\n pushSectionHeader(lines, LANGUAGES_HEADER, /* commented */ true);\n lines.push('# languages:');\n for (const c of byCategory.language) {\n for (const lang of c.file.contributes.languages ?? []) {\n lines.push(`# - ${lang}`);\n }\n }\n lines.push('');\n }\n if (byCategory.service.length > 0) {\n pushSectionHeader(lines, SERVICES_HEADER, /* commented */ true);\n lines.push('# services:');\n for (const c of byCategory.service) {\n for (const svc of c.file.contributes.services ?? []) {\n const body = renderServiceObjectBody(expandCuratedService(svc));\n lines.push(`# - ${body[0]}`);\n for (const line of body.slice(1)) lines.push(`# ${line}`);\n }\n }\n lines.push('');\n }\n if (byCategory.feature.length > 0) {\n pushSectionHeader(lines, FEATURES_HEADER_DOCUMENTED, /* commented */ true);\n lines.push('# features:');\n\n const renderedRefs = new Set<string>();\n const topLevel = byCategory.feature.filter((c) => !c.name.includes('/'));\n for (const c of topLevel) {\n for (const f of c.file.contributes.features ?? []) {\n if (renderedRefs.has(f.ref)) continue;\n renderedRefs.add(f.ref);\n lines.push('#');\n renderFeatureBlock(\n lines,\n f,\n lookupManifest(f.ref),\n /* commented */ true,\n );\n }\n }\n for (const c of byCategory.feature) {\n if (!c.name.includes('/')) continue;\n for (const f of c.file.contributes.features ?? []) {\n if (renderedRefs.has(f.ref)) continue;\n renderedRefs.add(f.ref);\n lines.push('#');\n renderFeatureBlock(\n lines,\n f,\n lookupManifest(f.ref),\n /* commented */ true,\n );\n }\n }\n lines.push('');\n }\n\n if (repoUrls.length > 0) {\n pushGitIdentityBlock(lines);\n pushSectionHeader(lines, REPOS_HEADER, /* commented */ false);\n lines.push('repos:');\n for (const url of repoUrls) {\n lines.push(` - url: ${url}`);\n }\n lines.push('');\n } else {\n pushSectionHeader(lines, REPOS_HEADER, /* commented */ true);\n lines.push('# repos:');\n lines.push('# - url: https://github.com/<org>/<repo>.git');\n lines.push('# path: <folder>');\n lines.push('# provider: github');\n lines.push('# git:');\n lines.push('# user:');\n lines.push('# name: Your Name');\n lines.push('# email: you@example.com');\n lines.push('');\n }\n\n if (ports.length > 0) {\n pushSectionHeader(lines, routingHeader(name), /* commented */ false);\n lines.push('routing:');\n lines.push(' ports:');\n for (const port of ports) {\n lines.push(` - ${port}`);\n }\n lines.push(' # vscodeAutoForward: false');\n lines.push('');\n } else {\n pushSectionHeader(lines, routingHeader(name), /* commented */ true);\n lines.push('# routing:');\n lines.push('# ports:');\n lines.push('# - 3000');\n lines.push('# - 5173');\n lines.push('# vscodeAutoForward: false');\n lines.push('');\n }\n\n return ensureTrailingNewline(lines.join('\\n'));\n}\n\n// ───── Section header text ────────────────────────────────────────\n\nconst LANGUAGES_HEADER =\n 'Language runtimes installed inside the dev-container. Pick the ones your projects build against. The catalog of available runtimes is shown by `monoceros list-components`.';\n\nconst SERVICES_HEADER =\n 'Sibling containers that run alongside the dev-container (databases, caches, message queues, …). Each service is reachable from inside the dev-container by its name as hostname (e.g. `postgres://postgres:5432`). Activating any service switches the container to docker-compose mode automatically.';\n\nconst APT_PACKAGES_HEADER =\n 'Debian/Ubuntu apt packages installed in the dev-container at build time. No curated list — any apt package name works; an invalid name surfaces as an apt error during build.';\n\n// Render one composed-mode service entry as a `services:` sequence item.\n// Curated services expand to the full catalog block; custom images get\n// name + image active plus the commented field scaffold.\nfunction pushServiceEntry(out: string[], svc: InitService): void {\n if (svc.kind === 'custom') {\n const { bodyLines, comment } = renderCustomService(\n svc.name,\n svc.image ?? '',\n );\n out.push(` - ${bodyLines[0]}`);\n for (const line of bodyLines.slice(1)) out.push(` ${line}`);\n for (const cl of comment.split('\\n')) out.push(` #${cl}`);\n return;\n }\n const body = renderServiceObjectBody(expandCuratedService(svc.name));\n out.push(` - ${body[0]}`);\n for (const line of body.slice(1)) out.push(` ${line}`);\n}\n\nconst FEATURES_HEADER_ACTIVE =\n 'A Monoceros dev-container is shaped by features — pluggable units that drop tooling (AI assistants, language CLIs, cloud SDKs, …) into the container and bring their own options. The features active for this container are listed below; adjust their options as needed. Shared credentials used across containers belong in monoceros-config.yml under `defaults.features.<ref>` rather than here. Full catalog: `monoceros list-components`.';\n\nconst FEATURES_HEADER_DOCUMENTED =\n 'A Monoceros dev-container is shaped by features — pluggable units that drop tooling (AI assistants, language CLIs, cloud SDKs, …) into the container and bring their own options. Un-comment the blocks below for the features you want active. Shared credentials used across containers belong in monoceros-config.yml under `defaults.features.<ref>` rather than here. Full catalog: `monoceros list-components`.';\n\nconst REPOS_HEADER =\n 'Git repositories cloned into `projects/` on container start-up. HTTPS URLs only. The provider is auto-detected for github.com / gitlab.com / bitbucket.org; for any other host (self-hosted GitLab, Gitea, …) declare `provider:` explicitly. Add more later with `monoceros add-repo`.';\n\nconst GIT_IDENTITY_HEADER =\n 'Git committer identity for commits made inside the container. The ${VAR} values resolve from <name>.env at apply time — fill them there, or leave them blank to fall back to your global git config (or a one-time prompt). Override per repo under repos[].git.user.';\n\n// Top-level `git.user` block with `${VAR}` placeholders. Rendered\n// whenever the container has repos: the identity then lives in the\n// gitignored <name>.env (seeded blank by init/add-repo), keeping the\n// shareable yml free of personal data while staying obvious.\nfunction pushGitIdentityBlock(lines: string[]): void {\n pushSectionHeader(lines, GIT_IDENTITY_HEADER, /* commented */ false);\n lines.push('git:');\n lines.push(' user:');\n lines.push(` name: \\${${GIT_IDENTITY_VAR.name}}`);\n lines.push(` email: \\${${GIT_IDENTITY_VAR.email}}`);\n lines.push('');\n}\n\nfunction routingHeader(name: string): string {\n return `Container ports exposed to the host through Traefik. Reach them in your browser as ${name}-<port>.localhost (e.g. ${name}-3000.localhost). The first entry is the default route and is also reachable as the bare ${name}.localhost. Manage the list with \\`monoceros add-port\\`.`;\n}\n\n// ───── Per-feature rendering ──────────────────────────────────────\n\ninterface RenderableFeature {\n ref: string;\n options?: Record<string, string | number | boolean>;\n}\n\n/**\n * Render one feature entry. Header comment block (from manifest) +\n * the `- ref:` / `options:` yaml. Commented (`commented: true`) means\n * every line carries one `#` prefix — builder strips it to activate.\n *\n * Format (both modes), `<lp>` = line prefix (`# ` when commented, ``\n * when active), `<ip>` = inside-options-prefix (`# ` commented,\n * ` ` active):\n *\n * <lp># <feature name and prose, wrapped>\n * <lp># Options: opt1 (desc), opt2 (desc), …\n * <lp># See <documentationURL> for further information.\n * <lp> - ref: <ref>\n * <lp> options:\n * <lp> <opt>: <value>\n */\nfunction renderFeatureBlock(\n out: string[],\n feature: RenderableFeature,\n summary: FeatureManifestSummary | undefined,\n commented: boolean,\n): void {\n // Header lines are ALWAYS plain `#` comments at single depth —\n // never double `# #`. In documented mode the yaml lines below get\n // a `# ` prefix that the builder strips to activate; in active\n // mode they have no prefix. Either way the header comments stay\n // as-is, because they ARE the documentation that should live in\n // the file forever.\n const yamlPrefix = commented ? '# ' : '';\n\n // Header lines come from the shared feature-doc builder so\n // `add-feature`'s AST writer can emit the exact same prose block.\n for (const wl of buildFeatureHeaderLines(summary, COMMENT_WIDTH - 2)) {\n out.push(`# ${wl}`.trimEnd());\n }\n\n // The `- ref:` block. Indented two spaces under `features:`.\n out.push(`${yamlPrefix} - ref: ${feature.ref}`);\n\n const options = feature.options ?? {};\n const activeKeys = Object.entries(options);\n // Hint keys carry a `${VAR}` placeholder so the builder sees exactly\n // which env var to fill (and `init` / `add-feature` seed the same var\n // into <name>.env). Derivation is shared via featureOptionHints.\n const hints = featureOptionHints(summary, feature.ref, Object.keys(options));\n\n if (activeKeys.length === 0 && hints.length === 0) return;\n\n if (commented) {\n // Documented mode: the whole feature block is single-`#`\n // commented at outer depth. The options skeleton lives INSIDE\n // that outer comment — no extra inner `#`, otherwise we'd\n // re-introduce `# # foo` nesting that the builder rightly\n // objected to. After stripping one `#` per line, the active\n // form has the hint keys as bare-null values which the\n // transform treats as \"fall through to default\" (see\n // `solutionConfigToCreateOptions`).\n out.push(`${yamlPrefix} options:`);\n for (const [key, value] of activeKeys) {\n out.push(`${yamlPrefix} ${key}: ${renderScalarValue(value)}`);\n }\n for (const hint of hints) {\n out.push(`${yamlPrefix} ${hint.key}: ${hint.placeholder}`);\n }\n return;\n }\n\n // Composed mode: `- ref:` is active, and so is a single `options:`\n // block — active sub-component values first, then the credential\n // hints as `${VAR}` placeholders. The placeholders are NOT bare-null\n // (they carry a value), so no yaml-lib trailing-comment-stealing on\n // round-trip; and an empty/missing `${VAR}` resolves to \"\" at apply,\n // which the transform skips → the monoceros-config default is\n // inherited (not clobbered). The matching `.env` keys are seeded blank\n // by init/add-feature, so the builder just fills the value.\n out.push(` options:`);\n for (const [key, value] of activeKeys) {\n out.push(` ${key}: ${renderScalarValue(value)}`);\n }\n for (const hint of hints) {\n out.push(` ${hint.key}: ${hint.placeholder}`);\n }\n}\n\n// ───── Misc helpers ───────────────────────────────────────────────\n\nfunction pushHeader(out: string[], header: string, name: string): void {\n for (const line of header.replace(/<name>/g, name).split('\\n')) {\n out.push(line);\n }\n}\n\nfunction pushSectionHeader(\n out: string[],\n text: string,\n _commented: boolean,\n): void {\n // All section headers are themselves `#`-comments regardless of\n // whether the section body is commented or active. The `_commented`\n // flag is kept for symmetry with future per-mode wording but is\n // unused today — the body's commented-ness is encoded in the body\n // lines, not in the header.\n void _commented;\n const wrapped = sharedWrapToComment(text, COMMENT_WIDTH - 2);\n for (const wl of wrapped) {\n out.push(`# ${wl}`.trimEnd());\n }\n}\n\nfunction renderScalarValue(value: string | number | boolean): string {\n if (typeof value === 'string') {\n return /^[A-Za-z_][A-Za-z0-9._-]*$/.test(value)\n ? value\n : JSON.stringify(value);\n }\n return String(value);\n}\n\nfunction groupByCategory(catalog: Map<string, Component>): {\n language: Component[];\n service: Component[];\n feature: Component[];\n} {\n const out: ReturnType<typeof groupByCategory> = {\n language: [],\n service: [],\n feature: [],\n };\n const sorted = [...catalog.values()].sort((a, b) =>\n a.name.localeCompare(b.name),\n );\n for (const c of sorted) {\n out[c.file.category].push(c);\n }\n return out;\n}\n\nfunction ensureTrailingNewline(s: string): string {\n return s.endsWith('\\n') ? s : s + '\\n';\n}\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { loadComponentCatalog } from '../init/components.js';\nimport { colorsFor } from '../util/format.js';\n\n// Category-key → human-readable section label. Same order is used\n// for rendering — languages first (most common), services next,\n// features last.\nconst CATEGORY_LABELS = {\n language: 'Languages',\n service: 'Services',\n feature: 'Features',\n} as const;\nconst CATEGORY_ORDER: ReadonlyArray<keyof typeof CATEGORY_LABELS> = [\n 'language',\n 'service',\n 'feature',\n];\n\nexport const listComponentsCommand = defineCommand({\n meta: {\n name: 'list-components',\n group: 'discovery',\n description:\n 'Print the components catalog used by `monoceros init --with-languages=… / --with-services=… / --with-features=…`, grouped by category (Languages, Services, Features). Component names render in cyan, descriptions in default colour; when piped, the formatting drops out and lines become `name<TAB>description` for grep/awk-friendly consumption.',\n },\n args: {},\n async run() {\n try {\n const catalog = await loadComponentCatalog();\n if (catalog.size === 0) {\n consola.warn(\n 'No components found. The workbench checkout looks incomplete.',\n );\n process.exit(0);\n }\n\n const fmt = colorsFor(process.stdout);\n const isTty = process.stdout.isTTY ?? false;\n\n // Group entries by category for sectioned rendering.\n const byCategory = new Map<\n string,\n Array<{ name: string; desc: string }>\n >();\n for (const c of catalog.values()) {\n const list = byCategory.get(c.file.category) ?? [];\n list.push({ name: c.name, desc: c.file.displayName });\n byCategory.set(c.file.category, list);\n }\n for (const list of byCategory.values()) {\n list.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n // Piped (non-TTY) output: stay machine-friendly with the\n // historical `name<TAB>description` shape, one category at a\n // time. No ANSI, no alignment padding — grep/awk consumers\n // want predictable columns.\n if (!isTty) {\n let first = true;\n for (const cat of CATEGORY_ORDER) {\n const items = byCategory.get(cat);\n if (!items || items.length === 0) continue;\n if (!first) process.stdout.write('\\n');\n first = false;\n process.stdout.write(`# ${cat}\\n`);\n for (const { name, desc } of items) {\n process.stdout.write(`${name}\\t${desc}\\n`);\n }\n }\n process.exit(0);\n }\n\n // Interactive (TTY) output: section headers + aligned\n // columns, same visual vocabulary as the help renderer and\n // the apply/install structured output. Cyan name column\n // padded to the widest entry in its section so the\n // description column lines up.\n let first = true;\n for (const cat of CATEGORY_ORDER) {\n const items = byCategory.get(cat);\n if (!items || items.length === 0) continue;\n if (!first) process.stdout.write('\\n');\n first = false;\n process.stdout.write(`${fmt.sectionLine(CATEGORY_LABELS[cat])}\\n\\n`);\n const nameWidth = Math.max(...items.map((i) => i.name.length));\n const gutter = 2;\n for (const { name, desc } of items) {\n const pad = ' '.repeat(nameWidth - name.length + gutter);\n process.stdout.write(` ${fmt.cyan(name)}${pad}${desc}\\n`);\n }\n }\n process.exit(0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { containerDir } from '../config/paths.js';\nimport { runLogs } from '../devcontainer/compose.js';\nimport { dispatch } from './_dispatch.js';\n\nexport const logsCommand = defineCommand({\n meta: {\n name: 'logs',\n group: 'run',\n description:\n 'Tail logs from the compose services of the named dev-container. Pass --no-follow for a one-shot dump.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n service: {\n type: 'string',\n description:\n 'Restrict to a single compose service (e.g. postgres). Defaults to all.',\n },\n follow: {\n type: 'boolean',\n description:\n 'Follow log output (default: true). Use --no-follow to disable.',\n alias: ['f'],\n default: true,\n },\n },\n run({ args }) {\n return dispatch(() =>\n runLogs({\n root: containerDir(args.name),\n ...(typeof args.service === 'string' ? { service: args.service } : {}),\n follow: args.follow,\n }),\n );\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { proxyHostPort, readMonocerosConfig } from '../config/global.js';\nimport { readConfig } from '../config/io.js';\nimport { containerConfigPath } from '../config/paths.js';\nimport { portNumber } from '../config/schema.js';\nimport { proxyUrlsFor } from '../proxy/dynamic.js';\nimport { colorsFor } from '../util/format.js';\n\nexport interface RunPortListingOptions {\n name: string;\n /** Override the resolved MONOCEROS_HOME (tests inject a tmpdir). */\n monocerosHome?: string;\n /** Where the table is printed. Defaults to process.stdout. */\n out?: NodeJS.WriteStream;\n /** Where the \"no ports declared\" hint goes. Defaults to consola. */\n info?: (message: string) => void;\n}\n\n/**\n * Render the port-listing table for one container. Pure I/O — no\n * `process.exit`, returns the intended exit code so the CLI wrapper\n * can stay thin.\n *\n * 0 → printed the table or the \"no ports declared\" hint\n * 1 → unrecoverable failure (yml missing, parse error, …)\n */\nexport async function runPortListing(\n opts: RunPortListingOptions,\n): Promise<number> {\n const out = opts.out ?? process.stdout;\n const info = opts.info ?? ((m) => consola.info(m));\n\n const parsed = await readConfig(\n containerConfigPath(opts.name, opts.monocerosHome),\n );\n const portEntries = parsed.config.routing?.ports ?? [];\n if (portEntries.length === 0) {\n info(\n `No ports declared in ${opts.name}.yml. Run \\`monoceros add-port ${opts.name} -- <port>\\` to expose one.`,\n );\n return 0;\n }\n const ports = portEntries.map(portNumber);\n const globalConfig = await readMonocerosConfig({\n ...(opts.monocerosHome ? { monocerosHome: opts.monocerosHome } : {}),\n });\n const hostPort = proxyHostPort(globalConfig);\n const urls = proxyUrlsFor(opts.name, ports, hostPort);\n\n const isTty = out.isTTY ?? false;\n const fmt = colorsFor(out);\n\n // The first port doubles as the default `<name>.localhost` route.\n // Emit that as an explicit extra row so the builder sees both URLs\n // alongside the explicit port mapping for the first entry.\n const portSuffix = hostPort === 80 ? '' : `:${hostPort}`;\n const rows: Array<{ port: number; url: string; tag: string }> = [];\n rows.push({\n port: urls[0]!.port,\n url: `http://${opts.name}.localhost${portSuffix}`,\n tag: 'default',\n });\n for (const u of urls) {\n rows.push({ port: u.port, url: u.url, tag: '' });\n }\n\n if (!isTty) {\n for (const r of rows) {\n out.write(`${r.port}\\t${r.url}\\t${r.tag}\\n`);\n }\n return 0;\n }\n\n // TTY: aligned three-column table, port cyan, url default, tag dim.\n const portWidth = Math.max(...rows.map((r) => String(r.port).length));\n const urlWidth = Math.max(...rows.map((r) => r.url.length));\n const gutter = 2;\n for (const r of rows) {\n const portStr = String(r.port).padStart(portWidth);\n const urlPad = ' '.repeat(urlWidth - r.url.length + gutter);\n const tag = r.tag ? fmt.dim(`(${r.tag})`) : '';\n out.write(` ${fmt.cyan(portStr)} → ${r.url}${urlPad}${tag}\\n`);\n }\n return 0;\n}\n\nexport const portCommand = defineCommand({\n meta: {\n name: 'port',\n group: 'discovery',\n description:\n 'List the Traefik URLs for a container. Reads ports from `routing.ports` in the container yml and the host port from `routing.hostPort` in monoceros-config.yml (default 80). When piped, drops formatting and emits `port<TAB>url<TAB>tag` per line for grep/awk consumption.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n },\n async run({ args }) {\n try {\n const code = await runPortListing({ name: args.name });\n process.exit(code);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { getInnerArgs } from '../inner-args.js';\nimport { runRemoveAptPackages } from '../modify/index.js';\n\nexport const removeAptPackagesCommand = defineCommand({\n meta: {\n name: 'remove-apt-packages',\n group: 'edit',\n description:\n 'Remove apt packages from the container config. Pass package names after `--` (e.g. `monoceros remove-apt-packages sandbox -- make jq`). Idempotent, prints a diff before writing.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n const packages = [...getInnerArgs()];\n if (packages.length === 0) {\n consola.error(\n 'No package names given. Usage: `monoceros remove-apt-packages <containername> [--yes] -- <pkg> [<pkg> …]`.',\n );\n process.exit(1);\n }\n try {\n const result = await runRemoveAptPackages({\n name: args.name,\n packages,\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runRemoveFeature } from '../modify/index.js';\n\nexport const removeFeatureCommand = defineCommand({\n meta: {\n name: 'remove-feature',\n group: 'edit',\n description:\n 'Remove a devcontainer feature from the container config. Accepts either a Monoceros catalog short-name (e.g. `atlassian`, `claude`) or a full OCI ref. Idempotent, prints a diff before writing.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n ref: {\n type: 'positional',\n description:\n 'Feature to remove. Either a Monoceros catalog short-name (e.g. `atlassian`, `atlassian/twg`, `claude` — see `monoceros list-components`) or a full OCI feature ref (e.g. `ghcr.io/devcontainers/features/docker-in-docker:2`).',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n try {\n const result = await runRemoveFeature({\n name: args.name,\n ref: args.ref,\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { createInterface } from 'node:readline/promises';\nimport { runRemove } from '../remove/index.js';\n\nexport const removeCommand = defineCommand({\n meta: {\n name: 'remove',\n group: 'lifecycle',\n description:\n 'Wipe everything belonging to a container: stop and remove the docker objects, back up the container-configs yml + container directory (incl. home/, projects/, data/), then delete them from disk. Shared docker images stay. By default the destructive step is confirmed interactively; pass -y to skip.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n backup: {\n type: 'boolean',\n // citty turns a default-true boolean automatically into a\n // `--no-X` flag for negation, so the builder gets the natural\n // `monoceros remove <name> --no-backup` form without us\n // needing to special-case the parsing. Defining the arg as\n // `no-backup` directly conflicts with citty's prefix logic\n // and silently fails to bind, so we always go through the\n // positive form.\n description:\n 'Write a backup of <container-dir> and the yml under container-backups/ before deleting. Default on; use `--no-backup` to skip.',\n default: true,\n },\n yes: {\n type: 'boolean',\n alias: 'y',\n description:\n 'Skip the interactive confirmation prompt. Useful in scripts.',\n default: false,\n },\n },\n async run({ args }) {\n try {\n const noBackup = args.backup === false;\n const skipPrompt = args.yes === true;\n\n if (!skipPrompt) {\n const warning = noBackup\n ? `About to remove '${args.name}' WITHOUT a backup. Docker objects, container-configs entry, and container directory will all be deleted.`\n : `About to remove '${args.name}'. A backup will be written to container-backups/ first, then docker objects, container-configs entry, and container directory will all be deleted.`;\n consola.warn(warning);\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n const answer = await rl.question('Continue? [y/N] ');\n rl.close();\n if (!/^y(es)?$/i.test(answer.trim())) {\n consola.info('Aborted. Nothing changed.');\n process.exit(0);\n }\n }\n\n await runRemove({\n name: args.name,\n ...(noBackup ? { noBackup: true } : {}),\n });\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { existsSync, promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { consola } from 'consola';\nimport {\n containerConfigPath,\n containerDir,\n containerEnvPath,\n monocerosHome as defaultMonocerosHome,\n prettyPath,\n} from '../config/paths.js';\nimport { REGEX } from '../config/schema.js';\nimport {\n cleanupDockerObjects,\n composeProjectName,\n spawnDocker,\n type DockerExec,\n} from '../devcontainer/compose.js';\nimport { maybeStopProxy } from '../proxy/index.js';\nimport { removeDynamicConfig } from '../proxy/dynamic.js';\n\n/**\n * `monoceros remove <name>` — wipe everything belonging to one\n * container.\n *\n * What \"everything\" means in practice (in this order):\n *\n * 1. Stop and remove docker objects scoped to the container:\n * - compose containers (label `com.docker.compose.project=<project>`)\n * - any image-mode container matching `vsc-<name>-*`\n * - the project network `<project>_default`\n * Named docker volumes are no longer used as of fea2b3f (DB data\n * is bind-mounted onto `<container-dir>/data/<svc>/`), so they\n * go away with the directory delete below.\n *\n * 2. Optionally back up the host-side state:\n * - `container-configs/<name>.yml`\n * - `container/<name>/` (entire scaffold incl. `home/`,\n * `projects/`, `.monoceros/`, `data/`)\n * Lands at `container-backups/<name>-<timestamp>/`. Plain\n * directory copy — readable with normal filesystem tools.\n *\n * 3. Delete the host-side state.\n *\n * Shared docker images (`monoceros-runtime:dev`, feature build\n * images, postgres/mysql/redis base images) are NOT removed — they\n * are shared across containers and pruning them is a separate\n * operation the builder can do with `docker image prune` when they\n * actually want to free that disk.\n */\n\nexport interface RunRemoveOptions {\n name: string;\n /** When true, skip the backup step. */\n noBackup?: boolean;\n /** Override of the user-data home. Tests inject a tmpdir. */\n monocerosHome?: string;\n /** Override the timestamp embedded in the backup directory name. */\n now?: Date;\n /**\n * Docker exec for the cleanup pipeline (ps/rm/network/run). Tests\n * inject a stub. Replaces the previous `dockerSpawn: ComposeSpawn`\n * which drove a bash script — direct docker spawn dodges the\n * Windows quoting issues on backslash-bearing label values.\n */\n dockerExec?: DockerExec;\n /** Override the docker exec used by the Traefik proxy lifecycle. */\n proxyDocker?: DockerExec;\n logger?: {\n info: (msg: string) => void;\n success: (msg: string) => void;\n warn?: (msg: string) => void;\n };\n}\n\nexport interface RunRemoveResult {\n /** Path the yml was at before deletion, or `null` if it didn't exist. */\n configPath: string | null;\n /** Path the container scaffold was at before deletion, or `null`. */\n containerPath: string | null;\n /** Directory of the backup, or `null` when --no-backup was passed. */\n backupPath: string | null;\n /** Exit code of the docker cleanup step (0 on success). */\n dockerExitCode: number;\n}\n\nexport async function runRemove(\n opts: RunRemoveOptions,\n): Promise<RunRemoveResult> {\n const home = opts.monocerosHome ?? defaultMonocerosHome();\n const logger = opts.logger ?? {\n info: (msg) => consola.info(msg),\n success: (msg) => consola.success(msg),\n warn: (msg) => consola.warn(msg),\n };\n\n if (!REGEX.solutionName.test(opts.name)) {\n throw new Error(\n `Invalid config name: ${JSON.stringify(opts.name)}. Use letters, digits, '.', '_' or '-'.`,\n );\n }\n\n const ymlPath = containerConfigPath(opts.name, home);\n const envPath = containerEnvPath(opts.name, home);\n const containerPath = containerDir(opts.name, home);\n const hasYml = existsSync(ymlPath);\n const hasEnv = existsSync(envPath);\n const hasContainer = existsSync(containerPath);\n\n if (!hasYml && !hasContainer) {\n throw new Error(\n `Nothing to remove for '${opts.name}': neither ${ymlPath} nor ${containerPath} exists.`,\n );\n }\n\n // ── Step 1: stop + remove docker objects ────────────────────────\n // Four overlapping filters because devcontainer-cli ranges over\n // multiple naming/labeling schemes depending on container mode:\n // 1. compose-mode containers carry the compose project label\n // 2. image-mode + feature-build intermediates carry the\n // devcontainer.local_folder label — the most reliable anchor,\n // because @devcontainers/cli lets Docker assign random names\n // like 'kind_cerf' that neither name-prefix filter catches.\n // 3. container-name prefix as a fallback for half-broken state\n // 4. deterministic `vsc-<name>-` prefix from older\n // devcontainer-cli versions\n // All four are union'd, deduplicated, and `docker rm -f`-ed\n // together via cleanupDockerObjects() (direct Node spawn of docker,\n // no shell wrapper).\n const projectName = composeProjectName(containerPath);\n const dockerExec = opts.dockerExec ?? spawnDocker;\n const { exitCode: dockerExitCode } = await cleanupDockerObjects({\n projectName,\n filters: [\n `label=com.docker.compose.project=${projectName}`,\n `label=devcontainer.local_folder=${containerPath}`,\n `name=^${projectName}-`,\n `name=^vsc-${opts.name}-`,\n ],\n network: `${projectName}_default`,\n logTag: 'remove',\n logger,\n exec: dockerExec,\n });\n\n // ── Step 2: optional backup ────────────────────────────────────\n let backupPath: string | null = null;\n if (!opts.noBackup && (hasYml || hasContainer)) {\n const ts = (opts.now ?? new Date()).toISOString().replace(/[:.]/g, '-');\n backupPath = path.join(home, 'container-backups', `${opts.name}-${ts}`);\n await fs.mkdir(backupPath, { recursive: true });\n if (hasYml) {\n await fs.copyFile(ymlPath, path.join(backupPath, `${opts.name}.yml`));\n }\n // The per-container env file holds the values behind the yml's\n // `${VAR}` references (secrets). It must travel with the backup, or\n // a restore would bring back a yml that can't be applied.\n if (hasEnv) {\n await fs.copyFile(envPath, path.join(backupPath, `${opts.name}.env`));\n }\n if (hasContainer) {\n await fs.cp(containerPath, path.join(backupPath, 'container'), {\n recursive: true,\n });\n }\n logger.info(`Backup written to ${prettyPath(backupPath)}.`);\n }\n\n // ── Step 3: delete host-side state ─────────────────────────────\n if (hasYml) {\n await fs.rm(ymlPath, { force: true });\n }\n if (hasEnv) {\n await fs.rm(envPath, { force: true });\n }\n if (hasContainer) {\n try {\n await fs.rm(containerPath, { recursive: true, force: true });\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== 'EACCES' && code !== 'EPERM') {\n throw err;\n }\n // Linux + rootful Docker quirk: bind-mounted service data\n // dirs (postgres, mysql, …) end up owned by the container\n // process's UID — root for the official postgres image. The\n // unprivileged monoceros process can't unlink them.\n //\n // Fall back to docker as the cleanup actor: alpine runs as\n // root, mounts the target dir, deletes everything inside.\n // After that the host-side rm clears the now-empty parent.\n //\n // macOS / Docker Desktop / rootless Docker never hit this\n // branch — the happy fs.rm above succeeds because files are\n // user-owned through the VM / userns layer.\n logger.info(\n `[remove] host-side rm hit ${code} on ${prettyPath(containerPath)}; using a throw-away alpine container to clean root-owned files…`,\n );\n const { exitCode: exit } = await dockerExec([\n 'run',\n '--rm',\n '-v',\n `${containerPath}:/target`,\n 'alpine:3.21',\n 'find',\n '/target',\n '-mindepth',\n '1',\n '-delete',\n ]);\n if (exit !== 0) {\n throw new Error(\n `docker-based cleanup of ${containerPath} exited ${exit}. Inspect with \\`sudo ls -la ${containerPath}\\` and clean manually.`,\n );\n }\n await fs.rm(containerPath, { recursive: true, force: true });\n }\n }\n\n logger.success(\n `Removed '${opts.name}': docker objects gone, container-configs entry deleted, container directory deleted.`,\n );\n if (!backupPath) {\n logger.warn?.(\n 'No backup created (--no-backup). The host-side state is gone for good.',\n );\n }\n\n // Drop the container's Traefik dynamic-config file so a future\n // container with the same yml-name (re-init after remove) starts\n // with a clean slate. No-op when the file is absent.\n try {\n await removeDynamicConfig(opts.name, { monocerosHome: home });\n } catch (err) {\n logger.warn?.(\n `Could not remove Traefik dynamic config for ${opts.name}: ${err instanceof Error ? err.message : String(err)}. Ignored.`,\n );\n }\n\n // Tear down the Traefik singleton if this was the last container\n // attached to its network. See ADR 0007 (variant A — stop and\n // remove are treated identically).\n try {\n await maybeStopProxy({\n ...(opts.proxyDocker ? { docker: opts.proxyDocker } : {}),\n monocerosHome: home,\n logger: { info: (msg) => logger.info(msg), warn: logger.warn },\n });\n } catch (err) {\n logger.warn?.(\n `Could not tear down the Traefik proxy: ${err instanceof Error ? err.message : String(err)}. Ignored.`,\n );\n }\n\n return {\n configPath: hasYml ? ymlPath : null,\n containerPath: hasContainer ? containerPath : null,\n backupPath,\n dockerExitCode,\n };\n}\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runRestore } from '../restore/index.js';\n\nexport const restoreCommand = defineCommand({\n meta: {\n name: 'restore',\n group: 'lifecycle',\n description:\n \"Restore a container's host-side state from a backup written by `monoceros remove`. Copies the yml and the container directory back into $MONOCEROS_HOME. Refuses to overwrite an existing config or container — remove the in-place container first if you need to clobber. Run `monoceros apply <name>` afterwards to bring it back up.\",\n },\n args: {\n 'backup-path': {\n type: 'positional',\n description:\n 'Path to a backup directory (typically `<MONOCEROS_HOME>/container-backups/<name>-<timestamp>/`).',\n required: true,\n },\n },\n async run({ args }) {\n try {\n await runRestore({ backupPath: args['backup-path'] });\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { existsSync, promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { consola } from 'consola';\nimport {\n containerConfigPath,\n containerConfigsDir,\n containerDir,\n containerEnvPath,\n monocerosHome as defaultMonocerosHome,\n prettyPath,\n} from '../config/paths.js';\n\n/**\n * `monoceros restore <backup-path>` — re-instantiate the host-side\n * state of a previously-removed container from a backup written by\n * `monoceros remove`.\n *\n * Backup layout (produced by runRemove):\n *\n * <backup>/<name>.yml ← container-configs source\n * <backup>/container/ ← full container scaffold\n * (home/, projects/, data/, .monoceros/, …)\n *\n * Restore copies both back into `$MONOCEROS_HOME`:\n *\n * $MONOCEROS_HOME/container-configs/<name>.yml\n * $MONOCEROS_HOME/container/<name>/\n *\n * Refuses to clobber an existing config or container dir — the\n * builder must remove the in-place container first (or pick a\n * different target name).\n *\n * Restore does NOT recreate the docker objects: builder runs\n * `monoceros apply <name>` afterwards. That keeps restore a\n * pure filesystem operation, safe to dry-run, with no side-\n * effects on the docker daemon.\n */\n\nexport interface RunRestoreOptions {\n /** Path to a `<MONOCEROS_HOME>/container-backups/<name>-<ts>/` dir. */\n backupPath: string;\n /** Override of the user-data home. Tests inject a tmpdir. */\n monocerosHome?: string;\n logger?: {\n info: (msg: string) => void;\n success: (msg: string) => void;\n };\n}\n\nexport interface RunRestoreResult {\n /** Container name detected from the backup contents. */\n name: string;\n /** Where the yml was restored to. */\n configPath: string;\n /** Where the container directory was restored to (or `null` when\n * the backup didn't carry one — e.g. a remove that ran before any\n * apply had materialized the container dir). */\n containerPath: string | null;\n}\n\nexport async function runRestore(\n opts: RunRestoreOptions,\n): Promise<RunRestoreResult> {\n const home = opts.monocerosHome ?? defaultMonocerosHome();\n const logger = opts.logger ?? {\n info: (msg) => consola.info(msg),\n success: (msg) => consola.success(msg),\n };\n\n const backup = path.resolve(opts.backupPath);\n if (!existsSync(backup)) {\n throw new Error(`Backup not found: ${backup}.`);\n }\n const stat = await fs.stat(backup);\n if (!stat.isDirectory()) {\n throw new Error(`Backup path is not a directory: ${backup}.`);\n }\n\n // Detect the container name from the single `.yml` file in the\n // backup root. runRemove writes `<name>.yml`; we don't depend on\n // the backup-directory name (`<name>-<timestamp>`) because the\n // builder might have renamed/moved the backup folder.\n const entries = await fs.readdir(backup);\n const ymlFiles = entries.filter((f) => f.endsWith('.yml'));\n if (ymlFiles.length === 0) {\n throw new Error(\n `Backup at ${backup} doesn't contain a *.yml — expected a single config file at the root.`,\n );\n }\n if (ymlFiles.length > 1) {\n throw new Error(\n `Backup at ${backup} contains multiple .yml files (${ymlFiles.join(', ')}). Expected exactly one.`,\n );\n }\n const ymlFile = ymlFiles[0]!;\n const name = ymlFile.replace(/\\.yml$/, '');\n\n const containerInBackup = path.join(backup, 'container');\n const hasContainer = existsSync(containerInBackup);\n\n // The env file (values behind the yml's `${VAR}` references) is\n // restored alongside the yml when the backup carries one.\n const envInBackup = path.join(backup, `${name}.env`);\n const hasEnv = existsSync(envInBackup);\n\n // Refuse to overwrite live state.\n const destYml = containerConfigPath(name, home);\n const destContainer = containerDir(name, home);\n if (existsSync(destYml)) {\n throw new Error(\n `Refusing to restore: ${destYml} already exists. Remove the current container first (\\`monoceros remove ${name}\\`) or rename the existing config.`,\n );\n }\n if (hasContainer && existsSync(destContainer)) {\n throw new Error(\n `Refusing to restore: ${destContainer} already exists. Remove the current container first (\\`monoceros remove ${name}\\`).`,\n );\n }\n\n // Copy back into place.\n await fs.mkdir(containerConfigsDir(home), { recursive: true });\n await fs.copyFile(path.join(backup, ymlFile), destYml);\n if (hasEnv) {\n await fs.copyFile(envInBackup, containerEnvPath(name, home));\n }\n if (hasContainer) {\n await fs.cp(containerInBackup, destContainer, { recursive: true });\n }\n\n logger.success(`Restored '${name}' from ${prettyPath(backup)}.`);\n logger.info(\n `Run \\`monoceros apply ${name}\\` to bring the container back up.`,\n );\n\n return {\n name,\n configPath: destYml,\n containerPath: hasContainer ? destContainer : null,\n };\n}\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runRemoveFromUrl } from '../modify/index.js';\n\nexport const removeFromUrlCommand = defineCommand({\n meta: {\n name: 'remove-from-url',\n group: 'edit',\n description:\n 'Remove a previously-added install URL from the container config. Idempotent, prints a diff before writing. The URL is dropped from post-create.sh on the next `monoceros apply`.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n url: {\n type: 'positional',\n description: 'Install URL to remove (must match the original exactly).',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n try {\n const result = await runRemoveFromUrl({\n name: args.name,\n url: args.url,\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runRemoveLanguage } from '../modify/index.js';\n\nexport const removeLanguageCommand = defineCommand({\n meta: {\n name: 'remove-language',\n group: 'edit',\n description:\n 'Remove a language toolchain from the container config. Idempotent, prints a diff before writing.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n language: {\n type: 'positional',\n description: 'Language identifier (e.g. python, java, rust).',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n try {\n const result = await runRemoveLanguage({\n name: args.name,\n language: args.language,\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { getInnerArgs } from '../inner-args.js';\nimport { runRemovePort } from '../modify/index.js';\n\nexport const removePortCommand = defineCommand({\n meta: {\n name: 'remove-port',\n group: 'edit',\n description:\n 'Remove one or more ports from the container config. Pass port numbers after `--` (e.g. `monoceros remove-port sandbox -- 3000 5173`). Idempotent — ports not present are skipped silently.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n const tokens = [...getInnerArgs()];\n if (tokens.length === 0) {\n consola.error(\n 'No ports given. Usage: `monoceros remove-port <containername> [--yes] -- <port> [<port> …]`.',\n );\n process.exit(1);\n }\n try {\n const result = await runRemovePort({\n name: args.name,\n ports: tokens.map(coerceToken),\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n\nfunction coerceToken(raw: string): number {\n const n = Number(raw);\n return Number.isFinite(n) ? n : (raw as unknown as number);\n}\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runRemoveRepo } from '../modify/index.js';\n\nexport const removeRepoCommand = defineCommand({\n meta: {\n name: 'remove-repo',\n group: 'edit',\n description:\n 'Remove a repo from the container config (matches by URL or by its projects/<folder> name). Does NOT delete the existing projects/<folder> directory — local edits are preserved; clean it up manually.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n target: {\n type: 'positional',\n description: 'Repo URL or its projects/<folder> name. Either works.',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n try {\n const result = await runRemoveRepo({\n name: args.name,\n target: args.target,\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runRemoveService } from '../modify/index.js';\n\nexport const removeServiceCommand = defineCommand({\n meta: {\n name: 'remove-service',\n group: 'edit',\n description:\n 'Remove a compose service from the container config. Idempotent, prints a diff before writing. Note: data volumes (e.g. postgres-data) are NOT cleaned up automatically.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n service: {\n type: 'positional',\n description: 'Service identifier (e.g. postgres, redis).',\n required: true,\n },\n yes: {\n type: 'boolean',\n description: 'Skip the interactive confirmation and apply the diff.',\n alias: ['y'],\n default: false,\n },\n },\n async run({ args }) {\n try {\n const result = await runRemoveService({\n name: args.name,\n service: args.service,\n yes: args.yes,\n });\n process.exit(result.status === 'aborted' ? 1 : 0);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { containerDir } from '../config/paths.js';\nimport { runInContainer } from '../devcontainer/run.js';\nimport { getInnerArgs } from '../inner-args.js';\n\nexport const runCommand = defineCommand({\n meta: {\n name: 'run',\n group: 'run',\n description:\n 'Run a one-off command inside the named dev-container. Use `--` to separate monoceros flags from the inner command.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n },\n async run({ args }) {\n const command = [...getInnerArgs()];\n if (command.length === 0) {\n consola.error(\n 'No command provided. Usage: `monoceros run <containername> -- <cmd> [args…]`.',\n );\n process.exit(1);\n }\n try {\n const exitCode = await runInContainer({\n root: containerDir(args.name),\n command,\n });\n process.exit(exitCode);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport { spawnDevcontainer, type DevcontainerSpawn } from './cli.js';\n\nexport interface ShellLogger {\n info: (message: string) => void;\n}\n\nexport interface RunShellOptions {\n /** Container root: `<MONOCEROS_HOME>/container/<name>/`. */\n root: string;\n spawn?: DevcontainerSpawn;\n}\n\nexport async function runShell(opts: RunShellOptions): Promise<number> {\n assertContainerExists(opts.root);\n const spawnFn = opts.spawn ?? spawnDevcontainer;\n\n const upCode = await spawnFn(\n ['up', '--workspace-folder', opts.root, '--mount-workspace-git-root=false'],\n opts.root,\n { quiet: true },\n );\n if (upCode !== 0) return upCode;\n\n return spawnFn(\n [\n 'exec',\n '--workspace-folder',\n opts.root,\n '--mount-workspace-git-root=false',\n 'bash',\n ],\n opts.root,\n { interactive: true },\n );\n}\n\nexport function assertContainerExists(root: string): void {\n if (!existsSync(path.join(root, '.devcontainer'))) {\n throw new Error(\n `No .devcontainer/ at ${root}. Run \\`monoceros apply <name>\\` first.`,\n );\n }\n}\n","import { spawnDevcontainer, type DevcontainerSpawn } from './cli.js';\nimport { assertContainerExists } from './shell.js';\n\nexport interface RunInContainerOptions {\n /** Container root: `<MONOCEROS_HOME>/container/<name>/`. */\n root: string;\n command: string[];\n spawn?: DevcontainerSpawn;\n}\n\n// Run a one-off command inside the named container. Brings the\n// container up if needed (silently — only the inner command's stdio is\n// passed through), then forwards the command verbatim to\n// `devcontainer exec`. The inner command's exit code is propagated.\nexport async function runInContainer(\n opts: RunInContainerOptions,\n): Promise<number> {\n if (opts.command.length === 0) {\n throw new Error(\n 'No command provided. Usage: `monoceros run <containername> -- <cmd> [args…]`.',\n );\n }\n assertContainerExists(opts.root);\n const spawnFn = opts.spawn ?? spawnDevcontainer;\n\n const upCode = await spawnFn(\n ['up', '--workspace-folder', opts.root, '--mount-workspace-git-root=false'],\n opts.root,\n { quiet: true },\n );\n if (upCode !== 0) return upCode;\n\n return spawnFn(\n [\n 'exec',\n '--workspace-folder',\n opts.root,\n '--mount-workspace-git-root=false',\n ...opts.command,\n ],\n opts.root,\n { interactive: true },\n );\n}\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { containerDir } from '../config/paths.js';\nimport { runShell } from '../devcontainer/shell.js';\n\nexport const shellCommand = defineCommand({\n meta: {\n name: 'shell',\n group: 'run',\n description:\n 'Open an interactive bash session inside the named dev-container.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n },\n async run({ args }) {\n try {\n const exitCode = await runShell({ root: containerDir(args.name) });\n process.exit(exitCode);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { proxyHostPort, readMonocerosConfig } from '../config/global.js';\nimport { readConfig } from '../config/io.js';\nimport { containerConfigPath, containerDir } from '../config/paths.js';\nimport { runStart } from '../devcontainer/compose.js';\nimport { ensureProxy } from '../proxy/index.js';\nimport { preflightHostPort } from '../proxy/port-check.js';\nimport { dispatch } from './_dispatch.js';\n\nexport const startCommand = defineCommand({\n meta: {\n name: 'start',\n group: 'run',\n description:\n 'Bring the named dev-container up via `devcontainer up` (workspace + runServices, postCreate, features).',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n },\n run({ args }) {\n return dispatch(async () => {\n // Re-establish the Traefik singleton before bringing the\n // container up when the yml declares ports. The pre-flight\n // host-port check fails hard with an actionable hint if port\n // 80 (or the configured `routing.hostPort`) is held by\n // somebody else; ensureProxy itself is idempotent and safe to\n // call when the proxy is already up. See ADR 0007.\n let needsProxy = false;\n let hostPort = 80;\n try {\n const parsed = await readConfig(containerConfigPath(args.name));\n if ((parsed.config.routing?.ports ?? []).length > 0) {\n needsProxy = true;\n const global = await readMonocerosConfig();\n hostPort = proxyHostPort(global);\n }\n } catch (err) {\n consola.warn(\n `Could not read container yml ahead of start: ${err instanceof Error ? err.message : String(err)}. Skipping Traefik pre-flight.`,\n );\n }\n if (needsProxy) {\n await preflightHostPort(hostPort);\n await ensureProxy({ hostPort });\n }\n return runStart({ root: containerDir(args.name) });\n });\n },\n});\n","import { defineCommand } from 'citty';\nimport { containerDir } from '../config/paths.js';\nimport { runStatus } from '../devcontainer/compose.js';\nimport { dispatch } from './_dispatch.js';\n\nexport const statusCommand = defineCommand({\n meta: {\n name: 'status',\n group: 'run',\n description:\n 'Show whether the compose services for the named dev-container are running.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n service: {\n type: 'string',\n description:\n 'Restrict to a single compose service (e.g. postgres). Defaults to all.',\n },\n },\n run({ args }) {\n return dispatch(() =>\n runStatus({\n root: containerDir(args.name),\n ...(typeof args.service === 'string' ? { service: args.service } : {}),\n }),\n );\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { containerDir } from '../config/paths.js';\nimport { runStop } from '../devcontainer/compose.js';\nimport { maybeStopProxy } from '../proxy/index.js';\nimport { dispatch } from './_dispatch.js';\n\nexport const stopCommand = defineCommand({\n meta: {\n name: 'stop',\n group: 'run',\n description:\n 'Stop the compose services for the named dev-container. Volumes are preserved.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n service: {\n type: 'string',\n description:\n 'Restrict to a single compose service (e.g. postgres). Defaults to all.',\n },\n },\n run({ args }) {\n return dispatch(async () => {\n const exit = await runStop({\n root: containerDir(args.name),\n ...(typeof args.service === 'string' ? { service: args.service } : {}),\n });\n // Tear down the Traefik singleton if this was the last container\n // depending on it. Cheap idempotent call — no-ops when the proxy\n // network is already gone or other containers are still attached.\n // See ADR 0007 (variant A: stop and remove treated identically).\n try {\n await maybeStopProxy({\n logger: { info: (msg) => consola.info(msg) },\n });\n } catch (err) {\n consola.warn(\n `Could not tear down the Traefik proxy: ${err instanceof Error ? err.message : String(err)}. Ignored.`,\n );\n }\n return exit;\n });\n },\n});\n","import { defineCommand } from 'citty';\nimport { consola } from 'consola';\nimport { runTunnel } from '../tunnel/run.js';\n\nexport const tunnelCommand = defineCommand({\n meta: {\n name: 'tunnel',\n group: 'discovery',\n description:\n 'Open a TCP tunnel from the host to a service or port inside the container. Foreground process — Ctrl+C closes the tunnel. Pass a service name (e.g. `postgres`, `mysql`, `redis`) from the container yml or a bare in-container port number. See ADR 0009.',\n },\n args: {\n name: {\n type: 'positional',\n description:\n 'Container name (yml in $MONOCEROS_HOME/container-configs/).',\n required: true,\n },\n target: {\n type: 'positional',\n description:\n 'Service name from the container yml (e.g. `postgres`), `service:port` for an explicit in-container port (e.g. `rustfs:9001`), or a bare in-container port number → workspace (e.g. `8080`).',\n required: true,\n },\n 'local-port': {\n type: 'string',\n description:\n 'Host port the tunnel listens on. Default: same as the internal port (e.g. postgres → 5432). Pass a different value when the default is busy.',\n },\n 'local-address': {\n type: 'string',\n description:\n 'Host interface the tunnel binds to. Default: 127.0.0.1 (loopback only — same machine). Pass 0.0.0.0 to expose on all interfaces (LAN, other devices on the same network).',\n },\n },\n async run({ args }) {\n try {\n const localPort = parseLocalPort(args['local-port']);\n const exitCode = await runTunnel({\n name: args.name,\n target: args.target,\n ...(localPort !== undefined ? { localPort } : {}),\n ...(args['local-address']\n ? { localAddress: args['local-address'] }\n : {}),\n });\n process.exit(exitCode);\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n },\n});\n\nfunction parseLocalPort(raw: string | undefined): number | undefined {\n if (raw === undefined) return undefined;\n const n = Number(raw);\n if (!Number.isInteger(n) || n <= 0 || n >= 65536) {\n throw new Error(\n `Invalid --local-port '${raw}': must be an integer between 1 and 65535.`,\n );\n }\n return n;\n}\n","import { spawn, type ChildProcess } from 'node:child_process';\nimport { consola } from 'consola';\nimport {\n resolveTunnelTarget,\n type ResolveOptions,\n type ResolvedTarget,\n} from './resolve.js';\nimport { preflightLocalPort } from './port-check.js';\n\nexport const SOCAT_IMAGE = 'alpine/socat:1.8.0.3';\n\nexport interface RunTunnelOptions {\n name: string;\n target: string;\n localPort?: number;\n localAddress?: string;\n monocerosHome?: string;\n /** Override the docker spawn (tests inject a fake). */\n dockerSpawn?: DockerSpawn;\n /** Override target resolution (tests inject a fake). */\n resolve?: (opts: ResolveOptions) => Promise<ResolvedTarget>;\n /** Override local-port pre-flight (tests inject a fake). */\n preflight?: typeof preflightLocalPort;\n /** Override the SIGINT install — tests skip it so the suite isn't muted. */\n installSignalHandler?: (handler: () => void) => () => void;\n logger?: TunnelLogger;\n}\n\nexport interface TunnelLogger {\n info: (message: string) => void;\n warn?: (message: string) => void;\n}\n\n/**\n * Spawn `docker run alpine/socat …` in the foreground. Returns the\n * child's exit code (0 on Ctrl+C/clean shutdown, non-zero on error).\n *\n * Why a custom DockerSpawn instead of the proxy/DockerExec used\n * elsewhere: tunnel is a long-running foreground process, not a one-\n * shot inspect. We need stdio: 'inherit' so the user sees socat's\n * own log lines and Ctrl+C reaches docker via the terminal's process\n * group. The DockerExec shape buffers stdout/stderr — wrong shape\n * for this use.\n */\nexport type DockerSpawn = (args: string[]) => DockerSpawnHandle;\n\nexport interface DockerSpawnHandle {\n /** Resolves with the child's exit code after the process exits. */\n exited: Promise<number>;\n /** Forward a signal to the child. No-op if already exited. */\n kill: (signal: NodeJS.Signals) => void;\n}\n\nconst defaultDockerSpawn: DockerSpawn = (args) => {\n const child: ChildProcess = spawn('docker', args, {\n stdio: 'inherit',\n });\n const exited = new Promise<number>((resolve, reject) => {\n child.on('error', reject);\n child.on('exit', (code, signal) => {\n if (typeof code === 'number') resolve(code);\n else if (signal) resolve(128 + signalNumber(signal));\n else resolve(0);\n });\n });\n return {\n exited,\n kill: (signal) => {\n try {\n child.kill(signal);\n } catch {\n /* already exited */\n }\n },\n };\n};\n\nfunction signalNumber(signal: NodeJS.Signals): number {\n // Sufficient subset — anything else collapses to \"1\".\n switch (signal) {\n case 'SIGINT':\n return 2;\n case 'SIGTERM':\n return 15;\n default:\n return 1;\n }\n}\n\n/**\n * Default SIGINT install: swallow the first SIGINT so Node doesn't\n * tear down ahead of the docker child. The terminal's process-group\n * SIGINT still reaches docker run, which catches it and triggers\n * container teardown. We just wait for `exited`.\n */\nconst installSigintDefault = (handler: () => void): (() => void) => {\n process.on('SIGINT', handler);\n return () => process.off('SIGINT', handler);\n};\n\nconst DEFAULT_LOCAL_ADDRESS = '127.0.0.1';\n\nexport async function runTunnel(opts: RunTunnelOptions): Promise<number> {\n const log: TunnelLogger = opts.logger ?? {\n info: (m) => consola.info(m),\n warn: (m) => consola.warn(m),\n };\n\n const resolve = opts.resolve ?? resolveTunnelTarget;\n const resolveArgs: ResolveOptions = {\n name: opts.name,\n target: opts.target,\n ...(opts.monocerosHome !== undefined\n ? { monocerosHome: opts.monocerosHome }\n : {}),\n };\n const resolved = await resolve(resolveArgs);\n\n const localPort = opts.localPort ?? resolved.internalPort;\n const localAddress = opts.localAddress ?? DEFAULT_LOCAL_ADDRESS;\n validateLocalAddress(localAddress);\n\n const preflight = opts.preflight ?? preflightLocalPort;\n await preflight({ port: localPort, address: localAddress });\n\n const dockerSpawn = opts.dockerSpawn ?? defaultDockerSpawn;\n const installSignalHandler =\n opts.installSignalHandler ?? installSigintDefault;\n\n const dockerArgs = buildDockerArgs({\n localAddress,\n localPort,\n internalPort: resolved.internalPort,\n network: resolved.network,\n targetHost: resolved.targetHost,\n });\n\n log.info(\n `Tunnel: ${localAddress}:${localPort} → ${resolved.display}:${resolved.internalPort} (Ctrl+C to stop)`,\n );\n\n const handle = dockerSpawn(dockerArgs);\n const uninstall = installSignalHandler(() => {\n // Swallow — let docker run handle the signal via the terminal's\n // process group. We just wait for `exited`.\n });\n try {\n const exitCode = await handle.exited;\n // docker run reports 130 on SIGINT (128 + 2). Treat that as a\n // clean user-initiated stop, not an error.\n if (exitCode === 130) return 0;\n return exitCode;\n } finally {\n uninstall();\n }\n}\n\nexport interface BuildDockerArgsInput {\n localAddress: string;\n localPort: number;\n internalPort: number;\n network: string;\n targetHost: string;\n}\n\nexport function buildDockerArgs(input: BuildDockerArgsInput): string[] {\n return [\n 'run',\n '--rm',\n '-i',\n `--network=${input.network}`,\n '-p',\n `${input.localAddress}:${input.localPort}:${input.internalPort}`,\n SOCAT_IMAGE,\n `TCP-LISTEN:${input.internalPort},fork,reuseaddr`,\n `TCP:${input.targetHost}:${input.internalPort}`,\n ];\n}\n\nconst IPV4_RE = /^(\\d{1,3}\\.){3}\\d{1,3}$/;\n\nfunction validateLocalAddress(addr: string): void {\n // Two common forms we want to accept verbatim: dotted-quad IPv4\n // (including 127.0.0.1 and 0.0.0.0) and the docker-recognised\n // localhost alias. Anything else is rejected — IPv6 isn't supported\n // by the `-p` flag form we emit, and arbitrary hostnames here are\n // a foot-gun (docker won't resolve them for `-p` mappings).\n if (addr === 'localhost') return;\n if (IPV4_RE.test(addr)) {\n for (const part of addr.split('.')) {\n const n = Number(part);\n if (n < 0 || n > 255) {\n throw new Error(\n `Invalid --local-address '${addr}': each dotted-quad octet must be 0-255.`,\n );\n }\n }\n return;\n }\n throw new Error(\n `Invalid --local-address '${addr}'. Use 127.0.0.1 (default), 0.0.0.0, or a specific IPv4 address.`,\n );\n}\n","import { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport { containerConfigPath, containerDir } from '../config/paths.js';\nimport { readConfig } from '../config/io.js';\nimport { composeProjectName } from '../devcontainer/compose.js';\nimport { resolveService } from '../create/catalog.js';\nimport type { ResolvedService } from '../create/types.js';\nimport {\n defaultDockerExec,\n PROXY_NETWORK_NAME,\n type DockerExec,\n} from '../proxy/index.js';\nimport type { SolutionConfig } from '../config/schema.js';\n\n/**\n * Resolved tunnel target — exactly what `docker run --network=… …\n * TCP:<host>:<port>` needs. The caller (run.ts) doesn't have to know\n * about compose-vs-image-mode or how IP lookups work; that's all\n * compressed into these three fields.\n */\nexport interface ResolvedTarget {\n /** Docker network the socat sidecar will join. */\n network: string;\n /** DNS name or IP address the socat sidecar forwards to. */\n targetHost: string;\n /** In-container port the target service listens on. */\n internalPort: number;\n /** Pretty label for the startup banner (\"hello/postgres\" or \"hello:8080\"). */\n display: string;\n}\n\nexport interface ResolveOptions {\n /** Container name (yml in $MONOCEROS_HOME/container-configs/). */\n name: string;\n /** The raw second positional from the CLI — service name or port string. */\n target: string;\n /** Override the resolved MONOCEROS_HOME (tests inject a tmpdir). */\n monocerosHome?: string;\n /** Docker exec (tests inject a fake). */\n docker?: DockerExec;\n}\n\n/**\n * Resolve `monoceros tunnel <name> <target>` to a concrete docker\n * network + target host + internal port. Throws Error with an\n * actionable message on any unresolvable case (unknown service,\n * non-numeric port, container not running, …) — the caller surfaces\n * the message and exits 1.\n *\n * See ADR 0009 for the full topology table.\n */\nexport async function resolveTunnelTarget(\n opts: ResolveOptions,\n): Promise<ResolvedTarget> {\n const ymlPath = containerConfigPath(opts.name, opts.monocerosHome);\n if (!existsSync(ymlPath)) {\n throw new Error(\n `No yml profile for '${opts.name}' at ${ymlPath}. Run \\`monoceros init ${opts.name}\\` first.`,\n );\n }\n const parsed = await readConfig(ymlPath);\n const config = parsed.config;\n\n const containerRoot = containerDir(opts.name, opts.monocerosHome);\n if (!existsSync(containerRoot)) {\n throw new Error(\n `Container '${opts.name}' is not materialised at ${containerRoot}. Run \\`monoceros apply ${opts.name}\\` first.`,\n );\n }\n\n const composePath = path.join(containerRoot, '.devcontainer', 'compose.yaml');\n const isCompose = existsSync(composePath);\n\n const parsedTarget = parseTargetArg(opts.target, config);\n const docker = opts.docker ?? defaultDockerExec;\n\n if (isCompose) {\n return resolveCompose({\n name: opts.name,\n containerRoot,\n parsedTarget,\n });\n }\n return resolveImageMode({\n name: opts.name,\n containerRoot,\n parsedTarget,\n config,\n docker,\n });\n}\n\ninterface ParsedService {\n kind: 'service';\n service: string;\n port: number;\n}\n\ninterface ParsedPort {\n kind: 'port';\n port: number;\n}\n\ntype ParsedTarget = ParsedService | ParsedPort;\n\nfunction parseTargetArg(raw: string, config: SolutionConfig): ParsedTarget {\n // Explicit `<service>:<port>` — forward a configured service on a\n // specific in-container port. Lets you reach a second port (e.g. a\n // console UI on 9001) even if the service declares a different / no\n // `port:`. Service names never contain ':' (schema), so the colon\n // unambiguously splits name from port.\n const colon = raw.indexOf(':');\n if (colon > 0) {\n const name = raw.slice(0, colon);\n const port = Number(raw.slice(colon + 1));\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new Error(\n `Invalid target '${raw}'. Use <service>:<port> with a numeric port (1–65535), a bare port number, or a configured service name.`,\n );\n }\n findConfiguredService(config, name); // throws if not configured\n return { kind: 'service', service: name, port };\n }\n\n const asNumber = Number(raw);\n if (Number.isInteger(asNumber) && asNumber > 0 && asNumber < 65536) {\n return { kind: 'port', port: asNumber };\n }\n\n // Bare service name. The port comes from the resolved entry — an\n // object's `port:` or a curated service's catalog default.\n const match = findConfiguredService(config, raw);\n if (match.port === undefined) {\n throw new Error(\n `Service '${raw}' declares no port, so tunnel can't know what to forward. ` +\n `Add \\`port: <n>\\` to the service in the yml and re-apply, or pass one explicitly: ` +\n `\\`monoceros tunnel <name> ${raw}:<port>\\`.`,\n );\n }\n return { kind: 'service', service: raw, port: match.port };\n}\n\n/**\n * Resolve a service name against the container yml (curated string or\n * explicit object both count). Throws an actionable error listing the\n * configured services when the name isn't one of them.\n */\nfunction findConfiguredService(\n config: SolutionConfig,\n name: string,\n): ResolvedService {\n const services = config.services.map(resolveService);\n const match = services.find((s) => s.name === name);\n if (!match) {\n const names = services.map((s) => s.name);\n const list = names.length > 0 ? names.join(', ') : '(none configured)';\n throw new Error(\n `Service '${name}' is not configured in this container's yml. Configured services: ${list}. Or pass a port number (e.g. \\`monoceros tunnel <name> 8080\\`).`,\n );\n }\n return match;\n}\n\nfunction resolveCompose(args: {\n name: string;\n containerRoot: string;\n parsedTarget: ParsedTarget;\n}): ResolvedTarget {\n const network = `${composeProjectName(args.containerRoot)}_default`;\n if (args.parsedTarget.kind === 'service') {\n return {\n network,\n targetHost: args.parsedTarget.service,\n internalPort: args.parsedTarget.port,\n display: `${args.name}/${args.parsedTarget.service}:${args.parsedTarget.port}`,\n };\n }\n return {\n network,\n targetHost: 'workspace',\n internalPort: args.parsedTarget.port,\n display: `${args.name}:${args.parsedTarget.port}`,\n };\n}\n\nasync function resolveImageMode(args: {\n name: string;\n containerRoot: string;\n parsedTarget: ParsedTarget;\n config: SolutionConfig;\n docker: DockerExec;\n}): Promise<ResolvedTarget> {\n if (args.parsedTarget.kind === 'service') {\n // Services live in compose; if the container is image-mode, the\n // declared service is nonsense — refuse early instead of letting\n // socat hang on a name that nowhere resolves.\n throw new Error(\n `Service '${args.parsedTarget.service}' is declared in the yml but '${args.name}' is image-mode (no compose.yaml). Services need compose mode — re-apply with at least one \\`services:\\` entry to get a compose setup.`,\n );\n }\n\n // Image-mode + port: prefer monoceros-proxy network when the yml\n // declares routing.ports (the container then has a stable alias on\n // it). Fall back to the container's bridge IP otherwise.\n const ports = args.config.routing?.ports ?? [];\n if (ports.length > 0) {\n return {\n network: PROXY_NETWORK_NAME,\n targetHost: args.name,\n internalPort: args.parsedTarget.port,\n display: `${args.name}:${args.parsedTarget.port}`,\n };\n }\n\n const { network, ip } = await lookupContainerNetwork({\n containerRoot: args.containerRoot,\n docker: args.docker,\n });\n return {\n network,\n targetHost: ip,\n internalPort: args.parsedTarget.port,\n display: `${args.name}:${args.parsedTarget.port}`,\n };\n}\n\ninterface NetworkLookup {\n network: string;\n ip: string;\n}\n\n/**\n * For image-mode containers without `routing.ports`: find the\n * container's running ID by its `devcontainer.local_folder` label\n * (the same anchor `monoceros remove` uses), then pick the first\n * network with a usable IP and return both. Socat joins that network\n * and dials the IP directly — no DNS available on the default bridge.\n *\n * Restarts of the target invalidate the IP and break the tunnel; the\n * builder reruns `monoceros tunnel`. Acceptable for the ad-hoc use\n * case this resolver covers (see ADR 0009 — image-mode-without-ports\n * is the \"notlösung\" path).\n */\nasync function lookupContainerNetwork(args: {\n containerRoot: string;\n docker: DockerExec;\n}): Promise<NetworkLookup> {\n const psResult = await args.docker([\n 'ps',\n '-q',\n '--filter',\n `label=devcontainer.local_folder=${args.containerRoot}`,\n ]);\n if (psResult.exitCode !== 0) {\n throw new Error(\n `docker ps failed: ${psResult.stderr.trim() || `exit ${psResult.exitCode}`}`,\n );\n }\n const containerId = psResult.stdout.trim().split('\\n')[0]?.trim();\n if (!containerId) {\n throw new Error(\n `No running container for '${args.containerRoot}'. Start it with \\`monoceros start <name>\\` (or open a shell with \\`monoceros shell <name>\\`) and retry.`,\n );\n }\n const inspect = await args.docker([\n 'inspect',\n '--format',\n '{{json .NetworkSettings.Networks}}',\n containerId,\n ]);\n if (inspect.exitCode !== 0) {\n throw new Error(\n `docker inspect failed: ${inspect.stderr.trim() || `exit ${inspect.exitCode}`}`,\n );\n }\n let networks: Record<string, { IPAddress?: string }> | null = null;\n try {\n networks = JSON.parse(inspect.stdout) as Record<\n string,\n { IPAddress?: string }\n >;\n } catch {\n throw new Error(\n `Unexpected docker inspect output: ${inspect.stdout.slice(0, 200)}`,\n );\n }\n if (!networks) {\n throw new Error(\n `Container ${containerId} reports no networks. Restart it and retry.`,\n );\n }\n for (const [name, settings] of Object.entries(networks)) {\n if (settings.IPAddress && settings.IPAddress.length > 0) {\n return { network: name, ip: settings.IPAddress };\n }\n }\n throw new Error(\n `Container ${containerId} has no network with a reachable IP. Restart it and retry.`,\n );\n}\n","import { Socket } from 'node:net';\n\n/**\n * TCP-connect probe for the *local* host port the tunnel sidecar will\n * bind. Mirrors `proxy/port-check.ts` — same reasoning (bind probes\n * trip EACCES on privileged ports under unprivileged Node; connect\n * probes don't) — but stripped down: tunnels have no \"already-mine\"\n * skip case (every invocation is a fresh sidecar).\n *\n * Throws Error with an actionable message when something's listening\n * on `port`. The message names the port and points at `--local-port`\n * as the override.\n */\n\nexport type PortProbe = (\n port: number,\n address: string,\n) => Promise<PortProbeResult>;\n\nexport type PortProbeResult =\n | { ok: true }\n | { ok: false; code: string; message: string };\n\nconst CONNECT_TIMEOUT_MS = 750;\n\nconst realPortProbe: PortProbe = (port, address) => {\n // For 0.0.0.0 (any-interface) bindings, probing loopback is the\n // realistic conflict surface — anything LISTEN'ing on 0.0.0.0 or\n // 127.0.0.1 will collide with our `-p 0.0.0.0:<port>:…` mapping.\n const probeHost = address === '0.0.0.0' ? '127.0.0.1' : address;\n return new Promise((resolve) => {\n const socket = new Socket();\n let settled = false;\n const settle = (result: PortProbeResult) => {\n if (settled) return;\n settled = true;\n socket.destroy();\n resolve(result);\n };\n socket.setTimeout(CONNECT_TIMEOUT_MS);\n socket.once('connect', () => {\n settle({\n ok: false,\n code: 'EADDRINUSE',\n message: `another process is listening on ${port}`,\n });\n });\n socket.once('timeout', () => {\n settle({ ok: true });\n });\n socket.once('error', (err: NodeJS.ErrnoException) => {\n const code = err.code ?? 'UNKNOWN';\n if (code === 'ECONNREFUSED') {\n settle({ ok: true });\n } else {\n settle({ ok: false, code, message: err.message });\n }\n });\n socket.connect(port, probeHost);\n });\n};\n\nexport interface PreflightLocalPortOptions {\n port: number;\n address: string;\n /** Tests inject a stub probe. */\n probe?: PortProbe;\n}\n\nexport async function preflightLocalPort(\n opts: PreflightLocalPortOptions,\n): Promise<void> {\n const probe = opts.probe ?? realPortProbe;\n const result = await probe(opts.port, opts.address);\n if (result.ok) return;\n throw new Error(formatLocalPortHeldError(opts.port, opts.address, result));\n}\n\nfunction formatLocalPortHeldError(\n port: number,\n address: string,\n result: Extract<PortProbeResult, { ok: false }>,\n): string {\n const lines: string[] = [];\n if (result.code === 'EADDRINUSE') {\n lines.push(`Local port ${port} on ${address} is already in use.`);\n lines.push('');\n lines.push('Identify the holder, then either stop it or pick a different');\n lines.push('port for the tunnel:');\n lines.push('');\n lines.push(` sudo lsof -iTCP:${port} -sTCP:LISTEN -n -P`);\n lines.push(` # or: sudo ss -tlnp | grep \":${port}\\\\b\"`);\n lines.push('');\n lines.push('Re-run with an explicit local port:');\n lines.push(` monoceros tunnel … --local-port=${port + 1}`);\n } else {\n lines.push(\n `Cannot probe local port ${port} on ${address}: ${result.message}`,\n );\n lines.push('');\n lines.push(\n 'Most likely the host network stack (firewall, namespace) is interfering.',\n );\n lines.push('Try a different local port via `--local-port=<n>`.');\n }\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["entry","main","path","defineCommand","consola","fs","path","entry","existsSync","path","existsSync","readFileSync","path","path","existsSync","readFileSync","fs","path","ANSI_BOLD","ANSI_UNDERLINE","ANSI_CYAN","ANSI_GREY","ANSI_RESET","ANSI_RE","ANSI_RE","isTty","ANSI_RESET","ANSI_BOLD","ANSI_UNDERLINE","ANSI_CYAN","ANSI_GREY","bold","underline","cyan","cyan","path","fs","cyan","spawn","fs","z","parseDocument","z","fs","parseDocument","existsSync","fs","path","z","z","existsSync","fs","entry","path","spawn","fs","path","spawn","path","fs","fs","path","fs","path","entry","existsSync","readFileSync","fs","path","APT_PACKAGE_NAME_RE","FEATURE_REF_RE","INSTALL_URL_RE","REPO_URL_RE","REPO_PATH_RE","entry","path","existsSync","readFileSync","fs","isMap","Pair","parseDocument","Scalar","YAMLMap","isMap","parseDocument","YAMLMap","Scalar","Pair","entry","path","path","entry","containerName","fs","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","existsSync","fs","consola","fs","path","path","fs","entry","path","path","Writable","Writable","cyan","spawn","existsSync","path","consola","spawn","readFileSync","path","Transform","Transform","readFileSync","path","child","spawn","spawn","path","existsSync","consola","spawn","spawn","cyan","spawn","fs","path","consola","consola","existsSync","override","fs","cyan","entry","consola","defineCommand","defineCommand","defineCommand","existsSync","fs","path","path","existsSync","fs","defineCommand","defineCommand","consola","existsSync","fs","path","consola","consola","existsSync","fs","path","defineCommand","consola","defineCommand","consola","defineCommand","consola","isTty","first","defineCommand","defineCommand","defineCommand","consola","consola","isTty","defineCommand","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","existsSync","fs","path","consola","consola","existsSync","path","fs","defineCommand","consola","defineCommand","consola","existsSync","fs","path","consola","consola","path","existsSync","fs","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","coerceToken","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","existsSync","path","existsSync","path","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","consola","defineCommand","defineCommand","defineCommand","consola","defineCommand","consola","defineCommand","consola","spawn","consola","existsSync","path","existsSync","path","resolveCompose","Socket","CONNECT_TIMEOUT_MS","realPortProbe","spawn","consola","resolveArgs","defineCommand","consola","defineCommand"]}