@lovelybunch/api 1.0.66 → 1.0.67

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.
Files changed (35) hide show
  1. package/dist/routes/api/v1/events/events.test.d.ts +4 -0
  2. package/dist/routes/api/v1/events/events.test.js +289 -0
  3. package/dist/routes/api/v1/events/index.d.ts +7 -0
  4. package/dist/routes/api/v1/events/index.js +19 -0
  5. package/dist/routes/api/v1/events/purge/route.d.ts +19 -0
  6. package/dist/routes/api/v1/events/purge/route.js +62 -0
  7. package/dist/routes/api/v1/events/route.d.ts +30 -0
  8. package/dist/routes/api/v1/events/route.js +109 -0
  9. package/dist/routes/api/v1/events/status/route.d.ts +20 -0
  10. package/dist/routes/api/v1/events/status/route.js +53 -0
  11. package/dist/routes/api/v1/events/stream/route.d.ts +9 -0
  12. package/dist/routes/api/v1/events/stream/route.js +132 -0
  13. package/dist/routes/api/v1/init/index.d.ts +1 -0
  14. package/dist/routes/api/v1/init/index.js +1 -0
  15. package/dist/routes/api/v1/init/route.d.ts +3 -0
  16. package/dist/routes/api/v1/init/route.js +129 -0
  17. package/dist/routes/api/v1/onboard/index.d.ts +3 -0
  18. package/dist/routes/api/v1/onboard/index.js +8 -0
  19. package/dist/routes/api/v1/onboard/route.d.ts +13 -0
  20. package/dist/routes/api/v1/onboard/route.js +311 -0
  21. package/dist/routes/api/v1/onboarding/check/index.d.ts +3 -0
  22. package/dist/routes/api/v1/onboarding/check/index.js +5 -0
  23. package/dist/routes/api/v1/onboarding/check/route.d.ts +12 -0
  24. package/dist/routes/api/v1/onboarding/check/route.js +24 -0
  25. package/dist/routes/api/v1/onboarding/index.d.ts +1 -0
  26. package/dist/routes/api/v1/onboarding/index.js +1 -0
  27. package/dist/routes/api/v1/onboarding/route.d.ts +3 -0
  28. package/dist/routes/api/v1/onboarding/route.js +158 -0
  29. package/dist/routes/api/v1/proposals/[id]/route.js +57 -0
  30. package/dist/routes/api/v1/proposals/route.js +18 -0
  31. package/dist/server-with-static.js +62 -0
  32. package/dist/server.js +63 -0
  33. package/package.json +4 -4
  34. package/static/assets/{index-DuLX7Zvh.js → index-aLGL6jN0.js} +33 -33
  35. package/static/index.html +1 -1
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Integration tests for Events API routes
3
+ */
4
+ export {};
@@ -0,0 +1,289 @@
1
+ /**
2
+ * Integration tests for Events API routes
3
+ */
4
+ import { describe, it, expect, beforeEach, afterEach, beforeAll } from "vitest";
5
+ import { Hono } from "hono";
6
+ import { promises as fs } from "fs";
7
+ import path from "path";
8
+ import os from "os";
9
+ import { getLogger, resetLogger } from "@lovelybunch/core/logging";
10
+ import events from "./index.js";
11
+ describe("Events API Routes", () => {
12
+ let app;
13
+ let testDir;
14
+ let logsDir;
15
+ beforeAll(() => {
16
+ // Create the Hono app with routes
17
+ app = new Hono();
18
+ app.route("/api/v1/events", events);
19
+ });
20
+ beforeEach(async () => {
21
+ // Create a temporary directory for tests
22
+ testDir = path.join(os.tmpdir(), `coconut-events-test-${Date.now()}`);
23
+ logsDir = path.join(testDir, "logs");
24
+ await fs.mkdir(logsDir, { recursive: true });
25
+ // Reset the logger and create a new one with test directory
26
+ resetLogger();
27
+ getLogger({
28
+ coconutId: "test-coconut",
29
+ logsDir: logsDir,
30
+ });
31
+ });
32
+ afterEach(async () => {
33
+ // Clean up logger and test directory
34
+ const logger = getLogger();
35
+ await logger.close();
36
+ resetLogger();
37
+ try {
38
+ await fs.rm(testDir, { recursive: true, force: true });
39
+ }
40
+ catch (err) {
41
+ // Ignore cleanup errors
42
+ }
43
+ });
44
+ describe("POST /api/v1/events", () => {
45
+ it("should enqueue a single event", async () => {
46
+ const event = {
47
+ kind: "proposal.create",
48
+ actor: "human:test@example.com",
49
+ subject: "proposal:cp-123",
50
+ tags: ["proposal"],
51
+ payload: { intent: "Test proposal" },
52
+ };
53
+ const res = await app.request("/api/v1/events", {
54
+ method: "POST",
55
+ headers: { "Content-Type": "application/json" },
56
+ body: JSON.stringify(event),
57
+ });
58
+ expect(res.status).toBe(200);
59
+ const data = await res.json();
60
+ expect(data.accepted).toBe(1);
61
+ expect(data.last_seq).toBeGreaterThan(0);
62
+ // Verify event was written to file
63
+ await getLogger().flush();
64
+ const logFile = path.join(logsDir, "events-current.jsonl");
65
+ const content = await fs.readFile(logFile, "utf-8");
66
+ const lines = content.trim().split("\n");
67
+ expect(lines.length).toBeGreaterThan(0);
68
+ const loggedEvent = JSON.parse(lines[lines.length - 1]);
69
+ expect(loggedEvent.kind).toBe("proposal.create");
70
+ expect(loggedEvent.actor).toBe("human:test@example.com");
71
+ });
72
+ it("should enqueue multiple events", async () => {
73
+ const events = [
74
+ {
75
+ kind: "proposal.create",
76
+ actor: "human:test@example.com",
77
+ subject: "proposal:cp-1",
78
+ },
79
+ {
80
+ kind: "proposal.update",
81
+ actor: "human:test@example.com",
82
+ subject: "proposal:cp-2",
83
+ },
84
+ {
85
+ kind: "proposal.status.change",
86
+ actor: "human:test@example.com",
87
+ subject: "proposal:cp-3",
88
+ },
89
+ ];
90
+ const res = await app.request("/api/v1/events", {
91
+ method: "POST",
92
+ headers: { "Content-Type": "application/json" },
93
+ body: JSON.stringify(events),
94
+ });
95
+ expect(res.status).toBe(200);
96
+ const data = await res.json();
97
+ expect(data.accepted).toBe(3);
98
+ expect(data.last_seq).toBeGreaterThan(0);
99
+ });
100
+ });
101
+ describe("GET /api/v1/events", () => {
102
+ beforeEach(async () => {
103
+ // Pre-populate with some events
104
+ const logger = getLogger();
105
+ for (let i = 1; i <= 10; i++) {
106
+ logger.log({
107
+ kind: "proposal.create",
108
+ actor: "human:test@example.com",
109
+ subject: `proposal:cp-${i}`,
110
+ payload: { index: i },
111
+ });
112
+ }
113
+ await logger.flush();
114
+ });
115
+ it("should fetch events from current file", async () => {
116
+ const res = await app.request("/api/v1/events");
117
+ expect(res.status).toBe(200);
118
+ const data = await res.json();
119
+ expect(data.items).toBeDefined();
120
+ expect(Array.isArray(data.items)).toBe(true);
121
+ expect(data.items.length).toBe(10);
122
+ expect(data.next_since_seq).toBeGreaterThan(0);
123
+ expect(data.eof).toBe(true);
124
+ });
125
+ it("should support pagination with since_seq", async () => {
126
+ const res1 = await app.request("/api/v1/events?limit=5");
127
+ expect(res1.status).toBe(200);
128
+ const data1 = await res1.json();
129
+ expect(data1.items.length).toBe(5);
130
+ expect(data1.eof).toBe(false);
131
+ const nextSeq = data1.next_since_seq;
132
+ const res2 = await app.request(`/api/v1/events?since_seq=${nextSeq}&limit=5`);
133
+ expect(res2.status).toBe(200);
134
+ const data2 = await res2.json();
135
+ expect(data2.items.length).toBeGreaterThan(0);
136
+ });
137
+ it("should respect limit parameter", async () => {
138
+ const res = await app.request("/api/v1/events?limit=3");
139
+ expect(res.status).toBe(200);
140
+ const data = await res.json();
141
+ expect(data.items.length).toBe(3);
142
+ expect(data.eof).toBe(false);
143
+ });
144
+ it("should return empty array when no events exist", async () => {
145
+ // Clean up existing events
146
+ await getLogger().close();
147
+ resetLogger();
148
+ await fs.rm(logsDir, { recursive: true, force: true });
149
+ await fs.mkdir(logsDir, { recursive: true });
150
+ getLogger({
151
+ coconutId: "test-coconut",
152
+ logsDir: logsDir,
153
+ });
154
+ const res = await app.request("/api/v1/events");
155
+ expect(res.status).toBe(200);
156
+ const data = await res.json();
157
+ expect(data.items).toEqual([]);
158
+ expect(data.next_since_seq).toBe(0);
159
+ expect(data.eof).toBe(true);
160
+ });
161
+ });
162
+ describe("GET /api/v1/events/status", () => {
163
+ beforeEach(async () => {
164
+ // Pre-populate with some events
165
+ const logger = getLogger();
166
+ for (let i = 1; i <= 5; i++) {
167
+ logger.log({
168
+ kind: "proposal.create",
169
+ subject: `proposal:cp-${i}`,
170
+ });
171
+ }
172
+ await logger.flush();
173
+ });
174
+ it("should return logging system status", async () => {
175
+ const res = await app.request("/api/v1/events/status");
176
+ expect(res.status).toBe(200);
177
+ const data = await res.json();
178
+ expect(data.currentFile).toBe("events-current.jsonl");
179
+ expect(data.sizeBytes).toBeGreaterThan(0);
180
+ expect(data.lastSeq).toBe(5);
181
+ expect(data.rotateBytes).toBe(128 * 1024 * 1024);
182
+ expect(data.logsDir).toBeDefined();
183
+ });
184
+ it("should handle missing log file gracefully", async () => {
185
+ // Clean up existing events
186
+ await getLogger().close();
187
+ resetLogger();
188
+ await fs.rm(logsDir, { recursive: true, force: true });
189
+ await fs.mkdir(logsDir, { recursive: true });
190
+ getLogger({
191
+ coconutId: "test-coconut",
192
+ logsDir: logsDir,
193
+ });
194
+ const res = await app.request("/api/v1/events/status");
195
+ expect(res.status).toBe(200);
196
+ const data = await res.json();
197
+ expect(data.currentFile).toBeNull();
198
+ expect(data.sizeBytes).toBe(0);
199
+ expect(data.lastSeq).toBe(0);
200
+ });
201
+ });
202
+ describe("POST /api/v1/events/purge", () => {
203
+ beforeEach(async () => {
204
+ // Create some rotated files with different timestamps
205
+ const oldDate = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000); // 7 days ago
206
+ const recentDate = new Date(Date.now() - 1 * 24 * 60 * 60 * 1000); // 1 day ago
207
+ await fs.writeFile(path.join(logsDir, "events-20250101-120000.jsonl"), "");
208
+ await fs.utimes(path.join(logsDir, "events-20250101-120000.jsonl"), oldDate, oldDate);
209
+ await fs.writeFile(path.join(logsDir, "events-20250115-120000.jsonl"), "");
210
+ await fs.utimes(path.join(logsDir, "events-20250115-120000.jsonl"), recentDate, recentDate);
211
+ });
212
+ it("should delete old rotated files", async () => {
213
+ const beforeIso = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(); // 3 days ago
214
+ const res = await app.request("/api/v1/events/purge", {
215
+ method: "POST",
216
+ headers: { "Content-Type": "application/json" },
217
+ body: JSON.stringify({ beforeIso }),
218
+ });
219
+ expect(res.status).toBe(200);
220
+ const data = await res.json();
221
+ expect(data.deleted).toBe(1);
222
+ // Verify old file was deleted and recent file remains
223
+ const files = await fs.readdir(logsDir);
224
+ expect(files).toContain("events-20250115-120000.jsonl");
225
+ expect(files).not.toContain("events-20250101-120000.jsonl");
226
+ });
227
+ it("should never delete current file", async () => {
228
+ const logger = getLogger();
229
+ logger.log({ kind: "proposal.create", subject: "proposal:cp-1" });
230
+ await logger.flush();
231
+ const futureDate = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(); // tomorrow
232
+ const res = await app.request("/api/v1/events/purge", {
233
+ method: "POST",
234
+ headers: { "Content-Type": "application/json" },
235
+ body: JSON.stringify({ beforeIso: futureDate }),
236
+ });
237
+ expect(res.status).toBe(200);
238
+ // Current file should still exist
239
+ const currentFile = path.join(logsDir, "events-current.jsonl");
240
+ const exists = await fs
241
+ .access(currentFile)
242
+ .then(() => true)
243
+ .catch(() => false);
244
+ expect(exists).toBe(true);
245
+ });
246
+ it("should return error for missing beforeIso", async () => {
247
+ const res = await app.request("/api/v1/events/purge", {
248
+ method: "POST",
249
+ headers: { "Content-Type": "application/json" },
250
+ body: JSON.stringify({}),
251
+ });
252
+ expect(res.status).toBe(400);
253
+ const data = await res.json();
254
+ expect(data.error).toBe("beforeIso is required");
255
+ });
256
+ it("should return error for invalid ISO date", async () => {
257
+ const res = await app.request("/api/v1/events/purge", {
258
+ method: "POST",
259
+ headers: { "Content-Type": "application/json" },
260
+ body: JSON.stringify({ beforeIso: "not-a-date" }),
261
+ });
262
+ expect(res.status).toBe(400);
263
+ const data = await res.json();
264
+ expect(data.error).toBe("Invalid ISO date");
265
+ });
266
+ });
267
+ describe("GET /api/v1/events/stream", () => {
268
+ it("should stream events via SSE", async () => {
269
+ // Pre-populate with some events
270
+ const logger = getLogger();
271
+ for (let i = 1; i <= 3; i++) {
272
+ logger.log({
273
+ kind: "proposal.create",
274
+ subject: `proposal:cp-${i}`,
275
+ payload: { index: i },
276
+ });
277
+ }
278
+ await logger.flush();
279
+ const res = await app.request("/api/v1/events/stream");
280
+ expect(res.status).toBe(200);
281
+ expect(res.headers.get("content-type")).toContain("text/event-stream");
282
+ expect(res.headers.get("cache-control")).toContain("no-cache");
283
+ expect(res.headers.get("connection")).toContain("keep-alive");
284
+ // Note: Full SSE testing would require streaming the response body,
285
+ // which is complex in this test environment. This test validates
286
+ // that the endpoint returns the correct headers for SSE.
287
+ });
288
+ });
289
+ });
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Events API routes index
3
+ * Routes for Coconut activity logging events ingestion, retrieval, and streaming
4
+ */
5
+ import { Hono } from 'hono';
6
+ declare const events: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
7
+ export default events;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Events API routes index
3
+ * Routes for Coconut activity logging events ingestion, retrieval, and streaming
4
+ */
5
+ import { Hono } from 'hono';
6
+ import { GET, POST } from './route.js';
7
+ import { GET as getStream } from './stream/route.js';
8
+ import { GET as getStatus } from './status/route.js';
9
+ import { POST as postPurge } from './purge/route.js';
10
+ const events = new Hono();
11
+ // Main events endpoints
12
+ events.get('/', GET); // GET /api/v1/events?since_seq=&limit= - Fetch paged events
13
+ events.post('/', POST); // POST /api/v1/events - Enqueue events
14
+ // Streaming endpoint
15
+ events.get('/stream', getStream); // GET /api/v1/events/stream?since_seq= - SSE streaming
16
+ // Status and management endpoints
17
+ events.get('/status', getStatus); // GET /api/v1/events/status - System diagnostics
18
+ events.post('/purge', postPurge); // POST /api/v1/events/purge - Delete old rotated files
19
+ export default events;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Events purge endpoint
3
+ */
4
+ import { Context } from "hono";
5
+ /**
6
+ * POST /api/v1/events/purge
7
+ * Delete rotated files older than a timestamp
8
+ * Note: The current file is never deleted
9
+ */
10
+ export declare function POST(c: Context): Promise<(Response & import("hono").TypedResponse<{
11
+ error: string;
12
+ }, 404, "json">) | (Response & import("hono").TypedResponse<{
13
+ error: string;
14
+ }, 400, "json">) | (Response & import("hono").TypedResponse<{
15
+ deleted: number;
16
+ }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
17
+ error: string;
18
+ message: any;
19
+ }, 500, "json">)>;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Events purge endpoint
3
+ */
4
+ import { promises as fs } from "fs";
5
+ import path from "path";
6
+ import { findGaitDirectory } from "../../../../../lib/gait-path.js";
7
+ /**
8
+ * Get the events directory path
9
+ */
10
+ async function getEventsDir() {
11
+ const gaitDir = await findGaitDirectory();
12
+ if (!gaitDir)
13
+ return null;
14
+ return path.join(gaitDir, "logs");
15
+ }
16
+ /**
17
+ * POST /api/v1/events/purge
18
+ * Delete rotated files older than a timestamp
19
+ * Note: The current file is never deleted
20
+ */
21
+ export async function POST(c) {
22
+ try {
23
+ const eventsDir = await getEventsDir();
24
+ if (!eventsDir) {
25
+ return c.json({ error: "Events directory not found" }, 404);
26
+ }
27
+ const body = await c.req.json();
28
+ const beforeIso = body.beforeIso;
29
+ if (!beforeIso) {
30
+ return c.json({ error: "beforeIso is required" }, 400);
31
+ }
32
+ const beforeDate = new Date(beforeIso);
33
+ if (isNaN(beforeDate.getTime())) {
34
+ return c.json({ error: "Invalid ISO date" }, 400);
35
+ }
36
+ // Read all files in the events directory
37
+ const files = await fs.readdir(eventsDir);
38
+ let deleted = 0;
39
+ for (const file of files) {
40
+ // Only process rotated event files (not events-current.jsonl or .seq)
41
+ if (!file.startsWith("events-") || file === "events-current.jsonl") {
42
+ continue;
43
+ }
44
+ const filePath = path.join(eventsDir, file);
45
+ const stats = await fs.stat(filePath);
46
+ // Check if file is older than the threshold
47
+ if (stats.mtime < beforeDate) {
48
+ await fs.unlink(filePath);
49
+ deleted++;
50
+ }
51
+ }
52
+ return c.json({
53
+ deleted,
54
+ });
55
+ }
56
+ catch (error) {
57
+ return c.json({
58
+ error: "Failed to purge events",
59
+ message: error.message,
60
+ }, 500);
61
+ }
62
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Events API routes for Coconut activity logging
3
+ * Provides endpoints for logging, querying, and streaming events
4
+ */
5
+ import { Context } from "hono";
6
+ /**
7
+ * POST /api/v1/events
8
+ * Enqueue one or many events
9
+ */
10
+ export declare function POST(c: Context): Promise<(Response & import("hono").TypedResponse<{
11
+ accepted: number;
12
+ last_seq: number;
13
+ }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
14
+ error: string;
15
+ message: any;
16
+ }, 500, "json">)>;
17
+ /**
18
+ * GET /api/v1/events?since_seq=&limit=
19
+ * Fetch a page of events from the current file
20
+ */
21
+ export declare function GET(c: Context): Promise<(Response & import("hono").TypedResponse<{
22
+ error: string;
23
+ }, 404, "json">) | (Response & import("hono").TypedResponse<{
24
+ items: any[];
25
+ next_since_seq: number;
26
+ eof: boolean;
27
+ }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
28
+ error: string;
29
+ message: any;
30
+ }, 500, "json">)>;
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Events API routes for Coconut activity logging
3
+ * Provides endpoints for logging, querying, and streaming events
4
+ */
5
+ import { getLogger } from "@lovelybunch/core/logging";
6
+ import { promises as fs } from "fs";
7
+ import * as fsSync from "fs";
8
+ import path from "path";
9
+ import { findGaitDirectory } from "../../../../lib/gait-path.js";
10
+ import readline from "readline";
11
+ /**
12
+ * Get the events directory path
13
+ */
14
+ async function getEventsDir() {
15
+ const gaitDir = await findGaitDirectory();
16
+ if (!gaitDir)
17
+ return null;
18
+ return path.join(gaitDir, "logs");
19
+ }
20
+ /**
21
+ * POST /api/v1/events
22
+ * Enqueue one or many events
23
+ */
24
+ export async function POST(c) {
25
+ try {
26
+ const body = await c.req.json();
27
+ const logger = getLogger();
28
+ // Handle array of events or single event
29
+ const events = Array.isArray(body) ? body : [body];
30
+ let lastSeq = 0;
31
+ for (const event of events) {
32
+ logger.log(event);
33
+ lastSeq = logger.getSeq();
34
+ }
35
+ return c.json({
36
+ accepted: events.length,
37
+ last_seq: lastSeq,
38
+ });
39
+ }
40
+ catch (error) {
41
+ return c.json({
42
+ error: "Failed to enqueue events",
43
+ message: error.message,
44
+ }, 500);
45
+ }
46
+ }
47
+ /**
48
+ * GET /api/v1/events?since_seq=&limit=
49
+ * Fetch a page of events from the current file
50
+ */
51
+ export async function GET(c) {
52
+ try {
53
+ const eventsDir = await getEventsDir();
54
+ if (!eventsDir) {
55
+ return c.json({ error: "Events directory not found" }, 404);
56
+ }
57
+ const url = new URL(c.req.url);
58
+ const sinceSeq = parseInt(url.searchParams.get("since_seq") || "0", 10);
59
+ const limit = Math.min(parseInt(url.searchParams.get("limit") || "1000", 10), 5000);
60
+ const currentFile = path.join(eventsDir, "events-current.jsonl");
61
+ // Check if file exists
62
+ try {
63
+ await fs.access(currentFile);
64
+ }
65
+ catch {
66
+ return c.json({
67
+ items: [],
68
+ next_since_seq: 0,
69
+ eof: true,
70
+ });
71
+ }
72
+ // Read events from file
73
+ const items = [];
74
+ const fileStream = fsSync.createReadStream(currentFile);
75
+ const rl = readline.createInterface({
76
+ input: fileStream,
77
+ crlfDelay: Infinity,
78
+ });
79
+ let lastSeq = sinceSeq;
80
+ let count = 0;
81
+ for await (const line of rl) {
82
+ if (!line.trim())
83
+ continue;
84
+ try {
85
+ const event = JSON.parse(line);
86
+ if (event.seq > sinceSeq && count < limit) {
87
+ items.push(event);
88
+ count++;
89
+ }
90
+ lastSeq = Math.max(lastSeq, event.seq);
91
+ }
92
+ catch (err) {
93
+ // Skip malformed lines
94
+ continue;
95
+ }
96
+ }
97
+ return c.json({
98
+ items,
99
+ next_since_seq: lastSeq,
100
+ eof: count < limit,
101
+ });
102
+ }
103
+ catch (error) {
104
+ return c.json({
105
+ error: "Failed to fetch events",
106
+ message: error.message,
107
+ }, 500);
108
+ }
109
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Events status endpoint
3
+ */
4
+ import { Context } from "hono";
5
+ /**
6
+ * GET /api/v1/events/status
7
+ * Get logging system status and configuration
8
+ */
9
+ export declare function GET(c: Context): Promise<(Response & import("hono").TypedResponse<{
10
+ error: string;
11
+ }, 404, "json">) | (Response & import("hono").TypedResponse<{
12
+ currentFile: string;
13
+ sizeBytes: number;
14
+ lastSeq: number;
15
+ rotateBytes: number;
16
+ logsDir: string;
17
+ }, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
18
+ error: string;
19
+ message: any;
20
+ }, 500, "json">)>;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Events status endpoint
3
+ */
4
+ import { getLogger } from "@lovelybunch/core/logging";
5
+ import { promises as fs } from "fs";
6
+ import path from "path";
7
+ import { findGaitDirectory } from "../../../../../lib/gait-path.js";
8
+ /**
9
+ * Get the events directory path
10
+ */
11
+ async function getEventsDir() {
12
+ const gaitDir = await findGaitDirectory();
13
+ if (!gaitDir)
14
+ return null;
15
+ return path.join(gaitDir, "logs");
16
+ }
17
+ /**
18
+ * GET /api/v1/events/status
19
+ * Get logging system status and configuration
20
+ */
21
+ export async function GET(c) {
22
+ try {
23
+ const eventsDir = await getEventsDir();
24
+ if (!eventsDir) {
25
+ return c.json({ error: "Events directory not found" }, 404);
26
+ }
27
+ const logger = getLogger();
28
+ const currentFile = path.join(eventsDir, "events-current.jsonl");
29
+ let sizeBytes = 0;
30
+ let exists = false;
31
+ try {
32
+ const stats = await fs.stat(currentFile);
33
+ sizeBytes = stats.size;
34
+ exists = true;
35
+ }
36
+ catch {
37
+ // File doesn't exist yet
38
+ }
39
+ return c.json({
40
+ currentFile: exists ? "events-current.jsonl" : null,
41
+ sizeBytes,
42
+ lastSeq: logger.getSeq(),
43
+ rotateBytes: 128 * 1024 * 1024, // 128MB default
44
+ logsDir: eventsDir,
45
+ });
46
+ }
47
+ catch (error) {
48
+ return c.json({
49
+ error: "Failed to get status",
50
+ message: error.message,
51
+ }, 500);
52
+ }
53
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Events streaming endpoint for real-time tailing
3
+ */
4
+ import { Context } from "hono";
5
+ /**
6
+ * GET /api/v1/events/stream?since_seq=
7
+ * Server-Sent Events stream for near real-time tails
8
+ */
9
+ export declare function GET(c: Context): Promise<Response>;