@devrouter/cli 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/devrouter.js CHANGED
@@ -4764,7 +4764,7 @@ function loadRepoConfig(repoPath, read2 = (file) => import_node_fs8.default.read
4764
4764
  const config = parseConfig(parsed ?? {}, configPath);
4765
4765
  const requiredVersion = config.devrouter?.version;
4766
4766
  if (requiredVersion && !hasWarnedVersionMismatch) {
4767
- const cliVersion = true ? "0.3.0" : "0.0.0-dev";
4767
+ const cliVersion = true ? "0.4.0" : "0.0.0-dev";
4768
4768
  if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
4769
4769
  hasWarnedVersionMismatch = true;
4770
4770
  process.stderr.write(
@@ -6763,7 +6763,7 @@ var init_reliability_contract = __esm({
6763
6763
 
6764
6764
  // src/core/reliability-operation-store.ts
6765
6765
  function installedCliVersion() {
6766
- return true ? "0.3.0" : "0.0.0-dev";
6766
+ return true ? "0.4.0" : "0.0.0-dev";
6767
6767
  }
6768
6768
  function compareCliVersions(left, right) {
6769
6769
  const parts = (value) => value.split(".").map((part) => Number.parseInt(part, 10) || 0);
@@ -22474,6 +22474,10 @@ var init_ai_prompt = __esm({
22474
22474
  command: "devrouter exec [path] -- <command...>",
22475
22475
  purpose: "Run literal argv inside the exact already-running workspace runtime and proven checkout mount."
22476
22476
  },
22477
+ {
22478
+ command: "devrouter env [path] [--json] | devrouter env [path] -- <command...>",
22479
+ purpose: "Report the checkout's host-reachable URLs, routed and loopback TCP endpoints and CA path without touching the runtime, or run a host command with them exported as DEVROUTER_* variables."
22480
+ },
22477
22481
  {
22478
22482
  command: "devrouter workspace up <branch> [--path <dir>] [--no-devpod] [--open]",
22479
22483
  purpose: "Create a git worktree for <branch>, then run the proven workspace startup unless --no-devpod is set."
@@ -24206,7 +24210,7 @@ function probeInstalledCliVersion(executable) {
24206
24210
  }
24207
24211
  function buildCliPathCheck(options = {}) {
24208
24212
  const id5 = "global.cli-path";
24209
- const runningVersion = options.runningVersion ?? (true ? "0.3.0" : "0.0.0-dev");
24213
+ const runningVersion = options.runningVersion ?? (true ? "0.4.0" : "0.0.0-dev");
24210
24214
  if (runningVersion === "0.0.0-dev") {
24211
24215
  return {
24212
24216
  id: id5,
@@ -25102,7 +25106,7 @@ async function buildDoctorReport(options = {}) {
25102
25106
  suggestion: `Remedy ${expansionNotice.remedy.code}: set a named default profile other than '${expansionNotice.profile}'${remedyCandidates}.`
25103
25107
  });
25104
25108
  }
25105
- const cliVersion = true ? "0.3.0" : "0.0.0-dev";
25109
+ const cliVersion = true ? "0.4.0" : "0.0.0-dev";
25106
25110
  const configVersion = config.devrouter?.version;
25107
25111
  const installedKnown = cliVersion !== "0.0.0-dev";
25108
25112
  const versionComparison = installedKnown && configVersion ? compareSemver(configVersion, cliVersion) : void 0;
@@ -25933,6 +25937,302 @@ var init_exec = __esm({
25933
25937
  }
25934
25938
  });
25935
25939
 
25940
+ // src/core/host-env.ts
25941
+ function parseLoopbackBinding(output2) {
25942
+ for (const line of output2.split(/\r?\n/)) {
25943
+ const match = line.trim().match(/^(127\.0\.0\.1|\[::1\]):(\d+)$/);
25944
+ if (match) {
25945
+ return { host: match[1] === "[::1]" ? "::1" : "127.0.0.1", port: Number(match[2]) };
25946
+ }
25947
+ }
25948
+ return void 0;
25949
+ }
25950
+ function resolveLoopback(app, repoPath, evidence) {
25951
+ if (app.runtime !== "proxy") {
25952
+ return { loopback: null, loopbackReason: "unsupported-runtime" };
25953
+ }
25954
+ const upstream = parseUpstream(app.upstream);
25955
+ if (LOOPBACK_UPSTREAM_HOSTS.has(upstream.host)) {
25956
+ return { loopback: { host: "127.0.0.1", port: upstream.port } };
25957
+ }
25958
+ if (!evidence.containers) {
25959
+ return { loopback: null, loopbackReason: "unavailable" };
25960
+ }
25961
+ const devcontainerDir = import_node_path58.default.join(repoPath, ".devcontainer");
25962
+ const owners = evidence.containers.filter(
25963
+ (container) => sameWorkspacePath(
25964
+ container.labels["com.docker.compose.project.working_dir"] ?? "",
25965
+ devcontainerDir
25966
+ ) && (container.networks[DEVNET_NAME]?.Aliases ?? []).includes(upstream.host)
25967
+ );
25968
+ if (owners.length === 0) return { loopback: null, loopbackReason: "not-found" };
25969
+ if (owners.length > 1) return { loopback: null, loopbackReason: "ambiguous" };
25970
+ const [owner] = owners;
25971
+ if (!owner.state.Running) return { loopback: null, loopbackReason: "not-running" };
25972
+ const bindings = evidence.publishedBindings(owner.id, upstream.port);
25973
+ if (bindings === void 0) return { loopback: null, loopbackReason: "unavailable" };
25974
+ const endpoint = parseLoopbackBinding(bindings);
25975
+ return endpoint ? { loopback: endpoint } : { loopback: null, loopbackReason: "not-published" };
25976
+ }
25977
+ function buildHostEnvReport(input2) {
25978
+ const { repoPath, config, workspace, evidence } = input2;
25979
+ const apps = [];
25980
+ for (const app of config.apps) {
25981
+ if (!("host" in app)) continue;
25982
+ const active = evidence.activeHosts.has(app.host);
25983
+ if (app.protocol === "http") {
25984
+ const scheme = evidence.tlsEnabled ? "https" : "http";
25985
+ apps.push({
25986
+ name: app.name,
25987
+ protocol: "http",
25988
+ host: app.host,
25989
+ active,
25990
+ url: `${scheme}://${app.host}`
25991
+ });
25992
+ continue;
25993
+ }
25994
+ apps.push({
25995
+ name: app.name,
25996
+ protocol: "tcp",
25997
+ tcpProtocol: app.tcpProtocol,
25998
+ host: app.host,
25999
+ active,
26000
+ routed: {
26001
+ host: app.host,
26002
+ port: TCP_PROTOCOL_REGISTRY[app.tcpProtocol]?.port ?? 0,
26003
+ params: { ...ROUTED_TCP_PARAMS[app.tcpProtocol] ?? {} }
26004
+ },
26005
+ ...resolveLoopback(app, repoPath, evidence)
26006
+ });
26007
+ }
26008
+ const ca = evidence.caPath;
26009
+ return {
26010
+ version: 1,
26011
+ repoPath,
26012
+ workspace: workspace ?? null,
26013
+ tls: evidence.tlsEnabled,
26014
+ ...typeof ca === "string" ? { caPath: ca } : { caPath: null, caReason: ca.error },
26015
+ apps
26016
+ };
26017
+ }
26018
+ function listDevcontainerContainers(repoPath) {
26019
+ const listed = (0, import_node_child_process34.spawnSync)(
26020
+ "docker",
26021
+ [
26022
+ "ps",
26023
+ "-a",
26024
+ "--filter",
26025
+ `label=com.docker.compose.project.working_dir=${import_node_path58.default.join(repoPath, ".devcontainer")}`,
26026
+ "--format",
26027
+ "{{.ID}}"
26028
+ ],
26029
+ { encoding: "utf-8", ...networkDockerOptions() }
26030
+ );
26031
+ if (listed.status !== 0) return void 0;
26032
+ const ids2 = listed.stdout.split(/\r?\n/).map((id5) => id5.trim()).filter(Boolean);
26033
+ try {
26034
+ return inspectWorkspaceContainers({ ids: ids2 });
26035
+ } catch {
26036
+ return void 0;
26037
+ }
26038
+ }
26039
+ function dockerPortBindings(containerId, port) {
26040
+ const result = (0, import_node_child_process34.spawnSync)("docker", ["port", containerId, `${port}/tcp`], {
26041
+ encoding: "utf-8",
26042
+ ...networkDockerOptions()
26043
+ });
26044
+ if (result.status !== 0) return result.error ? void 0 : "";
26045
+ return result.stdout;
26046
+ }
26047
+ async function collectActiveHosts(tlsEnabled) {
26048
+ const hosts = /* @__PURE__ */ new Set();
26049
+ for (const route of listHostRoutes(tlsEnabled)) {
26050
+ for (const host of route.hosts) hosts.add(host);
26051
+ }
26052
+ try {
26053
+ const { routes } = discoverRoutes(await listContainers(true), tlsEnabled, DEVNET_NAME);
26054
+ for (const route of routes) {
26055
+ for (const host of route.hosts) hosts.add(host);
26056
+ }
26057
+ } catch {
26058
+ }
26059
+ return hosts;
26060
+ }
26061
+ async function collectHostEnvReport(repoPath) {
26062
+ const workspace = resolveWorkspace(repoPath);
26063
+ const config = applyWorkspace(loadRepoConfig(repoPath), workspace, repoPath);
26064
+ const tlsEnabled = isTLSEnabled();
26065
+ let caPath;
26066
+ try {
26067
+ caPath = getMkcertRootCAPath({ repoPath });
26068
+ } catch (error) {
26069
+ caPath = { error: error instanceof Error ? error.message : String(error) };
26070
+ }
26071
+ const needsContainers = config.apps.some(
26072
+ (app) => "host" in app && app.protocol === "tcp" && app.runtime === "proxy"
26073
+ );
26074
+ return buildHostEnvReport({
26075
+ repoPath,
26076
+ config,
26077
+ workspace,
26078
+ evidence: {
26079
+ tlsEnabled,
26080
+ activeHosts: await collectActiveHosts(tlsEnabled),
26081
+ caPath,
26082
+ containers: needsContainers ? listDevcontainerContainers(repoPath) : [],
26083
+ publishedBindings: dockerPortBindings
26084
+ }
26085
+ });
26086
+ }
26087
+ function variablePrefix(appName) {
26088
+ return `DEVROUTER_${appName.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
26089
+ }
26090
+ function hostEnvVariables(report, processEnv) {
26091
+ const variables = {};
26092
+ if (report.workspace) variables.DEVROUTER_WORKSPACE = report.workspace;
26093
+ if (report.caPath && processEnv.NODE_EXTRA_CA_CERTS === void 0) {
26094
+ variables.NODE_EXTRA_CA_CERTS = report.caPath;
26095
+ }
26096
+ for (const app of report.apps) {
26097
+ const prefix = variablePrefix(app.name);
26098
+ if (app.protocol === "http") {
26099
+ variables[`${prefix}_URL`] = app.url;
26100
+ continue;
26101
+ }
26102
+ variables[`${prefix}_ROUTED_HOST`] = app.routed.host;
26103
+ variables[`${prefix}_ROUTED_PORT`] = String(app.routed.port);
26104
+ if (app.loopback) {
26105
+ variables[`${prefix}_HOST`] = app.loopback.host;
26106
+ variables[`${prefix}_PORT`] = String(app.loopback.port);
26107
+ }
26108
+ }
26109
+ return variables;
26110
+ }
26111
+ var import_node_child_process34, import_node_path58, LOOPBACK_UPSTREAM_HOSTS, ROUTED_TCP_PARAMS;
26112
+ var init_host_env = __esm({
26113
+ "src/core/host-env.ts"() {
26114
+ "use strict";
26115
+ import_node_child_process34 = require("child_process");
26116
+ import_node_path58 = __toESM(require("path"));
26117
+ init_devpod_environment();
26118
+ init_docker();
26119
+ init_host_routes();
26120
+ init_network_effect_scope();
26121
+ init_repo_config();
26122
+ init_router();
26123
+ init_routes();
26124
+ init_tls();
26125
+ init_workspace();
26126
+ LOOPBACK_UPSTREAM_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "0.0.0.0", "::1"]);
26127
+ ROUTED_TCP_PARAMS = {
26128
+ postgres: { sslmode: "require", sslnegotiation: "direct" }
26129
+ };
26130
+ }
26131
+ });
26132
+
26133
+ // src/commands/env.ts
26134
+ var env_exports = {};
26135
+ __export(env_exports, {
26136
+ parseEnvInvocation: () => parseEnvInvocation,
26137
+ runEnvCommand: () => runEnvCommand,
26138
+ runHostCommand: () => runHostCommand
26139
+ });
26140
+ function parseEnvInvocation(args) {
26141
+ const separator = args.indexOf("--");
26142
+ const before = separator < 0 ? args : args.slice(0, separator);
26143
+ const command = separator < 0 ? void 0 : args.slice(separator + 1);
26144
+ const json = before.includes("--json");
26145
+ const positional = before.filter((arg) => arg !== "--json");
26146
+ const unknown = positional.find((arg) => arg.startsWith("-"));
26147
+ if (unknown) throw new Error(`Unknown option '${unknown}' for devrouter env.`);
26148
+ if (positional.length > 1) {
26149
+ throw new Error("devrouter env accepts at most one checkout path.");
26150
+ }
26151
+ if (command !== void 0 && command.length === 0) {
26152
+ throw new Error("No command provided after `--`.");
26153
+ }
26154
+ if (command && json) {
26155
+ throw new Error("--json reports the environment; it cannot be combined with a command.");
26156
+ }
26157
+ return {
26158
+ ...positional[0] ? { path: positional[0] } : {},
26159
+ json,
26160
+ ...command ? { command } : {}
26161
+ };
26162
+ }
26163
+ function formatEnvReport(report) {
26164
+ const lines = [
26165
+ `Checkout: ${report.repoPath}`,
26166
+ `Workspace: ${report.workspace ?? "(primary checkout)"}`,
26167
+ `CA: ${report.caPath ?? `unavailable (${report.caReason})`}`,
26168
+ ""
26169
+ ];
26170
+ for (const app of report.apps) {
26171
+ const state = app.active ? "active" : "inactive";
26172
+ if (app.protocol === "http") {
26173
+ lines.push(`${app.name} ${app.url} (${state})`);
26174
+ continue;
26175
+ }
26176
+ const params = new URLSearchParams(app.routed.params).toString();
26177
+ const loopback = app.loopback ? `loopback ${app.loopback.host}:${app.loopback.port}` : `loopback unavailable (${app.loopbackReason})`;
26178
+ lines.push(
26179
+ `${app.name} ${app.tcpProtocol} routed ${app.routed.host}:${app.routed.port}${params ? ` ?${params}` : ""}; ${loopback} (${state})`
26180
+ );
26181
+ }
26182
+ return `${lines.join("\n")}
26183
+ `;
26184
+ }
26185
+ function runHostCommand(command, env) {
26186
+ return new Promise((resolve) => {
26187
+ const child = (0, import_node_child_process35.spawn)(command[0], command.slice(1), { env, stdio: "inherit" });
26188
+ const forward = (signal) => () => child.kill(signal);
26189
+ const handlers = [
26190
+ ["SIGINT", forward("SIGINT")],
26191
+ ["SIGTERM", forward("SIGTERM")]
26192
+ ];
26193
+ for (const [signal, handler] of handlers) process.on(signal, handler);
26194
+ let settled = false;
26195
+ const settle = (code) => {
26196
+ if (settled) return;
26197
+ settled = true;
26198
+ for (const [signal, handler] of handlers) process.off(signal, handler);
26199
+ resolve(code);
26200
+ };
26201
+ child.on("error", (error) => {
26202
+ process.stderr.write(`devrouter env: cannot run '${command[0]}': ${error.message}
26203
+ `);
26204
+ settle(error.code === "ENOENT" ? 127 : 126);
26205
+ });
26206
+ child.on("exit", (code, signal) => {
26207
+ settle(code ?? 128 + (signal ? import_node_os6.constants.signals[signal] ?? 0 : 0));
26208
+ });
26209
+ });
26210
+ }
26211
+ async function runEnvCommand(invocation) {
26212
+ const report = await collectHostEnvReport(resolveGitCheckoutPath(invocation.path));
26213
+ if (invocation.command) {
26214
+ const env = { ...process.env, ...hostEnvVariables(report, process.env) };
26215
+ process.exitCode = await runHostCommand(invocation.command, env);
26216
+ return;
26217
+ }
26218
+ if (invocation.json) {
26219
+ printJSON(report);
26220
+ return;
26221
+ }
26222
+ process.stdout.write(formatEnvReport(report));
26223
+ }
26224
+ var import_node_child_process35, import_node_os6;
26225
+ var init_env = __esm({
26226
+ "src/commands/env.ts"() {
26227
+ "use strict";
26228
+ import_node_child_process35 = require("child_process");
26229
+ import_node_os6 = require("os");
26230
+ init_host_env();
26231
+ init_output();
26232
+ init_environment_path();
26233
+ }
26234
+ });
26235
+
25936
26236
  // src/commands/down.ts
25937
26237
  var down_exports = {};
25938
26238
  __export(down_exports, {
@@ -25974,7 +26274,7 @@ var init_status2 = __esm({
25974
26274
  // src/core/harness-continuation.ts
25975
26275
  function harnessContinuationPath(repoRoot) {
25976
26276
  const key = (0, import_node_crypto27.createHash)("sha256").update(comparableWorkspacePath(repoRoot)).digest("hex");
25977
- return import_node_path58.default.join(DEVROUTER_HOME, "harness", `${key}.json`);
26277
+ return import_node_path59.default.join(DEVROUTER_HOME, "harness", `${key}.json`);
25978
26278
  }
25979
26279
  function counter4(value) {
25980
26280
  return Number.isSafeInteger(value) && value >= 0;
@@ -26015,7 +26315,7 @@ function claimHarnessContinuation(repoRoot, claim2) {
26015
26315
  const nowMs = claim2.nowMs ?? Date.now();
26016
26316
  try {
26017
26317
  const file = harnessContinuationPath(repoRoot);
26018
- import_node_fs46.default.mkdirSync(import_node_path58.default.dirname(file), { recursive: true, mode: 448 });
26318
+ import_node_fs46.default.mkdirSync(import_node_path59.default.dirname(file), { recursive: true, mode: 448 });
26019
26319
  return withFileLockSync(
26020
26320
  `${file}.lock`,
26021
26321
  { activity: "harness continuation claim", waitMs: HARNESS_CONTINUATION_LOCK_WAIT_MS },
@@ -26047,7 +26347,7 @@ function settleHarnessContinuation(repoRoot, toolUseId, settlement) {
26047
26347
  const nowMs = settlement.nowMs ?? Date.now();
26048
26348
  try {
26049
26349
  const file = harnessContinuationPath(repoRoot);
26050
- import_node_fs46.default.mkdirSync(import_node_path58.default.dirname(file), { recursive: true, mode: 448 });
26350
+ import_node_fs46.default.mkdirSync(import_node_path59.default.dirname(file), { recursive: true, mode: 448 });
26051
26351
  withFileLockSync(
26052
26352
  `${file}.lock`,
26053
26353
  { activity: "harness continuation settlement", waitMs: HARNESS_CONTINUATION_LOCK_WAIT_MS },
@@ -26071,13 +26371,13 @@ function settleHarnessContinuation(repoRoot, toolUseId, settlement) {
26071
26371
  } catch {
26072
26372
  }
26073
26373
  }
26074
- var import_node_crypto27, import_node_fs46, import_node_path58, HARNESS_CONTINUATION_MAX_ENTRIES, HARNESS_CONTINUATION_TTL_MS, HARNESS_CONTINUATION_LOCK_WAIT_MS, MAX_LEDGER_BYTES, MAX_TOOL_USE_ID_LENGTH, MAX_PHASE_LENGTH;
26374
+ var import_node_crypto27, import_node_fs46, import_node_path59, HARNESS_CONTINUATION_MAX_ENTRIES, HARNESS_CONTINUATION_TTL_MS, HARNESS_CONTINUATION_LOCK_WAIT_MS, MAX_LEDGER_BYTES, MAX_TOOL_USE_ID_LENGTH, MAX_PHASE_LENGTH;
26075
26375
  var init_harness_continuation = __esm({
26076
26376
  "src/core/harness-continuation.ts"() {
26077
26377
  "use strict";
26078
26378
  import_node_crypto27 = require("crypto");
26079
26379
  import_node_fs46 = __toESM(require("fs"));
26080
- import_node_path58 = __toESM(require("path"));
26380
+ import_node_path59 = __toESM(require("path"));
26081
26381
  init_atomic_file();
26082
26382
  init_file_lock();
26083
26383
  init_router();
@@ -26096,12 +26396,12 @@ function isTransitionalHarnessPhase(phase) {
26096
26396
  return TRANSITIONAL_PHASES.includes(phase);
26097
26397
  }
26098
26398
  function findHarnessGateRepoRoot(cwd) {
26099
- let current = import_node_path59.default.resolve(cwd);
26399
+ let current = import_node_path60.default.resolve(cwd);
26100
26400
  for (let depth = 0; depth < REPO_ROOT_MAX_DEPTH; depth += 1) {
26101
- if (import_node_fs47.default.existsSync(import_node_path59.default.join(current, ".devrouter.yml"))) {
26401
+ if (import_node_fs47.default.existsSync(import_node_path60.default.join(current, ".devrouter.yml"))) {
26102
26402
  return current;
26103
26403
  }
26104
- const parent = import_node_path59.default.dirname(current);
26404
+ const parent = import_node_path60.default.dirname(current);
26105
26405
  if (parent === current) {
26106
26406
  return void 0;
26107
26407
  }
@@ -26193,12 +26493,12 @@ function parseHarnessGateBudget(value) {
26193
26493
  }
26194
26494
  return Math.min(Math.trunc(parsed), HARNESS_GATE_MAX_BUDGET_MS);
26195
26495
  }
26196
- var import_node_fs47, import_node_path59, HARNESS_GATE_DEFAULT_BUDGET_MS, HARNESS_GATE_MAX_BUDGET_MS, HARNESS_GATE_POLL_INTERVAL_MS, TRANSITIONAL_PHASES, REPO_ROOT_MAX_DEPTH;
26496
+ var import_node_fs47, import_node_path60, HARNESS_GATE_DEFAULT_BUDGET_MS, HARNESS_GATE_MAX_BUDGET_MS, HARNESS_GATE_POLL_INTERVAL_MS, TRANSITIONAL_PHASES, REPO_ROOT_MAX_DEPTH;
26197
26497
  var init_harness_gate = __esm({
26198
26498
  "src/core/harness-gate.ts"() {
26199
26499
  "use strict";
26200
26500
  import_node_fs47 = __toESM(require("fs"));
26201
- import_node_path59 = __toESM(require("path"));
26501
+ import_node_path60 = __toESM(require("path"));
26202
26502
  init_reliability_operation_store();
26203
26503
  init_workspace();
26204
26504
  init_workspace_runtime();
@@ -26685,7 +26985,7 @@ async function runOpenCommand(name) {
26685
26985
  );
26686
26986
  return;
26687
26987
  }
26688
- const result = (0, import_node_child_process34.spawnSync)("open", [url], { encoding: "utf-8" });
26988
+ const result = (0, import_node_child_process36.spawnSync)("open", [url], { encoding: "utf-8" });
26689
26989
  if (result.status !== 0) {
26690
26990
  const details = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
26691
26991
  throw new Error(`Unable to open '${url}': ${details || "unknown error"}`);
@@ -26693,11 +26993,11 @@ async function runOpenCommand(name) {
26693
26993
  process.stdout.write(`Opened ${url}
26694
26994
  `);
26695
26995
  }
26696
- var import_node_child_process34;
26996
+ var import_node_child_process36;
26697
26997
  var init_open = __esm({
26698
26998
  "src/commands/open.ts"() {
26699
26999
  "use strict";
26700
- import_node_child_process34 = require("child_process");
27000
+ import_node_child_process36 = require("child_process");
26701
27001
  init_docker();
26702
27002
  init_host_routes();
26703
27003
  init_repo_config();
@@ -26720,7 +27020,7 @@ async function runLogsCommand(options) {
26720
27020
  const args = ["logs", "--tail", tail, ROUTER_CONTAINER_NAME];
26721
27021
  if (options.follow) {
26722
27022
  args.splice(1, 0, "-f");
26723
- const child = (0, import_node_child_process35.spawn)("docker", args, { stdio: "inherit" });
27023
+ const child = (0, import_node_child_process37.spawn)("docker", args, { stdio: "inherit" });
26724
27024
  const onSignal = () => {
26725
27025
  child.kill("SIGTERM");
26726
27026
  };
@@ -26734,17 +27034,17 @@ async function runLogsCommand(options) {
26734
27034
  });
26735
27035
  });
26736
27036
  } else {
26737
- const result = (0, import_node_child_process35.spawnSync)("docker", args, { stdio: "inherit" });
27037
+ const result = (0, import_node_child_process37.spawnSync)("docker", args, { stdio: "inherit" });
26738
27038
  if (result.status !== 0) {
26739
27039
  throw new Error("Failed to retrieve router logs.");
26740
27040
  }
26741
27041
  }
26742
27042
  }
26743
- var import_node_child_process35;
27043
+ var import_node_child_process37;
26744
27044
  var init_logs = __esm({
26745
27045
  "src/commands/logs.ts"() {
26746
27046
  "use strict";
26747
- import_node_child_process35 = require("child_process");
27047
+ import_node_child_process37 = require("child_process");
26748
27048
  init_docker();
26749
27049
  init_router();
26750
27050
  }
@@ -26794,7 +27094,7 @@ function readJson(filePath) {
26794
27094
  }
26795
27095
  }
26796
27096
  function relative(repoPath, filePath) {
26797
- return import_node_path60.default.relative(repoPath, filePath) || ".";
27097
+ return import_node_path61.default.relative(repoPath, filePath) || ".";
26798
27098
  }
26799
27099
  function redactEnvAssignments(value) {
26800
27100
  return value.replace(
@@ -26835,7 +27135,7 @@ function inspectPackageManager(repoPath, pkg) {
26835
27135
  ["bun.lock", "bun"]
26836
27136
  ];
26837
27137
  for (const [fileName, name] of lockfiles) {
26838
- if (import_node_fs48.default.existsSync(import_node_path60.default.join(repoPath, fileName))) {
27138
+ if (import_node_fs48.default.existsSync(import_node_path61.default.join(repoPath, fileName))) {
26839
27139
  return { name, source: fileName };
26840
27140
  }
26841
27141
  }
@@ -26850,7 +27150,7 @@ function inspectNode(repoPath, pkg) {
26850
27150
  if (typeof engines?.node === "string") {
26851
27151
  return { version: engines.node, source: "package.json:engines.node" };
26852
27152
  }
26853
- const nvmrc = import_node_path60.default.join(repoPath, ".nvmrc");
27153
+ const nvmrc = import_node_path61.default.join(repoPath, ".nvmrc");
26854
27154
  if (import_node_fs48.default.existsSync(nvmrc)) {
26855
27155
  const version = import_node_fs48.default.readFileSync(nvmrc, "utf-8").trim();
26856
27156
  return { version, source: ".nvmrc" };
@@ -26913,7 +27213,7 @@ function configuredComposeFiles(repoPath) {
26913
27213
  const files = config.apps.filter(
26914
27214
  (app) => app.runtime === "docker"
26915
27215
  ).flatMap((app) => app.docker.composeFiles).filter(
26916
- (fileName) => !import_node_path60.default.isAbsolute(fileName) && !import_node_path60.default.normalize(fileName).startsWith("..")
27216
+ (fileName) => !import_node_path61.default.isAbsolute(fileName) && !import_node_path61.default.normalize(fileName).startsWith("..")
26917
27217
  );
26918
27218
  return Array.from(new Set(files));
26919
27219
  } catch {
@@ -26931,7 +27231,7 @@ function composeFiles(repoPath) {
26931
27231
  ...configuredComposeFiles(repoPath)
26932
27232
  ];
26933
27233
  return Array.from(new Set(candidates)).filter(
26934
- (fileName) => import_node_fs48.default.existsSync(import_node_path60.default.join(repoPath, fileName))
27234
+ (fileName) => import_node_fs48.default.existsSync(import_node_path61.default.join(repoPath, fileName))
26935
27235
  );
26936
27236
  }
26937
27237
  function stringArray2(value) {
@@ -26969,7 +27269,7 @@ function inspectServices(repoPath) {
26969
27269
  const services = [];
26970
27270
  for (const fileName of composeFiles(repoPath)) {
26971
27271
  try {
26972
- const parsed = import_yaml7.default.parse(import_node_fs48.default.readFileSync(import_node_path60.default.join(repoPath, fileName), "utf-8"));
27272
+ const parsed = import_yaml7.default.parse(import_node_fs48.default.readFileSync(import_node_path61.default.join(repoPath, fileName), "utf-8"));
26973
27273
  const serviceMap = asRecord3(asRecord3(parsed)?.services);
26974
27274
  for (const [name, value] of Object.entries(serviceMap ?? {})) {
26975
27275
  const service = asRecord3(value);
@@ -27005,7 +27305,7 @@ function inspectServices(repoPath) {
27005
27305
  }
27006
27306
  function inspectEnvFiles(repoPath) {
27007
27307
  const files = import_node_fs48.default.readdirSync(repoPath).filter((fileName) => /^\.env(\.|$)/.test(fileName)).sort().map((fileName) => {
27008
- const content = import_node_fs48.default.readFileSync(import_node_path60.default.join(repoPath, fileName), "utf-8");
27308
+ const content = import_node_fs48.default.readFileSync(import_node_path61.default.join(repoPath, fileName), "utf-8");
27009
27309
  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();
27010
27310
  return { path: fileName, names };
27011
27311
  });
@@ -27019,13 +27319,13 @@ function inspectEnvFiles(repoPath) {
27019
27319
  };
27020
27320
  }
27021
27321
  function inspectDevcontainer(repoPath) {
27022
- const dir = import_node_path60.default.join(repoPath, ".devcontainer");
27322
+ const dir = import_node_path61.default.join(repoPath, ".devcontainer");
27023
27323
  if (!import_node_fs48.default.existsSync(dir)) {
27024
27324
  return { exists: false, files: [] };
27025
27325
  }
27026
27326
  return {
27027
27327
  exists: true,
27028
- files: import_node_fs48.default.readdirSync(dir).filter((fileName) => import_node_fs48.default.statSync(import_node_path60.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
27328
+ files: import_node_fs48.default.readdirSync(dir).filter((fileName) => import_node_fs48.default.statSync(import_node_path61.default.join(dir, fileName)).isFile()).sort().map((fileName) => `.devcontainer/${fileName}`)
27029
27329
  };
27030
27330
  }
27031
27331
  function inspectDevrouter(repoPath) {
@@ -27074,14 +27374,14 @@ function inspectAgentGuidance(repoPath) {
27074
27374
  ["AGENTS.md", "agents"],
27075
27375
  ["CLAUDE.md", "claude"]
27076
27376
  ]) {
27077
- if (import_node_fs48.default.existsSync(import_node_path60.default.join(repoPath, fileName))) {
27377
+ if (import_node_fs48.default.existsSync(import_node_path61.default.join(repoPath, fileName))) {
27078
27378
  results.push({ path: fileName, kind });
27079
27379
  }
27080
27380
  }
27081
- const skillsDir = import_node_path60.default.join(repoPath, ".agents", "skills");
27381
+ const skillsDir = import_node_path61.default.join(repoPath, ".agents", "skills");
27082
27382
  if (import_node_fs48.default.existsSync(skillsDir)) {
27083
27383
  for (const name of import_node_fs48.default.readdirSync(skillsDir).sort()) {
27084
- const skillPath = import_node_path60.default.join(skillsDir, name, "SKILL.md");
27384
+ const skillPath = import_node_path61.default.join(skillsDir, name, "SKILL.md");
27085
27385
  if (import_node_fs48.default.existsSync(skillPath)) {
27086
27386
  results.push({ path: relative(repoPath, skillPath), kind: "skill" });
27087
27387
  }
@@ -27125,7 +27425,7 @@ function buildIssues(report) {
27125
27425
  }
27126
27426
  function inspectRepo(options = {}) {
27127
27427
  const repoPath = resolveRepoPath(options.repo);
27128
- const pkg = readJson(import_node_path60.default.join(repoPath, "package.json"));
27428
+ const pkg = readJson(import_node_path61.default.join(repoPath, "package.json"));
27129
27429
  const scripts = inspectScripts(pkg);
27130
27430
  const reportWithoutIssues = {
27131
27431
  repoPath,
@@ -27144,12 +27444,12 @@ function inspectRepo(options = {}) {
27144
27444
  issues: buildIssues(reportWithoutIssues)
27145
27445
  };
27146
27446
  }
27147
- var import_node_fs48, import_node_path60, import_yaml7;
27447
+ var import_node_fs48, import_node_path61, import_yaml7;
27148
27448
  var init_repo_inspect = __esm({
27149
27449
  "src/core/repo-inspect.ts"() {
27150
27450
  "use strict";
27151
27451
  import_node_fs48 = __toESM(require("fs"));
27152
- import_node_path60 = __toESM(require("path"));
27452
+ import_node_path61 = __toESM(require("path"));
27153
27453
  import_yaml7 = __toESM(require("yaml"));
27154
27454
  init_repo_config();
27155
27455
  }
@@ -27254,7 +27554,7 @@ function requiredFileChecks(repoPath) {
27254
27554
  ".devcontainer/docker-compose.yml",
27255
27555
  ".devrouter.yml"
27256
27556
  ];
27257
- const missing = required.filter((fileName) => !import_node_fs49.default.existsSync(import_node_path61.default.join(repoPath, fileName)));
27557
+ const missing = required.filter((fileName) => !import_node_fs49.default.existsSync(import_node_path62.default.join(repoPath, fileName)));
27258
27558
  return {
27259
27559
  id: "repo.devcontainer.verify-files",
27260
27560
  level: missing.length === 0 ? "ok" : "error",
@@ -27479,12 +27779,12 @@ async function verifyDevcontainer(options = {}) {
27479
27779
  nextSteps: collectNextSteps3(checks)
27480
27780
  };
27481
27781
  }
27482
- var import_node_fs49, import_node_path61;
27782
+ var import_node_fs49, import_node_path62;
27483
27783
  var init_devcontainer_verify = __esm({
27484
27784
  "src/core/devcontainer-verify.ts"() {
27485
27785
  "use strict";
27486
27786
  import_node_fs49 = __toESM(require("fs"));
27487
- import_node_path61 = __toESM(require("path"));
27787
+ import_node_path62 = __toESM(require("path"));
27488
27788
  init_capabilities();
27489
27789
  init_doctor();
27490
27790
  init_host_routes();
@@ -27781,7 +28081,7 @@ function packageManagerIssues(repo) {
27781
28081
  }
27782
28082
  function plannedFiles(repoPath, version) {
27783
28083
  const repo = inspectRepo({ repo: repoPath });
27784
- const projectName = sanitizeProjectName(import_node_path62.default.basename(repo.repoPath));
28084
+ const projectName = sanitizeProjectName(import_node_path63.default.basename(repo.repoPath));
27785
28085
  const nodeMajor = majorVersion(repo.node?.version, "24");
27786
28086
  const pnpmVersion = repo.packageManager?.name === "pnpm" && repo.packageManager.version ? repo.packageManager.version : DEFAULT_PNPM_VERSION;
27787
28087
  const port = inferPort2(repo);
@@ -27826,7 +28126,7 @@ function plannedFiles(repoPath, version) {
27826
28126
  };
27827
28127
  }
27828
28128
  function classifyFile(repoPath, file) {
27829
- const absolutePath = import_node_path62.default.join(repoPath, file.relativePath);
28129
+ const absolutePath = import_node_path63.default.join(repoPath, file.relativePath);
27830
28130
  if (!import_node_fs50.default.existsSync(absolutePath)) {
27831
28131
  return {
27832
28132
  path: file.relativePath,
@@ -27892,8 +28192,8 @@ function buildPlan(repoPath, dryRun, version) {
27892
28192
  };
27893
28193
  }
27894
28194
  function writeFile(repoPath, file) {
27895
- const absolutePath = import_node_path62.default.join(repoPath, file.relativePath);
27896
- import_node_fs50.default.mkdirSync(import_node_path62.default.dirname(absolutePath), { recursive: true });
28195
+ const absolutePath = import_node_path63.default.join(repoPath, file.relativePath);
28196
+ import_node_fs50.default.mkdirSync(import_node_path63.default.dirname(absolutePath), { recursive: true });
27897
28197
  import_node_fs50.default.writeFileSync(absolutePath, file.content, "utf-8");
27898
28198
  if (file.executable) {
27899
28199
  import_node_fs50.default.chmodSync(absolutePath, 493);
@@ -27934,12 +28234,12 @@ function writeDevcontainer(options = {}) {
27934
28234
  nextSteps: postWriteNextSteps(repoPath)
27935
28235
  };
27936
28236
  }
27937
- var import_node_fs50, import_node_path62, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
28237
+ var import_node_fs50, import_node_path63, MANAGED_MARKER2, DEFAULT_DEVROUTER_VERSION, DEFAULT_PNPM_VERSION, VALID_PACKAGE_VERSION_RE;
27938
28238
  var init_devcontainer_write = __esm({
27939
28239
  "src/core/devcontainer-write.ts"() {
27940
28240
  "use strict";
27941
28241
  import_node_fs50 = __toESM(require("fs"));
27942
- import_node_path62 = __toESM(require("path"));
28242
+ import_node_path63 = __toESM(require("path"));
27943
28243
  init_repo_config();
27944
28244
  init_repo_inspect();
27945
28245
  MANAGED_MARKER2 = "devrouter:managed devcontainer";
@@ -28273,7 +28573,7 @@ function buildProfilePlanReport(options) {
28273
28573
  return { ...report, contractPath, bindings };
28274
28574
  }
28275
28575
  function loadContract(repoPath, contractPath) {
28276
- if (import_node_path63.default.isAbsolute(contractPath)) {
28576
+ if (import_node_path64.default.isAbsolute(contractPath)) {
28277
28577
  fail3("profile plan contract path must be relative to the repository.");
28278
28578
  }
28279
28579
  const absolutePath = assertPathWithinRepo(contractPath, repoPath, "Profile plan contract");
@@ -28298,7 +28598,7 @@ function loadContract(repoPath, contractPath) {
28298
28598
  }
28299
28599
  return {
28300
28600
  contract: parseProfilePlanContract(source),
28301
- contractPath: import_node_path63.default.relative(repoPath, absolutePath).split(import_node_path63.default.sep).join("/")
28601
+ contractPath: import_node_path64.default.relative(repoPath, absolutePath).split(import_node_path64.default.sep).join("/")
28302
28602
  };
28303
28603
  }
28304
28604
  function resolveProfilePlan(options) {
@@ -28312,12 +28612,12 @@ function resolveProfilePlan(options) {
28312
28612
  contractPath: loaded.contractPath
28313
28613
  });
28314
28614
  }
28315
- var import_node_fs51, import_node_path63, import_yaml8, CONTRACT_VERSION, MAX_CONTRACT_BYTES, MAX_APP_MAPPINGS, MAX_BINDING_KEYS, MAX_BINDING_VALUES, MAX_LITERAL_LENGTH, BINDING_KEY;
28615
+ var import_node_fs51, import_node_path64, import_yaml8, CONTRACT_VERSION, MAX_CONTRACT_BYTES, MAX_APP_MAPPINGS, MAX_BINDING_KEYS, MAX_BINDING_VALUES, MAX_LITERAL_LENGTH, BINDING_KEY;
28316
28616
  var init_profile_plan = __esm({
28317
28617
  "src/core/profile-plan.ts"() {
28318
28618
  "use strict";
28319
28619
  import_node_fs51 = __toESM(require("fs"));
28320
- import_node_path63 = __toESM(require("path"));
28620
+ import_node_path64 = __toESM(require("path"));
28321
28621
  import_yaml8 = require("yaml");
28322
28622
  init_paths();
28323
28623
  init_profile_resolution();
@@ -28349,7 +28649,7 @@ async function runProfileResolveCommand(options) {
28349
28649
  async function runProfilePlanCommand(options) {
28350
28650
  const report = resolveProfilePlan(options);
28351
28651
  if (options.output) {
28352
- writeFileAtomically(import_node_path64.default.resolve(options.output), `${JSON.stringify(report, null, 2)}
28652
+ writeFileAtomically(import_node_path65.default.resolve(options.output), `${JSON.stringify(report, null, 2)}
28353
28653
  `);
28354
28654
  }
28355
28655
  if (options.json) {
@@ -28396,14 +28696,14 @@ function printProfilePlanSummary(report, output2) {
28396
28696
  process.stdout.write(`Binding ${key}: ${renderValues(report.bindings[key] ?? [])}
28397
28697
  `);
28398
28698
  }
28399
- if (output2) process.stdout.write(`Output: ${import_node_path64.default.resolve(output2)}
28699
+ if (output2) process.stdout.write(`Output: ${import_node_path65.default.resolve(output2)}
28400
28700
  `);
28401
28701
  }
28402
- var import_node_path64;
28702
+ var import_node_path65;
28403
28703
  var init_profile = __esm({
28404
28704
  "src/commands/profile.ts"() {
28405
28705
  "use strict";
28406
- import_node_path64 = __toESM(require("path"));
28706
+ import_node_path65 = __toESM(require("path"));
28407
28707
  init_atomic_file();
28408
28708
  init_output();
28409
28709
  init_profile_plan();
@@ -28693,7 +28993,7 @@ function sanitizeRouterId(value) {
28693
28993
  return value.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
28694
28994
  }
28695
28995
  function repoHash(repoPath) {
28696
- return (0, import_node_crypto29.createHash)("sha1").update(import_node_path65.default.resolve(repoPath)).digest("hex").slice(0, 12);
28996
+ return (0, import_node_crypto29.createHash)("sha1").update(import_node_path66.default.resolve(repoPath)).digest("hex").slice(0, 12);
28697
28997
  }
28698
28998
  function asDockerApp(app) {
28699
28999
  return app.runtime === "docker";
@@ -28772,9 +29072,9 @@ function prepareDockerOverlay(repoPath, appName, apps, publishTcpPorts = false)
28772
29072
  if (dockerApps.length === 0) {
28773
29073
  throw new Error("No docker apps selected to prepare compose overlay.");
28774
29074
  }
28775
- const cachePath = import_node_path65.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
29075
+ const cachePath = import_node_path66.default.join(CACHE_DIR, repoHash(repoPath), sanitizeRouterId(appName));
28776
29076
  import_node_fs52.default.mkdirSync(cachePath, { recursive: true });
28777
- const overlayPath = import_node_path65.default.join(cachePath, "compose.devrouter.yml");
29077
+ const overlayPath = import_node_path66.default.join(cachePath, "compose.devrouter.yml");
28778
29078
  const overlayDocument = buildOverlayDocument(dockerApps, publishTcpPorts);
28779
29079
  import_node_fs52.default.writeFileSync(overlayPath, import_yaml9.default.stringify(overlayDocument, { lineWidth: 0 }), "utf-8");
28780
29080
  return {
@@ -28790,7 +29090,7 @@ function runDockerComposeUp(repoPath, composeFiles2, overlayPath, services) {
28790
29090
  fileArgs.push("-f", resolved2);
28791
29091
  }
28792
29092
  const args = ["compose", ...fileArgs, "-f", overlayPath, "up", "-d", "--wait", ...services];
28793
- const result = (0, import_node_child_process36.spawnSync)("docker", args, {
29093
+ const result = (0, import_node_child_process38.spawnSync)("docker", args, {
28794
29094
  encoding: "utf-8",
28795
29095
  cwd: repoPath
28796
29096
  });
@@ -28818,7 +29118,7 @@ function queryRunningComposeServices(repoPath, composeFiles2, overlayPath, servi
28818
29118
  "--services",
28819
29119
  ...services
28820
29120
  ];
28821
- const result = (0, import_node_child_process36.spawnSync)("docker", args, {
29121
+ const result = (0, import_node_child_process38.spawnSync)("docker", args, {
28822
29122
  encoding: "utf-8",
28823
29123
  cwd: repoPath
28824
29124
  });
@@ -28844,7 +29144,7 @@ function runDockerComposeStop(repoPath, composeFiles2, overlayPath, services) {
28844
29144
  fileArgs.push("-f", resolved2);
28845
29145
  }
28846
29146
  const args = ["compose", ...fileArgs, "-f", overlayPath, "stop", ...services];
28847
- const result = (0, import_node_child_process36.spawnSync)("docker", args, {
29147
+ const result = (0, import_node_child_process38.spawnSync)("docker", args, {
28848
29148
  encoding: "utf-8",
28849
29149
  cwd: repoPath
28850
29150
  });
@@ -28870,7 +29170,7 @@ function runDockerComposeLogs(repoPath, composeFiles2, overlayPath, services, ta
28870
29170
  String(tail),
28871
29171
  ...services
28872
29172
  ];
28873
- (0, import_node_child_process36.spawnSync)("docker", args, {
29173
+ (0, import_node_child_process38.spawnSync)("docker", args, {
28874
29174
  stdio: "inherit",
28875
29175
  cwd: repoPath
28876
29176
  });
@@ -28882,7 +29182,7 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
28882
29182
  fileArgs.push("-f", resolved2);
28883
29183
  }
28884
29184
  const args = ["compose", ...fileArgs, "-f", overlayPath, "port", service, String(internalPort)];
28885
- const result = (0, import_node_child_process36.spawnSync)("docker", args, {
29185
+ const result = (0, import_node_child_process38.spawnSync)("docker", args, {
28886
29186
  encoding: "utf-8",
28887
29187
  cwd: repoPath
28888
29188
  });
@@ -28896,14 +29196,14 @@ function queryMappedPort(repoPath, composeFiles2, overlayPath, service, internal
28896
29196
  const port = Number(match[1]);
28897
29197
  return Number.isInteger(port) && port > 0 ? port : void 0;
28898
29198
  }
28899
- var import_node_child_process36, import_node_crypto29, import_node_fs52, import_node_path65, import_yaml9;
29199
+ var import_node_child_process38, import_node_crypto29, import_node_fs52, import_node_path66, import_yaml9;
28900
29200
  var init_docker_run = __esm({
28901
29201
  "src/core/docker-run.ts"() {
28902
29202
  "use strict";
28903
- import_node_child_process36 = require("child_process");
29203
+ import_node_child_process38 = require("child_process");
28904
29204
  import_node_crypto29 = require("crypto");
28905
29205
  import_node_fs52 = __toESM(require("fs"));
28906
- import_node_path65 = __toESM(require("path"));
29206
+ import_node_path66 = __toESM(require("path"));
28907
29207
  import_yaml9 = __toESM(require("yaml"));
28908
29208
  init_docker_error_guidance();
28909
29209
  init_paths();
@@ -28993,7 +29293,7 @@ function isProcessRunning(pid) {
28993
29293
  }
28994
29294
  }
28995
29295
  function readProcessTree(rootPid) {
28996
- const result = (0, import_node_child_process37.spawnSync)("ps", ["-ax", "-o", "pid=,ppid="], { encoding: "utf-8" });
29296
+ const result = (0, import_node_child_process39.spawnSync)("ps", ["-ax", "-o", "pid=,ppid="], { encoding: "utf-8" });
28997
29297
  if (result.status !== 0) {
28998
29298
  return [rootPid];
28999
29299
  }
@@ -29104,12 +29404,12 @@ function detectListeningPorts(pids) {
29104
29404
  if (pids.length === 0) {
29105
29405
  return [];
29106
29406
  }
29107
- const result = (0, import_node_child_process37.spawnSync)("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", pids.join(",")], {
29407
+ const result = (0, import_node_child_process39.spawnSync)("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", pids.join(",")], {
29108
29408
  encoding: "utf-8"
29109
29409
  });
29110
29410
  if (result.error && result.error.code === "ENOENT") {
29111
29411
  if (process.platform === "linux") {
29112
- const ssResult = (0, import_node_child_process37.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
29412
+ const ssResult = (0, import_node_child_process39.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
29113
29413
  if (ssResult.status === 0 && ssResult.stdout) {
29114
29414
  return parseSsListeningPorts(ssResult.stdout, new Set(pids));
29115
29415
  }
@@ -29168,7 +29468,7 @@ async function runHostApp(repoPath, app, extraEnv = {}, secretManager, env, work
29168
29468
  app.hostRun.command,
29169
29469
  true
29170
29470
  ) : app.hostRun.command;
29171
- const child = (0, import_node_child_process37.spawn)(spawnCommand, {
29471
+ const child = (0, import_node_child_process39.spawn)(spawnCommand, {
29172
29472
  cwd: commandCwd,
29173
29473
  stdio: "inherit",
29174
29474
  shell: true,
@@ -29536,7 +29836,7 @@ async function execWithAppEnv(options) {
29536
29836
  options.command[0],
29537
29837
  true
29538
29838
  );
29539
- child = (0, import_node_child_process37.spawn)(wrapped, {
29839
+ child = (0, import_node_child_process39.spawn)(wrapped, {
29540
29840
  cwd: deps.repoPath,
29541
29841
  stdio: "inherit",
29542
29842
  shell: true,
@@ -29550,7 +29850,7 @@ async function execWithAppEnv(options) {
29550
29850
  false
29551
29851
  );
29552
29852
  const [cmd, ...wrappedArgs] = wrapped;
29553
- child = (0, import_node_child_process37.spawn)(cmd, wrappedArgs, {
29853
+ child = (0, import_node_child_process39.spawn)(cmd, wrappedArgs, {
29554
29854
  cwd: deps.repoPath,
29555
29855
  stdio: "inherit",
29556
29856
  shell: false,
@@ -29558,7 +29858,7 @@ async function execWithAppEnv(options) {
29558
29858
  });
29559
29859
  }
29560
29860
  } else if (options.shell) {
29561
- child = (0, import_node_child_process37.spawn)(options.command[0], {
29861
+ child = (0, import_node_child_process39.spawn)(options.command[0], {
29562
29862
  cwd: deps.repoPath,
29563
29863
  stdio: "inherit",
29564
29864
  shell: true,
@@ -29566,7 +29866,7 @@ async function execWithAppEnv(options) {
29566
29866
  });
29567
29867
  } else {
29568
29868
  const [command, ...args] = options.command;
29569
- child = (0, import_node_child_process37.spawn)(command, args, {
29869
+ child = (0, import_node_child_process39.spawn)(command, args, {
29570
29870
  cwd: deps.repoPath,
29571
29871
  stdio: "inherit",
29572
29872
  shell: false,
@@ -29587,11 +29887,11 @@ async function execWithAppEnv(options) {
29587
29887
  deps.stopDeps();
29588
29888
  }
29589
29889
  }
29590
- var import_node_child_process37, import_node_net5, import_node_process, import_promises, POLL_INTERVAL_MS, DEFAULT_PORT_TIMEOUT_MS, PROCESS_TERMINATION_GRACE_MS;
29890
+ var import_node_child_process39, import_node_net5, import_node_process, import_promises, POLL_INTERVAL_MS, DEFAULT_PORT_TIMEOUT_MS, PROCESS_TERMINATION_GRACE_MS;
29591
29891
  var init_app_run = __esm({
29592
29892
  "src/core/app-run.ts"() {
29593
29893
  "use strict";
29594
- import_node_child_process37 = require("child_process");
29894
+ import_node_child_process39 = require("child_process");
29595
29895
  import_node_net5 = __toESM(require("net"));
29596
29896
  import_node_process = require("process");
29597
29897
  import_promises = require("readline/promises");
@@ -29799,7 +30099,7 @@ function validateCandidate(value, index, action2, legacy) {
29799
30099
  }
29800
30100
  }
29801
30101
  for (const key of ["repo", "path", "commonDir", "gitDir"]) {
29802
- if (!import_node_path66.default.isAbsolute(candidate[key])) {
30102
+ if (!import_node_path67.default.isAbsolute(candidate[key])) {
29803
30103
  throw new Error(`${label2}.${key} must be an absolute path`);
29804
30104
  }
29805
30105
  }
@@ -29944,7 +30244,7 @@ function selectManifest(file, expectedSha256, ids2) {
29944
30244
  return canonicalJson(subset);
29945
30245
  }
29946
30246
  function gitLine(cwd, args) {
29947
- const result = (0, import_node_child_process38.spawnSync)("git", ["-C", cwd, ...args], {
30247
+ const result = (0, import_node_child_process40.spawnSync)("git", ["-C", cwd, ...args], {
29948
30248
  encoding: "utf-8",
29949
30249
  env: { ...process.env, GIT_OPTIONAL_LOCKS: "0", LC_ALL: "C" }
29950
30250
  });
@@ -29972,9 +30272,9 @@ function identityMismatch(expected, actual) {
29972
30272
  function unmanagedActivityEpoch(worktreePath, gitDir) {
29973
30273
  let latest;
29974
30274
  for (const file of [
29975
- import_node_path66.default.join(gitDir, "index"),
29976
- import_node_path66.default.join(gitDir, "HEAD"),
29977
- import_node_path66.default.join(gitDir, "logs", "HEAD"),
30275
+ import_node_path67.default.join(gitDir, "index"),
30276
+ import_node_path67.default.join(gitDir, "HEAD"),
30277
+ import_node_path67.default.join(gitDir, "logs", "HEAD"),
29978
30278
  worktreePath
29979
30279
  ]) {
29980
30280
  try {
@@ -30032,7 +30332,7 @@ function buildReclaimManifest(report, createdAtEpoch) {
30032
30332
  return canonicalJson(manifest);
30033
30333
  }
30034
30334
  function receiptPath(commonDir, manifestSha256, id5) {
30035
- return import_node_path66.default.join(commonDir, "worktree-reclaim", "receipts", manifestSha256, `${id5}.json`);
30335
+ return import_node_path67.default.join(commonDir, "worktree-reclaim", "receipts", manifestSha256, `${id5}.json`);
30036
30336
  }
30037
30337
  function matchingReceipt(candidate, manifestSha256) {
30038
30338
  try {
@@ -30046,7 +30346,7 @@ function matchingReceipt(candidate, manifestSha256) {
30046
30346
  }
30047
30347
  function writeReceipt(candidate, manifestSha256, steps, nowEpoch) {
30048
30348
  const file = receiptPath(candidate.commonDir, manifestSha256, candidate.id);
30049
- import_node_fs53.default.mkdirSync(import_node_path66.default.dirname(file), { recursive: true });
30349
+ import_node_fs53.default.mkdirSync(import_node_path67.default.dirname(file), { recursive: true });
30050
30350
  writeFileAtomically(
30051
30351
  file,
30052
30352
  canonicalJson({
@@ -30064,7 +30364,7 @@ function writeReceipt(candidate, manifestSha256, steps, nowEpoch) {
30064
30364
  );
30065
30365
  }
30066
30366
  function runVetoCommand(command, worktreePath) {
30067
- const result = (0, import_node_child_process38.spawnSync)(command, [worktreePath], {
30367
+ const result = (0, import_node_child_process40.spawnSync)(command, [worktreePath], {
30068
30368
  stdio: ["ignore", "ignore", "inherit"],
30069
30369
  timeout: VETO_TIMEOUT_MS
30070
30370
  });
@@ -30077,7 +30377,7 @@ function runVetoCommand(command, worktreePath) {
30077
30377
  return result.status === 0 && !result.error && result.signal === null;
30078
30378
  }
30079
30379
  function assertVetoCommand(command) {
30080
- if (!import_node_path66.default.isAbsolute(command)) throw new Error("--veto-command must be an absolute path");
30380
+ if (!import_node_path67.default.isAbsolute(command)) throw new Error("--veto-command must be an absolute path");
30081
30381
  try {
30082
30382
  import_node_fs53.default.accessSync(command, import_node_fs53.default.constants.X_OK);
30083
30383
  } catch {
@@ -30105,7 +30405,7 @@ function preflightManifestApply(options, action2, nowEpoch) {
30105
30405
  return { manifest, manifestSha256, now };
30106
30406
  }
30107
30407
  function deleteBranchIfUnmoved(candidate, steps) {
30108
- const exists2 = (0, import_node_child_process38.spawnSync)(
30408
+ const exists2 = (0, import_node_child_process40.spawnSync)(
30109
30409
  "git",
30110
30410
  ["-C", candidate.repo, "show-ref", "--verify", "--quiet", candidate.branchRef],
30111
30411
  { encoding: "utf-8" }
@@ -30114,7 +30414,7 @@ function deleteBranchIfUnmoved(candidate, steps) {
30114
30414
  if (exists2.status !== 0) {
30115
30415
  throw new Error(`cannot read branch ${candidate.branch}: ${(exists2.stderr ?? "").trim()}`);
30116
30416
  }
30117
- const result = (0, import_node_child_process38.spawnSync)(
30417
+ const result = (0, import_node_child_process40.spawnSync)(
30118
30418
  "git",
30119
30419
  ["-C", candidate.repo, "update-ref", "-d", candidate.branchRef, candidate.head],
30120
30420
  { encoding: "utf-8" }
@@ -30123,7 +30423,7 @@ function deleteBranchIfUnmoved(candidate, steps) {
30123
30423
  return `branch ${candidate.branch} no longer points at the approved HEAD; it was kept for review`;
30124
30424
  }
30125
30425
  steps.push("branch-deleted");
30126
- (0, import_node_child_process38.spawnSync)(
30426
+ (0, import_node_child_process40.spawnSync)(
30127
30427
  "git",
30128
30428
  ["-C", candidate.repo, "config", "--remove-section", `branch.${candidate.branch}`],
30129
30429
  { encoding: "utf-8" }
@@ -30230,14 +30530,14 @@ async function applyReclaimManifest(options, dependencies) {
30230
30530
  }
30231
30531
  return { manifestSha256, dryRun: !options.yes, targets, stoppedEarly };
30232
30532
  }
30233
- var import_node_child_process38, import_node_crypto30, import_node_fs53, import_node_path66, MANIFEST_KIND, LEGACY_MANIFEST_KIND, RECEIPT_KIND, SCHEMA_VERSION, HEX_SHA, HEX_ID, VETO_TIMEOUT_MS, FUTURE_TOLERANCE_SECONDS, ROOT_KEYS, SESSION_ROOT_KEYS, LEGACY_ROOT_KEYS, CANDIDATE_KEYS, SESSION_CANDIDATE_KEYS, OPTIONAL_CANDIDATE_KEYS, ReclaimSkip;
30533
+ var import_node_child_process40, import_node_crypto30, import_node_fs53, import_node_path67, MANIFEST_KIND, LEGACY_MANIFEST_KIND, RECEIPT_KIND, SCHEMA_VERSION, HEX_SHA, HEX_ID, VETO_TIMEOUT_MS, FUTURE_TOLERANCE_SECONDS, ROOT_KEYS, SESSION_ROOT_KEYS, LEGACY_ROOT_KEYS, CANDIDATE_KEYS, SESSION_CANDIDATE_KEYS, OPTIONAL_CANDIDATE_KEYS, ReclaimSkip;
30234
30534
  var init_worktree_reclaim = __esm({
30235
30535
  "src/core/worktree-reclaim.ts"() {
30236
30536
  "use strict";
30237
- import_node_child_process38 = require("child_process");
30537
+ import_node_child_process40 = require("child_process");
30238
30538
  import_node_crypto30 = require("crypto");
30239
30539
  import_node_fs53 = __toESM(require("fs"));
30240
- import_node_path66 = __toESM(require("path"));
30540
+ import_node_path67 = __toESM(require("path"));
30241
30541
  init_atomic_file();
30242
30542
  init_workspace();
30243
30543
  MANIFEST_KIND = "worktree-reclaim-manifest";
@@ -30357,8 +30657,8 @@ function checkoutOf(workingDir) {
30357
30657
  return workingDir.replace(/\/+$/, "").replace(/\/\.devcontainer$/, "");
30358
30658
  }
30359
30659
  function isInsideOrEqual(parent, child) {
30360
- const relative2 = import_node_path67.default.relative(parent, child);
30361
- return relative2 === "" || !relative2.startsWith("..") && !import_node_path67.default.isAbsolute(relative2);
30660
+ const relative2 = import_node_path68.default.relative(parent, child);
30661
+ return relative2 === "" || !relative2.startsWith("..") && !import_node_path68.default.isAbsolute(relative2);
30362
30662
  }
30363
30663
  function isAnonymousVolume(volume) {
30364
30664
  return /^[0-9a-f]{64}$/.test(volume.name) && !volume.labels[PROJECT_LABEL];
@@ -30370,9 +30670,9 @@ function vanishedInsideTrees(checkout, context) {
30370
30670
  (prefix) => checkout !== prefix && isInsideOrEqual(prefix, checkout)
30371
30671
  );
30372
30672
  if (!lexical || context.exists(checkout)) return "out-of-scope";
30373
- let ancestor = import_node_path67.default.dirname(checkout);
30673
+ let ancestor = import_node_path68.default.dirname(checkout);
30374
30674
  while (!context.exists(ancestor)) {
30375
- const parent = import_node_path67.default.dirname(ancestor);
30675
+ const parent = import_node_path68.default.dirname(ancestor);
30376
30676
  if (parent === ancestor) return "out-of-scope";
30377
30677
  ancestor = parent;
30378
30678
  }
@@ -30380,16 +30680,16 @@ function vanishedInsideTrees(checkout, context) {
30380
30680
  if (ancestorReal === void 0 || !isInsideOrEqual(treesReal, ancestorReal)) {
30381
30681
  return "out-of-scope";
30382
30682
  }
30383
- for (let current = ancestor; ; current = import_node_path67.default.dirname(current)) {
30683
+ for (let current = ancestor; ; current = import_node_path68.default.dirname(current)) {
30384
30684
  const real = context.realpath(current);
30385
30685
  if (real === void 0 || real === treesReal || !isInsideOrEqual(treesReal, real)) break;
30386
- if (context.exists(import_node_path67.default.join(current, ".git"))) return "inside-worktree";
30686
+ if (context.exists(import_node_path68.default.join(current, ".git"))) return "inside-worktree";
30387
30687
  }
30388
30688
  return "vanished";
30389
30689
  }
30390
30690
  function resolved(file, context) {
30391
30691
  const real = context.realpath(file);
30392
- return real && real !== import_node_path67.default.resolve(file) ? [import_node_path67.default.resolve(file), real] : [import_node_path67.default.resolve(file)];
30692
+ return real && real !== import_node_path68.default.resolve(file) ? [import_node_path68.default.resolve(file), real] : [import_node_path68.default.resolve(file)];
30393
30693
  }
30394
30694
  function relation(entry, checkout, context) {
30395
30695
  const entries = resolved(entry, context);
@@ -30494,7 +30794,7 @@ function readLeftoverContext(repoPath) {
30494
30794
  resetWorkspaceRuntimeCaches();
30495
30795
  const snapshots = getWorkspaceRegistrySnapshots();
30496
30796
  return {
30497
- treesPath: import_node_path67.default.join(repoPath, "trees"),
30797
+ treesPath: import_node_path68.default.join(repoPath, "trees"),
30498
30798
  worktreePaths: listGitWorktrees(repoPath).map((worktree) => worktree.path),
30499
30799
  ownershipPaths: listWorkspaceOwnership(repoPath).map((record5) => record5.worktreePath),
30500
30800
  providerPaths: listMergedRuntimeWorkspaces().map((workspace) => workspace.source.localFolder),
@@ -30572,7 +30872,7 @@ function validateLeftoverManifest(value) {
30572
30872
  throw new Error(`manifest schemaVersion must be ${SCHEMA_VERSION2}`);
30573
30873
  }
30574
30874
  requireString(root.repo, "manifest repo");
30575
- if (!import_node_path67.default.isAbsolute(root.repo)) throw new Error("manifest repo must be an absolute path");
30875
+ if (!import_node_path68.default.isAbsolute(root.repo)) throw new Error("manifest repo must be an absolute path");
30576
30876
  requireString(root.daemonId, "manifest daemonId");
30577
30877
  if (!Number.isInteger(root.createdAtEpoch) || root.createdAtEpoch < 0) {
30578
30878
  throw new Error("manifest createdAtEpoch must be a non-negative integer");
@@ -30594,7 +30894,7 @@ function validateLeftoverManifest(value) {
30594
30894
  );
30595
30895
  requireString(candidate.project, `${label2}.project`);
30596
30896
  requireString(candidate.checkout, `${label2}.checkout`);
30597
- if (!import_node_path67.default.isAbsolute(candidate.checkout)) {
30897
+ if (!import_node_path68.default.isAbsolute(candidate.checkout)) {
30598
30898
  throw new Error(`${label2}.checkout must be an absolute path`);
30599
30899
  }
30600
30900
  const members = [
@@ -30646,7 +30946,7 @@ function readLeftoverManifest(file) {
30646
30946
  return { raw, manifest };
30647
30947
  }
30648
30948
  function gitCommonDir(repo) {
30649
- const result = (0, import_node_child_process39.spawnSync)(
30949
+ const result = (0, import_node_child_process41.spawnSync)(
30650
30950
  "git",
30651
30951
  ["-C", repo, "rev-parse", "--path-format=absolute", "--git-common-dir"],
30652
30952
  { encoding: "utf-8" }
@@ -30814,11 +31114,11 @@ function applyLeftoverManifest(options, dependencies = {}) {
30814
31114
  return { manifestSha256, dryRun: !options.yes, targets };
30815
31115
  }
30816
31116
  function leftoverReceiptPath(commonDir, manifestSha256, id5) {
30817
- return import_node_path67.default.join(commonDir, "devrouter-leftovers", "receipts", manifestSha256, `${id5}.json`);
31117
+ return import_node_path68.default.join(commonDir, "devrouter-leftovers", "receipts", manifestSha256, `${id5}.json`);
30818
31118
  }
30819
31119
  function writeLeftoverReceipt(commonDir, manifestSha256, candidate, steps, nowEpoch) {
30820
31120
  const file = leftoverReceiptPath(commonDir, manifestSha256, candidate.id);
30821
- import_node_fs54.default.mkdirSync(import_node_path67.default.dirname(file), { recursive: true });
31121
+ import_node_fs54.default.mkdirSync(import_node_path68.default.dirname(file), { recursive: true });
30822
31122
  writeFileAtomically(
30823
31123
  file,
30824
31124
  canonicalJson({
@@ -30834,13 +31134,13 @@ function writeLeftoverReceipt(commonDir, manifestSha256, candidate, steps, nowEp
30834
31134
  })
30835
31135
  );
30836
31136
  }
30837
- var import_node_child_process39, import_node_fs54, import_node_path67, LEFTOVER_MANIFEST_KIND, LEFTOVER_RECEIPT_KIND, SCHEMA_VERSION2, MAX_DOCKER_OBJECTS, FUTURE_TOLERANCE_SECONDS2, PROJECT_LABEL, WORKING_DIR_LABEL, PROVIDER_LABEL, STOPPED_STATES, HEX_ID2, DockerListingBoundError, defaultDockerRunner, LeftoverSkip, defaultWithLocks;
31137
+ var import_node_child_process41, import_node_fs54, import_node_path68, LEFTOVER_MANIFEST_KIND, LEFTOVER_RECEIPT_KIND, SCHEMA_VERSION2, MAX_DOCKER_OBJECTS, FUTURE_TOLERANCE_SECONDS2, PROJECT_LABEL, WORKING_DIR_LABEL, PROVIDER_LABEL, STOPPED_STATES, HEX_ID2, DockerListingBoundError, defaultDockerRunner, LeftoverSkip, defaultWithLocks;
30838
31138
  var init_docker_leftovers = __esm({
30839
31139
  "src/core/docker-leftovers.ts"() {
30840
31140
  "use strict";
30841
- import_node_child_process39 = require("child_process");
31141
+ import_node_child_process41 = require("child_process");
30842
31142
  import_node_fs54 = __toESM(require("fs"));
30843
- import_node_path67 = __toESM(require("path"));
31143
+ import_node_path68 = __toESM(require("path"));
30844
31144
  init_atomic_file();
30845
31145
  init_devpod_mutation();
30846
31146
  init_devpod_workspaces();
@@ -30861,7 +31161,7 @@ var init_docker_leftovers = __esm({
30861
31161
  DockerListingBoundError = class extends Error {
30862
31162
  };
30863
31163
  defaultDockerRunner = (args) => {
30864
- const result = (0, import_node_child_process39.spawnSync)("docker", args, {
31164
+ const result = (0, import_node_child_process41.spawnSync)("docker", args, {
30865
31165
  encoding: "utf-8",
30866
31166
  timeout: 6e4,
30867
31167
  maxBuffer: 256 * 1024 * 1024
@@ -30924,7 +31224,7 @@ function measureWorktreeConsumption(worktreePath, options) {
30924
31224
  timedOut = true;
30925
31225
  break;
30926
31226
  }
30927
- const entryPath = import_node_path68.default.join(dirPath, entry.name);
31227
+ const entryPath = import_node_path69.default.join(dirPath, entry.name);
30928
31228
  let entryStat;
30929
31229
  try {
30930
31230
  entryStat = import_node_fs55.default.lstatSync(entryPath);
@@ -31001,12 +31301,12 @@ function describeError(error, worktreePath) {
31001
31301
  }
31002
31302
  return `could not stat worktree path '${worktreePath}': ${error?.message ?? String(error)}`;
31003
31303
  }
31004
- var import_node_fs55, import_node_path68, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
31304
+ var import_node_fs55, import_node_path69, DEFAULT_DEADLINE_MS, BLOCK_SIZE_BYTES;
31005
31305
  var init_workspace_consumption = __esm({
31006
31306
  "src/core/workspace-consumption.ts"() {
31007
31307
  "use strict";
31008
31308
  import_node_fs55 = __toESM(require("fs"));
31009
- import_node_path68 = __toESM(require("path"));
31309
+ import_node_path69 = __toESM(require("path"));
31010
31310
  init_devpod_environment();
31011
31311
  DEFAULT_DEADLINE_MS = 1e4;
31012
31312
  BLOCK_SIZE_BYTES = 512;
@@ -31680,7 +31980,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
31680
31980
  containers2 = measureContainersFn(worktreePaths);
31681
31981
  } catch (error) {
31682
31982
  const reason = `container measurement failed: ${describeCause(error)}`;
31683
- containers2 = new Map(worktreePaths.map((path72) => [path72, unknownContainers(reason)]));
31983
+ containers2 = new Map(worktreePaths.map((path73) => [path73, unknownContainers(reason)]));
31684
31984
  }
31685
31985
  }
31686
31986
  const rows2 = records.map((record5) => {
@@ -31739,7 +32039,7 @@ function buildWorkspaceCleanupReport(options = {}, dependencies = {}) {
31739
32039
  dependencies.listForge ?? ((identity2) => listForgeChanges(identity2))
31740
32040
  );
31741
32041
  const sources = (dependencies.readRuntimeSources ?? readRuntimeSources)(repoPath);
31742
- const disposable = import_node_fs56.default.existsSync(import_node_path69.default.join(repoPath, ".devrouter.yml")) ? loadRepoConfig(repoPath).worktrees?.disposable ?? [] : [];
32042
+ const disposable = import_node_fs56.default.existsSync(import_node_path70.default.join(repoPath, ".devrouter.yml")) ? loadRepoConfig(repoPath).worktrees?.disposable ?? [] : [];
31743
32043
  const [primary, ...linked] = worktrees;
31744
32044
  const safetyFor = (worktree, worktreePath, evidence) => toSafety(
31745
32045
  classify({
@@ -31808,7 +32108,7 @@ function readPathEvidence(repoPath, worktreePath, inactiveFor, dependencies) {
31808
32108
  }
31809
32109
  const sources = (dependencies.readRuntimeSources ?? readRuntimeSources)(repoPath);
31810
32110
  const cutoffEpoch = Math.floor(Date.now() / 1e3) - parseInactiveFor(inactiveFor).seconds;
31811
- const gitDir = (0, import_node_child_process40.spawnSync)(
32111
+ const gitDir = (0, import_node_child_process42.spawnSync)(
31812
32112
  "git",
31813
32113
  ["-C", worktreePath, "rev-parse", "--path-format=absolute", "--git-dir"],
31814
32114
  { encoding: "utf-8", env: READ_ONLY_GIT_ENV2 }
@@ -31854,7 +32154,7 @@ function prepareReclaimVerdicts(repo, inactiveFor, dependencies = {}) {
31854
32154
  dependencies.listForge ?? ((identity2) => listForgeChanges(identity2))
31855
32155
  );
31856
32156
  const forge = evidence.status === "not-checked" ? { status: "unavailable", reason: "forge not checked" } : evidence;
31857
- const disposable = import_node_fs56.default.existsSync(import_node_path69.default.join(repoPath, ".devrouter.yml")) ? loadRepoConfig(repoPath).worktrees?.disposable ?? [] : [];
32157
+ const disposable = import_node_fs56.default.existsSync(import_node_path70.default.join(repoPath, ".devrouter.yml")) ? loadRepoConfig(repoPath).worktrees?.disposable ?? [] : [];
31858
32158
  const paths = evidenceCache(
31859
32159
  (worktreePath) => readPathEvidence(repoPath, worktreePath, inactiveFor, dependencies)
31860
32160
  );
@@ -31873,7 +32173,7 @@ function prepareReclaimVerdicts(repo, inactiveFor, dependencies = {}) {
31873
32173
  }
31874
32174
  function prepareTrimChecks(repo, inactiveFor, dependencies = {}) {
31875
32175
  const repoPath = resolveRepoPath(repo);
31876
- const configured = import_node_fs56.default.existsSync(import_node_path69.default.join(repoPath, ".devrouter.yml")) ? loadRepoConfig(repoPath).worktrees : void 0;
32176
+ const configured = import_node_fs56.default.existsSync(import_node_path70.default.join(repoPath, ".devrouter.yml")) ? loadRepoConfig(repoPath).worktrees : void 0;
31877
32177
  const patterns = configured?.trim ?? [];
31878
32178
  const paths = evidenceCache(
31879
32179
  (worktreePath) => readPathEvidence(repoPath, worktreePath, inactiveFor, dependencies)
@@ -31898,13 +32198,13 @@ function prepareTrimChecks(repo, inactiveFor, dependencies = {}) {
31898
32198
  isActive: (worktreePath) => paths.last(worktreePath).active
31899
32199
  };
31900
32200
  }
31901
- var import_node_child_process40, import_node_fs56, import_node_path69, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
32201
+ var import_node_child_process42, import_node_fs56, import_node_path70, DEFAULT_INACTIVE_FOR, READ_ONLY_GIT_ENV2, ACTIVITY_SOURCES, defaultCommandRunner;
31902
32202
  var init_workspace_cleanup = __esm({
31903
32203
  "src/core/workspace-cleanup.ts"() {
31904
32204
  "use strict";
31905
- import_node_child_process40 = require("child_process");
32205
+ import_node_child_process42 = require("child_process");
31906
32206
  import_node_fs56 = __toESM(require("fs"));
31907
- import_node_path69 = __toESM(require("path"));
32207
+ import_node_path70 = __toESM(require("path"));
31908
32208
  init_devpod_workspaces();
31909
32209
  init_docker_leftovers();
31910
32210
  init_forge_changes();
@@ -31927,7 +32227,7 @@ var init_workspace_cleanup = __esm({
31927
32227
  ];
31928
32228
  defaultCommandRunner = (command, args, input2) => {
31929
32229
  const environment = command === "git" ? READ_ONLY_GIT_ENV2 : { ...process.env, LC_ALL: "C" };
31930
- const result = (0, import_node_child_process40.spawnSync)(command, args, {
32230
+ const result = (0, import_node_child_process42.spawnSync)(command, args, {
31931
32231
  encoding: "utf-8",
31932
32232
  env: environment,
31933
32233
  ...input2 === void 0 ? {} : { input: input2 }
@@ -32021,20 +32321,20 @@ function warnMissingWorkspaceOwnership(repoPath) {
32021
32321
  );
32022
32322
  }
32023
32323
  function defaultWorktreePath(mainRepo, ws) {
32024
- return import_node_path70.default.join(mainRepo, "trees", ws);
32324
+ return import_node_path71.default.join(mainRepo, "trees", ws);
32025
32325
  }
32026
32326
  function assertDefaultWorktreeRootIgnored(mainRepo) {
32027
- const ignored = (0, import_node_child_process41.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
32327
+ const ignored = (0, import_node_child_process43.spawnSync)("git", ["-C", mainRepo, "check-ignore", "-q", "--no-index", "trees/"], {
32028
32328
  encoding: "utf-8"
32029
32329
  });
32030
32330
  if (ignored.status !== 0) {
32031
32331
  throw new Error(
32032
- `Default worktree root '${import_node_path70.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path70.default.join(mainRepo, ".gitignore")}' or use --path.`
32332
+ `Default worktree root '${import_node_path71.default.join(mainRepo, "trees")}' is not ignored. Add 'trees/' to '${import_node_path71.default.join(mainRepo, ".gitignore")}' or use --path.`
32033
32333
  );
32034
32334
  }
32035
32335
  }
32036
32336
  function legacyDefaultWorktreePath(mainRepo, ws) {
32037
- return import_node_path70.default.join(import_node_path70.default.dirname(mainRepo), `${import_node_path70.default.basename(mainRepo)}-${ws}`);
32337
+ return import_node_path71.default.join(import_node_path71.default.dirname(mainRepo), `${import_node_path71.default.basename(mainRepo)}-${ws}`);
32038
32338
  }
32039
32339
  function teardownFallbackPath(mainRepo, workspace) {
32040
32340
  const candidates = [
@@ -32180,7 +32480,7 @@ async function workspaceUp(branch, opts = {}) {
32180
32480
  const generatedPath = candidatePaths.find(
32181
32481
  (candidate) => !import_node_fs57.default.existsSync(candidate) && !worktrees.some((worktree) => sameWorkspacePath(worktree.path, candidate))
32182
32482
  );
32183
- const selectedPath = opts.path ? import_node_path70.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
32483
+ const selectedPath = opts.path ? import_node_path71.default.resolve(opts.path) : existingBranch?.path ?? generatedPath;
32184
32484
  if (!selectedPath) {
32185
32485
  throw new Error(`Could not allocate a collision-safe worktree path for branch '${branch}'.`);
32186
32486
  }
@@ -32208,11 +32508,11 @@ async function workspaceUp(branch, opts = {}) {
32208
32508
  return selectedPath;
32209
32509
  }
32210
32510
  if (!opts.path) assertDefaultWorktreeRootIgnored(mainRepo);
32211
- const add = (0, import_node_child_process41.spawnSync)("git", ["-C", mainRepo, "worktree", "add", selectedPath, branch], {
32511
+ const add = (0, import_node_child_process43.spawnSync)("git", ["-C", mainRepo, "worktree", "add", selectedPath, branch], {
32212
32512
  encoding: "utf-8"
32213
32513
  });
32214
32514
  if (add.status !== 0) {
32215
- const addNew = (0, import_node_child_process41.spawnSync)(
32515
+ const addNew = (0, import_node_child_process43.spawnSync)(
32216
32516
  "git",
32217
32517
  ["-C", mainRepo, "worktree", "add", "-b", branch, selectedPath],
32218
32518
  { encoding: "utf-8" }
@@ -32353,7 +32653,7 @@ async function runWorkspaceLifecycle(action2, target, opts = {}) {
32353
32653
  const result = await mutateWorkspaceRuntime("delete", resolved2, worktrees, opts.quiet);
32354
32654
  if (removeWorktree) {
32355
32655
  if (resolved2.worktree && !resolved2.worktree.prunable && import_node_fs57.default.existsSync(resolved2.worktreePath)) {
32356
- const rm = (0, import_node_child_process41.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved2.worktreePath], {
32656
+ const rm = (0, import_node_child_process43.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", resolved2.worktreePath], {
32357
32657
  encoding: "utf-8"
32358
32658
  });
32359
32659
  if (rm.status !== 0) {
@@ -32397,7 +32697,7 @@ async function reclaimWorktreeExactPath(mainRepo, worktreePath, verify, afterRem
32397
32697
  );
32398
32698
  steps.push("runtime-deleted");
32399
32699
  }
32400
- const rm = (0, import_node_child_process41.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", worktreePath], {
32700
+ const rm = (0, import_node_child_process43.spawnSync)("git", ["-C", mainRepo, "worktree", "remove", worktreePath], {
32401
32701
  encoding: "utf-8"
32402
32702
  });
32403
32703
  if (rm.status !== 0) {
@@ -32418,13 +32718,13 @@ async function workspaceStop(target, opts = {}) {
32418
32718
  async function workspaceDown(target, opts = {}) {
32419
32719
  return runWorkspaceLifecycle("down", target, opts);
32420
32720
  }
32421
- var import_node_child_process41, import_node_fs57, import_node_path70, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
32721
+ var import_node_child_process43, import_node_fs57, import_node_path71, WORKSPACE_ALLOCATION_LOCK_WAIT_MS;
32422
32722
  var init_workspace_lifecycle = __esm({
32423
32723
  "src/core/workspace-lifecycle.ts"() {
32424
32724
  "use strict";
32425
- import_node_child_process41 = require("child_process");
32725
+ import_node_child_process43 = require("child_process");
32426
32726
  import_node_fs57 = __toESM(require("fs"));
32427
- import_node_path70 = __toESM(require("path"));
32727
+ import_node_path71 = __toESM(require("path"));
32428
32728
  init_devpod_mutation();
32429
32729
  init_devpod_workspaces();
32430
32730
  init_devsy_mutation();
@@ -32443,7 +32743,7 @@ var init_workspace_lifecycle = __esm({
32443
32743
 
32444
32744
  // src/core/worktree-trim.ts
32445
32745
  function listIgnoredPaths(worktreePath) {
32446
- const result = (0, import_node_child_process42.spawnSync)(
32746
+ const result = (0, import_node_child_process44.spawnSync)(
32447
32747
  "git",
32448
32748
  [
32449
32749
  "-C",
@@ -32527,8 +32827,8 @@ function buildTrimManifest(report, patterns, createdAtEpoch) {
32527
32827
  return canonicalJson(manifest);
32528
32828
  }
32529
32829
  function isInside(parent, child) {
32530
- const relative2 = import_node_path71.default.relative(parent, child);
32531
- return relative2 !== "" && !relative2.startsWith("..") && !import_node_path71.default.isAbsolute(relative2);
32830
+ const relative2 = import_node_path72.default.relative(parent, child);
32831
+ return relative2 !== "" && !relative2.startsWith("..") && !import_node_path72.default.isAbsolute(relative2);
32532
32832
  }
32533
32833
  function exists(file) {
32534
32834
  try {
@@ -32539,7 +32839,7 @@ function exists(file) {
32539
32839
  }
32540
32840
  }
32541
32841
  function hasTrackedFiles(worktreePath, relative2) {
32542
- const result = (0, import_node_child_process42.spawnSync)(
32842
+ const result = (0, import_node_child_process44.spawnSync)(
32543
32843
  "git",
32544
32844
  ["-C", worktreePath, "--literal-pathspecs", "ls-files", "-z", "--", relative2],
32545
32845
  { encoding: "utf-8", env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" } }
@@ -32547,7 +32847,7 @@ function hasTrackedFiles(worktreePath, relative2) {
32547
32847
  return result.status !== 0 || Boolean(result.error) || result.stdout.length > 0;
32548
32848
  }
32549
32849
  function trimOnePath(candidate, relative2, patterns, steps) {
32550
- const target = import_node_path71.default.join(candidate.path, relative2);
32850
+ const target = import_node_path72.default.join(candidate.path, relative2);
32551
32851
  let stat;
32552
32852
  try {
32553
32853
  stat = import_node_fs58.default.lstatSync(target);
@@ -32615,7 +32915,7 @@ async function applyTrimManifest(options, dependencies) {
32615
32915
  continue;
32616
32916
  }
32617
32917
  const listed = candidate.paths ?? [];
32618
- if (listed.every((relative2) => !exists(import_node_path71.default.join(candidate.path, relative2))) && matchingReceipt(candidate, manifestSha256)) {
32918
+ if (listed.every((relative2) => !exists(import_node_path72.default.join(candidate.path, relative2))) && matchingReceipt(candidate, manifestSha256)) {
32619
32919
  outcome.status = "already-done";
32620
32920
  continue;
32621
32921
  }
@@ -32639,7 +32939,7 @@ async function applyTrimManifest(options, dependencies) {
32639
32939
  if (!options.yes) {
32640
32940
  verify();
32641
32941
  outcome.status = "would-trim";
32642
- const present = listed.filter((relative2) => exists(import_node_path71.default.join(candidate.path, relative2)));
32942
+ const present = listed.filter((relative2) => exists(import_node_path72.default.join(candidate.path, relative2)));
32643
32943
  outcome.reason = present.length ? `would remove: ${present.join(", ")}` : "nothing present";
32644
32944
  continue;
32645
32945
  }
@@ -32676,13 +32976,13 @@ async function applyTrimManifest(options, dependencies) {
32676
32976
  }
32677
32977
  return { manifestSha256, dryRun: !options.yes, targets, stoppedEarly };
32678
32978
  }
32679
- var import_node_child_process42, import_node_fs58, import_node_path71;
32979
+ var import_node_child_process44, import_node_fs58, import_node_path72;
32680
32980
  var init_worktree_trim = __esm({
32681
32981
  "src/core/worktree-trim.ts"() {
32682
32982
  "use strict";
32683
- import_node_child_process42 = require("child_process");
32983
+ import_node_child_process44 = require("child_process");
32684
32984
  import_node_fs58 = __toESM(require("fs"));
32685
- import_node_path71 = __toESM(require("path"));
32985
+ import_node_path72 = __toESM(require("path"));
32686
32986
  init_reliability_lifecycle();
32687
32987
  init_worktree_reclaim();
32688
32988
  init_worktree_safety();
@@ -32778,7 +33078,7 @@ function runWorkspaceCleanupCommand(options) {
32778
33078
  throw new Error("--manifest requires --all-worktrees and --check-merged.");
32779
33079
  }
32780
33080
  const createdAtEpoch = Math.floor(Date.now() / 1e3);
32781
- const worktrees = import_node_fs59.default.existsSync(import_node_path72.default.join(repoPath, ".devrouter.yml")) ? loadRepoConfig(repoPath).worktrees : void 0;
33081
+ const worktrees = import_node_fs59.default.existsSync(import_node_path73.default.join(repoPath, ".devrouter.yml")) ? loadRepoConfig(repoPath).worktrees : void 0;
32782
33082
  const manifest = action2 === "trim" ? buildTrimManifest(
32783
33083
  report,
32784
33084
  { trim: worktrees?.trim ?? [], disposable: worktrees?.disposable },
@@ -32872,13 +33172,34 @@ function runWorkspaceLeftoverCommand(options) {
32872
33172
  {
32873
33173
  dryRun: report.dryRun,
32874
33174
  stoppedEarly: false,
32875
- targets: report.targets.map((target) => ({ ...target, path: target.project }))
33175
+ targets: report.targets.map((target) => ({
33176
+ ...target,
33177
+ path: target.project,
33178
+ steps: summarizeLeftoverSteps(target.steps)
33179
+ }))
32876
33180
  },
32877
33181
  "remove the leftovers"
32878
33182
  );
33183
+ if (!report.dryRun && report.targets.some((target) => target.steps.length)) {
33184
+ process.stdout.write(
33185
+ "Each removed object is listed in the stack's receipt; use --json for the full list.\n"
33186
+ );
33187
+ }
32879
33188
  }
32880
33189
  if (report.targets.some((target) => target.status === "failed")) process.exitCode = 1;
32881
33190
  }
33191
+ function summarizeLeftoverSteps(steps) {
33192
+ const counts = /* @__PURE__ */ new Map();
33193
+ for (const step of steps) {
33194
+ const [action2, kind] = step.slice(0, step.indexOf(":")).split("-");
33195
+ const byKind = counts.get(action2) ?? /* @__PURE__ */ new Map();
33196
+ byKind.set(kind, (byKind.get(kind) ?? 0) + 1);
33197
+ counts.set(action2, byKind);
33198
+ }
33199
+ return [...counts].map(
33200
+ ([action2, byKind]) => `${action2 === "gone" ? "already gone" : action2} ${[...byKind].map(([kind, count]) => `${count} ${kind}(s)`).join(", ")}`
33201
+ );
33202
+ }
32882
33203
  function runWorkspaceManifestSelectCommand(file, options) {
32883
33204
  process.stdout.write(selectManifest(file, options.expectedSha256, options.id));
32884
33205
  }
@@ -32927,12 +33248,12 @@ async function runWorkspaceTrimCommand(options) {
32927
33248
  process.exitCode = 1;
32928
33249
  }
32929
33250
  }
32930
- var import_node_fs59, import_node_path72;
33251
+ var import_node_fs59, import_node_path73;
32931
33252
  var init_workspace2 = __esm({
32932
33253
  "src/commands/workspace.ts"() {
32933
33254
  "use strict";
32934
33255
  import_node_fs59 = __toESM(require("fs"));
32935
- import_node_path72 = __toESM(require("path"));
33256
+ import_node_path73 = __toESM(require("path"));
32936
33257
  init_docker_leftovers();
32937
33258
  init_output();
32938
33259
  init_repo_config();
@@ -32985,7 +33306,7 @@ var init_version = __esm({
32985
33306
 
32986
33307
  // src/cli.ts
32987
33308
  var import_commander = require("commander");
32988
- var CLI_VERSION = true ? "0.3.0" : "0.0.0-dev";
33309
+ var CLI_VERSION = true ? "0.4.0" : "0.0.0-dev";
32989
33310
  var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
32990
33311
  function withErrorHandling(action2) {
32991
33312
  return async (...args) => {
@@ -33111,6 +33432,15 @@ program.command("exec").description("Run one literal command inside the exact ch
33111
33432
  await runExecCommand2(invocation);
33112
33433
  })
33113
33434
  );
33435
+ program.command("env").description(
33436
+ "Report one checkout's host-reachable URLs, TCP endpoints and CA path, or run a host command with them"
33437
+ ).argument("[args...]", "[path] [--json] | [path] -- <command...>").allowUnknownOption(true).action(
33438
+ withErrorHandling(async () => {
33439
+ const commandIndex = process.argv.indexOf("env", 2);
33440
+ const { parseEnvInvocation: parseEnvInvocation2, runEnvCommand: runEnvCommand2 } = await Promise.resolve().then(() => (init_env(), env_exports));
33441
+ await runEnvCommand2(parseEnvInvocation2(process.argv.slice(commandIndex + 1)));
33442
+ })
33443
+ );
33114
33444
  program.command("down").description("Stop the shared Traefik router stack").action(
33115
33445
  withErrorHandling(async () => {
33116
33446
  const { runDownCommand: runDownCommand2 } = await Promise.resolve().then(() => (init_down(), down_exports));
@@ -33447,10 +33777,10 @@ workspaceCommand2.command("down").description("Delete a workspace's runtime and
33447
33777
  workspaceCommand2.command("journal").description("Inspect and recover workspace lifecycle journals").command("settle").description(
33448
33778
  "Settle an interrupted lifecycle operation as unobservable so ensure and stop can proceed"
33449
33779
  ).argument("[path]", "Checkout path (defaults to current directory)").option("--json", "Output JSON").action(
33450
- withErrorHandling(async (path72, _options, command) => {
33780
+ withErrorHandling(async (path73, _options, command) => {
33451
33781
  const options = command.opts();
33452
33782
  const { runWorkspaceJournalSettleCommand: runWorkspaceJournalSettleCommand2 } = await Promise.resolve().then(() => (init_workspace2(), workspace_exports));
33453
- await runWorkspaceJournalSettleCommand2({ ...path72 ? { path: path72 } : {}, ...options });
33783
+ await runWorkspaceJournalSettleCommand2({ ...path73 ? { path: path73 } : {}, ...options });
33454
33784
  })
33455
33785
  );
33456
33786
  function parseVersionRequest(argv) {