@runtimescope/collector 0.9.2 → 0.10.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.
@@ -10,16 +10,17 @@ import {
10
10
  SessionManager,
11
11
  SqliteStore,
12
12
  isSqliteAvailable,
13
+ migrateProjectIds,
13
14
  resolveTlsConfig
14
- } from "./chunk-GENCCHYK.js";
15
+ } from "./chunk-WWFIEANS.js";
15
16
  import "./chunk-UP2VWCW5.js";
16
17
 
17
18
  // src/standalone.ts
18
19
  import { existsSync } from "fs";
19
20
  import { join } from "path";
20
21
  var HOST = process.env.RUNTIMESCOPE_HOST ?? "127.0.0.1";
21
- var COLLECTOR_PORT = parseInt(process.env.RUNTIMESCOPE_PORT ?? "9090", 10);
22
- var HTTP_PORT = parseInt(process.env.RUNTIMESCOPE_HTTP_PORT ?? "9091", 10);
22
+ var COLLECTOR_PORT = parseInt(process.env.RUNTIMESCOPE_PORT ?? "6767", 10);
23
+ var HTTP_PORT = parseInt(process.env.RUNTIMESCOPE_HTTP_PORT ?? "6768", 10);
23
24
  var BUFFER_SIZE = parseInt(process.env.RUNTIMESCOPE_BUFFER_SIZE ?? "10000", 10);
24
25
  var RETENTION_DAYS = parseInt(process.env.RUNTIMESCOPE_RETENTION_DAYS ?? "30", 10);
