@superbfowle/bash-history-mcp-test 0.1.0

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/CLAUDE.md ADDED
@@ -0,0 +1,106 @@
1
+
2
+ Default to using Bun instead of Node.js.
3
+
4
+ - Use `bun <file>` instead of `node <file>` or `ts-node <file>`
5
+ - Use `bun test` instead of `jest` or `vitest`
6
+ - Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
7
+ - Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
8
+ - Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
9
+ - Bun automatically loads .env, so don't use dotenv.
10
+
11
+ ## APIs
12
+
13
+ - `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
14
+ - `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
15
+ - `Bun.redis` for Redis. Don't use `ioredis`.
16
+ - `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
17
+ - `WebSocket` is built-in. Don't use `ws`.
18
+ - Prefer `Bun.file` over `node:fs`'s readFile/writeFile
19
+ - Bun.$`ls` instead of execa.
20
+
21
+ ## Testing
22
+
23
+ Use `bun test` to run tests.
24
+
25
+ ```ts#index.test.ts
26
+ import { test, expect } from "bun:test";
27
+
28
+ test("hello world", () => {
29
+ expect(1).toBe(1);
30
+ });
31
+ ```
32
+
33
+ ## Frontend
34
+
35
+ Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
36
+
37
+ Server:
38
+
39
+ ```ts#index.ts
40
+ import index from "./index.html"
41
+
42
+ Bun.serve({
43
+ routes: {
44
+ "/": index,
45
+ "/api/users/:id": {
46
+ GET: (req) => {
47
+ return new Response(JSON.stringify({ id: req.params.id }));
48
+ },
49
+ },
50
+ },
51
+ // optional websocket support
52
+ websocket: {
53
+ open: (ws) => {
54
+ ws.send("Hello, world!");
55
+ },
56
+ message: (ws, message) => {
57
+ ws.send(message);
58
+ },
59
+ close: (ws) => {
60
+ // handle close
61
+ }
62
+ },
63
+ development: {
64
+ hmr: true,
65
+ console: true,
66
+ }
67
+ })
68
+ ```
69
+
70
+ HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
71
+
72
+ ```html#index.html
73
+ <html>
74
+ <body>
75
+ <h1>Hello, world!</h1>
76
+ <script type="module" src="./frontend.tsx"></script>
77
+ </body>
78
+ </html>
79
+ ```
80
+
81
+ With the following `frontend.tsx`:
82
+
83
+ ```tsx#frontend.tsx
84
+ import React from "react";
85
+
86
+ // import .css files directly and it works
87
+ import './index.css';
88
+
89
+ import { createRoot } from "react-dom/client";
90
+
91
+ const root = createRoot(document.body);
92
+
93
+ export default function Frontend() {
94
+ return <h1>Hello, world!</h1>;
95
+ }
96
+
97
+ root.render(<Frontend />);
98
+ ```
99
+
100
+ Then, run index.ts
101
+
102
+ ```sh
103
+ bun --hot ./index.ts
104
+ ```
105
+
106
+ For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`.
package/README.md ADDED
@@ -0,0 +1,201 @@
1
+ # Atuin Integration for Claude Code
2
+
3
+ Bidirectional bash history integration between Claude Code and [atuin](https://github.com/atuinsh/atuin).
4
+
5
+ ## Problem
6
+ - Claude Code runs bash commands but they don't appear in your shell history
7
+ - Claude Code can't learn from your command patterns
8
+
9
+ ## Architecture
10
+
11
+ ### Write: Hook
12
+ **Claude Code hook** → writes commands to atuin after Claude executes each bash command
13
+
14
+ ```bash
15
+ # Post-bash-execution hook calls:
16
+ id=$(atuin history start "$COMMAND")
17
+ atuin history end --exit "$EXIT_CODE" --duration 0 "$id"
18
+ ```
19
+
20
+ ### Read: MCP Server
21
+ **MCP server** → Claude can query your atuin history
22
+
23
+ Tools:
24
+ - `search_history(query, limit?)` - Find commands matching a pattern
25
+ - `get_recent_history(limit?)` - Get recent commands with timestamps and exit codes
26
+
27
+ ## Benefits
28
+ - **Persistent history**: Rerun commands Claude executed from your terminal
29
+ - **Context awareness**: Claude can learn from your command patterns and workflow
30
+ - **Rich metadata**: Timestamps, working directory, exit codes, and duration
31
+ - **Cross-machine sync**: History syncs across machines (if atuin sync is enabled)
32
+
33
+ ## Setup
34
+
35
+ ### Prerequisites
36
+ - [atuin](https://github.com/atuinsh/atuin) installed and configured
37
+ - [Bun](https://bun.sh) installed
38
+
39
+ ### Write Hook Installation
40
+
41
+ **Note:** If you've previously installed this package and are updating to a new version, clear Bun's cache first:
42
+ ```bash
43
+ bun pm cache rm
44
+ ```
45
+
46
+ **Step 1: Locate your settings file**
47
+
48
+ Claude Code settings are in `~/.claude/settings.json`. Create it if it doesn't exist:
49
+
50
+ ```bash
51
+ mkdir -p ~/.claude
52
+ touch ~/.claude/settings.json
53
+ ```
54
+
55
+ **Step 2: Add the hook configuration**
56
+
57
+ Edit `~/.claude/settings.json` and add:
58
+
59
+ ```json
60
+ {
61
+ "hooks": {
62
+ "PostToolUse": [
63
+ {
64
+ "matcher": "Bash",
65
+ "hooks": [
66
+ {
67
+ "type": "command",
68
+ "command": "bunx github:nitsanavni/bash-history-mcp hook"
69
+ }
70
+ ]
71
+ }
72
+ ]
73
+ }
74
+ }
75
+ ```
76
+
77
+ **If you already have hooks configured**, merge with your existing configuration:
78
+
79
+ ```json
80
+ {
81
+ "hooks": {
82
+ "PostToolUse": [
83
+ {
84
+ "matcher": "Bash",
85
+ "hooks": [
86
+ {
87
+ "type": "command",
88
+ "command": "bunx github:nitsanavni/bash-history-mcp hook"
89
+ }
90
+ ]
91
+ },
92
+ // ... your other PostToolUse hooks
93
+ ]
94
+ // ... your other hook types (PreToolUse, etc.)
95
+ }
96
+ }
97
+ ```
98
+
99
+ **Step 3: Restart Claude Code**
100
+
101
+ The hook will be active in your next Claude Code session.
102
+
103
+ **Step 4: Verify it's working**
104
+
105
+ After Claude runs some bash commands:
106
+
107
+ ```bash
108
+ atuin history last
109
+ ```
110
+
111
+ You should see the commands Claude executed.
112
+
113
+ ### How It Works
114
+
115
+ After each bash command Claude executes:
116
+ 1. Hook receives JSON with command and exit code via stdin
117
+ 2. Calls `atuin history start "$COMMAND"` to get an entry ID
118
+ 3. Calls `atuin history end --exit $EXIT --duration 0 $ID`
119
+ 4. Fails silently if atuin is unavailable
120
+
121
+ ### Troubleshooting
122
+
123
+ **Hook not running?**
124
+ ```bash
125
+ # Check if hook is registered
126
+ claude --debug
127
+ # Run a command in Claude and look for hook execution logs
128
+ ```
129
+
130
+ **Commands not appearing in atuin?**
131
+ ```bash
132
+ # Test atuin manually
133
+ atuin history start "test command"
134
+ atuin history last
135
+ ```
136
+
137
+ ### MCP Server Installation
138
+
139
+ **Note:** If you've previously installed this package and are updating to a new version, clear Bun's cache first:
140
+ ```bash
141
+ bun pm cache rm
142
+ ```
143
+
144
+ **Configure Claude Code to use the MCP server:**
145
+
146
+ ```bash
147
+ claude mcp add -s user bash-history bunx -- github:nitsanavni/bash-history-mcp mcp
148
+ ```
149
+
150
+ Or manually add to `~/.claude/settings.json`:
151
+
152
+ ```json
153
+ {
154
+ "mcpServers": {
155
+ "bash-history": {
156
+ "command": "bunx",
157
+ "args": ["github:nitsanavni/bash-history-mcp", "mcp"]
158
+ }
159
+ }
160
+ }
161
+ ```
162
+
163
+ **Available Tools:**
164
+
165
+ - `search_history(query, limit?)` - Search for commands matching a pattern
166
+ ```
167
+ Example: search_history("git commit", 5)
168
+ ```
169
+
170
+ - `get_recent_history(limit?)` - Get recent commands
171
+ ```
172
+ Example: get_recent_history(10)
173
+ ```
174
+
175
+ ### OpenCode Plugin Installation
176
+
177
+ **Note:** If you've previously installed this package and are updating to a new version, clear Bun's cache first:
178
+
179
+ ```bash
180
+ bun pm cache rm
181
+ ```
182
+
183
+ **Add to your opencode config:**
184
+
185
+ Edit your `opencode.json` (either in `~/.config/opencode/` or project root) and add:
186
+
187
+ ```json
188
+ {
189
+ "$schema": "https://opencode.ai/config.json",
190
+ "plugin": ["bash-history-mcp"]
191
+ }
192
+ ```
193
+
194
+ The plugin will automatically log all bash commands executed by opencode to your atuin history.
195
+
196
+ ## Implementation Status
197
+
198
+ 1. ✅ Write hook with atuin integration
199
+ 2. ✅ Test hook integration
200
+ 3. ✅ MCP server with read-only atuin access
201
+ 4. ✅ OpenCode plugin
package/bun.lock ADDED
@@ -0,0 +1,208 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "workspaces": {
4
+ "": {
5
+ "name": "bash-history-mcp",
6
+ "dependencies": {
7
+ "@modelcontextprotocol/sdk": "^1.19.1",
8
+ },
9
+ "devDependencies": {
10
+ "@types/bun": "latest",
11
+ },
12
+ "peerDependencies": {
13
+ "typescript": "^5",
14
+ },
15
+ },
16
+ },
17
+ "packages": {
18
+ "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.19.1", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-3Y2h3MZKjec1eAqSTBclATlX+AbC6n1LgfVzRMJLt3v6w0RCYgwLrjbxPDbhsYHt6Wdqc/aCceNJYgj448ELQQ=="],
19
+
20
+ "@types/bun": ["@types/bun@1.2.23", "", { "dependencies": { "bun-types": "1.2.23" } }, "sha512-le8ueOY5b6VKYf19xT3McVbXqLqmxzPXHsQT/q9JHgikJ2X22wyTW3g3ohz2ZMnp7dod6aduIiq8A14Xyimm0A=="],
21
+
22
+ "@types/node": ["@types/node@24.6.2", "", { "dependencies": { "undici-types": "~7.13.0" } }, "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang=="],
23
+
24
+ "@types/react": ["@types/react@19.2.0", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA=="],
25
+
26
+ "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
27
+
28
+ "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
29
+
30
+ "body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],
31
+
32
+ "bun-types": ["bun-types@1.2.23", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-R9f0hKAZXgFU3mlrA0YpE/fiDvwV0FT9rORApt2aQVWSuJDzZOyB5QLc0N/4HF57CS8IXJ6+L5E4W1bW6NS2Aw=="],
33
+
34
+ "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
35
+
36
+ "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
37
+
38
+ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
39
+
40
+ "content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="],
41
+
42
+ "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
43
+
44
+ "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
45
+
46
+ "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
47
+
48
+ "cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="],
49
+
50
+ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
51
+
52
+ "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
53
+
54
+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
55
+
56
+ "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
57
+
58
+ "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
59
+
60
+ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
61
+
62
+ "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
63
+
64
+ "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
65
+
66
+ "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
67
+
68
+ "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
69
+
70
+ "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
71
+
72
+ "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
73
+
74
+ "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
75
+
76
+ "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="],
77
+
78
+ "express": ["express@5.1.0", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
79
+
80
+ "express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="],
81
+
82
+ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
83
+
84
+ "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
85
+
86
+ "finalhandler": ["finalhandler@2.1.0", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="],
87
+
88
+ "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
89
+
90
+ "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
91
+
92
+ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
93
+
94
+ "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
95
+
96
+ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
97
+
98
+ "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
99
+
100
+ "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
101
+
102
+ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
103
+
104
+ "http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
105
+
106
+ "iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="],
107
+
108
+ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
109
+
110
+ "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
111
+
112
+ "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
113
+
114
+ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
115
+
116
+ "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
117
+
118
+ "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
119
+
120
+ "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
121
+
122
+ "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
123
+
124
+ "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
125
+
126
+ "mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="],
127
+
128
+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
129
+
130
+ "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
131
+
132
+ "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
133
+
134
+ "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
135
+
136
+ "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
137
+
138
+ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
139
+
140
+ "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
141
+
142
+ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
143
+
144
+ "path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
145
+
146
+ "pkce-challenge": ["pkce-challenge@5.0.0", "", {}, "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ=="],
147
+
148
+ "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
149
+
150
+ "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
151
+
152
+ "qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
153
+
154
+ "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
155
+
156
+ "raw-body": ["raw-body@3.0.1", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.7.0", "unpipe": "1.0.0" } }, "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA=="],
157
+
158
+ "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
159
+
160
+ "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
161
+
162
+ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
163
+
164
+ "send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="],
165
+
166
+ "serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="],
167
+
168
+ "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
169
+
170
+ "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
171
+
172
+ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
173
+
174
+ "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
175
+
176
+ "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
177
+
178
+ "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
179
+
180
+ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
181
+
182
+ "statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
183
+
184
+ "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
185
+
186
+ "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
187
+
188
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
189
+
190
+ "undici-types": ["undici-types@7.13.0", "", {}, "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ=="],
191
+
192
+ "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
193
+
194
+ "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
195
+
196
+ "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
197
+
198
+ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
199
+
200
+ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
201
+
202
+ "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
203
+
204
+ "zod-to-json-schema": ["zod-to-json-schema@3.24.6", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg=="],
205
+
206
+ "body-parser/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
207
+ }
208
+ }
package/cli.ts ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env bun
2
+
3
+ const subcommand = process.argv[2];
4
+
5
+ if (subcommand === "hook") {
6
+ await import("./index.ts");
7
+ } else if (subcommand === "mcp") {
8
+ await import("./server.ts");
9
+ } else {
10
+ console.error(`Usage: bash-history-mcp <hook|mcp>`);
11
+ process.exit(1);
12
+ }
package/index.ts ADDED
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Claude Code PostToolUse hook for atuin integration
5
+ * Adds bash commands to atuin history
6
+ */
7
+
8
+ import { realpath } from "node:fs/promises";
9
+ import { fileURLToPath } from "node:url";
10
+ import { dirname } from "node:path";
11
+
12
+ export { OpencodeBashHistoryPlugin } from "./opencode.ts";
13
+
14
+ interface ToolInput {
15
+ command: string;
16
+ description?: string;
17
+ timeout?: number;
18
+ }
19
+
20
+ interface HookInput {
21
+ tool_name: string;
22
+ tool_input: ToolInput;
23
+ tool_response?: {
24
+ exit_code?: number;
25
+ };
26
+ }
27
+
28
+ async function main() {
29
+ try {
30
+ // Read JSON from stdin
31
+ const input = await Bun.stdin.text();
32
+ const data: HookInput = JSON.parse(input);
33
+
34
+ // Only process Bash tool calls
35
+ if (data.tool_name !== "Bash") {
36
+ process.exit(0);
37
+ }
38
+
39
+ const command = data.tool_input.command;
40
+ const exitCode = data.tool_response?.exit_code ?? 0;
41
+
42
+ // Step 1: Start the command to get an ID
43
+ const startProc = Bun.spawn(["atuin", "history", "start", command], {
44
+ stdout: "pipe",
45
+ stderr: "pipe",
46
+ });
47
+
48
+ const startExitCode = await startProc.exited;
49
+ if (startExitCode !== 0) {
50
+ const stderr = await Bun.readableStreamToText(startProc.stderr);
51
+ console.error(`Failed to start atuin history entry: ${stderr}`);
52
+ process.exit(0);
53
+ }
54
+
55
+ const id = (await Bun.readableStreamToText(startProc.stdout)).trim();
56
+
57
+ // Step 2: End the command with the exit code
58
+ const endProc = Bun.spawn(
59
+ ["atuin", "history", "end", "--exit", String(exitCode), "--duration", "0", id],
60
+ {
61
+ stderr: "pipe",
62
+ }
63
+ );
64
+
65
+ const endExitCode = await endProc.exited;
66
+ if (endExitCode !== 0) {
67
+ const stderr = await Bun.readableStreamToText(endProc.stderr);
68
+ console.error(`Failed to end atuin history entry: ${stderr}`);
69
+ }
70
+
71
+ process.exit(0);
72
+ } catch (error) {
73
+ console.error("Hook error:", error);
74
+ process.exit(1);
75
+ }
76
+ }
77
+
78
+ async function runIfMain(): Promise<void> {
79
+ const __filename = fileURLToPath(import.meta.url);
80
+ const __dirname = dirname(__filename);
81
+
82
+ try {
83
+ const resolvedMainPath = await realpath(process.argv[1]);
84
+
85
+ // Early guard return if not being run as main module
86
+ if (resolvedMainPath !== __filename) {
87
+ return;
88
+ }
89
+
90
+ // Only call main if we are the main module
91
+ await main();
92
+ } catch {}
93
+ }
94
+
95
+ runIfMain();
package/opencode.ts ADDED
@@ -0,0 +1,58 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+
3
+ export const OpencodeBashHistoryPlugin: Plugin = async ({ $ }) => {
4
+ return {
5
+ "tool.execute.after": async (input, output) => {
6
+ if (input.tool !== "bash") {
7
+ return;
8
+ }
9
+
10
+ try {
11
+ const command = output.metadata?.args?.command;
12
+ if (!command || typeof command !== "string") {
13
+ return;
14
+ }
15
+
16
+ const exitCode = output.metadata?.exitCode ?? 0;
17
+
18
+ const startProc = Bun.spawn(["atuin", "history", "start", command], {
19
+ stdout: "pipe",
20
+ stderr: "pipe",
21
+ });
22
+
23
+ const startExitCode = await startProc.exited;
24
+ if (startExitCode !== 0) {
25
+ const stderr = await Bun.readableStreamToText(startProc.stderr);
26
+ await $.console.error(`Failed to start atuin history entry: ${stderr}`);
27
+ return;
28
+ }
29
+
30
+ const id = (await Bun.readableStreamToText(startProc.stdout)).trim();
31
+
32
+ const endProc = Bun.spawn(
33
+ [
34
+ "atuin",
35
+ "history",
36
+ "end",
37
+ "--exit",
38
+ String(exitCode),
39
+ "--duration",
40
+ "0",
41
+ id,
42
+ ],
43
+ {
44
+ stderr: "pipe",
45
+ }
46
+ );
47
+
48
+ const endExitCode = await endProc.exited;
49
+ if (endExitCode !== 0) {
50
+ const stderr = await Bun.readableStreamToText(endProc.stderr);
51
+ await $.console.error(`Failed to end atuin history entry: ${stderr}`);
52
+ }
53
+ } catch (error) {
54
+ await $.console.error(`Bash history plugin error: ${error}`);
55
+ }
56
+ },
57
+ };
58
+ };
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@superbfowle/bash-history-mcp-test",
3
+ "version": "0.1.0",
4
+ "module": "index.ts",
5
+ "type": "module",
6
+ "bin": "./cli.ts",
7
+ "devDependencies": {
8
+ "@types/bun": "latest"
9
+ },
10
+ "peerDependencies": {
11
+ "typescript": "^5",
12
+ "@opencode-ai/plugin": "*"
13
+ },
14
+ "dependencies": {
15
+ "@modelcontextprotocol/sdk": "^1.19.1"
16
+ }
17
+ }
package/server.ts ADDED
@@ -0,0 +1,244 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Atuin MCP Server - Read access to atuin history
5
+ */
6
+
7
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
8
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
+ import {
10
+ CallToolRequestSchema,
11
+ ListToolsRequestSchema,
12
+ } from "@modelcontextprotocol/sdk/types.js";
13
+
14
+ const server = new Server(
15
+ {
16
+ name: "atuin-history",
17
+ version: "0.1.0",
18
+ },
19
+ {
20
+ capabilities: {
21
+ tools: {},
22
+ },
23
+ }
24
+ );
25
+
26
+ // List available tools
27
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
28
+ return {
29
+ tools: [
30
+ {
31
+ name: "search_history",
32
+ description: "Search command history using atuin. Returns matching commands with timestamps and context. You can also use atuin directly via bash - try 'atuin --help' to learn more.",
33
+ inputSchema: {
34
+ type: "object",
35
+ properties: {
36
+ query: {
37
+ type: "string",
38
+ description: "Search query to find matching commands",
39
+ },
40
+ limit: {
41
+ type: "number",
42
+ description: "Maximum number of results to return (default: 5)",
43
+ default: 5,
44
+ },
45
+ include_failed: {
46
+ type: "boolean",
47
+ description: "Include commands that failed (non-zero exit code). Default: false",
48
+ default: false,
49
+ },
50
+ },
51
+ required: ["query"],
52
+ },
53
+ },
54
+ {
55
+ name: "get_recent_history",
56
+ description: "Get recent command history from atuin with timestamps and exit codes. You can also use atuin directly via bash - try 'atuin --help' to learn more.",
57
+ inputSchema: {
58
+ type: "object",
59
+ properties: {
60
+ limit: {
61
+ type: "number",
62
+ description: "Number of recent commands to retrieve (default: 5)",
63
+ default: 5,
64
+ },
65
+ include_failed: {
66
+ type: "boolean",
67
+ description: "Include commands that failed (non-zero exit code). Default: false",
68
+ default: false,
69
+ },
70
+ },
71
+ },
72
+ },
73
+ ],
74
+ };
75
+ });
76
+
77
+ // Handle tool calls
78
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
79
+ const { name, arguments: args } = request.params;
80
+
81
+ if (name === "search_history") {
82
+ const query = args.query as string;
83
+ const limit = (args.limit as number) || 5;
84
+ const includeFailed = (args.include_failed as boolean) || false;
85
+
86
+ try {
87
+ const proc = Bun.spawn(
88
+ [
89
+ "atuin",
90
+ "search",
91
+ "--limit",
92
+ String(limit * 2), // Request more to account for filtering
93
+ "--search-mode",
94
+ "fuzzy",
95
+ "--filter-mode",
96
+ "global",
97
+ "--format",
98
+ "{exit}\t{command}",
99
+ query,
100
+ ],
101
+ {
102
+ stdout: "pipe",
103
+ stderr: "pipe",
104
+ }
105
+ );
106
+
107
+ const exitCode = await proc.exited;
108
+ const stdout = await Bun.readableStreamToText(proc.stdout);
109
+ const stderr = await Bun.readableStreamToText(proc.stderr);
110
+
111
+ if (exitCode !== 0) {
112
+ return {
113
+ content: [
114
+ {
115
+ type: "text",
116
+ text: `Error searching history: ${stderr}`,
117
+ },
118
+ ],
119
+ };
120
+ }
121
+
122
+ const lines = stdout.trim().split("\n").filter((line) => line.length > 0);
123
+ let commands = lines
124
+ .map((line) => {
125
+ const [exitCodeStr, ...commandParts] = line.split("\t");
126
+ return {
127
+ exitCode: parseInt(exitCodeStr, 10),
128
+ command: commandParts.join("\t"),
129
+ };
130
+ })
131
+ .filter((item) => includeFailed || item.exitCode === 0)
132
+ .map((item) => item.command)
133
+ .slice(0, limit);
134
+
135
+ const atuinCommand = `atuin search --limit ${limit * 2} --search-mode fuzzy --filter-mode global --format "{exit}\\t{command}" "${query}"`;
136
+
137
+ return {
138
+ content: [
139
+ {
140
+ type: "text",
141
+ text: `Found ${commands.length} matching commands:\n\n${commands.join("\n")}\n\n---\nAtuin command used:\n${atuinCommand}`,
142
+ },
143
+ ],
144
+ };
145
+ } catch (error) {
146
+ return {
147
+ content: [
148
+ {
149
+ type: "text",
150
+ text: `Error: ${error}`,
151
+ },
152
+ ],
153
+ };
154
+ }
155
+ }
156
+
157
+ if (name === "get_recent_history") {
158
+ const limit = (args.limit as number) || 5;
159
+ const includeFailed = (args.include_failed as boolean) || false;
160
+
161
+ try {
162
+ const proc = Bun.spawn(
163
+ [
164
+ "atuin",
165
+ "search",
166
+ "--limit",
167
+ String(limit * 2), // Request more to account for filtering
168
+ "--search-mode",
169
+ "fuzzy",
170
+ "--filter-mode",
171
+ "global",
172
+ "--format",
173
+ "{exit}\t{command}",
174
+ "",
175
+ ],
176
+ {
177
+ stdout: "pipe",
178
+ stderr: "pipe",
179
+ }
180
+ );
181
+
182
+ const exitCode = await proc.exited;
183
+ const stdout = await Bun.readableStreamToText(proc.stdout);
184
+ const stderr = await Bun.readableStreamToText(proc.stderr);
185
+
186
+ if (exitCode !== 0) {
187
+ return {
188
+ content: [
189
+ {
190
+ type: "text",
191
+ text: `Error getting recent history: ${stderr}`,
192
+ },
193
+ ],
194
+ };
195
+ }
196
+
197
+ const lines = stdout.trim().split("\n").filter((line) => line.length > 0);
198
+ let commands = lines
199
+ .map((line) => {
200
+ const [exitCodeStr, ...commandParts] = line.split("\t");
201
+ return {
202
+ exitCode: parseInt(exitCodeStr, 10),
203
+ command: commandParts.join("\t"),
204
+ };
205
+ })
206
+ .filter((item) => includeFailed || item.exitCode === 0)
207
+ .map((item) => item.command)
208
+ .slice(0, limit);
209
+
210
+ const atuinCommand = `atuin search --limit ${limit * 2} --search-mode fuzzy --filter-mode global --format "{exit}\\t{command}" ""`;
211
+
212
+ return {
213
+ content: [
214
+ {
215
+ type: "text",
216
+ text: `Recent ${commands.length} commands:\n\n${commands.join("\n")}\n\n---\nAtuin command used:\n${atuinCommand}`,
217
+ },
218
+ ],
219
+ };
220
+ } catch (error) {
221
+ return {
222
+ content: [
223
+ {
224
+ type: "text",
225
+ text: `Error: ${error}`,
226
+ },
227
+ ],
228
+ };
229
+ }
230
+ }
231
+
232
+ return {
233
+ content: [
234
+ {
235
+ type: "text",
236
+ text: `Unknown tool: ${name}`,
237
+ },
238
+ ],
239
+ };
240
+ });
241
+
242
+ // Start the server
243
+ const transport = new StdioServerTransport();
244
+ await server.connect(transport);
package/tsconfig.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Environment setup & latest features
4
+ "lib": ["ESNext"],
5
+ "target": "ESNext",
6
+ "module": "Preserve",
7
+ "moduleDetection": "force",
8
+ "jsx": "react-jsx",
9
+ "allowJs": true,
10
+
11
+ // Bundler mode
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "noEmit": true,
16
+
17
+ // Best practices
18
+ "strict": true,
19
+ "skipLibCheck": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "noUncheckedIndexedAccess": true,
22
+ "noImplicitOverride": true,
23
+
24
+ // Some stricter flags (disabled by default)
25
+ "noUnusedLocals": false,
26
+ "noUnusedParameters": false,
27
+ "noPropertyAccessFromIndexSignature": false
28
+ }
29
+ }