@pinta-ai/pinta-copilot 0.3.0 → 0.4.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/dist/index.js +14 -71
  3. package/dist/tools/doctor.js +4 -81
  4. package/dist/tools/install-hooks.js +3 -101
  5. package/package.json +9 -2
  6. package/dist/core/config.d.ts +0 -15
  7. package/dist/core/config.js +0 -19
  8. package/dist/core/config.js.map +0 -1
  9. package/dist/core/env-bridge.d.ts +0 -25
  10. package/dist/core/env-bridge.js +0 -43
  11. package/dist/core/env-bridge.js.map +0 -1
  12. package/dist/core/guard.d.ts +0 -14
  13. package/dist/core/guard.js +0 -53
  14. package/dist/core/guard.js.map +0 -1
  15. package/dist/core/otlp.d.ts +0 -55
  16. package/dist/core/otlp.js +0 -184
  17. package/dist/core/otlp.js.map +0 -1
  18. package/dist/core/redact.d.ts +0 -57
  19. package/dist/core/redact.js +0 -149
  20. package/dist/core/redact.js.map +0 -1
  21. package/dist/core/retry-queue.d.ts +0 -26
  22. package/dist/core/retry-queue.js +0 -127
  23. package/dist/core/retry-queue.js.map +0 -1
  24. package/dist/core/surface.d.ts +0 -21
  25. package/dist/core/surface.js +0 -13
  26. package/dist/core/surface.js.map +0 -1
  27. package/dist/core/trace.d.ts +0 -20
  28. package/dist/core/trace.js +0 -80
  29. package/dist/core/trace.js.map +0 -1
  30. package/dist/core/transport.d.ts +0 -18
  31. package/dist/core/transport.js +0 -127
  32. package/dist/core/transport.js.map +0 -1
  33. package/dist/core/types.d.ts +0 -49
  34. package/dist/core/types.js +0 -124
  35. package/dist/core/types.js.map +0 -1
  36. package/dist/env-file.d.ts +0 -4
  37. package/dist/env-file.js +0 -69
  38. package/dist/env-file.js.map +0 -1
  39. package/dist/index.d.ts +0 -1
  40. package/dist/index.js.map +0 -1
  41. package/dist/tools/doctor.d.ts +0 -1
  42. package/dist/tools/doctor.js.map +0 -1
  43. package/dist/tools/install-hooks.d.ts +0 -1
  44. package/dist/tools/install-hooks.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,50 @@
2
2
 
3
3
  All notable changes to pinta-copilot are documented here.
4
4
 
5
+ ## [0.4.0] - 2026-07-12
6
+
7
+ ### Changed
8
+
9
+ - **Shared core extraction.** The duplicated low-level utilities (otlp,
10
+ transport, retry-queue, redact, guard, session trace) now come from the
11
+ private `@pinta-ai/core` package; `src/core/*.ts` keeps only thin bindings
12
+ plus the copilot-specific bits (event flattening, surface detection, the
13
+ `envFilePath` override). Core is a devDependency, bundled into `dist/` by
14
+ esbuild at build time, so npmjs consumers never need private-registry access
15
+ and there is no runtime dependency on it.
16
+ - `@pinta-ai/core` bumped `^0.2.0` → `^0.3.0`. The upgrade is additive; no
17
+ adaptor source change was needed.
18
+
19
+ ### Added
20
+
21
+ - `pinta.client.rtt_ms` / `pinta.client.op` span attributes on guarded spans.
22
+ `buildOtlpPayload` already forwards its `guard` result into core's
23
+ `buildPayload`, and core `0.3.0` derives the client-call timing from the
24
+ `GuardResult.clientRttMs` it now measures. The manager can only time its own
25
+ handler, so `clientRttMs - durationMs` gives it the transport overhead.
26
+
27
+ ### Fixed
28
+
29
+ - `package-lock.json` resolved `@pinta-ai/core` as a local link to
30
+ `../pinta-core` while `package.json` declared a version range. `npm ci` did
31
+ not error on the mismatch — it created a dangling symlink to a dev-machine-only
32
+ path and the build then failed `TS2307: Cannot find module '@pinta-ai/core'`,
33
+ breaking pr-validate, build-dist and publish. The lock now pins core's
34
+ GitHub Packages tarball with its integrity hash.
35
+ - `npm publish` would have failed `ENEEDAUTH`: the committed `.npmrc` pointed
36
+ npmjs auth at `${NPM_TOKEN}`, but no such secret exists, so it expanded to an
37
+ empty token instead of falling through to OIDC trusted publishing. The npmjs
38
+ `_authToken` line is gone and `setup-node`'s `registry-url` is back;
39
+ `NODE_AUTH_TOKEN` is now scoped to the `npm ci` step so it is not sent to
40
+ npmjs during publish.
41
+ - `npm` pinned to `11.18.0` in the publish workflow. `npm@latest` is now 12.x,
42
+ which requires node >=22.22 and aborts the node-20 publish job with
43
+ `EBADENGINE`.
44
+ - `tests/core/guard.test.ts` asserted the core `0.2.0` `GuardResult` shape with
45
+ an exhaustive `toEqual` and broke once `clientRttMs` was added. It now matches
46
+ that field by type, since it is a measured wall-clock rather than a value
47
+ echoed from the response body.
48
+
5
49
  ## [0.2.0] - 2026-06-16
6
50
 
7
51
  ### Changed
