@getmonoceros/workbench 1.7.2 → 1.7.4
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 +47 -29
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
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/global.ts","../src/proxy/index.ts","../src/proxy/dynamic.ts","../src/proxy/port-check.ts","../src/create/catalog.ts","../src/create/scaffold.ts","../src/util/ref.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/util/format.ts","../src/devcontainer/compose.ts","../src/util/mask-secrets.ts","../src/devcontainer/cli.ts","../src/devcontainer/credentials.ts","../src/devcontainer/repo-reachability.ts","../src/devcontainer/docker-mode.ts","../src/devcontainer/identity.ts","../src/version.ts","../src/commands/_dispatch.ts","../src/commands/completion.ts","../src/commands/init.ts","../src/init/index.ts","../src/init/components.ts","../src/init/generator.ts","../src/init/manifest.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"],"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 */\nfunction alignTable(rows: Array<[string, string]>, indent: string): string {\n if (rows.length === 0) return '';\n const labelWidth = 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(labelWidth - visibleLen(left));\n const wrapped = wrapText(right, descWidth, continuationIndent);\n return `${indent}${left}${pad}${gutter}${wrapped}`;\n })\n .join('\\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 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 lines.push(alignTable(rows, ''));\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 { 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 { 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 completion: completionCommand,\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 type { Document } from 'yaml';\nimport { parseConfig, readConfig, stringifyConfig } from '../config/io.js';\nimport {\n containerConfigPath,\n monocerosHome as defaultMonocerosHome,\n} from '../config/paths.js';\nimport { proxyHostPort, readMonocerosConfig } from '../config/global.js';\nimport {\n KNOWN_PROVIDER_HOSTS,\n PROVIDER_VALUES,\n REGEX,\n portNumber,\n type RepoProvider,\n} from '../config/schema.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 SERVICE_CATALOG,\n knownLanguages,\n knownServices,\n} from '../create/catalog.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 addServiceToDoc,\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}\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\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 function runAddService(input: AddServiceInput): Promise<ModifyResult> {\n if (!SERVICE_CATALOG[input.service]) {\n throw new Error(\n `Unknown service: ${input.service}. Known: ${knownServices().join(', ')}.`,\n );\n }\n return mutate(input, (doc) => addServiceToDoc(doc, input.service));\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 // --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 return mutate(input, (doc) => addRepoToDoc(doc, entry));\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 function runAddFeature(input: AddFeatureInput): Promise<ModifyResult> {\n const ref = input.ref.trim();\n if (ref.length === 0) {\n throw new Error(\n 'Missing feature ref. Usage: monoceros add-feature <containername> <ref>.',\n );\n }\n return mutate(input, (doc) => addFeatureToDoc(doc, ref, input.options ?? {}));\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 function runRemoveFeature(\n input: RemoveFeatureInput,\n): Promise<ModifyResult> {\n const ref = input.ref.trim();\n if (ref.length === 0) {\n throw new Error(\n 'Missing feature ref. Usage: monoceros remove-feature <containername> <ref>.',\n );\n }\n return mutate(input, (doc) => removeFeatureFromDoc(doc, 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 // 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\nexport const FeatureOptionValueSchema = z.union([\n z.string(),\n z.number(),\n z.boolean(),\n]);\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\nexport const GitUserSchema = z.object({\n name: z.string().min(1),\n email: z\n .string()\n .min(3)\n .regex(/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/, 'Invalid email'),\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 in scope — see ADR 0006.',\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\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(z.string().min(1)).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 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\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 { promises as fs } from 'node:fs';\nimport { z } from 'zod';\nimport { parseDocument } from 'yaml';\nimport { FeatureOptionValueSchema, GitUserSchema, REGEX } 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 user: GitUserSchema.optional(),\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","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 { createServer } 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 bind the port via Node's net.createServer\n * and immediately release. Bind success ⇒ free. EADDRINUSE ⇒\n * held by something we don't control; throw a clear error.\n *\n * The bind probe is plumbed through `PortProbe` so tests can inject\n * 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 realPortProbe: PortProbe = (port) => {\n return new Promise((resolve) => {\n const server = createServer();\n server.unref();\n server.once('error', (err: NodeJS.ErrnoException) => {\n resolve({\n ok: false,\n code: err.code ?? 'UNKNOWN',\n message: err.message,\n });\n });\n server.once('listening', () => {\n server.close(() => resolve({ ok: true }));\n });\n // Bind on 0.0.0.0 — same address Docker's -p mapping reserves.\n // Binding only on 127.0.0.1 wouldn't catch the case where another\n // process holds the same port via 0.0.0.0.\n server.listen(port, '0.0.0.0');\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 bind host port ${hostPort}: ${systemMessage}`);\n lines.push('');\n if (code === 'EACCES') {\n lines.push(`Port ${hostPort} is a privileged port (<1024) and your`);\n lines.push(`current Docker setup can't bind it. For rootful Docker`);\n lines.push(`(what Monoceros requires) this should normally work —`);\n lines.push(`check that the docker daemon is running as root.`);\n lines.push('');\n }\n lines.push('You can also 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\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 env?: Readonly<Record<string, string>>;\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\n// The literal `monoceros` user/password/db on the service entries\n// below is a deliberate dev-only convention, not a secret. The\n// services are only reachable from inside the workspace container\n// (no host port mapping), and the value is hardcoded into the\n// catalog + docs so any builder running this workbench knows the\n// connection string at a glance:\n//\n// postgresql://monoceros:monoceros@postgres:5432/monoceros\n// mysql://monoceros:monoceros@mysql:3306/monoceros\n//\n// Because it isn't a secret, the secret-masking layer\n// (util/mask-secrets.ts) doesn't and shouldn't mask it. Builders\n// who want a real password should either:\n// - run their own DB outside the workbench and configure it via\n// `externalServices.postgres: postgresql://…` in the container\n// yml, OR\n// - swap to a per-container generated password — open issue when\n// this becomes a real need.\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 // 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 },\n mysql: {\n id: 'mysql',\n image: 'mysql:8',\n env: {\n MYSQL_ROOT_PASSWORD: 'monoceros',\n MYSQL_DATABASE: 'monoceros',\n },\n dataMount: '/var/lib/mysql',\n },\n redis: {\n id: 'redis',\n image: 'redis:8',\n dataMount: '/data',\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","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 SERVICE_CATALOG,\n knownLanguages,\n knownServices,\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 for (const svc of opts.services) {\n if (!SERVICE_CATALOG[svc]) {\n throw new Error(\n `Unknown service: ${svc}. Known: ${knownServices().join(', ')}.`,\n );\n }\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 let services = [...new Set(opts.services)].sort();\n if (opts.postgresUrl) {\n services = services.filter((s) => s !== 'postgres');\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 // Override of the workspace bind-mount. Set only when the host\n // runs rootless Docker — we append `idmap` so the kernel applies\n // the user-namespace mapping to the mount, which makes files\n // written by either side appear with sane UIDs on the other.\n // Without this, host-pre-created `projects/` appears as root in\n // the container and the non-root `node` user can't write into it.\n workspaceMount?: 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 ? { runServices: opts.services } : {}),\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 // No workspaceMount override today — see the comment above about\n // the reverted idmap attempt. Once we have a working rootless\n // strategy, the override comes back here.\n const workspaceMountField = {};\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// 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 svcId of opts.services) {\n const def = SERVICE_CATALOG[svcId];\n if (!def) continue;\n lines.push(` ${def.id}:`);\n lines.push(` image: ${def.image}`);\n if (def.env) {\n lines.push(' environment:');\n for (const [k, v] of Object.entries(def.env)) {\n lines.push(` ${k}: ${v}`);\n }\n }\n if (def.dataMount) {\n // Per-service data dir bind-mounted from the host so DB content\n // is visible at `<container-dir>/data/<svc>/`. See ADR 0003 for\n // the per-container state-model this slots into. Pre-created in\n // writeScaffold so docker doesn't auto-mkdir as root.\n lines.push(' volumes:');\n lines.push(` - ../data/${def.id}:${def.dataMount}`);\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 * 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 so docker bind-mounts onto\n // an existing host path (and doesn't auto-mkdir as root, which\n // breaks postgres/mysql first-run on Linux).\n for (const svcId of opts.services) {\n const def = SERVICE_CATALOG[svcId];\n if (def?.dataMount) {\n await fs.mkdir(path.join(dataDir, def.id), { 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 await fs.writeFile(\n path.join(targetDir, `${opts.name}.code-workspace`),\n JSON.stringify(buildCodeWorkspaceJson(opts), null, 2) + '\\n',\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 { type Document, isMap, isScalar, isSeq, YAMLMap, YAMLSeq } from 'yaml';\nimport type { FeatureOptions, RepoEntry } from '../create/types.js';\nimport { deriveRepoName } from '../create/scaffold.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\nexport function addServiceToDoc(doc: Document, service: string): boolean {\n const seq = ensureSeq(doc, 'services');\n if (seq.items.some((i) => scalarValue(i) === service)) return false;\n seq.add(service);\n return true;\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 * 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 */\nexport function addFeatureToDoc(\n doc: Document,\n ref: string,\n options: FeatureOptions = {},\n): boolean {\n const seq = ensureSeq(doc, 'features');\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 ${ref} is already configured with different options. Remove it first (\\`monoceros remove-feature ${ref}\\`) 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 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 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 if (repo.path !== deriveRepoName(repo.url)) {\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 seq.add(entry);\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 return removeScalarFromSeq(doc, 'services', service);\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 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 'Devcontainer feature ref (OCI image style, 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 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 compose service (postgres, mysql, redis, …) 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 service: {\n type: 'positional',\n description: 'Service identifier (postgres, mysql, 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 runAddService({\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 { 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 { readConfig } from '../config/io.js';\nimport {\n containerConfigPath,\n containerDir,\n monocerosHome as defaultMonocerosHome,\n prettyPath,\n} from '../config/paths.js';\nimport { REGEX } 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 {\n type ComposeSpawn,\n runContainerCycle,\n} from '../devcontainer/compose.js';\nimport {\n type CredentialsSpawn,\n collectGitCredentials,\n uniqueHttpsHosts,\n formatMissingCredentialsError,\n formatUnknownProviderError,\n} from '../devcontainer/credentials.js';\nimport {\n type ReachabilitySpawn,\n checkRepoReachability,\n formatUnreachableReposError,\n} from '../devcontainer/repo-reachability.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 IdentitySpawn,\n} from '../devcontainer/identity.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 cleanupSpawn?: ComposeSpawn;\n devcontainerSpawn?: DevcontainerSpawn;\n credentialsSpawn?: CredentialsSpawn;\n reachabilitySpawn?: ReachabilitySpawn;\n dockerInfoSpawn?: DockerInfoSpawn;\n identitySpawn?: IdentitySpawn;\n identityPrompt?: IdentityPrompt;\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 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 await collectGitIdentity(targetDir, {\n ...(opts.identitySpawn ? { spawn: opts.identitySpawn } : {}),\n ...(opts.identityPrompt ? { prompt: opts.identityPrompt } : {}),\n ...(parsed.config.git?.user\n ? { containerOverride: parsed.config.git.user }\n : {}),\n ...(globalConfig?.defaults?.git?.user\n ? { defaults: globalConfig.defaults.git.user }\n : {}),\n logger: idLogger,\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 // Pre-flight stage 2: now that credentials are in place, probe each\n // declared repo URL via host-side `git ls-remote`. Catches the\n // \"repo doesn't exist / token can't see it / DNS broken\" failure\n // modes before the docker build runs — saving ~1–2 min on first\n // apply and replacing the noisy devcontainer-cli stack trace with\n // a focused per-repo error.\n const declaredRepos = createOpts.repos ?? [];\n if (declaredRepos.length > 0) {\n const reachability = await checkRepoReachability(declaredRepos, {\n ...(opts.reachabilitySpawn ? { spawn: opts.reachabilitySpawn } : {}),\n });\n const unreachable = reachability.filter((r) => !r.ok);\n if (unreachable.length > 0) {\n throw new Error(formatUnreachableReposError(unreachable));\n }\n }\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 // ── 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.cleanupSpawn !== undefined\n ? { cleanupSpawn: opts.cleanupSpawn }\n : {}),\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","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 { 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 featureRecord[entry.ref] = { ...defaults, ...(entry.options ?? {}) };\n }\n\n const result: CreateOptions = {\n name: config.name,\n languages: [...config.languages],\n services: [...config.services],\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 ...(r.git?.user\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","// 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';\nimport { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport { consola } from 'consola';\nimport { createSecretMaskStream } from '../util/mask-secrets.js';\nimport { spawnDevcontainer, type DevcontainerSpawn } from './cli.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// Generic shell spawn used by `monoceros apply`/`remove` for label-\n// based docker cleanup pipelines. Same ComposeSpawn shape so tests\n// can inject a fake; `args[0]` is `-c`, `args[1]` is the shell\n// command string. Output goes through the secret masker for the\n// same reasons spawnDockerCompose does.\nexport const spawnBash: ComposeSpawn = (args, cwd) => {\n return new Promise((resolve, reject) => {\n const child = spawn('bash', 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\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 cleanupSpawn?: ComposeSpawn;\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 const cleanupSpawn = opts.cleanupSpawn ?? spawnBash;\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 script = [\n `set -u`,\n `echo \"[cleanup] checking project ${projectName}…\"`,\n `by_label=$(docker ps -aq --filter \"label=com.docker.compose.project=${projectName}\" 2>/dev/null || true)`,\n `by_name=$(docker ps -aq --filter \"name=^${projectName}-\" 2>/dev/null || true)`,\n `to_remove=$(printf \"%s\\\\n%s\\\\n\" \"$by_label\" \"$by_name\" | sort -u | grep -v \"^$\" || true)`,\n `if [ -n \"$to_remove\" ]; then echo \"[cleanup] removing: $(echo $to_remove | tr \"\\\\n\" \" \")\"; docker rm -f $to_remove >/dev/null || true; else echo \"[cleanup] no containers to remove\"; fi`,\n `docker network rm ${projectName}_default 2>/dev/null && echo \"[cleanup] network ${projectName}_default removed\" || echo \"[cleanup] network ${projectName}_default not present\"`,\n `remaining_label=$(docker ps -aq --filter \"label=com.docker.compose.project=${projectName}\" 2>/dev/null || true)`,\n `remaining_name=$(docker ps -aq --filter \"name=^${projectName}-\" 2>/dev/null || true)`,\n `if [ -n \"$remaining_label\" ] || [ -n \"$remaining_name\" ]; then echo \"\" >&2; echo \"ERROR: containers under project ${projectName} reappeared after removal.\" >&2; echo \"This typically means VS Code's Remote Containers extension is connected to\" >&2; echo \"this devcontainer and auto-recreated it. Close the dev container session\" >&2; echo \"in VS Code (Cmd+Shift+P → 'Dev Containers: Close Remote Connection')\" >&2; echo \"and retry \\\\\\`monoceros apply\\\\\\`.\" >&2; exit 1; fi`,\n `echo \"[cleanup] done\"`,\n ].join('; ');\n const cleanupCode = await cleanupSpawn(['-c', script], root);\n if (cleanupCode !== 0) return cleanupCode;\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';\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 `--with` stdio pipes 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 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","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 } 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 // GIT_ASKPASS='' / SSH_ASKPASS='' are belt-and-suspenders for\n // setups where a GUI askpass helper is configured globally —\n // emptying them prevents a popup that would also block apply.\n const child = spawn('git', ['credential', 'fill'], {\n stdio: ['pipe', 'pipe', 'inherit'],\n env: {\n ...process.env,\n GIT_TERMINAL_PROMPT: '0',\n GIT_ASKPASS: '',\n SSH_ASKPASS: '',\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 * 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 */\nfunction installCommandForOS(opts: {\n brew: string;\n winget: 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.\n */\n linuxBrew?: string;\n linuxDocsUrl: string;\n}): string {\n switch (process.platform) {\n case 'darwin':\n return cyan(opts.brew);\n case 'win32':\n return cyan(opts.winget);\n default:\n if (opts.linuxBrew) return cyan(opts.linuxBrew);\n return `See ${opts.linuxDocsUrl} for package instructions.`;\n }\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\"). Use the same brew command\n // on macOS AND Linux for symmetry with the GitLab hint — keeps\n // the \"all OSes look the same\" promise we made when designing\n // the provider hints. Winget on Windows for users who don't go\n // through WSL; docs URL only as the absolute last resort.\n const install = installCommandForOS({\n brew: 'brew install gh',\n winget: 'winget install --id GitHub.cli',\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\" — so we use the same brew command on macOS AND Linux,\n // with winget on Windows and a docs link as the absolute last\n // resort.\n const install = installCommandForOS({\n brew: 'brew install glab',\n winget: 'winget install --id GLab.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 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 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\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","import { spawn } from 'node:child_process';\nimport { cyan } from '../util/format.js';\n\n/**\n * Apply pre-flight stage 2: after credentials have been collected,\n * verify host-side that each declared repo URL actually resolves and\n * the stored credentials can read from it.\n *\n * The idea: `git ls-remote <url>` is a one-roundtrip probe that\n * exercises exactly the same auth path as the in-container `git clone`\n * would. If it succeeds host-side, the clone inside the container\n * will succeed too (we write the same credentials into\n * `.monoceros/git-credentials`). If it fails — repo doesn't exist,\n * token is wrong, host unreachable — we surface a per-repo error\n * BEFORE the docker build runs, saving 1–2 min of build time on\n * first apply and avoiding a noisy devcontainer-cli stack trace\n * for what's really just a typo in the URL or a missing token scope.\n *\n * This runs AFTER the credential pre-flight (`collectGitCredentials`).\n * Order matters: a missing-creds error wants a provider-specific\n * setup hint (gh / glab / Atlassian token), a present-but-wrong-creds\n * error wants a \"regenerate / fix scope\" hint. The stage-1 check\n * catches the first; this stage-2 check catches the rest.\n */\n\n/**\n * Spawn signature for `git ls-remote <url>`. Returns stdout+stderr\n * plus exit code. Injected by tests. stdout is empty on success\n * (we don't care about the ref list, just whether the call worked).\n */\nexport type ReachabilitySpawn = (url: string) => Promise<{\n stdout: string;\n stderr: string;\n exitCode: number;\n}>;\n\nconst realGitLsRemote: ReachabilitySpawn = (url) => {\n return new Promise((resolve, reject) => {\n // Same env-var hardening as credentials.ts: prevent any kind of\n // interactive prompt (terminal, GUI askpass) so the pre-flight\n // never hangs and always returns a useful exit code + stderr.\n const child = spawn('git', ['ls-remote', '--heads', '--', url], {\n stdio: ['ignore', 'pipe', 'pipe'],\n env: {\n ...process.env,\n GIT_TERMINAL_PROMPT: '0',\n GIT_ASKPASS: '',\n SSH_ASKPASS: '',\n },\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 * Categorization of a reachability failure. Drives the per-host hint\n * in the consolidated error message. Patterns were observed across\n * GitHub, GitLab, and Bitbucket Cloud responses to non-existent /\n * unauthorized repos. We err on the side of grouping rather than\n * fine-grained kinds — the actionable advice is largely the same\n * within each kind.\n */\nexport type ReachabilityFailureKind =\n | 'not-found-or-no-access'\n | 'auth-failed'\n | 'dns'\n | 'unknown';\n\nexport interface RepoReachabilityStatus {\n url: string;\n ok: boolean;\n kind?: ReachabilityFailureKind;\n /** Raw stderr from git, trimmed. Empty when ok. */\n detail: string;\n}\n\n/**\n * Classify a non-zero `git ls-remote` failure by stderr content.\n * Patterns are matched case-insensitively against the union of\n * substrings each provider tends to emit. Order matters: DNS errors\n * sometimes also produce \"Authentication failed\" follow-up lines on\n * some platforms, so we check DNS first.\n */\nfunction classifyStderr(stderr: string): ReachabilityFailureKind {\n const s = stderr.toLowerCase();\n if (\n s.includes('could not resolve host') ||\n s.includes('name or service not known') ||\n s.includes('temporary failure in name resolution') ||\n s.includes('no address associated with hostname')\n ) {\n return 'dns';\n }\n if (\n s.includes('repository not found') ||\n s.includes('may not have access') ||\n s.includes('no longer exists') ||\n s.includes(\"don't have permission\") ||\n s.includes('could not be found') ||\n s.includes('the requested url returned error: 404')\n ) {\n return 'not-found-or-no-access';\n }\n if (\n s.includes('authentication failed') ||\n s.includes('could not read username') ||\n s.includes('incorrect username or password') ||\n s.includes('the requested url returned error: 401') ||\n s.includes('the requested url returned error: 403')\n ) {\n return 'auth-failed';\n }\n return 'unknown';\n}\n\n/**\n * Probe each declared repo URL via host-side `git ls-remote`. Runs\n * sequentially (not parallel) so the output order matches the yml\n * order — easier to reason about when multiple repos fail, and the\n * total time is bounded by ~200 ms per repo against typical SaaS\n * hosts. Spawn-injected for tests.\n */\nexport async function checkRepoReachability(\n repos: readonly { url: string }[],\n options: { spawn?: ReachabilitySpawn } = {},\n): Promise<RepoReachabilityStatus[]> {\n const spawnFn = options.spawn ?? realGitLsRemote;\n const results: RepoReachabilityStatus[] = [];\n for (const repo of repos) {\n // Only HTTPS URLs reach this code path (schema enforces it; pre-\n // flight already filtered). Skip belt-and-suspenders is in\n // credentials.ts — here we trust the input.\n let result: Awaited<ReturnType<ReachabilitySpawn>>;\n try {\n result = await spawnFn(repo.url);\n } catch (err) {\n results.push({\n url: repo.url,\n ok: false,\n kind: 'unknown',\n detail: err instanceof Error ? err.message : String(err),\n });\n continue;\n }\n if (result.exitCode === 0) {\n results.push({ url: repo.url, ok: true, detail: '' });\n continue;\n }\n results.push({\n url: repo.url,\n ok: false,\n kind: classifyStderr(result.stderr),\n detail: result.stderr.trim(),\n });\n }\n return results;\n}\n\n/**\n * Render the consolidated pre-flight error for repos that couldn't\n * be reached. Groups failures by kind so each kind's actionable\n * advice appears once, with the failing URLs listed underneath.\n *\n * Layout:\n *\n * Cannot reach <N> declared repo(s):\n *\n * Repository not found (or your credentials don't grant access):\n * • https://...\n * • https://...\n * - <actionable advice>\n * - <actionable advice>\n *\n * Authentication failed:\n * • https://...\n * - <actionable advice>\n *\n * Then re-run `monoceros apply`.\n */\nexport function formatUnreachableReposError(\n failures: readonly RepoReachabilityStatus[],\n): string {\n const byKind = new Map<ReachabilityFailureKind, RepoReachabilityStatus[]>();\n for (const f of failures) {\n const kind = f.kind ?? 'unknown';\n const list = byKind.get(kind) ?? [];\n list.push(f);\n byKind.set(kind, list);\n }\n const totalUrls = failures.length;\n const lines: string[] = [\n totalUrls === 1\n ? `Cannot reach declared repo: ${failures[0]!.url}`\n : `Cannot reach ${totalUrls} declared repos:`,\n '',\n ];\n\n const sectionOrder: ReachabilityFailureKind[] = [\n 'not-found-or-no-access',\n 'auth-failed',\n 'dns',\n 'unknown',\n ];\n for (const kind of sectionOrder) {\n const entries = byKind.get(kind);\n if (!entries || entries.length === 0) continue;\n lines.push(headerForKind(kind));\n for (const e of entries) {\n lines.push(` • ${e.url}`);\n }\n for (const advice of adviceForKind(kind)) {\n lines.push(` - ${advice}`);\n }\n lines.push('');\n }\n lines.push(`Then re-run ${cyan('monoceros apply')}.`);\n return lines.join('\\n');\n}\n\nfunction headerForKind(kind: ReachabilityFailureKind): string {\n switch (kind) {\n case 'not-found-or-no-access':\n return \"Repository not found (or your credentials don't grant access):\";\n case 'auth-failed':\n return 'Authentication failed (credentials are present but rejected):';\n case 'dns':\n return \"Host unreachable (DNS / VPN / offline — git couldn't resolve the hostname):\";\n case 'unknown':\n return 'Unrecognised git error:';\n }\n}\n\nfunction adviceForKind(kind: ReachabilityFailureKind): string[] {\n switch (kind) {\n case 'not-found-or-no-access':\n return [\n 'Re-check the URL for typos (case-sensitive on most hosts).',\n 'Verify the repo still exists / is not archived in a way that hides it.',\n 'Ensure your token covers this org / workspace and has read scope (GitHub: `repo`; GitLab: `read_repository`; Bitbucket: repo read).',\n ];\n case 'auth-failed':\n return [\n 'Token may be expired or revoked — regenerate it from the provider UI.',\n 'Re-run the provider CLI login (gh auth login / glab auth login) — Monoceros picks up the refreshed token on the next apply.',\n ];\n case 'dns':\n return [\n 'Check your internet / VPN — corporate Git hosts often require VPN.',\n 'Verify the hostname spelling in the yml.',\n ];\n case 'unknown':\n return [\n 'Run `git ls-remote <url>` manually on the host to see the raw error.',\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. Background: see ${cyan('docs/docker-on-linux.md')} in`,\n `the workbench repo.`,\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\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\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 * 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\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 */\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 spawnFn = options.spawn ?? realGitConfigGet;\n const promptFn = options.prompt ?? realIdentityPrompt;\n const logger = options.logger ?? { info: () => {}, warn: () => {} };\n\n const existing = await readExistingGitconfig(gitconfigPath);\n\n // Resolution order per key:\n // 1. containerOverride (yml's `git.user`)\n // 2. defaults (monoceros-config.yml's `defaults.git.user`)\n // 3. host `git config --global --get <key>`\n // 4. previously persisted value (.monoceros/gitconfig)\n // 5. interactive prompt (skipped in non-TTY contexts)\n const name = await resolveKey('user.name', {\n override: options.containerOverride?.name,\n defaultValue: options.defaults?.name,\n spawnFn,\n persistedValue: existing.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: existing.email,\n promptFn,\n logger,\n });\n\n const lines: string[] = ['[user]'];\n if (name !== undefined) lines.push(`\\tname = ${name}`);\n if (email !== undefined) lines.push(`\\temail = ${email}`);\n\n await fs.mkdir(gitconfigDir, { recursive: true });\n await fs.writeFile(gitconfigPath, lines.join('\\n') + '\\n');\n\n return {\n ...(name !== undefined ? { name } : {}),\n ...(email !== undefined ? { email } : {}),\n gitconfigPath,\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\nasync function resolveKey(\n key: 'user.name' | 'user.email',\n opts: ResolveKeyOpts,\n): Promise<string | undefined> {\n if (opts.override !== undefined && opts.override.length > 0) {\n return opts.override;\n }\n if (opts.defaultValue !== undefined && opts.defaultValue.length > 0) {\n return opts.defaultValue;\n }\n const hostValue = await readKeyFromHost(opts.spawnFn, key, opts.logger);\n if (hostValue !== undefined) return hostValue;\n if (opts.persistedValue !== undefined && opts.persistedValue.length > 0) {\n return opts.persistedValue;\n }\n const prompted = await opts.promptFn(key);\n if (prompted !== undefined) return prompted;\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 or zsh to stdout. The user redirects it into a file their\n * shell loads at startup.\n *\n * Both scripts complete:\n * - subcommand names at position 1\n * - container names (read from `<MONOCEROS_HOME>/container-configs/`)\n * for the second positional of every command that takes a\n * `<NAME>` argument referring to an existing container — i.e.\n * everything *except* `init` (which expects a fresh name) and the\n * verb-only commands like `list-components` / `completion`.\n *\n * MONOCEROS_HOME respects the same precedence as the CLI itself: env\n * var first, then `$HOME/.monoceros`. Container-name completion in\n * the workbench-checkout dev environment looks at the env var if set;\n * otherwise it falls back to `~/.monoceros/`, which matches the\n * global-install case. A contributor who wants dev-container names\n * completed sets `MONOCEROS_HOME=$PWD/.local` in their shell.\n *\n * Install:\n * bash: monoceros completion bash > ~/.bash_completion.d/monoceros\n * (or any path your shell sources; `source` it from .bashrc)\n * zsh: monoceros completion zsh > \"${fpath[1]}/_monoceros\"\n * (after ensuring compinit is active)\n */\n\n// Keep these arrays in sync with main.ts. Single source of truth\n// would be nice but adds startup cost — citty's subCommands aren't\n// trivial to enumerate from a static context. Tests guard the\n// list in completion.test.ts.\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 'completion',\n] as const;\n\n// Commands whose first positional is an existing container name.\n// Everything else either takes no positional (`list-components`,\n// `completion`) or expects a fresh name (`init`, `restore`).\nconst COMMANDS_WITH_CONTAINER_ARG = [\n 'shell',\n 'run',\n 'logs',\n 'start',\n 'stop',\n 'status',\n 'apply',\n 'remove',\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] as const;\n\nconst SHELLS = ['bash', 'zsh', 'pwsh'] as const;\ntype Shell = (typeof SHELLS)[number];\n\nexport function renderCompletionScript(shell: Shell): string {\n const commands = ALL_COMMANDS.join(' ');\n const containerCommandsRegex = COMMANDS_WITH_CONTAINER_ARG.join('|');\n\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 '_monoceros() {',\n ' local cur prev cmd home configs_dir names',\n ' cur=\"${COMP_WORDS[COMP_CWORD]}\"',\n '',\n ' if [[ $COMP_CWORD -eq 1 ]]; then',\n ` COMPREPLY=( $(compgen -W \"${commands}\" -- \"$cur\") )`,\n ' return',\n ' fi',\n '',\n ' cmd=\"${COMP_WORDS[1]}\"',\n ' if [[ $COMP_CWORD -eq 2 ]]; then',\n ' case \"$cmd\" in',\n ` ${containerCommandsRegex})`,\n ' home=\"${MONOCEROS_HOME:-$HOME/.monoceros}\"',\n ' configs_dir=\"$home/container-configs\"',\n ' if [[ -d \"$configs_dir\" ]]; then',\n ` names=$(cd \"$configs_dir\" && ls *.yml 2>/dev/null | sed 's/\\\\.yml$//')`,\n ' COMPREPLY=( $(compgen -W \"$names\" -- \"$cur\") )',\n ' fi',\n ' ;;',\n ' completion)',\n ` COMPREPLY=( $(compgen -W \"${SHELLS.join(' ')}\" -- \"$cur\") )`,\n ' ;;',\n ' esac',\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 'Register-ArgumentCompleter -Native -CommandName monoceros -ScriptBlock {',\n ' param($wordToComplete, $commandAst, $cursorPosition)',\n '',\n ' $commands = @(',\n ...ALL_COMMANDS.map((c) => ` '${c}'`),\n ' )',\n ` $shells = @('${SHELLS.join(\"', '\")}')`,\n ' $containerCommands = @(',\n ...COMMANDS_WITH_CONTAINER_ARG.map((c) => ` '${c}'`),\n ' )',\n '',\n ' $tokens = $commandAst.CommandElements',\n ' $position = $tokens.Count',\n ' if ($wordToComplete) { $position-- }',\n '',\n ' if ($position -eq 1) {',\n ' $commands | Where-Object { $_ -like \"$wordToComplete*\" } |',\n ' ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, \"ParameterValue\", $_) }',\n ' return',\n ' }',\n '',\n ' if ($position -eq 2) {',\n ' $cmd = $tokens[1].Value',\n ' if ($containerCommands -contains $cmd) {',\n ' $home = if ($env:MONOCEROS_HOME) { $env:MONOCEROS_HOME } else { Join-Path $env:USERPROFILE \".monoceros\" }',\n ' $configsDir = Join-Path $home \"container-configs\"',\n ' if (Test-Path $configsDir) {',\n ' Get-ChildItem -Path $configsDir -Filter \"*.yml\" |',\n ' ForEach-Object { $_.BaseName } |',\n ' Where-Object { $_ -like \"$wordToComplete*\" } |',\n ' ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, \"ParameterValue\", $_) }',\n ' }',\n ' } elseif ($cmd -eq \"completion\") {',\n ' $shells | Where-Object { $_ -like \"$wordToComplete*\" } |',\n ' ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, \"ParameterValue\", $_) }',\n ' }',\n ' }',\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 '_monoceros() {',\n ' local -a commands shells',\n ' commands=(',\n ...ALL_COMMANDS.map((c) => ` '${c}'`),\n ' )',\n ` shells=(${SHELLS.map((s) => `'${s}'`).join(' ')})`,\n '',\n ' if (( CURRENT == 2 )); then',\n \" _describe 'monoceros command' commands\",\n ' return',\n ' fi',\n '',\n ' local cmd=${words[2]}',\n ' if (( CURRENT == 3 )); then',\n ' case $cmd in',\n ` ${containerCommandsRegex})`,\n ' local home=\"${MONOCEROS_HOME:-$HOME/.monoceros}\"',\n ' local configs_dir=\"$home/container-configs\"',\n ' if [[ -d $configs_dir ]]; then',\n ' local -a names',\n ' names=(${configs_dir}/*.yml(N:t:r))',\n \" _describe 'container' names\",\n ' fi',\n ' ;;',\n ' completion)',\n \" _describe 'shell' shells\",\n ' ;;',\n ' esac',\n ' fi',\n '}',\n '',\n '_monoceros \"$@\"',\n '',\n ].join('\\n');\n}\n\nexport const completionCommand = defineCommand({\n meta: {\n name: 'completion',\n group: 'tooling',\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\n// Exposed for tests so the static command list stays in sync with\n// what main.ts wires up.\nexport const COMPLETION_COMMANDS_FOR_TEST = ALL_COMMANDS;\nexport const COMPLETION_CONTAINER_COMMANDS_FOR_TEST =\n COMMANDS_WITH_CONTAINER_ARG;\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 .local/container-configs/<name>.yml. Without --with, the file is a documented default with every component commented out. With --with=<names>, the named components 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: {\n type: 'string',\n description:\n \"Comma-separated list of component names to compose, e.g. 'node,postgres,github,claude'. Sub-components use a slash, e.g. 'atlassian/twg'. When omitted, init writes a documented default with every catalog component commented out.\",\n required: false,\n },\n 'with-repo': {\n type: 'string',\n description:\n 'Git URL of a repo to clone into projects/ on first apply. Repeatable: pass --with-repo=URL1 --with-repo=URL2 for multiple repos. Folder name derived from URL (foo.git → projects/foo/); use `monoceros add-repo --path=...` post-init for subfolder paths.',\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 withList = collectWithList(args.with, rawArgs);\n const withRepoList = collectWithRepoList(rawArgs);\n const withPortsList = collectWithPortsList(args['with-ports'], rawArgs);\n await runInit({\n name: args.name,\n ...(withList ? { with: withList } : {}),\n ...(withRepoList.length > 0 ? { withRepo: withRepoList } : {}),\n ...(withPortsList && withPortsList.length > 0\n ? { withPorts: withPortsList }\n : {}),\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 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\n/**\n * Collect all `--with-repo=<url>` and `--with-repo <url>` tokens from\n * rawArgs. citty doesn't natively give us repeatable strings (the\n * single `args['with-repo']` only carries the last value), so we\n * scan the original argv. Returns URLs in order of appearance.\n */\nfunction collectWithRepoList(rawArgs: string[]): string[] {\n const urls: string[] = [];\n for (let i = 0; i < rawArgs.length; i += 1) {\n const t = rawArgs[i]!;\n if (t === '--with-repo') {\n const next = rawArgs[i + 1];\n if (typeof next === 'string' && !next.startsWith('-')) {\n urls.push(next);\n i += 1;\n }\n } else if (t.startsWith('--with-repo=')) {\n urls.push(t.slice('--with-repo='.length));\n }\n }\n return urls;\n}\n\n/**\n * Reconstruct the --with list from `args.with` plus any rawArgs\n * tokens that the shell tokenization split off.\n *\n * Background: a user writing\n * monoceros init dummy --with=a, b, c\n * gets shell-tokenized into argv entries:\n * ['init', 'dummy', '--with=a,', 'b,', 'c']\n * citty assigns `args.with = \"a,\"` and the rest float as extra\n * positionals that the `name` arg won't accept. To avoid forcing\n * the user to quote or remove the spaces, we look at rawArgs to\n * find the original --with token and pull in any subsequent non-\n * flag tokens until we hit something that looks like a flag or\n * run out. The collected pieces are joined back with commas and\n * re-split — same parser as before, but now seeing the full list.\n */\nfunction collectWithList(\n withArg: string | undefined,\n rawArgs: string[],\n): string[] | undefined {\n if (typeof withArg !== 'string' || withArg.trim().length === 0) {\n return undefined;\n }\n let combined = withArg.trim();\n // Find where --with starts in rawArgs, then keep eating non-flag\n // tokens. Both forms are supported by citty:\n // --with=value (combined in one token)\n // --with value (two tokens)\n const startIdx = rawArgs.findIndex(\n (t) => t === '--with' || t.startsWith('--with='),\n );\n if (startIdx >= 0) {\n // Skip the with token itself, plus its detached value when\n // `--with` was used in the two-token form.\n let scanFrom = startIdx + 1;\n if (rawArgs[startIdx] === '--with') scanFrom += 1;\n for (let i = scanFrom; i < rawArgs.length; i += 1) {\n const t = rawArgs[i]!;\n if (t.startsWith('--') || t === '-h' || t === '--help') break;\n // Re-join with a comma — the user separated with commas plus\n // (now-eaten) whitespace; comma alone is what our parser wants.\n const sep = combined.endsWith(',') ? '' : ',';\n combined += sep + t;\n }\n }\n const pieces = combined\n .split(',')\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n return pieces.length > 0 ? pieces : undefined;\n}\n","import { existsSync, promises as fs } from 'node:fs';\nimport { consola } from 'consola';\nimport {\n containerConfigPath,\n containerConfigsDir,\n monocerosHome as defaultMonocerosHome,\n workbenchRoot as defaultWorkbenchRoot,\n workbenchCheckoutRoot,\n componentsDir as defaultComponentsDir,\n prettyPath,\n} from '../config/paths.js';\nimport { KNOWN_PROVIDER_HOSTS, REGEX } from '../config/schema.js';\nimport { loadComponentCatalog, resolveComponents } from './components.js';\nimport { generateComposedYml, generateDocumentedYml } from './generator.js';\nimport { loadFeatureManifestSummary } from './manifest.js';\n\n/**\n * `monoceros init <name> [--with=<components>]` — produce a fresh\n * container-config yml at `<MONOCEROS_HOME>/container-configs/<name>.yml`.\n *\n * Two modes:\n *\n * - With `--with=node,postgres,github,claude` (or any comma-list\n * of component names from the catalog): the listed components\n * are merged and the result written as an active, immediately-\n * applyable yml. Per-feature option hints (auth/credentials\n * from the feature manifest) appear as commented lines next to\n * the active options so the builder can see what's available\n * without leaving the file.\n *\n * - Without `--with`: 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 * Component names to compose. When empty/undefined → documented\n * mode (every component commented out). When set → composed mode\n * with exactly these components active.\n */\n with?: 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 // 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 requested = opts.with ?? [];\n if (requested.length === 0) {\n text = generateDocumentedYml(opts.name, catalog, lookup, repos, ports);\n } else {\n const components = resolveComponents(catalog, requested);\n text = generateComposedYml(opts.name, components, lookup, repos, ports);\n }\n\n await fs.mkdir(containerConfigsDir(home), { recursive: true });\n await fs.writeFile(dest, text, 'utf8');\n\n const documented = requested.length === 0;\n const displayPath = prettyPath(dest);\n if (documented) {\n logger.success(\n `Wrote documented default to ${displayPath}. Un-comment what you need, then \\`monoceros apply ${opts.name}\\`.`,\n );\n } else {\n logger.success(\n `Composed ${requested.length} component(s) into ${displayPath}: ${requested.join(', ')}`,\n );\n logger.info(\n `Edit the file if you need to tweak, then \\`monoceros apply ${opts.name}\\`.`,\n );\n }\n\n return { configPath: dest, documented };\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=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=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\nfunction 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 type { Component, ResolvedComponent } from './components.js';\nimport { mergeComponents } from './components.js';\nimport type { FeatureManifestSummary } from './manifest.js';\n\n/**\n * Renderer for the container yml that `monoceros init` produces.\n *\n * Two modes:\n *\n * - **Composed** (`monoceros init <name> --with=node,…`): all\n * listed components are active. The output is a clean,\n * immediately-applyable yml. Per-feature option hints\n * (auth/credentials from the feature manifest) are appended as\n * commented lines beneath the active options block, so a builder\n * reading the yml can see at a glance which keys exist without\n * leaving the file.\n *\n * - **Documented** (`monoceros init <name>` without `--with`):\n * every section is commented out. The output is a self-explaining\n * reference; the builder un-comments what they need and runs\n * `monoceros apply`.\n *\n * Both modes share the per-feature block rendering. The only\n * difference is whether the section is commented out at the top\n * level or not.\n *\n * We hand-render the yml as a string instead of going through the\n * yaml library's AST. The shape is narrow enough that the explicit\n * line-by-line approach is shorter and easier to reason about than\n * juggling Document + Pair + Scalar nodes with attached comments.\n */\n\nexport type ManifestLookup = (\n ref: string,\n) => FeatureManifestSummary | undefined;\n\nconst SCHEMA_HEADER = [\n '# Monoceros solution-config. Edit freely, then run',\n '# `monoceros apply <name>` to materialize a dev-container.',\n '#',\n '# Schema reference: see the workbench `templates/components/README.md`',\n '# and `docs/konzept.md` for what each section does. Each feature',\n '# under `features:` also accepts options not shown here — check',\n \"# the feature's `devcontainer-feature.json` for the full list.\",\n] as const;\n\n/**\n * Render the active-mode yml for the given components.\n */\nexport function generateComposedYml(\n name: string,\n components: ResolvedComponent[],\n lookupManifest: ManifestLookup,\n repoUrls: readonly string[] = [],\n ports: readonly number[] = [],\n): string {\n const merged = mergeComponents(components);\n const lines: string[] = [];\n for (const h of SCHEMA_HEADER) lines.push(h);\n lines.push('');\n lines.push('schemaVersion: 1');\n lines.push(`name: ${name}`);\n lines.push('');\n\n if (merged.languages.length > 0) {\n lines.push('languages:');\n for (const lang of merged.languages) lines.push(` - ${lang}`);\n lines.push('');\n }\n if (merged.services.length > 0) {\n lines.push('services:');\n for (const svc of merged.services) lines.push(` - ${svc}`);\n lines.push('');\n }\n if (merged.features.length > 0) {\n lines.push('features:');\n for (const f of merged.features) {\n renderFeatureBlock(\n lines,\n f,\n lookupManifest(f.ref),\n /* commented */ false,\n );\n }\n lines.push('');\n }\n // Composed mode only emits a repos block when --with-repo provided\n // URLs. The \"show all available options\" requirement applies on a\n // per-entry basis there: each active repo entry carries commented\n // hints for its optional fields. With no URLs, composed mode stays\n // schlank — repos are surfaced via `monoceros add-repo` post-init.\n if (repoUrls.length > 0) {\n renderReposBlock(lines, repoUrls, /* commented */ false);\n }\n // Same convention for ports: active `routing:` block when\n // `--with-ports` provided values, nothing otherwise. Builder runs\n // `monoceros add-port` later if they want it post-init.\n if (ports.length > 0) {\n renderActiveRoutingBlock(lines, name, ports);\n }\n\n return ensureTrailingNewline(lines.join('\\n'));\n}\n\n/**\n * Render the documented-default yml: every component listed but\n * commented out, with section headers carrying short prose so a\n * fresh builder can read the file and figure out what to enable.\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 for (const h of SCHEMA_HEADER) lines.push(h);\n lines.push('#');\n lines.push('# Below is the full set of components shipped with this');\n lines.push('# workbench, every one commented out. Un-comment the lines');\n lines.push('# you want active. The same effect (and a cleaner yml) is');\n lines.push('# achievable by running `monoceros init <name> --with=…`');\n lines.push('# with a comma-separated list of component names.');\n lines.push('');\n lines.push('schemaVersion: 1');\n lines.push(`name: ${name}`);\n lines.push('');\n\n if (byCategory.language.length > 0) {\n const items = byCategory.language.flatMap((c) =>\n (c.file.contributes.languages ?? []).map((lang) => ({\n value: lang,\n label: c.file.displayName,\n })),\n );\n const width = Math.max(...items.map((i) => i.value.length)) + 2;\n lines.push('# Languages — runtime toolchains.');\n lines.push('# languages:');\n for (const item of items) {\n const pad = ' '.repeat(width - item.value.length);\n lines.push(`# - ${item.value}${pad}# ${item.label}`);\n }\n lines.push('');\n }\n if (byCategory.service.length > 0) {\n const items = byCategory.service.flatMap((c) =>\n (c.file.contributes.services ?? []).map((svc) => ({\n value: svc,\n label: c.file.displayName,\n })),\n );\n const width = Math.max(...items.map((i) => i.value.length)) + 2;\n lines.push('# Services — compose-mode siblings of the workspace');\n lines.push('# container (compose mode kicks in as soon as at least');\n lines.push('# one service is active).');\n lines.push('# services:');\n for (const item of items) {\n const pad = ' '.repeat(width - item.value.length);\n lines.push(`# - ${item.value}${pad}# ${item.label}`);\n }\n lines.push('');\n }\n if (byCategory.feature.length > 0) {\n lines.push('# Features — devcontainer features installed inside the');\n lines.push('# container. Each entry has an OCI-style `ref` plus an');\n lines.push('# optional `options` map. Credentials/auth keys appear');\n lines.push('# as commented hints; set them here per container, or');\n lines.push('# globally in monoceros-config.yml under');\n lines.push('# `defaults.features.<ref>`.');\n lines.push('#');\n lines.push('# Catalog:');\n lines.push('#');\n const nameColumnWidth =\n Math.max(...byCategory.feature.map((c) => c.name.length)) + 2;\n for (const c of byCategory.feature) {\n const pad = ' '.repeat(nameColumnWidth - c.name.length);\n lines.push(`# ${c.name}${pad}${c.file.displayName}`);\n }\n lines.push('#');\n lines.push('# Below: one block per feature ref. Un-comment what');\n lines.push(\"# you want active. Sub-components share their parent's\");\n lines.push('# block — pick the parent for the full preset, swap to');\n lines.push('# a sub-component name for a partial install.');\n lines.push('#');\n lines.push('# features:');\n\n // Render one feature block per unique ref. Prefer the top-level\n // component (e.g. `atlassian` over `atlassian/twg`) as the source\n // of the rendered options, since the top-level carries the\n // \"everything on\" default users typically want first.\n const renderedRefs = new Set<string>();\n const topLevel = byCategory.feature.filter((c) => !c.name.includes('/'));\n\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 renderFeatureBlock(\n lines,\n f,\n lookupManifest(f.ref),\n /* commented */ true,\n );\n }\n }\n // Any feature ref only mentioned through a sub-component (no\n // top-level component for the same ref) — render it from the\n // first sub-component.\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 renderFeatureBlock(\n lines,\n f,\n lookupManifest(f.ref),\n /* commented */ true,\n );\n }\n }\n lines.push('');\n }\n // Repos section — always rendered in documented mode (commented out\n // when no --with-repo URLs were provided, active when they were).\n // The block shows the full set of per-entry options so a builder\n // reading the yml sees what's possible without leaving the file —\n // same \"all available options are visible\" rule that drives the\n // features block above.\n renderReposBlock(lines, repoUrls, /* commented */ repoUrls.length === 0);\n\n // Routing — active when --with-ports provided values, otherwise\n // a fully-commented hint block (same \"all options visible\" rule\n // that drives repos and features). `monoceros add-port` activates\n // it later if the builder didn't pass --with-ports.\n if (ports.length > 0) {\n renderActiveRoutingBlock(lines, name, ports);\n } else {\n renderRoutingHintBlock(lines);\n }\n\n return ensureTrailingNewline(lines.join('\\n'));\n}\n\ninterface RenderableFeature {\n ref: string;\n options?: Record<string, string | number | boolean>;\n}\n\n// Target column width for rendered comment lines. Description text\n// and usage notes get word-wrapped against this width so the output\n// stays readable in a standard editor without horizontal scrolling.\nconst COMMENT_WIDTH = 72;\n\nfunction renderFeatureBlock(\n out: string[],\n feature: RenderableFeature,\n summary: FeatureManifestSummary | undefined,\n commented: boolean,\n): void {\n const c = commented ? '# ' : ' ';\n const optionHints = summary?.optionHints ?? [];\n const optionDescriptions = summary?.optionDescriptions ?? {};\n const usageNotes = summary?.usageNotes ?? [];\n\n // Per-feature usage notes — rendered as a wrapped comment block\n // right before the `- ref:` line. Multiple notes are separated\n // by an empty comment line so they read as distinct paragraphs.\n for (let i = 0; i < usageNotes.length; i++) {\n if (i > 0) out.push(`${c}#`);\n for (const line of wrapToComment(\n usageNotes[i]!,\n COMMENT_WIDTH - c.length,\n )) {\n out.push(`${c}# ${line}`);\n }\n }\n\n out.push(`${c}- ref: ${feature.ref}`);\n const options = feature.options ?? {};\n const activeOptions = Object.entries(options);\n const remainingHints = optionHints.filter((h) => !(h in options));\n\n // When there are active options, emit a real `options:` block.\n // When there are only hints (no active options), skip `options:`\n // entirely and emit the hints as plain comments at the same depth\n // — yaml parsers see `options:` with no content under it as\n // `null`, which fails our schema.\n if (activeOptions.length > 0) {\n out.push(`${c} options:`);\n for (const [key, value] of activeOptions) {\n out.push(`${c} ${key}: ${renderScalarValue(value)}`);\n }\n if (remainingHints.length > 0) {\n out.push(\n `${c} # Optional — override monoceros-config.yml defaults.features:`,\n );\n for (const hint of remainingHints) {\n emitHint(out, hint, optionDescriptions[hint], `${c} `);\n }\n }\n } else if (remainingHints.length > 0) {\n out.push(\n `${c} # Optional — override monoceros-config.yml defaults.features:`,\n );\n out.push(`${c} # options:`);\n for (const hint of remainingHints) {\n emitHint(out, hint, optionDescriptions[hint], `${c} # `);\n }\n }\n}\n\n/**\n * Emit a single option-hint line, optionally preceded by its\n * description as wrapped comment lines. `linePrefix` is the full\n * prefix (indent + any commented-out `# ` chars) that every\n * emitted line should start with; the hint itself is suffixed\n * with `: ` so the user can fill in a value.\n */\nfunction emitHint(\n out: string[],\n hint: string,\n description: string | undefined,\n linePrefix: string,\n): void {\n if (description) {\n for (const line of wrapToComment(\n description,\n COMMENT_WIDTH - linePrefix.length,\n )) {\n out.push(`${linePrefix}# ${line}`);\n }\n }\n out.push(`${linePrefix}${hint}:`);\n}\n\n/**\n * Render the `repos:` section. When `commented` is true the entire\n * block is prefixed with `# ` (documented mode, no --with-repo).\n * When false the `url:` lines are active but the optional fields\n * (path, provider, git.user) stay commented per entry so the\n * builder sees what else can go there.\n *\n * The \"all available options visible\" rule: every per-entry field\n * the schema accepts appears on every rendered entry, either active\n * (rare — only `url` is required) or as a commented hint.\n */\nfunction renderReposBlock(\n out: string[],\n urls: readonly string[],\n commented: boolean,\n): void {\n // Prose intro — always a comment block, regardless of whether the\n // section below is active or fully commented.\n out.push('# Repos — git repositories cloned into projects/ during');\n out.push('# post-create. HTTPS-only (ADR 0006). Provider auto-detected');\n out.push('# for github.com, gitlab.com, bitbucket.org; for any other host');\n out.push('# (self-hosted GitLab, Bitbucket Data Center, Gitea/Forgejo,');\n out.push('# GitHub Enterprise, …) declare provider explicitly.');\n out.push('#');\n\n if (commented) {\n // Pure documented mode (no --with-repo): a single example entry,\n // everything commented. Shows the full per-entry surface.\n out.push('# repos:');\n out.push('# - url: https://github.com/<org>/<repo>.git');\n out.push(\n '# # path: <folder> # subfolder under projects/; default: URL-derived',\n );\n out.push(\n '# # provider: github # github | gitlab | bitbucket | gitea',\n );\n out.push(\n '# # git: # per-repo committer identity override',\n );\n out.push('# # user:');\n out.push('# # name: Your Name');\n out.push('# # email: you@example.com');\n out.push('');\n return;\n }\n\n // Active entries from --with-repo. Each entry repeats the\n // commented hints for the optional fields so they're discoverable\n // without docs.\n out.push('repos:');\n for (const url of urls) {\n const derivedPath = deriveDefaultPath(url);\n out.push(` - url: ${url}`);\n out.push(\n ` # path: ${derivedPath} # subfolder under projects/; default: URL-derived (${derivedPath})`,\n );\n out.push(\n ' # provider: github # github | gitlab | bitbucket | gitea',\n );\n out.push(\n ' # git: # per-repo committer identity override',\n );\n out.push(' # user:');\n out.push(' # name: Your Name');\n out.push(' # email: you@example.com');\n }\n out.push('');\n}\n\n/**\n * Render the fully-commented `routing:` hint block. Used in\n * documented-mode init when the builder did not pass `--with-ports`.\n * Shows the full set of fields the schema accepts so the option\n * surface stays discoverable from inside the yml. See ADR 0007.\n */\nfunction renderRoutingHintBlock(out: string[]): void {\n out.push('# Routing — expose container ports to the host through the');\n out.push('# shared Traefik singleton. Once any port is declared the');\n out.push('# container joins the monoceros-proxy network and the proxy');\n out.push('# routes <name>.localhost (default port) and');\n out.push('# <name>-<port>.localhost (explicit). `monoceros add-port`');\n out.push('# manages the list; the block appears on first add. You can');\n out.push('# also pre-seed at init time via `--with-ports=3000,5173,…`.');\n out.push('#');\n out.push('# routing:');\n out.push('# ports: # internal container ports');\n out.push(\n '# - 3000 # first entry doubles as <name>.localhost',\n );\n out.push('# - 5173');\n out.push(\n '# vscodeAutoForward: false # default: false. Traefik is the single',\n );\n out.push(\n '# # source of truth — set true only if you',\n );\n out.push(\n \"# # want VS Code's port panel as primary.\",\n );\n out.push('');\n}\n\n/**\n * Render an active `routing:` block — used when `--with-ports`\n * provided values. First entry is the default (matches what add-port\n * writes, what proxyUrlsFor surfaces under `<name>.localhost`, and\n * what setDefaultPortInDoc moves to position 0). CLI order is\n * preserved.\n *\n * `vscodeAutoForward` is emitted as a commented hint with its\n * default value spelled out — same \"all options visible\" rule that\n * drives the features and repos blocks. Builder removes the `#` to\n * activate.\n */\nfunction renderActiveRoutingBlock(\n out: string[],\n name: string,\n ports: readonly number[],\n): void {\n out.push('# Routing — expose these container ports to the host through');\n out.push('# the shared Traefik singleton. First entry doubles as');\n out.push(`# http://${name}.localhost (the default route). See ADR 0007.`);\n out.push('routing:');\n out.push(' ports:');\n ports.forEach((port, idx) => {\n if (idx === 0) {\n out.push(` - ${port} # default → http://${name}.localhost`);\n } else {\n out.push(` - ${port}`);\n }\n });\n // Commented hint so the builder discovers the toggle without\n // leaving the file. Default `false` makes Traefik the single\n // source of truth — flip to `true` to keep VS Code's port-panel\n // alongside.\n out.push(\" # vscodeAutoForward: false # set true to keep VS Code's\");\n out.push(' # # port-panel alongside Traefik');\n out.push('');\n}\n\n/**\n * URL-derived default for the `path:` hint. Mirrors what\n * `deriveRepoName` in scaffold.ts does at apply time — inlined\n * here to keep the generator self-contained (no cross-module\n * dependency from init/ → create/).\n */\nfunction deriveDefaultPath(url: string): string {\n let last = url;\n const slash = url.lastIndexOf('/');\n if (slash >= 0) last = url.slice(slash + 1);\n if (last.endsWith('.git')) last = last.slice(0, -4);\n return last || 'repo';\n}\n\n/**\n * Word-wrap a single paragraph of plain text to `width` columns.\n * The returned strings do NOT include any prefix — the caller is\n * expected to prepend a comment marker (`# `) and any indent.\n * Long words that exceed `width` are emitted on their own line\n * rather than split mid-word.\n */\nfunction 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\nfunction renderScalarValue(value: string | number | boolean): string {\n if (typeof value === 'string') {\n // Quote anything that could be ambiguous to a yaml parser\n // (leading numerics, special chars). Quoting strings is always\n // safe; we only avoid it for booleans/numbers so they keep\n // their types.\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 // Stable sort by component name for deterministic output.\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 { 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 * - `optionHints` — names of feature options to render as\n * commented-out lines below the active options block, so a\n * builder reading the yml sees at a glance which keys exist\n * without going to the feature's docs.\n * - `optionDescriptions` — the `description` string from each\n * option in the manifest, keyed by option name. Init prints\n * these as a wrapped comment block above the matching hint\n * line so the builder knows what the option does without\n * opening the feature docs.\n * - `usageNotes` — free-text per-feature paragraphs from\n * `x-monoceros.usageNotes`. Init renders them as a comment\n * block right above the `- ref:` line. Use for things that\n * aren't tied to one option — e.g. \"alternative auth flow X\n * works inside the running container\".\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 /** 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 /** Free-text per-feature notes rendered above the `- ref:` line. */\n usageNotes: string[];\n}\n\ninterface RawManifest {\n options?: Record<string, { description?: string }>;\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 if (parsed.options) {\n for (const [key, opt] of Object.entries(parsed.options)) {\n if (\n opt &&\n typeof opt === 'object' &&\n typeof opt.description === 'string' &&\n opt.description.length > 0\n ) {\n optionDescriptions[key] = opt.description;\n }\n }\n }\n\n return { optionHints, optionDescriptions, usageNotes };\n } catch {\n return undefined;\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=…`, 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 (by its 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 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 monocerosHome as defaultMonocerosHome,\n prettyPath,\n} from '../config/paths.js';\nimport { REGEX } from '../config/schema.js';\nimport {\n composeProjectName,\n spawnBash,\n type ComposeSpawn,\n} from '../devcontainer/compose.js';\nimport { maybeStopProxy, type DockerExec } 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 /** Bash spawn for the docker cleanup script. Tests inject a stub. */\n dockerSpawn?: ComposeSpawn;\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 containerPath = containerDir(opts.name, home);\n const hasYml = existsSync(ymlPath);\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 const projectName = composeProjectName(containerPath);\n const dockerSpawn = opts.dockerSpawn ?? spawnBash;\n const script = [\n `set -u`,\n `echo \"[remove] tearing down docker project ${projectName}…\"`,\n // Compose-mode containers, identified by the compose project label.\n `by_label=$(docker ps -aq --filter \"label=com.docker.compose.project=${projectName}\" 2>/dev/null || true)`,\n // Devcontainer-cli containers (image-mode workspace + feature-\n // build intermediates) all carry this label, value = the absolute\n // container-dir path. Most reliable anchor we have, because\n // @devcontainers/cli lets Docker assign random names like\n // 'kind_cerf' — neither the project-name nor the vsc-<name>-\n // prefix filters below catch those.\n `by_dc_label=$(docker ps -aq --filter \"label=devcontainer.local_folder=${containerPath}\" 2>/dev/null || true)`,\n // Container-name prefix fallback (catches half-broken state).\n `by_compose_name=$(docker ps -aq --filter \"name=^${projectName}-\" 2>/dev/null || true)`,\n // Image-mode devcontainer-cli name fallback (only kicks in when\n // the cli used a deterministic name — modern versions don't).\n `by_image_name=$(docker ps -aq --filter \"name=^vsc-${opts.name}-\" 2>/dev/null || true)`,\n `to_remove=$(printf \"%s\\\\n%s\\\\n%s\\\\n%s\\\\n\" \"$by_label\" \"$by_dc_label\" \"$by_compose_name\" \"$by_image_name\" | sort -u | grep -v \"^$\" || true)`,\n `if [ -n \"$to_remove\" ]; then echo \"[remove] removing containers: $(echo $to_remove | tr \"\\\\n\" \" \")\"; docker rm -f $to_remove >/dev/null || true; else echo \"[remove] no containers found\"; fi`,\n `docker network rm ${projectName}_default 2>/dev/null && echo \"[remove] network ${projectName}_default removed\" || true`,\n `echo \"[remove] docker cleanup done\"`,\n ].join('; ');\n const dockerExitCode = await dockerSpawn(['-c', script], home);\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 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 (hasContainer) {\n await fs.rm(containerPath, { recursive: true, force: true });\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 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 // 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 (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"],"mappings":"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","names":["entry","main","path","defineCommand","consola","fs","entry","fs","z","parseDocument","z","fs","parseDocument","fs","path","path","fs","fs","path","fs","path","existsSync","fs","path","APT_PACKAGE_NAME_RE","FEATURE_REF_RE","INSTALL_URL_RE","REPO_URL_RE","REPO_PATH_RE","entry","path","existsSync","fs","entry","path","path","entry","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","ANSI_BOLD","ANSI_UNDERLINE","ANSI_CYAN","ANSI_GREY","ANSI_RESET","isTty","ANSI_RESET","ANSI_BOLD","ANSI_UNDERLINE","ANSI_CYAN","ANSI_GREY","bold","underline","cyan","spawn","existsSync","path","consola","spawn","readFileSync","path","readFileSync","path","child","spawn","spawn","path","existsSync","consola","spawn","fs","path","spawn","cyan","path","fs","cyan","spawn","spawn","cyan","spawn","spawn","cyan","spawn","fs","path","consola","consola","existsSync","fs","cyan","entry","consola","defineCommand","defineCommand","defineCommand","consola","existsSync","fs","consola","existsSync","fs","path","z","z","existsSync","fs","entry","path","existsSync","readFileSync","path","path","existsSync","readFileSync","consola","existsSync","fs","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"]}
|
|
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/global.ts","../src/proxy/index.ts","../src/proxy/dynamic.ts","../src/proxy/port-check.ts","../src/create/catalog.ts","../src/create/scaffold.ts","../src/util/ref.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/util/format.ts","../src/devcontainer/compose.ts","../src/util/mask-secrets.ts","../src/devcontainer/cli.ts","../src/devcontainer/credentials.ts","../src/devcontainer/repo-reachability.ts","../src/devcontainer/docker-mode.ts","../src/devcontainer/identity.ts","../src/version.ts","../src/commands/_dispatch.ts","../src/commands/completion.ts","../src/commands/init.ts","../src/init/index.ts","../src/init/components.ts","../src/init/generator.ts","../src/init/manifest.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"],"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 */\nfunction alignTable(rows: Array<[string, string]>, indent: string): string {\n if (rows.length === 0) return '';\n const labelWidth = 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(labelWidth - visibleLen(left));\n const wrapped = wrapText(right, descWidth, continuationIndent);\n return `${indent}${left}${pad}${gutter}${wrapped}`;\n })\n .join('\\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 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 lines.push(alignTable(rows, ''));\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 { 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 { 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 completion: completionCommand,\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 type { Document } from 'yaml';\nimport { parseConfig, readConfig, stringifyConfig } from '../config/io.js';\nimport {\n containerConfigPath,\n monocerosHome as defaultMonocerosHome,\n} from '../config/paths.js';\nimport { proxyHostPort, readMonocerosConfig } from '../config/global.js';\nimport {\n KNOWN_PROVIDER_HOSTS,\n PROVIDER_VALUES,\n REGEX,\n portNumber,\n type RepoProvider,\n} from '../config/schema.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 SERVICE_CATALOG,\n knownLanguages,\n knownServices,\n} from '../create/catalog.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 addServiceToDoc,\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}\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\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 function runAddService(input: AddServiceInput): Promise<ModifyResult> {\n if (!SERVICE_CATALOG[input.service]) {\n throw new Error(\n `Unknown service: ${input.service}. Known: ${knownServices().join(', ')}.`,\n );\n }\n return mutate(input, (doc) => addServiceToDoc(doc, input.service));\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 // --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 return mutate(input, (doc) => addRepoToDoc(doc, entry));\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 function runAddFeature(input: AddFeatureInput): Promise<ModifyResult> {\n const ref = input.ref.trim();\n if (ref.length === 0) {\n throw new Error(\n 'Missing feature ref. Usage: monoceros add-feature <containername> <ref>.',\n );\n }\n return mutate(input, (doc) => addFeatureToDoc(doc, ref, input.options ?? {}));\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 function runRemoveFeature(\n input: RemoveFeatureInput,\n): Promise<ModifyResult> {\n const ref = input.ref.trim();\n if (ref.length === 0) {\n throw new Error(\n 'Missing feature ref. Usage: monoceros remove-feature <containername> <ref>.',\n );\n }\n return mutate(input, (doc) => removeFeatureFromDoc(doc, 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 // 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\nexport const FeatureOptionValueSchema = z.union([\n z.string(),\n z.number(),\n z.boolean(),\n]);\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\nexport const GitUserSchema = z.object({\n name: z.string().min(1),\n email: z\n .string()\n .min(3)\n .regex(/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/, 'Invalid email'),\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\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(z.string().min(1)).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 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\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 { promises as fs } from 'node:fs';\nimport { z } from 'zod';\nimport { parseDocument } from 'yaml';\nimport { FeatureOptionValueSchema, GitUserSchema, REGEX } 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 user: GitUserSchema.optional(),\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","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\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 env?: Readonly<Record<string, string>>;\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\n// The literal `monoceros` user/password/db on the service entries\n// below is a deliberate dev-only convention, not a secret. The\n// services are only reachable from inside the workspace container\n// (no host port mapping), and the value is hardcoded into the\n// catalog + docs so any builder running this workbench knows the\n// connection string at a glance:\n//\n// postgresql://monoceros:monoceros@postgres:5432/monoceros\n// mysql://monoceros:monoceros@mysql:3306/monoceros\n//\n// Because it isn't a secret, the secret-masking layer\n// (util/mask-secrets.ts) doesn't and shouldn't mask it. Builders\n// who want a real password should either:\n// - run their own DB outside the workbench and configure it via\n// `externalServices.postgres: postgresql://…` in the container\n// yml, OR\n// - swap to a per-container generated password — open issue when\n// this becomes a real need.\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 // 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 },\n mysql: {\n id: 'mysql',\n image: 'mysql:8',\n env: {\n MYSQL_ROOT_PASSWORD: 'monoceros',\n MYSQL_DATABASE: 'monoceros',\n },\n dataMount: '/var/lib/mysql',\n },\n redis: {\n id: 'redis',\n image: 'redis:8',\n dataMount: '/data',\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","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 SERVICE_CATALOG,\n knownLanguages,\n knownServices,\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 for (const svc of opts.services) {\n if (!SERVICE_CATALOG[svc]) {\n throw new Error(\n `Unknown service: ${svc}. Known: ${knownServices().join(', ')}.`,\n );\n }\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 let services = [...new Set(opts.services)].sort();\n if (opts.postgresUrl) {\n services = services.filter((s) => s !== 'postgres');\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 // Override of the workspace bind-mount. Set only when the host\n // runs rootless Docker — we append `idmap` so the kernel applies\n // the user-namespace mapping to the mount, which makes files\n // written by either side appear with sane UIDs on the other.\n // Without this, host-pre-created `projects/` appears as root in\n // the container and the non-root `node` user can't write into it.\n workspaceMount?: 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 ? { runServices: opts.services } : {}),\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 // No workspaceMount override today — see the comment above about\n // the reverted idmap attempt. Once we have a working rootless\n // strategy, the override comes back here.\n const workspaceMountField = {};\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// 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 svcId of opts.services) {\n const def = SERVICE_CATALOG[svcId];\n if (!def) continue;\n lines.push(` ${def.id}:`);\n lines.push(` image: ${def.image}`);\n if (def.env) {\n lines.push(' environment:');\n for (const [k, v] of Object.entries(def.env)) {\n lines.push(` ${k}: ${v}`);\n }\n }\n if (def.dataMount) {\n // Per-service data dir bind-mounted from the host so DB content\n // is visible at `<container-dir>/data/<svc>/`. See ADR 0003 for\n // the per-container state-model this slots into. Pre-created in\n // writeScaffold so docker doesn't auto-mkdir as root.\n lines.push(' volumes:');\n lines.push(` - ../data/${def.id}:${def.dataMount}`);\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 * 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 so docker bind-mounts onto\n // an existing host path (and doesn't auto-mkdir as root, which\n // breaks postgres/mysql first-run on Linux).\n for (const svcId of opts.services) {\n const def = SERVICE_CATALOG[svcId];\n if (def?.dataMount) {\n await fs.mkdir(path.join(dataDir, def.id), { 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 await fs.writeFile(\n path.join(targetDir, `${opts.name}.code-workspace`),\n JSON.stringify(buildCodeWorkspaceJson(opts), null, 2) + '\\n',\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 { type Document, isMap, isScalar, isSeq, YAMLMap, YAMLSeq } from 'yaml';\nimport type { FeatureOptions, RepoEntry } from '../create/types.js';\nimport { deriveRepoName } from '../create/scaffold.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\nexport function addServiceToDoc(doc: Document, service: string): boolean {\n const seq = ensureSeq(doc, 'services');\n if (seq.items.some((i) => scalarValue(i) === service)) return false;\n seq.add(service);\n return true;\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 * 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 */\nexport function addFeatureToDoc(\n doc: Document,\n ref: string,\n options: FeatureOptions = {},\n): boolean {\n const seq = ensureSeq(doc, 'features');\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 ${ref} is already configured with different options. Remove it first (\\`monoceros remove-feature ${ref}\\`) 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 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 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 if (repo.path !== deriveRepoName(repo.url)) {\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 seq.add(entry);\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 return removeScalarFromSeq(doc, 'services', service);\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 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 'Devcontainer feature ref (OCI image style, 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 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 compose service (postgres, mysql, redis, …) 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 service: {\n type: 'positional',\n description: 'Service identifier (postgres, mysql, 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 runAddService({\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 { 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 { readConfig } from '../config/io.js';\nimport {\n containerConfigPath,\n containerDir,\n monocerosHome as defaultMonocerosHome,\n prettyPath,\n} from '../config/paths.js';\nimport { REGEX } 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 {\n type ComposeSpawn,\n runContainerCycle,\n} from '../devcontainer/compose.js';\nimport {\n type CredentialsSpawn,\n collectGitCredentials,\n uniqueHttpsHosts,\n formatMissingCredentialsError,\n formatUnknownProviderError,\n} from '../devcontainer/credentials.js';\nimport {\n type ReachabilitySpawn,\n checkRepoReachability,\n formatUnreachableReposError,\n} from '../devcontainer/repo-reachability.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 IdentitySpawn,\n} from '../devcontainer/identity.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 cleanupSpawn?: ComposeSpawn;\n devcontainerSpawn?: DevcontainerSpawn;\n credentialsSpawn?: CredentialsSpawn;\n reachabilitySpawn?: ReachabilitySpawn;\n dockerInfoSpawn?: DockerInfoSpawn;\n identitySpawn?: IdentitySpawn;\n identityPrompt?: IdentityPrompt;\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 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 await collectGitIdentity(targetDir, {\n ...(opts.identitySpawn ? { spawn: opts.identitySpawn } : {}),\n ...(opts.identityPrompt ? { prompt: opts.identityPrompt } : {}),\n ...(parsed.config.git?.user\n ? { containerOverride: parsed.config.git.user }\n : {}),\n ...(globalConfig?.defaults?.git?.user\n ? { defaults: globalConfig.defaults.git.user }\n : {}),\n logger: idLogger,\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 // Pre-flight stage 2: now that credentials are in place, probe each\n // declared repo URL via host-side `git ls-remote`. Catches the\n // \"repo doesn't exist / token can't see it / DNS broken\" failure\n // modes before the docker build runs — saving ~1–2 min on first\n // apply and replacing the noisy devcontainer-cli stack trace with\n // a focused per-repo error.\n const declaredRepos = createOpts.repos ?? [];\n if (declaredRepos.length > 0) {\n const reachability = await checkRepoReachability(declaredRepos, {\n ...(opts.reachabilitySpawn ? { spawn: opts.reachabilitySpawn } : {}),\n });\n const unreachable = reachability.filter((r) => !r.ok);\n if (unreachable.length > 0) {\n throw new Error(formatUnreachableReposError(unreachable));\n }\n }\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 // ── 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.cleanupSpawn !== undefined\n ? { cleanupSpawn: opts.cleanupSpawn }\n : {}),\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","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 { 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 featureRecord[entry.ref] = { ...defaults, ...(entry.options ?? {}) };\n }\n\n const result: CreateOptions = {\n name: config.name,\n languages: [...config.languages],\n services: [...config.services],\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 ...(r.git?.user\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","// 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';\nimport { existsSync } from 'node:fs';\nimport path from 'node:path';\nimport { consola } from 'consola';\nimport { createSecretMaskStream } from '../util/mask-secrets.js';\nimport { spawnDevcontainer, type DevcontainerSpawn } from './cli.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// Generic shell spawn used by `monoceros apply`/`remove` for label-\n// based docker cleanup pipelines. Same ComposeSpawn shape so tests\n// can inject a fake; `args[0]` is `-c`, `args[1]` is the shell\n// command string. Output goes through the secret masker for the\n// same reasons spawnDockerCompose does.\nexport const spawnBash: ComposeSpawn = (args, cwd) => {\n return new Promise((resolve, reject) => {\n const child = spawn('bash', 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\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 cleanupSpawn?: ComposeSpawn;\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 const cleanupSpawn = opts.cleanupSpawn ?? spawnBash;\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 script = [\n `set -u`,\n `echo \"[cleanup] checking project ${projectName}…\"`,\n `by_label=$(docker ps -aq --filter \"label=com.docker.compose.project=${projectName}\" 2>/dev/null || true)`,\n `by_name=$(docker ps -aq --filter \"name=^${projectName}-\" 2>/dev/null || true)`,\n `to_remove=$(printf \"%s\\\\n%s\\\\n\" \"$by_label\" \"$by_name\" | sort -u | grep -v \"^$\" || true)`,\n `if [ -n \"$to_remove\" ]; then echo \"[cleanup] removing: $(echo $to_remove | tr \"\\\\n\" \" \")\"; docker rm -f $to_remove >/dev/null || true; else echo \"[cleanup] no containers to remove\"; fi`,\n `docker network rm ${projectName}_default 2>/dev/null && echo \"[cleanup] network ${projectName}_default removed\" || echo \"[cleanup] network ${projectName}_default not present\"`,\n `remaining_label=$(docker ps -aq --filter \"label=com.docker.compose.project=${projectName}\" 2>/dev/null || true)`,\n `remaining_name=$(docker ps -aq --filter \"name=^${projectName}-\" 2>/dev/null || true)`,\n `if [ -n \"$remaining_label\" ] || [ -n \"$remaining_name\" ]; then echo \"\" >&2; echo \"ERROR: containers under project ${projectName} reappeared after removal.\" >&2; echo \"This typically means VS Code's Remote Containers extension is connected to\" >&2; echo \"this devcontainer and auto-recreated it. Close the dev container session\" >&2; echo \"in VS Code (Cmd+Shift+P → 'Dev Containers: Close Remote Connection')\" >&2; echo \"and retry \\\\\\`monoceros apply\\\\\\`.\" >&2; exit 1; fi`,\n `echo \"[cleanup] done\"`,\n ].join('; ');\n const cleanupCode = await cleanupSpawn(['-c', script], root);\n if (cleanupCode !== 0) return cleanupCode;\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';\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 `--with` stdio pipes 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 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","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 } 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 // GIT_ASKPASS='' / SSH_ASKPASS='' are belt-and-suspenders for\n // setups where a GUI askpass helper is configured globally —\n // emptying them prevents a popup that would also block apply.\n const child = spawn('git', ['credential', 'fill'], {\n stdio: ['pipe', 'pipe', 'inherit'],\n env: {\n ...process.env,\n GIT_TERMINAL_PROMPT: '0',\n GIT_ASKPASS: '',\n SSH_ASKPASS: '',\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 * 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 */\nfunction installCommandForOS(opts: {\n brew: string;\n winget: 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.\n */\n linuxBrew?: string;\n linuxDocsUrl: string;\n}): string {\n switch (process.platform) {\n case 'darwin':\n return cyan(opts.brew);\n case 'win32':\n return cyan(opts.winget);\n default:\n if (opts.linuxBrew) return cyan(opts.linuxBrew);\n return `See ${opts.linuxDocsUrl} for package instructions.`;\n }\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\"). Use the same brew command\n // on macOS AND Linux for symmetry with the GitLab hint — keeps\n // the \"all OSes look the same\" promise we made when designing\n // the provider hints. Winget on Windows for users who don't go\n // through WSL; docs URL only as the absolute last resort.\n const install = installCommandForOS({\n brew: 'brew install gh',\n winget: 'winget install --id GitHub.cli',\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\" — so we use the same brew command on macOS AND Linux,\n // with winget on Windows and a docs link as the absolute last\n // resort.\n const install = installCommandForOS({\n brew: 'brew install glab',\n winget: 'winget install --id GLab.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 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 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\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","import { spawn } from 'node:child_process';\nimport { cyan } from '../util/format.js';\n\n/**\n * Apply pre-flight stage 2: after credentials have been collected,\n * verify host-side that each declared repo URL actually resolves and\n * the stored credentials can read from it.\n *\n * The idea: `git ls-remote <url>` is a one-roundtrip probe that\n * exercises exactly the same auth path as the in-container `git clone`\n * would. If it succeeds host-side, the clone inside the container\n * will succeed too (we write the same credentials into\n * `.monoceros/git-credentials`). If it fails — repo doesn't exist,\n * token is wrong, host unreachable — we surface a per-repo error\n * BEFORE the docker build runs, saving 1–2 min of build time on\n * first apply and avoiding a noisy devcontainer-cli stack trace\n * for what's really just a typo in the URL or a missing token scope.\n *\n * This runs AFTER the credential pre-flight (`collectGitCredentials`).\n * Order matters: a missing-creds error wants a provider-specific\n * setup hint (gh / glab / Atlassian token), a present-but-wrong-creds\n * error wants a \"regenerate / fix scope\" hint. The stage-1 check\n * catches the first; this stage-2 check catches the rest.\n */\n\n/**\n * Spawn signature for `git ls-remote <url>`. Returns stdout+stderr\n * plus exit code. Injected by tests. stdout is empty on success\n * (we don't care about the ref list, just whether the call worked).\n */\nexport type ReachabilitySpawn = (url: string) => Promise<{\n stdout: string;\n stderr: string;\n exitCode: number;\n}>;\n\nconst realGitLsRemote: ReachabilitySpawn = (url) => {\n return new Promise((resolve, reject) => {\n // Same env-var hardening as credentials.ts: prevent any kind of\n // interactive prompt (terminal, GUI askpass) so the pre-flight\n // never hangs and always returns a useful exit code + stderr.\n const child = spawn('git', ['ls-remote', '--heads', '--', url], {\n stdio: ['ignore', 'pipe', 'pipe'],\n env: {\n ...process.env,\n GIT_TERMINAL_PROMPT: '0',\n GIT_ASKPASS: '',\n SSH_ASKPASS: '',\n },\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 * Categorization of a reachability failure. Drives the per-host hint\n * in the consolidated error message. Patterns were observed across\n * GitHub, GitLab, and Bitbucket Cloud responses to non-existent /\n * unauthorized repos. We err on the side of grouping rather than\n * fine-grained kinds — the actionable advice is largely the same\n * within each kind.\n */\nexport type ReachabilityFailureKind =\n | 'not-found-or-no-access'\n | 'auth-failed'\n | 'dns'\n | 'unknown';\n\nexport interface RepoReachabilityStatus {\n url: string;\n ok: boolean;\n kind?: ReachabilityFailureKind;\n /** Raw stderr from git, trimmed. Empty when ok. */\n detail: string;\n}\n\n/**\n * Classify a non-zero `git ls-remote` failure by stderr content.\n * Patterns are matched case-insensitively against the union of\n * substrings each provider tends to emit. Order matters: DNS errors\n * sometimes also produce \"Authentication failed\" follow-up lines on\n * some platforms, so we check DNS first.\n */\nfunction classifyStderr(stderr: string): ReachabilityFailureKind {\n const s = stderr.toLowerCase();\n if (\n s.includes('could not resolve host') ||\n s.includes('name or service not known') ||\n s.includes('temporary failure in name resolution') ||\n s.includes('no address associated with hostname')\n ) {\n return 'dns';\n }\n if (\n s.includes('repository not found') ||\n s.includes('may not have access') ||\n s.includes('no longer exists') ||\n s.includes(\"don't have permission\") ||\n s.includes('could not be found') ||\n s.includes('the requested url returned error: 404')\n ) {\n return 'not-found-or-no-access';\n }\n if (\n s.includes('authentication failed') ||\n s.includes('could not read username') ||\n s.includes('incorrect username or password') ||\n s.includes('the requested url returned error: 401') ||\n s.includes('the requested url returned error: 403')\n ) {\n return 'auth-failed';\n }\n return 'unknown';\n}\n\n/**\n * Probe each declared repo URL via host-side `git ls-remote`. Runs\n * sequentially (not parallel) so the output order matches the yml\n * order — easier to reason about when multiple repos fail, and the\n * total time is bounded by ~200 ms per repo against typical SaaS\n * hosts. Spawn-injected for tests.\n */\nexport async function checkRepoReachability(\n repos: readonly { url: string }[],\n options: { spawn?: ReachabilitySpawn } = {},\n): Promise<RepoReachabilityStatus[]> {\n const spawnFn = options.spawn ?? realGitLsRemote;\n const results: RepoReachabilityStatus[] = [];\n for (const repo of repos) {\n // Only HTTPS URLs reach this code path (schema enforces it; pre-\n // flight already filtered). Skip belt-and-suspenders is in\n // credentials.ts — here we trust the input.\n let result: Awaited<ReturnType<ReachabilitySpawn>>;\n try {\n result = await spawnFn(repo.url);\n } catch (err) {\n results.push({\n url: repo.url,\n ok: false,\n kind: 'unknown',\n detail: err instanceof Error ? err.message : String(err),\n });\n continue;\n }\n if (result.exitCode === 0) {\n results.push({ url: repo.url, ok: true, detail: '' });\n continue;\n }\n results.push({\n url: repo.url,\n ok: false,\n kind: classifyStderr(result.stderr),\n detail: result.stderr.trim(),\n });\n }\n return results;\n}\n\n/**\n * Render the consolidated pre-flight error for repos that couldn't\n * be reached. Groups failures by kind so each kind's actionable\n * advice appears once, with the failing URLs listed underneath.\n *\n * Layout:\n *\n * Cannot reach <N> declared repo(s):\n *\n * Repository not found (or your credentials don't grant access):\n * • https://...\n * • https://...\n * - <actionable advice>\n * - <actionable advice>\n *\n * Authentication failed:\n * • https://...\n * - <actionable advice>\n *\n * Then re-run `monoceros apply`.\n */\nexport function formatUnreachableReposError(\n failures: readonly RepoReachabilityStatus[],\n): string {\n const byKind = new Map<ReachabilityFailureKind, RepoReachabilityStatus[]>();\n for (const f of failures) {\n const kind = f.kind ?? 'unknown';\n const list = byKind.get(kind) ?? [];\n list.push(f);\n byKind.set(kind, list);\n }\n const totalUrls = failures.length;\n const lines: string[] = [\n totalUrls === 1\n ? `Cannot reach declared repo: ${failures[0]!.url}`\n : `Cannot reach ${totalUrls} declared repos:`,\n '',\n ];\n\n const sectionOrder: ReachabilityFailureKind[] = [\n 'not-found-or-no-access',\n 'auth-failed',\n 'dns',\n 'unknown',\n ];\n for (const kind of sectionOrder) {\n const entries = byKind.get(kind);\n if (!entries || entries.length === 0) continue;\n lines.push(headerForKind(kind));\n for (const e of entries) {\n lines.push(` • ${e.url}`);\n }\n for (const advice of adviceForKind(kind)) {\n lines.push(` - ${advice}`);\n }\n lines.push('');\n }\n lines.push(`Then re-run ${cyan('monoceros apply')}.`);\n return lines.join('\\n');\n}\n\nfunction headerForKind(kind: ReachabilityFailureKind): string {\n switch (kind) {\n case 'not-found-or-no-access':\n return \"Repository not found (or your credentials don't grant access):\";\n case 'auth-failed':\n return 'Authentication failed (credentials are present but rejected):';\n case 'dns':\n return \"Host unreachable (DNS / VPN / offline — git couldn't resolve the hostname):\";\n case 'unknown':\n return 'Unrecognised git error:';\n }\n}\n\nfunction adviceForKind(kind: ReachabilityFailureKind): string[] {\n switch (kind) {\n case 'not-found-or-no-access':\n return [\n 'Re-check the URL for typos (case-sensitive on most hosts).',\n 'Verify the repo still exists / is not archived in a way that hides it.',\n 'Ensure your token covers this org / workspace and has read scope (GitHub: `repo`; GitLab: `read_repository`; Bitbucket: repo read).',\n ];\n case 'auth-failed':\n return [\n 'Token may be expired or revoked — regenerate it from the provider UI.',\n 'Re-run the provider CLI login (gh auth login / glab auth login) — Monoceros picks up the refreshed token on the next apply.',\n ];\n case 'dns':\n return [\n 'Check your internet / VPN — corporate Git hosts often require VPN.',\n 'Verify the hostname spelling in the yml.',\n ];\n case 'unknown':\n return [\n 'Run `git ls-remote <url>` manually on the host to see the raw error.',\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\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\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 * 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\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 */\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 spawnFn = options.spawn ?? realGitConfigGet;\n const promptFn = options.prompt ?? realIdentityPrompt;\n const logger = options.logger ?? { info: () => {}, warn: () => {} };\n\n const existing = await readExistingGitconfig(gitconfigPath);\n\n // Resolution order per key:\n // 1. containerOverride (yml's `git.user`)\n // 2. defaults (monoceros-config.yml's `defaults.git.user`)\n // 3. host `git config --global --get <key>`\n // 4. previously persisted value (.monoceros/gitconfig)\n // 5. interactive prompt (skipped in non-TTY contexts)\n const name = await resolveKey('user.name', {\n override: options.containerOverride?.name,\n defaultValue: options.defaults?.name,\n spawnFn,\n persistedValue: existing.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: existing.email,\n promptFn,\n logger,\n });\n\n const lines: string[] = ['[user]'];\n if (name !== undefined) lines.push(`\\tname = ${name}`);\n if (email !== undefined) lines.push(`\\temail = ${email}`);\n\n await fs.mkdir(gitconfigDir, { recursive: true });\n await fs.writeFile(gitconfigPath, lines.join('\\n') + '\\n');\n\n return {\n ...(name !== undefined ? { name } : {}),\n ...(email !== undefined ? { email } : {}),\n gitconfigPath,\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\nasync function resolveKey(\n key: 'user.name' | 'user.email',\n opts: ResolveKeyOpts,\n): Promise<string | undefined> {\n if (opts.override !== undefined && opts.override.length > 0) {\n return opts.override;\n }\n if (opts.defaultValue !== undefined && opts.defaultValue.length > 0) {\n return opts.defaultValue;\n }\n const hostValue = await readKeyFromHost(opts.spawnFn, key, opts.logger);\n if (hostValue !== undefined) return hostValue;\n if (opts.persistedValue !== undefined && opts.persistedValue.length > 0) {\n return opts.persistedValue;\n }\n const prompted = await opts.promptFn(key);\n if (prompted !== undefined) return prompted;\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 or zsh to stdout. The user redirects it into a file their\n * shell loads at startup.\n *\n * Both scripts complete:\n * - subcommand names at position 1\n * - container names (read from `<MONOCEROS_HOME>/container-configs/`)\n * for the second positional of every command that takes a\n * `<NAME>` argument referring to an existing container — i.e.\n * everything *except* `init` (which expects a fresh name) and the\n * verb-only commands like `list-components` / `completion`.\n *\n * MONOCEROS_HOME respects the same precedence as the CLI itself: env\n * var first, then `$HOME/.monoceros`. Container-name completion in\n * the workbench-checkout dev environment looks at the env var if set;\n * otherwise it falls back to `~/.monoceros/`, which matches the\n * global-install case. A contributor who wants dev-container names\n * completed sets `MONOCEROS_HOME=$PWD/.local` in their shell.\n *\n * Install:\n * bash: monoceros completion bash > ~/.bash_completion.d/monoceros\n * (or any path your shell sources; `source` it from .bashrc)\n * zsh: monoceros completion zsh > \"${fpath[1]}/_monoceros\"\n * (after ensuring compinit is active)\n */\n\n// Keep these arrays in sync with main.ts. Single source of truth\n// would be nice but adds startup cost — citty's subCommands aren't\n// trivial to enumerate from a static context. Tests guard the\n// list in completion.test.ts.\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 'completion',\n] as const;\n\n// Commands whose first positional is an existing container name.\n// Everything else either takes no positional (`list-components`,\n// `completion`) or expects a fresh name (`init`, `restore`).\nconst COMMANDS_WITH_CONTAINER_ARG = [\n 'shell',\n 'run',\n 'logs',\n 'start',\n 'stop',\n 'status',\n 'apply',\n 'remove',\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] as const;\n\nconst SHELLS = ['bash', 'zsh', 'pwsh'] as const;\ntype Shell = (typeof SHELLS)[number];\n\nexport function renderCompletionScript(shell: Shell): string {\n const commands = ALL_COMMANDS.join(' ');\n const containerCommandsRegex = COMMANDS_WITH_CONTAINER_ARG.join('|');\n\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 '_monoceros() {',\n ' local cur prev cmd home configs_dir names',\n ' cur=\"${COMP_WORDS[COMP_CWORD]}\"',\n '',\n ' if [[ $COMP_CWORD -eq 1 ]]; then',\n ` COMPREPLY=( $(compgen -W \"${commands}\" -- \"$cur\") )`,\n ' return',\n ' fi',\n '',\n ' cmd=\"${COMP_WORDS[1]}\"',\n ' if [[ $COMP_CWORD -eq 2 ]]; then',\n ' case \"$cmd\" in',\n ` ${containerCommandsRegex})`,\n ' home=\"${MONOCEROS_HOME:-$HOME/.monoceros}\"',\n ' configs_dir=\"$home/container-configs\"',\n ' if [[ -d \"$configs_dir\" ]]; then',\n ` names=$(cd \"$configs_dir\" && ls *.yml 2>/dev/null | sed 's/\\\\.yml$//')`,\n ' COMPREPLY=( $(compgen -W \"$names\" -- \"$cur\") )',\n ' fi',\n ' ;;',\n ' completion)',\n ` COMPREPLY=( $(compgen -W \"${SHELLS.join(' ')}\" -- \"$cur\") )`,\n ' ;;',\n ' esac',\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 'Register-ArgumentCompleter -Native -CommandName monoceros -ScriptBlock {',\n ' param($wordToComplete, $commandAst, $cursorPosition)',\n '',\n ' $commands = @(',\n ...ALL_COMMANDS.map((c) => ` '${c}'`),\n ' )',\n ` $shells = @('${SHELLS.join(\"', '\")}')`,\n ' $containerCommands = @(',\n ...COMMANDS_WITH_CONTAINER_ARG.map((c) => ` '${c}'`),\n ' )',\n '',\n ' $tokens = $commandAst.CommandElements',\n ' $position = $tokens.Count',\n ' if ($wordToComplete) { $position-- }',\n '',\n ' if ($position -eq 1) {',\n ' $commands | Where-Object { $_ -like \"$wordToComplete*\" } |',\n ' ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, \"ParameterValue\", $_) }',\n ' return',\n ' }',\n '',\n ' if ($position -eq 2) {',\n ' $cmd = $tokens[1].Value',\n ' if ($containerCommands -contains $cmd) {',\n ' $home = if ($env:MONOCEROS_HOME) { $env:MONOCEROS_HOME } else { Join-Path $env:USERPROFILE \".monoceros\" }',\n ' $configsDir = Join-Path $home \"container-configs\"',\n ' if (Test-Path $configsDir) {',\n ' Get-ChildItem -Path $configsDir -Filter \"*.yml\" |',\n ' ForEach-Object { $_.BaseName } |',\n ' Where-Object { $_ -like \"$wordToComplete*\" } |',\n ' ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, \"ParameterValue\", $_) }',\n ' }',\n ' } elseif ($cmd -eq \"completion\") {',\n ' $shells | Where-Object { $_ -like \"$wordToComplete*\" } |',\n ' ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, \"ParameterValue\", $_) }',\n ' }',\n ' }',\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 '_monoceros() {',\n ' local -a commands shells',\n ' commands=(',\n ...ALL_COMMANDS.map((c) => ` '${c}'`),\n ' )',\n ` shells=(${SHELLS.map((s) => `'${s}'`).join(' ')})`,\n '',\n ' if (( CURRENT == 2 )); then',\n \" _describe 'monoceros command' commands\",\n ' return',\n ' fi',\n '',\n ' local cmd=${words[2]}',\n ' if (( CURRENT == 3 )); then',\n ' case $cmd in',\n ` ${containerCommandsRegex})`,\n ' local home=\"${MONOCEROS_HOME:-$HOME/.monoceros}\"',\n ' local configs_dir=\"$home/container-configs\"',\n ' if [[ -d $configs_dir ]]; then',\n ' local -a names',\n ' names=(${configs_dir}/*.yml(N:t:r))',\n \" _describe 'container' names\",\n ' fi',\n ' ;;',\n ' completion)',\n \" _describe 'shell' shells\",\n ' ;;',\n ' esac',\n ' fi',\n '}',\n '',\n '_monoceros \"$@\"',\n '',\n ].join('\\n');\n}\n\nexport const completionCommand = defineCommand({\n meta: {\n name: 'completion',\n group: 'tooling',\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\n// Exposed for tests so the static command list stays in sync with\n// what main.ts wires up.\nexport const COMPLETION_COMMANDS_FOR_TEST = ALL_COMMANDS;\nexport const COMPLETION_CONTAINER_COMMANDS_FOR_TEST =\n COMMANDS_WITH_CONTAINER_ARG;\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 .local/container-configs/<name>.yml. Without --with, the file is a documented default with every component commented out. With --with=<names>, the named components 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: {\n type: 'string',\n description:\n \"Comma-separated list of component names to compose, e.g. 'node,postgres,github,claude'. Sub-components use a slash, e.g. 'atlassian/twg'. When omitted, init writes a documented default with every catalog component commented out.\",\n required: false,\n },\n 'with-repo': {\n type: 'string',\n description:\n 'Git URL of a repo to clone into projects/ on first apply. Repeatable: pass --with-repo=URL1 --with-repo=URL2 for multiple repos. Folder name derived from URL (foo.git → projects/foo/); use `monoceros add-repo --path=...` post-init for subfolder paths.',\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 withList = collectWithList(args.with, rawArgs);\n const withRepoList = collectWithRepoList(rawArgs);\n const withPortsList = collectWithPortsList(args['with-ports'], rawArgs);\n await runInit({\n name: args.name,\n ...(withList ? { with: withList } : {}),\n ...(withRepoList.length > 0 ? { withRepo: withRepoList } : {}),\n ...(withPortsList && withPortsList.length > 0\n ? { withPorts: withPortsList }\n : {}),\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 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\n/**\n * Collect all `--with-repo=<url>` and `--with-repo <url>` tokens from\n * rawArgs. citty doesn't natively give us repeatable strings (the\n * single `args['with-repo']` only carries the last value), so we\n * scan the original argv. Returns URLs in order of appearance.\n */\nfunction collectWithRepoList(rawArgs: string[]): string[] {\n const urls: string[] = [];\n for (let i = 0; i < rawArgs.length; i += 1) {\n const t = rawArgs[i]!;\n if (t === '--with-repo') {\n const next = rawArgs[i + 1];\n if (typeof next === 'string' && !next.startsWith('-')) {\n urls.push(next);\n i += 1;\n }\n } else if (t.startsWith('--with-repo=')) {\n urls.push(t.slice('--with-repo='.length));\n }\n }\n return urls;\n}\n\n/**\n * Reconstruct the --with list from `args.with` plus any rawArgs\n * tokens that the shell tokenization split off.\n *\n * Background: a user writing\n * monoceros init dummy --with=a, b, c\n * gets shell-tokenized into argv entries:\n * ['init', 'dummy', '--with=a,', 'b,', 'c']\n * citty assigns `args.with = \"a,\"` and the rest float as extra\n * positionals that the `name` arg won't accept. To avoid forcing\n * the user to quote or remove the spaces, we look at rawArgs to\n * find the original --with token and pull in any subsequent non-\n * flag tokens until we hit something that looks like a flag or\n * run out. The collected pieces are joined back with commas and\n * re-split — same parser as before, but now seeing the full list.\n */\nfunction collectWithList(\n withArg: string | undefined,\n rawArgs: string[],\n): string[] | undefined {\n if (typeof withArg !== 'string' || withArg.trim().length === 0) {\n return undefined;\n }\n let combined = withArg.trim();\n // Find where --with starts in rawArgs, then keep eating non-flag\n // tokens. Both forms are supported by citty:\n // --with=value (combined in one token)\n // --with value (two tokens)\n const startIdx = rawArgs.findIndex(\n (t) => t === '--with' || t.startsWith('--with='),\n );\n if (startIdx >= 0) {\n // Skip the with token itself, plus its detached value when\n // `--with` was used in the two-token form.\n let scanFrom = startIdx + 1;\n if (rawArgs[startIdx] === '--with') scanFrom += 1;\n for (let i = scanFrom; i < rawArgs.length; i += 1) {\n const t = rawArgs[i]!;\n if (t.startsWith('--') || t === '-h' || t === '--help') break;\n // Re-join with a comma — the user separated with commas plus\n // (now-eaten) whitespace; comma alone is what our parser wants.\n const sep = combined.endsWith(',') ? '' : ',';\n combined += sep + t;\n }\n }\n const pieces = combined\n .split(',')\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n return pieces.length > 0 ? pieces : undefined;\n}\n","import { existsSync, promises as fs } from 'node:fs';\nimport { consola } from 'consola';\nimport {\n containerConfigPath,\n containerConfigsDir,\n monocerosHome as defaultMonocerosHome,\n workbenchRoot as defaultWorkbenchRoot,\n workbenchCheckoutRoot,\n componentsDir as defaultComponentsDir,\n prettyPath,\n} from '../config/paths.js';\nimport { KNOWN_PROVIDER_HOSTS, REGEX } from '../config/schema.js';\nimport { loadComponentCatalog, resolveComponents } from './components.js';\nimport { generateComposedYml, generateDocumentedYml } from './generator.js';\nimport { loadFeatureManifestSummary } from './manifest.js';\n\n/**\n * `monoceros init <name> [--with=<components>]` — produce a fresh\n * container-config yml at `<MONOCEROS_HOME>/container-configs/<name>.yml`.\n *\n * Two modes:\n *\n * - With `--with=node,postgres,github,claude` (or any comma-list\n * of component names from the catalog): the listed components\n * are merged and the result written as an active, immediately-\n * applyable yml. Per-feature option hints (auth/credentials\n * from the feature manifest) appear as commented lines next to\n * the active options so the builder can see what's available\n * without leaving the file.\n *\n * - Without `--with`: 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 * Component names to compose. When empty/undefined → documented\n * mode (every component commented out). When set → composed mode\n * with exactly these components active.\n */\n with?: 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 // 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 requested = opts.with ?? [];\n if (requested.length === 0) {\n text = generateDocumentedYml(opts.name, catalog, lookup, repos, ports);\n } else {\n const components = resolveComponents(catalog, requested);\n text = generateComposedYml(opts.name, components, lookup, repos, ports);\n }\n\n await fs.mkdir(containerConfigsDir(home), { recursive: true });\n await fs.writeFile(dest, text, 'utf8');\n\n const documented = requested.length === 0;\n const displayPath = prettyPath(dest);\n if (documented) {\n logger.success(\n `Wrote documented default to ${displayPath}. Un-comment what you need, then \\`monoceros apply ${opts.name}\\`.`,\n );\n } else {\n logger.success(\n `Composed ${requested.length} component(s) into ${displayPath}: ${requested.join(', ')}`,\n );\n logger.info(\n `Edit the file if you need to tweak, then \\`monoceros apply ${opts.name}\\`.`,\n );\n }\n\n return { configPath: dest, documented };\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=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=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\nfunction 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 type { Component, ResolvedComponent } from './components.js';\nimport { mergeComponents } from './components.js';\nimport type { FeatureManifestSummary } from './manifest.js';\n\n/**\n * Renderer for the container yml that `monoceros init` produces.\n *\n * Two modes:\n *\n * - **Composed** (`monoceros init <name> --with=node,…`): all\n * listed components are active. The output is a clean,\n * immediately-applyable yml. Per-feature option hints\n * (auth/credentials from the feature manifest) are appended as\n * commented lines beneath the active options block, so a builder\n * reading the yml can see at a glance which keys exist without\n * leaving the file.\n *\n * - **Documented** (`monoceros init <name>` without `--with`):\n * every section is commented out. The output is a self-explaining\n * reference; the builder un-comments what they need and runs\n * `monoceros apply`.\n *\n * Both modes share the per-feature block rendering. The only\n * difference is whether the section is commented out at the top\n * level or not.\n *\n * We hand-render the yml as a string instead of going through the\n * yaml library's AST. The shape is narrow enough that the explicit\n * line-by-line approach is shorter and easier to reason about than\n * juggling Document + Pair + Scalar nodes with attached comments.\n */\n\nexport type ManifestLookup = (\n ref: string,\n) => FeatureManifestSummary | undefined;\n\nconst SCHEMA_HEADER = [\n '# Monoceros solution-config. Edit freely, then run',\n '# `monoceros apply <name>` to materialize a dev-container.',\n '#',\n '# Each section below carries inline comments for the options it',\n '# accepts. Features expose additional knobs as commented hints',\n '# under their `options:` block — un-comment what you need.',\n] as const;\n\n/**\n * Render the active-mode yml for the given components.\n */\nexport function generateComposedYml(\n name: string,\n components: ResolvedComponent[],\n lookupManifest: ManifestLookup,\n repoUrls: readonly string[] = [],\n ports: readonly number[] = [],\n): string {\n const merged = mergeComponents(components);\n const lines: string[] = [];\n for (const h of SCHEMA_HEADER) lines.push(h);\n lines.push('');\n lines.push('schemaVersion: 1');\n lines.push(`name: ${name}`);\n lines.push('');\n\n if (merged.languages.length > 0) {\n lines.push('languages:');\n for (const lang of merged.languages) lines.push(` - ${lang}`);\n lines.push('');\n }\n if (merged.services.length > 0) {\n lines.push('services:');\n for (const svc of merged.services) lines.push(` - ${svc}`);\n lines.push('');\n }\n if (merged.features.length > 0) {\n lines.push('features:');\n for (const f of merged.features) {\n renderFeatureBlock(\n lines,\n f,\n lookupManifest(f.ref),\n /* commented */ false,\n );\n }\n lines.push('');\n }\n // Composed mode only emits a repos block when --with-repo provided\n // URLs. The \"show all available options\" requirement applies on a\n // per-entry basis there: each active repo entry carries commented\n // hints for its optional fields. With no URLs, composed mode stays\n // schlank — repos are surfaced via `monoceros add-repo` post-init.\n if (repoUrls.length > 0) {\n renderReposBlock(lines, repoUrls, /* commented */ false);\n }\n // Same convention for ports: active `routing:` block when\n // `--with-ports` provided values, nothing otherwise. Builder runs\n // `monoceros add-port` later if they want it post-init.\n if (ports.length > 0) {\n renderActiveRoutingBlock(lines, name, ports);\n }\n\n return ensureTrailingNewline(lines.join('\\n'));\n}\n\n/**\n * Render the documented-default yml: every component listed but\n * commented out, with section headers carrying short prose so a\n * fresh builder can read the file and figure out what to enable.\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 for (const h of SCHEMA_HEADER) lines.push(h);\n lines.push('#');\n lines.push('# Below is the full set of components shipped with this');\n lines.push('# workbench, every one commented out. Un-comment the lines');\n lines.push('# you want active. The same effect (and a cleaner yml) is');\n lines.push('# achievable by running `monoceros init <name> --with=…`');\n lines.push('# with a comma-separated list of component names.');\n lines.push('');\n lines.push('schemaVersion: 1');\n lines.push(`name: ${name}`);\n lines.push('');\n\n if (byCategory.language.length > 0) {\n const items = byCategory.language.flatMap((c) =>\n (c.file.contributes.languages ?? []).map((lang) => ({\n value: lang,\n label: c.file.displayName,\n })),\n );\n const width = Math.max(...items.map((i) => i.value.length)) + 2;\n lines.push('# Languages — runtime toolchains.');\n lines.push('# languages:');\n for (const item of items) {\n const pad = ' '.repeat(width - item.value.length);\n lines.push(`# - ${item.value}${pad}# ${item.label}`);\n }\n lines.push('');\n }\n if (byCategory.service.length > 0) {\n const items = byCategory.service.flatMap((c) =>\n (c.file.contributes.services ?? []).map((svc) => ({\n value: svc,\n label: c.file.displayName,\n })),\n );\n const width = Math.max(...items.map((i) => i.value.length)) + 2;\n lines.push('# Services — compose-mode siblings of the workspace');\n lines.push('# container (compose mode kicks in as soon as at least');\n lines.push('# one service is active).');\n lines.push('# services:');\n for (const item of items) {\n const pad = ' '.repeat(width - item.value.length);\n lines.push(`# - ${item.value}${pad}# ${item.label}`);\n }\n lines.push('');\n }\n if (byCategory.feature.length > 0) {\n lines.push('# Features — devcontainer features installed inside the');\n lines.push('# container. Each entry has an OCI-style `ref` plus an');\n lines.push('# optional `options` map. Credentials/auth keys appear');\n lines.push('# as commented hints; set them here per container, or');\n lines.push('# globally in monoceros-config.yml under');\n lines.push('# `defaults.features.<ref>`.');\n lines.push('#');\n lines.push('# Catalog:');\n lines.push('#');\n const nameColumnWidth =\n Math.max(...byCategory.feature.map((c) => c.name.length)) + 2;\n for (const c of byCategory.feature) {\n const pad = ' '.repeat(nameColumnWidth - c.name.length);\n lines.push(`# ${c.name}${pad}${c.file.displayName}`);\n }\n lines.push('#');\n lines.push('# Below: one block per feature ref. Un-comment what');\n lines.push(\"# you want active. Sub-components share their parent's\");\n lines.push('# block — pick the parent for the full preset, swap to');\n lines.push('# a sub-component name for a partial install.');\n lines.push('#');\n lines.push('# features:');\n\n // Render one feature block per unique ref. Prefer the top-level\n // component (e.g. `atlassian` over `atlassian/twg`) as the source\n // of the rendered options, since the top-level carries the\n // \"everything on\" default users typically want first.\n const renderedRefs = new Set<string>();\n const topLevel = byCategory.feature.filter((c) => !c.name.includes('/'));\n\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 renderFeatureBlock(\n lines,\n f,\n lookupManifest(f.ref),\n /* commented */ true,\n );\n }\n }\n // Any feature ref only mentioned through a sub-component (no\n // top-level component for the same ref) — render it from the\n // first sub-component.\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 renderFeatureBlock(\n lines,\n f,\n lookupManifest(f.ref),\n /* commented */ true,\n );\n }\n }\n lines.push('');\n }\n // Repos section — always rendered in documented mode (commented out\n // when no --with-repo URLs were provided, active when they were).\n // The block shows the full set of per-entry options so a builder\n // reading the yml sees what's possible without leaving the file —\n // same \"all available options are visible\" rule that drives the\n // features block above.\n renderReposBlock(lines, repoUrls, /* commented */ repoUrls.length === 0);\n\n // Routing — active when --with-ports provided values, otherwise\n // a fully-commented hint block (same \"all options visible\" rule\n // that drives repos and features). `monoceros add-port` activates\n // it later if the builder didn't pass --with-ports.\n if (ports.length > 0) {\n renderActiveRoutingBlock(lines, name, ports);\n } else {\n renderRoutingHintBlock(lines);\n }\n\n return ensureTrailingNewline(lines.join('\\n'));\n}\n\ninterface RenderableFeature {\n ref: string;\n options?: Record<string, string | number | boolean>;\n}\n\n// Target column width for rendered comment lines. Description text\n// and usage notes get word-wrapped against this width so the output\n// stays readable in a standard editor without horizontal scrolling.\nconst COMMENT_WIDTH = 72;\n\nfunction renderFeatureBlock(\n out: string[],\n feature: RenderableFeature,\n summary: FeatureManifestSummary | undefined,\n commented: boolean,\n): void {\n const c = commented ? '# ' : ' ';\n const optionHints = summary?.optionHints ?? [];\n const optionDescriptions = summary?.optionDescriptions ?? {};\n const usageNotes = summary?.usageNotes ?? [];\n\n // Per-feature usage notes — rendered as a wrapped comment block\n // right before the `- ref:` line. Multiple notes are separated\n // by an empty comment line so they read as distinct paragraphs.\n for (let i = 0; i < usageNotes.length; i++) {\n if (i > 0) out.push(`${c}#`);\n for (const line of wrapToComment(\n usageNotes[i]!,\n COMMENT_WIDTH - c.length,\n )) {\n out.push(`${c}# ${line}`);\n }\n }\n\n out.push(`${c}- ref: ${feature.ref}`);\n const options = feature.options ?? {};\n const activeOptions = Object.entries(options);\n const remainingHints = optionHints.filter((h) => !(h in options));\n\n // When there are active options, emit a real `options:` block.\n // When there are only hints (no active options), skip `options:`\n // entirely and emit the hints as plain comments at the same depth\n // — yaml parsers see `options:` with no content under it as\n // `null`, which fails our schema.\n if (activeOptions.length > 0) {\n out.push(`${c} options:`);\n for (const [key, value] of activeOptions) {\n out.push(`${c} ${key}: ${renderScalarValue(value)}`);\n }\n if (remainingHints.length > 0) {\n out.push(\n `${c} # Optional — override monoceros-config.yml defaults.features:`,\n );\n for (const hint of remainingHints) {\n emitHint(out, hint, optionDescriptions[hint], `${c} `);\n }\n }\n } else if (remainingHints.length > 0) {\n out.push(\n `${c} # Optional — override monoceros-config.yml defaults.features:`,\n );\n out.push(`${c} # options:`);\n for (const hint of remainingHints) {\n emitHint(out, hint, optionDescriptions[hint], `${c} # `);\n }\n }\n}\n\n/**\n * Emit a single option-hint line, optionally preceded by its\n * description as wrapped comment lines. `linePrefix` is the full\n * prefix (indent + any commented-out `# ` chars) that every\n * emitted line should start with; the hint itself is suffixed\n * with `: ` so the user can fill in a value.\n */\nfunction emitHint(\n out: string[],\n hint: string,\n description: string | undefined,\n linePrefix: string,\n): void {\n if (description) {\n for (const line of wrapToComment(\n description,\n COMMENT_WIDTH - linePrefix.length,\n )) {\n out.push(`${linePrefix}# ${line}`);\n }\n }\n out.push(`${linePrefix}${hint}:`);\n}\n\n/**\n * Render the `repos:` section. When `commented` is true the entire\n * block is prefixed with `# ` (documented mode, no --with-repo).\n * When false the `url:` lines are active but the optional fields\n * (path, provider, git.user) stay commented per entry so the\n * builder sees what else can go there.\n *\n * The \"all available options visible\" rule: every per-entry field\n * the schema accepts appears on every rendered entry, either active\n * (rare — only `url` is required) or as a commented hint.\n */\nfunction renderReposBlock(\n out: string[],\n urls: readonly string[],\n commented: boolean,\n): void {\n // Prose intro — always a comment block, regardless of whether the\n // section below is active or fully commented.\n out.push('# Repos — git repositories cloned into projects/ during');\n out.push('# post-create. HTTPS URLs only. Provider auto-detected');\n out.push('# for github.com, gitlab.com, bitbucket.org; for any other host');\n out.push('# (self-hosted GitLab, Bitbucket Data Center, Gitea/Forgejo,');\n out.push('# GitHub Enterprise, …) declare provider explicitly.');\n out.push('#');\n\n if (commented) {\n // Pure documented mode (no --with-repo): a single example entry,\n // everything commented. Shows the full per-entry surface.\n out.push('# repos:');\n out.push('# - url: https://github.com/<org>/<repo>.git');\n out.push(\n '# # path: <folder> # subfolder under projects/; default: URL-derived',\n );\n out.push(\n '# # provider: github # github | gitlab | bitbucket | gitea',\n );\n out.push(\n '# # git: # per-repo committer identity override',\n );\n out.push('# # user:');\n out.push('# # name: Your Name');\n out.push('# # email: you@example.com');\n out.push('');\n return;\n }\n\n // Active entries from --with-repo. Each entry repeats the\n // commented hints for the optional fields so they're discoverable\n // without docs.\n out.push('repos:');\n for (const url of urls) {\n const derivedPath = deriveDefaultPath(url);\n out.push(` - url: ${url}`);\n out.push(\n ` # path: ${derivedPath} # subfolder under projects/; default: URL-derived (${derivedPath})`,\n );\n out.push(\n ' # provider: github # github | gitlab | bitbucket | gitea',\n );\n out.push(\n ' # git: # per-repo committer identity override',\n );\n out.push(' # user:');\n out.push(' # name: Your Name');\n out.push(' # email: you@example.com');\n }\n out.push('');\n}\n\n/**\n * Render the fully-commented `routing:` hint block. Used in\n * documented-mode init when the builder did not pass `--with-ports`.\n * Shows the full set of fields the schema accepts so the option\n * surface stays discoverable from inside the yml. See ADR 0007.\n */\nfunction renderRoutingHintBlock(out: string[]): void {\n out.push('# Routing — expose container ports to the host through the');\n out.push('# shared Traefik singleton. Once any port is declared the');\n out.push('# container joins the monoceros-proxy network and the proxy');\n out.push('# routes <name>.localhost (default port) and');\n out.push('# <name>-<port>.localhost (explicit). `monoceros add-port`');\n out.push('# manages the list; the block appears on first add. You can');\n out.push('# also pre-seed at init time via `--with-ports=3000,5173,…`.');\n out.push('#');\n out.push('# routing:');\n out.push('# ports: # internal container ports');\n out.push(\n '# - 3000 # first entry doubles as <name>.localhost',\n );\n out.push('# - 5173');\n out.push(\n '# vscodeAutoForward: false # default: false. Traefik is the single',\n );\n out.push(\n '# # source of truth — set true only if you',\n );\n out.push(\n \"# # want VS Code's port panel as primary.\",\n );\n out.push('');\n}\n\n/**\n * Render an active `routing:` block — used when `--with-ports`\n * provided values. First entry is the default (matches what add-port\n * writes, what proxyUrlsFor surfaces under `<name>.localhost`, and\n * what setDefaultPortInDoc moves to position 0). CLI order is\n * preserved.\n *\n * `vscodeAutoForward` is emitted as a commented hint with its\n * default value spelled out — same \"all options visible\" rule that\n * drives the features and repos blocks. Builder removes the `#` to\n * activate.\n */\nfunction renderActiveRoutingBlock(\n out: string[],\n name: string,\n ports: readonly number[],\n): void {\n out.push('# Routing — expose these container ports to the host through');\n out.push('# the shared Traefik singleton. First entry doubles as');\n out.push(`# http://${name}.localhost (the default route).`);\n out.push('routing:');\n out.push(' ports:');\n ports.forEach((port, idx) => {\n if (idx === 0) {\n out.push(` - ${port} # default → http://${name}.localhost`);\n } else {\n out.push(` - ${port}`);\n }\n });\n // Commented hint so the builder discovers the toggle without\n // leaving the file. Default `false` makes Traefik the single\n // source of truth — flip to `true` to keep VS Code's port-panel\n // alongside.\n out.push(\" # vscodeAutoForward: false # set true to keep VS Code's\");\n out.push(' # # port-panel alongside Traefik');\n out.push('');\n}\n\n/**\n * URL-derived default for the `path:` hint. Mirrors what\n * `deriveRepoName` in scaffold.ts does at apply time — inlined\n * here to keep the generator self-contained (no cross-module\n * dependency from init/ → create/).\n */\nfunction deriveDefaultPath(url: string): string {\n let last = url;\n const slash = url.lastIndexOf('/');\n if (slash >= 0) last = url.slice(slash + 1);\n if (last.endsWith('.git')) last = last.slice(0, -4);\n return last || 'repo';\n}\n\n/**\n * Word-wrap a single paragraph of plain text to `width` columns.\n * The returned strings do NOT include any prefix — the caller is\n * expected to prepend a comment marker (`# `) and any indent.\n * Long words that exceed `width` are emitted on their own line\n * rather than split mid-word.\n */\nfunction 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\nfunction renderScalarValue(value: string | number | boolean): string {\n if (typeof value === 'string') {\n // Quote anything that could be ambiguous to a yaml parser\n // (leading numerics, special chars). Quoting strings is always\n // safe; we only avoid it for booleans/numbers so they keep\n // their types.\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 // Stable sort by component name for deterministic output.\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 { 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 * - `optionHints` — names of feature options to render as\n * commented-out lines below the active options block, so a\n * builder reading the yml sees at a glance which keys exist\n * without going to the feature's docs.\n * - `optionDescriptions` — the `description` string from each\n * option in the manifest, keyed by option name. Init prints\n * these as a wrapped comment block above the matching hint\n * line so the builder knows what the option does without\n * opening the feature docs.\n * - `usageNotes` — free-text per-feature paragraphs from\n * `x-monoceros.usageNotes`. Init renders them as a comment\n * block right above the `- ref:` line. Use for things that\n * aren't tied to one option — e.g. \"alternative auth flow X\n * works inside the running container\".\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 /** 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 /** Free-text per-feature notes rendered above the `- ref:` line. */\n usageNotes: string[];\n}\n\ninterface RawManifest {\n options?: Record<string, { description?: string }>;\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 if (parsed.options) {\n for (const [key, opt] of Object.entries(parsed.options)) {\n if (\n opt &&\n typeof opt === 'object' &&\n typeof opt.description === 'string' &&\n opt.description.length > 0\n ) {\n optionDescriptions[key] = opt.description;\n }\n }\n }\n\n return { optionHints, optionDescriptions, usageNotes };\n } catch {\n return undefined;\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=…`, 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 (by its 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 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 monocerosHome as defaultMonocerosHome,\n prettyPath,\n} from '../config/paths.js';\nimport { REGEX } from '../config/schema.js';\nimport {\n composeProjectName,\n spawnBash,\n type ComposeSpawn,\n} from '../devcontainer/compose.js';\nimport { maybeStopProxy, type DockerExec } 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 /** Bash spawn for the docker cleanup script. Tests inject a stub. */\n dockerSpawn?: ComposeSpawn;\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 containerPath = containerDir(opts.name, home);\n const hasYml = existsSync(ymlPath);\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 const projectName = composeProjectName(containerPath);\n const dockerSpawn = opts.dockerSpawn ?? spawnBash;\n const script = [\n `set -u`,\n `echo \"[remove] tearing down docker project ${projectName}…\"`,\n // Compose-mode containers, identified by the compose project label.\n `by_label=$(docker ps -aq --filter \"label=com.docker.compose.project=${projectName}\" 2>/dev/null || true)`,\n // Devcontainer-cli containers (image-mode workspace + feature-\n // build intermediates) all carry this label, value = the absolute\n // container-dir path. Most reliable anchor we have, because\n // @devcontainers/cli lets Docker assign random names like\n // 'kind_cerf' — neither the project-name nor the vsc-<name>-\n // prefix filters below catch those.\n `by_dc_label=$(docker ps -aq --filter \"label=devcontainer.local_folder=${containerPath}\" 2>/dev/null || true)`,\n // Container-name prefix fallback (catches half-broken state).\n `by_compose_name=$(docker ps -aq --filter \"name=^${projectName}-\" 2>/dev/null || true)`,\n // Image-mode devcontainer-cli name fallback (only kicks in when\n // the cli used a deterministic name — modern versions don't).\n `by_image_name=$(docker ps -aq --filter \"name=^vsc-${opts.name}-\" 2>/dev/null || true)`,\n `to_remove=$(printf \"%s\\\\n%s\\\\n%s\\\\n%s\\\\n\" \"$by_label\" \"$by_dc_label\" \"$by_compose_name\" \"$by_image_name\" | sort -u | grep -v \"^$\" || true)`,\n `if [ -n \"$to_remove\" ]; then echo \"[remove] removing containers: $(echo $to_remove | tr \"\\\\n\" \" \")\"; docker rm -f $to_remove >/dev/null || true; else echo \"[remove] no containers found\"; fi`,\n `docker network rm ${projectName}_default 2>/dev/null && echo \"[remove] network ${projectName}_default removed\" || true`,\n `echo \"[remove] docker cleanup done\"`,\n ].join('; ');\n const dockerExitCode = await dockerSpawn(['-c', script], home);\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 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 (hasContainer) {\n await fs.rm(containerPath, { recursive: true, force: true });\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 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 // 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 (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"],"mappings":"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","names":["entry","main","path","defineCommand","consola","fs","entry","fs","z","parseDocument","z","fs","parseDocument","fs","path","path","fs","fs","path","fs","path","existsSync","fs","path","APT_PACKAGE_NAME_RE","FEATURE_REF_RE","INSTALL_URL_RE","REPO_URL_RE","REPO_PATH_RE","entry","path","existsSync","fs","entry","path","path","entry","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","ANSI_BOLD","ANSI_UNDERLINE","ANSI_CYAN","ANSI_GREY","ANSI_RESET","isTty","ANSI_RESET","ANSI_BOLD","ANSI_UNDERLINE","ANSI_CYAN","ANSI_GREY","bold","underline","cyan","spawn","existsSync","path","consola","spawn","readFileSync","path","readFileSync","path","child","spawn","spawn","path","existsSync","consola","spawn","fs","path","spawn","cyan","path","fs","cyan","spawn","spawn","cyan","spawn","spawn","cyan","spawn","fs","path","consola","consola","existsSync","fs","cyan","entry","consola","defineCommand","defineCommand","defineCommand","consola","existsSync","fs","consola","existsSync","fs","path","z","z","existsSync","fs","entry","path","existsSync","readFileSync","path","path","existsSync","readFileSync","consola","existsSync","fs","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"]}
|