@devrouter/cli 0.0.23 → 0.0.25

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.
@@ -158,32 +158,32 @@ This folder is managed by the devrouter CLI.
158
158
 
159
159
  ## Commands
160
160
 
161
- - dev init [--write-agents] [--write-skill] [--with-linear]
162
- - dev -V [--repo <path>] (installed/local version + next upgrade)
163
- - dev upgrade [version] [--repo <path>]
164
- - dev setup --yes [--repo <path>] [--json]
165
- - dev up
166
- - dev down
167
- - dev status
168
- - dev doctor
169
- - dev ls
170
- - dev open <name>
171
- - dev logs [-f] [--tail N]
172
- - dev repo init
173
- - dev repo inspect [--repo <path>] [--json]
174
- - dev repo devcontainer write [--repo <path>] [--dry-run] [--yes] [--json]
175
- - dev repo devcontainer verify [--repo <path>] [--live] [--yes] [--json]
176
- - dev repo agents [--with-linear]
177
- - dev app add --name <name> --host <host.localhost> --protocol <http|tcp> --runtime <host|docker>
178
- - dev app run <name>
179
- - dev app exec <name> [--shell] [--env <env>] -- <command>
180
- - dev app ls
181
- - dev app rm <name>
182
- - dev tls install
161
+ - devrouter init [--write-agents] [--write-skill] [--with-linear]
162
+ - devrouter -V [--repo <path>] (installed/local version + next upgrade)
163
+ - devrouter upgrade [version] [--repo <path>]
164
+ - devrouter setup --yes [--repo <path>] [--json]
165
+ - devrouter up
166
+ - devrouter down
167
+ - devrouter status
168
+ - devrouter doctor
169
+ - devrouter ls
170
+ - devrouter open <name>
171
+ - devrouter logs [-f] [--tail N]
172
+ - devrouter repo init
173
+ - devrouter repo inspect [--repo <path>] [--json]
174
+ - devrouter repo devcontainer write [--repo <path>] [--dry-run] [--yes] [--json]
175
+ - devrouter repo devcontainer verify [--repo <path>] [--live] [--yes] [--json]
176
+ - devrouter repo agents [--with-linear]
177
+ - devrouter app add --name <name> --host <host.localhost> --protocol <http|tcp> --runtime <host|docker>
178
+ - devrouter app run <name>
179
+ - devrouter app exec <name> [--shell] [--env <env>] -- <command>
180
+ - devrouter app ls
181
+ - devrouter app rm <name>
182
+ - devrouter tls install
183
183
 
184
184
  ## Troubleshooting
185
185
 
186
- If dev up fails with port conflicts on 80/443 (or TCP protocol ports), run:
186
+ If devrouter up fails with port conflicts on 80/443 (or TCP protocol ports), run:
187
187
 
188
188
  - lsof -nP -iTCP:80 -sTCP:LISTEN
189
189
  - lsof -nP -iTCP:443 -sTCP:LISTEN
@@ -552,29 +552,36 @@ function listHostRouteState() {
552
552
  return [];
553
553
  }
554
554
  return parsed.filter((item) => item && typeof item === "object").map((item) => item);
555
- } catch {
555
+ } catch (err) {
556
+ const error = err;
557
+ if (error.code !== "ENOENT") {
558
+ process.stderr.write(
559
+ `Warning: devrouter host routes state file is corrupted or unreadable (${error.message}). Recreating route state.
560
+ `
561
+ );
562
+ }
556
563
  return [];
557
564
  }
558
565
  }
