@tokmizer/plugin 0.9.57 → 0.9.59

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.
Files changed (60) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +11 -0
  3. package/README.md +214 -8
  4. package/agent-plugins/amp.js +38 -0
  5. package/agent-plugins/hermes/__init__.py +53 -0
  6. package/agent-plugins/hermes/plugin.yaml +5 -0
  7. package/agent-plugins/kilo.js +72 -0
  8. package/agent-plugins/omp.js +32 -0
  9. package/agent-plugins/openclaw/index.js +58 -0
  10. package/agent-plugins/openclaw/openclaw.plugin.json +12 -0
  11. package/agent-plugins/openclaw/package.json +11 -0
  12. package/agent-plugins/opencode.js +72 -0
  13. package/agent-plugins/pi.js +30 -0
  14. package/codex-hooks/hooks.json +16 -0
  15. package/command-templates/tkr-gain.md +7 -2
  16. package/command-templates/tkr-status.md +5 -1
  17. package/command-templates/tkr-telemetry.md +3 -3
  18. package/command-templates/tkr-unlink.md +2 -2
  19. package/dist/bin/tkr-agent-hook.js +13 -0
  20. package/dist/bin/tkr-codex-hook.js +134 -40
  21. package/dist/bin/tkr-context-hook.js +4 -0
  22. package/dist/bin/tkr-copilot-hook.js +13 -0
  23. package/dist/bin/tkr-crush-hook.js +13 -0
  24. package/dist/bin/tkr-cursor-hook.js +13 -0
  25. package/dist/bin/tkr-daemon.js +194 -56
  26. package/dist/bin/tkr-droid-hook.js +13 -0
  27. package/dist/bin/tkr-exec +136 -42
  28. package/dist/bin/tkr-exec.js +136 -42
  29. package/dist/bin/tkr-gemini-hook.js +13 -0
  30. package/dist/bin/tkr-grok-hook.js +13 -0
  31. package/dist/bin/tkr-junie-hook.js +13 -0
  32. package/dist/bin/tkr-processor.js +166 -0
  33. package/dist/bin/tkr-qwen-hook.js +13 -0
  34. package/dist/bin/tkr-shim.js +138 -37
  35. package/dist/bin/tkr-shim.stable +1 -0
  36. package/dist/bin/tkr-trae-hook.js +13 -0
  37. package/dist/bin/tkr-zcode-hook.js +13 -0
  38. package/dist/bin/tkr.js +425 -157
  39. package/dist/hooks-host/post-tool-use.js +150 -49
  40. package/dist/hooks-host/pre-tool-use.js +10 -7
  41. package/dist/hooks-host/reducer-prompt-submit.js +2 -1
  42. package/dist/hooks-host/reducer-session-start.js +2 -1
  43. package/dist/hooks-host/reducer-stop.js +25 -16
  44. package/dist/hooks-host/session-recap.js +4 -3
  45. package/dist/index.js +160 -41
  46. package/dist/native/manifest.json +18 -8
  47. package/dist/native/single.json +38 -0
  48. package/dist/native/tkr-exec-darwin-arm64 +0 -0
  49. package/dist/native/tkr-exec-darwin-x64 +0 -0
  50. package/dist/native/tkr-exec-linux-arm64 +0 -0
  51. package/dist/native/tkr-exec-linux-x64 +0 -0
  52. package/dist/native/tkr-exec-win32-arm64.exe +0 -0
  53. package/dist/native/tkr-exec-win32-x64.exe +0 -0
  54. package/hooks/on-session-start.d.mts +1 -0
  55. package/hooks/on-session-start.mjs +37 -34
  56. package/package.json +26 -3
  57. package/scripts/README-INSTALL.md +50 -14
  58. package/scripts/install-mac-linux.sh +77 -7
  59. package/scripts/install-native.mjs +177 -25
  60. package/codex-plugin/plugin.json +0 -7
