@2digits/opencode-plugin 0.1.26 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { Hooks, PluginInput } from "@opencode-ai/plugin";
2
- declare function plugin(ctx: PluginInput): Promise<Hooks>;
1
+ import { Plugin } from "@opencode-ai/plugin";
2
+ declare const plugin: Plugin;
3
3
  export { plugin as default };
package/dist/index.js CHANGED
@@ -1,160 +1,4 @@
1
1
  // @bun
2
- import r from"dedent";import{tool as i}from"@opencode-ai/plugin";import s from"dedent";function o(t){return i({description:"Create GitHub issue in 2digits-agency/configs with agent feedback",args:{title:i.schema.string().meta({title:"Concise issue title",description:s`
3
- A concise title for the issue. This will be used as the issue title.
4
-
5
- Good example: "Prefer method notation over arrow functions in object literals"
6
- Bad example: "Write cleaner code"
7
- `}),feedback:i.schema.string().meta({title:"Detailed feedback",description:s`
8
- Provide a detailed explanation of what went wrong and what the user expected to happen.
9
- This will be used as the issue body.
10
- `}),context:i.schema.string().meta({title:"Relevant code or conversation snippets",description:s`
11
- Show the before and after code or conversation snippets that triggered the issue.
12
- `}),suggestedRule:i.schema.string().meta({title:"Suggested rule",description:s`
13
- Propose a rule to prevent this happening in the future.
14
- Keep in mind that the rule should be specific and not too broad.
15
- For example, if the issue is about a specific pattern, propose a rule that addresses that pattern.
16
- `})},async execute(e,a){let n=`[Agent Feedback] ${e.title}`,d=await t.$`opencode --version`.text(),c=s`
17
- # Feedback
18
-
19
- ${e.feedback}
20
-
21
- ## Context
22
-
23
- ${e.context}
24
-
25
- ## Suggested Rule
26
-
27
- ${e.suggestedRule}
28
-
29
- <details>
30
- <summary>Technical Details</summary>
31
-
32
- - OpenCode Version: ${d}
33
- - Project Name: ${t.directory}
34
- - Agent Name: ${a.agent}
35
- </details>
36
-
37
- ---
38
-
39
- _Created via \`/fix\` command in OpenCode_
40
- `;return`Created issue: ${(await t.$`gh issue create \
41
- --repo 2digits-agency/configs \
42
- --title ${n} \
43
- --body ${c} \
44
- --label agent-feedback`.text()).trim()}`}})}var l=r`
45
- Submit feedback about something the agent did wrong or could improve.
46
-
47
- ## Instructions
48
-
49
- 1. Ask clarifying questions to understand:
50
- - What specifically went wrong?
51
- - What should have happened instead?
52
- - Is this a recurring pattern or one-off?
53
-
54
- 2. Use the \`create_feedback_issue\` tool with:
55
- - **title**: Concise description of the issue
56
- - **feedback**: Detailed explanation of what went wrong
57
- - **context**: Relevant code/conversation snippets
58
- - **suggestedRule**: Propose a rule to prevent this
59
-
60
- 3. Return the issue URL so user can track/discuss
61
-
62
- ## User Input
63
-
64
- <Feedback>$ARGUMENTS</Feedback>
65
- `;async function p(t){return{config:async(e)=>{e.command=e.command??{},e.command.fix={description:"Submit feedback about agent behavior to improve shared rules",template:l}},"experimental.chat.system.transform":async(e,a)=>{a.system.push(r`
66
- Instructions from: @2digits/opencode-plugin
67
-
68
- ${`## Code Quality Standards
69
-
70
- - Make minimal, surgical changes
71
- - Never compromise type safety: No any, no non-null assertion operator (!), no type assertions (as Type)
72
- - Make illegal states unrepresentable: Model domain with ADTs/discriminated unions; parse inputs at boundaries into typed structures; if state can't exist, code can't mishandle it
73
- - Abstractions: Consciously constrained, pragmatically parameterised, doggedly documented
74
-
75
- ## Patterns to Avoid
76
-
77
- - Premature optimization
78
- - Magic strings/numbers without constants
79
- - Nested ternaries beyond 2 levels
80
- - Barrel files (index.ts re-exports) in large modules
81
-
82
- ## Method Notation in Object Literals
83
-
84
- Prefer method notation (\`name() {}\`) over arrow functions (\`name: () => {}\`) in object literals with function properties:
85
-
86
- \`\`\`ts
87
- // \u274C Arrow function syntax
88
- Effect.tryPromise({
89
- try: () => someAsyncOperation(),
90
- catch: () => new SomeError({ message: 'failed' }),
91
- });
92
-
93
- // \u2705 Method notation
94
- Effect.tryPromise({
95
- try(abortSignal) {
96
- return someAsyncOperation(abortSignal);
97
- },
98
- catch(cause) {
99
- return new SomeError({ cause });
100
- },
101
- });
102
- \`\`\`
103
-
104
- Benefits:
105
-
106
- - More concise (no \`=>\` needed)
107
- - More readable for multi-line function bodies
108
- - Exposes API-provided parameters (e.g., \`abortSignal\` in Effect.tryPromise, \`cause\` in catch)
109
-
110
- ## Embedded Language Strings
111
-
112
- Use \`dedent\` for multi-line template literals containing embedded languages. Alias the import to the language name for IDE syntax highlighting:
113
-
114
- \`\`\`ts
115
- import markdown from 'dedent';
116
- import sql from 'dedent';
117
- import html from 'dedent';
118
-
119
- const readme = markdown\`
120
- # My Package
121
-
122
- A description of the package.
123
- \`;
124
-
125
- const query = sql\`
126
- SELECT * FROM users
127
- WHERE id = \${userId}
128
- \`;
129
- \`\`\`
130
-
131
- ## ENTROPY REMINDER
132
-
133
- This codebase will outlive you. Every shortcut you take becomes someone else's burden. Every hack compounds into technical debt that slows the whole team down.
134
-
135
- You are not just writing code. You are shaping the future of this project. The patterns you establish will be copied. The corners you cut will be cut again.
136
-
137
- Fight entropy. Leave the codebase better than you found it.
138
-
139
-
140
- ## Effect Patterns
141
-
142
- - Use Effect.gen for readable sequential composition
143
- - Define tagged errors with Schema.TaggedError (not Data.TaggedError)
144
- - Services use Context.Tag class pattern: \\\`class Foo extends Context.Tag("@app/Foo")<Foo, Interface>() {}\\\`
145
- - Always use Effect.fn for traceable service methods
146
- - Prefer Layer.effect for service construction, Layer.succeed for simple values
147
- - Use HttpClient.filterStatusOk to convert non-2xx to ResponseError
148
- - Always call Effect.scoped after consuming response body
149
-
150
-
151
- ## React/Next.js Patterns
152
-
153
- - Prefer Server Components by default, use 'use client' only when needed
154
- - Colocate data fetching with components that use it
155
- - Use Suspense boundaries for loading states
156
- - Prefer controlled components with explicit state management
157
- - Extract complex logic into custom hooks
158
- - Name useState pairs as \`[value, setValue]\`
159
- `}
160
- `)},tool:{create_feedback_issue:o(t)}}}export{p as default};
2
+ import{PostHog as Te}from"posthog-node";var G="https://eu.i.posthog.com",R=12000,L=60,g="@2digits/opencode-plugin",A="opencode",I=/api[-_]?key|token|secret|password|authorization|cookie|session|bearer|x-api-key|credential/i;import ge from"path";function b(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function j(e){if(e===void 0||e===null)return;try{return JSON.stringify(e)}catch{return}}function H(e,t){if(t<=0)return"";if(e.length<=t)return e;let r=e.length-t;return`${e.slice(0,t)}...[truncated ${r} chars]`}function k(e,t,r){if(r>8)return"[DepthLimit]";if(e===null||e===void 0)return e;if(typeof e!=="object")return e;if(t.has(e))return"[Circular]";if(t.add(e),Array.isArray(e))return e.map((u)=>k(u,t,r+1));let p={};for(let[u,_]of Object.entries(e))p[u]=I.test(u)?"[REDACTED]":k(_,t,r+1);return p}function N(e,t){if(e===void 0||e===null)return;let r=k(e,new WeakSet,0);if(typeof r==="string")return H(r,t);let p=j(r);return p?H(p,t):void 0}function U(e){return e==="session"?"session":"message"}function w(e,t){let r=Number.parseInt(e??"",10);return Number.isFinite(r)&&r>0?r:t}function W(e){if(!e)return{};try{let t=JSON.parse(e);return b(t)?t:{}}catch{return{}}}function v(e){if(!e)return!0;try{return b(JSON.parse(e))}catch{return!1}}function C(e){let t=e?.trim();if(!t)return;return t}function B(e){let t=ge.basename(e);return t.length>0?t:"opencode-project"}function F(e){if(!e)return;let t=e.replaceAll(/\s+/g," ").trim();if(!t)return;return t.slice(0,120)}function J(e,t){return e.distinctId??e.gitEmail??t}function M(e,t){return`${e}:${t}`}function h(e,t){return`${e}:${t}`}function z(e,t){if(t)return"error";if(!e)return;if(e==="tool-calls")return"tool_calls";return e}function x(e){if(!e)return;if(b(e)){if(typeof e.message==="string")return e.message;if(b(e.data)&&typeof e.data.message==="string")return e.data.message;if(typeof e.name==="string")return e.name}return j(e)}function K(e){let t=e.filter((r)=>r.type==="text"&&!r.synthetic&&typeof r.text==="string").map((r)=>r.text??"").join(`
3
+ `).trim();return t.length>0?t:void 0}function V(e){if(!e)return;let t=e.order.map((r)=>e.parts.get(r)??"").join(`
4
+ `).trim();return t.length>0?t:void 0}function Y(e,t){if(t||!e)return;return[{role:"user",content:e}]}function Q(e,t,r){if(r)return;if(e)return[{role:"assistant",content:e}];if(t)return[{role:"assistant",content:t}];return}function X(e,t,r){return{$ai_trace_id:t.traceID,$ai_session_id:t.sessionID,$ai_latency:r,$ai_total_input_tokens:t.totalInputTokens,$ai_total_output_tokens:t.totalOutputTokens,$ai_is_error:t.hasError,$ai_error:t.errorMessage,$ai_span_name:t.traceName,$ai_lib:g,$ai_framework:A,$ai_project_name:e.projectName,$ai_agent_name:t.agentName,...e.customProperties}}function q(e){let{config:t,errorMessage:r,generation:p,info:u,output:_,pending:m,toolCallCount:f,toolsCalled:i,traceState:c}=e,a=Math.max(0,u.time.completed-u.time.created)/1000;return{$ai_trace_id:p.traceID,$ai_session_id:u.sessionID,$ai_span_id:p.spanID,$ai_model:u.modelID,$ai_provider:u.providerID,$ai_input:Y(m.prompt,t.privacyMode),$ai_output_choices:Q(_,r,t.privacyMode),$ai_user_prompt:t.privacyMode?void 0:m.prompt,$ai_input_tokens:u.tokens.input,$ai_output_tokens:u.tokens.output,$ai_total_tokens:u.tokens.input+u.tokens.output+u.tokens.reasoning+u.tokens.cache.read+u.tokens.cache.write,$ai_latency:a,$ai_total_cost_usd:u.cost,$ai_stop_reason:z(u.finish,Boolean(u.error)),$ai_is_error:Boolean(u.error),$ai_error:r,$ai_tools_called:i,$ai_tool_call_count:f,cache_read_input_tokens:u.tokens.cache.read,cache_creation_input_tokens:u.tokens.cache.write,$ai_lib:g,$ai_framework:A,$ai_project_name:t.projectName,$ai_agent_name:c.agentName,...t.customProperties}}function D(e){let{config:t,latency:r,output:p,toolCall:u,toolName:_,traceState:m}=e;return{$ai_trace_id:u.traceID,$ai_session_id:u.sessionID,$ai_span_id:crypto.randomUUID(),$ai_parent_id:u.parentSpanID,$ai_span_name:_,$ai_input_state:t.privacyMode?void 0:N(u.args,t.maxAttributeLength),$ai_output_state:t.privacyMode?void 0:N(p,t.maxAttributeLength),$ai_latency:r,$ai_is_error:!1,$ai_error:void 0,$ai_lib:g,$ai_framework:A,$ai_project_name:t.projectName,$ai_agent_name:m?.agentName??t.projectName,...t.customProperties}}function Z(e){let t,r=!1;function p(){if(t)return t;try{return t=new Te(e.apiKey,{host:e.host,flushAt:20,flushInterval:1e4}),t}catch(f){console.error(`${g}: failed to initialize PostHog client`,f);return}}function u(f,i,c){let a=p();if(!a)return;a.capture({distinctId:J(e,i),event:f,properties:c})}async function _(){if(!t)return;await t.shutdown(),t=void 0}function m(f){if(r)return;r=!0,process.once("beforeExit",()=>{f()}),process.once("SIGINT",()=>{f()}),process.once("SIGTERM",()=>{f()})}return{capture:u,registerShutdown:m,shutdown:_}}async function Pe(e){try{return C(await e.$`git config --get user.email`.text())}catch{return}}async function ee(e){let t=B(e.worktree||e.directory),r=W(process.env.POSTHOG_LLMA_CUSTOM_PROPERTIES);if(!v(process.env.POSTHOG_LLMA_CUSTOM_PROPERTIES))console.warn(`${g}: ignoring invalid POSTHOG_LLMA_CUSTOM_PROPERTIES JSON`);return{apiKey:process.env.POSTHOG_API_KEY??"phc_qAbssQWcjrSz9NutSQsNrWWpQqLsTGo7ZnHchwa5C6Tb",host:process.env.POSTHOG_HOST??G,enabled:process.env.POSTHOG_OPENCODE_ENABLED!=="false",privacyMode:process.env.POSTHOG_LLMA_PRIVACY_MODE==="true",traceGrouping:U(process.env.POSTHOG_LLMA_TRACE_GROUPING),sessionWindowMinutes:w(process.env.POSTHOG_LLMA_SESSION_WINDOW_MINUTES,L),maxAttributeLength:w(process.env.POSTHOG_MAX_ATTRIBUTE_LENGTH,R),distinctId:C(process.env.POSTHOG_LLMA_DISTINCT_ID),gitEmail:await Pe(e),projectName:t,customProperties:r}}import{randomUUID as S}from"crypto";function te(e){let t=new Map,r=new Map,p=new Map,u=new Map,_=new Map,m=new Map,f=new Map,i=new Map,c=new Set;function a(n){p.delete(n),r.delete(n),i.delete(n);for(let[s,d]of t.entries())if(d.sessionID===n)t.delete(s);let o=new Set;for(let[s,d]of f.entries()){if(d.sessionID!==n)continue;o.add(d.spanID),f.delete(s),m.delete(s),c.delete(s)}for(let s of o)_.delete(s);for(let[s,d]of u.entries())if(d.sessionID===n)u.delete(s)}function l(n){return p.get(n)}function T(){return[...p.keys()]}function $(n,o,s){let d=p.get(n)??{agentName:o??e.projectName,errorMessage:void 0,hasError:!1,lastActivityAt:s,sessionID:n,startedAt:s,totalCostUsd:0,totalInputTokens:0,totalOutputTokens:0,traceID:S(),traceName:void 0};return d.agentName=o??d.agentName,d.lastActivityAt=s,p.set(n,d),d}function E(n,o){let s=Date.now(),d=$(n.sessionID,n.agent,s),P=K(o.parts),y={agentName:n.agent??e.projectName,prompt:P,sessionID:n.sessionID,traceID:d.traceID,spanID:S(),startedAt:s,userMessageID:o.message.id};d.traceName??=F(P),t.set(M(n.sessionID,o.message.id),y),r.set(n.sessionID,y),i.delete(n.sessionID)}function O(n){let o=m.get(n.messageID)??{order:[],parts:new Map};if(!o.parts.has(n.id))o.order.push(n.id);o.parts.set(n.id,n.text),m.set(n.messageID,o)}function ne(n){return V(m.get(n))}function re(n,o){let s=p.get(n);if(!s)return;s.hasError=!0,s.errorMessage=x(o),s.lastActivityAt=Date.now()}function ie(n){let o=M(n.sessionID,n.parentID),s=t.get(o)??r.get(n.sessionID),d=p.get(n.sessionID);if(!s||!d)return;return{pending:s,pendingKey:o,traceState:d}}function oe(n){let o=ie(n);if(!o)return;let s=f.get(n.id);if(s)return{generation:s,...o};let d=i.get(n.sessionID),P=d&&d!==n.id?S():o.pending.spanID,y={assistantMessageID:n.id,pendingKey:o.pendingKey,sessionID:n.sessionID,spanID:P,traceID:o.pending.traceID};return o.pending.spanID=P,f.set(n.id,y),i.set(n.sessionID,n.id),{generation:y,...o}}function se(n,o,s){n.agentName=s.mode||o.agentName,n.lastActivityAt=s.time.completed,n.totalCostUsd+=s.cost,n.totalInputTokens+=s.tokens.input,n.totalOutputTokens+=s.tokens.output;let d=x(s.error);if(d)n.hasError=!0,n.errorMessage=d;return d}function ue(n){return c.has(n)}function ae(n){c.add(n)}function ce(n,o){m.delete(n),_.delete(o)}function de(n){return r.get(n)}function pe(n,o,s){u.set(h(n,o),s)}function fe(n,o){let s=h(n,o),d=u.get(s);if(!d)return;return u.delete(s),d}function _e(n,o){let s=_.get(n)??[];s.push(o),_.set(n,s)}function le(n){let o=_.get(n);if(!o||o.length===0)return{};return{toolsCalled:[...new Set(o)],toolCallCount:o.length}}function me(n){return n.role==="assistant"&&Boolean(n.time.completed)&&!c.has(n.id)}return{beginPrompt:E,clearSessionState:a,cleanupAssistantMessage:ce,getActiveMessage:de,getAssistantOutputForMessage:ne,getGenerationToolProperties:le,getOrCreateGenerationState:oe,getTraceState:l,getTraceSessionIDs:T,getOrCreateTraceState:$,hasCompletedAssistantMessage:ue,markAssistantMessageCompleted:ae,markSessionError:re,recordGenerationToolCall:_e,setToolCall:pe,shouldCompleteAssistantMessage:me,storeAssistantOutput:O,takeToolCall:fe,updateTraceStateFromAssistantMessage:se}}var ye=async(e)=>{let t=await ee(e);if(!t.enabled)return{};if(!t.apiKey)return console.warn(`${g}: missing POSTHOG_API_KEY, analytics disabled`),{};let r=te(t),p=Z(t);function u(i){let c=r.getTraceState(i);if(!c)return;let a=Math.max(0,c.lastActivityAt-c.startedAt)/1000;p.capture("$ai_trace",c.sessionID,X(t,c,a)),r.clearSessionState(i)}async function _(){for(let i of r.getTraceSessionIDs())u(i);await p.shutdown()}p.registerShutdown(_);function m(i){let c=r.getOrCreateGenerationState(i);if(!c)return;let{generation:a,pending:l,traceState:T}=c,$=r.updateTraceStateFromAssistantMessage(T,l,{mode:i.mode,time:{completed:i.time.completed},cost:i.cost,tokens:{input:i.tokens.input,output:i.tokens.output},error:i.error}),{toolsCalled:E,toolCallCount:O}=r.getGenerationToolProperties(a.spanID);p.capture("$ai_generation",i.sessionID,q({config:t,pending:l,generation:a,traceState:T,info:i,errorMessage:$,output:r.getAssistantOutputForMessage(i.id),toolsCalled:E,toolCallCount:O})),r.cleanupAssistantMessage(i.id,a.spanID)}async function f(i){if(i.type==="message.part.updated"){let{part:a}=i.properties;if(a.type==="text")r.storeAssistantOutput(a);return}if(i.type==="session.deleted"){u(i.properties.info.id);return}if(i.type==="session.error"){let{error:a,sessionID:l}=i.properties;if(l)r.markSessionError(l,a);return}if(i.type!=="message.updated")return;let{info:c}=i.properties;if(c.role!=="assistant")return;if(r.getOrCreateGenerationState(c),r.shouldCompleteAssistantMessage(c))r.markAssistantMessageCompleted(c.id),m(c)}return{"chat.message":async(i,c)=>{let a=Date.now(),l=r.getTraceState(i.sessionID),T=t.sessionWindowMinutes*60000;if(l&&t.traceGrouping==="message")u(i.sessionID);if(l&&t.traceGrouping==="session"&&a-l.lastActivityAt>=T)u(i.sessionID);r.beginPrompt(i,c)},"tool.execute.before":(i,c)=>{let a=r.getActiveMessage(i.sessionID);if(!a)return Promise.resolve();return r.setToolCall(i.sessionID,i.callID,{args:c.args,parentSpanID:a.spanID,sessionID:i.sessionID,startedAt:Date.now(),traceID:a.traceID}),r.recordGenerationToolCall(a.spanID,i.tool),Promise.resolve()},"tool.execute.after":async(i,c)=>{let a=r.takeToolCall(i.sessionID,i.callID);if(!a)return;let l=r.getTraceState(a.sessionID);if(l)l.lastActivityAt=Date.now();p.capture("$ai_span",a.sessionID,D({config:t,toolCall:a,traceState:l,toolName:i.tool,output:c,latency:Math.max(0,Date.now()-a.startedAt)/1000}))},event:async({event:i})=>f(i)}},Ue=ye;export{Ue as default};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@2digits/opencode-plugin",
3
- "version": "0.1.26",
4
- "description": "OpenCode plugin for 2digits team - shared rules and feedback system",
3
+ "version": "0.2.1",
4
+ "description": "OpenCode plugin for PostHog LLM analytics integration",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -13,6 +13,7 @@
13
13
  ],
14
14
  "type": "module",
15
15
  "sideEffects": false,
16
+ "main": "./dist/index.js",
16
17
  "module": "./dist/index.js",
17
18
  "types": "./dist/index.d.ts",
18
19
  "exports": {
@@ -22,22 +23,31 @@
22
23
  "default": "./dist/index.js"
23
24
  }
24
25
  },
26
+ "./server": {
27
+ "import": {
28
+ "types": "./dist/index.d.ts",
29
+ "default": "./dist/index.js"
30
+ }
31
+ },
25
32
  "./package.json": "./package.json"
26
33
  },
27
34
  "dependencies": {
28
- "@opencode-ai/plugin": "1.14.21",
29
- "dedent": "1.7.2"
35
+ "@opencode-ai/plugin": "1.14.22",
36
+ "posthog-node": "5.30.0"
30
37
  },
31
38
  "devDependencies": {
32
39
  "@types/bun": "1.3.13",
33
- "@typescript/native-preview": "7.0.0-dev.20260422.1",
40
+ "@typescript/native-preview": "7.0.0-dev.20260423.1",
34
41
  "bunup": "0.16.31",
35
42
  "typescript": "6.0.3",
43
+ "vite-plus": "0.1.19",
44
+ "vitest": "npm:@voidzero-dev/vite-plus-test@0.1.19",
36
45
  "@2digits/tsconfig": "0.8.7"
37
46
  },
38
47
  "scripts": {
39
48
  "build": "bunup",
40
49
  "dev": "bunup --watch",
50
+ "test": "vp test run",
41
51
  "types": "tsgo --noEmit"
42
52
  }
43
53
  }