kandev 0.16.0 → 0.39.2
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 +24 -6
- package/dist/args.js +119 -0
- package/dist/cli.js +57 -82
- package/dist/constants.js +1 -2
- package/dist/dev.js +2 -2
- package/dist/run.js +68 -61
- package/dist/runtime.js +76 -0
- package/dist/shared.js +5 -7
- package/dist/start.js +1 -1
- package/package.json +19 -2
- package/dist/update.js +0 -87
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
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
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
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ParseError = void 0;
|
|
4
|
+
exports.parseArgs = parseArgs;
|
|
5
|
+
exports.resolvePorts = resolvePorts;
|
|
6
|
+
class ParseError extends Error {
|
|
7
|
+
}
|
|
8
|
+
exports.ParseError = ParseError;
|
|
9
|
+
function parseArgs(argv) {
|
|
10
|
+
const opts = { command: "run" };
|
|
11
|
+
let showHelp = false;
|
|
12
|
+
const deprecatedFlags = [];
|
|
13
|
+
const noteDeprecated = (flag) => {
|
|
14
|
+
if (!deprecatedFlags.includes(flag))
|
|
15
|
+
deprecatedFlags.push(flag);
|
|
16
|
+
};
|
|
17
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
18
|
+
const arg = argv[i];
|
|
19
|
+
if (arg === "--help" || arg === "-h") {
|
|
20
|
+
showHelp = true;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (arg === "--version" || arg === "-V") {
|
|
24
|
+
opts.showVersion = true;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (arg === "dev" || arg === "run" || arg === "start") {
|
|
28
|
+
opts.command = arg;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (arg === "--runtime-version") {
|
|
32
|
+
opts.runtimeVersion = takeValue(argv, i, "--runtime-version");
|
|
33
|
+
i += 1;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (arg.startsWith("--runtime-version=")) {
|
|
37
|
+
const value = arg.slice("--runtime-version=".length);
|
|
38
|
+
if (value.length === 0)
|
|
39
|
+
throw new ParseError("--runtime-version requires a value");
|
|
40
|
+
opts.runtimeVersion = value;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (arg === "--dev") {
|
|
44
|
+
opts.command = "dev";
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
// --port is an alias for --backend-port (the user-facing port in run/start).
|
|
48
|
+
if (arg === "--port" || arg === "--backend-port") {
|
|
49
|
+
opts.backendPort = parsePort(takeValue(argv, i, arg), arg);
|
|
50
|
+
i += 1;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (arg.startsWith("--port=") || arg.startsWith("--backend-port=")) {
|
|
54
|
+
const flag = arg.startsWith("--port=") ? "--port" : "--backend-port";
|
|
55
|
+
opts.backendPort = parsePort(arg.slice(flag.length + 1), flag);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (arg === "--web-internal-port") {
|
|
59
|
+
opts.webPort = parsePort(takeValue(argv, i, "--web-internal-port"), "--web-internal-port");
|
|
60
|
+
i += 1;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (arg.startsWith("--web-internal-port=")) {
|
|
64
|
+
opts.webPort = parsePort(arg.slice("--web-internal-port=".length), "--web-internal-port");
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (arg === "--web-port") {
|
|
68
|
+
opts.webPort = parsePort(takeValue(argv, i, "--web-port"), "--web-port");
|
|
69
|
+
noteDeprecated("--web-port");
|
|
70
|
+
i += 1;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (arg.startsWith("--web-port=")) {
|
|
74
|
+
opts.webPort = parsePort(arg.slice("--web-port=".length), "--web-port");
|
|
75
|
+
noteDeprecated("--web-port");
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (arg === "--verbose" || arg === "-v") {
|
|
79
|
+
opts.verbose = true;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (arg === "--debug") {
|
|
83
|
+
opts.debug = true;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { options: opts, showHelp, deprecatedFlags };
|
|
88
|
+
}
|
|
89
|
+
function takeValue(argv, i, flag) {
|
|
90
|
+
const v = argv[i + 1];
|
|
91
|
+
if (v === undefined || v.startsWith("-")) {
|
|
92
|
+
throw new ParseError(`${flag} requires a value`);
|
|
93
|
+
}
|
|
94
|
+
return v;
|
|
95
|
+
}
|
|
96
|
+
function parsePort(raw, flag) {
|
|
97
|
+
const n = Number(raw);
|
|
98
|
+
if (raw === "" || !Number.isInteger(n) || n < 1 || n > 65535) {
|
|
99
|
+
throw new ParseError(`${flag} value must be an integer between 1 and 65535, got "${raw}"`);
|
|
100
|
+
}
|
|
101
|
+
return n;
|
|
102
|
+
}
|
|
103
|
+
// CLI flags beat env vars; KANDEV_PORT is an alias for KANDEV_BACKEND_PORT.
|
|
104
|
+
function resolvePorts(options, env) {
|
|
105
|
+
return {
|
|
106
|
+
backendPort: options.backendPort ?? envPort(env, "KANDEV_BACKEND_PORT") ?? envPort(env, "KANDEV_PORT"),
|
|
107
|
+
webPort: options.webPort ?? envPort(env, "KANDEV_WEB_PORT"),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function envPort(env, name) {
|
|
111
|
+
const val = env[name];
|
|
112
|
+
if (val === undefined)
|
|
113
|
+
return undefined;
|
|
114
|
+
const n = Number(val);
|
|
115
|
+
if (val === "" || !Number.isInteger(n) || n < 1 || n > 65535) {
|
|
116
|
+
throw new ParseError(`${name} must be an integer between 1 and 65535, got "${val}"`);
|
|
117
|
+
}
|
|
118
|
+
return n;
|
|
119
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -6,20 +6,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const node_path_1 = __importDefault(require("node:path"));
|
|
7
7
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
8
|
const package_json_1 = __importDefault(require("../package.json"));
|
|
9
|
+
const args_1 = require("./args");
|
|
9
10
|
const dev_1 = require("./dev");
|
|
10
11
|
const run_1 = require("./run");
|
|
11
12
|
const start_1 = require("./start");
|
|
12
13
|
const ports_1 = require("./ports");
|
|
13
|
-
const update_1 = require("./update");
|
|
14
14
|
function printHelp() {
|
|
15
15
|
console.log(`kandev launcher
|
|
16
16
|
|
|
17
17
|
Usage:
|
|
18
|
-
kandev run [--
|
|
19
|
-
kandev dev [--
|
|
20
|
-
kandev start [--
|
|
21
|
-
kandev [--
|
|
22
|
-
kandev --dev [--
|
|
18
|
+
kandev run [--port <port>] [--verbose] [--debug]
|
|
19
|
+
kandev dev [--port <port>]
|
|
20
|
+
kandev start [--port <port>] [--verbose] [--debug]
|
|
21
|
+
kandev [--port <port>] [--verbose] [--debug]
|
|
22
|
+
kandev --dev [--port <port>]
|
|
23
23
|
|
|
24
24
|
Examples:
|
|
25
25
|
kandev
|
|
@@ -27,81 +27,34 @@ Examples:
|
|
|
27
27
|
kandev --dev
|
|
28
28
|
kandev dev
|
|
29
29
|
kandev start
|
|
30
|
-
kandev --version
|
|
31
|
-
kandev --
|
|
30
|
+
kandev --version
|
|
31
|
+
kandev --port 3000
|
|
32
32
|
kandev --debug
|
|
33
33
|
|
|
34
34
|
Options:
|
|
35
|
-
dev
|
|
36
|
-
start
|
|
37
|
-
run
|
|
35
|
+
dev Use local repo for dev (make dev + next dev) if available.
|
|
36
|
+
start Use local production build (make build + next start).
|
|
37
|
+
run Use installed runtime bundle (default).
|
|
38
38
|
--dev Alias for "dev".
|
|
39
|
-
--version
|
|
40
|
-
--
|
|
41
|
-
|
|
39
|
+
--version, -V Print CLI version and exit.
|
|
40
|
+
--port Port for the Go backend (the URL kandev opens on in
|
|
41
|
+
start/run). Alias for --backend-port. Also reads
|
|
42
|
+
KANDEV_PORT or KANDEV_BACKEND_PORT.
|
|
42
43
|
--verbose, -v Show info logs from backend + web.
|
|
43
44
|
--debug Show debug logs + agent message dumps.
|
|
44
45
|
--help, -h Show help.
|
|
46
|
+
|
|
47
|
+
Advanced:
|
|
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
|
|
45
56
|
`);
|
|
46
57
|
}
|
|
47
|
-
function parseArgs(argv) {
|
|
48
|
-
const opts = { command: "run" };
|
|
49
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
50
|
-
const arg = argv[i];
|
|
51
|
-
if (arg === "--help" || arg === "-h") {
|
|
52
|
-
printHelp();
|
|
53
|
-
process.exit(0);
|
|
54
|
-
}
|
|
55
|
-
if (arg === "dev" || arg === "run" || arg === "start") {
|
|
56
|
-
opts.command = arg;
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
if (arg === "--version") {
|
|
60
|
-
opts.version = argv[i + 1];
|
|
61
|
-
i += 1;
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
if (arg.startsWith("--version=")) {
|
|
65
|
-
opts.version = arg.split("=")[1];
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
if (arg === "--dev") {
|
|
69
|
-
opts.command = "dev";
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
if (arg === "--backend-port") {
|
|
73
|
-
opts.backendPort = Number(argv[i + 1]);
|
|
74
|
-
i += 1;
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
if (arg.startsWith("--backend-port=")) {
|
|
78
|
-
opts.backendPort = Number(arg.split("=")[1]);
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
if (arg === "--web-port") {
|
|
82
|
-
opts.webPort = Number(argv[i + 1]);
|
|
83
|
-
i += 1;
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
if (arg.startsWith("--web-port=")) {
|
|
87
|
-
opts.webPort = Number(arg.split("=")[1]);
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
if (arg === "--verbose" || arg === "-v") {
|
|
91
|
-
opts.verbose = true;
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
if (arg === "--debug") {
|
|
95
|
-
opts.debug = true;
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return opts;
|
|
100
|
-
}
|
|
101
|
-
function envPort(name) {
|
|
102
|
-
const val = process.env[name];
|
|
103
|
-
return val ? Number(val) : undefined;
|
|
104
|
-
}
|
|
105
58
|
function findRepoRoot(startDir) {
|
|
106
59
|
let current = node_path_1.default.resolve(startDir);
|
|
107
60
|
while (true) {
|
|
@@ -125,10 +78,22 @@ function findRepoRoot(startDir) {
|
|
|
125
78
|
}
|
|
126
79
|
}
|
|
127
80
|
async function main() {
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
+
}
|
|
86
|
+
if (showHelp) {
|
|
87
|
+
printHelp();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
for (const flag of deprecatedFlags) {
|
|
91
|
+
process.stderr.write(`[kandev] ${flag} is deprecated; use --web-internal-port\n`);
|
|
92
|
+
}
|
|
93
|
+
const resolved = (0, args_1.resolvePorts)(options, process.env);
|
|
94
|
+
const backendPort = (0, ports_1.ensureValidPort)(resolved.backendPort, "backend port");
|
|
95
|
+
const webPort = (0, ports_1.ensureValidPort)(resolved.webPort, "web port");
|
|
96
|
+
if (options.command === "dev") {
|
|
132
97
|
const repoRoot = findRepoRoot(process.cwd());
|
|
133
98
|
if (!repoRoot) {
|
|
134
99
|
throw new Error("Unable to locate repo root for dev. Run from the repo.");
|
|
@@ -136,24 +101,34 @@ async function main() {
|
|
|
136
101
|
await (0, dev_1.runDev)({ repoRoot, backendPort, webPort });
|
|
137
102
|
return;
|
|
138
103
|
}
|
|
139
|
-
if (
|
|
104
|
+
if (options.command === "start") {
|
|
140
105
|
const repoRoot = findRepoRoot(process.cwd());
|
|
141
106
|
if (!repoRoot) {
|
|
142
107
|
throw new Error("Unable to locate repo root for start. Run from the repo.");
|
|
143
108
|
}
|
|
144
|
-
await (0, start_1.runStart)({
|
|
109
|
+
await (0, start_1.runStart)({
|
|
110
|
+
repoRoot,
|
|
111
|
+
backendPort,
|
|
112
|
+
webPort,
|
|
113
|
+
verbose: options.verbose,
|
|
114
|
+
debug: options.debug,
|
|
115
|
+
});
|
|
145
116
|
return;
|
|
146
117
|
}
|
|
147
|
-
await (0, update_1.maybePromptForUpdate)(package_json_1.default.version, process.argv.slice(2));
|
|
148
118
|
await (0, run_1.runRelease)({
|
|
149
|
-
|
|
119
|
+
runtimeVersion: options.runtimeVersion,
|
|
150
120
|
backendPort,
|
|
151
121
|
webPort,
|
|
152
|
-
verbose:
|
|
153
|
-
debug:
|
|
122
|
+
verbose: options.verbose,
|
|
123
|
+
debug: options.debug,
|
|
154
124
|
});
|
|
155
125
|
}
|
|
156
126
|
main().catch((err) => {
|
|
127
|
+
if (err instanceof args_1.ParseError) {
|
|
128
|
+
console.error(`[kandev] ${err.message}`);
|
|
129
|
+
console.error("[kandev] run --help for usage");
|
|
130
|
+
process.exit(2);
|
|
131
|
+
}
|
|
157
132
|
console.error(`[kandev] ${err instanceof Error ? err.message : String(err)}`);
|
|
158
133
|
process.exit(1);
|
|
159
134
|
});
|
package/dist/constants.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.DEV_KANDEV_DOTDIR = exports.DATA_DIR = exports.CACHE_DIR = exports.KANDEV_TASKS_DIR = exports.KANDEV_HOME_DIR = exports.KANDEV_DOTDIR = exports.HEALTH_TIMEOUT_MS_DEV = exports.HEALTH_TIMEOUT_MS_RELEASE = exports.RANDOM_PORT_RETRIES = exports.RANDOM_PORT_MAX = exports.RANDOM_PORT_MIN = exports.
|
|
6
|
+
exports.DEV_KANDEV_DOTDIR = exports.DATA_DIR = exports.CACHE_DIR = exports.KANDEV_TASKS_DIR = exports.KANDEV_HOME_DIR = exports.KANDEV_DOTDIR = exports.HEALTH_TIMEOUT_MS_DEV = exports.HEALTH_TIMEOUT_MS_RELEASE = exports.RANDOM_PORT_RETRIES = exports.RANDOM_PORT_MAX = exports.RANDOM_PORT_MIN = exports.DEFAULT_AGENTCTL_PORT = exports.DEFAULT_WEB_PORT = exports.DEFAULT_BACKEND_PORT = void 0;
|
|
7
7
|
exports.devKandevHome = devKandevHome;
|
|
8
8
|
const node_os_1 = __importDefault(require("node:os"));
|
|
9
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
@@ -13,7 +13,6 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
13
13
|
exports.DEFAULT_BACKEND_PORT = 38429;
|
|
14
14
|
exports.DEFAULT_WEB_PORT = 37429;
|
|
15
15
|
exports.DEFAULT_AGENTCTL_PORT = 39429;
|
|
16
|
-
exports.DEFAULT_MCP_PORT = 40429;
|
|
17
16
|
// Random fallback range for port selection.
|
|
18
17
|
exports.RANDOM_PORT_MIN = 10000;
|
|
19
18
|
exports.RANDOM_PORT_MAX = 60000;
|
package/dist/dev.js
CHANGED
|
@@ -19,6 +19,7 @@ async function runDev({ repoRoot, backendPort, webPort }) {
|
|
|
19
19
|
const backendEnv = (0, shared_1.buildBackendEnv)({ ports, extra });
|
|
20
20
|
const webEnv = (0, shared_1.buildWebEnv)({ ports, debug: true });
|
|
21
21
|
const logLevel = process.env.KANDEV_LOGGING_LEVEL?.trim() || process.env.KANDEV_LOG_LEVEL?.trim() || "info";
|
|
22
|
+
const webUrl = `http://localhost:${ports.webPort}`;
|
|
22
23
|
(0, shared_1.logStartupInfo)({
|
|
23
24
|
header: "dev mode: using local repo",
|
|
24
25
|
ports,
|
|
@@ -38,7 +39,6 @@ async function runDev({ repoRoot, backendPort, webPort }) {
|
|
|
38
39
|
console.log("[kandev] starting backend...");
|
|
39
40
|
await (0, health_1.waitForHealth)(ports.backendUrl, backendProc, healthTimeoutMs);
|
|
40
41
|
console.log(`[kandev] backend ready at ${ports.backendUrl}`);
|
|
41
|
-
const webUrl = `http://localhost:${ports.webPort}`;
|
|
42
42
|
console.log("[kandev] starting web...");
|
|
43
43
|
const webProc = (0, web_1.launchWebApp)({
|
|
44
44
|
command: "pnpm",
|
|
@@ -49,7 +49,7 @@ async function runDev({ repoRoot, backendPort, webPort }) {
|
|
|
49
49
|
label: "web",
|
|
50
50
|
});
|
|
51
51
|
await (0, health_1.waitForUrlReady)(webUrl, webProc, healthTimeoutMs);
|
|
52
|
-
console.log(`[kandev]
|
|
52
|
+
console.log(`[kandev] open: ${webUrl}`);
|
|
53
53
|
(0, web_1.openBrowser)(webUrl);
|
|
54
54
|
}
|
|
55
55
|
// Computes the dev-mode backend env. Dev mode always roots kandev under
|
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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
|
159
|
-
requestedVersion: version,
|
|
169
|
+
releaseTag,
|
|
160
170
|
webPort: actualWebPort,
|
|
161
171
|
agentctlPort,
|
|
162
172
|
dbPath,
|
|
@@ -179,29 +189,20 @@ function attachRingBuffer(stream, maxChars = 64 * 1024) {
|
|
|
179
189
|
});
|
|
180
190
|
return () => buf;
|
|
181
191
|
}
|
|
182
|
-
function
|
|
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}
|
|
194
|
+
header: `release: ${prepared.releaseTag}`,
|
|
188
195
|
ports: {
|
|
189
196
|
backendPort: Number(prepared.backendEnv.KANDEV_SERVER_PORT),
|
|
190
197
|
webPort: prepared.webPort,
|
|
191
198
|
agentctlPort: prepared.agentctlPort,
|
|
192
|
-
mcpPort: 0,
|
|
193
199
|
backendUrl: prepared.backendUrl,
|
|
194
|
-
mcpUrl: "",
|
|
195
200
|
},
|
|
196
201
|
dbPath: prepared.dbPath,
|
|
197
202
|
logLevel: prepared.logLevel,
|
|
198
203
|
});
|
|
199
204
|
const supervisor = (0, process_1.createProcessSupervisor)();
|
|
200
205
|
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).
|
|
205
206
|
const backendProc = (0, node_child_process_1.spawn)(prepared.backendBin, [], {
|
|
206
207
|
cwd: node_path_1.default.dirname(prepared.backendBin),
|
|
207
208
|
env: prepared.backendEnv,
|
|
@@ -228,9 +229,15 @@ function launchReleaseApps(prepared) {
|
|
|
228
229
|
}
|
|
229
230
|
return { supervisor, backendProc, webServerPath, dumpBackendLogs };
|
|
230
231
|
}
|
|
231
|
-
async function runRelease({
|
|
232
|
-
const prepared = await
|
|
233
|
-
|
|
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);
|
|
234
241
|
const healthTimeoutMs = (0, health_1.resolveHealthTimeoutMs)(constants_1.HEALTH_TIMEOUT_MS_RELEASE);
|
|
235
242
|
console.log("[kandev] starting backend...");
|
|
236
243
|
await (0, health_1.waitForHealth)(prepared.backendUrl, backendProc, healthTimeoutMs, dumpBackendLogs);
|
|
@@ -247,6 +254,6 @@ async function runRelease({ version, backendPort, webPort, verbose = false, debu
|
|
|
247
254
|
quiet: !prepared.showOutput,
|
|
248
255
|
});
|
|
249
256
|
await (0, health_1.waitForUrlReady)(webUrl, webProc, healthTimeoutMs);
|
|
250
|
-
console.log("[kandev]
|
|
257
|
+
console.log("[kandev] open: " + prepared.backendUrl);
|
|
251
258
|
(0, web_1.openBrowser)(prepared.backendUrl);
|
|
252
259
|
}
|
package/dist/runtime.js
ADDED
|
@@ -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/dist/shared.js
CHANGED
|
@@ -24,14 +24,11 @@ async function pickPorts(backendPort, webPort) {
|
|
|
24
24
|
const resolvedBackendPort = backendPort ?? (await (0, ports_1.pickAvailablePort)(constants_1.DEFAULT_BACKEND_PORT));
|
|
25
25
|
const resolvedWebPort = webPort ?? (await (0, ports_1.pickAvailablePort)(constants_1.DEFAULT_WEB_PORT));
|
|
26
26
|
const agentctlPort = await (0, ports_1.pickAvailablePort)(constants_1.DEFAULT_AGENTCTL_PORT);
|
|
27
|
-
const mcpPort = await (0, ports_1.pickAvailablePort)(constants_1.DEFAULT_MCP_PORT);
|
|
28
27
|
return {
|
|
29
28
|
backendPort: resolvedBackendPort,
|
|
30
29
|
webPort: resolvedWebPort,
|
|
31
30
|
agentctlPort,
|
|
32
|
-
mcpPort,
|
|
33
31
|
backendUrl: `http://localhost:${resolvedBackendPort}`,
|
|
34
|
-
mcpUrl: `http://localhost:${mcpPort}/sse`,
|
|
35
32
|
};
|
|
36
33
|
}
|
|
37
34
|
/**
|
|
@@ -47,7 +44,6 @@ function buildBackendEnv(options) {
|
|
|
47
44
|
KANDEV_SERVER_PORT: String(ports.backendPort),
|
|
48
45
|
KANDEV_WEB_INTERNAL_URL: `http://localhost:${ports.webPort}`,
|
|
49
46
|
KANDEV_AGENT_STANDALONE_PORT: String(ports.agentctlPort),
|
|
50
|
-
KANDEV_AGENT_MCP_SERVER_PORT: String(ports.mcpPort),
|
|
51
47
|
...(logLevel ? { KANDEV_LOG_LEVEL: logLevel } : {}),
|
|
52
48
|
...extra,
|
|
53
49
|
};
|
|
@@ -85,11 +81,13 @@ function buildWebEnv(options) {
|
|
|
85
81
|
*/
|
|
86
82
|
function logStartupInfo(options) {
|
|
87
83
|
const { header, ports, dbPath, logLevel } = options;
|
|
84
|
+
const backendUrl = ports.backendUrl;
|
|
85
|
+
const webUrl = `http://localhost:${ports.webPort}`;
|
|
88
86
|
console.log(`[kandev] ${header}`);
|
|
89
|
-
console.log("[kandev]
|
|
87
|
+
console.log("[kandev] backend:", backendUrl);
|
|
88
|
+
console.log("[kandev] web:", webUrl);
|
|
90
89
|
console.log("[kandev] agentctl port:", ports.agentctlPort);
|
|
91
|
-
console.log("[kandev] mcp
|
|
92
|
-
console.log("[kandev] mcp url:", ports.mcpUrl);
|
|
90
|
+
console.log("[kandev] mcp url:", `${backendUrl}/mcp`);
|
|
93
91
|
if (dbPath) {
|
|
94
92
|
console.log("[kandev] db path:", dbPath);
|
|
95
93
|
}
|
package/dist/start.js
CHANGED
|
@@ -171,6 +171,6 @@ async function runStart({ repoRoot, backendPort, webPort, verbose = false, debug
|
|
|
171
171
|
quiet: !showOutput,
|
|
172
172
|
});
|
|
173
173
|
await (0, health_1.waitForUrlReady)(webUrl, webProc, healthTimeoutMs);
|
|
174
|
-
console.log("[kandev]
|
|
174
|
+
console.log("[kandev] open: " + ports.backendUrl);
|
|
175
175
|
(0, web_1.openBrowser)(ports.backendUrl);
|
|
176
176
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kandev",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.2",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "
|
|
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.39.2",
|
|
26
|
+
"@kdlbs/runtime-linux-arm64": "0.39.2",
|
|
27
|
+
"@kdlbs/runtime-darwin-x64": "0.39.2",
|
|
28
|
+
"@kdlbs/runtime-darwin-arm64": "0.39.2",
|
|
29
|
+
"@kdlbs/runtime-win32-x64": "0.39.2"
|
|
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
|
-
}
|