@@ -1,22 +1,32 @@
1
1
  {
2
2
  "darwin-arm64": {
3
- "version": "0.9.57",
3
+ "version": "0.9.59",
4
4
  "file": "tkr-exec-darwin-arm64",
5
- "sha256": "c3979c74dc1faea94b6c5e3f974c3dd34df9615ca6dd3448a28a356c51f1948f"
5
+ "sha256": "51649a9a308547accdea592a35eeed18bc9fe36cd92bab493631f0496ee35c5d"
6
6
  },
7
7
  "darwin-x64": {
8
- "version": "0.9.57",
8
+ "version": "0.9.59",
9
9
  "file": "tkr-exec-darwin-x64",
10
- "sha256": "645abd7bf9bf7723db091de56d7aebefa5eeb4189303a1843739ab76529e6e1f"
10
+ "sha256": "b1d44e36eb8b18e4c811f0cb332e3541a7fb964b3748f3998998c3cefcd9fb72"
11
11
  },
12
12
  "linux-arm64": {
13
- "version": "0.9.57",
13
+ "version": "0.9.59",
14
14
  "file": "tkr-exec-linux-arm64",
15
- "sha256": "06f615141483965cc840c9eeec3da63149daf2a09603b9390471a712124ab160"
15
+ "sha256": "526edbcbdd7f0cf9b732e04968c238ef6a59f441d3c42bad73038407e441a6a8"
16
16
  },
17
17
  "linux-x64": {
18
- "version": "0.9.57",
18
+ "version": "0.9.59",
19
19
  "file": "tkr-exec-linux-x64",
20
- "sha256": "1a8ff65bdfb5f31444c3b2df2b68cd556ca2ebfdcc21c2e89d976f15f53e5dd2"
20
+ "sha256": "44acfffa5f099a53b9fff1337bfa7a87c39dd319713b05ed7dd8dd75e2125176"
21
+ },
22
+ "win32-arm64": {
23
+ "version": "0.9.59",
24
+ "file": "tkr-exec-win32-arm64.exe",
25
+ "sha256": "3aade1e4dc370b58903dac51c0c55379a9028c680f5b1e45af7b3b5065bd6a3b"
26
+ },
27
+ "win32-x64": {
28
+ "version": "0.9.59",
29
+ "file": "tkr-exec-win32-x64.exe",
30
+ "sha256": "3977f6a4dfcb983cc8cea7eb92ad60d524f5cc4db43ec8be3421c0fb315ba3ae"
21
31
  }
22
32
  }
@@ -0,0 +1,38 @@
1
+ {
2
+ "darwin-arm64": {
3
+ "version": "0.9.59",
4
+ "package": "@tokmizer/plugin-darwin-arm64",
5
+ "file": "tkr",
6
+ "sha256": "9fd4c2094fff7614a1f5766976bfad8a1161f1e19a935d7489a2aaef651428c4"
7
+ },
8
+ "darwin-x64": {
9
+ "version": "0.9.59",
10
+ "package": "@tokmizer/plugin-darwin-x64",
11
+ "file": "tkr",
12
+ "sha256": "088325e1ed8c6ff576ab45ee41ff3060e12db3c0f8d1b8822c2be6a1099db9cd"
13
+ },
14
+ "linux-arm64": {
15
+ "version": "0.9.59",
16
+ "package": "@tokmizer/plugin-linux-arm64",
17
+ "file": "tkr",
18
+ "sha256": "54ab3abe5ae7e0d53af930b34b0ad4ecc1e1ec59753b33c2aa5df0496a60bf7f"
19
+ },
20
+ "linux-x64": {
21
+ "version": "0.9.59",
22
+ "package": "@tokmizer/plugin-linux-x64",
23
+ "file": "tkr",
24
+ "sha256": "c7cd9ea3ff94cc69b4834550ed541dfdb5c6540f205f3ee1ff9a014d1b6bf1d7"
25
+ },
26
+ "win32-arm64": {
27
+ "version": "0.9.59",
28
+ "package": "@tokmizer/plugin-win32-arm64",
29
+ "file": "tkr.exe",
30
+ "sha256": "a2a82a175d3a36c75237a1371c3d5969295a8ca285115326562bbaa8d78236e5"
31
+ },
32
+ "win32-x64": {
33
+ "version": "0.9.59",
34
+ "package": "@tokmizer/plugin-win32-x64",
35
+ "file": "tkr.exe",
36
+ "sha256": "8ef4b646cc80f943c0f894ff4cc95bef01c940a5e85926462f73d95ca78238a5"
37
+ }
38
+ }
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1 @@
1
+ export {};
@@ -1,13 +1,23 @@
1
- import { spawn } from "node:child_process";
1
+ import { spawn, spawnSync } from "node:child_process";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { join, dirname, basename } from "node:path";
4
4
  import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync } from "node:fs";
