@ailog/cli 0.2.6 → 0.3.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/dist/index.js CHANGED
@@ -2,10 +2,36 @@
2
2
  import path from "path";
3
3
  import fs from "fs";
4
4
  var DB_DIR = path.join(process.cwd(), ".ailog");
5
- var INDEX_PATH = path.join(DB_DIR, "index.json");
6
- var RUNS_DIR = path.join(DB_DIR, "runs");
7
- var BLOBS_DIR = path.join(DB_DIR, "blobs");
5
+ var RUNS_DIR = () => path.join(DB_DIR, "runs");
6
+ var BLOBS_DIR = () => path.join(DB_DIR, "blobs");
7
+ var RUN_INDEX_DIR = () => path.join(DB_DIR, "index", "runs");
8
+ var THREAD_INDEX_DIR = () => path.join(DB_DIR, "index", "threads");
9
+ var PAGE_SIZE = 50;
8
10
  var AILOG_PORT = process.env.AILOG_PORT ? parseInt(process.env.AILOG_PORT) : 4985;
11
+ var dirResolved = false;
12
+ var dirResolvePromise = null;
13
+ var resolveDbDir = async () => {
14
+ if (dirResolved) return;
15
+ if (!dirResolvePromise) {
16
+ dirResolvePromise = (async () => {
17
+ try {
18
+ const res = await fetch(
19
+ `http://localhost:${AILOG_PORT}/api/db-dir`
20
+ );
21
+ if (res.ok) {
22
+ const body = await res.json();
23
+ if (typeof body.dir === "string" && body.dir.length > 0) {
24
+ DB_DIR = body.dir;
25
+ }
26
+ }
27
+ } catch {
28
+ } finally {
29
+ dirResolved = true;
30
+ }
31
+ })();
32
+ }
33
+ await dirResolvePromise;
34
+ };
9
35
  var BLOB_THRESHOLD = 16 * 1024;
10
36
  var notifyServer = (event, runId) => {
11
37
  notifyServerAsync(event, runId);
@@ -18,29 +44,18 @@ var notifyServerAsync = async (event, runId) => {
18
44
  body: JSON.stringify({
19
45
  event,
20
46
  timestamp: Date.now(),
21
- dbPath: INDEX_PATH,
22
47
  runId: runId ?? null
23
48
  })
24
49
  });
25
50
  } catch {
26
51
  }
27
52
  };
53
+ var digitTimestamp = (date) => date.toISOString().replace(/[-:T.Z]/g, "").slice(0, 17);
28
54
  var generateRunId = () => {
29
- const now = /* @__PURE__ */ new Date();
30
- const timestamp = now.toISOString().replace(/[-:T.Z]/g, "").slice(0, 17);
55
+ const timestamp = digitTimestamp(/* @__PURE__ */ new Date());
31
56
  const uniqueId = crypto.randomUUID().slice(0, 8);
32
57
  return `${timestamp}-${uniqueId}`;
33
58
  };
