@pinta-ai/pinta-codex 1.6.0 → 1.8.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/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +56 -0
- package/README.md +39 -12
- package/dist/index.js +24 -21
- package/dist/index.mjs +22 -19
- package/hooks.json +72 -2
- package/package.json +1 -1
- package/tools/_lib.ts +60 -12
- package/tools/doctor.ts +27 -9
- package/tools/setup.ts +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,62 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to pinta-codex are documented here.
|
|
4
4
|
|
|
5
|
+
## [1.8.0] - 2026-09-15
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **PermissionRequest gate.** Codex fires `PermissionRequest` wherever it would
|
|
10
|
+
otherwise prompt the user — including network access, which `PreToolUse` never
|
|
11
|
+
sees. It was unhandled, so that whole class of approval was unguarded.
|
|
12
|
+
Its output envelope is **not** `PreToolUse`'s: the decision is nested as
|
|
13
|
+
`hookSpecificOutput.decision = {behavior, message}` rather than the flat
|
|
14
|
+
`permissionDecision` / `permissionDecisionReason` pair. Sending the flat shape
|
|
15
|
+
parses as a valid envelope carrying no decision, so a deny is dropped with no
|
|
16
|
+
diagnostic. Both shapes are now typed at the point of use.
|
|
17
|
+
- **All 12 hook events handled**, up from 5. Codex 0.154.0 dispatches
|
|
18
|
+
`PreToolUse`, `PermissionRequest`, `PostToolUse`, `PreCompact`, `PostCompact`,
|
|
19
|
+
`SessionStart`, `SessionEnd`, `UserPromptSubmit`, `SubagentStart`,
|
|
20
|
+
`SubagentStop`, `Stop` and `Interrupt`. The seven that were previously
|
|
21
|
+
discarded are now forwarded by a single generic observe handler, and
|
|
22
|
+
`hooks.json` registers all twelve — an event handled in code but absent from
|
|
23
|
+
that template is never dispatched.
|
|
24
|
+
- `tests/core/self-version.test.ts`, which fails when the embedded version
|
|
25
|
+
constants drift from `package.json` (see Fixed).
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- `PreToolUse` and `PermissionRequest` now build their guard request through one
|
|
30
|
+
shared `evaluateToolGate`, so the two gates cannot drift into different
|
|
31
|
+
verdicts for the same action.
|
|
32
|
+
- Fail-open is now stated and tested as deliberate rather than left implicit. A
|
|
33
|
+
guard endpoint that is unreachable or slow allows: an outage in Pinta must not
|
|
34
|
+
stop an engineer from working.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- **Version misattribution.** `package.json` was 1.7.0 while `GUARD_UA`,
|
|
39
|
+
`PLUGIN_VERSION` and `.codex-plugin/plugin.json` were all still 1.6.0, so
|
|
40
|
+
every guard call and every OTLP span was attributed to a version that never
|
|
41
|
+
shipped. Confirmed live: the sidecar logged
|
|
42
|
+
`adaptor=pinta-codex ver=1.6.0` from an installation in `.../pinta-codex/1.7.0/`.
|
|
43
|
+
`scripts/bump.mjs` sets all four in lock-step and exists precisely to prevent
|
|
44
|
+
this; it had been bypassed.
|
|
45
|
+
- **`doctor` failed on a healthy install.** Codex renamed the feature flag from
|
|
46
|
+
`codex_hooks` to `hooks`, and the check only recognised the old spelling — so
|
|
47
|
+
a machine configured with `[features] hooks = true`, with hooks firing
|
|
48
|
+
normally, was told its install was broken. Both spellings are now accepted.
|
|
49
|
+
- **`setup` wrote the deprecated flag name.** It now writes canonical
|
|
50
|
+
`hooks = true`, and leaves an existing `codex_hooks = true` alone rather than
|
|
51
|
+
adding a second key for one setting.
|
|
52
|
+
- Measured on codex-cli 0.154.0 with an isolated `CODEX_HOME`: `hooks` is stage
|
|
53
|
+
`stable` and effective `true` with no config at all. Enabling it is now a
|
|
54
|
+
no-op guard for older builds, and an absent key is no longer reported as a
|
|
55
|
+
fault — only an explicit `false` is, since that is a silent kill switch.
|
|
56
|
+
- README corrected throughout: it claimed five events, `PreToolUse`/`PostToolUse`
|
|
57
|
+
were "Bash tool only" (untrue since codex 0.134.0, which extended `PreToolUse`
|
|
58
|
+
to `apply_patch`, MCP calls and every local function tool), and the feature
|
|
59
|
+
flag was given as `features.codex_hooks`.
|
|
60
|
+
|
|
5
61
|
## [1.5.1] - 2026-07-20
|
|
6
62
|
|
|
7
63
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# pinta-codex
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Guard and OTLP forwarder for Codex hook events. Blocks dangerous tool calls
|
|
4
|
+
before they run, and forwards every hook event as a span.
|
|
4
5
|
|
|
5
6
|
> **End-user install guide:** [`docs/installation-guide.md`](./docs/installation-guide.md)
|
|
6
7
|
> **Differences from pinta-cc:** [`docs/codex-vs-claude-code.md`](./docs/codex-vs-claude-code.md)
|
|
@@ -30,15 +31,39 @@ npm run doctor # verify everything green
|
|
|
30
31
|
|
|
31
32
|
## What it captures
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
Codex 0.154.0 dispatches **twelve** hook events. All twelve are handled: two are
|
|
35
|
+
pre-execution gates that can block, and ten are observed and forwarded.
|
|
36
|
+
|
|
37
|
+
Hooks require the `hooks` feature in `~/.codex/config.toml`:
|
|
38
|
+
|
|
39
|
+
```toml
|
|
40
|
+
[features]
|
|
41
|
+
hooks = true
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
> `codex_hooks` is the pre-0.13x spelling. Codex still accepts it as a legacy
|
|
45
|
+
> alias, but `hooks` is the canonical name and the one to write.
|
|
46
|
+
|
|
47
|
+
### Gates — these can deny
|
|
34
48
|
|
|
35
49
|
| Event | Notes |
|
|
36
50
|
|-------|-------|
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
51
|
+
| `PreToolUse` | Every local tool, not just Bash — `apply_patch`, MCP calls and all function tools since 0.134.0 |
|
|
52
|
+
| `PermissionRequest` | Fires when Codex would prompt the user, including for network access |
|
|
53
|
+
|
|
54
|
+
Both answer through stdout, and their output envelopes are **not the same
|
|
55
|
+
shape** — see `src/core/types.ts`. A deny must carry a non-empty reason; Codex
|
|
56
|
+
rejects one without it. `PreToolUse` accepts only `deny` (an allow is empty
|
|
57
|
+
stdout).
|
|
58
|
+
|
|
59
|
+
### Observed — forwarded, never block
|
|
60
|
+
|
|
61
|
+
`PostToolUse`, `PreCompact`, `PostCompact`, `SessionStart`, `SessionEnd`,
|
|
62
|
+
`UserPromptSubmit`, `SubagentStart`, `SubagentStop`, `Stop`, `Interrupt`.
|
|
63
|
+
|
|
64
|
+
`SessionStart` also drains the retry queue; `UserPromptSubmit` starts a new ULID
|
|
65
|
+
trace per user turn; `Stop` performs the final flush. `SessionEnd` is
|
|
66
|
+
send-only — Codex reads no reply from it.
|
|
42
67
|
|
|
43
68
|
Each invocation spawns `node dist/index.js`, maps the event to a single OTLP span, and POSTs it to `{endpoint}/traces`.
|
|
44
69
|
|
|
@@ -46,7 +71,8 @@ Each invocation spawns `node dist/index.js`, maps the event to a single OTLP spa
|
|
|
46
71
|
|
|
47
72
|
- OTLP/HTTP JSON transport. Headers are read from `OTEL_EXPORTER_OTLP_HEADERS` (`key=val,key=val` format)
|
|
48
73
|
- Top-level event fields are flattened into `codex.*` span attributes (Bronze; sibling adaptors use `cc.*`, `mcp.*`)
|
|
49
|
-
- **
|
|
74
|
+
- **Telemetry never blocks.** Every hook exits 0, and transmission failures are absorbed by the retry queue. A guard decision travels in stdout, so a dropped span cannot deny and a failed POST cannot stall a turn
|
|
75
|
+
- **Fail-open is deliberate.** If the guard endpoint is unreachable or slow, the gates allow. An outage in Pinta must not stop an engineer from working; the guard is a control, not a dependency
|
|
50
76
|
- Disk-backed retry queue at `.plugin-data/failed-spans.jsonl` (cap 1000). Drained on the next hook invocation
|
|
51
77
|
- One trace per user turn — based on the `UserPromptSubmit` ULID
|
|
52
78
|
|
|
@@ -104,7 +130,7 @@ Add the following manually to `~/.codex/config.toml`:
|
|
|
104
130
|
|
|
105
131
|
```toml
|
|
106
132
|
[features]
|
|
107
|
-
|
|
133
|
+
hooks = true
|
|
108
134
|
```
|
|
109
135
|
|
|
110
136
|
> **Why an install script?** Codex does not yet auto-load hooks from `.codex-plugin/plugin.json`. `install-hooks` substitutes `${CODEX_PLUGIN_ROOT}` in the bundled `hooks.json` template with an absolute path and merges it into the user-level file. Once Codex adds plugin-hook auto-discovery, this step will go away.
|
|
@@ -121,7 +147,7 @@ For a complete removal:
|
|
|
121
147
|
|
|
122
148
|
```bash
|
|
123
149
|
rm ~/.codex/pinta-codex.env
|
|
124
|
-
# Manually remove the [features]
|
|
150
|
+
# Manually remove the [features] hooks = true line from ~/.codex/config.toml
|
|
125
151
|
```
|
|
126
152
|
|
|
127
153
|
## Local development
|
|
@@ -192,10 +218,11 @@ pinta-codex/
|
|
|
192
218
|
│ └── codex-vs-claude-code.md # UX differences vs pinta-cc
|
|
193
219
|
├── src/
|
|
194
220
|
│ ├── core/ # OSS-reusable (config, transport, otlp, redact, retry-queue, trace, types, identity stub)
|
|
195
|
-
│ ├── handlers/ # per-event handlers
|
|
221
|
+
│ ├── handlers/ # per-event handlers (gates + shared tool-gate + generic observe)
|
|
196
222
|
│ └── index.ts # stdin → type guard → handler dispatch
|
|
197
223
|
├── tests/
|
|
198
|
-
│
|
|
224
|
+
│ ├── core/ # vitest tests (otlp, hook inventory, self-version)
|
|
225
|
+
│ └── handlers/ # gate output-envelope tests
|
|
199
226
|
├── tools/
|
|
200
227
|
│ ├── setup.ts # one-shot interactive installer
|
|
201
228
|
│ ├── doctor.ts # read-only health check
|
package/dist/index.js
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`;
|
|
3
|
-
`)}}readAll(){try{let t=
|
|
4
|
-
`))if(n.trim())try{let o=JSON.parse(n);Array.isArray(o?.payload?.resourceSpans)&&r.push(o)}catch{}return r}catch{return[]}}rewrite(t){try{if(t.length===0){
|
|
1
|
+
"use strict";var Et=Object.create;var le=Object.defineProperty;var vt=Object.getOwnPropertyDescriptor;var Ot=Object.getOwnPropertyNames;var St=Object.getPrototypeOf,Tt=Object.prototype.hasOwnProperty;var y=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var bt=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Ot(t))!Tt.call(e,o)&&o!==r&&le(e,o,{get:()=>t[o],enumerable:!(n=vt(t,o))||n.enumerable});return e};var _=(e,t,r)=>(r=e!=null?Et(St(e)):{},bt(t||!e||!e.__esModule?le(r,"default",{value:e,enumerable:!0}):r,e));var Y=y(p=>{"use strict";Object.defineProperty(p,"__esModule",{value:!0});p.PATTERNS=p.MAX_BYTES=void 0;p.truncateTo=de;p.truncate=xt;p.collectMatches=pe;p.resolveOverlaps=fe;p.applyMatches=me;p.redact=kt;p.MAX_BYTES=102400;function de(e,t){let r=Buffer.from(e,"utf-8");return r.length<=t?e:`${r.subarray(0,Math.max(0,t)).toString("utf-8")}\u2026[TRUNCATED:${r.length}]`}function xt(e){return de(e,p.MAX_BYTES)}p.PATTERNS=[{type:"aws_access_key",regex:/AKIA[0-9A-Z]{16}/g},{type:"aws_secret_key",regex:/(?:aws[_-]?secret(?:[_-]?(?:access)?[_-]?key)?)\s*[:=]\s*["']?([A-Za-z0-9/+=]{40})(?![A-Za-z0-9/+=])/gi,captureGroup:1},{type:"gcp_service_account",regex:/\{[\s\S]{0,200}?"type"\s*:\s*"service_account"[\s\S]*?\}/g},{type:"github_token",regex:/gh[pousr]_[A-Za-z0-9]{36,}/g},{type:"gitlab_token",regex:/glpat-[A-Za-z0-9_-]{20}/g},{type:"slack_token",regex:/xox[abrsp]-[0-9A-Za-z-]{10,}/g},{type:"openai_key",regex:/sk-(?:proj-)?[A-Za-z0-9_-]{40,}/g},{type:"anthropic_key",regex:/sk-ant-[A-Za-z0-9_-]{50,}/g},{type:"stripe_key",regex:/(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{20,}/g},{type:"jwt",regex:/eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g},{type:"private_key_block",regex:/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g},{type:"bearer_token",regex:/bearer\s+([A-Za-z0-9._~+/=-]{12,})/gi,captureGroup:1},{type:"basic_auth",regex:/basic\s+([A-Za-z0-9+/=]{12,})/gi,captureGroup:1},{type:"db_url_password",regex:/\b(?:postgres|postgresql|mysql|mariadb|mongodb(?:\+srv)?|redis):\/\/[^:\s/]+:([^@\s]+)@/gi,captureGroup:1},{type:"cli_password_flag",regex:/(?:--password|--pass|--pwd)[=\s]([^\s'"]+)/g,captureGroup:1},{type:"cli_password_short",regex:/\s-p([^\s'"]+)/g,captureGroup:1,requireContext:"bash"},{type:"env_var_secret",regex:/^(?:export\s+)?([A-Z][A-Z0-9_]*(?:KEY|SECRET|TOKEN|PASSWORD|PASSWD|PWD|API_KEY)[A-Z0-9_]*)\s*=\s*["']?([^\s"'\n]+)/gm,captureGroup:2}];function pe(e,t){let r=[];for(let n of p.PATTERNS){if(n.requireContext&&n.requireContext!==t.context)continue;let o=new RegExp(n.regex.source,n.regex.flags),s;for(;(s=o.exec(e))!==null;){let a=n.captureGroup??0,u=s[a];if(u===void 0){s.index===o.lastIndex&&o.lastIndex++;continue}let c=s.index,h=s.index+s[0].length,k=c+s[0].indexOf(u),Pt=k+u.length;r.push({start:c,end:h,replaceStart:k,replaceEnd:Pt,type:n.type}),s.index===o.lastIndex&&o.lastIndex++}}return r}function fe(e){let t=[...e].sort((o,s)=>o.start!==s.start?o.start-s.start:s.end-s.start-(o.end-o.start)),r=[],n=-1;for(let o of t)o.start<n||(r.push(o),n=o.end);return r}function me(e,t){let r=[...t].sort((o,s)=>s.replaceStart-o.replaceStart),n=e;for(let o of r)n=n.slice(0,o.replaceStart)+`[REDACTED:${o.type}]`+n.slice(o.replaceEnd);return n}function kt(e,t={}){if(e.length===0)return e;let r=pe(e,t);if(r.length===0)return e;let n=fe(r);return me(e,n)}});var F=y(d=>{"use strict";var At=d&&d.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(d,"__esModule",{value:!0});d.MAX_SPAN_BYTES=void 0;d.ulidToTraceId=ye;d.newSpanId=Pe;d.snakeCase=Mt;d.toOtlpValue=Ee;d.attrsFromRecord=Ct;d.guardAttrs=ve;d.clientAttrs=Oe;d.enforceSpanBudget=Se;d.buildPayload=Nt;d.mergeBatch=jt;var Rt=At(require("crypto")),V=Y(),wt="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function ye(e){if(e.length!==26)throw new Error(`ulidToTraceId: expected 26 chars, got ${e.length}`);let t=0n;for(let n of e){let o=wt.indexOf(n);if(o<0)throw new Error(`ulidToTraceId: invalid Crockford char "${n}"`);t=t<<5n|BigInt(o)}let r=(1n<<128n)-1n;return t&=r,t.toString(16).padStart(32,"0")}function Pe(){return Rt.default.randomBytes(8).toString("hex")}function Mt(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z])([A-Z][a-z])/g,"$1_$2").toLowerCase()}var _e=new Set;function N(e,t,r){let n=(0,V.truncate)(t);if((r.skipRedactKeys??_e).has(e))return n;let o=(r.bashContextKeys??_e).has(e)?"bash":void 0;return(0,V.redact)(n,{context:o})}function Ee(e,t,r={}){if(t==null)return null;switch(typeof t){case"string":return{stringValue:N(e,t,r)};case"boolean":return{boolValue:t};case"number":return Number.isInteger(t)?{intValue:t}:{doubleValue:t};case"object":try{return{stringValue:N(e,JSON.stringify(t),r)}}catch{return{stringValue:N(e,String(t),r)}}default:return{stringValue:N(e,String(t),r)}}}function Ct(e,t,r={}){let n=[];for(let[o,s]of Object.entries(e)){let a=`${t}.${o}`,u=Ee(a,s,r);u!==null&&n.push({key:a,value:u})}return n}function ve(e){let t=[{key:"pinta.guard.decision",value:{stringValue:e.decision.toLowerCase()}},{key:"pinta.guard.duration_ms",value:{intValue:e.durationMs}}];return e.reason&&t.push({key:"pinta.guard.matched_rule",value:{stringValue:e.reason}}),e.failOpenReason&&t.push({key:"pinta.guard.fail_open_reason",value:{stringValue:e.failOpenReason}}),t}function Oe(e){return[{key:"pinta.client.rtt_ms",value:Number.isInteger(e.rttMs)?{intValue:e.rttMs}:{doubleValue:e.rttMs}},{key:"pinta.client.op",value:{stringValue:e.op}}]}d.MAX_SPAN_BYTES=800*1024;function ge(e){let t=JSON.stringify(e);return t===void 0?0:Buffer.byteLength(t,"utf-8")}var he=1024;function Se(e,t=d.MAX_SPAN_BYTES){let r=ge(e);for(let n=0;n<100&&r>t;n++){let o=null,s=0;for(let c of e.attributes){if(!("stringValue"in c.value))continue;let h=Buffer.byteLength(c.value.stringValue,"utf-8");h>s&&(s=h,o=c.value)}if(o===null||s<=he)break;let a=r-t,u=Math.max(he,s-a-64);o.stringValue=(0,V.truncateTo)(o.stringValue,u),r=ge(e)}return e}function Nt(e){let t=e.now??Date.now(),r=(BigInt(t)*1000000n).toString(),n=[...e.attributes];e.guard&&n.push(...ve(e.guard));let o=e.clientTiming??(e.guard?.clientRttMs!==void 0?{op:"guard",rttMs:e.guard.clientRttMs}:null);o&&n.push(...Oe(o));let s={traceId:ye(e.traceId),spanId:Pe(),name:e.spanName,kind:e.spanKind??1,startTimeUnixNano:r,endTimeUnixNano:r,attributes:n};return Se(s),{resourceSpans:[{resource:{attributes:e.resource},scopeSpans:[{scope:e.scope,spans:[s]}]}]}}function jt(e){let t=[];for(let r of e)t.push(...r.resourceSpans);return{resourceSpans:t}}});var Te=y(H=>{"use strict";Object.defineProperty(H,"__esModule",{value:!0});H.evaluateGuard=qt;var Dt=1e4,It="pinta-core";function K(e,t){return{decision:"ALLOW",reason:null,userMessage:null,durationMs:e,clientRttMs:e,failOpenReason:t}}var Bt=new Set(["ALLOW","DENY","REVIEW"]);async function qt(e,t,r={}){if(!t||r.disabled)return null;let n=r.timeoutMs??Dt,o=Date.now(),s=new AbortController,a=setTimeout(()=>s.abort(),n);try{let u=await fetch(t,{method:"POST",headers:{"content-type":"application/json","user-agent":r.userAgent??It,"x-pinta-relay-token":r.token??""},body:JSON.stringify({input:e}),signal:s.signal});if(u.status!==200)return K(Date.now()-o,"error");let c=await u.json(),h=c?.durationMs===void 0||typeof c.durationMs=="number"&&Number.isFinite(c.durationMs);if(!c||!Bt.has(c.decision)||!h)return K(Date.now()-o,"error");let k=Date.now()-o;return{decision:c.decision,reason:c.reason??null,userMessage:c.userMessage??null,durationMs:c.durationMs??k,clientRttMs:k}}catch(u){let c=u.name,h=c==="AbortError"||c==="TimeoutError"?"timeout":"error";return K(Date.now()-o,h)}finally{clearTimeout(a)}}});var j=y(A=>{"use strict";var Ut=A&&A.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(A,"__esModule",{value:!0});A.atomicWriteFileSync=Lt;var be=Ut(require("fs"));function Lt(e,t){let r=`${e}.${process.pid}.tmp`;be.default.writeFileSync(r,t),be.default.renameSync(r,e)}});var Z=y(P=>{"use strict";var xe=P&&P.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(P,"__esModule",{value:!0});P.MemoryRetryQueue=P.DiskRetryQueue=void 0;var m=xe(require("fs")),D=xe(require("path")),$t=j(),I=1e3,Gt=50,Xt=5,z=class{filePath;lockPath;logPrefix;constructor(t,r){this.filePath=D.default.join(t,"failed-spans.jsonl"),this.lockPath=this.filePath+".lock",this.logPrefix=r}enqueue(t){try{m.default.mkdirSync(D.default.dirname(this.filePath),{recursive:!0});let r=JSON.stringify({savedAt:new Date().toISOString(),payload:t})+`
|
|
2
|
+
`;m.default.appendFileSync(this.filePath,r),this.trim()}catch(r){process.stderr.write(`[${this.logPrefix}] retry-queue enqueue failed: ${r}
|
|
3
|
+
`)}}readAll(){try{let t=m.default.readFileSync(this.filePath,"utf-8"),r=[];for(let n of t.split(`
|
|
4
|
+
`))if(n.trim())try{let o=JSON.parse(n);Array.isArray(o?.payload?.resourceSpans)&&r.push(o)}catch{}return r}catch{return[]}}rewrite(t){try{if(t.length===0){m.default.existsSync(this.filePath)&&m.default.unlinkSync(this.filePath);return}m.default.mkdirSync(D.default.dirname(this.filePath),{recursive:!0}),(0,$t.atomicWriteFileSync)(this.filePath,t.map(r=>JSON.stringify(r)).join(`
|
|
5
5
|
`)+`
|
|
6
6
|
`)}catch(r){process.stderr.write(`[${this.logPrefix}] retry-queue rewrite failed: ${r}
|
|
7
|
-
`)}}tryAcquireLock(){let t=Date.now();try{
|
|
8
|
-
`),!1}for(;Date.now()-t<
|
|
9
|
-
`),!1;try{let
|
|
10
|
-
`),this.rewrite(t.slice(r))}};P.DiskRetryQueue=
|
|
11
|
-
`),!1}return!0}catch(
|
|
12
|
-
`),!1}finally{clearTimeout(o)}}var
|
|
13
|
-
`);let
|
|
14
|
-
`)}finally{this.queue.release()}}dropIfOversized(t){let r=
|
|
15
|
-
`),!0)}};l.DiskTransport=
|
|
16
|
-
`);return}await
|
|
17
|
-
`);let
|
|
18
|
-
`)}}};
|
|
19
|
-
`)){let n=r.trim();if(!n||n.startsWith("#"))continue;let o=n.indexOf("=");if(o<0)continue;let
|
|
20
|
-
`)){let o=n.trim();if(!o||o.startsWith("#"))continue;let
|
|
21
|
-
`)}try{
|
|
22
|
-
`)}
|
|
23
|
-
`)
|
|
7
|
+
`)}}tryAcquireLock(){let t=Date.now();try{m.default.mkdirSync(D.default.dirname(this.lockPath),{recursive:!0})}catch(r){return process.stderr.write(`[${this.logPrefix}] retry-queue lock mkdir failed: ${r}
|
|
8
|
+
`),!1}for(;Date.now()-t<Gt;)try{let r=m.default.openSync(this.lockPath,"wx");return m.default.writeSync(r,String(process.pid)),m.default.closeSync(r),!0}catch(r){if(r?.code!=="EEXIST")return process.stderr.write(`[${this.logPrefix}] retry-queue lock open failed: ${r}
|
|
9
|
+
`),!1;try{let s=m.default.statSync(this.lockPath);if(Date.now()-s.mtimeMs>3e4){m.default.unlinkSync(this.lockPath);continue}}catch{}let n=Xt,o=Date.now()+n;for(;Date.now()<o;);}return!1}release(){try{m.default.unlinkSync(this.lockPath)}catch{}}trim(){let t=this.readAll();if(t.length<=I)return;let r=t.length-I;process.stderr.write(`[${this.logPrefix}] retry-queue full, dropping ${r} oldest entries
|
|
10
|
+
`),this.rewrite(t.slice(r))}};P.DiskRetryQueue=z;var W=class{entries=[];enqueue(t){this.entries.push(t),this.entries.length>I&&this.entries.splice(0,this.entries.length-I)}drain(){let t=this.entries;return this.entries=[],t}get size(){return this.entries.length}};P.MemoryRetryQueue=W});var Ce=y(l=>{"use strict";Object.defineProperty(l,"__esModule",{value:!0});l.MemoryTransport=l.DiskTransport=l.MAX_POST_BYTES=void 0;l.parseHeadersEnv=we;l.envOptionsResolver=Me;l.postOtlp=R;var ke=Z(),Ae=F(),Yt=5e3;l.MAX_POST_BYTES=900*1024;function B(e){let t=JSON.stringify(e);return t===void 0?0:Buffer.byteLength(t,"utf-8")}function Re(e,t,r){let n=[],o=[],s=[],a=0;for(let u of e){let c=t(u);if(c>r){o.push(u);continue}s.length>0&&a+c>r&&(n.push(s),s=[],a=0),s.push(u),a+=c}return s.length>0&&n.push(s),{chunks:n,oversize:o}}function we(e){if(!e)return{};if(typeof e=="object")return{...e};let t={};for(let r of e.split(",")){let[n,...o]=r.split("=");n&&o.length>0&&(t[n.trim()]=o.join("=").trim())}return t}function Me(){let e=process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,t=process.env.OTEL_EXPORTER_OTLP_ENDPOINT,r;return e?r=e.replace(/\/+$/,""):t&&(r=t.replace(/\/+$/,"")+"/v1/traces"),r?{endpoint:r,headers:we(process.env.OTEL_EXPORTER_OTLP_HEADERS)}:null}async function R(e,t,r){let n=new AbortController,o=setTimeout(()=>n.abort(),Yt);try{let s=await fetch(t.endpoint,{method:"POST",headers:{"Content-Type":"application/json",...t.headers},body:JSON.stringify(e),signal:n.signal});if(!s.ok){let a="";try{a=(await s.text()).slice(0,200)}catch{}let u=s.status===401||s.status===403?" \u2014 check OTEL_EXPORTER_OTLP_HEADERS (relay token)":s.status===404?" \u2014 check OTEL_EXPORTER_OTLP_TRACES_ENDPOINT path":s.status>=500?" \u2014 collector may be down":"";return process.stderr.write(`[${r}] OTLP POST ${s.status} ${t.endpoint}${u}${a?` body=${a}`:""}
|
|
11
|
+
`),!1}return!0}catch(s){return process.stderr.write(`[${r}] OTLP POST failed: ${s.message??String(s)}
|
|
12
|
+
`),!1}finally{clearTimeout(o)}}var J=class{queue;logPrefix;resolveOptions;constructor(t){this.queue=new ke.DiskRetryQueue(t.pluginData,t.logPrefix),this.logPrefix=t.logPrefix,this.resolveOptions=t.resolveOptions??Me}async send(t){let r=this.resolveOptions();if(!r||this.dropIfOversized(t))return;await R(t,r,this.logPrefix)||this.queue.enqueue(t)}async flush(){let t=this.resolveOptions();if(t&&this.queue.tryAcquireLock())try{let r=this.queue.readAll();if(r.length===0)return;let{chunks:n,oversize:o}=Re(r,u=>B(u.payload),l.MAX_POST_BYTES);o.length>0&&process.stderr.write(`[${this.logPrefix}] dropping ${o.length} oversized retry ${o.length===1?"entry":"entries"} (each > ${l.MAX_POST_BYTES} bytes \u2014 the backend caps the request body, so they can never be delivered)
|
|
13
|
+
`);let s=[].concat(...n),a=0;for(let u of n){if(!await R((0,Ae.mergeBatch)(u.map(h=>h.payload)),t,this.logPrefix))break;a+=u.length}this.queue.rewrite(s.slice(a))}catch(r){process.stderr.write(`[${this.logPrefix}] flush failed: ${r?.message??String(r)}
|
|
14
|
+
`)}finally{this.queue.release()}}dropIfOversized(t){let r=B(t);return r<=l.MAX_POST_BYTES?!1:(process.stderr.write(`[${this.logPrefix}] dropping oversized span payload (${r} > ${l.MAX_POST_BYTES} bytes) \u2014 undeliverable, not queued
|
|
15
|
+
`),!0)}};l.DiskTransport=J;var Q=class{queue=new ke.MemoryRetryQueue;logPrefix;resolveOptions;constructor(t){this.logPrefix=t.logPrefix,this.resolveOptions=t.resolveOptions}async send(t){let r=this.resolveOptions();if(!r)return;if(B(t)>l.MAX_POST_BYTES){process.stderr.write(`[${this.logPrefix}] dropping oversized span payload (> ${l.MAX_POST_BYTES} bytes) \u2014 undeliverable, not buffered
|
|
16
|
+
`);return}await R(t,r,this.logPrefix)||this.queue.enqueue(t)}async flush(){let t=this.resolveOptions();if(!t)return;let r=this.queue.drain();if(r.length===0)return;let{chunks:n,oversize:o}=Re(r,B,l.MAX_POST_BYTES);o.length>0&&process.stderr.write(`[${this.logPrefix}] dropping ${o.length} oversized buffered ${o.length===1?"payload":"payloads"} (each > ${l.MAX_POST_BYTES} bytes \u2014 undeliverable)
|
|
17
|
+
`);let s=[].concat(...n),a=0;for(let u of n){if(!await R((0,Ae.mergeBatch)(u),t,this.logPrefix))break;a+=u.length}for(let u of s.slice(a))this.queue.enqueue(u)}};l.MemoryTransport=Q});var ne=y(O=>{"use strict";var re=O&&O.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(O,"__esModule",{value:!0});O.TraceManager=void 0;O.generateUlid=je;var Ne=re(require("fs")),Vt=re(require("path")),Ft=re(require("crypto")),Kt=j(),ee="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function je(){let e=Date.now(),t="",r=e;for(let s=0;s<10;s++)t=ee[r&31]+t,r=Math.floor(r/32);let n=Ft.default.randomBytes(10),o="";for(let s=0;s<10;s++)o+=ee[n[s]&31];for(;o.length<16;)o+=ee[0];return t+o}var te=class{tracePath;constructor(t){this.tracePath=t}newTrace(){let t=je();return this.save(t),t}currentTrace(){try{let t=Ne.default.readFileSync(this.tracePath,"utf-8"),{traceId:r}=JSON.parse(t);if(r)return r}catch{}return this.newTrace()}save(t){try{Ne.default.mkdirSync(Vt.default.dirname(this.tracePath),{recursive:!0}),(0,Kt.atomicWriteFileSync)(this.tracePath,JSON.stringify({traceId:t}))}catch(r){process.stderr.write(`[pinta-core] trace persistence failed: ${r}
|
|
18
|
+
`)}}};O.TraceManager=te});var Ue=y(E=>{"use strict";var Ie=E&&E.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(E,"__esModule",{value:!0});E.MemorySessionTraceManager=E.DiskSessionTraceManager=void 0;var De=Ie(require("fs")),Ht=Ie(require("path")),Be=ne(),zt=j(),qe=200,oe=class{tracePath;maxSessions;constructor(t,r={}){this.tracePath=t,this.maxSessions=r.maxSessions??qe}read(){try{let t=JSON.parse(De.default.readFileSync(this.tracePath,"utf-8"));if(t&&typeof t=="object"&&!("traceId"in t))return t}catch{}return{}}write(t){try{De.default.mkdirSync(Ht.default.dirname(this.tracePath),{recursive:!0});let r=Object.entries(t),n=r.length>this.maxSessions?Object.fromEntries(r.slice(-this.maxSessions)):t;(0,zt.atomicWriteFileSync)(this.tracePath,JSON.stringify(n))}catch{}}newTrace(t){let r=t||"default",n=(0,Be.generateUlid)(),o=this.read();return o[r]=n,this.write(o),n}currentTrace(t){let r=t||"default",n=this.read()[r];return n||this.newTrace(r)}};E.DiskSessionTraceManager=oe;var se=class{map=new Map;maxSessions;constructor(t={}){this.maxSessions=t.maxSessions??qe}newTrace(t){let r=t||"default",n=(0,Be.generateUlid)();if(this.map.set(r,n),this.map.size>this.maxSessions){let o=this.map.keys().next().value;o!==void 0&&this.map.delete(o)}return n}currentTrace(t){let r=t||"default";return this.map.get(r)??this.newTrace(r)}};E.MemorySessionTraceManager=se});var Ge=y(S=>{"use strict";var ie=S&&S.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(S,"__esModule",{value:!0});S.envFilePath=Jt;S.parseEnvFile=$e;S.loadEnvFile=Qt;var Wt=ie(require("node:fs")),Zt=ie(require("node:os")),Le=ie(require("node:path"));function Jt(e,t,r){let n=r?process.env[r]:void 0,o=n&&n.length>0?n:Le.default.join(Zt.default.homedir(),e);return Le.default.join(o,t)}function $e(e){let t={};for(let r of e.split(`
|
|
19
|
+
`)){let n=r.trim();if(!n||n.startsWith("#"))continue;let o=n.indexOf("=");if(o<0)continue;let s=n.slice(0,o).trim(),a=n.slice(o+1).trim();(a.startsWith('"')&&a.endsWith('"')||a.startsWith("'")&&a.endsWith("'"))&&(a=a.slice(1,-1)),s&&(t[s]=a)}return t}function Qt(e){let t;try{t=Wt.default.readFileSync(e,"utf-8")}catch{return}let r=$e(t);for(let[n,o]of Object.entries(r))process.env[n]===void 0&&(process.env[n]=o)}});var x=y(i=>{"use strict";Object.defineProperty(i,"__esModule",{value:!0});i.loadEnvFile=i.parseEnvFile=i.envFilePath=i.MemorySessionTraceManager=i.DiskSessionTraceManager=i.generateUlid=i.TraceManager=i.MAX_POST_BYTES=i.parseHeadersEnv=i.envOptionsResolver=i.postOtlp=i.MemoryTransport=i.DiskTransport=i.MemoryRetryQueue=i.DiskRetryQueue=i.evaluateGuard=i.MAX_SPAN_BYTES=i.enforceSpanBudget=i.mergeBatch=i.buildPayload=i.clientAttrs=i.guardAttrs=i.attrsFromRecord=i.toOtlpValue=i.snakeCase=i.newSpanId=i.ulidToTraceId=i.MAX_BYTES=i.PATTERNS=i.applyMatches=i.resolveOverlaps=i.collectMatches=i.truncateTo=i.truncate=i.redact=void 0;var v=Y();Object.defineProperty(i,"redact",{enumerable:!0,get:function(){return v.redact}});Object.defineProperty(i,"truncate",{enumerable:!0,get:function(){return v.truncate}});Object.defineProperty(i,"truncateTo",{enumerable:!0,get:function(){return v.truncateTo}});Object.defineProperty(i,"collectMatches",{enumerable:!0,get:function(){return v.collectMatches}});Object.defineProperty(i,"resolveOverlaps",{enumerable:!0,get:function(){return v.resolveOverlaps}});Object.defineProperty(i,"applyMatches",{enumerable:!0,get:function(){return v.applyMatches}});Object.defineProperty(i,"PATTERNS",{enumerable:!0,get:function(){return v.PATTERNS}});Object.defineProperty(i,"MAX_BYTES",{enumerable:!0,get:function(){return v.MAX_BYTES}});var g=F();Object.defineProperty(i,"ulidToTraceId",{enumerable:!0,get:function(){return g.ulidToTraceId}});Object.defineProperty(i,"newSpanId",{enumerable:!0,get:function(){return g.newSpanId}});Object.defineProperty(i,"snakeCase",{enumerable:!0,get:function(){return g.snakeCase}});Object.defineProperty(i,"toOtlpValue",{enumerable:!0,get:function(){return g.toOtlpValue}});Object.defineProperty(i,"attrsFromRecord",{enumerable:!0,get:function(){return g.attrsFromRecord}});Object.defineProperty(i,"guardAttrs",{enumerable:!0,get:function(){return g.guardAttrs}});Object.defineProperty(i,"clientAttrs",{enumerable:!0,get:function(){return g.clientAttrs}});Object.defineProperty(i,"buildPayload",{enumerable:!0,get:function(){return g.buildPayload}});Object.defineProperty(i,"mergeBatch",{enumerable:!0,get:function(){return g.mergeBatch}});Object.defineProperty(i,"enforceSpanBudget",{enumerable:!0,get:function(){return g.enforceSpanBudget}});Object.defineProperty(i,"MAX_SPAN_BYTES",{enumerable:!0,get:function(){return g.MAX_SPAN_BYTES}});var er=Te();Object.defineProperty(i,"evaluateGuard",{enumerable:!0,get:function(){return er.evaluateGuard}});var Xe=Z();Object.defineProperty(i,"DiskRetryQueue",{enumerable:!0,get:function(){return Xe.DiskRetryQueue}});Object.defineProperty(i,"MemoryRetryQueue",{enumerable:!0,get:function(){return Xe.MemoryRetryQueue}});var b=Ce();Object.defineProperty(i,"DiskTransport",{enumerable:!0,get:function(){return b.DiskTransport}});Object.defineProperty(i,"MemoryTransport",{enumerable:!0,get:function(){return b.MemoryTransport}});Object.defineProperty(i,"postOtlp",{enumerable:!0,get:function(){return b.postOtlp}});Object.defineProperty(i,"envOptionsResolver",{enumerable:!0,get:function(){return b.envOptionsResolver}});Object.defineProperty(i,"parseHeadersEnv",{enumerable:!0,get:function(){return b.parseHeadersEnv}});Object.defineProperty(i,"MAX_POST_BYTES",{enumerable:!0,get:function(){return b.MAX_POST_BYTES}});var Ye=ne();Object.defineProperty(i,"TraceManager",{enumerable:!0,get:function(){return Ye.TraceManager}});Object.defineProperty(i,"generateUlid",{enumerable:!0,get:function(){return Ye.generateUlid}});var Ve=Ue();Object.defineProperty(i,"DiskSessionTraceManager",{enumerable:!0,get:function(){return Ve.DiskSessionTraceManager}});Object.defineProperty(i,"MemorySessionTraceManager",{enumerable:!0,get:function(){return Ve.MemorySessionTraceManager}});var ae=Ge();Object.defineProperty(i,"envFilePath",{enumerable:!0,get:function(){return ae.envFilePath}});Object.defineProperty(i,"parseEnvFile",{enumerable:!0,get:function(){return ae.parseEnvFile}});Object.defineProperty(i,"loadEnvFile",{enumerable:!0,get:function(){return ae.loadEnvFile}})});var ue=_(require("node:fs")),Fe=_(require("node:os")),w=_(require("node:path")),Ke=_(x());function tr(e){let t={},r;try{r=ue.default.readFileSync(e,"utf-8")}catch{return t}for(let n of r.split(`
|
|
20
|
+
`)){let o=n.trim();if(!o||o.startsWith("#"))continue;let s=o.indexOf("=");if(s<0)continue;let a=o.slice(0,s).trim(),u=o.slice(s+1).trim();(u.startsWith('"')&&u.endsWith('"')||u.startsWith("'")&&u.endsWith("'"))&&(u=u.slice(1,-1)),a&&(t[a]=u)}return t}function rr(e){let t=process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT??e.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT;if(t)return t;let r=process.env.OTEL_EXPORTER_OTLP_ENDPOINT??e.OTEL_EXPORTER_OTLP_ENDPOINT;return r?r.replace(/\/+$/,"")+"/v1/traces":process.env.PINTA_CODEX_ENDPOINT??e.PINTA_CODEX_ENDPOINT??process.env.CLAUDE_PLUGIN_OPTION_ENDPOINT}function nr(e){if(process.env.OTEL_EXPORTER_OTLP_HEADERS)return process.env.OTEL_EXPORTER_OTLP_HEADERS;if(e.OTEL_EXPORTER_OTLP_HEADERS)return e.OTEL_EXPORTER_OTLP_HEADERS;if(process.env.PINTA_CODEX_API_KEY)return`x-pinta-relay-token=${process.env.PINTA_CODEX_API_KEY}`;if(e.PINTA_CODEX_API_KEY)return`x-pinta-relay-token=${e.PINTA_CODEX_API_KEY}`;if(process.env.CLAUDE_PLUGIN_OPTION_API_KEY)return`x-pinta-relay-token=${process.env.CLAUDE_PLUGIN_OPTION_API_KEY}`}function He(){let e=process.env.CODEX_PLUGIN_ROOT??process.env.CLAUDE_PLUGIN_ROOT??process.cwd(),t=process.env.CODEX_PLUGIN_DATA??process.env.CLAUDE_PLUGIN_DATA??w.default.join(e,".plugin-data"),r=process.env.CODEX_HOME??w.default.join(Fe.default.homedir(),".codex"),n=w.default.join(r,"pinta-codex.env"),o=ue.default.existsSync(n)?tr(n):{},s=rr(o),a=nr(o);if(a&&!process.env.PINTA_RELAY_TOKEN){let c=a.match(/x-pinta-relay-token=([^,\s]+)/i);c&&(process.env.PINTA_RELAY_TOKEN=c[1])}let u=process.env.PINTA_GUARD_ENDPOINT??o.PINTA_GUARD_ENDPOINT;return{pluginRoot:e,pluginData:t,tracePath:w.default.join(t,"trace.json"),endpoint:s?.replace(/\/+$/,""),headers:(0,Ke.parseHeadersEnv)(a??""),guardEndpoint:u}}function ze(e){return e.hook_event_name==="PreToolUse"}function We(e){return e.hook_event_name==="PermissionRequest"}function Ze(e){return e.hook_event_name==="PostToolUse"}function Je(e){return e.hook_event_name==="UserPromptSubmit"}function Qe(e){return e.hook_event_name==="SessionStart"}function et(e){return e.hook_event_name==="Stop"}var or=["PreToolUse","PermissionRequest"],sr=["PostToolUse","PreCompact","PostCompact","SessionStart","SessionEnd","UserPromptSubmit","SubagentStart","SubagentStop","Stop","Interrupt"],ir=new Set([...or,...sr]);function tt(e){return!ir.has(e.hook_event_name)}var rt=_(x()),q=class extends rt.DiskTransport{constructor(t){super({pluginData:t.pluginData,logPrefix:"pinta-codex",resolveOptions:()=>t.endpoint?{endpoint:t.endpoint,headers:t.headers}:null})}};var nt=_(x()),U=class extends nt.TraceManager{constructor(t){super(t.tracePath)}};var M=_(require("fs")),$=_(require("os")),st=_(require("path")),T=_(x()),L;function ar(){if(L===void 0)try{L=$.default.userInfo().username}catch{L=process.env.USER??process.env.LOGNAME??(typeof process.getuid=="function"?String(process.getuid()):"unknown")}return L}var it="1.8.0",ce;function C(e){return typeof e=="string"&&e.trim()!==""?e.trim():void 0}var ot=256*1024;function ur(e){if(!e)return;let t;try{t=M.default.openSync(e,"r");let r=Buffer.allocUnsafe(ot),n=M.default.readSync(t,r,0,ot,0),o=r.toString("utf8",0,n),s=o.indexOf(`
|
|
21
|
+
`);if(s<0)return;let a=JSON.parse(o.slice(0,s));return a?.type!=="session_meta"?void 0:C(a.payload?.cli_version)}catch{return}finally{if(t!==void 0)try{M.default.closeSync(t)}catch{}}}function cr(){let e=C(process.env.CODEX_MANAGED_PACKAGE_ROOT);if(e)try{let t=JSON.parse(M.default.readFileSync(st.default.join(e,"package.json"),"utf8"));return C(t.version)}catch{return}}function lr(e){return ce===void 0&&(ce=ur(C(e?.transcript_path))??cr()??C(process.env.CODEX_CLI_VERSION)??null),ce??void 0}var dr=new Set(["codex.hook","codex.tool_name","codex.tool_use_id","codex.session_id","codex.transcript_path","codex.cwd","codex.permission_mode"]),pr=new Set(["codex.tool_input","codex.tool_response"]),fr={skipRedactKeys:dr,bashContextKeys:pr};function mr(e){let t=[];t.push({key:"ingest.type",value:{stringValue:"codex"}}),t.push({key:"codex.hook",value:{stringValue:e.hook_event_name}});let r={};for(let[n,o]of Object.entries(e))n!=="hook_event_name"&&(r[n]=o);return t.push(...(0,T.attrsFromRecord)(r,"codex",fr)),t}function _r(e){let t=lr(e);return[{key:"service.name",value:{stringValue:"codex"}},...t?[{key:"service.version",value:{stringValue:t}}]:[],{key:"telemetry.sdk.name",value:{stringValue:"pinta-codex"}},{key:"telemetry.sdk.language",value:{stringValue:"nodejs"}},{key:"telemetry.sdk.version",value:{stringValue:it}},{key:"process.pid",value:{intValue:process.pid}},{key:"process.owner",value:{stringValue:ar()}},{key:"host.name",value:{stringValue:$.default.hostname()}},{key:"host.arch",value:{stringValue:$.default.arch()}}]}function at(e){return(0,T.buildPayload)({traceId:e.traceId,spanName:`codex.${(0,T.snakeCase)(e.event.hook_event_name)}`,attributes:mr(e.event),resource:_r(e.event),scope:{name:"pinta-codex",version:it},now:e.now,guard:e.guard})}async function f(e,t,r){let n=new q(t);await n.flush();let o=new U(t),s=r.trace==="new"?o.newTrace():o.currentTrace(),a=at({event:e,traceId:s,guard:r.guard});await n.send(a)}var ut=_(x()),gr=50,hr="pinta-codex/1.8.0";async function ct(e,t){let r=await(0,ut.evaluateGuard)(e,t,{timeoutMs:gr,token:process.env.PINTA_RELAY_TOKEN??"",disabled:process.env.PINTA_GUARD_DISABLED==="1",userAgent:hr});if(r===null)return null;let n={decision:r.decision,reason:r.reason,durationMs:r.durationMs,clientRttMs:r.clientRttMs};return r.failOpenReason!==void 0&&(n.failOpenReason=r.failOpenReason),n}async function G(e,t){let r=typeof e.tool_input=="string"?e.tool_input:JSON.stringify(e.tool_input);return ct({spanId:e.session_id??"unknown",toolName:e.tool_name,method:e.hook_event_name,cwd:e.cwd,toolInput:e.tool_input,rawTextFields:{toolInput:r}},t.guardEndpoint)}function X(e){let t=e?.reason;return typeof t=="string"&&t.trim().length>0?t:"guard_deny"}async function lt(e,t){let r=await G(e,t);if(r?.decision==="DENY"){let n={hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:X(r)}};process.stdout.write(JSON.stringify(n)+`
|
|
22
|
+
`)}try{await f(e,t,{trace:"current",guard:r})}catch(n){process.stderr.write(`[pinta-codex] telemetry emit failed: ${n}
|
|
23
|
+
`)}return 0}async function dt(e,t){let r=await G(e,t);if(r?.decision==="DENY"){let n={hookSpecificOutput:{hookEventName:"PermissionRequest",decision:{behavior:"deny",message:X(r)}}};process.stdout.write(JSON.stringify(n)+`
|
|
24
|
+
`)}try{await f(e,t,{trace:"current",guard:r})}catch(n){process.stderr.write(`[pinta-codex] telemetry emit failed: ${n}
|
|
25
|
+
`)}return 0}async function pt(e,t){return await f(e,t,{trace:"current"}),0}async function ft(e,t){return await f(e,t,{trace:"new"}),0}async function mt(e,t){return await f(e,t,{trace:"current"}),0}async function _t(e,t){return await f(e,t,{trace:"current"}),0}async function gt(e,t){return await f(e,t,{trace:"current"}),0}async function ht(e){return 0}async function yr(){let e=[];for await(let t of process.stdin)e.push(t);return Buffer.concat(e).toString("utf-8")}async function yt(){let e=0;try{let t=He(),r=await yr(),n=JSON.parse(r);tt(n)?e=await ht(n):ze(n)?e=await lt(n,t):We(n)?e=await dt(n,t):Ze(n)?e=await pt(n,t):Je(n)?e=await ft(n,t):Qe(n)?e=await mt(n,t):et(n)?e=await _t(n,t):e=await gt(n,t)}catch(t){process.stderr.write(`[pinta-codex] error: ${t}
|
|
26
|
+
`),e=0}return e}async function Pr(){let e=await yt();process.exit(e)}Pr();
|
package/dist/index.mjs
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
import{createRequire as __pintaCreateRequire}from 'node:module';const require=__pintaCreateRequire(import.meta.url);
|
|
2
|
-
var
|
|
3
|
-
`;
|
|
4
|
-
`)}}readAll(){try{let t=
|
|
5
|
-
`))if(n.trim())try{let o=JSON.parse(n);Array.isArray(o?.payload?.resourceSpans)&&r.push(o)}catch{}return r}catch{return[]}}rewrite(t){try{if(t.length===0){
|
|
2
|
+
var Ot=Object.create;var pe=Object.defineProperty;var St=Object.getOwnPropertyDescriptor;var bt=Object.getOwnPropertyNames;var xt=Object.getPrototypeOf,kt=Object.prototype.hasOwnProperty;var _=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(t,r)=>(typeof require<"u"?require:t)[r]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});var y=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var At=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of bt(t))!kt.call(e,o)&&o!==r&&pe(e,o,{get:()=>t[o],enumerable:!(n=St(t,o))||n.enumerable});return e};var x=(e,t,r)=>(r=e!=null?Ot(xt(e)):{},At(t||!e||!e.__esModule?pe(r,"default",{value:e,enumerable:!0}):r,e));var X=y(d=>{"use strict";Object.defineProperty(d,"__esModule",{value:!0});d.PATTERNS=d.MAX_BYTES=void 0;d.truncateTo=de;d.truncate=Rt;d.collectMatches=fe;d.resolveOverlaps=me;d.applyMatches=_e;d.redact=wt;d.MAX_BYTES=102400;function de(e,t){let r=Buffer.from(e,"utf-8");return r.length<=t?e:`${r.subarray(0,Math.max(0,t)).toString("utf-8")}\u2026[TRUNCATED:${r.length}]`}function Rt(e){return de(e,d.MAX_BYTES)}d.PATTERNS=[{type:"aws_access_key",regex:/AKIA[0-9A-Z]{16}/g},{type:"aws_secret_key",regex:/(?:aws[_-]?secret(?:[_-]?(?:access)?[_-]?key)?)\s*[:=]\s*["']?([A-Za-z0-9/+=]{40})(?![A-Za-z0-9/+=])/gi,captureGroup:1},{type:"gcp_service_account",regex:/\{[\s\S]{0,200}?"type"\s*:\s*"service_account"[\s\S]*?\}/g},{type:"github_token",regex:/gh[pousr]_[A-Za-z0-9]{36,}/g},{type:"gitlab_token",regex:/glpat-[A-Za-z0-9_-]{20}/g},{type:"slack_token",regex:/xox[abrsp]-[0-9A-Za-z-]{10,}/g},{type:"openai_key",regex:/sk-(?:proj-)?[A-Za-z0-9_-]{40,}/g},{type:"anthropic_key",regex:/sk-ant-[A-Za-z0-9_-]{50,}/g},{type:"stripe_key",regex:/(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{20,}/g},{type:"jwt",regex:/eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g},{type:"private_key_block",regex:/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g},{type:"bearer_token",regex:/bearer\s+([A-Za-z0-9._~+/=-]{12,})/gi,captureGroup:1},{type:"basic_auth",regex:/basic\s+([A-Za-z0-9+/=]{12,})/gi,captureGroup:1},{type:"db_url_password",regex:/\b(?:postgres|postgresql|mysql|mariadb|mongodb(?:\+srv)?|redis):\/\/[^:\s/]+:([^@\s]+)@/gi,captureGroup:1},{type:"cli_password_flag",regex:/(?:--password|--pass|--pwd)[=\s]([^\s'"]+)/g,captureGroup:1},{type:"cli_password_short",regex:/\s-p([^\s'"]+)/g,captureGroup:1,requireContext:"bash"},{type:"env_var_secret",regex:/^(?:export\s+)?([A-Z][A-Z0-9_]*(?:KEY|SECRET|TOKEN|PASSWORD|PASSWD|PWD|API_KEY)[A-Z0-9_]*)\s*=\s*["']?([^\s"'\n]+)/gm,captureGroup:2}];function fe(e,t){let r=[];for(let n of d.PATTERNS){if(n.requireContext&&n.requireContext!==t.context)continue;let o=new RegExp(n.regex.source,n.regex.flags),s;for(;(s=o.exec(e))!==null;){let a=n.captureGroup??0,u=s[a];if(u===void 0){s.index===o.lastIndex&&o.lastIndex++;continue}let c=s.index,h=s.index+s[0].length,A=c+s[0].indexOf(u),Tt=A+u.length;r.push({start:c,end:h,replaceStart:A,replaceEnd:Tt,type:n.type}),s.index===o.lastIndex&&o.lastIndex++}}return r}function me(e){let t=[...e].sort((o,s)=>o.start!==s.start?o.start-s.start:s.end-s.start-(o.end-o.start)),r=[],n=-1;for(let o of t)o.start<n||(r.push(o),n=o.end);return r}function _e(e,t){let r=[...t].sort((o,s)=>s.replaceStart-o.replaceStart),n=e;for(let o of r)n=n.slice(0,o.replaceStart)+`[REDACTED:${o.type}]`+n.slice(o.replaceEnd);return n}function wt(e,t={}){if(e.length===0)return e;let r=fe(e,t);if(r.length===0)return e;let n=me(r);return _e(e,n)}});var V=y(p=>{"use strict";var Mt=p&&p.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(p,"__esModule",{value:!0});p.MAX_SPAN_BYTES=void 0;p.ulidToTraceId=Pe;p.newSpanId=Ee;p.snakeCase=It;p.toOtlpValue=ve;p.attrsFromRecord=Dt;p.guardAttrs=Te;p.clientAttrs=Oe;p.enforceSpanBudget=Se;p.buildPayload=Nt;p.mergeBatch=Bt;var Ct=Mt(_("crypto")),Y=X(),jt="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function Pe(e){if(e.length!==26)throw new Error(`ulidToTraceId: expected 26 chars, got ${e.length}`);let t=0n;for(let n of e){let o=jt.indexOf(n);if(o<0)throw new Error(`ulidToTraceId: invalid Crockford char "${n}"`);t=t<<5n|BigInt(o)}let r=(1n<<128n)-1n;return t&=r,t.toString(16).padStart(32,"0")}function Ee(){return Ct.default.randomBytes(8).toString("hex")}function It(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z])([A-Z][a-z])/g,"$1_$2").toLowerCase()}var ge=new Set;function C(e,t,r){let n=(0,Y.truncate)(t);if((r.skipRedactKeys??ge).has(e))return n;let o=(r.bashContextKeys??ge).has(e)?"bash":void 0;return(0,Y.redact)(n,{context:o})}function ve(e,t,r={}){if(t==null)return null;switch(typeof t){case"string":return{stringValue:C(e,t,r)};case"boolean":return{boolValue:t};case"number":return Number.isInteger(t)?{intValue:t}:{doubleValue:t};case"object":try{return{stringValue:C(e,JSON.stringify(t),r)}}catch{return{stringValue:C(e,String(t),r)}}default:return{stringValue:C(e,String(t),r)}}}function Dt(e,t,r={}){let n=[];for(let[o,s]of Object.entries(e)){let a=`${t}.${o}`,u=ve(a,s,r);u!==null&&n.push({key:a,value:u})}return n}function Te(e){let t=[{key:"pinta.guard.decision",value:{stringValue:e.decision.toLowerCase()}},{key:"pinta.guard.duration_ms",value:{intValue:e.durationMs}}];return e.reason&&t.push({key:"pinta.guard.matched_rule",value:{stringValue:e.reason}}),e.failOpenReason&&t.push({key:"pinta.guard.fail_open_reason",value:{stringValue:e.failOpenReason}}),t}function Oe(e){return[{key:"pinta.client.rtt_ms",value:Number.isInteger(e.rttMs)?{intValue:e.rttMs}:{doubleValue:e.rttMs}},{key:"pinta.client.op",value:{stringValue:e.op}}]}p.MAX_SPAN_BYTES=800*1024;function he(e){let t=JSON.stringify(e);return t===void 0?0:Buffer.byteLength(t,"utf-8")}var ye=1024;function Se(e,t=p.MAX_SPAN_BYTES){let r=he(e);for(let n=0;n<100&&r>t;n++){let o=null,s=0;for(let c of e.attributes){if(!("stringValue"in c.value))continue;let h=Buffer.byteLength(c.value.stringValue,"utf-8");h>s&&(s=h,o=c.value)}if(o===null||s<=ye)break;let a=r-t,u=Math.max(ye,s-a-64);o.stringValue=(0,Y.truncateTo)(o.stringValue,u),r=he(e)}return e}function Nt(e){let t=e.now??Date.now(),r=(BigInt(t)*1000000n).toString(),n=[...e.attributes];e.guard&&n.push(...Te(e.guard));let o=e.clientTiming??(e.guard?.clientRttMs!==void 0?{op:"guard",rttMs:e.guard.clientRttMs}:null);o&&n.push(...Oe(o));let s={traceId:Pe(e.traceId),spanId:Ee(),name:e.spanName,kind:e.spanKind??1,startTimeUnixNano:r,endTimeUnixNano:r,attributes:n};return Se(s),{resourceSpans:[{resource:{attributes:e.resource},scopeSpans:[{scope:e.scope,spans:[s]}]}]}}function Bt(e){let t=[];for(let r of e)t.push(...r.resourceSpans);return{resourceSpans:t}}});var be=y(z=>{"use strict";Object.defineProperty(z,"__esModule",{value:!0});z.evaluateGuard=$t;var qt=1e4,Lt="pinta-core";function K(e,t){return{decision:"ALLOW",reason:null,userMessage:null,durationMs:e,clientRttMs:e,failOpenReason:t}}var Ut=new Set(["ALLOW","DENY","REVIEW"]);async function $t(e,t,r={}){if(!t||r.disabled)return null;let n=r.timeoutMs??qt,o=Date.now(),s=new AbortController,a=setTimeout(()=>s.abort(),n);try{let u=await fetch(t,{method:"POST",headers:{"content-type":"application/json","user-agent":r.userAgent??Lt,"x-pinta-relay-token":r.token??""},body:JSON.stringify({input:e}),signal:s.signal});if(u.status!==200)return K(Date.now()-o,"error");let c=await u.json(),h=c?.durationMs===void 0||typeof c.durationMs=="number"&&Number.isFinite(c.durationMs);if(!c||!Ut.has(c.decision)||!h)return K(Date.now()-o,"error");let A=Date.now()-o;return{decision:c.decision,reason:c.reason??null,userMessage:c.userMessage??null,durationMs:c.durationMs??A,clientRttMs:A}}catch(u){let c=u.name,h=c==="AbortError"||c==="TimeoutError"?"timeout":"error";return K(Date.now()-o,h)}finally{clearTimeout(a)}}});var j=y(R=>{"use strict";var Ft=R&&R.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(R,"__esModule",{value:!0});R.atomicWriteFileSync=Gt;var xe=Ft(_("fs"));function Gt(e,t){let r=`${e}.${process.pid}.tmp`;xe.default.writeFileSync(r,t),xe.default.renameSync(r,e)}});var Z=y(P=>{"use strict";var ke=P&&P.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(P,"__esModule",{value:!0});P.MemoryRetryQueue=P.DiskRetryQueue=void 0;var m=ke(_("fs")),I=ke(_("path")),Xt=j(),D=1e3,Yt=50,Vt=5,H=class{filePath;lockPath;logPrefix;constructor(t,r){this.filePath=I.default.join(t,"failed-spans.jsonl"),this.lockPath=this.filePath+".lock",this.logPrefix=r}enqueue(t){try{m.default.mkdirSync(I.default.dirname(this.filePath),{recursive:!0});let r=JSON.stringify({savedAt:new Date().toISOString(),payload:t})+`
|
|
3
|
+
`;m.default.appendFileSync(this.filePath,r),this.trim()}catch(r){process.stderr.write(`[${this.logPrefix}] retry-queue enqueue failed: ${r}
|
|
4
|
+
`)}}readAll(){try{let t=m.default.readFileSync(this.filePath,"utf-8"),r=[];for(let n of t.split(`
|
|
5
|
+
`))if(n.trim())try{let o=JSON.parse(n);Array.isArray(o?.payload?.resourceSpans)&&r.push(o)}catch{}return r}catch{return[]}}rewrite(t){try{if(t.length===0){m.default.existsSync(this.filePath)&&m.default.unlinkSync(this.filePath);return}m.default.mkdirSync(I.default.dirname(this.filePath),{recursive:!0}),(0,Xt.atomicWriteFileSync)(this.filePath,t.map(r=>JSON.stringify(r)).join(`
|
|
6
6
|
`)+`
|
|
7
7
|
`)}catch(r){process.stderr.write(`[${this.logPrefix}] retry-queue rewrite failed: ${r}
|
|
8
|
-
`)}}tryAcquireLock(){let t=Date.now();try{
|
|
9
|
-
`),!1}for(;Date.now()-t<
|
|
10
|
-
`),!1;try{let s=
|
|
11
|
-
`),this.rewrite(t.slice(r))}};P.DiskRetryQueue=
|
|
8
|
+
`)}}tryAcquireLock(){let t=Date.now();try{m.default.mkdirSync(I.default.dirname(this.lockPath),{recursive:!0})}catch(r){return process.stderr.write(`[${this.logPrefix}] retry-queue lock mkdir failed: ${r}
|
|
9
|
+
`),!1}for(;Date.now()-t<Yt;)try{let r=m.default.openSync(this.lockPath,"wx");return m.default.writeSync(r,String(process.pid)),m.default.closeSync(r),!0}catch(r){if(r?.code!=="EEXIST")return process.stderr.write(`[${this.logPrefix}] retry-queue lock open failed: ${r}
|
|
10
|
+
`),!1;try{let s=m.default.statSync(this.lockPath);if(Date.now()-s.mtimeMs>3e4){m.default.unlinkSync(this.lockPath);continue}}catch{}let n=Vt,o=Date.now()+n;for(;Date.now()<o;);}return!1}release(){try{m.default.unlinkSync(this.lockPath)}catch{}}trim(){let t=this.readAll();if(t.length<=D)return;let r=t.length-D;process.stderr.write(`[${this.logPrefix}] retry-queue full, dropping ${r} oldest entries
|
|
11
|
+
`),this.rewrite(t.slice(r))}};P.DiskRetryQueue=H;var W=class{entries=[];enqueue(t){this.entries.push(t),this.entries.length>D&&this.entries.splice(0,this.entries.length-D)}drain(){let t=this.entries;return this.entries=[],t}get size(){return this.entries.length}};P.MemoryRetryQueue=W});var je=y(l=>{"use strict";Object.defineProperty(l,"__esModule",{value:!0});l.MemoryTransport=l.DiskTransport=l.MAX_POST_BYTES=void 0;l.parseHeadersEnv=Me;l.envOptionsResolver=Ce;l.postOtlp=w;var Ae=Z(),Re=V(),Kt=5e3;l.MAX_POST_BYTES=900*1024;function N(e){let t=JSON.stringify(e);return t===void 0?0:Buffer.byteLength(t,"utf-8")}function we(e,t,r){let n=[],o=[],s=[],a=0;for(let u of e){let c=t(u);if(c>r){o.push(u);continue}s.length>0&&a+c>r&&(n.push(s),s=[],a=0),s.push(u),a+=c}return s.length>0&&n.push(s),{chunks:n,oversize:o}}function Me(e){if(!e)return{};if(typeof e=="object")return{...e};let t={};for(let r of e.split(",")){let[n,...o]=r.split("=");n&&o.length>0&&(t[n.trim()]=o.join("=").trim())}return t}function Ce(){let e=process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,t=process.env.OTEL_EXPORTER_OTLP_ENDPOINT,r;return e?r=e.replace(/\/+$/,""):t&&(r=t.replace(/\/+$/,"")+"/v1/traces"),r?{endpoint:r,headers:Me(process.env.OTEL_EXPORTER_OTLP_HEADERS)}:null}async function w(e,t,r){let n=new AbortController,o=setTimeout(()=>n.abort(),Kt);try{let s=await fetch(t.endpoint,{method:"POST",headers:{"Content-Type":"application/json",...t.headers},body:JSON.stringify(e),signal:n.signal});if(!s.ok){let a="";try{a=(await s.text()).slice(0,200)}catch{}let u=s.status===401||s.status===403?" \u2014 check OTEL_EXPORTER_OTLP_HEADERS (relay token)":s.status===404?" \u2014 check OTEL_EXPORTER_OTLP_TRACES_ENDPOINT path":s.status>=500?" \u2014 collector may be down":"";return process.stderr.write(`[${r}] OTLP POST ${s.status} ${t.endpoint}${u}${a?` body=${a}`:""}
|
|
12
12
|
`),!1}return!0}catch(s){return process.stderr.write(`[${r}] OTLP POST failed: ${s.message??String(s)}
|
|
13
|
-
`),!1}finally{clearTimeout(o)}}var
|
|
14
|
-
`);let s=[].concat(...n),a=0;for(let u of n){if(!await
|
|
13
|
+
`),!1}finally{clearTimeout(o)}}var J=class{queue;logPrefix;resolveOptions;constructor(t){this.queue=new Ae.DiskRetryQueue(t.pluginData,t.logPrefix),this.logPrefix=t.logPrefix,this.resolveOptions=t.resolveOptions??Ce}async send(t){let r=this.resolveOptions();if(!r||this.dropIfOversized(t))return;await w(t,r,this.logPrefix)||this.queue.enqueue(t)}async flush(){let t=this.resolveOptions();if(t&&this.queue.tryAcquireLock())try{let r=this.queue.readAll();if(r.length===0)return;let{chunks:n,oversize:o}=we(r,u=>N(u.payload),l.MAX_POST_BYTES);o.length>0&&process.stderr.write(`[${this.logPrefix}] dropping ${o.length} oversized retry ${o.length===1?"entry":"entries"} (each > ${l.MAX_POST_BYTES} bytes \u2014 the backend caps the request body, so they can never be delivered)
|
|
14
|
+
`);let s=[].concat(...n),a=0;for(let u of n){if(!await w((0,Re.mergeBatch)(u.map(h=>h.payload)),t,this.logPrefix))break;a+=u.length}this.queue.rewrite(s.slice(a))}catch(r){process.stderr.write(`[${this.logPrefix}] flush failed: ${r?.message??String(r)}
|
|
15
15
|
`)}finally{this.queue.release()}}dropIfOversized(t){let r=N(t);return r<=l.MAX_POST_BYTES?!1:(process.stderr.write(`[${this.logPrefix}] dropping oversized span payload (${r} > ${l.MAX_POST_BYTES} bytes) \u2014 undeliverable, not queued
|
|
16
|
-
`),!0)}};l.DiskTransport=
|
|
17
|
-
`);return}await
|
|
18
|
-
`);let s=[].concat(...n),a=0;for(let u of n){if(!await
|
|
19
|
-
`)}}};
|
|
20
|
-
`)){let n=r.trim();if(!n||n.startsWith("#"))continue;let o=n.indexOf("=");if(o<0)continue;let s=n.slice(0,o).trim(),a=n.slice(o+1).trim();(a.startsWith('"')&&a.endsWith('"')||a.startsWith("'")&&a.endsWith("'"))&&(a=a.slice(1,-1)),s&&(t[s]=a)}return t}function
|
|
21
|
-
`)){let o=n.trim();if(!o||o.startsWith("#"))continue;let s=o.indexOf("=");if(s<0)continue;let a=o.slice(0,s).trim(),u=o.slice(s+1).trim();(u.startsWith('"')&&u.endsWith('"')||u.startsWith("'")&&u.endsWith("'"))&&(u=u.slice(1,-1)),a&&(t[a]=u)}return t}function
|
|
22
|
-
`)}try{
|
|
23
|
-
`)}
|
|
24
|
-
`)
|
|
16
|
+
`),!0)}};l.DiskTransport=J;var Q=class{queue=new Ae.MemoryRetryQueue;logPrefix;resolveOptions;constructor(t){this.logPrefix=t.logPrefix,this.resolveOptions=t.resolveOptions}async send(t){let r=this.resolveOptions();if(!r)return;if(N(t)>l.MAX_POST_BYTES){process.stderr.write(`[${this.logPrefix}] dropping oversized span payload (> ${l.MAX_POST_BYTES} bytes) \u2014 undeliverable, not buffered
|
|
17
|
+
`);return}await w(t,r,this.logPrefix)||this.queue.enqueue(t)}async flush(){let t=this.resolveOptions();if(!t)return;let r=this.queue.drain();if(r.length===0)return;let{chunks:n,oversize:o}=we(r,N,l.MAX_POST_BYTES);o.length>0&&process.stderr.write(`[${this.logPrefix}] dropping ${o.length} oversized buffered ${o.length===1?"payload":"payloads"} (each > ${l.MAX_POST_BYTES} bytes \u2014 undeliverable)
|
|
18
|
+
`);let s=[].concat(...n),a=0;for(let u of n){if(!await w((0,Re.mergeBatch)(u),t,this.logPrefix))break;a+=u.length}for(let u of s.slice(a))this.queue.enqueue(u)}};l.MemoryTransport=Q});var ne=y(T=>{"use strict";var re=T&&T.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(T,"__esModule",{value:!0});T.TraceManager=void 0;T.generateUlid=De;var Ie=re(_("fs")),zt=re(_("path")),Ht=re(_("crypto")),Wt=j(),ee="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function De(){let e=Date.now(),t="",r=e;for(let s=0;s<10;s++)t=ee[r&31]+t,r=Math.floor(r/32);let n=Ht.default.randomBytes(10),o="";for(let s=0;s<10;s++)o+=ee[n[s]&31];for(;o.length<16;)o+=ee[0];return t+o}var te=class{tracePath;constructor(t){this.tracePath=t}newTrace(){let t=De();return this.save(t),t}currentTrace(){try{let t=Ie.default.readFileSync(this.tracePath,"utf-8"),{traceId:r}=JSON.parse(t);if(r)return r}catch{}return this.newTrace()}save(t){try{Ie.default.mkdirSync(zt.default.dirname(this.tracePath),{recursive:!0}),(0,Wt.atomicWriteFileSync)(this.tracePath,JSON.stringify({traceId:t}))}catch(r){process.stderr.write(`[pinta-core] trace persistence failed: ${r}
|
|
19
|
+
`)}}};T.TraceManager=te});var Ue=y(E=>{"use strict";var Be=E&&E.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(E,"__esModule",{value:!0});E.MemorySessionTraceManager=E.DiskSessionTraceManager=void 0;var Ne=Be(_("fs")),Zt=Be(_("path")),qe=ne(),Jt=j(),Le=200,oe=class{tracePath;maxSessions;constructor(t,r={}){this.tracePath=t,this.maxSessions=r.maxSessions??Le}read(){try{let t=JSON.parse(Ne.default.readFileSync(this.tracePath,"utf-8"));if(t&&typeof t=="object"&&!("traceId"in t))return t}catch{}return{}}write(t){try{Ne.default.mkdirSync(Zt.default.dirname(this.tracePath),{recursive:!0});let r=Object.entries(t),n=r.length>this.maxSessions?Object.fromEntries(r.slice(-this.maxSessions)):t;(0,Jt.atomicWriteFileSync)(this.tracePath,JSON.stringify(n))}catch{}}newTrace(t){let r=t||"default",n=(0,qe.generateUlid)(),o=this.read();return o[r]=n,this.write(o),n}currentTrace(t){let r=t||"default",n=this.read()[r];return n||this.newTrace(r)}};E.DiskSessionTraceManager=oe;var se=class{map=new Map;maxSessions;constructor(t={}){this.maxSessions=t.maxSessions??Le}newTrace(t){let r=t||"default",n=(0,qe.generateUlid)();if(this.map.set(r,n),this.map.size>this.maxSessions){let o=this.map.keys().next().value;o!==void 0&&this.map.delete(o)}return n}currentTrace(t){let r=t||"default";return this.map.get(r)??this.newTrace(r)}};E.MemorySessionTraceManager=se});var Ge=y(O=>{"use strict";var ie=O&&O.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(O,"__esModule",{value:!0});O.envFilePath=tr;O.parseEnvFile=Fe;O.loadEnvFile=rr;var Qt=ie(_("node:fs")),er=ie(_("node:os")),$e=ie(_("node:path"));function tr(e,t,r){let n=r?process.env[r]:void 0,o=n&&n.length>0?n:$e.default.join(er.default.homedir(),e);return $e.default.join(o,t)}function Fe(e){let t={};for(let r of e.split(`
|
|
20
|
+
`)){let n=r.trim();if(!n||n.startsWith("#"))continue;let o=n.indexOf("=");if(o<0)continue;let s=n.slice(0,o).trim(),a=n.slice(o+1).trim();(a.startsWith('"')&&a.endsWith('"')||a.startsWith("'")&&a.endsWith("'"))&&(a=a.slice(1,-1)),s&&(t[s]=a)}return t}function rr(e){let t;try{t=Qt.default.readFileSync(e,"utf-8")}catch{return}let r=Fe(t);for(let[n,o]of Object.entries(r))process.env[n]===void 0&&(process.env[n]=o)}});var S=y(i=>{"use strict";Object.defineProperty(i,"__esModule",{value:!0});i.loadEnvFile=i.parseEnvFile=i.envFilePath=i.MemorySessionTraceManager=i.DiskSessionTraceManager=i.generateUlid=i.TraceManager=i.MAX_POST_BYTES=i.parseHeadersEnv=i.envOptionsResolver=i.postOtlp=i.MemoryTransport=i.DiskTransport=i.MemoryRetryQueue=i.DiskRetryQueue=i.evaluateGuard=i.MAX_SPAN_BYTES=i.enforceSpanBudget=i.mergeBatch=i.buildPayload=i.clientAttrs=i.guardAttrs=i.attrsFromRecord=i.toOtlpValue=i.snakeCase=i.newSpanId=i.ulidToTraceId=i.MAX_BYTES=i.PATTERNS=i.applyMatches=i.resolveOverlaps=i.collectMatches=i.truncateTo=i.truncate=i.redact=void 0;var v=X();Object.defineProperty(i,"redact",{enumerable:!0,get:function(){return v.redact}});Object.defineProperty(i,"truncate",{enumerable:!0,get:function(){return v.truncate}});Object.defineProperty(i,"truncateTo",{enumerable:!0,get:function(){return v.truncateTo}});Object.defineProperty(i,"collectMatches",{enumerable:!0,get:function(){return v.collectMatches}});Object.defineProperty(i,"resolveOverlaps",{enumerable:!0,get:function(){return v.resolveOverlaps}});Object.defineProperty(i,"applyMatches",{enumerable:!0,get:function(){return v.applyMatches}});Object.defineProperty(i,"PATTERNS",{enumerable:!0,get:function(){return v.PATTERNS}});Object.defineProperty(i,"MAX_BYTES",{enumerable:!0,get:function(){return v.MAX_BYTES}});var g=V();Object.defineProperty(i,"ulidToTraceId",{enumerable:!0,get:function(){return g.ulidToTraceId}});Object.defineProperty(i,"newSpanId",{enumerable:!0,get:function(){return g.newSpanId}});Object.defineProperty(i,"snakeCase",{enumerable:!0,get:function(){return g.snakeCase}});Object.defineProperty(i,"toOtlpValue",{enumerable:!0,get:function(){return g.toOtlpValue}});Object.defineProperty(i,"attrsFromRecord",{enumerable:!0,get:function(){return g.attrsFromRecord}});Object.defineProperty(i,"guardAttrs",{enumerable:!0,get:function(){return g.guardAttrs}});Object.defineProperty(i,"clientAttrs",{enumerable:!0,get:function(){return g.clientAttrs}});Object.defineProperty(i,"buildPayload",{enumerable:!0,get:function(){return g.buildPayload}});Object.defineProperty(i,"mergeBatch",{enumerable:!0,get:function(){return g.mergeBatch}});Object.defineProperty(i,"enforceSpanBudget",{enumerable:!0,get:function(){return g.enforceSpanBudget}});Object.defineProperty(i,"MAX_SPAN_BYTES",{enumerable:!0,get:function(){return g.MAX_SPAN_BYTES}});var nr=be();Object.defineProperty(i,"evaluateGuard",{enumerable:!0,get:function(){return nr.evaluateGuard}});var Xe=Z();Object.defineProperty(i,"DiskRetryQueue",{enumerable:!0,get:function(){return Xe.DiskRetryQueue}});Object.defineProperty(i,"MemoryRetryQueue",{enumerable:!0,get:function(){return Xe.MemoryRetryQueue}});var k=je();Object.defineProperty(i,"DiskTransport",{enumerable:!0,get:function(){return k.DiskTransport}});Object.defineProperty(i,"MemoryTransport",{enumerable:!0,get:function(){return k.MemoryTransport}});Object.defineProperty(i,"postOtlp",{enumerable:!0,get:function(){return k.postOtlp}});Object.defineProperty(i,"envOptionsResolver",{enumerable:!0,get:function(){return k.envOptionsResolver}});Object.defineProperty(i,"parseHeadersEnv",{enumerable:!0,get:function(){return k.parseHeadersEnv}});Object.defineProperty(i,"MAX_POST_BYTES",{enumerable:!0,get:function(){return k.MAX_POST_BYTES}});var Ye=ne();Object.defineProperty(i,"TraceManager",{enumerable:!0,get:function(){return Ye.TraceManager}});Object.defineProperty(i,"generateUlid",{enumerable:!0,get:function(){return Ye.generateUlid}});var Ve=Ue();Object.defineProperty(i,"DiskSessionTraceManager",{enumerable:!0,get:function(){return Ve.DiskSessionTraceManager}});Object.defineProperty(i,"MemorySessionTraceManager",{enumerable:!0,get:function(){return Ve.MemorySessionTraceManager}});var ae=Ge();Object.defineProperty(i,"envFilePath",{enumerable:!0,get:function(){return ae.envFilePath}});Object.defineProperty(i,"parseEnvFile",{enumerable:!0,get:function(){return ae.parseEnvFile}});Object.defineProperty(i,"loadEnvFile",{enumerable:!0,get:function(){return ae.loadEnvFile}})});import{realpathSync as Dr}from"node:fs";import{pathToFileURL as Nr}from"node:url";var ze=x(S());import Ke from"node:fs";import or from"node:os";import B from"node:path";function sr(e){let t={},r;try{r=Ke.readFileSync(e,"utf-8")}catch{return t}for(let n of r.split(`
|
|
21
|
+
`)){let o=n.trim();if(!o||o.startsWith("#"))continue;let s=o.indexOf("=");if(s<0)continue;let a=o.slice(0,s).trim(),u=o.slice(s+1).trim();(u.startsWith('"')&&u.endsWith('"')||u.startsWith("'")&&u.endsWith("'"))&&(u=u.slice(1,-1)),a&&(t[a]=u)}return t}function ir(e){let t=process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT??e.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT;if(t)return t;let r=process.env.OTEL_EXPORTER_OTLP_ENDPOINT??e.OTEL_EXPORTER_OTLP_ENDPOINT;return r?r.replace(/\/+$/,"")+"/v1/traces":process.env.PINTA_CODEX_ENDPOINT??e.PINTA_CODEX_ENDPOINT??process.env.CLAUDE_PLUGIN_OPTION_ENDPOINT}function ar(e){if(process.env.OTEL_EXPORTER_OTLP_HEADERS)return process.env.OTEL_EXPORTER_OTLP_HEADERS;if(e.OTEL_EXPORTER_OTLP_HEADERS)return e.OTEL_EXPORTER_OTLP_HEADERS;if(process.env.PINTA_CODEX_API_KEY)return`x-pinta-relay-token=${process.env.PINTA_CODEX_API_KEY}`;if(e.PINTA_CODEX_API_KEY)return`x-pinta-relay-token=${e.PINTA_CODEX_API_KEY}`;if(process.env.CLAUDE_PLUGIN_OPTION_API_KEY)return`x-pinta-relay-token=${process.env.CLAUDE_PLUGIN_OPTION_API_KEY}`}function He(){let e=process.env.CODEX_PLUGIN_ROOT??process.env.CLAUDE_PLUGIN_ROOT??process.cwd(),t=process.env.CODEX_PLUGIN_DATA??process.env.CLAUDE_PLUGIN_DATA??B.join(e,".plugin-data"),r=process.env.CODEX_HOME??B.join(or.homedir(),".codex"),n=B.join(r,"pinta-codex.env"),o=Ke.existsSync(n)?sr(n):{},s=ir(o),a=ar(o);if(a&&!process.env.PINTA_RELAY_TOKEN){let c=a.match(/x-pinta-relay-token=([^,\s]+)/i);c&&(process.env.PINTA_RELAY_TOKEN=c[1])}let u=process.env.PINTA_GUARD_ENDPOINT??o.PINTA_GUARD_ENDPOINT;return{pluginRoot:e,pluginData:t,tracePath:B.join(t,"trace.json"),endpoint:s?.replace(/\/+$/,""),headers:(0,ze.parseHeadersEnv)(a??""),guardEndpoint:u}}function We(e){return e.hook_event_name==="PreToolUse"}function Ze(e){return e.hook_event_name==="PermissionRequest"}function Je(e){return e.hook_event_name==="PostToolUse"}function Qe(e){return e.hook_event_name==="UserPromptSubmit"}function et(e){return e.hook_event_name==="SessionStart"}function tt(e){return e.hook_event_name==="Stop"}var ur=["PreToolUse","PermissionRequest"],cr=["PostToolUse","PreCompact","PostCompact","SessionStart","SessionEnd","UserPromptSubmit","SubagentStart","SubagentStop","Stop","Interrupt"],lr=new Set([...ur,...cr]);function rt(e){return!lr.has(e.hook_event_name)}var nt=x(S()),q=class extends nt.DiskTransport{constructor(t){super({pluginData:t.pluginData,logPrefix:"pinta-codex",resolveOptions:()=>t.endpoint?{endpoint:t.endpoint,headers:t.headers}:null})}};var ot=x(S()),L=class extends ot.TraceManager{constructor(t){super(t.tracePath)}};var b=x(S());import $ from"fs";import ce from"os";import pr from"path";var U;function dr(){if(U===void 0)try{U=ce.userInfo().username}catch{U=process.env.USER??process.env.LOGNAME??(typeof process.getuid=="function"?String(process.getuid()):"unknown")}return U}var it="1.8.0",ue;function M(e){return typeof e=="string"&&e.trim()!==""?e.trim():void 0}var st=256*1024;function fr(e){if(!e)return;let t;try{t=$.openSync(e,"r");let r=Buffer.allocUnsafe(st),n=$.readSync(t,r,0,st,0),o=r.toString("utf8",0,n),s=o.indexOf(`
|
|
22
|
+
`);if(s<0)return;let a=JSON.parse(o.slice(0,s));return a?.type!=="session_meta"?void 0:M(a.payload?.cli_version)}catch{return}finally{if(t!==void 0)try{$.closeSync(t)}catch{}}}function mr(){let e=M(process.env.CODEX_MANAGED_PACKAGE_ROOT);if(e)try{let t=JSON.parse($.readFileSync(pr.join(e,"package.json"),"utf8"));return M(t.version)}catch{return}}function _r(e){return ue===void 0&&(ue=fr(M(e?.transcript_path))??mr()??M(process.env.CODEX_CLI_VERSION)??null),ue??void 0}var gr=new Set(["codex.hook","codex.tool_name","codex.tool_use_id","codex.session_id","codex.transcript_path","codex.cwd","codex.permission_mode"]),hr=new Set(["codex.tool_input","codex.tool_response"]),yr={skipRedactKeys:gr,bashContextKeys:hr};function Pr(e){let t=[];t.push({key:"ingest.type",value:{stringValue:"codex"}}),t.push({key:"codex.hook",value:{stringValue:e.hook_event_name}});let r={};for(let[n,o]of Object.entries(e))n!=="hook_event_name"&&(r[n]=o);return t.push(...(0,b.attrsFromRecord)(r,"codex",yr)),t}function Er(e){let t=_r(e);return[{key:"service.name",value:{stringValue:"codex"}},...t?[{key:"service.version",value:{stringValue:t}}]:[],{key:"telemetry.sdk.name",value:{stringValue:"pinta-codex"}},{key:"telemetry.sdk.language",value:{stringValue:"nodejs"}},{key:"telemetry.sdk.version",value:{stringValue:it}},{key:"process.pid",value:{intValue:process.pid}},{key:"process.owner",value:{stringValue:dr()}},{key:"host.name",value:{stringValue:ce.hostname()}},{key:"host.arch",value:{stringValue:ce.arch()}}]}function at(e){return(0,b.buildPayload)({traceId:e.traceId,spanName:`codex.${(0,b.snakeCase)(e.event.hook_event_name)}`,attributes:Pr(e.event),resource:Er(e.event),scope:{name:"pinta-codex",version:it},now:e.now,guard:e.guard})}async function f(e,t,r){let n=new q(t);await n.flush();let o=new L(t),s=r.trace==="new"?o.newTrace():o.currentTrace(),a=at({event:e,traceId:s,guard:r.guard});await n.send(a)}var ut=x(S()),vr=50,Tr="pinta-codex/1.8.0";async function ct(e,t){let r=await(0,ut.evaluateGuard)(e,t,{timeoutMs:vr,token:process.env.PINTA_RELAY_TOKEN??"",disabled:process.env.PINTA_GUARD_DISABLED==="1",userAgent:Tr});if(r===null)return null;let n={decision:r.decision,reason:r.reason,durationMs:r.durationMs,clientRttMs:r.clientRttMs};return r.failOpenReason!==void 0&&(n.failOpenReason=r.failOpenReason),n}async function F(e,t){let r=typeof e.tool_input=="string"?e.tool_input:JSON.stringify(e.tool_input);return ct({spanId:e.session_id??"unknown",toolName:e.tool_name,method:e.hook_event_name,cwd:e.cwd,toolInput:e.tool_input,rawTextFields:{toolInput:r}},t.guardEndpoint)}function G(e){let t=e?.reason;return typeof t=="string"&&t.trim().length>0?t:"guard_deny"}async function lt(e,t){let r=await F(e,t);if(r?.decision==="DENY"){let n={hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:G(r)}};process.stdout.write(JSON.stringify(n)+`
|
|
23
|
+
`)}try{await f(e,t,{trace:"current",guard:r})}catch(n){process.stderr.write(`[pinta-codex] telemetry emit failed: ${n}
|
|
24
|
+
`)}return 0}async function pt(e,t){let r=await F(e,t);if(r?.decision==="DENY"){let n={hookSpecificOutput:{hookEventName:"PermissionRequest",decision:{behavior:"deny",message:G(r)}}};process.stdout.write(JSON.stringify(n)+`
|
|
25
|
+
`)}try{await f(e,t,{trace:"current",guard:r})}catch(n){process.stderr.write(`[pinta-codex] telemetry emit failed: ${n}
|
|
26
|
+
`)}return 0}async function dt(e,t){return await f(e,t,{trace:"current"}),0}async function ft(e,t){return await f(e,t,{trace:"new"}),0}async function mt(e,t){return await f(e,t,{trace:"current"}),0}async function _t(e,t){return await f(e,t,{trace:"current"}),0}async function gt(e,t){return await f(e,t,{trace:"current"}),0}async function ht(e){return 0}async function Or(){let e=[];for await(let t of process.stdin)e.push(t);return Buffer.concat(e).toString("utf-8")}async function yt(){let e=0;try{let t=He(),r=await Or(),n=JSON.parse(r);rt(n)?e=await ht(n):We(n)?e=await lt(n,t):Ze(n)?e=await pt(n,t):Je(n)?e=await dt(n,t):Qe(n)?e=await ft(n,t):et(n)?e=await mt(n,t):tt(n)?e=await _t(n,t):e=await gt(n,t)}catch(t){process.stderr.write(`[pinta-codex] error: ${t}
|
|
27
|
+
`),e=0}return e}var Pt=x(S());import{opendir as Sr,stat as br}from"node:fs/promises";import le from"node:path";var xr="pinta-codex",kr=/^rollout-.+-([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})\.jsonl$/;function Ar(){return(0,Pt.envFilePath)(".codex","sessions","CODEX_HOME")}function Rr(e,t){return le.relative(e,t).split(le.sep).join("/")}function wr(e){return e.endsWith(".jsonl")?"append-log":"rewritten-doc"}function Mr(e){return e.endsWith(".jsonl")?"session-log":"other"}function Cr(e){let t=e.slice(e.lastIndexOf("/")+1);return kr.exec(t)?.[1]}async function Et(){return[Ar()]}async function*vt(e,t){let r;try{r=await Sr(t)}catch{return}try{for await(let n of r){let o=le.join(t,n.name);n.isDirectory()?yield*vt(e,o):n.isFile()&&(yield{absPath:o,relPath:Rr(e,o)})}}catch{return}}async function*jr(e){let[t]=await Et(),r=e.since?.getTime();for await(let{absPath:n,relPath:o}of vt(t,t)){let s;try{s=await br(n)}catch{continue}r!==void 0&&s.mtime.getTime()<=r||(yield{relPath:o,absPath:n,size:s.size,mtime:s.mtime,sessionId:Cr(o),semantics:wr(o)})}}var Ir={id:xr,roots:Et,scan:jr,classify:Mr};function Br(){let e=process.argv[1];if(!e)return!1;try{return import.meta.url===Nr(Dr(e)).href}catch{return!1}}async function qr(){let e=await yt();process.exit(e)}Br()&&qr();export{Ir as lifecycle};
|
package/hooks.json
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
{
|
|
2
2
|
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"hooks": [
|
|
6
|
+
{
|
|
7
|
+
"type": "command",
|
|
8
|
+
"command": "node ${CODEX_PLUGIN_ROOT}/dist/index.js"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"PermissionRequest": [
|
|
14
|
+
{
|
|
15
|
+
"hooks": [
|
|
16
|
+
{
|
|
17
|
+
"type": "command",
|
|
18
|
+
"command": "node ${CODEX_PLUGIN_ROOT}/dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"PostToolUse": [
|
|
24
|
+
{
|
|
25
|
+
"hooks": [
|
|
26
|
+
{
|
|
27
|
+
"type": "command",
|
|
28
|
+
"command": "node ${CODEX_PLUGIN_ROOT}/dist/index.js"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"PreCompact": [
|
|
34
|
+
{
|
|
35
|
+
"hooks": [
|
|
36
|
+
{
|
|
37
|
+
"type": "command",
|
|
38
|
+
"command": "node ${CODEX_PLUGIN_ROOT}/dist/index.js"
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"PostCompact": [
|
|
44
|
+
{
|
|
45
|
+
"hooks": [
|
|
46
|
+
{
|
|
47
|
+
"type": "command",
|
|
48
|
+
"command": "node ${CODEX_PLUGIN_ROOT}/dist/index.js"
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
],
|
|
3
53
|
"SessionStart": [
|
|
4
54
|
{
|
|
5
55
|
"hooks": [
|
|
@@ -10,6 +60,16 @@
|
|
|
10
60
|
]
|
|
11
61
|
}
|
|
12
62
|
],
|
|
63
|
+
"SessionEnd": [
|
|
64
|
+
{
|
|
65
|
+
"hooks": [
|
|
66
|
+
{
|
|
67
|
+
"type": "command",
|
|
68
|
+
"command": "node ${CODEX_PLUGIN_ROOT}/dist/index.js"
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
],
|
|
13
73
|
"UserPromptSubmit": [
|
|
14
74
|
{
|
|
15
75
|
"hooks": [
|
|
@@ -20,7 +80,7 @@
|
|
|
20
80
|
]
|
|
21
81
|
}
|
|
22
82
|
],
|
|
23
|
-
"
|
|
83
|
+
"SubagentStart": [
|
|
24
84
|
{
|
|
25
85
|
"hooks": [
|
|
26
86
|
{
|
|
@@ -30,7 +90,7 @@
|
|
|
30
90
|
]
|
|
31
91
|
}
|
|
32
92
|
],
|
|
33
|
-
"
|
|
93
|
+
"SubagentStop": [
|
|
34
94
|
{
|
|
35
95
|
"hooks": [
|
|
36
96
|
{
|
|
@@ -49,6 +109,16 @@
|
|
|
49
109
|
}
|
|
50
110
|
]
|
|
51
111
|
}
|
|
112
|
+
],
|
|
113
|
+
"Interrupt": [
|
|
114
|
+
{
|
|
115
|
+
"hooks": [
|
|
116
|
+
{
|
|
117
|
+
"type": "command",
|
|
118
|
+
"command": "node ${CODEX_PLUGIN_ROOT}/dist/index.js"
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
}
|
|
52
122
|
]
|
|
53
123
|
}
|
|
54
124
|
}
|
package/package.json
CHANGED
package/tools/_lib.ts
CHANGED
|
@@ -152,18 +152,34 @@ export function migrateLegacyEnvKeys(p: string): string[] {
|
|
|
152
152
|
return renamed;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
// --- TOML: minimal, targeted `[features].
|
|
155
|
+
// --- TOML: minimal, targeted `[features].hooks = true` toggle ---
|
|
156
156
|
|
|
157
157
|
/**
|
|
158
|
-
* Ensure
|
|
159
|
-
*
|
|
158
|
+
* Ensure the hooks feature is enabled in the TOML source.
|
|
159
|
+
*
|
|
160
|
+
* `hooks` is the canonical name and the one written. `codex_hooks` was the
|
|
161
|
+
* pre-0.13x spelling; codex still maps it through `features/src/legacy.rs`, so
|
|
162
|
+
* an existing `codex_hooks = true` is honoured and left alone rather than
|
|
163
|
+
* "upgraded" — rewriting a key the user did not ask us to touch is the kind of
|
|
164
|
+
* edit that loses a config, and both spellings produce the same state.
|
|
165
|
+
*
|
|
166
|
+
* Measured on codex-cli 0.154.0 with an isolated `CODEX_HOME`
|
|
167
|
+
* (`codex features list`):
|
|
168
|
+
*
|
|
169
|
+
* codex_hooks = true → hooks stable true
|
|
170
|
+
* hooks = true → hooks stable true
|
|
171
|
+
* (absent) → hooks stable true
|
|
172
|
+
*
|
|
173
|
+
* The third row is why nothing here is load-bearing any more: the feature
|
|
174
|
+
* reached `stable` and is on by default. Writing it stays as a no-op guard for
|
|
175
|
+
* older codex builds and for a config that explicitly set it `false`.
|
|
160
176
|
*
|
|
161
177
|
* Handles:
|
|
162
178
|
* - no file / empty file → append section
|
|
163
179
|
* - no [features] section → append section
|
|
164
|
-
* - [features] present, key
|
|
165
|
-
* -
|
|
166
|
-
* - key
|
|
180
|
+
* - [features] present, either key already true → no change
|
|
181
|
+
* - `hooks` present with a different value → replace value
|
|
182
|
+
* - neither key present → insert `hooks = true`
|
|
167
183
|
*/
|
|
168
184
|
export function ensureCodexHooksEnabled(content: string): {
|
|
169
185
|
next: string;
|
|
@@ -175,7 +191,7 @@ export function ensureCodexHooksEnabled(content: string): {
|
|
|
175
191
|
const sep = content.length === 0 || content.endsWith("\n") ? "" : "\n";
|
|
176
192
|
const suffix = content.length === 0 ? "" : "\n";
|
|
177
193
|
return {
|
|
178
|
-
next: content + sep + suffix + "[features]\
|
|
194
|
+
next: content + sep + suffix + "[features]\nhooks = true\n",
|
|
179
195
|
changed: true,
|
|
180
196
|
};
|
|
181
197
|
}
|
|
@@ -186,11 +202,16 @@ export function ensureCodexHooksEnabled(content: string): {
|
|
|
186
202
|
const sectionEnd = nextSection ? sectionStart + nextSection.index! : content.length;
|
|
187
203
|
const body = content.slice(sectionStart, sectionEnd);
|
|
188
204
|
|
|
189
|
-
|
|
205
|
+
// An enabled legacy key is an enabled feature. Adding `hooks = true` beside
|
|
206
|
+
// it would leave two keys for one setting, which is how a later `disable`
|
|
207
|
+
// turns off the half the user can see and none of the half that matters.
|
|
208
|
+
if (LEGACY_HOOKS_TRUE_RE.test(body)) return { next: content, changed: false };
|
|
209
|
+
|
|
210
|
+
const keyRe = /^(\s*)hooks\s*=\s*(true|false|"[^"]*"|'[^']*')\s*$/m;
|
|
190
211
|
const keyMatch = body.match(keyRe);
|
|
191
212
|
if (keyMatch) {
|
|
192
213
|
if (keyMatch[2] === "true") return { next: content, changed: false };
|
|
193
|
-
const replaced = body.replace(keyRe, `${keyMatch[1] ?? ""}
|
|
214
|
+
const replaced = body.replace(keyRe, `${keyMatch[1] ?? ""}hooks = true`);
|
|
194
215
|
return {
|
|
195
216
|
next: content.slice(0, sectionStart) + replaced + content.slice(sectionEnd),
|
|
196
217
|
changed: true,
|
|
@@ -198,13 +219,28 @@ export function ensureCodexHooksEnabled(content: string): {
|
|
|
198
219
|
}
|
|
199
220
|
|
|
200
221
|
const sep = body.length === 0 || body.endsWith("\n") ? "" : "\n";
|
|
201
|
-
const insert = `${sep}
|
|
222
|
+
const insert = `${sep}hooks = true\n`;
|
|
202
223
|
return {
|
|
203
224
|
next: content.slice(0, sectionStart) + body + insert + content.slice(sectionEnd),
|
|
204
225
|
changed: true,
|
|
205
226
|
};
|
|
206
227
|
}
|
|
207
228
|
|
|
229
|
+
const LEGACY_HOOKS_TRUE_RE = /^\s*codex_hooks\s*=\s*(true|"true"|'true')\s*$/m;
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* True when the config explicitly enables hooks under either spelling.
|
|
233
|
+
*
|
|
234
|
+
* Reading only `codex_hooks` made this answer `false` for
|
|
235
|
+
* `[features] hooks = true` — the canonical, working configuration — so
|
|
236
|
+
* `doctor` reported "not enabled" and exited 1 on machines whose hooks were
|
|
237
|
+
* firing normally. A health check that fails on a healthy install is worse
|
|
238
|
+
* than no check: it sends people to edit the one file that was already right.
|
|
239
|
+
*
|
|
240
|
+
* `false` no longer means hooks are off. The feature is `stable` and default-on
|
|
241
|
+
* (see `ensureCodexHooksEnabled`), so callers must treat absence as unremarkable
|
|
242
|
+
* rather than as a fault.
|
|
243
|
+
*/
|
|
208
244
|
export function isCodexHooksEnabled(content: string): boolean {
|
|
209
245
|
const sectionHeader = /^\[features\]\s*$/m;
|
|
210
246
|
const match = content.match(sectionHeader);
|
|
@@ -214,8 +250,20 @@ export function isCodexHooksEnabled(content: string): boolean {
|
|
|
214
250
|
const nextSection = rest.match(/\n\[[^\]]+\]\s*$/m);
|
|
215
251
|
const sectionEnd = nextSection ? sectionStart + nextSection.index! : content.length;
|
|
216
252
|
const body = content.slice(sectionStart, sectionEnd);
|
|
217
|
-
|
|
218
|
-
|
|
253
|
+
return /^\s*hooks\s*=\s*(true|"true"|'true')\s*$/m.test(body) || LEGACY_HOOKS_TRUE_RE.test(body);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** True when the config explicitly turns hooks off under either spelling. */
|
|
257
|
+
export function isCodexHooksDisabled(content: string): boolean {
|
|
258
|
+
const sectionHeader = /^\[features\]\s*$/m;
|
|
259
|
+
const match = content.match(sectionHeader);
|
|
260
|
+
if (!match) return false;
|
|
261
|
+
const sectionStart = match.index! + match[0].length;
|
|
262
|
+
const rest = content.slice(sectionStart);
|
|
263
|
+
const nextSection = rest.match(/\n\[[^\]]+\]\s*$/m);
|
|
264
|
+
const sectionEnd = nextSection ? sectionStart + nextSection.index! : content.length;
|
|
265
|
+
const body = content.slice(sectionStart, sectionEnd);
|
|
266
|
+
return /^\s*(?:codex_)?hooks\s*=\s*(false|"false"|'false')\s*$/m.test(body);
|
|
219
267
|
}
|
|
220
268
|
|
|
221
269
|
// --- hooks.json merge ---
|
package/tools/doctor.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
PLUGIN_ENTRY,
|
|
17
17
|
PLUGIN_ROOT,
|
|
18
18
|
countRegisteredEvents,
|
|
19
|
+
isCodexHooksDisabled,
|
|
19
20
|
isCodexHooksEnabled,
|
|
20
21
|
readEnvFile,
|
|
21
22
|
readJson,
|
|
@@ -92,26 +93,43 @@ function checkEnv(): { endpoint?: string; headers?: string } {
|
|
|
92
93
|
return { endpoint, headers };
|
|
93
94
|
}
|
|
94
95
|
|
|
96
|
+
/**
|
|
97
|
+
* The hooks feature, which is no longer something an install has to turn on.
|
|
98
|
+
*
|
|
99
|
+
* Measured on codex-cli 0.154.0 with an isolated `CODEX_HOME`: `hooks` lists as
|
|
100
|
+
* `stable` and effective `true` with nothing in `config.toml` at all. So an
|
|
101
|
+
* absent key means "default", not "broken", and failing on it sent people to
|
|
102
|
+
* edit a file that was already correct.
|
|
103
|
+
*
|
|
104
|
+
* An explicit `false` is the one state worth failing on, because that is a
|
|
105
|
+
* kill switch: every hook stops dispatching and nothing else reports it.
|
|
106
|
+
*/
|
|
95
107
|
function checkCodexHooksFlag(): void {
|
|
96
108
|
let content = "";
|
|
97
109
|
try {
|
|
98
110
|
content = fs.readFileSync(CODEX_CONFIG_PATH, "utf-8");
|
|
99
111
|
} catch {
|
|
100
|
-
|
|
101
|
-
|
|
112
|
+
// Not an error either. codex creates this file on first run, and hooks are
|
|
113
|
+
// on by default whether or not it exists yet.
|
|
114
|
+
warn(
|
|
115
|
+
"hooks feature",
|
|
102
116
|
`${CODEX_CONFIG_PATH} not found`,
|
|
103
|
-
"
|
|
117
|
+
"Hooks default to on; run 'npm run setup' if Codex has never been started.",
|
|
104
118
|
);
|
|
105
119
|
return;
|
|
106
120
|
}
|
|
107
|
-
if (
|
|
108
|
-
pass("codex_hooks flag", "[features].codex_hooks = true");
|
|
109
|
-
} else {
|
|
121
|
+
if (isCodexHooksDisabled(content)) {
|
|
110
122
|
fail(
|
|
111
|
-
"
|
|
112
|
-
"
|
|
113
|
-
"
|
|
123
|
+
"hooks feature",
|
|
124
|
+
"[features] hooks = false — every hook is disabled",
|
|
125
|
+
"Set hooks = true in config.toml, or run 'npm run setup'.",
|
|
114
126
|
);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (isCodexHooksEnabled(content)) {
|
|
130
|
+
pass("hooks feature", "[features] hooks = true");
|
|
131
|
+
} else {
|
|
132
|
+
pass("hooks feature", "default (stable, on)");
|
|
115
133
|
}
|
|
116
134
|
}
|
|
117
135
|
|
package/tools/setup.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 1. Builds dist/ (runs tsc if dist/index.js missing or --rebuild)
|
|
8
8
|
* 2. Prompts for OTLP endpoint & headers (migrates legacy keys if present)
|
|
9
9
|
* 3. Writes them to ~/.codex/pinta-codex.env
|
|
10
|
-
* 4. Enables `
|
|
10
|
+
* 4. Enables `hooks = true` in ~/.codex/config.toml (idempotent; no-op on modern codex)
|
|
11
11
|
* 5. Merges this plugin's hooks into ~/.codex/hooks.json with absolute paths
|
|
12
12
|
* 6. Prints a summary of what to run next
|
|
13
13
|
*
|
|
@@ -122,7 +122,7 @@ async function stepEnv(ask: AskFn): Promise<{ endpoint: string; headers: string
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
async function stepConfigToml(): Promise<void> {
|
|
125
|
-
heading("3. Enable
|
|
125
|
+
heading("3. Enable hooks feature");
|
|
126
126
|
let current = "";
|
|
127
127
|
try {
|
|
128
128
|
current = fs.readFileSync(CODEX_CONFIG_PATH, "utf-8");
|
|
@@ -131,7 +131,7 @@ async function stepConfigToml(): Promise<void> {
|
|
|
131
131
|
}
|
|
132
132
|
const { next, changed } = ensureCodexHooksEnabled(current);
|
|
133
133
|
if (!changed) {
|
|
134
|
-
ok(`
|
|
134
|
+
ok(`hooks feature already enabled in ${CODEX_CONFIG_PATH}`);
|
|
135
135
|
return;
|
|
136
136
|
}
|
|
137
137
|
fs.mkdirSync(CODEX_HOME, { recursive: true });
|