package/dist/index.js CHANGED
@@ -1,71 +1,14 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- // Load ~/.copilot/pinta-copilot.env BEFORE any other import that may read
4
- // process.env. loadEnvFile only fills in unset keys, so explicit env / hook
5
- // `env` blocks always win (D5).
6
- const env_file_js_1 = require("./env-file.js");
7
- (0, env_file_js_1.loadEnvFile)();
8
- const config_js_1 = require("./core/config.js");
9
- const surface_js_1 = require("./core/surface.js");
10
- const types_js_1 = require("./core/types.js");
11
- const transport_js_1 = require("./core/transport.js");
12
- const trace_js_1 = require("./core/trace.js");
13
- const otlp_js_1 = require("./core/otlp.js");
14
- const guard_js_1 = require("./core/guard.js");
15
- async function readStdin() {
16
- const chunks = [];
17
- for await (const chunk of process.stdin)
18
- chunks.push(chunk);
19
- return Buffer.concat(chunks).toString("utf-8");
20
- }
21
- async function main() {
22
- // ⚠️ CLI preToolUse hooks are FAIL-CLOSED: a non-zero exit / crash / timeout
23
- // DENIES the tool — and a crashing hook bricks the whole agent (report_intent
24
- // / ask_user get blocked too, verified §9.6). Therefore this process MUST
25
- // exit 0 on every path. All work is wrapped; the catch-all stays exit 0.
26
- try {
27
- const config = (0, config_js_1.loadConfig)();
28
- const surface = (0, surface_js_1.detectSurface)();
29
- const raw = await readStdin();
30
- const event = JSON.parse(raw);
31
- const kind = (0, types_js_1.classify)(event);
32
- const sid = (0, types_js_1.sessionId)(event);
33
- const transport = new transport_js_1.Transport(config);
34
- await transport.flush(); // drain retry queue first
35
- const trace = new trace_js_1.TraceManager(config);
36
- // UserPromptSubmit starts a new per-turn trace; everything else reuses it.
37
- const traceId = kind === "UserPromptSubmit" ? trace.newTrace(sid) : trace.currentTrace(sid);
38
- // Guard runs on the two tool-gating events (PreToolUse: all surfaces;
39
- // PermissionRequest: CLI-only). Internal agent tools (report_intent,
40
- // ask_user) are telemetry-only — never guarded (would brick the turn).
41
- const toolNm = (0, types_js_1.toolName)(event);
42
- let guard = null;
43
- if ((0, types_js_1.isGuardEvent)(kind) && !(0, types_js_1.isInternalTool)(toolNm)) {
44
- const ti = (0, types_js_1.toolInput)(event);
45
- guard = await (0, guard_js_1.evaluateGuard)({
46
- spanId: sid ?? "unknown",
47
- toolName: toolNm,
48
- toolInput: ti,
49
- rawTextFields: {
50
- toolInput: typeof ti === "string" ? ti : JSON.stringify(ti ?? null),
51
- },
52
- }, process.env.PINTA_GUARD_ENDPOINT);
53
- }
54
- // Telemetry: one span per event (Bronze flattening, copilot.* prefix).
55
- const payload = (0, otlp_js_1.buildOtlpPayload)({ event, traceId, surface, guard });
56
- await transport.send(payload);
57
- // Enforcement: emit a deny decision in the format the firing event expects.
58
- if (guard?.decision === "DENY") {
59
- const out = (0, types_js_1.formatDeny)(kind, guard.userMessage ?? guard.reason ?? "guard_deny");
60
- if (out)
61
- process.stdout.write(out + "\n");
62
- }
63
- }
64
- catch (err) {
65
- process.stderr.write(`[pinta-copilot] error: ${err}\n`);
66
- // fail-open by design — never block a tool because the adapter crashed.
67
- }
68
- process.exit(0);
69
- }
70
- main();
71
- //# sourceMappingURL=index.js.map
1
+ "use strict";var He=Object.create;var se=Object.defineProperty;var Qe=Object.getOwnPropertyDescriptor;var We=Object.getOwnPropertyNames;var et=Object.getPrototypeOf,tt=Object.prototype.hasOwnProperty;var d=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var rt=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of We(t))!tt.call(e,o)&&o!==r&&se(e,o,{get:()=>t[o],enumerable:!(n=Qe(t,o))||n.enumerable});return e};var _=(e,t,r)=>(r=e!=null?He(et(e)):{},rt(t||!e||!e.__esModule?se(r,"default",{value:e,enumerable:!0}):r,e));var G=d(c=>{"use strict";Object.defineProperty(c,"__esModule",{value:!0});c.PATTERNS=c.MAX_BYTES=void 0;c.truncate=nt;c.collectMatches=ie;c.resolveOverlaps=ae;c.applyMatches=ue;c.redact=ot;c.MAX_BYTES=102400;function nt(e){let t=Buffer.from(e,"utf-8");return t.length<=c.MAX_BYTES?e:`${t.subarray(0,c.MAX_BYTES).toString("utf-8")}\u2026[TRUNCATED:${t.length}]`}c.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 ie(e,t){let r=[];for(let n of c.PATTERNS){if(n.requireContext&&n.requireContext!==t.context)continue;let o=new RegExp(n.regex.source,n.regex.flags),i;for(;(i=o.exec(e))!==null;){let a=n.captureGroup??0,u=i[a];if(u===void 0){i.index===o.lastIndex&&o.lastIndex++;continue}let R=i.index,I=i.index+i[0].length,h=R+i[0].indexOf(u),U=h+u.length;r.push({start:R,end:I,replaceStart:h,replaceEnd:U,type:n.type}),i.index===o.lastIndex&&o.lastIndex++}}return r}function ae(e){let t=[...e].sort((o,i)=>o.start!==i.start?o.start-i.start:i.end-i.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 ue(e,t){let r=[...t].sort((o,i)=>i.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 ot(e,t={}){if(e.length===0)return e;let r=ie(e,t);if(r.length===0)return e;let n=ae(r);return ue(e,n)}});var V=d(p=>{"use strict";var st=p&&p.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(p,"__esModule",{value:!0});p.ulidToTraceId=pe;p.newSpanId=de;p.snakeCase=ut;p.toOtlpValue=fe;p.attrsFromRecord=ct;p.guardAttrs=ge;p.clientAttrs=he;p.buildPayload=lt;p.mergeBatch=pt;var it=st(require("crypto")),ce=G(),at="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=at.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 de(){return it.default.randomBytes(8).toString("hex")}function ut(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z])([A-Z][a-z])/g,"$1_$2").toLowerCase()}var le=new Set;function x(e,t,r){let n=(0,ce.truncate)(t);if((r.skipRedactKeys??le).has(e))return n;let o=(r.bashContextKeys??le).has(e)?"bash":void 0;return(0,ce.redact)(n,{context:o})}function fe(e,t,r={}){if(t==null)return null;switch(typeof t){case"string":return{stringValue:x(e,t,r)};case"boolean":return{boolValue:t};case"number":return Number.isInteger(t)?{intValue:t}:{doubleValue:t};case"object":try{return{stringValue:x(e,JSON.stringify(t),r)}}catch{return{stringValue:x(e,String(t),r)}}default:return{stringValue:x(e,String(t),r)}}}function ct(e,t,r={}){let n=[];for(let[o,i]of Object.entries(e)){let a=`${t}.${o}`,u=fe(a,i,r);u!==null&&n.push({key:a,value:u})}return n}function ge(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 he(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}}]}function lt(e){let t=e.now??Date.now(),r=(BigInt(t)*1000000n).toString(),n=[...e.attributes];e.guard&&n.push(...ge(e.guard));let o=e.clientTiming??(e.guard?.clientRttMs!==void 0?{op:"guard",rttMs:e.guard.clientRttMs}:null);o&&n.push(...he(o));let i={traceId:pe(e.traceId),spanId:de(),name:e.spanName,kind:e.spanKind??1,startTimeUnixNano:r,endTimeUnixNano:r,attributes:n};return{resourceSpans:[{resource:{attributes:e.resource},scopeSpans:[{scope:e.scope,spans:[i]}]}]}}function pt(e){let t=[];for(let r of e)t.push(...r.resourceSpans);return{resourceSpans:t}}});var ye=d($=>{"use strict";Object.defineProperty($,"__esModule",{value:!0});$.evaluateGuard=ht;var dt=1e4,ft="pinta-core";function _e(e,t){return{decision:"ALLOW",reason:null,userMessage:null,durationMs:e,clientRttMs:e,failOpenReason:t}}function gt(e){return new Promise((t,r)=>setTimeout(()=>{let n=new Error("Guard request timed out");n.name="TimeoutError",r(n)},e))}async function ht(e,t,r={}){if(!t||r.disabled)return null;let n=r.timeoutMs??dt,o=Date.now();try{let i=await Promise.race([fetch(t,{method:"POST",headers:{"content-type":"application/json","user-agent":r.userAgent??ft,"x-pinta-relay-token":r.token??""},body:JSON.stringify({input:e})}),gt(n)]);if(i.status!==200)return _e(Date.now()-o,"error");let a=await i.json(),u=Date.now()-o;return{decision:a.decision,reason:a.reason,userMessage:a.userMessage??null,durationMs:a.durationMs??u,clientRttMs:u}}catch(i){let a=i.name==="TimeoutError"?"timeout":"error";return _e(Date.now()-o,a)}}});var Z=d(y=>{"use strict";var me=y&&y.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(y,"__esModule",{value:!0});y.MemoryRetryQueue=y.DiskRetryQueue=void 0;var l=me(require("fs")),M=me(require("path")),N=1e3,_t=50,yt=5,K=class{filePath;lockPath;logPrefix;constructor(t,r){this.filePath=M.default.join(t,"failed-spans.jsonl"),this.lockPath=this.filePath+".lock",this.logPrefix=r}enqueue(t){try{l.default.mkdirSync(M.default.dirname(this.filePath),{recursive:!0});let r=JSON.stringify({savedAt:new Date().toISOString(),payload:t})+`
2
+ `;l.default.appendFileSync(this.filePath,r),this.trim()}catch(r){process.stderr.write(`[${this.logPrefix}] retry-queue enqueue failed: ${r}
3
+ `)}}readAll(){try{let t=l.default.readFileSync(this.filePath,"utf-8"),r=[];for(let n of t.split(`
4
+ `))if(n.trim())try{r.push(JSON.parse(n))}catch{}return r}catch{return[]}}rewrite(t){try{if(t.length===0){l.default.existsSync(this.filePath)&&l.default.unlinkSync(this.filePath);return}l.default.mkdirSync(M.default.dirname(this.filePath),{recursive:!0}),l.default.writeFileSync(this.filePath,t.map(r=>JSON.stringify(r)).join(`
5
+ `)+`
6
+ `)}catch(r){process.stderr.write(`[${this.logPrefix}] retry-queue rewrite failed: ${r}
7
+ `)}}tryAcquireLock(){let t=Date.now();for(l.default.mkdirSync(M.default.dirname(this.lockPath),{recursive:!0});Date.now()-t<_t;)try{let r=l.default.openSync(this.lockPath,"wx");return l.default.writeSync(r,String(process.pid)),l.default.closeSync(r),!0}catch(r){if(r?.code!=="EEXIST")return process.stderr.write(`[${this.logPrefix}] retry-queue lock open failed: ${r}
8
+ `),!1;try{let i=l.default.statSync(this.lockPath);if(Date.now()-i.mtimeMs>3e4){l.default.unlinkSync(this.lockPath);continue}}catch{}let n=yt,o=Date.now()+n;for(;Date.now()<o;);}return!1}release(){try{l.default.unlinkSync(this.lockPath)}catch{}}trim(){let t=this.readAll();if(t.length<=N)return;let r=t.length-N;process.stderr.write(`[${this.logPrefix}] retry-queue full, dropping ${r} oldest entries
9
+ `),this.rewrite(t.slice(r))}};y.DiskRetryQueue=K;var B=class{entries=[];enqueue(t){this.entries.push(t),this.entries.length>N&&this.entries.splice(0,this.entries.length-N)}drain(){let t=this.entries;return this.entries=[],t}get size(){return this.entries.length}};y.MemoryRetryQueue=B});var ve=d(f=>{"use strict";Object.defineProperty(f,"__esModule",{value:!0});f.MemoryTransport=f.DiskTransport=void 0;f.parseHeadersEnv=Te;f.envOptionsResolver=Se;f.postOtlp=k;var Pe=Z(),Oe=V(),mt=5e3;function Te(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 Se(){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:Te(process.env.OTEL_EXPORTER_OTLP_HEADERS)}:null}async function k(e,t,r){let n=new AbortController,o=setTimeout(()=>n.abort(),mt);try{let i=await fetch(t.endpoint,{method:"POST",headers:{"Content-Type":"application/json",...t.headers},body:JSON.stringify(e),signal:n.signal});if(!i.ok){let a="";try{a=(await i.text()).slice(0,200)}catch{}let u=i.status===401||i.status===403?" \u2014 check OTEL_EXPORTER_OTLP_HEADERS (relay token)":i.status===404?" \u2014 check OTEL_EXPORTER_OTLP_TRACES_ENDPOINT path":i.status>=500?" \u2014 collector may be down":"";return process.stderr.write(`[${r}] OTLP POST ${i.status} ${t.endpoint}${u}${a?` body=${a}`:""}
10
+ `),!1}return!0}catch(i){return process.stderr.write(`[${r}] OTLP POST failed: ${i.message??String(i)}
11
+ `),!1}finally{clearTimeout(o)}}var X=class{queue;logPrefix;resolveOptions;constructor(t){this.queue=new Pe.DiskRetryQueue(t.pluginData,t.logPrefix),this.logPrefix=t.logPrefix,this.resolveOptions=t.resolveOptions??Se}async send(t){let r=this.resolveOptions();if(!r)return;await k(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 n=(0,Oe.mergeBatch)(r.map(i=>i.payload));await k(n,t,this.logPrefix)&&this.queue.rewrite([])}finally{this.queue.release()}}};f.DiskTransport=X;var J=class{queue=new Pe.MemoryRetryQueue;logPrefix;resolveOptions;constructor(t){this.logPrefix=t.logPrefix,this.resolveOptions=t.resolveOptions}async send(t){let r=this.resolveOptions();if(!r)return;await k(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 n=(0,Oe.mergeBatch)(r);if(!await k(n,t,this.logPrefix))for(let i of r)this.queue.enqueue(i)}};f.MemoryTransport=J});var W=d(O=>{"use strict";var Q=O&&O.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(O,"__esModule",{value:!0});O.TraceManager=void 0;O.generateUlid=Ee;var Y=Q(require("fs")),Pt=Q(require("path")),Ot=Q(require("crypto")),z="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function Ee(){let e=Date.now(),t="",r=e;for(let i=0;i<10;i++)t=z[r&31]+t,r=Math.floor(r/32);let n=Ot.default.randomBytes(10),o="";for(let i=0;i<10;i++)o+=z[n[i]&31];for(;o.length<16;)o+=z[0];return t+o}var H=class{tracePath;constructor(t){this.tracePath=t}newTrace(){let t=Ee();return this.save(t),t}currentTrace(){try{let t=Y.default.readFileSync(this.tracePath,"utf-8"),{traceId:r}=JSON.parse(t);if(r)return r}catch{}return this.newTrace()}save(t){Y.default.mkdirSync(Pt.default.dirname(this.tracePath),{recursive:!0}),Y.default.writeFileSync(this.tracePath,JSON.stringify({traceId:t}))}};O.TraceManager=H});var Ae=d(m=>{"use strict";var be=m&&m.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(m,"__esModule",{value:!0});m.MemorySessionTraceManager=m.DiskSessionTraceManager=void 0;var ee=be(require("fs")),Tt=be(require("path")),ke=W(),we=200,te=class{tracePath;maxSessions;constructor(t,r={}){this.tracePath=t,this.maxSessions=r.maxSessions??we}read(){try{let t=JSON.parse(ee.default.readFileSync(this.tracePath,"utf-8"));if(t&&typeof t=="object"&&!("traceId"in t))return t}catch{}return{}}write(t){try{ee.default.mkdirSync(Tt.default.dirname(this.tracePath),{recursive:!0});let r=Object.entries(t),n=r.length>this.maxSessions?Object.fromEntries(r.slice(-this.maxSessions)):t;ee.default.writeFileSync(this.tracePath,JSON.stringify(n))}catch{}}newTrace(t){let r=t||"default",n=(0,ke.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)}};m.DiskSessionTraceManager=te;var re=class{map=new Map;maxSessions;constructor(t={}){this.maxSessions=t.maxSessions??we}newTrace(t){let r=t||"default",n=(0,ke.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)}};m.MemorySessionTraceManager=re});var xe=d(T=>{"use strict";var ne=T&&T.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(T,"__esModule",{value:!0});T.envFilePath=Et;T.parseEnvFile=Ie;T.loadEnvFile=bt;var St=ne(require("node:fs")),vt=ne(require("node:os")),Re=ne(require("node:path"));function Et(e,t,r){let n=r?process.env[r]:void 0,o=n&&n.length>0?n:Re.default.join(vt.default.homedir(),e);return Re.default.join(o,t)}function Ie(e){let t={};for(let r of e.split(`
12
+ `)){let n=r.trim();if(!n||n.startsWith("#"))continue;let o=n.indexOf("=");if(o<0)continue;let i=n.slice(0,o).trim(),a=n.slice(o+1).trim();(a.startsWith('"')&&a.endsWith('"')||a.startsWith("'")&&a.endsWith("'"))&&(a=a.slice(1,-1)),i&&(t[i]=a)}return t}function bt(e){let t;try{t=St.default.readFileSync(e,"utf-8")}catch{return}let r=Ie(t);for(let[n,o]of Object.entries(r))process.env[n]===void 0&&(process.env[n]=o)}});var b=d(s=>{"use strict";Object.defineProperty(s,"__esModule",{value:!0});s.loadEnvFile=s.parseEnvFile=s.envFilePath=s.MemorySessionTraceManager=s.DiskSessionTraceManager=s.generateUlid=s.TraceManager=s.parseHeadersEnv=s.envOptionsResolver=s.postOtlp=s.MemoryTransport=s.DiskTransport=s.MemoryRetryQueue=s.DiskRetryQueue=s.evaluateGuard=s.mergeBatch=s.buildPayload=s.clientAttrs=s.guardAttrs=s.attrsFromRecord=s.toOtlpValue=s.snakeCase=s.newSpanId=s.ulidToTraceId=s.MAX_BYTES=s.PATTERNS=s.applyMatches=s.resolveOverlaps=s.collectMatches=s.truncate=s.redact=void 0;var S=G();Object.defineProperty(s,"redact",{enumerable:!0,get:function(){return S.redact}});Object.defineProperty(s,"truncate",{enumerable:!0,get:function(){return S.truncate}});Object.defineProperty(s,"collectMatches",{enumerable:!0,get:function(){return S.collectMatches}});Object.defineProperty(s,"resolveOverlaps",{enumerable:!0,get:function(){return S.resolveOverlaps}});Object.defineProperty(s,"applyMatches",{enumerable:!0,get:function(){return S.applyMatches}});Object.defineProperty(s,"PATTERNS",{enumerable:!0,get:function(){return S.PATTERNS}});Object.defineProperty(s,"MAX_BYTES",{enumerable:!0,get:function(){return S.MAX_BYTES}});var g=V();Object.defineProperty(s,"ulidToTraceId",{enumerable:!0,get:function(){return g.ulidToTraceId}});Object.defineProperty(s,"newSpanId",{enumerable:!0,get:function(){return g.newSpanId}});Object.defineProperty(s,"snakeCase",{enumerable:!0,get:function(){return g.snakeCase}});Object.defineProperty(s,"toOtlpValue",{enumerable:!0,get:function(){return g.toOtlpValue}});Object.defineProperty(s,"attrsFromRecord",{enumerable:!0,get:function(){return g.attrsFromRecord}});Object.defineProperty(s,"guardAttrs",{enumerable:!0,get:function(){return g.guardAttrs}});Object.defineProperty(s,"clientAttrs",{enumerable:!0,get:function(){return g.clientAttrs}});Object.defineProperty(s,"buildPayload",{enumerable:!0,get:function(){return g.buildPayload}});Object.defineProperty(s,"mergeBatch",{enumerable:!0,get:function(){return g.mergeBatch}});var kt=ye();Object.defineProperty(s,"evaluateGuard",{enumerable:!0,get:function(){return kt.evaluateGuard}});var Me=Z();Object.defineProperty(s,"DiskRetryQueue",{enumerable:!0,get:function(){return Me.DiskRetryQueue}});Object.defineProperty(s,"MemoryRetryQueue",{enumerable:!0,get:function(){return Me.MemoryRetryQueue}});var w=ve();Object.defineProperty(s,"DiskTransport",{enumerable:!0,get:function(){return w.DiskTransport}});Object.defineProperty(s,"MemoryTransport",{enumerable:!0,get:function(){return w.MemoryTransport}});Object.defineProperty(s,"postOtlp",{enumerable:!0,get:function(){return w.postOtlp}});Object.defineProperty(s,"envOptionsResolver",{enumerable:!0,get:function(){return w.envOptionsResolver}});Object.defineProperty(s,"parseHeadersEnv",{enumerable:!0,get:function(){return w.parseHeadersEnv}});var Ne=W();Object.defineProperty(s,"TraceManager",{enumerable:!0,get:function(){return Ne.TraceManager}});Object.defineProperty(s,"generateUlid",{enumerable:!0,get:function(){return Ne.generateUlid}});var De=Ae();Object.defineProperty(s,"DiskSessionTraceManager",{enumerable:!0,get:function(){return De.DiskSessionTraceManager}});Object.defineProperty(s,"MemorySessionTraceManager",{enumerable:!0,get:function(){return De.MemorySessionTraceManager}});var oe=xe();Object.defineProperty(s,"envFilePath",{enumerable:!0,get:function(){return oe.envFilePath}});Object.defineProperty(s,"parseEnvFile",{enumerable:!0,get:function(){return oe.parseEnvFile}});Object.defineProperty(s,"loadEnvFile",{enumerable:!0,get:function(){return oe.loadEnvFile}})});var A=_(b());function wt(){return(0,A.envFilePath)(".copilot","pinta-copilot.env","COPILOT_HOME")}function je(e=wt()){(0,A.loadEnvFile)(e)}var Ce=_(require("os")),D=_(require("path"));function At(){return process.env.COPILOT_HOME||D.default.join(Ce.default.homedir(),".copilot")}function qe(){let e=process.env.COPILOT_PLUGIN_DATA||D.default.join(At(),"pinta-copilot-data");return{pluginData:e,tracePath:D.default.join(e,"trace.json")}}function Le(e=process.env){return e.COPILOT_AGENT_JOB_ID||e.COPILOT_AGENT_SESSION_ID||e.COPILOT_AGENT_PROMPT?"cloud":e.ELECTRON_RUN_AS_NODE||e.VSCODE_IPC_HOOK||e.VSCODE_PID?"ext":"cli"}function v(e){return typeof e=="string"?e:void 0}function j(e){return v(e.hook_event_name)??v(e.hookEventName)??v(e.hookName)??(process.env.PINTA_COPILOT_EVENT||void 0)}function Fe(e){return v(e.session_id)??v(e.sessionId)}function Ue(e){return v(e.tool_name)??v(e.toolName)}function Ge(e){return e.tool_input??e.toolArgs??e.toolInput}var Rt={sessionstart:"SessionStart",sessionend:"SessionEnd",userpromptsubmit:"UserPromptSubmit",userpromptsubmitted:"UserPromptSubmit",pretooluse:"PreToolUse",posttooluse:"PostToolUse",posttoolusefailure:"PostToolUseFailure",stop:"Stop",agentstop:"Stop",subagentstart:"SubagentStart",subagentstop:"SubagentStop",precompact:"PreCompact",notification:"Notification",permissionrequest:"PermissionRequest",erroroccurred:"ErrorOccurred"};function Ve(e){let t=j(e);return t?Rt[t.toLowerCase()]??"Unknown":"Unknown"}function $e(e){return e==="PreToolUse"||e==="PermissionRequest"}var It=new Set(["report_intent","ask_user"]);function Ke(e){return e!==void 0&&It.has(e)}function Be(e,t){return e==="PreToolUse"?JSON.stringify({hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:t}}):e==="PermissionRequest"?JSON.stringify({behavior:"deny",message:t}):null}var q=_(b());function xt(){let e=process.env.COPILOT_PLUGIN_OPTION_ENDPOINT||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:(0,q.parseHeadersEnv)(process.env.COPILOT_PLUGIN_OPTION_HEADERS||process.env.OTEL_EXPORTER_OTLP_HEADERS)}:null}var C=class extends q.DiskTransport{constructor(t){super({pluginData:t.pluginData,logPrefix:"pinta-copilot",resolveOptions:xt})}};var Ze=_(b()),L=class extends Ze.DiskSessionTraceManager{constructor(t){super(t.tracePath)}};var F=_(require("os")),E=_(b());var Xe="0.4.0";function Mt(){return process.env.COPILOT_CLI_VERSION||"unknown"}var Nt=new Set(["copilot.hook","copilot.tool_name","copilot.toolName","copilot.tool_use_id","copilot.toolUseId","copilot.session_id","copilot.sessionId","copilot.transcript_path","copilot.transcriptPath","copilot.cwd","copilot.permission_mode","copilot.surface","copilot.agent_id","copilot.agent_type","copilot.agent_name","copilot.agent_display_name","copilot.stop_reason","copilot.notification_type"]),Dt=new Set(["copilot.tool_input","copilot.toolInput","copilot.tool_response","copilot.tool_result"]),jt={skipRedactKeys:Nt,bashContextKeys:Dt},Ct=new Set(["hook_event_name","hookEventName","hookName"]);function qt(e,t){let r=Object.fromEntries(Object.entries(e).filter(([n])=>!Ct.has(n)));return[{key:"ingest.type",value:{stringValue:"copilot"}},{key:"copilot.hook",value:{stringValue:j(e)??"unknown"}},{key:"copilot.surface",value:{stringValue:t}},...(0,E.attrsFromRecord)(r,"copilot",jt)]}function Lt(){return[{key:"service.name",value:{stringValue:"copilot"}},{key:"service.version",value:{stringValue:Mt()}},{key:"telemetry.sdk.name",value:{stringValue:"pinta-copilot"}},{key:"telemetry.sdk.language",value:{stringValue:"nodejs"}},{key:"telemetry.sdk.version",value:{stringValue:Xe}},{key:"process.pid",value:{intValue:process.pid}},{key:"process.owner",value:{stringValue:F.default.userInfo().username}},{key:"host.name",value:{stringValue:F.default.hostname()}},{key:"host.arch",value:{stringValue:F.default.arch()}}]}function Je(e){return(0,E.buildPayload)({traceId:e.traceId,spanName:`copilot.${(0,E.snakeCase)(j(e.event)??"unknown")}`,attributes:qt(e.event,e.surface),resource:Lt(),scope:{name:"pinta-copilot",version:Xe},now:e.now,guard:e.guard})}var Ye=_(b());function Ft(){return Number(process.env.PINTA_GUARD_TIMEOUT_MS)||50}var Ut="pinta-copilot/0.4.0";function ze(e,t){return(0,Ye.evaluateGuard)(e,t,{timeoutMs:Ft(),token:process.env.PINTA_RELAY_TOKEN??"",disabled:process.env.PINTA_GUARD_DISABLED==="1",userAgent:Ut})}je();async function Gt(){let e=[];for await(let t of process.stdin)e.push(t);return Buffer.concat(e).toString("utf-8")}async function Vt(){try{let e=qe(),t=Le(),r=await Gt(),n=JSON.parse(r),o=Ve(n),i=Fe(n),a=new C(e);await a.flush();let u=new L(e),R=o==="UserPromptSubmit"?u.newTrace(i):u.currentTrace(i),I=Ue(n),h=null;if($e(o)&&!Ke(I)){let P=Ge(n);h=await ze({spanId:i??"unknown",toolName:I,toolInput:P,rawTextFields:{toolInput:typeof P=="string"?P:JSON.stringify(P??null)}},process.env.PINTA_GUARD_ENDPOINT)}let U=Je({event:n,traceId:R,surface:t,guard:h});if(await a.send(U),h?.decision==="DENY"){let P=Be(o,h.userMessage??h.reason??"guard_deny");P&&process.stdout.write(P+`
13
+ `)}}catch(e){process.stderr.write(`[pinta-copilot] error: ${e}
14
+ `)}process.exit(0)}Vt();
@@ -1,81 +1,4 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- /**
7
- * doctor — read-only health check for a pinta-copilot install.
8
- *
9
- * node dist/tools/doctor.js (or: npm run doctor)
10
- *
11
- * Verifies the hook file, env file / OTel endpoint, and the built adapter.
12
- * Exits 1 if anything required is missing; 0 if healthy.
13
- */
14
- const node_fs_1 = __importDefault(require("node:fs"));
15
- const node_os_1 = __importDefault(require("node:os"));
16
- const node_path_1 = __importDefault(require("node:path"));
17
- const surface_js_1 = require("../core/surface.js");
18
- function copilotHome() {
19
- return process.env.COPILOT_HOME || node_path_1.default.join(node_os_1.default.homedir(), ".copilot");
20
- }
21
- const checks = [];
22
- function check(required, ok, label, detail) {
23
- checks.push({ ok, label, detail, required });
24
- }
25
- const home = copilotHome();
26
- const hookFile = node_path_1.default.join(home, "hooks", "pinta-copilot.json");
27
- const envFile = node_path_1.default.join(home, "pinta-copilot.env");
28
- const adapter = node_path_1.default.join(node_path_1.default.dirname(node_path_1.default.resolve(process.argv[1])), "..", "index.js");
29
- // 1. adapter built
30
- check(true, node_fs_1.default.existsSync(adapter), "adapter built (dist/index.js)", adapter);
31
- // 2. hook file installed + points at this adapter
32
- let hookOk = false;
33
- let hookDetail = `${hookFile} (missing — run \`npm run install-hooks\`)`;
34
- try {
35
- const j = JSON.parse(node_fs_1.default.readFileSync(hookFile, "utf-8"));
36
- const cmd = j?.hooks?.PreToolUse?.[0]?.command ?? "";
37
- const events = Object.keys(j?.hooks ?? {}).length;
38
- hookOk = cmd.includes("index.js");
39
- hookDetail = `${hookFile} (${events} events)`;
40
- }
41
- catch {
42
- /* missing */
43
- }
44
- check(true, hookOk, "hook file installed", hookDetail);
45
- // 3. env file (optional) + OTel endpoint configured (required for telemetry)
46
- const hasEnvFile = node_fs_1.default.existsSync(envFile);
47
- check(false, hasEnvFile, "env file present", hasEnvFile ? envFile : `${envFile} (none — config may come from process.env)`);
48
- let endpoint = process.env.COPILOT_PLUGIN_OPTION_ENDPOINT ||
49
- process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||
50
- process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
51
- if (!endpoint && hasEnvFile) {
52
- const m = node_fs_1.default
53
- .readFileSync(envFile, "utf-8")
54
- .match(/(?:COPILOT_PLUGIN_OPTION_ENDPOINT|OTEL_EXPORTER_OTLP_(?:TRACES_)?ENDPOINT)=(.+)/);
55
- if (m)
56
- endpoint = m[1].trim();
57
- }
58
- check(true, Boolean(endpoint), "traces endpoint configured", endpoint || "(unset — spans will be dropped)");
59
- // 4. guard endpoint (optional)
60
- let guardEp = process.env.PINTA_GUARD_ENDPOINT;
61
- if (!guardEp && hasEnvFile) {
62
- const m = node_fs_1.default.readFileSync(envFile, "utf-8").match(/PINTA_GUARD_ENDPOINT=(.+)/);
63
- if (m)
64
- guardEp = m[1].trim();
65
- }
66
- check(false, Boolean(guardEp), "guard endpoint (optional)", guardEp || "(none — telemetry only, no allow/deny)");
67
- // --- report ---
68
- let failed = 0;
69
- for (const c of checks) {
70
- const mark = c.ok ? "✅" : c.required ? "❌" : "⚠️ ";
71
- if (!c.ok && c.required)
72
- failed++;
73
- // eslint-disable-next-line no-console
74
- console.log(`${mark} ${c.label}${c.detail ? ` — ${c.detail}` : ""}`);
75
- }
76
- // eslint-disable-next-line no-console
77
- console.log(`\nsurface (this shell): ${(0, surface_js_1.detectSurface)()}`);
78
- // eslint-disable-next-line no-console
79
- console.log(failed === 0 ? "\nhealthy ✅" : `\n${failed} required check(s) failed ❌`);
80
- process.exit(failed === 0 ? 0 : 1);
81
- //# sourceMappingURL=doctor.js.map
1
+ "use strict";var k=Object.create;var _=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var x=Object.getPrototypeOf,R=Object.prototype.hasOwnProperty;var j=(e,o,n,l)=>{if(o&&typeof o=="object"||typeof o=="function")for(let r of C(o))!R.call(e,r)&&r!==n&&_(e,r,{get:()=>o[r],enumerable:!(l=L(o,r))||l.enumerable});return e};var a=(e,o,n)=>(n=e!=null?k(x(e)):{},j(o||!e||!e.__esModule?_(n,"default",{value:e,enumerable:!0}):n,e));var s=a(require("node:fs")),t=a(require("node:path"));function P(e=process.env){return e.COPILOT_AGENT_JOB_ID||e.COPILOT_AGENT_SESSION_ID||e.COPILOT_AGENT_PROMPT?"cloud":e.ELECTRON_RUN_AS_NODE||e.VSCODE_IPC_HOOK||e.VSCODE_PID?"ext":"cli"}var T=a(require("os")),E=a(require("path"));function m(){return process.env.COPILOT_HOME||E.default.join(T.default.homedir(),".copilot")}var h=[];function c(e,o,n,l){h.push({ok:o,label:n,detail:l,required:e})}function I(e,o){let n=s.default.readFileSync(e,"utf-8").match(o);return n?n[1].trim():void 0}var g=m(),u=t.default.join(g,"hooks","pinta-copilot.json"),i=t.default.join(g,"pinta-copilot.env"),N=t.default.join(t.default.dirname(t.default.resolve(process.argv[1])),"..","index.js");c(!0,s.default.existsSync(N),"adapter built (dist/index.js)",N);var D=!1,S=`${u} (missing \u2014 run \`npm run install-hooks\`)`;try{let e=JSON.parse(s.default.readFileSync(u,"utf-8")),o=e?.hooks?.PreToolUse?.[0]?.command??"",n=Object.keys(e?.hooks??{}).length;D=o.includes("index.js"),S=`${u} (${n} events)`}catch{}c(!0,D,"hook file installed",S);var p=s.default.existsSync(i);c(!1,p,"env file present",p?i:`${i} (none \u2014 config may come from process.env)`);var O=process.env.COPILOT_PLUGIN_OPTION_ENDPOINT||process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT||process.env.OTEL_EXPORTER_OTLP_ENDPOINT;!O&&p&&(O=I(i,/(?:COPILOT_PLUGIN_OPTION_ENDPOINT|OTEL_EXPORTER_OTLP_(?:TRACES_)?ENDPOINT)=(.+)/));c(!0,!!O,"traces endpoint configured",O||"(unset \u2014 spans will be dropped)");var d=process.env.PINTA_GUARD_ENDPOINT;!d&&p&&(d=I(i,/PINTA_GUARD_ENDPOINT=(.+)/));c(!1,!!d,"guard endpoint (optional)",d||"(none \u2014 telemetry only, no allow/deny)");var f=0;for(let e of h){let o=e.ok?"\u2705":e.required?"\u274C":"\u26A0\uFE0F ";!e.ok&&e.required&&f++,console.log(`${o} ${e.label}${e.detail?` \u2014 ${e.detail}`:""}`)}console.log(`
2
+ surface (this shell): ${P()}`);console.log(f===0?`
3
+ healthy \u2705`:`
4
+ ${f} required check(s) failed \u274C`);process.exit(f===0?0:1);
@@ -1,101 +1,3 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- /**
7
- * install-hooks — write the user-level Copilot hook file that points every
8
- * event at this adapter's `dist/index.js` (D4: direct hook-file install,
9
- * plugin auto-discovery channel unused).
10
- *
11
- * Target: `$COPILOT_HOME/hooks/pinta-copilot.json` (default `~/.copilot/hooks/`).
12
- * One file fires on BOTH the Copilot CLI and the VS Code extension — no VS Code
13
- * setting required (`chat.useClaudeHooks` default works, §9.7). Add the same
14
- * file under a repo's `.github/hooks/` for cloud/repo-scoped runs (out of scope
15
- * for now).
16
- *
17
- * Idempotent: re-running rewrites our file in place. `--uninstall` removes it.
18
- * Absolute paths are baked in at install time because user-level hooks don't
19
- * receive `${COPILOT_PLUGIN_ROOT}` substitution.
20
- *
21
- * Usage:
22
- * node dist/tools/install-hooks.js [--dry-run] [--uninstall]
23
- */
24
- const node_fs_1 = __importDefault(require("node:fs"));
25
- const node_os_1 = __importDefault(require("node:os"));
26
- const node_path_1 = __importDefault(require("node:path"));
27
- const HOOK_FILE = "pinta-copilot.json";
28
- // Every event routes to the single adapter binary; it branches internally
29
- // (telemetry for all; guard deny on PreToolUse + permissionRequest).
30
- const EVENTS = [
31
- "SessionStart",
32
- "SessionEnd",
33
- "UserPromptSubmit",
34
- "PreToolUse",
35
- "PostToolUse",
36
- "PostToolUseFailure",
37
- "Stop",
38
- "SubagentStart",
39
- "SubagentStop",
40
- "PreCompact",
41
- "Notification",
42
- "permissionRequest",
43
- ];
44
- function copilotHome() {
45
- return process.env.COPILOT_HOME || node_path_1.default.join(node_os_1.default.homedir(), ".copilot");
46
- }
47
- function hooksDir() {
48
- return node_path_1.default.join(copilotHome(), "hooks");
49
- }
50
- /** Absolute path to dist/index.js. This script runs as dist/tools/install-hooks.js,
51
- * so resolve relative to the invoked script path (CJS/ESM agnostic). */
52
- function adapterEntry() {
53
- const here = node_path_1.default.dirname(node_path_1.default.resolve(process.argv[1])); // .../dist/tools
54
- return node_path_1.default.join(here, "..", "index.js"); // .../dist/index.js
55
- }
56
- function buildHooksFile() {
57
- const cmd = `${process.execPath} ${adapterEntry()}`;
58
- const hooks = {};
59
- for (const ev of EVENTS) {
60
- // Stamp the registered event name into env as a fail-safe discriminator —
61
- // some payloads (CLI subagentStart) omit any hook-name field entirely.
62
- hooks[ev] = [{ type: "command", command: cmd, env: { PINTA_COPILOT_EVENT: ev } }];
63
- }
64
- return JSON.stringify({
65
- version: 1,
66
- _note: "Managed by @pinta-ai/pinta-copilot install-hooks. Do not edit by hand.",
67
- hooks,
68
- }, null, 2) + "\n";
69
- }
70
- function main() {
71
- const args = process.argv.slice(2);
72
- const dryRun = args.includes("--dry-run");
73
- const uninstall = args.includes("--uninstall");
74
- const target = node_path_1.default.join(hooksDir(), HOOK_FILE);
75
- if (uninstall) {
76
- if (node_fs_1.default.existsSync(target)) {
77
- if (!dryRun)
78
- node_fs_1.default.rmSync(target);
79
- console.log(`${dryRun ? "[dry-run] would remove" : "removed"}: ${target}`);
80
- }
81
- else {
82
- console.log(`nothing to remove: ${target}`);
83
- }
84
- return;
85
- }
86
- const content = buildHooksFile();
87
- if (node_fs_1.default.existsSync(target) && node_fs_1.default.readFileSync(target, "utf-8") === content) {
88
- console.log(`already up to date: ${target}`);
89
- return;
90
- }
91
- if (dryRun) {
92
- console.log(`[dry-run] would write: ${target}\n${content}`);
93
- return;
94
- }
95
- node_fs_1.default.mkdirSync(hooksDir(), { recursive: true });
96
- node_fs_1.default.writeFileSync(target, content);
97
- console.log(`installed: ${target}`);
98
- console.log("→ restart the Copilot CLI / reload the VS Code window to load hooks.");
99
- }
100
- main();
101
- //# sourceMappingURL=install-hooks.js.map
1
+ "use strict";var m=Object.create;var a=Object.defineProperty;var g=Object.getOwnPropertyDescriptor;var f=Object.getOwnPropertyNames;var S=Object.getPrototypeOf,h=Object.prototype.hasOwnProperty;var y=(o,n,e,t)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of f(n))!h.call(o,r)&&r!==e&&a(o,r,{get:()=>n[r],enumerable:!(t=g(n,r))||t.enumerable});return o};var c=(o,n,e)=>(e=o!=null?m(S(o)):{},y(n||!o||!o.__esModule?a(e,"default",{value:o,enumerable:!0}):e,o));var i=c(require("node:fs")),s=c(require("node:path"));var l=c(require("os")),u=c(require("path"));function p(){return process.env.COPILOT_HOME||u.default.join(l.default.homedir(),".copilot")}var P="pinta-copilot.json",v=["SessionStart","SessionEnd","UserPromptSubmit","PreToolUse","PostToolUse","PostToolUseFailure","Stop","SubagentStart","SubagentStop","PreCompact","Notification","permissionRequest"];function d(){return s.default.join(p(),"hooks")}function C(){let o=s.default.dirname(s.default.resolve(process.argv[1]));return s.default.join(o,"..","index.js")}function O(){let o=`${process.execPath} ${C()}`,n={};for(let e of v)n[e]=[{type:"command",command:o,env:{PINTA_COPILOT_EVENT:e}}];return JSON.stringify({version:1,_note:"Managed by @pinta-ai/pinta-copilot install-hooks. Do not edit by hand.",hooks:n},null,2)+`
2
+ `}function T(){let o=process.argv.slice(2),n=o.includes("--dry-run"),e=o.includes("--uninstall"),t=s.default.join(d(),P);if(e){i.default.existsSync(t)?(n||i.default.rmSync(t),console.log(`${n?"[dry-run] would remove":"removed"}: ${t}`)):console.log(`nothing to remove: ${t}`);return}let r=O();if(i.default.existsSync(t)&&i.default.readFileSync(t,"utf-8")===r){console.log(`already up to date: ${t}`);return}if(n){console.log(`[dry-run] would write: ${t}
3
+ ${r}`);return}i.default.mkdirSync(d(),{recursive:!0}),i.default.writeFileSync(t,r),console.log(`installed: ${t}`),console.log("\u2192 restart the Copilot CLI / reload the VS Code window to load hooks.")}T();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pinta-ai/pinta-copilot",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Generic OTLP forwarder + guard for GitHub Copilot hook events (CLI + VS Code extension)",
5
5
  "main": "dist/index.js",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",
@@ -19,7 +19,8 @@
19
19
  "url": "https://github.com/pinta-ai/pinta-copilot/issues"
20
20
  },
21
21
  "scripts": {
22
- "build": "tsc",
22
+ "bump": "node scripts/bump.mjs",
23
+ "build": "tsc --noEmit && esbuild src/index.ts src/tools/doctor.ts src/tools/install-hooks.ts --bundle --platform=node --format=cjs --target=node18 --minify --outbase=src --outdir=dist",
23
24
  "dev": "tsc --watch",
24
25
  "test": "vitest run",
25
26
  "test:watch": "vitest",
@@ -30,9 +31,15 @@
30
31
  "prepublishOnly": "npm run build"
31
32
  },
32
33
  "devDependencies": {
34
+ "@pinta-ai/core": "^0.3.0",
33
35
  "@types/node": "^22.0.0",
36
+ "esbuild": "^0.24.0",
34
37
  "tsx": "^4.0.0",
35
38
  "typescript": "^5.7.0",
36
39
  "vitest": "^2.1.0"
40
+ },
41
+ "publishConfig": {
42
+ "registry": "https://registry.npmjs.org",
43
+ "access": "public"
37
44
  }
38
45
  }
@@ -1,15 +0,0 @@
1
- /**
2
- * Runtime config. Endpoint/headers come from OTEL_EXPORTER_OTLP_* env vars
3
- * (loaded from ~/.copilot/pinta-copilot.env at startup) — not in this struct.
4
- *
5
- * pinta-copilot is installed as a direct hook file (D4), so the data dir
6
- * (trace + retry-queue) must be a STABLE location independent of cwd —
7
- * otherwise the per-turn trace written by UserPromptSubmit can't be read back
8
- * by the following PreToolUse if cwd differs. We anchor it under the Copilot
9
- * home (`$COPILOT_HOME` or `~/.copilot`).
10
- */
11
- export interface PintaConfig {
12
- pluginData: string;
13
- tracePath: string;
14
- }
15
- export declare function loadConfig(): PintaConfig;
@@ -1,19 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.loadConfig = loadConfig;
7
- const os_1 = __importDefault(require("os"));
8
- const path_1 = __importDefault(require("path"));
9
- function copilotHome() {
10
- return process.env.COPILOT_HOME || path_1.default.join(os_1.default.homedir(), ".copilot");
11
- }
12
- function loadConfig() {
13
- const pluginData = process.env.COPILOT_PLUGIN_DATA || path_1.default.join(copilotHome(), "pinta-copilot-data");
14
- return {
15
- pluginData,
16
- tracePath: path_1.default.join(pluginData, "trace.json"),
17
- };
18
- }
19
- //# sourceMappingURL=config.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":";;;;;AAsBA,gCAOC;AA7BD,4CAAoB;AACpB,gDAAwB;AAiBxB,SAAS,WAAW;IAClB,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,cAAI,CAAC,IAAI,CAAC,YAAE,CAAC,OAAO,EAAE,EAAE,UAAU,CAAC,CAAC;AACzE,CAAC;AAED,SAAgB,UAAU;IACxB,MAAM,UAAU,GACd,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,cAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,oBAAoB,CAAC,CAAC;IACpF,OAAO;QACL,UAAU;QACV,SAAS,EAAE,cAAI,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC;KAC/C,CAAC;AACJ,CAAC"}
@@ -1,25 +0,0 @@
1
- /**
2
- * Bridge Claude Code's userConfig env vars (CLAUDE_PLUGIN_OPTION_*) to the
3
- * OTel SDK standard env vars (OTEL_EXPORTER_OTLP_*).
4
- *
5
- * Claude Code maps each plugin.json `userConfig.<key>` to a corresponding
6
- * `CLAUDE_PLUGIN_OPTION_<KEY>` env var on hook spawn. We keep the user-
7
- * facing names friendly (`endpoint`, `api_key`) and translate them into the
8
- * canonical OTel env names so transport.ts (and any future OTel SDK adoption)
9
- * can read them via the OTel-spec names.
10
- *
11
- * The user-facing `endpoint` is treated as a *full* OTLP/HTTP traces URL
12
- * (e.g., `http://127.0.0.1:5147/v1/traces`), so we map it to
13
- * `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` (signal-specific full URL per OTel
14
- * spec) rather than `OTEL_EXPORTER_OTLP_ENDPOINT` (which is a base URL the
15
- * SDK appends `/v1/traces` to). This way both the OTel SDK (when used) and
16
- * pinta-cc's hand-written transport agree on the URL without any path
17
- * manipulation downstream.
18
- *
19
- * Pinta Manager auto-injects `CLAUDE_PLUGIN_OPTION_*` via Claude Code's
20
- * settings.json. OSS users fill them in via the `/plugin install` UI.
21
- *
22
- * Existing OTEL_EXPORTER_OTLP_TRACES_ENDPOINT / HEADERS take precedence
23
- * (explicit override).
24
- */
25
- export declare function bridgeUserConfigToOtelEnv(): void;
@@ -1,43 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.bridgeUserConfigToOtelEnv = bridgeUserConfigToOtelEnv;
4
- /**
5
- * Bridge Claude Code's userConfig env vars (CLAUDE_PLUGIN_OPTION_*) to the
6
- * OTel SDK standard env vars (OTEL_EXPORTER_OTLP_*).
7
- *
8
- * Claude Code maps each plugin.json `userConfig.<key>` to a corresponding
9
- * `CLAUDE_PLUGIN_OPTION_<KEY>` env var on hook spawn. We keep the user-
10
- * facing names friendly (`endpoint`, `api_key`) and translate them into the
11
- * canonical OTel env names so transport.ts (and any future OTel SDK adoption)
12
- * can read them via the OTel-spec names.
13
- *
14
- * The user-facing `endpoint` is treated as a *full* OTLP/HTTP traces URL
15
- * (e.g., `http://127.0.0.1:5147/v1/traces`), so we map it to
16
- * `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` (signal-specific full URL per OTel
17
- * spec) rather than `OTEL_EXPORTER_OTLP_ENDPOINT` (which is a base URL the
18
- * SDK appends `/v1/traces` to). This way both the OTel SDK (when used) and
19
- * pinta-cc's hand-written transport agree on the URL without any path
20
- * manipulation downstream.
21
- *
22
- * Pinta Manager auto-injects `CLAUDE_PLUGIN_OPTION_*` via Claude Code's
23
- * settings.json. OSS users fill them in via the `/plugin install` UI.
24
- *
25
- * Existing OTEL_EXPORTER_OTLP_TRACES_ENDPOINT / HEADERS take precedence
26
- * (explicit override).
27
- */
28
- function bridgeUserConfigToOtelEnv() {
29
- if (process.env.CLAUDE_PLUGIN_OPTION_ENDPOINT &&
30
- !process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT) {
31
- process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT =
32
- process.env.CLAUDE_PLUGIN_OPTION_ENDPOINT;
33
- }
34
- if (process.env.CLAUDE_PLUGIN_OPTION_API_KEY && !process.env.OTEL_EXPORTER_OTLP_HEADERS) {
35
- process.env.OTEL_EXPORTER_OTLP_HEADERS =
36
- `x-pinta-relay-token=${process.env.CLAUDE_PLUGIN_OPTION_API_KEY}`;
37
- }
38
- // Expose for guard.ts (~/guard/evaluate auth header)
39
- if (!process.env.PINTA_RELAY_TOKEN && process.env.CLAUDE_PLUGIN_OPTION_API_KEY) {
40
- process.env.PINTA_RELAY_TOKEN = process.env.CLAUDE_PLUGIN_OPTION_API_KEY;
41
- }
42
- }
43
- //# sourceMappingURL=env-bridge.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"env-bridge.js","sourceRoot":"","sources":["../../src/core/env-bridge.ts"],"names":[],"mappings":";;AAwBA,8DAgBC;AAxCD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,SAAgB,yBAAyB;IACvC,IACE,OAAO,CAAC,GAAG,CAAC,6BAA6B;QACzC,CAAC,OAAO,CAAC,GAAG,CAAC,kCAAkC,EAC/C,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,kCAAkC;YAC5C,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC;IAC9C,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,CAAC,4BAA4B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,0BAA0B,EAAE,CAAC;QACxF,OAAO,CAAC,GAAG,CAAC,0BAA0B;YACpC,uBAAuB,OAAO,CAAC,GAAG,CAAC,4BAA4B,EAAE,CAAC;IACtE,CAAC;IACD,qDAAqD;IACrD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,OAAO,CAAC,GAAG,CAAC,4BAA4B,EAAE,CAAC;QAC/E,OAAO,CAAC,GAAG,CAAC,iBAAiB,GAAG,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC;IAC3E,CAAC;AACH,CAAC"}
@@ -1,14 +0,0 @@
1
- export interface GuardInput {
2
- spanId: string;
3
- toolName?: string;
4
- toolInput?: unknown;
5
- rawTextFields?: Record<string, string>;
6
- }
7
- export interface GuardResult {
8
- decision: 'ALLOW' | 'DENY' | 'REVIEW';
9
- reason: string | null;
10
- userMessage: string | null;
11
- durationMs: number;
12
- failOpenReason?: 'timeout' | 'refused' | 'error';
13
- }
14
- export declare function evaluateGuard(input: GuardInput, endpoint: string | undefined): Promise<GuardResult | null>;
@@ -1,53 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.evaluateGuard = evaluateGuard;
4
- // Guard must be fast or fail-open. 50ms default keeps the hook snappy;
5
- // override for slower relays (or test harnesses) via PINTA_GUARD_TIMEOUT_MS.
6
- const TIMEOUT_MS = Number(process.env.PINTA_GUARD_TIMEOUT_MS) || 50;
7
- // Self-identify to the manager's guard route so it can attribute calls to this
8
- // adaptor (the route parses `pinta-*/<version>` out of the User-Agent). Keep the
9
- // version in sync with package.json.
10
- const GUARD_UA = 'pinta-copilot/0.3.0';
11
- function sleep(ms) {
12
- return new Promise((_, reject) => setTimeout(() => {
13
- const err = new Error('Guard request timed out');
14
- err.name = 'TimeoutError';
15
- reject(err);
16
- }, ms));
17
- }
18
- async function evaluateGuard(input, endpoint) {
19
- if (!endpoint)
20
- return null;
21
- if (process.env.PINTA_GUARD_DISABLED === '1')
22
- return null;
23
- const start = Date.now();
24
- try {
25
- const res = await Promise.race([
26
- fetch(endpoint, {
27
- method: 'POST',
28
- headers: {
29
- 'content-type': 'application/json',
30
- 'user-agent': GUARD_UA,
31
- 'x-pinta-relay-token': process.env.PINTA_RELAY_TOKEN ?? '',
32
- },
33
- body: JSON.stringify({ input }),
34
- }),
35
- sleep(TIMEOUT_MS),
36
- ]);
37
- if (res.status !== 200) {
38
- return { decision: 'ALLOW', reason: null, userMessage: null, durationMs: Date.now() - start, failOpenReason: 'error' };
39
- }
40
- const body = (await res.json());
41
- return {
42
- decision: body.decision,
43
- reason: body.reason,
44
- userMessage: body.userMessage ?? null,
45
- durationMs: body.durationMs ?? (Date.now() - start),
46
- };
47
- }
48
- catch (err) {
49
- const reason = err.name === 'TimeoutError' ? 'timeout' : 'error';
50
- return { decision: 'ALLOW', reason: null, userMessage: null, durationMs: Date.now() - start, failOpenReason: reason };
51
- }
52
- }
53
- //# sourceMappingURL=guard.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"guard.js","sourceRoot":"","sources":["../../src/core/guard.ts"],"names":[],"mappings":";;AAqCA,sCAuCC;AA1DD,uEAAuE;AACvE,6EAA6E;AAC7E,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,IAAI,EAAE,CAAC;AAEpE,+EAA+E;AAC/E,iFAAiF;AACjF,qCAAqC;AACrC,MAAM,QAAQ,GAAG,qBAAqB,CAAC;AAEvC,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAC/B,UAAU,CAAC,GAAG,EAAE;QACd,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QACjD,GAAG,CAAC,IAAI,GAAG,cAAc,CAAC;QAC1B,MAAM,CAAC,GAAG,CAAC,CAAC;IACd,CAAC,EAAE,EAAE,CAAC,CACP,CAAC;AACJ,CAAC;AAEM,KAAK,UAAU,aAAa,CACjC,KAAiB,EACjB,QAA4B;IAE5B,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAC1D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;YAC7B,KAAK,CAAC,QAAQ,EAAE;gBACd,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,YAAY,EAAE,QAAQ;oBACtB,qBAAqB,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,EAAE;iBAC3D;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC;aAChC,CAAC;YACF,KAAK,CAAC,UAAU,CAAC;SAClB,CAAC,CAAC;QACH,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC;QACzH,CAAC;QACD,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAK7B,CAAC;QACF,OAAO;YACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,IAAI;YACrC,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;SACpD,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,MAAM,GAAmC,GAAa,CAAC,IAAI,KAAK,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;QAC3G,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC;IACxH,CAAC;AACH,CAAC"}