34
- var EMPTY_INDEX = () => ({
35
- version: 1,
36
- runs: [],
37
- threads: []
38
- });
39
- var normalizeIndex = (raw) => ({
40
- version: 1,
41
- runs: raw.runs ?? [],
42
- threads: raw.threads ?? []
43
- });
44
59
  var ensureGitignore = () => {
45
60
  const gitignorePath = path.join(process.cwd(), ".gitignore");
46
61
  if (!fs.existsSync(gitignorePath)) return;
@@ -56,8 +71,10 @@ var ensureGitignore = () => {
56
71
  };
57
72
  var ensureLayout = () => {
58
73
  const isFirstRun = !fs.existsSync(DB_DIR);
59
- fs.mkdirSync(RUNS_DIR, { recursive: true });
60
- fs.mkdirSync(BLOBS_DIR, { recursive: true });
74
+ fs.mkdirSync(RUNS_DIR(), { recursive: true });
75
+ fs.mkdirSync(BLOBS_DIR(), { recursive: true });
76
+ fs.mkdirSync(RUN_INDEX_DIR(), { recursive: true });
77
+ fs.mkdirSync(THREAD_INDEX_DIR(), { recursive: true });
61
78
  if (isFirstRun) ensureGitignore();
62
79
  };
63
80
  var atomicWrite = (filePath, data) => {
@@ -86,34 +103,86 @@ var externalizeIfLarge = (value, stepId, field) => {
86
103
  writeBlob(rel, value);
87
104
  return { $blob: rel };
88
105
  };
89
- var indexCache = null;
90
106
  var runCache = /* @__PURE__ */ new Map();
91
- var remoteDbDir = null;
92
- var currentDbDir = () => remoteDbDir ?? DB_DIR;
93
- var currentIndexPath = () => path.join(currentDbDir(), "index.json");
94
- var currentRunPath = (runId) => path.join(currentDbDir(), "runs", `${runId}.json`);
95
- var loadIndex = () => {
96
- if (indexCache) return indexCache;
97
- indexCache = normalizeIndex(
98
- safeReadJson(currentIndexPath(), EMPTY_INDEX())
99
- );
100
- return indexCache;
107
+ var pageCache = /* @__PURE__ */ new Map();
108
+ var PAGE_FILE_RE = /^\d{17}\.json$/;
109
+ var nextPageFilePath = (dir) => {
110
+ let nowMs = Date.now();
111
+ const files = listPageFiles(dir);
112
+ if (files.length > 0) {
113
+ const latestName = path.basename(files[files.length - 1], ".json");
114
+ const latestMs = parseDigitTimestamp(latestName);
115
+ if (Number.isFinite(latestMs) && latestMs >= nowMs) nowMs = latestMs + 1;
116
+ }
117
+ return path.join(dir, digitTimestamp(new Date(nowMs)) + ".json");
118
+ };
119
+ var parseDigitTimestamp = (s) => {
120
+ if (!/^\d{17}$/.test(s)) return NaN;
121
+ const iso = s.slice(0, 4) + "-" + s.slice(4, 6) + "-" + s.slice(6, 8) + "T" + s.slice(8, 10) + ":" + s.slice(10, 12) + ":" + s.slice(12, 14) + "." + s.slice(14, 17) + "Z";
122
+ return new Date(iso).getTime();
123
+ };
124
+ var listPageFiles = (dir) => {
125
+ let entries;
126
+ try {
127
+ entries = fs.readdirSync(dir);
128
+ } catch {
129
+ return [];
130
+ }
131
+ return entries.filter((n) => PAGE_FILE_RE.test(n)).sort().map((n) => path.join(dir, n));
132
+ };
133
+ var loadPage = (file) => {
134
+ const cached = pageCache.get(file);
135
+ if (cached) return cached;
136
+ const data = safeReadJson(file, []);
137
+ pageCache.set(file, data);
138
+ return data;
139
+ };
140
+ var writePage = (file, items) => {
141
+ atomicWrite(file, JSON.stringify(items));
142
+ pageCache.set(file, items);
143
+ };
144
+ var appendToLatestPage = (dir, item) => {
145
+ ensureLayout();
146
+ const files = listPageFiles(dir);
147
+ if (files.length === 0) {
148
+ writePage(nextPageFilePath(dir), [item]);
149
+ return;
150
+ }
151
+ const latest = files[files.length - 1];
152
+ const page = loadPage(latest);
153
+ if (page.length >= PAGE_SIZE) {
154
+ writePage(nextPageFilePath(dir), [item]);
155
+ } else {
156
+ writePage(latest, [...page, item]);
157
+ }
101
158
  };
102
- var persistIndex = () => {
103
- if (!indexCache) return;
104
- atomicWrite(INDEX_PATH, JSON.stringify(indexCache, null, 2));
159
+ var updateInPlace = (dir, predicate, mutator) => {
160
+ const files = listPageFiles(dir).reverse();
161
+ for (const file of files) {
162
+ const page = loadPage(file);
163
+ const i = page.findIndex(predicate);
164
+ if (i === -1) continue;
165
+ const next = page.slice();
166
+ next[i] = mutator(page[i]);
167
+ writePage(file, next);
168
+ return true;
169
+ }
170
+ return false;
105
171
  };
106
172
  var loadRunFile = (runId) => {
107
173
  const cached = runCache.get(runId);
108
174
  if (cached) return cached;
109
- const file = safeReadJson(currentRunPath(runId), null);
175
+ const file = safeReadJson(
176
+ path.join(RUNS_DIR(), `${runId}.json`),
177
+ null
178
+ );
110
179
  if (!file) return null;
111
180
  runCache.set(runId, file);
112
181
  return file;
113
182
  };
114
183
  var persistRunFile = (runId, file) => {
115
184
  runCache.set(runId, file);
116
- atomicWrite(path.join(RUNS_DIR, `${runId}.json`), JSON.stringify(file));
185
+ atomicWrite(path.join(RUNS_DIR(), `${runId}.json`), JSON.stringify(file));
117
186
  };
118
187
  var stepIsInProgress = (s) => s.duration_ms === null && !s.error;
119
188
  var extractFirstMessage = (rawInput) => {
@@ -155,7 +224,6 @@ var recomputeSummary = (file) => {
155
224
  id: file.run.id,
156
225
  started_at: file.run.started_at,
157
226
  function_id: file.run.function_id,
158
- metadata: file.run.metadata,
159
227
  thread_id: file.run.thread_id,
160
228
  step_count: file.steps.length,
161
229
  has_error: hasError,
@@ -164,54 +232,57 @@ var recomputeSummary = (file) => {
164
232
  type: firstStep?.type ?? null
165
233
  };
166
234
  };
167
- var upsertThread = (threadId, runStartedAt) => {
168
- const index = loadIndex();
169
- const existing = index.threads.find((t) => t.id === threadId);
170
- if (existing) {
171
- if (new Date(runStartedAt) > new Date(existing.updated_at)) {
172
- existing.updated_at = runStartedAt;
173
- }
174
- existing.run_count += 1;
175
- } else {
176
- index.threads.push({
177
- id: threadId,
178
- created_at: runStartedAt,
179
- updated_at: runStartedAt,
180
- run_count: 1
181
- });
182
- }
235
+ var upsertThread = (threadId, runStartedAt, metadata) => {
236
+ const updated = updateInPlace(
237
+ THREAD_INDEX_DIR(),
238
+ (t) => t.id === threadId,
239
+ (existing) => ({
240
+ ...existing,
241
+ updated_at: new Date(runStartedAt) > new Date(existing.updated_at) ? runStartedAt : existing.updated_at,
242
+ run_count: existing.run_count + 1
243
+ })
244
+ );
245
+ if (updated) return;
246
+ appendToLatestPage(THREAD_INDEX_DIR(), {
247
+ id: threadId,
248
+ created_at: runStartedAt,
249
+ updated_at: runStartedAt,
250
+ run_count: 1,
251
+ metadata: metadata && typeof metadata === "object" ? JSON.stringify(metadata) : null
252
+ });
183
253
  };
184
254
  var upsertSummary = (file) => {
185
- const index = loadIndex();
186
255
  const summary = recomputeSummary(file);
187
- const i = index.runs.findIndex((r) => r.id === summary.id);
188
- if (i === -1) {
189
- index.runs.push(summary);
190
- } else {
191
- index.runs[i] = summary;
192
- }
256
+ const updated = updateInPlace(
257
+ RUN_INDEX_DIR(),
258
+ (s) => s.id === summary.id,
259
+ () => summary
260
+ );
261
+ if (updated) return;
262
+ appendToLatestPage(RUN_INDEX_DIR(), summary);
193
263
  };
194
- var createRun = async (id, functionId, metadata, threadId) => {
264
+ var createRun = async (id, functionId, threadId, metadata) => {
265
+ await resolveDbDir();
195
266
  ensureLayout();
196
267
  const existing = loadRunFile(id);
197
268
  if (existing) return existing.run;
269
+ const resolvedThreadId = threadId ?? `auto-${crypto.randomUUID().slice(0, 8)}`;
198
270
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
199
271
  const run = {
200
272
  id,
201
273
  started_at: startedAt,
202
274
  function_id: functionId ?? null,
203
- metadata: metadata && typeof metadata === "object" ? JSON.stringify(metadata) : null,
204
- thread_id: threadId ?? null
275
+ thread_id: resolvedThreadId
205
276
  };
206
277
  const file = { version: 1, run, steps: [] };
207
278
  persistRunFile(id, file);
208
279
  upsertSummary(file);
209
- if (threadId) upsertThread(threadId, startedAt);
210
- persistIndex();
280
+ upsertThread(resolvedThreadId, startedAt, metadata);
211
281
  notifyServer("run", id);
212
282
  return run;
213
283
  };
214
284
  var createStep = async (step) => {
285
+ await resolveDbDir();
215
286
  ensureLayout();
216
287
  const file = loadRunFile(step.run_id);
217
288
  if (!file) {
@@ -232,11 +303,11 @@ var createStep = async (step) => {
232
303
  file.steps.push(newStep);
233
304
  persistRunFile(file.run.id, file);
234
305
  upsertSummary(file);
235
- persistIndex();
236
306
  notifyServer("step", file.run.id);
237
307
  return stepNumber;
238
308
  };
239
309
  var updateStepResult = async (stepId, result) => {
310
+ await resolveDbDir();
240
311
  let file;
241
312
  let step;
242
313
  for (const cached of runCache.values()) {
@@ -248,15 +319,18 @@ var updateStepResult = async (stepId, result) => {
248
319
  }
249
320
  }
250
321
  if (!file || !step) {
251
- for (const summary of loadIndex().runs) {
252
- const candidate = loadRunFile(summary.id);
253
- if (!candidate) continue;
254
- const match = candidate.steps.find((s) => s.id === stepId);
255
- if (match) {
256
- file = candidate;
257
- step = match;
258
- break;
322
+ for (const pageFile of listPageFiles(RUN_INDEX_DIR())) {
323
+ for (const summary of loadPage(pageFile)) {
324
+ const candidate = loadRunFile(summary.id);
325
+ if (!candidate) continue;
326
+ const match = candidate.steps.find((s) => s.id === stepId);
327
+ if (match) {
328
+ file = candidate;
329
+ step = match;
330
+ break;
331
+ }
259
332
  }
333
+ if (file) break;
260
334
  }
261
335
  }
262
336
  if (!file || !step) return;
@@ -281,7 +355,6 @@ var updateStepResult = async (stepId, result) => {
281
355
  );
282
356
  persistRunFile(file.run.id, file);
283
357
  upsertSummary(file);
284
- persistIndex();
285
358
  notifyServer("step-update", file.run.id);
286
359
  };
287
360
 
@@ -325,15 +398,15 @@ var aiSdkMiddleware = (options = {}) => {
325
398
  );
326
399
  }
327
400
  registerSignalHandlers();
328
- const runId = options.runId ?? generateRunId();
401
+ const runId = generateRunId();
329
402
  let runCreated = false;
330
403
  const ensureRunCreated = async () => {
331
404
  if (!runCreated) {
332
405
  await createRun(
333
406
  runId,
334
407
  options.functionId,
335
- options.metadata,
336
- options.threadId
408
+ options.threadId,
409
+ options.metadata
337
410
  );
338
411
  runCreated = true;
339
412
  }
@@ -561,12 +634,12 @@ async function createLogger(options = {}) {
561
634
  "ailog should not be used in production. Remove createLogger calls from your production builds."
562
635
  );
563
636
  }
564
- const runId = options.runId ?? generateRunId();
637
+ const runId = generateRunId();
565
638
  await createRun(
566
639
  runId,
567
640
  options.functionId,
568
- options.metadata,
569
- options.threadId
641
+ options.threadId,
642
+ options.metadata
570
643
  );
571
644
  return {
572
645
  id: runId,
@@ -604,6 +677,5 @@ async function createLogger(options = {}) {
604
677
  }
605
678
  export {
606
679
  aiSdkMiddleware,
607
- createLogger,
608
- generateRunId
680
+ createLogger
609
681
  };
@@ -12,10 +12,36 @@ import { fileURLToPath } from "url";
12
12
  import path from "path";
13
13
  import fs from "fs";
14
14
  var DB_DIR = path.join(process.cwd(), ".ailog");
15
- var INDEX_PATH = path.join(DB_DIR, "index.json");
16
- var RUNS_DIR = path.join(DB_DIR, "runs");
17
- var BLOBS_DIR = path.join(DB_DIR, "blobs");
15
+ var RUNS_DIR = () => path.join(DB_DIR, "runs");
16
+ var BLOBS_DIR = () => path.join(DB_DIR, "blobs");
17
+ var RUN_INDEX_DIR = () => path.join(DB_DIR, "index", "runs");
18
+ var THREAD_INDEX_DIR = () => path.join(DB_DIR, "index", "threads");
19
+ var getDbDir = () => DB_DIR;
18
20
  var AILOG_PORT = process.env.AILOG_PORT ? parseInt(process.env.AILOG_PORT) : 4985;
21
+ var dirResolved = false;
22
+ var dirResolvePromise = null;
23
+ var resolveDbDir = async () => {
24
+ if (dirResolved) return;
25
+ if (!dirResolvePromise) {
26
+ dirResolvePromise = (async () => {
27
+ try {
28
+ const res = await fetch(
29
+ `http://localhost:${AILOG_PORT}/api/db-dir`
30
+ );
31
+ if (res.ok) {
32
+ const body = await res.json();
33
+ if (typeof body.dir === "string" && body.dir.length > 0) {
34
+ DB_DIR = body.dir;
35
+ }
36
+ }
37
+ } catch {
38
+ } finally {
39
+ dirResolved = true;
40
+ }
41
+ })();
42
+ }
43
+ await dirResolvePromise;
44
+ };
19
45
  var BLOB_THRESHOLD = 16 * 1024;
20
46
  var notifyServer = (event, runId) => {
21
47
  notifyServerAsync(event, runId);
@@ -28,23 +54,12 @@ var notifyServerAsync = async (event, runId) => {
28
54
  body: JSON.stringify({
29
55
  event,
30
56
  timestamp: Date.now(),
31
- dbPath: INDEX_PATH,
32
57
  runId: runId ?? null
33
58
  })
34
59
  });
35
60
  } catch {
36
61
  }
37
62
  };
38
- var EMPTY_INDEX = () => ({
39
- version: 1,
40
- runs: [],
41
- threads: []
42
- });
43
- var normalizeIndex = (raw) => ({
44
- version: 1,
45
- runs: raw.runs ?? [],
46
- threads: raw.threads ?? []
47
- });
48
63
  var ensureGitignore = () => {
49
64
  const gitignorePath = path.join(process.cwd(), ".gitignore");
50
65
  if (!fs.existsSync(gitignorePath)) return;
@@ -60,16 +75,12 @@ var ensureGitignore = () => {
60
75
  };
61
76
  var ensureLayout = () => {
62
77
  const isFirstRun = !fs.existsSync(DB_DIR);
63
- fs.mkdirSync(RUNS_DIR, { recursive: true });
64
- fs.mkdirSync(BLOBS_DIR, { recursive: true });
78
+ fs.mkdirSync(RUNS_DIR(), { recursive: true });
79
+ fs.mkdirSync(BLOBS_DIR(), { recursive: true });
80
+ fs.mkdirSync(RUN_INDEX_DIR(), { recursive: true });
81
+ fs.mkdirSync(THREAD_INDEX_DIR(), { recursive: true });
65
82
  if (isFirstRun) ensureGitignore();
66
83
  };
67
- var atomicWrite = (filePath, data) => {
68
- ensureLayout();
69
- const tmp = `${filePath}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 6)}.tmp`;
70
- fs.writeFileSync(tmp, data);
71
- fs.renameSync(tmp, filePath);
72
- };
73
84
  var safeReadJson = (filePath, fallback) => {
74
85
  try {
75
86
  if (!fs.existsSync(filePath)) return fallback;
@@ -79,7 +90,7 @@ var safeReadJson = (filePath, fallback) => {
79
90
  }
80
91
  };
81
92
  var readBlobIfPresent = (relPath) => {
82
- const absolute = path.join(currentDbDir(), relPath);
93
+ const absolute = path.join(DB_DIR, relPath);
83
94
  try {
84
95
  if (!fs.existsSync(absolute)) return null;
85
96
  return fs.readFileSync(absolute, "utf-8");
@@ -94,38 +105,68 @@ var hydrate = (v) => {
94
105
  if (isBlobRef(v)) return readBlobIfPresent(v.$blob);
95
106
  return null;
96
107
  };
97
- var indexCache = null;
98
108
  var runCache = /* @__PURE__ */ new Map();
99
- var remoteDbDir = null;
100
- var currentDbDir = () => remoteDbDir ?? DB_DIR;
101
- var currentIndexPath = () => path.join(currentDbDir(), "index.json");
102
- var currentRunPath = (runId) => path.join(currentDbDir(), "runs", `${runId}.json`);
103
- var loadIndex = () => {
104
- if (indexCache) return indexCache;
105
- indexCache = normalizeIndex(
106
- safeReadJson(currentIndexPath(), EMPTY_INDEX())
107
- );
108
- return indexCache;
109
+ var pageCache = /* @__PURE__ */ new Map();
110
+ var PAGE_FILE_RE = /^\d{17}\.json$/;
111
+ var listPageFiles = (dir) => {
112
+ let entries;
113
+ try {
114
+ entries = fs.readdirSync(dir);
115
+ } catch {
116
+ return [];
117
+ }
118
+ return entries.filter((n) => PAGE_FILE_RE.test(n)).sort().map((n) => path.join(dir, n));
119
+ };
120
+ var loadPage = (file) => {
121
+ const cached = pageCache.get(file);
122
+ if (cached) return cached;
123
+ const data = safeReadJson(file, []);
124
+ pageCache.set(file, data);
125
+ return data;
126
+ };
127
+ var loadPagedNewestFirst = (dir, offset, limit) => {
128
+ const files = listPageFiles(dir).reverse();
129
+ let total = 0;
130
+ for (const f of files) total += loadPage(f).length;
131
+ const items = [];
132
+ let skipped = 0;
133
+ for (const file of files) {
134
+ const page = loadPage(file);
135
+ for (let i = page.length - 1; i >= 0; i--) {
136
+ if (skipped < offset) {
137
+ skipped++;
138
+ continue;
139
+ }
140
+ items.push(page[i]);
141
+ if (items.length === limit) {
142
+ return {
143
+ items,
144
+ total,
145
+ hasMore: offset + items.length < total
146
+ };
147
+ }
148
+ }
149
+ }
150
+ return { items, total, hasMore: false };
109
151
  };
110
152
  var loadRunFile = (runId) => {
111
153
  const cached = runCache.get(runId);
112
154
  if (cached) return cached;
113
- const file = safeReadJson(currentRunPath(runId), null);
155
+ const file = safeReadJson(
156
+ path.join(RUNS_DIR(), `${runId}.json`),
157
+ null
158
+ );
114
159
  if (!file) return null;
115
160
  runCache.set(runId, file);
116
161
  return file;
117
162
  };
118
- var reloadDb = async (remoteIndexPath, runId) => {
119
- if (remoteIndexPath) {
120
- remoteDbDir = path.dirname(remoteIndexPath);
121
- }
122
- indexCache = null;
163
+ var reloadDb = async (runId) => {
164
+ pageCache.clear();
123
165
  if (runId) {
124
166
  runCache.delete(runId);
125
167
  } else {
126
168
  runCache.clear();
127
169
  }
128
- loadIndex();
129
170
  };
130
171
  var hydrateStep = (s) => ({
131
172
  ...s,
@@ -134,12 +175,8 @@ var hydrateStep = (s) => ({
134
175
  raw_response: hydrate(s.raw_response),
135
176
  raw_chunks: hydrate(s.raw_chunks)
136
177
  });
137
- var getRunSummaries = async () => {
138
- const index = loadIndex();
139
- return [...index.runs].sort(
140
- (a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime()
141
- );
142
- };
178
+ var getRunSummariesPage = async (offset, limit) => loadPagedNewestFirst(RUN_INDEX_DIR(), offset, limit);
179
+ var getThreadsPage = async (offset, limit) => loadPagedNewestFirst(THREAD_INDEX_DIR(), offset, limit);
143
180
  var getRunWithSteps = async (runId) => {
144
181
  const file = loadRunFile(runId);
145
182
  if (!file) return null;
@@ -149,14 +186,17 @@ var getRunWithSteps = async (runId) => {
149
186
  };
150
187
  };
151
188
  var clearDatabase = async () => {
189
+ await resolveDbDir();
152
190
  ensureLayout();
153
- fs.rmSync(RUNS_DIR, { recursive: true, force: true });
154
- fs.rmSync(BLOBS_DIR, { recursive: true, force: true });
155
- fs.mkdirSync(RUNS_DIR, { recursive: true });
156
- fs.mkdirSync(BLOBS_DIR, { recursive: true });
191
+ fs.rmSync(path.join(DB_DIR, "index"), { recursive: true, force: true });
192
+ fs.rmSync(RUNS_DIR(), { recursive: true, force: true });
193
+ fs.rmSync(BLOBS_DIR(), { recursive: true, force: true });
194
+ fs.mkdirSync(RUN_INDEX_DIR(), { recursive: true });
195
+ fs.mkdirSync(THREAD_INDEX_DIR(), { recursive: true });
196
+ fs.mkdirSync(RUNS_DIR(), { recursive: true });
197
+ fs.mkdirSync(BLOBS_DIR(), { recursive: true });
157
198
  runCache.clear();
158
- indexCache = EMPTY_INDEX();
159
- atomicWrite(INDEX_PATH, JSON.stringify(indexCache, null, 2));
199
+ pageCache.clear();
160
200
  notifyServer("clear");
161
201
  };
162
202
 
@@ -180,17 +220,24 @@ var devEnv = process.env.AILOG_DEV;
180
220
  var isDevMode = devEnv !== void 0 && devEnv !== "false" && devEnv !== "0";
181
221
  var projectRoot = path2.resolve(__dirname, "../..");
182
222
  var clientDir = path2.join(projectRoot, "dist/client");
183
- var remoteDbPath;
184
223
  var app = new Hono();
185
224
  app.use("/*", cors());
225
+ var parsePagination = (c) => {
226
+ const offset = Math.max(0, parseInt(c.req.query("offset") ?? "0", 10) || 0);
227
+ const limit = Math.min(
228
+ 200,
229
+ Math.max(1, parseInt(c.req.query("limit") ?? "50", 10) || 50)
230
+ );
231
+ return { offset, limit };
232
+ };
186
233
  app.get("/api/runs", async (c) => {
187
- await reloadDb(remoteDbPath);
188
- const summaries = await getRunSummaries();
189
- const runs = summaries.map((r) => ({
234
+ await reloadDb();
235
+ const { offset, limit } = parsePagination(c);
236
+ const page = await getRunSummariesPage(offset, limit);
237
+ const items = page.items.map((r) => ({
190
238
  id: r.id,
191
239
  started_at: r.started_at,
192
240
  function_id: r.function_id,
193
- metadata: r.metadata,
194
241
  thread_id: r.thread_id,
195
242
  stepCount: r.step_count,
196
243
  firstMessage: r.first_message ?? "No user message",
@@ -198,11 +245,11 @@ app.get("/api/runs", async (c) => {
198
245
  isInProgress: r.is_in_progress,
199
246
  type: r.type ?? void 0
200
247
  }));
201
- return c.json(runs);
248
+ return c.json({ items, total: page.total, hasMore: page.hasMore });
202
249
  });
203
250
  app.get("/api/runs/:id", async (c) => {
204
251
  const runId = c.req.param("id");
205
- await reloadDb(remoteDbPath, runId);
252
+ await reloadDb(runId);
206
253
  const data = await getRunWithSteps(runId);
207
254
  if (!data) {
208
255
  return c.json({ error: "Run not found" }, 404);
@@ -213,6 +260,14 @@ app.get("/api/runs/:id", async (c) => {
213
260
  steps: data.steps
214
261
  });
215
262
  });
263
+ app.get("/api/threads", async (c) => {
264
+ await reloadDb();
265
+ const { offset, limit } = parsePagination(c);
266
+ return c.json(await getThreadsPage(offset, limit));
267
+ });
268
+ app.get("/api/db-dir", (c) => {
269
+ return c.json({ dir: getDbDir() });
270
+ });
216
271
  app.post("/api/clear", async (c) => {
217
272
  await clearDatabase();
218
273
  return c.json({ success: true });
@@ -268,10 +323,7 @@ app.get("/api/events", (c) => {
268
323
  });
269
324
  app.post("/api/notify", async (c) => {
270
325
  const body = await c.req.json();
271
- if (body.dbPath) {
272
- remoteDbPath = body.dbPath;
273
- }
274
- await reloadDb(remoteDbPath, body.runId ?? void 0);
326
+ await reloadDb(body.runId ?? void 0);
275
327
  broadcastToClients("update", body);
276
328
  return c.json({ success: true });
277
329
  });
@@ -328,6 +380,7 @@ var startViewer = (port = 4985) => {
328
380
  } else {
329
381
  console.log(`\u{1F50D} ailog running at http://localhost:${port}`);
330
382
  }
383
+ console.log(` Reading .ailog from ${getDbDir()}`);
331
384
  }
332
385
  );
333
386
  server.on("error", (err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ailog/cli",
3
- "version": "0.2.6",
3
+ "version": "0.3.0",
4
4
  "description": "CLI to run the ailog LLM logging dashboard",
5
5
  "type": "module",
6
6
  "private": false,