kandev 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -15,10 +15,10 @@ function printHelp() {
15
15
  console.log(`kandev launcher
16
16
 
17
17
  Usage:
18
- kandev run [--version <tag>] [--backend-port <port>] [--web-port <port>]
18
+ kandev run [--version <tag>] [--backend-port <port>] [--web-port <port>] [--verbose] [--debug]
19
19
  kandev dev [--backend-port <port>] [--web-port <port>]
20
20
  kandev start [--backend-port <port>] [--web-port <port>] [--verbose] [--debug]
21
- kandev [--version <tag>] [--backend-port <port>] [--web-port <port>]
21
+ kandev [--version <tag>] [--backend-port <port>] [--web-port <port>] [--verbose] [--debug]
22
22
  kandev --dev [--backend-port <port>] [--web-port <port>]
23
23
 
24
24
  Examples:
@@ -29,6 +29,7 @@ Examples:
29
29
  kandev start
30
30
  kandev --version v0.1.0
31
31
  kandev --backend-port 18080 --web-port 13000
32
+ kandev --debug
32
33
 
33
34
  Options:
34
35
  dev Use local repo for dev (make dev + next dev) if available.
@@ -38,8 +39,8 @@ Options:
38
39
  --version Release tag to install (default: latest).
39
40
  --backend-port Override backend port (or KANDEV_BACKEND_PORT env var).
40
41
  --web-port Override web port (or KANDEV_WEB_PORT env var).
41
- --verbose, -v Show info logs from backend + web (start mode only).
42
- --debug Show debug logs + agent message dumps (start mode only).
42
+ --verbose, -v Show info logs from backend + web.
43
+ --debug Show debug logs + agent message dumps.
43
44
  --help, -h Show help.
44
45
  `);
45
46
  }
@@ -144,7 +145,13 @@ async function main() {
144
145
  return;
145
146
  }
146
147
  await (0, update_1.maybePromptForUpdate)(package_json_1.default.version, process.argv.slice(2));
147
- await (0, run_1.runRelease)({ version: raw.version, backendPort, webPort });
148
+ await (0, run_1.runRelease)({
149
+ version: raw.version,
150
+ backendPort,
151
+ webPort,
152
+ verbose: raw.verbose,
153
+ debug: raw.debug,
154
+ });
148
155
  }
