@loghead/core 0.1.32 → 0.1.34

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.
@@ -1,33 +1,29 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.startApiServer = startApiServer;
7
- const express_1 = __importDefault(require("express"));
8
- const cors_1 = __importDefault(require("cors"));
9
- const path_1 = __importDefault(require("path"));
10
- const auth_1 = require("../services/auth");
11
- const chalk_1 = __importDefault(require("chalk"));
12
- const auth = new auth_1.AuthService();
13
- async function startApiServer(db) {
14
- const app = (0, express_1.default)();
1
+ import express from "express";
2
+ import cors from "cors";
3
+ import path from "path";
4
+ import fs from "fs";
5
+ import { fileURLToPath } from "url";
6
+ import chalk from "chalk";
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ export async function startApiServer(db, auth) {
10
+ const app = express();
15
11
  const port = process.env.PORT || 4567;
16
- app.use((0, cors_1.default)());
17
- app.use(express_1.default.json());
12
+ app.use(cors());
13
+ app.use(express.json());
18
14
  // Serve static frontend files
19
15
  // Determine path based on whether we are running in src (dev) or dist (prod)
20
- let publicPath = path_1.default.join(__dirname, "../public");
21
- if (!require("fs").existsSync(publicPath)) {
16
+ let publicPath = path.join(__dirname, "../public");
17
+ if (!fs.existsSync(publicPath)) {
22
18
  // Try looking in dist/public if we are in src
23
- publicPath = path_1.default.join(__dirname, "../../dist/public");
19
+ publicPath = path.join(__dirname, "../../dist/public");
24
20
  }
25
- if (require("fs").existsSync(publicPath)) {
26
- console.log(chalk_1.default.blue(`Serving frontend from: ${publicPath}`));
27
- app.use(express_1.default.static(publicPath));
21
+ if (fs.existsSync(publicPath)) {
22
+ console.log(chalk.blue(`Serving frontend from: ${publicPath}`));
23
+ app.use(express.static(publicPath));
28
24
  }
29
25
  else {
30
- console.warn(chalk_1.default.yellow("Frontend build not found. Run 'npm run build' in packages/core/frontend to build the UI."));
26
+ console.warn(chalk.yellow("Frontend build not found. Run 'npm run build' in packages/core/frontend to build the UI."));
31
27
  }
32
28
  await auth.initialize();
33
29
  // console.log(chalk.bold.green(`\nšŸ’» API server running on:`));
@@ -195,37 +191,55 @@ async function startApiServer(db) {
195
191
  res.status(500).json({ error: String(e) });
196
192
  }
197
193
  });
198
- app.get("/api/projects", (req, res) => {
199
- const projects = db.listProjects();
194
+ app.get("/api/projects", async (req, res) => {
195
+ const projects = await db.listProjects();
200
196
  res.json(projects);
201
197
  });
202
- app.post("/api/projects", (req, res) => {
198
+ app.post("/api/projects", async (req, res) => {
203
199
  const { name } = req.body;
204
200
  if (!name)
205
201
  return res.status(400).json({ error: "Name required" });
206
- const project = db.createProject(name);
202
+ const project = await db.createProject(name);
207
203
  res.json(project);
208
204
  });
209
- app.delete("/api/projects/:id", (req, res) => {
205
+ app.delete("/api/projects/:id", async (req, res) => {
210
206
  const { id } = req.params;
211
- db.deleteProject(id);
207
+ await db.deleteProject(id);
212
208
  res.json({ success: true });
213
209
  });
214
- app.get("/api/streams", (req, res) => {
210
+ app.patch("/api/projects/:id", async (req, res) => {
211
+ const { id } = req.params;
212
+ const { name } = req.body;
213
+ if (!name || !name.trim()) {
214
+ return res.status(400).send("Name required");
215
+ }
216
+ const project = await db.renameProject(id, name.trim());
217
+ res.json(project);
218
+ });
219
+ app.get("/api/streams", async (req, res) => {
215
220
  const projectId = req.query.projectId;
216
221
  if (projectId) {
217
- const streams = db.listStreams(projectId);
222
+ const streams = await db.listStreams(projectId);
218
223
  res.json(streams);
219
224
  }
220
225
  else {
221
226
  res.status(400).send("Missing projectId");
222
227
  }
223
228
  });
224
- app.delete("/api/streams/:id", (req, res) => {
229
+ app.delete("/api/streams/:id", async (req, res) => {
225
230
  const { id } = req.params;
226
- db.deleteStream(id);
231
+ await db.deleteStream(id);
227
232
  res.json({ success: true });
228
233
  });
234
+ app.patch("/api/streams/:id", async (req, res) => {
235
+ const { id } = req.params;
236
+ const { name } = req.body;
237
+ if (!name || !name.trim()) {
238
+ return res.status(400).send("Name required");
239
+ }
240
+ const stream = await db.renameStream(id, name.trim());
241
+ res.json(stream);
242
+ });
229
243
  app.get("/api/streams/:id/token", async (req, res) => {
230
244
  const { id } = req.params;
231
245
  try {
@@ -236,19 +250,24 @@ async function startApiServer(db) {
236
250
  res.status(500).json({ error: String(e) });
237
251
  }
238
252
  });
239
- app.post("/api/streams", (req, res) => {
240
- // Deprecated or just listing? The previous code had this returning listStreams for POST?
241
- // I'll remove it or keep it if CLI uses it?
242
- // CLI uses db directly.
243
- // MCP uses GET /api/streams
244
- // I'll replace this with the actual CREATE logic to be RESTful, or keep /create
245
- const projectId = req.body.projectId;
246
- if (projectId) {
247
- const streams = db.listStreams(projectId);
248
- res.json(streams);
253
+ app.post("/api/streams", async (req, res) => {
254
+ try {
255
+ const { projectId, name, type, config } = req.body || {};
256
+ if (!projectId) {
257
+ return res.status(400).send("Missing projectId");
258
+ }
259
+ if (!name || typeof name !== "string" || !name.trim()) {
260
+ return res.status(400).send("Missing name");
261
+ }
262
+ if (!type || typeof type !== "string") {
263
+ return res.status(400).send("Missing type");
264
+ }
265
+ const stream = await db.createStream(projectId, type, name.trim(), config || {});
266
+ res.json(stream);
249
267
  }
250
- else {
251
- res.status(400).send("Missing projectId");
268
+ catch (e) {
269
+ console.error("Create stream error:", e);
270
+ res.status(500).json({ error: String(e) });
252
271
  }
253
272
  });
254
273
  app.post("/api/streams/create", async (req, res) => {
@@ -256,6 +275,23 @@ async function startApiServer(db) {
256
275
  const stream = await db.createStream(body.projectId, body.type, body.name, body.config || {});
257
276
  res.json(stream);
258
277
  });
278
+ app.get("/api/search", async (req, res) => {
279
+ const query = req.query.query || req.query.q;
280
+ const streamId = req.query.streamId;
281
+ const limitStr = req.query.limit;
282
+ const limit = limitStr ? parseInt(limitStr) : 50;
283
+ if (!query) {
284
+ return res.json([]);
285
+ }
286
+ try {
287
+ const results = await db.searchLogs(query, streamId, limit);
288
+ res.json(results);
289
+ }
290
+ catch (e) {
291
+ console.error("Error searching logs:", e);
292
+ res.status(500).send(String(e));
293
+ }
294
+ });
259
295
  app.get("/api/logs", async (req, res) => {
260
296
  const streamId = req.query.streamId;
261
297
  if (!streamId) {
@@ -275,20 +311,64 @@ async function startApiServer(db) {
275
311
  const query = req.query.q;
276
312
  let logs;
277
313
  if (query) {
278
- logs = await db.searchLogs(streamId, query, limit);
314
+ logs = await db.searchLogs(query, streamId, limit);
279
315
  }
280
316
  else {
281
- logs = db.getRecentLogs(streamId, limit, offset);
317
+ logs = await db.getRecentLogs(streamId, limit, offset);
282
318
  }
283
319
  res.json(logs);
284
320
  });
321
+ app.get("/api/issues", async (req, res) => {
322
+ const projectId = req.query.projectId;
323
+ const status = req.query.status;
324
+ const limit = parseInt(req.query.limit || "50");
325
+ if (!projectId) {
326
+ return res.status(400).send("Missing projectId");
327
+ }
328
+ try {
329
+ const issues = await db.getIssues(projectId, status, limit);
330
+ res.json(issues);
331
+ }
332
+ catch (e) {
333
+ console.error("Error fetching issues:", e);
334
+ res.status(500).send(String(e));
335
+ }
336
+ });
337
+ app.get("/api/issues/:id", async (req, res) => {
338
+ const { id } = req.params;
339
+ try {
340
+ const data = await db.getIssue(id);
341
+ if (!data)
342
+ return res.status(404).send("Issue not found");
343
+ res.json(data);
344
+ }
345
+ catch (e) {
346
+ console.error("Error fetching issue details:", e);
347
+ res.status(500).send(String(e));
348
+ }
349
+ });
350
+ app.patch("/api/issues/:id", async (req, res) => {
351
+ const { id } = req.params;
352
+ const { status } = req.body;
353
+ if (status !== "open" && status !== "resolved") {
354
+ return res.status(400).send("Invalid status");
355
+ }
356
+ try {
357
+ await db.updateIssueStatus(id, status);
358
+ res.json({ success: true });
359
+ }
360
+ catch (e) {
361
+ console.error("Error updating issue:", e);
362
+ res.status(500).send(String(e));
363
+ }
364
+ });
285
365
  // SPA fallback
286
366
  app.get("*", (req, res) => {
287
367
  if (req.path.startsWith("/api")) {
288
368
  return res.status(404).json({ error: "Not Found" });
289
369
  }
290
- if (require("fs").existsSync(path_1.default.join(publicPath, "index.html"))) {
291
- res.sendFile(path_1.default.join(publicPath, "index.html"));
370
+ if (fs.existsSync(path.join(publicPath, "index.html"))) {
371
+ res.sendFile(path.join(publicPath, "index.html"));
292
372
  }
293
373
  else {
294
374
  res.status(404).send("Dashboard not found. Please build the frontend.");
package/dist/cli_main.js CHANGED
@@ -1,59 +1,64 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
3
- var __importDefault = (this && this.__importDefault) || function (mod) {
4
- return (mod && mod.__esModule) ? mod : { "default": mod };
5
- };
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- const yargs_1 = __importDefault(require("yargs"));
8
- const helpers_1 = require("yargs/helpers");
9
- const db_1 = require("./services/db");
10
- const server_1 = require("./api/server");
11
- const migrate_1 = require("./db/migrate");
12
- // import { ensureInfrastructure } from "./utils/startup"; // Might need adjustment
13
- const auth_1 = require("./services/auth");
14
- const chalk_1 = __importDefault(require("chalk"));
15
- const db = new db_1.DbService();
16
- const auth = new auth_1.AuthService();
2
+ import yargs from "yargs";
3
+ import { hideBin } from "yargs/helpers";
4
+ import { dbService, authService, dbAdapter } from "./db/client.js";
5
+ import { startApiServer } from "./api/server.js";
6
+ import { migrate } from "@loghead/db";
7
+ import chalk from "chalk";
8
+ function isUnsupportedVectorIndexError(error) {
9
+ const msg = error instanceof Error ? error.message : String(error);
10
+ return msg.includes("invalid expression in CREATE INDEX") && msg.includes("libsql_vector_idx");
11
+ }
17
12
  async function main() {
18
- const argv = await (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
13
+ const argv = await yargs(hideBin(process.argv))
19
14
  .command(["start", "$0"], "Start API Server", {}, async (argv) => {
20
15
  // console.log("Ensuring database is initialized...");
21
- await (0, migrate_1.migrate)(false); // Run migrations silently
22
- const token = await auth.getOrCreateMcpToken();
16
+ try {
17
+ await migrate(dbAdapter, false); // Run migrations silently
18
+ }
19
+ catch (e) {
20
+ if (isUnsupportedVectorIndexError(e)) {
21
+ console.warn("[Vector] Embedded DB does not support vector index expressions yet; continuing without vec_logs_idx.");
22
+ }
23
+ else {
24
+ throw e;
25
+ }
26
+ }
27
+ const token = await authService.getOrCreateMcpToken();
23
28
  // Start API Server (this sets up express listen)
24
- await (0, server_1.startApiServer)(db);
29
+ await startApiServer(dbService, authService);
25
30
  // console.clear();
26
- console.log(chalk_1.default.bold.green(`
31
+ console.log(chalk.bold.green(`
27
32
  ā–Œ ā–Œ ā–Œ
28
33
  ā–Œ ā–žā–€ā––ā–žā–€ā–Œā–›ā–€ā––ā–žā–€ā––ā–ā–€ā––ā–žā–€ā–Œ
29
34
  ā–Œ ā–Œ ā–Œā–šā–„ā–Œā–Œ ā–Œā–›ā–€ ā–žā–€ā–Œā–Œ ā–Œ
30
35
  ā–€ā–€ā–˜ā–ā–€ ā–—ā–„ā–˜ā–˜ ā–˜ā–ā–€ā–˜ā–ā–€ā–˜ā–ā–€ā–˜
31
36
  `));
32
- console.log(chalk_1.default.gray("--------------------------------------------------"));
33
- console.log(chalk_1.default.bold(" 🟢 Loghead is running"));
34
- console.log(chalk_1.default.gray("--------------------------------------------------"));
37
+ console.log(chalk.gray("--------------------------------------------------"));
38
+ console.log(chalk.bold(" 🟢 Loghead is running"));
39
+ console.log(chalk.gray("--------------------------------------------------"));
35
40
  console.log("");
36
- console.log(chalk_1.default.bold(" šŸ–„ļø Dashboard : ") + chalk_1.default.cyan("http://localhost:4567"));
37
- console.log(chalk_1.default.bold(" šŸ”Œ MCP Server: ") + chalk_1.default.cyan("http://localhost:4567/sse"));
41
+ console.log(chalk.bold(" šŸ–„ļø Dashboard : ") + chalk.cyan("http://localhost:4567"));
42
+ console.log(chalk.bold(" šŸ”Œ MCP Server: ") + chalk.cyan("http://localhost:4567/sse"));
38
43
  console.log("");
39
- console.log(chalk_1.default.bold(" šŸ”‘ MCP Token : "));
40
- console.log(chalk_1.default.yellow(token));
44
+ console.log(chalk.bold(" šŸ”‘ MCP Token : "));
45
+ console.log(chalk.yellow(token));
41
46
  console.log("");
42
- console.log(chalk_1.default.gray("--------------------------------------------------"));
43
- console.log(chalk_1.default.gray(" Press Ctrl+C to stop"));
47
+ console.log(chalk.gray("--------------------------------------------------"));
48
+ console.log(chalk.gray(" Press Ctrl+C to stop"));
44
49
  })
45
50
  .command("projects <cmd> [name]", "Manage projects", (yargs) => {
46
51
  yargs
47
- .command("list", "List projects", {}, () => {
48
- const projects = db.listProjects();
52
+ .command("list", "List projects", {}, async () => {
53
+ const projects = await dbService.listProjects();
49
54
  console.table(projects);
50
55
  })
51
- .command("add <name>", "Add project", {}, (argv) => {
52
- const p = db.createProject(argv.name);
56
+ .command("add <name>", "Add project", {}, async (argv) => {
57
+ const p = await dbService.createProject(argv.name);
53
58
  console.log(`Project created: ${p.id}`);
54
59
  })
55
- .command("delete <id>", "Delete project", {}, (argv) => {
56
- db.deleteProject(argv.id);
60
+ .command("delete <id>", "Delete project", {}, async (argv) => {
61
+ await dbService.deleteProject(argv.id);
57
62
  console.log(`Project deleted: ${argv.id}`);
58
63
  });
59
64
  })
@@ -61,8 +66,8 @@ async function main() {
61
66
  yargs
62
67
  .command("list", "List streams", {
63
68
  project: { type: "string", demandOption: true },
64
- }, (argv) => {
65
- const streams = db.listStreams(argv.project);
69
+ }, async (argv) => {
70
+ const streams = await dbService.listStreams(argv.project);
66
71
  console.table(streams);
67
72
  })
68
73
  .command("add <type> <name>", "Add stream", {
@@ -73,16 +78,16 @@ async function main() {
73
78
  if (argv.type === "docker" && argv.container) {
74
79
  config.container = argv.container;
75
80
  }
76
- const s = await db.createStream(argv.project, argv.type, argv.name, config);
81
+ const s = await dbService.createStream(argv.project, argv.type, argv.name, config);
77
82
  console.log(`Stream created: ${s.id}`);
78
83
  console.log(`Token: ${s.token}`);
79
84
  })
80
85
  .command("token <streamId>", "Get token for stream", {}, async (argv) => {
81
- const token = await auth.createStreamToken(argv.streamId);
86
+ const token = await authService.createStreamToken(argv.streamId);
82
87
  console.log(`Token: ${token}`);
83
88
  })
84
- .command("delete <id>", "Delete stream", {}, (argv) => {
85
- db.deleteStream(argv.id);
89
+ .command("delete <id>", "Delete stream", {}, async (argv) => {
90
+ await dbService.deleteStream(argv.id);
86
91
  console.log(`Stream deleted: ${argv.id}`);
87
92
  });
88
93
  })
package/dist/db/client.js CHANGED
@@ -1,55 +1,39 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.db = void 0;
40
- const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
41
- const sqliteVec = __importStar(require("sqlite-vec"));
42
- const path_1 = __importDefault(require("path"));
43
- const dotenv_1 = __importDefault(require("dotenv"));
44
- dotenv_1.default.config();
1
+ import { connect } from "@tursodatabase/database";
2
+ import { rm } from "fs/promises";
3
+ import path from "path";
4
+ import dotenv from "dotenv";
5
+ import { LocalLibSqlAdapter, DbService, AuthService, OllamaService } from "@loghead/db";
6
+ dotenv.config();
45
7
  const dbPath = process.env.LOGHEAD_DB_PATH || "loghead.db";
46
- console.log(`[DB] Using database at: ${path_1.default.resolve(dbPath)}`);
47
- const db = new better_sqlite3_1.default(dbPath);
48
- exports.db = db;
49
- // Load sqlite-vec extension
50
- try {
51
- sqliteVec.load(db);
52
- }
53
- catch (e) {
54
- console.error("Failed to load sqlite-vec extension:", e);
8
+ const resolvedDbPath = path.resolve(dbPath);
9
+ console.log(`[DB] Using database at: ${resolvedDbPath}`);
10
+ async function openLocalDb() {
11
+ let db = await connect(resolvedDbPath);
12
+ try {
13
+ const legacyRows = (await db
14
+ .prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='vec_logs'")
15
+ .all());
16
+ const hasLegacyVec0 = legacyRows.some((r) => (r.sql || "").toLowerCase().includes("vec0"));
17
+ if (hasLegacyVec0) {
18
+ console.warn("[DB] Legacy sqlite-vec schema detected. Resetting local DB file.");
19
+ db.close();
20
+ await Promise.allSettled([
21
+ rm(resolvedDbPath, { force: true }),
22
+ rm(`${resolvedDbPath}-wal`, { force: true }),
23
+ rm(`${resolvedDbPath}-shm`, { force: true }),
24
+ rm(`${resolvedDbPath}-journal`, { force: true }),
25
+ ]);
26
+ db = await connect(resolvedDbPath);
27
+ }
28
+ }
29
+ catch {
30
+ // If sqlite_master inspection fails, continue and let migrations handle setup.
31
+ }
32
+ return db;
55
33
  }
34
+ const db = await openLocalDb();
35
+ const dbAdapter = new LocalLibSqlAdapter(db);
36
+ const authService = new AuthService(dbAdapter);
37
+ const ollamaService = new OllamaService();
38
+ const dbService = new DbService(dbAdapter, authService, ollamaService);
39
+ export { dbAdapter, authService, ollamaService, dbService };
@@ -60,19 +60,6 @@ function migrate(verbose = true) {
60
60
  catch (e) {
61
61
  console.warn("Failed to create virtual vector table. Is sqlite-vec loaded?", e);
62
62
  }
63
- // Trigger to clean up vec_logs when logs are deleted
64
- try {
65
- client_1.db.exec(`
66
- CREATE TRIGGER IF NOT EXISTS delete_vec_log
67
- AFTER DELETE ON logs
68
- BEGIN
69
- DELETE FROM vec_logs WHERE rowid = old.rowid;
70
- END;
71
- `);
72
- }
73
- catch (e) {
74
- console.warn("Failed to create trigger delete_vec_log", e);
75
- }
76
63
  if (verbose)
77
64
  console.log("Migrations complete.");
78
65
  }