@hasna/logs 0.3.3 → 0.3.5

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/cli/index.js CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  ingestLog,
11
11
  listPages,
12
12
  listProjects,
13
+ resolveProjectId,
13
14
  summarizeLogs
14
15
  } from "../index-5tvnhvgr.js";
15
16
  import {
@@ -2136,12 +2137,48 @@ var {
2136
2137
  } = import__.default;
2137
2138
 
2138
2139
  // src/cli/index.ts
2140
+ var C = {
2141
+ reset: "\x1B[0m",
2142
+ bold: "\x1B[1m",
2143
+ dim: "\x1B[2m",
2144
+ red: "\x1B[31m",
2145
+ yellow: "\x1B[33m",
2146
+ cyan: "\x1B[36m",
2147
+ gray: "\x1B[90m",
2148
+ bgRed: "\x1B[41m\x1B[97m",
2149
+ magenta: "\x1B[35m"
2150
+ };
2151
+ var LEVEL_COLOR = {
2152
+ fatal: C.bgRed,
2153
+ error: C.red,
2154
+ warn: C.yellow,
2155
+ info: "",
2156
+ debug: C.gray
2157
+ };
2158
+ function colorRow(ts, level, svc, msg) {
2159
+ const lc = LEVEL_COLOR[level.toLowerCase()] ?? "";
2160
+ const isTTY = process.stdout.isTTY;
2161
+ if (!isTTY)
2162
+ return `${ts} ${pad(level.toUpperCase(), 5)} ${pad(svc, 12)} ${msg}`;
2163
+ return `${C.dim}${ts}${C.reset} ${lc}${C.bold}${pad(level.toUpperCase(), 5)}${C.reset} ${C.cyan}${pad(svc, 12)}${C.reset} ${msg}`;
2164
+ }
2165
+ function colorLevel(level) {
2166
+ if (!process.stdout.isTTY)
2167
+ return pad(level.toUpperCase(), 5);
2168
+ const lc = LEVEL_COLOR[level.toLowerCase()] ?? "";
2169
+ return `${lc}${C.bold}${pad(level.toUpperCase(), 5)}${C.reset}`;
2170
+ }
2171
+ function resolveProject(nameOrId) {
2172
+ if (!nameOrId)
2173
+ return;
2174
+ return resolveProjectId(getDb(), nameOrId) ?? nameOrId;
2175
+ }
2139
2176
  var program2 = new Command().name("logs").description("@hasna/logs \u2014 log aggregation and monitoring").version("0.0.1");
2140
- program2.command("list").description("Search and list logs").option("--project <id>", "Filter by project ID").option("--page <id>", "Filter by page ID").option("--level <levels>", "Comma-separated levels (error,warn,info,debug,fatal)").option("--service <name>", "Filter by service").option("--since <iso>", "Since timestamp or relative (1h, 24h, 7d)").option("--text <query>", "Full-text search").option("--limit <n>", "Max results", "100").option("--format <fmt>", "Output format: table|json|compact", "table").action((opts) => {
2177
+ program2.command("list").description("Search and list logs").option("--project <name|id>", "Filter by project name or ID").option("--page <id>", "Filter by page ID").option("--level <levels>", "Comma-separated levels (error,warn,info,debug,fatal)").option("--service <name>", "Filter by service").option("--since <iso>", "Since timestamp or relative (1h, 24h, 7d)").option("--text <query>", "Full-text search").option("--limit <n>", "Max results", "100").option("--format <fmt>", "Output format: table|json|compact", "table").action((opts) => {
2141
2178
  const db = getDb();
2142
2179
  const since = parseRelativeTime(opts.since);
2143
2180
  const rows = searchLogs(db, {
2144
- project_id: opts.project,
2181
+ project_id: resolveProject(opts.project),
2145
2182
  page_id: opts.page,
2146
2183
  level: opts.level ? opts.level.split(",") : undefined,
2147
2184
  service: opts.service,
@@ -2160,27 +2197,27 @@ program2.command("list").description("Search and list logs").option("--project <
2160
2197
  }
2161
2198
  for (const r of rows) {
2162
2199
  const meta = r.metadata ? ` ${r.metadata}` : "";
2163
- console.log(`${r.timestamp} ${pad(r.level.toUpperCase(), 5)} ${pad(r.service ?? "-", 12)} ${r.message}${meta}`);
2200
+ console.log(`${colorRow(r.timestamp, r.level, r.service ?? "-", r.message)}${meta}`);
2164
2201
  }
2165
2202
  console.log(`
2166
2203
  ${rows.length} log(s)`);
2167
2204
  });
2168
- program2.command("tail").description("Show most recent logs").option("--project <id>").option("--n <count>", "Number of logs", "50").action((opts) => {
2169
- const rows = tailLogs(getDb(), opts.project, Number(opts.n));
2205
+ program2.command("tail").description("Show most recent logs").option("--project <name|id>", "Project name or ID").option("--n <count>", "Number of logs", "50").action((opts) => {
2206
+ const rows = tailLogs(getDb(), resolveProject(opts.project), Number(opts.n));
2170
2207
  for (const r of rows)
2171
- console.log(`${r.timestamp} ${pad(r.level.toUpperCase(), 5)} ${r.message}`);
2208
+ console.log(colorRow(r.timestamp, r.level, r.service ?? "-", r.message));
2172
2209
  });
2173
- program2.command("summary").description("Error/warn summary by service").option("--project <id>").option("--since <time>", "Relative time (1h, 24h, 7d)", "24h").action((opts) => {
2174
- const summary = summarizeLogs(getDb(), opts.project, parseRelativeTime(opts.since));
2210
+ program2.command("summary").description("Error/warn summary by service").option("--project <name|id>", "Project name or ID").option("--since <time>", "Relative time (1h, 24h, 7d)", "24h").action((opts) => {
2211
+ const summary = summarizeLogs(getDb(), resolveProject(opts.project), parseRelativeTime(opts.since));
2175
2212
  if (!summary.length) {
2176
2213
  console.log("No errors/warnings in this window.");
2177
2214
  return;
2178
2215
  }
2179
2216
  for (const s of summary)
2180
- console.log(`${pad(s.level.toUpperCase(), 5)} ${pad(s.service ?? "-", 15)} count=${s.count} latest=${s.latest}`);
2217
+ console.log(`${colorLevel(s.level)} ${C.cyan}${pad(s.service ?? "-", 15)}${C.reset} count=${s.count} latest=${s.latest}`);
2181
2218
  });
2182
- program2.command("push <message>").description("Push a log entry").option("--level <level>", "Log level", "info").option("--service <name>").option("--project <id>").option("--trace <id>", "Trace ID").action((message, opts) => {
2183
- const row = ingestLog(getDb(), { level: opts.level, message, service: opts.service, project_id: opts.project, trace_id: opts.trace });
2219
+ program2.command("push <message>").description("Push a log entry").option("--level <level>", "Log level", "info").option("--service <name>").option("--project <name|id>", "Project name or ID").option("--trace <id>", "Trace ID").action((message, opts) => {
2220
+ const row = ingestLog(getDb(), { level: opts.level, message, service: opts.service, project_id: resolveProject(opts.project), trace_id: opts.trace });
2184
2221
  console.log(`Logged: ${row.id}`);
2185
2222
  });
2186
2223
  var projectCmd = program2.command("project").description("Manage projects");
@@ -2198,38 +2235,38 @@ projectCmd.command("list").action(() => {
2198
2235
  console.log(`${p.id} ${p.name} ${p.base_url ?? ""} ${p.github_repo ?? ""}`);
2199
2236
  });
2200
2237
  var pageCmd = program2.command("page").description("Manage pages");
2201
- pageCmd.command("add").option("--project <id>").option("--url <url>").option("--name <name>").action((opts) => {
2238
+ pageCmd.command("add").option("--project <name|id>", "Project name or ID").option("--url <url>").option("--name <name>").action((opts) => {
2202
2239
  if (!opts.project || !opts.url) {
2203
2240
  console.error("--project and --url required");
2204
2241
  process.exit(1);
2205
2242
  }
2206
- const p = createPage(getDb(), { project_id: opts.project, url: opts.url, name: opts.name });
2243
+ const p = createPage(getDb(), { project_id: resolveProject(opts.project), url: opts.url, name: opts.name });
2207
2244
  console.log(`Page registered: ${p.id} \u2014 ${p.url}`);
2208
2245
  });
2209
- pageCmd.command("list").option("--project <id>").action((opts) => {
2246
+ pageCmd.command("list").option("--project <name|id>", "Project name or ID").action((opts) => {
2210
2247
  if (!opts.project) {
2211
2248
  console.error("--project required");
2212
2249
  process.exit(1);
2213
2250
  }
2214
- const pages = listPages(getDb(), opts.project);
2251
+ const pages = listPages(getDb(), resolveProject(opts.project));
2215
2252
  for (const p of pages)
2216
2253
  console.log(`${p.id} ${p.url} last=${p.last_scanned_at ?? "never"}`);
2217
2254
  });
2218
2255
  var jobCmd = program2.command("job").description("Manage scan jobs");
2219
- jobCmd.command("create").option("--project <id>").option("--schedule <cron>", "Cron expression", "*/30 * * * *").action((opts) => {
2256
+ jobCmd.command("create").option("--project <name|id>", "Project name or ID").option("--schedule <cron>", "Cron expression", "*/30 * * * *").action((opts) => {
2220
2257
  if (!opts.project) {
2221
2258
  console.error("--project required");
2222
2259
  process.exit(1);
2223
2260
  }
2224
- const j = createJob(getDb(), { project_id: opts.project, schedule: opts.schedule });
2261
+ const j = createJob(getDb(), { project_id: resolveProject(opts.project), schedule: opts.schedule });
2225
2262
  console.log(`Job created: ${j.id} \u2014 ${j.schedule}`);
2226
2263
  });
2227
- jobCmd.command("list").option("--project <id>").action((opts) => {
2228
- const jobs = listJobs(getDb(), opts.project);
2264
+ jobCmd.command("list").option("--project <name|id>", "Project name or ID").action((opts) => {
2265
+ const jobs = listJobs(getDb(), resolveProject(opts.project));
2229
2266
  for (const j of jobs)
2230
2267
  console.log(`${j.id} ${j.schedule} enabled=${j.enabled} last=${j.last_run_at ?? "never"}`);
2231
2268
  });
2232
- program2.command("scan").description("Run an immediate scan for a job").option("--job <id>").option("--project <id>").action(async (opts) => {
2269
+ program2.command("scan").description("Run an immediate scan for a job").option("--job <id>").option("--project <name|id>", "Project name or ID").action(async (opts) => {
2233
2270
  if (!opts.job) {
2234
2271
  console.error("--job required");
2235
2272
  process.exit(1);
@@ -2302,12 +2339,12 @@ Errors: ${errorCount} Warnings: ${warnCount}`);
2302
2339
  process.exit(0);
2303
2340
  });
2304
2341
  });
2305
- program2.command("export").description("Export logs to JSON or CSV").option("--project <id>").option("--since <time>", "Relative time or ISO").option("--level <level>").option("--service <name>").option("--format <fmt>", "json or csv", "json").option("--output <file>", "Output file (default: stdout)").option("--limit <n>", "Max rows", "100000").action(async (opts) => {
2342
+ program2.command("export").description("Export logs to JSON or CSV").option("--project <name|id>", "Project name or ID").option("--since <time>", "Relative time or ISO").option("--level <level>").option("--service <name>").option("--format <fmt>", "json or csv", "json").option("--output <file>", "Output file (default: stdout)").option("--limit <n>", "Max rows", "100000").action(async (opts) => {
2306
2343
  const { exportToCsv, exportToJson } = await import("../export-c3eqjste.js");
2307
2344
  const { createWriteStream } = await import("fs");
2308
2345
  const db = getDb();
2309
2346
  const options = {
2310
- project_id: opts.project,
2347
+ project_id: resolveProject(opts.project),
2311
2348
  since: parseRelativeTime(opts.since),
2312
2349
  level: opts.level,
2313
2350
  service: opts.service,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/logs",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "Log aggregation + browser script + headless page scanner + performance monitoring for AI agents",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/cli/index.ts CHANGED
@@ -5,10 +5,37 @@ import { ingestLog } from "../lib/ingest.ts"
5
5
  import { searchLogs, tailLogs } from "../lib/query.ts"
6
6
  import { summarizeLogs } from "../lib/summarize.ts"
7
7
  import { createJob, listJobs } from "../lib/jobs.ts"
8
- import { createPage, createProject, listPages, listProjects } from "../lib/projects.ts"
8
+ import { createPage, createProject, listPages, listProjects, resolveProjectId } from "../lib/projects.ts"
9
9
  import { runJob } from "../lib/scheduler.ts"
10
10
  import type { LogLevel } from "../types/index.ts"
11
11
 
12
+ // ── Color helpers ──────────────────────────────────────────
13
+ const C = {
14
+ reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
15
+ red: "\x1b[31m", yellow: "\x1b[33m", cyan: "\x1b[36m", gray: "\x1b[90m",
16
+ bgRed: "\x1b[41m\x1b[97m", magenta: "\x1b[35m",
17
+ };
18
+ const LEVEL_COLOR: Record<string, string> = {
19
+ fatal: C.bgRed, error: C.red, warn: C.yellow, info: "", debug: C.gray,
20
+ };
21
+ function colorRow(ts: string, level: string, svc: string, msg: string): string {
22
+ const lc = LEVEL_COLOR[level.toLowerCase()] ?? "";
23
+ const isTTY = process.stdout.isTTY;
24
+ if (!isTTY) return `${ts} ${pad(level.toUpperCase(), 5)} ${pad(svc, 12)} ${msg}`;
25
+ return `${C.dim}${ts}${C.reset} ${lc}${C.bold}${pad(level.toUpperCase(), 5)}${C.reset} ${C.cyan}${pad(svc, 12)}${C.reset} ${msg}`;
26
+ }
27
+ function colorLevel(level: string): string {
28
+ if (!process.stdout.isTTY) return pad(level.toUpperCase(), 5);
29
+ const lc = LEVEL_COLOR[level.toLowerCase()] ?? "";
30
+ return `${lc}${C.bold}${pad(level.toUpperCase(), 5)}${C.reset}`;
31
+ }
32
+
33
+ /** Resolve a project name or ID from CLI --project flag */
34
+ function resolveProject(nameOrId: string | undefined): string | undefined {
35
+ if (!nameOrId) return undefined;
36
+ return resolveProjectId(getDb(), nameOrId) ?? nameOrId;
37
+ }
38
+
12
39
  const program = new Command()
13
40
  .name("logs")
14
41
  .description("@hasna/logs — log aggregation and monitoring")
@@ -17,7 +44,7 @@ const program = new Command()
17
44
  // ── logs list ──────────────────────────────────────────────
18
45
  program.command("list")
19
46
  .description("Search and list logs")
20
- .option("--project <id>", "Filter by project ID")
47
+ .option("--project <name|id>", "Filter by project name or ID")
21
48
  .option("--page <id>", "Filter by page ID")
22
49
  .option("--level <levels>", "Comma-separated levels (error,warn,info,debug,fatal)")
23
50
  .option("--service <name>", "Filter by service")
@@ -29,7 +56,7 @@ program.command("list")
29
56
  const db = getDb()
30
57
  const since = parseRelativeTime(opts.since)
31
58
  const rows = searchLogs(db, {
32
- project_id: opts.project,
59
+ project_id: resolveProject(opts.project),
33
60
  page_id: opts.page,
34
61
  level: opts.level ? (opts.level.split(",") as LogLevel[]) : undefined,
35
62
  service: opts.service,
@@ -44,7 +71,7 @@ program.command("list")
44
71
  }
45
72
  for (const r of rows) {
46
73
  const meta = r.metadata ? ` ${r.metadata}` : ""
47
- console.log(`${r.timestamp} ${pad(r.level.toUpperCase(), 5)} ${pad(r.service ?? "-", 12)} ${r.message}${meta}`)
74
+ console.log(`${colorRow(r.timestamp, r.level, r.service ?? "-", r.message)}${meta}`)
48
75
  }
49
76
  console.log(`\n${rows.length} log(s)`)
50
77
  })
@@ -52,22 +79,22 @@ program.command("list")
52
79
  // ── logs tail ──────────────────────────────────────────────
53
80
  program.command("tail")
54
81
  .description("Show most recent logs")
55
- .option("--project <id>")
82
+ .option("--project <name|id>", "Project name or ID")
56
83
  .option("--n <count>", "Number of logs", "50")
57
84
  .action((opts) => {
58
- const rows = tailLogs(getDb(), opts.project, Number(opts.n))
59
- for (const r of rows) console.log(`${r.timestamp} ${pad(r.level.toUpperCase(), 5)} ${r.message}`)
85
+ const rows = tailLogs(getDb(), resolveProject(opts.project), Number(opts.n))
86
+ for (const r of rows) console.log(colorRow(r.timestamp, r.level, r.service ?? "-", r.message))
60
87
  })
61
88
 
62
89
  // ── logs summary ──────────────────────────────────────────
63
90
  program.command("summary")
64
91
  .description("Error/warn summary by service")
65
- .option("--project <id>")
92
+ .option("--project <name|id>", "Project name or ID")
66
93
  .option("--since <time>", "Relative time (1h, 24h, 7d)", "24h")
67
94
  .action((opts) => {
68
- const summary = summarizeLogs(getDb(), opts.project, parseRelativeTime(opts.since))
95
+ const summary = summarizeLogs(getDb(), resolveProject(opts.project), parseRelativeTime(opts.since))
69
96
  if (!summary.length) { console.log("No errors/warnings in this window."); return }
70
- for (const s of summary) console.log(`${pad(s.level.toUpperCase(), 5)} ${pad(s.service ?? "-", 15)} count=${s.count} latest=${s.latest}`)
97
+ for (const s of summary) console.log(`${colorLevel(s.level)} ${C.cyan}${pad(s.service ?? "-", 15)}${C.reset} count=${s.count} latest=${s.latest}`)
71
98
  })
72
99
 
73
100
  // ── logs push ─────────────────────────────────────────────
@@ -75,10 +102,10 @@ program.command("push <message>")
75
102
  .description("Push a log entry")
76
103
  .option("--level <level>", "Log level", "info")
77
104
  .option("--service <name>")
78
- .option("--project <id>")
105
+ .option("--project <name|id>", "Project name or ID")
79
106
  .option("--trace <id>", "Trace ID")
80
107
  .action((message, opts) => {
81
- const row = ingestLog(getDb(), { level: opts.level as LogLevel, message, service: opts.service, project_id: opts.project, trace_id: opts.trace })
108
+ const row = ingestLog(getDb(), { level: opts.level as LogLevel, message, service: opts.service, project_id: resolveProject(opts.project), trace_id: opts.trace })
82
109
  console.log(`Logged: ${row.id}`)
83
110
  })
84
111
 
@@ -104,18 +131,18 @@ projectCmd.command("list").action(() => {
104
131
  const pageCmd = program.command("page").description("Manage pages")
105
132
 
106
133
  pageCmd.command("add")
107
- .option("--project <id>")
134
+ .option("--project <name|id>", "Project name or ID")
108
135
  .option("--url <url>")
109
136
  .option("--name <name>")
110
137
  .action((opts) => {
111
138
  if (!opts.project || !opts.url) { console.error("--project and --url required"); process.exit(1) }
112
- const p = createPage(getDb(), { project_id: opts.project, url: opts.url, name: opts.name })
139
+ const p = createPage(getDb(), { project_id: resolveProject(opts.project), url: opts.url, name: opts.name })
113
140
  console.log(`Page registered: ${p.id} — ${p.url}`)
114
141
  })
115
142
 
116
- pageCmd.command("list").option("--project <id>").action((opts) => {
143
+ pageCmd.command("list").option("--project <name|id>", "Project name or ID").action((opts) => {
117
144
  if (!opts.project) { console.error("--project required"); process.exit(1) }
118
- const pages = listPages(getDb(), opts.project)
145
+ const pages = listPages(getDb(), resolveProject(opts.project))
119
146
  for (const p of pages) console.log(`${p.id} ${p.url} last=${p.last_scanned_at ?? "never"}`)
120
147
  })
121
148
 
@@ -123,16 +150,16 @@ pageCmd.command("list").option("--project <id>").action((opts) => {
123
150
  const jobCmd = program.command("job").description("Manage scan jobs")
124
151
 
125
152
  jobCmd.command("create")
126
- .option("--project <id>")
153
+ .option("--project <name|id>", "Project name or ID")
127
154
  .option("--schedule <cron>", "Cron expression", "*/30 * * * *")
128
155
  .action((opts) => {
129
156
  if (!opts.project) { console.error("--project required"); process.exit(1) }
130
- const j = createJob(getDb(), { project_id: opts.project, schedule: opts.schedule })
157
+ const j = createJob(getDb(), { project_id: resolveProject(opts.project), schedule: opts.schedule })
131
158
  console.log(`Job created: ${j.id} — ${j.schedule}`)
132
159
  })
133
160
 
134
- jobCmd.command("list").option("--project <id>").action((opts) => {
135
- const jobs = listJobs(getDb(), opts.project)
161
+ jobCmd.command("list").option("--project <name|id>", "Project name or ID").action((opts) => {
162
+ const jobs = listJobs(getDb(), resolveProject(opts.project))
136
163
  for (const j of jobs) console.log(`${j.id} ${j.schedule} enabled=${j.enabled} last=${j.last_run_at ?? "never"}`)
137
164
  })
138
165
 
@@ -140,7 +167,7 @@ jobCmd.command("list").option("--project <id>").action((opts) => {
140
167
  program.command("scan")
141
168
  .description("Run an immediate scan for a job")
142
169
  .option("--job <id>")
143
- .option("--project <id>")
170
+ .option("--project <name|id>", "Project name or ID")
144
171
  .action(async (opts) => {
145
172
  if (!opts.job) { console.error("--job required"); process.exit(1) }
146
173
  const db = getDb()
@@ -216,7 +243,7 @@ program.command("watch")
216
243
  // ── logs export ───────────────────────────────────────────
217
244
  program.command("export")
218
245
  .description("Export logs to JSON or CSV")
219
- .option("--project <id>")
246
+ .option("--project <name|id>", "Project name or ID")
220
247
  .option("--since <time>", "Relative time or ISO")
221
248
  .option("--level <level>")
222
249
  .option("--service <name>")
@@ -228,7 +255,7 @@ program.command("export")
228
255
  const { createWriteStream } = await import("node:fs")
229
256
  const db = getDb()
230
257
  const options = {
231
- project_id: opts.project,
258
+ project_id: resolveProject(opts.project),
232
259
  since: parseRelativeTime(opts.since),
233
260
  level: opts.level,
234
261
  service: opts.service,