kandev 0.17.0 → 0.40.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/README.md CHANGED
@@ -4,18 +4,36 @@ Manage tasks. Orchestrate agents. Review changes. Ship value.
4
4
 
5
5
  ## Quick Start
6
6
 
7
+ ### Homebrew
8
+
9
+ ```bash
10
+ brew install kdlbs/kandev/kandev
11
+ kandev
12
+ ```
13
+
14
+ ### NPX (requires npm 7+)
15
+
7
16
  ```bash
8
- npx kandev
17
+ npx kandev@latest
9
18
  ```
10
19
 
11
- Downloads the latest release, starts the backend + web app, and opens your browser. Data (worktrees, SQLite DB) is stored in `~/.kandev` by default.
20
+ Either install path resolves a platform-matched runtime (Go backend, agentctl, Next.js standalone web), launches the backend + web, and opens your browser. Data (worktrees, SQLite DB) is stored in `~/.kandev` by default.
12
21
 
13
22
  ## Version and Updates
14
23
 
15
- - `npx kandev` (run mode) uses the latest GitHub release bundle by default.
16
- - At startup, the launcher prints the resolved release tag (for example: `[kandev] release: v0.2.3 (github latest)`).
17
- - Use `--version <tag>` to pin a specific runtime bundle release.
18
- - CLI package updates are checked from npm and shown as a prompt. They are not forced.
24
+ The package manager owns the runtime version. `kandev@X.Y.Z` ships with the matching runtime.
25
+
26
+ - **Update via Homebrew**: `brew upgrade kandev`
27
+ - **Update via npm/npx**: `npx kandev@latest` or `npm install -g kandev@latest`
28
+ - **Print CLI version**: `kandev --version`
29
+
30
+ ### Advanced: pin a specific runtime tag
31
+
32
+ `--runtime-version <tag>` downloads a specific GitHub release runtime instead of using the installed one. For debugging compatibility issues only:
33
+
34
+ ```bash
35
+ kandev --runtime-version v0.16.0
36
+ ```
19
37
 
20
38
  ## What You Get
21
39
 
package/dist/args.js CHANGED
@@ -20,20 +20,24 @@ function parseArgs(argv) {
20
20
  showHelp = true;
21
21
  continue;
22
22
  }
23
+ if (arg === "--version" || arg === "-V") {
24
+ opts.showVersion = true;
25
+ continue;
26
+ }
23
27
  if (arg === "dev" || arg === "run" || arg === "start") {
24
28
  opts.command = arg;
25
29
  continue;
26
30
  }
