kandev 0.53.0 → 0.54.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/service/paths.js +36 -1
- package/dist/service/templates.js +33 -15
- package/package.json +6 -6
package/dist/service/paths.js
CHANGED
|
@@ -10,6 +10,7 @@ exports.linuxSystemUnitPath = linuxSystemUnitPath;
|
|
|
10
10
|
exports.macosUserAgentDir = macosUserAgentDir;
|
|
11
11
|
exports.macosUserPlistPath = macosUserPlistPath;
|
|
12
12
|
exports.macosSystemPlistPath = macosSystemPlistPath;
|
|
13
|
+
exports.homebrewShimPath = homebrewShimPath;
|
|
13
14
|
exports.captureLauncher = captureLauncher;
|
|
14
15
|
exports.resolveHomeDir = resolveHomeDir;
|
|
15
16
|
exports.resolveLogDir = resolveLogDir;
|
|
@@ -45,6 +46,30 @@ function macosUserPlistPath() {
|
|
|
45
46
|
function macosSystemPlistPath() {
|
|
46
47
|
return node_path_1.default.join(exports.MACOS_SYSTEM_DAEMON_DIR, `${exports.LAUNCHD_LABEL}.plist`);
|
|
47
48
|
}
|
|
49
|
+
// Homebrew is POSIX-only, so the Cellar segment is a hardcoded "/Cellar/"
|
|
50
|
+
// rather than `path.sep`-based. On a Windows CI runner `path.sep` would be
|
|
51
|
+
// "\\", which would never match a POSIX Cellar path and silently break shim
|
|
52
|
+
// derivation (and its tests).
|
|
53
|
+
const HOMEBREW_CELLAR_SEGMENT = "/Cellar/";
|
|
54
|
+
/**
|
|
55
|
+
* Derive the floating Homebrew launcher shim from a Cellar-installed cli.js path.
|
|
56
|
+
*
|
|
57
|
+
* Homebrew installs the CLI under `<prefix>/Cellar/kandev/<version>/...` and
|
|
58
|
+
* symlinks a version-independent shim at `<prefix>/bin/kandev`. That shim sets
|
|
59
|
+
* KANDEV_BUNDLE_DIR / KANDEV_VERSION itself and execs cli.js via the floating
|
|
60
|
+
* `opt/node` symlink, so it keeps working after `brew upgrade` deletes the old
|
|
61
|
+
* Cellar dir. Returns undefined when `cliEntry` isn't a Cellar layout (npm /
|
|
62
|
+
* unknown installs), so callers fall back to the version-pinned paths.
|
|
63
|
+
*/
|
|
64
|
+
function homebrewShimPath(cliEntry) {
|
|
65
|
+
const idx = cliEntry.indexOf(HOMEBREW_CELLAR_SEGMENT);
|
|
66
|
+
if (idx === -1)
|
|
67
|
+
return undefined;
|
|
68
|
+
const prefix = cliEntry.slice(0, idx);
|
|
69
|
+
// Homebrew layout is POSIX; use path.posix.join so the result keeps forward
|
|
70
|
+
// slashes regardless of the host the install/tests run on.
|
|
71
|
+
return node_path_1.default.posix.join(prefix, "bin", exports.SERVICE_NAME);
|
|
72
|
+
}
|
|
48
73
|
/**
|
|
49
74
|
* Snapshot the current invocation so the service unit can faithfully reproduce it.
|
|
50
75
|
*
|
|
@@ -63,7 +88,17 @@ function captureLauncher() {
|
|
|
63
88
|
: cliEntry.includes(`${node_path_1.default.sep}node_modules${node_path_1.default.sep}`)
|
|
64
89
|
? "npm"
|
|
65
90
|
: "unknown";
|
|
66
|
-
|
|
91
|
+
// For Homebrew installs, prefer the floating bin shim so the unit survives
|
|
92
|
+
// `brew upgrade` (which deletes the versioned Cellar dir baked into nodePath
|
|
93
|
+
// /cliEntry). Only adopt it when the shim actually exists on disk; otherwise
|
|
94
|
+
// fall back to the version-pinned paths below.
|
|
95
|
+
let shimPath;
|
|
96
|
+
if (kind === "homebrew") {
|
|
97
|
+
const candidate = homebrewShimPath(cliEntry);
|
|
98
|
+
if (candidate && node_fs_1.default.existsSync(candidate))
|
|
99
|
+
shimPath = candidate;
|
|
100
|
+
}
|
|
101
|
+
return { nodePath, cliEntry, kind, bundleDir, version, shimPath };
|
|
67
102
|
}
|
|
68
103
|
function resolveCliEntry() {
|
|
69
104
|
const argvEntry = process.argv[1];
|
|
@@ -9,12 +9,13 @@ exports.renderSystemdUnit = renderSystemdUnit;
|
|
|
9
9
|
exports.renderLaunchdPlist = renderLaunchdPlist;
|
|
10
10
|
const node_os_1 = __importDefault(require("node:os"));
|
|
11
11
|
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
-
// User-mode PATH includes ~/.local/bin so user-installed agent
|
|
13
|
-
// prefix, pipx, fnm, etc.) are
|
|
12
|
+
// User-mode PATH includes ~/.local/bin and ~/.bun/bin so user-installed agent
|
|
13
|
+
// CLIs (npm user prefix, pipx, fnm, Bun globals like oh-my-pi/omp, etc.) are
|
|
14
|
+
// discoverable.
|
|
14
15
|
const SYSTEMD_SYSTEM_PATH = "/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin";
|
|
15
|
-
const SYSTEMD_USER_PATH = `%h/.local/bin:${SYSTEMD_SYSTEM_PATH}`;
|
|
16
|
+
const SYSTEMD_USER_PATH = `%h/.local/bin:%h/.bun/bin:${SYSTEMD_SYSTEM_PATH}`;
|
|
16
17
|
const LAUNCHD_SYSTEM_PATH = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin";
|
|
17
|
-
const launchdUserPath = () => `${node_os_1.default.homedir()}/.local/bin:${LAUNCHD_SYSTEM_PATH}`;
|
|
18
|
+
const launchdUserPath = () => `${node_os_1.default.homedir()}/.local/bin:${node_os_1.default.homedir()}/.bun/bin:${LAUNCHD_SYSTEM_PATH}`;
|
|
18
19
|
// Prepend the launcher node's bin dir so `npm`/`npx` resolve under per-user
|
|
19
20
|
// node managers (fnm, nvm, asdf, volta, mise), where node lives in a versioned
|
|
20
21
|
// subdirectory not covered by the system PATH. ExecStart already points at this
|
|
@@ -51,29 +52,41 @@ function looksLikeManagedUnit(content) {
|
|
|
51
52
|
* Render a systemd unit file for kandev.
|
|
52
53
|
*
|
|
53
54
|
* The unit hard-codes absolute paths so it works without a user PATH. We pass
|
|
54
|
-
* `--headless` so the daemon doesn't try to open a browser.
|
|
55
|
-
*
|
|
56
|
-
*
|
|
55
|
+
* `--headless` so the daemon doesn't try to open a browser.
|
|
56
|
+
*
|
|
57
|
+
* For Homebrew installs the launcher carries a resolved `shimPath` — the
|
|
58
|
+
* floating `<prefix>/bin/kandev` shim that survives `brew upgrade`. When set we
|
|
59
|
+
* exec that shim and omit KANDEV_BUNDLE_DIR / KANDEV_VERSION and the
|
|
60
|
+
* version-pinned node bin dir from PATH, because the shim supplies all three
|
|
61
|
+
* itself. Otherwise (npm / unknown installs) we fall back to the version-pinned
|
|
62
|
+
* node + cli.js and surface KANDEV_BUNDLE_DIR / KANDEV_VERSION only when present.
|
|
57
63
|
*/
|
|
58
64
|
function renderSystemdUnit(input) {
|
|
65
|
+
const shimPath = input.launcher.shimPath;
|
|
59
66
|
const basePath = input.mode === "system" ? SYSTEMD_SYSTEM_PATH : SYSTEMD_USER_PATH;
|
|
67
|
+
// For the shim, prepend its own bin dir (the Homebrew prefix's `bin`, where
|
|
68
|
+
// node/npm/npx live) so npx-based agents resolve even when the prefix isn't
|
|
69
|
+
// one of the hardcoded defaults. pathWithNodeBinDir dedupes when it already is.
|
|
70
|
+
const pathValue = pathWithNodeBinDir(basePath, shimPath ?? input.launcher.nodePath);
|
|
60
71
|
const env = [
|
|
61
72
|
envLine("KANDEV_HOME_DIR", input.homeDir),
|
|
62
73
|
envLine("KANDEV_LOG_LEVEL", "info"),
|
|
63
|
-
envLine("PATH",
|
|
74
|
+
envLine("PATH", pathValue),
|
|
64
75
|
];
|
|
65
76
|
if (input.port !== undefined) {
|
|
66
77
|
env.push(envLine("KANDEV_SERVER_PORT", String(input.port)));
|
|
67
78
|
}
|
|
68
|
-
if (input.launcher.bundleDir) {
|
|
79
|
+
if (!shimPath && input.launcher.bundleDir) {
|
|
69
80
|
env.push(envLine("KANDEV_BUNDLE_DIR", input.launcher.bundleDir));
|
|
70
81
|
}
|
|
71
|
-
if (input.launcher.version) {
|
|
82
|
+
if (!shimPath && input.launcher.version) {
|
|
72
83
|
env.push(envLine("KANDEV_VERSION", input.launcher.version));
|
|
73
84
|
}
|
|
74
85
|
const wantedBy = input.mode === "system" ? "multi-user.target" : "default.target";
|
|
75
86
|
const userLine = input.mode === "system" && input.systemUser ? `User=${input.systemUser}\n` : "";
|
|
76
|
-
const exec =
|
|
87
|
+
const exec = shimPath
|
|
88
|
+
? `${quoteForUnit(shimPath)} --headless`
|
|
89
|
+
: `${quoteForUnit(input.launcher.nodePath)} ${quoteForUnit(input.launcher.cliEntry)} --headless`;
|
|
77
90
|
return `${SYSTEMD_MARKER}
|
|
78
91
|
[Unit]
|
|
79
92
|
Description=Kandev autonomous agent platform
|
|
@@ -102,25 +115,30 @@ WantedBy=${wantedBy}
|
|
|
102
115
|
* get a LaunchDaemon that runs at boot regardless of login.
|
|
103
116
|
*/
|
|
104
117
|
function renderLaunchdPlist(input) {
|
|
118
|
+
const shimPath = input.launcher.shimPath;
|
|
105
119
|
const basePath = input.mode === "system" ? LAUNCHD_SYSTEM_PATH : launchdUserPath();
|
|
120
|
+
// See renderSystemdUnit: prepend the shim's own bin dir so npm/npx resolve.
|
|
121
|
+
const pathValue = pathWithNodeBinDir(basePath, shimPath ?? input.launcher.nodePath);
|
|
106
122
|
const envEntries = [
|
|
107
123
|
["KANDEV_HOME_DIR", input.homeDir],
|
|
108
124
|
["KANDEV_LOG_LEVEL", "info"],
|
|
109
|
-
["PATH",
|
|
125
|
+
["PATH", pathValue],
|
|
110
126
|
];
|
|
111
127
|
if (input.port !== undefined) {
|
|
112
128
|
envEntries.push(["KANDEV_SERVER_PORT", String(input.port)]);
|
|
113
129
|
}
|
|
114
|
-
if (input.launcher.bundleDir) {
|
|
130
|
+
if (!shimPath && input.launcher.bundleDir) {
|
|
115
131
|
envEntries.push(["KANDEV_BUNDLE_DIR", input.launcher.bundleDir]);
|
|
116
132
|
}
|
|
117
|
-
if (input.launcher.version) {
|
|
133
|
+
if (!shimPath && input.launcher.version) {
|
|
118
134
|
envEntries.push(["KANDEV_VERSION", input.launcher.version]);
|
|
119
135
|
}
|
|
120
136
|
const envXml = envEntries
|
|
121
137
|
.map(([k, v]) => ` <key>${escapeXml(k)}</key>\n <string>${escapeXml(v)}</string>`)
|
|
122
138
|
.join("\n");
|
|
123
|
-
const args =
|
|
139
|
+
const args = shimPath
|
|
140
|
+
? [shimPath, "--headless"]
|
|
141
|
+
: [input.launcher.nodePath, input.launcher.cliEntry, "--headless"];
|
|
124
142
|
const argsXml = args.map((a) => ` <string>${escapeXml(a)}</string>`).join("\n");
|
|
125
143
|
// For system-mode LaunchDaemons, run as a specific user instead of root.
|
|
126
144
|
// For user agents this directive is omitted — the agent already runs as
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kandev",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.54.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Launcher for Kandev — manage tasks, orchestrate agents, review changes, and ship value",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
"npm": ">=7"
|
|
23
23
|
},
|
|
24
24
|
"optionalDependencies": {
|
|
25
|
-
"@kdlbs/runtime-linux-x64": "0.
|
|
26
|
-
"@kdlbs/runtime-linux-arm64": "0.
|
|
27
|
-
"@kdlbs/runtime-darwin-x64": "0.
|
|
28
|
-
"@kdlbs/runtime-darwin-arm64": "0.
|
|
29
|
-
"@kdlbs/runtime-win32-x64": "0.
|
|
25
|
+
"@kdlbs/runtime-linux-x64": "0.54.0",
|
|
26
|
+
"@kdlbs/runtime-linux-arm64": "0.54.0",
|
|
27
|
+
"@kdlbs/runtime-darwin-x64": "0.54.0",
|
|
28
|
+
"@kdlbs/runtime-darwin-arm64": "0.54.0",
|
|
29
|
+
"@kdlbs/runtime-win32-x64": "0.54.0"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"tar": "^7.5.11",
|