opencodekit 0.17.7 → 0.17.8

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.js CHANGED
@@ -759,7 +759,7 @@ var cac = (name = "") => new CAC(name);
759
759
  // package.json
760
760
  var package_default = {
761
761
  name: "opencodekit",
762
- version: "0.17.7",
762
+ version: "0.17.8",
763
763
  description: "CLI tool for bootstrapping and managing OpenCodeKit projects",
764
764
  keywords: ["agents", "cli", "mcp", "opencode", "opencodekit", "template"],
765
765
  license: "MIT",
@@ -88,9 +88,10 @@ Use specialist agents by intent:
88
88
  | `@review` | Correctness/security/debug review |
89
89
  | `@plan` | Architecture and execution plans |
90
90
  | `@vision` | UI/UX and accessibility judgment |
91
- | `@looker` | OCR/PDF/diagram extraction |
92
91
  | `@painter` | Image generation/editing |
93
92
 
93
+ **Note:** PDF extraction → use `pdf-extract` skill; Images → use vision-capable model directly
94
+
94
95
  **Parallelism rule**: Use parallel subagents for 3+ independent tasks; otherwise work sequentially.
95
96
 
96
97
  ---
@@ -232,5 +232,5 @@ Delegate to:
232
232
  - `@review` for deep debugging/security review
233
233
  - `@plan` for architecture or decomposition
234
234
  - `@vision` for UI/UX analysis
235
- - `@looker` for OCR/PDF extraction
235
+ - PDF extraction use `pdf-extract` skill
236
236
  - `@painter` for image generation/editing
@@ -34,7 +34,7 @@ Generate or edit images only when explicitly requested.
34
34
  ## Rules
35
35
 
36
36
  - No design critique or accessibility audit (delegate to `@vision`)
37
- - No OCR/PDF extraction tasks (delegate to `@looker`)
37
+ - No PDF extraction tasks (use `pdf-extract` skill)
38
38
  - Preserve `thoughtSignature` across iterative edits
39
39
  - Do not add visual elements not requested
40
40
  - Return deterministic metadata for every response
@@ -55,7 +55,7 @@ Assess visual quality, accessibility, and design consistency, then return concre
55
55
  ### Do Not Use For
56
56
 
57
57
  - Image generation/editing → delegate to `@painter`
58
- - OCR/PDF extraction-heavy work → delegate to `@looker`
58
+ - PDF extraction-heavy work → use `pdf-extract` skill
59
59
  - Code implementation → delegate to `@build`
60
60
 
61
61
  ## Skills
@@ -16,10 +16,6 @@
16
16
  "description": "General-purpose subagent for fast, well-defined tasks; delegates complexity quickly",
17
17
  "model": "opencode/minimax-m2.5-free"
18
18
  },
19
- "looker": {
20
- "description": "Media extraction specialist for images, PDFs, diagrams",
21
- "model": "proxypal/gemini-3-flash"
22
- },
23
19
  "painter": {
24
20
  "description": "Image generation and editing specialist using Gemini 3 Pro Image. Use for creating UI mockups, app icons, hero images, visual assets, and editing/redacting existing images",
25
21
  "model": "proxypal/gemini-3-pro-image"
@@ -153,7 +149,8 @@
153
149
  },
154
150
  "plugin": [
155
151
  "@tarquinen/opencode-dcp@latest",
156
- "@franlol/opencode-md-table-formatter@0.0.3"
152
+ "@franlol/opencode-md-table-formatter@0.0.3",
153
+ "openslimedit@latest"
157
154
  ],
