@teddysc/claude-run 0.10.1 → 0.12.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/README.md +22 -1
- package/dist/index.js +358 -2
- package/dist/web/assets/index-CrTCiHsZ.js +328 -0
- package/dist/web/assets/index-_XHzPMzO.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-BFGsV3Tx.css +0 -1
- package/dist/web/assets/index-C0duTJht.js +0 -318
package/README.md
CHANGED
|
@@ -30,8 +30,26 @@ bun install -g @teddysc/claude-run@latest
|
|
|
30
30
|
|
|
31
31
|
The browser will open automatically at http://localhost:12001.
|
|
32
32
|
|
|
33
|
+
## Specifications
|
|
34
|
+
|
|
35
|
+
See [spec.md](spec.md)
|
|
36
|
+
|
|
33
37
|
## Changelog
|
|
34
38
|
|
|
39
|
+
### 0.12.0
|
|
40
|
+
- Rename sessions directly from the web UI
|
|
41
|
+
- Searchable project dropdown with fuzzy matching on name and path
|
|
42
|
+
- Session summary caching for improved performance
|
|
43
|
+
|
|
44
|
+
### 0.11.0
|
|
45
|
+
- Display session metadata (model, start/end times) in conversation header
|
|
46
|
+
- Show full project path with ~ shorthand in header and sidebar tooltips
|
|
47
|
+
- Add "Hide Unknown model" filter to hide sessions without detected models
|
|
48
|
+
- Add "Delete Unknown" button to remove blank/orphaned sessions from disk
|
|
49
|
+
- Automatic cleanup of orphaned history entries on startup
|
|
50
|
+
- Add comprehensive logging for delete operations
|
|
51
|
+
- Fix URL state sync when unchecking checkboxes
|
|
52
|
+
|
|
35
53
|
### 0.10.0
|
|
36
54
|
- Enhance message handling: add message ID to ConversationMessage
|
|
37
55
|
- Normalize content blocks in message processing
|
|
@@ -78,12 +96,15 @@ The browser will open automatically at http://localhost:12001.
|
|
|
78
96
|
- **Truncation options** - Limit long tool outputs by line count or character count
|
|
79
97
|
- **Batch export** - Export multiple conversations at once
|
|
80
98
|
- **Real-time streaming** - Watch conversations update live as Claude responds
|
|
81
|
-
- **Filter by project** -
|
|
99
|
+
- **Filter by project** - Searchable dropdown with fuzzy matching on project name and path
|
|
82
100
|
- **Resume sessions** - Copy the resume command to continue any conversation in your terminal
|
|
83
101
|
- **Copy messages** - Click the copy button on any message to copy its text content
|
|
102
|
+
- **Session metadata** - See model and start/end times in the conversation header
|
|
103
|
+
- **Rename sessions** - Click the conversation title to rename a session
|
|
84
104
|
- **Collapsible sidebar** - Maximize your viewing area
|
|
85
105
|
- **Dark mode** - Easy on the eyes
|
|
86
106
|
- **Clean UI** - Familiar chat interface with collapsible tool calls
|
|
107
|
+
- **Filter & cleanup** - Hide Unknown-model sessions and delete blank sessions from disk
|
|
87
108
|
|
|
88
109
|
## Usage
|
|
89
110
|
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,8 @@ import { streamSSE } from "hono/streaming";
|
|
|
11
11
|
import { serve } from "@hono/node-server";
|
|
12
12
|
|
|
13
13
|
// api/storage.ts
|
|
14
|
-
import { readdir, readFile, stat, open } from "fs/promises";
|
|
14
|
+
import { readdir, readFile, stat, open, unlink, writeFile } from "fs/promises";
|
|
15
|
+
import { createReadStream } from "fs";
|
|
15
16
|
import { join, basename } from "path";
|
|
16
17
|
import { homedir } from "os";
|
|
17
18
|
import { createInterface } from "readline";
|
|
@@ -87,6 +88,7 @@ function mergeSidechainText(messages, sidechainMap) {
|
|
|
87
88
|
var claudeDir = join(homedir(), ".claude");
|
|
88
89
|
var projectsDir = join(claudeDir, "projects");
|
|
89
90
|
var fileIndex = /* @__PURE__ */ new Map();
|
|
91
|
+
var summaryCache = /* @__PURE__ */ new Map();
|
|
90
92
|
var historyCache = null;
|
|
91
93
|
var pendingRequests = /* @__PURE__ */ new Map();
|
|
92
94
|
function initStorage(dir) {
|
|
@@ -101,6 +103,58 @@ function invalidateHistoryCache() {
|
|
|
101
103
|
}
|
|
102
104
|
function addToFileIndex(sessionId, filePath) {
|
|
103
105
|
fileIndex.set(sessionId, filePath);
|
|
106
|
+
summaryCache.delete(sessionId);
|
|
107
|
+
}
|
|
108
|
+
async function readLastNonEmptyLine(filePath) {
|
|
109
|
+
try {
|
|
110
|
+
const fileStat = await stat(filePath);
|
|
111
|
+
const fileSize = fileStat.size;
|
|
112
|
+
if (fileSize === 0) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
const chunkSize = Math.min(8192, fileSize);
|
|
116
|
+
const start = Math.max(0, fileSize - chunkSize);
|
|
117
|
+
const fileHandle = await open(filePath, "r");
|
|
118
|
+
try {
|
|
119
|
+
const buffer = Buffer.alloc(chunkSize);
|
|
120
|
+
await fileHandle.read(buffer, 0, chunkSize, start);
|
|
121
|
+
const text = buffer.toString("utf-8");
|
|
122
|
+
const lines = text.split("\n").filter((line) => line.trim().length > 0);
|
|
123
|
+
if (lines.length === 0) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return lines[lines.length - 1] ?? null;
|
|
127
|
+
} finally {
|
|
128
|
+
await fileHandle.close();
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function getSessionSummary(sessionId) {
|
|
135
|
+
if (summaryCache.has(sessionId)) {
|
|
136
|
+
return summaryCache.get(sessionId) ?? null;
|
|
137
|
+
}
|
|
138
|
+
const filePath = await findSessionFile(sessionId);
|
|
139
|
+
if (!filePath) {
|
|
140
|
+
summaryCache.set(sessionId, null);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const lastLine = await readLastNonEmptyLine(filePath);
|
|
144
|
+
if (!lastLine) {
|
|
145
|
+
summaryCache.set(sessionId, null);
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
const msg = JSON.parse(lastLine);
|
|
150
|
+
if (msg.type === "summary" && typeof msg.summary === "string" && msg.summary.trim().length > 0) {
|
|
151
|
+
summaryCache.set(sessionId, msg.summary);
|
|
152
|
+
return msg.summary;
|
|
153
|
+
}
|
|
154
|
+
} catch {
|
|
155
|
+
}
|
|
156
|
+
summaryCache.set(sessionId, null);
|
|
157
|
+
return null;
|
|
104
158
|
}
|
|
105
159
|
function encodeProjectPath(path) {
|
|
106
160
|
return path.replace(/[/.]/g, "-");
|
|
@@ -220,8 +274,85 @@ async function findSessionFile(sessionId) {
|
|
|
220
274
|
}
|
|
221
275
|
return null;
|
|
222
276
|
}
|
|
277
|
+
async function getSessionModel(sessionId) {
|
|
278
|
+
const filePath = await findSessionFile(sessionId);
|
|
279
|
+
if (!filePath) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
const stream = createReadStream(filePath, { encoding: "utf-8" });
|
|
283
|
+
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
284
|
+
try {
|
|
285
|
+
for await (const line of rl) {
|
|
286
|
+
if (!line.trim()) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
const msg = JSON.parse(line);
|
|
291
|
+
if (msg.type !== "user" && msg.type !== "assistant") {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const model = msg.message?.model;
|
|
295
|
+
if (model) {
|
|
296
|
+
return model;
|
|
297
|
+
}
|
|
298
|
+
} catch {
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
} finally {
|
|
303
|
+
rl.close();
|
|
304
|
+
stream.close();
|
|
305
|
+
}
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
223
308
|
async function loadStorage() {
|
|
224
309
|
await Promise.all([buildFileIndex(), loadHistoryCache()]);
|
|
310
|
+
await cleanupOrphanedHistoryEntries();
|
|
311
|
+
}
|
|
312
|
+
async function cleanupOrphanedHistoryEntries() {
|
|
313
|
+
const history = historyCache ?? await loadHistoryCache();
|
|
314
|
+
const orphanedIds = [];
|
|
315
|
+
for (const entry of history) {
|
|
316
|
+
if (entry.sessionId && !await findSessionFile(entry.sessionId)) {
|
|
317
|
+
orphanedIds.push(entry.sessionId);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (orphanedIds.length === 0) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
console.log(
|
|
324
|
+
`[cleanup] Found ${orphanedIds.length} orphaned history entries, cleaning up...`
|
|
325
|
+
);
|
|
326
|
+
const historyPath = join(claudeDir, "history.jsonl");
|
|
327
|
+
const orphanedSet = new Set(orphanedIds);
|
|
328
|
+
try {
|
|
329
|
+
const content = await readFile(historyPath, "utf-8");
|
|
330
|
+
const lines = content.split("\n");
|
|
331
|
+
const filtered = [];
|
|
332
|
+
for (const line of lines) {
|
|
333
|
+
if (!line.trim()) continue;
|
|
334
|
+
try {
|
|
335
|
+
const entry = JSON.parse(line);
|
|
336
|
+
if (entry.sessionId && orphanedSet.has(entry.sessionId)) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
} catch {
|
|
340
|
+
}
|
|
341
|
+
filtered.push(line);
|
|
342
|
+
}
|
|
343
|
+
await writeFile(historyPath, filtered.join("\n") + "\n", "utf-8");
|
|
344
|
+
if (historyCache) {
|
|
345
|
+
historyCache = historyCache.filter(
|
|
346
|
+
(entry) => !(entry.sessionId && orphanedSet.has(entry.sessionId))
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
for (const id of orphanedIds) {
|
|
350
|
+
fileIndex.delete(id);
|
|
351
|
+
}
|
|
352
|
+
console.log(`[cleanup] Removed ${orphanedIds.length} orphaned entries`);
|
|
353
|
+
} catch (err) {
|
|
354
|
+
console.error("[cleanup] Failed to clean up orphaned entries:", err);
|
|
355
|
+
}
|
|
225
356
|
}
|
|
226
357
|
async function getSessions() {
|
|
227
358
|
return dedupe("getSessions", async () => {
|
|
@@ -238,9 +369,10 @@ async function getSessions() {
|
|
|
238
369
|
continue;
|
|
239
370
|
}
|
|
240
371
|
seenIds.add(sessionId);
|
|
372
|
+
const summary = await getSessionSummary(sessionId);
|
|
241
373
|
sessions.push({
|
|
242
374
|
id: sessionId,
|
|
243
|
-
display: entry.display,
|
|
375
|
+
display: summary ?? entry.display,
|
|
244
376
|
timestamp: entry.timestamp,
|
|
245
377
|
project: entry.project,
|
|
246
378
|
projectName: getProjectName(entry.project)
|
|
@@ -259,6 +391,156 @@ async function getProjects() {
|
|
|
259
391
|
}
|
|
260
392
|
return [...projects].sort();
|
|
261
393
|
}
|
|
394
|
+
async function getSessionsMetadata(ids) {
|
|
395
|
+
const unique = Array.from(new Set(ids.filter(Boolean)));
|
|
396
|
+
return Promise.all(
|
|
397
|
+
unique.map(async (id) => ({
|
|
398
|
+
id,
|
|
399
|
+
model: await getSessionModel(id)
|
|
400
|
+
}))
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
async function deleteSession(sessionId) {
|
|
404
|
+
console.log(`[deleteSession] Attempting to delete: ${sessionId}`);
|
|
405
|
+
const filePath = await findSessionFile(sessionId);
|
|
406
|
+
let fileDeleted = false;
|
|
407
|
+
if (filePath) {
|
|
408
|
+
console.log(`[deleteSession] Found file: ${filePath}`);
|
|
409
|
+
try {
|
|
410
|
+
await unlink(filePath);
|
|
411
|
+
console.log(`[deleteSession] Successfully deleted: ${filePath}`);
|
|
412
|
+
fileDeleted = true;
|
|
413
|
+
} catch (err) {
|
|
414
|
+
if (err.code === "ENOENT") {
|
|
415
|
+
console.log(`[deleteSession] File already deleted: ${filePath}`);
|
|
416
|
+
fileDeleted = true;
|
|
417
|
+
} else {
|
|
418
|
+
console.error(`[deleteSession] Failed to delete ${filePath}:`, err);
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
} else {
|
|
423
|
+
console.log(`[deleteSession] No file found for session: ${sessionId}`);
|
|
424
|
+
}
|
|
425
|
+
fileIndex.delete(sessionId);
|
|
426
|
+
summaryCache.delete(sessionId);
|
|
427
|
+
const historyPath = join(claudeDir, "history.jsonl");
|
|
428
|
+
let historyChanged = false;
|
|
429
|
+
try {
|
|
430
|
+
const content = await readFile(historyPath, "utf-8");
|
|
431
|
+
const lines = content.split("\n");
|
|
432
|
+
const filtered = [];
|
|
433
|
+
let changed = false;
|
|
434
|
+
for (const line of lines) {
|
|
435
|
+
if (!line.trim()) {
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
try {
|
|
439
|
+
const entry = JSON.parse(line);
|
|
440
|
+
if (entry.sessionId && entry.sessionId === sessionId) {
|
|
441
|
+
changed = true;
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
} catch {
|
|
445
|
+
}
|
|
446
|
+
filtered.push(line);
|
|
447
|
+
}
|
|
448
|
+
if (changed) {
|
|
449
|
+
await writeFile(historyPath, filtered.join("\n") + "\n", "utf-8");
|
|
450
|
+
console.log(`[deleteSession] Removed from history.jsonl: ${sessionId}`);
|
|
451
|
+
if (historyCache) {
|
|
452
|
+
historyCache = historyCache.filter(
|
|
453
|
+
(entry) => entry.sessionId !== sessionId
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
historyChanged = true;
|
|
457
|
+
}
|
|
458
|
+
} catch (err) {
|
|
459
|
+
console.error(`[deleteSession] Failed to update history.jsonl for ${sessionId}:`, err);
|
|
460
|
+
}
|
|
461
|
+
return fileDeleted || historyChanged;
|
|
462
|
+
}
|
|
463
|
+
async function getSessionFilePath(sessionId) {
|
|
464
|
+
return findSessionFile(sessionId);
|
|
465
|
+
}
|
|
466
|
+
async function renameSessionDisplay(params) {
|
|
467
|
+
const { sessionId, display, project, timestamp } = params;
|
|
468
|
+
const historyPath = join(claudeDir, "history.jsonl");
|
|
469
|
+
let changed = false;
|
|
470
|
+
let summaryChanged = false;
|
|
471
|
+
try {
|
|
472
|
+
const content = await readFile(historyPath, "utf-8");
|
|
473
|
+
const lines = content.split("\n");
|
|
474
|
+
const updated = [];
|
|
475
|
+
for (const line of lines) {
|
|
476
|
+
if (!line.trim()) {
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
try {
|
|
480
|
+
const entry = JSON.parse(line);
|
|
481
|
+
const matchesId = entry.sessionId && entry.sessionId === sessionId;
|
|
482
|
+
const matchesFallback = !entry.sessionId && project && typeof timestamp === "number" && entry.project === project && entry.timestamp === timestamp;
|
|
483
|
+
if (matchesId || matchesFallback) {
|
|
484
|
+
if (entry.display !== display) {
|
|
485
|
+
entry.display = display;
|
|
486
|
+
changed = true;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
updated.push(JSON.stringify(entry));
|
|
490
|
+
} catch {
|
|
491
|
+
updated.push(line);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (changed) {
|
|
495
|
+
await writeFile(historyPath, updated.join("\n") + "\n", "utf-8");
|
|
496
|
+
if (historyCache) {
|
|
497
|
+
historyCache = historyCache.map((entry) => {
|
|
498
|
+
const matchesId = entry.sessionId && entry.sessionId === sessionId;
|
|
499
|
+
const matchesFallback = !entry.sessionId && project && typeof timestamp === "number" && entry.project === project && entry.timestamp === timestamp;
|
|
500
|
+
if (matchesId || matchesFallback) {
|
|
501
|
+
return { ...entry, display };
|
|
502
|
+
}
|
|
503
|
+
return entry;
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
} catch (err) {
|
|
508
|
+
console.error(`[renameSessionDisplay] Failed to update history.jsonl for ${sessionId}:`, err);
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
const sessionFile = await findSessionFile(sessionId);
|
|
512
|
+
if (sessionFile) {
|
|
513
|
+
try {
|
|
514
|
+
const content = await readFile(sessionFile, "utf-8");
|
|
515
|
+
const lines = content.split("\n");
|
|
516
|
+
let summaryUpdated = false;
|
|
517
|
+
const updatedLines = lines.map((line) => {
|
|
518
|
+
if (!line.trim()) {
|
|
519
|
+
return line;
|
|
520
|
+
}
|
|
521
|
+
try {
|
|
522
|
+
const msg = JSON.parse(line);
|
|
523
|
+
if (msg.type === "summary") {
|
|
524
|
+
msg.summary = display;
|
|
525
|
+
summaryUpdated = true;
|
|
526
|
+
return JSON.stringify(msg);
|
|
527
|
+
}
|
|
528
|
+
} catch {
|
|
529
|
+
}
|
|
530
|
+
return line;
|
|
531
|
+
});
|
|
532
|
+
if (!summaryUpdated) {
|
|
533
|
+
updatedLines.push(JSON.stringify({ type: "summary", summary: display }));
|
|
534
|
+
}
|
|
535
|
+
await writeFile(sessionFile, updatedLines.join("\n") + "\n", "utf-8");
|
|
536
|
+
summaryCache.set(sessionId, display);
|
|
537
|
+
summaryChanged = true;
|
|
538
|
+
} catch (err) {
|
|
539
|
+
console.error(`[renameSessionDisplay] Failed to update session file for ${sessionId}:`, err);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return changed || summaryChanged;
|
|
543
|
+
}
|
|
262
544
|
async function getConversation(sessionId) {
|
|
263
545
|
return dedupe(`getConversation:${sessionId}`, async () => {
|
|
264
546
|
const filePath = await findSessionFile(sessionId);
|
|
@@ -582,6 +864,80 @@ function createServer(options) {
|
|
|
582
864
|
const projects = await getProjects();
|
|
583
865
|
return c.json(projects);
|
|
584
866
|
});
|
|
867
|
+
app.post("/api/sessions/metadata", async (c) => {
|
|
868
|
+
let body;
|
|
869
|
+
try {
|
|
870
|
+
body = await c.req.json();
|
|
871
|
+
} catch {
|
|
872
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
873
|
+
}
|
|
874
|
+
if (!body || !Array.isArray(body.ids)) {
|
|
875
|
+
return c.json({ error: "Missing ids" }, 400);
|
|
876
|
+
}
|
|
877
|
+
const sessions = await getSessionsMetadata(body.ids);
|
|
878
|
+
return c.json({ sessions });
|
|
879
|
+
});
|
|
880
|
+
app.post("/api/sessions/delete", async (c) => {
|
|
881
|
+
let body;
|
|
882
|
+
try {
|
|
883
|
+
body = await c.req.json();
|
|
884
|
+
} catch {
|
|
885
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
886
|
+
}
|
|
887
|
+
if (!body || !Array.isArray(body.ids)) {
|
|
888
|
+
return c.json({ error: "Missing ids" }, 400);
|
|
889
|
+
}
|
|
890
|
+
console.log(`[/api/sessions/delete] Received request to delete ${body.ids.length} sessions:`, body.ids);
|
|
891
|
+
const deleted = [];
|
|
892
|
+
const failed = [];
|
|
893
|
+
for (const id of body.ids) {
|
|
894
|
+
if (typeof id !== "string" || !id) {
|
|
895
|
+
console.log(`[/api/sessions/delete] Skipping invalid id:`, id);
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
const ok = await deleteSession(id);
|
|
899
|
+
if (ok) {
|
|
900
|
+
deleted.push(id);
|
|
901
|
+
} else {
|
|
902
|
+
failed.push(id);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
console.log(`[/api/sessions/delete] Result: ${deleted.length} deleted, ${failed.length} failed`);
|
|
906
|
+
console.log(`[/api/sessions/delete] Deleted:`, deleted);
|
|
907
|
+
console.log(`[/api/sessions/delete] Failed:`, failed);
|
|
908
|
+
return c.json({ deleted, failed });
|
|
909
|
+
});
|
|
910
|
+
app.post("/api/sessions/rename", async (c) => {
|
|
911
|
+
let body;
|
|
912
|
+
try {
|
|
913
|
+
body = await c.req.json();
|
|
914
|
+
} catch {
|
|
915
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
916
|
+
}
|
|
917
|
+
const id = body?.id?.trim();
|
|
918
|
+
const display = body?.display?.trim();
|
|
919
|
+
if (!id || !display) {
|
|
920
|
+
return c.json({ error: "Missing id/display" }, 400);
|
|
921
|
+
}
|
|
922
|
+
const ok = await renameSessionDisplay({
|
|
923
|
+
sessionId: id,
|
|
924
|
+
display,
|
|
925
|
+
project: body.project,
|
|
926
|
+
timestamp: body.timestamp
|
|
927
|
+
});
|
|
928
|
+
if (!ok) {
|
|
929
|
+
return c.json({ error: "Session not found" }, 404);
|
|
930
|
+
}
|
|
931
|
+
return c.json({ ok: true });
|
|
932
|
+
});
|
|
933
|
+
app.get("/api/sessions/:id/path", async (c) => {
|
|
934
|
+
const sessionId = c.req.param("id");
|
|
935
|
+
if (!sessionId) {
|
|
936
|
+
return c.json({ error: "Missing session id" }, 400);
|
|
937
|
+
}
|
|
938
|
+
const path = await getSessionFilePath(sessionId);
|
|
939
|
+
return c.json({ path });
|
|
940
|
+
});
|
|
585
941
|
app.post("/api/search", async (c) => {
|
|
586
942
|
let body;
|
|
587
943
|
try {
|