impulso 0.20.22 → 0.21.1
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 +8 -30
- package/harnesses/claude/README.md +2 -19
- package/harnesses/claude/hooks/preflight.cjs +3 -4
- package/harnesses/claude/hooks/pretooluse-bash.cjs +4 -26
- package/harnesses/claude/install.sh +0 -1
- package/harnesses/opencode/bin/darwin-amd64/impulso-hub +0 -0
- package/harnesses/opencode/bin/darwin-arm64/impulso-hub +0 -0
- package/harnesses/opencode/bin/linux-amd64/impulso-hub +0 -0
- package/harnesses/opencode/bin/linux-arm64/impulso-hub +0 -0
- package/harnesses/opencode/install.sh +8 -10
- package/harnesses/opencode/orchestration/index.js +1 -1
- package/harnesses/opencode/plugin.js +4 -9
- package/harnesses/opencode/uninstall.sh +2 -2
- package/opencode/opencode.jsonc +1 -1
- package/package.json +2 -4
- package/scripts/install-hub.js +9 -1
- package/shared/orchestration-config.cjs +1 -1
- package/harnesses/claude/hooks/rtk.cjs +0 -87
- package/harnesses/opencode/rtk/README.md +0 -100
- package/harnesses/opencode/rtk/rtk.ts +0 -41
package/README.md
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# Impulso
|
|
2
2
|
|
|
3
3
|
Plugin bundle for opencode + Claude Code: **Plannotator** (vendored skills) +
|
|
4
|
-
**Impulso-DirectSpeech** (terse communication mode)
|
|
5
|
-
savings via `rtk rewrite` for 60-90% LLM token savings on shell commands).
|
|
4
|
+
**Impulso-DirectSpeech** (terse communication mode).
|
|
6
5
|
|
|
7
6
|
Harness-neutral repo. Per-harness glue under `harnesses/`.
|
|
8
7
|
|
|
@@ -12,7 +11,6 @@ Harness-neutral repo. Per-harness glue under `harnesses/`.
|
|
|
12
11
|
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
12
|
| Plannotator | 3 vendored skills (annotate/last/review) + 3 commands. Skills invoke the `plannotator` CLI binary directly (user-installed); no npm plugin. |
|
|
14
13
|
| Impulso-DirectSpeech | Terse communication mode — **always active, enforced by the harness**. Ruleset in `shared/AGENTS.md` + per-turn reinforcement hook on each harness. No toggle. 3 auxiliary skills (compress/stats/help). |
|
|
15
|
-
| Impulso-RTK | Rewrites bash tool calls via `rtk rewrite` for 60-90% token savings. `rtk` + `tokensave` hard-required and enforced at runtime — launchers, hooks, and plugin block with install hints when missing. |
|
|
16
14
|
|
|
17
15
|
## Install routes
|
|
18
16
|
|
|
@@ -36,7 +34,7 @@ Inside any claude session:
|
|
|
36
34
|
/plugin install impulso@impulso
|
|
37
35
|
```
|
|
38
36
|
|
|
39
|
-
Loads the
|
|
37
|
+
Loads the hooks (preflight, DirectSpeech reinforcement — via
|
|
40
38
|
`${CLAUDE_PLUGIN_ROOT}`), all skills/commands, and the tokensave MCP server.
|
|
41
39
|
Update later with `/plugin marketplace update impulso`.
|
|
42
40
|
|
|
@@ -45,8 +43,8 @@ full ruleset in `shared/AGENTS.md`. DirectSpeech still enforces per turn via the
|
|
|
45
43
|
hook; for the complete ruleset add an import line to your `~/.claude/CLAUDE.md`
|
|
46
44
|
pointing at a checked-out copy, or use the installer route instead.
|
|
47
45
|
|
|
48
|
-
Requires `
|
|
49
|
-
|
|
46
|
+
Requires `tokensave` on PATH — the preflight hook blocks prompts otherwise
|
|
47
|
+
(that's the point).
|
|
50
48
|
|
|
51
49
|
## Install — opencode plugin (recommended)
|
|
52
50
|
|
|
@@ -162,7 +160,7 @@ To install into `~/.claude/` directly (shared with your normal Claude Code
|
|
|
162
160
|
config): `IMPULSO_CLAUDE_PROFILE="$HOME/.claude" ./harnesses/claude/install.sh`
|
|
163
161
|
|
|
164
162
|
See [`harnesses/claude/README.md`](harnesses/claude/README.md) for Plannotator
|
|
165
|
-
upstream plugin setup
|
|
163
|
+
upstream plugin setup.
|
|
166
164
|
|
|
167
165
|
## Install — isolated profiles
|
|
168
166
|
|
|
@@ -176,35 +174,16 @@ XDG_CONFIG_HOME=~/.config/impulso-profile opencode
|
|
|
176
174
|
CLAUDE_CONFIG_DIR=~/.config/impulso-profile/claude claude
|
|
177
175
|
```
|
|
178
176
|
|
|
179
|
-
## Token savings via RTK (hard-required)
|
|
180
|
-
|
|
181
|
-
`rtk` and `tokensave` are enforced at every layer — no degraded mode:
|
|
182
|
-
|
|
183
|
-
- `install.sh` scripts abort when either is missing.
|
|
184
|
-
- `run_here.sh` / `run_here_claude.sh` launchers fail fast.
|
|
185
|
-
- opencode: the Impulso plugin throws at init.
|
|
186
|
-
- Claude Code: a UserPromptSubmit preflight hook blocks prompts, and the rtk
|
|
187
|
-
PreToolUse hook blocks Bash calls — both with install hints.
|
|
188
|
-
|
|
189
|
-
```bash
|
|
190
|
-
brew install rtk
|
|
191
|
-
# or: cargo install --git https://github.com/rtk-ai/rtk
|
|
192
|
-
# or: curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh
|
|
193
|
-
```
|
|
194
|
-
|
|
195
177
|
The tokensave MCP server additionally needs a per-project index — run
|
|
196
178
|
`tokensave init` once in each repo you work in (`.tokensave/` is gitignored
|
|
197
179
|
globally by the installer).
|
|
198
180
|
|
|
199
|
-
See [`harnesses/opencode/rtk/README.md`](harnesses/opencode/rtk/README.md).
|
|
200
|
-
|
|
201
181
|
## Usage — everything in one place
|
|
202
182
|
|
|
203
183
|
### Daily use
|
|
204
184
|
|
|
205
|
-
DirectSpeech is always on — nothing to activate, no off-switch.
|
|
206
|
-
|
|
207
|
-
available once `tokensave init` has been run in the project.
|
|
185
|
+
DirectSpeech is always on — nothing to activate, no off-switch. tokensave MCP
|
|
186
|
+
tools are available once `tokensave init` has been run in the project.
|
|
208
187
|
|
|
209
188
|
| Slash command | What it does |
|
|
210
189
|
| ---------------------------------------- | --------------------------------------------------- |
|
|
@@ -427,11 +406,10 @@ impulso/
|
|
|
427
406
|
│ ├── install.sh # isolated profile installer
|
|
428
407
|
│ ├── uninstall.sh # removes the isolated profile
|
|
429
408
|
│ ├── directspeech/ # DirectSpeech plugin (always-on reinforcement)
|
|
430
|
-
│ └── rtk/ # vendored upstream RTK plugin (rtk.ts + README.md)
|
|
431
409
|
└── claude/ # Claude Code glue
|
|
432
410
|
├── install.sh # profile installer (merge-safe for shared profiles)
|
|
433
411
|
├── uninstall.sh # targeted reversal — never touches foreign content
|
|
434
412
|
├── merge-settings.cjs # settings.json merge (foreign hooks/keys survive)
|
|
435
413
|
├── tests.cjs
|
|
436
|
-
└── hooks/ # preflight.cjs,
|
|
414
|
+
└── hooks/ # preflight.cjs, directspeech-context.cjs
|
|
437
415
|
```
|
|
@@ -8,8 +8,8 @@ Glue for running Impulso on Claude Code.
|
|
|
8
8
|
./harnesses/claude/install.sh
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Idempotent. Hard-requires `
|
|
12
|
-
|
|
11
|
+
Idempotent. Hard-requires `tokensave` on PATH (aborts otherwise). Creates an
|
|
12
|
+
isolated profile at `~/.config/impulso-profile/claude/`:
|
|
13
13
|
|
|
14
14
|
- Symlinks `skills/` and `commands/` → repo dirs. If the target already has a
|
|
15
15
|
real dir (shared profiles), bundled entries are linked individually inside it
|
|
@@ -58,22 +58,6 @@ Requires the `plannotator` binary:
|
|
|
58
58
|
curl -fsSL https://plannotator.ai/install.sh | bash
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
## RTK (token savings)
|
|
62
|
-
|
|
63
|
-
`install.sh` hard-requires the `rtk` binary:
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
brew install rtk
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
PreToolUse hook rewrites Bash commands through `rtk rewrite` (only when rtk
|
|
70
|
-
exits 0). Enforcement is runtime, not just install time:
|
|
71
|
-
|
|
72
|
-
- `preflight.cjs` (UserPromptSubmit) blocks every prompt while `rtk` or
|
|
73
|
-
`tokensave` is missing from PATH, with install hints.
|
|
74
|
-
- `rtk.cjs` (PreToolUse) blocks Bash calls when `rtk` is missing.
|
|
75
|
-
- A failed rewrite with rtk present passes the command through unchanged.
|
|
76
|
-
|
|
77
61
|
## DirectSpeech
|
|
78
62
|
|
|
79
63
|
Always active — enforced by the harness, no toggle, no flag files.
|
|
@@ -104,6 +88,5 @@ impulso/harnesses/claude/
|
|
|
104
88
|
├── tests.cjs
|
|
105
89
|
└── hooks/
|
|
106
90
|
├── preflight.cjs
|
|
107
|
-
├── rtk.cjs
|
|
108
91
|
└── directspeech-context.cjs
|
|
109
92
|
```
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// impulso preflight hook — Claude Code
|
|
2
2
|
//
|
|
3
|
-
// UserPromptSubmit handler. Enforces the hard-required binaries (
|
|
4
|
-
//
|
|
5
|
-
//
|
|
3
|
+
// UserPromptSubmit handler. Enforces the hard-required binaries (tokensave):
|
|
4
|
+
// if it is missing from PATH, exit 2 blocks the prompt and surfaces install
|
|
5
|
+
// instructions. Impulso is opinionated — no degraded mode.
|
|
6
6
|
//
|
|
7
7
|
// Hook: UserPromptSubmit
|
|
8
8
|
//
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
const { spawnSync } = require('node:child_process');
|
|
13
13
|
|
|
14
14
|
const REQUIRED = [
|
|
15
|
-
{ bin: 'rtk', hint: 'brew install rtk-ai/tap/rtk' },
|
|
16
15
|
{ bin: 'tokensave', hint: 'brew install aovestdipaperino/tap/tokensave' },
|
|
17
16
|
{ bin: 'plannotator', hint: 'npm i -g plannotator' },
|
|
18
17
|
];
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
// impulso pretooluse-bash dispatcher — Claude Code
|
|
2
2
|
//
|
|
3
|
-
// PreToolUse(Bash) dispatcher. Reads stdin once, runs
|
|
4
|
-
// bash-state-guard. rtk's exit-2 (missing binary) short-circuits before the
|
|
5
|
-
// guard runs.
|
|
3
|
+
// PreToolUse(Bash) dispatcher. Reads stdin once, runs bash-state-guard.
|
|
6
4
|
//
|
|
7
5
|
// Hook: PreToolUse (matcher: Bash)
|
|
8
6
|
|
|
9
7
|
'use strict';
|
|
10
8
|
|
|
11
9
|
const path = require('node:path');
|
|
12
|
-
const rtk = require(path.join(__dirname, 'rtk.cjs'));
|
|
13
10
|
const bashStateGuard = require(path.join(__dirname, 'bash-state-guard.cjs'));
|
|
14
11
|
|
|
15
12
|
const chunks = [];
|
|
@@ -23,7 +20,7 @@ process.stdin.on('readable', () => {
|
|
|
23
20
|
function finish(raw) {
|
|
24
21
|
if (done) return;
|
|
25
22
|
done = true;
|
|
26
|
-
clearTimeout(timer);
|
|
23
|
+
clearTimeout(timer);
|
|
27
24
|
|
|
28
25
|
let input;
|
|
29
26
|
try {
|
|
@@ -33,33 +30,14 @@ function finish(raw) {
|
|
|
33
30
|
return;
|
|
34
31
|
}
|
|
35
32
|
|
|
36
|
-
// Stage
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
// Stage 2: bash-state-guard on the effective (possibly rewritten) input
|
|
40
|
-
const effectiveInput = rtkResult
|
|
41
|
-
? { ...input, tool_input: { ...input.tool_input, command: rtkResult.updatedInput.command } }
|
|
42
|
-
: input;
|
|
43
|
-
|
|
44
|
-
const guard = bashStateGuard.check(effectiveInput);
|
|
33
|
+
// Stage: bash-state-guard
|
|
34
|
+
const guard = bashStateGuard.check(input);
|
|
45
35
|
if (guard.block) {
|
|
46
36
|
if (guard.stderr) process.stderr.write(guard.stderr);
|
|
47
37
|
process.exit(2);
|
|
48
38
|
return;
|
|
49
39
|
}
|
|
50
40
|
|
|
51
|
-
// Emit rewrite result if rtk changed the command
|
|
52
|
-
if (rtkResult) {
|
|
53
|
-
process.stdout.write(
|
|
54
|
-
JSON.stringify({
|
|
55
|
-
hookSpecificOutput: {
|
|
56
|
-
hookEventName: 'PreToolUse',
|
|
57
|
-
updatedInput: rtkResult.updatedInput,
|
|
58
|
-
},
|
|
59
|
-
}) + '\n',
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
41
|
process.exit(0);
|
|
64
42
|
}
|
|
65
43
|
|
|
@@ -38,7 +38,6 @@ require_bin() {
|
|
|
38
38
|
fi
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
require_bin rtk "Install: brew install rtk-ai/tap/rtk (see https://www.rtk-ai.app)"
|
|
42
41
|
require_bin tokensave "Install: brew install aovestdipaperino/tap/tokensave (or: cargo install tokensave)"
|
|
43
42
|
require_bin plannotator "Install: curl -fsSL https://plannotator.ai/install.sh | bash"
|
|
44
43
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -13,9 +13,8 @@ echo " Repo: ${REPO}"
|
|
|
13
13
|
echo " Profile: ${PROFILE_DIR}"
|
|
14
14
|
|
|
15
15
|
# --- Preflight: hard-required binaries -------------------------------------
|
|
16
|
-
# Impulso is opinionated: tokensave (MCP server)
|
|
17
|
-
#
|
|
18
|
-
# the filesystem.
|
|
16
|
+
# Impulso is opinionated: tokensave (MCP server) must be on PATH.
|
|
17
|
+
# Missing it aborts install before touching the filesystem.
|
|
19
18
|
|
|
20
19
|
require_bin() {
|
|
21
20
|
local bin="$1"
|
|
@@ -27,22 +26,21 @@ require_bin() {
|
|
|
27
26
|
fi
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
require_bin rtk "Install: brew install rtk-ai/tap/rtk (see https://www.rtk-ai.app)"
|
|
31
29
|
require_bin tokensave "Install: brew install aovestdipaperino/tap/tokensave (or: cargo install tokensave)"
|
|
32
30
|
|
|
33
31
|
# --- Global gitignore for tool artifacts ------------------------------------
|
|
34
|
-
# .tokensave/ (per-project SQLite code graph)
|
|
35
|
-
#
|
|
36
|
-
#
|
|
32
|
+
# .tokensave/ (per-project SQLite code graph) must never land in a target
|
|
33
|
+
# repo. Write to a global git excludesfile so any repo the user touches with
|
|
34
|
+
# Impulso is protected. Prompted; default yes.
|
|
37
35
|
|
|
38
36
|
ensure_global_gitignore() {
|
|
39
37
|
local ignore_file="${HOME}/.config/git/ignore"
|
|
40
|
-
local entries=(".tokensave/"
|
|
38
|
+
local entries=(".tokensave/")
|
|
41
39
|
local answer="y"
|
|
42
40
|
|
|
43
41
|
echo
|
|
44
|
-
echo "Impulso can add .tokensave/
|
|
45
|
-
echo "so
|
|
42
|
+
echo "Impulso can add .tokensave/ to a global git excludesfile"
|
|
43
|
+
echo "so it is never committed to any target repo."
|
|
46
44
|
printf "Write to %s? [Y/n] " "${ignore_file}"
|
|
47
45
|
read -r answer || answer=""
|
|
48
46
|
|
|
@@ -163,7 +163,7 @@ export const ImpulsoOrchestrationPlugin = async (ctx) => {
|
|
|
163
163
|
|
|
164
164
|
// Disable opencode built-in subagents that would otherwise compete
|
|
165
165
|
// with impulso-owned roster agents at dispatch time. Built-ins
|
|
166
|
-
// have no impulso rules, no
|
|
166
|
+
// have no impulso rules, no path-guard. scout is
|
|
167
167
|
// NOT disabled — impulso's own scout registration overrides by name.
|
|
168
168
|
for (const name of DISABLED_BUILTINS) {
|
|
169
169
|
config.agent[name] = { disable: true };
|
|
@@ -22,17 +22,12 @@ import { installHub } from '../../scripts/install-hub.js';
|
|
|
22
22
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
23
23
|
const pkgRoot = path.resolve(here, '..', '..');
|
|
24
24
|
|
|
25
|
-
// Impulso is opinionated: rtk and tokensave are hard-required at runtime,
|
|
26
|
-
// not just at install time. Throw at plugin init so the failure is loud
|
|
27
|
-
// ([impulso] in logs — integration tests grep for it) instead of a silently
|
|
28
|
-
// degraded session.
|
|
29
25
|
const REQUIRED_BINARIES = [
|
|
30
|
-
{ bin: 'rtk', hint: 'brew install rtk-ai/tap/rtk' },
|
|
31
26
|
{ bin: 'tokensave', hint: 'brew install aovestdipaperino/tap/tokensave' },
|
|
32
27
|
{ bin: 'plannotator', hint: 'curl -fsSL https://plannotator.ai/install.sh | bash' },
|
|
33
28
|
];
|
|
34
29
|
|
|
35
|
-
function
|
|
30
|
+
function warnRequiredBinaries() {
|
|
36
31
|
const missing = REQUIRED_BINARIES.filter(({ bin }) => {
|
|
37
32
|
const r = spawnSync('which', [bin], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
38
33
|
return r.error || r.status !== 0;
|
|
@@ -42,13 +37,13 @@ function enforceRequiredBinaries() {
|
|
|
42
37
|
'[impulso] hard-required binaries missing from PATH: ' +
|
|
43
38
|
missing.map(({ bin, hint }) => `${bin} (install: ${hint})`).join(', ');
|
|
44
39
|
console.error(msg);
|
|
45
|
-
|
|
40
|
+
console.warn('[impulso] continuing without required binaries — some features may not work');
|
|
46
41
|
}
|
|
47
42
|
}
|
|
48
43
|
|
|
49
44
|
export const ImpulsoPlugin = async (ctx) => {
|
|
50
|
-
await installHub({ packageRoot: pkgRoot });
|
|
51
|
-
|
|
45
|
+
await installHub({ packageRoot: pkgRoot, verifyVersion: false });
|
|
46
|
+
warnRequiredBinaries();
|
|
52
47
|
|
|
53
48
|
const sub = [];
|
|
54
49
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
4
|
# Impulso opencode uninstall — removes the isolated profile created by
|
|
5
|
-
# install.sh. Global gitignore entries (.tokensave
|
|
5
|
+
# install.sh. Global gitignore entries (.tokensave/) are left in
|
|
6
6
|
# place — they are protective and harmless; remove manually if unwanted.
|
|
7
7
|
|
|
8
8
|
PROFILE_DIR="${HOME}/.config/impulso-profile/opencode"
|
|
@@ -33,4 +33,4 @@ if [[ -f "${OLD_GATE}" || -L "${OLD_GATE}" ]]; then
|
|
|
33
33
|
echo " Removed old impulso-gate binary"
|
|
34
34
|
fi
|
|
35
35
|
|
|
36
|
-
echo "==> Done. Global gitignore entries (.tokensave
|
|
36
|
+
echo "==> Done. Global gitignore entries (.tokensave/) left in place."
|
package/opencode/opencode.jsonc
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"small_model": "opencode/big-pickle",
|
|
4
4
|
"model": "proxycli/deepseek-v4-flash",
|
|
5
5
|
"instructions": ["AGENTS.md"],
|
|
6
|
-
"plugin": ["../harnesses/opencode/plugin.js"
|
|
6
|
+
"plugin": ["../harnesses/opencode/plugin.js"],
|
|
7
7
|
"mcp": {
|
|
8
8
|
"tokensave": {
|
|
9
9
|
"type": "local",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "impulso",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Impulso — plugin bundle for opencode and Claude Code: Plannotator skills + Impulso-DirectSpeech (rethemed ex-Caveman)
|
|
3
|
+
"version": "0.21.1",
|
|
4
|
+
"description": "Impulso — plugin bundle for opencode and Claude Code: Plannotator skills + Impulso-DirectSpeech (rethemed ex-Caveman). Harness-neutral repo; per-harness glue under harnesses/.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "harnesses/opencode/plugin.js",
|
|
7
7
|
"publishConfig": {
|
|
@@ -50,7 +50,6 @@
|
|
|
50
50
|
"harnesses/opencode/install.sh",
|
|
51
51
|
"harnesses/opencode/orchestration",
|
|
52
52
|
"harnesses/opencode/plugin.js",
|
|
53
|
-
"harnesses/opencode/rtk",
|
|
54
53
|
"harnesses/opencode/session-name",
|
|
55
54
|
"harnesses/opencode/sync-commands.js",
|
|
56
55
|
"harnesses/opencode/uninstall.sh",
|
|
@@ -88,7 +87,6 @@
|
|
|
88
87
|
"plannotator",
|
|
89
88
|
"impulso",
|
|
90
89
|
"directspeech",
|
|
91
|
-
"rtk",
|
|
92
90
|
"plugin"
|
|
93
91
|
],
|
|
94
92
|
"license": "MIT",
|
package/scripts/install-hub.js
CHANGED
|
@@ -40,6 +40,7 @@ export async function installHub({
|
|
|
40
40
|
version,
|
|
41
41
|
platform = process.platform,
|
|
42
42
|
arch = process.arch,
|
|
43
|
+
verifyVersion = true,
|
|
43
44
|
} = {}) {
|
|
44
45
|
const packageJson = JSON.parse(await readFile(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
45
46
|
const packageVersion = version || packageJson.version;
|
|
@@ -47,7 +48,14 @@ export async function installHub({
|
|
|
47
48
|
const source = bundledBinaryPath(packageRoot, target);
|
|
48
49
|
await stat(source);
|
|
49
50
|
|
|
50
|
-
if (
|
|
51
|
+
if (!verifyVersion) {
|
|
52
|
+
try {
|
|
53
|
+
await stat(destination);
|
|
54
|
+
return { target, updated: false, destination, version: packageVersion };
|
|
55
|
+
} catch (_e) {
|
|
56
|
+
// destination absent — fall through to copy
|
|
57
|
+
}
|
|
58
|
+
} else if ((await installedVersion(destination)) === packageVersion) {
|
|
51
59
|
return { target, updated: false, destination, version: packageVersion };
|
|
52
60
|
}
|
|
53
61
|
|
|
@@ -85,7 +85,7 @@ const ROSTER = {
|
|
|
85
85
|
// -----------------------------------------------------------------------
|
|
86
86
|
// DISABLED_BUILTINS
|
|
87
87
|
// opencode built-in subagents impulso disables at config-write time to prevent
|
|
88
|
-
// the LLM orchestrator from dispatching a built-in (no impulso rules,
|
|
88
|
+
// the LLM orchestrator from dispatching a built-in (no impulso rules,
|
|
89
89
|
// no path-guard) instead of an impulso-owned roster agent. `scout` is NOT
|
|
90
90
|
// listed: impulso's own `scout` registration overrides the built-in by name.
|
|
91
91
|
// -----------------------------------------------------------------------
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
// impulso rtk hook — Claude Code
|
|
2
|
-
//
|
|
3
|
-
// PreToolUse Bash handler. Rewrites bash commands through `rtk rewrite`
|
|
4
|
-
// for token savings. Mirrors the opencode rtk.ts plugin behavior.
|
|
5
|
-
//
|
|
6
|
-
// Hook: PreToolUse (matcher: Bash)
|
|
7
|
-
//
|
|
8
|
-
// Exported for use by pretooluse-bash.cjs dispatcher:
|
|
9
|
-
// rewriteResult(input) -> { updatedInput: {command} } | null
|
|
10
|
-
// Returns null when tool is not Bash, command is empty, or rtk exits
|
|
11
|
-
// non-zero. May process.exit(2) if rtk is missing from PATH.
|
|
12
|
-
|
|
13
|
-
const { spawnSync } = require('node:child_process');
|
|
14
|
-
|
|
15
|
-
// Returns the rewritten command, or null to pass through unchanged.
|
|
16
|
-
//
|
|
17
|
-
// rtk is hard-required: missing from PATH (spawn ENOENT) blocks the Bash
|
|
18
|
-
// call — exit 2 makes Claude Code reject the tool use and feeds stderr back
|
|
19
|
-
// to the model. Do not fall back to raw commands; fix the install.
|
|
20
|
-
//
|
|
21
|
-
// A non-zero exit with rtk PRESENT means this particular rewrite failed —
|
|
22
|
-
// pass through unchanged (rtk's partial stdout must not be trusted).
|
|
23
|
-
function rewrite(command) {
|
|
24
|
-
const r = spawnSync('rtk', ['rewrite', command], {
|
|
25
|
-
encoding: 'utf8',
|
|
26
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
27
|
-
timeout: 5000,
|
|
28
|
-
});
|
|
29
|
-
if (r.error && r.error.code === 'ENOENT') {
|
|
30
|
-
process.stderr.write(
|
|
31
|
-
'impulso: rtk is hard-required but missing from PATH. ' +
|
|
32
|
-
'Install: brew install rtk-ai/tap/rtk\n',
|
|
33
|
-
);
|
|
34
|
-
process.exit(2);
|
|
35
|
-
}
|
|
36
|
-
if (r.error || r.status !== 0) return null;
|
|
37
|
-
return r.stdout.trim();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Pure function: evaluate rtk rewrite for a hook input payload.
|
|
41
|
-
// Returns { updatedInput: { command } } when rtk rewrites the command,
|
|
42
|
-
// null otherwise. May process.exit(2) if rtk is missing from PATH.
|
|
43
|
-
function rewriteResult(input) {
|
|
44
|
-
const toolName = (input.tool_name || '').toLowerCase();
|
|
45
|
-
if (toolName !== 'bash' && toolName !== 'shell') return null;
|
|
46
|
-
const command = input.tool_input && input.tool_input.command;
|
|
47
|
-
if (typeof command !== 'string' || !command) return null;
|
|
48
|
-
const rewritten = rewrite(command);
|
|
49
|
-
if (rewritten && rewritten !== command) {
|
|
50
|
-
return { updatedInput: { command: rewritten } };
|
|
51
|
-
}
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
module.exports = { rewriteResult };
|
|
56
|
-
|
|
57
|
-
function main() {
|
|
58
|
-
const chunks = [];
|
|
59
|
-
process.stdin.setEncoding('utf8');
|
|
60
|
-
process.stdin.on('readable', () => {
|
|
61
|
-
let chunk;
|
|
62
|
-
while ((chunk = process.stdin.read()) !== null) {
|
|
63
|
-
chunks.push(chunk);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
process.stdin.on('end', () => {
|
|
68
|
-
try {
|
|
69
|
-
const input = JSON.parse(chunks.join(''));
|
|
70
|
-
const result = rewriteResult(input);
|
|
71
|
-
if (result) {
|
|
72
|
-
process.stdout.write(
|
|
73
|
-
JSON.stringify({
|
|
74
|
-
hookSpecificOutput: {
|
|
75
|
-
hookEventName: 'PreToolUse',
|
|
76
|
-
updatedInput: result.updatedInput,
|
|
77
|
-
},
|
|
78
|
-
}) + '\n',
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
} catch (e) {
|
|
82
|
-
// Parse / filesystem errors — pass through unchanged
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (require.main === module) main();
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
# Impulso-RTK (opencode harness)
|
|
2
|
-
|
|
3
|
-
Vendored OpenCode plugin for [RTK](https://github.com/rtk-ai/rtk) — the Rust CLI
|
|
4
|
-
proxy that reduces LLM token consumption 60-90% on common shell commands (`git`,
|
|
5
|
-
`cargo`, `pytest`, `tsc`, `docker`, `kubectl`, `aws`, …).
|
|
6
|
-
|
|
7
|
-
## Attribution
|
|
8
|
-
|
|
9
|
-
Vendored verbatim from
|
|
10
|
-
[`hooks/opencode/rtk.ts`](https://github.com/rtk-ai/rtk/blob/develop/hooks/opencode/rtk.ts)
|
|
11
|
-
in the upstream RTK repo (Apache-2.0). All rewrite logic lives in the `rtk`
|
|
12
|
-
binary; this file is a thin OpenCode plugin glue that shells out to
|
|
13
|
-
`rtk rewrite <command>`.
|
|
14
|
-
|
|
15
|
-
To check for upstream changes:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
diff harnesses/opencode/rtk/rtk.ts \
|
|
19
|
-
<(curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/develop/hooks/opencode/rtk.ts)
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Empty diff = up to date.
|
|
23
|
-
|
|
24
|
-
## Architecture
|
|
25
|
-
|
|
26
|
-
Single hook on the opencode plugin system:
|
|
27
|
-
|
|
28
|
-
```
|
|
29
|
-
user runs bash tool (e.g. "git status")
|
|
30
|
-
───► tool.execute.before() fires
|
|
31
|
-
───► plugin calls `rtk rewrite "git status"` as subprocess
|
|
32
|
-
───► rtk returns "rtk git status"
|
|
33
|
-
───► plugin mutates args.command in-place
|
|
34
|
-
───► opencode executes "rtk git status"
|
|
35
|
-
───► filtered output reaches LLM (~200 tokens instead of ~2,000)
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
If the `rtk` binary is not on `$PATH` at plugin load time, the plugin logs a
|
|
39
|
-
warning and returns no hooks — opencode proceeds with raw commands. No crash, no
|
|
40
|
-
broken sessions.
|
|
41
|
-
|
|
42
|
-
## Prerequisite — install the rtk binary
|
|
43
|
-
|
|
44
|
-
The plugin requires `rtk >= 0.23.0` on `$PATH`. Impulso does not install it for
|
|
45
|
-
you. Pick one:
|
|
46
|
-
|
|
47
|
-
### Homebrew (recommended on macOS / Linuxbrew)
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
brew install rtk
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### Quick install (Linux / macOS)
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
Installs to `~/.local/bin`. Add to PATH if needed:
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc # or ~/.zshrc
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### Cargo
|
|
66
|
-
|
|
67
|
-
```bash
|
|
68
|
-
cargo install --git https://github.com/rtk-ai/rtk
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Verify
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
rtk --version # should print "rtk 0.23.0" or higher
|
|
75
|
-
rtk gain # shows token savings stats
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
> Name collision warning: another crates.io package named `rtk` (Rust Type Kit)
|
|
79
|
-
> exists. If `rtk gain` fails, you have the wrong package. Use
|
|
80
|
-
> `cargo install --git` above instead.
|
|
81
|
-
|
|
82
|
-
## File overview
|
|
83
|
-
|
|
84
|
-
```
|
|
85
|
-
harnesses/opencode/rtk/
|
|
86
|
-
├── rtk.ts # opencode plugin (vendored verbatim from upstream)
|
|
87
|
-
└── README.md # this file
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## Disabling
|
|
91
|
-
|
|
92
|
-
Three ways to opt out without uninstalling:
|
|
93
|
-
|
|
94
|
-
- Per-command: prefix with `RTK_DISABLED=1` (e.g. `RTK_DISABLED=1 git status`
|
|
95
|
-
runs raw `git status`).
|
|
96
|
-
- Permanent exclusions: list commands in `~/.config/rtk/config.toml` under
|
|
97
|
-
`[hooks] exclude_commands = ["curl", "playwright"]`.
|
|
98
|
-
- Remove the plugin: delete the `../harnesses/opencode/rtk/rtk.ts` entry from
|
|
99
|
-
the `plugin` array in `opencode/opencode.jsonc` (and from
|
|
100
|
-
`harnesses/opencode/install.sh` heredoc if using installed profile).
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { Plugin } from "@opencode-ai/plugin"
|
|
2
|
-
|
|
3
|
-
// RTK OpenCode plugin — rewrites commands to use rtk for token savings.
|
|
4
|
-
// Requires: rtk >= 0.23.0 in PATH.
|
|
5
|
-
//
|
|
6
|
-
// This is a thin delegating plugin: all rewrite logic lives in `rtk rewrite`,
|
|
7
|
-
// which is the single source of truth (src/discover/registry.rs).
|
|
8
|
-
// To add or change rewrite rules, edit the Rust registry — not this file.
|
|
9
|
-
|
|
10
|
-
export const RtkOpenCodePlugin: Plugin = async ({ $ }) => {
|
|
11
|
-
try {
|
|
12
|
-
await $`which rtk`.quiet()
|
|
13
|
-
} catch {
|
|
14
|
-
console.warn("[rtk] rtk binary not found in PATH — plugin disabled")
|
|
15
|
-
return {}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return {
|
|
19
|
-
"tool.execute.before": async (input, output) => {
|
|
20
|
-
const tool = String(input?.tool ?? "").toLowerCase()
|
|
21
|
-
if (tool !== "bash" && tool !== "shell") return
|
|
22
|
-
const args = output?.args
|
|
23
|
-
if (!args || typeof args !== "object") return
|
|
24
|
-
|
|
25
|
-
const command = (args as Record<string, unknown>).command
|
|
26
|
-
if (typeof command !== "string" || !command) return
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
const result = await $`rtk rewrite ${command}`.quiet().nothrow()
|
|
30
|
-
// Non-zero exit: rtk failed — never trust partial stdout as a command.
|
|
31
|
-
if (result.exitCode !== 0) return
|
|
32
|
-
const rewritten = String(result.stdout).trim()
|
|
33
|
-
if (rewritten && rewritten !== command) {
|
|
34
|
-
;(args as Record<string, unknown>).command = rewritten
|
|
35
|
-
}
|
|
36
|
-
} catch {
|
|
37
|
-
// rtk rewrite failed — pass through unchanged
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
}
|
|
41
|
-
}
|