158
155
  "provider": {
159
156
  "modal": {
@@ -11,7 +11,7 @@
11
11
  "type-check": "tsc --noEmit"
12
12
  },
13
13
  "dependencies": {
14
- "@opencode-ai/plugin": "1.2.1"
14
+ "@opencode-ai/plugin": "1.2.5"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@types/node": "^25.1.0",
@@ -1,233 +1,26 @@
1
1
  /**
2
2
  * OpenCode Session Tools
3
- * Provides session browsing, searching, and context transfer
3
+ * Simplified to match AmpCode's find_thread / read_thread
4
+ *
5
+ * Core tools:
6
+ * - find_sessions: Search sessions by keyword (like AmpCode's find_thread)
7
+ * - read_session: Read session messages (like AmpCode's read_thread)
4
8
  */
5
9
 
6
10
  import type { Plugin } from "@opencode-ai/plugin";
7
11
  import { tool } from "@opencode-ai/plugin/tool";
8
12
 
9
13
  export const SessionsPlugin: Plugin = async ({ client }) => {
10
- const getProjectContext = () =>
11
- "Project context: OpenCodeKit CLI repo; sessions often mention .opencode/ plugins, commands, memory rules, beads IDs, and Bun/TypeScript constraints.";
12
-
13
- const getBestPractices = () =>
14
- "Best practices: Prefer recent sessions, keep queries focused, and reference exact file paths or IDs found in the session.";
15
-
16
14
  return {
17
- "tool.definition": async (input, output) => {
18
- const toolID = input.toolID;
19
- const projectContext = getProjectContext();
20
- const commonBestPractices = getBestPractices();
21
-
22
- switch (toolID) {
23
- case "list_sessions": {
24
- output.description = `${output.description}
25
-
26
- ${projectContext}
27
- ${commonBestPractices}
28
-
29
- Recent session examples:
30
- list_sessions({ since: "this week", limit: 5 })
31
- list_sessions({ since: "yesterday", limit: 3 })`;
32
- break;
33
- }
34
- case "read_session": {
35
- output.description = `${output.description}
36
-
37
- ${projectContext}
38
- Best practices: Use "last" for most recent context; add a short focus keyword (e.g., "sessions", "plugin", "beads") to reduce noise.
39
-
40
- Recent session examples:
41
- read_session({ session_reference: "last" })
42
- read_session({ session_reference: "last", focus: "sessions plugin" })`;
43
- break;
44
- }
45
- case "search_session": {
46
- output.description = `${output.description}
47
-
48
- ${projectContext}
49
- Best practices: Start with 1-2 specific keywords, then read the top match for full context.
50
-
51
- Recent session examples:
52
- search_session({ query: "sessions plugin", limit: 5 })
53
- search_session({ query: ".opencode", limit: 5 })`;
54
- break;
55
- }
56
- case "summarize_session": {
57
- output.description = `${output.description}
58
-
59
- ${projectContext}
60
- Best practices: Summarize long sessions only after confirming the ID from list_sessions; check back later for the generated summary.
61
-
62
- Recent session examples:
63
- summarize_session({ session_id: "abc123" }) // from list_sessions
64
- summarize_session({ session_id: "def456" })`;
65
- break;
66
- }
67
- default:
68
- break;
69
- }
70
- },
71
15
  tool: {
72
- list_sessions: tool({
73
- description: `List OpenCode sessions with metadata.
16
+ /** Like AmpCode's find_thread */
17
+ find_sessions: tool({
18
+ description: `Search sessions by keyword.
74
19
 
75
- Purpose:
76
- - Browse recent sessions by date
77
- - Returns session ID, title, and creation time
78
- - Default: last 20 sessions, most recent first
79
-
80
- Example:
81
- list_sessions({ since: "this week", limit: 10 })
82
- list_sessions({}) // All recent sessions`,
20
+ Example:
21
+ find_sessions({ query: "auth", limit: 5 })`,
83
22
  args: {
84
- since: tool.schema
85
- .string()
86
- .optional()
87
- .describe(
88
- "Filter by date (today, yesterday, this week, or ISO date)",
89
- ),
90
- limit: tool.schema
91
- .number()
92
- .optional()
93
- .describe("Max sessions to return (default: 20)"),
94
- },
95
- async execute(args: { since?: string; limit?: number }) {
96
- const result = await client.session.list();
97
- if (!result.data) return "No sessions found.";
98
-
99
- let sessions = result.data;
100
-
101
- // Filter by date
102
- if (args.since) {
103
- const sinceDate = parseDate(args.since);
104
- if (sinceDate) {
105
- sessions = sessions.filter((s) => {
106
- const created = s.time?.created
107
- ? new Date(s.time.created)
108
- : null;
109
- return created && created >= sinceDate;
110
- });
111
- }
112
- }
113
-
114
- // Limit results
115
- const limited = sessions.slice(0, args.limit || 20);
116
-
117
- if (limited.length === 0)
118
- return "No sessions found matching criteria.";
119
-
120
- return `# Sessions\n\n${limited
121
- .map(
122
- (s) =>
123
- `**${s.id}** - ${s.title || "Untitled"}\n Created: ${s.time?.created ? new Date(s.time.created).toLocaleString() : "Unknown"}`,
124
- )
125
- .join("\n\n")}`;
126
- },
127
- }),
128
-
129
- read_session: tool({
130
- description: `Read session context for handoff or reference.
131
-
132
- Purpose:
133
- - Retrieve full session details and messages
134
- - Use "last" to get most recent session
135
- - Optional focus keyword to filter messages
136
-
137
- Example:
138
- read_session({ session_reference: "last" })
139
- read_session({ session_reference: "abc123", focus: "auth" })`,
140
- args: {
141
- session_reference: tool.schema
142
- .string()
143
- .describe("Session ID, or 'last' for most recent session"),
144
- focus: tool.schema
145
- .string()
146
- .optional()
147
- .describe("Focus on specific topic (filters messages by keyword)"),
148
- },
149
- async execute(args: { session_reference: string; focus?: string }) {
150
- let sessionId = args.session_reference;
151
-
152
- // Handle "last" reference
153
- if (sessionId === "last") {
154
- const sessions = await client.session.list();
155
- if (!sessions.data?.length) return "No sessions found.";
156
- sessionId = sessions.data[0].id;
157
- }
158
-
159
- const session = await client.session.get({ path: { id: sessionId } });
160
- if (!session.data) return `Session ${sessionId} not found.`;
161
-
162
- const messages = await client.session.messages({
163
- path: { id: sessionId },
164
- });
165
- const messageData = messages.data;
166
-
167
- if (!messageData) return `No messages found in session ${sessionId}.`;
168
-
169
- let summary = `# Session: ${session.data.title || "Untitled"}\n\n`;
170
- summary += `**ID:** ${session.data.id}\n`;
171
- summary += `**Created:** ${session.data.time?.created ? new Date(session.data.time.created).toLocaleString() : "Unknown"}\n`;
172
- summary += `**Messages:** ${messageData.length}\n\n`;
173
-
174
- // Focus filtering
175
- if (args.focus) {
176
- summary += `**Focus:** ${args.focus}\n\n`;
177
- const focusLower = args.focus.toLowerCase();
178
-
179
- // Filter messages by keyword
180
- const relevant = messageData.filter(
181
- (m) =>
182
- m.info &&
183
- JSON.stringify(m.info).toLowerCase().includes(focusLower),
184
- );
185
- summary += `Found ${relevant.length} relevant messages.\n\n`;
186
-
187
- relevant.slice(0, 5).forEach((m, i) => {
188
- summary += `${i + 1}. **${m.info.role}**: `;
189
- const content = extractContent(m.info);
190
- summary += `${content.substring(0, 200)}\n\n`;
191
- });
192
- } else {
193
- // Show last 5 user messages
194
- const userMessages = messageData.filter(
195
- (m) => m.info?.role === "user",
196
- );
197
- summary += "## Recent User Messages\n\n";
198
- for (let i = 0; i < Math.min(userMessages.length, 5); i++) {
199
- const m = userMessages[i];
200
- const content = extractContent(m.info);
201
- summary += `${i + 1}. ${content.substring(0, 200)}\n`;
202
- }
203
-
204
- // Show last assistant message
205
- const assistantMessages = messageData.filter(
206
- (m) => m.info?.role === "assistant",
207
- );
208
- if (assistantMessages.length > 0) {
209
- const last = assistantMessages[assistantMessages.length - 1];
210
- const lastContent = extractContent(last.info);
211
- summary += `\n## Last Assistant Response\n\n${lastContent.substring(0, 500)}\n`;
212
- }
213
- }
214
-
215
- return summary;
216
- },
217
- }),
218
-
219
- search_session: tool({
220
- description: `Full-text search across session messages.
221
-
222
- Purpose:
223
- - Find sessions containing specific keywords
224
- - Searches up to 50 sessions (configurable via limit)
225
- - Returns match count and excerpt
226
-
227
- Example:
228
- search_session({ query: "authentication", limit: 5 })`,
229
- args: {
230
- query: tool.schema.string().describe("Search query text"),
23
+ query: tool.schema.string().describe("Search query"),
231
24
  limit: tool.schema
232
25
  .number()
233
26
  .optional()
@@ -241,7 +34,6 @@ summarize_session({ session_id: "def456" })`;
241
34
 
242
35
  if (!sessions.data) return "No sessions found.";
243
36
 
244
- // Search sessions until we find enough results
245
37
  for (const session of sessions.data) {
246
38
  if (results.length >= searchLimit) break;
247
39
 
@@ -250,11 +42,10 @@ summarize_session({ session_id: "def456" })`;
250
42
  path: { id: session.id },
251
43
  });
252
44
  const messageData = messages.data;
253
-
254
45
  if (!messageData) continue;
255
46
 
256
47
  const matches = messageData.filter(
257
- (m) =>
48
+ (m: any) =>
258
49
  m.info &&
259
50
  JSON.stringify(m.info)
260
51
  .toLowerCase()
@@ -264,81 +55,95 @@ summarize_session({ session_id: "def456" })`;
264
55
  if (matches.length > 0) {
265
56
  const excerpt = extractContent(matches[0].info) || "";
266
57
  results.push(
267
- `**${session.id}** - ${session.title || "Untitled"}\n Matches: ${matches.length}\n Excerpt: ${excerpt.substring(0, 150)}...`,
58
+ `**${session.id}** - ${session.title || "Untitled"}\n Matches: ${matches.length}\n ${excerpt.substring(0, 100)}...`,
268
59
  );
269
60
  }
270
61
 
271
62
  searched++;
272
- if (searched >= 50) break; // Don't search too many sessions
63
+ if (searched >= 50) break;
273
64
  } catch {
274
- // Skip inaccessible sessions
65
+ // Skip inaccessible
275
66
  }
276
67
  }
277
68
 
278
69
  if (results.length === 0)
279
- return `No matches found for "${args.query}" in ${searched} sessions searched.`;
70
+ return `No matches for "${args.query}" in ${searched} sessions.`;
280
71
 
281
- return `# Search Results: "${args.query}"\n\n${results.join("\n\n")}`;
72
+ return `# Results: "${args.query}"\n\n${results.join("\n\n")}`;
282
73
  },
283
74
  }),
284
75
 
285
- summarize_session: tool({
286
- description: `Generate AI summary of a session.
287
-
288
- Purpose:
289
- - Request OpenCode to summarize a session
290
- - Summary is generated asynchronously
291
- - Check back for the summary result
76
+ /** Like AmpCode's read_thread */
77
+ read_session: tool({
78
+ description: `Read session messages.
292
79
 
293
- Example:
294
- summarize_session({ session_id: "abc123" })`,
80
+ Example:
81
+ read_session({ session_id: "abc123" })
82
+ read_session({ session_id: "abc123", focus: "auth" })`,
295
83
  args: {
296
- session_id: tool.schema.string().describe("Session ID to summarize"),
84
+ session_id: tool.schema.string().describe("Session ID"),
85
+ focus: tool.schema.string().optional().describe("Filter by keyword"),
297
86
  },
298
- async execute(args: { session_id: string }) {
299
- // Request summary via OpenCode's summarization
300
- await client.session.summarize({
87
+ async execute(args: { session_id: string; focus?: string }) {
88
+ const session = await client.session.get({
89
+ path: { id: args.session_id },
90
+ });
91
+ if (!session.data) return `Session ${args.session_id} not found.`;
92
+
93
+ const messages = await client.session.messages({
301
94
  path: { id: args.session_id },
302
- body: { providerID: "proxypal", modelID: "gemini-3-flash-preview" },
303
95
  });
96
+ const messageData = messages.data;
97
+ if (!messageData) return `No messages in ${args.session_id}.`;
98
+
99
+ let summary = `# ${session.data.title || "Untitled"}\n`;
100
+ summary += `ID: ${session.data.id}\n`;
101
+ summary += `Created: ${session.data.time?.created ? new Date(session.data.time.created).toLocaleString() : "Unknown"}\n`;
102
+ summary += `Messages: ${messageData.length}\n\n`;
304
103
 
305
- return `Summarizing session ${args.session_id}... Summary will be available shortly.`;
104
+ if (args.focus) {
105
+ const focusLower = args.focus.toLowerCase();
106
+ const relevant = messageData.filter(
107
+ (m: any) =>
108
+ m.info &&
109
+ JSON.stringify(m.info).toLowerCase().includes(focusLower),
110
+ );
111
+ summary += `## Matching "${args.focus}" (${relevant.length})\n\n`;
112
+ relevant.slice(0, 5).forEach((m: any, i: number) => {
113
+ summary += `${i + 1}. **${m.info.role}**: ${extractContent(m.info).substring(0, 200)}\n\n`;
114
+ });
115
+ } else {
116
+ // Show recent user messages
117
+ const userMessages = messageData.filter(
118
+ (m: any) => m.info?.role === "user",
119
+ );
120
+ summary += "## Recent User Messages\n\n";
121
+ for (let i = 0; i < Math.min(userMessages.length, 5); i++) {
122
+ summary += `${i + 1}. ${extractContent(userMessages[i].info).substring(0, 200)}\n`;
123
+ }
124
+
125
+ // Last assistant response
126
+ const assistantMessages = messageData.filter(
127
+ (m: any) => m.info?.role === "assistant",
128
+ );
129
+ if (assistantMessages.length > 0) {
130
+ const last = assistantMessages[assistantMessages.length - 1];
131
+ summary += `\n## Last Response\n\n${extractContent(last.info).substring(0, 500)}\n`;
132
+ }
133
+ }
134
+
135
+ return summary;
306
136
  },
307
137
  }),
308
138
  },
309
139
  };
310
140
  };
311
141
 
312
- function parseDate(dateStr: string): Date | null {
313
- const today = new Date();
314
- today.setHours(0, 0, 0, 0);
315
-
316
- if (dateStr === "today") return today;
317
- if (dateStr === "yesterday") {
318
- const yesterday = new Date(today);
319
- yesterday.setDate(yesterday.getDate() - 1);
320
- return yesterday;
321
- }
322
- if (dateStr === "this week") {
323
- const weekStart = new Date(today);
324
- weekStart.setDate(weekStart.getDate() - weekStart.getDay());
325
- return weekStart;
326
- }
327
-
328
- const parsed = new Date(dateStr);
329
- if (!Number.isNaN(parsed.getTime())) return parsed;
330
-
331
- return null;
332
- }
333
-
334
142
  function extractContent(messageInfo: any): string {
335
143
  if (!messageInfo) return "[No info]";
336
-
337
- // Check for summary object
338
144
  if (typeof messageInfo.summary === "object" && messageInfo.summary !== null) {
339
145
  if (messageInfo.summary.title) return messageInfo.summary.title;
340
146
  if (messageInfo.summary.body) return messageInfo.summary.body;
341
147
  }
342
-
343
148
  return "[No content]";
344
149
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencodekit",
3
- "version": "0.17.7",
3
+ "version": "0.17.8",
4
4
  "description": "CLI tool for bootstrapping and managing OpenCodeKit projects",
5
5
  "keywords": ["agents", "cli", "mcp", "opencode", "opencodekit", "template"],
6
6
  "license": "MIT",
@@ -1,102 +0,0 @@
1
- ---
2
- description: Read-only media extraction specialist for OCR, PDF parsing, diagram interpretation, and structured visual content capture
3
- mode: subagent
4
- temperature: 0.1
5
- steps: 15
6
- tools:
7
- edit: false
8
- write: false
9
- bash: false
10
- task: false
11
- memory-update: false
12
- observation: false
13
- todowrite: false
14
- ---
15
-
16
- You are OpenCode, the best coding agent on the planet.
17
-
18
- # Looker Agent
19
-
20
- **Purpose**: Visual content translator — you extract signal from images without adding noise.
21
-
22
- > _"Seeing clearly is the first step toward acting correctly."_
23
-
24
- ## Identity
25
-
26
- You are a read-only media extraction specialist. You output only visible extracted content and clearly marked uncertainties.
27
-
28
- ## Task
29
-
30
- Extract text and structure from images, PDFs, screenshots, and diagrams.
31
-
32
- ## Rules
33
-
34
- - Never modify files — extraction only
35
- - Extract only visible content; never invent missing data
36
- - Preserve source language unless translation is explicitly requested
37
- - Mark uncertainty explicitly (`[unclear]`, `[unreadable]`)
38
-
39
- ## Before You Extract
40
-
41
- - **Extract only what's visible**: Never infer or invent content
42
- - **Mark uncertainty**: Use `[unclear]`, `[unreadable]` for ambiguous content
43
- - **Preserve structure**: Keep original formatting, layout, and language
44
-
45
- ## Scope
46
-
47
- ### Use For
48
-
49
- - OCR from screenshots/scans
50
- - PDF content extraction
51
- - Diagram component/flow extraction
52
- - Table extraction to markdown
53
-
54
- ### Do Not Use For
55
-
56
- - Design critique or UX review → delegate to `@vision`
57
- - Image generation/editing → delegate to `@painter`
58
- - Source-code analysis → delegate to `@explore`/`@build`
59
-
60
- ## Output
61
-
62
- | Media Type | Output Format |
63
- | ----------- | --------------------------------------- |
64
- | Text/OCR | Preserve source structure |
65
- | Tables | Markdown table format |
66
- | Diagrams | Components + relationships + flow steps |
67
- | Screenshots | Visible elements + state indicators |
68
-
69
- ## Output Schema
70
-
71
- ### OCR/PDF text
72
-
73
- - `text`
74
- - `language`
75
- - `uncertainties[]`
76
-
77
- ### Diagram
78
-
79
- - `diagram_type`
80
- - `components[]`
81
- - `relationships[]`
82
- - `flow_steps[]`
83
- - `uncertainties[]`
84
-
85
- ### Table
86
-
87
- - `columns[]`
88
- - `rows[]`
89
- - `notes`
90
-
91
- ### Screenshot/UI
92
-
93
- - `screen`
94
- - `elements[]`
95
- - `state[]`
96
- - `uncertainties[]`
97
-
98
- ## Failure Handling
99
-
100
- - **Low resolution**: extract legible content and mark unreadable parts
101
- - **Protected PDF**: state extraction is blocked
102
- - **Ambiguous handwriting**: return best-effort transcript with uncertainty markers