149
156
  main().catch((err) => {
150
157
  console.error(`[kandev] ${err instanceof Error ? err.message : String(err)}`);
package/dist/constants.js CHANGED
@@ -8,7 +8,7 @@ const node_os_1 = __importDefault(require("node:os"));
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
9
  // Default service ports (will auto-fallback if busy).
10
10
  exports.DEFAULT_BACKEND_PORT = 8080;
11
- exports.DEFAULT_WEB_PORT = 3000;
11
+ exports.DEFAULT_WEB_PORT = 37429;
12
12
  exports.DEFAULT_AGENTCTL_PORT = 9999;
13
13
  exports.DEFAULT_MCP_PORT = 9090;
14
14
  // Random fallback range for port selection.
@@ -16,7 +16,7 @@ exports.RANDOM_PORT_MIN = 10000;
16
16
  exports.RANDOM_PORT_MAX = 60000;
17
17
  exports.RANDOM_PORT_RETRIES = 10;
18
18
  // Backend healthcheck timeout during startup.
19
- exports.HEALTH_TIMEOUT_MS_RELEASE = 15000;
19
+ exports.HEALTH_TIMEOUT_MS_RELEASE = 45000;
20
20
  exports.HEALTH_TIMEOUT_MS_DEV = 600000;
21
21
  // Local user cache/data directories for release bundles and DB.
22
22
  exports.CACHE_DIR = node_path_1.default.join(node_os_1.default.homedir(), ".kandev", "bin");
package/dist/dev.js CHANGED
@@ -13,10 +13,11 @@ const shared_1 = require("./shared");
13
13
  const web_1 = require("./web");
14
14
  async function runDev({ repoRoot, backendPort, webPort }) {
15
15
  const ports = await (0, shared_1.pickPorts)(backendPort, webPort);
16
- const dbPath = node_path_1.default.join(repoRoot, "apps", "backend", "kandev.db");
16
+ const dbPath = process.env.KANDEV_DATABASE_PATH || node_path_1.default.join(repoRoot, "apps", "backend", "kandev.db");
17
17
  const extra = {
18
18
  KANDEV_DATABASE_PATH: dbPath,
19
19
  KANDEV_MOCK_AGENT: process.env.KANDEV_MOCK_AGENT || "true",
20
+ KANDEV_DEBUG_PPROF_ENABLED: "true",
20
21
  };
21
22
  const backendEnv = (0, shared_1.buildBackendEnv)({ ports, extra });
22
23
  const webEnv = (0, shared_1.buildWebEnv)({ ports, debug: true });
package/dist/health.js CHANGED
@@ -28,12 +28,13 @@ function resolveHealthTimeoutMs(defaultMs) {
28
28
  }
29
29
  return Math.floor(parsed);
30
30
  }
31
- async function waitForHealth(baseUrl, proc, timeoutMs) {
31
+ async function waitForHealth(baseUrl, proc, timeoutMs, onFailure) {
32
32
  const deadline = Date.now() + timeoutMs;
33
33
  const healthUrl = `${baseUrl}/health`;
34
34
  while (Date.now() < deadline) {
35
35
  if (proc.exitCode !== null) {
36
- throw new Error("Backend exited before healthcheck passed");
36
+ onFailure?.();
37
+ throw new Error(`Backend exited (code ${proc.exitCode}) before healthcheck passed`);
37
38
  }
38
39
  try {
39
40
  const res = await fetch(healthUrl);
@@ -46,7 +47,10 @@ async function waitForHealth(baseUrl, proc, timeoutMs) {
46
47
  }
47
48
  await delay(300);
48
49
  }
49
- throw new Error(`Backend healthcheck timed out after ${timeoutMs}ms`);
50
+ onFailure?.();
51
+ throw new Error(`Backend healthcheck timed out after ${timeoutMs}ms at ${healthUrl}. ` +
52
+ `If your machine is slow on first run (antivirus scan, cold disk), ` +
53
+ `set KANDEV_HEALTH_TIMEOUT_MS=120000 and retry.`);
50
54
  }
51
55
  async function waitForUrlReady(url, proc, timeoutMs) {
52
56
  const deadline = Date.now() + timeoutMs;
package/dist/run.js CHANGED
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.findCachedRelease = findCachedRelease;
7
7
  exports.cleanOldReleases = cleanOldReleases;
8
+ exports.attachRingBuffer = attachRingBuffer;
8
9
  exports.runRelease = runRelease;
9
10
  const node_child_process_1 = require("node:child_process");
10
11
  const node_fs_1 = __importDefault(require("node:fs"));
@@ -76,7 +77,7 @@ function cleanOldReleases(currentTag) {
76
77
  // Non-critical — don't fail the launch if cleanup errors.
77
78
  }
78
79
  }
79
- async function prepareReleaseBundle({ version, backendPort, webPort, }) {
80
+ async function prepareReleaseBundle({ version, backendPort, webPort, verbose = false, debug = false, }) {
80
81
  const platformDir = (0, platform_1.getPlatformDir)();
81
82
  let tag;
82
83
  let cacheDir;
@@ -122,13 +123,14 @@ async function prepareReleaseBundle({ version, backendPort, webPort, }) {
122
123
  const actualWebPort = webPort ?? (await (0, ports_1.pickAvailablePort)(constants_1.DEFAULT_WEB_PORT));
123
124
  const agentctlPort = await (0, ports_1.pickAvailablePort)(constants_1.DEFAULT_AGENTCTL_PORT);
124
125
  const backendUrl = `http://localhost:${actualBackendPort}`;
125
- const logLevel = process.env.KANDEV_LOG_LEVEL?.trim() || "warn";
126
+ const showOutput = verbose || debug;
127
+ const logLevel = process.env.KANDEV_LOG_LEVEL?.trim() || (debug ? "debug" : verbose ? "info" : "warn");
126
128
  node_fs_1.default.mkdirSync(constants_1.DATA_DIR, { recursive: true });
127
129
  const dbPath = node_path_1.default.join(constants_1.DATA_DIR, "kandev.db");
128
130
  // Note: Release mode doesn't configure MCP server ports as it uses
129
131
  // the bundled configuration. Only backend and agentctl ports are set.
130
132
  // Log level defaults to warn for clean output and can be overridden
131
- // via KANDEV_LOG_LEVEL.
133
+ // via KANDEV_LOG_LEVEL or --verbose/--debug flags.
132
134
  const backendEnv = {
133
135
  ...process.env,
134
136
  KANDEV_SERVER_PORT: String(actualBackendPort),
@@ -136,11 +138,15 @@ async function prepareReleaseBundle({ version, backendPort, webPort, }) {
136
138
  KANDEV_AGENT_STANDALONE_PORT: String(agentctlPort),
137
139
  KANDEV_DATABASE_PATH: dbPath,
138
140
  KANDEV_LOG_LEVEL: logLevel,
141
+ ...(debug ? { KANDEV_DEBUG_AGENT_MESSAGES: "true", KANDEV_DEBUG_PPROF_ENABLED: "true" } : {}),
139
142
  };
140
143
  const webEnv = {
141
144
  ...process.env,
142
145
  KANDEV_API_BASE_URL: backendUrl,
143
146
  PORT: String(actualWebPort),
147
+ // Ensure Next.js standalone server binds to 127.0.0.1 so localhost health checks work.
148
+ // Without this, HOSTNAME from the host environment can cause binding issues.
149
+ HOSTNAME: "127.0.0.1",
144
150
  };
145
151
  webEnv.NODE_ENV = "production";
146
152
  return {
@@ -155,8 +161,24 @@ async function prepareReleaseBundle({ version, backendPort, webPort, }) {
155
161
  agentctlPort,
156
162
  dbPath,
157
163
  logLevel,
164
+ showOutput,
158
165
  };
159
166
  }
167
+ /**
168
+ * Attach a ring buffer to a readable stream, keeping roughly the last `maxChars`
169
+ * characters. Note: the limit is measured in JS string length (UTF-16 code units),
170
+ * not bytes — fine for log output which is overwhelmingly ASCII.
171
+ */
172
+ function attachRingBuffer(stream, maxChars = 64 * 1024) {
173
+ let buf = "";
174
+ stream?.on("data", (chunk) => {
175
+ buf += typeof chunk === "string" ? chunk : chunk.toString("utf8");
176
+ if (buf.length > maxChars) {
177
+ buf = buf.slice(buf.length - maxChars);
178
+ }
179
+ });
180
+ return () => buf;
181
+ }
160
182
  function launchReleaseApps(prepared) {
161
183
  const releaseSource = prepared.requestedVersion
162
184
  ? `(requested: ${prepared.requestedVersion})`
@@ -176,25 +198,42 @@ function launchReleaseApps(prepared) {
176
198
  });
177
199
  const supervisor = (0, process_1.createProcessSupervisor)();
178
200
  supervisor.attachSignalHandlers();
201
+ // Start backend: ignore stdin, always show stderr immediately.
202
+ // In verbose/debug mode stream stdout live; otherwise capture it into a ring
203
+ // buffer so we can dump the last few KB if the healthcheck fails (users were
204
+ // previously seeing opaque "timed out" errors with no backend context).
179
205
  const backendProc = (0, node_child_process_1.spawn)(prepared.backendBin, [], {
180
206
  cwd: node_path_1.default.dirname(prepared.backendBin),
181
207
  env: prepared.backendEnv,
182
- stdio: "inherit",
208
+ stdio: prepared.showOutput ? ["ignore", "inherit", "inherit"] : ["ignore", "pipe", "inherit"],
183
209
  });
184
210
  supervisor.children.push(backendProc);
211
+ const readBuffered = prepared.showOutput ? () => "" : attachRingBuffer(backendProc.stdout);
212
+ let dumped = false;
213
+ const dumpBackendLogs = () => {
214
+ if (dumped)
215
+ return;
216
+ dumped = true;
217
+ const buffered = readBuffered();
218
+ if (buffered.trim().length === 0)
219
+ return;
220
+ console.error("[kandev] --- backend stdout (last captured output) ---");
221
+ console.error(buffered.trimEnd());
222
+ console.error("[kandev] --- end backend stdout ---");
223
+ };
185
224
  (0, shared_1.attachBackendExitHandler)(backendProc, supervisor);
186
225
  const webServerPath = (0, bundle_1.resolveWebServerPath)(prepared.bundleDir);
187
226
  if (!webServerPath) {
188
227
  throw new Error("Web server entry (server.js) not found in bundle");
189
228
  }
190
- return { supervisor, backendProc, webServerPath };
229
+ return { supervisor, backendProc, webServerPath, dumpBackendLogs };
191
230
  }
192
- async function runRelease({ version, backendPort, webPort }) {
193
- const prepared = await prepareReleaseBundle({ version, backendPort, webPort });
194
- const { supervisor, backendProc, webServerPath } = launchReleaseApps(prepared);
231
+ async function runRelease({ version, backendPort, webPort, verbose = false, debug = false, }) {
232
+ const prepared = await prepareReleaseBundle({ version, backendPort, webPort, verbose, debug });
233
+ const { supervisor, backendProc, webServerPath, dumpBackendLogs } = launchReleaseApps(prepared);
195
234
  const healthTimeoutMs = (0, health_1.resolveHealthTimeoutMs)(constants_1.HEALTH_TIMEOUT_MS_RELEASE);
196
235
  console.log("[kandev] starting backend...");
197
- await (0, health_1.waitForHealth)(prepared.backendUrl, backendProc, healthTimeoutMs);
236
+ await (0, health_1.waitForHealth)(prepared.backendUrl, backendProc, healthTimeoutMs, dumpBackendLogs);
198
237
  console.log(`[kandev] backend ready at ${prepared.backendUrl}`);
199
238
  const webUrl = `http://localhost:${prepared.webPort}`;
200
239
  console.log("[kandev] starting web...");
@@ -205,7 +244,7 @@ async function runRelease({ version, backendPort, webPort }) {
205
244
  env: prepared.webEnv,
206
245
  supervisor,
207
246
  label: "web",
208
- quiet: true,
247
+ quiet: !prepared.showOutput,
209
248
  });
210
249
  await (0, health_1.waitForUrlReady)(webUrl, webProc, healthTimeoutMs);
211
250
  console.log("[kandev] ready at " + prepared.backendUrl);
package/dist/shared.js CHANGED
@@ -64,10 +64,13 @@ function buildWebEnv(options) {
64
64
  ...process.env,
65
65
  // Server-side: full localhost URL for SSR fetches (Next.js SSR → Go backend)
66
66
  KANDEV_API_BASE_URL: ports.backendUrl,
67
- // Client-side: port injection for dev mode (browser on :3000, API on :8080).
67
+ // Client-side: port injection for dev mode (browser on web port, API on backend port).
68
68
  // In production, the backend reverse-proxies Next.js so the client uses same-origin.
69
69
  NEXT_PUBLIC_KANDEV_API_PORT: String(ports.backendPort),
70
70
  PORT: String(ports.webPort),
71
+ // Ensure Next.js standalone server binds to 127.0.0.1 so localhost health checks work.
72
+ // Without this, HOSTNAME from the host environment can cause binding issues.
73
+ HOSTNAME: "127.0.0.1",
71
74
  };
72
75
  if (production) {
73
76
  env.NODE_ENV = "production";
package/dist/start.js CHANGED
@@ -81,7 +81,7 @@ async function runStart({ repoRoot, backendPort, webPort, verbose = false, debug
81
81
  logLevel,
82
82
  extra: {
83
83
  KANDEV_DATABASE_PATH: dbPath,
84
- ...(debug ? { KANDEV_DEBUG_AGENT_MESSAGES: "true" } : {}),
84
+ ...(debug ? { KANDEV_DEBUG_AGENT_MESSAGES: "true", KANDEV_DEBUG_PPROF_ENABLED: "true" } : {}),
85
85
  },
86
86
  });
87
87
  const webEnv = (0, shared_1.buildWebEnv)({ ports, production: true, debug });
@@ -93,17 +93,14 @@ async function runStart({ repoRoot, backendPort, webPort, verbose = false, debug
93
93
  });
94
94
  const supervisor = (0, process_1.createProcessSupervisor)();
95
95
  supervisor.attachSignalHandlers();
96
- // Start backend with piped stdio (quiet mode unless verbose/debug)
96
+ // Start backend: ignore stdin, show stdout only in verbose/debug mode, always show stderr
97
+ // Stderr is always inherited to ensure error messages are visible immediately (no pipe buffering)
97
98
  const backendProc = (0, node_child_process_1.spawn)(backendBin, [], {
98
99
  cwd: node_path_1.default.dirname(backendBin),
99
100
  env: backendEnv,
100
- stdio: showOutput ? ["ignore", "inherit", "inherit"] : ["ignore", "pipe", "pipe"],
101
+ stdio: showOutput ? ["ignore", "inherit", "inherit"] : ["ignore", "ignore", "inherit"],
101
102
  });
102
103
  supervisor.children.push(backendProc);
103
- // Forward stderr only (warnings/errors) when quiet
104
- if (!showOutput) {
105
- backendProc.stderr?.pipe(process.stderr);
106
- }
107
104
  (0, shared_1.attachBackendExitHandler)(backendProc, supervisor);
108
105
  const healthTimeoutMs = (0, health_1.resolveHealthTimeoutMs)(constants_1.HEALTH_TIMEOUT_MS_RELEASE);
109
106
  console.log("[kandev] starting backend...");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kandev",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "private": false,
5
5
  "description": "NPX launcher for Kandev",
6
6
  "license": "AGPL-3.0-only",
@@ -14,7 +14,7 @@
14
14
  "dist"
15
15
  ],
16
16
  "dependencies": {
17
- "tar": "^7.5.10",
17
+ "tar": "^7.5.11",
18
18
  "tree-kill": "^1.2.2"
19
19
  },
20
20
  "devDependencies": {