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 +1 -1
- package/dist/template/.opencode/AGENTS.md +2 -1
- package/dist/template/.opencode/agent/general.md +1 -1
- package/dist/template/.opencode/agent/painter.md +1 -1
- package/dist/template/.opencode/agent/vision.md +1 -1
- package/dist/template/.opencode/opencode.json +2 -5
- package/dist/template/.opencode/package.json +1 -1
- package/dist/template/.opencode/plugin/sessions.ts +71 -266
- package/package.json +1 -1
- package/dist/template/.opencode/agent/looker.md +0 -102
- package/dist/template/.opencode/plugin/hashline.ts.bak +0 -757
- package/dist/template/.opencode/plugin/swarm-enforcer.ts +0 -371
- package/dist/template/.opencode/tool/swarm.ts +0 -605
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.
|
|
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
|
-
-
|
|
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
|
|
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
|
-
-
|
|
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": {
|
|
@@ -1,233 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenCode Session Tools
|
|
3
|
-
*
|
|
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
|
-
|
|
73
|
-
|
|
16
|
+
/** Like AmpCode's find_thread */
|
|
17
|
+
find_sessions: tool({
|
|
18
|
+
description: `Search sessions by keyword.
|
|
74
19
|
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
|
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;
|
|
63
|
+
if (searched >= 50) break;
|
|
273
64
|
} catch {
|
|
274
|
-
// Skip inaccessible
|
|
65
|
+
// Skip inaccessible
|
|
275
66
|
}
|
|
276
67
|
}
|
|
277
68
|
|
|
278
69
|
if (results.length === 0)
|
|
279
|
-
return `No matches
|
|
70
|
+
return `No matches for "${args.query}" in ${searched} sessions.`;
|
|
280
71
|
|
|
281
|
-
return `#
|
|
72
|
+
return `# Results: "${args.query}"\n\n${results.join("\n\n")}`;
|
|
282
73
|
},
|
|
283
74
|
}),
|
|
284
75
|
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
|
|
294
|
-
|
|
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
|
|
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
|
-
|
|
300
|
-
|
|
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
|
-
|
|
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,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
|