@kurrent/kcap 0.11.11 → 0.11.12
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/bin/kcap.js +8 -26
- package/bin/refresh.js +104 -1
- package/bin/resolve.js +61 -0
- package/kcap/.claude-plugin/plugin.json +1 -1
- package/package.json +9 -9
package/bin/kcap.js
CHANGED
|
@@ -5,28 +5,11 @@
|
|
|
5
5
|
const { execFileSync, spawnSync } = require("child_process");
|
|
6
6
|
const path = require("path");
|
|
7
7
|
const fs = require("fs");
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} catch {
|
|
14
|
-
try {
|
|
15
|
-
return fs.readdirSync("/lib").some((f) => f.startsWith("ld-musl-"));
|
|
16
|
-
} catch {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const PLATFORM_PACKAGES = {
|
|
23
|
-
"darwin-arm64": "@kurrent/kcap-darwin-arm64",
|
|
24
|
-
"linux-x64": "@kurrent/kcap-linux-x64",
|
|
25
|
-
"linux-arm64": "@kurrent/kcap-linux-arm64",
|
|
26
|
-
"linux-musl-x64": "@kurrent/kcap-linux-musl-x64",
|
|
27
|
-
"linux-musl-arm64": "@kurrent/kcap-linux-musl-arm64",
|
|
28
|
-
"win32-x64": "@kurrent/kcap-win-x64",
|
|
29
|
-
};
|
|
8
|
+
// Platform detection + native-binary resolution live in resolve.js — a
|
|
9
|
+
// side-effect-free module shared with refresh.js (which must never require
|
|
10
|
+
// THIS file: during `kcap update`, runUpdate executes before the final
|
|
11
|
+
// module.exports below, so it would see partial exports).
|
|
12
|
+
const { PLATFORM_PACKAGES, platformKey } = require("./resolve");
|
|
30
13
|
|
|
31
14
|
// Resolves the npm dist-tag to install for `kcap update`, based on the
|
|
32
15
|
// resolved channel reported by `kcap update --check`'s `install_tag` field.
|
|
@@ -203,12 +186,11 @@ function printLockedBinaryHelp(globalRoot, onlyIfFound = false) {
|
|
|
203
186
|
// runs the update flow), so it's guarded to only run when this file is
|
|
204
187
|
// executed directly — not when `require()`d (e.g. by the test).
|
|
205
188
|
if (require.main === module) {
|
|
206
|
-
const
|
|
207
|
-
const
|
|
208
|
-
const packageName = PLATFORM_PACKAGES[platformKey];
|
|
189
|
+
const key = platformKey();
|
|
190
|
+
const packageName = PLATFORM_PACKAGES[key];
|
|
209
191
|
|
|
210
192
|
if (!packageName) {
|
|
211
|
-
console.error(`Unsupported platform: ${
|
|
193
|
+
console.error(`Unsupported platform: ${key}`);
|
|
212
194
|
console.error(`Supported: ${Object.keys(PLATFORM_PACKAGES).join(", ")}`);
|
|
213
195
|
process.exit(1);
|
|
214
196
|
}
|
package/bin/refresh.js
CHANGED
|
@@ -7,8 +7,14 @@
|
|
|
7
7
|
// when the package manager allows install scripts to run.
|
|
8
8
|
// - kcap.js `update` — runs after a user-initiated `kcap update`, which works
|
|
9
9
|
// even when the package manager blocks postinstall scripts.
|
|
10
|
+
//
|
|
11
|
+
// This file must never require kcap.js: during `kcap update`, runUpdate executes
|
|
12
|
+
// before kcap.js's final module.exports assignment, so it would observe partial
|
|
13
|
+
// exports. Shared platform/binary resolution lives in resolve.js instead.
|
|
10
14
|
|
|
11
15
|
const { spawnSync } = require("child_process");
|
|
16
|
+
const path = require("path");
|
|
17
|
+
const fs = require("fs");
|
|
12
18
|
|
|
13
19
|
// One entry per agent. Order is independent — each refresh is gated by its own
|
|
14
20
|
// marker via `--if-installed`, which no-ops unless the user has previously
|
|
@@ -28,6 +34,11 @@ const REFRESHES = [
|
|
|
28
34
|
// from one never prevents the others and never throws to the caller — a failed
|
|
29
35
|
// refresh must never break `npm install` or report `kcap update` as failed.
|
|
30
36
|
function runRefreshes(launcherPath) {
|
|
37
|
+
// Point the shipped Claude plugin's MCP entries at the native binary. Runs
|
|
38
|
+
// here because runRefreshes fires at exactly the two moments the plugin dir
|
|
39
|
+
// has just been (re)written by npm: postinstall and `kcap update`.
|
|
40
|
+
patchPluginMcpConfig(launcherPath);
|
|
41
|
+
|
|
31
42
|
for (const argv of REFRESHES) {
|
|
32
43
|
try {
|
|
33
44
|
spawnSync(process.execPath, [launcherPath, ...argv], {
|
|
@@ -44,4 +55,96 @@ function runRefreshes(launcherPath) {
|
|
|
44
55
|
}
|
|
45
56
|
}
|
|
46
57
|
|
|
47
|
-
|
|
58
|
+
// ── Claude plugin .mcp.json patch ───────────────────────────────────────────
|
|
59
|
+
//
|
|
60
|
+
// The plugin ships `"command": "kcap"`, which resolves to the node wrapper —
|
|
61
|
+
// one resident Node runtime per MCP server per open Claude session (~25 MB
|
|
62
|
+
// idle each). Rewriting the command to the resolved native binary makes Claude
|
|
63
|
+
// spawn the binary directly. Best-effort: on any failure the shipped value
|
|
64
|
+
// stays in place, which still works via the wrapper.
|
|
65
|
+
|
|
66
|
+
// The servers shipped in the plugin's .mcp.json, keyed by canonical
|
|
67
|
+
// `["mcp", <suffix>]` args. Only these exact name/args pairs are patched —
|
|
68
|
+
// anything else in the file is preserved verbatim.
|
|
69
|
+
const PLUGIN_MCP_SERVERS = {
|
|
70
|
+
"kcap-review": "review",
|
|
71
|
+
"kcap-sessions": "sessions",
|
|
72
|
+
"kcap-flows": "flows",
|
|
73
|
+
"kcap-memory": "memory",
|
|
74
|
+
"kcap-workitems": "workitems",
|
|
75
|
+
"kcap-analytics": "analytics",
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// A command this patcher may rewrite: the shipped literal "kcap", or an
|
|
79
|
+
// absolute path whose basename is the kcap binary (a previous patch that has
|
|
80
|
+
// gone stale after an npm re-layout). Handles both path flavors so a stale
|
|
81
|
+
// Windows path is still recognized. Anything else is a customization → keep.
|
|
82
|
+
function isPatchableKcapCommand(command) {
|
|
83
|
+
if (command === "kcap") return true;
|
|
84
|
+
if (typeof command !== "string") return false;
|
|
85
|
+
if (!path.posix.isAbsolute(command) && !path.win32.isAbsolute(command)) return false;
|
|
86
|
+
const base = command.split(/[\\/]/).pop().toLowerCase();
|
|
87
|
+
return base === "kcap" || base === "kcap.exe";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Pure: rewrites in place the canonical plugin entries of `cfg` whose command
|
|
91
|
+
// is patchable, returning whether anything changed. Throws on a shape that
|
|
92
|
+
// isn't the plugin config (caller turns that into the single warning).
|
|
93
|
+
function patchPluginMcpServers(cfg, binaryPath) {
|
|
94
|
+
if (!cfg || typeof cfg !== "object" || Array.isArray(cfg)) {
|
|
95
|
+
throw new Error("config is not an object");
|
|
96
|
+
}
|
|
97
|
+
const servers = cfg.mcpServers;
|
|
98
|
+
if (!servers || typeof servers !== "object" || Array.isArray(servers)) {
|
|
99
|
+
throw new Error("mcpServers is not an object");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let changed = false;
|
|
103
|
+
for (const [name, suffix] of Object.entries(PLUGIN_MCP_SERVERS)) {
|
|
104
|
+
const entry = servers[name];
|
|
105
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
106
|
+
if (!Array.isArray(entry.args) || entry.args.length !== 2 ||
|
|
107
|
+
entry.args[0] !== "mcp" || entry.args[1] !== suffix) continue; // customized args → keep
|
|
108
|
+
if (!isPatchableKcapCommand(entry.command)) continue; // customized command → keep
|
|
109
|
+
if (entry.command === binaryPath) continue; // already current
|
|
110
|
+
entry.command = binaryPath;
|
|
111
|
+
changed = true;
|
|
112
|
+
}
|
|
113
|
+
return changed;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Rewrites the plugin's shipped .mcp.json (a sibling `kcap/` dir of the
|
|
117
|
+
// launcher's `bin/`) so Claude spawns the native binary directly. Validates
|
|
118
|
+
// before writing, writes a sibling temp file, then renames atomically — the
|
|
119
|
+
// original survives any pre-rename failure. Never throws; on failure it emits
|
|
120
|
+
// ONE concise warning and leaves the shipped `"command": "kcap"` in place.
|
|
121
|
+
// `binaryPath` is a test seam; real callers let it resolve.
|
|
122
|
+
function patchPluginMcpConfig(launcherPath, binaryPath) {
|
|
123
|
+
let mcpJsonPath;
|
|
124
|
+
try {
|
|
125
|
+
binaryPath = binaryPath || require("./resolve").resolveNativeBinary();
|
|
126
|
+
if (!binaryPath) return; // unsupported platform / package missing — wrapper keeps working
|
|
127
|
+
|
|
128
|
+
mcpJsonPath = path.join(path.dirname(launcherPath), "..", "kcap", ".mcp.json");
|
|
129
|
+
if (!fs.existsSync(mcpJsonPath)) return; // dev checkout / plugin not shipped
|
|
130
|
+
|
|
131
|
+
const cfg = JSON.parse(fs.readFileSync(mcpJsonPath, "utf8"));
|
|
132
|
+
if (!patchPluginMcpServers(cfg, binaryPath)) return; // already patched / nothing canonical
|
|
133
|
+
|
|
134
|
+
const tmp = `${mcpJsonPath}.tmp-${process.pid}-${Date.now()}`;
|
|
135
|
+
try {
|
|
136
|
+
fs.writeFileSync(tmp, JSON.stringify(cfg, null, 2) + "\n");
|
|
137
|
+
fs.renameSync(tmp, mcpJsonPath);
|
|
138
|
+
} catch (e) {
|
|
139
|
+
try { fs.unlinkSync(tmp); } catch {}
|
|
140
|
+
throw e;
|
|
141
|
+
}
|
|
142
|
+
} catch (e) {
|
|
143
|
+
console.warn(
|
|
144
|
+
`kcap: could not point the Claude plugin MCP entries at the native binary` +
|
|
145
|
+
` (${e && e.message ? e.message : e}); they will keep launching via the node wrapper.`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = { runRefreshes, patchPluginMcpConfig, patchPluginMcpServers, isPatchableKcapCommand };
|
package/bin/resolve.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Side-effect-free platform/native-binary resolution, shared by kcap.js (the
|
|
2
|
+
// launcher) and refresh.js (postinstall / `kcap update` refresh).
|
|
3
|
+
//
|
|
4
|
+
// This lives in its OWN module — refresh.js must never require kcap.js: during
|
|
5
|
+
// `kcap update`, runUpdate executes BEFORE kcap.js's final module.exports
|
|
6
|
+
// assignment, so a refresh.js → kcap.js require would observe partial exports
|
|
7
|
+
// exactly on that path. Requiring this module has no side effects.
|
|
8
|
+
|
|
9
|
+
const path = require("path");
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
|
|
12
|
+
function isMusl() {
|
|
13
|
+
if (process.platform !== "linux") return false;
|
|
14
|
+
try {
|
|
15
|
+
return fs.readFileSync("/usr/bin/ldd", "utf8").includes("musl");
|
|
16
|
+
} catch {
|
|
17
|
+
try {
|
|
18
|
+
return fs.readdirSync("/lib").some((f) => f.startsWith("ld-musl-"));
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const PLATFORM_PACKAGES = {
|
|
26
|
+
"darwin-arm64": "@kurrent/kcap-darwin-arm64",
|
|
27
|
+
"linux-x64": "@kurrent/kcap-linux-x64",
|
|
28
|
+
"linux-arm64": "@kurrent/kcap-linux-arm64",
|
|
29
|
+
"linux-musl-x64": "@kurrent/kcap-linux-musl-x64",
|
|
30
|
+
"linux-musl-arm64": "@kurrent/kcap-linux-musl-arm64",
|
|
31
|
+
"win32-x64": "@kurrent/kcap-win-x64",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function platformKey() {
|
|
35
|
+
const musl = process.platform === "linux" && isMusl() ? "-musl" : "";
|
|
36
|
+
return `${process.platform}${musl}-${process.arch}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Absolute path to the native kcap binary for the current platform, or null
|
|
40
|
+
// when the platform is unsupported, the platform package isn't installed, or
|
|
41
|
+
// the binary file is missing. `basedir` (optional) overrides where module
|
|
42
|
+
// resolution starts — tests point it at a fake package layout; callers inside
|
|
43
|
+
// the real install tree omit it (resolution then starts from this file).
|
|
44
|
+
function resolveNativeBinary(basedir) {
|
|
45
|
+
const packageName = PLATFORM_PACKAGES[platformKey()];
|
|
46
|
+
if (!packageName) return null;
|
|
47
|
+
|
|
48
|
+
let binaryDir;
|
|
49
|
+
try {
|
|
50
|
+
const opts = basedir ? { paths: [basedir] } : undefined;
|
|
51
|
+
binaryDir = path.dirname(require.resolve(`${packageName}/package.json`, opts));
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
57
|
+
const binaryPath = path.join(binaryDir, "bin", `kcap${ext}`);
|
|
58
|
+
return fs.existsSync(binaryPath) ? binaryPath : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = { PLATFORM_PACKAGES, isMusl, platformKey, resolveNativeBinary };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kurrent/kcap",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.12",
|
|
4
4
|
"description": "CLI companion for Kurrent Capacitor — records and visualizes Claude Code sessions",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"repository": {
|
|
@@ -14,17 +14,17 @@
|
|
|
14
14
|
"postinstall": "node bin/postinstall.js"
|
|
15
15
|
},
|
|
16
16
|
"optionalDependencies": {
|
|
17
|
-
"@kurrent/kcap-darwin-arm64": "0.11.
|
|
18
|
-
"@kurrent/kcap-linux-arm64": "0.11.
|
|
19
|
-
"@kurrent/kcap-linux-musl-arm64": "0.11.
|
|
20
|
-
"@kurrent/kcap-linux-musl-x64": "0.11.
|
|
21
|
-
"@kurrent/kcap-linux-x64": "0.11.
|
|
22
|
-
"@kurrent/kcap-win-x64": "0.11.
|
|
17
|
+
"@kurrent/kcap-darwin-arm64": "0.11.12",
|
|
18
|
+
"@kurrent/kcap-linux-arm64": "0.11.12",
|
|
19
|
+
"@kurrent/kcap-linux-musl-arm64": "0.11.12",
|
|
20
|
+
"@kurrent/kcap-linux-musl-x64": "0.11.12",
|
|
21
|
+
"@kurrent/kcap-linux-x64": "0.11.12",
|
|
22
|
+
"@kurrent/kcap-win-x64": "0.11.12"
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"bin/",
|
|
26
26
|
"kcap/"
|
|
27
27
|
],
|
|
28
|
-
"kcapReleaseCommit": "
|
|
29
|
-
"kcapReleaseTag": "v0.11.
|
|
28
|
+
"kcapReleaseCommit": "b02e5f0a0e20700edd6787f61704419b78556f30",
|
|
29
|
+
"kcapReleaseTag": "v0.11.12"
|
|
30
30
|
}
|