codesesh 0.14.0 → 0.16.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 CHANGED
@@ -21,6 +21,7 @@ Your browser will open at `http://localhost:4521` with all your sessions ready t
21
21
  - **Unified Timeline** — Browse sessions across all your AI agents in a single, searchable interface
22
22
  - **Flexible Time Ranges** — Switch between rolling presets, all history, or a custom date range without restarting the server
23
23
  - **Session Aliases** — Give important sessions memorable local names that carry through search, bookmarks, and activity views
24
+ - **Persistent Themes** — Choose light, dark, or system appearance and keep your UI preferences across sessions
24
25
  - **Structured Global Search** — Search titles, messages, tool output, and file paths with filters for agent, project, smart tag, tool, file activity, and cost
25
26
  - **Dashboard & Activity Trends** — See totals, daily activity, agent distribution, model usage, token trends, smart tags, bookmarks, and recent sessions
26
27
  - **Project Browse Mode** — Open a dedicated projects view with project-level metrics, sessions, and cross-agent drill-down
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ setCoreDiagnostics
4
+ } from "./chunk-MWSJTNOW.js";
5
+
6
+ // src/logging.ts
7
+ import {
8
+ appendFileSync,
9
+ existsSync,
10
+ mkdirSync,
11
+ readdirSync,
12
+ renameSync,
13
+ statSync,
14
+ unlinkSync
15
+ } from "fs";
16
+ import { homedir } from "os";
17
+ import { join } from "path";
18
+ var LEVEL_WEIGHT = {
19
+ debug: 10,
20
+ info: 20,
21
+ warn: 30,
22
+ error: 40
23
+ };
24
+ function parseLevel(value) {
25
+ if (value === "debug" || value === "info" || value === "warn" || value === "error") {
26
+ return value;
27
+ }
28
+ return "info";
29
+ }
30
+ function parsePositiveInt(value, fallback) {
31
+ const parsed = Number(value);
32
+ return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
33
+ }
34
+ function getDefaultLogDir() {
35
+ const base = process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache");
36
+ return join(base, "codesesh", "logs");
37
+ }
38
+ function toLogValue(value, depth = 0) {
39
+ if (value == null || typeof value === "string" || typeof value === "number") return value;
40
+ if (typeof value === "boolean") return value;
41
+ if (typeof value === "bigint") return value.toString();
42
+ if (value instanceof Error) {
43
+ return {
44
+ name: value.name,
45
+ message: value.message,
46
+ stack: value.stack
47
+ };
48
+ }
49
+ if (depth >= 4) return "[truncated]";
50
+ if (Array.isArray(value)) return value.slice(0, 50).map((item) => toLogValue(item, depth + 1));
51
+ if (typeof value === "object") {
52
+ return Object.fromEntries(
53
+ Object.entries(value).map(([key, item]) => [
54
+ key,
55
+ toLogValue(item, depth + 1)
56
+ ])
57
+ );
58
+ }
59
+ return String(value);
60
+ }
61
+ function timestampForFile(date = /* @__PURE__ */ new Date()) {
62
+ return date.toISOString().replace(/[:.]/g, "-");
63
+ }
64
+ var AppLogger = class {
65
+ logDir;
66
+ level;
67
+ maxBytes;
68
+ maxFiles;
69
+ currentPath;
70
+ rotationIndex = 0;
71
+ constructor(options = {}) {
72
+ this.logDir = options.logDir ?? process.env.CODESESH_LOG_DIR ?? getDefaultLogDir();
73
+ this.level = options.level ?? parseLevel(process.env.CODESESH_LOG_LEVEL);
74
+ this.maxBytes = options.maxBytes ?? parsePositiveInt(process.env.CODESESH_LOG_MAX_BYTES, 5e6);
75
+ this.maxFiles = options.maxFiles ?? parsePositiveInt(process.env.CODESESH_LOG_MAX_FILES, 5);
76
+ this.currentPath = join(this.logDir, "codesesh.log");
77
+ }
78
+ getLogPath() {
79
+ return this.currentPath;
80
+ }
81
+ debug(event, data = {}) {
82
+ this.write("debug", event, data);
83
+ }
84
+ info(event, data = {}) {
85
+ this.write("info", event, data);
86
+ }
87
+ warn(event, data = {}) {
88
+ this.write("warn", event, data);
89
+ }
90
+ error(event, data = {}) {
91
+ this.write("error", event, data);
92
+ }
93
+ write(level, event, data) {
94
+ if (LEVEL_WEIGHT[level] < LEVEL_WEIGHT[this.level]) return;
95
+ try {
96
+ mkdirSync(this.logDir, { recursive: true });
97
+ const line = `${JSON.stringify({
98
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
99
+ level,
100
+ event,
101
+ pid: process.pid,
102
+ ...toLogValue(data)
103
+ })}
104
+ `;
105
+ this.rotateIfNeeded(Buffer.byteLength(line));
106
+ appendFileSync(this.currentPath, line, "utf8");
107
+ } catch {
108
+ }
109
+ }
110
+ rotateIfNeeded(nextBytes) {
111
+ if (!existsSync(this.currentPath)) {
112
+ this.removeExpiredLogs();
113
+ return;
114
+ }
115
+ const currentSize = statSync(this.currentPath).size;
116
+ if (currentSize + nextBytes <= this.maxBytes) return;
117
+ this.rotationIndex += 1;
118
+ const rotatedPath = join(
119
+ this.logDir,
120
+ `codesesh-${timestampForFile()}-${process.pid}-${this.rotationIndex}.log`
121
+ );
122
+ renameSync(this.currentPath, rotatedPath);
123
+ this.removeExpiredLogs();
124
+ }
125
+ removeExpiredLogs() {
126
+ const rotated = readdirSync(this.logDir).filter((name) => /^codesesh-.+\.log$/.test(name)).map((name) => {
127
+ const path = join(this.logDir, name);
128
+ return { path, mtimeMs: statSync(path).mtimeMs };
129
+ }).toSorted((a, b) => b.mtimeMs - a.mtimeMs);
130
+ for (const item of rotated.slice(Math.max(0, this.maxFiles - 1))) {
131
+ unlinkSync(item.path);
132
+ }
133
+ }
134
+ };
135
+ var appLogger = new AppLogger();
136
+ function logSearchIndexSync(context, result, data = {}) {
137
+ if (!result || result.mode !== "bulk" || result.rebuildDurationMs == null) {
138
+ return;
139
+ }
140
+ appLogger.info("search_index.sync", {
141
+ context,
142
+ agent: result.agentName,
143
+ mode: result.mode,
144
+ sessions: result.sessions,
145
+ changed: result.changed,
146
+ deleted: result.deleted,
147
+ indexed: result.indexed,
148
+ skipped: result.skipped,
149
+ duration_ms: Math.round(result.durationMs),
150
+ rebuild_duration_ms: Math.round(result.rebuildDurationMs),
151
+ ...data
152
+ });
153
+ }
154
+
155
+ // src/diagnostics-bridge.ts
156
+ setCoreDiagnostics({
157
+ warn(event, detail) {
158
+ appLogger.warn(event, detail);
159
+ }
160
+ });
161
+
162
+ export {
163
+ appLogger,
164
+ logSearchIndexSync
165
+ };
166
+ //# sourceMappingURL=chunk-EWC6IODK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/logging.ts","../src/diagnostics-bridge.ts"],"sourcesContent":["import {\n appendFileSync,\n existsSync,\n mkdirSync,\n readdirSync,\n renameSync,\n statSync,\n unlinkSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { SearchIndexSyncResult } from \"@codesesh/core\";\n\ntype LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nconst LEVEL_WEIGHT: Record<LogLevel, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n};\n\nexport interface LoggerOptions {\n logDir?: string;\n level?: LogLevel;\n maxBytes?: number;\n maxFiles?: number;\n}\n\nfunction parseLevel(value: string | undefined): LogLevel {\n if (value === \"debug\" || value === \"info\" || value === \"warn\" || value === \"error\") {\n return value;\n }\n return \"info\";\n}\n\nfunction parsePositiveInt(value: string | undefined, fallback: number): number {\n const parsed = Number(value);\n return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;\n}\n\nfunction getDefaultLogDir(): string {\n const base = process.env.XDG_CACHE_HOME ?? join(homedir(), \".cache\");\n return join(base, \"codesesh\", \"logs\");\n}\n\nfunction toLogValue(value: unknown, depth = 0): unknown {\n if (value == null || typeof value === \"string\" || typeof value === \"number\") return value;\n if (typeof value === \"boolean\") return value;\n if (typeof value === \"bigint\") return value.toString();\n if (value instanceof Error) {\n return {\n name: value.name,\n message: value.message,\n stack: value.stack,\n };\n }\n if (depth >= 4) return \"[truncated]\";\n if (Array.isArray(value)) return value.slice(0, 50).map((item) => toLogValue(item, depth + 1));\n if (typeof value === \"object\") {\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>).map(([key, item]) => [\n key,\n toLogValue(item, depth + 1),\n ]),\n );\n }\n return String(value);\n}\n\nfunction timestampForFile(date = new Date()): string {\n return date.toISOString().replace(/[:.]/g, \"-\");\n}\n\nexport class AppLogger {\n private readonly logDir: string;\n private readonly level: LogLevel;\n private readonly maxBytes: number;\n private readonly maxFiles: number;\n private readonly currentPath: string;\n private rotationIndex = 0;\n\n constructor(options: LoggerOptions = {}) {\n this.logDir = options.logDir ?? process.env.CODESESH_LOG_DIR ?? getDefaultLogDir();\n this.level = options.level ?? parseLevel(process.env.CODESESH_LOG_LEVEL);\n this.maxBytes =\n options.maxBytes ?? parsePositiveInt(process.env.CODESESH_LOG_MAX_BYTES, 5_000_000);\n this.maxFiles = options.maxFiles ?? parsePositiveInt(process.env.CODESESH_LOG_MAX_FILES, 5);\n this.currentPath = join(this.logDir, \"codesesh.log\");\n }\n\n getLogPath(): string {\n return this.currentPath;\n }\n\n debug(event: string, data: Record<string, unknown> = {}): void {\n this.write(\"debug\", event, data);\n }\n\n info(event: string, data: Record<string, unknown> = {}): void {\n this.write(\"info\", event, data);\n }\n\n warn(event: string, data: Record<string, unknown> = {}): void {\n this.write(\"warn\", event, data);\n }\n\n error(event: string, data: Record<string, unknown> = {}): void {\n this.write(\"error\", event, data);\n }\n\n private write(level: LogLevel, event: string, data: Record<string, unknown>): void {\n if (LEVEL_WEIGHT[level] < LEVEL_WEIGHT[this.level]) return;\n\n try {\n mkdirSync(this.logDir, { recursive: true });\n const line = `${JSON.stringify({\n ts: new Date().toISOString(),\n level,\n event,\n pid: process.pid,\n ...(toLogValue(data) as Record<string, unknown>),\n })}\\n`;\n this.rotateIfNeeded(Buffer.byteLength(line));\n appendFileSync(this.currentPath, line, \"utf8\");\n } catch {}\n }\n\n private rotateIfNeeded(nextBytes: number): void {\n if (!existsSync(this.currentPath)) {\n this.removeExpiredLogs();\n return;\n }\n\n const currentSize = statSync(this.currentPath).size;\n if (currentSize + nextBytes <= this.maxBytes) return;\n\n this.rotationIndex += 1;\n const rotatedPath = join(\n this.logDir,\n `codesesh-${timestampForFile()}-${process.pid}-${this.rotationIndex}.log`,\n );\n renameSync(this.currentPath, rotatedPath);\n this.removeExpiredLogs();\n }\n\n private removeExpiredLogs(): void {\n const rotated = readdirSync(this.logDir)\n .filter((name) => /^codesesh-.+\\.log$/.test(name))\n .map((name) => {\n const path = join(this.logDir, name);\n return { path, mtimeMs: statSync(path).mtimeMs };\n })\n .toSorted((a, b) => b.mtimeMs - a.mtimeMs);\n\n for (const item of rotated.slice(Math.max(0, this.maxFiles - 1))) {\n unlinkSync(item.path);\n }\n }\n}\n\nexport const appLogger = new AppLogger();\n\nexport function logSearchIndexSync(\n context: string,\n result: SearchIndexSyncResult | null,\n data: Record<string, unknown> = {},\n): void {\n if (!result || result.mode !== \"bulk\" || result.rebuildDurationMs == null) {\n return;\n }\n\n appLogger.info(\"search_index.sync\", {\n context,\n agent: result.agentName,\n mode: result.mode,\n sessions: result.sessions,\n changed: result.changed,\n deleted: result.deleted,\n indexed: result.indexed,\n skipped: result.skipped,\n duration_ms: Math.round(result.durationMs),\n rebuild_duration_ms: Math.round(result.rebuildDurationMs),\n ...data,\n });\n}\n","import { setCoreDiagnostics } from \"@codesesh/core\";\nimport { appLogger } from \"./logging.js\";\n\n/**\n * Bridges core's diagnostics sink to appLogger. Import this once, for its\n * side effect, from every entry point that gets its own module graph — the\n * main CLI thread and each worker_threads script (scan-refresh-worker,\n * search-index-worker, smart-tag-worker) — since core's module-level\n * diagnostics singleton is per-thread, not shared across workers.\n */\nsetCoreDiagnostics({\n warn(event, detail) {\n appLogger.warn(event, detail);\n },\n});\n"],"mappings":"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","names":[]}