@kurrent/kcap 0.9.1 → 0.9.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/bin/kcap.js CHANGED
@@ -28,73 +28,98 @@ const PLATFORM_PACKAGES = {
28
28
  "win32-x64": "@kurrent/kcap-win-x64",
29
29
  };
30
30
 
31
- const musl = process.platform === "linux" && isMusl() ? "-musl" : "";
32
- const platformKey = `${process.platform}${musl}-${process.arch}`;
33
- const packageName = PLATFORM_PACKAGES[platformKey];
34
-
35
- if (!packageName) {
36
- console.error(`Unsupported platform: ${platformKey}`);
37
- console.error(`Supported: ${Object.keys(PLATFORM_PACKAGES).join(", ")}`);
38
- process.exit(1);
31
+ // Resolves the npm dist-tag to install for `kcap update`, based on the
32
+ // resolved channel reported by `kcap update --check`'s `install_tag` field.
33
+ // Falls back to "latest" when the probe is missing, failed, or has no tag,
34
+ // preserving today's default behavior.
35
+ function resolveInstallSpec(info) {
36
+ const tag = info && typeof info.install_tag === "string" && info.install_tag
37
+ ? info.install_tag
38
+ : "latest";
39
+ return `@kurrent/kcap@${tag}`;
39
40
  }
40
41
 
41
- // Resolve the platform package
42
- let binaryDir;
43
- try {
44
- binaryDir = path.dirname(require.resolve(`${packageName}/package.json`));
45
- } catch {
46
- console.error(`Platform package ${packageName} is not installed.`);
47
- console.error(`Try: npm install -g @kurrent/kcap`);
48
- process.exit(1);
42
+ // Builds the arg list for the `kcap update --check` probe, forwarding only
43
+ // the channel-switch flags (`--beta`/`--stable`) from the user's `kcap update`
44
+ // invocation. Nothing else is forwarded — the probe already supplies `--check`
45
+ // and `--no-update-check` itself, so this is a controlled call.
46
+ function probeArgs(updArgs) {
47
+ const channelFlags = (updArgs || []).filter((a) => a === "--beta" || a === "--stable");
48
+ return ["update", "--check", "--no-update-check", ...channelFlags];
49
49
  }
50
50
 
51
- const ext = process.platform === "win32" ? ".exe" : "";
52
- const binaryPath = path.join(binaryDir, "bin", `kcap${ext}`);
51
+ // Everything below actually DOES something (resolves the binary, execs it,
52
+ // runs the update flow), so it's guarded to only run when this file is
53
+ // executed directly — not when `require()`d (e.g. by the test).
54
+ if (require.main === module) {
55
+ const musl = process.platform === "linux" && isMusl() ? "-musl" : "";
56
+ const platformKey = `${process.platform}${musl}-${process.arch}`;
57
+ const packageName = PLATFORM_PACKAGES[platformKey];
58
+
59
+ if (!packageName) {
60
+ console.error(`Unsupported platform: ${platformKey}`);
61
+ console.error(`Supported: ${Object.keys(PLATFORM_PACKAGES).join(", ")}`);
62
+ process.exit(1);
63
+ }
53
64
 
54
- if (!fs.existsSync(binaryPath)) {
55
- console.error(`Binary not found at ${binaryPath}`);
56
- process.exit(1);
57
- }
65
+ // Resolve the platform package
66
+ let binaryDir;
67
+ try {
68
+ binaryDir = path.dirname(require.resolve(`${packageName}/package.json`));
69
+ } catch {
70
+ console.error(`Platform package ${packageName} is not installed.`);
71
+ console.error(`Try: npm install -g @kurrent/kcap`);
72
+ process.exit(1);
73
+ }
58
74
 
59
- // Ensure the binary is executable (npm doesn't always preserve permissions)
60
- try {
61
- fs.accessSync(binaryPath, fs.constants.X_OK);
62
- } catch {
63
- try { fs.chmodSync(binaryPath, 0o755); } catch {}
64
- }
75
+ const ext = process.platform === "win32" ? ".exe" : "";
76
+ const binaryPath = path.join(binaryDir, "bin", `kcap${ext}`);
65
77
 
66
- // `kcap update` for npm-global installs is driven HERE, in the Node launcher,
67
- // not by the native binary. The OS locks an executable image for the whole
68
- // process lifetime but a script only during load — so by driving the upgrade
69
- // from this script (with the native binary not running) npm can overwrite the
70
- // binary even on Windows, with no temp-file/rename/detached-helper dance.
71
- // `--check` (JSON probe) and `--help` fall through to the native binary.
72
- {
73
- const updArgs = process.argv.slice(3);
74
- const checkOnly = updArgs.includes("--check");
75
- const wantsHelp = updArgs.some((a) => a === "--help" || a === "-h");
76
- if (process.argv[2] === "update" && !checkOnly && !wantsHelp) {
77
- runUpdate(binaryPath); // never returns
78
+ if (!fs.existsSync(binaryPath)) {
79
+ console.error(`Binary not found at ${binaryPath}`);
80
+ process.exit(1);
78
81
  }
79
- }
80
82
 
81
- // Exec the native binary, replacing this process
82
- try {
83
- execFileSync(binaryPath, process.argv.slice(2), {
84
- stdio: "inherit",
85
- env: process.env,
86
- });
87
- process.exit(0);
88
- } catch (e) {
89
- if (e.status !== null) {
90
- process.exit(e.status);
83
+ // Ensure the binary is executable (npm doesn't always preserve permissions)
84
+ try {
85
+ fs.accessSync(binaryPath, fs.constants.X_OK);
86
+ } catch {
87
+ try { fs.chmodSync(binaryPath, 0o755); } catch {}
88
+ }
89
+
90
+ // `kcap update` for npm-global installs is driven HERE, in the Node launcher,
91
+ // not by the native binary. The OS locks an executable image for the whole
92
+ // process lifetime but a script only during load — so by driving the upgrade
93
+ // from this script (with the native binary not running) npm can overwrite the
94
+ // binary even on Windows, with no temp-file/rename/detached-helper dance.
95
+ // `--check` (JSON probe) and `--help` fall through to the native binary.
96
+ {
97
+ const updArgs = process.argv.slice(3);
98
+ const checkOnly = updArgs.includes("--check");
99
+ const wantsHelp = updArgs.some((a) => a === "--help" || a === "-h");
100
+ if (process.argv[2] === "update" && !checkOnly && !wantsHelp) {
101
+ runUpdate(binaryPath, updArgs); // never returns
102
+ }
103
+ }
104
+
105
+ // Exec the native binary, replacing this process
106
+ try {
107
+ execFileSync(binaryPath, process.argv.slice(2), {
108
+ stdio: "inherit",
109
+ env: process.env,
110
+ });
111
+ process.exit(0);
112
+ } catch (e) {
113
+ if (e.status !== null) {
114
+ process.exit(e.status);
115
+ }
116
+ process.exit(1);
91
117
  }
92
- process.exit(1);
93
118
  }
94
119
 
95
120
  // Performs `kcap update`: upgrade the global npm package, then refresh
96
121
  // user-scope skills/hooks. Always exits the process; never returns.
97
- function runUpdate(binaryPath) {
122
+ function runUpdate(binaryPath, updArgs) {
98
123
  // On Windows, `npm` is a .cmd shim; Node refuses to spawn .cmd/.bat directly
99
124
  // (2024 command-injection fix), so route npm through a shell there. Our npm
100
125
  // args are static, so shell use is safe.
@@ -127,12 +152,13 @@ function runUpdate(binaryPath) {
127
152
  // child fully EXITS before we run npm, so the binary file is unlocked when
128
153
  // npm overwrites it (matters on Windows). `--no-update-check` keeps the
129
154
  // background nudge from racing onto stderr.
155
+ let info = null;
130
156
  try {
131
- const out = execFileSync(binaryPath, ["update", "--check", "--no-update-check"], { encoding: "utf8" });
157
+ const out = execFileSync(binaryPath, probeArgs(updArgs), { encoding: "utf8" });
132
158
  // The probe prints one JSON line; take the last {...} line in case anything
133
159
  // else (e.g. a git-remote warning) landed on stdout first.
134
160
  const line = out.split(/\r?\n/).reverse().find((l) => l.trim().startsWith("{"));
135
- const info = JSON.parse(line);
161
+ info = JSON.parse(line);
136
162
  // Only short-circuit when the probe is CONFIDENT we're up to date: `newer`
137
163
  // explicitly false AND a known current version. `newer: null` (version
138
164
  // unknown / check failed) falls through to the upgrade rather than stranding
@@ -177,7 +203,7 @@ function runUpdate(binaryPath) {
177
203
  process.exit(1);
178
204
  }
179
205
 
180
- const res = spawnSync("npm", ["install", "-g", "@kurrent/kcap@latest"], {
206
+ const res = spawnSync("npm", ["install", "-g", resolveInstallSpec(info)], {
181
207
  stdio: "inherit",
182
208
  windowsHide: true,
183
209
  ...npmOpts,
@@ -211,3 +237,5 @@ function runUpdate(binaryPath) {
211
237
 
212
238
  process.exit(0);
213
239
  }
240
+
241
+ module.exports = { resolveInstallSpec, probeArgs };
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "kcap",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "Records and visualizes Claude Code sessions via kcap CLI hooks"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kurrent/kcap",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
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,12 +14,12 @@
14
14
  "postinstall": "node bin/postinstall.js"
15
15
  },
16
16
  "optionalDependencies": {
17
- "@kurrent/kcap-darwin-arm64": "0.9.1",
18
- "@kurrent/kcap-linux-x64": "0.9.1",
19
- "@kurrent/kcap-linux-arm64": "0.9.1",
20
- "@kurrent/kcap-linux-musl-x64": "0.9.1",
21
- "@kurrent/kcap-linux-musl-arm64": "0.9.1",
22
- "@kurrent/kcap-win-x64": "0.9.1"
17
+ "@kurrent/kcap-darwin-arm64": "0.9.2",
18
+ "@kurrent/kcap-linux-x64": "0.9.2",
19
+ "@kurrent/kcap-linux-arm64": "0.9.2",
20
+ "@kurrent/kcap-linux-musl-x64": "0.9.2",
21
+ "@kurrent/kcap-linux-musl-arm64": "0.9.2",
22
+ "@kurrent/kcap-win-x64": "0.9.2"
23
23
  },
24
24
  "files": [
25
25
  "bin/",