@codemem/server 0.0.0 → 0.20.0-alpha.2
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/LICENSE +21 -0
- package/dist/helpers.d.ts +19 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.test.d.ts +2 -0
- package/dist/helpers.test.d.ts.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1383 -0
- package/dist/index.js.map +1 -0
- package/dist/index.test.d.ts +8 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/middleware.d.ts +33 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/routes/config.d.ts +13 -0
- package/dist/routes/config.d.ts.map +1 -0
- package/dist/routes/memory.d.ts +9 -0
- package/dist/routes/memory.d.ts.map +1 -0
- package/dist/routes/observer-status.d.ts +16 -0
- package/dist/routes/observer-status.d.ts.map +1 -0
- package/dist/routes/raw-events.d.ts +10 -0
- package/dist/routes/raw-events.d.ts.map +1 -0
- package/dist/routes/stats.d.ts +13 -0
- package/dist/routes/stats.d.ts.map +1 -0
- package/dist/routes/sync.d.ts +9 -0
- package/dist/routes/sync.d.ts.map +1 -0
- package/dist/viewer-html.d.ts +8 -0
- package/dist/viewer-html.d.ts.map +1 -0
- package/package.json +45 -1
- package/static/app.js +3726 -0
- package/static/favicon.svg +14 -0
- package/static/index.html +1860 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/middleware.ts","../src/routes/config.ts","../src/helpers.ts","../src/routes/memory.ts","../src/routes/observer-status.ts","../src/routes/raw-events.ts","../src/routes/stats.ts","../src/routes/sync.ts","../src/index.ts"],"sourcesContent":["/**\n * CORS and cross-origin protection middleware.\n *\n * Ports Python's reject_cross_origin() logic from codemem/viewer_http.py.\n * GETs are allowed from any origin (viewer is local-only).\n * Mutations (POST/DELETE/PATCH/PUT) require an Origin header matching a\n * loopback address, or are rejected with 403.\n */\n\nimport type { Context, Next } from \"hono\";\nimport { createMiddleware } from \"hono/factory\";\n\nconst LOOPBACK_HOSTS = new Set([\"127.0.0.1\", \"localhost\", \"::1\"]);\n\n/**\n * Check whether an Origin header value is a valid loopback URL.\n * Mirrors Python's _is_allowed_loopback_origin_url().\n */\nfunction isLoopbackOrigin(origin: string): boolean {\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(origin);\n\t} catch {\n\t\treturn false;\n\t}\n\tif (url.protocol !== \"http:\" && url.protocol !== \"https:\") return false;\n\tif (url.username || url.password) return false;\n\treturn LOOPBACK_HOSTS.has(url.hostname);\n}\n\n/** HTTP methods that mutate state and require origin validation. */\nconst UNSAFE_METHODS = new Set([\"POST\", \"DELETE\", \"PATCH\", \"PUT\"]);\n\n/**\n * Check whether a missing-Origin request looks like a cross-site browser\n * request. Matches Python's `_is_unsafe_missing_origin()`:\n *\n * - Sec-Fetch-Site present and NOT same-origin/same-site/none → unsafe\n * - Referer present and NOT loopback → unsafe\n * - Otherwise → safe (CLI / programmatic caller, no browser context)\n */\nfunction isUnsafeMissingOrigin(c: Context): boolean {\n\tconst secFetchSite = (c.req.header(\"Sec-Fetch-Site\") ?? \"\").trim().toLowerCase();\n\tif (secFetchSite && ![\"same-origin\", \"same-site\", \"none\"].includes(secFetchSite)) {\n\t\treturn true;\n\t}\n\tconst referer = c.req.header(\"Referer\");\n\tif (!referer) return false;\n\treturn !isLoopbackOrigin(referer);\n}\n\n/**\n * Cross-origin protection middleware.\n *\n * Ports Python's `reject_cross_origin(missing_origin_policy=\"reject_if_unsafe\")`:\n *\n * - GET/HEAD/OPTIONS: allowed from any origin (viewer is local-only).\n * - POST/DELETE/PATCH/PUT:\n * - Origin present + loopback → allowed (browser on localhost)\n * - Origin present + non-loopback → rejected 403\n * - No Origin + no suspicious browser signals → allowed (CLI callers)\n * - No Origin + suspicious Sec-Fetch-Site/Referer → rejected 403\n *\n * For same-origin requests (no Origin header) on safe methods, no\n * Access-Control-Allow-Origin is set — the browser doesn't need it.\n * For valid loopback origins, ACAO is echoed back.\n */\nexport function originGuard() {\n\treturn createMiddleware(async (c: Context, next: Next) => {\n\t\tconst origin = c.req.header(\"Origin\");\n\t\tconst method = c.req.method;\n\n\t\tif (UNSAFE_METHODS.has(method)) {\n\t\t\tif (origin) {\n\t\t\t\t// Origin present — must be loopback\n\t\t\t\tif (!isLoopbackOrigin(origin)) {\n\t\t\t\t\treturn c.json({ error: \"forbidden\" }, 403);\n\t\t\t\t}\n\t\t\t\t// Valid loopback origin — echo it for CORS\n\t\t\t\tc.header(\"Access-Control-Allow-Origin\", origin);\n\t\t\t\tc.header(\"Access-Control-Allow-Methods\", \"GET, POST, DELETE, OPTIONS\");\n\t\t\t\tc.header(\"Access-Control-Allow-Headers\", \"Content-Type\");\n\t\t\t} else {\n\t\t\t\t// No Origin — reject only if browser signals indicate cross-site\n\t\t\t\t// (matches Python's reject_if_unsafe policy for API endpoints)\n\t\t\t\tif (isUnsafeMissingOrigin(c)) {\n\t\t\t\t\treturn c.json({ error: \"forbidden\" }, 403);\n\t\t\t\t}\n\t\t\t\t// CLI / programmatic caller — no CORS headers needed\n\t\t\t}\n\t\t} else if (origin && isLoopbackOrigin(origin)) {\n\t\t\t// Safe method with valid origin — echo for preflight\n\t\t\tc.header(\"Access-Control-Allow-Origin\", origin);\n\t\t\tc.header(\"Access-Control-Allow-Methods\", \"GET, POST, DELETE, OPTIONS\");\n\t\t\tc.header(\"Access-Control-Allow-Headers\", \"Content-Type\");\n\t\t}\n\t\t// No origin or non-loopback on safe method: no ACAO header set.\n\t\t// Browser enforces same-origin; we don't set permissive headers.\n\n\t\tawait next();\n\t});\n}\n\n/**\n * Handle OPTIONS preflight requests.\n * Returns 204 with appropriate CORS headers for loopback origins.\n */\nexport function preflightHandler() {\n\treturn createMiddleware(async (c: Context, next: Next) => {\n\t\tif (c.req.method !== \"OPTIONS\") {\n\t\t\tawait next();\n\t\t\treturn;\n\t\t}\n\t\tconst origin = c.req.header(\"Origin\");\n\t\tif (origin && isLoopbackOrigin(origin)) {\n\t\t\tc.header(\"Access-Control-Allow-Origin\", origin);\n\t\t\tc.header(\"Access-Control-Allow-Methods\", \"GET, POST, DELETE, OPTIONS\");\n\t\t\tc.header(\"Access-Control-Allow-Headers\", \"Content-Type\");\n\t\t\tc.header(\"Access-Control-Max-Age\", \"86400\");\n\t\t\treturn c.body(null, 204);\n\t\t}\n\t\treturn c.body(null, 204);\n\t});\n}\n","/**\n * Config routes — GET /api/config, POST /api/config.\n *\n * Ports the user-facing config read/write path from Python's\n * codemem/viewer_routes/config.py, scoped to the TS runtime's current needs.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n\tCODEMEM_CONFIG_ENV_OVERRIDES,\n\tgetCodememConfigPath,\n\tgetCodememEnvOverrides,\n\ttype RawEventSweeper,\n\treadCodememConfigFile,\n\tstripJsonComments,\n\tstripTrailingCommas,\n\twriteCodememConfigFile,\n} from \"@codemem/core\";\nimport { Hono } from \"hono\";\n\ntype ConfigData = Record<string, unknown>;\n\nconst RUNTIMES = new Set([\"api_http\", \"claude_sidecar\"]);\nconst AUTH_SOURCES = new Set([\"auto\", \"env\", \"file\", \"command\", \"none\"]);\nconst HOT_RELOAD_KEYS = new Set([\"raw_events_sweeper_interval_s\"]);\nconst ALLOWED_KEYS = [\n\t\"claude_command\",\n\t\"observer_base_url\",\n\t\"observer_provider\",\n\t\"observer_model\",\n\t\"observer_runtime\",\n\t\"observer_auth_source\",\n\t\"observer_auth_file\",\n\t\"observer_auth_command\",\n\t\"observer_auth_timeout_ms\",\n\t\"observer_auth_cache_ttl_s\",\n\t\"observer_headers\",\n\t\"observer_max_chars\",\n\t\"pack_observation_limit\",\n\t\"pack_session_limit\",\n\t\"sync_enabled\",\n\t\"sync_host\",\n\t\"sync_port\",\n\t\"sync_interval_s\",\n\t\"sync_mdns\",\n\t\"sync_coordinator_url\",\n\t\"sync_coordinator_group\",\n\t\"sync_coordinator_timeout_s\",\n\t\"sync_coordinator_presence_ttl_s\",\n\t\"raw_events_sweeper_interval_s\",\n] as const;\n\nconst DEFAULTS: ConfigData = {\n\tclaude_command: [\"claude\"],\n\tobserver_runtime: \"api_http\",\n\tobserver_auth_source: \"auto\",\n\tobserver_auth_command: [],\n\tobserver_auth_timeout_ms: 1500,\n\tobserver_auth_cache_ttl_s: 300,\n\tobserver_headers: {},\n\tobserver_max_chars: 12000,\n\tpack_observation_limit: 50,\n\tpack_session_limit: 10,\n\tsync_enabled: false,\n\tsync_host: \"0.0.0.0\",\n\tsync_port: 7337,\n\tsync_interval_s: 120,\n\tsync_mdns: true,\n\tsync_coordinator_timeout_s: 3,\n\tsync_coordinator_presence_ttl_s: 180,\n\traw_events_sweeper_interval_s: 30,\n};\n\nexport interface ConfigRouteOptions {\n\tgetSweeper?: () => RawEventSweeper | null;\n}\n\nfunction loadProviderOptions(): string[] {\n\treturn [\"openai\", \"anthropic\", \"google\", \"xai\", \"groq\", \"deepseek\", \"mistral\", \"together\"];\n}\n\nfunction getConfigPath(): string {\n\tconst envPath = process.env.CODEMEM_CONFIG;\n\tif (envPath) return envPath.replace(/^~/, homedir());\n\tconst configDir = join(homedir(), \".config\", \"codemem\");\n\tconst candidates = [join(configDir, \"config.json\"), join(configDir, \"config.jsonc\")];\n\treturn candidates.find((p) => existsSync(p)) ?? join(configDir, \"config.json\");\n}\n\nfunction readConfigFile(configPath: string): ConfigData {\n\tif (!existsSync(configPath)) return {};\n\ttry {\n\t\tlet text = readFileSync(configPath, \"utf-8\").trim();\n\t\tif (!text) return {};\n\t\ttry {\n\t\t\treturn JSON.parse(text) as ConfigData;\n\t\t} catch {\n\t\t\ttext = stripTrailingCommas(stripJsonComments(text));\n\t\t\treturn JSON.parse(text) as ConfigData;\n\t\t}\n\t} catch {\n\t\treturn {};\n\t}\n}\n\nfunction getEffectiveConfig(configData: ConfigData): ConfigData {\n\tconst effective: ConfigData = { ...DEFAULTS, ...configData };\n\tfor (const [key, envVar] of Object.entries(CODEMEM_CONFIG_ENV_OVERRIDES) as Array<\n\t\t[string, string]\n\t>) {\n\t\tconst val = process.env[envVar];\n\t\tif (val != null && val !== \"\") effective[key] = val;\n\t}\n\treturn effective;\n}\n\nfunction parsePositiveInt(value: unknown, allowZero = false): number | null {\n\tif (typeof value === \"boolean\") return null;\n\tconst parsed =\n\t\ttypeof value === \"number\"\n\t\t\t? value\n\t\t\t: typeof value === \"string\" && /^-?\\d+$/.test(value.trim())\n\t\t\t\t? Number(value.trim())\n\t\t\t\t: Number.NaN;\n\tif (!Number.isFinite(parsed) || !Number.isInteger(parsed)) return null;\n\tif (allowZero) return parsed >= 0 ? parsed : null;\n\treturn parsed > 0 ? parsed : null;\n}\n\nfunction asStringMap(value: unknown): Record<string, string> | null {\n\tif (value == null || typeof value !== \"object\" || Array.isArray(value)) return null;\n\tconst parsed: Record<string, string> = {};\n\tfor (const [key, item] of Object.entries(value)) {\n\t\tif (typeof item !== \"string\") return null;\n\t\tconst stripped = key.trim();\n\t\tif (!stripped) return null;\n\t\tparsed[stripped] = item;\n\t}\n\treturn parsed;\n}\n\nfunction asExecutableArgv(value: unknown): string[] | null {\n\tif (!Array.isArray(value)) return null;\n\tconst argv: string[] = [];\n\tfor (const item of value) {\n\t\tif (typeof item !== \"string\") return null;\n\t\tconst token = item.trim();\n\t\tif (!token) return null;\n\t\targv.push(token);\n\t}\n\treturn argv;\n}\n\nfunction validateAndApplyUpdate(\n\tconfigData: ConfigData,\n\tkey: (typeof ALLOWED_KEYS)[number],\n\tvalue: unknown,\n\tproviders: Set<string>,\n): string | null {\n\tif (value == null || value === \"\") {\n\t\tdelete configData[key];\n\t\treturn null;\n\t}\n\tif (key === \"observer_provider\") {\n\t\tif (typeof value !== \"string\") return \"observer_provider must be string\";\n\t\tconst provider = value.trim().toLowerCase();\n\t\tconst savedBaseUrl = configData.observer_base_url;\n\t\tconst hasSavedBaseUrl = typeof savedBaseUrl === \"string\" && savedBaseUrl.trim().length > 0;\n\t\tif (!providers.has(provider) && !hasSavedBaseUrl) {\n\t\t\treturn \"observer_provider must match a configured provider\";\n\t\t}\n\t\tconfigData[key] = provider;\n\t\treturn null;\n\t}\n\tif (key === \"observer_runtime\") {\n\t\tif (typeof value !== \"string\") return \"observer_runtime must be string\";\n\t\tconst runtime = value.trim().toLowerCase();\n\t\tif (!RUNTIMES.has(runtime)) {\n\t\t\treturn \"observer_runtime must be one of: api_http, claude_sidecar\";\n\t\t}\n\t\tconfigData[key] = runtime;\n\t\treturn null;\n\t}\n\tif (key === \"observer_auth_source\") {\n\t\tif (typeof value !== \"string\") return \"observer_auth_source must be string\";\n\t\tconst source = value.trim().toLowerCase();\n\t\tif (!AUTH_SOURCES.has(source)) {\n\t\t\treturn \"observer_auth_source must be one of: auto, env, file, command, none\";\n\t\t}\n\t\tconfigData[key] = source;\n\t\treturn null;\n\t}\n\tif (key === \"claude_command\" || key === \"observer_auth_command\") {\n\t\tconst argv = asExecutableArgv(value);\n\t\tif (argv == null) return `${key} must be string array`;\n\t\tif (argv.length > 0) configData[key] = argv;\n\t\telse delete configData[key];\n\t\treturn null;\n\t}\n\tif (key === \"observer_headers\") {\n\t\tconst headers = asStringMap(value);\n\t\tif (headers == null) return \"observer_headers must be object of string values\";\n\t\tif (Object.keys(headers).length > 0) configData[key] = headers;\n\t\telse delete configData[key];\n\t\treturn null;\n\t}\n\tif (key === \"sync_enabled\" || key === \"sync_mdns\") {\n\t\tif (typeof value !== \"boolean\") return `${key} must be boolean`;\n\t\tconfigData[key] = value;\n\t\treturn null;\n\t}\n\tif (\n\t\tkey === \"observer_base_url\" ||\n\t\tkey === \"observer_model\" ||\n\t\tkey === \"observer_auth_file\" ||\n\t\tkey === \"sync_host\" ||\n\t\tkey === \"sync_coordinator_url\" ||\n\t\tkey === \"sync_coordinator_group\"\n\t) {\n\t\tif (typeof value !== \"string\") return `${key} must be string`;\n\t\tconst trimmed = value.trim();\n\t\tif (!trimmed) delete configData[key];\n\t\telse configData[key] = trimmed;\n\t\treturn null;\n\t}\n\tconst allowZero = key === \"observer_auth_cache_ttl_s\";\n\tconst parsed = parsePositiveInt(value, allowZero);\n\tif (parsed == null) return `${key} must be ${allowZero ? \"non-negative int\" : \"int\"}`;\n\tconfigData[key] = parsed;\n\treturn null;\n}\n\nfunction applyRuntimeEffects(changedKeys: string[], opts: ConfigRouteOptions): string[] {\n\tconst applied: string[] = [];\n\tif (changedKeys.includes(\"raw_events_sweeper_interval_s\")) {\n\t\tconst configValue = readCodememConfigFile().raw_events_sweeper_interval_s;\n\t\tconst seconds =\n\t\t\ttypeof configValue === \"number\"\n\t\t\t\t? configValue\n\t\t\t\t: Number.parseInt(String(configValue ?? \"\"), 10);\n\t\tif (Number.isFinite(seconds) && seconds > 0) {\n\t\t\tprocess.env.CODEMEM_RAW_EVENTS_SWEEPER_INTERVAL_MS = String(seconds * 1000);\n\t\t} else {\n\t\t\tdelete process.env.CODEMEM_RAW_EVENTS_SWEEPER_INTERVAL_MS;\n\t\t}\n\t\topts.getSweeper?.()?.notifyConfigChanged();\n\t\tapplied.push(\"raw_events_sweeper_interval_s\");\n\t}\n\treturn applied;\n}\n\nexport function configRoutes(opts: ConfigRouteOptions = {}) {\n\tconst app = new Hono();\n\n\tapp.get(\"/api/config\", (c) => {\n\t\tconst configPath = getConfigPath();\n\t\tconst configData = readConfigFile(configPath);\n\t\treturn c.json({\n\t\t\tpath: configPath,\n\t\t\tconfig: configData,\n\t\t\tdefaults: DEFAULTS,\n\t\t\teffective: getEffectiveConfig(configData),\n\t\t\tenv_overrides: getCodememEnvOverrides(),\n\t\t\tproviders: loadProviderOptions(),\n\t\t});\n\t});\n\n\tapp.post(\"/api/config\", async (c) => {\n\t\tlet payload: unknown;\n\t\ttry {\n\t\t\tpayload = (await c.req.json()) as unknown;\n\t\t} catch {\n\t\t\treturn c.json({ error: \"invalid json\" }, 400);\n\t\t}\n\t\tif (payload == null || typeof payload !== \"object\" || Array.isArray(payload)) {\n\t\t\treturn c.json({ error: \"payload must be an object\" }, 400);\n\t\t}\n\t\tif (\n\t\t\t\"config\" in payload &&\n\t\t\t(payload as ConfigData).config != null &&\n\t\t\t(typeof (payload as ConfigData).config !== \"object\" ||\n\t\t\t\tArray.isArray((payload as ConfigData).config))\n\t\t) {\n\t\t\treturn c.json({ error: \"config must be an object\" }, 400);\n\t\t}\n\t\tconst updates =\n\t\t\t\"config\" in payload &&\n\t\t\t(payload as ConfigData).config != null &&\n\t\t\ttypeof (payload as ConfigData).config === \"object\" &&\n\t\t\t!Array.isArray((payload as ConfigData).config)\n\t\t\t\t? ((payload as ConfigData).config as ConfigData)\n\t\t\t\t: (payload as ConfigData);\n\n\t\tconst configPath = getCodememConfigPath();\n\t\tconst beforeConfig = readCodememConfigFile();\n\t\tconst beforeEffective = getEffectiveConfig(beforeConfig);\n\t\tconst nextConfig: ConfigData = { ...beforeConfig };\n\t\tconst providers = new Set(loadProviderOptions());\n\n\t\tconst touchedKeys = ALLOWED_KEYS.filter((key) => key in updates);\n\t\tfor (const key of ALLOWED_KEYS) {\n\t\t\tif (!(key in updates)) continue;\n\t\t\tconst error = validateAndApplyUpdate(nextConfig, key, updates[key], providers);\n\t\t\tif (error) return c.json({ error }, 400);\n\t\t}\n\n\t\tlet savedPath: string;\n\t\ttry {\n\t\t\tsavedPath = writeCodememConfigFile(nextConfig, configPath);\n\t\t} catch {\n\t\t\treturn c.json({ error: \"failed to write config\" }, 500);\n\t\t}\n\n\t\tconst afterEffective = getEffectiveConfig(nextConfig);\n\t\tconst savedChangedKeys = ALLOWED_KEYS.filter((key) => beforeConfig[key] !== nextConfig[key]);\n\t\tconst effectiveChangedKeys = ALLOWED_KEYS.filter(\n\t\t\t(key) => beforeEffective[key] !== afterEffective[key],\n\t\t);\n\t\tconst envOverrides = getCodememEnvOverrides();\n\t\tconst ignoredByEnvKeys = savedChangedKeys.filter(\n\t\t\t(key) => !effectiveChangedKeys.includes(key) && key in envOverrides,\n\t\t);\n\t\tconst runtimeChangedKeys = [\n\t\t\t...new Set([...touchedKeys, ...savedChangedKeys, ...effectiveChangedKeys]),\n\t\t];\n\t\tconst hotReloadedKeys = applyRuntimeEffects(runtimeChangedKeys, opts);\n\t\tconst restartRequiredKeys = effectiveChangedKeys.filter(\n\t\t\t(key) => !HOT_RELOAD_KEYS.has(key) && !(key in envOverrides),\n\t\t);\n\n\t\treturn c.json({\n\t\t\tpath: savedPath,\n\t\t\tconfig: nextConfig,\n\t\t\teffective: afterEffective,\n\t\t\teffects: {\n\t\t\t\tsaved_keys: savedChangedKeys,\n\t\t\t\teffective_keys: effectiveChangedKeys,\n\t\t\t\thot_reloaded_keys: hotReloadedKeys,\n\t\t\t\trestart_required_keys: restartRequiredKeys,\n\t\t\t\tignored_by_env_keys: ignoredByEnvKeys,\n\t\t\t\twarnings: ignoredByEnvKeys.map(\n\t\t\t\t\t(key) =>\n\t\t\t\t\t\t`${key} is currently controlled by ${envOverrides[key]}; saved config will not take effect until that override is removed.`,\n\t\t\t\t),\n\t\t\t},\n\t\t});\n\t});\n\n\treturn app;\n}\n","import { parseStrictInteger } from \"@codemem/core\";\n\n/**\n * Shared helpers for viewer-server routes.\n */\n\n/**\n * Parse a JSON string that should be an array of strings.\n * Returns an empty array on null, invalid JSON, or non-array values.\n * Mirrors Python's store._safe_json_list().\n */\nexport function safeJsonList(raw: string | null | undefined): string[] {\n\tif (raw == null) return [];\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(raw);\n\t\tif (!Array.isArray(parsed)) return [];\n\t\treturn parsed.filter((item): item is string => typeof item === \"string\");\n\t} catch {\n\t\treturn [];\n\t}\n}\n\n/**\n * Parse a query parameter as an integer, returning the default on failure.\n */\nexport function queryInt(value: string | undefined, defaultValue: number): number {\n\tif (value == null) return defaultValue;\n\tconst parsed = parseStrictInteger(value);\n\treturn parsed == null ? defaultValue : parsed;\n}\n\n/**\n * Parse a query parameter as a boolean flag.\n * Recognizes \"1\", \"true\", \"yes\" as truthy.\n */\nexport function queryBool(value: string | undefined): boolean {\n\tif (value == null) return false;\n\treturn value === \"1\" || value === \"true\" || value === \"yes\";\n}\n","/**\n * Memory routes — observations, summaries, sessions, projects, pack, artifacts.\n */\n\nimport type { MemoryStore } from \"@codemem/core\";\nimport { fromJson, parseStrictInteger, schema } from \"@codemem/core\";\nimport { desc, eq, inArray, isNotNull } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/better-sqlite3\";\nimport { Hono } from \"hono\";\nimport { queryInt } from \"../helpers.js\";\n\ntype StoreFactory = () => MemoryStore;\n\n/**\n * Attach session project/cwd fields to memory items.\n */\nfunction attachSessionFields(store: MemoryStore, items: Record<string, unknown>[]): void {\n\tconst sessionIds: number[] = [];\n\tconst seen = new Set<number>();\n\tfor (const item of items) {\n\t\tconst value = item.session_id;\n\t\tif (value == null) continue;\n\t\tconst sid = Number(value);\n\t\tif (Number.isNaN(sid) || seen.has(sid)) continue;\n\t\tseen.add(sid);\n\t\tsessionIds.push(sid);\n\t}\n\tif (sessionIds.length === 0) return;\n\n\tconst d = drizzle(store.db, { schema });\n\tconst rows = d\n\t\t.select({\n\t\t\tid: schema.sessions.id,\n\t\t\tproject: schema.sessions.project,\n\t\t\tcwd: schema.sessions.cwd,\n\t\t})\n\t\t.from(schema.sessions)\n\t\t.where(inArray(schema.sessions.id, sessionIds))\n\t\t.all();\n\n\tconst bySession = new Map<number, { project: string; cwd: string }>();\n\tfor (const row of rows) {\n\t\tconst projectRaw = String(row.project ?? \"\").trim();\n\t\tconst project = projectRaw ? projectBasename(projectRaw) : \"\";\n\t\tconst cwd = String(row.cwd ?? \"\");\n\t\tbySession.set(row.id, { project, cwd });\n\t}\n\n\tfor (const item of items) {\n\t\tconst sid = Number(item.session_id);\n\t\tif (Number.isNaN(sid)) continue;\n\t\tconst fields = bySession.get(sid);\n\t\tif (!fields) continue;\n\t\titem.project ??= fields.project;\n\t\titem.cwd ??= fields.cwd;\n\t}\n}\n\n/**\n * Extract the basename of a project path.\n * Strips \"fatal:\" prefixed values.\n */\nfunction projectBasename(raw: string): string {\n\tif (raw.toLowerCase().startsWith(\"fatal:\")) return \"\";\n\tconst parts = raw.replace(/\\\\/g, \"/\").split(\"/\");\n\treturn parts[parts.length - 1] ?? raw;\n}\n\nexport function memoryRoutes(getStore: StoreFactory) {\n\tconst app = new Hono();\n\n\t// GET /api/sessions\n\tapp.get(\"/api/sessions\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst limit = queryInt(c.req.query(\"limit\"), 20);\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tconst rows = d\n\t\t\t\t.select()\n\t\t\t\t.from(schema.sessions)\n\t\t\t\t.orderBy(desc(schema.sessions.started_at))\n\t\t\t\t.limit(limit)\n\t\t\t\t.all();\n\t\t\tconst items = rows.map((row) => ({\n\t\t\t\t...row,\n\t\t\t\tmetadata_json: fromJson(row.metadata_json),\n\t\t\t}));\n\t\t\treturn c.json({ items });\n\t\t}\n\t});\n\n\t// GET /api/projects\n\tapp.get(\"/api/projects\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tconst rows = d\n\t\t\t\t.selectDistinct({ project: schema.sessions.project })\n\t\t\t\t.from(schema.sessions)\n\t\t\t\t.where(isNotNull(schema.sessions.project))\n\t\t\t\t.all();\n\t\t\tconst projects = [\n\t\t\t\t...new Set(\n\t\t\t\t\trows\n\t\t\t\t\t\t.map((r) => String(r.project ?? \"\").trim())\n\t\t\t\t\t\t.filter((p) => p && !p.toLowerCase().startsWith(\"fatal:\"))\n\t\t\t\t\t\t.map((p) => projectBasename(p))\n\t\t\t\t\t\t.filter(Boolean),\n\t\t\t\t),\n\t\t\t].sort();\n\t\t\treturn c.json({ projects });\n\t\t}\n\t});\n\n\t// GET /api/observations (aliased from /api/memories)\n\tapp.get(\"/api/memories\", (c) => c.redirect(\"/api/observations\", 301));\n\n\tapp.get(\"/api/observations\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst limit = Math.max(1, queryInt(c.req.query(\"limit\"), 20));\n\t\t\tconst offset = Math.max(0, queryInt(c.req.query(\"offset\"), 0));\n\t\t\tconst project = c.req.query(\"project\") || undefined;\n\t\t\tconst kinds = [\n\t\t\t\t\"bugfix\",\n\t\t\t\t\"change\",\n\t\t\t\t\"decision\",\n\t\t\t\t\"discovery\",\n\t\t\t\t\"exploration\",\n\t\t\t\t\"feature\",\n\t\t\t\t\"refactor\",\n\t\t\t];\n\t\t\tconst filters: Record<string, unknown> = {};\n\t\t\tif (project) filters.project = project;\n\n\t\t\tconst items = store.recentByKinds(kinds, limit + 1, filters, offset);\n\t\t\tconst hasMore = items.length > limit;\n\t\t\tconst result = hasMore ? items.slice(0, limit) : items;\n\t\t\tconst asRecords = result as unknown as Record<string, unknown>[];\n\t\t\tattachSessionFields(store, asRecords);\n\t\t\treturn c.json({\n\t\t\t\titems: asRecords,\n\t\t\t\tpagination: {\n\t\t\t\t\tlimit,\n\t\t\t\t\toffset,\n\t\t\t\t\tnext_offset: hasMore ? offset + result.length : null,\n\t\t\t\t\thas_more: hasMore,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t});\n\n\t// GET /api/summaries\n\tapp.get(\"/api/summaries\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst limit = Math.max(1, queryInt(c.req.query(\"limit\"), 50));\n\t\t\tconst offset = Math.max(0, queryInt(c.req.query(\"offset\"), 0));\n\t\t\tconst project = c.req.query(\"project\") || undefined;\n\t\t\tconst filters: Record<string, unknown> = { kind: \"session_summary\" };\n\t\t\tif (project) filters.project = project;\n\n\t\t\tconst items = store.recent(limit + 1, filters, offset);\n\t\t\tconst hasMore = items.length > limit;\n\t\t\tconst result = hasMore ? items.slice(0, limit) : items;\n\t\t\tconst asRecords = result as unknown as Record<string, unknown>[];\n\t\t\tattachSessionFields(store, asRecords);\n\t\t\treturn c.json({\n\t\t\t\titems: asRecords,\n\t\t\t\tpagination: {\n\t\t\t\t\tlimit,\n\t\t\t\t\toffset,\n\t\t\t\t\tnext_offset: hasMore ? offset + result.length : null,\n\t\t\t\t\thas_more: hasMore,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t});\n\n\t// GET /api/session (aggregate counts)\n\tapp.get(\"/api/session\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst project = c.req.query(\"project\") || null;\n\t\t\tconst count = (sql: string, ...params: unknown[]): number => {\n\t\t\t\tconst row = store.db.prepare(sql).get(...params) as Record<string, unknown> | undefined;\n\t\t\t\treturn Number(row?.total ?? 0);\n\t\t\t};\n\n\t\t\tlet prompts: number;\n\t\t\tlet artifacts: number;\n\t\t\tlet memories: number;\n\t\t\tlet observations: number;\n\t\t\tif (project) {\n\t\t\t\tprompts = count(\"SELECT COUNT(*) AS total FROM user_prompts WHERE project = ?\", project);\n\t\t\t\tartifacts = count(\n\t\t\t\t\t`SELECT COUNT(*) AS total FROM artifacts\n\t\t\t\t\t JOIN sessions ON sessions.id = artifacts.session_id\n\t\t\t\t\t WHERE sessions.project = ?`,\n\t\t\t\t\tproject,\n\t\t\t\t);\n\t\t\t\tmemories = count(\n\t\t\t\t\t`SELECT COUNT(*) AS total FROM memory_items\n\t\t\t\t\t JOIN sessions ON sessions.id = memory_items.session_id\n\t\t\t\t\t WHERE sessions.project = ?`,\n\t\t\t\t\tproject,\n\t\t\t\t);\n\t\t\t\tobservations = count(\n\t\t\t\t\t`SELECT COUNT(*) AS total FROM memory_items\n\t\t\t\t\t JOIN sessions ON sessions.id = memory_items.session_id\n\t\t\t\t\t WHERE kind != 'session_summary' AND sessions.project = ?`,\n\t\t\t\t\tproject,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tprompts = count(\"SELECT COUNT(*) AS total FROM user_prompts\");\n\t\t\t\tartifacts = count(\"SELECT COUNT(*) AS total FROM artifacts\");\n\t\t\t\tmemories = count(\"SELECT COUNT(*) AS total FROM memory_items\");\n\t\t\t\tobservations = count(\n\t\t\t\t\t\"SELECT COUNT(*) AS total FROM memory_items WHERE kind != 'session_summary'\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst total = prompts + artifacts + memories;\n\t\t\treturn c.json({ total, memories, artifacts, prompts, observations });\n\t\t}\n\t});\n\n\t// GET /api/pack\n\tapp.get(\"/api/pack\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst context = c.req.query(\"context\") || \"\";\n\t\t\tif (!context) {\n\t\t\t\treturn c.json({ error: \"context required\" }, 400);\n\t\t\t}\n\t\t\tconst limit = queryInt(c.req.query(\"limit\"), 10);\n\t\t\tconst tokenBudgetStr = c.req.query(\"token_budget\");\n\t\t\tlet tokenBudget: number | undefined;\n\t\t\tif (tokenBudgetStr) {\n\t\t\t\ttokenBudget = parseStrictInteger(tokenBudgetStr) ?? undefined;\n\t\t\t\tif (tokenBudget === undefined) {\n\t\t\t\t\treturn c.json({ error: \"token_budget must be int\" }, 400);\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst project = c.req.query(\"project\") || undefined;\n\t\t\tconst filters: Record<string, unknown> = {};\n\t\t\tif (project) filters.project = project;\n\t\t\tconst pack = store.buildMemoryPack(context, limit, tokenBudget ?? null, filters);\n\t\t\treturn c.json(pack);\n\t\t}\n\t});\n\n\t// GET /api/memory\n\tapp.get(\"/api/memory\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst limit = queryInt(c.req.query(\"limit\"), 20);\n\t\t\tconst kind = c.req.query(\"kind\") || undefined;\n\t\t\tconst project = c.req.query(\"project\") || undefined;\n\t\t\tconst filters: Record<string, unknown> = {};\n\t\t\tif (kind) filters.kind = kind;\n\t\t\tif (project) filters.project = project;\n\t\t\tconst items = store.recent(limit, filters);\n\t\t\tconst asRecords = items as unknown as Record<string, unknown>[];\n\t\t\tattachSessionFields(store, asRecords);\n\t\t\treturn c.json({ items: asRecords });\n\t\t}\n\t});\n\n\t// GET /api/artifacts\n\tapp.get(\"/api/artifacts\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst sessionIdStr = c.req.query(\"session_id\");\n\t\t\tif (!sessionIdStr) {\n\t\t\t\treturn c.json({ error: \"session_id required\" }, 400);\n\t\t\t}\n\t\t\tconst sessionId = parseStrictInteger(sessionIdStr);\n\t\t\tif (sessionId == null) {\n\t\t\t\treturn c.json({ error: \"session_id must be int\" }, 400);\n\t\t\t}\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tconst rows = d\n\t\t\t\t.select()\n\t\t\t\t.from(schema.artifacts)\n\t\t\t\t.where(eq(schema.artifacts.session_id, sessionId))\n\t\t\t\t.all();\n\t\t\treturn c.json({ items: rows });\n\t\t}\n\t});\n\n\t// POST /api/memories/visibility\n\tapp.post(\"/api/memories/visibility\", async (c) => {\n\t\tconst store = getStore();\n\t\tconst body = await c.req.json<Record<string, unknown>>();\n\t\tconst memoryId = parseStrictInteger(\n\t\t\ttypeof body.memory_id === \"string\" ? body.memory_id : String(body.memory_id ?? \"\"),\n\t\t);\n\t\tif (memoryId == null || memoryId <= 0) {\n\t\t\treturn c.json({ error: \"memory_id must be int\" }, 400);\n\t\t}\n\t\tconst visibility = String(body.visibility ?? \"\").trim();\n\t\tif (visibility !== \"private\" && visibility !== \"shared\") {\n\t\t\treturn c.json({ error: \"visibility must be private or shared\" }, 400);\n\t\t}\n\t\ttry {\n\t\t\tconst item = store.updateMemoryVisibility(memoryId, visibility);\n\t\t\treturn c.json({ item });\n\t\t} catch (err) {\n\t\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\t\tif (msg.includes(\"not found\")) return c.json({ error: msg }, 404);\n\t\t\tif (msg.includes(\"not owned\")) return c.json({ error: msg }, 403);\n\t\t\treturn c.json({ error: msg }, 400);\n\t\t}\n\t});\n\n\treturn app;\n}\n","/**\n * Observer status route — GET /api/observer-status.\n *\n * Ports Python's viewer_routes/observer_status.py.\n * Returns observer runtime info, credential availability, and queue status.\n */\n\nimport type { MemoryStore, RawEventSweeper } from \"@codemem/core\";\nimport { Hono } from \"hono\";\n\ntype StoreFactory = () => MemoryStore;\n\nexport interface ObserverStatusDeps {\n\tgetStore: StoreFactory;\n\tgetSweeper: () => RawEventSweeper | null;\n}\n\nfunction buildFailureImpact(\n\tlatestFailure: Record<string, unknown> | null,\n\tqueueTotals: { pending: number; sessions: number },\n\tauthBackoff: { active: boolean; remainingS: number },\n): string | null {\n\tif (!latestFailure) return null;\n\tif (authBackoff.active) {\n\t\treturn `Queue retries paused for ~${authBackoff.remainingS}s after an observer auth failure.`;\n\t}\n\tif (queueTotals.pending > 0) {\n\t\treturn `${queueTotals.pending} queued raw events across ${queueTotals.sessions} session(s) are waiting on a successful flush.`;\n\t}\n\treturn \"Failed flush batches are pending retry.\";\n}\n\nexport function observerStatusRoutes(deps?: ObserverStatusDeps) {\n\tconst app = new Hono();\n\n\tapp.get(\"/api/observer-status\", (c) => {\n\t\tconst store = deps?.getStore();\n\t\tconst sweeper = deps?.getSweeper();\n\n\t\t// Stub fallback when store doesn't have the required methods (e.g. tests with mock store)\n\t\tif (!store || typeof store.rawEventBacklogTotals !== \"function\") {\n\t\t\treturn c.json({\n\t\t\t\tactive: null,\n\t\t\t\tavailable_credentials: {},\n\t\t\t\tlatest_failure: null,\n\t\t\t\tqueue: {\n\t\t\t\t\tpending: 0,\n\t\t\t\t\tsessions: 0,\n\t\t\t\t\tauth_backoff_active: false,\n\t\t\t\t\tauth_backoff_remaining_s: 0,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tconst queueTotals = store.rawEventBacklogTotals();\n\t\tconst authBackoff = sweeper?.authBackoffStatus() ?? { active: false, remainingS: 0 };\n\t\tconst latestFailure = store.latestRawEventFlushFailure();\n\n\t\tconst failureWithImpact = latestFailure\n\t\t\t? { ...latestFailure, impact: buildFailureImpact(latestFailure, queueTotals, authBackoff) }\n\t\t\t: null;\n\n\t\treturn c.json({\n\t\t\tactive: null, // TODO: wire observer singleton status when available\n\t\t\tavailable_credentials: {}, // TODO: port probe_available_credentials\n\t\t\tlatest_failure: failureWithImpact,\n\t\t\tqueue: {\n\t\t\t\t...queueTotals,\n\t\t\t\tauth_backoff_active: authBackoff.active,\n\t\t\t\tauth_backoff_remaining_s: authBackoff.remainingS,\n\t\t\t},\n\t\t});\n\t});\n\n\treturn app;\n}\n","/**\n * Raw events routes — GET & POST /api/raw-events, GET /api/raw-events/status,\n * POST /api/claude-hooks.\n */\n\nimport { createHash } from \"node:crypto\";\nimport type { MemoryStore, RawEventSweeper } from \"@codemem/core\";\nimport { buildRawEventEnvelopeFromHook, schema, stripPrivateObj } from \"@codemem/core\";\nimport { desc } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/better-sqlite3\";\nimport { Hono } from \"hono\";\nimport { queryInt } from \"../helpers.js\";\n\ntype StoreFactory = () => MemoryStore;\n\nconst MAX_RAW_EVENTS_BODY_BYTES =\n\tNumber.parseInt(process.env.CODEMEM_RAW_EVENTS_MAX_BODY_BYTES ?? \"\", 10) || 1048576;\n\n/** Keys to check (in priority order) when resolving a session stream id. */\nconst SESSION_ID_KEYS = [\n\t\"session_stream_id\",\n\t\"session_id\",\n\t\"stream_id\",\n\t\"opencode_session_id\",\n] as const;\n\n/**\n * Resolve a session stream id from a payload object.\n * Checks multiple field aliases. Throws on conflicting values.\n */\nfunction resolveSessionStreamId(payload: Record<string, unknown>): string | null {\n\tconst values = new Map<string, string>();\n\tfor (const key of SESSION_ID_KEYS) {\n\t\tconst value = payload[key];\n\t\tif (value == null) continue;\n\t\tif (typeof value !== \"string\") throw new Error(`${key} must be string`);\n\t\tconst text = value.trim();\n\t\tif (text) values.set(key, text);\n\t}\n\tif (values.size === 0) return null;\n\tconst unique = new Set(values.values());\n\tif (unique.size > 1) throw new Error(\"conflicting session id fields\");\n\t// Return the first matching key's value (preserves priority order)\n\tfor (const key of SESSION_ID_KEYS) {\n\t\tconst v = values.get(key);\n\t\tif (v) return v;\n\t}\n\treturn null;\n}\n\n/**\n * Parse and validate a JSON object body, enforcing size limits.\n * Returns the parsed payload or a Hono Response on error.\n */\nasync function parseJsonObjectBody(\n\tc: {\n\t\treq: { header: (name: string) => string | undefined; text: () => Promise<string> };\n\t\tjson: (data: unknown, status?: number) => Response;\n\t},\n\tmaxBytes: number,\n): Promise<Record<string, unknown> | Response> {\n\tconst contentLength = Number.parseInt(c.req.header(\"content-length\") ?? \"0\", 10);\n\tif (Number.isNaN(contentLength) || contentLength < 0) {\n\t\treturn c.json({ error: \"invalid content-length\" }, 400);\n\t}\n\tif (contentLength > maxBytes) {\n\t\treturn c.json({ error: \"payload too large\", max_bytes: maxBytes }, 413);\n\t}\n\tlet raw: string;\n\ttry {\n\t\traw = await c.req.text();\n\t} catch {\n\t\treturn c.json({ error: \"invalid json\" }, 400);\n\t}\n\tif (Buffer.byteLength(raw, \"utf-8\") > maxBytes) {\n\t\treturn c.json({ error: \"payload too large\", max_bytes: maxBytes }, 413);\n\t}\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = raw ? JSON.parse(raw) : {};\n\t} catch {\n\t\treturn c.json({ error: \"invalid json\" }, 400);\n\t}\n\tif (parsed == null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\treturn c.json({ error: \"payload must be an object\" }, 400);\n\t}\n\treturn parsed as Record<string, unknown>;\n}\n\n/** Nudge the sweeper safely — never crashes the caller. */\nfunction nudgeSweeper(sweeper: RawEventSweeper | null | undefined): void {\n\ttry {\n\t\tsweeper?.nudge();\n\t} catch {\n\t\t// never crash the request if sweeper nudge fails\n\t}\n}\n\nexport function rawEventsRoutes(getStore: StoreFactory, sweeper?: RawEventSweeper | null) {\n\tconst app = new Hono();\n\n\t// GET /api/raw-events (compat endpoint for stats panel)\n\tapp.get(\"/api/raw-events\", (c) => {\n\t\tconst store = getStore();\n\t\tconst totals = store.rawEventBacklogTotals();\n\t\treturn c.json(totals);\n\t});\n\n\t// GET /api/raw-events/status\n\tapp.get(\"/api/raw-events/status\", (c) => {\n\t\tconst store = getStore();\n\t\tconst limit = queryInt(c.req.query(\"limit\"), 25);\n\t\tconst d = drizzle(store.db, { schema });\n\t\tconst rows = d\n\t\t\t.select({\n\t\t\t\tsource: schema.rawEventSessions.source,\n\t\t\t\tstream_id: schema.rawEventSessions.stream_id,\n\t\t\t\topencode_session_id: schema.rawEventSessions.opencode_session_id,\n\t\t\t\tcwd: schema.rawEventSessions.cwd,\n\t\t\t\tproject: schema.rawEventSessions.project,\n\t\t\t\tstarted_at: schema.rawEventSessions.started_at,\n\t\t\t\tlast_seen_ts_wall_ms: schema.rawEventSessions.last_seen_ts_wall_ms,\n\t\t\t\tlast_received_event_seq: schema.rawEventSessions.last_received_event_seq,\n\t\t\t\tlast_flushed_event_seq: schema.rawEventSessions.last_flushed_event_seq,\n\t\t\t\tupdated_at: schema.rawEventSessions.updated_at,\n\t\t\t})\n\t\t\t.from(schema.rawEventSessions)\n\t\t\t.orderBy(desc(schema.rawEventSessions.updated_at))\n\t\t\t.limit(limit)\n\t\t\t.all();\n\t\tconst items = rows.map((row) => {\n\t\t\tconst streamId = String(row.stream_id ?? row.opencode_session_id ?? \"\");\n\t\t\treturn {\n\t\t\t\t...row,\n\t\t\t\tsession_stream_id: streamId,\n\t\t\t\tsession_id: streamId,\n\t\t\t};\n\t\t});\n\t\tconst totals = store.rawEventBacklogTotals();\n\t\treturn c.json({\n\t\t\titems,\n\t\t\ttotals,\n\t\t\tingest: {\n\t\t\t\tavailable: true,\n\t\t\t\tmode: \"stream_queue\",\n\t\t\t\tmax_body_bytes: MAX_RAW_EVENTS_BODY_BYTES,\n\t\t\t},\n\t\t});\n\t});\n\n\t// POST /api/raw-events — ingest raw events from plugin\n\tapp.post(\"/api/raw-events\", async (c) => {\n\t\tconst result = await parseJsonObjectBody(c, MAX_RAW_EVENTS_BODY_BYTES);\n\t\tif (result instanceof Response) return result;\n\t\tconst payload = result;\n\n\t\tconst store = getStore();\n\t\ttry {\n\t\t\t// Validate top-level string fields\n\t\t\tconst cwd = payload.cwd;\n\t\t\tif (cwd != null && typeof cwd !== \"string\") {\n\t\t\t\treturn c.json({ error: \"cwd must be string\" }, 400);\n\t\t\t}\n\t\t\tconst project = payload.project;\n\t\t\tif (project != null && typeof project !== \"string\") {\n\t\t\t\treturn c.json({ error: \"project must be string\" }, 400);\n\t\t\t}\n\t\t\tconst startedAt = payload.started_at;\n\t\t\tif (startedAt != null && typeof startedAt !== \"string\") {\n\t\t\t\treturn c.json({ error: \"started_at must be string\" }, 400);\n\t\t\t}\n\n\t\t\t// Determine event list: batch (events array) or single-event payload\n\t\t\tlet items = payload.events;\n\t\t\tif (items == null) {\n\t\t\t\titems = [payload];\n\t\t\t}\n\t\t\tif (!Array.isArray(items)) {\n\t\t\t\treturn c.json({ error: \"events must be a list\" }, 400);\n\t\t\t}\n\n\t\t\t// Resolve default session id from top-level payload\n\t\t\tlet defaultSessionId: string;\n\t\t\ttry {\n\t\t\t\tdefaultSessionId = resolveSessionStreamId(payload) ?? \"\";\n\t\t\t} catch (err) {\n\t\t\t\treturn c.json({ error: (err as Error).message }, 400);\n\t\t\t}\n\t\t\tif (defaultSessionId.startsWith(\"msg_\")) {\n\t\t\t\treturn c.json({ error: \"invalid session id\" }, 400);\n\t\t\t}\n\n\t\t\tlet inserted = 0;\n\t\t\tconst lastSeenBySession = new Map<string, number>();\n\t\t\tconst metaBySession = new Map<string, Record<string, string>>();\n\t\t\tconst sessionIds = new Set<string>();\n\t\t\tconst batchBySession = new Map<string, Record<string, unknown>[]>();\n\n\t\t\tfor (const item of items) {\n\t\t\t\tif (item == null || typeof item !== \"object\" || Array.isArray(item)) {\n\t\t\t\t\treturn c.json({ error: \"event must be an object\" }, 400);\n\t\t\t\t}\n\t\t\t\tconst itemObj = item as Record<string, unknown>;\n\n\t\t\t\tlet itemSessionId: string | null;\n\t\t\t\ttry {\n\t\t\t\t\titemSessionId = resolveSessionStreamId(itemObj);\n\t\t\t\t} catch (err) {\n\t\t\t\t\treturn c.json({ error: (err as Error).message }, 400);\n\t\t\t\t}\n\t\t\t\tconst opencodeSessionId = String(itemSessionId ?? defaultSessionId ?? \"\");\n\t\t\t\tif (!opencodeSessionId) {\n\t\t\t\t\treturn c.json({ error: \"session id required\" }, 400);\n\t\t\t\t}\n\t\t\t\tif (opencodeSessionId.startsWith(\"msg_\")) {\n\t\t\t\t\treturn c.json({ error: \"invalid session id\" }, 400);\n\t\t\t\t}\n\n\t\t\t\tlet eventId = String(itemObj.event_id ?? \"\");\n\t\t\t\tconst eventType = String(itemObj.event_type ?? \"\");\n\t\t\t\tif (!eventType) {\n\t\t\t\t\treturn c.json({ error: \"event_type required\" }, 400);\n\t\t\t\t}\n\n\t\t\t\tconst eventSeqValue = itemObj.event_seq;\n\t\t\t\tif (eventSeqValue != null) {\n\t\t\t\t\tconst parsed = Number(eventSeqValue);\n\t\t\t\t\tif (!Number.isFinite(parsed) || parsed !== Math.floor(parsed)) {\n\t\t\t\t\t\treturn c.json({ error: \"event_seq must be int\" }, 400);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet tsWallMs = itemObj.ts_wall_ms;\n\t\t\t\tif (tsWallMs != null) {\n\t\t\t\t\tconst parsed = Number(tsWallMs);\n\t\t\t\t\tif (!Number.isFinite(parsed)) {\n\t\t\t\t\t\treturn c.json({ error: \"ts_wall_ms must be int\" }, 400);\n\t\t\t\t\t}\n\t\t\t\t\ttsWallMs = Math.floor(parsed);\n\t\t\t\t\tconst prev = lastSeenBySession.get(opencodeSessionId) ?? (tsWallMs as number);\n\t\t\t\t\tlastSeenBySession.set(opencodeSessionId, Math.max(prev, tsWallMs as number));\n\t\t\t\t}\n\n\t\t\t\tlet tsMonoMs = itemObj.ts_mono_ms;\n\t\t\t\tif (tsMonoMs != null) {\n\t\t\t\t\tconst parsed = Number(tsMonoMs);\n\t\t\t\t\tif (!Number.isFinite(parsed)) {\n\t\t\t\t\t\treturn c.json({ error: \"ts_mono_ms must be number\" }, 400);\n\t\t\t\t\t}\n\t\t\t\t\ttsMonoMs = parsed;\n\t\t\t\t}\n\n\t\t\t\tlet eventPayload = itemObj.payload;\n\t\t\t\tif (eventPayload == null) eventPayload = {};\n\t\t\t\tif (typeof eventPayload !== \"object\" || Array.isArray(eventPayload)) {\n\t\t\t\t\treturn c.json({ error: \"payload must be an object\" }, 400);\n\t\t\t\t}\n\n\t\t\t\t// Per-item meta fields\n\t\t\t\tconst itemCwd = itemObj.cwd;\n\t\t\t\tif (itemCwd != null && typeof itemCwd !== \"string\") {\n\t\t\t\t\treturn c.json({ error: \"cwd must be string\" }, 400);\n\t\t\t\t}\n\t\t\t\tconst itemProject = itemObj.project;\n\t\t\t\tif (itemProject != null && typeof itemProject !== \"string\") {\n\t\t\t\t\treturn c.json({ error: \"project must be string\" }, 400);\n\t\t\t\t}\n\t\t\t\tconst itemStartedAt = itemObj.started_at;\n\t\t\t\tif (itemStartedAt != null && typeof itemStartedAt !== \"string\") {\n\t\t\t\t\treturn c.json({ error: \"started_at must be string\" }, 400);\n\t\t\t\t}\n\n\t\t\t\t// Sanitize payload\n\t\t\t\teventPayload = stripPrivateObj(eventPayload) as Record<string, unknown>;\n\n\t\t\t\t// Generate stable event_id for legacy senders.\n\t\t\t\t// Python uses json.dumps(sort_keys=True) which recursively sorts all keys.\n\t\t\t\t// We replicate with a recursive key-sorting replacer.\n\t\t\t\tif (!eventId) {\n\t\t\t\t\tconst sortedStringify = (obj: unknown): string =>\n\t\t\t\t\t\tJSON.stringify(obj, (_key, value) => {\n\t\t\t\t\t\t\tif (value != null && typeof value === \"object\" && !Array.isArray(value)) {\n\t\t\t\t\t\t\t\tconst sorted: Record<string, unknown> = {};\n\t\t\t\t\t\t\t\tfor (const k of Object.keys(value as Record<string, unknown>).sort()) {\n\t\t\t\t\t\t\t\t\tsorted[k] = (value as Record<string, unknown>)[k];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn sorted;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn value;\n\t\t\t\t\t\t});\n\t\t\t\t\tif (eventSeqValue != null) {\n\t\t\t\t\t\tconst rawId = sortedStringify({\n\t\t\t\t\t\t\ts: eventSeqValue,\n\t\t\t\t\t\t\tt: eventType,\n\t\t\t\t\t\t\tp: eventPayload,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst hash = createHash(\"sha256\").update(rawId, \"utf-8\").digest(\"hex\").slice(0, 16);\n\t\t\t\t\t\teventId = `legacy-seq-${eventSeqValue}-${hash}`;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst rawId = sortedStringify({\n\t\t\t\t\t\t\tm: tsMonoMs ?? null,\n\t\t\t\t\t\t\tp: eventPayload,\n\t\t\t\t\t\t\tt: eventType,\n\t\t\t\t\t\t\tw: tsWallMs ?? null,\n\t\t\t\t\t\t});\n\t\t\t\t\t\teventId = `legacy-${createHash(\"sha256\").update(rawId, \"utf-8\").digest(\"hex\").slice(0, 16)}`;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst eventEntry: Record<string, unknown> = {\n\t\t\t\t\tevent_id: eventId,\n\t\t\t\t\tevent_type: eventType,\n\t\t\t\t\tpayload: eventPayload,\n\t\t\t\t\tts_wall_ms: tsWallMs ?? null,\n\t\t\t\t\tts_mono_ms: tsMonoMs ?? null,\n\t\t\t\t};\n\n\t\t\t\tsessionIds.add(opencodeSessionId);\n\t\t\t\tconst list = batchBySession.get(opencodeSessionId) ?? [];\n\t\t\t\tlist.push({ ...eventEntry });\n\t\t\t\tbatchBySession.set(opencodeSessionId, list);\n\n\t\t\t\tif (itemCwd || itemProject || itemStartedAt) {\n\t\t\t\t\tconst perSession = metaBySession.get(opencodeSessionId) ?? {};\n\t\t\t\t\tif (itemCwd) perSession.cwd = itemCwd as string;\n\t\t\t\t\tif (itemProject) perSession.project = itemProject as string;\n\t\t\t\t\tif (itemStartedAt) perSession.started_at = itemStartedAt as string;\n\t\t\t\t\tmetaBySession.set(opencodeSessionId, perSession);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Insert events\n\t\t\tif (sessionIds.size === 1) {\n\t\t\t\tconst singleSessionId = sessionIds.values().next().value as string;\n\t\t\t\tconst batch = batchBySession.get(singleSessionId) ?? [];\n\t\t\t\tconst result = store.recordRawEventsBatch(singleSessionId, batch);\n\t\t\t\tinserted = result.inserted;\n\t\t\t} else {\n\t\t\t\tfor (const [sid, sidEvents] of batchBySession) {\n\t\t\t\t\tconst result = store.recordRawEventsBatch(sid, sidEvents);\n\t\t\t\t\tinserted += result.inserted;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Update session metadata\n\t\t\tfor (const metaSessionId of sessionIds) {\n\t\t\t\tconst sessionMeta = metaBySession.get(metaSessionId) ?? {};\n\t\t\t\tconst applyRequestMeta = sessionIds.size === 1 || metaSessionId === defaultSessionId;\n\t\t\t\tstore.updateRawEventSessionMeta({\n\t\t\t\t\topencodeSessionId: metaSessionId,\n\t\t\t\t\tcwd:\n\t\t\t\t\t\tsessionMeta.cwd ?? (applyRequestMeta ? (cwd as string | undefined) : undefined) ?? null,\n\t\t\t\t\tproject:\n\t\t\t\t\t\tsessionMeta.project ??\n\t\t\t\t\t\t(applyRequestMeta ? (project as string | undefined) : undefined) ??\n\t\t\t\t\t\tnull,\n\t\t\t\t\tstartedAt:\n\t\t\t\t\t\tsessionMeta.started_at ??\n\t\t\t\t\t\t(applyRequestMeta ? (startedAt as string | undefined) : undefined) ??\n\t\t\t\t\t\tnull,\n\t\t\t\t\tlastSeenTsWallMs: lastSeenBySession.get(metaSessionId) ?? null,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Nudge sweeper once after all metadata updates (mirrors Python note_activity)\n\t\t\tnudgeSweeper(sweeper);\n\n\t\t\treturn c.json({ inserted, received: (items as unknown[]).length });\n\t\t} catch (err) {\n\t\t\tconst response: Record<string, unknown> = { error: \"internal server error\" };\n\t\t\tif (process.env.CODEMEM_VIEWER_DEBUG === \"1\") {\n\t\t\t\tresponse.detail = (err as Error).message;\n\t\t\t}\n\t\t\treturn c.json(response, 500);\n\t\t}\n\t});\n\n\t// POST /api/claude-hooks — ingest Claude Code hook events\n\tapp.post(\"/api/claude-hooks\", async (c) => {\n\t\tconst result = await parseJsonObjectBody(c, MAX_RAW_EVENTS_BODY_BYTES);\n\t\tif (result instanceof Response) return result;\n\t\tconst payload = result;\n\n\t\t// Map hook payload → raw event envelope\n\t\tconst envelope = buildRawEventEnvelopeFromHook(payload);\n\t\tif (envelope === null) {\n\t\t\t// Unsupported event type or missing required fields — skip gracefully\n\t\t\treturn c.json({ inserted: 0, skipped: 1 });\n\t\t}\n\n\t\tconst store = getStore();\n\t\ttry {\n\t\t\tconst opencodeSessionId = envelope.opencode_session_id;\n\t\t\tconst source = envelope.source;\n\t\t\tconst strippedPayload = stripPrivateObj(envelope.payload) as Record<string, unknown>;\n\n\t\t\tconst inserted = store.recordRawEvent({\n\t\t\t\topencodeSessionId,\n\t\t\t\tsource,\n\t\t\t\teventId: envelope.event_id,\n\t\t\t\teventType: \"claude.hook\",\n\t\t\t\tpayload: strippedPayload,\n\t\t\t\ttsWallMs: envelope.ts_wall_ms,\n\t\t\t});\n\n\t\t\tstore.updateRawEventSessionMeta({\n\t\t\t\topencodeSessionId,\n\t\t\t\tsource,\n\t\t\t\tcwd: envelope.cwd,\n\t\t\t\tproject: envelope.project,\n\t\t\t\tstartedAt: envelope.started_at,\n\t\t\t\tlastSeenTsWallMs: envelope.ts_wall_ms,\n\t\t\t});\n\n\t\t\t// Nudge sweeper to process promptly\n\t\t\tnudgeSweeper(sweeper);\n\n\t\t\treturn c.json({ inserted: inserted ? 1 : 0, skipped: 0 });\n\t\t} catch (err) {\n\t\t\tconst response: Record<string, unknown> = { error: \"internal server error\" };\n\t\t\tif (process.env.CODEMEM_VIEWER_DEBUG === \"1\") {\n\t\t\t\tresponse.detail = (err as Error).message;\n\t\t\t}\n\t\t\treturn c.json(response, 500);\n\t\t}\n\t});\n\n\treturn app;\n}\n","/**\n * Stats routes — GET /api/stats, GET /api/usage.\n *\n * Ports Python's viewer_routes/stats.py.\n */\n\nimport type { MemoryStore } from \"@codemem/core\";\nimport { Hono } from \"hono\";\n\n/**\n * Create stats routes. The store factory is called per-request to get a\n * fresh connection (matching the Python viewer pattern).\n */\nexport function statsRoutes(getStore: () => MemoryStore) {\n\tconst app = new Hono();\n\n\tapp.get(\"/api/stats\", (c) => {\n\t\tconst store = getStore();\n\t\treturn c.json(store.stats());\n\t});\n\n\tapp.get(\"/api/usage\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst projectFilter = c.req.query(\"project\") || null;\n\t\t\tconst eventsGlobal = store.db\n\t\t\t\t.prepare(\n\t\t\t\t\t`SELECT event,\n\t\t\t\t\t\tSUM(tokens_read) AS total_tokens_read,\n\t\t\t\t\t\tSUM(tokens_written) AS total_tokens_written,\n\t\t\t\t\t\tSUM(tokens_saved) AS total_tokens_saved,\n\t\t\t\t\t\tCOUNT(*) AS count\n\t\t\t\t\t FROM usage_events GROUP BY event ORDER BY event`,\n\t\t\t\t)\n\t\t\t\t.all() as Record<string, unknown>[];\n\t\t\tconst totalsGlobal = store.db\n\t\t\t\t.prepare(\n\t\t\t\t\t`SELECT COALESCE(SUM(tokens_read),0) AS tokens_read,\n\t\t\t\t\t\tCOALESCE(SUM(tokens_written),0) AS tokens_written,\n\t\t\t\t\t\tCOALESCE(SUM(tokens_saved),0) AS tokens_saved,\n\t\t\t\t\t\tCOUNT(*) AS count\n\t\t\t\t\t FROM usage_events`,\n\t\t\t\t)\n\t\t\t\t.get() as Record<string, unknown>;\n\t\t\tlet eventsFiltered: Record<string, unknown>[] | null = null;\n\t\t\tlet totalsFiltered: Record<string, unknown> | null = null;\n\t\t\tif (projectFilter) {\n\t\t\t\teventsFiltered = store.db\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t`SELECT event,\n\t\t\t\t\t\t\tSUM(tokens_read) AS total_tokens_read,\n\t\t\t\t\t\t\tSUM(tokens_written) AS total_tokens_written,\n\t\t\t\t\t\t\tSUM(tokens_saved) AS total_tokens_saved,\n\t\t\t\t\t\t\tCOUNT(*) AS count\n\t\t\t\t\t\t FROM usage_events\n\t\t\t\t\t\t JOIN sessions ON sessions.id = usage_events.session_id\n\t\t\t\t\t\t WHERE sessions.project = ?\n\t\t\t\t\t\t GROUP BY event ORDER BY event`,\n\t\t\t\t\t)\n\t\t\t\t\t.all(projectFilter) as Record<string, unknown>[];\n\t\t\t\ttotalsFiltered = store.db\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t`SELECT COALESCE(SUM(tokens_read),0) AS tokens_read,\n\t\t\t\t\t\t\tCOALESCE(SUM(tokens_written),0) AS tokens_written,\n\t\t\t\t\t\t\tCOALESCE(SUM(tokens_saved),0) AS tokens_saved,\n\t\t\t\t\t\t\tCOUNT(*) AS count\n\t\t\t\t\t\t FROM usage_events\n\t\t\t\t\t\t JOIN sessions ON sessions.id = usage_events.session_id\n\t\t\t\t\t\t WHERE sessions.project = ?`,\n\t\t\t\t\t)\n\t\t\t\t\t.get(projectFilter) as Record<string, unknown>;\n\t\t\t}\n\t\t\treturn c.json({\n\t\t\t\tproject: projectFilter,\n\t\t\t\tevents: projectFilter ? eventsFiltered : eventsGlobal,\n\t\t\t\ttotals: projectFilter ? totalsFiltered : totalsGlobal,\n\t\t\t\tevents_global: eventsGlobal,\n\t\t\t\ttotals_global: totalsGlobal,\n\t\t\t\tevents_filtered: eventsFiltered,\n\t\t\t\ttotals_filtered: totalsFiltered,\n\t\t\t\trecent_packs: [],\n\t\t\t});\n\t\t}\n\t});\n\n\treturn app;\n}\n","/**\n * Sync routes — status, peers, actors, attempts, pairing, mutations.\n */\n\nimport type { MemoryStore } from \"@codemem/core\";\nimport { ensureDeviceIdentity, schema } from \"@codemem/core\";\nimport { count, desc, eq, max, ne } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/better-sqlite3\";\nimport { Hono } from \"hono\";\nimport { queryBool, queryInt, safeJsonList } from \"../helpers.js\";\n\ntype StoreFactory = () => MemoryStore;\n\nconst SYNC_STALE_AFTER_SECONDS = 10 * 60;\n\nconst PAIRING_FILTER_HINT =\n\t\"Run this on another device with codemem sync pair --accept '<payload>'. \" +\n\t\"On that accepting device, --include/--exclude only control what it sends to peers. \" +\n\t\"This device does not yet enforce incoming project filters.\";\n\n// ---------------------------------------------------------------------------\n// Peer row mapping — deduplicated helper (fix #4)\n// ---------------------------------------------------------------------------\n\n/**\n * Map a raw sync_peers DB row to the API response shape.\n * When showDiag is false, sensitive fields (fingerprint, last_error, addresses)\n * are redacted.\n */\nfunction mapPeerRow(row: Record<string, unknown>, showDiag: boolean): Record<string, unknown> {\n\treturn {\n\t\tpeer_device_id: row.peer_device_id,\n\t\tname: row.name,\n\t\tfingerprint: showDiag ? row.pinned_fingerprint : null,\n\t\tpinned: Boolean(row.pinned_fingerprint),\n\t\taddresses: showDiag ? safeJsonList(row.addresses_json as string | null) : [],\n\t\tlast_seen_at: row.last_seen_at,\n\t\tlast_sync_at: row.last_sync_at,\n\t\tlast_error: showDiag ? row.last_error : null,\n\t\thas_error: Boolean(row.last_error),\n\t\tclaimed_local_actor: Boolean(row.claimed_local_actor),\n\t\tactor_id: row.actor_id ?? null,\n\t\tactor_display_name: row.actor_display_name ?? null,\n\t\tproject_scope: {\n\t\t\tinclude: safeJsonList(row.projects_include_json as string | null),\n\t\t\texclude: safeJsonList(row.projects_exclude_json as string | null),\n\t\t\teffective_include: safeJsonList(row.projects_include_json as string | null),\n\t\t\teffective_exclude: safeJsonList(row.projects_exclude_json as string | null),\n\t\t\tinherits_global: row.projects_include_json == null && row.projects_exclude_json == null,\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Peer status helpers\n// ---------------------------------------------------------------------------\n\nfunction isRecentIso(value: unknown, windowS = SYNC_STALE_AFTER_SECONDS): boolean {\n\tconst raw = String(value ?? \"\").trim();\n\tif (!raw) return false;\n\tconst normalized = raw.replace(\"Z\", \"+00:00\");\n\tconst hasOffset = /(?:Z|[+-]\\d{2}:?\\d{2})$/i.test(raw);\n\tconst ts = new Date(hasOffset ? normalized : `${normalized}+00:00`);\n\tif (Number.isNaN(ts.getTime())) return false;\n\tconst ageS = (Date.now() - ts.getTime()) / 1000;\n\treturn ageS >= 0 && ageS <= windowS;\n}\n\nfunction peerStatus(peer: Record<string, unknown>): Record<string, unknown> {\n\tconst lastSyncAt = peer.last_sync_at;\n\tconst lastPingAt = peer.last_seen_at;\n\tconst hasError = Boolean(peer.has_error);\n\n\tconst syncFresh = isRecentIso(lastSyncAt);\n\tconst pingFresh = isRecentIso(lastPingAt);\n\n\tlet peerState: string;\n\tif (hasError && !(syncFresh || pingFresh)) peerState = \"offline\";\n\telse if (hasError) peerState = \"degraded\";\n\telse if (syncFresh || pingFresh) peerState = \"online\";\n\telse if (lastSyncAt || lastPingAt) peerState = \"stale\";\n\telse peerState = \"unknown\";\n\n\tconst syncStatus = hasError ? \"error\" : syncFresh ? \"ok\" : lastSyncAt ? \"stale\" : \"unknown\";\n\tconst pingStatus = pingFresh ? \"ok\" : lastPingAt ? \"stale\" : \"unknown\";\n\n\treturn {\n\t\tsync_status: syncStatus,\n\t\tping_status: pingStatus,\n\t\tpeer_state: peerState,\n\t\tfresh: syncFresh || pingFresh,\n\t\tlast_sync_at: lastSyncAt,\n\t\tlast_ping_at: lastPingAt,\n\t};\n}\n\nfunction attemptStatus(attempt: Record<string, unknown>): string {\n\tif (attempt.ok) return \"ok\";\n\tif (attempt.error) return \"error\";\n\treturn \"unknown\";\n}\n\nconst PEERS_QUERY = `\n\tSELECT p.peer_device_id, p.name, p.pinned_fingerprint, p.addresses_json,\n\t p.last_seen_at, p.last_sync_at, p.last_error,\n\t p.projects_include_json, p.projects_exclude_json, p.claimed_local_actor,\n\t p.actor_id, a.display_name AS actor_display_name\n\tFROM sync_peers AS p\n\tLEFT JOIN actors AS a ON a.actor_id = p.actor_id\n\tORDER BY name, peer_device_id\n`;\n\n// ---------------------------------------------------------------------------\n// Route factory\n// ---------------------------------------------------------------------------\n\nexport function syncRoutes(getStore: StoreFactory) {\n\tconst app = new Hono();\n\n\t// GET /api/sync/status\n\tapp.get(\"/api/sync/status\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst showDiag = queryBool(c.req.query(\"includeDiagnostics\"));\n\t\t\tconst _project = c.req.query(\"project\") || null;\n\n\t\t\tconst d = drizzle(store.db, { schema });\n\n\t\t\tconst deviceRow = d\n\t\t\t\t.select({\n\t\t\t\t\tdevice_id: schema.syncDevice.device_id,\n\t\t\t\t\tfingerprint: schema.syncDevice.fingerprint,\n\t\t\t\t})\n\t\t\t\t.from(schema.syncDevice)\n\t\t\t\t.limit(1)\n\t\t\t\t.get();\n\n\t\t\tconst daemonState = d\n\t\t\t\t.select()\n\t\t\t\t.from(schema.syncDaemonState)\n\t\t\t\t.where(eq(schema.syncDaemonState.id, 1))\n\t\t\t\t.get();\n\n\t\t\tconst peerCountRow = d.select({ total: count() }).from(schema.syncPeers).get();\n\n\t\t\tconst lastSyncRow = d\n\t\t\t\t.select({ last_sync_at: max(schema.syncPeers.last_sync_at) })\n\t\t\t\t.from(schema.syncPeers)\n\t\t\t\t.get();\n\n\t\t\tconst lastError = daemonState?.last_error as string | null;\n\t\t\tconst lastErrorAt = daemonState?.last_error_at as string | null;\n\t\t\tconst lastOkAt = daemonState?.last_ok_at as string | null;\n\n\t\t\tlet daemonStateValue = \"ok\";\n\t\t\t// Simplified: without full config access, default to enabled\n\t\t\tif (lastError && (!lastOkAt || String(lastOkAt) < String(lastErrorAt ?? \"\"))) {\n\t\t\t\tdaemonStateValue = \"error\";\n\t\t\t}\n\n\t\t\tconst statusPayload: Record<string, unknown> = {\n\t\t\t\tenabled: true,\n\t\t\t\tinterval_s: 60,\n\t\t\t\tpeer_count: Number(peerCountRow?.total ?? 0),\n\t\t\t\tlast_sync_at: lastSyncRow?.last_sync_at ?? null,\n\t\t\t\tdaemon_state: daemonStateValue,\n\t\t\t\tdaemon_running: false,\n\t\t\t\tdaemon_detail: null,\n\t\t\t\tproject_filter_active: false,\n\t\t\t\tproject_filter: { include: [], exclude: [] },\n\t\t\t\tredacted: !showDiag,\n\t\t\t};\n\n\t\t\tif (showDiag) {\n\t\t\t\tstatusPayload.device_id = deviceRow?.device_id ?? null;\n\t\t\t\tstatusPayload.fingerprint = deviceRow?.fingerprint ?? null;\n\t\t\t\tstatusPayload.bind = null;\n\t\t\t\tstatusPayload.daemon_last_error = lastError;\n\t\t\t\tstatusPayload.daemon_last_error_at = lastErrorAt;\n\t\t\t\tstatusPayload.daemon_last_ok_at = lastOkAt;\n\t\t\t}\n\n\t\t\t// Build peers list using deduplicated mapPeerRow\n\t\t\tconst peerRows = store.db.prepare(PEERS_QUERY).all() as Record<string, unknown>[];\n\t\t\tconst peersItems = peerRows.map((row) => {\n\t\t\t\tconst peer = mapPeerRow(row, showDiag);\n\t\t\t\tpeer.status = peerStatus(peer);\n\t\t\t\treturn peer;\n\t\t\t});\n\n\t\t\tconst peersMap: Record<string, unknown> = {};\n\t\t\tfor (const peer of peersItems) {\n\t\t\t\tpeersMap[String(peer.peer_device_id)] = peer.status;\n\t\t\t}\n\n\t\t\t// Attempts\n\t\t\tconst attemptRows = d\n\t\t\t\t.select({\n\t\t\t\t\tpeer_device_id: schema.syncAttempts.peer_device_id,\n\t\t\t\t\tok: schema.syncAttempts.ok,\n\t\t\t\t\terror: schema.syncAttempts.error,\n\t\t\t\t\tstarted_at: schema.syncAttempts.started_at,\n\t\t\t\t\tfinished_at: schema.syncAttempts.finished_at,\n\t\t\t\t\tops_in: schema.syncAttempts.ops_in,\n\t\t\t\t\tops_out: schema.syncAttempts.ops_out,\n\t\t\t\t})\n\t\t\t\t.from(schema.syncAttempts)\n\t\t\t\t.orderBy(desc(schema.syncAttempts.finished_at))\n\t\t\t\t.limit(25)\n\t\t\t\t.all();\n\t\t\tconst attemptsItems = attemptRows.map((row) => ({\n\t\t\t\t...row,\n\t\t\t\tstatus: attemptStatus(row),\n\t\t\t\taddress: null,\n\t\t\t}));\n\n\t\t\tconst statusBlock = {\n\t\t\t\t...statusPayload,\n\t\t\t\tpeers: peersMap,\n\t\t\t\tpending: 0,\n\t\t\t\tsync: {},\n\t\t\t\tping: {},\n\t\t\t};\n\n\t\t\treturn c.json({\n\t\t\t\t...statusPayload,\n\t\t\t\tstatus: statusBlock,\n\t\t\t\tpeers: peersItems,\n\t\t\t\tattempts: attemptsItems.slice(0, 5),\n\t\t\t\tlegacy_devices: [],\n\t\t\t\tsharing_review: { unreviewed: 0 },\n\t\t\t\tcoordinator: { enabled: false, configured: false },\n\t\t\t\tjoin_requests: [],\n\t\t\t});\n\t\t}\n\t});\n\n\t// GET /api/sync/peers\n\tapp.get(\"/api/sync/peers\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst showDiag = queryBool(c.req.query(\"includeDiagnostics\"));\n\t\t\tconst rows = store.db.prepare(PEERS_QUERY).all() as Record<string, unknown>[];\n\t\t\t// Use deduplicated mapPeerRow helper (fix #4)\n\t\t\tconst peers = rows.map((row) => mapPeerRow(row, showDiag));\n\t\t\treturn c.json({ items: peers, redacted: !showDiag });\n\t\t}\n\t});\n\n\t// GET /api/sync/actors\n\tapp.get(\"/api/sync/actors\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tconst includeMerged = queryBool(c.req.query(\"includeMerged\"));\n\t\t\tconst query = d.select().from(schema.actors);\n\t\t\tconst rows = includeMerged\n\t\t\t\t? query.orderBy(schema.actors.display_name).all()\n\t\t\t\t: query.where(ne(schema.actors.status, \"merged\")).orderBy(schema.actors.display_name).all();\n\t\t\treturn c.json({ items: rows });\n\t\t}\n\t});\n\n\t// GET /api/sync/attempts\n\tapp.get(\"/api/sync/attempts\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tlet limit = queryInt(c.req.query(\"limit\"), 25);\n\t\t\tif (limit <= 0) return c.json({ error: \"invalid_limit\" }, 400);\n\t\t\tlimit = Math.min(limit, 500);\n\t\t\tconst rows = d\n\t\t\t\t.select({\n\t\t\t\t\tpeer_device_id: schema.syncAttempts.peer_device_id,\n\t\t\t\t\tok: schema.syncAttempts.ok,\n\t\t\t\t\terror: schema.syncAttempts.error,\n\t\t\t\t\tstarted_at: schema.syncAttempts.started_at,\n\t\t\t\t\tfinished_at: schema.syncAttempts.finished_at,\n\t\t\t\t\tops_in: schema.syncAttempts.ops_in,\n\t\t\t\t\tops_out: schema.syncAttempts.ops_out,\n\t\t\t\t})\n\t\t\t\t.from(schema.syncAttempts)\n\t\t\t\t.orderBy(desc(schema.syncAttempts.finished_at))\n\t\t\t\t.limit(limit)\n\t\t\t\t.all();\n\t\t\treturn c.json({ items: rows });\n\t\t}\n\t});\n\n\t// GET /api/sync/pairing — uses ensureDeviceIdentity from core (fix #5 context)\n\tapp.get(\"/api/sync/pairing\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst showDiag = queryBool(c.req.query(\"includeDiagnostics\"));\n\t\t\tif (!showDiag) {\n\t\t\t\treturn c.json({\n\t\t\t\t\tredacted: true,\n\t\t\t\t\tpairing_filter_hint: PAIRING_FILTER_HINT,\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tconst deviceRow = d\n\t\t\t\t.select({\n\t\t\t\t\tdevice_id: schema.syncDevice.device_id,\n\t\t\t\t\tpublic_key: schema.syncDevice.public_key,\n\t\t\t\t\tfingerprint: schema.syncDevice.fingerprint,\n\t\t\t\t})\n\t\t\t\t.from(schema.syncDevice)\n\t\t\t\t.limit(1)\n\t\t\t\t.get();\n\n\t\t\tlet deviceId: string | undefined;\n\t\t\tlet publicKey: string | undefined;\n\t\t\tlet fingerprint: string | undefined;\n\n\t\t\tif (deviceRow) {\n\t\t\t\tdeviceId = String(deviceRow.device_id);\n\t\t\t\tpublicKey = String(deviceRow.public_key);\n\t\t\t\tfingerprint = String(deviceRow.fingerprint);\n\t\t\t} else {\n\t\t\t\t// Fall back to ensureDeviceIdentity if no row exists\n\t\t\t\ttry {\n\t\t\t\t\tconst [id, fp] = ensureDeviceIdentity(store.db);\n\t\t\t\t\tdeviceId = id;\n\t\t\t\t\tfingerprint = fp;\n\t\t\t\t\tconst newRow = d\n\t\t\t\t\t\t.select({ public_key: schema.syncDevice.public_key })\n\t\t\t\t\t\t.from(schema.syncDevice)\n\t\t\t\t\t\t.where(eq(schema.syncDevice.device_id, id))\n\t\t\t\t\t\t.get();\n\t\t\t\t\tpublicKey = newRow?.public_key ?? \"\";\n\t\t\t\t} catch {\n\t\t\t\t\treturn c.json({ error: \"device identity unavailable\" }, 500);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!deviceId || !fingerprint) {\n\t\t\t\treturn c.json({ error: \"public key missing\" }, 500);\n\t\t\t}\n\n\t\t\treturn c.json({\n\t\t\t\tdevice_id: deviceId,\n\t\t\t\tfingerprint,\n\t\t\t\tpublic_key: publicKey ?? null,\n\t\t\t\tpairing_filter_hint: PAIRING_FILTER_HINT,\n\t\t\t\taddresses: [],\n\t\t\t});\n\t\t}\n\t});\n\n\t// ------------------------------------------------------------------\n\t// POST mutations\n\t// ------------------------------------------------------------------\n\n\t// POST /api/sync/peers/rename\n\tapp.post(\"/api/sync/peers/rename\", async (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tconst body = await c.req.json<Record<string, unknown>>();\n\t\t\tconst peerDeviceId = String(body.peer_device_id ?? \"\").trim();\n\t\t\tconst name = String(body.name ?? \"\").trim();\n\t\t\tif (!peerDeviceId) return c.json({ error: \"peer_device_id required\" }, 400);\n\t\t\tif (!name) return c.json({ error: \"name required\" }, 400);\n\t\t\tconst exists = d\n\t\t\t\t.select({ peer_device_id: schema.syncPeers.peer_device_id })\n\t\t\t\t.from(schema.syncPeers)\n\t\t\t\t.where(eq(schema.syncPeers.peer_device_id, peerDeviceId))\n\t\t\t\t.get();\n\t\t\tif (!exists) return c.json({ error: \"peer not found\" }, 404);\n\t\t\td.update(schema.syncPeers)\n\t\t\t\t.set({ name })\n\t\t\t\t.where(eq(schema.syncPeers.peer_device_id, peerDeviceId))\n\t\t\t\t.run();\n\t\t\treturn c.json({ ok: true });\n\t\t}\n\t});\n\n\t// DELETE /api/sync/peers/:peer_device_id\n\tapp.delete(\"/api/sync/peers/:peer_device_id\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tconst peerDeviceId = c.req.param(\"peer_device_id\")?.trim();\n\t\t\tif (!peerDeviceId) return c.json({ error: \"peer_device_id required\" }, 400);\n\t\t\tconst exists = d\n\t\t\t\t.select({ peer_device_id: schema.syncPeers.peer_device_id })\n\t\t\t\t.from(schema.syncPeers)\n\t\t\t\t.where(eq(schema.syncPeers.peer_device_id, peerDeviceId))\n\t\t\t\t.get();\n\t\t\tif (!exists) return c.json({ error: \"peer not found\" }, 404);\n\t\t\td.delete(schema.syncPeers).where(eq(schema.syncPeers.peer_device_id, peerDeviceId)).run();\n\t\t\treturn c.json({ ok: true });\n\t\t}\n\t});\n\n\treturn app;\n}\n","/**\n * @codemem/server — HTTP server (viewer, sync, API).\n *\n * Single HTTP server handling viewer routes and sync daemon.\n * Shares one better-sqlite3 connection between viewer and sync.\n * Embedding inference runs in a worker_thread (lazy-started).\n *\n * Entry: `codemem serve`\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { MemoryStore, type RawEventSweeper, resolveDbPath, VERSION } from \"@codemem/core\";\nimport { serveStatic } from \"@hono/node-server/serve-static\";\nimport { Hono } from \"hono\";\nimport { originGuard, preflightHandler } from \"./middleware.js\";\nimport { configRoutes } from \"./routes/config.js\";\nimport { memoryRoutes } from \"./routes/memory.js\";\nimport { observerStatusRoutes } from \"./routes/observer-status.js\";\nimport { rawEventsRoutes } from \"./routes/raw-events.js\";\nimport { statsRoutes } from \"./routes/stats.js\";\nimport { syncRoutes } from \"./routes/sync.js\";\n\nexport { VERSION };\n\n/** Shared store instance — SQLite WAL mode handles concurrent reads safely. */\nlet sharedStore: MemoryStore | null = null;\n\n/** Get (or create) the shared store instance. Exported so the sweeper can share it. */\nexport function getStore(): MemoryStore {\n\tif (!sharedStore) {\n\t\tsharedStore = new MemoryStore(resolveDbPath());\n\t}\n\treturn sharedStore;\n}\n\n/** Close the shared store (called on shutdown). */\nexport function closeStore(): void {\n\tsharedStore?.close();\n\tsharedStore = null;\n}\n\n/**\n * Create the Hono app with all viewer routes.\n * Exported for testing — pass a custom store factory to inject test DBs.\n */\nexport interface AppOptions {\n\tstoreFactory?: () => MemoryStore;\n\tsweeper?: RawEventSweeper | null;\n}\n\nexport function createApp(opts?: AppOptions) {\n\tconst storeFactory = opts?.storeFactory ?? getStore;\n\tconst sweeper = opts?.sweeper ?? null;\n\tconst app = new Hono();\n\n\t// CORS / origin guard\n\tapp.use(\"*\", preflightHandler());\n\tapp.use(\"*\", originGuard());\n\n\t// API routes\n\tapp.route(\"/\", statsRoutes(storeFactory));\n\tapp.route(\"/\", memoryRoutes(storeFactory));\n\tapp.route(\"/\", observerStatusRoutes({ getStore: storeFactory, getSweeper: () => sweeper }));\n\tapp.route(\"/\", configRoutes({ getSweeper: () => sweeper }));\n\tapp.route(\"/\", rawEventsRoutes(storeFactory, sweeper));\n\tapp.route(\"/\", syncRoutes(storeFactory));\n\n\t// Static assets — serve under /assets/*\n\t// Resolves to packages/viewer-server/static/ both in dev and when installed from npm.\n\tconst staticRoot =\n\t\tprocess.env.CODEMEM_VIEWER_STATIC_DIR ?? join(import.meta.dirname ?? \".\", \"../static\");\n\n\tapp.use(\n\t\t\"/assets/*\",\n\t\tserveStatic({\n\t\t\troot: staticRoot,\n\t\t\trewriteRequestPath: (path) => path.replace(/^\\/assets/, \"\"),\n\t\t}),\n\t);\n\n\t// SPA — serve index.html for root and all client-side routes\n\tconst indexHtml = readFileSync(join(staticRoot, \"index.html\"), \"utf-8\");\n\tapp.get(\"*\", (c) => {\n\t\tif (c.req.path.startsWith(\"/api/\")) {\n\t\t\treturn c.json({ error: \"not found\" }, 404);\n\t\t}\n\t\treturn c.html(indexHtml);\n\t});\n\n\treturn app;\n}\n\n// No auto-start — the CLI's `serve` command owns server startup.\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CORS and cross-origin protection middleware.
|
|
3
|
+
*
|
|
4
|
+
* Ports Python's reject_cross_origin() logic from codemem/viewer_http.py.
|
|
5
|
+
* GETs are allowed from any origin (viewer is local-only).
|
|
6
|
+
* Mutations (POST/DELETE/PATCH/PUT) require an Origin header matching a
|
|
7
|
+
* loopback address, or are rejected with 403.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Cross-origin protection middleware.
|
|
11
|
+
*
|
|
12
|
+
* Ports Python's `reject_cross_origin(missing_origin_policy="reject_if_unsafe")`:
|
|
13
|
+
*
|
|
14
|
+
* - GET/HEAD/OPTIONS: allowed from any origin (viewer is local-only).
|
|
15
|
+
* - POST/DELETE/PATCH/PUT:
|
|
16
|
+
* - Origin present + loopback → allowed (browser on localhost)
|
|
17
|
+
* - Origin present + non-loopback → rejected 403
|
|
18
|
+
* - No Origin + no suspicious browser signals → allowed (CLI callers)
|
|
19
|
+
* - No Origin + suspicious Sec-Fetch-Site/Referer → rejected 403
|
|
20
|
+
*
|
|
21
|
+
* For same-origin requests (no Origin header) on safe methods, no
|
|
22
|
+
* Access-Control-Allow-Origin is set — the browser doesn't need it.
|
|
23
|
+
* For valid loopback origins, ACAO is echoed back.
|
|
24
|
+
*/
|
|
25
|
+
export declare function originGuard(): import("hono").MiddlewareHandler<any, any, {}, Response | (Response & import("hono").TypedResponse<{
|
|
26
|
+
error: string;
|
|
27
|
+
}, 403, "json">)>;
|
|
28
|
+
/**
|
|
29
|
+
* Handle OPTIONS preflight requests.
|
|
30
|
+
* Returns 204 with appropriate CORS headers for loopback origins.
|
|
31
|
+
*/
|
|
32
|
+
export declare function preflightHandler(): import("hono").MiddlewareHandler<any, any, {}, Response | (Response & import("hono").TypedResponse<null, 204, "body">)>;
|
|
33
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA4CH;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW;;kBAkC1B;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,4HAgB/B"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config routes — GET /api/config, POST /api/config.
|
|
3
|
+
*
|
|
4
|
+
* Ports the user-facing config read/write path from Python's
|
|
5
|
+
* codemem/viewer_routes/config.py, scoped to the TS runtime's current needs.
|
|
6
|
+
*/
|
|
7
|
+
import { type RawEventSweeper } from "@codemem/core";
|
|
8
|
+
import { Hono } from "hono";
|
|
9
|
+
export interface ConfigRouteOptions {
|
|
10
|
+
getSweeper?: () => RawEventSweeper | null;
|
|
11
|
+
}
|
|
12
|
+
export declare function configRoutes(opts?: ConfigRouteOptions): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
13
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/routes/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAIN,KAAK,eAAe,EAKpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAuD5B,MAAM,WAAW,kBAAkB;IAClC,UAAU,CAAC,EAAE,MAAM,eAAe,GAAG,IAAI,CAAC;CAC1C;AAgLD,wBAAgB,YAAY,CAAC,IAAI,GAAE,kBAAuB,8EAkGzD"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory routes — observations, summaries, sessions, projects, pack, artifacts.
|
|
3
|
+
*/
|
|
4
|
+
import type { MemoryStore } from "@codemem/core";
|
|
5
|
+
import { Hono } from "hono";
|
|
6
|
+
type StoreFactory = () => MemoryStore;
|
|
7
|
+
export declare function memoryRoutes(getStore: StoreFactory): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/routes/memory.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAIjD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,KAAK,YAAY,GAAG,MAAM,WAAW,CAAC;AAyDtC,wBAAgB,YAAY,CAAC,QAAQ,EAAE,YAAY,8EAwPlD"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observer status route — GET /api/observer-status.
|
|
3
|
+
*
|
|
4
|
+
* Ports Python's viewer_routes/observer_status.py.
|
|
5
|
+
* Returns observer runtime info, credential availability, and queue status.
|
|
6
|
+
*/
|
|
7
|
+
import type { MemoryStore, RawEventSweeper } from "@codemem/core";
|
|
8
|
+
import { Hono } from "hono";
|
|
9
|
+
type StoreFactory = () => MemoryStore;
|
|
10
|
+
export interface ObserverStatusDeps {
|
|
11
|
+
getStore: StoreFactory;
|
|
12
|
+
getSweeper: () => RawEventSweeper | null;
|
|
13
|
+
}
|
|
14
|
+
export declare function observerStatusRoutes(deps?: ObserverStatusDeps): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=observer-status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observer-status.d.ts","sourceRoot":"","sources":["../../src/routes/observer-status.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,KAAK,YAAY,GAAG,MAAM,WAAW,CAAC;AAEtC,MAAM,WAAW,kBAAkB;IAClC,QAAQ,EAAE,YAAY,CAAC;IACvB,UAAU,EAAE,MAAM,eAAe,GAAG,IAAI,CAAC;CACzC;AAiBD,wBAAgB,oBAAoB,CAAC,IAAI,CAAC,EAAE,kBAAkB,8EA2C7D"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raw events routes — GET & POST /api/raw-events, GET /api/raw-events/status,
|
|
3
|
+
* POST /api/claude-hooks.
|
|
4
|
+
*/
|
|
5
|
+
import type { MemoryStore, RawEventSweeper } from "@codemem/core";
|
|
6
|
+
import { Hono } from "hono";
|
|
7
|
+
type StoreFactory = () => MemoryStore;
|
|
8
|
+
export declare function rawEventsRoutes(getStore: StoreFactory, sweeper?: RawEventSweeper | null): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=raw-events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"raw-events.d.ts","sourceRoot":"","sources":["../../src/routes/raw-events.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAIlE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,KAAK,YAAY,GAAG,MAAM,WAAW,CAAC;AAqFtC,wBAAgB,eAAe,CAAC,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,8EA0UvF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stats routes — GET /api/stats, GET /api/usage.
|
|
3
|
+
*
|
|
4
|
+
* Ports Python's viewer_routes/stats.py.
|
|
5
|
+
*/
|
|
6
|
+
import type { MemoryStore } from "@codemem/core";
|
|
7
|
+
import { Hono } from "hono";
|
|
8
|
+
/**
|
|
9
|
+
* Create stats routes. The store factory is called per-request to get a
|
|
10
|
+
* fresh connection (matching the Python viewer pattern).
|
|
11
|
+
*/
|
|
12
|
+
export declare function statsRoutes(getStore: () => MemoryStore): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
13
|
+
//# sourceMappingURL=stats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/routes/stats.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,WAAW,8EAyEtD"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync routes — status, peers, actors, attempts, pairing, mutations.
|
|
3
|
+
*/
|
|
4
|
+
import type { MemoryStore } from "@codemem/core";
|
|
5
|
+
import { Hono } from "hono";
|
|
6
|
+
type StoreFactory = () => MemoryStore;
|
|
7
|
+
export declare function syncRoutes(getStore: StoreFactory): Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/routes/sync.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAIjD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,KAAK,YAAY,GAAG,MAAM,WAAW,CAAC;AAyGtC,wBAAgB,UAAU,CAAC,QAAQ,EAAE,YAAY,8EAyRhD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewer-html.d.ts","sourceRoot":"","sources":["../src/viewer-html.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,wBAAgB,UAAU,IAAI,MAAM,CAanC"}
|
package/package.json
CHANGED
|
@@ -1 +1,45 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"name": "@codemem/server",
|
|
3
|
+
"version": "0.20.0-alpha.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"source": "./src/index.ts",
|
|
8
|
+
"import": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist/",
|
|
14
|
+
"static/"
|
|
15
|
+
],
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@hono/node-server": "^1.14.3",
|
|
18
|
+
"drizzle-orm": "^0.45.1",
|
|
19
|
+
"hono": "^4.7.10",
|
|
20
|
+
"@codemem/core": "^0.20.0-alpha.2"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
24
|
+
"@types/node": "^22.15.21",
|
|
25
|
+
"better-sqlite3": "^12.8.0",
|
|
26
|
+
"sqlite-vec": "0.1.7-alpha.2",
|
|
27
|
+
"typescript": "^5.8.2",
|
|
28
|
+
"vite": "^8.0.0",
|
|
29
|
+
"vitest": "^3.1.4"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/kunickiaj/codemem.git",
|
|
34
|
+
"directory": "packages/viewer-server"
|
|
35
|
+
},
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"clean": "rm -rf dist",
|
|
42
|
+
"build": "pnpm exec vite build --ssr src/index.ts --outDir dist",
|
|
43
|
+
"typecheck": "pnpm exec tsc --noEmit"
|
|
44
|
+
}
|
|
45
|
+
}
|