ohmyjev 0.0.0-stage → 0.3.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/README.md CHANGED
@@ -1,3 +1,385 @@
1
- # Temporary Holding Version
1
+ ```
2
+ _ _
3
+ ___ | |__ _ __ ___ _ _ (_) ___ __ __
4
+ / _ \ | '_ \ | '_ ` _ \ | | | | | | / _ \ \ \ / /
5
+ | (_) || | | || | | | | || |_| | | || __/ \ V /
6
+ \___/ |_| |_||_| |_| |_| \__, | _/ | \___| \_/
7
+ |___/ |__/
8
+ ```
2
9
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
10
+ <p align="center">
11
+ Guardrails for Claude Code, pi and omp, decided by <a href="https://typesafe.ai">Jev</a>.<br>
12
+ One mod that blocks destructive commands, catches prompt injection, pushes back on an unverified "done" and routes effort per turn.
13
+ </p>
14
+
15
+ <p align="center">
16
+ <img alt="Claude Code 2.1.287+" src="https://img.shields.io/badge/Claude_Code-2.1.287%2B-d97757">
17
+ <img alt="Decided by Jev" src="https://img.shields.io/badge/decided_by-Jev-101315">
18
+ <img alt="pi and omp" src="https://img.shields.io/badge/also_for-pi_·_omp-86a893">
19
+ <a href="https://www.npmjs.com/package/ohmyjev"><img alt="npm" src="https://img.shields.io/npm/v/ohmyjev?color=de6145"></a>
20
+ <img alt="MIT license" src="https://img.shields.io/badge/license-MIT-798186">
21
+ </p>
22
+
23
+ <p align="center">
24
+ <a href="https://ohmyjev.xyz"><b>ohmyjev.xyz</b></a> ·
25
+ <a href="#quick-start">Quick start</a> ·
26
+ <a href="#what-you-get">What you get</a> ·
27
+ <a href="#install">Install</a> ·
28
+ <a href="#pi-and-omp">pi and omp</a> ·
29
+ <a href="#using-it">Using it</a> ·
30
+ <a href="#settings">Settings</a> ·
31
+ <a href="#privacy">Privacy</a> ·
32
+ <a href="#troubleshooting">Troubleshooting</a>
33
+ </p>
34
+
35
+ ---
36
+
37
+ Jev is TypeSafe's decision model. You send it some state and a few typed questions, and it sends back probabilities
38
+ in about 300 ms for a fraction of a cent. That's fast and cheap enough to ask about every command your agent runs, so
39
+ ohmyjev does exactly that. It runs as in-process hooks inside Claude Code, which means no extra process and no server
40
+ to keep up.
41
+
42
+ ## Quick start
43
+
44
+ ```bash
45
+ export TYPESAFE_API_KEY=ts_... # put this in your shell profile
46
+ ```
47
+
48
+ Then start Claude Code and run:
49
+
50
+ ```
51
+ /plugin install ohmyjev --marketplace Novacon/ohmyjev
52
+ /omj
53
+ ```
54
+
55
+ If `/omj` ends with `key: env TYPESAFE_API_KEY · typesafe · jev-1.13.0`, you're set.
56
+
57
+ ## What you get
58
+
59
+ | Feature | What it does |
60
+ |---|---|
61
+ | **Bash gate** | Denies a command when Jev rates it irreversible at 0.6 or more, or destructive at 0.7 or more. |
62
+ | **Write gate** | Denies writes outside the repo and `allowPaths`, and writes that contain a real credential. The repo is every worktree of the one the session started in, plus every worktree of whichever repo the agent has moved into. The path check is plain code, not Jev, and it follows symlinks the same way the OS does. |
63
+ | **Exfil gate** | Denies a WebFetch or MCP call when Jev rates it 0.7 or more for sending your local data, files or credentials out. |
64
+ | **Policies** | Every gate also checks the call against your own rules in the `policies` setting. |
65
+ | **Injection screen** | When output from Bash, WebFetch, an MCP tool, or a Read outside the repo has instructions aimed at the model, it adds a note telling the model to treat that output as data. |
66
+ | **Done-check** | If the agent says it's done but nothing shows it ran a check, it blocks the stop once and tells the agent to verify. |
67
+ | **Router** | Asks Jev once per request for a tier, an effort level and a risk score. Then it raises or lowers effort and picks the model for general-purpose subagents. With no key it falls back to Claude Code's built-in classifier, which reports no confidence, so then it only ever routes up. |
68
+ | **Auto-compact** | When the task changes after a finished step and the context is at least 40% full, it compacts the conversation. The summary keeps the new request in full. |
69
+ | **`ask_jev`** | A tool the model can use to ask Jev about repo files or text without loading them into its own context. |
70
+ | **`/omj`** | Shows this session's Jev calls, errors, cost, median latency and denies, plus where the key comes from. It never prints the key itself. `/omj stats` opens a dashboard of every session in your browser. `/omj off` and `/omj on` switch ohmyjev for the session. `/ohmyjev` is the same command. |
71
+
72
+ ### How a tool call goes through it
73
+
74
+ ```mermaid
75
+ flowchart LR
76
+ A[Claude calls a tool] --> B{ohmyjev asks Jev}
77
+ B -- clearly risky --> C[Denied, with the reason]
78
+ B -- fine or unsure --> D[Claude Code's normal permission flow]
79
+ B -- Jev down, slow or no key --> D
80
+ D --> E[Tool runs]
81
+ E --> F{Output has instructions<br>aimed at the model?}
82
+ F -- yes --> G[Model gets a<br>'treat as data' note]
83
+ F -- no --> H[Output as is]
84
+ ```
85
+
86
+ Anything ohmyjev doesn't deny just goes on to Claude Code's normal permission flow. That covers middling answers, Jev
87
+ being down or slower than `timeoutMs` (1.5 s), a missing key, and any error inside ohmyjev. It never stops to ask you to approve
88
+ anything, so it works fine with agents running in bypass mode. Jev can be wrong though, so keep your deny rules in
89
+ `settings.json`.
90
+
91
+ ## Install
92
+
93
+ ### 1. Check what you need
94
+
95
+ - **Claude Code 2.1.287 or later.** Mods are still early access. Run `claude --version` to check.
96
+ - **A Jev key.** Either a TypeSafe key, or an OpenRouter key if you'd rather go through OpenRouter.
97
+
98
+ ### 2. Install the plugin
99
+
100
+ Run this at the prompt of a terminal Claude Code session:
101
+
102
+ ```
103
+ /plugin install ohmyjev --marketplace Novacon/ohmyjev
104
+ ```
105
+
106
+ Answer `y` to add the marketplace, then pick a scope. User scope is the first option and turns ohmyjev on for every
107
+ session. Once it says `Installed ohmyjev`, the hooks are already running in that session, so there's nothing to
108
+ restart.
109
+
110
+ You can also do it from your shell:
111
+
112
+ ```bash
113
+ claude plugin marketplace add Novacon/ohmyjev
114
+ claude plugin install ohmyjev@ohmyjev
115
+ ```
116
+
117
+ ### 3. Add your key
118
+
119
+ You've got three options, in the order ohmyjev looks for them:
120
+
121
+ 1. **The settings screen.** During the install, ohmyjev shows a screen with its options, including the TypeSafe
122
+ key. Claude Code keeps it in secure storage, not in a settings file.
123
+ 2. **`TYPESAFE_API_KEY`** in your environment, for example in `~/.zshrc`.
124
+ 3. **`OPENROUTER_API_KEY`** in your environment. ohmyjev then calls Jev through OpenRouter as `~typesafe/omj-latest`.
125
+
126
+ ### 4. Check it works
127
+
128
+ Start a session and run `/omj`. The last line tells you where the key came from:
129
+
130
+ ```
131
+ key: env TYPESAFE_API_KEY · typesafe · jev-1.13.0
132
+ ```
133
+
134
+ If it says `key: none`, ohmyjev can't see a key. In that case every gate stays open, the router falls back to Claude
135
+ Code's built-in classifier (up only), and the status under the prompt shows `jev ⚠ no key`.
136
+
137
+ ## pi and omp
138
+
139
+ The same checks run in [pi](https://pi.dev) and [omp](https://github.com/can1357/oh-my-pi) as a native extension.
140
+ They share the decision logic and the key lookup (`TYPESAFE_API_KEY`, then `OPENROUTER_API_KEY`) with the Claude Code
141
+ plugin, and write to the same `~/.ohmyjev` logs, so `/omj` and the statusline segment work the same way.
142
+
143
+ ### omp
144
+
145
+ ```bash
146
+ omp plugin install ohmyjev
147
+ ```
148
+
149
+ That installs the npm package; `omp plugin install github:Novacon/ohmyjev` installs straight from GitHub instead. Or use
150
+ the marketplace this repo already serves: `/marketplace add Novacon/ohmyjev`, then `/marketplace install ohmyjev@ohmyjev`.
151
+ Restart the session after installing. Settings use the names in [Settings](#settings):
152
+
153
+ ```bash
154
+ omp plugin config list ohmyjev
155
+ omp plugin config set ohmyjev routeMainModel true
156
+ ```
157
+
158
+ The router's tiers default to omp's own model roles: `@smol`, `@default` and `@slow`.
159
+
160
+ ### pi
161
+
162
+ ```bash
163
+ pi install npm:ohmyjev
164
+ ```
165
+
166
+ Or straight from GitHub: `pi install git:github.com/Novacon/ohmyjev`.
167
+
168
+ pi has no settings screen for extensions, so put yours under an `ohmyjev` key in `~/.pi/agent/settings.json`, or in
169
+ `.pi/settings.json` for one project (project values win):
170
+
171
+ ```json
172
+ { "ohmyjev": { "routeMainModel": true, "fastModel": "anthropic/claude-haiku-5-5" } }
173
+ ```
174
+
175
+ ### What differs
176
+
177
+ | | Claude Code | omp | pi |
178
+ |---|---|---|---|
179
+ | Bash gate | Bash | `bash`, `eval`, and `github` pushes and PRs | `bash`, `powershell` |
180
+ | Write gate | Write, Edit, NotebookEdit | `write`, `edit` (patches and renames included), `ast_edit` | `write`, `edit` |
181
+ | Exfil gate | WebFetch, MCP tools | `read` of a URL, MCP tools | MCP tools (pi has no fetch tool; `curl` goes through the bash gate) |
182
+ | Injection screen | Bash, WebFetch, MCP, Reads outside the repo | `bash`, `github`, URL reads, MCP, reads outside the repo | `bash`, `powershell`, MCP, reads outside the repo |
183
+ | Done-check | ✓ | ✓ | ✓ |
184
+ | Router effort | ✓ | ✓ (left alone while you're on `auto`) | ✓ |
185
+ | Router subagent model | general-purpose subagents | subagents on the default `task` role | none: pi has no subagents |
186
+ | Router without a key | Claude Code's built-in classifier, up only | off: omp has no separate classifier call for extensions | the cheapest model you've set up, up only |
187
+ | Decision lines | in the transcript | notifications | notifications |
188
+ | Status | under the prompt | in the footer | in the footer |
189
+
190
+ ## Using it
191
+
192
+ Most of the time you won't notice it. Here's what it looks like when it does step in.
193
+
194
+ ### When a command gets blocked
195
+
196
+ Claude gets the denial as the tool's result, along with an instruction not to work around it:
197
+
198
+ ```
199
+ ohmyjev blocked this: irreversible (0.95): nothing would restore what this removes or overwrites.
200
+ This block is final. Do not try to work around it with another command, another tool, a different path,
201
+ or an encoding that does the same thing. Stop and tell the user what was blocked and why.
202
+ ```
203
+
204
+ ### `/omj`
205
+
206
+ Run it any time to see what this session has been up to (`/ohmyjev` does the same). Here's an example:
207
+
208
+ ```
209
+ jev ✓23 ⛔1 ↑opus/high
210
+ calls 23 · errors 0 · cost $0.000966 · p50 310ms
211
+ denies: Bash 1
212
+ Bash: irreversible (0.95): nothing would restore what this removes or overwrites
213
+ key: env TYPESAFE_API_KEY · typesafe · jev-1.13.0
214
+ ```
215
+
216
+ ### Your own rules
217
+
218
+ Put plain-English rules in the `policies` setting, separated by `;`. Every gate then asks Jev whether the call breaks
219
+ one of them:
220
+
221
+ ```
222
+ never touch the prod cluster; no deploys on Friday; don't edit migrations that already ran
223
+ ```
224
+
225
+ ### `ask_jev`
226
+
227
+ The model can call this tool on its own when it wants a quick judgment without reading a pile of files. For example:
228
+
229
+ ```json
230
+ {
231
+ "question": "Which of these files handles session login?",
232
+ "type": "choice",
233
+ "options": ["src/auth.ts", "src/session.ts", "src/routes.ts"],
234
+ "files": ["src/auth.ts", "src/session.ts", "src/routes.ts"]
235
+ }
236
+ ```
237
+
238
+ Jev reads the files, not the model. ohmyjev only sends files that are inside the repo, up to 8000 characters each and
239
+ 80000 in total.
240
+
241
+ ### In the transcript
242
+
243
+ Each routing decision shows up as a dim line in the transcript. The model never sees these lines. First what Jev
244
+ said, then what the router did with it:
245
+
246
+ ```
247
+ [ohmyjev] ready: Jev via typesafe (jev-1.13.0, key from env TYPESAFE_API_KEY)
248
+ [ohmyjev] jev: tier fast (0.41) · effort 0.4 (0.38) · risky 0.01 · 210ms
249
+ [ohmyjev] main loop kept opus/medium, wanted opus/low (confidence 0.38)
250
+ ```
251
+
252
+ ### `/omj stats`
253
+
254
+ Writes every session's decisions into one page at `~/.ohmyjev/dashboard.html` and opens it: calls, denies, flags, cost
255
+ and latency, a 30-day chart, a table per tool and per session, and the latest denies with their reasons. It's a plain
256
+ file, read from the logs, so nothing on it leaves the machine (the page fetches its fonts from ohmyjev.xyz, and falls
257
+ back to system fonts offline).
258
+
259
+ ### `/omj off`
260
+
261
+ Turns every gate, screen, the done-check and the router off for this session, and the status shows `jev off`.
262
+ `/omj on` brings them back. To start sessions off by default, turn off the `enabled` setting.
263
+
264
+ The last line is the router declining to act: it wanted to spend less, but 0.38 is under the 0.6 it takes to move
265
+ down. In a `claude -p` or SDK run the same lines arrive as `ui_log` messages and in the debug log. Turn them off with
266
+ `logDecisions`.
267
+
268
+ ### Status line
269
+
270
+ ohmyjev pins its status under the prompt, so there's nothing to set up:
271
+
272
+ ```
273
+ jev ✓23 ⛔1 ↑opus/high 🗜2 Jev calls, denies, this turn's route, auto-compactions
274
+ jev ⚠ down Jev failed or timed out in the last 5 minutes, so the gates let calls through
275
+ jev ⚠ no key no key configured
276
+ ```
277
+
278
+ If you'd rather have it in your own statusline script, copy `jev_segment()` from
279
+ [`extras/statusline_segment.py`](extras/statusline_segment.py) into it and add the segment wherever you like. It
280
+ reads one small file per session, so it's quick. Turn the built-in one off with `statusLine`.
281
+
282
+ ```python
283
+ jev = jev_segment(data.get("session_id")) # data = the statusline JSON from stdin
284
+ if jev:
285
+ parts.append(jev)
286
+ ```
287
+
288
+ ## Settings
289
+
290
+ Run `/omj settings` to see every setting's current value (the key stays hidden). To change them, run
291
+ `/plugin configure ohmyjev@ohmyjev` inside Claude Code, then `/reload-plugins`.
292
+
293
+ | Setting | Default | What it changes |
294
+ |---|---|---|
295
+ | `enabled` | on | The whole mod. Off lets every call through; `/omj on` and `/omj off` change it for one session. |
296
+ | `bashGate`, `writeGate`, `exfilGate` | on | Turns each gate on or off. |
297
+ | `injectionScreen`, `screenReads` | on | Screens tool output, including Reads from outside the repo. |
298
+ | `doneCheck` | on | Pushes back on an unverified "done". |
299
+ | `routeEffort`, `routeSubagents` | on | Lets the router change effort and the subagent model. |
300
+ | `routeMainModel` | off | Also switches the main model. It's off because switching models throws away the prompt cache. |
301
+ | `routeWithoutKey` | on | With no key, routes with Claude Code's built-in classifier instead. It has no confidence, so it only routes up. |
302
+ | `autoCompact` | on | Compacts when the task changes. `compactMinPercent` (40) sets how full the context has to be first. |
303
+ | `askJev` | on | Gives the model the `ask_jev` tool. |
304
+ | `policies` | empty | Your own rules, separated by `;`. |
305
+ | `allowPaths` | `~/.claude;$TMPDIR;/tmp` | Places outside the repo where writes are allowed. ohmyjev ignores any entry with `..` in it. You don't need it for a sibling worktree or another git repo the agent `cd`s into: those count as the repo. |
306
+ | `fastModel`, `balancedModel`, `deepModel` | `claude-haiku-5-5`, `claude-sonnet-5-5`, `claude-opus-5-5` | The model id for each router tier. |
307
+ | `jevModel` | `jev-1.13.0` | The Jev model asked through TypeSafe. |
308
+ | `timeoutMs` | 1500 | How long a gate, the screen or the router waits for Jev before letting the call through. |
309
+ | `logDecisions` | on | Shows each routing decision as a line in the transcript. |
310
+ | `statusLine` | on | Pins ohmyjev's status under the prompt. |
311
+
312
+ Each gate also has its own threshold setting. The defaults are the numbers in [What you get](#what-you-get).
313
+
314
+ ## Logs
315
+
316
+ Every decision gets one line in `~/.ohmyjev/log/<session>.jsonl`, and only you can read it. The log holds the
317
+ verdict, Jev's probabilities, the latency and the cost, never your commands' output. ohmyjev doesn't wait on that
318
+ write, so if one fails you lose that line and nothing else.
319
+
320
+ ## Privacy
321
+
322
+ With a key set, ohmyjev sends Jev (TypeSafe, or OpenRouter with an OpenRouter key) only what each decision needs, cut to
323
+ a fixed size:
324
+
325
+ | What asks | What it sends |
326
+ |---|---|
327
+ | Bash gate | The command (up to 16000 characters), the working directory and the tool call's description |
328
+ | Write gate | The file path and the new content (up to 16000 characters). Writes outside the repo and `allowPaths` are denied by code, before anything is sent |
329
+ | Exfil gate | The tool's name and its input (up to 4000 characters) |
330
+ | Injection screen | The tool's output (up to 6000 characters), from Bash, WebFetch, MCP tools and Reads outside the repo |
331
+ | Done-check | The current request (600), up to five earlier requests (200 each), up to 20 of this turn's tool calls with their input (200 each) and outcome, and Claude's last message (1500) |
332
+ | Router | The request (up to 1500 characters) |
333
+ | `ask_jev` | The model's question, any text it passes (up to 20000 characters) and the repo files it names (8000 each, 80000 in total). Files outside the repo are never sent |
334
+
335
+ When you've set `policies`, every gate sends those too. Turning a feature off stops its calls.
336
+
337
+ With no key, nothing goes to Jev. The router's built-in classifier sends the request (up to 1500 characters) to
338
+ Claude's own small model, over the same connection Claude Code already uses. In pi it goes to the cheapest model you
339
+ have set up, through pi's own provider. Logs and session files stay in `~/.ohmyjev`, readable only by you.
340
+
341
+ ## Update or remove
342
+
343
+ ```bash
344
+ claude plugin update ohmyjev@ohmyjev # then restart Claude Code
345
+ claude plugin uninstall ohmyjev@ohmyjev
346
+
347
+ omp plugin upgrade ohmyjev # omp; restart the session
348
+ omp plugin uninstall ohmyjev
349
+
350
+ pi update npm:ohmyjev # pi
351
+ pi remove npm:ohmyjev
352
+ ```
353
+
354
+ ## Troubleshooting
355
+
356
+ **The status says `jev ⚠ no key`.** ohmyjev can't find a key. Set `TYPESAFE_API_KEY` in the shell that starts
357
+ Claude Code, or enter the key with `/plugin configure ohmyjev@ohmyjev`.
358
+
359
+ **The status says `jev ⚠ down`.** A Jev call failed or took longer than `timeoutMs` (1.5 s) in the last 5 minutes. The
360
+ gates let calls through until Jev answers again. `/omj` shows the error count.
361
+
362
+ **Something got blocked that shouldn't have.** `/omj` shows the reason and Jev's numbers. Raise that gate's threshold
363
+ with `/plugin configure ohmyjev@ohmyjev`, or turn the gate off. Local MCP tools sometimes trip the exfil gate, and `exfilGate` is the switch for
364
+ that.
365
+
366
+ **Nothing seems to happen.** The first transcript line of a session should be `[ohmyjev] ready: …` or `[ohmyjev] no
367
+ Jev key: …`. If neither shows, run `claude --debug`: if a hook fails, the debug log has a line naming it and the
368
+ reason.
369
+
370
+ ## Develop
371
+
372
+ ```bash
373
+ git clone https://github.com/Novacon/ohmyjev && cd ohmyjev && bun install
374
+ claude --plugin-dir "$PWD" # run your working copy, and it writes the API types to .claude-plugin/types
375
+ omp -e ./adapters/omp.ts # or: pi -e ./adapters/pi.ts
376
+ bun run test && bun run typecheck && claude plugin validate .
377
+ ```
378
+
379
+ The decision logic lives in `hooks/policy.ts` and is pure, so you can test it with plain tables. `hooks/ohmyjev.ts`
380
+ wires it to Claude Code; `adapters/omp.ts` and `adapters/pi.ts` wire it to omp and pi through `adapters/shared.ts`.
381
+ The design and plans are in [`docs/superpowers`](docs/superpowers).
382
+
383
+ ## Credits
384
+
385
+ The question rubrics come from [disler/ten-levels-of-jev](https://github.com/disler/ten-levels-of-jev). MIT license.