skillo 0.2.1 → 0.2.3

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.
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getConfigValue,
4
+ getDefaultConfig,
5
+ loadConfig,
6
+ saveConfig,
7
+ setConfigValue
8
+ } from "./chunk-CPL3P2OF.js";
9
+ import "./chunk-WJKZWKER.js";
10
+ export {
11
+ getConfigValue,
12
+ getDefaultConfig,
13
+ loadConfig,
14
+ saveConfig,
15
+ setConfigValue
16
+ };
17
+ //# sourceMappingURL=config-P5EM5L7N.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ SkilloDatabase
4
+ } from "./chunk-2CVEPT6U.js";
5
+ import "./chunk-WJKZWKER.js";
6
+ export {
7
+ SkilloDatabase
8
+ };
9
+ //# sourceMappingURL=database-F3BFFZKG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -5,11 +5,11 @@ import {
5
5
  } from "./chunk-WJKZWKER.js";
6
6
 
7
7
  // src/tray/tray.ts
8
- import SysTray from "systray2";
9
- import { readFileSync as readFileSync2, existsSync as existsSync2, writeFileSync as writeFileSync2, unlinkSync } from "fs";
8
+ import SysTrayModule from "systray2";
9
+ import { readFileSync as readFileSync2, existsSync as existsSync2, writeFileSync as writeFileSync2, unlinkSync, chmodSync, readdirSync } from "fs";
10
10
  import { exec } from "child_process";
11
11
  import { join as join2 } from "path";
12
- import { platform } from "os";
12
+ import { platform, homedir } from "os";
13
13
 
14
14
  // src/utils/status-writer.ts
15
15
  import { writeFileSync, readFileSync, existsSync } from "fs";
@@ -87,6 +87,8 @@ var StatusWriter = class _StatusWriter {
87
87
  };
88
88
 
89
89
  // src/tray/tray.ts
90
+ var SysTray = SysTrayModule.default || SysTrayModule;
91
+ var SEPARATOR = { title: "<SEPARATOR>", tooltip: "", enabled: true };
90
92
  var ICON_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAhklEQVQ4T2NkoBAwUqifgWoGMDIyNjAyMP5nYGD4T8gFjHADGBkZAxgZGQMIGQK3gYmJqYCRkfE/IyPj/4KCgv9whzAyMDIwMDL+Z2Bk/M/AwMjwn4GBsaCgoOA/uhehuwBuACMjI9wQBgYGBkZGxv8FBQUBcBfgC0e4C4gJR7gBxIQj1QwAAFbxMBHleBQjAAAAAElFTkSuQmCC";
91
93
  var TRAY_PID_FILE = "tray.pid";
92
94
  var STATUS_POLL_MS = 3e3;
@@ -129,7 +131,7 @@ function buildMenu(status) {
129
131
  enabled: false,
130
132
  checked: false
131
133
  },
132
- SysTray.separator,
134
+ SEPARATOR,
133
135
  // User