25
26
  async function main() {
@@ -27,9 +28,18 @@ async function main() {
27
28
  const projectManager = new ProjectManager();
28
29
  projectManager.ensureGlobalDir();
29
30
  const globalConfig = projectManager.getGlobalConfig();
31
+ const envAuthToken = process.env.RUNTIMESCOPE_AUTH_TOKEN;
32
+ const authFromEnv = envAuthToken ? {
33
+ enabled: true,
34
+ apiKeys: envAuthToken.split(",").map((t) => t.trim()).filter(Boolean).map((key, i) => ({
35
+ key,
36
+ label: `env-token-${i + 1}`,
37
+ createdAt: Date.now()
38
+ }))
39
+ } : null;
30
40
  const authManager = new AuthManager({
31
- enabled: globalConfig.auth?.enabled ?? false,
32
- apiKeys: globalConfig.auth?.apiKeys ?? []
41
+ enabled: authFromEnv?.enabled ?? globalConfig.auth?.enabled ?? false,
42
+ apiKeys: authFromEnv?.apiKeys ?? globalConfig.auth?.apiKeys ?? []
33
43
  });
34
44
  const tlsConfig = resolveTlsConfig() ?? globalConfig.tls ?? void 0;
35
45
  const redactor = new Redactor({
@@ -43,7 +53,9 @@ async function main() {
43
53
  });
44
54
  const corsOrigins = process.env.RUNTIMESCOPE_CORS_ORIGINS?.split(",").map((s) => s.trim()) ?? globalConfig.corsOrigins;
45
55
  if (authManager.isEnabled()) {
46
- console.error(`[RuntimeScope] Auth enabled (${globalConfig.auth?.apiKeys?.length ?? 0} API keys)`);
56
+ const keyCount = authFromEnv?.apiKeys?.length ?? globalConfig.auth?.apiKeys?.length ?? 0;
57
+ const source = authFromEnv ? "env" : "config";
58
+ console.error(`[RuntimeScope] Auth enabled (${keyCount} API key${keyCount === 1 ? "" : "s"} from ${source})`);
47
59
  }
48
60
  if (tlsConfig) {
49
61
  console.error(`[RuntimeScope] TLS enabled (cert: ${tlsConfig.certPath})`);
@@ -95,8 +107,21 @@ async function main() {
95
107
  const pmDbPath = join(projectManager.rootDir, "pm.db");
96
108
  pmStore = new PmStore({ dbPath: pmDbPath });
97
109
  discovery = new ProjectDiscovery(pmStore, projectManager);
110
+ collector.setPmStore(pmStore);
98
111
  discovery.discoverAll().then((result) => {
99
112
  console.error(`[RuntimeScope] PM: ${result.projectsDiscovered} projects, ${result.sessionsDiscovered} sessions discovered`);
113
+ projectManager.rebuildAppIndex(pmStore);
114
+ try {
115
+ const migrationResult = migrateProjectIds(projectManager, pmStore);
116
+ if (migrationResult.unified > 0) {
117
+ console.error(`[RuntimeScope] Unified ${migrationResult.unified} project IDs`);
118
+ for (const detail of migrationResult.details) {
119
+ console.error(`[RuntimeScope] ${detail}`);
120
+ }
121
+ projectManager.rebuildAppIndex(pmStore);
122
+ }
123
+ } catch {
124
+ }
100
125
  }).catch((err) => {
101
126
  console.error("[RuntimeScope] PM discovery error:", err.message);
102
127
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/standalone.ts"],"sourcesContent":["#!/usr/bin/env node\n\n// ============================================================\n// RuntimeScope Standalone Collector\n// Runs CollectorServer + HttpServer as a standalone service\n// without MCP, Playwright, or ProcessMonitor.\n//\n// Usage:\n// node dist/standalone.js\n// npx @runtimescope/collector\n// docker run runtimescope\n// ============================================================\n\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { CollectorServer } from './server.js';\nimport { HttpServer } from './http-server.js';\nimport { ProjectManager } from './project-manager.js';\nimport { PmStore } from './pm/pm-store.js';\nimport { ProjectDiscovery } from './pm/project-discovery.js';\nimport { SessionManager } from './session-manager.js';\nimport { SqliteStore } from './sqlite-store.js';\nimport { isSqliteAvailable } from './sqlite-check.js';\nimport { AuthManager } from './auth.js';\nimport { Redactor } from './redactor.js';\nimport { resolveTlsConfig } from './tls.js';\n\nconst HOST = process.env.RUNTIMESCOPE_HOST ?? '127.0.0.1';\n// Same default ports as MCP server — only one should run at a time\nconst COLLECTOR_PORT = parseInt(process.env.RUNTIMESCOPE_PORT ?? '9090', 10);\nconst HTTP_PORT = parseInt(process.env.RUNTIMESCOPE_HTTP_PORT ?? '9091', 10);\nconst BUFFER_SIZE = parseInt(process.env.RUNTIMESCOPE_BUFFER_SIZE ?? '10000', 10);\nconst RETENTION_DAYS = parseInt(process.env.RUNTIMESCOPE_RETENTION_DAYS ?? '30', 10);\n\nasync function main() {\n console.error('[RuntimeScope] Starting standalone collector...');\n\n // 1. Initialize project management + config\n const projectManager = new ProjectManager();\n projectManager.ensureGlobalDir();\n const globalConfig = projectManager.getGlobalConfig();\n\n // 2. Security: auth, TLS, redaction, CORS\n const authManager = new AuthManager({\n enabled: globalConfig.auth?.enabled ?? false,\n apiKeys: globalConfig.auth?.apiKeys ?? [],\n });\n\n const tlsConfig = resolveTlsConfig() ?? globalConfig.tls ?? undefined;\n\n const redactor = new Redactor({\n enabled: globalConfig.redaction?.enabled ?? false,\n useBuiltIn: true,\n rules: globalConfig.redaction?.rules?.map(r => ({\n name: r.name,\n pattern: new RegExp(r.pattern, 'gi'),\n replacement: r.replacement,\n })),\n });\n\n const corsOrigins = process.env.RUNTIMESCOPE_CORS_ORIGINS?.split(',').map(s => s.trim())\n ?? globalConfig.corsOrigins;\n\n if (authManager.isEnabled()) {\n console.error(`[RuntimeScope] Auth enabled (${globalConfig.auth?.apiKeys?.length ?? 0} API keys)`);\n }\n if (tlsConfig) {\n console.error(`[RuntimeScope] TLS enabled (cert: ${tlsConfig.certPath})`);\n }\n if (redactor.isEnabled()) {\n console.error('[RuntimeScope] Payload redaction enabled');\n }\n\n // 3. Start collector WebSocket server\n const collector = new CollectorServer({\n bufferSize: BUFFER_SIZE,\n projectManager,\n authManager,\n rateLimits: globalConfig.rateLimits,\n tls: tlsConfig,\n });\n await collector.start({ port: COLLECTOR_PORT, host: HOST, maxRetries: 5, retryDelayMs: 1000 });\n\n const store = collector.getStore();\n\n // Wire redactor for defense-in-depth\n if (redactor.isEnabled()) {\n store.setRedactor(redactor);\n }\n\n // 4. Session management — auto-snapshot on disconnect\n const sqliteStores = collector.getSqliteStores();\n const sessionManager = new SessionManager(projectManager, sqliteStores, store);\n\n collector.onDisconnect((sessionId, projectName) => {\n try {\n sessionManager.createSnapshot(sessionId, projectName);\n console.error(`[RuntimeScope] Session ${sessionId} metrics saved`);\n } catch {\n // Non-fatal\n }\n });\n\n // 5. Retention pruning (only if SQLite is available)\n if (isSqliteAvailable()) {\n const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1000;\n for (const projectName of projectManager.listProjects()) {\n const dbPath = projectManager.getProjectDbPath(projectName);\n if (existsSync(dbPath)) {\n try {\n const tempStore = new SqliteStore({ dbPath });\n const deleted = tempStore.deleteOldEvents(cutoffMs);\n if (deleted > 0) {\n console.error(`[RuntimeScope] Pruned ${deleted} events older than ${RETENTION_DAYS}d from \"${projectName}\"`);\n }\n tempStore.close();\n } catch {\n // Non-fatal\n }\n }\n }\n }\n\n // 6. Project Management layer (requires SQLite)\n let pmStore: PmStore | undefined;\n let discovery: ProjectDiscovery | undefined;\n\n if (isSqliteAvailable()) {\n const pmDbPath = join(projectManager.rootDir, 'pm.db');\n pmStore = new PmStore({ dbPath: pmDbPath });\n discovery = new ProjectDiscovery(pmStore, projectManager);\n\n // Run discovery in background (non-blocking)\n discovery.discoverAll().then((result) => {\n console.error(`[RuntimeScope] PM: ${result.projectsDiscovered} projects, ${result.sessionsDiscovered} sessions discovered`);\n }).catch((err) => {\n console.error('[RuntimeScope] PM discovery error:', (err as Error).message);\n });\n }\n\n // 7. Start HTTP API server (with POST /api/events + PM routes)\n const httpServer = new HttpServer(store, undefined, {\n authManager,\n allowedOrigins: corsOrigins,\n rateLimiter: collector.getRateLimiter(),\n pmStore,\n discovery,\n projectManager,\n getConnectedSessions: () => collector.getConnectedSessions(),\n });\n\n try {\n await httpServer.start({ port: HTTP_PORT, host: HOST, tls: tlsConfig });\n } catch (err) {\n console.error('[RuntimeScope] HTTP API failed to start:', (err as Error).message);\n }\n\n // Push session connect/disconnect to dashboard in real-time\n collector.onConnect((sessionId, projectName, projectId) => {\n httpServer.broadcastSessionChange('session_connected', sessionId, projectName);\n // Auto-link SDK appName to PM project\n if (pmStore) {\n try { pmStore.autoLinkApp(projectName, projectId); } catch { /* non-fatal */ }\n }\n });\n collector.onDisconnect((sessionId, projectName) => {\n httpServer.broadcastSessionChange('session_disconnected', sessionId, projectName);\n });\n\n // 7. Startup summary\n const proto = tlsConfig ? 'wss' : 'ws';\n const httpProto = tlsConfig ? 'https' : 'http';\n console.error(`[RuntimeScope] Standalone collector ready`);\n console.error(`[RuntimeScope] WebSocket: ${proto}://${HOST}:${COLLECTOR_PORT}`);\n console.error(`[RuntimeScope] HTTP API: ${httpProto}://${HOST}:${HTTP_PORT}`);\n console.error(`[RuntimeScope] Health: ${httpProto}://${HOST}:${HTTP_PORT}/api/health`);\n console.error(`[RuntimeScope] Ingest: POST ${httpProto}://${HOST}:${HTTP_PORT}/api/events`);\n\n // 8. Graceful shutdown\n let shuttingDown = false;\n const shutdown = async () => {\n if (shuttingDown) return;\n shuttingDown = true;\n console.error('[RuntimeScope] Shutting down...');\n\n await httpServer.stop();\n collector.stop();\n pmStore?.close();\n\n process.exit(0);\n };\n\n process.on('SIGINT', () => { shutdown(); });\n process.on('SIGTERM', () => { shutdown(); });\n}\n\nmain().catch((err) => {\n console.error('[RuntimeScope] Fatal error:', err);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/standalone.ts"],"sourcesContent":["#!/usr/bin/env node\n\n// ============================================================\n// RuntimeScope Standalone Collector\n// Runs CollectorServer + HttpServer as a standalone service\n// without MCP, Playwright, or ProcessMonitor.\n//\n// Usage:\n// node dist/standalone.js\n// npx @runtimescope/collector\n// docker run runtimescope\n// ============================================================\n\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { CollectorServer } from './server.js';\nimport { HttpServer } from './http-server.js';\nimport { ProjectManager } from './project-manager.js';\nimport { PmStore } from './pm/pm-store.js';\nimport { ProjectDiscovery } from './pm/project-discovery.js';\nimport { SessionManager } from './session-manager.js';\nimport { SqliteStore } from './sqlite-store.js';\nimport { isSqliteAvailable } from './sqlite-check.js';\nimport { AuthManager } from './auth.js';\nimport { Redactor } from './redactor.js';\nimport { resolveTlsConfig } from './tls.js';\nimport { migrateProjectIds } from './project-config.js';\n\nconst HOST = process.env.RUNTIMESCOPE_HOST ?? '127.0.0.1';\n// Same default ports as MCP server — only one should run at a time\nconst COLLECTOR_PORT = parseInt(process.env.RUNTIMESCOPE_PORT ?? '6767', 10);\nconst HTTP_PORT = parseInt(process.env.RUNTIMESCOPE_HTTP_PORT ?? '6768', 10);\nconst BUFFER_SIZE = parseInt(process.env.RUNTIMESCOPE_BUFFER_SIZE ?? '10000', 10);\nconst RETENTION_DAYS = parseInt(process.env.RUNTIMESCOPE_RETENTION_DAYS ?? '30', 10);\n\nasync function main() {\n console.error('[RuntimeScope] Starting standalone collector...');\n\n // 1. Initialize project management + config\n const projectManager = new ProjectManager();\n projectManager.ensureGlobalDir();\n const globalConfig = projectManager.getGlobalConfig();\n\n // 2. Security: auth, TLS, redaction, CORS\n // RUNTIMESCOPE_AUTH_TOKEN env var provides a quick way to enable auth in Docker\n // deployments without mounting a config file. Multiple tokens can be passed\n // comma-separated. If set, it takes precedence over the config file.\n const envAuthToken = process.env.RUNTIMESCOPE_AUTH_TOKEN;\n const authFromEnv = envAuthToken\n ? {\n enabled: true,\n apiKeys: envAuthToken\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean)\n .map((key, i) => ({\n key,\n label: `env-token-${i + 1}`,\n createdAt: Date.now(),\n })),\n }\n : null;\n const authManager = new AuthManager({\n enabled: authFromEnv?.enabled ?? globalConfig.auth?.enabled ?? false,\n apiKeys: authFromEnv?.apiKeys ?? globalConfig.auth?.apiKeys ?? [],\n });\n\n const tlsConfig = resolveTlsConfig() ?? globalConfig.tls ?? undefined;\n\n const redactor = new Redactor({\n enabled: globalConfig.redaction?.enabled ?? false,\n useBuiltIn: true,\n rules: globalConfig.redaction?.rules?.map(r => ({\n name: r.name,\n pattern: new RegExp(r.pattern, 'gi'),\n replacement: r.replacement,\n })),\n });\n\n const corsOrigins = process.env.RUNTIMESCOPE_CORS_ORIGINS?.split(',').map(s => s.trim())\n ?? globalConfig.corsOrigins;\n\n if (authManager.isEnabled()) {\n const keyCount = (authFromEnv?.apiKeys?.length ?? globalConfig.auth?.apiKeys?.length ?? 0);\n const source = authFromEnv ? 'env' : 'config';\n console.error(`[RuntimeScope] Auth enabled (${keyCount} API key${keyCount === 1 ? '' : 's'} from ${source})`);\n }\n if (tlsConfig) {\n console.error(`[RuntimeScope] TLS enabled (cert: ${tlsConfig.certPath})`);\n }\n if (redactor.isEnabled()) {\n console.error('[RuntimeScope] Payload redaction enabled');\n }\n\n // 3. Start collector WebSocket server\n const collector = new CollectorServer({\n bufferSize: BUFFER_SIZE,\n projectManager,\n authManager,\n rateLimits: globalConfig.rateLimits,\n tls: tlsConfig,\n });\n await collector.start({ port: COLLECTOR_PORT, host: HOST, maxRetries: 5, retryDelayMs: 1000 });\n\n const store = collector.getStore();\n\n // Wire redactor for defense-in-depth\n if (redactor.isEnabled()) {\n store.setRedactor(redactor);\n }\n\n // 4. Session management — auto-snapshot on disconnect\n const sqliteStores = collector.getSqliteStores();\n const sessionManager = new SessionManager(projectManager, sqliteStores, store);\n\n collector.onDisconnect((sessionId, projectName) => {\n try {\n sessionManager.createSnapshot(sessionId, projectName);\n console.error(`[RuntimeScope] Session ${sessionId} metrics saved`);\n } catch {\n // Non-fatal\n }\n });\n\n // 5. Retention pruning (only if SQLite is available)\n if (isSqliteAvailable()) {\n const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1000;\n for (const projectName of projectManager.listProjects()) {\n const dbPath = projectManager.getProjectDbPath(projectName);\n if (existsSync(dbPath)) {\n try {\n const tempStore = new SqliteStore({ dbPath });\n const deleted = tempStore.deleteOldEvents(cutoffMs);\n if (deleted > 0) {\n console.error(`[RuntimeScope] Pruned ${deleted} events older than ${RETENTION_DAYS}d from \"${projectName}\"`);\n }\n tempStore.close();\n } catch {\n // Non-fatal\n }\n }\n }\n }\n\n // 6. Project Management layer (requires SQLite)\n let pmStore: PmStore | undefined;\n let discovery: ProjectDiscovery | undefined;\n\n if (isSqliteAvailable()) {\n const pmDbPath = join(projectManager.rootDir, 'pm.db');\n pmStore = new PmStore({ dbPath: pmDbPath });\n discovery = new ProjectDiscovery(pmStore, projectManager);\n\n // Wire PM store into collector so handshake can resolve projectIds\n collector.setPmStore(pmStore);\n\n // Run discovery in background (non-blocking)\n discovery.discoverAll().then((result) => {\n console.error(`[RuntimeScope] PM: ${result.projectsDiscovered} projects, ${result.sessionsDiscovered} sessions discovered`);\n\n // Rebuild app index after discovery completes\n projectManager.rebuildAppIndex(pmStore);\n\n // Migrate project IDs — unify multi-app projects\n try {\n const migrationResult = migrateProjectIds(projectManager, pmStore);\n if (migrationResult.unified > 0) {\n console.error(`[RuntimeScope] Unified ${migrationResult.unified} project IDs`);\n for (const detail of migrationResult.details) {\n console.error(`[RuntimeScope] ${detail}`);\n }\n // Rebuild the app index after migration\n projectManager.rebuildAppIndex(pmStore);\n }\n } catch { /* non-fatal */ }\n }).catch((err) => {\n console.error('[RuntimeScope] PM discovery error:', (err as Error).message);\n });\n }\n\n // 7. Start HTTP API server (with POST /api/events + PM routes)\n const httpServer = new HttpServer(store, undefined, {\n authManager,\n allowedOrigins: corsOrigins,\n rateLimiter: collector.getRateLimiter(),\n pmStore,\n discovery,\n projectManager,\n getConnectedSessions: () => collector.getConnectedSessions(),\n });\n\n try {\n await httpServer.start({ port: HTTP_PORT, host: HOST, tls: tlsConfig });\n } catch (err) {\n console.error('[RuntimeScope] HTTP API failed to start:', (err as Error).message);\n }\n\n // Push session connect/disconnect to dashboard in real-time\n collector.onConnect((sessionId, projectName, projectId) => {\n httpServer.broadcastSessionChange('session_connected', sessionId, projectName);\n // Auto-link SDK appName to PM project\n if (pmStore) {\n try { pmStore.autoLinkApp(projectName, projectId); } catch { /* non-fatal */ }\n }\n });\n collector.onDisconnect((sessionId, projectName) => {\n httpServer.broadcastSessionChange('session_disconnected', sessionId, projectName);\n });\n\n // 7. Startup summary\n const proto = tlsConfig ? 'wss' : 'ws';\n const httpProto = tlsConfig ? 'https' : 'http';\n console.error(`[RuntimeScope] Standalone collector ready`);\n console.error(`[RuntimeScope] WebSocket: ${proto}://${HOST}:${COLLECTOR_PORT}`);\n console.error(`[RuntimeScope] HTTP API: ${httpProto}://${HOST}:${HTTP_PORT}`);\n console.error(`[RuntimeScope] Health: ${httpProto}://${HOST}:${HTTP_PORT}/api/health`);\n console.error(`[RuntimeScope] Ingest: POST ${httpProto}://${HOST}:${HTTP_PORT}/api/events`);\n\n // 8. Graceful shutdown\n let shuttingDown = false;\n const shutdown = async () => {\n if (shuttingDown) return;\n shuttingDown = true;\n console.error('[RuntimeScope] Shutting down...');\n\n await httpServer.stop();\n collector.stop();\n pmStore?.close();\n\n process.exit(0);\n };\n\n process.on('SIGINT', () => { shutdown(); });\n process.on('SIGTERM', () => { shutdown(); });\n}\n\nmain().catch((err) => {\n console.error('[RuntimeScope] Fatal error:', err);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runtimescope/collector",
3
- "version": "0.9.2",
3
+ "version": "0.10.0",
4
4
  "description": "Event collector and persistence layer for RuntimeScope",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",