opencodekit 0.0.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/README.md +258 -0
- package/dist/index.js +3391 -0
- package/dist/template/.opencode/.env.example +193 -0
- package/dist/template/.opencode/AGENTS.md +214 -0
- package/dist/template/.opencode/README.md +269 -0
- package/dist/template/.opencode/agent/build.md +75 -0
- package/dist/template/.opencode/agent/explore.md +66 -0
- package/dist/template/.opencode/agent/planner.md +83 -0
- package/dist/template/.opencode/agent/review.md +90 -0
- package/dist/template/.opencode/agent/rush.md +85 -0
- package/dist/template/.opencode/agent/scout.md +93 -0
- package/dist/template/.opencode/command/analyze-project.md +39 -0
- package/dist/template/.opencode/command/brainstorm.md +11 -0
- package/dist/template/.opencode/command/commit.md +11 -0
- package/dist/template/.opencode/command/create.md +118 -0
- package/dist/template/.opencode/command/design.md +15 -0
- package/dist/template/.opencode/command/finish.md +233 -0
- package/dist/template/.opencode/command/fix-ci.md +20 -0
- package/dist/template/.opencode/command/fix-types.md +10 -0
- package/dist/template/.opencode/command/fix-ui.md +22 -0
- package/dist/template/.opencode/command/fix.md +22 -0
- package/dist/template/.opencode/command/handoff.md +146 -0
- package/dist/template/.opencode/command/implement.md +167 -0
- package/dist/template/.opencode/command/import-plan.md +188 -0
- package/dist/template/.opencode/command/integration-test.md +36 -0
- package/dist/template/.opencode/command/issue.md +41 -0
- package/dist/template/.opencode/command/plan.md +158 -0
- package/dist/template/.opencode/command/pr.md +36 -0
- package/dist/template/.opencode/command/quick-build.md +13 -0
- package/dist/template/.opencode/command/research-and-implement.md +21 -0
- package/dist/template/.opencode/command/research-ui.md +32 -0
- package/dist/template/.opencode/command/research.md +153 -0
- package/dist/template/.opencode/command/resume.md +127 -0
- package/dist/template/.opencode/command/review-codebase.md +13 -0
- package/dist/template/.opencode/command/skill-create.md +29 -0
- package/dist/template/.opencode/command/skill-optimize.md +28 -0
- package/dist/template/.opencode/command/status.md +109 -0
- package/dist/template/.opencode/command/ui-review.md +28 -0
- package/dist/template/.opencode/dcp.jsonc +34 -0
- package/dist/template/.opencode/memory/README.md +128 -0
- package/dist/template/.opencode/memory/_templates/handoff.md +33 -0
- package/dist/template/.opencode/memory/_templates/research.md +29 -0
- package/dist/template/.opencode/memory/_templates/task-prd.md +43 -0
- package/dist/template/.opencode/memory/_templates/task-review.md +73 -0
- package/dist/template/.opencode/memory/_templates/task-spec.md +71 -0
- package/dist/template/.opencode/memory/design-guidelines.md +281 -0
- package/dist/template/.opencode/memory/handoffs/README.md +83 -0
- package/dist/template/.opencode/opencode.json +469 -0
- package/dist/template/.opencode/package.json +23 -0
- package/dist/template/.opencode/pickle-thinker.jsonc +11 -0
- package/dist/template/.opencode/plugin/README.md +162 -0
- package/dist/template/.opencode/plugin/notification.ts +88 -0
- package/dist/template/.opencode/plugin/sessions.ts +434 -0
- package/dist/template/.opencode/plugin/superpowers.ts +332 -0
- package/dist/template/.opencode/plugin/tsconfig.json +15 -0
- package/dist/template/.opencode/superpowers/.claude/settings.local.json +141 -0
- package/dist/template/.opencode/superpowers/.claude-plugin/marketplace.json +20 -0
- package/dist/template/.opencode/superpowers/.claude-plugin/plugin.json +13 -0
- package/dist/template/.opencode/superpowers/.codex/INSTALL.md +35 -0
- package/dist/template/.opencode/superpowers/.codex/superpowers-bootstrap.md +33 -0
- package/dist/template/.opencode/superpowers/.codex/superpowers-codex +267 -0
- package/dist/template/.opencode/superpowers/.github/FUNDING.yml +3 -0
- package/dist/template/.opencode/superpowers/.opencode/INSTALL.md +135 -0
- package/dist/template/.opencode/superpowers/.opencode/plugin/superpowers.js +215 -0
- package/dist/template/.opencode/superpowers/LICENSE +21 -0
- package/dist/template/.opencode/superpowers/README.md +165 -0
- package/dist/template/.opencode/superpowers/RELEASE-NOTES.md +493 -0
- package/dist/template/.opencode/superpowers/agents/code-reviewer.md +48 -0
- package/dist/template/.opencode/superpowers/commands/brainstorm.md +5 -0
- package/dist/template/.opencode/superpowers/commands/execute-plan.md +5 -0
- package/dist/template/.opencode/superpowers/commands/write-plan.md +5 -0
- package/dist/template/.opencode/superpowers/docs/README.codex.md +153 -0
- package/dist/template/.opencode/superpowers/docs/README.opencode.md +234 -0
- package/dist/template/.opencode/superpowers/docs/plans/2025-11-22-opencode-support-design.md +294 -0
- package/dist/template/.opencode/superpowers/docs/plans/2025-11-22-opencode-support-implementation.md +1095 -0
- package/dist/template/.opencode/superpowers/hooks/hooks.json +15 -0
- package/dist/template/.opencode/superpowers/hooks/session-start.sh +34 -0
- package/dist/template/.opencode/superpowers/lib/skills-core.js +208 -0
- package/dist/template/.opencode/superpowers/skills/brainstorming/SKILL.md +54 -0
- package/dist/template/.opencode/superpowers/skills/condition-based-waiting/SKILL.md +120 -0
- package/dist/template/.opencode/superpowers/skills/condition-based-waiting/example.ts +158 -0
- package/dist/template/.opencode/superpowers/skills/defense-in-depth/SKILL.md +127 -0
- package/dist/template/.opencode/superpowers/skills/dispatching-parallel-agents/SKILL.md +180 -0
- package/dist/template/.opencode/superpowers/skills/executing-plans/SKILL.md +76 -0
- package/dist/template/.opencode/superpowers/skills/finishing-a-development-branch/SKILL.md +200 -0
- package/dist/template/.opencode/superpowers/skills/frontend-aesthetics/SKILL.md +137 -0
- package/dist/template/.opencode/superpowers/skills/gemini-large-context/SKILL.md +205 -0
- package/dist/template/.opencode/superpowers/skills/receiving-code-review/SKILL.md +209 -0
- package/dist/template/.opencode/superpowers/skills/requesting-code-review/SKILL.md +105 -0
- package/dist/template/.opencode/superpowers/skills/requesting-code-review/code-reviewer.md +146 -0
- package/dist/template/.opencode/superpowers/skills/root-cause-tracing/SKILL.md +174 -0
- package/dist/template/.opencode/superpowers/skills/root-cause-tracing/find-polluter.sh +63 -0
- package/dist/template/.opencode/superpowers/skills/sharing-skills/SKILL.md +194 -0
- package/dist/template/.opencode/superpowers/skills/subagent-driven-development/SKILL.md +189 -0
- package/dist/template/.opencode/superpowers/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/dist/template/.opencode/superpowers/skills/systematic-debugging/SKILL.md +295 -0
- package/dist/template/.opencode/superpowers/skills/systematic-debugging/test-academic.md +14 -0
- package/dist/template/.opencode/superpowers/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/dist/template/.opencode/superpowers/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/dist/template/.opencode/superpowers/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/dist/template/.opencode/superpowers/skills/test-driven-development/SKILL.md +364 -0
- package/dist/template/.opencode/superpowers/skills/testing-anti-patterns/SKILL.md +302 -0
- package/dist/template/.opencode/superpowers/skills/testing-skills-with-subagents/SKILL.md +387 -0
- package/dist/template/.opencode/superpowers/skills/testing-skills-with-subagents/examples/CLAUDE_MD_TESTING.md +189 -0
- package/dist/template/.opencode/superpowers/skills/ui-ux-research/SKILL.md +191 -0
- package/dist/template/.opencode/superpowers/skills/using-git-worktrees/SKILL.md +213 -0
- package/dist/template/.opencode/superpowers/skills/using-superpowers/SKILL.md +101 -0
- package/dist/template/.opencode/superpowers/skills/verification-before-completion/SKILL.md +139 -0
- package/dist/template/.opencode/superpowers/skills/writing-plans/SKILL.md +116 -0
- package/dist/template/.opencode/superpowers/skills/writing-skills/SKILL.md +622 -0
- package/dist/template/.opencode/superpowers/skills/writing-skills/anthropic-best-practices.md +1150 -0
- package/dist/template/.opencode/superpowers/skills/writing-skills/graphviz-conventions.dot +172 -0
- package/dist/template/.opencode/superpowers/skills/writing-skills/persuasion-principles.md +187 -0
- package/dist/template/.opencode/superpowers/tests/opencode/run-tests.sh +165 -0
- package/dist/template/.opencode/superpowers/tests/opencode/setup.sh +73 -0
- package/dist/template/.opencode/superpowers/tests/opencode/test-plugin-loading.sh +81 -0
- package/dist/template/.opencode/superpowers/tests/opencode/test-priority.sh +198 -0
- package/dist/template/.opencode/superpowers/tests/opencode/test-skills-core.sh +440 -0
- package/dist/template/.opencode/superpowers/tests/opencode/test-tools.sh +104 -0
- package/dist/template/.opencode/tool/memory-read.ts +66 -0
- package/dist/template/.opencode/tool/memory-update.ts +61 -0
- package/dist/template/.opencode/tsconfig.json +21 -0
- package/package.json +52 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode Notification Plugin
|
|
3
|
+
* Sends native notifications when sessions complete
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
7
|
+
|
|
8
|
+
export const NotificationPlugin: Plugin = async ({ client, $ }) => {
|
|
9
|
+
// Log plugin initialization
|
|
10
|
+
client.app
|
|
11
|
+
.log({
|
|
12
|
+
body: {
|
|
13
|
+
service: "notification-plugin",
|
|
14
|
+
level: "info",
|
|
15
|
+
message: "🔔 Notification Plugin loaded",
|
|
16
|
+
},
|
|
17
|
+
})
|
|
18
|
+
.catch(() => {});
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
event: async ({ event }) => {
|
|
22
|
+
// Send notification on session completion
|
|
23
|
+
if (event.type === "session.idle") {
|
|
24
|
+
const sessionId = event.properties?.sessionID;
|
|
25
|
+
|
|
26
|
+
// Get session summary with simple retry
|
|
27
|
+
setTimeout(async () => {
|
|
28
|
+
try {
|
|
29
|
+
let summary = "Session completed";
|
|
30
|
+
|
|
31
|
+
// Try to fetch session summary
|
|
32
|
+
const messagesResponse = await client.session.messages({
|
|
33
|
+
path: { id: sessionId },
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (messagesResponse.data && Array.isArray(messagesResponse.data)) {
|
|
37
|
+
const lastUserMessage = messagesResponse.data
|
|
38
|
+
.filter((m) => m.info.role === "user")
|
|
39
|
+
.pop();
|
|
40
|
+
|
|
41
|
+
// Type guard for summary object
|
|
42
|
+
const messageSummary = lastUserMessage?.info?.summary;
|
|
43
|
+
if (
|
|
44
|
+
messageSummary &&
|
|
45
|
+
typeof messageSummary === "object" &&
|
|
46
|
+
messageSummary !== null
|
|
47
|
+
) {
|
|
48
|
+
if ("body" in messageSummary && messageSummary.body) {
|
|
49
|
+
summary = String(messageSummary.body).trim().slice(0, 100);
|
|
50
|
+
} else if ("title" in messageSummary && messageSummary.title) {
|
|
51
|
+
summary = String(messageSummary.title).trim();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Send notification
|
|
57
|
+
if (process.platform === "darwin") {
|
|
58
|
+
const script = `display notification "${summary.replace(/"/g, '\\"')}" with title "OpenCode"`;
|
|
59
|
+
await $`osascript -e ${script}`;
|
|
60
|
+
} else {
|
|
61
|
+
await $`notify-send "OpenCode" ${summary}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
client.app
|
|
65
|
+
.log({
|
|
66
|
+
body: {
|
|
67
|
+
service: "notification-plugin",
|
|
68
|
+
level: "info",
|
|
69
|
+
message: `✅ Notification sent: ${summary}`,
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
.catch(() => {});
|
|
73
|
+
} catch (error) {
|
|
74
|
+
client.app
|
|
75
|
+
.log({
|
|
76
|
+
body: {
|
|
77
|
+
service: "notification-plugin",
|
|
78
|
+
level: "warn",
|
|
79
|
+
message: `⚠️ Notification failed: ${(error as Error).message}`,
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
.catch(() => {});
|
|
83
|
+
}
|
|
84
|
+
}, 2000);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
};
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { type Plugin, tool } from "@opencode-ai/plugin";
|
|
4
|
+
import { readFile } from "fs/promises";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* read-session plugin - AmpCode-style thread context transfer
|
|
8
|
+
*
|
|
9
|
+
* Enables short, focused sessions by allowing cross-session context reads.
|
|
10
|
+
* Similar to AmpCode's read_thread tool.
|
|
11
|
+
*
|
|
12
|
+
* OpenCode Storage Structure:
|
|
13
|
+
* - Sessions: ~/.local/share/opencode/storage/session/<project_hash>/<session_id>.json
|
|
14
|
+
* - Messages: ~/.local/share/opencode/storage/message/<session_id>/msg_*.json
|
|
15
|
+
* - Diffs: ~/.local/share/opencode/storage/session_diff/<session_id>.json
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
interface SessionMetadata {
|
|
19
|
+
id: string;
|
|
20
|
+
title?: string;
|
|
21
|
+
directory?: string;
|
|
22
|
+
projectID?: string;
|
|
23
|
+
time?: {
|
|
24
|
+
created: number;
|
|
25
|
+
updated: number;
|
|
26
|
+
};
|
|
27
|
+
summary?: any;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface SessionInfo extends SessionMetadata {
|
|
31
|
+
messageCount?: number;
|
|
32
|
+
fileCount?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseDate(dateStr: string): Date | null {
|
|
36
|
+
const today = new Date();
|
|
37
|
+
today.setHours(0, 0, 0, 0);
|
|
38
|
+
|
|
39
|
+
if (dateStr === "today") {
|
|
40
|
+
return today;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (dateStr === "yesterday") {
|
|
44
|
+
const yesterday = new Date(today);
|
|
45
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
46
|
+
return yesterday;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (dateStr === "this week") {
|
|
50
|
+
const weekStart = new Date(today);
|
|
51
|
+
weekStart.setDate(weekStart.getDate() - weekStart.getDay());
|
|
52
|
+
return weekStart;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Try parsing ISO date
|
|
56
|
+
const parsed = new Date(dateStr);
|
|
57
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
58
|
+
return parsed;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const SessionsPlugin: Plugin = async ({ directory }) => {
|
|
65
|
+
const storageDir = join(
|
|
66
|
+
process.env.HOME || "",
|
|
67
|
+
".local/share/opencode/storage",
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
async function getSessionMetadata(
|
|
71
|
+
sessionId: string,
|
|
72
|
+
): Promise<SessionMetadata | null> {
|
|
73
|
+
const sessionDir = join(storageDir, "session");
|
|
74
|
+
if (!existsSync(sessionDir)) return null;
|
|
75
|
+
|
|
76
|
+
// Search all project directories
|
|
77
|
+
const projectDirs = readdirSync(sessionDir);
|
|
78
|
+
for (const projectHash of projectDirs) {
|
|
79
|
+
const sessionFile = join(sessionDir, projectHash, `${sessionId}.json`);
|
|
80
|
+
if (existsSync(sessionFile)) {
|
|
81
|
+
const content = await readFile(sessionFile, "utf-8");
|
|
82
|
+
return JSON.parse(content);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function listSessions(
|
|
89
|
+
projectDir?: string,
|
|
90
|
+
since?: string,
|
|
91
|
+
limit = 20,
|
|
92
|
+
): Promise<SessionInfo[]> {
|
|
93
|
+
const messageDir = join(storageDir, "message");
|
|
94
|
+
if (!existsSync(messageDir)) return [];
|
|
95
|
+
|
|
96
|
+
const sessionIds = readdirSync(messageDir).filter((f) =>
|
|
97
|
+
f.startsWith("ses_"),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const sessions: SessionInfo[] = [];
|
|
101
|
+
let sinceDate: Date | null = null;
|
|
102
|
+
|
|
103
|
+
if (since) {
|
|
104
|
+
sinceDate = parseDate(since);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const sessionId of sessionIds) {
|
|
108
|
+
const metadata = await getSessionMetadata(sessionId);
|
|
109
|
+
if (!metadata) continue;
|
|
110
|
+
|
|
111
|
+
// Filter by project
|
|
112
|
+
if (projectDir && metadata.directory !== projectDir) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Filter by date
|
|
117
|
+
if (sinceDate && metadata.time) {
|
|
118
|
+
const sessionDate = new Date(metadata.time.created);
|
|
119
|
+
if (sessionDate < sinceDate) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Get message count
|
|
125
|
+
const sessionMsgDir = join(messageDir, sessionId);
|
|
126
|
+
let messageCount = 0;
|
|
127
|
+
if (existsSync(sessionMsgDir)) {
|
|
128
|
+
messageCount = readdirSync(sessionMsgDir).filter((f) =>
|
|
129
|
+
f.endsWith(".json"),
|
|
130
|
+
).length;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Get file count
|
|
134
|
+
const diffFile = join(storageDir, "session_diff", `${sessionId}.json`);
|
|
135
|
+
let fileCount = 0;
|
|
136
|
+
if (existsSync(diffFile)) {
|
|
137
|
+
const diffContent = await readFile(diffFile, "utf-8");
|
|
138
|
+
const diffs = JSON.parse(diffContent);
|
|
139
|
+
fileCount = diffs.length;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
sessions.push({
|
|
143
|
+
...metadata,
|
|
144
|
+
messageCount,
|
|
145
|
+
fileCount,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Sort by time descending
|
|
150
|
+
sessions.sort((a, b) => {
|
|
151
|
+
const aTime = a.time?.created || 0;
|
|
152
|
+
const bTime = b.time?.created || 0;
|
|
153
|
+
return bTime - aTime;
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return sessions.slice(0, limit);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
tool: {
|
|
161
|
+
list_sessions: tool({
|
|
162
|
+
description:
|
|
163
|
+
"List OpenCode sessions with metadata. Filter by project and date. Use this before read_session to discover available sessions.",
|
|
164
|
+
args: {
|
|
165
|
+
project: tool.schema
|
|
166
|
+
.string()
|
|
167
|
+
.optional()
|
|
168
|
+
.describe(
|
|
169
|
+
"Filter by project: 'current' (default), 'all', or absolute path",
|
|
170
|
+
),
|
|
171
|
+
since: tool.schema
|
|
172
|
+
.string()
|
|
173
|
+
.optional()
|
|
174
|
+
.describe(
|
|
175
|
+
"Filter by date: 'today', 'yesterday', 'this week', or ISO date (e.g. '2024-12-10')",
|
|
176
|
+
),
|
|
177
|
+
limit: tool.schema
|
|
178
|
+
.number()
|
|
179
|
+
.optional()
|
|
180
|
+
.describe("Max sessions to return (default: 20)"),
|
|
181
|
+
},
|
|
182
|
+
async execute(args) {
|
|
183
|
+
const projectFilter =
|
|
184
|
+
!args.project || args.project === "current"
|
|
185
|
+
? directory
|
|
186
|
+
: args.project === "all"
|
|
187
|
+
? undefined
|
|
188
|
+
: args.project;
|
|
189
|
+
|
|
190
|
+
const sessions = await listSessions(
|
|
191
|
+
projectFilter,
|
|
192
|
+
args.since,
|
|
193
|
+
args.limit || 20,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
if (sessions.length === 0) {
|
|
197
|
+
return "No sessions found matching filters.";
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let output = `# Sessions (${sessions.length})\n\n`;
|
|
201
|
+
|
|
202
|
+
if (projectFilter && projectFilter !== "all") {
|
|
203
|
+
output += `**Project:** ${projectFilter}\n`;
|
|
204
|
+
}
|
|
205
|
+
if (args.since) {
|
|
206
|
+
output += `**Since:** ${args.since}\n`;
|
|
207
|
+
}
|
|
208
|
+
output += "\n";
|
|
209
|
+
|
|
210
|
+
sessions.forEach((s, i) => {
|
|
211
|
+
const date = s.time?.created
|
|
212
|
+
? new Date(s.time.created).toLocaleString()
|
|
213
|
+
: "Unknown";
|
|
214
|
+
const title = s.title || s.id;
|
|
215
|
+
output += `${i + 1}. **${s.id}**\n`;
|
|
216
|
+
output += ` Title: ${title}\n`;
|
|
217
|
+
output += ` Date: ${date}\n`;
|
|
218
|
+
output += ` Messages: ${s.messageCount || 0}, Files: ${s.fileCount || 0}\n`;
|
|
219
|
+
if (s.summary && typeof s.summary === "object") {
|
|
220
|
+
const sumObj = s.summary as any;
|
|
221
|
+
if (
|
|
222
|
+
sumObj.additions !== undefined ||
|
|
223
|
+
sumObj.deletions !== undefined
|
|
224
|
+
) {
|
|
225
|
+
output += ` Changes: +${sumObj.additions || 0}/-${sumObj.deletions || 0}\n`;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
output += "\n";
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
return output;
|
|
232
|
+
},
|
|
233
|
+
}),
|
|
234
|
+
|
|
235
|
+
read_session: tool({
|
|
236
|
+
description: `Read context from a previous OpenCode session. Use list_sessions first to discover sessions. Supports project filtering and date-based selection.`,
|
|
237
|
+
args: {
|
|
238
|
+
session_reference: tool.schema
|
|
239
|
+
.string()
|
|
240
|
+
.describe(
|
|
241
|
+
"Session ID (e.g. 'ses_abc123'), relative ref ('last', 'previous', '2 ago'), or date ('today', 'yesterday')",
|
|
242
|
+
),
|
|
243
|
+
project: tool.schema
|
|
244
|
+
.string()
|
|
245
|
+
.optional()
|
|
246
|
+
.describe(
|
|
247
|
+
"Filter by project: 'current' (default), 'all', or absolute path. Applied when using relative/date references.",
|
|
248
|
+
),
|
|
249
|
+
focus: tool.schema
|
|
250
|
+
.string()
|
|
251
|
+
.optional()
|
|
252
|
+
.describe(
|
|
253
|
+
"Optional: specific aspect to extract (e.g. 'implementation', 'bug findings', 'file changes')",
|
|
254
|
+
),
|
|
255
|
+
},
|
|
256
|
+
async execute(args) {
|
|
257
|
+
if (!existsSync(storageDir)) {
|
|
258
|
+
return "Error: OpenCode storage directory not found.";
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
let sessionId = args.session_reference;
|
|
262
|
+
const projectFilter =
|
|
263
|
+
!args.project || args.project === "current"
|
|
264
|
+
? directory
|
|
265
|
+
: args.project === "all"
|
|
266
|
+
? undefined
|
|
267
|
+
: args.project;
|
|
268
|
+
|
|
269
|
+
// Handle date-based references
|
|
270
|
+
const parsedDate = parseDate(sessionId);
|
|
271
|
+
if (parsedDate) {
|
|
272
|
+
const sessions = await listSessions(projectFilter, sessionId, 1);
|
|
273
|
+
if (sessions.length === 0) {
|
|
274
|
+
return `Error: No sessions found for '${sessionId}' in ${projectFilter || "all projects"}`;
|
|
275
|
+
}
|
|
276
|
+
sessionId = sessions[0].id;
|
|
277
|
+
}
|
|
278
|
+
// Handle relative references
|
|
279
|
+
else if (sessionId.match(/^(last|previous|\d+\s*(ago|back))$/i)) {
|
|
280
|
+
const sessions = await listSessions(projectFilter, undefined, 50);
|
|
281
|
+
|
|
282
|
+
if (sessions.length === 0) {
|
|
283
|
+
return `Error: No sessions found in ${projectFilter || "all projects"}`;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (
|
|
287
|
+
sessionId.toLowerCase() === "last" ||
|
|
288
|
+
sessionId.toLowerCase() === "previous"
|
|
289
|
+
) {
|
|
290
|
+
sessionId = sessions[0].id;
|
|
291
|
+
} else {
|
|
292
|
+
const match = sessionId.match(/^(\d+)/);
|
|
293
|
+
const index = match ? Number.parseInt(match[1]) : 1;
|
|
294
|
+
if (index >= sessions.length) {
|
|
295
|
+
return `Error: Only ${sessions.length} sessions available, requested ${index} ago`;
|
|
296
|
+
}
|
|
297
|
+
sessionId = sessions[index].id;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Clean session ID
|
|
302
|
+
sessionId = sessionId.split("/").pop() || sessionId;
|
|
303
|
+
if (!sessionId.startsWith("ses_")) {
|
|
304
|
+
return `Error: Invalid session ID format. Expected 'ses_...' but got '${sessionId}'`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Read session metadata, messages, and diffs
|
|
308
|
+
const messageDir = join(storageDir, "message", sessionId);
|
|
309
|
+
const diffFile = join(
|
|
310
|
+
storageDir,
|
|
311
|
+
"session_diff",
|
|
312
|
+
`${sessionId}.json`,
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
if (!existsSync(messageDir)) {
|
|
316
|
+
const available = await listSessions(projectFilter, undefined, 5);
|
|
317
|
+
const list = available.map((s) => s.id).join("\n");
|
|
318
|
+
return `Error: Session '${sessionId}' not found.\n\nRecent sessions:\n${list}`;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const metadata = await getSessionMetadata(sessionId);
|
|
322
|
+
|
|
323
|
+
// Read all message files
|
|
324
|
+
const messageFiles = readdirSync(messageDir).filter((f) =>
|
|
325
|
+
f.endsWith(".json"),
|
|
326
|
+
);
|
|
327
|
+
const messages = await Promise.all(
|
|
328
|
+
messageFiles.map(async (file) => {
|
|
329
|
+
const content = await readFile(join(messageDir, file), "utf-8");
|
|
330
|
+
return JSON.parse(content);
|
|
331
|
+
}),
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
// Read diffs if available
|
|
335
|
+
let diffs = [];
|
|
336
|
+
if (existsSync(diffFile)) {
|
|
337
|
+
const diffContent = await readFile(diffFile, "utf-8");
|
|
338
|
+
diffs = JSON.parse(diffContent);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Build summary
|
|
342
|
+
let summary = `# Session: ${sessionId}\n\n`;
|
|
343
|
+
|
|
344
|
+
if (metadata) {
|
|
345
|
+
summary += `**Title:** ${metadata.title || "Untitled"}\n`;
|
|
346
|
+
summary += `**Project:** ${metadata.directory || "Unknown"}\n`;
|
|
347
|
+
if (metadata.time?.created) {
|
|
348
|
+
summary += `**Date:** ${new Date(metadata.time.created).toLocaleString()}\n`;
|
|
349
|
+
}
|
|
350
|
+
summary += "\n";
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Message analysis
|
|
354
|
+
const userMessages = messages.filter((m: any) => m.role === "user");
|
|
355
|
+
const assistantMessages = messages.filter(
|
|
356
|
+
(m: any) => m.role === "assistant",
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
summary += `**Messages:** ${messages.length} total (${userMessages.length} user, ${assistantMessages.length} assistant)\n`;
|
|
360
|
+
|
|
361
|
+
if (diffs.length > 0) {
|
|
362
|
+
const totalAdditions = diffs.reduce(
|
|
363
|
+
(sum: number, d: any) => sum + (d.additions || 0),
|
|
364
|
+
0,
|
|
365
|
+
);
|
|
366
|
+
const totalDeletions = diffs.reduce(
|
|
367
|
+
(sum: number, d: any) => sum + (d.deletions || 0),
|
|
368
|
+
0,
|
|
369
|
+
);
|
|
370
|
+
summary += `**File Changes:** ${diffs.length} files (+${totalAdditions}/-${totalDeletions})\n\n`;
|
|
371
|
+
} else {
|
|
372
|
+
summary += "\n";
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Focus filtering
|
|
376
|
+
if (args.focus) {
|
|
377
|
+
summary += `**Focus:** ${args.focus}\n\n`;
|
|
378
|
+
|
|
379
|
+
if (
|
|
380
|
+
args.focus.toLowerCase().includes("file") ||
|
|
381
|
+
args.focus.toLowerCase().includes("change") ||
|
|
382
|
+
args.focus.toLowerCase().includes("diff")
|
|
383
|
+
) {
|
|
384
|
+
// Show file changes
|
|
385
|
+
if (diffs.length > 0) {
|
|
386
|
+
summary += `## File Changes\n\n`;
|
|
387
|
+
diffs.slice(0, 10).forEach((diff: any) => {
|
|
388
|
+
summary += `### ${diff.file}\n`;
|
|
389
|
+
summary += `+${diff.additions || 0}/-${diff.deletions || 0}\n\n`;
|
|
390
|
+
if (diff.before || diff.after) {
|
|
391
|
+
summary += `**Before:** ${(diff.before || "").substring(0, 200)}...\n`;
|
|
392
|
+
summary += `**After:** ${(diff.after || "").substring(0, 200)}...\n\n`;
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
// Filter messages by keyword
|
|
398
|
+
const relevant = messages.filter((m: any) =>
|
|
399
|
+
JSON.stringify(m)
|
|
400
|
+
.toLowerCase()
|
|
401
|
+
.includes(args.focus!.toLowerCase()),
|
|
402
|
+
);
|
|
403
|
+
summary += `Found ${relevant.length} relevant messages.\n\n`;
|
|
404
|
+
}
|
|
405
|
+
} else {
|
|
406
|
+
// Full summary
|
|
407
|
+
summary += `## User Messages\n\n`;
|
|
408
|
+
userMessages.slice(0, 5).forEach((m: any, i: number) => {
|
|
409
|
+
const content = m.summary?.title || m.content || "[No content]";
|
|
410
|
+
summary += `${i + 1}. ${content.substring(0, 200)}\n`;
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
if (diffs.length > 0) {
|
|
414
|
+
summary += `\n## Files Modified\n\n`;
|
|
415
|
+
diffs.slice(0, 10).forEach((diff: any) => {
|
|
416
|
+
summary += `- ${diff.file} (+${diff.additions || 0}/-${diff.deletions || 0})\n`;
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Last assistant message
|
|
421
|
+
if (assistantMessages.length > 0) {
|
|
422
|
+
const last = assistantMessages[assistantMessages.length - 1];
|
|
423
|
+
const lastContent =
|
|
424
|
+
last.summary?.body || last.content || "[No content]";
|
|
425
|
+
summary += `\n## Last Assistant Response\n\n${lastContent.substring(0, 500)}\n`;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return summary;
|
|
430
|
+
},
|
|
431
|
+
}),
|
|
432
|
+
},
|
|
433
|
+
};
|
|
434
|
+
};
|