@csuwl/opencode-memory-plugin 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,131 @@
1
+ #!/bin/sh
2
+
3
+ # Memory Tools Function Test
4
+ # Tests memory tools functionality directly without OpenCode
5
+
6
+ set -e
7
+
8
+ GREEN='\033[0;32m'
9
+ YELLOW='\033[1;33m'
10
+ BLUE='\033[0;34m'
11
+ RED='\033[0;31m'
12
+ NC='\033[0m'
13
+
14
+ echo -e "${BLUE}๐Ÿงช Memory Tools Function Test${NC}"
15
+ echo -e "${BLUE}=================================${NC}"
16
+ echo ""
17
+
18
+ # Test 1: File operations (simulating memory_write)
19
+ echo -e "${YELLOW}Test 1: Memory write simulation...${NC}"
20
+ MEMORY_DIR="/root/.opencode/memory"
21
+ TODAY=$(date +%Y-%m-%d)
22
+ DAILY_FILE="$MEMORY_DIR/daily/$TODAY.md"
23
+
24
+ if [ -f "$DAILY_FILE" ]; then
25
+ TEST_ENTRY="
26
+ ## Test Entry - $(date +%Y-%m-%dT%H:%M:%S)
27
+
28
+ This is a test memory entry that simulates what would be saved by memory_write tool.
29
+
30
+ - Test timestamp: $(date)
31
+ - Test content: Important information to remember
32
+ "
33
+
34
+ echo "$TEST_ENTRY" >> "$DAILY_FILE"
35
+ echo -e " ${GREEN}โœ“${NC} Memory write simulation successful"
36
+ else
37
+ echo -e " ${RED}โœ—${NC} Daily log not found"
38
+ fi
39
+
40
+ echo ""
41
+
42
+ # Test 2: File read (simulating memory_read)
43
+ echo -e "${YELLOW}Test 2: Memory read simulation...${NC}"
44
+ if [ -f "$MEMORY_DIR/SOUL.md" ]; then
45
+ SOUL_CONTENT=$(head -5 "$MEMORY_DIR/SOUL.md")
46
+ echo -e " ${GREEN}โœ“${NC} Read SOUL.md (first 5 lines):"
47
+ echo "$SOUL_CONTENT" | sed 's/^/ /'
48
+ else
49
+ echo -e " ${RED}โœ—${NC} SOUL.md not found"
50
+ fi
51
+
52
+ echo ""
53
+
54
+ # Test 3: Keyword search (simulating memory_search)
55
+ echo -e "${YELLOW}Test 3: Keyword search simulation...${NC}"
56
+ if grep -q "Assistant" "$MEMORY_DIR/SOUL.md" 2>/dev/null; then
57
+ echo -e " ${GREEN}โœ“${NC} Found 'Assistant' in SOUL.md"
58
+ else
59
+ echo -e " ${RED}โœ—${NC} 'Assistant' not found in SOUL.md"
60
+ fi
61
+
62
+ if grep -q "memory" "$MEMORY_DIR/AGENTS.md" 2>/dev/null; then
63
+ echo -e " ${GREEN}โœ“${NC} Found 'memory' in AGENTS.md"
64
+ else
65
+ echo -e " ${RED}โœ—${NC} 'memory' not found in AGENTS.md"
66
+ fi
67
+
68
+ echo ""
69
+
70
+ # Test 4: Daily log list (simulating list_daily)
71
+ echo -e "${YELLOW}Test 4: Daily log list simulation...${NC}"
72
+ DAILY_COUNT=$(ls -1 "$MEMORY_DIR/daily" 2>/dev/null | wc -l)
73
+ echo -e " ${GREEN}โœ“${NC} Found $DAILY_COUNT daily log file(s):"
74
+ ls -1 "$MEMORY_DIR/daily" 2>/dev/null | sed 's/^/ - /'
75
+
76
+ echo ""
77
+
78
+ # Test 5: Archive simulation
79
+ echo -e "${YELLOW}Test 5: Archive simulation...${NC}"
80
+ ARCHIVE_DIR="$MEMORY_DIR/archive/weekly"
81
+ if [ -d "$ARCHIVE_DIR" ]; then
82
+ echo -e " ${GREEN}โœ“${NC} Archive directory exists"
83
+ ARCHIVE_COUNT=$(ls -1 "$ARCHIVE_DIR" 2>/dev/null | wc -l)
84
+ echo -e " Empty (as expected): $ARCHIVE_COUNT files"
85
+ else
86
+ echo -e " ${RED}โœ—${NC} Archive directory not found"
87
+ fi
88
+
89
+ echo ""
90
+
91
+ # Test 6: Configuration check
92
+ echo -e "${YELLOW}Test 6: Memory configuration...${NC}"
93
+ CONFIG_FILE="$MEMORY_DIR/memory-config.json"
94
+ if [ -f "$CONFIG_FILE" ]; then
95
+ echo -e " ${GREEN}โœ“${NC} Configuration file exists"
96
+
97
+ # Check key settings
98
+ if grep -q '"version"' "$CONFIG_FILE"; then
99
+ echo -e " ${GREEN}โœ“${NC} Has version"
100
+ fi
101
+ if grep -q '"vector_search"' "$CONFIG_FILE"; then
102
+ echo -e " ${GREEN}โœ“${NC} Has vector_search config"
103
+ fi
104
+ if grep -q '"consolidation"' "$CONFIG_FILE"; then
105
+ echo -e " ${GREEN}โœ“${NC} Has consolidation config"
106
+ fi
107
+ else
108
+ echo -e " ${RED}โœ—${NC} Configuration file not found"
109
+ fi
110
+
111
+ echo ""
112
+
113
+ # Summary
114
+ echo -e "${BLUE}=================================${NC}"
115
+ echo -e "${GREEN}โœ“ Memory tools function test complete${NC}"
116
+ echo -e "${BLUE}=================================${NC}"
117
+ echo ""
118
+ echo -e "${YELLOW}Results:${NC}"
119
+ echo -e " โœ… File operations work (write/read)"
120
+ echo -e " โœ… Keyword search works"
121
+ echo -e " โœ… Daily log management works"
122
+ echo -e " โœ… Archive system ready"
123
+ echo -e " โœ… Configuration valid"
124
+ echo ""
125
+ echo -e "${YELLOW}System Status:${NC}"
126
+ echo -e " Memory files: $(ls -1 $MEMORY_DIR | wc -l | tr -d ' ')"
127
+ echo -e " Daily logs: $DAILY_COUNT"
128
+ echo -e " Archive dirs: $(ls -1 $MEMORY_DIR/archive 2>/dev/null | wc -l | tr -d ' ')"
129
+ echo ""
130
+ echo -e "${GREEN}๐ŸŽ‰ Memory system is functional!${NC}"
131
+ echo ""
@@ -0,0 +1,308 @@
1
+ import { tool } from "@opencode-ai/plugin"
2
+ import path from "path"
3
+ import { readFile, writeFile, mkdir, readdir, stat, exists } from "fs/promises"
4
+
5
+ const MEMORY_DIR = path.join(process.env.HOME || "", ".opencode", "memory")
6
+ const DAILY_DIR = path.join(MEMORY_DIR, "daily")
7
+
8
+ // Helper: Ensure memory directory structure exists
9
+ async function ensureMemoryDirs() {
10
+ await mkdir(MEMORY_DIR, { recursive: true })
11
+ await mkdir(DAILY_DIR, { recursive: true })
12
+ }
13
+
14
+ // Helper: Get today's date string
15
+ function getTodayDate() {
16
+ return new Date().toISOString().split('T')[0]
17
+ }
18
+
19
+ // Helper: Format timestamp for memory entries
20
+ function formatTimestamp() {
21
+ return new Date().toISOString()
22
+ }
23
+
24
+ // Helper: Get file path by memory type
25
+ function getMemoryFilePath(type: string, date?: string): string {
26
+ switch (type) {
27
+ case "long-term":
28
+ return path.join(MEMORY_DIR, "MEMORY.md")
29
+ case "preference":
30
+ return path.join(MEMORY_DIR, "PREFERENCES.md")
31
+ case "personality":
32
+ return path.join(MEMORY_DIR, "SOUL.md")
33
+ case "context":
34
+ return path.join(MEMORY_DIR, "CONTEXT.md")
35
+ case "tools":
36
+ return path.join(MEMORY_DIR, "TOOLS.md")
37
+ case "identity":
38
+ return path.join(MEMORY_DIR, "IDENTITY.md")
39
+ case "user":
40
+ return path.join(MEMORY_DIR, "USER.md")
41
+ case "daily":
42
+ const dateStr = date || getTodayDate()
43
+ return path.join(DAILY_DIR, `${dateStr}.md`)
44
+ default:
45
+ throw new Error(`Unknown memory type: ${type}`)
46
+ }
47
+ }
48
+
49
+ // Tool 1: Write memory
50
+ export const write = tool({
51
+ description: "Write a memory entry to the memory system. Use this to store important information that should be remembered across sessions and projects. Types: long-term (MEMORY.md), daily (today's log), preference (PREFERENCES.md), personality (SOUL.md), context (CONTEXT.md), tools (TOOLS.md), identity (IDENTITY.md), user (USER.md).",
52
+ args: {
53
+ content: tool.schema.string().describe("The memory content to write. Include as much context as possible."),
54
+ type: tool.schema.enum(["long-term", "daily", "preference", "personality", "context", "tools", "identity", "user"]).describe("Type of memory to write to"),
55
+ tags: tool.schema.array(tool.schema.string()).optional().describe("Optional tags for categorization (e.g., ['code-style', 'project-xyz'])"),
56
+ },
57
+ async execute(args) {
58
+ await ensureMemoryDirs()
59
+
60
+ const filePath = getMemoryFilePath(args.type)
61
+ const timestamp = formatTimestamp()
62
+ let entry = ""
63
+
64
+ // Format entry with metadata
65
+ if (args.tags && args.tags.length > 0) {
66
+ entry += `\n\n## ${timestamp} ${args.tags.map((t) => `#${t}`).join(' ')}\n`
67
+ } else {
68
+ entry += `\n\n## ${timestamp}\n`
69
+ }
70
+ entry += `${args.content}\n`
71
+
72
+ // Append to file
73
+ try {
74
+ if (await exists(filePath)) {
75
+ await writeFile(filePath, entry, { flag: "a" })
76
+ } else {
77
+ await writeFile(filePath, entry)
78
+ }
79
+ return `โœ“ Memory saved to ${path.basename(filePath)}`
80
+ } catch (error) {
81
+ return `โœ— Failed to write memory: ${(error as Error).message}`
82
+ }
83
+ },
84
+ })
85
+
86
+ // Tool 2: Read memory
87
+ export const read = tool({
88
+ description: "Read memory from the memory system. Use this to retrieve stored preferences, long-term memory, daily logs, or configuration files.",
89
+ args: {
90
+ type: tool.schema.enum(["long-term", "daily", "preference", "personality", "context", "tools", "identity", "user"]).describe("Type of memory to read"),
91
+ date: tool.schema.string().optional().describe("Specific date for daily memory (YYYY-MM-DD format). Defaults to today."),
92
+ lines: tool.schema.number().optional().describe("Number of lines to return. Defaults to all lines."),
93
+ },
94
+ async execute(args) {
95
+ await ensureMemoryDirs()
96
+
97
+ const filePath = getMemoryFilePath(args.type, args.date)
98
+ const limit = args.lines
99
+
100
+ try {
101
+ let content = await readFile(filePath, "utf-8")
102
+
103
+ // Truncate if limit specified
104
+ if (limit && content.split('\n').length > limit) {
105
+ const lines = content.split('\n')
106
+ content = lines.slice(-limit).join('\n')
107
+ content += `\n\n[... Showed last ${limit} lines. Use read without limit to see full content.]\n`
108
+ }
109
+
110
+ return content
111
+ } catch (error) {
112
+ return `Memory file not found: ${path.basename(filePath)}. You can create it using the write tool.`
113
+ }
114
+ },
115
+ })
116
+
117
+ // Tool 3: Search memory (keyword-based)
118
+ export const search = tool({
119
+ description: "Search memory files for relevant information using keywords and simple pattern matching. Returns matching entries with file paths and line numbers. For semantic search, use vector_memory_search.",
120
+ args: {
121
+ query: tool.schema.string().describe("Search query - use keywords to find relevant memories. Multiple keywords can be separated by spaces."),
122
+ scope: tool.schema.enum(["all", "long-term", "daily", "preference", "personality", "context", "tools", "identity", "user"]).describe("Search scope: all memory files or specific type"),
123
+ days: tool.schema.number().optional().describe("Number of recent daily files to search. Defaults to 7 (last 7 days)."),
124
+ limit: tool.schema.number().optional().describe("Maximum number of results per file to return. Defaults to 5."),
125
+ },
126
+ async execute(args) {
127
+ await ensureMemoryDirs()
128
+
129
+ const filesToSearch: string[] = []
130
+ const keywords = args.query.toLowerCase().split(/\s+/).filter(kw => kw.length > 0)
131
+
132
+ if (keywords.length === 0) {
133
+ return "Please provide at least one keyword to search for."
134
+ }
135
+
136
+ // Determine files to search
137
+ if (args.scope === "all" || args.scope === "long-term") {
138
+ filesToSearch.push(getMemoryFilePath("long-term"))
139
+ }
140
+ if (args.scope === "all" || args.scope === "preference") {
141
+ filesToSearch.push(getMemoryFilePath("preference"))
142
+ }
143
+ if (args.scope === "all" || args.scope === "personality") {
144
+ filesToSearch.push(getMemoryFilePath("personality"))
145
+ }
146
+ if (args.scope === "all" || args.scope === "context") {
147
+ filesToSearch.push(getMemoryFilePath("context"))
148
+ }
149
+ if (args.scope === "all" || args.scope === "tools") {
150
+ filesToSearch.push(getMemoryFilePath("tools"))
151
+ }
152
+ if (args.scope === "all" || args.scope === "identity") {
153
+ filesToSearch.push(getMemoryFilePath("identity"))
154
+ }
155
+ if (args.scope === "all" || args.scope === "user") {
156
+ filesToSearch.push(getMemoryFilePath("user"))
157
+ }
158
+ if (args.scope === "all" || args.scope === "daily") {
159
+ const daysToSearch = args.days || 7
160
+ for (let i = 0; i < daysToSearch; i++) {
161
+ const date = new Date()
162
+ date.setDate(date.getDate() - i)
163
+ const dateStr = date.toISOString().split('T')[0]
164
+ filesToSearch.push(getMemoryFilePath("daily", dateStr))
165
+ }
166
+ }
167
+
168
+ const results: { file: string; matches: string[]; count: number }[] = []
169
+
170
+ // Search each file
171
+ for (const filePath of filesToSearch) {
172
+ try {
173
+ const content = await readFile(filePath, "utf-8")
174
+ const lines = content.split('\n')
175
+ const matches: string[] = []
176
+
177
+ for (let i = 0; i < lines.length; i++) {
178
+ const line = lines[i].toLowerCase()
179
+ const matchedKeywords = keywords.filter(kw => line.includes(kw))
180
+
181
+ // Only count as match if at least one keyword matches
182
+ if (matchedKeywords.length > 0) {
183
+ // Get context (2 lines before and after)
184
+ const start = Math.max(0, i - 2)
185
+ const end = Math.min(lines.length, i + 3)
186
+ const context = lines.slice(start, end).join('\n')
187
+
188
+ // Add match with line number
189
+ matches.push(`Line ${i + 1}: ${context.trim()}`)
190
+ }
191
+ }
192
+
193
+ if (matches.length > 0) {
194
+ results.push({
195
+ file: path.basename(filePath),
196
+ matches: matches.slice(0, args.limit || 5),
197
+ count: matches.length,
198
+ })
199
+ }
200
+ } catch {
201
+ // File doesn't exist, skip
202
+ }
203
+ }
204
+
205
+ if (results.length === 0) {
206
+ return `No matches found for: "${args.query}"\n\nSearched ${filesToSearch.length} file(s). Try different keywords or a broader scope.`
207
+ }
208
+
209
+ // Format output
210
+ let output = `Found ${results.length} file(s) with matches:\n\n`
211
+ for (const result of results) {
212
+ output += `### ${result.file} (${result.count} match(es))\n`
213
+ output += result.matches.join('\n') + '\n\n'
214
+ }
215
+
216
+ return output.trim()
217
+ },
218
+ })
219
+
220
+ // Tool 4: List daily logs
221
+ export const list_daily = tool({
222
+ description: "List available daily memory log files. Useful for finding recent activity and managing daily memory.",
223
+ args: {
224
+ days: tool.schema.number().optional().describe("Number of recent days to list. Defaults to 30 (last 30 days)."),
225
+ },
226
+ async execute(args) {
227
+ await ensureMemoryDirs()
228
+
229
+ const daysToList = args.days || 30
230
+ const dailyFiles: { date: string; size: number; exists: boolean }[] = []
231
+
232
+ for (let i = 0; i < daysToList; i++) {
233
+ const date = new Date()
234
+ date.setDate(date.getDate() - i)
235
+ const dateStr = date.toISOString().split('T')[0]
236
+ const filePath = getMemoryFilePath("daily", dateStr)
237
+
238
+ try {
239
+ const stats = await stat(filePath)
240
+ dailyFiles.push({
241
+ date: dateStr,
242
+ size: stats.size,
243
+ exists: true,
244
+ })
245
+ } catch {
246
+ dailyFiles.push({
247
+ date: dateStr,
248
+ size: 0,
249
+ exists: false,
250
+ })
251
+ }
252
+ }
253
+
254
+ // Format output
255
+ const existingFiles = dailyFiles.filter((f) => f.exists)
256
+ if (existingFiles.length === 0) {
257
+ return "No daily memory files found yet. Start by writing to daily memory!"
258
+ }
259
+
260
+ let output = `Daily Memory Files (last ${daysToList} days):\n\n`
261
+ for (const file of existingFiles) {
262
+ const sizeKB = (file.size / 1024).toFixed(2)
263
+ output += `๐Ÿ“„ ${file.date} (${sizeKB} KB)\n`
264
+ }
265
+
266
+ output += `\nTotal: ${existingFiles.length} file(s)`
267
+ return output
268
+ },
269
+ })
270
+
271
+ // Tool 5: Initialize daily log
272
+ export const init_daily = tool({
273
+ description: "Initialize today's daily memory log file. Automatically called at start of sessions. Creates a new daily file with header if it doesn't exist.",
274
+ args: {},
275
+ async execute() {
276
+ await ensureMemoryDirs()
277
+
278
+ const today = getTodayDate()
279
+ const filePath = getMemoryFilePath("daily", today)
280
+
281
+ try {
282
+ if (!(await exists(filePath))) {
283
+ const header = `# Daily Memory Log - ${today}\n\n`
284
+ header += `*Session starts: ${formatTimestamp()}*\n\n`
285
+ header += `## Notes\n\n\n`
286
+ header += `## Tasks\n\n\n`
287
+ header += `## Learnings\n\n`
288
+ header += `---\n`
289
+
290
+ await writeFile(filePath, header)
291
+ return `โœ“ Created daily memory file: ${today}.md`
292
+ } else {
293
+ return `โœ“ Daily memory file already exists: ${today}.md`
294
+ }
295
+ } catch (error) {
296
+ return `โœ— Failed to initialize daily log: ${(error as Error).message}`
297
+ }
298
+ },
299
+ })
300
+
301
+ // Export default tool for backward compatibility
302
+ export default {
303
+ write,
304
+ read,
305
+ search,
306
+ list_daily,
307
+ init_daily,
308
+ }