@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.
- package/LICENSE +21 -0
- package/README.md +75 -116
- package/dist/{dev.js → devrouter.js} +345 -644
- package/package.json +6 -4
- package/upgrade-prompts/0.0.24.md +10 -0
- package/upgrade-prompts/0.0.25.md +20 -0
|
@@ -158,32 +158,32 @@ This folder is managed by the devrouter CLI.
|
|
|
158
158
|
|
|
159
159
|
## Commands
|
|
160
160
|
|
|
161
|
-
-
|
|
162
|
-
-
|
|
163
|
-
-
|
|
164
|
-
-
|
|
165
|
-
-
|
|
166
|
-
-
|
|
167
|
-
-
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
-
|
|
171
|
-
-
|
|
172
|
-
-
|
|
173
|
-
-
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
-
|
|
177
|
-
-
|
|
178
|
-
-
|
|
179
|
-
-
|
|
180
|
-
-
|
|
181
|
-
-
|
|
182
|
-
-
|
|
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
|
|
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(
|
|
566
|
+
function upsertHostRoute(input2) {
|
|
560
567
|
return withStateLock(() => {
|
|
561
568
|
const routes = listHostRouteState();
|
|
562
|
-
const id = buildHostRouteId(
|
|
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:
|
|
568
|
-
host:
|
|
569
|
-
protocol:
|
|
570
|
-
tcpProtocol:
|
|
571
|
-
repoPath:
|
|
572
|
-
port:
|
|
573
|
-
mode:
|
|
574
|
-
upstreamHost:
|
|
575
|
-
pid:
|
|
576
|
-
command:
|
|
577
|
-
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
|
-
|
|
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(
|
|
1486
|
-
if (!
|
|
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(
|
|
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 `
|
|
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
|
|
1607
|
-
"-
|
|
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`, `
|
|
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 `
|
|
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, `
|
|
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
|
-
"- `
|
|
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: `
|
|
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 `
|
|
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
|
-
"- `
|
|
1645
|
-
"- With TLS enabled, `
|
|
1646
|
-
"- Robust one-shot migrate example (argv-safe, no nested shell quoting): `
|
|
1647
|
-
"- Robust one-shot seed example: `
|
|
1648
|
-
"- Environment probe/debug example (run before migrations): `
|
|
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 `
|
|
1654
|
-
"2) Run `
|
|
1655
|
-
"3) For the supported Node/pnpm/Postgres devcontainer shape, run `
|
|
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 `
|
|
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
|
-
"-
|
|
1663
|
-
"-
|
|
1664
|
-
"-
|
|
1665
|
-
"-
|
|
1666
|
-
"-
|
|
1667
|
-
"-
|
|
1668
|
-
"- After `devpod up <REPO_PATH>`:
|
|
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
|
-
"-
|
|
1672
|
-
"-
|
|
1673
|
-
"-
|
|
1674
|
-
"- For each entry (when safe):
|
|
1675
|
-
"- Run one-shot commands with dep env:
|
|
1676
|
-
"- Probe effective env before migration/seed:
|
|
1677
|
-
"-
|
|
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
|
-
" -
|
|
1691
|
-
" -
|
|
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: "
|
|
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: "
|
|
1743
|
+
command: "devrouter -V",
|
|
1729
1744
|
purpose: "Show installed CLI version, local repo version, and next upgrade target."
|
|
1730
1745
|
},
|
|
1731
1746
|
{
|
|
1732
|
-
command: "
|
|
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: "
|
|
1751
|
+
command: "devrouter setup",
|
|
1737
1752
|
purpose: "Run first-time devrouter machine setup and report structured diagnostics."
|
|
1738
1753
|
},
|
|
1739
1754
|
{
|
|
1740
|
-
command: "
|
|
1755
|
+
command: "devrouter up",
|
|
1741
1756
|
purpose: "Start shared Traefik and ensure the shared devnet network."
|
|
1742
1757
|
},
|
|
1743
|
-
{ command: "
|
|
1758
|
+
{ command: "devrouter down", purpose: "Stop the shared Traefik router stack." },
|
|
1744
1759
|
{
|
|
1745
|
-
command: "
|
|
1760
|
+
command: "devrouter status",
|
|
1746
1761
|
purpose: "Show router/container/network/TLS health and bound ports."
|
|
1747
1762
|
},
|
|
1748
1763
|
{
|
|
1749
|
-
command: "
|
|
1764
|
+
command: "devrouter doctor",
|
|
1750
1765
|
purpose: "Run deep diagnostics across global router state and repo config."
|
|
1751
1766
|
},
|
|
1752
1767
|
{
|
|
1753
|
-
command: "
|
|
1768
|
+
command: "devrouter ls",
|
|
1754
1769
|
purpose: "List active HTTP and TCP routes resolved by devrouter."
|
|
1755
1770
|
},
|
|
1756
1771
|
{
|
|
1757
|
-
command: "
|
|
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: "
|
|
1776
|
+
command: "devrouter tls install",
|
|
1762
1777
|
purpose: "Install mkcert certs and enable TLS/HTTPS for local routing."
|
|
1763
1778
|
},
|
|
1764
1779
|
{
|
|
1765
|
-
command: "
|
|
1780
|
+
command: "devrouter repo init",
|
|
1766
1781
|
purpose: "Create `.devrouter.yml` in a target repository."
|
|
1767
1782
|
},
|
|
1768
1783
|
{
|
|
1769
|
-
command: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
1796
|
+
command: "devrouter app add",
|
|
1782
1797
|
purpose: "Add or update one app entry in `.devrouter.yml`."
|
|
1783
1798
|
},
|
|
1784
|
-
{ command: "
|
|
1799
|
+
{ command: "devrouter app ls", purpose: "List app entries from `.devrouter.yml`." },
|
|
1785
1800
|
{
|
|
1786
|
-
command: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
1799
|
-
purpose: "Write/update devrouter section in the repo's AGENTS.md and
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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
|
-
"- `
|
|
1831
|
-
"- `
|
|
1832
|
-
"- `
|
|
1833
|
-
"- `
|
|
1834
|
-
"- `
|
|
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
|
-
|
|
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
|
|
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 \`
|
|
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 (\`
|
|
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, \`
|
|
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**: \`
|
|
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
|
-
\`
|
|
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
|
-
\`
|
|
2057
|
+
\`devrouter app exec <app> --yes -- printenv DB_URL DB_HOST DB_PORT DB_SHADOW_URL\`
|
|
2177
2058
|
- Canonical Doppler migrate command:
|
|
2178
|
-
\`
|
|
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 \`
|
|
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 \`
|
|
2189
|
-
- Use \`
|
|
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 \`
|
|
2192
|
-
- Re-run validation after upgrade: \`
|
|
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
|
-
- \`
|
|
2208
|
-
- \`
|
|
2209
|
-
- \`
|
|
2210
|
-
- \`
|
|
2211
|
-
- \`
|
|
2212
|
-
- \`
|
|
2213
|
-
- \`
|
|
2214
|
-
- \`
|
|
2215
|
-
- \`
|
|
2216
|
-
- \`
|
|
2217
|
-
- \`
|
|
2218
|
-
- \`
|
|
2219
|
-
- \`
|
|
2220
|
-
- \`
|
|
2221
|
-
- \`
|
|
2222
|
-
- \`
|
|
2223
|
-
- \`
|
|
2224
|
-
- \`
|
|
2225
|
-
- \`
|
|
2226
|
-
- \`
|
|
2227
|
-
- \`
|
|
2228
|
-
- \`
|
|
2229
|
-
- \`
|
|
2230
|
-
- \`
|
|
2231
|
-
- \`
|
|
2232
|
-
- \`
|
|
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. \`
|
|
2239
|
-
2. \`
|
|
2240
|
-
3. \`
|
|
2241
|
-
4. \`
|
|
2242
|
-
5. \`
|
|
2243
|
-
6. \`
|
|
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. \`
|
|
2115
|
+
8. \`devrouter repo devcontainer verify --repo . --live --yes --json\`
|
|
2246
2116
|
|
|
2247
2117
|
For existing host/docker runtime apps:
|
|
2248
2118
|
|
|
2249
|
-
1. \`
|
|
2250
|
-
2. \`
|
|
2251
|
-
3. \`
|
|
2252
|
-
4. \`
|
|
2253
|
-
5. \`
|
|
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 \`
|
|
2125
|
+
7. For TCP/Postgres, use \`devrouter open <name>\` for the connection hint.
|
|
2256
2126
|
|
|
2257
2127
|
## Runtime behavior notes
|
|
2258
2128
|
|
|
2259
|
-
- \`
|
|
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 \`
|
|
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 (\`
|
|
2264
|
-
- \`
|
|
2265
|
-
- \`
|
|
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
|
|
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 `
|
|
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
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
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
|
|
3752
|
-
return
|
|
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
|
|
3399
|
+
const output2 = outputFromResult(result);
|
|
3765
3400
|
if (result.status === 0) {
|
|
3766
|
-
return { ok: true, output:
|
|
3401
|
+
return { ok: true, output: output2 };
|
|
3767
3402
|
}
|
|
3768
3403
|
return {
|
|
3769
3404
|
ok: false,
|
|
3770
|
-
output:
|
|
3771
|
-
error:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
5446
|
+
devrouter setup --yes
|
|
5788
5447
|
devpod up .
|
|
5789
|
-
|
|
5790
|
-
|
|
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:
|
|
5459
|
+
`Run: devrouter setup --repo ${quotedRepoPath} --yes`,
|
|
5801
5460
|
`Run: cd ${quotedRepoPath} && devpod up .`,
|
|
5802
|
-
`Run:
|
|
5803
|
-
`Run:
|
|
5804
|
-
`Optional:
|
|
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:
|
|
5469
|
+
return [...steps, "Re-run: devrouter repo devcontainer write --dry-run --json"];
|
|
5811
5470
|
}
|
|
5812
|
-
return ["Resolve reported errors, then re-run:
|
|
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
|
|
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:
|
|
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:
|
|
5620
|
+
suggestion: `Run: devrouter repo devcontainer write --repo ${shellSingleQuote(repoPath)} --yes`
|
|
5962
5621
|
}
|
|
5963
5622
|
],
|
|
5964
|
-
nextSteps: [`Run:
|
|
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:
|
|
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:
|
|
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:
|
|
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 \`
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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,
|
|
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,
|
|
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
|
-
|
|
7179
|
+
import_promises = require("readline/promises");
|
|
7483
7180
|
import_node_child_process11 = require("child_process");
|
|
7484
|
-
|
|
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: {
|
|
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:
|
|
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.
|
|
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("
|
|
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").
|
|
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)").
|
|
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
|
}));
|