5
5
  import { homedir } from "node:os";
6
6
 
7
+ const drained = new Promise((resolve) => {
8
+ if (process.stdin.isTTY) return resolve();
9
+ const timer = setTimeout(resolve, 2000);
10
+ process.stdin.on("data", () => {}).once("end", () => { clearTimeout(timer); resolve(); }).once("error", () => { clearTimeout(timer); resolve(); });
11
+ });
12
+
7
13
  const home = process.env.TOKMIZER_HOME || join(homedir(), ".tokmizer");
14
+ const single = process.env.TOKMIZER_SINGLE_BINARY === "1";
15
+ const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || (single ? join(dirname(process.execPath), "..", "..") : join(dirname(fileURLToPath(import.meta.url)), ".."));
16
+ const networkOff =
17
+ ["0", "off", "false", "no"].includes(String(process.env.TOKMIZER_NETWORK ?? "").trim().toLowerCase()) ||
18
+ existsSync(join(home, "network-off"));
8
19
 
9
20
  try {
10
- const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || join(dirname(fileURLToPath(import.meta.url)), "..");
11
21
  const srcDir = join(pluginRoot, "command-templates");
12
22
  const destDir = process.env.TOKMIZER_CLAUDE_COMMANDS_DIR || join(homedir(), ".claude", "commands");
13
23
  const MARKER = "managed-by: tokmizer";
@@ -30,7 +40,12 @@ try {
30
40
  } catch {}
31
41
 
32
42
  try {
33
- const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || join(dirname(fileURLToPath(import.meta.url)), "..");
43
+ const args = single ? ["shims-heal"] : [join(dirname(fileURLToPath(import.meta.url)), "..", "dist", "bin", "tkr.js"), "shims-heal"];
44
+ const healed = spawnSync(process.execPath, args, { stdio: ["ignore", "pipe", "ignore"], encoding: "utf8", timeout: 3000, windowsHide: true });
45
+ if (healed.status === 0 && healed.stdout && healed.stdout.trim()) console.log(healed.stdout.trim());
46
+ } catch {}
47
+
48
+ try {
34
49
  const SEMVER = /^\d+\.\d+\.\d+$/;
35
50
  const self = basename(pluginRoot);
36
51
  if (SEMVER.test(self)) {
@@ -69,31 +84,18 @@ try {
69
84
  } catch {}
70
85
 
71
86
  try {
72
- const onboardFile = join(home, "onboarded");
73
- const shimNudge = join(home, "shim-nudge");
74
- const shimsDir = join(home, "shims");
75
- if (existsSync(onboardFile) && !existsSync(shimNudge) && !existsSync(shimsDir)) {
76
- const onboardedAt = Number(readFileSync(onboardFile, "utf8")) || 0;
77
- if (onboardedAt > 0 && Date.now() - onboardedAt >= 24 * 60 * 60 * 1000) {
78
- const win = process.platform === "win32";
79
- const hasTkr = (process.env.PATH || "").split(win ? ";" : ":").some((d) => {
80
- try {
81
- return d && existsSync(join(d, win ? "tkr.cmd" : "tkr"));
82
- } catch {
83
- return false;
84
- }
85
- });
86
- const fixCmd = hasTkr
87
- ? "tkr shim install"
88
- : win
89
- ? "npm install -g @tokmizer/plugin && tkr shim install"
90
- : "curl -fsSL https://tokmizer.com/install.sh | bash";
91
- console.log(
92
- "Tokmizer covers this agent. Your other terminals and sub-agents are not covered yet. " +
93
- "One command fixes that: " + fixCmd
94
- );
95
- writeFileSync(shimNudge, String(Date.now()));
96
- }
87
+ const noticeFile = join(home, "network-notice-shown");
88
+ if (!networkOff && !existsSync(noticeFile)) {
89
+ console.log(
90
+ [
91
+ "Tokmizer contacts tokmizer.com to keep its rules up to date and to improve the product. No account or email is needed.",
92
+ "It sends a random install ID, your computer name, OS and CPU model, a hash of your network card address and of your user name, and the plugin version. Command contents and output are never sent.",
93
+ "Usage stats (command names, token counts and install health counts) are sent only while they are on. Check with: tkr telemetry status.",
94
+ "Turn all network access off: tkr network off. Usage stats only: tkr telemetry off."
95
+ ].join("\n")
96
+ );
97
+ mkdirSync(home, { recursive: true });
98
+ writeFileSync(noticeFile, String(Date.now()));
97
99
  }
98
100
  } catch {}
99
101
 
@@ -126,8 +128,7 @@ try {
126
128
  } catch {}
127
129
 
128
130
  try {
129
- const here = dirname(fileURLToPath(import.meta.url));
130
- const manifest = JSON.parse(readFileSync(join(here, "..", ".claude-plugin", "plugin.json"), "utf8"));
131
+ const manifest = JSON.parse(readFileSync(join(pluginRoot, ".claude-plugin", "plugin.json"), "utf8"));
131
132
  const state = JSON.parse(readFileSync(join(home, "state.json"), "utf8"));
132
133
  const latest = state.latestPluginVersion;
133
134
  if (latest && manifest.version && latest !== manifest.version) {
@@ -149,16 +150,18 @@ try {
149
150
  last = Number(readFileSync(throttleFile, "utf8")) || 0;
150
151
  } catch {}
151
152
 
152
- if (existsSync(stateFile) && Date.now() - last >= THROTTLE_MS) {
153
+ if (!networkOff && existsSync(stateFile) && Date.now() - last >= THROTTLE_MS) {
153
154
  try {
154
155
  writeFileSync(throttleFile, String(Date.now()));
155
156
  } catch {}
156
- const here = dirname(fileURLToPath(import.meta.url));
157
- const tkr = join(here, "..", "dist", "bin", "tkr.js");
158
- const child = spawn(process.execPath, [tkr, "heartbeat"], {
157
+ const args = single ? ["heartbeat"] : [join(dirname(fileURLToPath(import.meta.url)), "..", "dist", "bin", "tkr.js"), "heartbeat"];
158
+ const child = spawn(process.execPath, args, {
159
159
  detached: true,
160
160
  stdio: "ignore"
161
161
  });
162
162
  child.unref();
163
163
  }
164
164
  } catch {}
165
+
166
+ await drained;
167
+ process.stdin.destroy();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokmizer/plugin",
3
- "version": "0.9.57",
3
+ "version": "0.9.59",
4
4
  "description": "Tokmizer reduces token consumption for AI coding tools (Claude Code, Codex, Cursor, Aider, Gemini CLI, Cline, Windsurf, Copilot). Cross-OS.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -12,14 +12,27 @@
12
12
  "tkr-exec": "dist/bin/tkr-exec",
13
13
  "tkr-shim": "dist/bin/tkr-shim.js",
14
14
  "tkr-codex-hook": "dist/bin/tkr-codex-hook.js",
15
+ "tkr-cursor-hook": "dist/bin/tkr-cursor-hook.js",
16
+ "tkr-gemini-hook": "dist/bin/tkr-gemini-hook.js",
17
+ "tkr-agent-hook": "dist/bin/tkr-agent-hook.js",
18
+ "tkr-copilot-hook": "dist/bin/tkr-copilot-hook.js",
19
+ "tkr-droid-hook": "dist/bin/tkr-droid-hook.js",
20
+ "tkr-trae-hook": "dist/bin/tkr-trae-hook.js",
21
+ "tkr-qwen-hook": "dist/bin/tkr-qwen-hook.js",
22
+ "tkr-crush-hook": "dist/bin/tkr-crush-hook.js",
23
+ "tkr-junie-hook": "dist/bin/tkr-junie-hook.js",
24
+ "tkr-zcode-hook": "dist/bin/tkr-zcode-hook.js",
25
+ "tkr-grok-hook": "dist/bin/tkr-grok-hook.js",
15
26
  "tkr-daemon": "dist/bin/tkr-daemon.js"
16
27
  },
17
28
  "files": [
18
29
  "dist",
19
- "codex-plugin",
30
+ ".codex-plugin",
31
+ "codex-hooks",
20
32
  ".claude-plugin",
21
33
  "hooks",
22
34
  "command-templates",
35
+ "agent-plugins",
23
36
  "scripts/install-mac-linux.sh",
24
37
  "scripts/install-windows.ps1",
25
38
  "scripts/install-native.mjs",
@@ -31,7 +44,9 @@
31
44
  "build": "tsc -p tsconfig.json --noEmit && node scripts/bundle.mjs",
32
45
  "build:native": "node scripts/build-native.mjs",
33
46
  "postinstall": "node scripts/install-native.mjs",
34
- "prepublishOnly": "npm run build && node scripts/check-native-artifacts.mjs && ( cd ../.. && npx vitest run packages/plugin ) && node scripts/check-prod-key.mjs && node scripts/check-version.mjs && node scripts/check-dist-clean.mjs && node scripts/check-pack-clean.mjs && node dist/bin/tkr-exec.js echo dist-smoke-ok && node dist/bin/tkr.js help > /dev/null"
47
+ "prepublishOnly": "npm run build && node scripts/check-native-artifacts.mjs && ( cd ../.. && npx vitest run packages/plugin ) && node scripts/check-upgrade-safety.mjs && node scripts/check-prod-key.mjs && node scripts/check-version.mjs && node scripts/check-dist-clean.mjs && node scripts/check-pack-clean.mjs && node scripts/smoke-dist.mjs",
48
+ "build:single": "node scripts/build-single.mjs",
49
+ "pack:single": "node scripts/pack-single.mjs"
35
50
  },
36
51
  "devDependencies": {
37
52
  "@types/cross-spawn": "^6.0.6",
@@ -41,6 +56,14 @@
41
56
  "smol-toml": "^1.9.0",
42
57
  "zod": "^3.23.8"
43
58
  },
59
+ "optionalDependencies": {
60
+ "@tokmizer/plugin-darwin-arm64": "0.9.59",
61
+ "@tokmizer/plugin-darwin-x64": "0.9.59",
62
+ "@tokmizer/plugin-linux-arm64": "0.9.59",
63
+ "@tokmizer/plugin-linux-x64": "0.9.59",
64
+ "@tokmizer/plugin-win32-arm64": "0.9.59",
65
+ "@tokmizer/plugin-win32-x64": "0.9.59"
66
+ },
44
67
  "publishConfig": {
45
68
  "access": "public"
46
69
  },
@@ -2,7 +2,7 @@
2
2
 
3
3
  Tokmizer works with **Claude Code, OpenAI Codex, Cursor, Cline, Aider, Gemini CLI, Windsurf, and GitHub Copilot** — on **macOS, Linux, and Windows**.
4
4
 
5
- Claude Code and Codex use native hooks. Other tools can use command wrappers configured by `tkr shim install`. Node.js 20.11 or newer is required for npm installation. Tokmizer is free and requires no account.
5
+ Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, Factory Droid, Trae, Qwen Code, Crush, Junie CLI, ZCode, Grok Build and Kilo Code use native hooks or plugins (`tkr setup-<tool>` installs them; see the table below). Aider, Cline and Windsurf have no hook system: Tokmizer reaches them through command shims that are active only inside the agent, never in your own terminals. Tokmizer does not edit your shell profile. Node.js 20.11 or newer is required for npm installation. Tokmizer is free and requires no account.
6
6
 
7
7
  ## macOS / Linux
8
8
 
@@ -14,7 +14,6 @@ curl -fsSL https://tokmizer.com/install.sh | bash
14
14
 
15
15
  ```powershell
16
16
  npm.cmd install -g @tokmizer/plugin
17
- tkr.cmd shim install
18
17
  ```
19
18
 
20
19
  Claude Code users can also install via the marketplace (see Per-tool notes below).
@@ -22,30 +21,67 @@ Claude Code users can also install via the marketplace (see Per-tool notes below
22
21
  ## npm (macOS / Linux)
23
22
 
24
23
  ```bash
25
- npm install -g @tokmizer/plugin && tkr shim install
24
+ npm install -g @tokmizer/plugin
26
25
  ```
27
26
 
28
27
  ## Homebrew
29
28
 
30
29
  ```bash
31
- brew install norwelltw/tokmizer/tokmizer && tkr shim install
30
+ brew install norwelltw/tokmizer/tokmizer
32
31
  ```
33
32
 
34
33
  ## Per-tool notes
35
34
 
36
35
  | Tool | Adapter | Setup |
37
36
  |---------------|-----------------|----------------------------------------------------------------|
38
- | Claude Code | Native hooks | `/plugin marketplace add norwelltw/Tokmizer` then `/plugin install tokmizer@tokmizer` |
37
+ | Claude Code | Native hooks | `/plugin marketplace add norwelltw/Tokmizer` then `/plugin install tokmizer@tokmizer`. Without Node.js, `install.sh` runs `tkr setup-claude` for you (`tkr setup-claude --remove` undoes it) |
39
38
  | Codex CLI | Native hook | Install tkr (npm or brew), run `tkr setup-codex`, restart Codex, then approve in `/hooks` |
40
- | Cursor | Shell-shim | Open a new terminal after install. |
41
- | Cline | Shell-shim | Open a new terminal after install. |
42
- | Aider | Shell-shim | Open a new terminal after install. |
43
- | Gemini CLI | Shell-shim | Open a new terminal after install. |
44
- | Windsurf | Shell-shim | Open a new terminal after install. |
45
- | GitHub Copilot| Shell-shim | Open a new terminal after install. |
46
- | Plain shell | Shell-shim | Works out of the box. |
47
-
48
- Open a new terminal after installation and run `tkr status`. If activation is pending because the service could not be reached, rerun `tkr status` when online. Connecting an account is optional: `tkr link <your-key>`.
39
+ | Cursor | Native hook | Install tkr (npm or brew), run `tkr setup-cursor`, then restart Cursor. |
40
+ | Cline | Agent shims | Start VS Code from a terminal with `tkr wrap --host cline code .`, or add the workspace setting below. |
41
+ | Aider | Agent shims | Start it with `tkr wrap aider [args]`. |
42
+ | Gemini CLI | Native hook | Install tkr (npm or brew), run `tkr setup-gemini`, then restart Gemini CLI. |
43
+ | Windsurf | Agent shims | Start it from a terminal with `tkr wrap --host windsurf windsurf .`, or add the workspace setting below. |
44
+ | GitHub Copilot| Native hook | Install tkr (npm or brew), run `tkr setup-copilot`, then restart VS Code or Copilot CLI. |
45
+ | Factory Droid | Native hook | Run `tkr setup-droid`, then restart Droid. |
46
+ | Trae | Native hook | Run `tkr setup-trae`, then restart Trae. |
47
+ | Qwen Code | Native hook | Run `tkr setup-qwen`, then restart Qwen Code. |
48
+ | Crush | Native hook | Run `tkr setup-crush`, then restart Crush. |
49
+ | Junie CLI | Native hook | Run `tkr setup-junie`, then restart Junie. |
50
+ | ZCode | Native hook | Run `tkr setup-zcode`, then restart ZCode. |
51
+ | Grok Build | Native hook | Run `tkr setup-grok`, then restart Grok Build (1.0.4 or newer). |
52
+ | Kilo Code | Plugin | Run `tkr setup-kilo`, then restart Kilo Code. |
53
+ | Plain shell | None by default | Your terminals are left alone. To route every terminal through Tokmizer anyway: `tkr shim global-path on` (undo: `tkr shim global-path off`). |
54
+
55
+ Run `tkr status` after installation. If activation is pending because the service could not be reached, rerun `tkr status` when online. Connecting an account is optional: `tkr link <your-key>`.
56
+
57
+ ## Agents without hooks
58
+
59
+ `tkr wrap [--host aider|cline|windsurf] <command> [args]` starts the agent with the shim folder first on its `PATH` and `TOKMIZER_HOST` set, so only that agent and the commands it runs go through Tokmizer.
60
+
61
+ For Cline and Windsurf (both run commands in the editor terminal), the same can be set per workspace in `.vscode/settings.json`. Replace `HOME` with your home folder and use `terminal.integrated.env.linux` on Linux:
62
+
63
+ ```json
64
+ {
65
+ "terminal.integrated.env.osx": {
66
+ "PATH": "HOME/.tokmizer/shims:${env:PATH}",
67
+ "TOKMIZER_HOST": "cline"
68
+ }
69
+ }
70
+ ```
71
+
72
+ Shims never call a versioned install folder. They go through `~/.tokmizer/bin/tkr-shim-launch`, which finds the current install at run time. If no install is found, the command runs unchanged and prints a one-line hint to run `tkr doctor --fix`.
73
+
74
+ ## Repair and upgrades
75
+
76
+ `tkr doctor` lists outdated shims, old PATH lines in your shell profile and hooks that point at a removed install. `tkr doctor --fix` repairs them. Each edited shell profile is first copied to `~/.tokmizer/backup/rc/` (private to you), and only the exact two lines Tokmizer wrote are removed. `tkr shim global-path on` puts them back.
77
+
78
+ If an old version left your shell unable to run commands (`Error: Cannot find module .../tkr-shim.js`), run this in any terminal; it needs no Node.js and no network:
79
+
80
+ ```bash
81
+ mv ~/.tokmizer/shims ~/.tokmizer/shims.off
82
+ ```
83
+
84
+ Then open a new terminal, update Tokmizer, and run `npx -y -p @tokmizer/plugin@latest tkr doctor --fix` (or `tkr doctor --fix`) to remove the old PATH line.
49
85
 
50
86
  ## Choose a mode
51
87
 
@@ -4,15 +4,85 @@ set -euo pipefail
4
4
  err() { printf 'tkr install: %s\n' "$*" >&2; exit 1; }
5
5
  log() { printf 'tkr: %s\n' "$*"; }
6
6
 
7
- command -v node >/dev/null 2>&1 || err "Node.js 20.11+ is required."
8
- command -v npm >/dev/null 2>&1 || err "npm is required."
9
- node -e 'const [major, minor] = process.versions.node.split(".").map(Number); process.exit(major > 20 || major === 20 && minor >= 11 ? 0 : 1)' || err "Node.js 20.11+ is required."
7
+ node_ready() {
8
+ command -v node >/dev/null 2>&1 && command -v npm >/dev/null 2>&1 &&
9
+ node -e 'const [major, minor] = process.versions.node.split(".").map(Number); process.exit(major > 20 || major === 20 && minor >= 11 ? 0 : 1)' 2>/dev/null
10
+ }
10
11
 
11
- log "Installing @tokmizer/plugin from npm..."
12
- npm install -g @tokmizer/plugin
13
- command -v tkr >/dev/null 2>&1 || err "tkr is not on PATH. Add the npm global bin directory to PATH and run tkr shim install."
12
+ work=""
13
+
14
+ sha256_of() {
15
+ if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1
16
+ else shasum -a 256 "$1" | cut -d' ' -f1
17
+ fi
18
+ }
19
+
20
+ install_standalone() {
21
+ local os arch key base line name sum root bindir unavailable
22
+ unavailable="Install Node.js 20.11+ and retry."
23
+ case "$(uname -s)" in
24
+ Darwin) os=darwin ;;
25
+ Linux) os=linux ;;
26
+ *) err "No standalone build for $(uname -s). $unavailable" ;;
27
+ esac
28
+ case "$(uname -m)" in
29
+ arm64|aarch64) arch=arm64 ;;
30
+ x86_64|amd64) arch=x64 ;;
31
+ *) err "No standalone build for $(uname -m). $unavailable" ;;
32
+ esac
33
+ key="$os-$arch"
34
+ base="${TOKMIZER_DOWNLOAD_BASE:-https://github.com/norwelltw/Tokmizer/releases/latest/download}"
35
+ root="${TOKMIZER_INSTALL_DIR:-$HOME/.local/share/tokmizer}"
36
+ bindir="${TOKMIZER_BIN_DIR:-$HOME/.local/bin}"
37
+ command -v curl >/dev/null 2>&1 || err "curl is required."
38
+ command -v tar >/dev/null 2>&1 || err "tar is required."
39
+ work="$(mktemp -d)"
40
+ trap 'rm -rf "${work:-}"' EXIT
41
+ curl -fsSL "$base/SHA256SUMS" -o "$work/SHA256SUMS" || err "Could not download the standalone build. $unavailable"
42
+ line="$(grep -E "^[0-9a-f]{64} tokmizer-[0-9][^ /]*-$key\.tar\.gz$" "$work/SHA256SUMS" | head -n 1)" || true
43
+ [ -n "$line" ] || err "No standalone build for $key. $unavailable"
44
+ sum="${line%% *}"
45
+ name="${line#* }"
46
+ log "Downloading $name..."
47
+ curl -fsSL "$base/$name" -o "$work/$name" || err "Could not download $name."
48
+ [ "$(sha256_of "$work/$name")" = "$sum" ] || err "Checksum mismatch for $name."
49
+ tar -xzf "$work/$name" -C "$work"
50
+ "$work/${name%.tar.gz}/dist/bin/tkr" --single-check >/dev/null 2>&1 || err "The downloaded executable does not run on this system. $unavailable"
51
+ mkdir -p "$root" "$bindir"
52
+ rm -rf "${root:?}/${name%.tar.gz}"
53
+ mv "$work/${name%.tar.gz}" "$root/${name%.tar.gz}"
54
+ ln -sfn "${name%.tar.gz}" "$root/current"
55
+ ln -sf "$root/current/dist/bin/tkr" "$bindir/tkr"
56
+ ln -sf "$root/current/dist/bin/tkr-exec" "$bindir/tkr-exec"
57
+ case ":$PATH:" in
58
+ *":$bindir:"*) ;;
59
+ *) PATH="$bindir:$PATH"; log "Add $bindir to your PATH." ;;
60
+ esac
61
+ }
62
+
63
+ claude_code_present() {
64
+ [ -d "${CLAUDE_CONFIG_DIR:-$HOME/.claude}" ] || command -v claude >/dev/null 2>&1
65
+ }
66
+
67
+ claude_before=0
68
+ if claude_code_present; then claude_before=1; fi
69
+
70
+ standalone=0
71
+ if [ "${TOKMIZER_STANDALONE:-}" = "1" ] || ! node_ready; then
72
+ log "Installing the standalone tkr executable (Node.js not needed)..."
73
+ install_standalone
74
+ standalone=1
75
+ else
76
+ log "Installing @tokmizer/plugin from npm..."
77
+ npm install -g @tokmizer/plugin
78
+ fi
79
+ command -v tkr >/dev/null 2>&1 || err "tkr is not on PATH. Add its directory to PATH and run tkr shim install."
14
80
 
15
81
  tkr shim install </dev/null
16
- log "Done. Open a new terminal. Tokmizer is free and does not require an account."
82
+ if [ "$standalone" = "1" ] && [ "$claude_before" = "1" ]; then
83
+ tkr setup-claude </dev/null || log "Could not add the Claude Code hooks. Run: tkr setup-claude"
84
+ fi
85
+ log "Done. Tokmizer is free and does not require an account. Your shell profile was not changed."
17
86
  log "For Codex, also run: tkr setup-codex"
87
+ log "For Aider, Cline or Windsurf, start them with: tkr wrap <agent>"
18
88
  log "Check the installation with: tkr status"