@devrouter/cli 0.0.46 → 0.0.48

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/devrouter.js CHANGED
@@ -44,6 +44,7 @@ function buildDevrouterSection() {
44
44
  "",
45
45
  "Quick validation sequence:",
46
46
  "- Managed devcontainer consumer images contain no devrouter package or helper; `devrouter ensure` delivers the matching helper at runtime.",
47
+ "- Devsy runtime: run `devrouter setup --yes --workspace-runtime devsy` once; `doctor` then reports verified agent readiness without network access.",
47
48
  "- `devrouter up`",
48
49
  "- `devrouter tls install` (required when repo defines tcp/postgres apps)",
49
50
  "- `devrouter app ls --repo .`",
@@ -322,7 +323,7 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
322
323
  - **When active**: hosts auto-namespace (\`web.localhost\` \u2192 \`web.<ws>.localhost\`), \`\${WORKSPACE}\` in \`upstream\` is substituted with the token, and the docker \`router\` key is suffixed per workspace. Managed \`ensure\` rejects every HTTP/TCP proxy upstream outside that exact alias namespace before it mutates DevPod or routes. The runtime config is computed in memory only \u2014 the committed \`.devrouter.yml\` is never rewritten.
323
324
  - **TLS**: namespaced hosts (\`web.<ws>.localhost\`) are not covered by the \`*.localhost\` wildcard; devrouter auto-extends the mkcert cert SANs for active hosts when TLS is enabled.
324
325
  - **devcontainer integration**: managed scaffolds list the base compose file, then \`\${localEnv:DEVCONTAINER_COMPOSE_OVERLAY:docker-compose.default.yml}\`; custom repositories may keep another default overlay. Selecting \`.devcontainer/docker-compose.devrouter.yml\` for linked worktrees must pass \`WORKSPACE\` and \`DEVROUTER_WORKSPACE\` across the combined base/overlay config and bind-mount \`\${DEVROUTER_GIT_COMMON_DIR}\` to the same absolute app-container path. The app exposes \`\${WORKSPACE}-app\`; the proxy uses \`upstream: \${WORKSPACE}-app:<port>\`.
325
- - **Lifecycle**: after one-time \`setup\`, use \`ensure .\` for both primary and linked checkouts; never branch manually on checkout kind or use live verify as startup. \`stop .\` is non-destructive; \`stop . --delete\` is explicit exact-owner cleanup without worktree removal; and \`exec . -- <command...>\` runs one-shot commands only in the exact running workspace runtime (DevPod or Devsy). Never substitute raw \`devpod up\`, \`stop\`, \`delete\`, or the Devsy equivalents: they bypass devrouter's machine-global ownership lock, which serializes provider mutations in a fair arrival-order queue and lets contenders wait up to thirty minutes with throttled stderr progress before failing with the queue position or holder PID and true durations. Runtime selection is path-aware: \`DEVROUTER_WORKSPACE_RUNTIME=devpod|devsy\` forces one runtime, an exact-path registry owner wins next, then the machine preference from \`devrouter setup --workspace-runtime\`, then installed-CLI auto-detection. \`workspace up\` creates linked worktrees; destructive worktree removal and GC remain ledger-scoped. Dirty or locked full down fails before side effects. A failed Devsy start that reports the agent-binary acquisition failure names the \`DEVSY_AGENT_BINARY\` remediation.
326
+ - **Lifecycle**: after one-time \`setup\`, use \`ensure .\` for both primary and linked checkouts; never branch manually on checkout kind or use live verify as startup. \`stop .\` is non-destructive; \`stop . --delete\` is explicit exact-owner cleanup without worktree removal; and \`exec . -- <command...>\` runs one-shot commands only in the exact running workspace runtime (DevPod or Devsy). Never substitute raw \`devpod up\`, \`stop\`, \`delete\`, or the Devsy equivalents: they bypass devrouter's machine-global ownership lock, which serializes provider mutations in a fair arrival-order queue and lets contenders wait up to thirty minutes with throttled stderr progress before failing with the queue position or holder PID and true durations. Runtime selection is path-aware: \`DEVROUTER_WORKSPACE_RUNTIME=devpod|devsy\` forces one runtime, an exact-path registry owner wins next, then the machine preference from \`devrouter setup --workspace-runtime\`, then installed-CLI auto-detection. \`workspace up\` creates linked worktrees; destructive worktree removal and GC remain ledger-scoped. Dirty or locked full down fails before side effects. For Devsy, run \`devrouter setup --yes --workspace-runtime devsy\` once so Devrouter acquires and verifies the pinned agent in its own machine cache. \`doctor\` reports \`ready\`, \`missing\`, \`stale\`, or \`invalid\` without network access, and \`ensure\` fails before the provider queue when readiness is not \`ready\`. An explicit \`DEVSY_AGENT_BINARY\` remains authoritative and must match a pinned official asset.
326
327
  - **Managed process identity**: \`ensure\` executes an exact captured adapter snapshot. Default reuse includes command argv, workspace, and adapter SHA-256. Set \`DEVROUTER_PROCESS_FINGERPRINT_ENV\` only to comma-separated non-secret environment names whose values affect runtime identity; secret-like names are rejected and raw values are never persisted.
327
328
  - **Route state**: the versioned Traefik dynamic file is authoritative for both metadata and rendering. JSON is a compatibility mirror; valid headerless generations migrate automatically, while corrupt canonical metadata fails closed.
328
329
  - **Cleanup**: \`workspace cleanup --repo . --inactive-for 30d --json\` is a report-only, no-\`--yes\` report for managed linked workspaces. It joins ownership (\`present|missing|locked|conflict\`), workspace runtime registration, runtime state (\`running|stopped|busy|not-found|absent|unknown\`), checkout, route, advisory activity, and integration evidence without mutating the workspace runtime, routes, ownership, Git, Docker, applications, worktrees, or branches. Local DevPod/Devsy list/status checks always run; \`--check-merged\` alone enables read-only origin and matching GitHub/GitLab checks. Treat \`not-found\` as stale runtime after Docker pruning; busy, unavailable, or conflicting evidence suppresses destructive suggestions. Explicit \`gc\`/\`down\` can remove exact stale registration only after expected-ID \`NotFound\` proof and ownership revalidation. GC never removes Git worktrees, branches, or prune state. Git has no worktree-removal hook.
@@ -376,8 +377,9 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
376
377
  - \`devrouter init [--write-agents] [--write-skill]\`: print AI onboarding prompt (non-mutating by default)
377
378
  - \`devrouter -V [--repo .]\`: show installed CLI version, local repo version, and next upgrade target
378
379
  - \`devrouter upgrade [version] [--repo .]\`: list upgrade targets or print target Agent Adaptation Prompt
379
- - \`devrouter setup --yes [--repo .] [--json]\`: first-run machine setup plus structured diagnostics
380
+ - \`devrouter setup --yes [--repo .] [--json] [--workspace-runtime <devpod|devsy>]\`: first-run machine setup plus structured diagnostics; explicit Devsy selection acquires its verified agent
380
381
  - \`devrouter ensure [path] [--profile <name>] [--open] [--json]\`: canonical startup/reconciliation for primary and linked checkouts; a managed profile selects its independent resource dimensions
382
+ - \`devrouter profile resolve --repo <path> [--profile <selection>] [--json]\`: resolve exact profile resources for automation without starting or inspecting a runtime
381
383
  - \`devrouter stop [path] [--delete] [--json]\`: stop the exact workspace runtime and remove exact routes; \`--delete\` explicitly deletes its ownership-proven data without removing the checkout
382
384
  - \`devrouter exec [path] -- <command...>\`: literal one-shot command inside the exact running workspace runtime
383
385
  - \`devrouter up\` / \`devrouter down\`: start/stop shared Traefik router
@@ -411,6 +413,9 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
411
413
 
412
414
  For devcontainer onboarding:
413
415
 
416
+ When Devsy owns the workspace, first run
417
+ \`devrouter setup --repo . --yes --workspace-runtime devsy --json\`.
418
+
414
419
  1. \`devrouter setup --repo . --yes --json\`
415
420
  2. \`devrouter doctor --repo . --json\`
416
421
  3. \`devrouter repo inspect --repo . --json\`
@@ -720,6 +725,9 @@ function runDockerCompose(args) {
720
725
  function startRouterStack() {
721
726
  runDockerCompose(["up", "-d"]);
722
727
  }
728
+ function restartRouterStack() {
729
+ runDockerCompose(["restart", "traefik"]);
730
+ }
723
731
  function stopRouterStack() {
724
732
  runDockerCompose(["down"]);
725
733
  }
@@ -1406,6 +1414,12 @@ function hostRouteFromInput(input2, existing, now) {
1406
1414
  function sanitizeKey(value) {
1407
1415
  return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
1408
1416
  }
1417
+ function buildHostRouteRouterKey(routeId) {
1418
+ return `host-${sanitizeKey(routeId)}`;
1419
+ }
1420
+ function buildHostRouteRouterName(repoPath, name) {
1421
+ return `${buildHostRouteRouterKey(buildHostRouteId(repoPath, name))}@file`;
1422
+ }
1409
1423
  function tcpTlsOptionName(tcpProtocol) {
1410
1424
  return `devrouter-tcp-${tcpProtocol}`;
1411
1425
  }
@@ -1416,7 +1430,7 @@ function buildHostRoutesDocument(routes, tlsEnabled) {
1416
1430
  const tcpServices = {};
1417
1431
  const tlsOptions = {};
1418
1432
  for (const route of routes) {
1419
- const key = `host-${sanitizeKey(route.id)}`;
1433
+ const key = buildHostRouteRouterKey(route.id);
1420
1434
  if (route.protocol === "tcp") {
1421
1435
  const alpn = route.tcpProtocol ? TCP_TLS_ALPN_PROTOCOLS[route.tcpProtocol] : void 0;
1422
1436
  let tls = {};
@@ -2654,7 +2668,7 @@ function loadRepoConfig(repoPath) {
2654
2668
  const config = parseConfig(parsed ?? {}, configPath);
2655
2669
  const requiredVersion = config.devrouter?.version;
2656
2670
  if (requiredVersion && !hasWarnedVersionMismatch) {
2657
- const cliVersion = true ? "0.0.46" : "0.0.0-dev";
2671
+ const cliVersion = true ? "0.0.48" : "0.0.0-dev";
2658
2672
  if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
2659
2673
  hasWarnedVersionMismatch = true;
2660
2674
  process.stderr.write(
@@ -3185,6 +3199,7 @@ function buildOnboardingPrompt(options = {}) {
3185
3199
  `- When a workspace is active: hosts auto-namespace (\`web.localhost\` \u2192 \`web.<ws>.localhost\`), \`${WORKSPACE_PLACEHOLDER}\` in \`upstream\` is substituted with the token, and the docker \`router\` key is suffixed per workspace. Managed ensure rejects every HTTP/TCP upstream outside that exact alias namespace before mutation. The runtime config is computed in memory only \u2014 the committed \`.devrouter.yml\` is never rewritten.`,
3186
3200
  "- TLS: namespaced hosts (`web.<ws>.localhost`) are not covered by the `*.localhost` wildcard; devrouter auto-extends the mkcert cert SANs for active hosts when TLS is enabled.",
3187
3201
  "- Lifecycle: after one-time setup, use `devrouter ensure .` for both primary and linked checkouts; never branch on checkout kind or use live verify as startup. Managed consumer images contain no devrouter package/helper: ensure delivers its matching helper at runtime and invokes an exact captured snapshot of the repository-owned post-start adapter. Keep `.devrouter.yml` as the only consumer-side version pin. Use `devrouter stop .` for a non-destructive pause, `devrouter stop . --delete` only for explicit exact-owner cleanup without removing the checkout, and `devrouter exec . -- <command...>` for container commands. Never substitute raw DevPod/Devsy mutations; they bypass the machine-global ownership lock, which serializes provider mutations in a fair arrival-order queue and lets contenders wait up to thirty minutes with throttled stderr progress before failing with the queue position or holder PID and true durations. `workspace up` creates linked worktrees; destructive worktree removal and GC remain ledger-scoped.",
3202
+ "- Devsy agent readiness: run `devrouter setup --yes --workspace-runtime devsy` once so Devrouter acquires and verifies the pinned agent in its own machine cache. `doctor` reports `ready`, `missing`, `stale`, or `invalid` without network access, and `ensure` fails before the provider queue when readiness is not `ready`. An explicit `DEVSY_AGENT_BINARY` remains authoritative and must match a pinned official asset.",
3188
3203
  "- A managed adapter plus `postCreateCommand` requires `waitFor` exactly `postCreateCommand` or `postStartCommand`; managed selective config preserves lifecycle fields and changes only `runServices`.",
3189
3204
  "- Managed process reuse fingerprints command argv, workspace identity, and the exact adapter snapshot. If a non-secret runtime value also affects reuse, set `DEVROUTER_PROCESS_FINGERPRINT_ENV` to its comma-separated environment names; secret-like names are rejected and raw values are never persisted.",
3190
3205
  "- Owner status is `present`, `missing`, `locked`, or `conflict`. Dirty or locked full down fails before side effects. `workspace gc` is a dry run; only `--yes` deletes exact eligible missing resources and records, never Git worktrees, branches, or prune state.",
@@ -3216,7 +3231,7 @@ function buildOnboardingPrompt(options = {}) {
3216
3231
  "- Warning: Do not run migration/seed until env probe confirms expected DB variables and values.",
3217
3232
  "",
3218
3233
  "Required workflow:",
3219
- "1) Run `devrouter setup --yes --json` for devrouter-owned machine state; use `devrouter doctor --repo <REPO_PATH> --json` to diagnose missing prerequisites without mutation.",
3234
+ "1) Run `devrouter setup --yes --json` for devrouter-owned machine state. When selecting Devsy, use `devrouter setup --yes --workspace-runtime devsy --json` to acquire its verified agent. Use `devrouter doctor --repo <REPO_PATH> --json` to diagnose missing prerequisites without mutation.",
3220
3235
  "2) Run `devrouter repo inspect --repo <REPO_PATH> --json` before editing files.",
3221
3236
  "3) For the supported Node/pnpm/Postgres devcontainer shape, run `devrouter repo devcontainer write --repo <REPO_PATH> --dry-run --json`, review the plan, then run `devrouter repo devcontainer write --repo <REPO_PATH> --yes`.",
3222
3237
  "4) For unsupported shapes or custom existing files, make minimal manual edits and explain the assumptions.",
@@ -3226,6 +3241,7 @@ function buildOnboardingPrompt(options = {}) {
3226
3241
  "",
3227
3242
  "Validation commands to run/report for the devcontainer path:",
3228
3243
  "- devrouter setup --yes --json",
3244
+ "- Devsy runtime setup: devrouter setup --yes --workspace-runtime devsy --json",
3229
3245
  "- devrouter doctor --repo <REPO_PATH> --json",
3230
3246
  "- devrouter repo inspect --repo <REPO_PATH> --json",
3231
3247
  "- devrouter repo devcontainer write --repo <REPO_PATH> --dry-run --json",
@@ -3353,6 +3369,10 @@ var init_ai_prompt = __esm({
3353
3369
  command: "devrouter ensure [path] [--profile <name>] [--open] [--json]",
3354
3370
  purpose: "Canonical startup and proof for an exact primary or linked checkout; atomically publishes routes after readiness."
3355
3371
  },
3372
+ {
3373
+ command: "devrouter profile resolve --repo <path> [--profile <selection>] [--json]",
3374
+ purpose: "Resolve exact profile resources without starting or inspecting a runtime; fail closed on invalid selections or unknown report schemas."
3375
+ },
3356
3376
  {
3357
3377
  command: "devrouter stop [path] [--delete] [--json]",
3358
3378
  purpose: "Stop the exact checkout workspace runtime and routes; --delete explicitly deletes the ownership-proven runtime without removing the checkout."
@@ -4055,6 +4075,384 @@ var init_upgrade2 = __esm({
4055
4075
  }
4056
4076
  });
4057
4077
 
4078
+ // src/core/devsy-agent.ts
4079
+ function parseVersion(output2) {
4080
+ return output2?.match(
4081
+ /\bv?(\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?)\b/
4082
+ )?.[1];
4083
+ }
4084
+ function devsyAgentRepairSuggestion(inspection) {
4085
+ if (inspection.state === "stale") {
4086
+ return `Install Devsy ${SUPPORTED_DEVSY_VERSION} for a supported host, then run: ${DEVSY_AGENT_SETUP_COMMAND}`;
4087
+ }
4088
+ return inspection.source === "explicit" ? `Fix or unset DEVSY_AGENT_BINARY, then run: ${DEVSY_AGENT_SETUP_COMMAND}` : `Run: ${DEVSY_AGENT_SETUP_COMMAND}`;
4089
+ }
4090
+ function installedVersionOutput() {
4091
+ const result = (0, import_node_child_process4.spawnSync)("devsy", ["--version"], { encoding: "utf-8" });
4092
+ if (result.error || result.status !== 0) return void 0;
4093
+ return [result.stdout, result.stderr].filter(Boolean).join("\n").trim() || void 0;
4094
+ }
4095
+ function nativeAssetName(platform, arch) {
4096
+ if (platform !== "darwin") return void 0;
4097
+ if (arch === "arm64") return "devsy-linux-arm64";
4098
+ if (arch === "x64") return "devsy-linux-amd64";
4099
+ return void 0;
4100
+ }
4101
+ function managedBinaryPath(cacheRoot, asset) {
4102
+ return import_node_path9.default.join(cacheRoot, `v${SUPPORTED_DEVSY_VERSION}`, asset.name);
4103
+ }
4104
+ function hashOpenFile(file) {
4105
+ const digest = (0, import_node_crypto4.createHash)("sha256");
4106
+ const buffer = Buffer.allocUnsafe(1024 * 1024);
4107
+ let position = 0;
4108
+ for (; ; ) {
4109
+ const bytesRead = import_node_fs9.default.readSync(file, buffer, 0, buffer.length, position);
4110
+ if (bytesRead === 0) break;
4111
+ digest.update(buffer.subarray(0, bytesRead));
4112
+ position += bytesRead;
4113
+ }
4114
+ return digest.digest("hex");
4115
+ }
4116
+ function inspectBinary(binaryPath, assets, missingState) {
4117
+ let file;
4118
+ try {
4119
+ file = import_node_fs9.default.openSync(binaryPath, import_node_fs9.default.constants.O_RDONLY | import_node_fs9.default.constants.O_NOFOLLOW);
4120
+ const stat = import_node_fs9.default.fstatSync(file);
4121
+ if (!stat.isFile()) {
4122
+ return { state: "invalid", reason: "the selected source is not a regular file" };
4123
+ }
4124
+ const sizeMatches = assets.filter((asset2) => asset2.size === stat.size);
4125
+ if (sizeMatches.length === 0) {
4126
+ return { state: "invalid", reason: "the selected source has an unexpected size" };
4127
+ }
4128
+ const sha2562 = hashOpenFile(file);
4129
+ const asset = sizeMatches.find((candidate) => candidate.sha256 === sha2562);
4130
+ return asset ? { state: "ready", reason: "the selected source matches the pinned manifest", asset } : { state: "invalid", reason: "the selected source has an unexpected digest" };
4131
+ } catch (error) {
4132
+ const code = error.code;
4133
+ return code === "ENOENT" ? { state: missingState, reason: "the selected source is missing" } : { state: "invalid", reason: "the selected source is not a readable regular file" };
4134
+ } finally {
4135
+ if (file !== void 0) import_node_fs9.default.closeSync(file);
4136
+ }
4137
+ }
4138
+ function resolvedOptions(options) {
4139
+ return {
4140
+ env: options.env ?? process.env,
4141
+ platform: options.platform ?? process.platform,
4142
+ arch: options.arch ?? process.arch,
4143
+ versionOutput: options.versionOutput ?? installedVersionOutput(),
4144
+ cacheRoot: options.cacheRoot ?? import_node_path9.default.join(CACHE_DIR, "devsy", "agents"),
4145
+ lockPath: options.lockPath ?? DEVSY_AGENT_CACHE_LOCK,
4146
+ assets: options.assets ?? DEVSY_AGENT_ASSETS,
4147
+ nativeAssetName: options.nativeAssetName
4148
+ };
4149
+ }
4150
+ function inspectDevsyAgent(options = {}) {
4151
+ const resolved = resolvedOptions(options);
4152
+ const installedVersion = parseVersion(resolved.versionOutput);
4153
+ const explicitPath = resolved.env.DEVSY_AGENT_BINARY?.trim();
4154
+ const source = explicitPath ? "explicit" : "managed";
4155
+ if (installedVersion !== SUPPORTED_DEVSY_VERSION) {
4156
+ return {
4157
+ state: "stale",
4158
+ source,
4159
+ reason: installedVersion ? `installed Devsy ${installedVersion} is not supported by this Devrouter release` : "the supported Devsy version is not available",
4160
+ installedVersion
4161
+ };
4162
+ }
4163
+ if (explicitPath) {
4164
+ return {
4165
+ ...inspectBinary(explicitPath, resolved.assets, "invalid"),
4166
+ source,
4167
+ binaryPath: explicitPath,
4168
+ installedVersion
4169
+ };
4170
+ }
4171
+ const expectedName = resolved.nativeAssetName ?? nativeAssetName(resolved.platform, resolved.arch);
4172
+ const asset = resolved.assets.find((candidate) => candidate.name === expectedName);
4173
+ if (!asset) {
4174
+ return {
4175
+ state: "stale",
4176
+ source,
4177
+ reason: `this Devrouter release has no pinned Devsy agent for ${resolved.platform}/${resolved.arch}`,
4178
+ installedVersion
4179
+ };
4180
+ }
4181
+ const binaryPath = managedBinaryPath(resolved.cacheRoot, asset);
4182
+ return {
4183
+ ...inspectBinary(binaryPath, [asset], "missing"),
4184
+ source,
4185
+ binaryPath,
4186
+ asset,
4187
+ installedVersion
4188
+ };
4189
+ }
4190
+ async function writeAll(handle, chunk, position) {
4191
+ let offset = 0;
4192
+ while (offset < chunk.byteLength) {
4193
+ const { bytesWritten } = await handle.write(
4194
+ chunk,
4195
+ offset,
4196
+ chunk.byteLength - offset,
4197
+ position + offset
4198
+ );
4199
+ if (bytesWritten === 0) throw new Error("Devsy agent download stopped making progress");
4200
+ offset += bytesWritten;
4201
+ }
4202
+ }
4203
+ function appendBoundedTail(current, chunk) {
4204
+ if (chunk.length >= GITHUB_CLI_STDERR_TAIL_BYTES) {
4205
+ return Buffer.from(chunk.subarray(chunk.length - GITHUB_CLI_STDERR_TAIL_BYTES));
4206
+ }
4207
+ const combined = Buffer.concat([current, chunk]);
4208
+ return combined.length > GITHUB_CLI_STDERR_TAIL_BYTES ? combined.subarray(combined.length - GITHUB_CLI_STDERR_TAIL_BYTES) : combined;
4209
+ }
4210
+ function errorMessage(error) {
4211
+ return error instanceof Error ? error.message : String(error);
4212
+ }
4213
+ async function downloadWithGitHubCli(asset, writeChunk) {
4214
+ const child = (0, import_node_child_process4.spawn)(
4215
+ "gh",
4216
+ [
4217
+ "api",
4218
+ `repos/devsy-org/devsy/releases/assets/${asset.githubAssetId}`,
4219
+ "-H",
4220
+ "Accept: application/octet-stream"
4221
+ ],
4222
+ { stdio: ["ignore", "pipe", "pipe"] }
4223
+ );
4224
+ if (!child.stdout || !child.stderr) {
4225
+ child.kill();
4226
+ throw new Error("GitHub CLI fallback did not create output pipes");
4227
+ }
4228
+ let stderrTail = Buffer.alloc(0);
4229
+ child.stderr.on("data", (chunk) => {
4230
+ const value = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
4231
+ stderrTail = appendBoundedTail(stderrTail, value);
4232
+ });
4233
+ let spawnError;
4234
+ const completion = new Promise((resolve) => {
4235
+ child.once("error", (error) => {
4236
+ spawnError = error;
4237
+ });
4238
+ child.once("close", (status) => {
4239
+ resolve(status);
4240
+ });
4241
+ });
4242
+ try {
4243
+ for await (const chunk of child.stdout) {
4244
+ await writeChunk(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
4245
+ }
4246
+ const status = await completion;
4247
+ if (spawnError) throw spawnError;
4248
+ if (status !== 0) {
4249
+ const details = stderrTail.toString("utf-8").trim();
4250
+ throw new Error(
4251
+ `gh api exited with status ${status ?? "unknown"}${details ? `: ${details}` : ""}`
4252
+ );
4253
+ }
4254
+ } catch (error) {
4255
+ if (child.exitCode === null) child.kill();
4256
+ await completion.catch(() => void 0);
4257
+ throw error;
4258
+ }
4259
+ }
4260
+ function fsyncDirectory2(directory) {
4261
+ const handle = import_node_fs9.default.openSync(directory, "r");
4262
+ try {
4263
+ import_node_fs9.default.fsyncSync(handle);
4264
+ } finally {
4265
+ import_node_fs9.default.closeSync(handle);
4266
+ }
4267
+ }
4268
+ async function downloadAndPublish(asset, binaryPath, fetcher, githubCliDownloader) {
4269
+ const directory = import_node_path9.default.dirname(binaryPath);
4270
+ import_node_fs9.default.mkdirSync(directory, { recursive: true, mode: 448 });
4271
+ const temporaryPath = import_node_path9.default.join(
4272
+ directory,
4273
+ `.${import_node_path9.default.basename(binaryPath)}.${process.pid}.${(0, import_node_crypto4.randomUUID)()}.tmp`
4274
+ );
4275
+ let handle;
4276
+ try {
4277
+ let response;
4278
+ let directFailure;
4279
+ try {
4280
+ response = await fetcher(asset.url);
4281
+ } catch (error) {
4282
+ directFailure = error;
4283
+ }
4284
+ handle = await import_node_fs9.default.promises.open(temporaryPath, "wx", 384);
4285
+ const digest = (0, import_node_crypto4.createHash)("sha256");
4286
+ let size = 0;
4287
+ const writeChunk = async (chunk) => {
4288
+ size += chunk.byteLength;
4289
+ if (size > asset.size) {
4290
+ throw new Error("Devsy agent download exceeded the pinned size");
4291
+ }
4292
+ digest.update(chunk);
4293
+ await writeAll(handle, chunk, size - chunk.byteLength);
4294
+ };
4295
+ let transport;
4296
+ if (response) {
4297
+ if (!response.ok) {
4298
+ throw new Error(`Devsy agent download failed with HTTP ${response.status}`);
4299
+ }
4300
+ if (!response.body) throw new Error("Devsy agent download returned no response body");
4301
+ const reader = response.body.getReader();
4302
+ try {
4303
+ for (; ; ) {
4304
+ const { done, value } = await reader.read();
4305
+ if (done) break;
4306
+ await writeChunk(value);
4307
+ }
4308
+ } catch (error) {
4309
+ await reader.cancel().catch(() => void 0);
4310
+ throw error;
4311
+ }
4312
+ transport = "https";
4313
+ } else {
4314
+ try {
4315
+ await githubCliDownloader(asset, writeChunk);
4316
+ } catch (fallbackError) {
4317
+ throw new Error(
4318
+ `Devsy agent direct HTTPS download failed: ${errorMessage(directFailure)}; GitHub CLI fallback failed: ${errorMessage(fallbackError)}. Restore direct GitHub release access or install and authenticate gh.`
4319
+ );
4320
+ }
4321
+ transport = "github-cli";
4322
+ }
4323
+ if (size !== asset.size) throw new Error("Devsy agent download has an unexpected size");
4324
+ if (digest.digest("hex") !== asset.sha256) {
4325
+ throw new Error("Devsy agent download has an unexpected digest");
4326
+ }
4327
+ await handle.sync();
4328
+ await handle.chmod(448);
4329
+ await handle.sync();
4330
+ await handle.close();
4331
+ handle = void 0;
4332
+ await import_node_fs9.default.promises.rename(temporaryPath, binaryPath);
4333
+ fsyncDirectory2(directory);
4334
+ return transport;
4335
+ } finally {
4336
+ if (handle) await handle.close();
4337
+ await import_node_fs9.default.promises.rm(temporaryPath, { force: true });
4338
+ }
4339
+ }
4340
+ async function prepareDevsyAgent(options = {}) {
4341
+ const resolved = resolvedOptions(options);
4342
+ const inspectOptions = resolved;
4343
+ const before = inspectDevsyAgent(inspectOptions);
4344
+ if (before.state === "ready" && before.binaryPath && before.asset) {
4345
+ return {
4346
+ binaryPath: before.binaryPath,
4347
+ source: before.source,
4348
+ asset: before.asset,
4349
+ changed: false,
4350
+ transport: "existing"
4351
+ };
4352
+ }
4353
+ if (before.source === "explicit" || before.state === "stale" || !before.binaryPath || !before.asset) {
4354
+ throw new DevsyAgentReadinessError(before);
4355
+ }
4356
+ const lock = options.withLock ?? ((operation) => {
4357
+ import_node_fs9.default.mkdirSync(import_node_path9.default.dirname(resolved.lockPath), { recursive: true });
4358
+ return withFileLock(
4359
+ resolved.lockPath,
4360
+ {
4361
+ activity: "Devsy agent acquisition",
4362
+ waitMs: DEVSY_AGENT_LOCK_WAIT_MS,
4363
+ fair: true,
4364
+ onWait: createStderrWaitReporter("Devsy agent acquisition", "the managed cache")
4365
+ },
4366
+ operation
4367
+ );
4368
+ });
4369
+ return lock(async () => {
4370
+ const current = inspectDevsyAgent(inspectOptions);
4371
+ if (current.state === "ready" && current.binaryPath && current.asset) {
4372
+ return {
4373
+ binaryPath: current.binaryPath,
4374
+ source: current.source,
4375
+ asset: current.asset,
4376
+ changed: false,
4377
+ transport: "existing"
4378
+ };
4379
+ }
4380
+ if (current.source === "explicit" || current.state === "stale" || !current.binaryPath || !current.asset) {
4381
+ throw new DevsyAgentReadinessError(current);
4382
+ }
4383
+ const transport = await downloadAndPublish(
4384
+ current.asset,
4385
+ current.binaryPath,
4386
+ options.fetcher ?? fetch,
4387
+ options.githubCliDownloader ?? downloadWithGitHubCli
4388
+ );
4389
+ const published = inspectDevsyAgent(inspectOptions);
4390
+ if (published.state !== "ready" || !published.binaryPath || !published.asset) {
4391
+ throw new DevsyAgentReadinessError(published);
4392
+ }
4393
+ return {
4394
+ binaryPath: published.binaryPath,
4395
+ source: published.source,
4396
+ asset: published.asset,
4397
+ changed: true,
4398
+ transport
4399
+ };
4400
+ });
4401
+ }
4402
+ function requireReadyDevsyAgent(options = {}) {
4403
+ const inspection = inspectDevsyAgent(options);
4404
+ if (inspection.state !== "ready" || !inspection.binaryPath || !inspection.asset) {
4405
+ throw new DevsyAgentReadinessError(inspection);
4406
+ }
4407
+ return {
4408
+ binaryPath: inspection.binaryPath,
4409
+ source: inspection.source,
4410
+ asset: inspection.asset,
4411
+ changed: false,
4412
+ transport: "existing"
4413
+ };
4414
+ }
4415
+ var import_node_child_process4, import_node_crypto4, import_node_fs9, import_node_path9, SUPPORTED_DEVSY_VERSION, DEVSY_AGENT_SETUP_COMMAND, DEVSY_AGENT_ASSETS, DevsyAgentReadinessError, DEVSY_AGENT_CACHE_LOCK, DEVSY_AGENT_LOCK_WAIT_MS, GITHUB_CLI_STDERR_TAIL_BYTES;
4416
+ var init_devsy_agent = __esm({
4417
+ "src/core/devsy-agent.ts"() {
4418
+ "use strict";
4419
+ import_node_child_process4 = require("child_process");
4420
+ import_node_crypto4 = require("crypto");
4421
+ import_node_fs9 = __toESM(require("fs"));
4422
+ import_node_path9 = __toESM(require("path"));
4423
+ init_file_lock();
4424
+ init_router();
4425
+ SUPPORTED_DEVSY_VERSION = "1.16.2";
4426
+ DEVSY_AGENT_SETUP_COMMAND = "devrouter setup --yes --workspace-runtime devsy";
4427
+ DEVSY_AGENT_ASSETS = [
4428
+ {
4429
+ githubAssetId: 529830010,
4430
+ name: "devsy-linux-arm64",
4431
+ size: 124518562,
4432
+ sha256: "31060b96486b5398f2aa3ee0875b2555782a2db0954a799d387be38ed4b4990d",
4433
+ url: "https://github.com/devsy-org/devsy/releases/download/v1.16.2/devsy-linux-arm64"
4434
+ },
4435
+ {
4436
+ githubAssetId: 529830011,
4437
+ name: "devsy-linux-amd64",
4438
+ size: 133505186,
4439
+ sha256: "4983c52a3536c5a91d1b5f356a1c3428778ebf3f896d9897f60bce3978abc839",
4440
+ url: "https://github.com/devsy-org/devsy/releases/download/v1.16.2/devsy-linux-amd64"
4441
+ }
4442
+ ];
4443
+ DevsyAgentReadinessError = class extends Error {
4444
+ constructor(inspection) {
4445
+ super(`Devsy agent is ${inspection.state}: ${inspection.reason}`);
4446
+ this.inspection = inspection;
4447
+ this.name = "DevsyAgentReadinessError";
4448
+ }
4449
+ };
4450
+ DEVSY_AGENT_CACHE_LOCK = import_node_path9.default.join(DEVROUTER_HOME, "devsy-agent-cache.lock");
4451
+ DEVSY_AGENT_LOCK_WAIT_MS = 18e5;
4452
+ GITHUB_CLI_STDERR_TAIL_BYTES = 4096;
4453
+ }
4454
+ });
4455
+
4058
4456
  // src/core/docker.ts
4059
4457
  async function getDockerodeConstructor() {
4060
4458
  if (!dockerodeConstructorPromise) {
@@ -4066,7 +4464,7 @@ async function getDockerodeConstructor() {
4066
4464
  return dockerodeConstructorPromise;
4067
4465
  }
4068
4466
  function runDockerContextCommand(args) {
4069
- const result = (0, import_node_child_process4.spawnSync)("docker", ["context", ...args], {
4467
+ const result = (0, import_node_child_process5.spawnSync)("docker", ["context", ...args], {
4070
4468
  encoding: "utf-8"
4071
4469
  });
4072
4470
  if (result.status !== 0) {
@@ -4079,7 +4477,7 @@ function getCurrentDockerContext() {
4079
4477
  return runDockerContextCommand(["show"]);
4080
4478
  }
4081
4479
  function getDockerHostFromContext(context) {
4082
- const result = (0, import_node_child_process4.spawnSync)(
4480
+ const result = (0, import_node_child_process5.spawnSync)(
4083
4481
  "docker",
4084
4482
  ["context", "inspect", context, "--format", "{{ .Endpoints.docker.Host }}"],
4085
4483
  { encoding: "utf-8" }
@@ -4147,11 +4545,11 @@ async function networkExists(name) {
4147
4545
  return false;
4148
4546
  }
4149
4547
  }
4150
- var import_node_child_process4, dockerodeConstructorPromise;
4548
+ var import_node_child_process5, dockerodeConstructorPromise;
4151
4549
  var init_docker = __esm({
4152
4550
  "src/core/docker.ts"() {
4153
4551
  "use strict";
4154
- import_node_child_process4 = require("child_process");
4552
+ import_node_child_process5 = require("child_process");
4155
4553
  dockerodeConstructorPromise = null;
4156
4554
  }
4157
4555
  });
@@ -4178,11 +4576,11 @@ function parseDevcontainerConfig(contents, sourcePath) {
4178
4576
  return parsed;
4179
4577
  }
4180
4578
  function readDevcontainerConfig(repoPath) {
4181
- const sourcePath = import_node_path9.default.join(repoPath, ".devcontainer", "devcontainer.json");
4182
- if (!import_node_fs9.default.existsSync(sourcePath) || !import_node_fs9.default.lstatSync(sourcePath).isFile()) {
4579
+ const sourcePath = import_node_path10.default.join(repoPath, ".devcontainer", "devcontainer.json");
4580
+ if (!import_node_fs10.default.existsSync(sourcePath) || !import_node_fs10.default.lstatSync(sourcePath).isFile()) {
4183
4581
  throw new Error(`Managed Dev Container source config does not exist: ${sourcePath}`);
4184
4582
  }
4185
- const sourceContents = import_node_fs9.default.readFileSync(sourcePath, "utf-8");
4583
+ const sourceContents = import_node_fs10.default.readFileSync(sourcePath, "utf-8");
4186
4584
  return {
4187
4585
  sourcePath,
4188
4586
  sourceContents,
@@ -4198,12 +4596,12 @@ function assertManagedDevcontainerLifecycle(repoPath) {
4198
4596
  `Managed Dev Container source '${sourcePath}' defines postCreateCommand, but waitFor ${actual}. Set waitFor to 'postCreateCommand' or 'postStartCommand' before retrying devrouter ensure.`
4199
4597
  );
4200
4598
  }
4201
- var import_node_fs9, import_node_path9, import_jsonc_parser;
4599
+ var import_node_fs10, import_node_path10, import_jsonc_parser;
4202
4600
  var init_devcontainer_config = __esm({
4203
4601
  "src/core/devcontainer-config.ts"() {
4204
4602
  "use strict";
4205
- import_node_fs9 = __toESM(require("fs"));
4206
- import_node_path9 = __toESM(require("path"));
4603
+ import_node_fs10 = __toESM(require("fs"));
4604
+ import_node_path10 = __toESM(require("path"));
4207
4605
  import_jsonc_parser = require("jsonc-parser");
4208
4606
  }
4209
4607
  });
@@ -4213,7 +4611,7 @@ function renderDeliveryScript(targetPath) {
4213
4611
  if (!/^\/tmp\/devrouter\/bin\/[a-zA-Z0-9._-]+$/.test(targetPath)) {
4214
4612
  throw new Error(`Unsafe managed runtime delivery path: ${targetPath}`);
4215
4613
  }
4216
- const temporaryPrefix = import_node_path10.default.posix.basename(targetPath);
4614
+ const temporaryPrefix = import_node_path11.default.posix.basename(targetPath);
4217
4615
  return `set -eu
4218
4616
  umask 077
4219
4617
  runtime_root=/tmp/devrouter
@@ -4237,7 +4635,7 @@ function commandFailure(result) {
4237
4635
  return [result.error?.message, result.stderr, result.stdout].filter(Boolean).map((value) => String(value).trim()).filter(Boolean).join("\n");
4238
4636
  }
4239
4637
  function deliverRuntimeFile(containerId, targetPath, contents, label) {
4240
- const result = (0, import_node_child_process5.spawnSync)(
4638
+ const result = (0, import_node_child_process6.spawnSync)(
4241
4639
  "docker",
4242
4640
  ["exec", "-i", containerId, "sh", "-c", renderDeliveryScript(targetPath)],
4243
4641
  { input: contents, encoding: "utf-8" }
@@ -4248,32 +4646,32 @@ function deliverRuntimeFile(containerId, targetPath, contents, label) {
4248
4646
  }
4249
4647
  }
4250
4648
  function readRegularFileBytes(filePath) {
4251
- if (!import_node_fs10.default.existsSync(filePath) || !import_node_fs10.default.lstatSync(filePath).isFile()) return void 0;
4252
- return import_node_fs10.default.readFileSync(filePath);
4649
+ if (!import_node_fs11.default.existsSync(filePath) || !import_node_fs11.default.lstatSync(filePath).isFile()) return void 0;
4650
+ return import_node_fs11.default.readFileSync(filePath);
4253
4651
  }
4254
4652
  function readRegularFile(filePath) {
4255
4653
  return readRegularFileBytes(filePath)?.toString("utf-8");
4256
4654
  }
4257
4655
  function adapterFingerprint(adapter) {
4258
- return (0, import_node_crypto4.createHash)("sha256").update(adapter).digest("hex");
4656
+ return (0, import_node_crypto5.createHash)("sha256").update(adapter).digest("hex");
4259
4657
  }
4260
4658
  function resolveProcessHelperPath() {
4261
4659
  const candidates = [
4262
- import_node_path10.default.resolve(__dirname, "..", "bin", "devrouter-process"),
4263
- import_node_path10.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
4660
+ import_node_path11.default.resolve(__dirname, "..", "bin", "devrouter-process"),
4661
+ import_node_path11.default.resolve(__dirname, "..", "..", "bin", "devrouter-process")
4264
4662
  ];
4265
- const helperPath = candidates.find((candidate) => import_node_fs10.default.existsSync(candidate));
4663
+ const helperPath = candidates.find((candidate) => import_node_fs11.default.existsSync(candidate));
4266
4664
  if (!helperPath) {
4267
4665
  throw new Error("Could not locate the packaged devrouter-process helper.");
4268
4666
  }
4269
4667
  return helperPath;
4270
4668
  }
4271
4669
  function resolveManagedPostStartPlan(repoPath) {
4272
- const adapterPath = import_node_path10.default.join(repoPath, MANAGED_ADAPTER_PATH);
4670
+ const adapterPath = import_node_path11.default.join(repoPath, MANAGED_ADAPTER_PATH);
4273
4671
  const adapterBytes = readRegularFileBytes(adapterPath);
4274
4672
  const adapterText = adapterBytes?.toString("utf-8");
4275
- const dockerfilePath = import_node_path10.default.join(repoPath, ".devcontainer", "Dockerfile");
4276
- const devcontainerPath = import_node_path10.default.join(repoPath, ".devcontainer", "devcontainer.json");
4673
+ const dockerfilePath = import_node_path11.default.join(repoPath, ".devcontainer", "Dockerfile");
4674
+ const devcontainerPath = import_node_path11.default.join(repoPath, ".devcontainer", "devcontainer.json");
4277
4675
  const dockerfile = readRegularFile(dockerfilePath) ?? "";
4278
4676
  const devcontainer = readRegularFile(devcontainerPath) ?? "";
4279
4677
  const devrouterPattern = [adapterText, dockerfile, devcontainer].filter((value) => value !== void 0).some(
@@ -4314,7 +4712,7 @@ function resolveManagedPostStartPlan(repoPath) {
4314
4712
  }
4315
4713
  function runManagedPostStart(options) {
4316
4714
  if (options.plan.kind !== "runtime") return;
4317
- const helper = import_node_fs10.default.readFileSync(resolveProcessHelperPath());
4715
+ const helper = import_node_fs11.default.readFileSync(resolveProcessHelperPath());
4318
4716
  deliverRuntimeFile(
4319
4717
  options.container.id,
4320
4718
  RUNTIME_HELPER_PATH,
@@ -4328,7 +4726,7 @@ function runManagedPostStart(options) {
4328
4726
  options.plan.adapterContents,
4329
4727
  "the managed post-start adapter"
4330
4728
  );
4331
- const started = (0, import_node_child_process5.spawnSync)(
4729
+ const started = (0, import_node_child_process6.spawnSync)(
4332
4730
  "docker",
4333
4731
  [
4334
4732
  "exec",
@@ -4366,7 +4764,7 @@ function assertSafeProcessName(name) {
4366
4764
  }
4367
4765
  function runManagedProcessAction(options) {
4368
4766
  assertSafeProcessName(options.name);
4369
- const result = (0, import_node_child_process5.spawnSync)(
4767
+ const result = (0, import_node_child_process6.spawnSync)(
4370
4768
  "docker",
4371
4769
  [
4372
4770
  "exec",
@@ -4402,14 +4800,14 @@ function runManagedProcessAction(options) {
4402
4800
  }
4403
4801
  return "drifted";
4404
4802
  }
4405
- var import_node_child_process5, import_node_crypto4, import_node_fs10, import_node_path10, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH, ADAPTER_WRAPPER;
4803
+ var import_node_child_process6, import_node_crypto5, import_node_fs11, import_node_path11, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH, ADAPTER_WRAPPER;
4406
4804
  var init_managed_post_start = __esm({
4407
4805
  "src/core/managed-post-start.ts"() {
4408
4806
  "use strict";
4409
- import_node_child_process5 = require("child_process");
4410
- import_node_crypto4 = require("crypto");
4411
- import_node_fs10 = __toESM(require("fs"));
4412
- import_node_path10 = __toESM(require("path"));
4807
+ import_node_child_process6 = require("child_process");
4808
+ import_node_crypto5 = require("crypto");
4809
+ import_node_fs11 = __toESM(require("fs"));
4810
+ import_node_path11 = __toESM(require("path"));
4413
4811
  init_devcontainer_config();
4414
4812
  MANAGED_MARKER = "devrouter:managed devcontainer";
4415
4813
  MANAGED_ADAPTER_PATH = ".devcontainer/post-start.sh";
@@ -4476,7 +4874,7 @@ function isLoopbackHost(host) {
4476
4874
  return host === "127.0.0.1" || host === "localhost" || host === "host.docker.internal";
4477
4875
  }
4478
4876
  function inspectCompose(composeFile, _workspace) {
4479
- if (!import_node_fs11.default.existsSync(composeFile)) {
4877
+ if (!import_node_fs12.default.existsSync(composeFile)) {
4480
4878
  return {
4481
4879
  aliases: [],
4482
4880
  publishedPorts: [],
@@ -4485,7 +4883,7 @@ function inspectCompose(composeFile, _workspace) {
4485
4883
  };
4486
4884
  }
4487
4885
  try {
4488
- const raw = import_node_fs11.default.readFileSync(composeFile, "utf-8");
4886
+ const raw = import_node_fs12.default.readFileSync(composeFile, "utf-8");
4489
4887
  const parsed = import_yaml3.default.parse(raw);
4490
4888
  const root = asRecord(parsed);
4491
4889
  const services = asRecord(root?.services);
@@ -4542,11 +4940,11 @@ function routedProxyApps(config) {
4542
4940
  );
4543
4941
  }
4544
4942
  function buildDevcontainerChecks(repoPath, config, workspace) {
4545
- const devcontainerDir = import_node_path11.default.join(repoPath, ".devcontainer");
4546
- if (!import_node_fs11.default.existsSync(devcontainerDir)) {
4943
+ const devcontainerDir = import_node_path12.default.join(repoPath, ".devcontainer");
4944
+ if (!import_node_fs12.default.existsSync(devcontainerDir)) {
4547
4945
  return [];
4548
4946
  }
4549
- const compose = inspectCompose(import_node_path11.default.join(devcontainerDir, "docker-compose.yml"), workspace);
4947
+ const compose = inspectCompose(import_node_path12.default.join(devcontainerDir, "docker-compose.yml"), workspace);
4550
4948
  const checks = [];
4551
4949
  if (compose.parseError) {
4552
4950
  checks.push({
@@ -4573,9 +4971,9 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
4573
4971
  details: compose.publishedPorts.length > 0 ? compose.publishedPorts.join(", ") : void 0,
4574
4972
  suggestion: compose.parseError || compose.publishedPorts.length > 0 ? "Remove published ports and route services through devnet aliases with devrouter proxy apps." : void 0
4575
4973
  });
4576
- const dockerfilePath = import_node_path11.default.join(devcontainerDir, "Dockerfile");
4577
- const dockerfileExists = import_node_fs11.default.existsSync(dockerfilePath);
4578
- const dockerfile = dockerfileExists ? import_node_fs11.default.readFileSync(dockerfilePath, "utf-8") : "";
4974
+ const dockerfilePath = import_node_path12.default.join(devcontainerDir, "Dockerfile");
4975
+ const dockerfileExists = import_node_fs12.default.existsSync(dockerfilePath);
4976
+ const dockerfile = dockerfileExists ? import_node_fs12.default.readFileSync(dockerfilePath, "utf-8") : "";
4579
4977
  const devrouterArtifacts = ["@devrouter/cli", "devrouter-process"].filter(
4580
4978
  (artifact) => dockerfile.includes(artifact)
4581
4979
  );
@@ -4630,12 +5028,12 @@ function buildDevcontainerChecks(repoPath, config, workspace) {
4630
5028
  });
4631
5029
  return checks;
4632
5030
  }
4633
- var import_node_fs11, import_node_path11, import_yaml3;
5031
+ var import_node_fs12, import_node_path12, import_yaml3;
4634
5032
  var init_devcontainer_diagnostics = __esm({
4635
5033
  "src/core/devcontainer-diagnostics.ts"() {
4636
5034
  "use strict";
4637
- import_node_fs11 = __toESM(require("fs"));
4638
- import_node_path11 = __toESM(require("path"));
5035
+ import_node_fs12 = __toESM(require("fs"));
5036
+ import_node_path12 = __toESM(require("path"));
4639
5037
  import_yaml3 = __toESM(require("yaml"));
4640
5038
  init_managed_post_start();
4641
5039
  }
@@ -4643,18 +5041,18 @@ var init_devcontainer_diagnostics = __esm({
4643
5041
 
4644
5042
  // src/core/paths.ts
4645
5043
  function assertPathWithinRepo(filePath, repoRoot, label) {
4646
- const resolvedRoot = import_node_path12.default.resolve(repoRoot);
4647
- const resolved = import_node_path12.default.resolve(repoRoot, filePath);
4648
- if (resolved !== resolvedRoot && !resolved.startsWith(resolvedRoot + import_node_path12.default.sep)) {
5044
+ const resolvedRoot = import_node_path13.default.resolve(repoRoot);
5045
+ const resolved = import_node_path13.default.resolve(repoRoot, filePath);
5046
+ if (resolved !== resolvedRoot && !resolved.startsWith(resolvedRoot + import_node_path13.default.sep)) {
4649
5047
  throw new Error(`${label} path '${filePath}' escapes the repository root.`);
4650
5048
  }
4651
5049
  return resolved;
4652
5050
  }
4653
- var import_node_path12;
5051
+ var import_node_path13;
4654
5052
  var init_paths = __esm({
4655
5053
  "src/core/paths.ts"() {
4656
5054
  "use strict";
4657
- import_node_path12 = __toESM(require("path"));
5055
+ import_node_path13 = __toESM(require("path"));
4658
5056
  }
4659
5057
  });
4660
5058
 
@@ -4739,11 +5137,11 @@ function reconcileRouteRunConflict(repoPath, app) {
4739
5137
  }
4740
5138
  }
4741
5139
  }
4742
- var import_node_fs12;
5140
+ var import_node_fs13;
4743
5141
  var init_route_state = __esm({
4744
5142
  "src/core/route-state.ts"() {
4745
5143
  "use strict";
4746
- import_node_fs12 = __toESM(require("fs"));
5144
+ import_node_fs13 = __toESM(require("fs"));
4747
5145
  init_host_routes();
4748
5146
  init_workspace();
4749
5147
  }
@@ -4909,10 +5307,10 @@ var init_routes = __esm({
4909
5307
 
4910
5308
  // src/core/managed-runtime-state.ts
4911
5309
  function stateKey(repoPath, workspace) {
4912
- return (0, import_node_crypto5.createHash)("sha256").update(`${repoPath}\0${workspace ?? ""}`, "utf-8").digest("hex");
5310
+ return (0, import_node_crypto6.createHash)("sha256").update(`${repoPath}\0${workspace ?? ""}`, "utf-8").digest("hex");
4913
5311
  }
4914
5312
  function managedRuntimeStatePath(repoPath, workspace) {
4915
- return import_node_path13.default.join(DEVROUTER_HOME, "managed-runtime", `${stateKey(repoPath, workspace)}.json`);
5313
+ return import_node_path14.default.join(DEVROUTER_HOME, "managed-runtime", `${stateKey(repoPath, workspace)}.json`);
4916
5314
  }
4917
5315
  function isStringArray(value) {
4918
5316
  return Array.isArray(value) && value.every((item) => typeof item === "string") && new Set(value).size === value.length;
@@ -4975,10 +5373,10 @@ function validateState(value, repoPath, workspace) {
4975
5373
  }
4976
5374
  function readManagedRuntimeState(repoPath, workspace) {
4977
5375
  const statePath = managedRuntimeStatePath(repoPath, workspace);
4978
- if (!import_node_fs13.default.existsSync(statePath)) return void 0;
5376
+ if (!import_node_fs14.default.existsSync(statePath)) return void 0;
4979
5377
  let parsed;
4980
5378
  try {
4981
- parsed = JSON.parse(import_node_fs13.default.readFileSync(statePath, "utf-8"));
5379
+ parsed = JSON.parse(import_node_fs14.default.readFileSync(statePath, "utf-8"));
4982
5380
  } catch (error) {
4983
5381
  throw new Error(`Could not parse managed runtime state: ${String(error)}`);
4984
5382
  }
@@ -5000,13 +5398,13 @@ function markManagedRuntimeDegraded(state, transitionPhase) {
5000
5398
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
5001
5399
  });
5002
5400
  }
5003
- var import_node_crypto5, import_node_fs13, import_node_path13;
5401
+ var import_node_crypto6, import_node_fs14, import_node_path14;
5004
5402
  var init_managed_runtime_state = __esm({
5005
5403
  "src/core/managed-runtime-state.ts"() {
5006
5404
  "use strict";
5007
- import_node_crypto5 = require("crypto");
5008
- import_node_fs13 = __toESM(require("fs"));
5009
- import_node_path13 = __toESM(require("path"));
5405
+ import_node_crypto6 = require("crypto");
5406
+ import_node_fs14 = __toESM(require("fs"));
5407
+ import_node_path14 = __toESM(require("path"));
5010
5408
  init_atomic_file();
5011
5409
  init_router();
5012
5410
  }
@@ -5057,14 +5455,14 @@ function resolveComposeFiles(source, repoPath, linked) {
5057
5455
  "managedRuntime requires .devcontainer/devcontainer.json to define dockerComposeFile."
5058
5456
  );
5059
5457
  }
5060
- const directory = import_node_path14.default.join(repoPath, ".devcontainer");
5458
+ const directory = import_node_path15.default.join(repoPath, ".devcontainer");
5061
5459
  const files = references.map((reference) => {
5062
5460
  const resolved2 = assertPathWithinRepo(
5063
5461
  resolveComposeReference(reference, linked),
5064
5462
  directory,
5065
5463
  "dockerComposeFile"
5066
5464
  );
5067
- if (!import_node_fs14.default.existsSync(resolved2) || !import_node_fs14.default.lstatSync(resolved2).isFile()) {
5465
+ if (!import_node_fs15.default.existsSync(resolved2) || !import_node_fs15.default.lstatSync(resolved2).isFile()) {
5068
5466
  throw new Error(`Managed Dev Container compose file does not exist: ${resolved2}`);
5069
5467
  }
5070
5468
  return resolved2;
@@ -5072,7 +5470,7 @@ function resolveComposeFiles(source, repoPath, linked) {
5072
5470
  for (const file of files) {
5073
5471
  let parsed;
5074
5472
  try {
5075
- parsed = import_yaml4.default.parse(import_node_fs14.default.readFileSync(file, "utf-8"));
5473
+ parsed = import_yaml4.default.parse(import_node_fs15.default.readFileSync(file, "utf-8"));
5076
5474
  } catch (error) {
5077
5475
  throw new Error(
5078
5476
  `Could not parse managed Dev Container compose file '${file}': ${String(error)}`
@@ -5083,7 +5481,7 @@ function resolveComposeFiles(source, repoPath, linked) {
5083
5481
  }
5084
5482
  }
5085
5483
  const fileArgs = files.flatMap((file) => ["-f", file]);
5086
- const resolved = (0, import_node_child_process6.spawnSync)(
5484
+ const resolved = (0, import_node_child_process7.spawnSync)(
5087
5485
  "docker",
5088
5486
  [
5089
5487
  "compose",
@@ -5109,7 +5507,7 @@ function resolveComposeFiles(source, repoPath, linked) {
5109
5507
  return { directory, files, services: services.sort() };
5110
5508
  }
5111
5509
  function assertIgnoredGeneratedPath(repoPath, generatedPath) {
5112
- const ignored = (0, import_node_child_process6.spawnSync)(
5510
+ const ignored = (0, import_node_child_process7.spawnSync)(
5113
5511
  "git",
5114
5512
  ["-C", repoPath, "check-ignore", "--quiet", "--no-index", "--", generatedPath],
5115
5513
  { encoding: "utf-8" }
@@ -5121,12 +5519,12 @@ function assertIgnoredGeneratedPath(repoPath, generatedPath) {
5121
5519
  }
5122
5520
  }
5123
5521
  function assertGeneratedPathOwnership(generatedPath) {
5124
- if (!import_node_fs14.default.existsSync(generatedPath)) return;
5125
- const stat = import_node_fs14.default.lstatSync(generatedPath);
5522
+ if (!import_node_fs15.default.existsSync(generatedPath)) return;
5523
+ const stat = import_node_fs15.default.lstatSync(generatedPath);
5126
5524
  if (!stat.isFile() || stat.isSymbolicLink()) {
5127
5525
  throw new Error(`Managed Dev Container path '${generatedPath}' is not a regular file.`);
5128
5526
  }
5129
- const firstLine2 = import_node_fs14.default.readFileSync(generatedPath, "utf-8").split(/\r?\n/, 1)[0];
5527
+ const firstLine2 = import_node_fs15.default.readFileSync(generatedPath, "utf-8").split(/\r?\n/, 1)[0];
5130
5528
  if (firstLine2 !== MANAGED_DEVCONTAINER_MARKER) {
5131
5529
  throw new Error(
5132
5530
  `Managed Dev Container path '${generatedPath}' exists without the devrouter ownership marker.`
@@ -5137,7 +5535,7 @@ function isWildcard(values) {
5137
5535
  return values?.length === 1 && values[0] === "*";
5138
5536
  }
5139
5537
  function sha256(contents) {
5140
- return (0, import_node_crypto6.createHash)("sha256").update(contents, "utf-8").digest("hex");
5538
+ return (0, import_node_crypto7.createHash)("sha256").update(contents, "utf-8").digest("hex");
5141
5539
  }
5142
5540
  function safeComposeProject(value) {
5143
5541
  if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value)) {
@@ -5202,7 +5600,7 @@ function inspectManagedDevcontainerConfig(options) {
5202
5600
  const contents = `${MANAGED_DEVCONTAINER_MARKER}
5203
5601
  ${JSON.stringify(effective, null, 2)}
5204
5602
  `;
5205
- const generatedPath = import_node_path14.default.join(options.repoPath, MANAGED_DEVCONTAINER_PATH);
5603
+ const generatedPath = import_node_path15.default.join(options.repoPath, MANAGED_DEVCONTAINER_PATH);
5206
5604
  assertIgnoredGeneratedPath(options.repoPath, generatedPath);
5207
5605
  assertGeneratedPathOwnership(generatedPath);
5208
5606
  return {
@@ -5229,13 +5627,13 @@ function writeManagedDevcontainerConfig(plan) {
5229
5627
  function inspectManagedDevcontainerGeneratedConfig(plan) {
5230
5628
  let stat;
5231
5629
  try {
5232
- stat = import_node_fs14.default.lstatSync(plan.generatedPath);
5630
+ stat = import_node_fs15.default.lstatSync(plan.generatedPath);
5233
5631
  } catch (error) {
5234
5632
  if (error.code === "ENOENT") return { status: "missing" };
5235
5633
  throw error;
5236
5634
  }
5237
5635
  if (!stat.isFile() || stat.isSymbolicLink()) return { status: "foreign" };
5238
- const contents = import_node_fs14.default.readFileSync(plan.generatedPath, "utf-8");
5636
+ const contents = import_node_fs15.default.readFileSync(plan.generatedPath, "utf-8");
5239
5637
  if (!contents.startsWith(`${MANAGED_DEVCONTAINER_MARKER}
5240
5638
  `)) {
5241
5639
  return { status: "foreign" };
@@ -5247,17 +5645,17 @@ function inspectManagedDevcontainerGeneratedConfig(plan) {
5247
5645
  };
5248
5646
  }
5249
5647
  function removeManagedDevcontainerConfig(plan) {
5250
- if (!import_node_fs14.default.existsSync(plan.generatedPath)) return;
5251
- const stat = import_node_fs14.default.lstatSync(plan.generatedPath);
5648
+ if (!import_node_fs15.default.existsSync(plan.generatedPath)) return;
5649
+ const stat = import_node_fs15.default.lstatSync(plan.generatedPath);
5252
5650
  if (!stat.isFile()) {
5253
5651
  throw new Error(`Managed Dev Container path '${plan.generatedPath}' is not a regular file.`);
5254
5652
  }
5255
- const contents = import_node_fs14.default.readFileSync(plan.generatedPath, "utf-8");
5653
+ const contents = import_node_fs15.default.readFileSync(plan.generatedPath, "utf-8");
5256
5654
  if (!contents.startsWith(`${MANAGED_DEVCONTAINER_MARKER}
5257
5655
  `)) {
5258
5656
  throw new Error(`Managed Dev Container path '${plan.generatedPath}' is not devrouter-owned.`);
5259
5657
  }
5260
- import_node_fs14.default.unlinkSync(plan.generatedPath);
5658
+ import_node_fs15.default.unlinkSync(plan.generatedPath);
5261
5659
  }
5262
5660
  function assertSafeContainerId(containerId) {
5263
5661
  if (!/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(containerId)) {
@@ -5284,7 +5682,7 @@ function startExactManagedServices(options) {
5284
5682
  delete env.DEVROUTER_GIT_COMMON_DIR;
5285
5683
  delete env.DEVCONTAINER_COMPOSE_OVERLAY;
5286
5684
  }
5287
- const result = (0, import_node_child_process6.spawnSync)(
5685
+ const result = (0, import_node_child_process7.spawnSync)(
5288
5686
  "docker",
5289
5687
  [
5290
5688
  "compose",
@@ -5314,7 +5712,7 @@ function startExactManagedServices(options) {
5314
5712
  }
5315
5713
  function stopExactManagedService(containerId, service) {
5316
5714
  assertSafeContainerId(containerId);
5317
- const result = (0, import_node_child_process6.spawnSync)("docker", ["stop", containerId], {
5715
+ const result = (0, import_node_child_process7.spawnSync)("docker", ["stop", containerId], {
5318
5716
  encoding: "utf-8",
5319
5717
  stdio: "inherit"
5320
5718
  });
@@ -5322,14 +5720,14 @@ function stopExactManagedService(containerId, service) {
5322
5720
  throw new Error(`Could not stop exact managed service '${service}' (${containerId}).`);
5323
5721
  }
5324
5722
  }
5325
- var import_node_child_process6, import_node_crypto6, import_node_fs14, import_node_path14, import_yaml4, MANAGED_DEVCONTAINER_PATH, MANAGED_DEVCONTAINER_MARKER;
5723
+ var import_node_child_process7, import_node_crypto7, import_node_fs15, import_node_path15, import_yaml4, MANAGED_DEVCONTAINER_PATH, MANAGED_DEVCONTAINER_MARKER;
5326
5724
  var init_devcontainer_profile = __esm({
5327
5725
  "src/core/devcontainer-profile.ts"() {
5328
5726
  "use strict";
5329
- import_node_child_process6 = require("child_process");
5330
- import_node_crypto6 = require("crypto");
5331
- import_node_fs14 = __toESM(require("fs"));
5332
- import_node_path14 = __toESM(require("path"));
5727
+ import_node_child_process7 = require("child_process");
5728
+ import_node_crypto7 = require("crypto");
5729
+ import_node_fs15 = __toESM(require("fs"));
5730
+ import_node_path15 = __toESM(require("path"));
5333
5731
  import_yaml4 = __toESM(require("yaml"));
5334
5732
  init_atomic_file();
5335
5733
  init_devcontainer_config();
@@ -5343,7 +5741,7 @@ var init_devcontainer_profile = __esm({
5343
5741
  function inspectWorkspaceContainers(options) {
5344
5742
  let ids = options?.ids;
5345
5743
  if (!ids) {
5346
- const listed = (0, import_node_child_process7.spawnSync)(
5744
+ const listed = (0, import_node_child_process8.spawnSync)(
5347
5745
  "docker",
5348
5746
  [
5349
5747
  "ps",
@@ -5363,7 +5761,7 @@ function inspectWorkspaceContainers(options) {
5363
5761
  }
5364
5762
  if (ids.length === 0) return [];
5365
5763
  const template = options?.withSize ? SIZE_INSPECT_TEMPLATE : SAFE_INSPECT_TEMPLATE;
5366
- const inspected = (0, import_node_child_process7.spawnSync)(
5764
+ const inspected = (0, import_node_child_process8.spawnSync)(
5367
5765
  "docker",
5368
5766
  ["inspect", ...options?.withSize ? ["--size"] : [], "--format", template, ...ids],
5369
5767
  { encoding: "utf-8" }
@@ -5378,7 +5776,7 @@ function inspectWorkspaceContainers(options) {
5378
5776
  function workspaceAppContainers(containers, repoPath) {
5379
5777
  return containers.filter((container) => {
5380
5778
  const workingDir = container.labels["com.docker.compose.project.working_dir"];
5381
- return Boolean(workingDir && sameWorkspacePath(workingDir, import_node_path15.default.join(repoPath, ".devcontainer"))) && container.mounts.some(
5779
+ return Boolean(workingDir && sameWorkspacePath(workingDir, import_node_path16.default.join(repoPath, ".devcontainer"))) && container.mounts.some(
5382
5780
  (mount) => mount.Type === "bind" && sameWorkspacePath(mount.Source, repoPath)
5383
5781
  );
5384
5782
  });
@@ -5386,7 +5784,7 @@ function workspaceAppContainers(containers, repoPath) {
5386
5784
  function hasExactComposeIdentity(container, options) {
5387
5785
  if (options.composeProject !== void 0 && container.labels["com.docker.compose.project"] !== options.composeProject || container.labels["com.docker.compose.service"] !== options.service || !sameWorkspacePath(
5388
5786
  container.labels["com.docker.compose.project.working_dir"] ?? "",
5389
- import_node_path15.default.join(options.repoPath, ".devcontainer")
5787
+ import_node_path16.default.join(options.repoPath, ".devcontainer")
5390
5788
  )) {
5391
5789
  return false;
5392
5790
  }
@@ -5423,12 +5821,12 @@ function resolveRunningWorkspaceContainer(repoPath) {
5423
5821
  }
5424
5822
  return { id: container.id, workspacePath: repoMount.Destination };
5425
5823
  }
5426
- var import_node_child_process7, import_node_path15, SAFE_INSPECT_TEMPLATE, SIZE_INSPECT_TEMPLATE;
5824
+ var import_node_child_process8, import_node_path16, SAFE_INSPECT_TEMPLATE, SIZE_INSPECT_TEMPLATE;
5427
5825
  var init_devpod_environment = __esm({
5428
5826
  "src/core/devpod-environment.ts"() {
5429
5827
  "use strict";
5430
- import_node_child_process7 = require("child_process");
5431
- import_node_path15 = __toESM(require("path"));
5828
+ import_node_child_process8 = require("child_process");
5829
+ import_node_path16 = __toESM(require("path"));
5432
5830
  init_workspace();
5433
5831
  SAFE_INSPECT_TEMPLATE = '{"id":{{json .Id}},"state":{"Running":{{json .State.Running}},"Health":{{with (index .State "Health")}}{"Status":{{json .Status}}}{{else}}null{{end}}},"labels":{"com.docker.compose.project":{{json (index .Config.Labels "com.docker.compose.project")}},"com.docker.compose.service":{{json (index .Config.Labels "com.docker.compose.service")}},"com.docker.compose.project.working_dir":{{json (index .Config.Labels "com.docker.compose.project.working_dir")}},"com.docker.compose.project.config_files":{{json (index .Config.Labels "com.docker.compose.project.config_files")}}},"mounts":{{json .Mounts}},"networks":{{json .NetworkSettings.Networks}}}';
5434
5832
  SIZE_INSPECT_TEMPLATE = SAFE_INSPECT_TEMPLATE.replace(
@@ -5882,7 +6280,7 @@ function toRepoStatus(repoPath) {
5882
6280
  const resolvedRepoPath = resolveRepoPath(repoPath);
5883
6281
  const configPath = getRepoConfigPath(resolvedRepoPath);
5884
6282
  const explicitRepo = typeof repoPath === "string" && repoPath.trim().length > 0;
5885
- const configExists = import_node_fs15.default.existsSync(configPath);
6283
+ const configExists = import_node_fs16.default.existsSync(configPath);
5886
6284
  if (!explicitRepo && !configExists) {
5887
6285
  return void 0;
5888
6286
  }
@@ -6030,11 +6428,11 @@ async function collectRouterStatus(repoPath) {
6030
6428
  }
6031
6429
  };
6032
6430
  }
6033
- var import_node_fs15;
6431
+ var import_node_fs16;
6034
6432
  var init_status = __esm({
6035
6433
  "src/core/status.ts"() {
6036
6434
  "use strict";
6037
- import_node_fs15 = __toESM(require("fs"));
6435
+ import_node_fs16 = __toESM(require("fs"));
6038
6436
  init_docker();
6039
6437
  init_managed_runtime_state();
6040
6438
  init_managed_runtime_status();
@@ -6045,7 +6443,7 @@ var init_status = __esm({
6045
6443
 
6046
6444
  // src/core/tls.ts
6047
6445
  function runOrThrow(command, args) {
6048
- const result = (0, import_node_child_process8.spawnSync)(command, args, {
6446
+ const result = (0, import_node_child_process9.spawnSync)(command, args, {
6049
6447
  encoding: "utf-8"
6050
6448
  });
6051
6449
  if (result.status !== 0) {
@@ -6055,7 +6453,7 @@ function runOrThrow(command, args) {
6055
6453
  return result.stdout.trim();
6056
6454
  }
6057
6455
  function commandExists(command) {
6058
- const result = (0, import_node_child_process8.spawnSync)("sh", ["-c", `command -v ${command}`], { encoding: "utf-8" });
6456
+ const result = (0, import_node_child_process9.spawnSync)("sh", ["-c", `command -v ${command}`], { encoding: "utf-8" });
6059
6457
  return result.status === 0;
6060
6458
  }
6061
6459
  function ensureMkcert() {
@@ -6073,8 +6471,8 @@ function tlsSetupCommand(repoPath) {
6073
6471
  }
6074
6472
  function getMkcertRootCAPath(options = {}) {
6075
6473
  ensureMkcert();
6076
- const rootCAPath = import_node_path16.default.join(runOrThrow("mkcert", ["-CAROOT"]), "rootCA.pem");
6077
- if (!import_node_fs16.default.existsSync(rootCAPath)) {
6474
+ const rootCAPath = import_node_path17.default.join(runOrThrow("mkcert", ["-CAROOT"]), "rootCA.pem");
6475
+ if (!import_node_fs17.default.existsSync(rootCAPath)) {
6078
6476
  throw new Error(
6079
6477
  `mkcert root CA was not found at '${rootCAPath}'. Run: ${tlsSetupCommand(options.repoPath)}`
6080
6478
  );
@@ -6107,7 +6505,7 @@ function parseDnsHostsFromSubjectAltName(subjectAltName) {
6107
6505
  return normalizeUniqueHosts(names);
6108
6506
  }
6109
6507
  function parseCertificateDnsHosts(pem) {
6110
- const certificate = new import_node_crypto7.X509Certificate(pem);
6508
+ const certificate = new import_node_crypto8.X509Certificate(pem);
6111
6509
  const subjectAltName = certificate.subjectAltName ?? "";
6112
6510
  if (subjectAltName.length === 0) {
6113
6511
  return [];
@@ -6175,11 +6573,11 @@ function compactTLSCertificateHosts(hosts) {
6175
6573
  );
6176
6574
  }
6177
6575
  function readCurrentCertificateHosts(options = {}) {
6178
- if (!import_node_fs16.default.existsSync(CERT_FILE)) {
6576
+ if (!import_node_fs17.default.existsSync(CERT_FILE)) {
6179
6577
  return [];
6180
6578
  }
6181
6579
  try {
6182
- const pem = import_node_fs16.default.readFileSync(CERT_FILE, "utf-8");
6580
+ const pem = import_node_fs17.default.readFileSync(CERT_FILE, "utf-8");
6183
6581
  return parseCertificateDnsHosts(pem);
6184
6582
  } catch (error) {
6185
6583
  if (options.replaceMalformed) {
@@ -6307,20 +6705,20 @@ Run: ${tlsSetupCommand(options.repoPath)}`
6307
6705
  );
6308
6706
  }
6309
6707
  }
6310
- var import_node_child_process8, import_node_crypto7, import_node_fs16, import_node_path16, DEFAULT_TLS_CERT_HOSTS, TLS_CERTIFICATE_LOCK_FILE, TLS_CERTIFICATE_LOCK_WAIT_MS, TLS_CERTIFICATE_WRITE_ATTEMPTS;
6708
+ var import_node_child_process9, import_node_crypto8, import_node_fs17, import_node_path17, DEFAULT_TLS_CERT_HOSTS, TLS_CERTIFICATE_LOCK_FILE, TLS_CERTIFICATE_LOCK_WAIT_MS, TLS_CERTIFICATE_WRITE_ATTEMPTS;
6311
6709
  var init_tls = __esm({
6312
6710
  "src/core/tls.ts"() {
6313
6711
  "use strict";
6314
- import_node_child_process8 = require("child_process");
6315
- import_node_crypto7 = require("crypto");
6316
- import_node_fs16 = __toESM(require("fs"));
6317
- import_node_path16 = __toESM(require("path"));
6712
+ import_node_child_process9 = require("child_process");
6713
+ import_node_crypto8 = require("crypto");
6714
+ import_node_fs17 = __toESM(require("fs"));
6715
+ import_node_path17 = __toESM(require("path"));
6318
6716
  init_docker();
6319
6717
  init_file_lock();
6320
6718
  init_host_routes();
6321
6719
  init_router();
6322
6720
  DEFAULT_TLS_CERT_HOSTS = ["localhost", "*.localhost"];
6323
- TLS_CERTIFICATE_LOCK_FILE = import_node_path16.default.join(DEVROUTER_HOME, "tls-certificate.lock");
6721
+ TLS_CERTIFICATE_LOCK_FILE = import_node_path17.default.join(DEVROUTER_HOME, "tls-certificate.lock");
6324
6722
  TLS_CERTIFICATE_LOCK_WAIT_MS = 6e4;
6325
6723
  TLS_CERTIFICATE_WRITE_ATTEMPTS = 2;
6326
6724
  }
@@ -6328,7 +6726,7 @@ var init_tls = __esm({
6328
6726
 
6329
6727
  // src/core/devpod-registry.ts
6330
6728
  function listDevpodWorkspacesRaw() {
6331
- const result = (0, import_node_child_process9.spawnSync)("devpod", ["list", "--output", "json", "--skip-pro"], {
6729
+ const result = (0, import_node_child_process10.spawnSync)("devpod", ["list", "--output", "json", "--skip-pro"], {
6332
6730
  encoding: "utf-8"
6333
6731
  });
6334
6732
  if (result.status !== 0) {
@@ -6363,17 +6761,17 @@ function listDevpodWorkspacesRaw() {
6363
6761
  return workspace;
6364
6762
  });
6365
6763
  }
6366
- var import_node_child_process9;
6764
+ var import_node_child_process10;
6367
6765
  var init_devpod_registry = __esm({
6368
6766
  "src/core/devpod-registry.ts"() {
6369
6767
  "use strict";
6370
- import_node_child_process9 = require("child_process");
6768
+ import_node_child_process10 = require("child_process");
6371
6769
  }
6372
6770
  });
6373
6771
 
6374
6772
  // src/core/devsy-workspaces.ts
6375
6773
  function listDevsyWorkspaces() {
6376
- const result = (0, import_node_child_process10.spawnSync)(
6774
+ const result = (0, import_node_child_process11.spawnSync)(
6377
6775
  "devsy",
6378
6776
  ["workspace", "list", "--result-format", "json", "--skip-pro"],
6379
6777
  {
@@ -6439,7 +6837,7 @@ function parseDevsyRuntimeStatus(output2, expectedId) {
6439
6837
  }
6440
6838
  }
6441
6839
  function inspectDevsyRuntimeStatus(devsyId) {
6442
- const result = (0, import_node_child_process10.spawnSync)("devsy", ["workspace", "status", devsyId, "--result-format", "json"], {
6840
+ const result = (0, import_node_child_process11.spawnSync)("devsy", ["workspace", "status", devsyId, "--result-format", "json"], {
6443
6841
  encoding: "utf-8",
6444
6842
  timeout: 1e4
6445
6843
  });
@@ -6472,11 +6870,11 @@ function selectDevsyWorkspace(workspaces, repoPath) {
6472
6870
  }
6473
6871
  return matches[0];
6474
6872
  }
6475
- var import_node_child_process10;
6873
+ var import_node_child_process11;
6476
6874
  var init_devsy_workspaces = __esm({
6477
6875
  "src/core/devsy-workspaces.ts"() {
6478
6876
  "use strict";
6479
- import_node_child_process10 = require("child_process");
6877
+ import_node_child_process11 = require("child_process");
6480
6878
  init_workspace();
6481
6879
  }
6482
6880
  });
@@ -6493,13 +6891,13 @@ function parseWorkspaceRuntime(value) {
6493
6891
  }
6494
6892
  function isRuntimeInstalled(runtime) {
6495
6893
  const probeArgs = runtime === "devsy" ? ["--version"] : ["version"];
6496
- const probe = (0, import_node_child_process11.spawnSync)(runtime, probeArgs, { encoding: "utf-8" });
6894
+ const probe = (0, import_node_child_process12.spawnSync)(runtime, probeArgs, { encoding: "utf-8" });
6497
6895
  return probe.status === 0 && !probe.error;
6498
6896
  }
6499
6897
  function readWorkspaceRuntimeConfig() {
6500
6898
  let raw;
6501
6899
  try {
6502
- raw = JSON.parse(import_node_fs17.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8"));
6900
+ raw = JSON.parse(import_node_fs18.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8"));
6503
6901
  } catch {
6504
6902
  return {};
6505
6903
  }
@@ -6520,7 +6918,7 @@ function readWorkspaceRuntimeConfig() {
6520
6918
  function inspectWorkspaceRuntimeConfig() {
6521
6919
  let rawText;
6522
6920
  try {
6523
- rawText = import_node_fs17.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8");
6921
+ rawText = import_node_fs18.default.readFileSync(RUNTIME_CONFIG_FILE, "utf-8");
6524
6922
  } catch {
6525
6923
  return { exists: false, config: {}, problems: [] };
6526
6924
  }
@@ -6571,7 +6969,7 @@ function writeWorkspaceRuntimeConfig(config) {
6571
6969
  }
6572
6970
  next.devsyInactivityTimeout = timeout;
6573
6971
  }
6574
- import_node_fs17.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
6972
+ import_node_fs18.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
6575
6973
  writeFileAtomically(RUNTIME_CONFIG_FILE, `${JSON.stringify(next, null, 2)}
6576
6974
  `);
6577
6975
  }
@@ -6679,20 +7077,20 @@ function resetWorkspaceRuntimeCaches() {
6679
7077
  cachedRuntime = void 0;
6680
7078
  cachedSnapshots = void 0;
6681
7079
  }
6682
- var import_node_child_process11, import_node_fs17, import_node_path17, SUPPORTED_RUNTIMES2, RUNTIME_CONFIG_FILE, INACTIVITY_TIMEOUT_PATTERN, cachedRuntime, cachedSnapshots, UnsupportedWorkspaceRuntimeError, WorkspaceRuntimeOwnershipError;
7080
+ var import_node_child_process12, import_node_fs18, import_node_path18, SUPPORTED_RUNTIMES2, RUNTIME_CONFIG_FILE, INACTIVITY_TIMEOUT_PATTERN, cachedRuntime, cachedSnapshots, UnsupportedWorkspaceRuntimeError, WorkspaceRuntimeOwnershipError;
6683
7081
  var init_workspace_runtime = __esm({
6684
7082
  "src/core/workspace-runtime.ts"() {
6685
7083
  "use strict";
6686
- import_node_child_process11 = require("child_process");
6687
- import_node_fs17 = __toESM(require("fs"));
6688
- import_node_path17 = __toESM(require("path"));
7084
+ import_node_child_process12 = require("child_process");
7085
+ import_node_fs18 = __toESM(require("fs"));
7086
+ import_node_path18 = __toESM(require("path"));
6689
7087
  init_atomic_file();
6690
7088
  init_devpod_registry();
6691
7089
  init_devsy_workspaces();
6692
7090
  init_router();
6693
7091
  init_workspace();
6694
7092
  SUPPORTED_RUNTIMES2 = ["devpod", "devsy"];
6695
- RUNTIME_CONFIG_FILE = import_node_path17.default.join(DEVROUTER_HOME, "workspace-runtime.json");
7093
+ RUNTIME_CONFIG_FILE = import_node_path18.default.join(DEVROUTER_HOME, "workspace-runtime.json");
6696
7094
  INACTIVITY_TIMEOUT_PATTERN = /^(?:\d+(?:ms|s|m|h))+$/;
6697
7095
  UnsupportedWorkspaceRuntimeError = class extends Error {
6698
7096
  };
@@ -6713,7 +7111,7 @@ function outputFromResult(result) {
6713
7111
  return output2.length > 0 ? output2 : void 0;
6714
7112
  }
6715
7113
  function runTool(command, args = []) {
6716
- const result = (0, import_node_child_process12.spawnSync)(command, args, {
7114
+ const result = (0, import_node_child_process13.spawnSync)(command, args, {
6717
7115
  encoding: "utf-8"
6718
7116
  });
6719
7117
  if (result.error) {
@@ -6770,12 +7168,12 @@ function parseMinimumNodeMajor(value) {
6770
7168
  return Number(match[1]);
6771
7169
  }
6772
7170
  function readPackageJson(repoPath) {
6773
- const packagePath = import_node_path18.default.join(repoPath, "package.json");
6774
- if (!import_node_fs18.default.existsSync(packagePath)) {
7171
+ const packagePath = import_node_path19.default.join(repoPath, "package.json");
7172
+ if (!import_node_fs19.default.existsSync(packagePath)) {
6775
7173
  return void 0;
6776
7174
  }
6777
7175
  try {
6778
- return JSON.parse(import_node_fs18.default.readFileSync(packagePath, "utf-8"));
7176
+ return JSON.parse(import_node_fs19.default.readFileSync(packagePath, "utf-8"));
6779
7177
  } catch {
6780
7178
  return void 0;
6781
7179
  }
@@ -6840,7 +7238,44 @@ function nodeToolchainCheck(repoPath) {
6840
7238
  details: details.join(", ")
6841
7239
  };
6842
7240
  }
7241
+ function devsyAgentCheck(inspection) {
7242
+ const details = [
7243
+ `state=${inspection.state}`,
7244
+ `source=${inspection.source}`,
7245
+ ...inspection.installedVersion ? [`version=${inspection.installedVersion}`] : [],
7246
+ ...inspection.asset ? [`asset=${inspection.asset.name}`] : []
7247
+ ].join(", ");
7248
+ if (inspection.state === "ready") {
7249
+ return {
7250
+ id: "global.devsy-agent",
7251
+ level: "ok",
7252
+ summary: "Devsy agent source is ready.",
7253
+ details
7254
+ };
7255
+ }
7256
+ const summary = inspection.state === "missing" ? "Managed Devsy agent source is missing." : inspection.state === "stale" ? "Devsy agent source is stale for this Devrouter release." : "Devsy agent source is invalid.";
7257
+ return {
7258
+ id: "global.devsy-agent",
7259
+ level: "error",
7260
+ summary,
7261
+ details: `${details}, ${inspection.reason}`,
7262
+ suggestion: devsyAgentRepairSuggestion(inspection)
7263
+ };
7264
+ }
6843
7265
  function buildGlobalToolChecks(repoPath) {
7266
+ const previous = process.env[DEVSY_DISABLE_TELEMETRY];
7267
+ process.env[DEVSY_DISABLE_TELEMETRY] = "true";
7268
+ try {
7269
+ return buildGlobalToolChecksWithoutTelemetry(repoPath);
7270
+ } finally {
7271
+ if (previous === void 0) {
7272
+ delete process.env[DEVSY_DISABLE_TELEMETRY];
7273
+ } else {
7274
+ process.env[DEVSY_DISABLE_TELEMETRY] = previous;
7275
+ }
7276
+ }
7277
+ }
7278
+ function buildGlobalToolChecksWithoutTelemetry(repoPath) {
6844
7279
  const checks = [];
6845
7280
  const compose = runTool("docker", ["compose", "version"]);
6846
7281
  checks.push({
@@ -6894,6 +7329,13 @@ function buildGlobalToolChecks(repoPath) {
6894
7329
  ].filter(Boolean).join(", "),
6895
7330
  suggestion: runtimeTool.ok ? void 0 : workspaceRuntime === "devsy" ? "Install Devsy for devcontainer workspace flows: brew install devsy-org/homebrew-tap/devsy" : "Install DevPod for devcontainer workspace flows: brew install devpod"
6896
7331
  });
7332
+ if (workspaceRuntime === "devsy") {
7333
+ checks.push(
7334
+ devsyAgentCheck(
7335
+ inspectDevsyAgent({ versionOutput: runtimeTool.ok ? runtimeTool.output : "" })
7336
+ )
7337
+ );
7338
+ }
6897
7339
  const configProblems = [...configInspection.problems];
6898
7340
  const machineResolvedDevpod = workspaceRuntime === "devpod" && (runtimeResolution.source === "auto-detect" || runtimeResolution.source === "default");
6899
7341
  if (machineConfig.devsyInactivityTimeout && machineResolvedDevpod) {
@@ -6915,14 +7357,16 @@ function buildGlobalToolChecks(repoPath) {
6915
7357
  checks.push(nodeToolchainCheck(repoPath));
6916
7358
  return checks;
6917
7359
  }
6918
- var import_node_child_process12, import_node_fs18, import_node_path18;
7360
+ var import_node_child_process13, import_node_fs19, import_node_path19, DEVSY_DISABLE_TELEMETRY;
6919
7361
  var init_tool_diagnostics = __esm({
6920
7362
  "src/core/tool-diagnostics.ts"() {
6921
7363
  "use strict";
6922
- import_node_child_process12 = require("child_process");
6923
- import_node_fs18 = __toESM(require("fs"));
6924
- import_node_path18 = __toESM(require("path"));
7364
+ import_node_child_process13 = require("child_process");
7365
+ import_node_fs19 = __toESM(require("fs"));
7366
+ import_node_path19 = __toESM(require("path"));
7367
+ init_devsy_agent();
6925
7368
  init_workspace_runtime();
7369
+ DEVSY_DISABLE_TELEMETRY = "DEVSY_DISABLE_TELEMETRY";
6926
7370
  }
6927
7371
  });
6928
7372
 
@@ -6991,7 +7435,7 @@ function inspectDevpodRuntimeStatus(devpodId, repoPath) {
6991
7435
  if (resolveWorkspaceRuntimeOrDefault(repoPath) === "devsy") {
6992
7436
  return inspectDevsyRuntimeStatus(devpodId);
6993
7437
  }
6994
- const result = (0, import_node_child_process13.spawnSync)("devpod", ["status", devpodId, "--output", "json", "--timeout", "5s"], {
7438
+ const result = (0, import_node_child_process14.spawnSync)("devpod", ["status", devpodId, "--output", "json", "--timeout", "5s"], {
6995
7439
  encoding: "utf-8"
6996
7440
  });
6997
7441
  if (result.status !== 0 || result.error) return "unknown";
@@ -7024,11 +7468,11 @@ function selectDevpodWorkspace(workspaces, repoPath) {
7024
7468
  }
7025
7469
  return matches[0];
7026
7470
  }
7027
- var import_node_child_process13;
7471
+ var import_node_child_process14;
7028
7472
  var init_devpod_workspaces = __esm({
7029
7473
  "src/core/devpod-workspaces.ts"() {
7030
7474
  "use strict";
7031
- import_node_child_process13 = require("child_process");
7475
+ import_node_child_process14 = require("child_process");
7032
7476
  init_devpod_registry();
7033
7477
  init_devsy_workspaces();
7034
7478
  init_workspace();
@@ -7048,7 +7492,7 @@ function failedStartMayHaveAttached(devsyId, repoPath) {
7048
7492
  }
7049
7493
  }
7050
7494
  function withMutationLock(activity, target, operation) {
7051
- import_node_fs19.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
7495
+ import_node_fs20.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
7052
7496
  return withFileLockSync(
7053
7497
  DEVSY_MUTATION_LOCK_FILE,
7054
7498
  {
@@ -7062,7 +7506,7 @@ function withMutationLock(activity, target, operation) {
7062
7506
  );
7063
7507
  }
7064
7508
  function withMutationLockAsync(activity, target, operation) {
7065
- import_node_fs19.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
7509
+ import_node_fs20.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
7066
7510
  return withFileLock(
7067
7511
  DEVSY_MUTATION_LOCK_FILE,
7068
7512
  {
@@ -7080,7 +7524,7 @@ function commandFailure2(result) {
7080
7524
  }
7081
7525
  function runDevsyUp(args, env, quiet) {
7082
7526
  return new Promise((resolve) => {
7083
- const child = (0, import_node_child_process14.spawn)("devsy", args, {
7527
+ const child = (0, import_node_child_process15.spawn)("devsy", args, {
7084
7528
  stdio: ["inherit", quiet ? 2 : "inherit", "pipe"],
7085
7529
  env
7086
7530
  });
@@ -7112,7 +7556,7 @@ function runDevsyUp(args, env, quiet) {
7112
7556
  }
7113
7557
  function runDevsyAction(action2, devsyId, force = false) {
7114
7558
  const args = action2 === "delete" ? ["delete", devsyId, ...force ? ["--force"] : [], "--ignore-not-found"] : ["stop", devsyId];
7115
- const result = (0, import_node_child_process14.spawnSync)("devsy", ["workspace", ...args], { encoding: "utf-8" });
7559
+ const result = (0, import_node_child_process15.spawnSync)("devsy", ["workspace", ...args], { encoding: "utf-8" });
7116
7560
  if (result.status !== 0) {
7117
7561
  throw new Error(
7118
7562
  `devsy workspace ${action2}${force ? " --force" : ""} failed for '${devsyId}': ${commandFailure2(result) || "unknown error"}`
@@ -7172,8 +7616,16 @@ function assertDevsyTarget(devsyId, repoPath) {
7172
7616
  }
7173
7617
  return id;
7174
7618
  }
7175
- function startDevsyWorkspace(options) {
7619
+ async function startDevsyWorkspace(options) {
7176
7620
  const activity = options.recreate ? "Devsy recreate" : "Devsy start";
7621
+ let agent;
7622
+ try {
7623
+ agent = requireReadyDevsyAgent();
7624
+ } catch (error) {
7625
+ if (!(error instanceof DevsyAgentReadinessError)) throw error;
7626
+ const repair = devsyAgentRepairSuggestion(error.inspection);
7627
+ throw new Error(`${error.message}. ${repair}`);
7628
+ }
7177
7629
  return withMutationLockAsync(activity, options.repoPath, async () => {
7178
7630
  let devsyId = assertDevsyTarget(options.devsyId, options.repoPath);
7179
7631
  if (devsyId && options.recreate) {
@@ -7203,6 +7655,7 @@ function startDevsyWorkspace(options) {
7203
7655
  }
7204
7656
  if (options.recreate) args.push("--recreate");
7205
7657
  const env = { ...process.env };
7658
+ env.DEVSY_AGENT_BINARY = agent.binaryPath;
7206
7659
  if (options.workspace) {
7207
7660
  env.WORKSPACE = options.workspace.token;
7208
7661
  env.DEVROUTER_WORKSPACE = options.workspace.token;
@@ -7219,7 +7672,7 @@ function startDevsyWorkspace(options) {
7219
7672
  let message = `devsy workspace up failed for '${devsyId ?? options.repoPath}'.`;
7220
7673
  if (result.error?.message) message += ` ${result.error.message}`;
7221
7674
  if (AGENT_ACQUISITION_RE.test(result.stderrTail)) {
7222
- message += " Devsy could not obtain its agent binary; allow Devsy to download it or set DEVSY_AGENT_BINARY to a verified official Devsy agent binary matching this platform and Devsy version.";
7675
+ message += ` Devsy rejected the verified agent source. Run: ${DEVSY_AGENT_SETUP_COMMAND}`;
7223
7676
  }
7224
7677
  if (failedStartMayHaveAttached(devsyId, options.repoPath)) {
7225
7678
  throw new DevsyStartPostconditionError(message);
@@ -7246,17 +7699,18 @@ function startDevsyWorkspace(options) {
7246
7699
  }
7247
7700
  });
7248
7701
  }
7249
- var import_node_child_process14, import_node_fs19, import_node_path19, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS, DevsyStartPostconditionError, AGENT_ACQUISITION_RE, DEVSY_STDERR_TAIL_BYTES;
7702
+ var import_node_child_process15, import_node_fs20, import_node_path20, DEVSY_MUTATION_LOCK_FILE, DEVSY_MUTATION_WAIT_MS, DevsyStartPostconditionError, AGENT_ACQUISITION_RE, DEVSY_STDERR_TAIL_BYTES;
7250
7703
  var init_devsy_mutation = __esm({
7251
7704
  "src/core/devsy-mutation.ts"() {
7252
7705
  "use strict";
7253
- import_node_child_process14 = require("child_process");
7254
- import_node_fs19 = __toESM(require("fs"));
7255
- import_node_path19 = __toESM(require("path"));
7706
+ import_node_child_process15 = require("child_process");
7707
+ import_node_fs20 = __toESM(require("fs"));
7708
+ import_node_path20 = __toESM(require("path"));
7709
+ init_devsy_agent();
7256
7710
  init_devsy_workspaces();
7257
7711
  init_file_lock();
7258
7712
  init_router();
7259
- DEVSY_MUTATION_LOCK_FILE = import_node_path19.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
7713
+ DEVSY_MUTATION_LOCK_FILE = import_node_path20.default.join(DEVROUTER_HOME, "devsy-mutation.lock");
7260
7714
  DEVSY_MUTATION_WAIT_MS = 18e5;
7261
7715
  DevsyStartPostconditionError = class extends Error {
7262
7716
  };
@@ -7267,7 +7721,7 @@ var init_devsy_mutation = __esm({
7267
7721
 
7268
7722
  // src/core/devpod-mutation.ts
7269
7723
  function withMutationLock2(activity, target, operation) {
7270
- import_node_fs20.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
7724
+ import_node_fs21.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
7271
7725
  return withFileLockSync(
7272
7726
  DEVPOD_MUTATION_LOCK_FILE,
7273
7727
  {
@@ -7285,7 +7739,7 @@ function commandFailure3(result) {
7285
7739
  }
7286
7740
  function runDevpodAction(action2, devpodId, force = false) {
7287
7741
  const args = action2 === "delete" ? [action2, devpodId, ...force ? ["--force"] : [], "--ignore-not-found"] : [action2, devpodId];
7288
- const result = (0, import_node_child_process15.spawnSync)("devpod", args, { encoding: "utf-8" });
7742
+ const result = (0, import_node_child_process16.spawnSync)("devpod", args, { encoding: "utf-8" });
7289
7743
  if (result.status !== 0) {
7290
7744
  throw new Error(
7291
7745
  `devpod ${action2}${force ? " --force" : ""} failed for '${devpodId}': ${commandFailure3(result) || "unknown error"}`
@@ -7413,7 +7867,7 @@ async function startDevpodWorkspace(options) {
7413
7867
  delete env.DEVROUTER_GIT_COMMON_DIR;
7414
7868
  delete env.DEVCONTAINER_COMPOSE_OVERLAY;
7415
7869
  }
7416
- const result = (0, import_node_child_process15.spawnSync)("devpod", args, {
7870
+ const result = (0, import_node_child_process16.spawnSync)("devpod", args, {
7417
7871
  stdio: options.quiet ? ["inherit", 2, "inherit"] : "inherit",
7418
7872
  env
7419
7873
  });
@@ -7441,19 +7895,19 @@ async function startDevpodWorkspace(options) {
7441
7895
  }
7442
7896
  });
7443
7897
  }
7444
- var import_node_child_process15, import_node_fs20, import_node_path20, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS, DevpodStartPostconditionError;
7898
+ var import_node_child_process16, import_node_fs21, import_node_path21, DEVPOD_MUTATION_LOCK_FILE, DEVPOD_MUTATION_WAIT_MS, DevpodStartPostconditionError;
7445
7899
  var init_devpod_mutation = __esm({
7446
7900
  "src/core/devpod-mutation.ts"() {
7447
7901
  "use strict";
7448
- import_node_child_process15 = require("child_process");
7449
- import_node_fs20 = __toESM(require("fs"));
7450
- import_node_path20 = __toESM(require("path"));
7902
+ import_node_child_process16 = require("child_process");
7903
+ import_node_fs21 = __toESM(require("fs"));
7904
+ import_node_path21 = __toESM(require("path"));
7451
7905
  init_devpod_workspaces();
7452
7906
  init_devsy_mutation();
7453
7907
  init_file_lock();
7454
7908
  init_router();
7455
7909
  init_workspace_runtime();
7456
- DEVPOD_MUTATION_LOCK_FILE = import_node_path20.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
7910
+ DEVPOD_MUTATION_LOCK_FILE = import_node_path21.default.join(DEVROUTER_HOME, "devpod-mutation.lock");
7457
7911
  DEVPOD_MUTATION_WAIT_MS = 18e5;
7458
7912
  DevpodStartPostconditionError = class extends Error {
7459
7913
  };
@@ -7467,7 +7921,7 @@ function commandError(command, repoPath, stderr) {
7467
7921
  );
7468
7922
  }
7469
7923
  function resolveGitCommonDir(repoPath) {
7470
- const result = (0, import_node_child_process16.spawnSync)("git", ["-C", repoPath, "rev-parse", "--git-common-dir"], {
7924
+ const result = (0, import_node_child_process17.spawnSync)("git", ["-C", repoPath, "rev-parse", "--git-common-dir"], {
7471
7925
  encoding: "utf-8",
7472
7926
  env: READ_ONLY_GIT_ENV
7473
7927
  });
@@ -7475,10 +7929,10 @@ function resolveGitCommonDir(repoPath) {
7475
7929
  if (result.status !== 0 || !output2) {
7476
7930
  throw commandError("Could not resolve the Git common directory", repoPath, result.stderr);
7477
7931
  }
7478
- return comparableWorkspacePath(import_node_path21.default.isAbsolute(output2) ? output2 : import_node_path21.default.resolve(repoPath, output2));
7932
+ return comparableWorkspacePath(import_node_path22.default.isAbsolute(output2) ? output2 : import_node_path22.default.resolve(repoPath, output2));
7479
7933
  }
7480
7934
  function resolveGitTopLevel(repoPath) {
7481
- const result = (0, import_node_child_process16.spawnSync)("git", ["-C", repoPath, "rev-parse", "--show-toplevel"], {
7935
+ const result = (0, import_node_child_process17.spawnSync)("git", ["-C", repoPath, "rev-parse", "--show-toplevel"], {
7482
7936
  encoding: "utf-8",
7483
7937
  env: READ_ONLY_GIT_ENV
7484
7938
  });
@@ -7489,7 +7943,7 @@ function resolveGitTopLevel(repoPath) {
7489
7943
  return comparableWorkspacePath(output2);
7490
7944
  }
7491
7945
  function listGitWorktrees(repoPath) {
7492
- const result = (0, import_node_child_process16.spawnSync)("git", ["-C", repoPath, "worktree", "list", "--porcelain"], {
7946
+ const result = (0, import_node_child_process17.spawnSync)("git", ["-C", repoPath, "worktree", "list", "--porcelain"], {
7493
7947
  encoding: "utf-8",
7494
7948
  env: READ_ONLY_GIT_ENV
7495
7949
  });
@@ -7526,7 +7980,7 @@ function listGitWorktrees(repoPath) {
7526
7980
  return worktrees;
7527
7981
  }
7528
7982
  function ownershipDirectory(repoPath) {
7529
- return import_node_path21.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
7983
+ return import_node_path22.default.join(resolveGitCommonDir(repoPath), OWNERSHIP_DIR);
7530
7984
  }
7531
7985
  function validateWorkspace(value, label) {
7532
7986
  if (typeof value !== "string" || wsFromBranch(value) !== value) {
@@ -7554,7 +8008,7 @@ function validateRecord(value, expectedWorkspace) {
7554
8008
  `workspace ownership file '${expectedWorkspace}' contains identity '${workspace}'`
7555
8009
  );
7556
8010
  }
7557
- if (typeof candidate.worktreePath !== "string" || !import_node_path21.default.isAbsolute(candidate.worktreePath)) {
8011
+ if (typeof candidate.worktreePath !== "string" || !import_node_path22.default.isAbsolute(candidate.worktreePath)) {
7558
8012
  throw new Error("invalid workspace ownership worktreePath");
7559
8013
  }
7560
8014
  if (candidate.branch !== null && typeof candidate.branch !== "string") {
@@ -7574,7 +8028,7 @@ function validateRecord(value, expectedWorkspace) {
7574
8028
  function readRecordFile(filePath, expectedWorkspace) {
7575
8029
  let parsed;
7576
8030
  try {
7577
- parsed = JSON.parse(import_node_fs21.default.readFileSync(filePath, "utf-8"));
8031
+ parsed = JSON.parse(import_node_fs22.default.readFileSync(filePath, "utf-8"));
7578
8032
  } catch (error) {
7579
8033
  if (error instanceof SyntaxError) {
7580
8034
  throw new Error(`invalid workspace ownership JSON at '${filePath}'`);
@@ -7590,7 +8044,7 @@ function listWorkspaceOwnership(repoPath) {
7590
8044
  function listWorkspaceOwnershipInDirectory(directory) {
7591
8045
  let entries;
7592
8046
  try {
7593
- entries = import_node_fs21.default.readdirSync(directory, { withFileTypes: true });
8047
+ entries = import_node_fs22.default.readdirSync(directory, { withFileTypes: true });
7594
8048
  } catch (error) {
7595
8049
  if (error.code === "ENOENT") return [];
7596
8050
  throw error;
@@ -7598,14 +8052,14 @@ function listWorkspaceOwnershipInDirectory(directory) {
7598
8052
  return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name)).map((entry) => {
7599
8053
  const workspace = entry.name.slice(0, -".json".length);
7600
8054
  validateWorkspace(workspace, "filename");
7601
- return readRecordFile(import_node_path21.default.join(directory, entry.name), workspace);
8055
+ return readRecordFile(import_node_path22.default.join(directory, entry.name), workspace);
7602
8056
  });
7603
8057
  }
7604
8058
  function writeWorkspaceOwnershipInDirectory(directory, input2) {
7605
8059
  const workspace = validateWorkspace(input2.workspace, "workspace");
7606
8060
  const devpodId = validateWorkspace(input2.devpodId, "devpodId");
7607
8061
  const worktreePath = comparableWorkspacePath(input2.worktreePath);
7608
- const filePath = import_node_path21.default.join(directory, `${workspace}.json`);
8062
+ const filePath = import_node_path22.default.join(directory, `${workspace}.json`);
7609
8063
  const now = (/* @__PURE__ */ new Date()).toISOString();
7610
8064
  const records = listWorkspaceOwnershipInDirectory(directory);
7611
8065
  const existing = records.find((record2) => record2.workspace === workspace);
@@ -7641,9 +8095,9 @@ function writeWorkspaceOwnershipInDirectory(directory, input2) {
7641
8095
  return record;
7642
8096
  }
7643
8097
  function removeWorkspaceOwnershipInDirectory(directory, workspace) {
7644
- const filePath = import_node_path21.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
8098
+ const filePath = import_node_path22.default.join(directory, `${validateWorkspace(workspace, "workspace")}.json`);
7645
8099
  try {
7646
- import_node_fs21.default.rmSync(filePath);
8100
+ import_node_fs22.default.rmSync(filePath);
7647
8101
  return true;
7648
8102
  } catch (error) {
7649
8103
  if (error.code === "ENOENT") return false;
@@ -7654,7 +8108,7 @@ function sameOwnershipRecord(left, right) {
7654
8108
  return left.version === right.version && left.workspace === right.workspace && sameWorkspacePath(left.worktreePath, right.worktreePath) && left.branch === right.branch && left.devpodId === right.devpodId && left.createdAt === right.createdAt && left.updatedAt === right.updatedAt;
7655
8109
  }
7656
8110
  function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
7657
- const filePath = import_node_path21.default.join(
8111
+ const filePath = import_node_path22.default.join(
7658
8112
  directory,
7659
8113
  `${validateWorkspace(expected.workspace, "workspace")}.json`
7660
8114
  );
@@ -7666,14 +8120,14 @@ function removeWorkspaceOwnershipIfMatchesInDirectory(directory, expected) {
7666
8120
  throw error;
7667
8121
  }
7668
8122
  if (!sameOwnershipRecord(current, expected)) return "changed";
7669
- import_node_fs21.default.rmSync(filePath);
8123
+ import_node_fs22.default.rmSync(filePath);
7670
8124
  return "removed";
7671
8125
  }
7672
8126
  function withWorkspaceOwnershipTransaction(repoPath, operation, options = {}) {
7673
8127
  const directory = ownershipDirectory(repoPath);
7674
- import_node_fs21.default.mkdirSync(directory, { recursive: true });
8128
+ import_node_fs22.default.mkdirSync(directory, { recursive: true });
7675
8129
  return withFileLockSync(
7676
- import_node_path21.default.join(directory, ".lock"),
8130
+ import_node_path22.default.join(directory, ".lock"),
7677
8131
  {
7678
8132
  activity: "workspace ownership transaction",
7679
8133
  target: `'${repoPath}'`,
@@ -7706,7 +8160,7 @@ function persistedWorkspaceOwners(repoPath, worktreePath) {
7706
8160
  try {
7707
8161
  workspace = readPersistedWorkspace(worktree.path);
7708
8162
  } catch (error) {
7709
- if (!import_node_fs21.default.existsSync(worktree.path)) continue;
8163
+ if (!import_node_fs22.default.existsSync(worktree.path)) continue;
7710
8164
  throw error;
7711
8165
  }
7712
8166
  if (!workspace) continue;
@@ -7869,7 +8323,7 @@ function inspectWorkspaceOwnership(record, worktrees, devpods) {
7869
8323
  if (worktree?.locked) {
7870
8324
  return { ownerStatus: "locked", devpodStatus, worktree };
7871
8325
  }
7872
- if ((!worktree || worktree.prunable) && import_node_fs21.default.existsSync(record.worktreePath)) {
8326
+ if ((!worktree || worktree.prunable) && import_node_fs22.default.existsSync(record.worktreePath)) {
7873
8327
  return { ownerStatus: "conflict", devpodStatus, worktree };
7874
8328
  }
7875
8329
  if (!worktree || worktree.prunable) {
@@ -7893,20 +8347,20 @@ function listMissingWorkspaceOwnership(repoPath) {
7893
8347
  (record) => inspectWorkspaceOwnership(record, worktrees, void 0).ownerStatus === "missing"
7894
8348
  );
7895
8349
  }
7896
- var import_node_child_process16, import_node_fs21, import_node_path21, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
8350
+ var import_node_child_process17, import_node_fs22, import_node_path22, READ_ONLY_GIT_ENV, OWNERSHIP_VERSION, OWNERSHIP_DIR;
7897
8351
  var init_workspace_ownership = __esm({
7898
8352
  "src/core/workspace-ownership.ts"() {
7899
8353
  "use strict";
7900
- import_node_child_process16 = require("child_process");
7901
- import_node_fs21 = __toESM(require("fs"));
7902
- import_node_path21 = __toESM(require("path"));
8354
+ import_node_child_process17 = require("child_process");
8355
+ import_node_fs22 = __toESM(require("fs"));
8356
+ import_node_path22 = __toESM(require("path"));
7903
8357
  init_atomic_file();
7904
8358
  init_devpod_workspaces();
7905
8359
  init_file_lock();
7906
8360
  init_workspace();
7907
8361
  READ_ONLY_GIT_ENV = { ...process.env, GIT_OPTIONAL_LOCKS: "0" };
7908
8362
  OWNERSHIP_VERSION = 1;
7909
- OWNERSHIP_DIR = import_node_path21.default.join("devrouter", "workspaces");
8363
+ OWNERSHIP_DIR = import_node_path22.default.join("devrouter", "workspaces");
7910
8364
  }
7911
8365
  });
7912
8366
 
@@ -7920,10 +8374,10 @@ function inRepositoryWorkspaceScope(repoPath, worktreePath, livePaths) {
7920
8374
  if (livePaths.some((candidate) => sameWorkspacePath(candidate, worktreePath))) return true;
7921
8375
  const comparableRepo = comparableWorkspacePath(repoPath);
7922
8376
  const comparableWorktree = comparableWorkspacePath(worktreePath);
7923
- const localRoot = import_node_path22.default.join(comparableRepo, "trees") + import_node_path22.default.sep;
8377
+ const localRoot = import_node_path23.default.join(comparableRepo, "trees") + import_node_path23.default.sep;
7924
8378
  if (comparableWorktree.startsWith(localRoot)) return true;
7925
- const legacyPrefix = `${import_node_path22.default.basename(comparableRepo)}-`;
7926
- return import_node_path22.default.dirname(comparableWorktree) === import_node_path22.default.dirname(comparableRepo) && import_node_path22.default.basename(comparableWorktree).startsWith(legacyPrefix);
8379
+ const legacyPrefix = `${import_node_path23.default.basename(comparableRepo)}-`;
8380
+ return import_node_path23.default.dirname(comparableWorktree) === import_node_path23.default.dirname(comparableRepo) && import_node_path23.default.basename(comparableWorktree).startsWith(legacyPrefix);
7927
8381
  }
7928
8382
  function previewActions(devpodStatus, routeCount, includeRecord) {
7929
8383
  const actions = [
@@ -8175,11 +8629,11 @@ function applyWorkspaceGc(plan) {
8175
8629
  candidates
8176
8630
  };
8177
8631
  }
8178
- var import_node_path22;
8632
+ var import_node_path23;
8179
8633
  var init_workspace_gc = __esm({
8180
8634
  "src/core/workspace-gc.ts"() {
8181
8635
  "use strict";
8182
- import_node_path22 = __toESM(require("path"));
8636
+ import_node_path23 = __toESM(require("path"));
8183
8637
  init_devpod_mutation();
8184
8638
  init_devpod_workspaces();
8185
8639
  init_host_routes();
@@ -8249,11 +8703,11 @@ function inspectPostgresCredentials(repoPath, config) {
8249
8703
  parseErrors.push(`${app.name}: ${composeFile} (${message})`);
8250
8704
  continue;
8251
8705
  }
8252
- if (!import_node_fs22.default.existsSync(absolutePath)) {
8706
+ if (!import_node_fs23.default.existsSync(absolutePath)) {
8253
8707
  continue;
8254
8708
  }
8255
8709
  try {
8256
- const raw = import_node_fs22.default.readFileSync(absolutePath, "utf-8");
8710
+ const raw = import_node_fs23.default.readFileSync(absolutePath, "utf-8");
8257
8711
  const parsed = import_yaml5.default.parse(raw);
8258
8712
  const root = asRecord2(parsed);
8259
8713
  const services = asRecord2(root?.services);
@@ -8532,7 +8986,7 @@ async function buildDoctorReport(options = {}) {
8532
8986
  const config = runtimeConfig.config;
8533
8987
  loadedConfig = config;
8534
8988
  loadedWorkspace = runtimeConfig.workspace;
8535
- const cliVersion = true ? "0.0.46" : "0.0.0-dev";
8989
+ const cliVersion = true ? "0.0.48" : "0.0.0-dev";
8536
8990
  const configVersion = config.devrouter?.version;
8537
8991
  if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
8538
8992
  addCheck(checks, {
@@ -8562,9 +9016,9 @@ async function buildDoctorReport(options = {}) {
8562
9016
  (app) => app.docker.composeFiles.map((filePath) => ({
8563
9017
  app: app.name,
8564
9018
  filePath,
8565
- absolutePath: import_node_path23.default.resolve(repo.path, filePath)
9019
+ absolutePath: import_node_path24.default.resolve(repo.path, filePath)
8566
9020
  }))
8567
- ).filter((entry) => !import_node_fs22.default.existsSync(entry.absolutePath));
9021
+ ).filter((entry) => !import_node_fs23.default.existsSync(entry.absolutePath));
8568
9022
  addCheck(checks, {
8569
9023
  id: "repo.compose-files",
8570
9024
  level: missingComposeFiles.length === 0 ? "ok" : "error",
@@ -8607,8 +9061,8 @@ async function buildDoctorReport(options = {}) {
8607
9061
  const missingHostCwds = config.apps.filter((app) => app.runtime === "host").map((app) => ({
8608
9062
  app: app.name,
8609
9063
  cwd: app.hostRun.cwd,
8610
- absolutePath: import_node_path23.default.resolve(repo.path, app.hostRun.cwd)
8611
- })).filter((entry) => !import_node_fs22.default.existsSync(entry.absolutePath));
9064
+ absolutePath: import_node_path24.default.resolve(repo.path, app.hostRun.cwd)
9065
+ })).filter((entry) => !import_node_fs23.default.existsSync(entry.absolutePath));
8612
9066
  addCheck(checks, {
8613
9067
  id: "repo.host-cwd",
8614
9068
  level: missingHostCwds.length === 0 ? "ok" : "error",
@@ -8752,12 +9206,12 @@ async function buildDoctorReport(options = {}) {
8752
9206
  nextSteps
8753
9207
  };
8754
9208
  }
8755
- var import_node_fs22, import_node_path23, import_yaml5, POSTGRES_DEFAULTS;
9209
+ var import_node_fs23, import_node_path24, import_yaml5, POSTGRES_DEFAULTS;
8756
9210
  var init_doctor = __esm({
8757
9211
  "src/core/doctor.ts"() {
8758
9212
  "use strict";
8759
- import_node_fs22 = __toESM(require("fs"));
8760
- import_node_path23 = __toESM(require("path"));
9213
+ import_node_fs23 = __toESM(require("fs"));
9214
+ import_node_path24 = __toESM(require("path"));
8761
9215
  import_yaml5 = __toESM(require("yaml"));
8762
9216
  init_docker();
8763
9217
  init_host_routes();
@@ -8864,11 +9318,13 @@ async function runSetup(options = {}) {
8864
9318
  })
8865
9319
  );
8866
9320
  }
9321
+ let requestedWorkspaceRuntime;
8867
9322
  if (options.workspaceRuntime !== void 0 || options.devsyInactivityTimeout !== void 0) {
8868
9323
  try {
8869
9324
  const existing = readWorkspaceRuntimeConfig();
9325
+ requestedWorkspaceRuntime = options.workspaceRuntime ? parseWorkspaceRuntime(options.workspaceRuntime) : void 0;
8870
9326
  const next = {
8871
- runtime: options.workspaceRuntime ? parseWorkspaceRuntime(options.workspaceRuntime) : existing.runtime,
9327
+ runtime: requestedWorkspaceRuntime ?? existing.runtime,
8872
9328
  devsyInactivityTimeout: options.devsyInactivityTimeout ?? existing.devsyInactivityTimeout
8873
9329
  };
8874
9330
  writeWorkspaceRuntimeConfig(next);
@@ -8890,6 +9346,28 @@ async function runSetup(options = {}) {
8890
9346
  );
8891
9347
  }
8892
9348
  }
9349
+ if (requestedWorkspaceRuntime === "devsy") {
9350
+ try {
9351
+ const prepared = await prepareDevsyAgent();
9352
+ actions.push(
9353
+ action(prepared.changed ? "performed" : "skipped", {
9354
+ id: "global.devsy-agent",
9355
+ summary: prepared.changed ? `Downloaded and verified ${prepared.asset.name} for Devsy.` : `Verified the ${prepared.source} Devsy agent source.`,
9356
+ details: `version=${SUPPORTED_DEVSY_VERSION}, asset=${prepared.asset.name}, source=${prepared.source}, transport=${prepared.transport}`
9357
+ })
9358
+ );
9359
+ } catch (error) {
9360
+ const details = error instanceof DevsyAgentReadinessError ? `state=${error.inspection.state}, source=${error.inspection.source}, ${error.inspection.reason}` : error instanceof Error ? error.message : String(error);
9361
+ actions.push(
9362
+ action("failed", {
9363
+ id: "global.devsy-agent",
9364
+ summary: "Failed to prepare a verified Devsy agent source.",
9365
+ details,
9366
+ suggestion: error instanceof DevsyAgentReadinessError ? devsyAgentRepairSuggestion(error.inspection) : `Run: ${DEVSY_AGENT_SETUP_COMMAND}`
9367
+ })
9368
+ );
9369
+ }
9370
+ }
8893
9371
  try {
8894
9372
  const existed = await networkExists(DEVNET_NAME);
8895
9373
  await ensureNetwork(DEVNET_NAME);
@@ -8979,6 +9457,7 @@ async function runSetup(options = {}) {
8979
9457
  var init_setup = __esm({
8980
9458
  "src/core/setup.ts"() {
8981
9459
  "use strict";
9460
+ init_devsy_agent();
8982
9461
  init_docker();
8983
9462
  init_doctor();
8984
9463
  init_repo_config();
@@ -9047,12 +9526,12 @@ function parseSsPortListeners(stdout, port) {
9047
9526
  return listeners;
9048
9527
  }
9049
9528
  function findPortListeners(port) {
9050
- const result = (0, import_node_child_process17.spawnSync)("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN"], {
9529
+ const result = (0, import_node_child_process18.spawnSync)("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN"], {
9051
9530
  encoding: "utf-8"
9052
9531
  });
9053
9532
  if (result.error && result.error.code === "ENOENT") {
9054
9533
  if (process.platform === "linux") {
9055
- const ssResult = (0, import_node_child_process17.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
9534
+ const ssResult = (0, import_node_child_process18.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
9056
9535
  if (ssResult.status === 0 && ssResult.stdout) {
9057
9536
  return parseSsPortListeners(ssResult.stdout, port);
9058
9537
  }
@@ -9081,11 +9560,11 @@ function findPortListeners(port) {
9081
9560
  };
9082
9561
  });
9083
9562
  }
9084
- var import_node_child_process17;
9563
+ var import_node_child_process18;
9085
9564
  var init_ports = __esm({
9086
9565
  "src/util/ports.ts"() {
9087
9566
  "use strict";
9088
- import_node_child_process17 = require("child_process");
9567
+ import_node_child_process18 = require("child_process");
9089
9568
  }
9090
9569
  });
9091
9570
 
@@ -9162,18 +9641,18 @@ function probeHttpRoute(host, options = {}) {
9162
9641
  args.push("--cacert", CERT_FILE);
9163
9642
  }
9164
9643
  args.push(url);
9165
- const result = (0, import_node_child_process18.spawnSync)("curl", args, { encoding: "utf-8" });
9644
+ const result = (0, import_node_child_process19.spawnSync)("curl", args, { encoding: "utf-8" });
9166
9645
  const stdout = result.stdout?.trim() ?? "";
9167
9646
  const status = /^\d{3}$/.test(stdout) ? Number(stdout) : void 0;
9168
9647
  const ok = result.status === 0 && status !== void 0 && status >= 100 && status < 500;
9169
9648
  const details = ok ? `HTTP ${status}` : [status === void 0 ? void 0 : `HTTP ${status}`, result.stderr?.trim()].filter(Boolean).join(": ") || `curl exited with status ${result.status ?? "unknown"}`;
9170
9649
  return { ok, status, details };
9171
9650
  }
9172
- var import_node_child_process18;
9651
+ var import_node_child_process19;
9173
9652
  var init_http_route_probe = __esm({
9174
9653
  "src/core/http-route-probe.ts"() {
9175
9654
  "use strict";
9176
- import_node_child_process18 = require("child_process");
9655
+ import_node_child_process19 = require("child_process");
9177
9656
  init_router();
9178
9657
  init_tls();
9179
9658
  }
@@ -9249,14 +9728,135 @@ var init_route_publication = __esm({
9249
9728
  }
9250
9729
  });
9251
9730
 
9731
+ // src/core/traefik-route-health.ts
9732
+ function sleep(ms) {
9733
+ return new Promise((resolve) => setTimeout(resolve, ms));
9734
+ }
9735
+ function inspectRouterApi(protocol) {
9736
+ const result = (0, import_node_child_process20.spawnSync)(
9737
+ "curl",
9738
+ [
9739
+ "--silent",
9740
+ "--show-error",
9741
+ "--fail",
9742
+ "--max-time",
9743
+ "2",
9744
+ `${ROUTER_API_BASE}/${protocol}/routers`
9745
+ ],
9746
+ { encoding: "utf-8" }
9747
+ );
9748
+ if (result.status !== 0) {
9749
+ return {
9750
+ ok: false,
9751
+ details: result.stderr?.trim() || `curl exited with status ${result.status ?? "unknown"}`
9752
+ };
9753
+ }
9754
+ let value;
9755
+ try {
9756
+ value = JSON.parse(result.stdout);
9757
+ } catch {
9758
+ return { ok: false, details: `${protocol.toUpperCase()} router API returned invalid JSON` };
9759
+ }
9760
+ if (!Array.isArray(value)) {
9761
+ return { ok: false, details: `${protocol.toUpperCase()} router API returned a non-array` };
9762
+ }
9763
+ return {
9764
+ ok: true,
9765
+ names: new Set(
9766
+ value.flatMap((item) => {
9767
+ if (!item || typeof item !== "object") return [];
9768
+ const name = item.name;
9769
+ return typeof name === "string" ? [name] : [];
9770
+ })
9771
+ )
9772
+ };
9773
+ }
9774
+ function inspectExpectedRoutes(routes) {
9775
+ const missing = [];
9776
+ const failures = [];
9777
+ for (const protocol of ["http", "tcp"]) {
9778
+ const expected = routes.filter((route) => (route.protocol ?? "http") === protocol).map((route) => buildHostRouteRouterName(route.repoPath, route.name));
9779
+ if (expected.length === 0) continue;
9780
+ const result = inspectRouterApi(protocol);
9781
+ if (!result.ok) {
9782
+ failures.push(result.details);
9783
+ missing.push(...expected);
9784
+ continue;
9785
+ }
9786
+ missing.push(...expected.filter((name) => !result.names.has(name)));
9787
+ }
9788
+ return missing.length === 0 ? { ok: true } : {
9789
+ ok: false,
9790
+ missing,
9791
+ details: failures.length > 0 ? failures.join("; ") : "router names are absent"
9792
+ };
9793
+ }
9794
+ async function waitForExpectedRoutes(routes, timeoutMs, pollIntervalMs) {
9795
+ const deadline = Date.now() + timeoutMs;
9796
+ let result;
9797
+ do {
9798
+ result = inspectExpectedRoutes(routes);
9799
+ if (result.ok) return result;
9800
+ if (Date.now() < deadline) {
9801
+ await sleep(Math.min(pollIntervalMs, Math.max(1, deadline - Date.now())));
9802
+ }
9803
+ } while (Date.now() < deadline);
9804
+ return result;
9805
+ }
9806
+ async function ensureTraefikRoutesLoaded(routes, options = {}) {
9807
+ if (routes.length === 0) return { restarted: false };
9808
+ const initialTimeoutMs = options.initialTimeoutMs ?? DEFAULT_INITIAL_TIMEOUT_MS;
9809
+ const recoveryTimeoutMs = options.recoveryTimeoutMs ?? DEFAULT_RECOVERY_TIMEOUT_MS;
9810
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
9811
+ const initial = await waitForExpectedRoutes(routes, initialTimeoutMs, pollIntervalMs);
9812
+ if (initial.ok) return { restarted: false };
9813
+ import_node_fs24.default.mkdirSync(DEVROUTER_HOME, { recursive: true });
9814
+ return withFileLock(
9815
+ ROUTER_RELOAD_LOCK_FILE,
9816
+ {
9817
+ activity: "Traefik route reload recovery",
9818
+ waitMs: initialTimeoutMs + recoveryTimeoutMs + 1e4,
9819
+ fair: true,
9820
+ onWait: createStderrWaitReporter("Traefik route reload recovery", "shared router")
9821
+ },
9822
+ async () => {
9823
+ const recheck = await waitForExpectedRoutes(routes, 0, pollIntervalMs);
9824
+ if (recheck.ok) return { restarted: false };
9825
+ restartRouterStack();
9826
+ const recovered = await waitForExpectedRoutes(routes, recoveryTimeoutMs, pollIntervalMs);
9827
+ if (recovered.ok) return { restarted: true };
9828
+ throw new Error(
9829
+ `Traefik did not load file-provider routes after one restart: ${recovered.missing.join(", ")} (${recovered.details}). Inspect: devrouter logs --tail 100`
9830
+ );
9831
+ }
9832
+ );
9833
+ }
9834
+ var import_node_child_process20, import_node_fs24, import_node_path25, ROUTER_API_BASE, ROUTER_RELOAD_LOCK_FILE, DEFAULT_INITIAL_TIMEOUT_MS, DEFAULT_RECOVERY_TIMEOUT_MS, DEFAULT_POLL_INTERVAL_MS;
9835
+ var init_traefik_route_health = __esm({
9836
+ "src/core/traefik-route-health.ts"() {
9837
+ "use strict";
9838
+ import_node_child_process20 = require("child_process");
9839
+ import_node_fs24 = __toESM(require("fs"));
9840
+ import_node_path25 = __toESM(require("path"));
9841
+ init_file_lock();
9842
+ init_host_routes();
9843
+ init_router();
9844
+ ROUTER_API_BASE = "http://127.0.0.1:8080/api";
9845
+ ROUTER_RELOAD_LOCK_FILE = import_node_path25.default.join(DEVROUTER_HOME, "router-reload.lock");
9846
+ DEFAULT_INITIAL_TIMEOUT_MS = 3e3;
9847
+ DEFAULT_RECOVERY_TIMEOUT_MS = 1e4;
9848
+ DEFAULT_POLL_INTERVAL_MS = 250;
9849
+ }
9850
+ });
9851
+
9252
9852
  // src/core/workspace-ensure.ts
9253
9853
  function assertOverlay(container, repoPath) {
9254
9854
  const workingDir = container.labels["com.docker.compose.project.working_dir"];
9255
- if (!workingDir || !sameWorkspacePath(workingDir, import_node_path24.default.join(repoPath, ".devcontainer"))) {
9855
+ if (!workingDir || !sameWorkspacePath(workingDir, import_node_path26.default.join(repoPath, ".devcontainer"))) {
9256
9856
  throw new Error(`Container '${container.id}' does not belong to the exact worktree.`);
9257
9857
  }
9258
9858
  const configFiles = (container.labels["com.docker.compose.project.config_files"] ?? "").split(",").filter(Boolean);
9259
- const expectedOverlay = import_node_path24.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
9859
+ const expectedOverlay = import_node_path26.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
9260
9860
  if (!configFiles.some((configFile) => sameWorkspacePath(configFile, expectedOverlay))) {
9261
9861
  throw new Error(`Container '${container.id}' was not started with ${DEVCONTAINER_OVERLAY}.`);
9262
9862
  }
@@ -9330,7 +9930,7 @@ async function waitForManagedServices(plan, repoPath, composeProject, expectedSe
9330
9930
  } catch (error) {
9331
9931
  lastError = error;
9332
9932
  }
9333
- if (Date.now() < deadline) await sleep(POLL_INTERVAL_MS);
9933
+ if (Date.now() < deadline) await sleep2(POLL_INTERVAL_MS);
9334
9934
  } while (Date.now() < deadline);
9335
9935
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
9336
9936
  }
@@ -9439,7 +10039,7 @@ function hasExactManagedComposeProject(repoPath, state) {
9439
10039
  }).some((container) => {
9440
10040
  const workingDir = container.labels["com.docker.compose.project.working_dir"];
9441
10041
  return Boolean(
9442
- workingDir && sameWorkspacePath(workingDir, import_node_path24.default.join(repoPath, ".devcontainer"))
10042
+ workingDir && sameWorkspacePath(workingDir, import_node_path26.default.join(repoPath, ".devcontainer"))
9443
10043
  );
9444
10044
  });
9445
10045
  } catch {
@@ -9533,7 +10133,7 @@ function validateWorkspaceContainers(containers, options) {
9533
10133
  }
9534
10134
  return { id: appContainer.id, workspacePath: repoMount.Destination };
9535
10135
  }
9536
- function sleep(ms) {
10136
+ function sleep2(ms) {
9537
10137
  return new Promise((resolve) => setTimeout(resolve, ms));
9538
10138
  }
9539
10139
  async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeoutMs) {
@@ -9547,7 +10147,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
9547
10147
  target
9548
10148
  });
9549
10149
  if (target.kind === "linked") {
9550
- const workspaceEnv = (0, import_node_child_process19.spawnSync)(
10150
+ const workspaceEnv = (0, import_node_child_process21.spawnSync)(
9551
10151
  "docker",
9552
10152
  ["exec", appContainer.id, "printenv", "WORKSPACE"],
9553
10153
  { encoding: "utf-8" }
@@ -9557,7 +10157,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
9557
10157
  `Workspace app container must expose WORKSPACE='${target.workspace}' (got '${workspaceEnv.stdout.trim() || "(empty)"}').`
9558
10158
  );
9559
10159
  }
9560
- const devrouterWorkspaceEnv = (0, import_node_child_process19.spawnSync)(
10160
+ const devrouterWorkspaceEnv = (0, import_node_child_process21.spawnSync)(
9561
10161
  "docker",
9562
10162
  ["exec", appContainer.id, "printenv", "DEVROUTER_WORKSPACE"],
9563
10163
  { encoding: "utf-8" }
@@ -9568,7 +10168,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
9568
10168
  );
9569
10169
  }
9570
10170
  }
9571
- const gitCheck = (0, import_node_child_process19.spawnSync)(
10171
+ const gitCheck = (0, import_node_child_process21.spawnSync)(
9572
10172
  "docker",
9573
10173
  [
9574
10174
  "exec",
@@ -9589,7 +10189,7 @@ async function waitForContainerPreflight(repoPath, target, upstreamHosts, timeou
9589
10189
  lastError = error;
9590
10190
  }
9591
10191
  if (Date.now() < deadline) {
9592
- await sleep(POLL_INTERVAL_MS);
10192
+ await sleep2(POLL_INTERVAL_MS);
9593
10193
  }
9594
10194
  } while (Date.now() < deadline);
9595
10195
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
@@ -9614,7 +10214,7 @@ async function waitForHttpRoutes(repoPath, apps, timeoutMs) {
9614
10214
  return;
9615
10215
  }
9616
10216
  if (Date.now() < deadline) {
9617
- await sleep(POLL_INTERVAL_MS);
10217
+ await sleep2(POLL_INTERVAL_MS);
9618
10218
  }
9619
10219
  } while (Date.now() < deadline);
9620
10220
  throw new Error(
@@ -9660,14 +10260,14 @@ function resolvePrimaryTarget(repoPath) {
9660
10260
  }
9661
10261
  function isPrimaryCheckout(repoPath) {
9662
10262
  try {
9663
- return import_node_fs23.default.statSync(import_node_path24.default.join(repoPath, ".git")).isDirectory();
10263
+ return import_node_fs25.default.statSync(import_node_path26.default.join(repoPath, ".git")).isDirectory();
9664
10264
  } catch {
9665
10265
  return false;
9666
10266
  }
9667
10267
  }
9668
10268
  function openUrls(urls) {
9669
10269
  for (const url of urls) {
9670
- const opened = (0, import_node_child_process19.spawnSync)("open", [url], { encoding: "utf-8" });
10270
+ const opened = (0, import_node_child_process21.spawnSync)("open", [url], { encoding: "utf-8" });
9671
10271
  if (opened.status !== 0) {
9672
10272
  process.stderr.write(`Warning: could not open '${url}'.
9673
10273
  `);
@@ -9706,12 +10306,16 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
9706
10306
  let managedConfigWritten = false;
9707
10307
  let managedWorkspaceEnv;
9708
10308
  let firstTransitionBaseline;
10309
+ const routeLoadOptions = {
10310
+ initialTimeoutMs: Math.min(options.httpTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS, 3e3),
10311
+ recoveryTimeoutMs: Math.min(options.httpTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS, 1e4)
10312
+ };
9709
10313
  try {
9710
10314
  const target = linked ? resolveLinkedTarget(repoPath) : resolvePrimaryTarget(repoPath);
9711
10315
  let devpodId = target.devpodId;
9712
10316
  if (target.kind === "linked") {
9713
- const overlayPath = import_node_path24.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
9714
- if (!import_node_fs23.default.existsSync(overlayPath)) {
10317
+ const overlayPath = import_node_path26.default.join(repoPath, ".devcontainer", DEVCONTAINER_OVERLAY);
10318
+ if (!import_node_fs25.default.existsSync(overlayPath)) {
9715
10319
  throw new Error(`Missing required DevPod compose overlay: ${overlayPath}`);
9716
10320
  }
9717
10321
  }
@@ -9781,7 +10385,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
9781
10385
  }
9782
10386
  const apps = proxyAppsFromConfig(runtime.config);
9783
10387
  const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
9784
- const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path24.default.basename(repoPath)) ?? "app";
10388
+ const aliasPrefix = target.kind === "linked" ? target.workspace : wsFromBranch(runtime.config.project?.name ?? import_node_path26.default.basename(repoPath)) ?? "app";
9785
10389
  for (const [index, app] of apps.entries()) {
9786
10390
  if (!parsedUpstreams[index].host.startsWith(`${aliasPrefix}-`)) {
9787
10391
  const owner = target.kind === "linked" ? "workspace" : "checkout";
@@ -9945,6 +10549,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
9945
10549
  target.workspace
9946
10550
  );
9947
10551
  candidateRoutesPublished = true;
10552
+ await ensureTraefikRoutesLoaded(publication.routes, routeLoadOptions);
9948
10553
  try {
9949
10554
  await waitForHttpRoutes(
9950
10555
  repoPath,
@@ -9968,6 +10573,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
9968
10573
  });
9969
10574
  recreated = true;
9970
10575
  replaceHostRoutesForRepo(repoPath, publication.routes);
10576
+ await ensureTraefikRoutesLoaded(publication.routes, routeLoadOptions);
9971
10577
  await waitForHttpRoutes(
9972
10578
  repoPath,
9973
10579
  apps,
@@ -10141,7 +10747,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
10141
10747
  }
10142
10748
  if (candidateRoutesPublished) {
10143
10749
  try {
10144
- replaceHostRoutesForRepo(repoPath, previousRoutes.map(routeInputFromState));
10750
+ const rollbackRoutes = previousRoutes.map(routeInputFromState);
10751
+ replaceHostRoutesForRepo(repoPath, rollbackRoutes);
10752
+ await ensureTraefikRoutesLoaded(rollbackRoutes, routeLoadOptions);
10145
10753
  } catch (rollbackError) {
10146
10754
  rollbackErrors.push(
10147
10755
  `routes: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`
@@ -10184,7 +10792,9 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
10184
10792
  }
10185
10793
  if (candidateRoutesPublished) {
10186
10794
  try {
10187
- replaceHostRoutesForRepo(repoPath, previousRoutes.map(routeInputFromState));
10795
+ const rollbackRoutes = previousRoutes.map(routeInputFromState);
10796
+ replaceHostRoutesForRepo(repoPath, rollbackRoutes);
10797
+ await ensureTraefikRoutesLoaded(rollbackRoutes, routeLoadOptions);
10188
10798
  } catch (rollbackError) {
10189
10799
  rollbackErrors.push(
10190
10800
  `routes: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`
@@ -10213,13 +10823,13 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
10213
10823
  }
10214
10824
  });
10215
10825
  }
10216
- var import_node_child_process19, import_node_fs23, import_node_path24, DEVCONTAINER_OVERLAY, DEFAULT_READINESS_TIMEOUT_MS, POLL_INTERVAL_MS;
10826
+ var import_node_child_process21, import_node_fs25, import_node_path26, DEVCONTAINER_OVERLAY, DEFAULT_READINESS_TIMEOUT_MS, POLL_INTERVAL_MS;
10217
10827
  var init_workspace_ensure = __esm({
10218
10828
  "src/core/workspace-ensure.ts"() {
10219
10829
  "use strict";
10220
- import_node_child_process19 = require("child_process");
10221
- import_node_fs23 = __toESM(require("fs"));
10222
- import_node_path24 = __toESM(require("path"));
10830
+ import_node_child_process21 = require("child_process");
10831
+ import_node_fs25 = __toESM(require("fs"));
10832
+ import_node_path26 = __toESM(require("path"));
10223
10833
  init_devcontainer_profile();
10224
10834
  init_devpod_environment();
10225
10835
  init_devpod_mutation();
@@ -10232,6 +10842,7 @@ var init_workspace_ensure = __esm({
10232
10842
  init_repo_config();
10233
10843
  init_route_publication();
10234
10844
  init_router();
10845
+ init_traefik_route_health();
10235
10846
  init_workspace();
10236
10847
  init_workspace_ownership();
10237
10848
  init_workspace_runtime();
@@ -10305,20 +10916,20 @@ function warnMissingWorkspaceOwnership(repoPath) {
10305
10916
  );
10306
10917
  }
10307
10918
  function defaultWorktreePath(mainRepo, ws) {
10308
- return import_node_path25.default.join(mainRepo, "trees", ws);
10919
+ return import_node_path27.default.join(mainRepo, "trees", ws);
10309
10920
  }
10310
10921
  function assertDefaultWorktreeRootIgnored(mainRepo) {
10311
- const ignored = (0, import_node_child_process20.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
10922
+ const ignored = (0, import_node_child_process22.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
10312
10923
  encoding: "utf-8"
10313
10924
  });
10314
10925
  if (ignored.status !== 0) {
10315
10926
  throw new Error(
10316
- `Default worktree root '${import_node_path25.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path25.default.join(mainRepo, ".gitignore")}' or use --path.`
10927
+ `Default worktree root '${import_node_path27.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path27.default.join(mainRepo, ".gitignore")}' or use --path.`
10317
10928
  );
10318
10929
  }
10319
10930
  }
10320
10931
  function legacyDefaultWorktreePath(mainRepo, ws) {
10321
- return import_node_path25.default.join(import_node_path25.default.dirname(mainRepo), `${import_node_path25.default.basename(mainRepo)}-${ws}`);
10932
+ return import_node_path27.default.join(import_node_path27.default.dirname(mainRepo), `${import_node_path27.default.basename(mainRepo)}-${ws}`);
10322
10933
  }
10323
10934
  function teardownFallbackPath(mainRepo, workspace) {
10324
10935
  const candidates = [
@@ -10430,13 +11041,13 @@ function assertFullDownPreflight(mainRepo, target) {
10430
11041
  if (sameWorkspacePath(target.worktreePath, mainRepo)) {
10431
11042
  throw new Error("Refusing to remove the primary Git checkout.");
10432
11043
  }
10433
- if (!target.worktree || target.worktree.prunable || !import_node_fs24.default.existsSync(target.worktreePath)) return;
11044
+ if (!target.worktree || target.worktree.prunable || !import_node_fs26.default.existsSync(target.worktreePath)) return;
10434
11045
  if (target.worktree.locked) {
10435
11046
  throw new Error(
10436
11047
  `Worktree '${target.worktreePath}' is locked; unlock it before workspace down.`
10437
11048
  );
10438
11049
  }
10439
- const status = (0, import_node_child_process20.spawnSync)(
11050
+ const status = (0, import_node_child_process22.spawnSync)(
10440
11051
  "git",
10441
11052
  ["-C", target.worktreePath, "status", "--porcelain", "--untracked-files=normal"],
10442
11053
  { encoding: "utf-8" }
@@ -10470,9 +11081,9 @@ async function workspaceUp(branch, opts = {}) {
10470
11081
  (candidate) => defaultWorktreePath(mainRepo, candidate)
10471
11082
  );
10472
11083
  const generatedPath = candidatePaths.find(
10473
- (candidate) => !import_node_fs24.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
11084
+ (candidate) => !import_node_fs26.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
10474
11085
  );
10475
- const selectedPath = opts.path ? import_node_path25.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
11086
+ const selectedPath = opts.path ? import_node_path27.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
10476
11087
  if (!selectedPath) {
10477
11088
  throw new Error(`Could not allocate a collision-safe worktree path for branch '${branch}'.`);
10478
11089
  }
@@ -10481,7 +11092,7 @@ async function workspaceUp(branch, opts = {}) {
10481
11092
  `Branch '${branch}' already uses worktree '${existingBranch.path}', not '${selectedPath}'.`
10482
11093
  );
10483
11094
  }
10484
- if (import_node_fs24.default.existsSync(selectedPath)) {
11095
+ if (import_node_fs26.default.existsSync(selectedPath)) {
10485
11096
  const registered = worktrees.find(
10486
11097
  (worktree) => sameWorkspacePath(worktree.path, selectedPath)
10487
11098
  );
@@ -10500,11 +11111,11 @@ async function workspaceUp(branch, opts = {}) {
10500
11111
  return selectedPath;
10501
11112
  }
10502
11113
  if (!opts.path) assertDefaultWorktreeRootIgnored(mainRepo);
10503
- const add = (0, import_node_child_process20.spawnSync)("git", ["-C", mainRepo, "worktree", "add", selectedPath, branch], {
11114
+ const add = (0, import_node_child_process22.spawnSync)("git", ["-C", mainRepo, "worktree", "add", selectedPath, branch], {
10504
11115
  encoding: "utf-8"
10505
11116
  });
10506
11117
  if (add.status !== 0) {
10507
- const addNew = (0, import_node_child_process20.spawnSync)(
11118
+ const addNew = (0, import_node_child_process22.spawnSync)(
10508
11119
  "git",
10509
11120
  ["-C", mainRepo, "worktree", "add", "-b", branch, selectedPath],
10510
11121
  { encoding: "utf-8" }
@@ -10606,8 +11217,8 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
10606
11217
  opts.quiet
10607
11218
  );
10608
11219
  if (removeWorktree) {
10609
- if (resolved.worktree && !resolved.worktree.prunable && import_node_fs24.default.existsSync(resolved.worktreePath)) {
10610
- const rm = (0, import_node_child_process20.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved.worktreePath], {
11220
+ if (resolved.worktree && !resolved.worktree.prunable && import_node_fs26.default.existsSync(resolved.worktreePath)) {
11221
+ const rm = (0, import_node_child_process22.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved.worktreePath], {
10611
11222
  encoding: "utf-8"
10612
11223
  });
10613
11224
  if (rm.status !== 0) {
@@ -10625,7 +11236,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
10625
11236
  }
10626
11237
  return result;
10627
11238
  };
10628
- return resolved.worktree && !resolved.worktree.prunable && import_node_fs24.default.existsSync(resolved.worktreePath) ? withWorkspaceLifecycleLock(resolved.worktreePath, operation) : operation();
11239
+ return resolved.worktree && !resolved.worktree.prunable && import_node_fs26.default.existsSync(resolved.worktreePath) ? withWorkspaceLifecycleLock(resolved.worktreePath, operation) : operation();
10629
11240
  }
10630
11241
  async function mutateWorkspaceOwnedPath(action2, worktreePath, opts = {}) {
10631
11242
  const mainRepo = resolveRepoPath(opts.repoPath);
@@ -10660,13 +11271,13 @@ async function workspaceStop(target, opts = {}) {
10660
11271
  async function workspaceDown(target, opts = {}) {
10661
11272
  return runWorkspaceLifecycle("down", target, opts);
10662
11273
  }
10663
- var import_node_child_process20, import_node_fs24, import_node_path25, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
11274
+ var import_node_child_process22, import_node_fs26, import_node_path27, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
10664
11275
  var init_workspace_lifecycle = __esm({
10665
11276
  "src/core/workspace-lifecycle.ts"() {
10666
11277
  "use strict";
10667
- import_node_child_process20 = require("child_process");
10668
- import_node_fs24 = __toESM(require("fs"));
10669
- import_node_path25 = __toESM(require("path"));
11278
+ import_node_child_process22 = require("child_process");
11279
+ import_node_fs26 = __toESM(require("fs"));
11280
+ import_node_path27 = __toESM(require("path"));
10670
11281
  init_devpod_mutation();
10671
11282
  init_devpod_workspaces();
10672
11283
  init_repo_config();
@@ -10795,17 +11406,17 @@ async function devsyExec(repoPath, command) {
10795
11406
  }
10796
11407
  const args = ["workspace", "exec", "--result-format", "plain", workspace.id, "--", ...command];
10797
11408
  return new Promise((resolve, reject) => {
10798
- const child = (0, import_node_child_process21.spawn)("devsy", args, { stdio: "inherit" });
11409
+ const child = (0, import_node_child_process23.spawn)("devsy", args, { stdio: "inherit" });
10799
11410
  child.once("error", (error) => reject(new Error(`devsy exec failed: ${error.message}`)));
10800
11411
  child.once("close", (code) => resolve(code ?? 1));
10801
11412
  });
10802
11413
  });
10803
11414
  }
10804
- var import_node_child_process21;
11415
+ var import_node_child_process23;
10805
11416
  var init_devsy_exec = __esm({
10806
11417
  "src/core/devsy-exec.ts"() {
10807
11418
  "use strict";
10808
- import_node_child_process21 = require("child_process");
11419
+ import_node_child_process23 = require("child_process");
10809
11420
  init_devsy_workspaces();
10810
11421
  init_workspace();
10811
11422
  }
@@ -10827,7 +11438,7 @@ function ensureGuidance2(repoPath) {
10827
11438
  return `Run 'devrouter ensure ${repoPath}' first.`;
10828
11439
  }
10829
11440
  function assertRunningDevpod(devpodId, repoPath) {
10830
- const result = (0, import_node_child_process22.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
11441
+ const result = (0, import_node_child_process24.spawnSync)("devpod", ["status", devpodId, "--output", "json"], {
10831
11442
  encoding: "utf-8"
10832
11443
  });
10833
11444
  if (result.status !== 0) {
@@ -10868,7 +11479,7 @@ async function devpodExec(repoPath, command) {
10868
11479
  }
10869
11480
  assertRunningDevpod(devpod.id, repoPath);
10870
11481
  const workspaceDirectory = resolveWorkspaceDirectory(repoPath);
10871
- const statusMarker = `__DEVROUTER_EXIT_${(0, import_node_crypto8.randomUUID)()}__:`;
11482
+ const statusMarker = `__DEVROUTER_EXIT_${(0, import_node_crypto9.randomUUID)()}__:`;
10872
11483
  const statusMarkerBytes = Buffer.from(statusMarker, "ascii");
10873
11484
  const literalCommand = command.map(quotePosixArg).join(" ");
10874
11485
  const wrappedCommand = `${literalCommand}; __devrouter_status=$?; printf '${statusMarker}%s\\n' "$__devrouter_status" >&2; exit 0`;
@@ -10886,7 +11497,7 @@ async function devpodExec(repoPath, command) {
10886
11497
  wrappedCommand
10887
11498
  ];
10888
11499
  return new Promise((resolve, reject) => {
10889
- const child = (0, import_node_child_process22.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
11500
+ const child = (0, import_node_child_process24.spawn)("devpod", args, { stdio: ["inherit", "inherit", "pipe"] });
10890
11501
  let pending = Buffer.alloc(0);
10891
11502
  let statusBytes = Buffer.alloc(0);
10892
11503
  let readingStatus = false;
@@ -10945,12 +11556,12 @@ async function devpodExec(repoPath, command) {
10945
11556
  });
10946
11557
  });
10947
11558
  }
10948
- var import_node_child_process22, import_node_crypto8, DEVPOD_MISSING_EXIT_STATUS_DIAGNOSTIC;
11559
+ var import_node_child_process24, import_node_crypto9, DEVPOD_MISSING_EXIT_STATUS_DIAGNOSTIC;
10949
11560
  var init_devpod_exec = __esm({
10950
11561
  "src/core/devpod-exec.ts"() {
10951
11562
  "use strict";
10952
- import_node_child_process22 = require("child_process");
10953
- import_node_crypto8 = require("crypto");
11563
+ import_node_child_process24 = require("child_process");
11564
+ import_node_crypto9 = require("crypto");
10954
11565
  init_devpod_environment();
10955
11566
  init_devpod_workspaces();
10956
11567
  init_devsy_exec();
@@ -11136,7 +11747,7 @@ async function runOpenCommand(name) {
11136
11747
  );
11137
11748
  return;
11138
11749
  }
11139
- const result = (0, import_node_child_process23.spawnSync)("open", [url], { encoding: "utf-8" });
11750
+ const result = (0, import_node_child_process25.spawnSync)("open", [url], { encoding: "utf-8" });
11140
11751
  if (result.status !== 0) {
11141
11752
  const details = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
11142
11753
  throw new Error(`Unable to open '${url}': ${details || "unknown error"}`);
@@ -11144,11 +11755,11 @@ async function runOpenCommand(name) {
11144
11755
  process.stdout.write(`Opened ${url}
11145
11756
  `);
11146
11757
  }
11147
- var import_node_child_process23;
11758
+ var import_node_child_process25;
11148
11759
  var init_open = __esm({
11149
11760
  "src/commands/open.ts"() {
11150
11761
  "use strict";
11151
- import_node_child_process23 = require("child_process");
11762
+ import_node_child_process25 = require("child_process");
11152
11763
  init_docker();
11153
11764
  init_host_routes();
11154
11765
  init_repo_config();
@@ -11171,7 +11782,7 @@ async function runLogsCommand(options) {
11171
11782
  const args = ["logs", "--tail", tail, ROUTER_CONTAINER_NAME];
11172
11783
  if (options.follow) {
11173
11784
  args.splice(1, 0, "-f");
11174
- const child = (0, import_node_child_process24.spawn)("docker", args, { stdio: "inherit" });
11785
+ const child = (0, import_node_child_process26.spawn)("docker", args, { stdio: "inherit" });
11175
11786
  const onSignal = () => {
11176
11787
  child.kill("SIGTERM");
11177
11788
  };
@@ -11185,17 +11796,17 @@ async function runLogsCommand(options) {
11185
11796
  });
11186
11797
  });
11187
11798
  } else {
11188
- const result = (0, import_node_child_process24.spawnSync)("docker", args, { stdio: "inherit" });
11799
+ const result = (0, import_node_child_process26.spawnSync)("docker", args, { stdio: "inherit" });
11189
11800
  if (result.status !== 0) {
11190
11801
  throw new Error("Failed to retrieve router logs.");
11191
11802
  }
11192
11803
  }
11193
11804
  }
11194
- var import_node_child_process24;
11805
+ var import_node_child_process26;
11195
11806
  var init_logs = __esm({
11196
11807
  "src/commands/logs.ts"() {
11197
11808
  "use strict";
11198
- import_node_child_process24 = require("child_process");
11809
+ import_node_child_process26 = require("child_process");
11199
11810
  init_docker();
11200
11811
  init_router();
11201
11812
  }
@@ -11235,17 +11846,17 @@ function asRecord3(value) {
11235
11846
  return value;
11236
11847
  }
11237
11848
  function readJson(filePath) {
11238
- if (!import_node_fs25.default.existsSync(filePath)) {
11849
+ if (!import_node_fs27.default.existsSync(filePath)) {
11239
11850
  return void 0;
11240
11851
  }
11241
11852
  try {
11242
- return JSON.parse(import_node_fs25.default.readFileSync(filePath, "utf-8"));
11853
+ return JSON.parse(import_node_fs27.default.readFileSync(filePath, "utf-8"));
11243
11854
  } catch {
11244
11855
  return void 0;
11245
11856
  }
11246
11857
  }
11247
11858
  function relative(repoPath, filePath) {
11248
- return import_node_path26.default.relative(repoPath, filePath) || ".";
11859
+ return import_node_path28.default.relative(repoPath, filePath) || ".";
11249
11860
  }
11250
11861
  function redactEnvAssignments(value) {
11251
11862
  return value.replace(
@@ -11286,7 +11897,7 @@ function inspectPackageManager(repoPath, pkg) {
11286
11897
  ["bun.lock", "bun"]
11287
11898
  ];
11288
11899
  for (const [fileName, name] of lockfiles) {
11289
- if (import_node_fs25.default.existsSync(import_node_path26.default.join(repoPath, fileName))) {
11900
+ if (import_node_fs27.default.existsSync(import_node_path28.default.join(repoPath, fileName))) {
11290
11901
  return { name, source: fileName };
11291
11902
  }
11292
11903
  }
@@ -11301,9 +11912,9 @@ function inspectNode(repoPath, pkg) {
11301
11912
  if (typeof engines?.node === "string") {
11302
11913
  return { version: engines.node, source: "package.json:engines.node" };
11303
11914
  }
11304
- const nvmrc = import_node_path26.default.join(repoPath, ".nvmrc");
11305
- if (import_node_fs25.default.existsSync(nvmrc)) {
11306
- const version = import_node_fs25.default.readFileSync(nvmrc, "utf-8").trim();
11915
+ const nvmrc = import_node_path28.default.join(repoPath, ".nvmrc");
11916
+ if (import_node_fs27.default.existsSync(nvmrc)) {
11917
+ const version = import_node_fs27.default.readFileSync(nvmrc, "utf-8").trim();
11307
11918
  return { version, source: ".nvmrc" };
11308
11919
  }
11309
11920
  return void 0;
@@ -11364,7 +11975,7 @@ function configuredComposeFiles(repoPath) {
11364
11975
  const files = config.apps.filter(
11365
11976
  (app) => app.runtime === "docker"
11366
11977
  ).flatMap((app) => app.docker.composeFiles).filter(
11367
- (fileName) => !import_node_path26.default.isAbsolute(fileName) && !import_node_path26.default.normalize(fileName).startsWith("..")
11978
+ (fileName) => !import_node_path28.default.isAbsolute(fileName) && !import_node_path28.default.normalize(fileName).startsWith("..")
11368
11979
  );
11369
11980
  return Array.from(new Set(files));
11370
11981
  } catch {
@@ -11382,7 +11993,7 @@ function composeFiles(repoPath) {
11382
11993
  ...configuredComposeFiles(repoPath)
11383
11994
  ];
11384
11995
  return Array.from(new Set(candidates)).filter(
11385
- (fileName) => import_node_fs25.default.existsSync(import_node_path26.default.join(repoPath, fileName))
11996
+ (fileName) => import_node_fs27.default.existsSync(import_node_path28.default.join(repoPath, fileName))
11386
11997
  );
11387
11998
  }
11388
11999
  function stringArray2(value) {
@@ -11420,7 +12031,7 @@ function inspectServices(repoPath) {
11420
12031
  const services = [];
11421
12032
  for (const fileName of composeFiles(repoPath)) {
11422
12033
  try {
11423
- const parsed = import_yaml6.default.parse(import_node_fs25.default.readFileSync(import_node_path26.default.join(repoPath, fileName), "utf-8"));
12034
+ const parsed = import_yaml6.default.parse(import_node_fs27.default.readFileSync(import_node_path28.default.join(repoPath, fileName), "utf-8"));
11424
12035
  const serviceMap = asRecord3(asRecord3(parsed)?.services);
11425
12036
  for (const [name, value] of Object.entries(serviceMap ?? {})) {
11426
12037
  const service = asRecord3(value);
@@ -11455,8 +12066,8 @@ function inspectServices(repoPath) {
11455
12066
  return services;
11456
12067
  }
11457
12068
  function inspectEnvFiles(repoPath) {
11458
- const files = import_node_fs25.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
11459
- const content = import_node_fs25.default.readFileSync(import_node_path26.default.join(repoPath, fileName), "utf-8");
12069
+ const files = import_node_fs27.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
12070
+ const content = import_node_fs27.default.readFileSync(import_node_path28.default.join(repoPath, fileName), "utf-8");
11460
12071
  const names = content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && line.includes("=")).map((line) => line.split("=")[0]?.trim()).filter((name) => Boolean(name)).sort();
11461
12072
  return { path: fileName, names };
11462
12073
  });
@@ -11470,18 +12081,18 @@ function inspectEnvFiles(repoPath) {
11470
12081
  };
11471
12082
  }
11472
12083
  function inspectDevcontainer(repoPath) {
11473
- const dir = import_node_path26.default.join(repoPath, ".devcontainer");
11474
- if (!import_node_fs25.default.existsSync(dir)) {
12084
+ const dir = import_node_path28.default.join(repoPath, ".devcontainer");
12085
+ if (!import_node_fs27.default.existsSync(dir)) {
11475
12086
  return { exists: false, files: [] };
11476
12087
  }
11477
12088
  return {
11478
12089
  exists: true,
11479
- files: import_node_fs25.default.readdirSync(dir).filter((fileName) => import_node_fs25.default.statSync(import_node_path26.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
12090
+ files: import_node_fs27.default.readdirSync(dir).filter((fileName) => import_node_fs27.default.statSync(import_node_path28.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
11480
12091
  };
11481
12092
  }
11482
12093
  function inspectDevrouter(repoPath) {
11483
12094
  const configPath = getRepoConfigPath(repoPath);
11484
- if (!import_node_fs25.default.existsSync(configPath)) {
12095
+ if (!import_node_fs27.default.existsSync(configPath)) {
11485
12096
  return {
11486
12097
  exists: false,
11487
12098
  configPath,
@@ -11525,15 +12136,15 @@ function inspectAgentGuidance(repoPath) {
11525
12136
  ["AGENTS.md", "agents"],
11526
12137
  ["CLAUDE.md", "claude"]
11527
12138
  ]) {
11528
- if (import_node_fs25.default.existsSync(import_node_path26.default.join(repoPath, fileName))) {
12139
+ if (import_node_fs27.default.existsSync(import_node_path28.default.join(repoPath, fileName))) {
11529
12140
  results.push({ path: fileName, kind });
11530
12141
  }
11531
12142
  }
11532
- const skillsDir = import_node_path26.default.join(repoPath, ".agents", "skills");
11533
- if (import_node_fs25.default.existsSync(skillsDir)) {
11534
- for (const name of import_node_fs25.default.readdirSync(skillsDir).sort()) {
11535
- const skillPath = import_node_path26.default.join(skillsDir, name, "SKILL.md");
11536
- if (import_node_fs25.default.existsSync(skillPath)) {
12143
+ const skillsDir = import_node_path28.default.join(repoPath, ".agents", "skills");
12144
+ if (import_node_fs27.default.existsSync(skillsDir)) {
12145
+ for (const name of import_node_fs27.default.readdirSync(skillsDir).sort()) {
12146
+ const skillPath = import_node_path28.default.join(skillsDir, name, "SKILL.md");
12147
+ if (import_node_fs27.default.existsSync(skillPath)) {
11537
12148
  results.push({ path: relative(repoPath, skillPath), kind: "skill" });
11538
12149
  }
11539
12150
  }
@@ -11576,7 +12187,7 @@ function buildIssues(report) {
11576
12187
  }
11577
12188
  function inspectRepo(options = {}) {
11578
12189
  const repoPath = resolveRepoPath(options.repo);
11579
- const pkg = readJson(import_node_path26.default.join(repoPath, "package.json"));
12190
+ const pkg = readJson(import_node_path28.default.join(repoPath, "package.json"));
11580
12191
  const scripts = inspectScripts(pkg);
11581
12192
  const reportWithoutIssues = {
11582
12193
  repoPath,
@@ -11595,12 +12206,12 @@ function inspectRepo(options = {}) {
11595
12206
  issues: buildIssues(reportWithoutIssues)
11596
12207
  };
11597
12208
  }
11598
- var import_node_fs25, import_node_path26, import_yaml6;
12209
+ var import_node_fs27, import_node_path28, import_yaml6;
11599
12210
  var init_repo_inspect = __esm({
11600
12211
  "src/core/repo-inspect.ts"() {
11601
12212
  "use strict";
11602
- import_node_fs25 = __toESM(require("fs"));
11603
- import_node_path26 = __toESM(require("path"));
12213
+ import_node_fs27 = __toESM(require("fs"));
12214
+ import_node_path28 = __toESM(require("path"));
11604
12215
  import_yaml6 = __toESM(require("yaml"));
11605
12216
  init_repo_config();
11606
12217
  }
@@ -11705,7 +12316,7 @@ function requiredFileChecks(repoPath) {
11705
12316
  ".devcontainer/docker-compose.yml",
11706
12317
  ".devrouter.yml"
11707
12318
  ];
11708
- const missing = required.filter((fileName) => !import_node_fs26.default.existsSync(import_node_path27.default.join(repoPath, fileName)));
12319
+ const missing = required.filter((fileName) => !import_node_fs28.default.existsSync(import_node_path29.default.join(repoPath, fileName)));
11709
12320
  return {
11710
12321
  id: "repo.devcontainer.verify-files",
11711
12322
  level: missing.length === 0 ? "ok" : "error",
@@ -11924,12 +12535,12 @@ async function verifyDevcontainer(options = {}) {
11924
12535
  nextSteps: collectNextSteps3(checks)
11925
12536
  };
11926
12537
  }
11927
- var import_node_fs26, import_node_path27;
12538
+ var import_node_fs28, import_node_path29;
11928
12539
  var init_devcontainer_verify = __esm({
11929
12540
  "src/core/devcontainer-verify.ts"() {
11930
12541
  "use strict";
11931
- import_node_fs26 = __toESM(require("fs"));
11932
- import_node_path27 = __toESM(require("path"));
12542
+ import_node_fs28 = __toESM(require("fs"));
12543
+ import_node_path29 = __toESM(require("path"));
11933
12544
  init_capabilities();
11934
12545
  init_doctor();
11935
12546
  init_host_routes();
@@ -12226,7 +12837,7 @@ function packageManagerIssues(repo) {
12226
12837
  }
12227
12838
  function plannedFiles(repoPath, version) {
12228
12839
  const repo = inspectRepo({ repo: repoPath });
12229
- const projectName = sanitizeProjectName(import_node_path28.default.basename(repo.repoPath));
12840
+ const projectName = sanitizeProjectName(import_node_path30.default.basename(repo.repoPath));
12230
12841
  const nodeMajor = majorVersion(repo.node?.version, "24");
12231
12842
  const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
12232
12843
  const port = inferPort2(repo);
@@ -12271,8 +12882,8 @@ function plannedFiles(repoPath, version) {
12271
12882
  };
12272
12883
  }
12273
12884
  function classifyFile(repoPath, file) {
12274
- const absolutePath = import_node_path28.default.join(repoPath, file.relativePath);
12275
- if (!import_node_fs27.default.existsSync(absolutePath)) {
12885
+ const absolutePath = import_node_path30.default.join(repoPath, file.relativePath);
12886
+ if (!import_node_fs29.default.existsSync(absolutePath)) {
12276
12887
  return {
12277
12888
  path: file.relativePath,
12278
12889
  action: "create",
@@ -12280,7 +12891,7 @@ function classifyFile(repoPath, file) {
12280
12891
  bytes: Buffer.byteLength(file.content)
12281
12892
  };
12282
12893
  }
12283
- const current = import_node_fs27.default.readFileSync(absolutePath, "utf-8");
12894
+ const current = import_node_fs29.default.readFileSync(absolutePath, "utf-8");
12284
12895
  if (!current.includes(MANAGED_MARKER2)) {
12285
12896
  return {
12286
12897
  path: file.relativePath,
@@ -12337,11 +12948,11 @@ function buildPlan(repoPath, dryRun, version) {
12337
12948
  };
12338
12949
  }
12339
12950
  function writeFile(repoPath, file) {
12340
- const absolutePath = import_node_path28.default.join(repoPath, file.relativePath);
12341
- import_node_fs27.default.mkdirSync(import_node_path28.default.dirname(absolutePath), { recursive: true });
12342
- import_node_fs27.default.writeFileSync(absolutePath, file.content, "utf-8");
12951
+ const absolutePath = import_node_path30.default.join(repoPath, file.relativePath);
12952
+ import_node_fs29.default.mkdirSync(import_node_path30.default.dirname(absolutePath), { recursive: true });
12953
+ import_node_fs29.default.writeFileSync(absolutePath, file.content, "utf-8");
12343
12954
  if (file.executable) {
12344
- import_node_fs27.default.chmodSync(absolutePath, 493);
12955
+ import_node_fs29.default.chmodSync(absolutePath, 493);
12345
12956
  }
12346
12957
  }
12347
12958
  function writeDevcontainer(options = {}) {
@@ -12379,12 +12990,12 @@ function writeDevcontainer(options = {}) {
12379
12990
  nextSteps: postWriteNextSteps(repoPath)
12380
12991
  };
12381
12992
  }
12382
- var import_node_fs27, import_node_path28, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
12993
+ var import_node_fs29, import_node_path30, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
12383
12994
  var init_devcontainer_write = __esm({
12384
12995
  "src/core/devcontainer-write.ts"() {
12385
12996
  "use strict";
12386
- import_node_fs27 = __toESM(require("fs"));
12387
- import_node_path28 = __toESM(require("path"));
12997
+ import_node_fs29 = __toESM(require("fs"));
12998
+ import_node_path30 = __toESM(require("path"));
12388
12999
  init_repo_config();
12389
13000
  init_repo_inspect();
12390
13001
  MANAGED_MARKER2 = "devrouter:managed devcontainer";
@@ -12493,6 +13104,119 @@ var init_repo_devcontainer = __esm({
12493
13104
  }
12494
13105
  });
12495
13106
 
13107
+ // src/core/profile-resolution.ts
13108
+ function sortedUnique2(values) {
13109
+ return Array.from(new Set(values)).sort();
13110
+ }
13111
+ function expandsAll(values) {
13112
+ return values?.length === 1 && values[0] === "*";
13113
+ }
13114
+ function selectedManagedResources(profile, selected, registry) {
13115
+ if (!profile || expandsAll(selected)) return sortedUnique2(registry);
13116
+ return sortedUnique2(selected ?? []);
13117
+ }
13118
+ function selectedReadiness(config, profileName, profile, routedApps) {
13119
+ const selectedNames = profileName.split(",");
13120
+ if (selectedNames.length === 1) {
13121
+ return sortedUnique2(
13122
+ profile?.readiness ?? routedApps.filter((app) => app.protocol === "http").map((app) => app.name)
13123
+ );
13124
+ }
13125
+ const readiness = /* @__PURE__ */ new Set();
13126
+ for (const selectedName of selectedNames) {
13127
+ const selected = resolveProfile(config, selectedName);
13128
+ const selectedApps = applyProfile(config, selected.profile).apps.filter(
13129
+ (app) => app.kind !== "dependency"
13130
+ );
13131
+ const selectedTargets = selected.profile?.readiness ?? selectedApps.filter((app) => app.protocol === "http").map((app) => app.name);
13132
+ for (const target of selectedTargets) readiness.add(target);
13133
+ }
13134
+ return sortedUnique2(readiness);
13135
+ }
13136
+ function buildProfileResolutionReport(config, repoPath, profileOverride) {
13137
+ const resolved = resolveProfile(config, profileOverride);
13138
+ const filtered = applyProfile(config, resolved.profile);
13139
+ const routedApps = filtered.apps.filter((app) => app.kind !== "dependency");
13140
+ const dependencies = filtered.apps.filter((app) => app.kind === "dependency");
13141
+ const readiness = selectedReadiness(config, resolved.name, resolved.profile, routedApps);
13142
+ const managedRuntime = config.managedRuntime;
13143
+ const baseServices = sortedUnique2(managedRuntime?.devcontainer.baseServices ?? []);
13144
+ const profileServices = managedRuntime ? selectedManagedResources(
13145
+ resolved.profile,
13146
+ resolved.profile?.devcontainerServices,
13147
+ managedRuntime.devcontainer.profileServices
13148
+ ) : [];
13149
+ const processes = managedRuntime ? selectedManagedResources(
13150
+ resolved.profile,
13151
+ resolved.profile?.processes,
13152
+ managedRuntime.processes
13153
+ ) : [];
13154
+ return {
13155
+ schemaVersion: 1,
13156
+ repoPath,
13157
+ profile: resolved.name,
13158
+ apps: sortedUnique2(routedApps.map((app) => app.name)),
13159
+ dependencies: sortedUnique2(dependencies.map((app) => app.name)),
13160
+ readiness: sortedUnique2(readiness),
13161
+ managedRuntime: {
13162
+ baseServices,
13163
+ profileServices,
13164
+ services: sortedUnique2([...baseServices, ...profileServices]),
13165
+ processes
13166
+ }
13167
+ };
13168
+ }
13169
+ function resolveProfileReport(options) {
13170
+ const repoPath = resolveRepoPath(options.repo);
13171
+ return buildProfileResolutionReport(loadRepoConfig(repoPath), repoPath, options.profile);
13172
+ }
13173
+ var init_profile_resolution = __esm({
13174
+ "src/core/profile-resolution.ts"() {
13175
+ "use strict";
13176
+ init_repo_config();
13177
+ }
13178
+ });
13179
+
13180
+ // src/commands/profile.ts
13181
+ var profile_exports = {};
13182
+ __export(profile_exports, {
13183
+ runProfileResolveCommand: () => runProfileResolveCommand
13184
+ });
13185
+ async function runProfileResolveCommand(options) {
13186
+ const report = resolveProfileReport(options);
13187
+ if (options.json) {
13188
+ printJSON(report);
13189
+ return;
13190
+ }
13191
+ printProfileResolutionSummary(report);
13192
+ }
13193
+ function renderValues(values) {
13194
+ return values.join(", ") || "-";
13195
+ }
13196
+ function printProfileResolutionSummary(report) {
13197
+ process.stdout.write(`Repo: ${report.repoPath}
13198
+ `);
13199
+ process.stdout.write(`Profile: ${report.profile}
13200
+ `);
13201
+ process.stdout.write(`Apps: ${renderValues(report.apps)}
13202
+ `);
13203
+ process.stdout.write(`Dependencies: ${renderValues(report.dependencies)}
13204
+ `);
13205
+ process.stdout.write(`Readiness: ${renderValues(report.readiness)}
13206
+ `);
13207
+ process.stdout.write(`Managed services: ${renderValues(report.managedRuntime.services)}
13208
+ `);
13209
+ process.stdout.write(`Managed processes: ${renderValues(report.managedRuntime.processes)}
13210
+ `);
13211
+ }
13212
+ var init_profile = __esm({
13213
+ "src/commands/profile.ts"() {
13214
+ "use strict";
13215
+ init_output();
13216
+ init_profile_resolution();
13217
+ }
13218
+ });
13219
+
12496
13220
  // src/commands/app-add.ts
12497
13221
  var app_add_exports = {};
12498
13222
  __export(app_add_exports, {
@@ -12775,7 +13499,7 @@ function sanitizeRouterId(value) {
12775
13499
  return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
12776
13500
  }
12777
13501
  function repoHash(repoPath) {
12778
- return (0, import_node_crypto9.createHash)("sha1").update(import_node_path29.default.resolve(repoPath)).digest("hex").slice(0, 12);
13502
+ return (0, import_node_crypto10.createHash)("sha1").update(import_node_path31.default.resolve(repoPath)).digest("hex").slice(0, 12);
12779
13503
  }
12780
13504
  function asDockerApp(app) {
12781
13505
  return app.runtime === "docker";
@@ -12854,11 +13578,11 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
12854
13578
  if (dockerApps.length === 0) {
12855
13579
  throw new Error("No docker apps selected to prepare compose overlay.");
12856
13580
  }
12857
- const cachePath = import_node_path29.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
12858
- import_node_fs28.default.mkdirSync(cachePath, { recursive: true });
12859
- const overlayPath = import_node_path29.default.join(cachePath, "compose.devrouter.yml");
13581
+ const cachePath = import_node_path31.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
13582
+ import_node_fs30.default.mkdirSync(cachePath, { recursive: true });
13583
+ const overlayPath = import_node_path31.default.join(cachePath, "compose.devrouter.yml");
12860
13584
  const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
12861
- import_node_fs28.default.writeFileSync(overlayPath, import_yaml7.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
13585
+ import_node_fs30.default.writeFileSync(overlayPath, import_yaml7.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
12862
13586
  return {
12863
13587
  overlayPath,
12864
13588
  composeFiles: ensureComposeFiles(dockerApps),
@@ -12872,7 +13596,7 @@ function runDockerComposeUp(repoPath, composeFiles2, overlayPath, services) {
12872
13596
  fileArgs.push("-f", resolved);
12873
13597
  }
12874
13598
  const args = ["compose", ...fileArgs, "-f", overlayPath, "up", "-d", "--wait", ...services];
12875
- const result = (0, import_node_child_process25.spawnSync)("docker", args, {
13599
+ const result = (0, import_node_child_process27.spawnSync)("docker", args, {
12876
13600
  encoding: "utf-8",
12877
13601
  cwd: repoPath
12878
13602
  });
@@ -12900,7 +13624,7 @@ function queryRunningComposeServices(repoPath, composeFiles2, overlayPath, servi
12900
13624
  "--services",
12901
13625
  ...services
12902
13626
  ];
12903
- const result = (0, import_node_child_process25.spawnSync)("docker", args, {
13627
+ const result = (0, import_node_child_process27.spawnSync)("docker", args, {
12904
13628
  encoding: "utf-8",
12905
13629
  cwd: repoPath
12906
13630
  });
@@ -12926,7 +13650,7 @@ function runDockerComposeStop(repoPath, composeFiles2, overlayPath, services) {
12926
13650
  fileArgs.push("-f", resolved);
12927
13651
  }
12928
13652
  const args = ["compose", ...fileArgs, "-f", overlayPath, "stop", ...services];
12929
- const result = (0, import_node_child_process25.spawnSync)("docker", args, {
13653
+ const result = (0, import_node_child_process27.spawnSync)("docker", args, {
12930
13654
  encoding: "utf-8",
12931
13655
  cwd: repoPath
12932
13656
  });
@@ -12952,7 +13676,7 @@ function runDockerComposeLogs(repoPath, composeFiles2, overlayPath, services, ta
12952
13676
  String(tail),
12953
13677
  ...services
12954
13678
  ];
12955
- (0, import_node_child_process25.spawnSync)("docker", args, {
13679
+ (0, import_node_child_process27.spawnSync)("docker", args, {
12956
13680
  stdio: "inherit",
12957
13681
  cwd: repoPath
12958
13682
  });
@@ -12964,7 +13688,7 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
12964
13688
  fileArgs.push("-f", resolved);
12965
13689
  }
12966
13690
  const args = ["compose", ...fileArgs, "-f", overlayPath, "port", service, String(internalPort)];
12967
- const result = (0, import_node_child_process25.spawnSync)("docker", args, {
13691
+ const result = (0, import_node_child_process27.spawnSync)("docker", args, {
12968
13692
  encoding: "utf-8",
12969
13693
  cwd: repoPath
12970
13694
  });
@@ -12978,14 +13702,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
12978
13702
  const port = Number(match[1]);
12979
13703
  return Number.isInteger(port) && port > 0 ? port : void 0;
12980
13704
  }
12981
- var import_node_child_process25, import_node_crypto9, import_node_fs28, import_node_path29, import_yaml7;
13705
+ var import_node_child_process27, import_node_crypto10, import_node_fs30, import_node_path31, import_yaml7;
12982
13706
  var init_docker_run = __esm({
12983
13707
  "src/core/docker-run.ts"() {
12984
13708
  "use strict";
12985
- import_node_child_process25 = require("child_process");
12986
- import_node_crypto9 = require("crypto");
12987
- import_node_fs28 = __toESM(require("fs"));
12988
- import_node_path29 = __toESM(require("path"));
13709
+ import_node_child_process27 = require("child_process");
13710
+ import_node_crypto10 = require("crypto");
13711
+ import_node_fs30 = __toESM(require("fs"));
13712
+ import_node_path31 = __toESM(require("path"));
12989
13713
  import_yaml7 = __toESM(require("yaml"));
12990
13714
  init_docker_error_guidance();
12991
13715
  init_paths();
@@ -13022,7 +13746,7 @@ function resolveSmCommand(command, defaultEnv, overrideEnv) {
13022
13746
  }
13023
13747
  return command.replace(/\{env\}/g, env);
13024
13748
  }
13025
- function sleep2(ms) {
13749
+ function sleep3(ms) {
13026
13750
  return new Promise((resolve) => {
13027
13751
  setTimeout(resolve, ms);
13028
13752
  });
@@ -13075,7 +13799,7 @@ function isProcessRunning(pid) {
13075
13799
  }
13076
13800
  }
13077
13801
  function readProcessTree(rootPid) {
13078
- const result = (0, import_node_child_process26.spawnSync)("ps", ["-ax", "-o", "pid=,ppid="], { encoding: "utf-8" });
13802
+ const result = (0, import_node_child_process28.spawnSync)("ps", ["-ax", "-o", "pid=,ppid="], { encoding: "utf-8" });
13079
13803
  if (result.status !== 0) {
13080
13804
  return [rootPid];
13081
13805
  }
@@ -13133,7 +13857,7 @@ async function terminateProcessTree(rootPid) {
13133
13857
  if (!isProcessRunning(rootPid)) {
13134
13858
  return;
13135
13859
  }
13136
- await sleep2(100);
13860
+ await sleep3(100);
13137
13861
  }
13138
13862
  if (isProcessRunning(rootPid)) {
13139
13863
  killProcessTree(rootPid, "SIGKILL");
@@ -13186,12 +13910,12 @@ function detectListeningPorts(pids) {
13186
13910
  if (pids.length === 0) {
13187
13911
  return [];
13188
13912
  }
13189
- const result = (0, import_node_child_process26.spawnSync)("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", pids.join(",")], {
13913
+ const result = (0, import_node_child_process28.spawnSync)("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", pids.join(",")], {
13190
13914
  encoding: "utf-8"
13191
13915
  });
13192
13916
  if (result.error && result.error.code === "ENOENT") {
13193
13917
  if (process.platform === "linux") {
13194
- const ssResult = (0, import_node_child_process26.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
13918
+ const ssResult = (0, import_node_child_process28.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
13195
13919
  if (ssResult.status === 0 && ssResult.stdout) {
13196
13920
  return parseSsListeningPorts(ssResult.stdout, new Set(pids));
13197
13921
  }
@@ -13250,7 +13974,7 @@ async function runHostApp(repoPath, app, extraEnv = {}, secretManager, env, work
13250
13974
  app.hostRun.command,
13251
13975
  true
13252
13976
  ) : app.hostRun.command;
13253
- const child = (0, import_node_child_process26.spawn)(spawnCommand, {
13977
+ const child = (0, import_node_child_process28.spawn)(spawnCommand, {
13254
13978
  cwd: commandCwd,
13255
13979
  stdio: "inherit",
13256
13980
  shell: true,
@@ -13320,7 +14044,7 @@ async function runHostApp(repoPath, app, extraEnv = {}, secretManager, env, work
13320
14044
  );
13321
14045
  }
13322
14046
  }
13323
- await sleep2(POLL_INTERVAL_MS2);
14047
+ await sleep3(POLL_INTERVAL_MS2);
13324
14048
  }
13325
14049
  } catch (error) {
13326
14050
  fatalError = toError(error);
@@ -13618,7 +14342,7 @@ async function execWithAppEnv(options) {
13618
14342
  options.command[0],
13619
14343
  true
13620
14344
  );
13621
- child = (0, import_node_child_process26.spawn)(wrapped, {
14345
+ child = (0, import_node_child_process28.spawn)(wrapped, {
13622
14346
  cwd: deps.repoPath,
13623
14347
  stdio: "inherit",
13624
14348
  shell: true,
@@ -13632,7 +14356,7 @@ async function execWithAppEnv(options) {
13632
14356
  false
13633
14357
  );
13634
14358
  const [cmd, ...wrappedArgs] = wrapped;
13635
- child = (0, import_node_child_process26.spawn)(cmd, wrappedArgs, {
14359
+ child = (0, import_node_child_process28.spawn)(cmd, wrappedArgs, {
13636
14360
  cwd: deps.repoPath,
13637
14361
  stdio: "inherit",
13638
14362
  shell: false,
@@ -13640,7 +14364,7 @@ async function execWithAppEnv(options) {
13640
14364
  });
13641
14365
  }
13642
14366
  } else if (options.shell) {
13643
- child = (0, import_node_child_process26.spawn)(options.command[0], {
14367
+ child = (0, import_node_child_process28.spawn)(options.command[0], {
13644
14368
  cwd: deps.repoPath,
13645
14369
  stdio: "inherit",
13646
14370
  shell: true,
@@ -13648,7 +14372,7 @@ async function execWithAppEnv(options) {
13648
14372
  });
13649
14373
  } else {
13650
14374
  const [command, ...args] = options.command;
13651
- child = (0, import_node_child_process26.spawn)(command, args, {
14375
+ child = (0, import_node_child_process28.spawn)(command, args, {
13652
14376
  cwd: deps.repoPath,
13653
14377
  stdio: "inherit",
13654
14378
  shell: false,
@@ -13669,11 +14393,11 @@ async function execWithAppEnv(options) {
13669
14393
  deps.stopDeps();
13670
14394
  }
13671
14395
  }
13672
- var import_node_child_process26, import_node_net, import_node_process, import_promises, POLL_INTERVAL_MS2, DEFAULT_PORT_TIMEOUT_MS, PROCESS_TERMINATION_GRACE_MS;
14396
+ var import_node_child_process28, import_node_net, import_node_process, import_promises, POLL_INTERVAL_MS2, DEFAULT_PORT_TIMEOUT_MS, PROCESS_TERMINATION_GRACE_MS;
13673
14397
  var init_app_run = __esm({
13674
14398
  "src/core/app-run.ts"() {
13675
14399
  "use strict";
13676
- import_node_child_process26 = require("child_process");
14400
+ import_node_child_process28 = require("child_process");
13677
14401
  import_node_net = __toESM(require("net"));
13678
14402
  import_node_process = require("process");
13679
14403
  import_promises = require("readline/promises");
@@ -13807,13 +14531,13 @@ function measureWorktreeConsumption(worktreePath, options) {
13807
14531
  const startedAt = Date.now();
13808
14532
  let rootStat;
13809
14533
  try {
13810
- rootStat = import_node_fs29.default.lstatSync(worktreePath);
14534
+ rootStat = import_node_fs31.default.lstatSync(worktreePath);
13811
14535
  } catch (error) {
13812
14536
  return { status: "unknown", reason: describeError(error, worktreePath) };
13813
14537
  }
13814
14538
  let rootEntries;
13815
14539
  try {
13816
- rootEntries = import_node_fs29.default.readdirSync(worktreePath, { withFileTypes: true });
14540
+ rootEntries = import_node_fs31.default.readdirSync(worktreePath, { withFileTypes: true });
13817
14541
  } catch (error) {
13818
14542
  return { status: "unknown", reason: describeError(error, worktreePath) };
13819
14543
  }
@@ -13839,10 +14563,10 @@ function measureWorktreeConsumption(worktreePath, options) {
13839
14563
  timedOut = true;
13840
14564
  break;
13841
14565
  }
13842
- const entryPath = import_node_path30.default.join(dirPath, entry.name);
14566
+ const entryPath = import_node_path32.default.join(dirPath, entry.name);
13843
14567
  let entryStat;
13844
14568
  try {
13845
- entryStat = import_node_fs29.default.lstatSync(entryPath);
14569
+ entryStat = import_node_fs31.default.lstatSync(entryPath);
13846
14570
  } catch (error) {
13847
14571
  if (error?.code === "ENOENT") continue;
13848
14572
  unreadableReason = describeIncompleteWalk(error);
@@ -13852,7 +14576,7 @@ function measureWorktreeConsumption(worktreePath, options) {
13852
14576
  if (!entryStat.isDirectory()) continue;
13853
14577
  let childEntries;
13854
14578
  try {
13855
- childEntries = import_node_fs29.default.readdirSync(entryPath, { withFileTypes: true });
14579
+ childEntries = import_node_fs31.default.readdirSync(entryPath, { withFileTypes: true });
13856
14580
  } catch (error) {
13857
14581
  unreadableReason = describeIncompleteWalk(error);
13858
14582
  break;
@@ -13916,12 +14640,12 @@ function describeError(error, worktreePath) {
13916
14640
  }
13917
14641
  return `could not stat worktree path '${worktreePath}': ${error?.message ?? String(error)}`;
13918
14642
  }
13919
- var import_node_fs29, import_node_path30, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
14643
+ var import_node_fs31, import_node_path32, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
13920
14644
  var init_workspace_consumption = __esm({
13921
14645
  "src/core/workspace-consumption.ts"() {
13922
14646
  "use strict";
13923
- import_node_fs29 = __toESM(require("fs"));
13924
- import_node_path30 = __toESM(require("path"));
14647
+ import_node_fs31 = __toESM(require("fs"));
14648
+ import_node_path32 = __toESM(require("path"));
13925
14649
  init_devpod_environment();
13926
14650
  DEFAULT_DEADLINE_MS = 1e4;
13927
14651
  BLOCK_SIZE_BYTES = 512;
@@ -14022,7 +14746,7 @@ function evaluateWorkspaceActivity(evidence, cutoff) {
14022
14746
  }
14023
14747
  function readGitSnapshot(worktree, commandRunner) {
14024
14748
  const comparablePath = comparableWorkspacePath(worktree.path);
14025
- if (worktree.prunable || !import_node_fs30.default.existsSync(comparablePath)) {
14749
+ if (worktree.prunable || !import_node_fs32.default.existsSync(comparablePath)) {
14026
14750
  return { worktree, checkout: "missing", head: null, committerDate: null };
14027
14751
  }
14028
14752
  const head = gitOutput(comparablePath, ["rev-parse", "--verify", "HEAD"], commandRunner);
@@ -14547,7 +15271,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
14547
15271
  containers = measureContainersFn(worktreePaths);
14548
15272
  } catch (error) {
14549
15273
  const reason = `container measurement failed: ${describeCause(error)}`;
14550
- containers = new Map(worktreePaths.map((path30) => [path30, unknownContainers(reason)]));
15274
+ containers = new Map(worktreePaths.map((path32) => [path32, unknownContainers(reason)]));
14551
15275
  }
14552
15276
  }
14553
15277
  const rows = records.map((record) => {
@@ -14596,12 +15320,12 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
14596
15320
  workspaces: rows
14597
15321
  };
14598
15322
  }
14599
- var import_node_child_process27, import_node_fs30, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
15323
+ var import_node_child_process29, import_node_fs32, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
14600
15324
  var init_workspace_cleanup = __esm({
14601
15325
  "src/core/workspace-cleanup.ts"() {
14602
15326
  "use strict";
14603
- import_node_child_process27 = require("child_process");
14604
- import_node_fs30 = __toESM(require("fs"));
15327
+ import_node_child_process29 = require("child_process");
15328
+ import_node_fs32 = __toESM(require("fs"));
14605
15329
  init_devpod_workspaces();
14606
15330
  init_host_routes();
14607
15331
  init_repo_config();
@@ -14618,7 +15342,7 @@ var init_workspace_cleanup = __esm({
14618
15342
  ];
14619
15343
  defaultCommandRunner = (command, args, input2) => {
14620
15344
  const environment = command === "git" ? READ_ONLY_GIT_ENV2 : { ...process.env, LC_ALL: "C" };
14621
- const result = (0, import_node_child_process27.spawnSync)(command, args, {
15345
+ const result = (0, import_node_child_process29.spawnSync)(command, args, {
14622
15346
  encoding: "utf-8",
14623
15347
  env: environment,
14624
15348
  ...input2 === void 0 ? {} : { input: input2 }
@@ -14778,7 +15502,7 @@ var init_version = __esm({
14778
15502
 
14779
15503
  // src/cli.ts
14780
15504
  var import_commander = require("commander");
14781
- var CLI_VERSION = true ? "0.0.46" : "0.0.0-dev";
15505
+ var CLI_VERSION = true ? "0.0.48" : "0.0.0-dev";
14782
15506
  var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
14783
15507
  function withErrorHandling(action2) {
14784
15508
  return async (...args) => {
@@ -14936,6 +15660,16 @@ repoDevcontainerCommand.command("verify").description("Verify devcontainer onboa
14936
15660
  }
14937
15661
  )
14938
15662
  );
15663
+ var profileCommand = program.command("profile").description("Resolve profiles from `.devrouter.yml` without starting a runtime");
15664
+ profileCommand.command("resolve").description("Print the exact apps, dependencies, services, and processes for a profile").option("--repo <path>", "Repository path (defaults to current directory)").option(
15665
+ "--profile <name>",
15666
+ "Profile name or comma-separated selection (defaults to configured profile)"
15667
+ ).option("--json", "Output stable JSON").action(
15668
+ withErrorHandling(async (options) => {
15669
+ const { runProfileResolveCommand: runProfileResolveCommand2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
15670
+ await runProfileResolveCommand2(options);
15671
+ })
15672
+ );
14939
15673
  var appCommand = program.command("app").description("Manage app entries and runtime actions from `.devrouter.yml`");
14940
15674
  appCommand.command("add").description("Add or update one app definition in `.devrouter.yml`").requiredOption("--name <name>", "App name").option("--kind <kind>", "app or dependency", "app").option("--host <host>", "Hostname ending with .localhost (required for --kind app)").option("--protocol <protocol>", "http or tcp (required for --kind app)").option(
14941
15675
  "--runtime <runtime>",