@kurrent/kcap 0.7.1 → 0.7.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 +1 -1
- package/bin/kcap.js +100 -1
- package/bin/postinstall.js +14 -42
- package/bin/refresh.js +44 -0
- package/kcap/.claude-plugin/plugin.json +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ kcap status Show server, auth, and daemon status
|
|
|
24
24
|
kcap daemon start [-d] Start the daemon
|
|
25
25
|
kcap daemon stop Stop the daemon
|
|
26
26
|
kcap review <pr> Launch Claude Code with PR review context
|
|
27
|
-
kcap update
|
|
27
|
+
kcap update Upgrade the CLI and refresh agent plugins
|
|
28
28
|
kcap --version Show version
|
|
29
29
|
kcap --help Show all commands
|
|
30
30
|
```
|
package/bin/kcap.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// Resolves and exec's the native kcap binary for the current platform.
|
|
4
4
|
|
|
5
|
-
const { execFileSync } = require("child_process");
|
|
5
|
+
const { execFileSync, spawnSync } = require("child_process");
|
|
6
6
|
const path = require("path");
|
|
7
7
|
const fs = require("fs");
|
|
8
8
|
|
|
@@ -63,6 +63,21 @@ try {
|
|
|
63
63
|
try { fs.chmodSync(binaryPath, 0o755); } catch {}
|
|
64
64
|
}
|
|
65
65
|
|
|
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
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
66
81
|
// Exec the native binary, replacing this process
|
|
67
82
|
try {
|
|
68
83
|
execFileSync(binaryPath, process.argv.slice(2), {
|
|
@@ -76,3 +91,87 @@ try {
|
|
|
76
91
|
}
|
|
77
92
|
process.exit(1);
|
|
78
93
|
}
|
|
94
|
+
|
|
95
|
+
// Performs `kcap update`: upgrade the global npm package, then refresh
|
|
96
|
+
// user-scope skills/hooks. Always exits the process; never returns.
|
|
97
|
+
function runUpdate(binaryPath) {
|
|
98
|
+
// On Windows, `npm` is a .cmd shim; Node refuses to spawn .cmd/.bat directly
|
|
99
|
+
// (2024 command-injection fix), so route npm through a shell there. Our npm
|
|
100
|
+
// args are static, so shell use is safe.
|
|
101
|
+
const npmOpts = process.platform === "win32" ? { shell: true } : {};
|
|
102
|
+
|
|
103
|
+
// Resolve npm's global root so we can (a) confirm this is a global install we
|
|
104
|
+
// own and (b) check we can write to it before starting.
|
|
105
|
+
let globalRoot = null;
|
|
106
|
+
try {
|
|
107
|
+
globalRoot = execFileSync("npm", ["root", "-g"], { encoding: "utf8", ...npmOpts }).trim();
|
|
108
|
+
} catch {}
|
|
109
|
+
|
|
110
|
+
let isGlobalNpm = false;
|
|
111
|
+
try {
|
|
112
|
+
if (globalRoot) {
|
|
113
|
+
const root = fs.realpathSync(globalRoot) + path.sep;
|
|
114
|
+
isGlobalNpm = fs.realpathSync(__filename).startsWith(root);
|
|
115
|
+
}
|
|
116
|
+
} catch {}
|
|
117
|
+
|
|
118
|
+
if (!isGlobalNpm) {
|
|
119
|
+
console.error("kcap was not installed via a global npm install, so it can't");
|
|
120
|
+
console.error("self-update. Update it the way you installed it, e.g.:");
|
|
121
|
+
console.error(" npm install -g @kurrent/kcap@latest (npm)");
|
|
122
|
+
console.error(" brew upgrade kcap (Homebrew)");
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Ask the native binary whether a newer version exists. This short-lived
|
|
127
|
+
// child fully EXITS before we run npm, so the binary file is unlocked when
|
|
128
|
+
// npm overwrites it (matters on Windows). `--no-update-check` keeps the
|
|
129
|
+
// background nudge from racing onto stderr.
|
|
130
|
+
try {
|
|
131
|
+
const out = execFileSync(binaryPath, ["update", "--check", "--no-update-check"], { encoding: "utf8" });
|
|
132
|
+
// The probe prints one JSON line; take the last {...} line in case anything
|
|
133
|
+
// else (e.g. a git-remote warning) landed on stdout first.
|
|
134
|
+
const line = out.split(/\r?\n/).reverse().find((l) => l.trim().startsWith("{"));
|
|
135
|
+
const info = JSON.parse(line);
|
|
136
|
+
// Only short-circuit when the probe is CONFIDENT we're up to date: `newer`
|
|
137
|
+
// explicitly false AND a known current version. `newer: null` (version
|
|
138
|
+
// unknown / check failed) falls through to the upgrade rather than stranding
|
|
139
|
+
// the user on a stale CLI.
|
|
140
|
+
if (info && info.newer === false && info.current) {
|
|
141
|
+
console.log(`Already up to date: ${info.current}`);
|
|
142
|
+
process.exit(0);
|
|
143
|
+
}
|
|
144
|
+
if (info && info.newer) {
|
|
145
|
+
console.log(`Updating kcap: ${info.current} → ${info.latest}`);
|
|
146
|
+
}
|
|
147
|
+
} catch {
|
|
148
|
+
// Couldn't determine — fall through and let npm decide (it's idempotent).
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Fail clearly instead of half-installing under a root-owned prefix.
|
|
152
|
+
try {
|
|
153
|
+
fs.accessSync(globalRoot, fs.constants.W_OK);
|
|
154
|
+
} catch {
|
|
155
|
+
console.error(`Cannot write to the global npm directory (${globalRoot}).`);
|
|
156
|
+
console.error("Re-run with the permissions you installed kcap with, or reinstall");
|
|
157
|
+
console.error("to a user-owned prefix to avoid needing elevated rights on updates.");
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const res = spawnSync("npm", ["install", "-g", "@kurrent/kcap@latest"], {
|
|
162
|
+
stdio: "inherit",
|
|
163
|
+
windowsHide: true,
|
|
164
|
+
...npmOpts,
|
|
165
|
+
});
|
|
166
|
+
if (res.status !== 0) {
|
|
167
|
+
console.error("npm install failed; kcap was not updated.");
|
|
168
|
+
process.exit(res.status == null ? 1 : res.status);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// npm has now overwritten kcap.js, refresh.js, and the binary. require()
|
|
172
|
+
// reads the NEW refresh.js, which spawns the NEW launcher -> NEW binary.
|
|
173
|
+
console.log("Refreshing hooks and skills…");
|
|
174
|
+
require("./refresh").runRefreshes(fs.realpathSync(__filename));
|
|
175
|
+
console.log("kcap updated.");
|
|
176
|
+
process.exit(0);
|
|
177
|
+
}
|
package/bin/postinstall.js
CHANGED
|
@@ -2,58 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
// Runs after `npm install -g @kurrent/kcap` (including upgrades).
|
|
4
4
|
//
|
|
5
|
-
// Refreshes user-scope kcap agent installations so users pick up
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// Refreshes user-scope kcap agent installations so users pick up new or updated
|
|
6
|
+
// skills, hook commands, and Claude plugin registration without manually
|
|
7
|
+
// re-running `kcap setup`. The actual refresh logic lives in refresh.js, shared
|
|
8
|
+
// with the `kcap update` path.
|
|
8
9
|
//
|
|
9
10
|
// Contract:
|
|
10
|
-
// - Only runs on global installs. Skipping non-global installs avoids
|
|
11
|
-
//
|
|
11
|
+
// - Only runs on global installs. Skipping non-global installs avoids touching
|
|
12
|
+
// ~/.agents/, ~/.codex/, ~/.cursor/, or ~/.claude/ during unrelated
|
|
12
13
|
// local/transitive installs on already-opted-in machines.
|
|
13
|
-
// -
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
14
|
+
// - Always exits 0 — a failed refresh must never break `npm install`.
|
|
15
|
+
//
|
|
16
|
+
// Note: modern package managers gate install scripts behind an "allowed
|
|
17
|
+
// scripts" allowlist, so this hook may not run on upgrade. `kcap update` runs
|
|
18
|
+
// the same refresh and is not subject to that gate (the user invokes it
|
|
19
|
+
// explicitly).
|
|
19
20
|
|
|
20
|
-
const { spawnSync } = require("child_process");
|
|
21
21
|
const path = require("path");
|
|
22
22
|
|
|
23
23
|
const isGlobal =
|
|
24
24
|
process.env.npm_config_global === "true" ||
|
|
25
25
|
process.env.npm_config_location === "global";
|
|
26
26
|
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const launcher = path.join(__dirname, "kcap.js");
|
|
32
|
-
|
|
33
|
-
// One entry per agent. Order is independent — each refresh is gated by
|
|
34
|
-
// its own marker.
|
|
35
|
-
const refreshes = [
|
|
36
|
-
["plugin", "install", "--skills", "--if-installed"],
|
|
37
|
-
["plugin", "install", "--codex", "--if-installed"],
|
|
38
|
-
["plugin", "install", "--cursor", "--if-installed"],
|
|
39
|
-
["plugin", "install", "--copilot", "--if-installed"],
|
|
40
|
-
["plugin", "install", "--if-installed"], // Claude
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
for (const argv of refreshes) {
|
|
44
|
-
try {
|
|
45
|
-
spawnSync(process.execPath, [launcher, ...argv], {
|
|
46
|
-
stdio: "ignore",
|
|
47
|
-
env: process.env,
|
|
48
|
-
// Hard ceiling so a stalled child can never hang `npm install`.
|
|
49
|
-
// Each refresh is bounded independently.
|
|
50
|
-
timeout: 60_000,
|
|
51
|
-
killSignal: "SIGKILL",
|
|
52
|
-
windowsHide: true,
|
|
53
|
-
});
|
|
54
|
-
} catch {
|
|
55
|
-
// Never fail npm install.
|
|
56
|
-
}
|
|
27
|
+
if (isGlobal) {
|
|
28
|
+
require("./refresh").runRefreshes(path.join(__dirname, "kcap.js"));
|
|
57
29
|
}
|
|
58
30
|
|
|
59
31
|
process.exit(0);
|
package/bin/refresh.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Refreshes user-scope kcap agent installations so users pick up new or updated
|
|
2
|
+
// skills, Codex/Cursor/Copilot hook commands, and Claude plugin registration.
|
|
3
|
+
//
|
|
4
|
+
// Shared by:
|
|
5
|
+
// - postinstall.js — runs after `npm install -g @kurrent/kcap` (incl. upgrades),
|
|
6
|
+
// when the package manager allows install scripts to run.
|
|
7
|
+
// - kcap.js `update` — runs after a user-initiated `kcap update`, which works
|
|
8
|
+
// even when the package manager blocks postinstall scripts.
|
|
9
|
+
|
|
10
|
+
const { spawnSync } = require("child_process");
|
|
11
|
+
|
|
12
|
+
// One entry per agent. Order is independent — each refresh is gated by its own
|
|
13
|
+
// marker via `--if-installed`, which no-ops unless the user has previously
|
|
14
|
+
// opted in (marker file present OR pre-marker install detected).
|
|
15
|
+
const REFRESHES = [
|
|
16
|
+
["plugin", "install", "--skills", "--if-installed"],
|
|
17
|
+
["plugin", "install", "--codex", "--if-installed"],
|
|
18
|
+
["plugin", "install", "--cursor", "--if-installed"],
|
|
19
|
+
["plugin", "install", "--copilot", "--if-installed"],
|
|
20
|
+
["plugin", "install", "--if-installed"], // Claude
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
// Runs each refresh via the given launcher (an absolute path to kcap.js).
|
|
24
|
+
// Each refresh runs independently: a failure, timeout, or unexpected exit code
|
|
25
|
+
// from one never prevents the others and never throws to the caller — a failed
|
|
26
|
+
// refresh must never break `npm install` or report `kcap update` as failed.
|
|
27
|
+
function runRefreshes(launcherPath) {
|
|
28
|
+
for (const argv of REFRESHES) {
|
|
29
|
+
try {
|
|
30
|
+
spawnSync(process.execPath, [launcherPath, ...argv], {
|
|
31
|
+
stdio: "ignore",
|
|
32
|
+
env: process.env,
|
|
33
|
+
// Hard ceiling so a stalled child can never hang the caller.
|
|
34
|
+
timeout: 60_000,
|
|
35
|
+
killSignal: "SIGKILL",
|
|
36
|
+
windowsHide: true,
|
|
37
|
+
});
|
|
38
|
+
} catch {
|
|
39
|
+
// Never fail the caller.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { runRefreshes };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kurrent/kcap",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.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.7.
|
|
18
|
-
"@kurrent/kcap-linux-x64": "0.7.
|
|
19
|
-
"@kurrent/kcap-linux-arm64": "0.7.
|
|
20
|
-
"@kurrent/kcap-linux-musl-x64": "0.7.
|
|
21
|
-
"@kurrent/kcap-linux-musl-arm64": "0.7.
|
|
22
|
-
"@kurrent/kcap-win-x64": "0.7.
|
|
17
|
+
"@kurrent/kcap-darwin-arm64": "0.7.2",
|
|
18
|
+
"@kurrent/kcap-linux-x64": "0.7.2",
|
|
19
|
+
"@kurrent/kcap-linux-arm64": "0.7.2",
|
|
20
|
+
"@kurrent/kcap-linux-musl-x64": "0.7.2",
|
|
21
|
+
"@kurrent/kcap-linux-musl-arm64": "0.7.2",
|
|
22
|
+
"@kurrent/kcap-win-x64": "0.7.2"
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"bin/",
|