devchain-cli 0.12.1 → 0.12.3
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/README.md +4 -0
- package/dist/cli.js +154 -33
- package/dist/drizzle/0060_huge_proemial_gods.sql +12 -0
- package/dist/drizzle/0061_easy_silver_surfer.sql +2 -0
- package/dist/drizzle/meta/0060_snapshot.json +5170 -0
- package/dist/drizzle/meta/0061_snapshot.json +5163 -0
- package/dist/drizzle/meta/_journal.json +14 -0
- package/dist/server/common/config/env.config.d.ts +1 -1
- package/dist/server/common/config/env.config.js +9 -1
- package/dist/server/common/config/env.config.js.map +1 -1
- package/dist/server/common/config/host-helpers.d.ts +7 -0
- package/dist/server/common/config/host-helpers.js +34 -0
- package/dist/server/common/config/host-helpers.js.map +1 -0
- package/dist/server/main.js +5 -3
- package/dist/server/main.js.map +1 -1
- package/dist/server/modules/core/core-normal.module.js +3 -2
- package/dist/server/modules/core/core-normal.module.js.map +1 -1
- package/dist/server/modules/core/services/gemini-trusted-folders.service.d.ts +26 -0
- package/dist/server/modules/core/services/gemini-trusted-folders.service.js +181 -0
- package/dist/server/modules/core/services/gemini-trusted-folders.service.js.map +1 -0
- package/dist/server/modules/core/services/preflight.service.js +2 -1
- package/dist/server/modules/core/services/preflight.service.js.map +1 -1
- package/dist/server/modules/core/services/provider-mcp-ensure.service.d.ts +10 -1
- package/dist/server/modules/core/services/provider-mcp-ensure.service.js +86 -19
- package/dist/server/modules/core/services/provider-mcp-ensure.service.js.map +1 -1
- package/dist/server/modules/events/catalog/claude.hooks.session.started.d.ts +2 -2
- package/dist/server/modules/events/catalog/index.d.ts +20 -2
- package/dist/server/modules/events/catalog/index.js +5 -1
- package/dist/server/modules/events/catalog/index.js.map +1 -1
- package/dist/server/modules/events/catalog/session.restored.d.ts +21 -0
- package/dist/server/modules/events/catalog/session.restored.js +14 -0
- package/dist/server/modules/events/catalog/session.restored.js.map +1 -0
- package/dist/server/modules/hooks/dtos/hook-event.dto.d.ts +2 -2
- package/dist/server/modules/mcp/services/mcp-provider-registration.service.d.ts +1 -0
- package/dist/server/modules/mcp/services/mcp-provider-registration.service.js +91 -4
- package/dist/server/modules/mcp/services/mcp-provider-registration.service.js.map +1 -1
- package/dist/server/modules/projects/controllers/projects.controller.d.ts +4 -1
- package/dist/server/modules/projects/controllers/projects.controller.js +1 -1
- package/dist/server/modules/projects/controllers/projects.controller.js.map +1 -1
- package/dist/server/modules/projects/projects.module.js +11 -2
- package/dist/server/modules/projects/projects.module.js.map +1 -1
- package/dist/server/modules/projects/services/project-provider-provisioning.service.d.ts +18 -0
- package/dist/server/modules/projects/services/project-provider-provisioning.service.js +92 -0
- package/dist/server/modules/projects/services/project-provider-provisioning.service.js.map +1 -0
- package/dist/server/modules/projects/services/projects.service.d.ts +9 -2
- package/dist/server/modules/projects/services/projects.service.js +31 -4
- package/dist/server/modules/projects/services/projects.service.js.map +1 -1
- package/dist/server/modules/providers/adapters/claude.adapter.d.ts +4 -1
- package/dist/server/modules/providers/adapters/claude.adapter.js +6 -0
- package/dist/server/modules/providers/adapters/claude.adapter.js.map +1 -1
- package/dist/server/modules/providers/adapters/codex.adapter.d.ts +4 -1
- package/dist/server/modules/providers/adapters/codex.adapter.js +6 -0
- package/dist/server/modules/providers/adapters/codex.adapter.js.map +1 -1
- package/dist/server/modules/providers/adapters/gemini.adapter.d.ts +6 -1
- package/dist/server/modules/providers/adapters/gemini.adapter.js +14 -3
- package/dist/server/modules/providers/adapters/gemini.adapter.js.map +1 -1
- package/dist/server/modules/providers/adapters/opencode.adapter.d.ts +4 -1
- package/dist/server/modules/providers/adapters/opencode.adapter.js +6 -0
- package/dist/server/modules/providers/adapters/opencode.adapter.js.map +1 -1
- package/dist/server/modules/providers/adapters/provider-adapter.interface.d.ts +10 -0
- package/dist/server/modules/providers/controllers/providers.controller.d.ts +1 -0
- package/dist/server/modules/providers/controllers/providers.controller.js +1 -0
- package/dist/server/modules/providers/controllers/providers.controller.js.map +1 -1
- package/dist/server/modules/reviews/dtos/review.dto.d.ts +2 -2
- package/dist/server/modules/session-reader/data/pricing.json +20 -0
- package/dist/server/modules/session-reader/services/transcript-persistence.listener.js +3 -3
- package/dist/server/modules/session-reader/services/transcript-persistence.listener.js.map +1 -1
- package/dist/server/modules/sessions/controllers/sessions.controller.d.ts +3 -1
- package/dist/server/modules/sessions/controllers/sessions.controller.js +57 -0
- package/dist/server/modules/sessions/controllers/sessions.controller.js.map +1 -1
- package/dist/server/modules/sessions/dtos/sessions.dto.d.ts +25 -1
- package/dist/server/modules/sessions/dtos/sessions.dto.js +4 -1
- package/dist/server/modules/sessions/dtos/sessions.dto.js.map +1 -1
- package/dist/server/modules/sessions/services/sessions.service.d.ts +35 -1
- package/dist/server/modules/sessions/services/sessions.service.js +480 -118
- package/dist/server/modules/sessions/services/sessions.service.js.map +1 -1
- package/dist/server/modules/sessions/utils/env-builder.d.ts +1 -1
- package/dist/server/modules/sessions/utils/env-builder.js +3 -3
- package/dist/server/modules/sessions/utils/env-builder.js.map +1 -1
- package/dist/server/modules/storage/db/schema.d.ts +38 -2
- package/dist/server/modules/storage/db/schema.js +4 -1
- package/dist/server/modules/storage/db/schema.js.map +1 -1
- package/dist/server/modules/subscribers/events/event-fields-catalog.js +12 -0
- package/dist/server/modules/subscribers/events/event-fields-catalog.js.map +1 -1
- package/dist/server/modules/terminal/gateways/terminal.gateway.d.ts +6 -0
- package/dist/server/modules/terminal/gateways/terminal.gateway.js +17 -0
- package/dist/server/modules/terminal/gateways/terminal.gateway.js.map +1 -1
- package/dist/server/templates/3-agents-dev.json +111 -64
- package/dist/server/templates/5-agents-dev.json +153 -106
- package/dist/server/templates/teams-dev.json +160 -112
- package/dist/server/test-setup-node.js +7 -1
- package/dist/server/test-setup-node.js.map +1 -1
- package/dist/server/test-setup.js +7 -1
- package/dist/server/test-setup.js.map +1 -1
- package/dist/server/tsconfig.tsbuildinfo +1 -1
- package/dist/server/ui/assets/{ReviewDetailPage-CEMxN6RQ.js → ReviewDetailPage-Dy3PfRrg.js} +1 -1
- package/dist/server/ui/assets/{ReviewsPage-BzMksnGd.js → ReviewsPage-DUPZ_NtL.js} +1 -1
- package/dist/server/ui/assets/index-BzphIngp.css +32 -0
- package/dist/server/ui/assets/{index-Csagg3g2.js → index-CIlp3VXc.js} +219 -219
- package/dist/server/ui/assets/{useReviewSubscription-B2ejeWE4.js → useReviewSubscription-jEyfNAEJ.js} +1 -1
- package/dist/server/ui/index.html +2 -2
- package/dist/templates/3-agents-dev.json +111 -64
- package/dist/templates/5-agents-dev.json +153 -106
- package/dist/templates/teams-dev.json +160 -112
- package/package.json +18 -3
- package/dist/server/ui/assets/index-ChJ1IUMI.css +0 -32
package/README.md
CHANGED
|
@@ -89,6 +89,9 @@ devchain start --project /path/to/your/project
|
|
|
89
89
|
# Run in foreground with logs
|
|
90
90
|
devchain start --foreground
|
|
91
91
|
|
|
92
|
+
# Bind to all interfaces for VM/remote browser access (prints loud security warning)
|
|
93
|
+
devchain start --host 0.0.0.0
|
|
94
|
+
|
|
92
95
|
# Stop the server
|
|
93
96
|
devchain stop
|
|
94
97
|
```
|
|
@@ -108,6 +111,7 @@ On first run, import a template from the project page to provision your agent te
|
|
|
108
111
|
| Option | Description |
|
|
109
112
|
|--------|-------------|
|
|
110
113
|
| `-p, --port <number>` | Port to run on (default: 3000 or next available) |
|
|
114
|
+
| `--host <address>` | Bind address (default: `127.0.0.1`; use `0.0.0.0` for remote/VM access — see [docs/setup.md](docs/setup.md)) |
|
|
111
115
|
| `-f, --foreground` | Run in foreground with visible logs |
|
|
112
116
|
| `--no-open` | Don't open browser automatically |
|
|
113
117
|
| `--db <path>` | Custom database directory path |
|
package/dist/cli.js
CHANGED
|
@@ -62,6 +62,87 @@ function isNewerVersion(latest, current) {
|
|
|
62
62
|
return false;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
// ── Host normalization + URL builder helpers ──────────────────────────────────
|
|
66
|
+
|
|
67
|
+
function normalizeHost(input) {
|
|
68
|
+
const raw = input ?? '';
|
|
69
|
+
|
|
70
|
+
// Check for control characters before trimming (trim strips \n, \t, etc.)
|
|
71
|
+
for (let i = 0; i < raw.length; i++) {
|
|
72
|
+
const code = raw.charCodeAt(i);
|
|
73
|
+
if (code < 0x20 || code === 0x7f) {
|
|
74
|
+
throw new Error(`Invalid host: contains control characters`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const trimmed = raw.trim();
|
|
79
|
+
if (!trimmed) return '127.0.0.1';
|
|
80
|
+
|
|
81
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
82
|
+
throw new Error(`Invalid host "${trimmed}": pass a hostname or IP, not a URL`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (trimmed === '*') {
|
|
86
|
+
throw new Error(
|
|
87
|
+
'Invalid host "*": use 0.0.0.0 for all IPv4 interfaces or :: for all IPv6 interfaces',
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (/^\[.*\]$/.test(trimmed)) {
|
|
92
|
+
const inner = trimmed.slice(1, -1);
|
|
93
|
+
throw new Error(`Invalid host "${trimmed}": drop the brackets — use ${inner}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Distinguish IPv6 from host:port — IPv6 has 2+ colons or starts with : or contains ::
|
|
97
|
+
const colonCount = (trimmed.match(/:/g) || []).length;
|
|
98
|
+
const looksLikeIpv6 = colonCount >= 2 || trimmed.startsWith(':') || trimmed.includes('::');
|
|
99
|
+
|
|
100
|
+
if (!looksLikeIpv6 && colonCount === 1) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Invalid host "${trimmed}": looks like host:port — pass only the host part`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return trimmed;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function isWildcardHost(host) {
|
|
110
|
+
return host === '0.0.0.0' || host === '::';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function isNonLoopbackHost(host) {
|
|
114
|
+
if (isWildcardHost(host)) return true;
|
|
115
|
+
const lower = host.toLowerCase();
|
|
116
|
+
return lower !== '127.0.0.1' && lower !== '::1' && lower !== 'localhost';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function connectableHost(host) {
|
|
120
|
+
if (host === '0.0.0.0') return '127.0.0.1';
|
|
121
|
+
if (host === '::') return '::1';
|
|
122
|
+
return host;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function formatHostForUrl(host) {
|
|
126
|
+
if (/^\[.*\]$/.test(host)) {
|
|
127
|
+
throw new Error(`formatHostForUrl received already-bracketed input: ${host}`);
|
|
128
|
+
}
|
|
129
|
+
// IPv6 detection: contains colon(s)
|
|
130
|
+
if (host.includes(':')) return `[${host}]`;
|
|
131
|
+
return host;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function buildInternalBaseUrl({ host, port }) {
|
|
135
|
+
return `http://${formatHostForUrl(connectableHost(host))}:${port}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function buildDisplayUrls({ host, port }) {
|
|
139
|
+
return {
|
|
140
|
+
primary: buildInternalBaseUrl({ host, port }),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ── End host helpers ──────────────────────────────────────────────────────────
|
|
145
|
+
|
|
65
146
|
function getChangelogBetweenVersions(changelog, fromVersion, toVersion) {
|
|
66
147
|
if (!changelog || typeof changelog !== 'object') return [];
|
|
67
148
|
|
|
@@ -486,7 +567,7 @@ function resolveDevchainApiBaseUrlForRestart({
|
|
|
486
567
|
);
|
|
487
568
|
}
|
|
488
569
|
|
|
489
|
-
return
|
|
570
|
+
return buildInternalBaseUrl({ host: pidData.host || '127.0.0.1', port });
|
|
490
571
|
}
|
|
491
572
|
|
|
492
573
|
function normalizeWorktreeListPayload(payload) {
|
|
@@ -956,9 +1037,9 @@ function getPidFilePath() {
|
|
|
956
1037
|
return join(devchainDir, 'devchain.pid');
|
|
957
1038
|
}
|
|
958
1039
|
|
|
959
|
-
function writePidFile(port) {
|
|
1040
|
+
function writePidFile(port, host) {
|
|
960
1041
|
const pidFile = getPidFilePath();
|
|
961
|
-
const data = JSON.stringify({ pid: process.pid, port, timestamp: Date.now() });
|
|
1042
|
+
const data = JSON.stringify({ pid: process.pid, port, host: host || '127.0.0.1', timestamp: Date.now() });
|
|
962
1043
|
writeFileSync(pidFile, data, 'utf8');
|
|
963
1044
|
}
|
|
964
1045
|
|
|
@@ -969,7 +1050,9 @@ function readPidFile() {
|
|
|
969
1050
|
}
|
|
970
1051
|
try {
|
|
971
1052
|
const data = readFileSync(pidFile, 'utf8');
|
|
972
|
-
|
|
1053
|
+
const parsed = JSON.parse(data);
|
|
1054
|
+
if (parsed && !parsed.host) parsed.host = '127.0.0.1';
|
|
1055
|
+
return parsed;
|
|
973
1056
|
} catch {
|
|
974
1057
|
return null;
|
|
975
1058
|
}
|
|
@@ -1287,6 +1370,7 @@ async function main(argv) {
|
|
|
1287
1370
|
.command('start [args...]')
|
|
1288
1371
|
.description('Start the Devchain local app')
|
|
1289
1372
|
.option('-p, --port <number>', 'Port to listen on (default: 3000 or next free)')
|
|
1373
|
+
.option('--host <address>', 'Host/bind address (default: 127.0.0.1; use 0.0.0.0 for all IPv4 interfaces, :: for all IPv6)')
|
|
1290
1374
|
.option('-f, --foreground', 'Run in foreground (attached to terminal). Shows startup output with colors and spinners.')
|
|
1291
1375
|
.option('-d, --detach', 'Run in background as a detached process (default). Use "devchain stop" to stop it.')
|
|
1292
1376
|
.option('--no-open', 'Do not open a browser window')
|
|
@@ -1337,7 +1421,7 @@ async function main(argv) {
|
|
|
1337
1421
|
const existingPid = readPidFile();
|
|
1338
1422
|
if (existingPid && isProcessRunning(existingPid.pid)) {
|
|
1339
1423
|
console.error(`Devchain is already running (PID ${existingPid.pid}, port ${existingPid.port})`);
|
|
1340
|
-
console.error(`Access it at:
|
|
1424
|
+
console.error(`Access it at: ${buildDisplayUrls({ host: existingPid.host || '127.0.0.1', port: existingPid.port }).primary}`);
|
|
1341
1425
|
console.error('Use "devchain stop" to stop it first.');
|
|
1342
1426
|
process.exit(1);
|
|
1343
1427
|
}
|
|
@@ -1435,6 +1519,35 @@ async function main(argv) {
|
|
|
1435
1519
|
port = await getPort({ port: preferPort });
|
|
1436
1520
|
}
|
|
1437
1521
|
|
|
1522
|
+
// Resolve effective host before detach so child inherits normalized HOST env.
|
|
1523
|
+
// The --host flag also flows through childArgs (detach filter only strips --port
|
|
1524
|
+
// and --detach), so the child receives the effective host via two independent paths.
|
|
1525
|
+
let effectiveHost;
|
|
1526
|
+
try {
|
|
1527
|
+
effectiveHost = normalizeHost(opts.host ?? process.env.HOST ?? '');
|
|
1528
|
+
} catch (e) {
|
|
1529
|
+
console.error(e.message);
|
|
1530
|
+
process.exit(1);
|
|
1531
|
+
}
|
|
1532
|
+
process.env.HOST = effectiveHost;
|
|
1533
|
+
|
|
1534
|
+
// Security warning for non-loopback bind (before detach so parent terminal sees it)
|
|
1535
|
+
if (!worktreeRuntimeMode && isNonLoopbackHost(effectiveHost)) {
|
|
1536
|
+
console.error('');
|
|
1537
|
+
console.error(`⚠ DevChain is binding to ${effectiveHost}. There is no remote authentication`);
|
|
1538
|
+
console.error(' boundary; the API, terminals, MCP, and project files are exposed');
|
|
1539
|
+
console.error(' to anyone who can reach this address. Use only on a trusted');
|
|
1540
|
+
console.error(' network, VPN, or behind firewall rules.');
|
|
1541
|
+
if (opts.dev) {
|
|
1542
|
+
console.error('');
|
|
1543
|
+
console.error(' Note: --dev mode runs the UI on a separate Vite dev server bound to');
|
|
1544
|
+
console.error(' 127.0.0.1 only. Remote access affects the API server only. For full');
|
|
1545
|
+
console.error(' remote access including the UI dev server, use production mode');
|
|
1546
|
+
console.error(' (no --dev flag).');
|
|
1547
|
+
}
|
|
1548
|
+
console.error('');
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1438
1551
|
// === DETACH POINT ===
|
|
1439
1552
|
// All interactive prompts are done. Now spawn the detached child if needed.
|
|
1440
1553
|
if (shouldDetach) {
|
|
@@ -1473,9 +1586,8 @@ async function main(argv) {
|
|
|
1473
1586
|
process.exit(0);
|
|
1474
1587
|
}
|
|
1475
1588
|
|
|
1476
|
-
// Apply env before requiring the server
|
|
1589
|
+
// Apply env before requiring the server (HOST already set pre-detach)
|
|
1477
1590
|
process.env.PORT = String(port);
|
|
1478
|
-
process.env.HOST = process.env.HOST || '127.0.0.1';
|
|
1479
1591
|
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
|
|
1480
1592
|
const dbEnv = parseDbPath(opts.db);
|
|
1481
1593
|
if (dbEnv.DB_PATH) process.env.DB_PATH = dbEnv.DB_PATH;
|
|
@@ -1534,10 +1646,11 @@ async function main(argv) {
|
|
|
1534
1646
|
detached: platform() !== 'win32', // Create process group on Unix
|
|
1535
1647
|
});
|
|
1536
1648
|
|
|
1537
|
-
const
|
|
1649
|
+
const internalBaseUrl = buildInternalBaseUrl({ host: effectiveHost, port });
|
|
1650
|
+
const displayUrl = buildDisplayUrls({ host: effectiveHost, port }).primary;
|
|
1538
1651
|
|
|
1539
1652
|
// Wait for API to be ready (longer timeout for dev mode compilation)
|
|
1540
|
-
const ready = await waitForHealth(`${
|
|
1653
|
+
const ready = await waitForHealth(`${internalBaseUrl}/health`, { timeoutMs: 60000 });
|
|
1541
1654
|
if (!ready) {
|
|
1542
1655
|
cli.error('API did not become ready in time');
|
|
1543
1656
|
killProcessGroup(nestProcess);
|
|
@@ -1545,8 +1658,8 @@ async function main(argv) {
|
|
|
1545
1658
|
}
|
|
1546
1659
|
|
|
1547
1660
|
cli.blank();
|
|
1548
|
-
cli.success(`API ready at ${
|
|
1549
|
-
cli.info(`API docs: ${
|
|
1661
|
+
cli.success(`API ready at ${displayUrl}`);
|
|
1662
|
+
cli.info(`API docs: ${displayUrl}/api/docs`);
|
|
1550
1663
|
|
|
1551
1664
|
// Ensure provider rows exist
|
|
1552
1665
|
if (
|
|
@@ -1554,7 +1667,7 @@ async function main(argv) {
|
|
|
1554
1667
|
&& opts.__providersDetected
|
|
1555
1668
|
&& opts.__providersDetected.size > 0
|
|
1556
1669
|
) {
|
|
1557
|
-
await ensureProvidersInDb(
|
|
1670
|
+
await ensureProvidersInDb(internalBaseUrl, opts.__providersDetected, log);
|
|
1558
1671
|
}
|
|
1559
1672
|
|
|
1560
1673
|
// Determine startup path for MCP validation
|
|
@@ -1564,7 +1677,7 @@ async function main(argv) {
|
|
|
1564
1677
|
|
|
1565
1678
|
// Validate MCP for all providers
|
|
1566
1679
|
if (!worktreeRuntimeMode) {
|
|
1567
|
-
await validateMcpForProviders(
|
|
1680
|
+
await validateMcpForProviders(internalBaseUrl, cli, opts, log, startupPath);
|
|
1568
1681
|
}
|
|
1569
1682
|
|
|
1570
1683
|
// Note: Claude bypass prompt already handled before server start
|
|
@@ -1585,12 +1698,12 @@ async function main(argv) {
|
|
|
1585
1698
|
cli.blank();
|
|
1586
1699
|
cli.success('Development servers running');
|
|
1587
1700
|
cli.info(`${devUiConfig.logLabel}: ${devUiConfig.url}`);
|
|
1588
|
-
cli.info(`API: ${
|
|
1701
|
+
cli.info(`API: ${displayUrl}`);
|
|
1589
1702
|
cli.blank();
|
|
1590
1703
|
|
|
1591
1704
|
// Write PID file for top-level runtime only
|
|
1592
1705
|
if (!worktreeRuntimeMode) {
|
|
1593
|
-
writePidFile(port);
|
|
1706
|
+
writePidFile(port, effectiveHost);
|
|
1594
1707
|
}
|
|
1595
1708
|
|
|
1596
1709
|
// Handle cleanup on exit - kill entire process groups
|
|
@@ -1640,10 +1753,11 @@ async function main(argv) {
|
|
|
1640
1753
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
1641
1754
|
require(serverEntry);
|
|
1642
1755
|
|
|
1643
|
-
const
|
|
1644
|
-
const
|
|
1756
|
+
const internalBaseUrl = buildInternalBaseUrl({ host: effectiveHost, port });
|
|
1757
|
+
const displayUrl = buildDisplayUrls({ host: effectiveHost, port }).primary;
|
|
1758
|
+
const ready = await waitForHealth(`${internalBaseUrl}/health`);
|
|
1645
1759
|
if (!ready) {
|
|
1646
|
-
log('error', 'Server did not become ready in time', { url:
|
|
1760
|
+
log('error', 'Server did not become ready in time', { url: internalBaseUrl });
|
|
1647
1761
|
if (spinner) {
|
|
1648
1762
|
spinner.stop('✗ timeout');
|
|
1649
1763
|
cli.blank();
|
|
@@ -1656,15 +1770,15 @@ async function main(argv) {
|
|
|
1656
1770
|
}
|
|
1657
1771
|
|
|
1658
1772
|
if (opts.foreground) {
|
|
1659
|
-
log('info', `Devchain is running at ${
|
|
1660
|
-
log('info', `API docs: ${
|
|
1661
|
-
console.log(`\nDevchain is running at ${
|
|
1662
|
-
console.log(`API docs: ${
|
|
1773
|
+
log('info', `Devchain is running at ${displayUrl}`);
|
|
1774
|
+
log('info', `API docs: ${displayUrl}/api/docs`);
|
|
1775
|
+
console.log(`\nDevchain is running at ${displayUrl}`);
|
|
1776
|
+
console.log(`API docs: ${displayUrl}/api/docs`);
|
|
1663
1777
|
console.log('Press Ctrl+C to stop.\n');
|
|
1664
1778
|
} else {
|
|
1665
1779
|
cli.blank();
|
|
1666
|
-
cli.success(`Server ready at ${
|
|
1667
|
-
cli.info(`API docs: ${
|
|
1780
|
+
cli.success(`Server ready at ${displayUrl}`);
|
|
1781
|
+
cli.info(`API docs: ${displayUrl}/api/docs`);
|
|
1668
1782
|
}
|
|
1669
1783
|
|
|
1670
1784
|
// Ensure provider rows exist (idempotent) before opening UI
|
|
@@ -1673,7 +1787,7 @@ async function main(argv) {
|
|
|
1673
1787
|
&& opts.__providersDetected
|
|
1674
1788
|
&& opts.__providersDetected.size > 0
|
|
1675
1789
|
) {
|
|
1676
|
-
await ensureProvidersInDb(
|
|
1790
|
+
await ensureProvidersInDb(internalBaseUrl, opts.__providersDetected, log);
|
|
1677
1791
|
}
|
|
1678
1792
|
|
|
1679
1793
|
// Determine startup path for MCP validation and URL
|
|
@@ -1683,19 +1797,19 @@ async function main(argv) {
|
|
|
1683
1797
|
|
|
1684
1798
|
// Validate MCP for all providers (with project context)
|
|
1685
1799
|
if (!worktreeRuntimeMode) {
|
|
1686
|
-
await validateMcpForProviders(
|
|
1800
|
+
await validateMcpForProviders(internalBaseUrl, cli, opts, log, startupPath);
|
|
1687
1801
|
}
|
|
1688
1802
|
|
|
1689
1803
|
// Note: Claude bypass prompt already handled before server start (in parent process for detach mode)
|
|
1690
1804
|
|
|
1691
1805
|
// Determine URL to open based on project path
|
|
1692
|
-
let urlToOpen =
|
|
1806
|
+
let urlToOpen = displayUrl;
|
|
1693
1807
|
try {
|
|
1694
|
-
const byPathUrl = `${
|
|
1808
|
+
const byPathUrl = `${internalBaseUrl}/api/projects/by-path?path=${encodeURIComponent(startupPath)}`;
|
|
1695
1809
|
const resByPath = await fetchWithTimeout(byPathUrl, {}, 2500);
|
|
1696
1810
|
if (resByPath.ok) {
|
|
1697
1811
|
const project = await resByPath.json();
|
|
1698
|
-
urlToOpen = `${
|
|
1812
|
+
urlToOpen = `${displayUrl}/projects?projectId=${encodeURIComponent(project.id)}`;
|
|
1699
1813
|
if (opts.foreground) {
|
|
1700
1814
|
log('info', 'Resolved startup path to existing project', { startupPath, projectId: project.id });
|
|
1701
1815
|
} else if (opts.open) {
|
|
@@ -1703,7 +1817,7 @@ async function main(argv) {
|
|
|
1703
1817
|
}
|
|
1704
1818
|
} else {
|
|
1705
1819
|
// 404 or invalid — fall back to newProjectPath to prefill dialog
|
|
1706
|
-
urlToOpen = `${
|
|
1820
|
+
urlToOpen = `${displayUrl}/projects?newProjectPath=${encodeURIComponent(startupPath)}`;
|
|
1707
1821
|
if (opts.foreground) {
|
|
1708
1822
|
log('info', 'No project at startup path; prefill create dialog', { startupPath });
|
|
1709
1823
|
} else if (opts.open) {
|
|
@@ -1712,7 +1826,7 @@ async function main(argv) {
|
|
|
1712
1826
|
}
|
|
1713
1827
|
} catch (e) {
|
|
1714
1828
|
// Network/timeouts: still prefer prefilled create dialog
|
|
1715
|
-
urlToOpen = `${
|
|
1829
|
+
urlToOpen = `${displayUrl}/projects?newProjectPath=${encodeURIComponent(startupPath)}`;
|
|
1716
1830
|
if (opts.foreground) {
|
|
1717
1831
|
log('warn', 'Failed to resolve startup path; opening create dialog', {
|
|
1718
1832
|
error: e instanceof Error ? e.message : String(e),
|
|
@@ -1736,7 +1850,7 @@ async function main(argv) {
|
|
|
1736
1850
|
|
|
1737
1851
|
if (!worktreeRuntimeMode) {
|
|
1738
1852
|
// Write PID file for stop command
|
|
1739
|
-
writePidFile(port);
|
|
1853
|
+
writePidFile(port, effectiveHost);
|
|
1740
1854
|
|
|
1741
1855
|
// Clean up PID file on exit (main.ts handles SIGINT/SIGTERM and graceful shutdown)
|
|
1742
1856
|
process.on('exit', () => {
|
|
@@ -1820,7 +1934,7 @@ async function main(argv) {
|
|
|
1820
1934
|
console.log('No running Devchain instance found.');
|
|
1821
1935
|
process.exit(1);
|
|
1822
1936
|
} else {
|
|
1823
|
-
const { pid, port } = pidData;
|
|
1937
|
+
const { pid, port, host } = pidData;
|
|
1824
1938
|
|
|
1825
1939
|
if (!isProcessRunning(pid)) {
|
|
1826
1940
|
console.log(`Devchain process (PID ${pid}) is not running. Cleaning up stale PID file.`);
|
|
@@ -1877,6 +1991,13 @@ module.exports = {
|
|
|
1877
1991
|
main,
|
|
1878
1992
|
normalizeCliArgv,
|
|
1879
1993
|
__test__: {
|
|
1994
|
+
normalizeHost,
|
|
1995
|
+
isWildcardHost,
|
|
1996
|
+
isNonLoopbackHost,
|
|
1997
|
+
connectableHost,
|
|
1998
|
+
formatHostForUrl,
|
|
1999
|
+
buildInternalBaseUrl,
|
|
2000
|
+
buildDisplayUrls,
|
|
1880
2001
|
ensureDockerAvailable,
|
|
1881
2002
|
isDockerAvailable,
|
|
1882
2003
|
deriveRepoRootFromGit,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
ALTER TABLE `sessions` ADD `provider_session_id` text;--> statement-breakpoint
|
|
2
|
+
ALTER TABLE `sessions` ADD `provider_name_at_launch` text;--> statement-breakpoint
|
|
3
|
+
ALTER TABLE `sessions` ADD `size_bytes` integer;--> statement-breakpoint
|
|
4
|
+
CREATE INDEX `idx_sessions_agent_history` ON `sessions` (`agent_id`,`status`,`last_activity_at`);--> statement-breakpoint
|
|
5
|
+
UPDATE `sessions` SET `provider_session_id` = `claude_session_id` WHERE `claude_session_id` IS NOT NULL;--> statement-breakpoint
|
|
6
|
+
UPDATE `sessions` SET `provider_name_at_launch` = (
|
|
7
|
+
SELECT LOWER(p.name)
|
|
8
|
+
FROM agents a
|
|
9
|
+
JOIN profile_provider_configs ppc ON a.provider_config_id = ppc.id
|
|
10
|
+
JOIN providers p ON ppc.provider_id = p.id
|
|
11
|
+
WHERE a.id = sessions.agent_id
|
|
12
|
+
) WHERE `agent_id` IS NOT NULL;
|