skillo 0.2.5 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +198 -198
- package/dist/api-client-BF6GDR7Q.js +12 -0
- package/dist/{chunk-WJKZWKER.js → chunk-63FVALWX.js} +1 -1
- package/dist/chunk-63FVALWX.js.map +1 -0
- package/dist/chunk-6GOJPFZ7.js +113 -0
- package/dist/chunk-6GOJPFZ7.js.map +1 -0
- package/dist/chunk-6UGTWBUW.js +89 -0
- package/dist/chunk-6UGTWBUW.js.map +1 -0
- package/dist/{chunk-2CVEPT6U.js → chunk-73NUWYUO.js} +2 -2
- package/dist/chunk-73NUWYUO.js.map +1 -0
- package/dist/chunk-QIV4VIXA.js +221 -0
- package/dist/chunk-QIV4VIXA.js.map +1 -0
- package/dist/{chunk-CPL3P2OF.js → chunk-QUXHHRRK.js} +2 -2
- package/dist/chunk-QUXHHRRK.js.map +1 -0
- package/dist/{chunk-ODOZM4QV.js → chunk-RQ2SC5HW.js} +72 -10
- package/dist/chunk-RQ2SC5HW.js.map +1 -0
- package/dist/chunk-U53QWUOR.js +727 -0
- package/dist/chunk-U53QWUOR.js.map +1 -0
- package/dist/chunk-VAQ73XPE.js +68 -0
- package/dist/chunk-VAQ73XPE.js.map +1 -0
- package/dist/chunk-XLJGCOVT.js +975 -0
- package/dist/chunk-XLJGCOVT.js.map +1 -0
- package/dist/{claude-watcher-N6GN6WHJ.js → claude-watcher-WKGBJYKN.js} +65 -3
- package/dist/claude-watcher-WKGBJYKN.js.map +1 -0
- package/dist/cli.js +499 -1807
- package/dist/cli.js.map +1 -1
- package/dist/{config-P5EM5L7N.js → config-ZOKAP2LJ.js} +3 -3
- package/dist/daemon-6DTCMOJB.js +28 -0
- package/dist/daemon-runner.js +352 -65
- package/dist/daemon-runner.js.map +1 -1
- package/dist/database-KQY5OSCS.js +9 -0
- package/dist/git-OGUSYBJS.js +16 -0
- package/dist/git-OGUSYBJS.js.map +1 -0
- package/dist/git-OUAHIOY2.js +110 -0
- package/dist/git-OUAHIOY2.js.map +1 -0
- package/dist/index.js.map +1 -1
- package/dist/{paths-INOKEM66.js → paths-MPOZBOKE.js} +2 -2
- package/dist/paths-MPOZBOKE.js.map +1 -0
- package/dist/project-OFU2W6MH.js +19 -0
- package/dist/project-OFU2W6MH.js.map +1 -0
- package/dist/shell-NZABRJLA.js +16 -0
- package/dist/shell-NZABRJLA.js.map +1 -0
- package/dist/skill-installer-F67OAOQN.js +121 -0
- package/dist/skill-installer-F67OAOQN.js.map +1 -0
- package/dist/{skill-usage-detector-EO26MRYV.js → skill-usage-detector-MSW5VWQZ.js} +2 -2
- package/dist/skill-usage-detector-MSW5VWQZ.js.map +1 -0
- package/dist/{tray-YOL4R2RH.js → tray-WKFGUUTO.js} +117 -179
- package/dist/tray-WKFGUUTO.js.map +1 -0
- package/package.json +62 -63
- package/scripts/postinstall.mjs +415 -364
- package/scripts/tray-helper-darwin +0 -0
- package/scripts/tray-helper-darwin.swift +180 -180
- package/scripts/tray-helper-linux.py +322 -0
- package/scripts/tray-helper-windows.cs +322 -0
- package/dist/api-client-KUQW7FSC.js +0 -12
- package/dist/chunk-2CVEPT6U.js.map +0 -1
- package/dist/chunk-CPL3P2OF.js.map +0 -1
- package/dist/chunk-ODOZM4QV.js.map +0 -1
- package/dist/chunk-WJKZWKER.js.map +0 -1
- package/dist/claude-watcher-N6GN6WHJ.js.map +0 -1
- package/dist/database-F3BFFZKG.js +0 -9
- package/dist/skill-usage-detector-EO26MRYV.js.map +0 -1
- package/dist/tray-YOL4R2RH.js.map +0 -1
- /package/dist/{api-client-KUQW7FSC.js.map → api-client-BF6GDR7Q.js.map} +0 -0
- /package/dist/{config-P5EM5L7N.js.map → config-ZOKAP2LJ.js.map} +0 -0
- /package/dist/{database-F3BFFZKG.js.map → daemon-6DTCMOJB.js.map} +0 -0
- /package/dist/{paths-INOKEM66.js.map → database-KQY5OSCS.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/daemon-runner.ts","../src/core/config.ts","../src/utils/paths.ts","../src/core/api-client.ts","../src/core/claude-watcher.ts","../src/core/skill-usage-detector.ts","../src/utils/status-writer.ts"],"sourcesContent":["/**\n * Daemon Runner - Entry point for the forked daemon child process.\n *\n * Runs:\n * 1. Claude history watcher (auto-syncs conversations for tracked projects)\n * 2. Tracked projects cache writer (shell hooks read this for fast detection)\n * 3. Stale session cleanup (ends sessions where terminal PID is dead)\n */\n\nimport { existsSync, writeFileSync, unlinkSync, readFileSync, readdirSync, appendFileSync } from \"fs\";\nimport { loadConfig } from \"./core/config.js\";\nimport { getApiClient } from \"./core/api-client.js\";\nimport { getPidFile, getLogFile, getTrackedProjectsCacheFile, getActiveSessionsDir, ensureDirectory } from \"./utils/paths.js\";\nimport { ClaudeWatcher, loadTrackedProjects } from \"./core/claude-watcher.js\";\nimport { SkillUsageDetector } from \"./core/skill-usage-detector.js\";\nimport { StatusWriter } from \"./utils/status-writer.js\";\n\n// ── Daemon Logger ──\n\nfunction log(level: string, msg: string) {\n const line = `[${new Date().toISOString()}] [${level}] ${msg}\\n`;\n try {\n appendFileSync(getLogFile(), line);\n } catch {\n // Can't log, ignore\n }\n}\n\n// ── Tracked Projects Cache Writer ──\n\nasync function updateTrackedProjectsCache(client: ReturnType<typeof getApiClient>) {\n try {\n const result = await client.listProjects(true);\n if (result.success && result.data?.projects) {\n const cacheData = {\n updatedAt: Date.now(),\n projects: result.data.projects\n .filter((p: { trackingEnabled: boolean }) => p.trackingEnabled)\n .map((p: { path: string; name: string }) => ({ path: p.path, name: p.name })),\n };\n writeFileSync(getTrackedProjectsCacheFile(), JSON.stringify(cacheData, null, 2));\n log(\"DEBUG\", `Updated tracked projects cache: ${cacheData.projects.length} project(s)`);\n }\n } catch (error) {\n log(\"ERROR\", `Failed to update tracked projects cache: ${error}`);\n }\n}\n\n// ── Stale Session Cleanup ──\n\nasync function cleanupStaleSessions(client: ReturnType<typeof getApiClient>) {\n const sessionsDir = getActiveSessionsDir();\n if (!existsSync(sessionsDir)) return;\n\n try {\n const files = readdirSync(sessionsDir);\n for (const file of files) {\n if (!file.endsWith(\".json\")) continue;\n\n const pid = parseInt(file.replace(\".json\", \"\"), 10);\n if (isNaN(pid)) continue;\n\n // Check if process is still running\n let isRunning = false;\n try {\n process.kill(pid, 0);\n isRunning = true;\n } catch {\n // Process doesn't exist\n }\n\n if (!isRunning) {\n try {\n const sessionData = JSON.parse(readFileSync(`${sessionsDir}/${file}`, \"utf-8\"));\n if (sessionData.sessionId) {\n await client.endSession(sessionData.sessionId);\n log(\"INFO\", `Ended stale session ${sessionData.sessionId.slice(0, 8)} (PID ${pid} dead)`);\n }\n unlinkSync(`${sessionsDir}/${file}`);\n } catch {\n // Clean up the file even if API call fails\n try { unlinkSync(`${sessionsDir}/${file}`); } catch { /* ignore */ }\n }\n }\n }\n } catch (error) {\n log(\"ERROR\", `Stale session cleanup error: ${error}`);\n }\n}\n\n// ── Main Daemon Loop ──\n\nasync function main() {\n const pidFile = getPidFile();\n writeFileSync(pidFile, String(process.pid));\n\n log(\"INFO\", `Daemon starting (PID: ${process.pid})`);\n\n // Status writer\n const statusWriter = new StatusWriter();\n\n // Signal handlers\n const cleanup = () => {\n log(\"INFO\", \"Daemon shutting down\");\n statusWriter.stop();\n if (existsSync(pidFile)) {\n try { unlinkSync(pidFile); } catch { /* ignore */ }\n }\n process.exit(0);\n };\n process.on(\"SIGINT\", cleanup);\n process.on(\"SIGTERM\", cleanup);\n\n // Load config\n let config;\n try {\n config = loadConfig();\n } catch {\n log(\"WARN\", \"Could not load config, using defaults\");\n config = { daemon: { conversationCheckInterval: 5, patternCheckInterval: 60 } };\n }\n\n // Check API key\n const client = getApiClient();\n if (!client.hasApiKey()) {\n log(\"ERROR\", \"Not logged in. Daemon cannot run without API key.\");\n cleanup();\n return;\n }\n\n // Ensure directories\n ensureDirectory(getActiveSessionsDir());\n\n // Initial cache update\n await updateTrackedProjectsCache(client);\n\n // Clean up stale sessions from previous crashes\n await cleanupStaleSessions(client);\n\n // Start Claude watcher\n const watchInterval = ((config as { daemon?: { conversationCheckInterval?: number } }).daemon?.conversationCheckInterval || 5) * 1000;\n const watcher = new ClaudeWatcher(client, {\n intervalMs: watchInterval,\n callbacks: {\n onSync: (count) => log(\"INFO\", `Synced ${count} Claude prompt(s)`),\n onError: (err) => log(\"ERROR\", `Watcher error: ${err.message}`),\n log: (level, msg) => log(level, msg),\n },\n });\n await watcher.start();\n\n // Start skill usage detector\n const skillDetector = new SkillUsageDetector(client, {\n intervalMs: 30000,\n callbacks: {\n onDetection: (count) => {\n log(\"INFO\", `Detected ${count} skill usage(s)`);\n statusWriter.update({ skillDetector: { usagesDetected: count, lastDetection: new Date().toISOString(), lastError: null } });\n },\n onError: (err) => {\n log(\"ERROR\", `Skill detection error: ${err.message}`);\n statusWriter.update({ skillDetector: { lastError: err.message } });\n },\n log: (level, msg) => log(level, msg),\n },\n });\n await skillDetector.start();\n\n // Start status writer\n statusWriter.start();\n\n // Refresh tracked projects cache every 60s\n setInterval(async () => {\n await updateTrackedProjectsCache(client);\n // Update status writer with project count\n try {\n const cacheData = JSON.parse(readFileSync(getTrackedProjectsCacheFile(), \"utf-8\"));\n statusWriter.update({\n trackedProjects: cacheData.projects || [],\n });\n } catch { /* ignore */ }\n }, 60000);\n\n // Clean up stale sessions every 5 minutes\n setInterval(() => cleanupStaleSessions(client), 300000);\n\n log(\"INFO\", \"Daemon started successfully — watching Claude conversations & skill usage\");\n\n // Keep alive\n await new Promise(() => {});\n}\n\nmain().catch((err) => {\n log(\"ERROR\", `Daemon crashed: ${err}`);\n process.exit(1);\n});\n","/**\n * Configuration management for Skillo.\n */\n\nimport { readFileSync, writeFileSync, existsSync } from \"fs\";\nimport { dirname } from \"path\";\nimport YAML from \"yaml\";\nimport { ensureDirectory, getConfigFile } from \"../utils/paths.js\";\n\nexport interface SkilloConfig {\n // Shell settings\n defaultShell: string | null;\n\n // Pattern detection\n patternDetection: {\n minCount: number;\n sessionTimeout: number;\n similarityThreshold: number;\n maxSequenceLength: number;\n };\n\n // Privacy settings\n privacy: {\n autoRedact: boolean;\n trackOutput: boolean;\n neverTrack: string[];\n redactionPatterns: string[];\n };\n\n // Notification settings\n notifications: {\n enabled: boolean;\n style: \"inline\" | \"desktop\" | \"both\";\n sound: boolean;\n };\n\n // Skill generation\n skillGeneration: {\n outputDir: string | null;\n includeScripts: boolean;\n includeExamples: boolean;\n autoGenerate: boolean;\n };\n\n // Claude Code integration\n claudeCode: {\n watchConversations: boolean;\n conversationDir: string | null;\n };\n\n // Daemon settings\n daemon: {\n logLevel: \"DEBUG\" | \"INFO\" | \"WARN\" | \"ERROR\";\n patternCheckInterval: number;\n conversationCheckInterval: number;\n };\n\n // Team settings\n team: {\n enabled: boolean;\n slug: string | null;\n autoSync: boolean;\n syncInterval: number;\n };\n\n // API settings\n api: {\n baseUrl: string | null;\n timeout: number;\n };\n\n // API key (stored separately, not in YAML for security)\n apiKey?: string;\n}\n\nexport function getDefaultConfig(): SkilloConfig {\n return {\n defaultShell: null,\n\n patternDetection: {\n minCount: 3,\n sessionTimeout: 30,\n similarityThreshold: 0.8,\n maxSequenceLength: 10,\n },\n\n privacy: {\n autoRedact: true,\n trackOutput: false,\n neverTrack: [\n \"*password*\",\n \"*secret*\",\n \"vault *\",\n \"1password *\",\n \"op *\",\n \"export *_KEY=*\",\n \"export *_SECRET=*\",\n \"export *_TOKEN=*\",\n ],\n redactionPatterns: [\n \"password[=:]\\\\s*\\\\S+\",\n \"token[=:]\\\\s*\\\\S+\",\n \"secret[=:]\\\\s*\\\\S+\",\n \"api[_-]?key[=:]\\\\s*\\\\S+\",\n \"auth[=:]\\\\s*\\\\S+\",\n \"bearer\\\\s+\\\\S+\",\n \"-----BEGIN.*PRIVATE KEY-----\",\n \"AKIA[0-9A-Z]{16}\",\n '[\"\\'][A-Za-z0-9+/]{40,}[\"\\']',\n ],\n },\n\n notifications: {\n enabled: true,\n style: \"inline\",\n sound: false,\n },\n\n skillGeneration: {\n outputDir: null,\n includeScripts: true,\n includeExamples: true,\n autoGenerate: false,\n },\n\n claudeCode: {\n watchConversations: true,\n conversationDir: null,\n },\n\n daemon: {\n logLevel: \"INFO\",\n patternCheckInterval: 60,\n conversationCheckInterval: 5,\n },\n\n team: {\n enabled: false,\n slug: null,\n autoSync: true,\n syncInterval: 300,\n },\n\n api: {\n baseUrl: null,\n timeout: 30000,\n },\n };\n}\n\nfunction deepMerge<T extends object>(base: T, override: Partial<T>): T {\n const result = { ...base };\n\n for (const key of Object.keys(override) as Array<keyof T>) {\n const value = override[key];\n if (\n value !== undefined &&\n typeof result[key] === \"object\" &&\n result[key] !== null &&\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value)\n ) {\n result[key] = deepMerge(\n result[key] as Record<string, unknown>,\n value as Record<string, unknown>\n ) as T[keyof T];\n } else if (value !== undefined) {\n result[key] = value as T[keyof T];\n }\n }\n\n return result;\n}\n\nexport function loadConfig(path?: string): SkilloConfig {\n const configPath = path || getConfigFile();\n\n if (!existsSync(configPath)) {\n return getDefaultConfig();\n }\n\n try {\n const content = readFileSync(configPath, \"utf-8\");\n const parsed = YAML.parse(content) || {};\n\n // Convert snake_case to camelCase for compatibility\n const converted = convertKeysToCamelCase(parsed);\n const defaultConfig = getDefaultConfig();\n\n // Deep merge with default config\n return deepMerge(defaultConfig, converted as Partial<SkilloConfig>);\n } catch {\n return getDefaultConfig();\n }\n}\n\nexport function saveConfig(config: SkilloConfig, path?: string): void {\n const configPath = path || getConfigFile();\n\n ensureDirectory(dirname(configPath));\n\n // Convert camelCase to snake_case for YAML file\n const converted = convertKeysToSnakeCase(config);\n\n const content = YAML.stringify(converted, {\n indent: 2,\n lineWidth: 0,\n });\n\n writeFileSync(configPath, content, \"utf-8\");\n}\n\nexport function getConfigValue(config: SkilloConfig, key: string): unknown {\n const keys = key.split(\".\");\n let current: unknown = config;\n\n for (const k of keys) {\n if (current && typeof current === \"object\" && k in current) {\n current = (current as Record<string, unknown>)[k];\n } else {\n return undefined;\n }\n }\n\n return current;\n}\n\nexport function setConfigValue(config: SkilloConfig, key: string, value: unknown): SkilloConfig {\n const keys = key.split(\".\");\n const result = JSON.parse(JSON.stringify(config)) as SkilloConfig;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let current: any = result;\n\n for (let i = 0; i < keys.length - 1; i++) {\n const k = keys[i];\n if (!(k in current)) {\n current[k] = {};\n }\n current = current[k] as Record<string, unknown>;\n }\n\n current[keys[keys.length - 1]] = value;\n return result;\n}\n\n// Helper functions for key conversion\nfunction toCamelCase(str: string): string {\n return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());\n}\n\nfunction toSnakeCase(str: string): string {\n return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n}\n\nfunction convertKeysToCamelCase(obj: unknown): unknown {\n if (Array.isArray(obj)) {\n return obj.map(convertKeysToCamelCase);\n }\n\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[toCamelCase(key)] = convertKeysToCamelCase(value);\n }\n return result;\n }\n\n return obj;\n}\n\nfunction convertKeysToSnakeCase(obj: unknown): unknown {\n if (Array.isArray(obj)) {\n return obj.map(convertKeysToSnakeCase);\n }\n\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[toSnakeCase(key)] = convertKeysToSnakeCase(value);\n }\n return result;\n }\n\n return obj;\n}\n","/**\n * Path utilities for Skillo.\n *\n * Handles platform-specific path resolution for data directories,\n * configuration files, and skill storage locations.\n */\n\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { mkdirSync, existsSync } from \"fs\";\n\nexport function getHomeDir(): string {\n return homedir();\n}\n\n/**\n * Get Skillo data directory.\n * This is where the database and other data files are stored.\n *\n * - Linux/macOS: ~/.skillo/\n * - Windows: %USERPROFILE%/.skillo/\n */\nexport function getDataDir(): string {\n const envPath = process.env.SKILLO_DATA_DIR;\n if (envPath) return envPath;\n\n return join(getHomeDir(), \".skillo\");\n}\n\n/**\n * Get Skillo configuration directory.\n * This is where config.yaml and other configuration files are stored.\n *\n * - Linux: ~/.config/skillo/\n * - macOS: ~/.config/skillo/\n * - Windows: %USERPROFILE%/.config/skillo/\n */\nexport function getConfigDir(): string {\n const envPath = process.env.SKILLO_CONFIG_DIR;\n if (envPath) return envPath;\n\n const xdgConfig = process.env.XDG_CONFIG_HOME;\n if (xdgConfig) return join(xdgConfig, \"skillo\");\n\n return join(getHomeDir(), \".config\", \"skillo\");\n}\n\n/**\n * Get personal skills directory.\n * This is where generated skills are stored and where Claude Code\n * reads personal skills from.\n *\n * - All platforms: ~/.claude/skills/\n */\nexport function getSkillsDir(): string {\n const envPath = process.env.SKILLO_SKILLS_DIR;\n if (envPath) return envPath;\n\n return join(getClaudeDir(), \"skills\");\n}\n\n/**\n * Get Claude Code directory.\n * This is where Claude Code stores its data, including conversations.\n *\n * - All platforms: ~/.claude/\n */\nexport function getClaudeDir(): string {\n return join(getHomeDir(), \".claude\");\n}\n\n/**\n * Get project-specific skills directory.\n * This is where project-specific skills are stored.\n *\n * - Path: <project>/.claude/skills/\n */\nexport function getProjectSkillsDir(projectPath?: string): string {\n const project = projectPath || process.cwd();\n return join(project, \".claude\", \"skills\");\n}\n\n/**\n * Get team skills directory.\n * This is where team skills are synced to.\n *\n * - Path: ~/.claude/skills/team/<team-slug>/\n */\nexport function getTeamSkillsDir(teamSlug?: string): string {\n const base = join(getSkillsDir(), \"team\");\n if (teamSlug) return join(base, teamSlug);\n return base;\n}\n\n/**\n * Ensure a directory exists, creating it if necessary.\n *\n * @returns True if directory was created, False if it already existed.\n */\nexport function ensureDirectory(path: string): boolean {\n if (existsSync(path)) {\n return false;\n }\n\n mkdirSync(path, { recursive: true });\n return true;\n}\n\n/**\n * Get path to daemon log file.\n */\nexport function getLogFile(): string {\n return join(getDataDir(), \"daemon.log\");\n}\n\n/**\n * Get path to daemon PID file.\n */\nexport function getPidFile(): string {\n return join(getDataDir(), \"daemon.pid\");\n}\n\n/**\n * Get path to SQLite database file.\n */\nexport function getDbPath(): string {\n return join(getDataDir(), \"skillo.db\");\n}\n\n/**\n * Get path to config file.\n */\nexport function getConfigFile(): string {\n return join(getConfigDir(), \"config.yaml\");\n}\n\n/**\n * Get path to tracked projects cache file.\n * Written by daemon, read by shell hooks for fast project detection.\n */\nexport function getTrackedProjectsCacheFile(): string {\n return join(getDataDir(), \"tracked-projects.json\");\n}\n\n/**\n * Get path to active sessions directory.\n * Each terminal gets a file named by its PID.\n */\nexport function getActiveSessionsDir(): string {\n return join(getDataDir(), \"active-sessions\");\n}\n\n/**\n * Get path to shell integration directory.\n */\nexport function getShellIntegrationDir(): string {\n return join(getDataDir(), \"shell-integration\");\n}\n","/**\n * Skillo Platform API Client\n *\n * Handles communication with the Skillo platform API.\n */\n\nimport { loadConfig, saveConfig } from \"./config.js\";\n\nconst DEFAULT_API_URL = \"https://www.skillo.one/api/cli\";\n\ninterface ApiResponse<T = unknown> {\n success: boolean;\n data?: T;\n error?: string;\n message?: string;\n}\n\ninterface AuthResponse {\n success: boolean;\n user: {\n id: string;\n email: string;\n name: string;\n };\n}\n\ninterface SyncResponse {\n success: boolean;\n skills?: Array<{\n id: string;\n name: string;\n slug: string;\n description: string;\n content: string;\n commands: string[];\n }>;\n patterns?: Array<{\n id: string;\n name: string;\n description: string;\n commands: string[];\n status: string;\n }>;\n}\n\ninterface GenerateResponse {\n success: boolean;\n skill: {\n id: string;\n name: string;\n slug: string;\n description: string;\n content: string;\n commands: string[];\n };\n}\n\ninterface DeviceAuthResponse {\n code: string;\n verification_url: string;\n expires_in: number;\n interval: number;\n}\n\ninterface TokenStatusResponse {\n status: \"pending\" | \"ready\" | \"expired\" | \"used\" | \"not_found\";\n}\n\ninterface TokenExchangeResponse {\n success: boolean;\n api_key: string;\n user: {\n id: string;\n };\n}\n\nexport class ApiClient {\n private baseUrl: string;\n private apiKey: string | null;\n\n constructor() {\n const config = loadConfig();\n this.baseUrl = config.api?.baseUrl || DEFAULT_API_URL;\n this.apiKey = this.loadApiKey();\n }\n\n /**\n * Load API key from config\n */\n private loadApiKey(): string | null {\n const config = loadConfig();\n return (config as { apiKey?: string }).apiKey || null;\n }\n\n /**\n * Save API key to config\n */\n saveApiKey(key: string): void {\n const config = loadConfig();\n (config as { apiKey?: string }).apiKey = key;\n saveConfig(config);\n this.apiKey = key;\n }\n\n /**\n * Clear API key from config\n */\n clearApiKey(): void {\n const config = loadConfig();\n delete (config as { apiKey?: string }).apiKey;\n saveConfig(config);\n this.apiKey = null;\n }\n\n /**\n * Check if API key is configured\n */\n hasApiKey(): boolean {\n return !!this.apiKey;\n }\n\n /**\n * Get the configured API key (masked)\n */\n getMaskedApiKey(): string | null {\n if (!this.apiKey) return null;\n return `${this.apiKey.substring(0, 12)}...${this.apiKey.substring(this.apiKey.length - 4)}`;\n }\n\n /**\n * Set the API base URL\n */\n setBaseUrl(url: string): void {\n const config = loadConfig();\n if (!config.api) {\n (config as { api?: { baseUrl: string } }).api = { baseUrl: url };\n } else {\n config.api.baseUrl = url;\n }\n saveConfig(config);\n this.baseUrl = url;\n }\n\n /**\n * Make an authenticated API request\n */\n private async request<T>(\n endpoint: string,\n options: RequestInit = {}\n ): Promise<ApiResponse<T>> {\n if (!this.apiKey) {\n return { success: false, error: \"No API key configured. Run 'skillo login' first.\" };\n }\n\n const url = `${this.baseUrl}${endpoint}`;\n const headers: HeadersInit = {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n ...options.headers,\n };\n\n try {\n const response = await fetch(url, {\n ...options,\n headers,\n });\n\n const data = await response.json();\n\n if (process.env.SKILLO_DEBUG) {\n console.log('[DEBUG] Request:', url);\n console.log('[DEBUG] Status:', response.status);\n console.log('[DEBUG] Response:', JSON.stringify(data));\n }\n\n if (!response.ok) {\n return {\n success: false,\n error: data.error || `Request failed with status ${response.status}`,\n };\n }\n\n // If the response already has success/data structure, return as-is\n // Otherwise wrap the data\n if (data.success !== undefined && data.data !== undefined) {\n return { success: data.success, data: data.data as T, message: data.message };\n }\n\n // If response just has success field (no data wrapper), return as-is\n if (data.success !== undefined) {\n return data;\n }\n\n // Legacy: wrap raw data\n return { success: true, data: data as T };\n } catch (error) {\n if (process.env.SKILLO_DEBUG) {\n console.log('[DEBUG] Error:', error);\n }\n if (error instanceof Error) {\n if (error.message.includes(\"ECONNREFUSED\")) {\n return {\n success: false,\n error: \"Cannot connect to Skillo platform. Is the server running?\",\n };\n }\n return { success: false, error: error.message };\n }\n return { success: false, error: \"Unknown error occurred\" };\n }\n }\n\n /**\n * Authenticate with the platform\n */\n async authenticate(): Promise<ApiResponse<AuthResponse>> {\n return this.request<AuthResponse>(\"/auth\", { method: \"POST\" });\n }\n\n /**\n * Sync commands to platform\n */\n async syncCommands(\n commands: Array<{\n timestamp: string;\n command: string;\n normalized: string;\n cwd: string;\n exitCode?: number | null;\n durationMs?: number | null;\n sessionId?: string;\n variables?: Record<string, string> | null;\n }>\n ): Promise<ApiResponse> {\n return this.request(\"/sync\", {\n method: \"POST\",\n body: JSON.stringify({\n type: \"commands\",\n data: commands,\n }),\n });\n }\n\n /**\n * Sync a pattern to platform\n */\n async syncPattern(pattern: {\n sourceType: string;\n name?: string;\n description?: string;\n commands: string[];\n category?: string;\n frequency?: number;\n score?: number;\n firstSeen?: string;\n lastSeen?: string;\n context?: Record<string, unknown>;\n }): Promise<ApiResponse<{ pattern: { id: string; name: string; status: string } }>> {\n return this.request(\"/sync\", {\n method: \"POST\",\n body: JSON.stringify({\n type: \"pattern\",\n data: pattern,\n }),\n });\n }\n\n /**\n * Create a session on the platform\n */\n async createSession(session: {\n startedAt: string;\n shell: string;\n endedAt?: string;\n commandCount?: number;\n }): Promise<ApiResponse<{ session: { id: string } }>> {\n return this.request(\"/sync\", {\n method: \"POST\",\n body: JSON.stringify({\n type: \"session\",\n data: session,\n }),\n });\n }\n\n /**\n * End a session on the platform\n */\n async endSession(sessionId: string): Promise<ApiResponse<{ success: boolean }>> {\n return this.request(\"/sessions\", {\n method: \"PATCH\",\n body: JSON.stringify({ sessionId }),\n });\n }\n\n /**\n * Start a new session on the platform (for standalone terminals)\n */\n async startSession(shell: string, projectPath?: string): Promise<ApiResponse<{ sessionId: string }>> {\n return this.request(\"/sessions\", {\n method: \"POST\",\n body: JSON.stringify({ shell, projectPath }),\n });\n }\n\n /**\n * Download skills and patterns from platform\n */\n async downloadData(type: \"all\" | \"skills\" | \"patterns\" = \"all\"): Promise<ApiResponse<SyncResponse>> {\n return this.request<SyncResponse>(`/sync?type=${type}`, { method: \"GET\" });\n }\n\n /**\n * Generate a skill from a pattern\n */\n async generateSkill(options: {\n patternId?: string;\n commands?: string[];\n name?: string;\n description?: string;\n category?: string;\n context?: Record<string, unknown>;\n }): Promise<ApiResponse<GenerateResponse>> {\n return this.request<GenerateResponse>(\"/generate\", {\n method: \"POST\",\n body: JSON.stringify(options),\n });\n }\n\n /**\n * Start device authorization flow (no auth required)\n */\n async startDeviceAuth(deviceName?: string): Promise<ApiResponse<DeviceAuthResponse>> {\n const url = `${this.baseUrl}/auth/device`;\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ device_name: deviceName }),\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n return {\n success: false,\n error: data.error || `Request failed with status ${response.status}`,\n };\n }\n\n return { success: true, data };\n } catch (error) {\n if (error instanceof Error) {\n if (error.message.includes(\"ECONNREFUSED\")) {\n return {\n success: false,\n error: \"Cannot connect to Skillo platform. Is the server running?\",\n };\n }\n return { success: false, error: error.message };\n }\n return { success: false, error: \"Unknown error occurred\" };\n }\n }\n\n /**\n * Check token status (polling)\n */\n async checkTokenStatus(code: string): Promise<ApiResponse<TokenStatusResponse>> {\n const url = `${this.baseUrl}/token?code=${encodeURIComponent(code)}`;\n try {\n const response = await fetch(url, { method: \"GET\" });\n const data = await response.json();\n\n if (!response.ok) {\n return {\n success: false,\n error: data.error || `Request failed with status ${response.status}`,\n };\n }\n\n return { success: true, data };\n } catch (error) {\n if (error instanceof Error) {\n return { success: false, error: error.message };\n }\n return { success: false, error: \"Unknown error occurred\" };\n }\n }\n\n /**\n * Exchange code for API key\n */\n async exchangeToken(code: string, deviceName?: string): Promise<ApiResponse<TokenExchangeResponse>> {\n const url = `${this.baseUrl}/token`;\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ code, device_name: deviceName }),\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n return {\n success: false,\n error: data.error || `Request failed with status ${response.status}`,\n };\n }\n\n return { success: true, data };\n } catch (error) {\n if (error instanceof Error) {\n return { success: false, error: error.message };\n }\n return { success: false, error: \"Unknown error occurred\" };\n }\n }\n\n /**\n * Sync Claude Code prompts to platform\n */\n async syncClaudePrompts(\n prompts: Array<{\n display: string;\n timestamp: number;\n project: string;\n sessionId: string;\n pastedContents?: Record<string, unknown>;\n }>,\n options?: {\n terminalSessionId?: string;\n projectPath?: string;\n }\n ): Promise<ApiResponse<{\n sessionsCreated: number;\n promptsCreated: number;\n promptsSkipped: number;\n }>> {\n return this.request(\"/claude\", {\n method: \"POST\",\n body: JSON.stringify({\n prompts,\n terminalSessionId: options?.terminalSessionId,\n projectPath: options?.projectPath,\n }),\n });\n }\n\n /**\n * Get Claude Code sessions from platform\n */\n async getClaudeSessions(limit = 20): Promise<ApiResponse<{\n sessions: Array<{\n id: string;\n claudeSessionId: string;\n projectPath: string;\n projectName: string;\n startedAt: string;\n endedAt?: string;\n promptCount: number;\n prompts?: Array<{\n id: string;\n prompt: string;\n timestamp: string;\n category?: string;\n }>;\n }>;\n }>> {\n return this.request(`/claude?limit=${limit}`, { method: \"GET\" });\n }\n\n /**\n * Report skill usage detections to platform\n */\n async reportSkillUsage(\n usages: Array<{\n skillSlug: string;\n claudeSessionId: string;\n projectPath: string;\n timestamp: string;\n }>\n ): Promise<ApiResponse<{ logged: number }>> {\n return this.request(\"/skill-usage\", {\n method: \"POST\",\n body: JSON.stringify({ usages }),\n });\n }\n\n // ============================================================================\n // PROJECT TRACKING\n // ============================================================================\n\n /**\n * Connect a project for tracking\n */\n async connectProject(params: {\n path: string;\n name?: string;\n gitRemote?: string;\n gitRemoteNormalized?: string;\n language?: string;\n framework?: string;\n }): Promise<ApiResponse<{\n id: string;\n name: string;\n path: string;\n trackingEnabled: boolean;\n connectedAt: string;\n }>> {\n return this.request(\"/projects/connect\", {\n method: \"POST\",\n body: JSON.stringify(params),\n });\n }\n\n /**\n * Disconnect a project from tracking\n */\n async disconnectProject(path: string): Promise<ApiResponse<{\n id: string;\n name: string;\n trackingEnabled: boolean;\n }>> {\n return this.request(`/projects/connect?path=${encodeURIComponent(path)}`, {\n method: \"DELETE\",\n });\n }\n\n /**\n * Get tracking status for a project\n */\n async getProjectStatus(path: string): Promise<ApiResponse<{\n connected: boolean;\n tracked: boolean;\n connectedAt?: string;\n project?: {\n id: string;\n name: string;\n path: string;\n gitRemote?: string;\n gitRemoteNormalized?: string;\n trackingEnabled: boolean;\n };\n }>> {\n return this.request(`/projects/connect?path=${encodeURIComponent(path)}`, {\n method: \"GET\",\n });\n }\n\n /**\n * List all tracked projects\n */\n async listProjects(includeDisabled = false): Promise<ApiResponse<{\n projects: Array<{\n id: string;\n name: string;\n path: string;\n gitRemote?: string;\n gitRemoteNormalized?: string;\n trackingEnabled: boolean;\n connectedAt?: string;\n }>;\n totalTracked: number;\n totalProjects: number;\n }>> {\n return this.request(`/projects/tracked?includeDisabled=${includeDisabled}`, {\n method: \"GET\",\n });\n }\n\n /**\n * Check if a path is in a tracked project\n * Returns the project if tracked, null if not\n */\n async isProjectTracked(path: string): Promise<{\n tracked: boolean;\n project?: {\n id: string;\n name: string;\n path: string;\n };\n }> {\n const result = await this.getProjectStatus(path);\n if (result.success && result.data?.connected) {\n return {\n tracked: true,\n project: result.data.project ? {\n id: result.data.project.id,\n name: result.data.project.name,\n path: result.data.project.path,\n } : undefined,\n };\n }\n return { tracked: false };\n }\n}\n\n// Singleton instance\nlet clientInstance: ApiClient | null = null;\n\nexport function getApiClient(): ApiClient {\n if (!clientInstance) {\n clientInstance = new ApiClient();\n }\n return clientInstance;\n}\n","/**\n * Claude Watcher - Reusable module for watching Claude Code history.\n *\n * Polls ~/.claude/history.jsonl for new entries and syncs them to the platform.\n * Used by both the daemon (background) and the `claude watch` command (foreground).\n */\n\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport * as os from \"os\";\nimport * as readline from \"readline\";\n\n// Types\nexport interface ClaudeHistoryEntry {\n display: string;\n timestamp: number;\n project: string;\n sessionId: string;\n pastedContents?: Record<string, unknown>;\n}\n\ninterface TrackedProjectInfo {\n tracked: boolean;\n name?: string;\n}\n\nexport interface ClaudeWatcherCallbacks {\n onSync?: (promptsCreated: number) => void;\n onError?: (error: Error) => void;\n log?: (level: string, msg: string) => void;\n}\n\n// ── Path helpers ──\n\nfunction getClaudeHistoryPath(): string {\n return path.join(os.homedir(), \".claude\", \"history.jsonl\");\n}\n\nfunction normalizePath(p: string): string {\n return p.toLowerCase().replace(/\\\\/g, \"/\").replace(/\\/+$/, \"\");\n}\n\nfunction matchesProjectPath(claudeProjectPath: string, filterPath: string): boolean {\n const normalizedClaude = normalizePath(claudeProjectPath);\n const normalizedFilter = normalizePath(filterPath);\n return normalizedClaude === normalizedFilter ||\n normalizedClaude.startsWith(normalizedFilter + \"/\");\n}\n\n// ── Project tracking cache ──\n\nconst projectCache = {\n projects: new Map<string, TrackedProjectInfo>(),\n lastFetched: 0,\n};\n\nconst CACHE_TTL_MS = 60000;\n\nexport async function loadTrackedProjects(\n client: { listProjects: (includeDisabled: boolean) => Promise<{ success: boolean; data?: { projects: Array<{ path: string; name: string; trackingEnabled: boolean }> } }> }\n): Promise<Map<string, TrackedProjectInfo>> {\n const now = Date.now();\n\n if (projectCache.lastFetched > 0 && now - projectCache.lastFetched < CACHE_TTL_MS) {\n return projectCache.projects;\n }\n\n try {\n const result = await client.listProjects(true);\n if (result.success && result.data?.projects) {\n projectCache.projects.clear();\n for (const project of result.data.projects) {\n const normalizedPath = normalizePath(project.path);\n projectCache.projects.set(normalizedPath, {\n tracked: project.trackingEnabled,\n name: project.name,\n });\n }\n projectCache.lastFetched = now;\n }\n } catch {\n // Use stale cache on failure\n }\n\n return projectCache.projects;\n}\n\nexport function getTrackedProjectCount(): number {\n return Array.from(projectCache.projects.values()).filter(p => p.tracked).length;\n}\n\nasync function isProjectTracked(projectPath: string): Promise<TrackedProjectInfo> {\n const normalizedPath = normalizePath(projectPath);\n\n if (projectCache.projects.has(normalizedPath)) {\n return projectCache.projects.get(normalizedPath)!;\n }\n\n for (const [trackedPath, info] of projectCache.projects.entries()) {\n if (normalizedPath.startsWith(trackedPath + \"/\")) {\n return info;\n }\n }\n\n return { tracked: false };\n}\n\n// ── Watcher ──\n\nexport class ClaudeWatcher {\n private lastSize = 0;\n private lastMtime = 0;\n private intervalId: ReturnType<typeof setInterval> | null = null;\n private historyPath: string;\n private intervalMs: number;\n private projectFilter?: string;\n private sessionFilter?: string;\n private callbacks: ClaudeWatcherCallbacks;\n private client: {\n syncClaudePrompts: (prompts: ClaudeHistoryEntry[], options?: { terminalSessionId?: string; projectPath?: string }) => Promise<{ success: boolean; data?: { promptsCreated: number; sessionsCreated: number; promptsSkipped: number }; error?: string }>;\n listProjects: (includeDisabled: boolean) => Promise<{ success: boolean; data?: { projects: Array<{ path: string; name: string; trackingEnabled: boolean }> } }>;\n };\n\n constructor(\n client: ClaudeWatcher[\"client\"],\n options: {\n intervalMs?: number;\n projectFilter?: string;\n sessionFilter?: string;\n callbacks?: ClaudeWatcherCallbacks;\n } = {}\n ) {\n this.client = client;\n this.historyPath = getClaudeHistoryPath();\n this.intervalMs = options.intervalMs || 5000;\n this.projectFilter = options.projectFilter;\n this.sessionFilter = options.sessionFilter;\n this.callbacks = options.callbacks || {};\n }\n\n private log(level: string, msg: string) {\n if (this.callbacks.log) {\n this.callbacks.log(level, msg);\n }\n }\n\n /** Initialize file position to current end (don't sync old entries). */\n initPosition() {\n try {\n if (fs.existsSync(this.historyPath)) {\n const stats = fs.statSync(this.historyPath);\n this.lastSize = stats.size;\n this.lastMtime = stats.mtimeMs;\n }\n } catch {\n // File doesn't exist yet\n }\n }\n\n /** Start watching. Runs an initial check then polls on interval. */\n async start() {\n this.initPosition();\n\n // Load tracked projects\n await loadTrackedProjects(this.client);\n const trackedCount = getTrackedProjectCount();\n this.log(\"INFO\", `Watching ${trackedCount} tracked project(s)`);\n\n // Initial check\n await this.checkAndSync();\n\n // Poll\n this.intervalId = setInterval(() => this.checkAndSync(), this.intervalMs);\n }\n\n /** Stop watching. */\n stop() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = null;\n }\n }\n\n /** Check for new entries and sync them. */\n async checkAndSync() {\n try {\n if (!fs.existsSync(this.historyPath)) return;\n\n const stats = fs.statSync(this.historyPath);\n\n // Skip if no changes\n if (stats.size === this.lastSize && stats.mtimeMs === this.lastMtime) {\n return;\n }\n\n // Refresh tracked projects cache\n await loadTrackedProjects(this.client);\n\n // Read new content\n const newPrompts: ClaudeHistoryEntry[] = [];\n\n if (stats.size > this.lastSize) {\n const stream = fs.createReadStream(this.historyPath, {\n start: this.lastSize,\n encoding: \"utf-8\",\n });\n\n const rl = readline.createInterface({\n input: stream,\n crlfDelay: Infinity,\n });\n\n for await (const line of rl) {\n if (!line.trim()) continue;\n\n try {\n const entry = JSON.parse(line) as ClaudeHistoryEntry;\n if (!entry.display || !entry.timestamp || !entry.project || !entry.sessionId) {\n continue;\n }\n\n // Project path filter\n if (this.projectFilter && !matchesProjectPath(entry.project, this.projectFilter)) {\n continue;\n }\n\n // Privacy: only tracked projects\n const trackingStatus = await isProjectTracked(entry.project);\n if (!trackingStatus.tracked) {\n continue;\n }\n\n newPrompts.push(entry);\n } catch {\n // Skip invalid JSON\n }\n }\n }\n\n this.lastSize = stats.size;\n this.lastMtime = stats.mtimeMs;\n\n if (newPrompts.length > 0) {\n this.log(\"INFO\", `Detected ${newPrompts.length} new prompt(s), syncing...`);\n\n const response = await this.client.syncClaudePrompts(newPrompts, {\n terminalSessionId: this.sessionFilter,\n projectPath: this.projectFilter,\n });\n\n if (response.success && response.data) {\n this.log(\"INFO\", `Synced ${response.data.promptsCreated} prompt(s)`);\n this.callbacks.onSync?.(response.data.promptsCreated);\n } else {\n this.log(\"ERROR\", `Sync failed: ${response.error}`);\n }\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n this.log(\"ERROR\", `Watch error: ${err.message}`);\n this.callbacks.onError?.(err);\n }\n }\n}\n","/**\n * Skill Usage Detector — detects when Claude Code uses skills.\n *\n * Scans Claude Code session JSONL files for:\n * - Read tool calls to ~/.claude/skills/{slug}/SKILL.md\n * - Bash tool calls matching skill command arrays\n *\n * Tracks byte offsets per file to avoid re-processing.\n * Reports detections to the Skillo platform via batch API.\n */\n\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport * as readline from \"readline\";\nimport { getSkillsDir, getClaudeDir, getDataDir, ensureDirectory } from \"../utils/paths.js\";\n\nexport interface SkillUsageEvent {\n skillSlug: string;\n claudeSessionId: string;\n projectPath: string;\n timestamp: string;\n}\n\ninterface OffsetState {\n [filePath: string]: {\n byteOffset: number;\n lastModified: number;\n };\n}\n\nexport interface SkillUsageDetectorCallbacks {\n onDetection?: (count: number) => void;\n onError?: (error: Error) => void;\n log?: (level: string, msg: string) => void;\n}\n\nconst MAX_NEW_BYTES_PER_FILE = 2 * 1024 * 1024; // 2MB cap per cycle\nconst OFFSETS_FILE = \"skill-usage-offsets.json\";\n\nexport class SkillUsageDetector {\n private intervalId: ReturnType<typeof setInterval> | null = null;\n private intervalMs: number;\n private callbacks: SkillUsageDetectorCallbacks;\n private client: {\n reportSkillUsage: (usages: SkillUsageEvent[]) => Promise<{\n success: boolean;\n data?: { logged: number };\n error?: string;\n }>;\n };\n\n constructor(\n client: SkillUsageDetector[\"client\"],\n options: {\n intervalMs?: number;\n callbacks?: SkillUsageDetectorCallbacks;\n } = {}\n ) {\n this.client = client;\n this.intervalMs = options.intervalMs || 30000;\n this.callbacks = options.callbacks || {};\n }\n\n private log(level: string, msg: string) {\n this.callbacks.log?.(level, msg);\n }\n\n async start() {\n // Initialize offsets to current file sizes\n this.initOffsets();\n this.log(\"INFO\", \"Skill usage detector started\");\n\n // Poll\n this.intervalId = setInterval(() => this.detect(), this.intervalMs);\n }\n\n stop() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = null;\n }\n }\n\n /** Build slug inventory from ~/.claude/skills/ */\n private getDeployedSkillSlugs(): Set<string> {\n const slugs = new Set<string>();\n const skillsDir = getSkillsDir();\n\n if (!fs.existsSync(skillsDir)) return slugs;\n\n try {\n const entries = fs.readdirSync(skillsDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.name.startsWith(\".\")) continue;\n const skillFile = path.join(skillsDir, entry.name, \"SKILL.md\");\n if (fs.existsSync(skillFile)) {\n slugs.add(entry.name);\n }\n }\n } catch {\n // ignore\n }\n\n return slugs;\n }\n\n /** Encode project path to Claude's directory name format */\n private encodeProjectPath(projectPath: string): string {\n // /Users/foo/bar → -Users-foo-bar\n return projectPath.replace(/\\//g, \"-\");\n }\n\n /** Find session JSONL files for tracked projects */\n private getSessionFiles(): string[] {\n const claudeDir = getClaudeDir();\n const projectsDir = path.join(claudeDir, \"projects\");\n\n if (!fs.existsSync(projectsDir)) return [];\n\n const files: string[] = [];\n try {\n const projectDirs = fs.readdirSync(projectsDir, { withFileTypes: true });\n for (const dir of projectDirs) {\n if (!dir.isDirectory()) continue;\n const dirPath = path.join(projectsDir, dir.name);\n const jsonlFiles = fs.readdirSync(dirPath).filter(\n (f) => f.endsWith(\".jsonl\") && !f.startsWith(\"agent-\")\n );\n for (const f of jsonlFiles) {\n files.push(path.join(dirPath, f));\n }\n }\n } catch {\n // ignore\n }\n\n return files;\n }\n\n /** Load persisted offsets */\n private loadOffsets(): OffsetState {\n const offsetsPath = path.join(getDataDir(), OFFSETS_FILE);\n try {\n if (fs.existsSync(offsetsPath)) {\n return JSON.parse(fs.readFileSync(offsetsPath, \"utf-8\"));\n }\n } catch {\n // ignore\n }\n return {};\n }\n\n /** Save offsets */\n private saveOffsets(state: OffsetState) {\n ensureDirectory(getDataDir());\n const offsetsPath = path.join(getDataDir(), OFFSETS_FILE);\n try {\n fs.writeFileSync(offsetsPath, JSON.stringify(state), \"utf-8\");\n } catch {\n // ignore\n }\n }\n\n /** Initialize offsets to current file sizes (skip existing data) */\n private initOffsets() {\n const files = this.getSessionFiles();\n const state = this.loadOffsets();\n\n for (const file of files) {\n if (!state[file]) {\n try {\n const stats = fs.statSync(file);\n state[file] = { byteOffset: stats.size, lastModified: stats.mtimeMs };\n } catch {\n // ignore\n }\n }\n }\n\n this.saveOffsets(state);\n }\n\n /** Main detection cycle */\n async detect() {\n try {\n const skillSlugs = this.getDeployedSkillSlugs();\n if (skillSlugs.size === 0) return; // No skills deployed\n\n const skillsDir = getSkillsDir();\n const files = this.getSessionFiles();\n const offsets = this.loadOffsets();\n const events: SkillUsageEvent[] = [];\n\n for (const file of files) {\n try {\n const stats = fs.statSync(file);\n const currentOffset = offsets[file]?.byteOffset ?? 0;\n const currentMtime = offsets[file]?.lastModified ?? 0;\n\n // Skip unchanged files\n if (stats.size <= currentOffset && stats.mtimeMs === currentMtime) continue;\n\n // Cap how much we read per cycle\n const startOffset = Math.max(currentOffset, stats.size - MAX_NEW_BYTES_PER_FILE);\n\n if (stats.size <= startOffset) {\n offsets[file] = { byteOffset: stats.size, lastModified: stats.mtimeMs };\n continue;\n }\n\n // Read new content\n const stream = fs.createReadStream(file, {\n start: startOffset,\n encoding: \"utf-8\",\n });\n\n const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });\n\n for await (const line of rl) {\n if (!line.trim()) continue;\n\n try {\n const entry = JSON.parse(line);\n const msg = entry?.message;\n if (!msg || msg.role !== \"assistant\") continue;\n\n const content = msg.content;\n if (!Array.isArray(content)) continue;\n\n const sessionId = entry.sessionId || \"\";\n const timestamp = entry.timestamp || new Date().toISOString();\n const cwd = entry.cwd || \"\";\n\n for (const block of content) {\n if (block?.type !== \"tool_use\") continue;\n\n const toolName = block.name;\n const toolInput = block.input || {};\n\n // Signal 1: Read calls to skill files\n if (toolName === \"Read\") {\n const filePath = toolInput.file_path || \"\";\n // Match paths like ~/.claude/skills/{slug}/SKILL.md or full path\n if (filePath.includes(\"/skills/\") && filePath.endsWith(\"/SKILL.md\")) {\n // Extract slug from path\n const parts = filePath.split(\"/skills/\");\n if (parts.length >= 2) {\n const afterSkills = parts[parts.length - 1]; // \"my-skill/SKILL.md\"\n const slug = afterSkills.split(\"/\")[0];\n if (slug && skillSlugs.has(slug)) {\n events.push({\n skillSlug: slug,\n claudeSessionId: sessionId,\n projectPath: cwd,\n timestamp,\n });\n }\n }\n }\n }\n\n // Signal 2: Bash commands matching skill commands\n // (skipped for v1 — Read detection is sufficient)\n }\n } catch {\n // Skip invalid JSON lines\n }\n }\n\n offsets[file] = { byteOffset: stats.size, lastModified: stats.mtimeMs };\n } catch {\n // Skip inaccessible files\n }\n }\n\n // Clean up offsets for deleted files\n for (const key of Object.keys(offsets)) {\n if (!fs.existsSync(key)) {\n delete offsets[key];\n }\n }\n\n this.saveOffsets(offsets);\n\n // Deduplicate: same slug+session within batch\n const seen = new Set<string>();\n const uniqueEvents = events.filter((e) => {\n const key = `${e.skillSlug}:${e.claudeSessionId}:${e.timestamp}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n\n // Report to server\n if (uniqueEvents.length > 0) {\n this.log(\"INFO\", `Detected ${uniqueEvents.length} skill usage(s), reporting...`);\n\n const response = await this.client.reportSkillUsage(uniqueEvents);\n if (response.success) {\n this.log(\"INFO\", `Reported ${response.data?.logged ?? 0} skill usage(s)`);\n this.callbacks.onDetection?.(response.data?.logged ?? 0);\n } else {\n this.log(\"ERROR\", `Report failed: ${response.error}`);\n }\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n this.log(\"ERROR\", `Detection error: ${err.message}`);\n this.callbacks.onError?.(err);\n }\n }\n}\n","/**\n * StatusWriter — writes daemon status JSON for tray icon and diagnostics.\n *\n * Writes ~/.skillo/daemon-status.json every 10s and on key events.\n * Tray icon and `skillo status` read this file.\n */\n\nimport { writeFileSync, readFileSync, existsSync } from \"fs\";\nimport { join } from \"path\";\nimport { getDataDir, ensureDirectory } from \"./paths.js\";\n\nexport interface DaemonStatus {\n running: boolean;\n pid: number | null;\n startedAt: string | null;\n updatedAt: string;\n user?: string;\n claudeWatcher?: {\n lastSync?: string | null;\n promptsSynced?: number;\n lastError?: string | null;\n };\n skillDetector?: {\n deployedSkills?: number;\n usagesDetected?: number;\n lastDetection?: string | null;\n lastError?: string | null;\n };\n trackedProjects?: Array<{ name: string; path: string }>;\n activeSessions?: number;\n}\n\nconst STATUS_FILE = \"daemon-status.json\";\nconst WRITE_INTERVAL_MS = 10000;\n\nexport class StatusWriter {\n private intervalId: ReturnType<typeof setInterval> | null = null;\n private status: DaemonStatus;\n private filePath: string;\n\n constructor() {\n this.filePath = join(getDataDir(), STATUS_FILE);\n this.status = {\n running: true,\n pid: process.pid,\n startedAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n claudeWatcher: { lastSync: null, promptsSynced: 0, lastError: null },\n skillDetector: { deployedSkills: 0, usagesDetected: 0, lastDetection: null, lastError: null },\n trackedProjects: [],\n activeSessions: 0,\n };\n }\n\n /** Start periodic writes */\n start() {\n ensureDirectory(getDataDir());\n this.write();\n this.intervalId = setInterval(() => this.write(), WRITE_INTERVAL_MS);\n }\n\n /** Stop writing and mark as not running */\n stop() {\n if (this.intervalId) {\n clearInterval(this.intervalId);\n this.intervalId = null;\n }\n this.status.running = false;\n this.status.pid = null;\n this.status.updatedAt = new Date().toISOString();\n this.write();\n }\n\n /** Merge partial status updates */\n update(partial: Partial<DaemonStatus>) {\n // Deep merge for nested objects\n if (partial.claudeWatcher) {\n this.status.claudeWatcher = { ...this.status.claudeWatcher, ...partial.claudeWatcher };\n delete partial.claudeWatcher;\n }\n if (partial.skillDetector) {\n this.status.skillDetector = { ...this.status.skillDetector, ...partial.skillDetector };\n delete partial.skillDetector;\n }\n Object.assign(this.status, partial);\n }\n\n /** Get the status file path */\n static getStatusFilePath(): string {\n return join(getDataDir(), STATUS_FILE);\n }\n\n /** Read current status from disk (static, for tray/status commands) */\n static read(): DaemonStatus | null {\n const filePath = StatusWriter.getStatusFilePath();\n try {\n if (existsSync(filePath)) {\n return JSON.parse(readFileSync(filePath, \"utf-8\"));\n }\n } catch {\n // ignore\n }\n return null;\n }\n\n private write() {\n this.status.updatedAt = new Date().toISOString();\n try {\n writeFileSync(this.filePath, JSON.stringify(this.status, null, 2), \"utf-8\");\n } catch {\n // Can't write status, ignore\n }\n }\n}\n"],"mappings":"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","names":["existsSync","writeFileSync","readFileSync","readdirSync","existsSync","path","path","existsSync","path","fs","path","readline","writeFileSync","readFileSync","existsSync","join","join","existsSync","readFileSync","writeFileSync","writeFileSync","existsSync","readdirSync","readFileSync"]}
|
|
1
|
+
{"version":3,"sources":["../src/daemon-runner.ts","../src/core/config.ts","../src/utils/paths.ts","../src/core/api-client.ts","../src/core/claude-watcher.ts","../src/core/skill-usage-detector.ts","../src/core/skill-installer.ts","../src/utils/status-writer.ts","../src/utils/daemon-tasks.ts"],"sourcesContent":["/**\r\n * Daemon Runner - Entry point for the forked daemon child process.\r\n *\r\n * Runs:\r\n * 1. Claude history watcher (auto-syncs conversations for tracked projects)\r\n * 2. Tracked projects cache writer (shell hooks read this for fast detection)\r\n * 3. Stale session cleanup (ends sessions where terminal PID is dead)\r\n */\r\n\r\nimport { existsSync, writeFileSync, unlinkSync, appendFileSync } from \"fs\";\r\nimport { loadConfig } from \"./core/config.js\";\r\nimport { getApiClient } from \"./core/api-client.js\";\r\nimport { getPidFile, getLogFile, getActiveSessionsDir, ensureDirectory } from \"./utils/paths.js\";\r\nimport { ClaudeWatcher, loadTrackedProjects } from \"./core/claude-watcher.js\";\r\nimport { SkillUsageDetector } from \"./core/skill-usage-detector.js\";\r\nimport { SkillInstaller } from \"./core/skill-installer.js\";\r\nimport { StatusWriter } from \"./utils/status-writer.js\";\r\nimport { updateTrackedProjectsCache, cleanupStaleSessions, readTrackedProjectsFromCache } from \"./utils/daemon-tasks.js\";\r\n\r\n// ── Daemon Logger ──\r\n\r\nfunction log(level: string, msg: string) {\r\n const line = `[${new Date().toISOString()}] [${level}] ${msg}\\n`;\r\n try {\r\n appendFileSync(getLogFile(), line);\r\n } catch {\r\n // Can't log, ignore\r\n }\r\n}\r\n\r\n// ── Main Daemon Loop ──\r\n\r\nasync function main() {\r\n const pidFile = getPidFile();\r\n writeFileSync(pidFile, String(process.pid));\r\n\r\n log(\"INFO\", `Daemon starting (PID: ${process.pid})`);\r\n\r\n // Status writer\r\n const statusWriter = new StatusWriter();\r\n\r\n // Signal handlers\r\n const cleanup = () => {\r\n log(\"INFO\", \"Daemon shutting down\");\r\n statusWriter.stop();\r\n if (existsSync(pidFile)) {\r\n try { unlinkSync(pidFile); } catch { /* ignore */ }\r\n }\r\n process.exit(0);\r\n };\r\n process.on(\"SIGINT\", cleanup);\r\n process.on(\"SIGTERM\", cleanup);\r\n\r\n // Load config\r\n let config;\r\n try {\r\n config = loadConfig();\r\n } catch {\r\n log(\"WARN\", \"Could not load config, using defaults\");\r\n config = { daemon: { conversationCheckInterval: 5, patternCheckInterval: 60 } };\r\n }\r\n\r\n // Check API key\r\n const client = getApiClient();\r\n if (!client.hasApiKey()) {\r\n log(\"ERROR\", \"Not logged in. Daemon cannot run without API key.\");\r\n cleanup();\r\n return;\r\n }\r\n\r\n // Fetch user info for status display\r\n try {\r\n const authResult = await client.authenticate() as any;\r\n const user = authResult.data?.user || authResult.user;\r\n if (authResult.success && user) {\r\n statusWriter.update({ user: user.name });\r\n log(\"INFO\", `Authenticated as: ${user.name}`);\r\n }\r\n } catch (err) {\r\n log(\"WARN\", `Could not fetch user info: ${err}`);\r\n }\r\n\r\n // Ensure directories\r\n ensureDirectory(getActiveSessionsDir());\r\n\r\n // Initial cache update + populate status with tracked projects\r\n const projects = await updateTrackedProjectsCache(client, log);\r\n if (projects) {\r\n statusWriter.update({ trackedProjects: projects });\r\n } else {\r\n const cached = readTrackedProjectsFromCache();\r\n if (cached) statusWriter.update({ trackedProjects: cached });\r\n }\r\n\r\n // Clean up stale sessions from previous crashes\r\n await cleanupStaleSessions(client, log);\r\n\r\n // Start Claude watcher\r\n const watchInterval = ((config as { daemon?: { conversationCheckInterval?: number } }).daemon?.conversationCheckInterval || 5) * 1000;\r\n const watcher = new ClaudeWatcher(client, {\r\n intervalMs: watchInterval,\r\n callbacks: {\r\n onSync: (count) => {\r\n log(\"INFO\", `Synced ${count} Claude prompt(s)`);\r\n statusWriter.update({ claudeWatcher: { promptsSynced: count, lastSync: new Date().toISOString(), lastError: null } });\r\n },\r\n onError: (err) => {\r\n log(\"ERROR\", `Watcher error: ${err.message}`);\r\n statusWriter.update({ claudeWatcher: { lastError: err.message } });\r\n },\r\n log: (level, msg) => log(level, msg),\r\n },\r\n });\r\n await watcher.start();\r\n\r\n // Start skill usage detector\r\n const skillDetector = new SkillUsageDetector(client, {\r\n intervalMs: 30000,\r\n callbacks: {\r\n onDetection: (count) => {\r\n log(\"INFO\", `Detected ${count} skill usage(s)`);\r\n statusWriter.update({ skillDetector: { usagesDetected: count, lastDetection: new Date().toISOString(), lastError: null } });\r\n },\r\n onError: (err) => {\r\n log(\"ERROR\", `Skill detection error: ${err.message}`);\r\n statusWriter.update({ skillDetector: { lastError: err.message } });\r\n },\r\n log: (level, msg) => log(level, msg),\r\n },\r\n });\r\n await skillDetector.start();\r\n\r\n // Start skill installer (polls for pending installs/uninstalls)\r\n const skillInstaller = new SkillInstaller(client, {\r\n intervalMs: 30000,\r\n callbacks: {\r\n onInstall: (slug) => {\r\n log(\"INFO\", `Installed skill: ${slug}`);\r\n statusWriter.update({ skillInstaller: { lastInstall: slug, lastAction: new Date().toISOString(), lastError: null } });\r\n },\r\n onUninstall: (slug) => {\r\n log(\"INFO\", `Uninstalled skill: ${slug}`);\r\n statusWriter.update({ skillInstaller: { lastUninstall: slug, lastAction: new Date().toISOString(), lastError: null } });\r\n },\r\n onError: (err) => {\r\n log(\"ERROR\", `Skill installer error: ${err.message}`);\r\n statusWriter.update({ skillInstaller: { lastError: err.message } });\r\n },\r\n log: (level, msg) => log(level, msg),\r\n },\r\n });\r\n await skillInstaller.start();\r\n\r\n // Start status writer\r\n statusWriter.start();\r\n\r\n // Refresh tracked projects cache every 60s\r\n setInterval(async () => {\r\n const updated = await updateTrackedProjectsCache(client, log);\r\n if (updated) statusWriter.update({ trackedProjects: updated });\r\n }, 60000);\r\n\r\n // Clean up stale sessions every 5 minutes\r\n setInterval(() => cleanupStaleSessions(client, log), 300000);\r\n\r\n log(\"INFO\", \"Daemon started successfully — watching Claude conversations & skill usage\");\r\n\r\n // Keep alive\r\n await new Promise(() => {});\r\n}\r\n\r\nmain().catch((err) => {\r\n log(\"ERROR\", `Daemon crashed: ${err}`);\r\n process.exit(1);\r\n});\r\n","/**\r\n * Configuration management for Skillo.\r\n */\r\n\r\nimport { readFileSync, writeFileSync, existsSync } from \"fs\";\r\nimport { dirname } from \"path\";\r\nimport YAML from \"yaml\";\r\nimport { ensureDirectory, getConfigFile } from \"../utils/paths.js\";\r\n\r\nexport interface SkilloConfig {\r\n // Shell settings\r\n defaultShell: string | null;\r\n\r\n // Pattern detection\r\n patternDetection: {\r\n minCount: number;\r\n sessionTimeout: number;\r\n similarityThreshold: number;\r\n maxSequenceLength: number;\r\n };\r\n\r\n // Privacy settings\r\n privacy: {\r\n autoRedact: boolean;\r\n trackOutput: boolean;\r\n neverTrack: string[];\r\n redactionPatterns: string[];\r\n };\r\n\r\n // Notification settings\r\n notifications: {\r\n enabled: boolean;\r\n style: \"inline\" | \"desktop\" | \"both\";\r\n sound: boolean;\r\n };\r\n\r\n // Skill generation\r\n skillGeneration: {\r\n outputDir: string | null;\r\n includeScripts: boolean;\r\n includeExamples: boolean;\r\n autoGenerate: boolean;\r\n };\r\n\r\n // Claude Code integration\r\n claudeCode: {\r\n watchConversations: boolean;\r\n conversationDir: string | null;\r\n };\r\n\r\n // Daemon settings\r\n daemon: {\r\n logLevel: \"DEBUG\" | \"INFO\" | \"WARN\" | \"ERROR\";\r\n patternCheckInterval: number;\r\n conversationCheckInterval: number;\r\n };\r\n\r\n // Team settings\r\n team: {\r\n enabled: boolean;\r\n slug: string | null;\r\n autoSync: boolean;\r\n syncInterval: number;\r\n };\r\n\r\n // API settings\r\n api: {\r\n baseUrl: string | null;\r\n timeout: number;\r\n };\r\n\r\n // API key (stored separately, not in YAML for security)\r\n apiKey?: string;\r\n}\r\n\r\nexport function getDefaultConfig(): SkilloConfig {\r\n return {\r\n defaultShell: null,\r\n\r\n patternDetection: {\r\n minCount: 3,\r\n sessionTimeout: 30,\r\n similarityThreshold: 0.8,\r\n maxSequenceLength: 10,\r\n },\r\n\r\n privacy: {\r\n autoRedact: true,\r\n trackOutput: false,\r\n neverTrack: [\r\n \"*password*\",\r\n \"*secret*\",\r\n \"vault *\",\r\n \"1password *\",\r\n \"op *\",\r\n \"export *_KEY=*\",\r\n \"export *_SECRET=*\",\r\n \"export *_TOKEN=*\",\r\n ],\r\n redactionPatterns: [\r\n \"password[=:]\\\\s*\\\\S+\",\r\n \"token[=:]\\\\s*\\\\S+\",\r\n \"secret[=:]\\\\s*\\\\S+\",\r\n \"api[_-]?key[=:]\\\\s*\\\\S+\",\r\n \"auth[=:]\\\\s*\\\\S+\",\r\n \"bearer\\\\s+\\\\S+\",\r\n \"-----BEGIN.*PRIVATE KEY-----\",\r\n \"AKIA[0-9A-Z]{16}\",\r\n '[\"\\'][A-Za-z0-9+/]{40,}[\"\\']',\r\n ],\r\n },\r\n\r\n notifications: {\r\n enabled: true,\r\n style: \"inline\",\r\n sound: false,\r\n },\r\n\r\n skillGeneration: {\r\n outputDir: null,\r\n includeScripts: true,\r\n includeExamples: true,\r\n autoGenerate: false,\r\n },\r\n\r\n claudeCode: {\r\n watchConversations: true,\r\n conversationDir: null,\r\n },\r\n\r\n daemon: {\r\n logLevel: \"INFO\",\r\n patternCheckInterval: 60,\r\n conversationCheckInterval: 5,\r\n },\r\n\r\n team: {\r\n enabled: false,\r\n slug: null,\r\n autoSync: true,\r\n syncInterval: 300,\r\n },\r\n\r\n api: {\r\n baseUrl: null,\r\n timeout: 30000,\r\n },\r\n };\r\n}\r\n\r\nfunction deepMerge<T extends object>(base: T, override: Partial<T>): T {\r\n const result = { ...base };\r\n\r\n for (const key of Object.keys(override) as Array<keyof T>) {\r\n const value = override[key];\r\n if (\r\n value !== undefined &&\r\n typeof result[key] === \"object\" &&\r\n result[key] !== null &&\r\n typeof value === \"object\" &&\r\n value !== null &&\r\n !Array.isArray(value)\r\n ) {\r\n result[key] = deepMerge(\r\n result[key] as Record<string, unknown>,\r\n value as Record<string, unknown>\r\n ) as T[keyof T];\r\n } else if (value !== undefined) {\r\n result[key] = value as T[keyof T];\r\n }\r\n }\r\n\r\n return result;\r\n}\r\n\r\nexport function loadConfig(path?: string): SkilloConfig {\r\n const configPath = path || getConfigFile();\r\n\r\n if (!existsSync(configPath)) {\r\n return getDefaultConfig();\r\n }\r\n\r\n try {\r\n const content = readFileSync(configPath, \"utf-8\");\r\n const parsed = YAML.parse(content) || {};\r\n\r\n // Convert snake_case to camelCase for compatibility\r\n const converted = convertKeysToCamelCase(parsed);\r\n const defaultConfig = getDefaultConfig();\r\n\r\n // Deep merge with default config\r\n return deepMerge(defaultConfig, converted as Partial<SkilloConfig>);\r\n } catch {\r\n return getDefaultConfig();\r\n }\r\n}\r\n\r\nexport function saveConfig(config: SkilloConfig, path?: string): void {\r\n const configPath = path || getConfigFile();\r\n\r\n ensureDirectory(dirname(configPath));\r\n\r\n // Convert camelCase to snake_case for YAML file\r\n const converted = convertKeysToSnakeCase(config);\r\n\r\n const content = YAML.stringify(converted, {\r\n indent: 2,\r\n lineWidth: 0,\r\n });\r\n\r\n writeFileSync(configPath, content, \"utf-8\");\r\n}\r\n\r\nexport function getConfigValue(config: SkilloConfig, key: string): unknown {\r\n const keys = key.split(\".\");\r\n let current: unknown = config;\r\n\r\n for (const k of keys) {\r\n if (current && typeof current === \"object\" && k in current) {\r\n current = (current as Record<string, unknown>)[k];\r\n } else {\r\n return undefined;\r\n }\r\n }\r\n\r\n return current;\r\n}\r\n\r\nexport function setConfigValue(config: SkilloConfig, key: string, value: unknown): SkilloConfig {\r\n const keys = key.split(\".\");\r\n const result = JSON.parse(JSON.stringify(config)) as SkilloConfig;\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n let current: any = result;\r\n\r\n for (let i = 0; i < keys.length - 1; i++) {\r\n const k = keys[i];\r\n if (!(k in current)) {\r\n current[k] = {};\r\n }\r\n current = current[k] as Record<string, unknown>;\r\n }\r\n\r\n current[keys[keys.length - 1]] = value;\r\n return result;\r\n}\r\n\r\n// Helper functions for key conversion\r\nfunction toCamelCase(str: string): string {\r\n return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());\r\n}\r\n\r\nfunction toSnakeCase(str: string): string {\r\n return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\r\n}\r\n\r\nfunction convertKeysToCamelCase(obj: unknown): unknown {\r\n if (Array.isArray(obj)) {\r\n return obj.map(convertKeysToCamelCase);\r\n }\r\n\r\n if (obj !== null && typeof obj === \"object\") {\r\n const result: Record<string, unknown> = {};\r\n for (const [key, value] of Object.entries(obj)) {\r\n result[toCamelCase(key)] = convertKeysToCamelCase(value);\r\n }\r\n return result;\r\n }\r\n\r\n return obj;\r\n}\r\n\r\nfunction convertKeysToSnakeCase(obj: unknown): unknown {\r\n if (Array.isArray(obj)) {\r\n return obj.map(convertKeysToSnakeCase);\r\n }\r\n\r\n if (obj !== null && typeof obj === \"object\") {\r\n const result: Record<string, unknown> = {};\r\n for (const [key, value] of Object.entries(obj)) {\r\n result[toSnakeCase(key)] = convertKeysToSnakeCase(value);\r\n }\r\n return result;\r\n }\r\n\r\n return obj;\r\n}\r\n","/**\r\n * Path utilities for Skillo.\r\n *\r\n * Handles platform-specific path resolution for data directories,\r\n * configuration files, and skill storage locations.\r\n */\r\n\r\nimport { homedir } from \"os\";\r\nimport { join } from \"path\";\r\nimport { mkdirSync, existsSync } from \"fs\";\r\n\r\nexport function getHomeDir(): string {\r\n return homedir();\r\n}\r\n\r\n/**\r\n * Get Skillo data directory.\r\n * This is where the database and other data files are stored.\r\n *\r\n * - Linux/macOS: ~/.skillo/\r\n * - Windows: %USERPROFILE%/.skillo/\r\n */\r\nexport function getDataDir(): string {\r\n const envPath = process.env.SKILLO_DATA_DIR;\r\n if (envPath) return envPath;\r\n\r\n return join(getHomeDir(), \".skillo\");\r\n}\r\n\r\n/**\r\n * Get Skillo configuration directory.\r\n * This is where config.yaml and other configuration files are stored.\r\n *\r\n * - Linux: ~/.config/skillo/\r\n * - macOS: ~/.config/skillo/\r\n * - Windows: %USERPROFILE%/.config/skillo/\r\n */\r\nexport function getConfigDir(): string {\r\n const envPath = process.env.SKILLO_CONFIG_DIR;\r\n if (envPath) return envPath;\r\n\r\n const xdgConfig = process.env.XDG_CONFIG_HOME;\r\n if (xdgConfig) return join(xdgConfig, \"skillo\");\r\n\r\n return join(getHomeDir(), \".config\", \"skillo\");\r\n}\r\n\r\n/**\r\n * Get personal skills directory.\r\n * This is where generated skills are stored and where Claude Code\r\n * reads personal skills from.\r\n *\r\n * - All platforms: ~/.claude/skills/\r\n */\r\nexport function getSkillsDir(): string {\r\n const envPath = process.env.SKILLO_SKILLS_DIR;\r\n if (envPath) return envPath;\r\n\r\n return join(getClaudeDir(), \"skills\");\r\n}\r\n\r\n/**\r\n * Get Claude Code directory.\r\n * This is where Claude Code stores its data, including conversations.\r\n *\r\n * - All platforms: ~/.claude/\r\n */\r\nexport function getClaudeDir(): string {\r\n return join(getHomeDir(), \".claude\");\r\n}\r\n\r\n/**\r\n * Get project-specific skills directory.\r\n * This is where project-specific skills are stored.\r\n *\r\n * - Path: <project>/.claude/skills/\r\n */\r\nexport function getProjectSkillsDir(projectPath?: string): string {\r\n const project = projectPath || process.cwd();\r\n return join(project, \".claude\", \"skills\");\r\n}\r\n\r\n/**\r\n * Get team skills directory.\r\n * This is where team skills are synced to.\r\n *\r\n * - Path: ~/.claude/skills/team/<team-slug>/\r\n */\r\nexport function getTeamSkillsDir(teamSlug?: string): string {\r\n const base = join(getSkillsDir(), \"team\");\r\n if (teamSlug) return join(base, teamSlug);\r\n return base;\r\n}\r\n\r\n/**\r\n * Ensure a directory exists, creating it if necessary.\r\n *\r\n * @returns True if directory was created, False if it already existed.\r\n */\r\nexport function ensureDirectory(path: string): boolean {\r\n if (existsSync(path)) {\r\n return false;\r\n }\r\n\r\n mkdirSync(path, { recursive: true });\r\n return true;\r\n}\r\n\r\n/**\r\n * Get path to daemon log file.\r\n */\r\nexport function getLogFile(): string {\r\n return join(getDataDir(), \"daemon.log\");\r\n}\r\n\r\n/**\r\n * Get path to daemon PID file.\r\n */\r\nexport function getPidFile(): string {\r\n return join(getDataDir(), \"daemon.pid\");\r\n}\r\n\r\n/**\r\n * Get path to SQLite database file.\r\n */\r\nexport function getDbPath(): string {\r\n return join(getDataDir(), \"skillo.db\");\r\n}\r\n\r\n/**\r\n * Get path to config file.\r\n */\r\nexport function getConfigFile(): string {\r\n return join(getConfigDir(), \"config.yaml\");\r\n}\r\n\r\n/**\r\n * Get path to tracked projects cache file.\r\n * Written by daemon, read by shell hooks for fast project detection.\r\n */\r\nexport function getTrackedProjectsCacheFile(): string {\r\n return join(getDataDir(), \"tracked-projects.json\");\r\n}\r\n\r\n/**\r\n * Get path to active sessions directory.\r\n * Each terminal gets a file named by its PID.\r\n */\r\nexport function getActiveSessionsDir(): string {\r\n return join(getDataDir(), \"active-sessions\");\r\n}\r\n\r\n/**\r\n * Get path to shell integration directory.\r\n */\r\nexport function getShellIntegrationDir(): string {\r\n return join(getDataDir(), \"shell-integration\");\r\n}\r\n","/**\r\n * Skillo Platform API Client\r\n *\r\n * Handles communication with the Skillo platform API.\r\n */\r\n\r\nimport { loadConfig, saveConfig } from \"./config.js\";\r\n\r\nconst DEFAULT_API_URL = \"https://www.skillo.one/api/cli\";\r\n\r\ninterface ApiResponse<T = unknown> {\r\n success: boolean;\r\n data?: T;\r\n error?: string;\r\n message?: string;\r\n}\r\n\r\ninterface AuthResponse {\r\n success: boolean;\r\n user: {\r\n id: string;\r\n email: string;\r\n name: string;\r\n };\r\n}\r\n\r\ninterface SyncResponse {\r\n success: boolean;\r\n skills?: Array<{\r\n id: string;\r\n name: string;\r\n slug: string;\r\n description: string;\r\n content: string;\r\n commands: string[];\r\n }>;\r\n patterns?: Array<{\r\n id: string;\r\n name: string;\r\n description: string;\r\n commands: string[];\r\n status: string;\r\n }>;\r\n}\r\n\r\ninterface GenerateResponse {\r\n success: boolean;\r\n skill: {\r\n id: string;\r\n name: string;\r\n slug: string;\r\n description: string;\r\n content: string;\r\n commands: string[];\r\n };\r\n}\r\n\r\ninterface DeviceAuthResponse {\r\n code: string;\r\n verification_url: string;\r\n expires_in: number;\r\n interval: number;\r\n}\r\n\r\ninterface TokenStatusResponse {\r\n status: \"pending\" | \"ready\" | \"expired\" | \"used\" | \"not_found\";\r\n}\r\n\r\ninterface TokenExchangeResponse {\r\n success: boolean;\r\n api_key: string;\r\n user: {\r\n id: string;\r\n };\r\n}\r\n\r\nexport class ApiClient {\r\n private baseUrl: string;\r\n private apiKey: string | null;\r\n\r\n constructor() {\r\n const config = loadConfig();\r\n this.baseUrl = config.api?.baseUrl || DEFAULT_API_URL;\r\n this.apiKey = this.loadApiKey();\r\n }\r\n\r\n /**\r\n * Load API key from config\r\n */\r\n private loadApiKey(): string | null {\r\n const config = loadConfig();\r\n return (config as { apiKey?: string }).apiKey || null;\r\n }\r\n\r\n /**\r\n * Save API key to config\r\n */\r\n saveApiKey(key: string): void {\r\n const config = loadConfig();\r\n (config as { apiKey?: string }).apiKey = key;\r\n saveConfig(config);\r\n this.apiKey = key;\r\n }\r\n\r\n /**\r\n * Clear API key from config\r\n */\r\n clearApiKey(): void {\r\n const config = loadConfig();\r\n delete (config as { apiKey?: string }).apiKey;\r\n saveConfig(config);\r\n this.apiKey = null;\r\n }\r\n\r\n /**\r\n * Check if API key is configured\r\n */\r\n hasApiKey(): boolean {\r\n return !!this.apiKey;\r\n }\r\n\r\n /**\r\n * Get the configured API key (masked)\r\n */\r\n getMaskedApiKey(): string | null {\r\n if (!this.apiKey) return null;\r\n return `${this.apiKey.substring(0, 12)}...${this.apiKey.substring(this.apiKey.length - 4)}`;\r\n }\r\n\r\n /**\r\n * Set the API base URL\r\n */\r\n setBaseUrl(url: string): void {\r\n const config = loadConfig();\r\n if (!config.api) {\r\n (config as { api?: { baseUrl: string } }).api = { baseUrl: url };\r\n } else {\r\n config.api.baseUrl = url;\r\n }\r\n saveConfig(config);\r\n this.baseUrl = url;\r\n }\r\n\r\n /**\r\n * Safely parse JSON from a fetch response, returning an error ApiResponse on failure.\r\n */\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n private async parseJsonResponse<T>(response: Response): Promise<{ data: any } | ApiResponse<T>> {\r\n try {\r\n return { data: await response.json() };\r\n } catch {\r\n return {\r\n success: false,\r\n error: `Server returned non-JSON response (status ${response.status})`,\r\n };\r\n }\r\n }\r\n\r\n /**\r\n * Make an authenticated API request\r\n */\r\n private async request<T>(\r\n endpoint: string,\r\n options: RequestInit = {}\r\n ): Promise<ApiResponse<T>> {\r\n if (!this.apiKey) {\r\n return { success: false, error: \"No API key configured. Run 'skillo login' first.\" };\r\n }\r\n\r\n const url = `${this.baseUrl}${endpoint}`;\r\n const headers: HeadersInit = {\r\n \"Content-Type\": \"application/json\",\r\n Authorization: `Bearer ${this.apiKey}`,\r\n ...options.headers,\r\n };\r\n\r\n try {\r\n const response = await fetch(url, {\r\n ...options,\r\n headers,\r\n });\r\n\r\n const parsed = await this.parseJsonResponse<T>(response);\r\n if (\"success\" in parsed) return parsed;\r\n const { data } = parsed;\r\n\r\n if (process.env.SKILLO_DEBUG) {\r\n console.log('[DEBUG] Request:', url);\r\n console.log('[DEBUG] Status:', response.status);\r\n console.log('[DEBUG] Response:', JSON.stringify(data));\r\n }\r\n\r\n if (!response.ok) {\r\n return {\r\n success: false,\r\n error: (data.error as string) || `Request failed with status ${response.status}`,\r\n };\r\n }\r\n\r\n // If the response already has success/data structure, return as-is\r\n // Otherwise wrap the data\r\n if (data.success !== undefined && data.data !== undefined) {\r\n return { success: data.success as boolean, data: data.data as T, message: data.message as string };\r\n }\r\n\r\n // If response just has success field (no data wrapper), return as-is\r\n if (data.success !== undefined) {\r\n return data as ApiResponse<T>;\r\n }\r\n\r\n // Legacy: wrap raw data\r\n return { success: true, data: data as T };\r\n } catch (error) {\r\n if (process.env.SKILLO_DEBUG) {\r\n console.log('[DEBUG] Error:', error);\r\n }\r\n if (error instanceof Error) {\r\n if (error.message.includes(\"ECONNREFUSED\")) {\r\n return {\r\n success: false,\r\n error: \"Cannot connect to Skillo platform. Is the server running?\",\r\n };\r\n }\r\n return { success: false, error: error.message };\r\n }\r\n return { success: false, error: \"Unknown error occurred\" };\r\n }\r\n }\r\n\r\n /**\r\n * Authenticate with the platform\r\n */\r\n async authenticate(): Promise<ApiResponse<AuthResponse>> {\r\n return this.request<AuthResponse>(\"/auth\", { method: \"POST\" });\r\n }\r\n\r\n /**\r\n * Sync commands to platform\r\n */\r\n async syncCommands(\r\n commands: Array<{\r\n timestamp: string;\r\n command: string;\r\n normalized: string;\r\n cwd: string;\r\n exitCode?: number | null;\r\n durationMs?: number | null;\r\n sessionId?: string;\r\n variables?: Record<string, string> | null;\r\n }>\r\n ): Promise<ApiResponse> {\r\n return this.request(\"/sync\", {\r\n method: \"POST\",\r\n body: JSON.stringify({\r\n type: \"commands\",\r\n data: commands,\r\n }),\r\n });\r\n }\r\n\r\n /**\r\n * Sync a pattern to platform\r\n */\r\n async syncPattern(pattern: {\r\n sourceType: string;\r\n name?: string;\r\n description?: string;\r\n commands: string[];\r\n category?: string;\r\n frequency?: number;\r\n score?: number;\r\n firstSeen?: string;\r\n lastSeen?: string;\r\n context?: Record<string, unknown>;\r\n }): Promise<ApiResponse<{ pattern: { id: string; name: string; status: string } }>> {\r\n return this.request(\"/sync\", {\r\n method: \"POST\",\r\n body: JSON.stringify({\r\n type: \"pattern\",\r\n data: pattern,\r\n }),\r\n });\r\n }\r\n\r\n /**\r\n * Create a session on the platform\r\n */\r\n async createSession(session: {\r\n startedAt: string;\r\n shell: string;\r\n endedAt?: string;\r\n commandCount?: number;\r\n }): Promise<ApiResponse<{ session: { id: string } }>> {\r\n return this.request(\"/sync\", {\r\n method: \"POST\",\r\n body: JSON.stringify({\r\n type: \"session\",\r\n data: session,\r\n }),\r\n });\r\n }\r\n\r\n /**\r\n * End a session on the platform\r\n */\r\n async endSession(sessionId: string): Promise<ApiResponse<{ success: boolean }>> {\r\n return this.request(\"/sessions\", {\r\n method: \"PATCH\",\r\n body: JSON.stringify({ sessionId }),\r\n });\r\n }\r\n\r\n /**\r\n * Start a new session on the platform (for standalone terminals)\r\n */\r\n async startSession(shell: string, projectPath?: string): Promise<ApiResponse<{ sessionId: string }>> {\r\n return this.request(\"/sessions\", {\r\n method: \"POST\",\r\n body: JSON.stringify({ shell, projectPath }),\r\n });\r\n }\r\n\r\n /**\r\n * Download skills and patterns from platform\r\n */\r\n async downloadData(type: \"all\" | \"skills\" | \"patterns\" = \"all\"): Promise<ApiResponse<SyncResponse>> {\r\n return this.request<SyncResponse>(`/sync?type=${type}`, { method: \"GET\" });\r\n }\r\n\r\n /**\r\n * Generate a skill from a pattern\r\n */\r\n async generateSkill(options: {\r\n patternId?: string;\r\n commands?: string[];\r\n name?: string;\r\n description?: string;\r\n category?: string;\r\n context?: Record<string, unknown>;\r\n }): Promise<ApiResponse<GenerateResponse>> {\r\n return this.request<GenerateResponse>(\"/generate\", {\r\n method: \"POST\",\r\n body: JSON.stringify(options),\r\n });\r\n }\r\n\r\n /**\r\n * Start device authorization flow (no auth required)\r\n */\r\n async startDeviceAuth(deviceName?: string): Promise<ApiResponse<DeviceAuthResponse>> {\r\n const url = `${this.baseUrl}/auth/device`;\r\n try {\r\n const response = await fetch(url, {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: JSON.stringify({ device_name: deviceName }),\r\n });\r\n\r\n const parsed = await this.parseJsonResponse<DeviceAuthResponse>(response);\r\n if (\"success\" in parsed) return parsed;\r\n const { data } = parsed;\r\n\r\n if (!response.ok) {\r\n return {\r\n success: false,\r\n error: data.error || `Request failed with status ${response.status}`,\r\n };\r\n }\r\n\r\n return { success: true, data };\r\n } catch (error) {\r\n if (error instanceof Error) {\r\n if (error.message.includes(\"ECONNREFUSED\")) {\r\n return {\r\n success: false,\r\n error: \"Cannot connect to Skillo platform. Is the server running?\",\r\n };\r\n }\r\n return { success: false, error: error.message };\r\n }\r\n return { success: false, error: \"Unknown error occurred\" };\r\n }\r\n }\r\n\r\n /**\r\n * Check token status (polling)\r\n */\r\n async checkTokenStatus(code: string): Promise<ApiResponse<TokenStatusResponse>> {\r\n const url = `${this.baseUrl}/token?code=${encodeURIComponent(code)}`;\r\n try {\r\n const response = await fetch(url, { method: \"GET\" });\r\n\r\n const parsed = await this.parseJsonResponse<TokenStatusResponse>(response);\r\n if (\"success\" in parsed) return parsed;\r\n const { data } = parsed;\r\n\r\n if (!response.ok) {\r\n return {\r\n success: false,\r\n error: data.error || `Request failed with status ${response.status}`,\r\n };\r\n }\r\n\r\n return { success: true, data };\r\n } catch (error) {\r\n if (error instanceof Error) {\r\n return { success: false, error: error.message };\r\n }\r\n return { success: false, error: \"Unknown error occurred\" };\r\n }\r\n }\r\n\r\n /**\r\n * Exchange code for API key\r\n */\r\n async exchangeToken(code: string, deviceName?: string): Promise<ApiResponse<TokenExchangeResponse>> {\r\n const url = `${this.baseUrl}/token`;\r\n try {\r\n const response = await fetch(url, {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: JSON.stringify({ code, device_name: deviceName }),\r\n });\r\n\r\n const parsed = await this.parseJsonResponse<TokenExchangeResponse>(response);\r\n if (\"success\" in parsed) return parsed;\r\n const { data } = parsed;\r\n\r\n if (!response.ok) {\r\n return {\r\n success: false,\r\n error: data.error || `Request failed with status ${response.status}`,\r\n };\r\n }\r\n\r\n return { success: true, data };\r\n } catch (error) {\r\n if (error instanceof Error) {\r\n return { success: false, error: error.message };\r\n }\r\n return { success: false, error: \"Unknown error occurred\" };\r\n }\r\n }\r\n\r\n /**\r\n * Sync Claude Code prompts to platform\r\n */\r\n async syncClaudePrompts(\r\n prompts: Array<{\r\n display: string;\r\n timestamp: number;\r\n project: string;\r\n sessionId: string;\r\n pastedContents?: Record<string, unknown>;\r\n toolsUsed?: string[];\r\n filesReferenced?: string[];\r\n }>,\r\n options?: {\r\n terminalSessionId?: string;\r\n projectPath?: string;\r\n }\r\n ): Promise<ApiResponse<{\r\n sessionsCreated: number;\r\n promptsCreated: number;\r\n promptsSkipped: number;\r\n }>> {\r\n return this.request(\"/claude\", {\r\n method: \"POST\",\r\n body: JSON.stringify({\r\n prompts,\r\n terminalSessionId: options?.terminalSessionId,\r\n projectPath: options?.projectPath,\r\n }),\r\n });\r\n }\r\n\r\n /**\r\n * Get Claude Code sessions from platform\r\n */\r\n async getClaudeSessions(limit = 20): Promise<ApiResponse<{\r\n sessions: Array<{\r\n id: string;\r\n claudeSessionId: string;\r\n projectPath: string;\r\n projectName: string;\r\n startedAt: string;\r\n endedAt?: string;\r\n promptCount: number;\r\n prompts?: Array<{\r\n id: string;\r\n prompt: string;\r\n timestamp: string;\r\n category?: string;\r\n }>;\r\n }>;\r\n }>> {\r\n return this.request(`/claude?limit=${limit}`, { method: \"GET\" });\r\n }\r\n\r\n /**\r\n * Report skill usage detections to platform\r\n */\r\n async reportSkillUsage(\r\n usages: Array<{\r\n skillSlug: string;\r\n claudeSessionId: string;\r\n projectPath: string;\r\n timestamp: string;\r\n }>\r\n ): Promise<ApiResponse<{ logged: number }>> {\r\n return this.request(\"/skill-usage\", {\r\n method: \"POST\",\r\n body: JSON.stringify({ usages }),\r\n });\r\n }\r\n\r\n // ============================================================================\r\n // SKILL INSTALLATION\r\n // ============================================================================\r\n\r\n /**\r\n * Get pending skill installs/uninstalls (daemon polling)\r\n */\r\n async getPendingInstalls(): Promise<ApiResponse<{\r\n pendingInstalls: Array<{\r\n subscriptionId: string;\r\n slug: string;\r\n content: string;\r\n }>;\r\n pendingUninstalls: Array<{\r\n subscriptionId: string;\r\n slug: string;\r\n }>;\r\n }>> {\r\n return this.request(\"/skills/pending-installs\", { method: \"GET\" });\r\n }\r\n\r\n /**\r\n * Report install/uninstall completion (daemon)\r\n */\r\n async reportInstallComplete(\r\n subscriptionId: string,\r\n action: \"installed\" | \"uninstalled\"\r\n ): Promise<ApiResponse<{ ok: boolean }>> {\r\n return this.request(\"/skills/install-complete\", {\r\n method: \"POST\",\r\n body: JSON.stringify({ subscriptionId, action }),\r\n });\r\n }\r\n\r\n /**\r\n * Install a skill by slug (CLI direct install)\r\n */\r\n async installSkill(slug: string): Promise<ApiResponse<{\r\n subscription: { id: string };\r\n slug: string;\r\n name: string;\r\n content: string;\r\n }>> {\r\n return this.request(\"/skills/install\", {\r\n method: \"POST\",\r\n body: JSON.stringify({ slug }),\r\n });\r\n }\r\n\r\n /**\r\n * Uninstall a skill by slug (CLI)\r\n */\r\n async uninstallSkill(slug: string): Promise<ApiResponse<{ ok: boolean }>> {\r\n return this.request(\"/skills/uninstall\", {\r\n method: \"POST\",\r\n body: JSON.stringify({ slug }),\r\n });\r\n }\r\n\r\n // ============================================================================\r\n // PROJECT TRACKING\r\n // ============================================================================\r\n\r\n /**\r\n * Connect a project for tracking\r\n */\r\n async connectProject(params: {\r\n path: string;\r\n name?: string;\r\n gitRemote?: string;\r\n gitRemoteNormalized?: string;\r\n language?: string;\r\n framework?: string;\r\n }): Promise<ApiResponse<{\r\n id: string;\r\n name: string;\r\n path: string;\r\n trackingEnabled: boolean;\r\n connectedAt: string;\r\n }>> {\r\n return this.request(\"/projects/connect\", {\r\n method: \"POST\",\r\n body: JSON.stringify(params),\r\n });\r\n }\r\n\r\n /**\r\n * Disconnect a project from tracking\r\n */\r\n async disconnectProject(path: string, gitRemoteNormalized?: string): Promise<ApiResponse<{\r\n id: string;\r\n name: string;\r\n trackingEnabled: boolean;\r\n }>> {\r\n const params = new URLSearchParams({ path });\r\n if (gitRemoteNormalized) params.set(\"gitRemoteNormalized\", gitRemoteNormalized);\r\n return this.request(`/projects/connect?${params.toString()}`, {\r\n method: \"DELETE\",\r\n });\r\n }\r\n\r\n /**\r\n * Get tracking status for a project\r\n */\r\n async getProjectStatus(path: string, gitRemoteNormalized?: string): Promise<ApiResponse<{\r\n connected: boolean;\r\n tracked: boolean;\r\n connectedAt?: string;\r\n project?: {\r\n id: string;\r\n name: string;\r\n path: string;\r\n gitRemote?: string;\r\n gitRemoteNormalized?: string;\r\n trackingEnabled: boolean;\r\n };\r\n }>> {\r\n const params = new URLSearchParams({ path });\r\n if (gitRemoteNormalized) params.set(\"gitRemoteNormalized\", gitRemoteNormalized);\r\n return this.request(`/projects/connect?${params.toString()}`, {\r\n method: \"GET\",\r\n });\r\n }\r\n\r\n /**\r\n * List all tracked projects\r\n */\r\n async listProjects(includeDisabled = false): Promise<ApiResponse<{\r\n projects: Array<{\r\n id: string;\r\n name: string;\r\n path: string;\r\n gitRemote?: string;\r\n gitRemoteNormalized?: string;\r\n trackingEnabled: boolean;\r\n connectedAt?: string;\r\n }>;\r\n totalTracked: number;\r\n totalProjects: number;\r\n }>> {\r\n return this.request(`/projects/tracked?includeDisabled=${includeDisabled}`, {\r\n method: \"GET\",\r\n });\r\n }\r\n\r\n /**\r\n * Check if a path is in a tracked project\r\n * Returns the project if tracked, null if not\r\n */\r\n async isProjectTracked(path: string): Promise<{\r\n tracked: boolean;\r\n project?: {\r\n id: string;\r\n name: string;\r\n path: string;\r\n };\r\n }> {\r\n const result = await this.getProjectStatus(path);\r\n if (result.success && result.data?.connected) {\r\n return {\r\n tracked: true,\r\n project: result.data.project ? {\r\n id: result.data.project.id,\r\n name: result.data.project.name,\r\n path: result.data.project.path,\r\n } : undefined,\r\n };\r\n }\r\n return { tracked: false };\r\n }\r\n}\r\n\r\n// Singleton instance\r\nlet clientInstance: ApiClient | null = null;\r\n\r\nexport function getApiClient(): ApiClient {\r\n if (!clientInstance) {\r\n clientInstance = new ApiClient();\r\n }\r\n return clientInstance;\r\n}\r\n","/**\r\n * Claude Watcher - Reusable module for watching Claude Code history.\r\n *\r\n * Polls ~/.claude/history.jsonl for new entries and syncs them to the platform.\r\n * Used by both the daemon (background) and the `claude watch` command (foreground).\r\n */\r\n\r\nimport * as fs from \"fs\";\r\nimport * as path from \"path\";\r\nimport * as os from \"os\";\r\nimport * as readline from \"readline\";\r\n\r\n// Types\r\nexport interface ClaudeHistoryEntry {\r\n display: string;\r\n timestamp: number;\r\n project: string;\r\n sessionId: string;\r\n pastedContents?: Record<string, unknown>;\r\n toolsUsed?: string[];\r\n filesReferenced?: string[];\r\n}\r\n\r\ninterface TrackedProjectInfo {\r\n tracked: boolean;\r\n name?: string;\r\n}\r\n\r\nexport interface ClaudeWatcherCallbacks {\r\n onSync?: (promptsCreated: number) => void;\r\n onError?: (error: Error) => void;\r\n log?: (level: string, msg: string) => void;\r\n}\r\n\r\n// ── Path helpers ──\r\n\r\nfunction getClaudeHistoryPath(): string {\r\n return path.join(os.homedir(), \".claude\", \"history.jsonl\");\r\n}\r\n\r\nfunction normalizePath(p: string): string {\r\n return p.toLowerCase().replace(/\\\\/g, \"/\").replace(/\\/+$/, \"\");\r\n}\r\n\r\nfunction matchesProjectPath(claudeProjectPath: string, filterPath: string): boolean {\r\n const normalizedClaude = normalizePath(claudeProjectPath);\r\n const normalizedFilter = normalizePath(filterPath);\r\n return normalizedClaude === normalizedFilter ||\r\n normalizedClaude.startsWith(normalizedFilter + \"/\");\r\n}\r\n\r\n// ── Project tracking cache ──\r\n\r\nconst projectCache = {\r\n projects: new Map<string, TrackedProjectInfo>(),\r\n gitRemoteIndex: new Map<string, TrackedProjectInfo>(),\r\n lastFetched: 0,\r\n};\r\n\r\nconst CACHE_TTL_MS = 60000;\r\n\r\nexport async function loadTrackedProjects(\r\n client: { listProjects: (includeDisabled: boolean) => Promise<{ success: boolean; data?: { projects: Array<{ path: string; name: string; trackingEnabled: boolean; gitRemoteNormalized?: string }> } }> }\r\n): Promise<Map<string, TrackedProjectInfo>> {\r\n const now = Date.now();\r\n\r\n if (projectCache.lastFetched > 0 && now - projectCache.lastFetched < CACHE_TTL_MS) {\r\n return projectCache.projects;\r\n }\r\n\r\n try {\r\n const result = await client.listProjects(true);\r\n if (result.success && result.data?.projects) {\r\n projectCache.projects.clear();\r\n projectCache.gitRemoteIndex.clear();\r\n for (const project of result.data.projects) {\r\n const normalizedPath = normalizePath(project.path);\r\n const info: TrackedProjectInfo = {\r\n tracked: project.trackingEnabled,\r\n name: project.name,\r\n };\r\n projectCache.projects.set(normalizedPath, info);\r\n // Also index by gitRemoteNormalized for cross-machine matching\r\n if (project.gitRemoteNormalized) {\r\n projectCache.gitRemoteIndex.set(project.gitRemoteNormalized, info);\r\n }\r\n }\r\n projectCache.lastFetched = now;\r\n }\r\n } catch {\r\n // Use stale cache on failure\r\n }\r\n\r\n return projectCache.projects;\r\n}\r\n\r\nexport function getTrackedProjectCount(): number {\r\n return Array.from(projectCache.projects.values()).filter(p => p.tracked).length;\r\n}\r\n\r\nasync function isProjectTracked(projectPath: string): Promise<TrackedProjectInfo> {\r\n const normalizedPath = normalizePath(projectPath);\r\n\r\n // Check by path (exact or parent match)\r\n if (projectCache.projects.has(normalizedPath)) {\r\n return projectCache.projects.get(normalizedPath)!;\r\n }\r\n\r\n for (const [trackedPath, info] of projectCache.projects.entries()) {\r\n if (normalizedPath.startsWith(trackedPath + \"/\")) {\r\n return info;\r\n }\r\n }\r\n\r\n // Fall back to matching by git remote (handles different paths on different machines)\r\n if (projectCache.gitRemoteIndex.size > 0) {\r\n try {\r\n const { getGitInfo } = await import(\"../utils/git.js\");\r\n const gitInfo = getGitInfo(projectPath);\r\n if (gitInfo.remoteNormalized) {\r\n const remoteMatch = projectCache.gitRemoteIndex.get(gitInfo.remoteNormalized);\r\n if (remoteMatch) return remoteMatch;\r\n }\r\n } catch (error) {\r\n if (process.env.DEBUG) {\r\n console.error(\"[skillo] git detection failed:\", error);\r\n }\r\n }\r\n }\r\n\r\n return { tracked: false };\r\n}\r\n\r\n// ── Watcher ──\r\n\r\nexport class ClaudeWatcher {\r\n private lastSize = 0;\r\n private lastMtime = 0;\r\n private intervalId: ReturnType<typeof setInterval> | null = null;\r\n private historyPath: string;\r\n private intervalMs: number;\r\n private projectFilter?: string;\r\n private sessionFilter?: string;\r\n private callbacks: ClaudeWatcherCallbacks;\r\n private client: {\r\n syncClaudePrompts: (prompts: ClaudeHistoryEntry[], options?: { terminalSessionId?: string; projectPath?: string }) => Promise<{ success: boolean; data?: { promptsCreated: number; sessionsCreated: number; promptsSkipped: number }; error?: string }>;\r\n listProjects: (includeDisabled: boolean) => Promise<{ success: boolean; data?: { projects: Array<{ path: string; name: string; trackingEnabled: boolean }> } }>;\r\n };\r\n\r\n constructor(\r\n client: ClaudeWatcher[\"client\"],\r\n options: {\r\n intervalMs?: number;\r\n projectFilter?: string;\r\n sessionFilter?: string;\r\n callbacks?: ClaudeWatcherCallbacks;\r\n } = {}\r\n ) {\r\n this.client = client;\r\n this.historyPath = getClaudeHistoryPath();\r\n this.intervalMs = options.intervalMs || 5000;\r\n this.projectFilter = options.projectFilter;\r\n this.sessionFilter = options.sessionFilter;\r\n this.callbacks = options.callbacks || {};\r\n }\r\n\r\n private log(level: string, msg: string) {\r\n if (this.callbacks.log) {\r\n this.callbacks.log(level, msg);\r\n }\r\n }\r\n\r\n /** Initialize file position to current end (don't sync old entries). */\r\n initPosition() {\r\n try {\r\n if (fs.existsSync(this.historyPath)) {\r\n const stats = fs.statSync(this.historyPath);\r\n this.lastSize = stats.size;\r\n this.lastMtime = stats.mtimeMs;\r\n }\r\n } catch {\r\n // File doesn't exist yet\r\n }\r\n }\r\n\r\n /** Start watching. Runs an initial check then polls on interval. */\r\n async start() {\r\n this.initPosition();\r\n\r\n // Load tracked projects\r\n await loadTrackedProjects(this.client);\r\n const trackedCount = getTrackedProjectCount();\r\n this.log(\"INFO\", `Watching ${trackedCount} tracked project(s)`);\r\n\r\n // Initial check\r\n await this.checkAndSync();\r\n\r\n // Poll\r\n this.intervalId = setInterval(() => this.checkAndSync(), this.intervalMs);\r\n }\r\n\r\n /** Stop watching. */\r\n stop() {\r\n if (this.intervalId) {\r\n clearInterval(this.intervalId);\r\n this.intervalId = null;\r\n }\r\n }\r\n\r\n /** Check for new entries and sync them. */\r\n async checkAndSync() {\r\n try {\r\n if (!fs.existsSync(this.historyPath)) return;\r\n\r\n const stats = fs.statSync(this.historyPath);\r\n\r\n // Skip if no changes\r\n if (stats.size === this.lastSize && stats.mtimeMs === this.lastMtime) {\r\n return;\r\n }\r\n\r\n // Refresh tracked projects cache\r\n await loadTrackedProjects(this.client);\r\n\r\n // Read new content\r\n const newPrompts: ClaudeHistoryEntry[] = [];\r\n\r\n if (stats.size > this.lastSize) {\r\n const stream = fs.createReadStream(this.historyPath, {\r\n start: this.lastSize,\r\n encoding: \"utf-8\",\r\n });\r\n\r\n const rl = readline.createInterface({\r\n input: stream,\r\n crlfDelay: Infinity,\r\n });\r\n\r\n for await (const line of rl) {\r\n if (!line.trim()) continue;\r\n\r\n try {\r\n const entry = JSON.parse(line) as ClaudeHistoryEntry;\r\n if (!entry.display || !entry.timestamp || !entry.project || !entry.sessionId) {\r\n continue;\r\n }\r\n\r\n // Project path filter\r\n if (this.projectFilter && !matchesProjectPath(entry.project, this.projectFilter)) {\r\n continue;\r\n }\r\n\r\n // Privacy: only tracked projects\r\n const trackingStatus = await isProjectTracked(entry.project);\r\n if (!trackingStatus.tracked) {\r\n continue;\r\n }\r\n\r\n // Extract tool and file metadata from prompt text\r\n entry.toolsUsed = extractToolsUsed(entry.display);\r\n entry.filesReferenced = extractFilesReferenced(entry.display);\r\n\r\n newPrompts.push(entry);\r\n } catch {\r\n // Skip invalid JSON\r\n }\r\n }\r\n }\r\n\r\n this.lastSize = stats.size;\r\n this.lastMtime = stats.mtimeMs;\r\n\r\n if (newPrompts.length > 0) {\r\n this.log(\"INFO\", `Detected ${newPrompts.length} new prompt(s), syncing...`);\r\n\r\n const response = await this.client.syncClaudePrompts(newPrompts, {\r\n terminalSessionId: this.sessionFilter,\r\n projectPath: this.projectFilter,\r\n });\r\n\r\n if (response.success && response.data) {\r\n this.log(\"INFO\", `Synced ${response.data.promptsCreated} prompt(s)`);\r\n this.callbacks.onSync?.(response.data.promptsCreated);\r\n } else {\r\n this.log(\"ERROR\", `Sync failed: ${response.error}`);\r\n }\r\n }\r\n } catch (error) {\r\n const err = error instanceof Error ? error : new Error(String(error));\r\n this.log(\"ERROR\", `Watch error: ${err.message}`);\r\n this.callbacks.onError?.(err);\r\n }\r\n }\r\n}\r\n\r\n// ── Metadata extraction helpers ──\r\n\r\nconst TOOL_PATTERNS = [\r\n /\\b(grep|rg|ripgrep)\\b/i,\r\n /\\b(git)\\s+(commit|push|pull|merge|rebase|checkout|branch|stash|log|diff|add|reset)\\b/i,\r\n /\\b(npm|yarn|pnpm|bun)\\s+(install|run|build|test|dev|start|publish)\\b/i,\r\n /\\b(docker|docker-compose|podman)\\s+\\w+/i,\r\n /\\b(curl|wget|fetch)\\b/i,\r\n /\\b(vim|nvim|nano|code|emacs)\\b/i,\r\n /\\b(make|cmake|cargo|go\\s+build|rustc|gcc|clang)\\b/i,\r\n /\\b(pytest|jest|vitest|mocha|cypress|playwright)\\b/i,\r\n /\\b(prisma|drizzle|sequelize|typeorm)\\s+\\w+/i,\r\n /\\b(kubectl|helm|terraform|ansible)\\s+\\w+/i,\r\n];\r\n\r\nfunction extractToolsUsed(text: string): string[] {\r\n const tools = new Set<string>();\r\n for (const pattern of TOOL_PATTERNS) {\r\n const match = text.match(pattern);\r\n if (match) {\r\n tools.add(match[0].trim().toLowerCase());\r\n }\r\n }\r\n return Array.from(tools);\r\n}\r\n\r\n// Match file paths that look intentional: must contain a slash OR a known config filename\r\nconst FILE_PATTERN = /(?:^|\\s|['\"`])((?:\\.{0,2}\\/)?[\\w-]+\\/[\\w./-]+\\.(?:ts|tsx|js|jsx|mjs|cjs|json|yaml|yml|toml|md|css|scss|html|sql|py|rs|go|java|rb|sh|prisma|graphql))\\b/gi;\r\nconst CONFIG_FILE_PATTERN = /(?:^|\\s|['\"`])((?:package\\.json|tsconfig\\.json|\\.env|Dockerfile|Makefile|docker-compose\\.ya?ml|\\.gitignore|prisma\\/schema\\.prisma))\\b/gi;\r\n\r\nfunction extractFilesReferenced(text: string): string[] {\r\n const files = new Set<string>();\r\n let match;\r\n while ((match = FILE_PATTERN.exec(text)) !== null) {\r\n const filePath = match[1];\r\n if (filePath.length < 200 && !filePath.includes(\"..\")) {\r\n files.add(filePath);\r\n }\r\n }\r\n FILE_PATTERN.lastIndex = 0;\r\n while ((match = CONFIG_FILE_PATTERN.exec(text)) !== null) {\r\n files.add(match[1]);\r\n }\r\n CONFIG_FILE_PATTERN.lastIndex = 0;\r\n return Array.from(files);\r\n}\r\n","/**\r\n * Skill Usage Detector — detects when Claude Code uses skills.\r\n *\r\n * Scans Claude Code session JSONL files for:\r\n * - Read tool calls to ~/.claude/skills/{slug}/SKILL.md\r\n * - Bash tool calls matching skill command arrays\r\n *\r\n * Tracks byte offsets per file to avoid re-processing.\r\n * Reports detections to the Skillo platform via batch API.\r\n */\r\n\r\nimport * as fs from \"fs\";\r\nimport * as path from \"path\";\r\nimport * as readline from \"readline\";\r\nimport { getSkillsDir, getClaudeDir, getDataDir, ensureDirectory } from \"../utils/paths.js\";\r\n\r\nexport interface SkillUsageEvent {\r\n skillSlug: string;\r\n claudeSessionId: string;\r\n projectPath: string;\r\n timestamp: string;\r\n}\r\n\r\ninterface OffsetState {\r\n [filePath: string]: {\r\n byteOffset: number;\r\n lastModified: number;\r\n };\r\n}\r\n\r\nexport interface SkillUsageDetectorCallbacks {\r\n onDetection?: (count: number) => void;\r\n onError?: (error: Error) => void;\r\n log?: (level: string, msg: string) => void;\r\n}\r\n\r\nconst MAX_NEW_BYTES_PER_FILE = 2 * 1024 * 1024; // 2MB cap per cycle\r\nconst OFFSETS_FILE = \"skill-usage-offsets.json\";\r\n\r\nexport class SkillUsageDetector {\r\n private intervalId: ReturnType<typeof setInterval> | null = null;\r\n private intervalMs: number;\r\n private callbacks: SkillUsageDetectorCallbacks;\r\n private client: {\r\n reportSkillUsage: (usages: SkillUsageEvent[]) => Promise<{\r\n success: boolean;\r\n data?: { logged: number };\r\n error?: string;\r\n }>;\r\n };\r\n\r\n constructor(\r\n client: SkillUsageDetector[\"client\"],\r\n options: {\r\n intervalMs?: number;\r\n callbacks?: SkillUsageDetectorCallbacks;\r\n } = {}\r\n ) {\r\n this.client = client;\r\n this.intervalMs = options.intervalMs || 30000;\r\n this.callbacks = options.callbacks || {};\r\n }\r\n\r\n private log(level: string, msg: string) {\r\n this.callbacks.log?.(level, msg);\r\n }\r\n\r\n async start() {\r\n // Initialize offsets to current file sizes\r\n this.initOffsets();\r\n this.log(\"INFO\", \"Skill usage detector started\");\r\n\r\n // Poll\r\n this.intervalId = setInterval(() => this.detect(), this.intervalMs);\r\n }\r\n\r\n stop() {\r\n if (this.intervalId) {\r\n clearInterval(this.intervalId);\r\n this.intervalId = null;\r\n }\r\n }\r\n\r\n /** Build slug inventory from ~/.claude/skills/ */\r\n private getDeployedSkillSlugs(): Set<string> {\r\n const slugs = new Set<string>();\r\n const skillsDir = getSkillsDir();\r\n\r\n if (!fs.existsSync(skillsDir)) return slugs;\r\n\r\n try {\r\n const entries = fs.readdirSync(skillsDir, { withFileTypes: true });\r\n for (const entry of entries) {\r\n if (!entry.isDirectory() || entry.name.startsWith(\".\")) continue;\r\n const skillFile = path.join(skillsDir, entry.name, \"SKILL.md\");\r\n if (fs.existsSync(skillFile)) {\r\n slugs.add(entry.name);\r\n }\r\n }\r\n } catch {\r\n // ignore\r\n }\r\n\r\n return slugs;\r\n }\r\n\r\n /** Encode project path to Claude's directory name format */\r\n private encodeProjectPath(projectPath: string): string {\r\n // /Users/foo/bar → -Users-foo-bar\r\n return projectPath.replace(/\\//g, \"-\");\r\n }\r\n\r\n /** Find session JSONL files for tracked projects */\r\n private getSessionFiles(): string[] {\r\n const claudeDir = getClaudeDir();\r\n const projectsDir = path.join(claudeDir, \"projects\");\r\n\r\n if (!fs.existsSync(projectsDir)) return [];\r\n\r\n const files: string[] = [];\r\n try {\r\n const projectDirs = fs.readdirSync(projectsDir, { withFileTypes: true });\r\n for (const dir of projectDirs) {\r\n if (!dir.isDirectory()) continue;\r\n const dirPath = path.join(projectsDir, dir.name);\r\n const jsonlFiles = fs.readdirSync(dirPath).filter(\r\n (f) => f.endsWith(\".jsonl\") && !f.startsWith(\"agent-\")\r\n );\r\n for (const f of jsonlFiles) {\r\n files.push(path.join(dirPath, f));\r\n }\r\n }\r\n } catch {\r\n // ignore\r\n }\r\n\r\n return files;\r\n }\r\n\r\n /** Load persisted offsets */\r\n private loadOffsets(): OffsetState {\r\n const offsetsPath = path.join(getDataDir(), OFFSETS_FILE);\r\n try {\r\n if (fs.existsSync(offsetsPath)) {\r\n return JSON.parse(fs.readFileSync(offsetsPath, \"utf-8\"));\r\n }\r\n } catch {\r\n // ignore\r\n }\r\n return {};\r\n }\r\n\r\n /** Save offsets */\r\n private saveOffsets(state: OffsetState) {\r\n ensureDirectory(getDataDir());\r\n const offsetsPath = path.join(getDataDir(), OFFSETS_FILE);\r\n try {\r\n fs.writeFileSync(offsetsPath, JSON.stringify(state), \"utf-8\");\r\n } catch {\r\n // ignore\r\n }\r\n }\r\n\r\n /** Initialize offsets to current file sizes (skip existing data) */\r\n private initOffsets() {\r\n const files = this.getSessionFiles();\r\n const state = this.loadOffsets();\r\n\r\n for (const file of files) {\r\n if (!state[file]) {\r\n try {\r\n const stats = fs.statSync(file);\r\n state[file] = { byteOffset: stats.size, lastModified: stats.mtimeMs };\r\n } catch {\r\n // ignore\r\n }\r\n }\r\n }\r\n\r\n this.saveOffsets(state);\r\n }\r\n\r\n /** Main detection cycle */\r\n async detect() {\r\n try {\r\n const skillSlugs = this.getDeployedSkillSlugs();\r\n if (skillSlugs.size === 0) return; // No skills deployed\r\n\r\n const skillsDir = getSkillsDir();\r\n const files = this.getSessionFiles();\r\n const offsets = this.loadOffsets();\r\n const events: SkillUsageEvent[] = [];\r\n\r\n for (const file of files) {\r\n try {\r\n const stats = fs.statSync(file);\r\n const currentOffset = offsets[file]?.byteOffset ?? 0;\r\n const currentMtime = offsets[file]?.lastModified ?? 0;\r\n\r\n // Skip unchanged files\r\n if (stats.size <= currentOffset && stats.mtimeMs === currentMtime) continue;\r\n\r\n // Cap how much we read per cycle\r\n const startOffset = Math.max(currentOffset, stats.size - MAX_NEW_BYTES_PER_FILE);\r\n\r\n if (stats.size <= startOffset) {\r\n offsets[file] = { byteOffset: stats.size, lastModified: stats.mtimeMs };\r\n continue;\r\n }\r\n\r\n // Read new content\r\n const stream = fs.createReadStream(file, {\r\n start: startOffset,\r\n encoding: \"utf-8\",\r\n });\r\n\r\n const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });\r\n\r\n for await (const line of rl) {\r\n if (!line.trim()) continue;\r\n\r\n try {\r\n const entry = JSON.parse(line);\r\n const msg = entry?.message;\r\n if (!msg || msg.role !== \"assistant\") continue;\r\n\r\n const content = msg.content;\r\n if (!Array.isArray(content)) continue;\r\n\r\n const sessionId = entry.sessionId || \"\";\r\n const timestamp = entry.timestamp || new Date().toISOString();\r\n const cwd = entry.cwd || \"\";\r\n\r\n for (const block of content) {\r\n if (block?.type !== \"tool_use\") continue;\r\n\r\n const toolName = block.name;\r\n const toolInput = block.input || {};\r\n\r\n // Signal 1: Read calls to skill files\r\n if (toolName === \"Read\") {\r\n const filePath = toolInput.file_path || \"\";\r\n // Match paths like ~/.claude/skills/{slug}/SKILL.md or full path\r\n if (filePath.includes(\"/skills/\") && filePath.endsWith(\"/SKILL.md\")) {\r\n // Extract slug from path\r\n const parts = filePath.split(\"/skills/\");\r\n if (parts.length >= 2) {\r\n const afterSkills = parts[parts.length - 1]; // \"my-skill/SKILL.md\"\r\n const slug = afterSkills.split(\"/\")[0];\r\n if (slug && skillSlugs.has(slug)) {\r\n events.push({\r\n skillSlug: slug,\r\n claudeSessionId: sessionId,\r\n projectPath: cwd,\r\n timestamp,\r\n });\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Signal 2: Bash commands matching skill commands\r\n // (skipped for v1 — Read detection is sufficient)\r\n }\r\n } catch {\r\n // Skip invalid JSON lines\r\n }\r\n }\r\n\r\n offsets[file] = { byteOffset: stats.size, lastModified: stats.mtimeMs };\r\n } catch {\r\n // Skip inaccessible files\r\n }\r\n }\r\n\r\n // Clean up offsets for deleted files\r\n for (const key of Object.keys(offsets)) {\r\n if (!fs.existsSync(key)) {\r\n delete offsets[key];\r\n }\r\n }\r\n\r\n this.saveOffsets(offsets);\r\n\r\n // Deduplicate: same slug+session within batch\r\n const seen = new Set<string>();\r\n const uniqueEvents = events.filter((e) => {\r\n const key = `${e.skillSlug}:${e.claudeSessionId}:${e.timestamp}`;\r\n if (seen.has(key)) return false;\r\n seen.add(key);\r\n return true;\r\n });\r\n\r\n // Report to server\r\n if (uniqueEvents.length > 0) {\r\n this.log(\"INFO\", `Detected ${uniqueEvents.length} skill usage(s), reporting...`);\r\n\r\n const response = await this.client.reportSkillUsage(uniqueEvents);\r\n if (response.success) {\r\n this.log(\"INFO\", `Reported ${response.data?.logged ?? 0} skill usage(s)`);\r\n this.callbacks.onDetection?.(response.data?.logged ?? 0);\r\n } else {\r\n this.log(\"ERROR\", `Report failed: ${response.error}`);\r\n }\r\n }\r\n } catch (error) {\r\n const err = error instanceof Error ? error : new Error(String(error));\r\n this.log(\"ERROR\", `Detection error: ${err.message}`);\r\n this.callbacks.onError?.(err);\r\n }\r\n }\r\n}\r\n","/**\r\n * Skill Installer — polls the Skillo platform for pending skill installs/uninstalls\r\n * and writes/removes SKILL.md files in ~/.claude/skills/.\r\n *\r\n * Follows the same lifecycle pattern as SkillUsageDetector.\r\n */\r\n\r\nimport * as fs from \"fs\";\r\nimport * as path from \"path\";\r\nimport { getSkillsDir } from \"../utils/paths.js\";\r\n\r\ninterface PendingInstall {\r\n subscriptionId: string;\r\n slug: string;\r\n content: string;\r\n}\r\n\r\ninterface PendingUninstall {\r\n subscriptionId: string;\r\n slug: string;\r\n}\r\n\r\ninterface PersonalSkill {\r\n skillId: string;\r\n slug: string;\r\n content: string;\r\n}\r\n\r\ninterface PendingInstallsResponse {\r\n pendingInstalls: PendingInstall[];\r\n pendingUninstalls: PendingUninstall[];\r\n personalSkills?: PersonalSkill[];\r\n}\r\n\r\nexport interface SkillInstallerCallbacks {\r\n onInstall?: (slug: string) => void;\r\n onUninstall?: (slug: string) => void;\r\n onError?: (error: Error) => void;\r\n log?: (level: string, msg: string) => void;\r\n}\r\n\r\nexport class SkillInstaller {\r\n private intervalId: ReturnType<typeof setInterval> | null = null;\r\n private intervalMs: number;\r\n private callbacks: SkillInstallerCallbacks;\r\n private client: {\r\n getPendingInstalls: () => Promise<{\r\n success: boolean;\r\n data?: PendingInstallsResponse;\r\n error?: string;\r\n }>;\r\n reportInstallComplete: (\r\n subscriptionId: string,\r\n action: \"installed\" | \"uninstalled\"\r\n ) => Promise<{ success: boolean; error?: string }>;\r\n };\r\n\r\n constructor(\r\n client: SkillInstaller[\"client\"],\r\n options: {\r\n intervalMs?: number;\r\n callbacks?: SkillInstallerCallbacks;\r\n } = {}\r\n ) {\r\n this.client = client;\r\n this.intervalMs = options.intervalMs || 30000;\r\n this.callbacks = options.callbacks || {};\r\n }\r\n\r\n private log(level: string, msg: string) {\r\n this.callbacks.log?.(level, msg);\r\n }\r\n\r\n async start() {\r\n this.log(\"INFO\", \"Skill installer started\");\r\n // Do an initial poll immediately\r\n await this.poll();\r\n // Then poll at interval\r\n this.intervalId = setInterval(() => this.poll(), this.intervalMs);\r\n }\r\n\r\n stop() {\r\n if (this.intervalId) {\r\n clearInterval(this.intervalId);\r\n this.intervalId = null;\r\n }\r\n }\r\n\r\n async poll() {\r\n try {\r\n const response = await this.client.getPendingInstalls();\r\n if (!response.success || !response.data) {\r\n if (response.error) {\r\n this.log(\"WARN\", `Failed to fetch pending installs: ${response.error}`);\r\n }\r\n return;\r\n }\r\n\r\n const { pendingInstalls, pendingUninstalls, personalSkills } = response.data;\r\n\r\n // Process personal skills (write if not already on disk with same content)\r\n if (personalSkills?.length) {\r\n for (const skill of personalSkills) {\r\n try {\r\n const existing = this.readSkill(skill.slug);\r\n if (existing === skill.content) continue; // Already up to date\r\n\r\n this.writeSkill(skill.slug, skill.content);\r\n this.log(\"INFO\", `Installed personal skill: ${skill.slug}`);\r\n this.callbacks.onInstall?.(skill.slug);\r\n } catch (err) {\r\n const error = err instanceof Error ? err : new Error(String(err));\r\n this.log(\"ERROR\", `Failed to install personal skill ${skill.slug}: ${error.message}`);\r\n this.callbacks.onError?.(error);\r\n\r\n if (error.message.includes(\"ENOSPC\") || error.message.includes(\"EACCES\")) {\r\n this.log(\"ERROR\", \"Filesystem error detected, stopping personal skill installation\");\r\n break;\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Process installs\r\n for (const install of pendingInstalls) {\r\n try {\r\n this.writeSkill(install.slug, install.content);\r\n this.log(\"INFO\", `Installed skill: ${install.slug}`);\r\n this.callbacks.onInstall?.(install.slug);\r\n\r\n await this.client.reportInstallComplete(install.subscriptionId, \"installed\");\r\n } catch (err) {\r\n const error = err instanceof Error ? err : new Error(String(err));\r\n this.log(\"ERROR\", `Failed to install skill ${install.slug}: ${error.message}`);\r\n this.callbacks.onError?.(error);\r\n }\r\n }\r\n\r\n // Process uninstalls\r\n for (const uninstall of pendingUninstalls) {\r\n try {\r\n this.removeSkill(uninstall.slug);\r\n this.log(\"INFO\", `Uninstalled skill: ${uninstall.slug}`);\r\n this.callbacks.onUninstall?.(uninstall.slug);\r\n\r\n await this.client.reportInstallComplete(uninstall.subscriptionId, \"uninstalled\");\r\n } catch (err) {\r\n const error = err instanceof Error ? err : new Error(String(err));\r\n this.log(\"ERROR\", `Failed to uninstall skill ${uninstall.slug}: ${error.message}`);\r\n this.callbacks.onError?.(error);\r\n }\r\n }\r\n } catch (error) {\r\n const err = error instanceof Error ? error : new Error(String(error));\r\n this.log(\"ERROR\", `Skill installer poll error: ${err.message}`);\r\n this.callbacks.onError?.(err);\r\n }\r\n }\r\n\r\n private writeSkill(slug: string, content: string) {\r\n const skillsDir = getSkillsDir();\r\n const skillDir = path.join(skillsDir, slug);\r\n\r\n if (!fs.existsSync(skillsDir)) {\r\n fs.mkdirSync(skillsDir, { recursive: true });\r\n }\r\n\r\n if (!fs.existsSync(skillDir)) {\r\n fs.mkdirSync(skillDir, { recursive: true });\r\n }\r\n\r\n fs.writeFileSync(path.join(skillDir, \"SKILL.md\"), content, \"utf-8\");\r\n }\r\n\r\n private readSkill(slug: string): string | null {\r\n const skillFile = path.join(getSkillsDir(), slug, \"SKILL.md\");\r\n try {\r\n return fs.existsSync(skillFile) ? fs.readFileSync(skillFile, \"utf-8\") : null;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n private removeSkill(slug: string) {\r\n const skillDir = path.join(getSkillsDir(), slug);\r\n if (fs.existsSync(skillDir)) {\r\n fs.rmSync(skillDir, { recursive: true, force: true });\r\n }\r\n }\r\n}\r\n","/**\r\n * StatusWriter — writes daemon status JSON for tray icon and diagnostics.\r\n *\r\n * Writes ~/.skillo/daemon-status.json every 10s and on key events.\r\n * Tray icon and `skillo status` read this file.\r\n */\r\n\r\nimport { writeFileSync, readFileSync, existsSync } from \"fs\";\r\nimport { join } from \"path\";\r\nimport { getDataDir, ensureDirectory } from \"./paths.js\";\r\n\r\nexport interface DaemonStatus {\r\n running: boolean;\r\n pid: number | null;\r\n startedAt: string | null;\r\n updatedAt: string;\r\n user?: string;\r\n claudeWatcher?: {\r\n lastSync?: string | null;\r\n promptsSynced?: number;\r\n lastError?: string | null;\r\n };\r\n skillDetector?: {\r\n deployedSkills?: number;\r\n usagesDetected?: number;\r\n lastDetection?: string | null;\r\n lastError?: string | null;\r\n };\r\n skillInstaller?: {\r\n lastInstall?: string | null;\r\n lastUninstall?: string | null;\r\n lastAction?: string | null;\r\n lastError?: string | null;\r\n };\r\n trackedProjects?: Array<{ name: string; path: string }>;\r\n activeSessions?: number;\r\n}\r\n\r\nconst STATUS_FILE = \"daemon-status.json\";\r\nconst WRITE_INTERVAL_MS = 10000;\r\n\r\nexport class StatusWriter {\r\n private intervalId: ReturnType<typeof setInterval> | null = null;\r\n private status: DaemonStatus;\r\n private filePath: string;\r\n\r\n constructor() {\r\n this.filePath = join(getDataDir(), STATUS_FILE);\r\n this.status = {\r\n running: true,\r\n pid: process.pid,\r\n startedAt: new Date().toISOString(),\r\n updatedAt: new Date().toISOString(),\r\n claudeWatcher: { lastSync: null, promptsSynced: 0, lastError: null },\r\n skillDetector: { deployedSkills: 0, usagesDetected: 0, lastDetection: null, lastError: null },\r\n trackedProjects: [],\r\n activeSessions: 0,\r\n };\r\n }\r\n\r\n /** Start periodic writes */\r\n start() {\r\n ensureDirectory(getDataDir());\r\n this.write();\r\n this.intervalId = setInterval(() => this.write(), WRITE_INTERVAL_MS);\r\n }\r\n\r\n /** Stop writing and mark as not running */\r\n stop() {\r\n if (this.intervalId) {\r\n clearInterval(this.intervalId);\r\n this.intervalId = null;\r\n }\r\n this.status.running = false;\r\n this.status.pid = null;\r\n this.status.updatedAt = new Date().toISOString();\r\n this.write();\r\n }\r\n\r\n /** Merge partial status updates */\r\n update(partial: Partial<DaemonStatus>) {\r\n // Deep merge for nested objects\r\n if (partial.claudeWatcher) {\r\n this.status.claudeWatcher = { ...this.status.claudeWatcher, ...partial.claudeWatcher };\r\n delete partial.claudeWatcher;\r\n }\r\n if (partial.skillDetector) {\r\n this.status.skillDetector = { ...this.status.skillDetector, ...partial.skillDetector };\r\n delete partial.skillDetector;\r\n }\r\n if (partial.skillInstaller) {\r\n this.status.skillInstaller = { ...this.status.skillInstaller, ...partial.skillInstaller };\r\n delete partial.skillInstaller;\r\n }\r\n Object.assign(this.status, partial);\r\n }\r\n\r\n /** Get the status file path */\r\n static getStatusFilePath(): string {\r\n return join(getDataDir(), STATUS_FILE);\r\n }\r\n\r\n /** Read current status from disk (static, for tray/status commands) */\r\n static read(): DaemonStatus | null {\r\n const filePath = StatusWriter.getStatusFilePath();\r\n try {\r\n if (existsSync(filePath)) {\r\n return JSON.parse(readFileSync(filePath, \"utf-8\"));\r\n }\r\n } catch {\r\n // ignore\r\n }\r\n return null;\r\n }\r\n\r\n private write() {\r\n this.status.updatedAt = new Date().toISOString();\r\n try {\r\n writeFileSync(this.filePath, JSON.stringify(this.status, null, 2), \"utf-8\");\r\n } catch {\r\n // Can't write status, ignore\r\n }\r\n }\r\n}\r\n","/**\r\n * Shared daemon tasks used by both daemon-runner.ts (background)\r\n * and daemon.ts foreground mode.\r\n */\r\n\r\nimport { existsSync, readFileSync, writeFileSync, unlinkSync, readdirSync } from \"fs\";\r\nimport { getTrackedProjectsCacheFile, getActiveSessionsDir } from \"./paths.js\";\r\n\r\ntype LogFn = (level: string, msg: string) => void;\r\ntype ApiClient = { listProjects: (trackingOnly: boolean) => Promise<any>; endSession: (id: string) => Promise<any> };\r\n\r\nexport async function updateTrackedProjectsCache(client: ApiClient, log: LogFn) {\r\n try {\r\n const result = await client.listProjects(true);\r\n if (result.success && result.data?.projects) {\r\n const projects = result.data.projects\r\n .filter((p: { trackingEnabled: boolean }) => p.trackingEnabled)\r\n .map((p: { path: string; name: string; gitRemoteNormalized?: string }) => ({ path: p.path, name: p.name, gitRemoteNormalized: p.gitRemoteNormalized }));\r\n const cacheData = { updatedAt: Date.now(), projects };\r\n writeFileSync(getTrackedProjectsCacheFile(), JSON.stringify(cacheData, null, 2));\r\n log(\"DEBUG\", `Updated tracked projects cache: ${projects.length} project(s)`);\r\n return projects;\r\n }\r\n } catch (error) {\r\n log(\"ERROR\", `Failed to update tracked projects cache: ${error}`);\r\n }\r\n return null;\r\n}\r\n\r\nexport async function cleanupStaleSessions(client: ApiClient, log: LogFn) {\r\n const sessionsDir = getActiveSessionsDir();\r\n if (!existsSync(sessionsDir)) return;\r\n\r\n try {\r\n const files = readdirSync(sessionsDir);\r\n for (const file of files) {\r\n if (!file.endsWith(\".json\")) continue;\r\n const pid = parseInt(file.replace(\".json\", \"\"), 10);\r\n if (isNaN(pid)) continue;\r\n\r\n let isRunning = false;\r\n try { process.kill(pid, 0); isRunning = true; } catch { /* process dead */ }\r\n\r\n if (!isRunning) {\r\n try {\r\n const sessionData = JSON.parse(readFileSync(`${sessionsDir}/${file}`, \"utf-8\"));\r\n if (sessionData.sessionId) {\r\n await client.endSession(sessionData.sessionId);\r\n log(\"INFO\", `Ended stale session ${sessionData.sessionId.slice(0, 8)} (PID ${pid} dead)`);\r\n }\r\n unlinkSync(`${sessionsDir}/${file}`);\r\n } catch {\r\n try { unlinkSync(`${sessionsDir}/${file}`); } catch { /* ignore */ }\r\n }\r\n }\r\n }\r\n } catch (error) {\r\n log(\"ERROR\", `Stale session cleanup error: ${error}`);\r\n }\r\n}\r\n\r\nexport function readTrackedProjectsFromCache(): Array<{ path: string; name: string; gitRemoteNormalized?: string }> | null {\r\n try {\r\n const cacheData = JSON.parse(readFileSync(getTrackedProjectsCacheFile(), \"utf-8\"));\r\n return cacheData.projects || [];\r\n } catch {\r\n return null;\r\n }\r\n}\r\n"],"mappings":"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","names":["existsSync","writeFileSync","unlinkSync","existsSync","path","path","existsSync","path","fs","path","readline","fs","path","writeFileSync","readFileSync","existsSync","join","join","existsSync","readFileSync","writeFileSync","existsSync","readFileSync","writeFileSync","readdirSync","log","writeFileSync","existsSync","readdirSync","readFileSync","writeFileSync","existsSync","unlinkSync"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
findGitRoot,
|
|
4
|
+
getGitInfo,
|
|
5
|
+
isInsideGitRepo,
|
|
6
|
+
normalizeGitRemote,
|
|
7
|
+
parseGitRemote
|
|
8
|
+
} from "./chunk-6GOJPFZ7.js";
|
|
9
|
+
export {
|
|
10
|
+
findGitRoot,
|
|
11
|
+
getGitInfo,
|
|
12
|
+
isInsideGitRepo,
|
|
13
|
+
normalizeGitRemote,
|
|
14
|
+
parseGitRemote
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=git-OGUSYBJS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// src/utils/git.ts
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import { join, dirname, basename } from "path";
|
|
5
|
+
function getGitInfo(projectPath) {
|
|
6
|
+
const result = {
|
|
7
|
+
isGitRepo: false,
|
|
8
|
+
rootPath: null,
|
|
9
|
+
remote: null,
|
|
10
|
+
remoteNormalized: null,
|
|
11
|
+
branch: null,
|
|
12
|
+
projectName: null
|
|
13
|
+
};
|
|
14
|
+
try {
|
|
15
|
+
const rootPath = execSync("git rev-parse --show-toplevel", {
|
|
16
|
+
cwd: projectPath,
|
|
17
|
+
encoding: "utf-8",
|
|
18
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
19
|
+
}).trim();
|
|
20
|
+
if (!rootPath) return result;
|
|
21
|
+
result.isGitRepo = true;
|
|
22
|
+
result.rootPath = rootPath;
|
|
23
|
+
result.projectName = basename(rootPath);
|
|
24
|
+
try {
|
|
25
|
+
result.branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
26
|
+
cwd: projectPath,
|
|
27
|
+
encoding: "utf-8",
|
|
28
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
29
|
+
}).trim();
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
result.remote = execSync("git remote get-url origin", {
|
|
34
|
+
cwd: projectPath,
|
|
35
|
+
encoding: "utf-8",
|
|
36
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
37
|
+
}).trim();
|
|
38
|
+
} catch {
|
|
39
|
+
try {
|
|
40
|
+
const remotes = execSync("git remote", {
|
|
41
|
+
cwd: projectPath,
|
|
42
|
+
encoding: "utf-8",
|
|
43
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
44
|
+
}).trim().split("\n");
|
|
45
|
+
if (remotes.length > 0 && remotes[0]) {
|
|
46
|
+
result.remote = execSync(`git remote get-url ${remotes[0]}`, {
|
|
47
|
+
cwd: projectPath,
|
|
48
|
+
encoding: "utf-8",
|
|
49
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
50
|
+
}).trim();
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (result.remote) {
|
|
56
|
+
result.remoteNormalized = normalizeGitRemote(result.remote);
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
function normalizeGitRemote(remoteUrl) {
|
|
63
|
+
let url = remoteUrl.trim();
|
|
64
|
+
url = url.replace(/\.git$/, "");
|
|
65
|
+
const sshMatch = url.match(/^git@([^:]+):(.+)$/);
|
|
66
|
+
if (sshMatch) {
|
|
67
|
+
return `${sshMatch[1]}/${sshMatch[2]}`;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
let normalized = url.replace(/^(https?|git|ssh):\/\//, "").replace(/^git@/, "");
|
|
71
|
+
normalized = normalized.replace(/^[^@]+@/, "");
|
|
72
|
+
normalized = normalized.replace(/:\d+\//, "/");
|
|
73
|
+
normalized = normalized.replace(/\/+/g, "/");
|
|
74
|
+
normalized = normalized.replace(/^\/|\/$/g, "");
|
|
75
|
+
return normalized.toLowerCase();
|
|
76
|
+
} catch {
|
|
77
|
+
return url.toLowerCase();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function findGitRoot(startPath) {
|
|
81
|
+
let current = startPath;
|
|
82
|
+
while (current !== dirname(current)) {
|
|
83
|
+
if (existsSync(join(current, ".git"))) {
|
|
84
|
+
return current;
|
|
85
|
+
}
|
|
86
|
+
current = dirname(current);
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
function isInsideGitRepo(path) {
|
|
91
|
+
return findGitRoot(path) !== null;
|
|
92
|
+
}
|
|
93
|
+
function parseGitRemote(normalizedRemote) {
|
|
94
|
+
const parts = normalizedRemote.split("/");
|
|
95
|
+
if (parts.length < 3) return null;
|
|
96
|
+
return {
|
|
97
|
+
host: parts[0],
|
|
98
|
+
owner: parts[1],
|
|
99
|
+
repo: parts.slice(2).join("/")
|
|
100
|
+
// Handle nested paths like gitlab subgroups
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export {
|
|
104
|
+
findGitRoot,
|
|
105
|
+
getGitInfo,
|
|
106
|
+
isInsideGitRepo,
|
|
107
|
+
normalizeGitRemote,
|
|
108
|
+
parseGitRemote
|
|
109
|
+
};
|
|
110
|
+
//# sourceMappingURL=git-OUAHIOY2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/git.ts"],"sourcesContent":["/**\r\n * Git utilities for project detection and identification.\r\n */\r\n\r\nimport { execSync } from \"child_process\";\r\nimport { existsSync } from \"fs\";\r\nimport { join, dirname, basename } from \"path\";\r\n\r\nexport interface GitInfo {\r\n isGitRepo: boolean;\r\n rootPath: string | null;\r\n remote: string | null;\r\n remoteNormalized: string | null;\r\n branch: string | null;\r\n projectName: string | null;\r\n}\r\n\r\n/**\r\n * Get git information for a given path.\r\n */\r\nexport function getGitInfo(projectPath: string): GitInfo {\r\n const result: GitInfo = {\r\n isGitRepo: false,\r\n rootPath: null,\r\n remote: null,\r\n remoteNormalized: null,\r\n branch: null,\r\n projectName: null,\r\n };\r\n\r\n try {\r\n // Check if it's a git repo and get root\r\n const rootPath = execSync(\"git rev-parse --show-toplevel\", {\r\n cwd: projectPath,\r\n encoding: \"utf-8\",\r\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\r\n }).trim();\r\n\r\n if (!rootPath) return result;\r\n\r\n result.isGitRepo = true;\r\n result.rootPath = rootPath;\r\n result.projectName = basename(rootPath);\r\n\r\n // Get current branch\r\n try {\r\n result.branch = execSync(\"git rev-parse --abbrev-ref HEAD\", {\r\n cwd: projectPath,\r\n encoding: \"utf-8\",\r\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\r\n }).trim();\r\n } catch {\r\n // Might be in detached HEAD state\r\n }\r\n\r\n // Get remote URL (prefer origin)\r\n try {\r\n result.remote = execSync(\"git remote get-url origin\", {\r\n cwd: projectPath,\r\n encoding: \"utf-8\",\r\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\r\n }).trim();\r\n } catch {\r\n // Try to get any remote\r\n try {\r\n const remotes = execSync(\"git remote\", {\r\n cwd: projectPath,\r\n encoding: \"utf-8\",\r\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\r\n }).trim().split(\"\\n\");\r\n\r\n if (remotes.length > 0 && remotes[0]) {\r\n result.remote = execSync(`git remote get-url ${remotes[0]}`, {\r\n cwd: projectPath,\r\n encoding: \"utf-8\",\r\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\r\n }).trim();\r\n }\r\n } catch {\r\n // No remotes configured\r\n }\r\n }\r\n\r\n // Normalize the remote URL for team matching\r\n if (result.remote) {\r\n result.remoteNormalized = normalizeGitRemote(result.remote);\r\n }\r\n } catch {\r\n // Not a git repository\r\n }\r\n\r\n return result;\r\n}\r\n\r\n/**\r\n * Normalize a git remote URL for consistent matching across team members.\r\n * Converts various formats to: \"host/owner/repo\"\r\n *\r\n * Examples:\r\n * - git@github.com:acme/webapp.git -> github.com/acme/webapp\r\n * - https://github.com/acme/webapp.git -> github.com/acme/webapp\r\n * - ssh://git@github.com/acme/webapp -> github.com/acme/webapp\r\n * - git://github.com/acme/webapp.git -> github.com/acme/webapp\r\n */\r\nexport function normalizeGitRemote(remoteUrl: string): string {\r\n let url = remoteUrl.trim();\r\n\r\n // Remove .git suffix\r\n url = url.replace(/\\.git$/, \"\");\r\n\r\n // Handle SSH format: git@github.com:owner/repo\r\n const sshMatch = url.match(/^git@([^:]+):(.+)$/);\r\n if (sshMatch) {\r\n return `${sshMatch[1]}/${sshMatch[2]}`;\r\n }\r\n\r\n // Handle various URL formats\r\n try {\r\n // Remove protocol prefix for parsing\r\n let normalized = url\r\n .replace(/^(https?|git|ssh):\\/\\//, \"\")\r\n .replace(/^git@/, \"\");\r\n\r\n // Remove username if present (e.g., git@host or user@host)\r\n normalized = normalized.replace(/^[^@]+@/, \"\");\r\n\r\n // Remove port if present\r\n normalized = normalized.replace(/:\\d+\\//, \"/\");\r\n\r\n // Clean up any double slashes\r\n normalized = normalized.replace(/\\/+/g, \"/\");\r\n\r\n // Remove leading/trailing slashes\r\n normalized = normalized.replace(/^\\/|\\/$/g, \"\");\r\n\r\n return normalized.toLowerCase();\r\n } catch {\r\n // Fallback: return as-is but lowercase\r\n return url.toLowerCase();\r\n }\r\n}\r\n\r\n/**\r\n * Find git root by walking up the directory tree.\r\n */\r\nexport function findGitRoot(startPath: string): string | null {\r\n let current = startPath;\r\n\r\n while (current !== dirname(current)) {\r\n if (existsSync(join(current, \".git\"))) {\r\n return current;\r\n }\r\n current = dirname(current);\r\n }\r\n\r\n return null;\r\n}\r\n\r\n/**\r\n * Check if a path is inside a git repository.\r\n */\r\nexport function isInsideGitRepo(path: string): boolean {\r\n return findGitRoot(path) !== null;\r\n}\r\n\r\n/**\r\n * Extract owner and repo name from a normalized git remote.\r\n */\r\nexport function parseGitRemote(normalizedRemote: string): { host: string; owner: string; repo: string } | null {\r\n const parts = normalizedRemote.split(\"/\");\r\n\r\n if (parts.length < 3) return null;\r\n\r\n return {\r\n host: parts[0],\r\n owner: parts[1],\r\n repo: parts.slice(2).join(\"/\"), // Handle nested paths like gitlab subgroups\r\n };\r\n}\r\n"],"mappings":"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","names":[]}
|