27
- if (arg === "--version") {
28
- opts.version = takeValue(argv, i, "--version");
31
+ if (arg === "--runtime-version") {
32
+ opts.runtimeVersion = takeValue(argv, i, "--runtime-version");
29
33
  i += 1;
30
34
  continue;
31
35
  }
32
- if (arg.startsWith("--version=")) {
33
- const value = arg.slice("--version=".length);
36
+ if (arg.startsWith("--runtime-version=")) {
37
+ const value = arg.slice("--runtime-version=".length);
34
38
  if (value.length === 0)
35
- throw new ParseError("--version requires a value");
36
- opts.version = value;
39
+ throw new ParseError("--runtime-version requires a value");
40
+ opts.runtimeVersion = value;
37
41
  continue;
38
42
  }
39
43
  if (arg === "--dev") {
package/dist/cli.js CHANGED
@@ -11,15 +11,14 @@ const dev_1 = require("./dev");
11
11
  const run_1 = require("./run");
12
12
  const start_1 = require("./start");
13
13
  const ports_1 = require("./ports");
14
- const update_1 = require("./update");
15
14
  function printHelp() {
16
15
  console.log(`kandev launcher
17
16
 
18
17
  Usage:
19
- kandev run [--version <tag>] [--port <port>] [--verbose] [--debug]
18
+ kandev run [--port <port>] [--verbose] [--debug]
20
19
  kandev dev [--port <port>]
21
20
  kandev start [--port <port>] [--verbose] [--debug]
22
- kandev [--version <tag>] [--port <port>] [--verbose] [--debug]
21
+ kandev [--port <port>] [--verbose] [--debug]
23
22
  kandev --dev [--port <port>]
24
23
 
25
24
  Examples:
@@ -28,16 +27,16 @@ Examples:
28
27
  kandev --dev
29
28
  kandev dev
30
29
  kandev start
31
- kandev --version v0.1.0
30
+ kandev --version
32
31
  kandev --port 3000
33
32
  kandev --debug
34
33
 
35
34
  Options:
36
35
  dev Use local repo for dev (make dev + next dev) if available.
37
36
  start Use local production build (make build + next start).
38
- run Use release bundles (default).
37
+ run Use installed runtime bundle (default).
39
38
  --dev Alias for "dev".
40
- --version Release tag to install (default: latest).
39
+ --version, -V Print CLI version and exit.
41
40
  --port Port for the Go backend (the URL kandev opens on in
42
41
  start/run). Alias for --backend-port. Also reads
43
42
  KANDEV_PORT or KANDEV_BACKEND_PORT.
@@ -46,11 +45,14 @@ Options:
46
45
  --help, -h Show help.
47
46
 
48
47
  Advanced:
49
- --backend-port Same as --port.
50
- --web-internal-port Override the internal Next.js port. The Go backend
51
- reverse-proxies to it; users hit the backend port.
52
- Also reads KANDEV_WEB_PORT.
53
- --web-port Deprecated alias for --web-internal-port.
48
+ --backend-port Same as --port.
49
+ --web-internal-port Override the internal Next.js port. The Go backend
50
+ reverse-proxies to it; users hit the backend port.
51
+ Also reads KANDEV_WEB_PORT.
52
+ --web-port Deprecated alias for --web-internal-port.
53
+ --runtime-version <tag> Download and use a specific release tag instead of
54
+ the installed runtime. For debugging only.
55
+ Example: kandev --runtime-version v0.16.0
54
56
  `);
55
57
  }
56
58
  function findRepoRoot(startDir) {
@@ -77,6 +79,10 @@ function findRepoRoot(startDir) {
77
79
  }
78
80
  async function main() {
79
81
  const { options, showHelp, deprecatedFlags } = (0, args_1.parseArgs)(process.argv.slice(2));
82
+ if (options.showVersion) {
83
+ console.log(package_json_1.default.version);
84
+ return;
85
+ }
80
86
  if (showHelp) {
81
87
  printHelp();
82
88
  return;
@@ -109,9 +115,8 @@ async function main() {
109
115
  });
110
116
  return;
111
117
  }
112
- await (0, update_1.maybePromptForUpdate)(package_json_1.default.version, process.argv.slice(2));
113
118
  await (0, run_1.runRelease)({
114
- version: options.version,
119
+ runtimeVersion: options.runtimeVersion,
115
120
  backendPort,
116
121
  webPort,
117
122
  verbose: options.verbose,
package/dist/run.js CHANGED
@@ -18,6 +18,7 @@ const platform_1 = require("./platform");
18
18
  const version_1 = require("./version");
19
19
  const ports_1 = require("./ports");
20
20
  const process_1 = require("./process");
21
+ const runtime_1 = require("./runtime");
21
22
  const shared_1 = require("./shared");
22
23
  const web_1 = require("./web");
23
24
  /**
@@ -77,47 +78,62 @@ function cleanOldReleases(currentTag) {
77
78
  // Non-critical — don't fail the launch if cleanup errors.
78
79
  }
79
80
  }
80
- async function prepareReleaseBundle({ version, backendPort, webPort, verbose = false, debug = false, }) {
81
+ /**
82
+ * Download a specific release version into the local cache.
83
+ * Only used when --runtime-version is given explicitly.
84
+ */
85
+ async function downloadRuntimeVersion(runtimeVersion) {
81
86
  const platformDir = (0, platform_1.getPlatformDir)();
82
- let tag;
83
- let cacheDir;
84
- try {
85
- const release = await (0, github_1.getRelease)(version);
86
- tag = release.tag_name;
87
- const assetName = `kandev-${platformDir}.tar.gz`;
88
- cacheDir = node_path_1.default.join(constants_1.CACHE_DIR, tag, platformDir);
89
- const archivePath = await (0, github_1.ensureAsset)(tag, assetName, cacheDir, (downloaded, total) => {
90
- const percent = total ? Math.round((downloaded / total) * 100) : 0;
91
- const mb = (downloaded / (1024 * 1024)).toFixed(1);
92
- const totalMb = total ? (total / (1024 * 1024)).toFixed(1) : "?";
93
- process.stderr.write(`\r Downloading: ${mb}MB / ${totalMb}MB (${percent}%)`);
94
- });
95
- process.stderr.write("\n");
96
- (0, bundle_1.ensureExtracted)(archivePath, cacheDir);
97
- cleanOldReleases(tag);
98
- }
99
- catch (err) {
100
- // GitHub unreachable — try to launch from cache.
101
- const cached = findCachedRelease(platformDir, version);
102
- if (!cached) {
103
- const target = version ? `version ${version}` : "latest version";
104
- const reason = err instanceof Error ? err.message : String(err);
105
- throw new Error(`Failed to fetch ${target} and no cached release found.\n` +
106
- ` Reason: ${reason}\n` +
107
- ` Run kandev once while online to cache a release for offline use.`);
87
+ const release = await (0, github_1.getRelease)(runtimeVersion);
88
+ const tag = release.tag_name;
89
+ const assetName = `kandev-${platformDir}.tar.gz`;
90
+ const cacheDir = node_path_1.default.join(constants_1.CACHE_DIR, tag, platformDir);
91
+ const archivePath = await (0, github_1.ensureAsset)(tag, assetName, cacheDir, (downloaded, total) => {
92
+ const percent = total ? Math.round((downloaded / total) * 100) : 0;
93
+ const mb = (downloaded / (1024 * 1024)).toFixed(1);
94
+ const totalMb = total ? (total / (1024 * 1024)).toFixed(1) : "?";
95
+ process.stderr.write(`\r Downloading: ${mb}MB / ${totalMb}MB (${percent}%)`);
96
+ });
97
+ process.stderr.write("\n");
98
+ (0, bundle_1.ensureExtracted)(archivePath, cacheDir);
99
+ cleanOldReleases(tag);
100
+ return tag;
101
+ }
102
+ async function prepareBundleForLaunch({ runtimeVersion, backendPort, webPort, verbose = false, debug = false, }) {
103
+ let bundleDir;
104
+ let releaseTag;
105
+ if (runtimeVersion) {
106
+ // Explicit version: ensure it is in the cache (downloading if needed), then resolve.
107
+ const platformDir = (0, platform_1.getPlatformDir)();
108
+ const cached = findCachedRelease(platformDir, runtimeVersion);
109
+ let tag;
110
+ if (cached) {
111
+ tag = cached.tag;
112
+ bundleDir = (0, bundle_1.findBundleRoot)(cached.cacheDir);
108
113
  }
109
- tag = cached.tag;
110
- cacheDir = cached.cacheDir;
111
- process.stderr.write(`[kandev] GitHub unreachable — using cached release ${tag}\n`);
112
- }
113
- const bundleDir = (0, bundle_1.findBundleRoot)(cacheDir);
114
- const backendBin = node_path_1.default.join(bundleDir, "bin", (0, platform_1.getBinaryName)("kandev"));
115
- if (!node_fs_1.default.existsSync(backendBin)) {
116
- throw new Error(`Backend binary not found at ${backendBin}`);
114
+ else {
115
+ try {
116
+ tag = await downloadRuntimeVersion(runtimeVersion);
117
+ const cacheDir = node_path_1.default.join(constants_1.CACHE_DIR, tag, platformDir);
118
+ bundleDir = (0, bundle_1.findBundleRoot)(cacheDir);
119
+ }
120
+ catch (err) {
121
+ const reason = err instanceof Error ? err.message : String(err);
122
+ throw new Error(`Failed to fetch runtime version ${runtimeVersion}.\n` +
123
+ ` Reason: ${reason}\n` +
124
+ ` Run kandev once while online to cache a release for offline use.`);
125
+ }
126
+ }
127
+ // Validate the resolved bundle has all required binaries before launching.
128
+ (0, runtime_1.validateBundle)(bundleDir);
129
+ releaseTag = tag;
117
130
  }
118
- const agentctlBin = node_path_1.default.join(bundleDir, "bin", (0, platform_1.getBinaryName)("agentctl"));
119
- if (!node_fs_1.default.existsSync(agentctlBin)) {
120
- throw new Error(`agentctl binary not found at ${agentctlBin}`);
131
+ else {
132
+ // Default path: resolve from KANDEV_BUNDLE_DIR or installed npm runtime package.
133
+ const resolved = (0, runtime_1.resolveRuntime)();
134
+ bundleDir = resolved.bundleDir;
135
+ // Use KANDEV_VERSION if set (e.g. by Homebrew wrapper), otherwise show source.
136
+ releaseTag = process.env.KANDEV_VERSION ?? `(${resolved.source})`;
121
137
  }
122
138
  const actualBackendPort = backendPort ?? (await (0, ports_1.pickAvailablePort)(constants_1.DEFAULT_BACKEND_PORT));
123
139
  const actualWebPort = webPort ?? (await (0, ports_1.pickAvailablePort)(constants_1.DEFAULT_WEB_PORT));
@@ -127,10 +143,7 @@ async function prepareReleaseBundle({ version, backendPort, webPort, verbose = f
127
143
  const logLevel = process.env.KANDEV_LOG_LEVEL?.trim() || (debug ? "debug" : verbose ? "info" : "warn");
128
144
  node_fs_1.default.mkdirSync(constants_1.DATA_DIR, { recursive: true });
129
145
  const dbPath = node_path_1.default.join(constants_1.DATA_DIR, "kandev.db");
130
- // Note: Release mode doesn't configure MCP server ports as it uses
131
- // the bundled configuration. Only backend and agentctl ports are set.
132
- // Log level defaults to warn for clean output and can be overridden
133
- // via KANDEV_LOG_LEVEL or --verbose/--debug flags.
146
+ const backendBin = node_path_1.default.join(bundleDir, "bin", (0, platform_1.getBinaryName)("kandev"));
134
147
  const backendEnv = {
135
148
  ...process.env,
136
149
  KANDEV_SERVER_PORT: String(actualBackendPort),
@@ -144,8 +157,6 @@ async function prepareReleaseBundle({ version, backendPort, webPort, verbose = f
144
157
  ...process.env,
145
158
  KANDEV_API_BASE_URL: backendUrl,
146
159
  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
160
  HOSTNAME: "127.0.0.1",
150
161
  };
151
162
  webEnv.NODE_ENV = "production";
@@ -155,8 +166,7 @@ async function prepareReleaseBundle({ version, backendPort, webPort, verbose = f
155
166
  backendUrl,
156
167
  backendEnv,
157
168
  webEnv,
158
- releaseTag: tag,
159
- requestedVersion: version,
169
+ releaseTag,
160
170
  webPort: actualWebPort,
161
171
  agentctlPort,
162
172
  dbPath,
@@ -179,12 +189,9 @@ function attachRingBuffer(stream, maxChars = 64 * 1024) {
179
189
  });
180
190
  return () => buf;
181
191
  }
182
- function launchReleaseApps(prepared) {
183
- const releaseSource = prepared.requestedVersion
184
- ? `(requested: ${prepared.requestedVersion})`
185
- : "(github latest)";
192
+ function launchBundle(prepared) {
186
193
  (0, shared_1.logStartupInfo)({
187
- header: `release: ${prepared.releaseTag} ${releaseSource}`,
194
+ header: `release: ${prepared.releaseTag}`,
188
195
  ports: {
189
196
  backendPort: Number(prepared.backendEnv.KANDEV_SERVER_PORT),
190
197
  webPort: prepared.webPort,
@@ -196,10 +203,6 @@ function launchReleaseApps(prepared) {
196
203
  });
197
204
  const supervisor = (0, process_1.createProcessSupervisor)();
198
205
  supervisor.attachSignalHandlers();
199
- // Start backend: ignore stdin, always show stderr immediately.
200
- // In verbose/debug mode stream stdout live; otherwise capture it into a ring
201
- // buffer so we can dump the last few KB if the healthcheck fails (users were
202
- // previously seeing opaque "timed out" errors with no backend context).
203
206
  const backendProc = (0, node_child_process_1.spawn)(prepared.backendBin, [], {
204
207
  cwd: node_path_1.default.dirname(prepared.backendBin),
205
208
  env: prepared.backendEnv,
@@ -226,9 +229,15 @@ function launchReleaseApps(prepared) {
226
229
  }
227
230
  return { supervisor, backendProc, webServerPath, dumpBackendLogs };
228
231
  }
229
- async function runRelease({ version, backendPort, webPort, verbose = false, debug = false, }) {
230
- const prepared = await prepareReleaseBundle({ version, backendPort, webPort, verbose, debug });
231
- const { supervisor, backendProc, webServerPath, dumpBackendLogs } = launchReleaseApps(prepared);
232
+ async function runRelease({ runtimeVersion, backendPort, webPort, verbose = false, debug = false, }) {
233
+ const prepared = await prepareBundleForLaunch({
234
+ runtimeVersion,
235
+ backendPort,
236
+ webPort,
237
+ verbose,
238
+ debug,
239
+ });
240
+ const { supervisor, backendProc, webServerPath, dumpBackendLogs } = launchBundle(prepared);
232
241
  const healthTimeoutMs = (0, health_1.resolveHealthTimeoutMs)(constants_1.HEALTH_TIMEOUT_MS_RELEASE);
233
242
  console.log("[kandev] starting backend...");
234
243
  await (0, health_1.waitForHealth)(prepared.backendUrl, backendProc, healthTimeoutMs, dumpBackendLogs);
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveRuntime = resolveRuntime;
7
+ exports.validateBundle = validateBundle;
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ const bundle_1 = require("./bundle");
11
+ const platform_1 = require("./platform");
12
+ const PLATFORM_TO_NPM_PACKAGE = {
13
+ "linux-x64": "@kdlbs/runtime-linux-x64",
14
+ "linux-arm64": "@kdlbs/runtime-linux-arm64",
15
+ "macos-x64": "@kdlbs/runtime-darwin-x64",
16
+ "macos-arm64": "@kdlbs/runtime-darwin-arm64",
17
+ "windows-x64": "@kdlbs/runtime-win32-x64",
18
+ };
19
+ /**
20
+ * Resolve the runtime bundle directory using a two-step priority chain:
21
+ *
22
+ * 1. KANDEV_BUNDLE_DIR env var — set by the Homebrew formula wrapper and
23
+ * useful for local testing. Skips all other resolution.
24
+ * 2. Installed npm runtime package — looks for @kdlbs/runtime-{platform}
25
+ * in node_modules via Node module resolution. Works after
26
+ * `npx kandev@latest` or `npm install -g kandev` (requires npm 7+).
27
+ *
28
+ * The explicit `--runtime-version <tag>` download path is handled directly
29
+ * in run.ts (which manages the GitHub download + cache itself); it does
30
+ * not flow through this function.
31
+ *
32
+ * Throws with an actionable error message if no runtime is found.
33
+ */
34
+ function resolveRuntime() {
35
+ const envBundleDir = process.env.KANDEV_BUNDLE_DIR;
36
+ if (envBundleDir) {
37
+ validateBundle(envBundleDir);
38
+ return { bundleDir: envBundleDir, source: "env" };
39
+ }
40
+ const platformDir = (0, platform_1.getPlatformDir)();
41
+ const packageName = PLATFORM_TO_NPM_PACKAGE[platformDir];
42
+ let pkgJsonPath = null;
43
+ try {
44
+ pkgJsonPath = require.resolve(`${packageName}/package.json`);
45
+ }
46
+ catch {
47
+ // MODULE_NOT_FOUND — npm runtime package is not installed. Fall through
48
+ // to the actionable error below.
49
+ }
50
+ if (pkgJsonPath) {
51
+ // The package IS installed. If validateBundle throws here, the bundle is
52
+ // present but corrupt — surface the error rather than the generic
53
+ // "no runtime found" message below.
54
+ const packageRoot = node_path_1.default.dirname(pkgJsonPath);
55
+ validateBundle(packageRoot);
56
+ return { bundleDir: packageRoot, source: "npm" };
57
+ }
58
+ throw new Error(`No Kandev runtime found for ${platformDir}.\n` +
59
+ ` Install via npm (requires npm 7+): npx kandev@latest\n` +
60
+ ` Install via Homebrew: brew install kdlbs/kandev/kandev\n` +
61
+ ` Download a specific version (debug): kandev --runtime-version <tag>`);
62
+ }
63
+ function validateBundle(bundleDir) {
64
+ const backendBin = node_path_1.default.join(bundleDir, "bin", (0, platform_1.getBinaryName)("kandev"));
65
+ if (!node_fs_1.default.existsSync(backendBin)) {
66
+ throw new Error(`Backend binary not found in bundle at ${bundleDir}`);
67
+ }
68
+ const agentctlBin = node_path_1.default.join(bundleDir, "bin", (0, platform_1.getBinaryName)("agentctl"));
69
+ if (!node_fs_1.default.existsSync(agentctlBin)) {
70
+ throw new Error(`agentctl binary not found in bundle at ${bundleDir}`);
71
+ }
72
+ const webServerPath = (0, bundle_1.resolveWebServerPath)(bundleDir);
73
+ if (!webServerPath) {
74
+ throw new Error(`Web server (server.js) not found in bundle at ${bundleDir}`);
75
+ }
76
+ }
package/package.json CHANGED
@@ -1,9 +1,14 @@
1
1
  {
2
2
  "name": "kandev",
3
- "version": "0.17.0",
3
+ "version": "0.40.0",
4
4
  "private": false,
5
- "description": "NPX launcher for Kandev",
5
+ "description": "Launcher for Kandev — manage tasks, orchestrate agents, review changes, and ship value",
6
6
  "license": "AGPL-3.0-only",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/kdlbs/kandev.git"
10
+ },
11
+ "homepage": "https://github.com/kdlbs/kandev",
7
12
  "type": "commonjs",
8
13
  "bin": {
9
14
  "kandev": "bin/cli.js"
@@ -13,12 +18,23 @@
13
18
  "bin",
14
19
  "dist"
15
20
  ],
21
+ "engines": {
22
+ "npm": ">=7"
23
+ },
24
+ "optionalDependencies": {
25
+ "@kdlbs/runtime-linux-x64": "0.40.0",
26
+ "@kdlbs/runtime-linux-arm64": "0.40.0",
27
+ "@kdlbs/runtime-darwin-x64": "0.40.0",
28
+ "@kdlbs/runtime-darwin-arm64": "0.40.0",
29
+ "@kdlbs/runtime-win32-x64": "0.40.0"
30
+ },
16
31
  "dependencies": {
17
32
  "tar": "^7.5.11",
18
33
  "tree-kill": "^1.2.2"
19
34
  },
20
35
  "devDependencies": {
21
36
  "@types/node": "^20",
37
+ "esbuild": "^0.24.0",
22
38
  "tsx": "^4.15.7",
23
39
  "typescript": "^5",
24
40
  "vitest": "^1.6.0"
@@ -26,6 +42,7 @@
26
42
  "scripts": {
27
43
  "dev": "unset npm_config_prefix && tsx src/cli.ts",
28
44
  "build": "tsc -p tsconfig.json",
45
+ "bundle": "esbuild dist/cli.js --bundle --platform=node --format=cjs --outfile=dist/cli.bundle.js",
29
46
  "start": "node dist/cli.js",
30
47
  "test": "vitest run",
31
48
  "prepublishOnly": "pnpm build"
package/dist/update.js DELETED
@@ -1,87 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.maybePromptForUpdate = maybePromptForUpdate;
7
- const node_child_process_1 = require("node:child_process");
8
- const node_https_1 = __importDefault(require("node:https"));
9
- const node_readline_1 = __importDefault(require("node:readline"));
10
- const version_1 = require("./version");
11
- function requestJson(url) {
12
- return new Promise((resolve, reject) => {
13
- const req = node_https_1.default.get(url, { headers: { "User-Agent": "kandev-npx" } }, (res) => {
14
- if (res.statusCode !== 200) {
15
- return reject(new Error(`HTTP ${res.statusCode} fetching ${url}`));
16
- }
17
- let body = "";
18
- res.on("data", (chunk) => (body += chunk));
19
- res.on("end", () => {
20
- try {
21
- resolve(JSON.parse(body));
22
- }
23
- catch {
24
- reject(new Error(`Failed to parse JSON from ${url}`));
25
- }
26
- });
27
- });
28
- req.setTimeout(5000, () => {
29
- req.destroy(new Error(`Request timed out fetching ${url}`));
30
- });
31
- req.on("error", reject);
32
- });
33
- }
34
- async function getLatestNpmVersion() {
35
- const data = await requestJson("https://registry.npmjs.org/kandev");
36
- return data?.["dist-tags"]?.latest;
37
- }
38
- function promptYesNo(question, defaultYes = false) {
39
- return new Promise((resolve) => {
40
- if (!process.stdin.isTTY) {
41
- resolve(false);
42
- return;
43
- }
44
- const rl = node_readline_1.default.createInterface({
45
- input: process.stdin,
46
- output: process.stdout,
47
- });
48
- const suffix = defaultYes ? "[Y/n]" : "[y/N]";
49
- rl.question(`${question} ${suffix} `, (answer) => {
50
- rl.close();
51
- const normalized = String(answer || "")
52
- .trim()
53
- .toLowerCase();
54
- if (!normalized) {
55
- resolve(Boolean(defaultYes));
56
- return;
57
- }
58
- resolve(normalized === "y" || normalized === "yes");
59
- });
60
- });
61
- }
62
- async function maybePromptForUpdate(currentVersion, args) {
63
- // Allow disabling update checks for CI or automation.
64
- if (process.env.KANDEV_SKIP_UPDATE === "1" || process.env.KANDEV_NO_UPDATE_PROMPT === "1") {
65
- return;
66
- }
67
- try {
68
- const latest = await getLatestNpmVersion();
69
- if (!latest)
70
- return;
71
- if ((0, version_1.compareVersions)(latest, currentVersion) <= 0)
72
- return;
73
- const wantsUpdate = await promptYesNo(`Update available: ${currentVersion} -> ${latest}. Update now?`, false);
74
- if (!wantsUpdate)
75
- return;
76
- const env = { ...process.env, KANDEV_SKIP_UPDATE: "1" };
77
- const child = (0, node_child_process_1.spawn)("npx", ["kandev@latest", ...args], {
78
- stdio: "inherit",
79
- env,
80
- });
81
- child.on("exit", (code) => process.exit(code || 0));
82
- await new Promise(() => { });
83
- }
84
- catch {
85
- // ignore update errors
86
- }
87
- }