@geekbeer/minion 2.68.6 → 2.73.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.
package/core/api.js CHANGED
@@ -88,75 +88,75 @@ async function sendHeartbeat(data) {
88
88
 
89
89
  /**
90
90
  * Create a project thread (help or discussion) on HQ.
91
- * @param {object} data - { project_id, title, description, thread_type?, mentions?, context? }
91
+ * @param {object} data - { project_id, title, content, thread_type?, mentions?, context? }
92
92
  * @returns {Promise<{ thread: object }>}
93
93
  */
94
- async function createHelpThread(data) {
95
- return request('/help-threads', {
94
+ async function createThread(data) {
95
+ return request('/threads', {
96
96
  method: 'POST',
97
97
  body: JSON.stringify(data),
98
98
  })
99
99
  }
100
100
 
101
101
  /**
102
- * Get open help threads in this minion's projects.
102
+ * Get open threads in this minion's projects.
103
103
  * @returns {Promise<{ threads: object[] }>}
104
104
  */
105
- async function getOpenHelpThreads() {
106
- return request('/help-threads/open')
105
+ async function getOpenThreads() {
106
+ return request('/threads/open')
107
107
  }
108
108
 
109
109
  /**
110
- * Get a help thread by ID with messages.
110
+ * Get a thread by ID with messages.
111
111
  * @param {string} threadId
112
112
  * @returns {Promise<{ thread: object, messages: object[] }>}
113
113
  */
114
- async function getHelpThread(threadId) {
115
- return request(`/help-threads/${threadId}`)
114
+ async function getThread(threadId) {
115
+ return request(`/threads/${threadId}`)
116
116
  }
117
117
 
118
118
  /**
119
- * Post a message to a help thread.
119
+ * Post a message to a thread.
120
120
  * @param {string} threadId
121
121
  * @param {object} data - { content, attachments?, mentions? }
122
122
  */
123
- async function postHelpMessage(threadId, data) {
124
- return request(`/help-threads/${threadId}/messages`, {
123
+ async function postThreadMessage(threadId, data) {
124
+ return request(`/threads/${threadId}/messages`, {
125
125
  method: 'POST',
126
126
  body: JSON.stringify(data),
127
127
  })
128
128
  }
129
129
 
130
130
  /**
131
- * Resolve a help thread.
131
+ * Resolve a thread.
132
132
  * @param {string} threadId
133
133
  * @param {string} resolution
134
134
  */
135
- async function resolveHelpThread(threadId, resolution) {
136
- return request(`/help-threads/${threadId}/resolve`, {
135
+ async function resolveThread(threadId, resolution) {
136
+ return request(`/threads/${threadId}/resolve`, {
137
137
  method: 'PATCH',
138
138
  body: JSON.stringify({ resolution }),
139
139
  })
140
140
  }
141
141
 
142
142
  /**
143
- * Cancel a help thread.
143
+ * Cancel a thread.
144
144
  * @param {string} threadId
145
145
  * @param {string} [reason]
146
146
  */
147
- async function cancelHelpThread(threadId, reason) {
148
- return request(`/help-threads/${threadId}/cancel`, {
147
+ async function cancelThread(threadId, reason) {
148
+ return request(`/threads/${threadId}/cancel`, {
149
149
  method: 'PATCH',
150
150
  body: JSON.stringify({ reason: reason || null }),
151
151
  })
152
152
  }
153
153
 
154
154
  /**
155
- * Permanently delete a help thread (PM only).
155
+ * Permanently delete a thread (PM only).
156
156
  * @param {string} threadId
157
157
  */
158
- async function deleteHelpThread(threadId) {
159
- return request(`/help-threads/${threadId}`, {
158
+ async function deleteThread(threadId) {
159
+ return request(`/threads/${threadId}`, {
160
160
  method: 'DELETE',
161
161
  })
162
162
  }
@@ -192,14 +192,13 @@ module.exports = {
192
192
  reportStepComplete,
193
193
  reportIssue,
194
194
  sendHeartbeat,
195
- createHelpThread,
196
- getOpenHelpThreads,
197
- getHelpThread,
198
- postHelpMessage,
199
- resolveHelpThread,
200
-
201
- cancelHelpThread,
202
- deleteHelpThread,
195
+ createThread,
196
+ getOpenThreads,
197
+ getThread,
198
+ postThreadMessage,
199
+ resolveThread,
200
+ cancelThread,
201
+ deleteThread,
203
202
  createProjectMemory,
204
203
  searchProjectMemories,
205
204
  }
package/core/db.js ADDED
@@ -0,0 +1,297 @@
1
+ /**
2
+ * SQLite Database Module
3
+ * Provides a singleton SQLite database connection for all minion stores.
4
+ * Uses better-sqlite3 (synchronous API) with WAL mode for performance.
5
+ *
6
+ * Database file: $DATA_DIR/minion.db
7
+ *
8
+ * FTS5 uses the trigram tokenizer for Japanese/CJK language support.
9
+ * Trigram splits text into 3-character sequences, enabling substring search
10
+ * in any language without a language-specific tokenizer.
11
+ * Note: trigram requires search queries of 3+ characters.
12
+ * Shorter queries fall back to LIKE-based search in the store layer.
13
+ */
14
+
15
+ const path = require('path')
16
+ const fs = require('fs')
17
+
18
+ const { DATA_DIR } = require('./lib/platform')
19
+ const { config } = require('./config')
20
+
21
+ let _db = null
22
+
23
+ /**
24
+ * Resolve the database file path.
25
+ * Uses DATA_DIR if accessible, otherwise falls back to HOME_DIR.
26
+ */
27
+ function resolveDbPath() {
28
+ try {
29
+ fs.accessSync(DATA_DIR, fs.constants.W_OK)
30
+ return path.join(DATA_DIR, 'minion.db')
31
+ } catch {
32
+ return path.join(config.HOME_DIR, 'minion.db')
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Get the singleton database connection.
38
+ * Initializes the database and schema on first call.
39
+ * @returns {import('better-sqlite3').Database}
40
+ */
41
+ function getDb() {
42
+ if (_db) return _db
43
+
44
+ const dbPath = resolveDbPath()
45
+ fs.mkdirSync(path.dirname(dbPath), { recursive: true })
46
+
47
+ const Database = require('better-sqlite3')
48
+ _db = new Database(dbPath)
49
+
50
+ // Performance & integrity settings
51
+ _db.pragma('journal_mode = WAL')
52
+ _db.pragma('foreign_keys = ON')
53
+
54
+ initSchema(_db)
55
+ migrateSchema(_db)
56
+
57
+ console.log(`[DB] SQLite database initialized: ${dbPath}`)
58
+ return _db
59
+ }
60
+
61
+ /**
62
+ * Initialize all tables, indices, FTS virtual tables, and triggers.
63
+ * All statements use IF NOT EXISTS for idempotency.
64
+ */
65
+ function initSchema(db) {
66
+ db.exec(`
67
+ -- ==================== memories ====================
68
+ CREATE TABLE IF NOT EXISTS memories (
69
+ id TEXT PRIMARY KEY,
70
+ title TEXT NOT NULL DEFAULT 'Untitled',
71
+ category TEXT NOT NULL DEFAULT 'reference'
72
+ CHECK (category IN ('user', 'feedback', 'project', 'reference')),
73
+ content TEXT NOT NULL DEFAULT '',
74
+ created_at TEXT NOT NULL,
75
+ updated_at TEXT NOT NULL
76
+ );
77
+
78
+ CREATE INDEX IF NOT EXISTS idx_memories_category ON memories(category);
79
+ CREATE INDEX IF NOT EXISTS idx_memories_updated_at ON memories(updated_at DESC);
80
+
81
+ -- FTS5 with trigram tokenizer for Japanese/CJK support
82
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
83
+ title,
84
+ content,
85
+ content=memories,
86
+ content_rowid=rowid,
87
+ tokenize='trigram'
88
+ );
89
+
90
+ -- Triggers to keep FTS index in sync with memories table
91
+ CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
92
+ INSERT INTO memories_fts(rowid, title, content)
93
+ VALUES (new.rowid, new.title, new.content);
94
+ END;
95
+
96
+ CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
97
+ INSERT INTO memories_fts(memories_fts, rowid, title, content)
98
+ VALUES ('delete', old.rowid, old.title, old.content);
99
+ END;
100
+
101
+ CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
102
+ INSERT INTO memories_fts(memories_fts, rowid, title, content)
103
+ VALUES ('delete', old.rowid, old.title, old.content);
104
+ INSERT INTO memories_fts(rowid, title, content)
105
+ VALUES (new.rowid, new.title, new.content);
106
+ END;
107
+
108
+ -- ==================== daily_logs ====================
109
+ CREATE TABLE IF NOT EXISTS daily_logs (
110
+ date TEXT PRIMARY KEY,
111
+ content TEXT NOT NULL DEFAULT '',
112
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
113
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
114
+ );
115
+
116
+ -- FTS5 with trigram tokenizer for Japanese/CJK support
117
+ CREATE VIRTUAL TABLE IF NOT EXISTS daily_logs_fts USING fts5(
118
+ content,
119
+ content=daily_logs,
120
+ content_rowid=rowid,
121
+ tokenize='trigram'
122
+ );
123
+
124
+ CREATE TRIGGER IF NOT EXISTS daily_logs_ai AFTER INSERT ON daily_logs BEGIN
125
+ INSERT INTO daily_logs_fts(rowid, content)
126
+ VALUES (new.rowid, new.content);
127
+ END;
128
+
129
+ CREATE TRIGGER IF NOT EXISTS daily_logs_ad AFTER DELETE ON daily_logs BEGIN
130
+ INSERT INTO daily_logs_fts(daily_logs_fts, rowid, content)
131
+ VALUES ('delete', old.rowid, old.content);
132
+ END;
133
+
134
+ CREATE TRIGGER IF NOT EXISTS daily_logs_au AFTER UPDATE ON daily_logs BEGIN
135
+ INSERT INTO daily_logs_fts(daily_logs_fts, rowid, content)
136
+ VALUES ('delete', old.rowid, old.content);
137
+ INSERT INTO daily_logs_fts(rowid, content)
138
+ VALUES (new.rowid, new.content);
139
+ END;
140
+
141
+ -- ==================== chat_sessions ====================
142
+ CREATE TABLE IF NOT EXISTS chat_sessions (
143
+ session_id TEXT PRIMARY KEY,
144
+ turn_count INTEGER NOT NULL DEFAULT 0,
145
+ created_at INTEGER NOT NULL,
146
+ updated_at INTEGER NOT NULL
147
+ );
148
+
149
+ CREATE TABLE IF NOT EXISTS chat_messages (
150
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
151
+ session_id TEXT NOT NULL REFERENCES chat_sessions(session_id) ON DELETE CASCADE,
152
+ role TEXT NOT NULL,
153
+ content TEXT NOT NULL,
154
+ timestamp INTEGER NOT NULL
155
+ );
156
+
157
+ CREATE INDEX IF NOT EXISTS idx_chat_messages_session ON chat_messages(session_id, id);
158
+
159
+ -- ==================== executions ====================
160
+ CREATE TABLE IF NOT EXISTS executions (
161
+ id TEXT PRIMARY KEY,
162
+ skill_name TEXT,
163
+ workflow_id TEXT,
164
+ status TEXT,
165
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
166
+ data TEXT NOT NULL
167
+ );
168
+
169
+ CREATE INDEX IF NOT EXISTS idx_executions_workflow ON executions(workflow_id);
170
+ CREATE INDEX IF NOT EXISTS idx_executions_created ON executions(created_at DESC);
171
+
172
+ -- ==================== routines ====================
173
+ CREATE TABLE IF NOT EXISTS routines (
174
+ id TEXT PRIMARY KEY,
175
+ name TEXT NOT NULL,
176
+ data TEXT NOT NULL
177
+ );
178
+
179
+ CREATE INDEX IF NOT EXISTS idx_routines_name ON routines(name);
180
+
181
+ -- ==================== workflows ====================
182
+ CREATE TABLE IF NOT EXISTS workflows (
183
+ id TEXT PRIMARY KEY,
184
+ name TEXT NOT NULL,
185
+ data TEXT NOT NULL
186
+ );
187
+
188
+ CREATE INDEX IF NOT EXISTS idx_workflows_name ON workflows(name);
189
+
190
+ -- ==================== schema_version ====================
191
+ CREATE TABLE IF NOT EXISTS schema_version (
192
+ version INTEGER PRIMARY KEY,
193
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
194
+ );
195
+ `)
196
+ }
197
+
198
+ /**
199
+ * Run schema migrations for existing databases.
200
+ * Each migration checks schema_version to avoid re-running.
201
+ */
202
+ function migrateSchema(db) {
203
+ const currentVersion = db.prepare(
204
+ 'SELECT COALESCE(MAX(version), 0) as v FROM schema_version'
205
+ ).get().v
206
+
207
+ if (currentVersion < 1) {
208
+ // Migration 1: Recreate FTS tables with trigram tokenizer for Japanese support.
209
+ // The default unicode61 tokenizer doesn't tokenize CJK text correctly.
210
+ console.log('[DB] Migration 1: Switching FTS5 to trigram tokenizer...')
211
+
212
+ db.exec(`
213
+ -- Drop old FTS tables and triggers (may use unicode61 tokenizer)
214
+ DROP TRIGGER IF EXISTS memories_ai;
215
+ DROP TRIGGER IF EXISTS memories_ad;
216
+ DROP TRIGGER IF EXISTS memories_au;
217
+ DROP TABLE IF EXISTS memories_fts;
218
+
219
+ DROP TRIGGER IF EXISTS daily_logs_ai;
220
+ DROP TRIGGER IF EXISTS daily_logs_ad;
221
+ DROP TRIGGER IF EXISTS daily_logs_au;
222
+ DROP TABLE IF EXISTS daily_logs_fts;
223
+
224
+ -- Recreate with trigram tokenizer
225
+ CREATE VIRTUAL TABLE memories_fts USING fts5(
226
+ title,
227
+ content,
228
+ content=memories,
229
+ content_rowid=rowid,
230
+ tokenize='trigram'
231
+ );
232
+
233
+ CREATE TRIGGER memories_ai AFTER INSERT ON memories BEGIN
234
+ INSERT INTO memories_fts(rowid, title, content)
235
+ VALUES (new.rowid, new.title, new.content);
236
+ END;
237
+
238
+ CREATE TRIGGER memories_ad AFTER DELETE ON memories BEGIN
239
+ INSERT INTO memories_fts(memories_fts, rowid, title, content)
240
+ VALUES ('delete', old.rowid, old.title, old.content);
241
+ END;
242
+
243
+ CREATE TRIGGER memories_au AFTER UPDATE ON memories BEGIN
244
+ INSERT INTO memories_fts(memories_fts, rowid, title, content)
245
+ VALUES ('delete', old.rowid, old.title, old.content);
246
+ INSERT INTO memories_fts(rowid, title, content)
247
+ VALUES (new.rowid, new.title, new.content);
248
+ END;
249
+
250
+ CREATE VIRTUAL TABLE daily_logs_fts USING fts5(
251
+ content,
252
+ content=daily_logs,
253
+ content_rowid=rowid,
254
+ tokenize='trigram'
255
+ );
256
+
257
+ CREATE TRIGGER daily_logs_ai AFTER INSERT ON daily_logs BEGIN
258
+ INSERT INTO daily_logs_fts(rowid, content)
259
+ VALUES (new.rowid, new.content);
260
+ END;
261
+
262
+ CREATE TRIGGER daily_logs_ad AFTER DELETE ON daily_logs BEGIN
263
+ INSERT INTO daily_logs_fts(daily_logs_fts, rowid, content)
264
+ VALUES ('delete', old.rowid, old.content);
265
+ END;
266
+
267
+ CREATE TRIGGER daily_logs_au AFTER UPDATE ON daily_logs BEGIN
268
+ INSERT INTO daily_logs_fts(daily_logs_fts, rowid, content)
269
+ VALUES ('delete', old.rowid, old.content);
270
+ INSERT INTO daily_logs_fts(rowid, content)
271
+ VALUES (new.rowid, new.content);
272
+ END;
273
+
274
+ -- Rebuild FTS index from existing data
275
+ INSERT INTO memories_fts(memories_fts) VALUES ('rebuild');
276
+ INSERT INTO daily_logs_fts(daily_logs_fts) VALUES ('rebuild');
277
+
278
+ INSERT INTO schema_version (version) VALUES (1);
279
+ `)
280
+
281
+ console.log('[DB] Migration 1 complete: FTS5 trigram tokenizer applied')
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Close the database connection.
287
+ * Call this during graceful shutdown to flush WAL.
288
+ */
289
+ function closeDb() {
290
+ if (_db) {
291
+ _db.close()
292
+ _db = null
293
+ console.log('[DB] SQLite database closed')
294
+ }
295
+ }
296
+
297
+ module.exports = { getDb, closeDb }
@@ -10,7 +10,7 @@
10
10
  * 3. Rate limiting: max 1 LLM evaluation per thread per 5 minutes
11
11
  *
12
12
  * Flow per poll cycle:
13
- * 1. GET /api/minion/help-threads/open → list of open threads
13
+ * 1. GET /api/minion/threads/open → list of open threads
14
14
  * 2. For each thread with new activity:
15
15
  * a. Check mentions → if mentioned, must evaluate
16
16
  * b. Check read state → skip if no new messages
@@ -98,7 +98,7 @@ async function pollOnce() {
98
98
 
99
99
  polling = true
100
100
  try {
101
- const data = await api.request('/help-threads/open')
101
+ const data = await api.request('/threads/open')
102
102
 
103
103
  if (!data.threads || data.threads.length === 0) {
104
104
  return
@@ -139,7 +139,7 @@ async function processThread(thread, myProjects, now) {
139
139
  // Fetch thread detail to get message count and check for new messages
140
140
  let detail
141
141
  try {
142
- detail = await api.request(`/help-threads/${thread.id}`)
142
+ detail = await api.request(`/threads/${thread.id}`)
143
143
  } catch {
144
144
  return
145
145
  }
@@ -155,6 +155,13 @@ async function processThread(thread, myProjects, now) {
155
155
  // New messages detected
156
156
  const newMessages = messages.slice(state.lastMessageCount)
157
157
 
158
+ // Skip if all new messages are from self (avoid re-triggering on own posts)
159
+ const newFromOthers = newMessages.filter(m => m.sender_minion_id !== config.MINION_ID)
160
+ if (newFromOthers.length === 0) {
161
+ readState.set(thread.id, { ...state, lastMessageCount: messageCount })
162
+ return
163
+ }
164
+
158
165
  // Update read state (message count only — eval timestamp updated after LLM call)
159
166
  readState.set(thread.id, { ...state, lastMessageCount: messageCount })
160
167
 
@@ -187,16 +194,16 @@ async function evaluateWithLlm(threadSummary, threadDetail, allMessages, newMess
187
194
  const messageHistory = recentMessages
188
195
  .map(m => {
189
196
  const sender = m.sender_minion_id
190
- ? (m.sender_minion_id === config.MINION_ID ? 'You' : `Minion(${m.sender_minion_id.slice(0, 8)})`)
191
- : 'User'
197
+ ? (m.sender_minion_id === config.MINION_ID ? 'You' : (m.sender_name || `Minion(${m.sender_minion_id.slice(0, 8)})`))
198
+ : (m.sender_name || 'User')
192
199
  return `[${sender}] ${m.content}`
193
200
  })
194
201
  .join('\n')
195
202
 
196
203
  const threadType = threadDetail.thread_type || 'help'
197
- const mentionNote = mentioned
198
- ? '\n\nあなたはこのスレッドでメンションされています。必ず返信してください。'
199
- : ''
204
+ const isRequester = threadDetail.requester_minion_id === config.MINION_ID
205
+ const mentions = threadDetail.mentions || []
206
+ const mentionsSummary = mentions.length > 0 ? mentions.join(', ') : 'なし(全員に向けた投稿)'
200
207
 
201
208
  // Extract optional context metadata
202
209
  const ctx = threadDetail.context || {}
@@ -205,13 +212,14 @@ async function evaluateWithLlm(threadSummary, threadDetail, allMessages, newMess
205
212
  ctx.attempted_resolution ? `試行済み: ${ctx.attempted_resolution}` : '',
206
213
  ].filter(Boolean).join('\n')
207
214
 
208
- const prompt = `あなたはプロジェクト「${myProject.name}」のチームメンバー(ロール: ${myProject.role})です。
215
+ const prompt = `あなたはプロジェクト「${myProject.name}」のチームメンバー(ロール: ${myProject.role}、ID: ${config.MINION_ID})です。
209
216
  以下のスレッドに対して、あなたが返信すべきかどうかを判断し、返信する場合はその内容を生成してください。
210
217
 
211
218
  スレッドタイプ: ${threadType}
212
219
  タイトル: ${threadDetail.title}
213
- 説明: ${threadDetail.description}
214
- ${contextInfo}${mentionNote}
220
+ 起票者: ${isRequester ? 'あなた自身' : `他のミニオン(${threadDetail.requester_minion_id?.slice(0, 8)})`}
221
+ メンション対象: ${mentionsSummary}
222
+ ${contextInfo}
215
223
 
216
224
  --- メッセージ履歴 ---
217
225
  ${messageHistory || '(メッセージなし)'}
@@ -225,10 +233,11 @@ ${messageHistory || '(メッセージなし)'}
225
233
  }
226
234
 
227
235
  判断基準:
236
+ - 自分が起票したスレッドの場合、他のメンバーの回答を待つべき(追加情報がある場合を除く)
237
+ - メンション対象が特定のロールやミニオンに限定されている場合、自分が対象でなければ静観する
228
238
  - 自分のロール(${myProject.role})に関連する話題か
229
239
  - 自分が貢献できる知見や意見があるか
230
240
  - 既に十分な回答がある場合は重複を避ける
231
- - メンションされている場合は必ず返信する
232
241
  - 人間に聞くべき場合は @user メンションを含めて返信する`
233
242
 
234
243
  try {
@@ -251,7 +260,7 @@ ${messageHistory || '(メッセージなし)'}
251
260
  mentions.push('user')
252
261
  }
253
262
 
254
- await api.request(`/help-threads/${threadSummary.id}/messages`, {
263
+ await api.request(`/threads/${threadSummary.id}/messages`, {
255
264
  method: 'POST',
256
265
  body: JSON.stringify({
257
266
  content: parsed.response,
@@ -281,7 +290,7 @@ async function fallbackMemoryMatch(thread) {
281
290
 
282
291
  if (!memData.memories || memData.memories.length === 0) return
283
292
 
284
- const keywords = (thread.title + ' ' + thread.description)
293
+ const keywords = thread.title
285
294
  .toLowerCase()
286
295
  .split(/[\s,.\-_/]+/)
287
296
  .filter(w => w.length >= 3)
@@ -301,7 +310,7 @@ async function fallbackMemoryMatch(thread) {
301
310
  .map(m => `[${m.title}] ${m.content}`)
302
311
  .join('\n\n')
303
312
 
304
- await api.request(`/help-threads/${thread.id}/messages`, {
313
+ await api.request(`/threads/${thread.id}/messages`, {
305
314
  method: 'POST',
306
315
  body: JSON.stringify({
307
316
  content: `関連するプロジェクトメモリーが見つかりました:\n\n${knowledgeSummary}`,
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Daily log endpoints
3
3
  *
4
- * Provides CRUD access to daily conversation summaries.
5
- * Logs are stored as markdown files in $DATA_DIR/daily-logs/YYYY-MM-DD.md.
4
+ * Provides CRUD + search for daily conversation summaries.
5
+ * Logs are stored in SQLite with FTS5 full-text search.
6
6
  *
7
7
  * Endpoints:
8
- * GET /api/daily-logs - List all logs (date + size)
8
+ * GET /api/daily-logs - List all logs (or search with ?search=keyword)
9
9
  * POST /api/daily-logs - Create a new daily log
10
10
  * GET /api/daily-logs/:date - Get a specific day's log
11
11
  * PUT /api/daily-logs/:date - Update a daily log
@@ -21,14 +21,20 @@ const dailyLogStore = require('../stores/daily-log-store')
21
21
  */
22
22
  async function dailyLogRoutes(fastify) {
23
23
 
24
- // GET /api/daily-logs - List all logs
24
+ // GET /api/daily-logs - List or search logs
25
25
  fastify.get('/api/daily-logs', async (request, reply) => {
26
26
  if (!verifyToken(request)) {
27
27
  reply.code(401)
28
28
  return { success: false, error: 'Unauthorized' }
29
29
  }
30
30
 
31
- const logs = await dailyLogStore.listLogs()
31
+ const { search } = request.query || {}
32
+ if (search) {
33
+ const logs = dailyLogStore.searchLogs(search)
34
+ return { success: true, logs }
35
+ }
36
+
37
+ const logs = dailyLogStore.listLogs()
32
38
  return { success: true, logs }
33
39
  })
34
40
 
@@ -46,13 +52,13 @@ async function dailyLogRoutes(fastify) {
46
52
  }
47
53
 
48
54
  // Check if log already exists
49
- const existing = await dailyLogStore.loadLog(date)
55
+ const existing = dailyLogStore.loadLog(date)
50
56
  if (existing !== null) {
51
57
  reply.code(409)
52
58
  return { success: false, error: 'Log already exists for this date. Use PUT to update.' }
53
59
  }
54
60
 
55
- await dailyLogStore.saveLog(date, content)
61
+ dailyLogStore.saveLog(date, content)
56
62
  console.log(`[DailyLogs] Created log: ${date}`)
57
63
  return { success: true, log: { date, content } }
58
64
  })
@@ -65,7 +71,7 @@ async function dailyLogRoutes(fastify) {
65
71
  }
66
72
 
67
73
  const { date } = request.params
68
- const content = await dailyLogStore.loadLog(date)
74
+ const content = dailyLogStore.loadLog(date)
69
75
  if (content === null) {
70
76
  reply.code(404)
71
77
  return { success: false, error: 'Log not found' }
@@ -89,13 +95,13 @@ async function dailyLogRoutes(fastify) {
89
95
  }
90
96
 
91
97
  // Check if log exists
92
- const existing = await dailyLogStore.loadLog(date)
98
+ const existing = dailyLogStore.loadLog(date)
93
99
  if (existing === null) {
94
100
  reply.code(404)
95
101
  return { success: false, error: 'Log not found' }
96
102
  }
97
103
 
98
- await dailyLogStore.saveLog(date, content)
104
+ dailyLogStore.saveLog(date, content)
99
105
  console.log(`[DailyLogs] Updated log: ${date}`)
100
106
  return { success: true, log: { date, content } }
101
107
  })
@@ -108,7 +114,7 @@ async function dailyLogRoutes(fastify) {
108
114
  }
109
115
 
110
116
  const { date } = request.params
111
- const deleted = await dailyLogStore.deleteLog(date)
117
+ const deleted = dailyLogStore.deleteLog(date)
112
118
  if (!deleted) {
113
119
  reply.code(404)
114
120
  return { success: false, error: 'Log not found' }