559
- function upsertHostRoute(input3) {
566
+ function upsertHostRoute(input2) {
560
567
  return withStateLock(() => {
561
568
  const routes = listHostRouteState();
562
- const id = buildHostRouteId(input3.repoPath, input3.name);
569
+ const id = buildHostRouteId(input2.repoPath, input2.name);
563
570
  const existing = routes.find((route) => route.id === id);
564
571
  const now = (/* @__PURE__ */ new Date()).toISOString();
565
572
  const next = {
566
573
  id,
567
- name: input3.name,
568
- host: input3.host,
569
- protocol: input3.protocol ?? "http",
570
- tcpProtocol: input3.tcpProtocol,
571
- repoPath: input3.repoPath,
572
- port: input3.port,
573
- mode: input3.mode,
574
- upstreamHost: input3.upstreamHost,
575
- pid: input3.pid,
576
- command: input3.command,
577
- workspace: input3.workspace,
574
+ name: input2.name,
575
+ host: input2.host,
576
+ protocol: input2.protocol ?? "http",
577
+ tcpProtocol: input2.tcpProtocol,
578
+ repoPath: input2.repoPath,
579
+ port: input2.port,
580
+ mode: input2.mode,
581
+ upstreamHost: input2.upstreamHost,
582
+ pid: input2.pid,
583
+ command: input2.command,
584
+ workspace: input2.workspace,
578
585
  createdAt: existing?.createdAt ?? now,
579
586
  updatedAt: now
580
587
  };
@@ -716,6 +723,17 @@ var init_workspace = __esm({
716
723
  });
717
724
 
718
725
  // src/core/repo-config.ts
726
+ function compareSemver(a, b) {
727
+ const parse = (v) => {
728
+ const match = v.trim().match(/^v?(\d+)\.(\d+)\.(\d+)$/);
729
+ return match ? { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) } : { major: 0, minor: 0, patch: 0 };
730
+ };
731
+ const left = parse(a);
732
+ const right = parse(b);
733
+ if (left.major !== right.major) return left.major - right.major;
734
+ if (left.minor !== right.minor) return left.minor - right.minor;
735
+ return left.patch - right.patch;
736
+ }
719
737
  function assertUpstreamSpec(upstream, label) {
720
738
  if (upstream.includes(WORKSPACE_PLACEHOLDER)) {
721
739
  if (!UPSTREAM_TEMPLATE_RE.test(upstream.trim())) {
@@ -1136,7 +1154,22 @@ function loadRepoConfig(repoPath) {
1136
1154
  }
1137
1155
  const raw = import_node_fs4.default.readFileSync(configPath, "utf-8");
1138
1156
  const parsed = import_yaml2.default.parse(raw);
1139
- return parseConfig(parsed ?? {}, configPath);
1157
+ const config = parseConfig(parsed ?? {}, configPath);
1158
+ const requiredVersion = config.devrouter?.version;
1159
+ if (requiredVersion && !hasWarnedVersionMismatch) {
1160
+ const cliVersion = true ? "0.0.25" : "0.0.0-dev";
1161
+ if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
1162
+ hasWarnedVersionMismatch = true;
1163
+ process.stderr.write(
1164
+ `
1165
+ \u26A0\uFE0F Warning: The repository configuration requires devrouter version ${requiredVersion}, but you are running version ${cliVersion}.
1166
+ Please upgrade your CLI to avoid unexpected behavior: npm install -g @devrouter/cli
1167
+
1168
+ `
1169
+ );
1170
+ }
1171
+ }
1172
+ return config;
1140
1173
  }
1141
1174
  function initRepoConfig(repoPath, options = {}) {
1142
1175
  const resolvedRepoPath = resolveRepoPath(repoPath);
@@ -1455,7 +1488,7 @@ function resolveAppDependencies(config, app) {
1455
1488
  }
1456
1489
  return results;
1457
1490
  }
1458
- var import_node_fs4, import_node_path4, import_yaml2, CONFIG_FILE_NAME, DEFAULT_TCP_PROTOCOL, VALID_HOSTNAME_RE, DEVROUTER_VERSION_RE, VALID_ENV_NAME_RE, VALID_ENV_VAR_RE, UPSTREAM_TEMPLATE_RE, MAX_COMMAND_LENGTH, DEFAULT_HOST_STRATEGY;
1491
+ var import_node_fs4, import_node_path4, import_yaml2, hasWarnedVersionMismatch, CONFIG_FILE_NAME, DEFAULT_TCP_PROTOCOL, VALID_HOSTNAME_RE, DEVROUTER_VERSION_RE, VALID_ENV_NAME_RE, VALID_ENV_VAR_RE, UPSTREAM_TEMPLATE_RE, MAX_COMMAND_LENGTH, DEFAULT_HOST_STRATEGY;
1459
1492
  var init_repo_config = __esm({
1460
1493
  "src/core/repo-config.ts"() {
1461
1494
  "use strict";
@@ -1465,6 +1498,7 @@ var init_repo_config = __esm({
1465
1498
  init_host_routes();
1466
1499
  init_workspace();
1467
1500
  init_capabilities();
1501
+ hasWarnedVersionMismatch = false;
1468
1502
  CONFIG_FILE_NAME = ".devrouter.yml";
1469
1503
  DEFAULT_TCP_PROTOCOL = "postgres";
1470
1504
  VALID_HOSTNAME_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*\.localhost$/;
@@ -1482,13 +1516,13 @@ var init_repo_config = __esm({
1482
1516
  });
1483
1517
 
1484
1518
  // src/core/ai-prompt.ts
1485
- function normalizeEntriesJson(input3) {
1486
- if (!input3) {
1519
+ function normalizeEntriesJson(input2) {
1520
+ if (!input2) {
1487
1521
  return "<JSON_ARRAY_OF_APP_ENTRIES>";
1488
1522
  }
1489
1523
  let parsed;
1490
1524
  try {
1491
- parsed = JSON.parse(input3);
1525
+ parsed = JSON.parse(input2);
1492
1526
  } catch {
1493
1527
  throw new Error("--entries-json must be valid JSON.");
1494
1528
  }
@@ -1516,7 +1550,6 @@ function buildOnboardingPrompt(options = {}) {
1516
1550
  const repoPath = resolveRepoPath(options.repo);
1517
1551
  const entriesJson = normalizeEntriesJson(options.entriesJson);
1518
1552
  const projectName = repoPath.split(/[\\/]/).filter(Boolean).pop() ?? "repo";
1519
- const withLinear = Boolean(options.withLinear);
1520
1553
  return [
1521
1554
  "You are adapting an existing repository to devrouter using the unified .devrouter.yml model.",
1522
1555
  "",
@@ -1536,7 +1569,7 @@ function buildOnboardingPrompt(options = {}) {
1536
1569
  "",
1537
1570
  "Top-level .devrouter.yml schema:",
1538
1571
  "- version: 1 (required)",
1539
- "- devrouter.version: semantic version string (recommended; required for `dev -V`/`dev upgrade`)",
1572
+ "- devrouter.version: semantic version string (recommended; required for `devrouter -V`/`devrouter upgrade`)",
1540
1573
  "- project.name: string (optional)",
1541
1574
  `- secretManager.command: string (optional; SM command including trailing \`--\` boundary; supports \`${SECRET_MANAGER_ENV_PLACEHOLDER}\` template placeholder)`,
1542
1575
  `- secretManager.defaultEnv: string (optional; fallback env for \`${SECRET_MANAGER_ENV_PLACEHOLDER}\` template; required when command contains \`${SECRET_MANAGER_ENV_PLACEHOLDER}\`)`,
@@ -1603,20 +1636,20 @@ function buildOnboardingPrompt(options = {}) {
1603
1636
  '- Example healthcheck for postgres: `test: ["CMD-SHELL", "pg_isready -U <user> -d <db>"]` with `interval: 5s`, `timeout: 3s`, `retries: 20`.',
1604
1637
  "",
1605
1638
  "Runtime behavior to account for:",
1606
- "- Docker dependencies can be auto-started by dev app run.",
1607
- "- dev app run waits for Docker dependencies to become healthy before starting the host or docker app.",
1608
- "- Docker dependencies are automatically stopped when a host app exits (Ctrl+C or error); docker app services remain running until explicit cleanup (`docker compose down`, `dev down`, or equivalent).",
1639
+ "- Docker dependencies can be auto-started by devrouter app run.",
1640
+ "- devrouter app run waits for Docker dependencies to become healthy before starting the host or docker app.",
1641
+ "- Docker dependencies are automatically stopped when a host app exits (Ctrl+C or error); docker app services remain running until explicit cleanup (`docker compose down`, `devrouter down`, or equivalent).",
1609
1642
  "- Recent dependency logs (last 20 lines) are printed after dependencies start.",
1610
1643
  "- Host-runtime dependencies are NOT auto-started in v1 (must be started manually).",
1611
- "- kind=dependency entries are dependency-only: they do not create routes and cannot be direct targets for `dev app run`, `dev app exec`, or `dev open`.",
1644
+ "- kind=dependency entries are dependency-only: they do not create routes and cannot be direct targets for `devrouter app run`, `devrouter app exec`, or `devrouter open`.",
1612
1645
  "- kind=dependency services are started/stopped as declared in compose (no Traefik labels added, no env/port injection).",
1613
1646
  `- For TCP dependencies of host apps, devrouter publishes a random host port and injects per-dep deterministic vars: \`{PREFIX}_HOST=localhost\`, \`{PREFIX}_PORT=<port>\`, \`{PREFIX}_URL\` (protocol-specific), \`{PREFIX}_SHADOW_URL\` (postgres only). \`{PREFIX} = dep.name.toUpperCase().replace(/-/g, "_")\`.`,
1614
1647
  "- Config-level `envMap` on dependency references aliases per-dep vars to app-expected names. Example: `envMap: { DATABASE_URL: DB_URL }` maps per-dep `DB_URL` to `DATABASE_URL` in the app process.",
1615
1648
  "- If the repo's Postgres docker-compose service uses different credentials than the injected defaults (`prisma:prisma`), flag this to the user and recommend aligning the compose env vars or using `envMap` aliasing.",
1616
1649
  "- Postgres multiplexing on shared :5432 requires TLS/SNI (useful for psql 17+, pgAdmin, not standard app clients).",
1617
- "- When TLS is enabled, `dev app run` and `dev app exec` auto-refresh cert SAN coverage for configured repo hosts before startup.",
1650
+ "- When TLS is enabled, `devrouter app run` and `devrouter app exec` auto-refresh cert SAN coverage for configured repo hosts before startup.",
1618
1651
  "- For TCP/Postgres, standard app frameworks should use the injected port env vars; only direct-TLS-capable tools should use sslmode=require on :5432.",
1619
- "- `dev app exec <name> -- <command>` starts dependencies as needed, resolves env vars, and runs a one-shot command with the resolved env. Exec stops only dependencies started by that invocation (already-running dependencies stay running). If ownership detection fails, exec leaves selected dependencies running to avoid non-owned teardown. Exec preserves argv semantics by default (`shell: false`) and supports explicit shell mode via `--shell` when needed.",
1652
+ "- `devrouter app exec <name> -- <command>` starts dependencies as needed, resolves env vars, and runs a one-shot command with the resolved env. Exec stops only dependencies started by that invocation (already-running dependencies stay running). If ownership detection fails, exec leaves selected dependencies running to avoid non-owned teardown. Exec preserves argv semantics by default (`shell: false`) and supports explicit shell mode via `--shell` when needed.",
1620
1653
  "- `envMap` on dependency references (config-level) aliases per-dep vars after dependency env resolution. `envMap` fails fast when source var is missing.",
1621
1654
  "",
1622
1655
  "Workspace isolation (parallel git worktrees / agents):",
@@ -1624,11 +1657,11 @@ function buildOnboardingPrompt(options = {}) {
1624
1657
  "- Token resolution precedence: `--workspace <slug>` flag > `DEVROUTER_WORKSPACE` env var > auto-derived from a linked git worktree branch (sanitized: lowercase, non-alphanumeric \u2192 `-`, capped at 32 chars) > none. The primary checkout resolves to no token and routes exactly as before (fully back-compatible).",
1625
1658
  `- 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. The runtime config is computed in memory only \u2014 the committed \`.devrouter.yml\` is never rewritten.`,
1626
1659
  "- 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.",
1627
- "- Lifecycle: `dev workspace up <branch>` (create worktree + devpod + routes), `dev workspace ls` (list worktrees/tokens/route counts), `dev workspace down <workspace|branch>` (free routes + stop devpod + remove worktree). `dev doctor` reports orphaned workspace proxy routes whose worktree dir was removed without `dev workspace down`.",
1660
+ "- Lifecycle: `devrouter workspace up <branch>` (create worktree + devpod + routes), `devrouter workspace ls` (list worktrees/tokens/route counts), `devrouter workspace down <workspace|branch>` (free routes + stop devpod + remove worktree). `devrouter doctor` reports orphaned workspace proxy routes whose worktree dir was removed without `devrouter workspace down`.",
1628
1661
  `- devcontainer integration: the devcontainer compose service exposes a devnet alias \`${WORKSPACE_PLACEHOLDER}-app\` (default \`WORKSPACE=<project>\` in \`devcontainer.env\`), and the proxy app uses \`upstream: ${WORKSPACE_PLACEHOLDER}-app:<port>\`. Spinning up workspace \`feat-a\` \u2192 alias \`feat-a-app\`, host \`app.feat-a.localhost\`.`,
1629
1662
  "",
1630
1663
  "Secret Manager Integration (config-based):",
1631
- "- Optional top-level `secretManager.command` in `.devrouter.yml` wraps `dev app run` and `dev app exec` commands with the SM command and re-applies devrouter-injected dep env vars after the SM boundary via `env KEY=VAL` prefix.",
1664
+ "- Optional top-level `secretManager.command` in `.devrouter.yml` wraps `devrouter app run` and `devrouter app exec` commands with the SM command and re-applies devrouter-injected dep env vars after the SM boundary via `env KEY=VAL` prefix.",
1632
1665
  `- Example config: \`secretManager: { command: "infisical run --env ${SECRET_MANAGER_ENV_PLACEHOLDER} --", defaultEnv: "dev" }\`.`,
1633
1666
  `- \`${SECRET_MANAGER_ENV_PLACEHOLDER}\` template placeholder in \`secretManager.command\` is resolved at runtime. \`defaultEnv\` provides the fallback; \`--env\` CLI flag overrides it.`,
1634
1667
  "- When configured, the effective command becomes: `<secretManager.command> env {PREFIX}_URL=<val> ... <user-command>`.",
@@ -1641,40 +1674,40 @@ function buildOnboardingPrompt(options = {}) {
1641
1674
  "- Secret managers may also define DB variables. Do not assume secret-manager precedence. Confirm the effective values in the command process before migrate/seed.",
1642
1675
  "- For apps that require legacy names like `DATABASE_URL`, use config-level `envMap` on the dependency reference: `envMap: { DATABASE_URL: DB_URL }`.",
1643
1676
  "- Avoid pre-wrapper DB assignments such as `DATABASE_URI=... <wrapper> run -- ...`; wrapper-managed env may override those values.",
1644
- "- `dev doctor --repo <REPO_PATH>` warns on risky pre-wrapper DB assignments before `run --` for host apps that depend on postgres.",
1645
- "- With TLS enabled, `dev doctor --repo <REPO_PATH>` also warns on cert SAN mismatches for configured hosts (`repo.tls-host-coverage`).",
1646
- "- Robust one-shot migrate example (argv-safe, no nested shell quoting): `dev app exec <name> --repo <REPO_PATH> --yes -- infisical run --projectId <id> --env=<env> -- pnpm payload migrate`.",
1647
- "- Robust one-shot seed example: `dev app exec <name> --repo <REPO_PATH> --yes -- infisical run --projectId <id> --env=<env> -- pnpm payload seed`.",
1648
- "- Environment probe/debug example (run before migrations): `dev app exec <name> --repo <REPO_PATH> --yes -- printenv DB_URL DB_HOST DB_PORT DB_SHADOW_URL`.",
1677
+ "- `devrouter doctor --repo <REPO_PATH>` warns on risky pre-wrapper DB assignments before `run --` for host apps that depend on postgres.",
1678
+ "- With TLS enabled, `devrouter doctor --repo <REPO_PATH>` also warns on cert SAN mismatches for configured hosts (`repo.tls-host-coverage`).",
1679
+ "- Robust one-shot migrate example (argv-safe, no nested shell quoting): `devrouter app exec <name> --repo <REPO_PATH> --yes -- infisical run --projectId <id> --env=<env> -- pnpm payload migrate`.",
1680
+ "- Robust one-shot seed example: `devrouter app exec <name> --repo <REPO_PATH> --yes -- infisical run --projectId <id> --env=<env> -- pnpm payload seed`.",
1681
+ "- Environment probe/debug example (run before migrations): `devrouter app exec <name> --repo <REPO_PATH> --yes -- printenv DB_URL DB_HOST DB_PORT DB_SHADOW_URL`.",
1649
1682
  "- Use `--shell` only when shell expansion is required; it must receive exactly one command string after `--`.",
1650
1683
  "- Warning: Do not run migration/seed until env probe confirms expected DB variables and values.",
1651
1684
  "",
1652
1685
  "Required workflow:",
1653
- "1) Run `dev setup --yes --json` for devrouter-owned machine state; use `dev doctor --repo <REPO_PATH> --json` to diagnose missing prerequisites without mutation.",
1654
- "2) Run `dev repo inspect --repo <REPO_PATH> --json` before editing files.",
1655
- "3) For the supported Node/pnpm/Postgres devcontainer shape, run `dev repo devcontainer write --repo <REPO_PATH> --dry-run --json`, review the plan, then run `dev repo devcontainer write --repo <REPO_PATH> --yes`.",
1686
+ "1) Run `devrouter setup --yes --json` for devrouter-owned machine state; use `devrouter doctor --repo <REPO_PATH> --json` to diagnose missing prerequisites without mutation.",
1687
+ "2) Run `devrouter repo inspect --repo <REPO_PATH> --json` before editing files.",
1688
+ "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`.",
1656
1689
  "4) For unsupported shapes or custom existing files, make minimal manual edits and explain the assumptions.",
1657
- "5) Verify static evidence with `dev repo devcontainer verify --repo <REPO_PATH> --json`; after the devcontainer is running, use `dev repo devcontainer verify --repo <REPO_PATH> --live --yes --json` for route probes.",
1690
+ "5) Verify static evidence with `devrouter repo devcontainer verify --repo <REPO_PATH> --json`; after the devcontainer is running, use `devrouter repo devcontainer verify --repo <REPO_PATH> --live --yes --json` for route probes.",
1658
1691
  "6) Keep edits minimal, explicit, and idempotent. Do not modify unrelated services.",
1659
1692
  "7) If required info is missing or ambiguous, stop and ask targeted questions.",
1660
1693
  "",
1661
1694
  "Validation commands to run/report for the devcontainer path:",
1662
- "- dev setup --yes --json",
1663
- "- dev doctor --repo <REPO_PATH> --json",
1664
- "- dev repo inspect --repo <REPO_PATH> --json",
1665
- "- dev repo devcontainer write --repo <REPO_PATH> --dry-run --json",
1666
- "- dev repo devcontainer write --repo <REPO_PATH> --yes",
1667
- "- dev repo devcontainer verify --repo <REPO_PATH> --json",
1668
- "- After `devpod up <REPO_PATH>`: dev repo devcontainer verify --repo <REPO_PATH> --live --yes --json",
1695
+ "- devrouter setup --yes --json",
1696
+ "- devrouter doctor --repo <REPO_PATH> --json",
1697
+ "- devrouter repo inspect --repo <REPO_PATH> --json",
1698
+ "- devrouter repo devcontainer write --repo <REPO_PATH> --dry-run --json",
1699
+ "- devrouter repo devcontainer write --repo <REPO_PATH> --yes",
1700
+ "- devrouter repo devcontainer verify --repo <REPO_PATH> --json",
1701
+ "- After `devpod up <REPO_PATH>`: devrouter repo devcontainer verify --repo <REPO_PATH> --live --yes --json",
1669
1702
  "",
1670
1703
  "Validation commands to run/report for host/docker runtime apps:",
1671
- "- dev setup --yes --json",
1672
- "- dev doctor --repo <REPO_PATH> --json",
1673
- "- dev app ls --repo <REPO_PATH>",
1674
- "- For each entry (when safe): dev app run <name> --repo <REPO_PATH> --yes",
1675
- "- Run one-shot commands with dep env: dev app exec <name> --repo <REPO_PATH> --yes -- <command>",
1676
- "- Probe effective env before migration/seed: dev app exec <name> --repo <REPO_PATH> --yes -- printenv DB_URL DB_HOST DB_PORT DB_SHADOW_URL",
1677
- "- dev ls",
1704
+ "- devrouter setup --yes --json",
1705
+ "- devrouter doctor --repo <REPO_PATH> --json",
1706
+ "- devrouter app ls --repo <REPO_PATH>",
1707
+ "- For each entry (when safe): devrouter app run <name> --repo <REPO_PATH> --yes",
1708
+ "- Run one-shot commands with dep env: devrouter app exec <name> --repo <REPO_PATH> --yes -- <command>",
1709
+ "- Probe effective env before migration/seed: devrouter app exec <name> --repo <REPO_PATH> --yes -- printenv DB_URL DB_HOST DB_PORT DB_SHADOW_URL",
1710
+ "- devrouter ls",
1678
1711
  "- For HTTP entries: curl -I http://<host>",
1679
1712
  '- For TCP postgres entries: provide connection hint (example: psql "... sslmode=require")',
1680
1713
  "",
@@ -1687,28 +1720,10 @@ function buildOnboardingPrompt(options = {}) {
1687
1720
  "6) Unresolved questions/risks (if any).",
1688
1721
  "7) Definition-of-done checklist status:",
1689
1722
  " - .devrouter.yml exists and validates",
1690
- " - dev app ls matches expected entries",
1691
- " - dev ls exposes expected endpoints",
1723
+ " - devrouter app ls matches expected entries",
1724
+ " - devrouter ls exposes expected endpoints",
1692
1725
  " - HTTP routes reachable",
1693
1726
  " - TCP Postgres route configured with TLS requirement noted",
1694
- ...withLinear ? [
1695
- "",
1696
- "Linear milestone workflow (enabled via --with-linear):",
1697
- "- Before creating/updating Linear issues, confirm repository mapping basics with the user:",
1698
- " - Which Linear workspace does this repository belong to?",
1699
- " - Which Linear team owns this repository? (optional team key)",
1700
- " - Which Linear project should this work use? (optional project id)",
1701
- "- When `--with-linear` is used together with AGENTS write flows, persist answers into the managed AGENTS block between:",
1702
- " - `<!-- devrouter-linear-workflow-config:start -->`",
1703
- " - `<!-- devrouter-linear-workflow-config:end -->`",
1704
- "- If placeholders are present in that block, ask these questions again and update the mapping.",
1705
- "- While implementing Linear-tracked work, set issue status at session start and at each phase transition.",
1706
- "- Post progress comments at meaningful checkpoints during implementation (not only at the end).",
1707
- "- Before ending a session, post a final recap comment with completed work, remaining work, risks, and next step, then re-check status/comment freshness.",
1708
- "- Optional bootstrap commands for repo artifacts: `dev init --repo <REPO_PATH> --with-linear --write-agents --write-skill` or `dev repo agents --repo <REPO_PATH> --with-linear`.",
1709
- "- If the repository uses devrouter, keep `.devrouter.yml` metadata `devrouter.version` updated and run `dev -V` to verify installed/local versions plus the next target.",
1710
- "- Resolve adaptation prompts with `dev upgrade` (list targets) and `dev upgrade <version>` (target prompt), sourced from `upgrade-prompts/<version>.md` in the devrouter release."
1711
- ] : [],
1712
1727
  "",
1713
1728
  renderCommandIntentSection()
1714
1729
  ].join("\n");
@@ -1721,93 +1736,93 @@ var init_ai_prompt = __esm({
1721
1736
  init_repo_config();
1722
1737
  COMMAND_INTENTS = [
1723
1738
  {
1724
- command: "dev init [--with-linear]",
1739
+ command: "devrouter init",
1725
1740
  purpose: "Print the AI onboarding prompt template for a repository (non-mutating by default)."
1726
1741
  },
1727
1742
  {
1728
- command: "dev -V",
1743
+ command: "devrouter -V",
1729
1744
  purpose: "Show installed CLI version, local repo version, and next upgrade target."
1730
1745
  },
1731
1746
  {
1732
- command: "dev upgrade [version]",
1747
+ command: "devrouter upgrade [version]",
1733
1748
  purpose: "Show upgrade targets from .devrouter.yml devrouter.version and print target adaptation prompt."
1734
1749
  },
1735
1750
  {
1736
- command: "dev setup",
1751
+ command: "devrouter setup",
1737
1752
  purpose: "Run first-time devrouter machine setup and report structured diagnostics."
1738
1753
  },
1739
1754
  {
1740
- command: "dev up",
1755
+ command: "devrouter up",
1741
1756
  purpose: "Start shared Traefik and ensure the shared devnet network."
1742
1757
  },
1743
- { command: "dev down", purpose: "Stop the shared Traefik router stack." },
1758
+ { command: "devrouter down", purpose: "Stop the shared Traefik router stack." },
1744
1759
  {
1745
- command: "dev status",
1760
+ command: "devrouter status",
1746
1761
  purpose: "Show router/container/network/TLS health and bound ports."
1747
1762
  },
1748
1763
  {
1749
- command: "dev doctor",
1764
+ command: "devrouter doctor",
1750
1765
  purpose: "Run deep diagnostics across global router state and repo config."
1751
1766
  },
1752
1767
  {
1753
- command: "dev ls",
1768
+ command: "devrouter ls",
1754
1769
  purpose: "List active HTTP and TCP routes resolved by devrouter."
1755
1770
  },
1756
1771
  {
1757
- command: "dev open <name>",
1772
+ command: "devrouter open <name>",
1758
1773
  purpose: "Open HTTP routes or print connection hints for TCP routes (match app/service/host)."
1759
1774
  },
1760
1775
  {
1761
- command: "dev tls install",
1776
+ command: "devrouter tls install",
1762
1777
  purpose: "Install mkcert certs and enable TLS/HTTPS for local routing."
1763
1778
  },
1764
1779
  {
1765
- command: "dev repo init",
1780
+ command: "devrouter repo init",
1766
1781
  purpose: "Create `.devrouter.yml` in a target repository."
1767
1782
  },
1768
1783
  {
1769
- command: "dev repo inspect",
1784
+ command: "devrouter repo inspect",
1770
1785
  purpose: "Inspect package, scripts, compose services, env names, devcontainer, devrouter config, and agent guidance for onboarding."
1771
1786
  },
1772
1787
  {
1773
- command: "dev repo devcontainer write",
1788
+ command: "devrouter repo devcontainer write",
1774
1789
  purpose: "Dry-run or write conservative managed Node/pnpm/Postgres devcontainer/devrouter scaffold files."
1775
1790
  },
1776
1791
  {
1777
- command: "dev repo devcontainer verify",
1792
+ command: "devrouter repo devcontainer verify",
1778
1793
  purpose: "Emit static onboarding evidence, or live route probes with --live --yes."
1779
1794
  },
1780
1795
  {
1781
- command: "dev app add",
1796
+ command: "devrouter app add",
1782
1797
  purpose: "Add or update one app entry in `.devrouter.yml`."
1783
1798
  },
1784
- { command: "dev app ls", purpose: "List app entries from `.devrouter.yml`." },
1799
+ { command: "devrouter app ls", purpose: "List app entries from `.devrouter.yml`." },
1785
1800
  {
1786
- command: "dev app run [--env <env>]",
1801
+ command: "devrouter app run [--env <env>]",
1787
1802
  purpose: "Run one configured app and reconcile its route at runtime (--env overrides SM defaultEnv)."
1788
1803
  },
1789
1804
  {
1790
- command: "dev app exec [--shell] [--env <env>]",
1805
+ command: "devrouter app exec [--shell] [--env <env>]",
1791
1806
  purpose: "Run a one-shot command with resolved dependency env vars (env aliasing via config-level envMap)."
1792
1807
  },
1793
1808
  {
1794
- command: "dev app rm [--keep-config]",
1809
+ command: "devrouter app rm [--keep-config]",
1795
1810
  purpose: "Remove one app entry from `.devrouter.yml` and free its route. `--keep-config` frees only the live route/hostname (e.g. to release one claimed by another repo) and leaves the config file untouched."
1796
1811
  },
1797
1812
  {
1798
- command: "dev repo agents [--with-linear]",
1799
- purpose: "Write/update devrouter section in the repo's AGENTS.md and optionally add Linear workflow assets."
1813
+ command: "devrouter repo agents",
1814
+ purpose: "Write/update devrouter section in the repo's AGENTS.md and install the devrouter skill."
1800
1815
  },
1801
1816
  {
1802
- command: "dev workspace up <branch> [--path <dir>] [--no-devpod] [--open]",
1817
+ command: "devrouter workspace up <branch> [--path <dir>] [--no-devpod] [--open]",
1803
1818
  purpose: "Create a git worktree for <branch>, bring up its devpod (`devpod up --id <ws>`), and register workspace-namespaced routes."
1804
1819
  },
1805
1820
  {
1806
- command: "dev workspace ls [--json]",
1821
+ command: "devrouter workspace ls [--json]",
1807
1822
  purpose: "List git worktrees with their resolved workspace token and active route count."
1808
1823
  },
1809
1824
  {
1810
- command: "dev workspace down <workspace|branch> [--keep-worktree] [--keep-devpod]",
1825
+ command: "devrouter workspace down <workspace|branch> [--keep-worktree] [--keep-devpod]",
1811
1826
  purpose: "Free a workspace's routes, stop its devpod, and remove its worktree (routes are freed by state-file workspace tag, no config load)."
1812
1827
  }
1813
1828
  ];
@@ -1827,92 +1842,13 @@ function buildDevrouterSection() {
1827
1842
  `\`${DEVROUTER_SKILL_REL_PATH}\``,
1828
1843
  "",
1829
1844
  "Quick validation sequence:",
1830
- "- `dev up`",
1831
- "- `dev tls install` (required when repo defines tcp/postgres apps)",
1832
- "- `dev app ls --repo .`",
1833
- "- `dev app run <host-app> --repo . --yes`",
1834
- "- `dev ls`"
1845
+ "- `devrouter up`",
1846
+ "- `devrouter tls install` (required when repo defines tcp/postgres apps)",
1847
+ "- `devrouter app ls --repo .`",
1848
+ "- `devrouter app run <host-app> --repo . --yes`",
1849
+ "- `devrouter ls`"
1835
1850
  ].join("\n");
1836
1851
  }
1837
- function buildLinearWorkflowSection() {
1838
- return [
1839
- LINEAR_WORKFLOW_SENTINEL,
1840
- "## linear-workflow",
1841
- "",
1842
- "This repository can optionally use a Linear-centered workflow with a minimal workspace/team/project mapping.",
1843
- "Use the managed AGENTS metadata block as source of truth before creating/updating Linear issues.",
1844
- "",
1845
- "Skill and templates:",
1846
- `- \`${LINEAR_SKILL_REL_PATH}\``,
1847
- `- \`${LINEAR_ISSUE_TEMPLATE_REL_PATH}\``,
1848
- `- \`${LINEAR_MILESTONE_TEMPLATE_REL_PATH}\``,
1849
- `- \`${LINEAR_PROGRESS_TEMPLATE_REL_PATH}\``,
1850
- "",
1851
- "Managed metadata block:",
1852
- `- \`${LINEAR_WORKFLOW_CONFIG_START}\``,
1853
- `- \`${LINEAR_WORKFLOW_CONFIG_END}\``,
1854
- "",
1855
- "Required Linear execution hygiene:",
1856
- "- Set issue status at session start and update it at each phase transition.",
1857
- "- Post progress comments at meaningful checkpoints during implementation.",
1858
- "- Before ending a session, post a final comment with completed work, remaining work, risks, and next step.",
1859
- "- Re-check status and comment freshness toward/at session end before stopping.",
1860
- "",
1861
- "Bootstrap commands:",
1862
- "- `dev init --with-linear --write-agents --write-skill`",
1863
- "- `dev repo agents --with-linear`"
1864
- ].join("\n");
1865
- }
1866
- function yamlQuote(value) {
1867
- return JSON.stringify(value);
1868
- }
1869
- function renderLinearWorkflowConfig(metadata) {
1870
- const lines = [
1871
- "linear:",
1872
- " workspace:",
1873
- ` name: ${yamlQuote(metadata.workspace.name)}`,
1874
- " team:",
1875
- ` name: ${yamlQuote(metadata.team.name)}`
1876
- ];
1877
- if (metadata.team.key) {
1878
- lines.push(` key: ${yamlQuote(metadata.team.key)}`);
1879
- }
1880
- lines.push(" project:");
1881
- lines.push(` name: ${yamlQuote(metadata.project.name)}`);
1882
- if (metadata.project.id) {
1883
- lines.push(` id: ${yamlQuote(metadata.project.id)}`);
1884
- }
1885
- lines.push(` updated_at: ${yamlQuote(metadata.updatedAt)}`);
1886
- lines.push(` capture_mode: ${yamlQuote(metadata.captureMode)}`);
1887
- return lines.join("\n");
1888
- }
1889
- function renderLinearWorkflowConfigBlock(metadata) {
1890
- return [
1891
- LINEAR_WORKFLOW_CONFIG_START,
1892
- "```yaml",
1893
- renderLinearWorkflowConfig(metadata),
1894
- "```",
1895
- LINEAR_WORKFLOW_CONFIG_END
1896
- ].join("\n");
1897
- }
1898
- function escapeRegExp(input3) {
1899
- return input3.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1900
- }
1901
- function upsertLinearWorkflowConfigBlock(content, metadata) {
1902
- const block = renderLinearWorkflowConfigBlock(metadata);
1903
- const pattern = new RegExp(
1904
- `${escapeRegExp(LINEAR_WORKFLOW_CONFIG_START)}[\\s\\S]*?${escapeRegExp(LINEAR_WORKFLOW_CONFIG_END)}\\n?`,
1905
- "m"
1906
- );
1907
- if (pattern.test(content)) {
1908
- return content.replace(pattern, `${block}
1909
- `);
1910
- }
1911
- return `${content.trimEnd()}
1912
-
1913
- ${block}
1914
- `;
1915
- }
1916
1852
  function writeRepoFile(repoPath, relPath, content) {
1917
1853
  const absolutePath = (0, import_node_path5.join)(repoPath, relPath);
1918
1854
  (0, import_node_fs5.mkdirSync)((0, import_node_path5.dirname)(absolutePath), { recursive: true });
@@ -1940,29 +1876,6 @@ function ensureAgentsMdSection(repoPath) {
1940
1876
  );
1941
1877
  return { path: filePath, written: true };
1942
1878
  }
1943
- function ensureLinearWorkflowAgentsSection(repoPath, metadata) {
1944
- const filePath = (0, import_node_path5.join)(repoPath, AGENTS_MD);
1945
- if ((0, import_node_fs5.existsSync)(filePath)) {
1946
- let content = (0, import_node_fs5.readFileSync)(filePath, "utf-8");
1947
- let changed = false;
1948
- if (!content.includes(LINEAR_WORKFLOW_SENTINEL)) {
1949
- content = content.trimEnd() + "\n\n" + buildLinearWorkflowSection() + "\n";
1950
- changed = true;
1951
- }
1952
- const withConfig2 = upsertLinearWorkflowConfigBlock(content, metadata);
1953
- if (withConfig2 !== content) {
1954
- changed = true;
1955
- }
1956
- if (changed) {
1957
- (0, import_node_fs5.writeFileSync)(filePath, withConfig2, "utf-8");
1958
- }
1959
- return { path: filePath, written: changed };
1960
- }
1961
- const initialContent = "# AGENTS.md\n\n" + buildLinearWorkflowSection() + "\n";
1962
- const withConfig = upsertLinearWorkflowConfigBlock(initialContent, metadata);
1963
- (0, import_node_fs5.writeFileSync)(filePath, withConfig, "utf-8");
1964
- return { path: filePath, written: true };
1965
- }
1966
1879
  function ensureSkillFile(repoPath) {
1967
1880
  const filePath = writeRepoFile(
1968
1881
  repoPath,
@@ -1971,47 +1884,15 @@ function ensureSkillFile(repoPath) {
1971
1884
  );
1972
1885
  return { path: filePath, written: true };
1973
1886
  }
1974
- function ensureLinearWorkflowSkillFiles(repoPath) {
1975
- const paths = [
1976
- writeRepoFile(
1977
- repoPath,
1978
- LINEAR_SKILL_REL_PATH,
1979
- LINEAR_WORKFLOW_SKILL_CONTENT
1980
- ),
1981
- writeRepoFile(
1982
- repoPath,
1983
- LINEAR_ISSUE_TEMPLATE_REL_PATH,
1984
- LINEAR_ISSUE_TEMPLATE_CONTENT
1985
- ),
1986
- writeRepoFile(
1987
- repoPath,
1988
- LINEAR_MILESTONE_TEMPLATE_REL_PATH,
1989
- LINEAR_MILESTONE_TEMPLATE_CONTENT
1990
- ),
1991
- writeRepoFile(
1992
- repoPath,
1993
- LINEAR_PROGRESS_TEMPLATE_REL_PATH,
1994
- LINEAR_PROGRESS_TEMPLATE_CONTENT
1995
- )
1996
- ];
1997
- return { paths, written: true };
1998
- }
1999
- var import_node_fs5, import_node_path5, DEVROUTER_SENTINEL, LINEAR_WORKFLOW_SENTINEL, LINEAR_WORKFLOW_CONFIG_START, LINEAR_WORKFLOW_CONFIG_END, AGENTS_MD, DEVROUTER_SKILL_REL_PATH, LINEAR_SKILL_REL_PATH, LINEAR_ISSUE_TEMPLATE_REL_PATH, LINEAR_MILESTONE_TEMPLATE_REL_PATH, LINEAR_PROGRESS_TEMPLATE_REL_PATH, DEVROUTER_SKILL_CONTENT, LINEAR_WORKFLOW_SKILL_CONTENT, LINEAR_ISSUE_TEMPLATE_CONTENT, LINEAR_MILESTONE_TEMPLATE_CONTENT, LINEAR_PROGRESS_TEMPLATE_CONTENT;
1887
+ var import_node_fs5, import_node_path5, DEVROUTER_SENTINEL, AGENTS_MD, DEVROUTER_SKILL_REL_PATH, DEVROUTER_SKILL_CONTENT;
2000
1888
  var init_agents_md = __esm({
2001
1889
  "src/core/agents-md.ts"() {
2002
1890
  "use strict";
2003
1891
  import_node_fs5 = require("fs");
2004
1892
  import_node_path5 = require("path");
2005
1893
  DEVROUTER_SENTINEL = "<!-- devrouter -->";
2006
- LINEAR_WORKFLOW_SENTINEL = "<!-- devrouter-linear-workflow -->";
2007
- LINEAR_WORKFLOW_CONFIG_START = "<!-- devrouter-linear-workflow-config:start -->";
2008
- LINEAR_WORKFLOW_CONFIG_END = "<!-- devrouter-linear-workflow-config:end -->";
2009
1894
  AGENTS_MD = "AGENTS.md";
2010
1895
  DEVROUTER_SKILL_REL_PATH = ".agents/skills/devrouter/SKILL.md";
2011
- LINEAR_SKILL_REL_PATH = ".agents/skills/linear-workflow/SKILL.md";
2012
- LINEAR_ISSUE_TEMPLATE_REL_PATH = ".agents/skills/linear-workflow/references/LINEAR_ISSUE_TEMPLATE.md";
2013
- LINEAR_MILESTONE_TEMPLATE_REL_PATH = ".agents/skills/linear-workflow/references/MILESTONE_PLAN_TEMPLATE.md";
2014
- LINEAR_PROGRESS_TEMPLATE_REL_PATH = ".agents/skills/linear-workflow/references/PROGRESS_UPDATE_TEMPLATE.md";
2015
1896
  DEVROUTER_SKILL_CONTENT = `---
2016
1897
  name: devrouter
2017
1898
  description: Work with devrouter for local dev routing (HTTP + TCP/Postgres + dependency-only Docker services)
@@ -2034,7 +1915,7 @@ Local dev routing via a shared Traefik reverse proxy. Provides stable \`*.localh
2034
1915
  \`\`\`yaml
2035
1916
  version: 1
2036
1917
  devrouter:
2037
- version: <semver> # required for dev -V / dev upgrade
1918
+ version: <semver> # required for devrouter -V / devrouter upgrade
2038
1919
  project:
2039
1920
  name: <string> # optional
2040
1921
  apps:
@@ -2067,7 +1948,7 @@ apps:
2067
1948
  # protocol: tcp
2068
1949
  # tcpProtocol: postgres # selects shared entrypoint :5432
2069
1950
  # upstream: <db-alias>:5432 # devnet alias of the DB container
2070
- # Requires \`dev tls install\` (SNI is read from the TLS ClientHello). Connect
1951
+ # Requires \`devrouter tls install\` (SNI is read from the TLS ClientHello). Connect
2071
1952
  # with direct-SSL so the ClientHello carries SNI, e.g.:
2072
1953
  # psql "host=db.<app>.localhost port=5432 sslmode=require sslnegotiation=direct ..."
2073
1954
 
@@ -2102,7 +1983,7 @@ Validation rules:
2102
1983
 
2103
1984
  - \`kind=app\`: \`host\` must end with \`.localhost\`
2104
1985
  - \`kind=app\`: \`runtime=host\` supports \`protocol=http\` only
2105
- - \`kind=app\`: \`runtime=proxy\` supports \`protocol=http\` or \`protocol=tcp\`, requires \`upstream\` (\`host:port\`), and forbids \`hostRun\`/\`docker\`/\`dependencies\` (it only registers a route to an externally-managed upstream). \`protocol=tcp\` additionally requires \`tcpProtocol\` and TLS (\`dev tls install\`)
1986
+ - \`kind=app\`: \`runtime=proxy\` supports \`protocol=http\` or \`protocol=tcp\`, requires \`upstream\` (\`host:port\`), and forbids \`hostRun\`/\`docker\`/\`dependencies\` (it only registers a route to an externally-managed upstream). \`protocol=tcp\` additionally requires \`tcpProtocol\` and TLS (\`devrouter tls install\`)
2106
1987
  - \`kind=app\`: \`protocol=tcp\` requires \`runtime=docker\` (devrouter-managed container) or \`runtime=proxy\` (externally-managed upstream), plus a supported \`tcpProtocol\` (postgres/redis/mariadb/mysql)
2107
1988
  - \`kind=dependency\`: must use \`runtime=docker\` and does not allow routed fields (\`host\`/\`protocol\`/\`tcpProtocol\`/\`hostRun\`/\`docker.internalPort\`/\`docker.router\`)
2108
1989
  - Unknown keys rejected (strict schema)
@@ -2126,7 +2007,7 @@ healthcheck:
2126
2007
 
2127
2008
  ## Env var injection
2128
2009
 
2129
- When a host app depends on a TCP Docker service, \`dev app run\` and \`dev app exec\` inject per-dep deterministic vars (where \`{PREFIX} = dep.name.toUpperCase().replace(/-/g, "_")\`):
2010
+ When a host app depends on a TCP Docker service, \`devrouter app run\` and \`devrouter app exec\` inject per-dep deterministic vars (where \`{PREFIX} = dep.name.toUpperCase().replace(/-/g, "_")\`):
2130
2011
 
2131
2012
  | Variable | Value |
2132
2013
  | ----------------------- | ----------------------------------------------------------- |
@@ -2147,7 +2028,7 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
2147
2028
  - **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. The runtime config is computed in memory only \u2014 the committed \`.devrouter.yml\` is never rewritten.
2148
2029
  - **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.
2149
2030
  - **devcontainer integration**: the devcontainer compose service exposes a devnet alias \`\${WORKSPACE}-app\` (default \`WORKSPACE=<project>\` in \`devcontainer.env\`); the proxy app uses \`upstream: \${WORKSPACE}-app:<port>\`. Workspace \`feat-a\` \u2192 alias \`feat-a-app\`, host \`app.feat-a.localhost\`.
2150
- - **Lifecycle**: \`dev workspace up <branch>\` (create worktree + devpod + routes), \`dev workspace ls\` (list worktrees/tokens/route counts), \`dev workspace down <workspace|branch>\` (free routes by state-file workspace tag + stop devpod + remove worktree). \`dev doctor\` reports orphaned workspace proxy routes whose worktree dir was removed without \`dev workspace down\`.
2031
+ - **Lifecycle**: \`devrouter workspace up <branch>\` (create worktree + devpod + routes), \`devrouter workspace ls\` (list worktrees/tokens/route counts), \`devrouter workspace down <workspace|branch>\` (free routes by state-file workspace tag + stop devpod + remove worktree). \`devrouter doctor\` reports orphaned workspace proxy routes whose worktree dir was removed without \`devrouter workspace down\`.
2151
2032
 
2152
2033
  ## Secret manager interop (Infisical/Doppler)
2153
2034
 
@@ -2171,311 +2052,92 @@ Run several worktrees of one repo in parallel without host/route collisions. A *
2171
2052
  \`\`\`
2172
2053
  - Prefer argv-safe command forms. Do not wrap \`infisical run\` or \`doppler run\` in \`sh -lc\` unless shell expansion is strictly required.
2173
2054
  - Canonical Infisical migrate command:
2174
- \`dev app exec <app> --yes -- infisical run --projectId <id> --env=<env> -- pnpm payload migrate\`
2055
+ \`devrouter app exec <app> --yes -- infisical run --projectId <id> --env=<env> -- pnpm payload migrate\`
2175
2056
  - Canonical env probe command (run before migrate/seed):
2176
- \`dev app exec <app> --yes -- printenv DB_URL DB_HOST DB_PORT DB_SHADOW_URL\`
2057
+ \`devrouter app exec <app> --yes -- printenv DB_URL DB_HOST DB_PORT DB_SHADOW_URL\`
2177
2058
  - Canonical Doppler migrate command:
2178
- \`dev app exec <app> --yes -- doppler run -- pnpm payload migrate\`
2059
+ \`devrouter app exec <app> --yes -- doppler run -- pnpm payload migrate\`
2179
2060
  - Precedence best practice: avoid defining per-dep var names in Infisical/Doppler when you expect devrouter local DB injection.
2180
2061
  - Precedence best practice: store remote/prod URLs under non-conflicting names (for example \`PROD_DATABASE_URL\`) and map intentionally via \`envMap\`.
2181
2062
  - Precedence best practice: if secret manager must define DB vars, run the env probe and verify values before any migration/seed.
2182
- - Use \`dev app exec --shell -- "<single command string>"\` only when shell expansion is required.
2063
+ - Use \`devrouter app exec --shell -- "<single command string>"\` only when shell expansion is required.
2183
2064
  - \`envMap\` fails fast when source var is missing so migrations do not run with partial mapping.
2184
2065
 
2185
2066
  ## Upgrade handling (required)
2186
2067
 
2187
2068
  - Keep \`.devrouter.yml\` metadata \`devrouter.version\` aligned with the currently applied devrouter release.
2188
- - Verify versions with \`dev -V\` (shows installed CLI version, local repo version, and next upgrade target).
2189
- - Use \`dev upgrade\` to list available upgrade targets and \`dev upgrade <version>\` to print that target's Agent Adaptation Prompt from \`upgrade-prompts/<version>.md\`.
2069
+ - Verify versions with \`devrouter -V\` (shows installed CLI version, local repo version, and next upgrade target).
2070
+ - Use \`devrouter upgrade\` to list available upgrade targets and \`devrouter upgrade <version>\` to print that target's Agent Adaptation Prompt from \`upgrade-prompts/<version>.md\`.
2190
2071
  - Do not assume user-provided instructions include all required adaptation steps.
2191
- - After upgrading the CLI in a dependent repo, refresh discoverability artifacts with \`dev repo agents\` (or \`dev init --write-agents --write-skill\`).
2192
- - Re-run validation after upgrade: \`dev doctor --repo .\`, \`dev app ls --repo .\`, one representative \`dev app exec\` flow, and \`dev ls\`.
2193
-
2194
- ## Optional Linear workflow bootstrap
2195
-
2196
- - To add Linear task-management workflow assets to a repo, run:
2197
- - \`dev init --with-linear --write-agents --write-skill\`, or
2198
- - \`dev repo agents --with-linear\`
2199
- - This writes \`.agents/skills/linear-workflow/SKILL.md\` and reference templates, plus an idempotent AGENTS section.
2200
- - On AGENTS write flows, devrouter asks for minimal Linear mapping (workspace/team/project) and stores it in a managed AGENTS block:
2201
- - \`<!-- devrouter-linear-workflow-config:start -->\`
2202
- - \`<!-- devrouter-linear-workflow-config:end -->\`
2203
- - In non-interactive mode, placeholder values are written and should be replaced in the next interactive session.
2072
+ - After upgrading the CLI in a dependent repo, refresh discoverability artifacts with \`devrouter repo agents\` (or \`devrouter init --write-agents --write-skill\`).
2073
+ - Re-run validation after upgrade: \`devrouter doctor --repo .\`, \`devrouter app ls --repo .\`, one representative \`devrouter app exec\` flow, and \`devrouter ls\`.
2204
2074
 
2205
2075
  ## Commands
2206
2076
 
2207
- - \`dev init [--write-agents] [--write-skill] [--with-linear]\`: print AI onboarding prompt (non-mutating by default)
2208
- - \`dev -V [--repo .]\`: show installed CLI version, local repo version, and next upgrade target
2209
- - \`dev upgrade [version] [--repo .]\`: list upgrade targets or print target Agent Adaptation Prompt
2210
- - \`dev setup --yes [--repo .] [--json]\`: first-run machine setup plus structured diagnostics
2211
- - \`dev up\` / \`dev down\`: start/stop shared Traefik router
2212
- - \`dev status\`: router/container/network/TLS health
2213
- - \`dev doctor [--repo .]\`: deep diagnostics (global + repo)
2214
- - \`dev ls\`: list active HTTP + TCP routes
2215
- - \`dev open <name>\`: open HTTP route or print TCP connection hint (matches app name, then service/container/host identities)
2216
- - \`dev logs [-f]\`: Traefik access logs
2217
- - \`dev tls install\`: install mkcert certs, enable HTTPS + TCP/SNI
2218
- - \`dev repo init\`: create \`.devrouter.yml\`
2219
- - \`dev repo inspect [--json]\`: inspect package, scripts, compose services, env names, devcontainer, devrouter config, and agent guidance for onboarding
2220
- - \`dev repo devcontainer write --dry-run --json\`: plan conservative Node/pnpm/Postgres devcontainer/devrouter scaffold files without writing
2221
- - \`dev repo devcontainer write --yes\`: write managed Node/pnpm/Postgres devcontainer/devrouter scaffold files when no custom-file conflicts exist
2222
- - \`dev repo devcontainer verify --json\`: emit read-only onboarding evidence for PRs
2223
- - \`dev repo devcontainer verify --live --yes --json\`: register proxy routes and probe HTTP routes after the devcontainer is running
2224
- - \`dev repo agents [--with-linear]\`: write devrouter section in AGENTS.md + install this skill (and optional Linear workflow assets)
2225
- - \`dev app add\`: add/update app entry in \`.devrouter.yml\`
2226
- - \`dev app ls\`: list app entries
2227
- - \`dev app run <name> [--env <env>] [--workspace <slug>]\`: run app with dependency lifecycle (--env overrides SM defaultEnv; --workspace overrides the per-workspace token)
2228
- - \`dev app exec <name> [--shell] [--env <env>] [--workspace <slug>] -- <cmd>\`: one-shot command with resolved dep env
2229
- - \`dev app rm <name> [--keep-config]\`: remove app entry (\`--keep-config\` frees only the live route/hostname, leaves \`.devrouter.yml\` untouched)
2230
- - \`dev workspace up <branch> [--path <dir>] [--no-devpod] [--open]\`: create a worktree + devpod + namespaced routes
2231
- - \`dev workspace ls [--json]\`: list git worktrees with workspace token + route count
2232
- - \`dev workspace down <workspace|branch> [--keep-worktree] [--keep-devpod]\`: free routes + stop devpod + remove worktree
2077
+ - \`devrouter init [--write-agents] [--write-skill]\`: print AI onboarding prompt (non-mutating by default)
2078
+ - \`devrouter -V [--repo .]\`: show installed CLI version, local repo version, and next upgrade target
2079
+ - \`devrouter upgrade [version] [--repo .]\`: list upgrade targets or print target Agent Adaptation Prompt
2080
+ - \`devrouter setup --yes [--repo .] [--json]\`: first-run machine setup plus structured diagnostics
2081
+ - \`devrouter up\` / \`devrouter down\`: start/stop shared Traefik router
2082
+ - \`devrouter status\`: router/container/network/TLS health
2083
+ - \`devrouter doctor [--repo .]\`: deep diagnostics (global + repo)
2084
+ - \`devrouter ls\`: list active HTTP + TCP routes
2085
+ - \`devrouter open <name>\`: open HTTP route or print TCP connection hint (matches app name, then service/container/host identities)
2086
+ - \`devrouter logs [-f]\`: Traefik access logs
2087
+ - \`devrouter tls install\`: install mkcert certs, enable HTTPS + TCP/SNI
2088
+ - \`devrouter repo init\`: create \`.devrouter.yml\`
2089
+ - \`devrouter repo inspect [--json]\`: inspect package, scripts, compose services, env names, devcontainer, devrouter config, and agent guidance for onboarding
2090
+ - \`devrouter repo devcontainer write --dry-run --json\`: plan conservative Node/pnpm/Postgres devcontainer/devrouter scaffold files without writing
2091
+ - \`devrouter repo devcontainer write --yes\`: write managed Node/pnpm/Postgres devcontainer/devrouter scaffold files when no custom-file conflicts exist
2092
+ - \`devrouter repo devcontainer verify --json\`: emit read-only onboarding evidence for PRs
2093
+ - \`devrouter repo devcontainer verify --live --yes --json\`: register proxy routes and probe HTTP routes after the devcontainer is running
2094
+ - \`devrouter repo agents\`: write devrouter section in AGENTS.md + install this skill
2095
+ - \`devrouter app add\`: add/update app entry in \`.devrouter.yml\`
2096
+ - \`devrouter app ls\`: list app entries
2097
+ - \`devrouter app run <name> [--env <env>] [--workspace <slug>]\`: run app with dependency lifecycle (--env overrides SM defaultEnv; --workspace overrides the per-workspace token)
2098
+ - \`devrouter app exec <name> [--shell] [--env <env>] [--workspace <slug>] -- <cmd>\`: one-shot command with resolved dep env
2099
+ - \`devrouter app rm <name> [--keep-config]\`: remove app entry (\`--keep-config\` frees only the live route/hostname, leaves \`.devrouter.yml\` untouched)
2100
+ - \`devrouter workspace up <branch> [--path <dir>] [--no-devpod] [--open]\`: create a worktree + devpod + namespaced routes
2101
+ - \`devrouter workspace ls [--json]\`: list git worktrees with workspace token + route count
2102
+ - \`devrouter workspace down <workspace|branch> [--keep-worktree] [--keep-devpod]\`: free routes + stop devpod + remove worktree
2233
2103
 
2234
2104
  ## Validation workflow
2235
2105
 
2236
2106
  For devcontainer onboarding:
2237
2107
 
2238
- 1. \`dev setup --repo . --yes --json\`
2239
- 2. \`dev doctor --repo . --json\`
2240
- 3. \`dev repo inspect --repo . --json\`
2241
- 4. \`dev repo devcontainer write --repo . --dry-run --json\`
2242
- 5. \`dev repo devcontainer write --repo . --yes\`
2243
- 6. \`dev repo devcontainer verify --repo . --json\`
2108
+ 1. \`devrouter setup --repo . --yes --json\`
2109
+ 2. \`devrouter doctor --repo . --json\`
2110
+ 3. \`devrouter repo inspect --repo . --json\`
2111
+ 4. \`devrouter repo devcontainer write --repo . --dry-run --json\`
2112
+ 5. \`devrouter repo devcontainer write --repo . --yes\`
2113
+ 6. \`devrouter repo devcontainer verify --repo . --json\`
2244
2114
  7. Start the devcontainer, for example \`devpod up .\`
2245
- 8. \`dev repo devcontainer verify --repo . --live --yes --json\`
2115
+ 8. \`devrouter repo devcontainer verify --repo . --live --yes --json\`
2246
2116
 
2247
2117
  For existing host/docker runtime apps:
2248
2118
 
2249
- 1. \`dev setup --repo . --yes\`
2250
- 2. \`dev doctor --repo .\`
2251
- 3. \`dev app ls --repo .\`
2252
- 4. \`dev app run <host-app> --repo . --yes\`
2253
- 5. \`dev ls\`
2119
+ 1. \`devrouter setup --repo . --yes\`
2120
+ 2. \`devrouter doctor --repo .\`
2121
+ 3. \`devrouter app ls --repo .\`
2122
+ 4. \`devrouter app run <host-app> --repo . --yes\`
2123
+ 5. \`devrouter ls\`
2254
2124
  6. \`curl -I https://<host>.localhost\`
2255
- 7. For TCP/Postgres, use \`dev open <name>\` for the connection hint.
2125
+ 7. For TCP/Postgres, use \`devrouter open <name>\` for the connection hint.
2256
2126
 
2257
2127
  ## Runtime behavior notes
2258
2128
 
2259
- - \`dev app run\` auto-starts Docker dependencies and waits for health. Host app runs stop auto-started docker deps on exit; docker app runs leave target services running until explicit cleanup.
2129
+ - \`devrouter app run\` auto-starts Docker dependencies and waits for health. Host app runs stop auto-started docker deps on exit; docker app runs leave target services running until explicit cleanup.
2260
2130
  - Host-runtime dependencies are NOT auto-started (v1).
2261
- - \`kind=dependency\` entries do not create routes and cannot be direct targets for \`dev app run\`, \`dev app exec\`, or \`dev open\`.
2131
+ - \`kind=dependency\` entries do not create routes and cannot be direct targets for \`devrouter app run\`, \`devrouter app exec\`, or \`devrouter open\`.
2262
2132
  - \`kind=dependency\` services start as declared in compose (no Traefik label wiring, no random port publishing, no injected env vars).
2263
- - Postgres on shared \`:5432\` requires TLS/SNI (\`dev tls install\`). Standard app clients should use the injected random port instead.
2264
- - \`dev app exec\` follows the same dep lifecycle for one-shot commands and preserves argv semantics by default (\`shell: false\`).
2265
- - \`dev app exec --shell\` is explicit and requires exactly one command string after \`--\`.
2133
+ - Postgres on shared \`:5432\` requires TLS/SNI (\`devrouter tls install\`). Standard app clients should use the injected random port instead.
2134
+ - \`devrouter app exec\` follows the same dep lifecycle for one-shot commands and preserves argv semantics by default (\`shell: false\`).
2135
+ - \`devrouter app exec --shell\` is explicit and requires exactly one command string after \`--\`.
2266
2136
  - Secret-manager overlap caveat: if Infisical/Doppler defines DB vars too, probe effective env (\`printenv DB_URL DB_HOST DB_PORT\`) before migrate/seed.
2267
- `;
2268
- LINEAR_WORKFLOW_SKILL_CONTENT = `---
2269
- name: linear-workflow
2270
- description: Use a minimal Linear workspace/team/project mapping for cross-session continuity
2271
- user-invocable: false
2272
- ---
2273
-
2274
- # linear-workflow
2275
-
2276
- Use this skill when a repository enables Linear workflow via devrouter.
2277
-
2278
- ## First step: read AGENTS mapping
2279
-
2280
- Check \`AGENTS.md\` for the managed Linear block:
2281
-
2282
- - \`<!-- devrouter-linear-workflow-config:start -->\`
2283
- - \`<!-- devrouter-linear-workflow-config:end -->\`
2284
-
2285
- Use that block as source of truth for:
2286
-
2287
- - workspace name
2288
- - team name (and optional key)
2289
- - project name (and optional id)
2290
-
2291
- ## If mapping is missing or placeholder
2292
-
2293
- Ask the user these guided questions and update the AGENTS managed block:
2294
-
2295
- 1. Which Linear workspace does this repository belong to?
2296
- 2. Which Linear team owns this repository? (optional team key)
2297
- 3. Which Linear project should milestones/issues be created in? (optional project id)
2298
-
2299
- If non-interactive context prevents asking, keep placeholders and request values in the next interactive session.
2300
-
2301
- ## Usage rule
2302
-
2303
- - Do not hardcode workspace/team/project assumptions.
2304
- - Always resolve them from AGENTS metadata first.
2305
-
2306
- ## Required execution hygiene
2307
-
2308
- When working on Linear-tracked issues, this is required:
2309
-
2310
- 1. Set issue status at session start and update it at each phase transition.
2311
- 2. Post progress comments at meaningful checkpoints during implementation.
2312
- 3. Before ending a session, post a final comment with completed work, remaining work, risks, and next step.
2313
- 4. Re-check status and comment freshness toward/at session end before stopping.
2314
-
2315
- ## Devrouter-specific note
2316
-
2317
- If the repository uses devrouter, use \`dev upgrade\` to resolve the required Agent Adaptation Prompt for the target version before major changes (prompt files are versioned under \`upgrade-prompts/<version>.md\`).
2318
- `;
2319
- LINEAR_ISSUE_TEMPLATE_CONTENT = `# Linear Issue Template
2320
-
2321
- ## Problem
2322
-
2323
- ## Goal / Expected Outcome
2324
-
2325
- ## Scope
2326
- - In scope:
2327
- - Out of scope:
2328
-
2329
- ## Technical Approach
2330
-
2331
- ## Acceptance Criteria
2332
-
2333
- ## Validation Plan
2334
-
2335
- ## Dependencies / Blockers
2336
-
2337
- ## Rollout Risks
2338
- `;
2339
- LINEAR_MILESTONE_TEMPLATE_CONTENT = `# Milestone Plan Template
2340
-
2341
- ## Milestone Goal
2342
-
2343
- ## Tracker Issue
2344
- - Identifier:
2345
- - Owner:
2346
-
2347
- ## Child Issues
2348
- - [ ] Issue 1:
2349
- - [ ] Issue 2:
2350
- - [ ] Issue 3:
2351
-
2352
- ## Sequencing
2353
- 1.
2354
- 2.
2355
- 3.
2356
-
2357
- ## Risks and Mitigations
2358
-
2359
- ## Definition of Done
2360
- `;
2361
- LINEAR_PROGRESS_TEMPLATE_CONTENT = `# Progress Update Template
2362
-
2363
- ## Summary
2364
-
2365
- ## Completed
2366
- - <item>
2367
-
2368
- ## In Progress
2369
- - <item>
2370
-
2371
- ## Next
2372
- - <item>
2373
-
2374
- ## Risks / Blockers
2375
- - <item>
2376
2137
  `;
2377
2138
  }
2378
2139
  });
2379
2140
 
2380
- // src/core/linear-onboarding.ts
2381
- function isoTimestamp(now) {
2382
- return (now ?? /* @__PURE__ */ new Date()).toISOString();
2383
- }
2384
- function requiredPlaceholder(path16) {
2385
- return `<REQUIRED: ${path16}>`;
2386
- }
2387
- function normalizeOptionalValue(value) {
2388
- const trimmed = value.trim();
2389
- return trimmed.length > 0 ? trimmed : void 0;
2390
- }
2391
- async function askRequired(askQuestion, question, fieldName) {
2392
- while (true) {
2393
- const answer = (await askQuestion(question)).trim();
2394
- if (answer.length > 0) {
2395
- return answer;
2396
- }
2397
- import_node_process.stdout.write(`${fieldName} is required. Please enter a value.
2398
- `);
2399
- }
2400
- }
2401
- function buildPlaceholderLinearWorkflowMetadata(now) {
2402
- return {
2403
- workspace: {
2404
- name: requiredPlaceholder("workspace.name")
2405
- },
2406
- team: {
2407
- name: requiredPlaceholder("team.name")
2408
- },
2409
- project: {
2410
- name: requiredPlaceholder("project.name")
2411
- },
2412
- updatedAt: isoTimestamp(now),
2413
- captureMode: "placeholder"
2414
- };
2415
- }
2416
- async function collectLinearWorkflowMetadata(options = {}) {
2417
- const interactive = options.isInteractive ?? Boolean(import_node_process.stdin.isTTY && import_node_process.stdout.isTTY);
2418
- if (!interactive) {
2419
- return buildPlaceholderLinearWorkflowMetadata(options.now);
2420
- }
2421
- if (options.askQuestion) {
2422
- const workspaceName = await askRequired(options.askQuestion, "Linear workspace name: ", "workspace.name");
2423
- const teamName = await askRequired(options.askQuestion, "Linear team name: ", "team.name");
2424
- const teamKey = normalizeOptionalValue(await options.askQuestion("Linear team key (optional): "));
2425
- const projectName = await askRequired(options.askQuestion, "Linear project name: ", "project.name");
2426
- const projectId = normalizeOptionalValue(await options.askQuestion("Linear project id (optional): "));
2427
- return {
2428
- workspace: {
2429
- name: workspaceName
2430
- },
2431
- team: {
2432
- name: teamName,
2433
- ...teamKey ? { key: teamKey } : {}
2434
- },
2435
- project: {
2436
- name: projectName,
2437
- ...projectId ? { id: projectId } : {}
2438
- },
2439
- updatedAt: isoTimestamp(options.now),
2440
- captureMode: "interactive"
2441
- };
2442
- }
2443
- const rl = (0, import_promises.createInterface)({ input: import_node_process.stdin, output: import_node_process.stdout });
2444
- try {
2445
- const ask = (question) => rl.question(question);
2446
- const workspaceName = await askRequired(ask, "Linear workspace name: ", "workspace.name");
2447
- const teamName = await askRequired(ask, "Linear team name: ", "team.name");
2448
- const teamKey = normalizeOptionalValue(await ask("Linear team key (optional): "));
2449
- const projectName = await askRequired(ask, "Linear project name: ", "project.name");
2450
- const projectId = normalizeOptionalValue(await ask("Linear project id (optional): "));
2451
- return {
2452
- workspace: {
2453
- name: workspaceName
2454
- },
2455
- team: {
2456
- name: teamName,
2457
- ...teamKey ? { key: teamKey } : {}
2458
- },
2459
- project: {
2460
- name: projectName,
2461
- ...projectId ? { id: projectId } : {}
2462
- },
2463
- updatedAt: isoTimestamp(options.now),
2464
- captureMode: "interactive"
2465
- };
2466
- } finally {
2467
- rl.close();
2468
- }
2469
- }
2470
- var import_node_process, import_promises;
2471
- var init_linear_onboarding = __esm({
2472
- "src/core/linear-onboarding.ts"() {
2473
- "use strict";
2474
- import_node_process = require("process");
2475
- import_promises = require("readline/promises");
2476
- }
2477
- });
2478
-
2479
2141
  // src/util/timeago.ts
2480
2142
  function formatAge(createdAtSeconds) {
2481
2143
  const ageMs = Date.now() - createdAtSeconds * 1e3;
@@ -2759,14 +2421,13 @@ var init_exports = {};
2759
2421
  __export(init_exports, {
2760
2422
  runInitCommand: () => runInitCommand
2761
2423
  });
2762
- async function runInitCommand(options, deps = {}) {
2424
+ async function runInitCommand(options) {
2763
2425
  if (options.json && (options.writeAgents || options.writeSkill)) {
2764
2426
  throw new Error("--json cannot be combined with --write-agents or --write-skill.");
2765
2427
  }
2766
2428
  const prompt = buildOnboardingPrompt({
2767
2429
  repo: options.repo,
2768
- entriesJson: options.entriesJson,
2769
- withLinear: Boolean(options.withLinear)
2430
+ entriesJson: options.entriesJson
2770
2431
  });
2771
2432
  if (options.json) {
2772
2433
  printJSON({
@@ -2786,13 +2447,6 @@ async function runInitCommand(options, deps = {}) {
2786
2447
  process.stdout.write(`
2787
2448
  Wrote skill to ${skill.path}
2788
2449
  `);
2789
- if (options.withLinear) {
2790
- const linearSkills = ensureLinearWorkflowSkillFiles(repoPath);
2791
- for (const filePath of linearSkills.paths) {
2792
- process.stdout.write(`Wrote Linear workflow artifact to ${filePath}
2793
- `);
2794
- }
2795
- }
2796
2450
  }
2797
2451
  if (options.writeAgents) {
2798
2452
  const result = ensureAgentsMdSection(repoPath);
@@ -2803,22 +2457,6 @@ Wrote skill to ${skill.path}
2803
2457
  process.stdout.write(`devrouter section already present: ${result.path}
2804
2458
  `);
2805
2459
  }
2806
- if (options.withLinear) {
2807
- const linearMetadata = await (deps.collectLinearMetadata ?? collectLinearWorkflowMetadata)();
2808
- if (linearMetadata.captureMode === "placeholder") {
2809
- process.stdout.write(
2810
- "Warning: non-interactive mode detected; wrote placeholder Linear mapping values. Re-run in a TTY to capture workspace/team/project.\n"
2811
- );
2812
- }
2813
- const linearAgents = ensureLinearWorkflowAgentsSection(repoPath, linearMetadata);
2814
- if (linearAgents.written) {
2815
- process.stdout.write(`Wrote Linear workflow section to ${linearAgents.path}
2816
- `);
2817
- } else {
2818
- process.stdout.write(`Linear workflow section already present: ${linearAgents.path}
2819
- `);
2820
- }
2821
- }
2822
2460
  }
2823
2461
  }
2824
2462
  var init_init = __esm({
@@ -2826,7 +2464,6 @@ var init_init = __esm({
2826
2464
  "use strict";
2827
2465
  init_ai_prompt();
2828
2466
  init_agents_md();
2829
- init_linear_onboarding();
2830
2467
  init_output();
2831
2468
  init_repo_config();
2832
2469
  }
@@ -2976,7 +2613,7 @@ function printUpgradeTargets(currentVersion, availableTargets) {
2976
2613
  process.stdout.write(`- ${release.version}${suffix}
2977
2614
  `);
2978
2615
  }
2979
- process.stdout.write("\nRun `dev upgrade <version>` to print the Agent Adaptation Prompt for a target version.\n");
2616
+ process.stdout.write("\nRun `devrouter upgrade <version>` to print the Agent Adaptation Prompt for a target version.\n");
2980
2617
  }
2981
2618
  async function runUpgradeCommand(options, deps = {}) {
2982
2619
  const catalog = loadUpgradeCatalog({
@@ -3153,13 +2790,11 @@ function commandExists(command) {
3153
2790
  return result.status === 0;
3154
2791
  }
3155
2792
  function ensureMkcert() {
3156
- if (commandExists("mkcert")) {
3157
- return;
3158
- }
3159
- if (!commandExists("brew")) {
3160
- throw new Error("mkcert is missing and Homebrew is not available.");
2793
+ if (!commandExists("mkcert")) {
2794
+ throw new Error(
2795
+ "mkcert is not installed. Please install it to use TLS features (e.g., 'brew install mkcert' or via your package manager)."
2796
+ );
3161
2797
  }
3162
- runOrThrow("brew", ["install", "mkcert"]);
3163
2798
  }
3164
2799
  function normalizeHost(host) {
3165
2800
  return host.trim().toLowerCase();
@@ -3748,8 +3383,8 @@ var init_route_state = __esm({
3748
3383
  function outputFromResult(result) {
3749
3384
  const stdout = typeof result.stdout === "string" ? result.stdout.trim() : "";
3750
3385
  const stderr = typeof result.stderr === "string" ? result.stderr.trim() : "";
3751
- const output3 = [stdout, stderr].filter(Boolean).join("\n").trim();
3752
- return output3.length > 0 ? output3 : void 0;
3386
+ const output2 = [stdout, stderr].filter(Boolean).join("\n").trim();
3387
+ return output2.length > 0 ? output2 : void 0;
3753
3388
  }
3754
3389
  function runTool(command, args = []) {
3755
3390
  const result = (0, import_node_child_process5.spawnSync)(command, args, {
@@ -3761,14 +3396,14 @@ function runTool(command, args = []) {
3761
3396
  error: result.error.message
3762
3397
  };
3763
3398
  }
3764
- const output3 = outputFromResult(result);
3399
+ const output2 = outputFromResult(result);
3765
3400
  if (result.status === 0) {
3766
- return { ok: true, output: output3 };
3401
+ return { ok: true, output: output2 };
3767
3402
  }
3768
3403
  return {
3769
3404
  ok: false,
3770
- output: output3,
3771
- error: output3 ?? `${command} ${args.join(" ")} exited with status ${result.status ?? "unknown"}`
3405
+ output: output2,
3406
+ error: output2 ?? `${command} ${args.join(" ")} exited with status ${result.status ?? "unknown"}`
3772
3407
  };
3773
3408
  }
3774
3409
  function firstLine(value) {
@@ -3885,7 +3520,7 @@ function buildGlobalToolChecks(repoPath) {
3885
3520
  level: compose.ok ? "ok" : "error",
3886
3521
  summary: compose.ok ? "Docker Compose v2 is reachable." : "Docker Compose v2 is not reachable.",
3887
3522
  details: firstLine(compose.output) ?? compose.error,
3888
- suggestion: compose.ok ? void 0 : "Install/start Docker with Compose v2, then run: dev setup --yes"
3523
+ suggestion: compose.ok ? void 0 : "Install/start Docker with Compose v2, then run: devrouter setup --yes"
3889
3524
  });
3890
3525
  const mkcert = runTool("mkcert", ["-version"]);
3891
3526
  const brew = runTool("brew", ["--version"]);
@@ -3894,7 +3529,7 @@ function buildGlobalToolChecks(repoPath) {
3894
3529
  level: mkcert.ok ? "ok" : "warn",
3895
3530
  summary: mkcert.ok ? "mkcert is installed." : "mkcert is not installed.",
3896
3531
  details: mkcert.ok ? firstLine(mkcert.output) : mkcert.error,
3897
- suggestion: mkcert.ok ? void 0 : brew.ok ? "Install mkcert: brew install mkcert" : "Install mkcert for local HTTPS, then run: dev setup --yes"
3532
+ suggestion: mkcert.ok ? void 0 : brew.ok ? "Install mkcert: brew install mkcert" : "Install mkcert for local HTTPS, then run: devrouter setup --yes"
3898
3533
  });
3899
3534
  const devpod = runTool("devpod", ["version"]);
3900
3535
  checks.push({
@@ -4429,6 +4064,22 @@ async function buildDoctorReport(options = {}) {
4429
4064
  const config = runtimeConfig.config;
4430
4065
  loadedConfig = config;
4431
4066
  loadedWorkspace = runtimeConfig.workspace;
4067
+ const cliVersion = true ? "0.0.25" : "0.0.0-dev";
4068
+ const configVersion = config.devrouter?.version;
4069
+ if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
4070
+ addCheck(checks, {
4071
+ id: "repo.cli-outdated",
4072
+ level: "error",
4073
+ summary: `Installed CLI (${cliVersion}) is older than required repo version (${configVersion}).`,
4074
+ suggestion: "Upgrade CLI: npm install -g @devrouter/cli"
4075
+ });
4076
+ } else {
4077
+ addCheck(checks, {
4078
+ id: "repo.cli-outdated",
4079
+ level: "ok",
4080
+ summary: "Installed CLI version is compatible with repo configuration."
4081
+ });
4082
+ }
4432
4083
  const appNames = new Set(config.apps.map((app) => app.name));
4433
4084
  const missingDependencies = config.apps.flatMap(
4434
4085
  (app) => app.dependencies.filter((dependency) => !appNames.has(dependency.app)).map((dependency) => `${app.name}->${dependency.app}`)
@@ -4670,7 +4321,7 @@ async function runSetup(options = {}) {
4670
4321
  actions.push(action("failed", {
4671
4322
  id: "setup.confirmation",
4672
4323
  summary: "Setup requires --yes before mutating devrouter-owned machine state.",
4673
- suggestion: "Run: dev setup --yes"
4324
+ suggestion: "Run: devrouter setup --yes"
4674
4325
  }));
4675
4326
  const doctor2 = await buildDoctorReport({ repo: options.repo });
4676
4327
  const partialReport2 = { actions, checks: doctor2.checks };
@@ -4699,7 +4350,7 @@ async function runSetup(options = {}) {
4699
4350
  id: "global.router-files",
4700
4351
  summary: "Failed to ensure global router files.",
4701
4352
  details: message,
4702
- suggestion: "Check write access to ~/.config/devrouter, then run: dev setup --yes"
4353
+ suggestion: "Check write access to ~/.config/devrouter, then run: devrouter setup --yes"
4703
4354
  }));
4704
4355
  }
4705
4356
  try {
@@ -4715,7 +4366,7 @@ async function runSetup(options = {}) {
4715
4366
  id: "global.devnet",
4716
4367
  summary: "Failed to ensure shared Docker network devnet.",
4717
4368
  details: message,
4718
- suggestion: "Start Docker and verify Docker context, then run: dev setup --yes"
4369
+ suggestion: "Start Docker and verify Docker context, then run: devrouter setup --yes"
4719
4370
  }));
4720
4371
  }
4721
4372
  try {
@@ -4731,7 +4382,7 @@ async function runSetup(options = {}) {
4731
4382
  id: "global.router-stack",
4732
4383
  summary: "Failed to start shared Traefik router.",
4733
4384
  details: message,
4734
- suggestion: "Resolve Docker/port conflicts on 80, 443, or 5432, then run: dev setup --yes"
4385
+ suggestion: "Resolve Docker/port conflicts on 80, 443, or 5432, then run: devrouter setup --yes"
4735
4386
  }));
4736
4387
  }
4737
4388
  const mkcert = runTool("mkcert", ["-version"]);
@@ -4740,7 +4391,7 @@ async function runSetup(options = {}) {
4740
4391
  id: "global.tls",
4741
4392
  summary: "Skipped TLS setup because mkcert is not installed.",
4742
4393
  details: mkcert.error,
4743
- suggestion: "Install mkcert, then run: dev setup --yes"
4394
+ suggestion: "Install mkcert, then run: devrouter setup --yes"
4744
4395
  }));
4745
4396
  } else {
4746
4397
  try {
@@ -4756,7 +4407,7 @@ async function runSetup(options = {}) {
4756
4407
  id: "global.tls",
4757
4408
  summary: "Failed to install local TLS certificates.",
4758
4409
  details: message,
4759
- suggestion: "Run: dev tls install"
4410
+ suggestion: "Run: devrouter tls install"
4760
4411
  }));
4761
4412
  }
4762
4413
  }
@@ -4811,10 +4462,46 @@ var init_setup2 = __esm({
4811
4462
  });
4812
4463
 
4813
4464
  // src/util/ports.ts
4465
+ function parseSsPortListeners(stdout, port) {
4466
+ const listeners = [];
4467
+ const lines = stdout.split(/\r?\n/);
4468
+ for (const line of lines) {
4469
+ if (!line.trim()) continue;
4470
+ const parts = line.trim().split(/\s+/);
4471
+ if (parts.length < 4) continue;
4472
+ const localAddr = parts[3];
4473
+ const colonIdx = localAddr.lastIndexOf(":");
4474
+ if (colonIdx === -1) continue;
4475
+ const linePort = Number(localAddr.slice(colonIdx + 1));
4476
+ if (linePort !== port) continue;
4477
+ const usersCol = parts.slice(5).join(" ");
4478
+ const pidMatch = /pid=(\d+)/.exec(usersCol);
4479
+ const cmdMatch = /"([^"]+)"/.exec(usersCol);
4480
+ const command = cmdMatch ? cmdMatch[1] : "?";
4481
+ const pid = pidMatch ? pidMatch[1] : "?";
4482
+ listeners.push({
4483
+ port,
4484
+ command,
4485
+ pid,
4486
+ user: "?",
4487
+ address: localAddr
4488
+ });
4489
+ }
4490
+ return listeners;
4491
+ }
4814
4492
  function findPortListeners(port) {
4815
4493
  const result = (0, import_node_child_process6.spawnSync)("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN"], {
4816
4494
  encoding: "utf-8"
4817
4495
  });
4496
+ if (result.error && result.error.code === "ENOENT") {
4497
+ if (process.platform === "linux") {
4498
+ const ssResult = (0, import_node_child_process6.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
4499
+ if (ssResult.status === 0 && ssResult.stdout) {
4500
+ return parseSsPortListeners(ssResult.stdout, port);
4501
+ }
4502
+ }
4503
+ return [];
4504
+ }
4818
4505
  if (result.status !== 0 || !result.stdout.trim()) {
4819
4506
  return [];
4820
4507
  }
@@ -5542,44 +5229,17 @@ var repo_agents_exports = {};
5542
5229
  __export(repo_agents_exports, {
5543
5230
  runRepoAgentsCommand: () => runRepoAgentsCommand
5544
5231
  });
5545
- async function runRepoAgentsCommand(options, deps = {}) {
5232
+ async function runRepoAgentsCommand(options) {
5546
5233
  const repoPath = resolveRepoPath(options.repo);
5547
- let linearMetadata = null;
5548
5234
  const skill = ensureSkillFile(repoPath);
5549
5235
  process.stdout.write(`Wrote skill to ${skill.path}
5550
5236
  `);
5551
- if (options.withLinear) {
5552
- linearMetadata = await (deps.collectLinearMetadata ?? collectLinearWorkflowMetadata)();
5553
- if (linearMetadata.captureMode === "placeholder") {
5554
- process.stdout.write(
5555
- "Warning: non-interactive mode detected; wrote placeholder Linear mapping values. Re-run in a TTY to capture workspace/team/project.\n"
5556
- );
5557
- }
5558
- const linearSkills = ensureLinearWorkflowSkillFiles(repoPath);
5559
- for (const filePath of linearSkills.paths) {
5560
- process.stdout.write(`Wrote Linear workflow artifact to ${filePath}
5561
- `);
5562
- }
5563
- }
5564
5237
  const result = ensureAgentsMdSection(repoPath);
5565
5238
  if (!result.written) {
5566
5239
  process.stdout.write(`devrouter section already present: ${result.path}
5567
5240
  `);
5568
5241
  } else {
5569
5242
  process.stdout.write(`Wrote devrouter section to ${result.path}
5570
- `);
5571
- }
5572
- if (options.withLinear) {
5573
- if (!linearMetadata) {
5574
- throw new Error("Linear metadata was not collected.");
5575
- }
5576
- const linearAgents = ensureLinearWorkflowAgentsSection(repoPath, linearMetadata);
5577
- if (!linearAgents.written) {
5578
- process.stdout.write(`Linear workflow section already present: ${linearAgents.path}
5579
- `);
5580
- return;
5581
- }
5582
- process.stdout.write(`Wrote Linear workflow section to ${linearAgents.path}
5583
5243
  `);
5584
5244
  }
5585
5245
  }
@@ -5588,7 +5248,6 @@ var init_repo_agents = __esm({
5588
5248
  "use strict";
5589
5249
  init_repo_config();
5590
5250
  init_agents_md();
5591
- init_linear_onboarding();
5592
5251
  }
5593
5252
  });
5594
5253
 
@@ -5784,10 +5443,10 @@ function renderReadme(projectName) {
5784
5443
  Use this repo through the devcontainer, with devrouter providing stable local routes.
5785
5444
 
5786
5445
  \`\`\`bash
5787
- dev setup --yes
5446
+ devrouter setup --yes
5788
5447
  devpod up .
5789
- dev app run app --repo . --yes
5790
- dev app run db --repo . --yes
5448
+ devrouter app run app --repo . --yes
5449
+ devrouter app run db --repo . --yes
5791
5450
  \`\`\`
5792
5451
 
5793
5452
  - App: https://${projectName}.localhost
@@ -5797,19 +5456,19 @@ dev app run db --repo . --yes
5797
5456
  function postWriteNextSteps(repoPath) {
5798
5457
  const quotedRepoPath = shellSingleQuote(repoPath);
5799
5458
  return [
5800
- `Run: dev setup --repo ${quotedRepoPath} --yes`,
5459
+ `Run: devrouter setup --repo ${quotedRepoPath} --yes`,
5801
5460
  `Run: cd ${quotedRepoPath} && devpod up .`,
5802
- `Run: dev app run app --repo ${quotedRepoPath} --yes`,
5803
- `Run: dev app run db --repo ${quotedRepoPath} --yes`,
5804
- `Optional: dev repo agents --repo ${quotedRepoPath}`
5461
+ `Run: devrouter app run app --repo ${quotedRepoPath} --yes`,
5462
+ `Run: devrouter app run db --repo ${quotedRepoPath} --yes`,
5463
+ `Optional: devrouter repo agents --repo ${quotedRepoPath}`
5805
5464
  ];
5806
5465
  }
5807
5466
  function issueNextSteps(issues) {
5808
5467
  const steps = issues.filter((issue) => issue.level === "error").map((issue) => issue.suggestion).filter((suggestion) => Boolean(suggestion));
5809
5468
  if (steps.length > 0) {
5810
- return [...steps, "Re-run: dev repo devcontainer write --dry-run --json"];
5469
+ return [...steps, "Re-run: devrouter repo devcontainer write --dry-run --json"];
5811
5470
  }
5812
- return ["Resolve reported errors, then re-run: dev repo devcontainer write --dry-run --json"];
5471
+ return ["Resolve reported errors, then re-run: devrouter repo devcontainer write --dry-run --json"];
5813
5472
  }
5814
5473
  function packageManagerIssues(repo) {
5815
5474
  if (!repo.packageManager) {
@@ -5919,7 +5578,7 @@ function buildPlan(repoPath, dryRun, version) {
5919
5578
  filePlans.push({
5920
5579
  path: "AGENTS.md",
5921
5580
  action: "suggest",
5922
- reason: "run dev repo agents after reviewing the scaffold"
5581
+ reason: "run devrouter repo agents after reviewing the scaffold"
5923
5582
  });
5924
5583
  return {
5925
5584
  files,
@@ -5930,7 +5589,7 @@ function buildPlan(repoPath, dryRun, version) {
5930
5589
  dryRun,
5931
5590
  files: filePlans,
5932
5591
  issues,
5933
- nextSteps: issues.some((issue) => issue.level === "error") ? issueNextSteps(issues) : dryRun ? [`Review this plan, then run: dev repo devcontainer write --repo ${shellSingleQuote(repoPath)} --yes`] : postWriteNextSteps(repoPath)
5592
+ nextSteps: issues.some((issue) => issue.level === "error") ? issueNextSteps(issues) : dryRun ? [`Review this plan, then run: devrouter repo devcontainer write --repo ${shellSingleQuote(repoPath)} --yes`] : postWriteNextSteps(repoPath)
5934
5593
  }
5935
5594
  };
5936
5595
  }
@@ -5958,10 +5617,10 @@ function writeDevcontainer(options = {}) {
5958
5617
  id: "repo.devcontainer.confirmation",
5959
5618
  level: "error",
5960
5619
  summary: "Writing devcontainer files requires --yes.",
5961
- suggestion: `Run: dev repo devcontainer write --repo ${shellSingleQuote(repoPath)} --yes`
5620
+ suggestion: `Run: devrouter repo devcontainer write --repo ${shellSingleQuote(repoPath)} --yes`
5962
5621
  }
5963
5622
  ],
5964
- nextSteps: [`Run: dev repo devcontainer write --repo ${shellSingleQuote(repoPath)} --yes`]
5623
+ nextSteps: [`Run: devrouter repo devcontainer write --repo ${shellSingleQuote(repoPath)} --yes`]
5965
5624
  };
5966
5625
  }
5967
5626
  for (const file of files) {
@@ -6093,7 +5752,7 @@ function requiredFileChecks(repoPath) {
6093
5752
  id: "repo.devcontainer.verify-files",
6094
5753
  level: missing.length === 0 ? "ok" : "error",
6095
5754
  summary: missing.length === 0 ? "Required devcontainer/devrouter files are present." : `Missing required devcontainer/devrouter file(s): ${missing.join(", ")}.`,
6096
- suggestion: missing.length === 0 ? void 0 : "Run: dev repo devcontainer write --dry-run --json"
5755
+ suggestion: missing.length === 0 ? void 0 : "Run: devrouter repo devcontainer write --dry-run --json"
6097
5756
  };
6098
5757
  }
6099
5758
  function proxyConfigCheck(apps) {
@@ -6101,7 +5760,7 @@ function proxyConfigCheck(apps) {
6101
5760
  id: "repo.devcontainer.verify-proxy-apps",
6102
5761
  level: apps.length > 0 ? "ok" : "error",
6103
5762
  summary: apps.length > 0 ? `Found ${apps.length} proxy app(s) for devcontainer routing.` : "No proxy app entries found for devcontainer routing.",
6104
- suggestion: apps.length > 0 ? void 0 : "Add runtime: proxy app entries to .devrouter.yml or run: dev repo devcontainer write --dry-run --json"
5763
+ suggestion: apps.length > 0 ? void 0 : "Add runtime: proxy app entries to .devrouter.yml or run: devrouter repo devcontainer write --dry-run --json"
6105
5764
  };
6106
5765
  }
6107
5766
  function workspaceTemplateCheck(apps) {
@@ -6144,7 +5803,7 @@ function doctorGateCheck(doctor) {
6144
5803
  level: blocking.length === 0 ? "ok" : "error",
6145
5804
  summary: blocking.length === 0 ? "Doctor has no blocking devcontainer diagnostics." : `Doctor reported ${blocking.length} blocking devcontainer diagnostic(s).`,
6146
5805
  details: blocking.length > 0 ? blocking.map((check) => check.id).join(", ") : void 0,
6147
- suggestion: blocking.length > 0 ? "Run: dev doctor --repo <path> --json" : void 0
5806
+ suggestion: blocking.length > 0 ? "Run: devrouter doctor --repo <path> --json" : void 0
6148
5807
  };
6149
5808
  }
6150
5809
  function routeUrl2(host) {
@@ -6167,7 +5826,7 @@ function registerProxyRoute(repoPath, app, workspace) {
6167
5826
  const { port, upstreamHost: upstreamHost2 } = parseUpstream(app.upstream);
6168
5827
  if (app.protocol === "tcp" && !isTLSEnabled()) {
6169
5828
  throw new Error(
6170
- `App "${app.name}" is a TCP proxy route, which requires TLS (SNI). Run \`dev tls install\` first.`
5829
+ `App "${app.name}" is a TCP proxy route, which requires TLS (SNI). Run \`devrouter tls install\` first.`
6171
5830
  );
6172
5831
  }
6173
5832
  if (app.protocol === "tcp") {
@@ -6199,7 +5858,7 @@ async function liveChecks(repoPath, yes) {
6199
5858
  id: "repo.devcontainer.verify-live-confirmation",
6200
5859
  level: "error",
6201
5860
  summary: "Live devcontainer verification requires --yes.",
6202
- suggestion: "Run: dev repo devcontainer verify --live --yes --json"
5861
+ suggestion: "Run: devrouter repo devcontainer verify --live --yes --json"
6203
5862
  }
6204
5863
  ]
6205
5864
  };
@@ -6216,7 +5875,7 @@ async function liveChecks(repoPath, yes) {
6216
5875
  level: "error",
6217
5876
  summary: "Could not load runtime config for live verification.",
6218
5877
  details: error instanceof Error ? error.message : String(error),
6219
- suggestion: "Fix .devrouter.yml and re-run: dev repo devcontainer verify --live --yes --json"
5878
+ suggestion: "Fix .devrouter.yml and re-run: devrouter repo devcontainer verify --live --yes --json"
6220
5879
  }
6221
5880
  ]
6222
5881
  };
@@ -6268,7 +5927,7 @@ async function liveChecks(repoPath, yes) {
6268
5927
  level: "error",
6269
5928
  summary: `Could not register proxy route '${app.name}'.`,
6270
5929
  details: message,
6271
- suggestion: "Run: dev setup --yes, start the devcontainer, then retry live verification."
5930
+ suggestion: "Run: devrouter setup --yes, start the devcontainer, then retry live verification."
6272
5931
  });
6273
5932
  }
6274
5933
  }
@@ -6294,7 +5953,7 @@ async function verifyDevcontainer(options = {}) {
6294
5953
  level: "error",
6295
5954
  summary: "Could not load .devrouter.yml for devcontainer verification.",
6296
5955
  details: error instanceof Error ? error.message : String(error),
6297
- suggestion: "Fix .devrouter.yml and re-run: dev repo devcontainer verify --json"
5956
+ suggestion: "Fix .devrouter.yml and re-run: devrouter repo devcontainer verify --json"
6298
5957
  });
6299
5958
  }
6300
5959
  let liveRoutes;
@@ -7025,6 +6684,35 @@ function parseListeningPorts(outputText) {
7025
6684
  }
7026
6685
  return Array.from(ports.values()).sort((a, b) => a - b);
7027
6686
  }
6687
+ function parseSsListeningPorts(stdout, targetPids) {
6688
+ const ports = /* @__PURE__ */ new Set();
6689
+ const lines = stdout.split(/\r?\n/);
6690
+ for (const line of lines) {
6691
+ if (!line.trim()) continue;
6692
+ let matchedPid = false;
6693
+ for (const pid of targetPids) {
6694
+ if (line.includes(`pid=${pid}`)) {
6695
+ matchedPid = true;
6696
+ break;
6697
+ }
6698
+ }
6699
+ if (!matchedPid) {
6700
+ continue;
6701
+ }
6702
+ const parts = line.trim().split(/\s+/);
6703
+ if (parts.length < 4) continue;
6704
+ const localAddr = parts[3];
6705
+ const colonIdx = localAddr.lastIndexOf(":");
6706
+ if (colonIdx !== -1) {
6707
+ const portStr = localAddr.slice(colonIdx + 1);
6708
+ const port = Number(portStr);
6709
+ if (Number.isInteger(port) && port > 0) {
6710
+ ports.add(port);
6711
+ }
6712
+ }
6713
+ }
6714
+ return Array.from(ports.values()).sort((a, b) => a - b);
6715
+ }
7028
6716
  function detectListeningPorts(pids) {
7029
6717
  if (pids.length === 0) {
7030
6718
  return [];
@@ -7034,6 +6722,15 @@ function detectListeningPorts(pids) {
7034
6722
  ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", pids.join(",")],
7035
6723
  { encoding: "utf-8" }
7036
6724
  );
6725
+ if (result.error && result.error.code === "ENOENT") {
6726
+ if (process.platform === "linux") {
6727
+ const ssResult = (0, import_node_child_process11.spawnSync)("ss", ["-H", "-lntp", "-p"], { encoding: "utf-8" });
6728
+ if (ssResult.status === 0 && ssResult.stdout) {
6729
+ return parseSsListeningPorts(ssResult.stdout, new Set(pids));
6730
+ }
6731
+ }
6732
+ return [];
6733
+ }
7037
6734
  if (result.status !== 0) {
7038
6735
  return [];
7039
6736
  }
@@ -7185,7 +6882,7 @@ async function shouldStartDependencies(appName, dependencies, yes) {
7185
6882
  ).join(", ")}). Re-run with --yes in non-interactive mode.`
7186
6883
  );
7187
6884
  }
7188
- const rl = (0, import_promises2.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
6885
+ const rl = (0, import_promises.createInterface)({ input: import_node_process.stdin, output: import_node_process.stdout });
7189
6886
  try {
7190
6887
  const answer = await rl.question(
7191
6888
  `Start dependencies for '${appName}' (${dependencyNames2(dependencies).join(", ")})? [y/N] `
@@ -7474,14 +7171,14 @@ async function execWithAppEnv(options) {
7474
7171
  deps.stopDeps();
7475
7172
  }
7476
7173
  }
7477
- var import_node_net, import_promises2, import_node_child_process11, import_node_process2, POLL_INTERVAL_MS, DEFAULT_PORT_TIMEOUT_MS, PROCESS_TERMINATION_GRACE_MS;
7174
+ var import_node_net, import_promises, import_node_child_process11, import_node_process, POLL_INTERVAL_MS, DEFAULT_PORT_TIMEOUT_MS, PROCESS_TERMINATION_GRACE_MS;
7478
7175
  var init_app_run = __esm({
7479
7176
  "src/core/app-run.ts"() {
7480
7177
  "use strict";
7481
7178
  import_node_net = __toESM(require("net"));
7482
- import_promises2 = require("readline/promises");
7179
+ import_promises = require("readline/promises");
7483
7180
  import_node_child_process11 = require("child_process");
7484
- import_node_process2 = require("process");
7181
+ import_node_process = require("process");
7485
7182
  init_docker_run();
7486
7183
  init_repo_config();
7487
7184
  init_host_routes();
@@ -7670,7 +7367,11 @@ async function workspaceUp(branch, opts = {}) {
7670
7367
  if (!opts.noDevpod && hasDevpod()) {
7671
7368
  const dp = (0, import_node_child_process12.spawnSync)("devpod", ["up", worktreePath, "--id", ws, "--open-ide=false"], {
7672
7369
  stdio: "inherit",
7673
- env: { ...process.env, WORKSPACE: ws }
7370
+ env: {
7371
+ ...process.env,
7372
+ WORKSPACE: ws,
7373
+ DEVCONTAINER_COMPOSE_OVERLAY: "docker-compose.devrouter.yml"
7374
+ }
7674
7375
  });
7675
7376
  if (dp.status !== 0) {
7676
7377
  process.stderr.write(`Warning: 'devpod up' failed; continuing with route registration.
@@ -7854,7 +7555,7 @@ async function runVersionCommand(options, deps = {}) {
7854
7555
  `);
7855
7556
  process.stdout.write(`All upgrade targets: ${availableTargets.map((entry) => entry.version).join(", ")}
7856
7557
  `);
7857
- process.stdout.write(`Run: dev upgrade ${next.version}
7558
+ process.stdout.write(`Run: devrouter upgrade ${next.version}
7858
7559
  `);
7859
7560
  }
7860
7561
  var init_version = __esm({
@@ -7866,7 +7567,7 @@ var init_version = __esm({
7866
7567
 
7867
7568
  // src/cli.ts
7868
7569
  var import_commander = require("commander");
7869
- var CLI_VERSION = true ? "0.0.23" : "0.0.0-dev";
7570
+ var CLI_VERSION = true ? "0.0.25" : "0.0.0-dev";
7870
7571
  var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
7871
7572
  function withErrorHandling(action2) {
7872
7573
  return async (...args) => {
@@ -7881,8 +7582,8 @@ function withErrorHandling(action2) {
7881
7582
  };
7882
7583
  }
7883
7584
  var program = new import_commander.Command();
7884
- program.name("dev").description("Local dev router CLI for stable .localhost routing across repositories").showSuggestionAfterError(true).showHelpAfterError();
7885
- program.command("init").description("Print an AI onboarding prompt template for adapting a repository to devrouter").option("--repo <path>", "Repository path to embed in the prompt (defaults to current directory)").option("--entries-json <json>", "Optional JSON array of app entries to embed in the prompt").option("--json", "Output prompt and command intents as JSON").option("--write-agents", "Write/update devrouter section in AGENTS.md").option("--write-skill", "Write .agents/skills/devrouter/SKILL.md").option("--with-linear", "Include optional Linear workflow guidance/artifacts when writing").action(withErrorHandling(async (options) => {
7585
+ program.name("devrouter").description("Local dev router CLI for stable .localhost routing across repositories").showSuggestionAfterError(true).showHelpAfterError();
7586
+ program.command("init").description("Print an AI onboarding prompt template for adapting a repository to devrouter").option("--repo <path>", "Repository path to embed in the prompt (defaults to current directory)").option("--entries-json <json>", "Optional JSON array of app entries to embed in the prompt").option("--json", "Output prompt and command intents as JSON").option("--write-agents", "Write/update devrouter section in AGENTS.md").option("--write-skill", "Write .agents/skills/devrouter/SKILL.md").action(withErrorHandling(async (options) => {
7886
7587
  const { runInitCommand: runInitCommand2 } = await Promise.resolve().then(() => (init_init(), init_exports));
7887
7588
  await runInitCommand2(options);
7888
7589
  }));
@@ -7932,7 +7633,7 @@ repoCommand.command("inspect").description("Inspect repository stack facts for a
7932
7633
  const { runRepoInspectCommand: runRepoInspectCommand2 } = await Promise.resolve().then(() => (init_repo_inspect2(), repo_inspect_exports));
7933
7634
  await runRepoInspectCommand2(options);
7934
7635
  }));
7935
- repoCommand.command("agents").description("Write/update devrouter section in the repo's AGENTS.md").option("--repo <path>", "Repository path (defaults to current directory)").option("--with-linear", "Also install optional Linear workflow skill/assets and AGENTS section").action(withErrorHandling(async (options) => {
7636
+ repoCommand.command("agents").description("Write/update devrouter section in the repo's AGENTS.md").option("--repo <path>", "Repository path (defaults to current directory)").action(withErrorHandling(async (options) => {
7936
7637
  const { runRepoAgentsCommand: runRepoAgentsCommand2 } = await Promise.resolve().then(() => (init_repo_agents(), repo_agents_exports));
7937
7638
  await runRepoAgentsCommand2(options);
7938
7639
  }));