134
136
  {
135
137
  title: status?.user ? `Logged in as: ${status.user}` : "Not logged in",
@@ -137,7 +139,7 @@ function buildMenu(status) {
137
139
  enabled: false,
138
140
  checked: false
139
141
  },
140
- SysTray.separator,
142
+ SEPARATOR,
141
143
  // Projects
142
144
  {
143
145
  title: `Tracked Projects (${projects.length})`,
@@ -146,7 +148,7 @@ function buildMenu(status) {
146
148
  checked: false
147
149
  },
148
150
  ...projectItems,
149
- SysTray.separator,
151
+ SEPARATOR,
150
152
  // Sync info
151
153
  {
152
154
  title: `Last sync: ${formatTimeAgo(status?.claudeWatcher?.lastSync)}`,
@@ -166,7 +168,7 @@ function buildMenu(status) {
166
168
  enabled: false,
167
169
  checked: false
168
170
  },
169
- SysTray.separator,
171
+ SEPARATOR,
170
172
  // Actions
171
173
  {
172
174
  title: running ? "Stop Daemon" : "Start Daemon",
@@ -180,7 +182,7 @@ function buildMenu(status) {
180
182
  enabled: true,
181
183
  checked: false
182
184
  },
183
- SysTray.separator,
185
+ SEPARATOR,
184
186
  // Quit
185
187
  {
186
188
  title: "Quit Tray",
@@ -213,7 +215,24 @@ function execSkillo(args) {
213
215
  exec(`skillo ${args}`, () => {
214
216
  });
215
217
  }
218
+ function fixSystrayBinaryPermissions() {
219
+ if (platform() === "win32") return;
220
+ try {
221
+ const cacheDir = join2(homedir(), ".cache", "node-systray");
222
+ if (!existsSync2(cacheDir)) return;
223
+ for (const ver of readdirSync(cacheDir)) {
224
+ const dir = join2(cacheDir, ver);
225
+ for (const file of readdirSync(dir)) {
226
+ if (file.startsWith("tray_")) {
227
+ chmodSync(join2(dir, file), 493);
228
+ }
229
+ }
230
+ }
231
+ } catch {
232
+ }
233
+ }
216
234
  async function startTray() {
235
+ fixSystrayBinaryPermissions();
217
236
  const pidFile = join2(getDataDir(), TRAY_PID_FILE);
218
237
  ensureDirectory(getDataDir());
219
238
  if (existsSync2(pidFile)) {
@@ -275,4 +294,4 @@ async function startTray() {
275
294
  export {
276
295
  startTray
277
296
  };
278
- //# sourceMappingURL=tray-62I5KIFE.js.map
297
+ //# sourceMappingURL=tray-RZQV6GDP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tray/tray.ts","../src/utils/status-writer.ts"],"sourcesContent":["/**\n * Skillo System Tray Icon\n *\n * Shows daemon status, tracked projects, sync info in the system tray.\n * Reads ~/.skillo/daemon-status.json (written by StatusWriter in daemon).\n * Uses systray2 for cross-platform tray support.\n */\n\nimport SysTrayModule from \"systray2\";\nimport { readFileSync, existsSync, writeFileSync, unlinkSync, chmodSync, readdirSync } from \"fs\";\nimport { exec } from \"child_process\";\nimport { join } from \"path\";\nimport { platform, homedir } from \"os\";\nimport { StatusWriter, type DaemonStatus } from \"../utils/status-writer.js\";\nimport { getDataDir, ensureDirectory } from \"../utils/paths.js\";\n\n// Handle CJS/ESM interop — systray2 is CJS, so default import may be wrapped\nconst SysTray = (SysTrayModule as any).default || SysTrayModule;\n\ntype MenuItem = {\n title: string;\n tooltip: string;\n checked?: boolean;\n enabled?: boolean;\n hidden?: boolean;\n items?: MenuItem[];\n};\n\ntype Menu = {\n icon: string;\n title: string;\n tooltip: string;\n items: MenuItem[];\n};\n\nconst SEPARATOR: MenuItem = { title: \"<SEPARATOR>\", tooltip: \"\", enabled: true };\n\n// Minimal 16x16 template icon (black circle on transparent, suitable for macOS menu bar)\n// This is a tiny PNG encoded in base64\nconst ICON_BASE64 =\n \"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAhklEQVQ4T2NkoBAwUqifgWoG\" +\n \"MDIyNjAyMP5nYGD4T8gFjHADGBkZAxgZGQMIGQK3gYmJqYCRkfE/IyPj/4KCgv9whzAyMDIw\" +\n \"MDL+Z2Bk/M/AwMjwn4GBsaCgoOA/uhehuwBuACMjI9wQBgYGBkZGxv8FBQUBcBfgC0e4C4gJ\" +\n \"R7gBxIQj1QwAAFbxMBHleBQjAAAAAElFTkSuQmCC\";\n\nconst TRAY_PID_FILE = \"tray.pid\";\nconst STATUS_POLL_MS = 3000;\n\n// Menu item sequence IDs\nconst SEQ = {\n STATUS_HEADER: 0,\n SEP1: 1,\n USER: 2,\n SEP2: 3,\n PROJECTS_HEADER: 4,\n // Projects: 100-199\n SEP3: 200,\n LAST_SYNC: 201,\n PROMPTS_SYNCED: 202,\n SKILLS_DETECTED: 203,\n SEP4: 204,\n TOGGLE_DAEMON: 205,\n OPEN_DASHBOARD: 206,\n SEP5: 207,\n QUIT: 208,\n};\n\nfunction formatTimeAgo(isoString: string | null | undefined): string {\n if (!isoString) return \"Never\";\n const diff = Date.now() - new Date(isoString).getTime();\n const seconds = Math.floor(diff / 1000);\n if (seconds < 60) return \"Just now\";\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes} min ago`;\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.floor(hours / 24);\n return `${days}d ago`;\n}\n\nfunction readStatus(): DaemonStatus | null {\n return StatusWriter.read();\n}\n\nfunction buildMenu(status: DaemonStatus | null): Menu {\n const running = status?.running ?? false;\n const statusIcon = running ? \"\\u25CF\" : \"\\u25CB\"; // ● or ○\n const statusText = running ? \"Running\" : \"Stopped\";\n\n const projects = status?.trackedProjects ?? [];\n const projectItems: MenuItem[] = projects.length > 0\n ? projects.map((p, i) => ({\n title: ` ${p.name}`,\n tooltip: p.path,\n enabled: false,\n checked: false,\n }))\n : [{\n title: \" (none)\",\n tooltip: \"No tracked projects\",\n enabled: false,\n checked: false,\n }];\n\n const items: MenuItem[] = [\n // Status header\n {\n title: `${statusIcon} Skillo \\u2014 ${statusText}`,\n tooltip: \"Daemon status\",\n enabled: false,\n checked: false,\n },\n SEPARATOR,\n // User\n {\n title: status?.user ? `Logged in as: ${status.user}` : \"Not logged in\",\n tooltip: \"User info\",\n enabled: false,\n checked: false,\n },\n SEPARATOR,\n // Projects\n {\n title: `Tracked Projects (${projects.length})`,\n tooltip: \"Projects being tracked\",\n enabled: false,\n checked: false,\n },\n ...projectItems,\n SEPARATOR,\n // Sync info\n {\n title: `Last sync: ${formatTimeAgo(status?.claudeWatcher?.lastSync)}`,\n tooltip: \"Last Claude conversation sync\",\n enabled: false,\n checked: false,\n },\n {\n title: `Prompts synced: ${status?.claudeWatcher?.promptsSynced ?? 0}`,\n tooltip: \"Total prompts synced\",\n enabled: false,\n checked: false,\n },\n {\n title: `Skills detected: ${status?.skillDetector?.usagesDetected ?? 0}`,\n tooltip: \"Skill usages detected\",\n enabled: false,\n checked: false,\n },\n SEPARATOR,\n // Actions\n {\n title: running ? \"Stop Daemon\" : \"Start Daemon\",\n tooltip: running ? \"Stop the Skillo daemon\" : \"Start the Skillo daemon\",\n enabled: true,\n checked: false,\n },\n {\n title: \"Open Dashboard\",\n tooltip: \"Open Skillo dashboard in browser\",\n enabled: true,\n checked: false,\n },\n SEPARATOR,\n // Quit\n {\n title: \"Quit Tray\",\n tooltip: \"Hide tray icon (daemon keeps running)\",\n enabled: true,\n checked: false,\n },\n ];\n\n return {\n icon: ICON_BASE64,\n title: \"\",\n tooltip: `Skillo \\u2014 ${statusText}`,\n items,\n };\n}\n\nfunction openBrowser(url: string) {\n const os = platform();\n let cmd: string;\n if (os === \"darwin\") {\n cmd = `open \"${url}\"`;\n } else if (os === \"win32\") {\n cmd = `start \"\" \"${url}\"`;\n } else {\n cmd = `xdg-open \"${url}\"`;\n }\n exec(cmd, () => {});\n}\n\nfunction execSkillo(args: string) {\n exec(`skillo ${args}`, () => {});\n}\n\n/** Ensure the systray2 binary has execute permission (npm sometimes strips it) */\nfunction fixSystrayBinaryPermissions() {\n if (platform() === \"win32\") return;\n try {\n const cacheDir = join(homedir(), \".cache\", \"node-systray\");\n if (!existsSync(cacheDir)) return;\n for (const ver of readdirSync(cacheDir)) {\n const dir = join(cacheDir, ver);\n for (const file of readdirSync(dir)) {\n if (file.startsWith(\"tray_\")) {\n chmodSync(join(dir, file), 0o755);\n }\n }\n }\n } catch {\n // best-effort\n }\n}\n\nexport async function startTray(): Promise<void> {\n fixSystrayBinaryPermissions();\n\n const pidFile = join(getDataDir(), TRAY_PID_FILE);\n ensureDirectory(getDataDir());\n\n // Check if another tray is already running\n if (existsSync(pidFile)) {\n try {\n const pid = parseInt(readFileSync(pidFile, \"utf-8\").trim(), 10);\n process.kill(pid, 0); // Check if alive\n console.error(\"Tray icon is already running (PID: \" + pid + \")\");\n process.exit(1);\n } catch {\n // Stale PID file, continue\n }\n }\n\n // Write our PID\n writeFileSync(pidFile, String(process.pid));\n\n // Build initial menu\n const initialStatus = readStatus();\n const menu = buildMenu(initialStatus);\n\n const systray = new SysTray({\n menu,\n debug: false,\n copyDir: true,\n });\n\n // Cleanup on exit\n const cleanup = () => {\n try { unlinkSync(pidFile); } catch { /* ignore */ }\n systray.kill(false);\n process.exit(0);\n };\n process.on(\"SIGINT\", cleanup);\n process.on(\"SIGTERM\", cleanup);\n\n // Handle clicks\n await systray.ready();\n\n systray.onClick(async (action) => {\n const clickedTitle = action.item.title;\n\n if (clickedTitle === \"Stop Daemon\" || clickedTitle === \"Start Daemon\") {\n if (clickedTitle === \"Stop Daemon\") {\n execSkillo(\"stop\");\n } else {\n execSkillo(\"start\");\n }\n } else if (clickedTitle === \"Open Dashboard\") {\n openBrowser(\"https://www.skillo.one/dashboard\");\n } else if (clickedTitle === \"Quit Tray\") {\n cleanup();\n }\n });\n\n // Poll status and update menu\n setInterval(async () => {\n try {\n const status = readStatus();\n const updatedMenu = buildMenu(status);\n await systray.sendAction({\n type: \"update-menu\",\n menu: updatedMenu,\n });\n } catch {\n // ignore update errors\n }\n }, STATUS_POLL_MS);\n\n // Keep alive\n await new Promise(() => {});\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":["readFileSync","existsSync","writeFileSync","join","join","existsSync","readFileSync","writeFileSync"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillo",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Autonomous workflow learning & skill generation system - Learn workflows by observation, not explanation",
5
5
  "keywords": [
6
6
  "cli",
@@ -328,19 +328,16 @@ try {
328
328
  if (installed) {
329
329
  const dim = "\x1b[90m";
330
330
  const green = "\x1b[32m";
331
+ const bold = "\x1b[1m";
331
332
  const reset = "\x1b[0m";
332
- const rcHint = {
333
- zsh: "source ~/.zshrc",
334
- bash: "source ~/.bashrc",
335
- fish: "source ~/.config/fish/config.fish",
336
- powershell: ". $PROFILE",
337
- }[shell] || "";
338
333
  console.log("");
339
- console.log(`${green}Skillo${reset}: Shell integration installed for ${shell}.`);
340
- if (rcHint) {
341
- console.log(`${dim}Restart your terminal or run: ${rcHint}${reset}`);
342
- }
343
- console.log(`${dim}Run 'skillo setup-shell --uninstall' to remove.${reset}`);
334
+ console.log(`${green}${bold}Skillo installed!${reset} Shell integration set up for ${shell}.`);
335
+ console.log("");
336
+ console.log(`${bold} Get started:${reset}`);
337
+ console.log(`${green} skillo start${reset}`);
338
+ console.log("");
339
+ console.log(`${dim} That's it — opens browser to log in, starts the daemon,${reset}`);
340
+ console.log(`${dim} installs auto-start, and adds a tray icon.${reset}`);
344
341
  console.log("");
345
342
  }
346
343
  } catch {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/config.ts","../src/core/api-client.ts"],"sourcesContent":["/**\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 * 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"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/tray/tray.ts","../src/utils/status-writer.ts"],"sourcesContent":["/**\n * Skillo System Tray Icon\n *\n * Shows daemon status, tracked projects, sync info in the system tray.\n * Reads ~/.skillo/daemon-status.json (written by StatusWriter in daemon).\n * Uses systray2 for cross-platform tray support.\n */\n\nimport SysTray, { type MenuItem, type Menu } from \"systray2\";\nimport { readFileSync, existsSync, writeFileSync, unlinkSync } from \"fs\";\nimport { exec } from \"child_process\";\nimport { join } from \"path\";\nimport { platform } from \"os\";\nimport { StatusWriter, type DaemonStatus } from \"../utils/status-writer.js\";\nimport { getDataDir, ensureDirectory } from \"../utils/paths.js\";\n\n// Minimal 16x16 template icon (black circle on transparent, suitable for macOS menu bar)\n// This is a tiny PNG encoded in base64\nconst ICON_BASE64 =\n \"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAhklEQVQ4T2NkoBAwUqifgWoG\" +\n \"MDIyNjAyMP5nYGD4T8gFjHADGBkZAxgZGQMIGQK3gYmJqYCRkfE/IyPj/4KCgv9whzAyMDIw\" +\n \"MDL+Z2Bk/M/AwMjwn4GBsaCgoOA/uhehuwBuACMjI9wQBgYGBkZGxv8FBQUBcBfgC0e4C4gJ\" +\n \"R7gBxIQj1QwAAFbxMBHleBQjAAAAAElFTkSuQmCC\";\n\nconst TRAY_PID_FILE = \"tray.pid\";\nconst STATUS_POLL_MS = 3000;\n\n// Menu item sequence IDs\nconst SEQ = {\n STATUS_HEADER: 0,\n SEP1: 1,\n USER: 2,\n SEP2: 3,\n PROJECTS_HEADER: 4,\n // Projects: 100-199\n SEP3: 200,\n LAST_SYNC: 201,\n PROMPTS_SYNCED: 202,\n SKILLS_DETECTED: 203,\n SEP4: 204,\n TOGGLE_DAEMON: 205,\n OPEN_DASHBOARD: 206,\n SEP5: 207,\n QUIT: 208,\n};\n\nfunction formatTimeAgo(isoString: string | null | undefined): string {\n if (!isoString) return \"Never\";\n const diff = Date.now() - new Date(isoString).getTime();\n const seconds = Math.floor(diff / 1000);\n if (seconds < 60) return \"Just now\";\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes} min ago`;\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.floor(hours / 24);\n return `${days}d ago`;\n}\n\nfunction readStatus(): DaemonStatus | null {\n return StatusWriter.read();\n}\n\nfunction buildMenu(status: DaemonStatus | null): Menu {\n const running = status?.running ?? false;\n const statusIcon = running ? \"\\u25CF\" : \"\\u25CB\"; // ● or ○\n const statusText = running ? \"Running\" : \"Stopped\";\n\n const projects = status?.trackedProjects ?? [];\n const projectItems: MenuItem[] = projects.length > 0\n ? projects.map((p, i) => ({\n title: ` ${p.name}`,\n tooltip: p.path,\n enabled: false,\n checked: false,\n }))\n : [{\n title: \" (none)\",\n tooltip: \"No tracked projects\",\n enabled: false,\n checked: false,\n }];\n\n const items: MenuItem[] = [\n // Status header\n {\n title: `${statusIcon} Skillo \\u2014 ${statusText}`,\n tooltip: \"Daemon status\",\n enabled: false,\n checked: false,\n },\n SysTray.separator,\n // User\n {\n title: status?.user ? `Logged in as: ${status.user}` : \"Not logged in\",\n tooltip: \"User info\",\n enabled: false,\n checked: false,\n },\n SysTray.separator,\n // Projects\n {\n title: `Tracked Projects (${projects.length})`,\n tooltip: \"Projects being tracked\",\n enabled: false,\n checked: false,\n },\n ...projectItems,\n SysTray.separator,\n // Sync info\n {\n title: `Last sync: ${formatTimeAgo(status?.claudeWatcher?.lastSync)}`,\n tooltip: \"Last Claude conversation sync\",\n enabled: false,\n checked: false,\n },\n {\n title: `Prompts synced: ${status?.claudeWatcher?.promptsSynced ?? 0}`,\n tooltip: \"Total prompts synced\",\n enabled: false,\n checked: false,\n },\n {\n title: `Skills detected: ${status?.skillDetector?.usagesDetected ?? 0}`,\n tooltip: \"Skill usages detected\",\n enabled: false,\n checked: false,\n },\n SysTray.separator,\n // Actions\n {\n title: running ? \"Stop Daemon\" : \"Start Daemon\",\n tooltip: running ? \"Stop the Skillo daemon\" : \"Start the Skillo daemon\",\n enabled: true,\n checked: false,\n },\n {\n title: \"Open Dashboard\",\n tooltip: \"Open Skillo dashboard in browser\",\n enabled: true,\n checked: false,\n },\n SysTray.separator,\n // Quit\n {\n title: \"Quit Tray\",\n tooltip: \"Hide tray icon (daemon keeps running)\",\n enabled: true,\n checked: false,\n },\n ];\n\n return {\n icon: ICON_BASE64,\n title: \"\",\n tooltip: `Skillo \\u2014 ${statusText}`,\n items,\n };\n}\n\nfunction openBrowser(url: string) {\n const os = platform();\n let cmd: string;\n if (os === \"darwin\") {\n cmd = `open \"${url}\"`;\n } else if (os === \"win32\") {\n cmd = `start \"\" \"${url}\"`;\n } else {\n cmd = `xdg-open \"${url}\"`;\n }\n exec(cmd, () => {});\n}\n\nfunction execSkillo(args: string) {\n exec(`skillo ${args}`, () => {});\n}\n\nexport async function startTray(): Promise<void> {\n const pidFile = join(getDataDir(), TRAY_PID_FILE);\n ensureDirectory(getDataDir());\n\n // Check if another tray is already running\n if (existsSync(pidFile)) {\n try {\n const pid = parseInt(readFileSync(pidFile, \"utf-8\").trim(), 10);\n process.kill(pid, 0); // Check if alive\n console.error(\"Tray icon is already running (PID: \" + pid + \")\");\n process.exit(1);\n } catch {\n // Stale PID file, continue\n }\n }\n\n // Write our PID\n writeFileSync(pidFile, String(process.pid));\n\n // Build initial menu\n const initialStatus = readStatus();\n const menu = buildMenu(initialStatus);\n\n const systray = new SysTray({\n menu,\n debug: false,\n copyDir: true,\n });\n\n // Cleanup on exit\n const cleanup = () => {\n try { unlinkSync(pidFile); } catch { /* ignore */ }\n systray.kill(false);\n process.exit(0);\n };\n process.on(\"SIGINT\", cleanup);\n process.on(\"SIGTERM\", cleanup);\n\n // Handle clicks\n await systray.ready();\n\n systray.onClick(async (action) => {\n const clickedTitle = action.item.title;\n\n if (clickedTitle === \"Stop Daemon\" || clickedTitle === \"Start Daemon\") {\n if (clickedTitle === \"Stop Daemon\") {\n execSkillo(\"stop\");\n } else {\n execSkillo(\"start\");\n }\n } else if (clickedTitle === \"Open Dashboard\") {\n openBrowser(\"https://www.skillo.one/dashboard\");\n } else if (clickedTitle === \"Quit Tray\") {\n cleanup();\n }\n });\n\n // Poll status and update menu\n setInterval(async () => {\n try {\n const status = readStatus();\n const updatedMenu = buildMenu(status);\n await systray.sendAction({\n type: \"update-menu\",\n menu: updatedMenu,\n });\n } catch {\n // ignore update errors\n }\n }, STATUS_POLL_MS);\n\n // Keep alive\n await new Promise(() => {});\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":["readFileSync","existsSync","writeFileSync","join","join","existsSync","readFileSync","writeFileSync"]}