skillo 0.1.5 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,12 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- getApiClient,
3
+ SkilloDatabase
4
+ } from "./chunk-2CVEPT6U.js";
5
+ import {
6
+ getApiClient
7
+ } from "./chunk-ODOZM4QV.js";
8
+ import {
4
9
  getConfigValue,
5
10
  getDefaultConfig,
6
11
  loadConfig,
7
12
  saveConfig,
8
13
  setConfigValue
9
- } from "./chunk-SYULMYPN.js";
14
+ } from "./chunk-CPL3P2OF.js";
10
15
  import {
11
16
  ensureDirectory,
12
17
  getActiveSessionsDir,
@@ -22,465 +27,12 @@ import {
22
27
  } from "./chunk-WJKZWKER.js";
23
28
 
24
29
  // src/cli.ts
25
- import { Command as Command13 } from "commander";
30
+ import { Command as Command14 } from "commander";
26
31
 
27
32
  // src/commands/init.ts
28
- import { existsSync as existsSync2 } from "fs";
33
+ import { existsSync } from "fs";
29
34
  import { Command } from "commander";
30
35
 
31
- // src/core/database.ts
32
- import initSqlJs from "sql.js";
33
- import { readFileSync, writeFileSync, existsSync } from "fs";
34
- import { randomUUID } from "crypto";
35
- import { dirname } from "path";
36
- var SQL = null;
37
- async function getSqlJs() {
38
- if (!SQL) {
39
- SQL = await initSqlJs();
40
- }
41
- return SQL;
42
- }
43
- var SkilloDatabase = class {
44
- db = null;
45
- dbPath;
46
- initialized = false;
47
- constructor(dbPath) {
48
- this.dbPath = dbPath || getDbPath();
49
- ensureDirectory(dirname(this.dbPath));
50
- }
51
- async getDb() {
52
- if (!this.db) {
53
- const SQL2 = await getSqlJs();
54
- if (existsSync(this.dbPath)) {
55
- const buffer = readFileSync(this.dbPath);
56
- this.db = new SQL2.Database(buffer);
57
- } else {
58
- this.db = new SQL2.Database();
59
- }
60
- }
61
- return this.db;
62
- }
63
- save() {
64
- if (this.db) {
65
- const data = this.db.export();
66
- const buffer = Buffer.from(data);
67
- writeFileSync(this.dbPath, buffer);
68
- }
69
- }
70
- async initialize() {
71
- if (this.initialized) return;
72
- const db = await this.getDb();
73
- const schema = `
74
- -- Command history
75
- CREATE TABLE IF NOT EXISTS commands (
76
- id TEXT PRIMARY KEY,
77
- timestamp DATETIME NOT NULL,
78
- command TEXT NOT NULL,
79
- normalized TEXT NOT NULL,
80
- variables TEXT,
81
- cwd TEXT NOT NULL,
82
- exit_code INTEGER,
83
- duration_ms INTEGER,
84
- session_id TEXT NOT NULL,
85
- project_id TEXT,
86
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
87
- );
88
-
89
- CREATE INDEX IF NOT EXISTS idx_commands_session ON commands(session_id);
90
- CREATE INDEX IF NOT EXISTS idx_commands_timestamp ON commands(timestamp);
91
- CREATE INDEX IF NOT EXISTS idx_commands_normalized ON commands(normalized);
92
-
93
- -- Terminal sessions
94
- CREATE TABLE IF NOT EXISTS sessions (
95
- id TEXT PRIMARY KEY,
96
- started_at DATETIME NOT NULL,
97
- ended_at DATETIME,
98
- shell TEXT NOT NULL,
99
- project_path TEXT,
100
- command_count INTEGER DEFAULT 0,
101
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
102
- );
103
-
104
- -- Conversations (Claude Code)
105
- CREATE TABLE IF NOT EXISTS conversations (
106
- id TEXT PRIMARY KEY,
107
- project_path TEXT,
108
- started_at DATETIME,
109
- updated_at DATETIME,
110
- message_count INTEGER DEFAULT 0,
111
- tool_calls TEXT,
112
- files_touched TEXT,
113
- intent_summary TEXT,
114
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
115
- );
116
-
117
- CREATE INDEX IF NOT EXISTS idx_conversations_project ON conversations(project_path);
118
-
119
- -- Detected patterns
120
- CREATE TABLE IF NOT EXISTS patterns (
121
- id TEXT PRIMARY KEY,
122
- source_type TEXT NOT NULL,
123
- description TEXT NOT NULL,
124
- count INTEGER DEFAULT 1,
125
- first_seen DATETIME NOT NULL,
126
- last_seen DATETIME NOT NULL,
127
- data TEXT NOT NULL,
128
- status TEXT DEFAULT 'active',
129
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
130
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
131
- );
132
-
133
- CREATE INDEX IF NOT EXISTS idx_patterns_status ON patterns(status);
134
- CREATE INDEX IF NOT EXISTS idx_patterns_count ON patterns(count);
135
-
136
- -- Generated skills
137
- CREATE TABLE IF NOT EXISTS skills (
138
- id TEXT PRIMARY KEY,
139
- name TEXT UNIQUE NOT NULL,
140
- path TEXT NOT NULL,
141
- source_patterns TEXT,
142
- source_type TEXT NOT NULL,
143
- trigger_phrase TEXT,
144
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
145
- usage_count INTEGER DEFAULT 0,
146
- last_used_at DATETIME
147
- );
148
-
149
- CREATE INDEX IF NOT EXISTS idx_skills_name ON skills(name);
150
-
151
- -- Configuration
152
- CREATE TABLE IF NOT EXISTS config (
153
- key TEXT PRIMARY KEY,
154
- value TEXT NOT NULL,
155
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
156
- );
157
-
158
- -- Ignored patterns
159
- CREATE TABLE IF NOT EXISTS ignored_patterns (
160
- pattern_hash TEXT PRIMARY KEY,
161
- reason TEXT,
162
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
163
- );
164
-
165
- -- Notifications
166
- CREATE TABLE IF NOT EXISTS notifications (
167
- id TEXT PRIMARY KEY,
168
- type TEXT NOT NULL,
169
- title TEXT NOT NULL,
170
- message TEXT,
171
- data TEXT,
172
- is_read INTEGER DEFAULT 0,
173
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
174
- );
175
- `;
176
- db.run(schema);
177
- this.save();
178
- this.initialized = true;
179
- }
180
- close() {
181
- if (this.db) {
182
- this.save();
183
- this.db.close();
184
- this.db = null;
185
- }
186
- }
187
- // Command operations
188
- async addCommand(params) {
189
- const db = await this.getDb();
190
- const id = randomUUID();
191
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
192
- db.run(
193
- `INSERT INTO commands (id, timestamp, command, normalized, variables, cwd, exit_code, duration_ms, session_id, project_id)
194
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
195
- [
196
- id,
197
- timestamp,
198
- params.command,
199
- params.normalized,
200
- params.variables ? JSON.stringify(params.variables) : null,
201
- params.cwd,
202
- params.exitCode ?? null,
203
- params.durationMs ?? null,
204
- params.sessionId,
205
- params.projectId ?? null
206
- ]
207
- );
208
- this.save();
209
- return id;
210
- }
211
- async getRecentCommands(options) {
212
- const db = await this.getDb();
213
- const limit = options?.limit ?? 100;
214
- let query = "SELECT * FROM commands";
215
- const params = [];
216
- const conditions = [];
217
- if (options?.sessionId) {
218
- conditions.push("session_id = ?");
219
- params.push(options.sessionId);
220
- }
221
- if (options?.days) {
222
- const cutoff = /* @__PURE__ */ new Date();
223
- cutoff.setDate(cutoff.getDate() - options.days);
224
- conditions.push("timestamp >= ?");
225
- params.push(cutoff.toISOString());
226
- }
227
- if (conditions.length > 0) {
228
- query += " WHERE " + conditions.join(" AND ");
229
- }
230
- query += " ORDER BY timestamp DESC LIMIT ?";
231
- params.push(limit);
232
- const results = db.exec(query, params);
233
- if (results.length === 0) return [];
234
- return results[0].values.map((row) => this.mapCommand(results[0].columns, row));
235
- }
236
- // Session operations
237
- async createSession(shell, projectPath) {
238
- const db = await this.getDb();
239
- const id = randomUUID();
240
- const startedAt = (/* @__PURE__ */ new Date()).toISOString();
241
- db.run(
242
- `INSERT INTO sessions (id, started_at, shell, project_path) VALUES (?, ?, ?, ?)`,
243
- [id, startedAt, shell, projectPath ?? null]
244
- );
245
- this.save();
246
- return id;
247
- }
248
- async endSession(sessionId) {
249
- const db = await this.getDb();
250
- const endedAt = (/* @__PURE__ */ new Date()).toISOString();
251
- db.run(
252
- `UPDATE sessions
253
- SET ended_at = ?, command_count = (SELECT COUNT(*) FROM commands WHERE session_id = ?)
254
- WHERE id = ?`,
255
- [endedAt, sessionId, sessionId]
256
- );
257
- this.save();
258
- }
259
- async getSession(sessionId) {
260
- const db = await this.getDb();
261
- const results = db.exec("SELECT * FROM sessions WHERE id = ?", [sessionId]);
262
- if (results.length === 0 || results[0].values.length === 0) return null;
263
- return this.mapSession(results[0].columns, results[0].values[0]);
264
- }
265
- // Pattern operations
266
- async addPattern(params) {
267
- const db = await this.getDb();
268
- const patternId = params.patternHash || randomUUID();
269
- const now = (/* @__PURE__ */ new Date()).toISOString();
270
- const existing = db.exec("SELECT id, count FROM patterns WHERE id = ?", [patternId]);
271
- if (existing.length > 0 && existing[0].values.length > 0) {
272
- db.run(`UPDATE patterns SET count = count + 1, last_seen = ?, updated_at = ? WHERE id = ?`, [
273
- now,
274
- now,
275
- patternId
276
- ]);
277
- } else {
278
- db.run(
279
- `INSERT INTO patterns (id, source_type, description, data, first_seen, last_seen)
280
- VALUES (?, ?, ?, ?, ?, ?)`,
281
- [patternId, params.sourceType, params.description, JSON.stringify(params.data), now, now]
282
- );
283
- }
284
- this.save();
285
- return patternId;
286
- }
287
- async getPatterns(options) {
288
- const db = await this.getDb();
289
- const minCount = options?.minCount ?? 1;
290
- const limit = options?.limit ?? 50;
291
- let query = "SELECT * FROM patterns WHERE count >= ?";
292
- const params = [minCount];
293
- if (options?.type) {
294
- query += " AND source_type = ?";
295
- params.push(options.type);
296
- }
297
- if (options?.status) {
298
- query += " AND status = ?";
299
- params.push(options.status);
300
- }
301
- query += " ORDER BY count DESC, last_seen DESC LIMIT ?";
302
- params.push(limit);
303
- const results = db.exec(query, params);
304
- if (results.length === 0) return [];
305
- return results[0].values.map((row) => this.mapPattern(results[0].columns, row));
306
- }
307
- async getPattern(patternId) {
308
- const db = await this.getDb();
309
- let results = db.exec("SELECT * FROM patterns WHERE id = ?", [patternId]);
310
- if (results.length === 0 || results[0].values.length === 0) {
311
- results = db.exec("SELECT * FROM patterns WHERE id LIKE ?", [`${patternId}%`]);
312
- }
313
- if (results.length === 0 || results[0].values.length === 0) return null;
314
- return this.mapPattern(results[0].columns, results[0].values[0]);
315
- }
316
- async updatePatternStatus(patternId, status) {
317
- const db = await this.getDb();
318
- const now = (/* @__PURE__ */ new Date()).toISOString();
319
- db.run(`UPDATE patterns SET status = ?, updated_at = ? WHERE id = ? OR id LIKE ?`, [
320
- status,
321
- now,
322
- patternId,
323
- `${patternId}%`
324
- ]);
325
- this.save();
326
- }
327
- async addIgnoredPattern(patternHash, reason) {
328
- const db = await this.getDb();
329
- db.run(`INSERT OR REPLACE INTO ignored_patterns (pattern_hash, reason) VALUES (?, ?)`, [
330
- patternHash,
331
- reason ?? null
332
- ]);
333
- this.save();
334
- }
335
- async isPatternIgnored(patternHash) {
336
- const db = await this.getDb();
337
- const results = db.exec("SELECT 1 FROM ignored_patterns WHERE pattern_hash = ?", [patternHash]);
338
- return results.length > 0 && results[0].values.length > 0;
339
- }
340
- // Skill operations
341
- async registerSkill(params) {
342
- const db = await this.getDb();
343
- const id = randomUUID();
344
- db.run(
345
- `INSERT INTO skills (id, name, path, source_patterns, source_type, trigger_phrase)
346
- VALUES (?, ?, ?, ?, ?, ?)`,
347
- [
348
- id,
349
- params.name,
350
- params.path,
351
- params.sourcePatterns ? JSON.stringify(params.sourcePatterns) : null,
352
- params.sourceType,
353
- params.triggerPhrase ?? null
354
- ]
355
- );
356
- this.save();
357
- return id;
358
- }
359
- async getSkills(options) {
360
- const db = await this.getDb();
361
- const sortMap = {
362
- name: "name",
363
- created: "created_at",
364
- usage: "usage_count DESC"
365
- };
366
- const order = sortMap[options?.sortBy ?? "name"];
367
- let query = "SELECT * FROM skills";
368
- const params = [];
369
- if (options?.source) {
370
- query += " WHERE source_type = ?";
371
- params.push(options.source);
372
- }
373
- query += ` ORDER BY ${order}`;
374
- const results = db.exec(query, params);
375
- if (results.length === 0) return [];
376
- return results[0].values.map((row) => this.mapSkill(results[0].columns, row));
377
- }
378
- async getSkill(name) {
379
- const db = await this.getDb();
380
- const results = db.exec("SELECT * FROM skills WHERE name = ?", [name]);
381
- if (results.length === 0 || results[0].values.length === 0) return null;
382
- return this.mapSkill(results[0].columns, results[0].values[0]);
383
- }
384
- async deleteSkill(name) {
385
- const db = await this.getDb();
386
- db.run("DELETE FROM skills WHERE name = ?", [name]);
387
- this.save();
388
- }
389
- async recordSkillUsage(name) {
390
- const db = await this.getDb();
391
- const now = (/* @__PURE__ */ new Date()).toISOString();
392
- db.run(`UPDATE skills SET usage_count = usage_count + 1, last_used_at = ? WHERE name = ?`, [
393
- now,
394
- name
395
- ]);
396
- this.save();
397
- }
398
- // Statistics
399
- async getStats() {
400
- const db = await this.getDb();
401
- const today = /* @__PURE__ */ new Date();
402
- today.setHours(0, 0, 0, 0);
403
- const todayIso = today.toISOString();
404
- const getCount = (query, params = []) => {
405
- const results = db.exec(query, params);
406
- if (results.length === 0 || results[0].values.length === 0) return 0;
407
- return results[0].values[0][0];
408
- };
409
- return {
410
- commandsToday: getCount("SELECT COUNT(*) FROM commands WHERE timestamp >= ?", [todayIso]),
411
- commandsTotal: getCount("SELECT COUNT(*) FROM commands"),
412
- patternsActive: getCount("SELECT COUNT(*) FROM patterns WHERE status = 'active'"),
413
- skillsTotal: getCount("SELECT COUNT(*) FROM skills"),
414
- conversationsTotal: getCount("SELECT COUNT(*) FROM conversations")
415
- };
416
- }
417
- // Mapping helpers
418
- mapCommand(columns, values) {
419
- const row = this.toObject(columns, values);
420
- return {
421
- id: row.id,
422
- timestamp: row.timestamp,
423
- command: row.command,
424
- normalized: row.normalized,
425
- variables: row.variables,
426
- cwd: row.cwd,
427
- exitCode: row.exit_code,
428
- durationMs: row.duration_ms,
429
- sessionId: row.session_id,
430
- projectId: row.project_id,
431
- createdAt: row.created_at
432
- };
433
- }
434
- mapSession(columns, values) {
435
- const row = this.toObject(columns, values);
436
- return {
437
- id: row.id,
438
- startedAt: row.started_at,
439
- endedAt: row.ended_at,
440
- shell: row.shell,
441
- projectPath: row.project_path,
442
- commandCount: row.command_count,
443
- createdAt: row.created_at
444
- };
445
- }
446
- mapPattern(columns, values) {
447
- const row = this.toObject(columns, values);
448
- return {
449
- id: row.id,
450
- sourceType: row.source_type,
451
- description: row.description,
452
- count: row.count,
453
- firstSeen: row.first_seen,
454
- lastSeen: row.last_seen,
455
- data: JSON.parse(row.data),
456
- status: row.status,
457
- createdAt: row.created_at,
458
- updatedAt: row.updated_at
459
- };
460
- }
461
- mapSkill(columns, values) {
462
- const row = this.toObject(columns, values);
463
- return {
464
- id: row.id,
465
- name: row.name,
466
- path: row.path,
467
- sourcePatterns: row.source_patterns,
468
- sourceType: row.source_type,
469
- triggerPhrase: row.trigger_phrase,
470
- createdAt: row.created_at,
471
- usageCount: row.usage_count,
472
- lastUsedAt: row.last_used_at
473
- };
474
- }
475
- toObject(columns, values) {
476
- const obj = {};
477
- columns.forEach((col, i) => {
478
- obj[col] = values[i];
479
- });
480
- return obj;
481
- }
482
- };
483
-
484
36
  // src/utils/logger.ts
485
37
  import chalk from "chalk";
486
38
  var logger = {
@@ -552,7 +104,7 @@ var initCommand = new Command("init").description("Initialize Skillo configurati
552
104
  const configDir = getConfigDir();
553
105
  const skillsDir = getSkillsDir();
554
106
  const configFile = getConfigFile();
555
- if (existsSync2(configFile) && !options.force) {
107
+ if (existsSync(configFile) && !options.force) {
556
108
  logger_default.warn("Skillo is already initialized.");
557
109
  logger_default.dim("Use --force to reinitialize and overwrite configuration.");
558
110
  return;
@@ -595,6 +147,435 @@ var initCommand = new Command("init").description("Initialize Skillo configurati
595
147
  // src/commands/status.ts
596
148
  import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
597
149
  import { Command as Command2 } from "commander";
150
+
151
+ // src/utils/os-service.ts
152
+ import { existsSync as existsSync2, writeFileSync, unlinkSync, mkdirSync, readdirSync } from "fs";
153
+ import { join } from "path";
154
+ import { homedir, platform } from "os";
155
+ import { execSync } from "child_process";
156
+ var DAEMON_LABEL = "one.skillo.daemon";
157
+ var TRAY_LABEL = "one.skillo.tray";
158
+ var WIN_REG_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
159
+ var WIN_DAEMON_VALUE = "SkilloDaemon";
160
+ var WIN_TRAY_VALUE = "SkilloTray";
161
+ var isWin = platform() === "win32";
162
+ var pathSep = isWin ? ";" : ":";
163
+ function getLaunchAgentsDir() {
164
+ return join(homedir(), "Library", "LaunchAgents");
165
+ }
166
+ function getSystemdUserDir() {
167
+ return join(homedir(), ".config", "systemd", "user");
168
+ }
169
+ function getDaemonPlistPath() {
170
+ return join(getLaunchAgentsDir(), `${DAEMON_LABEL}.plist`);
171
+ }
172
+ function getTrayPlistPath() {
173
+ return join(getLaunchAgentsDir(), `${TRAY_LABEL}.plist`);
174
+ }
175
+ function getDaemonServicePath() {
176
+ return join(getSystemdUserDir(), "skillo-daemon.service");
177
+ }
178
+ function getTrayServicePath() {
179
+ return join(getSystemdUserDir(), "skillo-tray.service");
180
+ }
181
+ function getSkilloDataDir() {
182
+ return join(homedir(), ".skillo");
183
+ }
184
+ function getWinDaemonVbsPath() {
185
+ return join(getSkilloDataDir(), "skillo-daemon.vbs");
186
+ }
187
+ function findSkilloBin() {
188
+ try {
189
+ const whichCmd = isWin ? "where skillo" : "which skillo";
190
+ const result = execSync(whichCmd, { encoding: "utf-8" }).trim();
191
+ const firstLine = result.split(/\r?\n/)[0].trim();
192
+ if (firstLine) return firstLine;
193
+ } catch {
194
+ }
195
+ if (isWin) {
196
+ const appData = process.env.APPDATA || join(homedir(), "AppData", "Roaming");
197
+ const candidates = [
198
+ join(appData, "npm", "skillo.cmd"),
199
+ join(homedir(), "AppData", "Local", "fnm_multishells")
200
+ ];
201
+ for (const c of candidates) {
202
+ if (existsSync2(c)) return c;
203
+ }
204
+ } else {
205
+ const candidates = [
206
+ "/usr/local/bin/skillo",
207
+ "/usr/bin/skillo"
208
+ ];
209
+ for (const c of candidates) {
210
+ if (existsSync2(c)) return c;
211
+ }
212
+ }
213
+ return "skillo";
214
+ }
215
+ function buildPath() {
216
+ const paths = /* @__PURE__ */ new Set();
217
+ const home = homedir();
218
+ (process.env.PATH || "").split(pathSep).forEach((p) => paths.add(p));
219
+ if (isWin) {
220
+ const appData = process.env.APPDATA || join(home, "AppData", "Roaming");
221
+ paths.add(join(appData, "npm"));
222
+ paths.add(join(home, "AppData", "Local", "Programs", "nodejs"));
223
+ const nvmHome = process.env.NVM_HOME;
224
+ if (nvmHome) paths.add(nvmHome);
225
+ const nvmSymlink = process.env.NVM_SYMLINK;
226
+ if (nvmSymlink) paths.add(nvmSymlink);
227
+ const fnmDir = join(home, ".fnm");
228
+ if (existsSync2(fnmDir)) paths.add(fnmDir);
229
+ } else {
230
+ const nvmDir = process.env.NVM_DIR || join(home, ".nvm");
231
+ if (existsSync2(nvmDir)) {
232
+ try {
233
+ const defaultAlias = join(nvmDir, "alias", "default");
234
+ if (existsSync2(defaultAlias)) {
235
+ const versionsDir = join(nvmDir, "versions", "node");
236
+ if (existsSync2(versionsDir)) {
237
+ const versions = readdirSync(versionsDir);
238
+ for (const v of versions) {
239
+ paths.add(join(versionsDir, v, "bin"));
240
+ }
241
+ }
242
+ }
243
+ } catch {
244
+ }
245
+ }
246
+ paths.add(join(home, ".fnm", "current", "bin"));
247
+ paths.add("/opt/homebrew/bin");
248
+ paths.add("/usr/local/bin");
249
+ paths.add("/usr/bin");
250
+ paths.add("/bin");
251
+ paths.add(join(home, ".npm-global", "bin"));
252
+ paths.add(join(home, ".local", "bin"));
253
+ }
254
+ return [...paths].filter(Boolean).join(pathSep);
255
+ }
256
+ function generateDaemonPlist(skilloBin, envPath) {
257
+ return `<?xml version="1.0" encoding="UTF-8"?>
258
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
259
+ <plist version="1.0">
260
+ <dict>
261
+ <key>Label</key>
262
+ <string>${DAEMON_LABEL}</string>
263
+ <key>ProgramArguments</key>
264
+ <array>
265
+ <string>${skilloBin}</string>
266
+ <string>start</string>
267
+ <string>--foreground</string>
268
+ </array>
269
+ <key>RunAtLoad</key>
270
+ <true/>
271
+ <key>KeepAlive</key>
272
+ <dict>
273
+ <key>SuccessfulExit</key>
274
+ <false/>
275
+ </dict>
276
+ <key>ThrottleInterval</key>
277
+ <integer>10</integer>
278
+ <key>EnvironmentVariables</key>
279
+ <dict>
280
+ <key>PATH</key>
281
+ <string>${envPath}</string>
282
+ <key>HOME</key>
283
+ <string>${homedir()}</string>
284
+ </dict>
285
+ <key>StandardOutPath</key>
286
+ <string>${join(homedir(), ".skillo", "launchd-stdout.log")}</string>
287
+ <key>StandardErrorPath</key>
288
+ <string>${join(homedir(), ".skillo", "launchd-stderr.log")}</string>
289
+ </dict>
290
+ </plist>`;
291
+ }
292
+ function generateTrayPlist(skilloBin, envPath) {
293
+ return `<?xml version="1.0" encoding="UTF-8"?>
294
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
295
+ <plist version="1.0">
296
+ <dict>
297
+ <key>Label</key>
298
+ <string>${TRAY_LABEL}</string>
299
+ <key>ProgramArguments</key>
300
+ <array>
301
+ <string>${skilloBin}</string>
302
+ <string>tray</string>
303
+ </array>
304
+ <key>RunAtLoad</key>
305
+ <true/>
306
+ <key>LimitLoadToSessionType</key>
307
+ <string>Aqua</string>
308
+ <key>KeepAlive</key>
309
+ <dict>
310
+ <key>SuccessfulExit</key>
311
+ <false/>
312
+ </dict>
313
+ <key>ThrottleInterval</key>
314
+ <integer>10</integer>
315
+ <key>EnvironmentVariables</key>
316
+ <dict>
317
+ <key>PATH</key>
318
+ <string>${envPath}</string>
319
+ <key>HOME</key>
320
+ <string>${homedir()}</string>
321
+ </dict>
322
+ <key>StandardOutPath</key>
323
+ <string>${join(homedir(), ".skillo", "launchd-tray-stdout.log")}</string>
324
+ <key>StandardErrorPath</key>
325
+ <string>${join(homedir(), ".skillo", "launchd-tray-stderr.log")}</string>
326
+ </dict>
327
+ </plist>`;
328
+ }
329
+ function generateDaemonUnit(skilloBin, envPath) {
330
+ return `[Unit]
331
+ Description=Skillo Daemon
332
+ After=network.target
333
+
334
+ [Service]
335
+ Type=simple
336
+ ExecStart=${skilloBin} start --foreground
337
+ Restart=on-failure
338
+ RestartSec=10
339
+ Environment=PATH=${envPath}
340
+ Environment=HOME=${homedir()}
341
+
342
+ [Install]
343
+ WantedBy=default.target
344
+ `;
345
+ }
346
+ function generateTrayUnit(skilloBin, envPath) {
347
+ return `[Unit]
348
+ Description=Skillo Tray Icon
349
+ After=graphical-session.target
350
+ PartOf=graphical-session.target
351
+
352
+ [Service]
353
+ Type=simple
354
+ ExecStart=${skilloBin} tray
355
+ Restart=on-failure
356
+ RestartSec=10
357
+ Environment=PATH=${envPath}
358
+ Environment=HOME=${homedir()}
359
+ Environment=DISPLAY=:0
360
+
361
+ [Install]
362
+ WantedBy=graphical-session.target
363
+ `;
364
+ }
365
+ function generateDaemonVbs(skilloBin) {
366
+ const escaped = skilloBin.replace(/\\/g, "\\\\");
367
+ return `' Skillo Daemon \u2014 hidden launcher\r
368
+ Set WshShell = CreateObject("WScript.Shell")\r
369
+ WshShell.Run """${escaped}"" start --foreground", 0, False\r
370
+ `;
371
+ }
372
+ function winRegAdd(valueName, data) {
373
+ execSync(
374
+ `reg add "${WIN_REG_KEY}" /v "${valueName}" /t REG_SZ /d "${data}" /f`,
375
+ { stdio: "ignore" }
376
+ );
377
+ }
378
+ function winRegDelete(valueName) {
379
+ try {
380
+ execSync(
381
+ `reg delete "${WIN_REG_KEY}" /v "${valueName}" /f`,
382
+ { stdio: "ignore" }
383
+ );
384
+ } catch {
385
+ }
386
+ }
387
+ function winRegExists(valueName) {
388
+ try {
389
+ execSync(`reg query "${WIN_REG_KEY}" /v "${valueName}"`, { stdio: "ignore" });
390
+ return true;
391
+ } catch {
392
+ return false;
393
+ }
394
+ }
395
+ async function installService(options) {
396
+ const os2 = platform();
397
+ const skilloBin = findSkilloBin();
398
+ const envPath = buildPath();
399
+ const includeTray = options?.includeTray ?? true;
400
+ try {
401
+ if (os2 === "darwin") {
402
+ const agentsDir = getLaunchAgentsDir();
403
+ if (!existsSync2(agentsDir)) mkdirSync(agentsDir, { recursive: true });
404
+ const daemonPlist = getDaemonPlistPath();
405
+ writeFileSync(daemonPlist, generateDaemonPlist(skilloBin, envPath), "utf-8");
406
+ try {
407
+ execSync(`launchctl unload "${daemonPlist}" 2>/dev/null`);
408
+ } catch {
409
+ }
410
+ execSync(`launchctl load "${daemonPlist}"`);
411
+ if (includeTray) {
412
+ const trayPlist = getTrayPlistPath();
413
+ writeFileSync(trayPlist, generateTrayPlist(skilloBin, envPath), "utf-8");
414
+ try {
415
+ execSync(`launchctl unload "${trayPlist}" 2>/dev/null`);
416
+ } catch {
417
+ }
418
+ execSync(`launchctl load "${trayPlist}"`);
419
+ }
420
+ return { success: true };
421
+ } else if (os2 === "linux") {
422
+ const systemdDir = getSystemdUserDir();
423
+ if (!existsSync2(systemdDir)) mkdirSync(systemdDir, { recursive: true });
424
+ writeFileSync(getDaemonServicePath(), generateDaemonUnit(skilloBin, envPath), "utf-8");
425
+ execSync("systemctl --user daemon-reload");
426
+ execSync("systemctl --user enable skillo-daemon.service");
427
+ execSync("systemctl --user start skillo-daemon.service");
428
+ if (includeTray) {
429
+ writeFileSync(getTrayServicePath(), generateTrayUnit(skilloBin, envPath), "utf-8");
430
+ execSync("systemctl --user daemon-reload");
431
+ execSync("systemctl --user enable skillo-tray.service");
432
+ try {
433
+ execSync("systemctl --user start skillo-tray.service");
434
+ } catch {
435
+ }
436
+ }
437
+ return { success: true };
438
+ } else if (os2 === "win32") {
439
+ const dataDir = getSkilloDataDir();
440
+ if (!existsSync2(dataDir)) mkdirSync(dataDir, { recursive: true });
441
+ const vbsPath = getWinDaemonVbsPath();
442
+ writeFileSync(vbsPath, generateDaemonVbs(skilloBin), "utf-8");
443
+ winRegAdd(WIN_DAEMON_VALUE, `wscript.exe "${vbsPath}"`);
444
+ if (includeTray) {
445
+ winRegAdd(WIN_TRAY_VALUE, `"${skilloBin}" tray`);
446
+ }
447
+ return { success: true };
448
+ } else {
449
+ return { success: false, error: `Unsupported platform: ${os2}. Auto-start is available on macOS, Linux, and Windows.` };
450
+ }
451
+ } catch (error) {
452
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
453
+ }
454
+ }
455
+ async function uninstallService() {
456
+ const os2 = platform();
457
+ try {
458
+ if (os2 === "darwin") {
459
+ const daemonPlist = getDaemonPlistPath();
460
+ if (existsSync2(daemonPlist)) {
461
+ try {
462
+ execSync(`launchctl unload "${daemonPlist}" 2>/dev/null`);
463
+ } catch {
464
+ }
465
+ unlinkSync(daemonPlist);
466
+ }
467
+ const trayPlist = getTrayPlistPath();
468
+ if (existsSync2(trayPlist)) {
469
+ try {
470
+ execSync(`launchctl unload "${trayPlist}" 2>/dev/null`);
471
+ } catch {
472
+ }
473
+ unlinkSync(trayPlist);
474
+ }
475
+ return { success: true };
476
+ } else if (os2 === "linux") {
477
+ const daemonService = getDaemonServicePath();
478
+ if (existsSync2(daemonService)) {
479
+ try {
480
+ execSync("systemctl --user stop skillo-daemon.service 2>/dev/null");
481
+ } catch {
482
+ }
483
+ try {
484
+ execSync("systemctl --user disable skillo-daemon.service 2>/dev/null");
485
+ } catch {
486
+ }
487
+ unlinkSync(daemonService);
488
+ }
489
+ const trayService = getTrayServicePath();
490
+ if (existsSync2(trayService)) {
491
+ try {
492
+ execSync("systemctl --user stop skillo-tray.service 2>/dev/null");
493
+ } catch {
494
+ }
495
+ try {
496
+ execSync("systemctl --user disable skillo-tray.service 2>/dev/null");
497
+ } catch {
498
+ }
499
+ unlinkSync(trayService);
500
+ }
501
+ try {
502
+ execSync("systemctl --user daemon-reload");
503
+ } catch {
504
+ }
505
+ return { success: true };
506
+ } else if (os2 === "win32") {
507
+ winRegDelete(WIN_DAEMON_VALUE);
508
+ winRegDelete(WIN_TRAY_VALUE);
509
+ const vbsPath = getWinDaemonVbsPath();
510
+ if (existsSync2(vbsPath)) {
511
+ try {
512
+ unlinkSync(vbsPath);
513
+ } catch {
514
+ }
515
+ }
516
+ return { success: true };
517
+ } else {
518
+ return { success: false, error: `Unsupported platform: ${os2}` };
519
+ }
520
+ } catch (error) {
521
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
522
+ }
523
+ }
524
+ function getServiceStatus() {
525
+ const os2 = platform();
526
+ if (os2 === "darwin") {
527
+ const daemonInstalled = existsSync2(getDaemonPlistPath());
528
+ const trayInstalled = existsSync2(getTrayPlistPath());
529
+ let daemonLoaded = false;
530
+ let trayLoaded = false;
531
+ try {
532
+ const output = execSync("launchctl list", { encoding: "utf-8" });
533
+ daemonLoaded = output.includes(DAEMON_LABEL);
534
+ trayLoaded = output.includes(TRAY_LABEL);
535
+ } catch {
536
+ }
537
+ return {
538
+ platform: "macos",
539
+ daemon: { installed: daemonInstalled, loaded: daemonLoaded },
540
+ tray: { installed: trayInstalled, loaded: trayLoaded }
541
+ };
542
+ } else if (os2 === "linux") {
543
+ const daemonInstalled = existsSync2(getDaemonServicePath());
544
+ const trayInstalled = existsSync2(getTrayServicePath());
545
+ let daemonLoaded = false;
546
+ let trayLoaded = false;
547
+ try {
548
+ execSync("systemctl --user is-active skillo-daemon.service", { encoding: "utf-8" });
549
+ daemonLoaded = true;
550
+ } catch {
551
+ }
552
+ try {
553
+ execSync("systemctl --user is-active skillo-tray.service", { encoding: "utf-8" });
554
+ trayLoaded = true;
555
+ } catch {
556
+ }
557
+ return {
558
+ platform: "linux",
559
+ daemon: { installed: daemonInstalled, loaded: daemonLoaded },
560
+ tray: { installed: trayInstalled, loaded: trayLoaded }
561
+ };
562
+ } else if (os2 === "win32") {
563
+ const daemonInstalled = winRegExists(WIN_DAEMON_VALUE);
564
+ const trayInstalled = winRegExists(WIN_TRAY_VALUE);
565
+ return {
566
+ platform: "windows",
567
+ daemon: { installed: daemonInstalled, loaded: daemonInstalled },
568
+ tray: { installed: trayInstalled, loaded: trayInstalled }
569
+ };
570
+ }
571
+ return {
572
+ platform: "unsupported",
573
+ daemon: { installed: false, loaded: false },
574
+ tray: { installed: false, loaded: false }
575
+ };
576
+ }
577
+
578
+ // src/commands/status.ts
598
579
  function isDaemonRunning() {
599
580
  const pidFile = getPidFile();
600
581
  if (!existsSync3(pidFile)) {
@@ -625,8 +606,15 @@ var statusCommand = new Command2("status").description("Show daemon status and s
625
606
  logger_default.stopped("Daemon is not running");
626
607
  logger_default.blank();
627
608
  logger_default.dim("Start with: skillo start");
628
- return;
629
609
  }
610
+ const serviceStatus = getServiceStatus();
611
+ if (serviceStatus.daemon.installed) {
612
+ logger_default.running("Auto-start: enabled");
613
+ } else {
614
+ logger_default.stopped("Auto-start: disabled");
615
+ logger_default.dim(" Enable with: skillo service install");
616
+ }
617
+ if (!running) return;
630
618
  logger_default.blank();
631
619
  const dbPath = getDbPath();
632
620
  if (!existsSync3(dbPath)) {
@@ -851,9 +839,9 @@ patternsCommand.command("ignore <id>").description("Ignore a pattern (never sugg
851
839
  }
852
840
  });
853
841
  patternsCommand.command("generate <id>").description("Generate a skill from a pattern").option("-n, --name <name>", "Custom skill name").option("--dry-run", "Preview without creating").action(async (id, options) => {
854
- const { getApiClient: getApiClient2 } = await import("./api-client-WO6NUCIJ.js");
855
- const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync2 } = await import("fs");
856
- const { join: join6 } = await import("path");
842
+ const { getApiClient: getApiClient2 } = await import("./api-client-KUQW7FSC.js");
843
+ const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync3 } = await import("fs");
844
+ const { join: join7 } = await import("path");
857
845
  const { getSkillsDir: getSkillsDir2, ensureDirectory: ensureDirectory2 } = await import("./paths-INOKEM66.js");
858
846
  const dbPath = getDbPath();
859
847
  if (!existsSync5(dbPath)) {
@@ -907,9 +895,9 @@ patternsCommand.command("generate <id>").description("Generate a skill from a pa
907
895
  } else {
908
896
  const skillsDir = getSkillsDir2();
909
897
  ensureDirectory2(skillsDir);
910
- const skillDir = join6(skillsDir, skill.slug);
911
- const skillFile = join6(skillDir, "SKILL.md");
912
- mkdirSync2(skillDir, { recursive: true });
898
+ const skillDir = join7(skillsDir, skill.slug);
899
+ const skillFile = join7(skillDir, "SKILL.md");
900
+ mkdirSync3(skillDir, { recursive: true });
913
901
  writeFileSync7(skillFile, skill.content, "utf-8");
914
902
  await db.updatePatternStatus(id, "converted");
915
903
  logger_default.success(`Skill saved to: ~/.claude/skills/${skill.slug}/SKILL.md`);
@@ -933,8 +921,8 @@ patternsCommand.command("clear").description("Clear all patterns").option("-f, -
933
921
  });
934
922
 
935
923
  // src/commands/skills.ts
936
- import { existsSync as existsSync6, readdirSync, rmSync } from "fs";
937
- import { join } from "path";
924
+ import { existsSync as existsSync6, readdirSync as readdirSync2, rmSync } from "fs";
925
+ import { join as join2 } from "path";
938
926
  import { Command as Command5 } from "commander";
939
927
  import chalk3 from "chalk";
940
928
  var skillsCommand = new Command5("skills").description(
@@ -956,9 +944,9 @@ skillsCommand.command("list").description("List all skills").option("-s, --sourc
956
944
  const skillsDir = getSkillsDir();
957
945
  const fsSkills = [];
958
946
  if (existsSync6(skillsDir)) {
959
- const entries = readdirSync(skillsDir, { withFileTypes: true });
947
+ const entries = readdirSync2(skillsDir, { withFileTypes: true });
960
948
  entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).forEach((e) => {
961
- const skillMd = join(skillsDir, e.name, "SKILL.md");
949
+ const skillMd = join2(skillsDir, e.name, "SKILL.md");
962
950
  if (existsSync6(skillMd)) {
963
951
  fsSkills.push(e.name);
964
952
  }
@@ -1008,12 +996,12 @@ skillsCommand.command("show <name>").description("Show skill details").action(as
1008
996
  const skill = await db.getSkill(name);
1009
997
  db.close();
1010
998
  if (!skill) {
1011
- const skillPath = join(getSkillsDir(), name, "SKILL.md");
999
+ const skillPath = join2(getSkillsDir(), name, "SKILL.md");
1012
1000
  if (existsSync6(skillPath)) {
1013
1001
  logger_default.blank();
1014
1002
  logger_default.bold(name);
1015
1003
  logger_default.blank();
1016
- logger_default.dim(` Path: ${join(getSkillsDir(), name)}`);
1004
+ logger_default.dim(` Path: ${join2(getSkillsDir(), name)}`);
1017
1005
  logger_default.dim(` Status: Not registered in database`);
1018
1006
  logger_default.blank();
1019
1007
  return;
@@ -1047,7 +1035,7 @@ skillsCommand.command("delete <name>").description("Delete a skill").option("-f,
1047
1035
  const db = new SkilloDatabase();
1048
1036
  await db.initialize();
1049
1037
  const skill = await db.getSkill(name);
1050
- const skillDir = skill?.path || join(getSkillsDir(), name);
1038
+ const skillDir = skill?.path || join2(getSkillsDir(), name);
1051
1039
  if (!skill && !existsSync6(skillDir)) {
1052
1040
  db.close();
1053
1041
  logger_default.error(`Skill not found: ${name}`);
@@ -1090,8 +1078,8 @@ skillsCommand.command("open").description("Open skills directory in file manager
1090
1078
  // src/commands/shell.ts
1091
1079
  import { existsSync as existsSync7, writeFileSync as writeFileSync2, readFileSync as readFileSync4, appendFileSync } from "fs";
1092
1080
  import { spawn } from "child_process";
1093
- import { homedir } from "os";
1094
- import { join as join2 } from "path";
1081
+ import { homedir as homedir2 } from "os";
1082
+ import { join as join3 } from "path";
1095
1083
  import { Command as Command6 } from "commander";
1096
1084
  function normalizeCommand(cmd) {
1097
1085
  const variables = {};
@@ -1203,7 +1191,7 @@ var recordCommand = new Command6("record").description("Record a command (used b
1203
1191
  db.close();
1204
1192
  if (options.sync !== false) {
1205
1193
  try {
1206
- const { getApiClient: getApiClient2 } = await import("./api-client-WO6NUCIJ.js");
1194
+ const { getApiClient: getApiClient2 } = await import("./api-client-KUQW7FSC.js");
1207
1195
  const client = getApiClient2();
1208
1196
  if (client.hasApiKey()) {
1209
1197
  await client.syncCommands([{
@@ -1224,7 +1212,7 @@ var recordCommand = new Command6("record").description("Record a command (used b
1224
1212
  );
1225
1213
  var setupShellCommand = new Command6("setup-shell").description("Set up shell integration for automatic command tracking").option("--bash", "Set up Bash integration").option("--zsh", "Set up Zsh integration").option("--powershell", "Set up PowerShell integration").option("--fish", "Set up Fish integration").option("--uninstall", "Remove shell integration").action(async (options) => {
1226
1214
  logger_default.blank();
1227
- const home = homedir();
1215
+ const home = homedir2();
1228
1216
  const dataDir = getDataDir();
1229
1217
  ensureDirectory(dataDir);
1230
1218
  let shell = null;
@@ -1272,7 +1260,7 @@ var setupShellCommand = new Command6("setup-shell").description("Set up shell in
1272
1260
  logger_default.blank();
1273
1261
  });
1274
1262
  async function installShellIntegration(shell, home, dataDir) {
1275
- const scriptPath = join2(dataDir, "shell-integration");
1263
+ const scriptPath = join3(dataDir, "shell-integration");
1276
1264
  ensureDirectory(scriptPath);
1277
1265
  if (shell === "powershell") {
1278
1266
  const psScript = `# Skillo CLI Integration
@@ -1420,11 +1408,11 @@ $null = Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action {
1420
1408
 
1421
1409
  Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
1422
1410
  `;
1423
- const psScriptFile = join2(scriptPath, "skillo.ps1");
1411
+ const psScriptFile = join3(scriptPath, "skillo.ps1");
1424
1412
  writeFileSync2(psScriptFile, psScript, "utf-8");
1425
1413
  logger_default.success(`Created ${psScriptFile}`);
1426
- const profilePath = join2(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
1427
- const profileDir = join2(home, "Documents", "WindowsPowerShell");
1414
+ const profilePath = join3(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
1415
+ const profileDir = join3(home, "Documents", "WindowsPowerShell");
1428
1416
  ensureDirectory(profileDir);
1429
1417
  const sourceLine = `
1430
1418
  # Skillo CLI Integration
@@ -1509,10 +1497,10 @@ Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
1509
1497
  "",
1510
1498
  'echo -e "\\033[90mSkillo: Command tracking enabled\\033[0m"'
1511
1499
  ].join("\n");
1512
- const bashScriptFile = join2(scriptPath, "skillo.bash");
1500
+ const bashScriptFile = join3(scriptPath, "skillo.bash");
1513
1501
  writeFileSync2(bashScriptFile, bashScript, "utf-8");
1514
1502
  logger_default.success(`Created ${bashScriptFile}`);
1515
- const bashrcPath = join2(home, ".bashrc");
1503
+ const bashrcPath = join3(home, ".bashrc");
1516
1504
  const sourceLine = `
1517
1505
  # Skillo CLI Integration
1518
1506
  [ -f "${bashScriptFile}" ] && source "${bashScriptFile}"
@@ -1592,10 +1580,10 @@ Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
1592
1580
  "",
1593
1581
  'echo -e "\\033[90mSkillo: Command tracking enabled\\033[0m"'
1594
1582
  ].join("\n");
1595
- const zshScriptFile = join2(scriptPath, "skillo.zsh");
1583
+ const zshScriptFile = join3(scriptPath, "skillo.zsh");
1596
1584
  writeFileSync2(zshScriptFile, zshScript, "utf-8");
1597
1585
  logger_default.success(`Created ${zshScriptFile}`);
1598
- const zshrcPath = join2(home, ".zshrc");
1586
+ const zshrcPath = join3(home, ".zshrc");
1599
1587
  const sourceLine = `
1600
1588
  # Skillo CLI Integration
1601
1589
  [ -f "${zshScriptFile}" ] && source "${zshScriptFile}"
@@ -1627,12 +1615,12 @@ Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
1627
1615
  "",
1628
1616
  'echo -e "\\033[90mSkillo: Command tracking enabled\\033[0m"'
1629
1617
  ].join("\n");
1630
- const fishScriptFile = join2(scriptPath, "skillo.fish");
1618
+ const fishScriptFile = join3(scriptPath, "skillo.fish");
1631
1619
  writeFileSync2(fishScriptFile, fishScript, "utf-8");
1632
1620
  logger_default.success(`Created ${fishScriptFile}`);
1633
- const fishConfigDir = join2(home, ".config", "fish");
1621
+ const fishConfigDir = join3(home, ".config", "fish");
1634
1622
  ensureDirectory(fishConfigDir);
1635
- const fishConfigPath = join2(fishConfigDir, "config.fish");
1623
+ const fishConfigPath = join3(fishConfigDir, "config.fish");
1636
1624
  const sourceLine = `
1637
1625
  # Skillo CLI Integration
1638
1626
  if test -f "${fishScriptFile}"
@@ -1661,19 +1649,19 @@ async function uninstallShellIntegration(shell, home) {
1661
1649
  writeFileSync2(filePath, content, "utf-8");
1662
1650
  };
1663
1651
  if (shell === "powershell") {
1664
- const profilePath = join2(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
1652
+ const profilePath = join3(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
1665
1653
  removeFromFile(profilePath);
1666
1654
  } else if (shell === "bash") {
1667
- removeFromFile(join2(home, ".bashrc"));
1655
+ removeFromFile(join3(home, ".bashrc"));
1668
1656
  } else if (shell === "zsh") {
1669
- removeFromFile(join2(home, ".zshrc"));
1657
+ removeFromFile(join3(home, ".zshrc"));
1670
1658
  } else if (shell === "fish") {
1671
- removeFromFile(join2(home, ".config", "fish", "config.fish"));
1659
+ removeFromFile(join3(home, ".config", "fish", "config.fish"));
1672
1660
  }
1673
1661
  }
1674
1662
 
1675
1663
  // src/commands/daemon.ts
1676
- import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync3, unlinkSync } from "fs";
1664
+ import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs";
1677
1665
  import { fork } from "child_process";
1678
1666
  import { Command as Command7 } from "commander";
1679
1667
  function isDaemonRunning2() {
@@ -1692,7 +1680,7 @@ function isDaemonRunning2() {
1692
1680
  return { running: true, pid };
1693
1681
  } catch {
1694
1682
  try {
1695
- unlinkSync(pidFile);
1683
+ unlinkSync2(pidFile);
1696
1684
  } catch {
1697
1685
  }
1698
1686
  return { running: false, pid: null };
@@ -1718,15 +1706,102 @@ function startDaemonProcess() {
1718
1706
  }
1719
1707
  return null;
1720
1708
  }
1709
+ async function ensureInitialized() {
1710
+ if (existsSync8(getConfigFile())) return;
1711
+ const { getDefaultConfig: getDefaultConfig2, saveConfig: saveConfig2 } = await import("./config-P5EM5L7N.js");
1712
+ const { SkilloDatabase: SkilloDatabase2 } = await import("./database-F3BFFZKG.js");
1713
+ const { getConfigDir: getConfigDir2, getSkillsDir: getSkillsDir2 } = await import("./paths-INOKEM66.js");
1714
+ ensureDirectory(getDataDir());
1715
+ ensureDirectory(getConfigDir2());
1716
+ ensureDirectory(getSkillsDir2());
1717
+ saveConfig2(getDefaultConfig2());
1718
+ const db = new SkilloDatabase2();
1719
+ await db.initialize();
1720
+ db.close();
1721
+ logger_default.dim("Skillo initialized.");
1722
+ }
1723
+ async function ensureLoggedIn() {
1724
+ const { getApiClient: getApiClient2 } = await import("./api-client-KUQW7FSC.js");
1725
+ const client = getApiClient2();
1726
+ if (client.hasApiKey()) {
1727
+ const result = await client.authenticate();
1728
+ if (result.success) return true;
1729
+ client.clearApiKey();
1730
+ logger_default.warn("Session expired. Logging in again...");
1731
+ }
1732
+ logger_default.blank();
1733
+ logger_default.info("Login required. Opening browser...");
1734
+ logger_default.blank();
1735
+ const { hostname: hostname2 } = await import("os");
1736
+ const deviceName = `CLI on ${hostname2()}`;
1737
+ const deviceAuthResult = await client.startDeviceAuth(deviceName);
1738
+ if (!deviceAuthResult.success || !deviceAuthResult.data) {
1739
+ logger_default.error("Failed to start authentication: " + (deviceAuthResult.error || "Unknown error"));
1740
+ logger_default.dim("You can also login manually: skillo login <api-key>");
1741
+ return false;
1742
+ }
1743
+ const { code, verification_url, interval } = deviceAuthResult.data;
1744
+ logger_default.highlight(` ${verification_url}`);
1745
+ logger_default.blank();
1746
+ try {
1747
+ const { exec: execCb } = await import("child_process");
1748
+ const { promisify } = await import("util");
1749
+ const execAsync = promisify(execCb);
1750
+ const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? 'start ""' : "xdg-open";
1751
+ await execAsync(`${openCmd} "${verification_url}"`);
1752
+ logger_default.dim("Browser opened. Waiting for authorization...");
1753
+ } catch {
1754
+ logger_default.dim("Open the URL above in your browser to authorize.");
1755
+ }
1756
+ logger_default.dim("(Press Ctrl+C to cancel)");
1757
+ const maxAttempts = 120;
1758
+ for (let i = 0; i < maxAttempts; i++) {
1759
+ await new Promise((r) => setTimeout(r, interval * 1e3));
1760
+ const statusResult = await client.checkTokenStatus(code);
1761
+ if (!statusResult.success) continue;
1762
+ const status = statusResult.data?.status;
1763
+ if (status === "ready") {
1764
+ const tokenResult = await client.exchangeToken(code, deviceName);
1765
+ if (tokenResult.success && tokenResult.data) {
1766
+ client.saveApiKey(tokenResult.data.api_key);
1767
+ const authResult = await client.authenticate();
1768
+ logger_default.blank();
1769
+ logger_default.success("Login successful!");
1770
+ if (authResult.success && authResult.data) {
1771
+ logger_default.info(`Welcome, ${authResult.data.user.name}!`);
1772
+ }
1773
+ logger_default.blank();
1774
+ return true;
1775
+ }
1776
+ logger_default.error("Failed to complete login: " + (tokenResult.error || "Unknown error"));
1777
+ return false;
1778
+ }
1779
+ if (status === "expired" || status === "used" || status === "not_found") {
1780
+ logger_default.error(`Authorization ${status}. Please try again.`);
1781
+ return false;
1782
+ }
1783
+ }
1784
+ logger_default.error("Authorization timed out.");
1785
+ return false;
1786
+ }
1721
1787
  var startCommand = new Command7("start").description("Start the Skillo daemon").option("-f, --foreground", "Run in foreground (don't daemonize)").action(async (options) => {
1722
- if (!existsSync8(getConfigFile())) {
1723
- logger_default.error("Skillo not initialized.");
1724
- logger_default.dim("Run 'skillo init' first.");
1725
- process.exit(1);
1788
+ await ensureInitialized();
1789
+ if (!options.foreground) {
1790
+ const loggedIn = await ensureLoggedIn();
1791
+ if (!loggedIn) {
1792
+ process.exit(1);
1793
+ }
1726
1794
  }
1727
1795
  const { running, pid } = isDaemonRunning2();
1728
1796
  if (running) {
1729
- logger_default.warn(`Daemon is already running (PID: ${pid})`);
1797
+ logger_default.success(`Daemon is already running (PID: ${pid})`);
1798
+ const serviceStatus = getServiceStatus();
1799
+ if (!serviceStatus.daemon.installed) {
1800
+ const serviceResult = await installService({ includeTray: true });
1801
+ if (serviceResult.success) {
1802
+ logger_default.dim("Auto-start service installed.");
1803
+ }
1804
+ }
1730
1805
  return;
1731
1806
  }
1732
1807
  if (options.foreground) {
@@ -1737,14 +1812,21 @@ var startCommand = new Command7("start").description("Start the Skillo daemon").
1737
1812
  } else {
1738
1813
  logger_default.info("Starting Skillo daemon...");
1739
1814
  if (process.platform === "win32") {
1740
- logger_default.warn("Background daemon not fully supported on Windows.");
1741
- logger_default.dim("Running in foreground mode...");
1815
+ logger_default.dim("Running in foreground mode on Windows...");
1742
1816
  logger_default.blank();
1817
+ const serviceResult = await installService({ includeTray: true });
1818
+ if (serviceResult.success) {
1819
+ logger_default.dim("Auto-start service installed. Daemon will run in background on next login.");
1820
+ }
1743
1821
  await runDaemonForeground();
1744
1822
  } else {
1745
1823
  const daemonPid = startDaemonProcess();
1746
1824
  if (daemonPid) {
1747
1825
  logger_default.success(`Daemon started (PID: ${daemonPid})`);
1826
+ const serviceResult = await installService({ includeTray: true });
1827
+ if (serviceResult.success) {
1828
+ logger_default.dim("Auto-start & tray service installed. Daemon will survive reboots.");
1829
+ }
1748
1830
  logger_default.blank();
1749
1831
  logger_default.dim("Use 'skillo status' to check daemon status");
1750
1832
  logger_default.dim("Use 'skillo stop' to stop the daemon");
@@ -1761,6 +1843,10 @@ var stopCommand = new Command7("stop").description("Stop the Skillo daemon").act
1761
1843
  return;
1762
1844
  }
1763
1845
  logger_default.info(`Stopping daemon (PID: ${pid})...`);
1846
+ const serviceResult = await uninstallService();
1847
+ if (serviceResult.success) {
1848
+ logger_default.dim("Auto-start service removed.");
1849
+ }
1764
1850
  try {
1765
1851
  process.kill(pid, "SIGTERM");
1766
1852
  logger_default.success("Daemon stopped");
@@ -1777,7 +1863,7 @@ var stopCommand = new Command7("stop").description("Stop the Skillo daemon").act
1777
1863
  const pidFile = getPidFile();
1778
1864
  if (existsSync8(pidFile)) {
1779
1865
  try {
1780
- unlinkSync(pidFile);
1866
+ unlinkSync2(pidFile);
1781
1867
  } catch {
1782
1868
  }
1783
1869
  }
@@ -1808,6 +1894,53 @@ function showLogs(logFile, numLines) {
1808
1894
  const lastLines = lines.slice(-numLines);
1809
1895
  lastLines.forEach((line) => console.log(line));
1810
1896
  }
1897
+ var serviceCommand = new Command7("service").description("Manage auto-start service").addCommand(
1898
+ new Command7("install").description("Install auto-start service (LaunchAgent on macOS, systemd on Linux)").option("--no-tray", "Skip installing tray icon service").action(async (options) => {
1899
+ logger_default.info("Installing auto-start service...");
1900
+ const result = await installService({ includeTray: options.tray !== false });
1901
+ if (result.success) {
1902
+ logger_default.success("Auto-start service installed.");
1903
+ logger_default.dim("Daemon will start automatically on login and restart on crash.");
1904
+ if (options.tray !== false) {
1905
+ logger_default.dim("Tray icon will appear in GUI sessions.");
1906
+ }
1907
+ } else {
1908
+ logger_default.error(`Failed to install service: ${result.error}`);
1909
+ }
1910
+ })
1911
+ ).addCommand(
1912
+ new Command7("uninstall").description("Remove auto-start service").action(async () => {
1913
+ logger_default.info("Removing auto-start service...");
1914
+ const result = await uninstallService();
1915
+ if (result.success) {
1916
+ logger_default.success("Auto-start service removed.");
1917
+ } else {
1918
+ logger_default.error(`Failed to remove service: ${result.error}`);
1919
+ }
1920
+ })
1921
+ ).addCommand(
1922
+ new Command7("status").description("Show auto-start service status").action(async () => {
1923
+ const status = getServiceStatus();
1924
+ logger_default.blank();
1925
+ logger_default.info(`Platform: ${status.platform}`);
1926
+ logger_default.blank();
1927
+ if (status.daemon.installed) {
1928
+ logger_default.running(`Daemon service: installed${status.daemon.loaded ? " & loaded" : " (not loaded)"}`);
1929
+ } else {
1930
+ logger_default.stopped("Daemon service: not installed");
1931
+ }
1932
+ if (status.tray.installed) {
1933
+ logger_default.running(`Tray service: installed${status.tray.loaded ? " & loaded" : " (not loaded)"}`);
1934
+ } else {
1935
+ logger_default.stopped("Tray service: not installed");
1936
+ }
1937
+ logger_default.blank();
1938
+ if (!status.daemon.installed) {
1939
+ logger_default.dim("Run 'skillo service install' to enable auto-start.");
1940
+ }
1941
+ logger_default.blank();
1942
+ })
1943
+ );
1811
1944
  async function runDaemonForeground() {
1812
1945
  const pidFile = getPidFile();
1813
1946
  writeFileSync3(pidFile, String(process.pid));
@@ -1819,7 +1952,7 @@ async function runDaemonForeground() {
1819
1952
  logger_default.info("Shutting down daemon...");
1820
1953
  if (existsSync8(pidFile)) {
1821
1954
  try {
1822
- unlinkSync(pidFile);
1955
+ unlinkSync2(pidFile);
1823
1956
  } catch {
1824
1957
  }
1825
1958
  }
@@ -1828,7 +1961,7 @@ async function runDaemonForeground() {
1828
1961
  process.on("SIGINT", cleanup);
1829
1962
  process.on("SIGTERM", cleanup);
1830
1963
  const config = loadConfig();
1831
- const { getApiClient: getApiClient2 } = await import("./api-client-WO6NUCIJ.js");
1964
+ const { getApiClient: getApiClient2 } = await import("./api-client-KUQW7FSC.js");
1832
1965
  const client = getApiClient2();
1833
1966
  if (!client.hasApiKey()) {
1834
1967
  logger_default.error("Not logged in. Run 'skillo login' first.");
@@ -1871,7 +2004,7 @@ async function runDaemonForeground() {
1871
2004
 
1872
2005
  // src/commands/session.ts
1873
2006
  import { Command as Command8 } from "commander";
1874
- import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2 } from "fs";
2007
+ import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3 } from "fs";
1875
2008
  var sessionCommand = new Command8("session").description("Manage terminal sessions");
1876
2009
  sessionCommand.command("start").description("Start a new terminal session (for standalone terminals)").option("--shell <shell>", "Shell type (powershell, bash, zsh, cmd)", "powershell").action(async (options) => {
1877
2010
  try {
@@ -1960,7 +2093,7 @@ var sessionAutoCommand = new Command8("session-auto").description("Auto-detect p
1960
2093
  } catch {
1961
2094
  }
1962
2095
  try {
1963
- unlinkSync2(sessionFile);
2096
+ unlinkSync3(sessionFile);
1964
2097
  } catch {
1965
2098
  }
1966
2099
  }
@@ -2270,8 +2403,8 @@ import { Command as Command10 } from "commander";
2270
2403
  import { existsSync as existsSync11, writeFileSync as writeFileSync5 } from "fs";
2271
2404
 
2272
2405
  // src/utils/git.ts
2273
- import { execSync } from "child_process";
2274
- import { join as join4, dirname as dirname2, basename as basename2 } from "path";
2406
+ import { execSync as execSync2 } from "child_process";
2407
+ import { join as join5, dirname as dirname2, basename as basename2 } from "path";
2275
2408
  function getGitInfo(projectPath) {
2276
2409
  const result = {
2277
2410
  isGitRepo: false,
@@ -2282,7 +2415,7 @@ function getGitInfo(projectPath) {
2282
2415
  projectName: null
2283
2416
  };
2284
2417
  try {
2285
- const rootPath = execSync("git rev-parse --show-toplevel", {
2418
+ const rootPath = execSync2("git rev-parse --show-toplevel", {
2286
2419
  cwd: projectPath,
2287
2420
  encoding: "utf-8",
2288
2421
  stdio: ["pipe", "pipe", "pipe"]
@@ -2292,7 +2425,7 @@ function getGitInfo(projectPath) {
2292
2425
  result.rootPath = rootPath;
2293
2426
  result.projectName = basename2(rootPath);
2294
2427
  try {
2295
- result.branch = execSync("git rev-parse --abbrev-ref HEAD", {
2428
+ result.branch = execSync2("git rev-parse --abbrev-ref HEAD", {
2296
2429
  cwd: projectPath,
2297
2430
  encoding: "utf-8",
2298
2431
  stdio: ["pipe", "pipe", "pipe"]
@@ -2300,20 +2433,20 @@ function getGitInfo(projectPath) {
2300
2433
  } catch {
2301
2434
  }
2302
2435
  try {
2303
- result.remote = execSync("git remote get-url origin", {
2436
+ result.remote = execSync2("git remote get-url origin", {
2304
2437
  cwd: projectPath,
2305
2438
  encoding: "utf-8",
2306
2439
  stdio: ["pipe", "pipe", "pipe"]
2307
2440
  }).trim();
2308
2441
  } catch {
2309
2442
  try {
2310
- const remotes = execSync("git remote", {
2443
+ const remotes = execSync2("git remote", {
2311
2444
  cwd: projectPath,
2312
2445
  encoding: "utf-8",
2313
2446
  stdio: ["pipe", "pipe", "pipe"]
2314
2447
  }).trim().split("\n");
2315
2448
  if (remotes.length > 0 && remotes[0]) {
2316
- result.remote = execSync(`git remote get-url ${remotes[0]}`, {
2449
+ result.remote = execSync2(`git remote get-url ${remotes[0]}`, {
2317
2450
  cwd: projectPath,
2318
2451
  encoding: "utf-8",
2319
2452
  stdio: ["pipe", "pipe", "pipe"]
@@ -2538,11 +2671,11 @@ async function openBrowser(url) {
2538
2671
  const { exec } = await import("child_process");
2539
2672
  const { promisify } = await import("util");
2540
2673
  const execAsync = promisify(exec);
2541
- const platform = process.platform;
2674
+ const platform2 = process.platform;
2542
2675
  let command;
2543
- if (platform === "darwin") {
2676
+ if (platform2 === "darwin") {
2544
2677
  command = `open "${url}"`;
2545
- } else if (platform === "win32") {
2678
+ } else if (platform2 === "win32") {
2546
2679
  command = `start "" "${url}"`;
2547
2680
  } else {
2548
2681
  command = `xdg-open "${url}"`;
@@ -2556,6 +2689,22 @@ async function openBrowser(url) {
2556
2689
  function sleep(ms) {
2557
2690
  return new Promise((resolve2) => setTimeout(resolve2, ms));
2558
2691
  }
2692
+ async function autoStartDaemon() {
2693
+ try {
2694
+ const { running } = isDaemonRunning2();
2695
+ if (!running) {
2696
+ const pid = startDaemonProcess();
2697
+ if (pid) {
2698
+ logger_default.dim(`Background daemon started (PID: ${pid}).`);
2699
+ }
2700
+ }
2701
+ const result = await installService();
2702
+ if (result.success) {
2703
+ logger_default.dim("Auto-start installed. Daemon will survive reboots.");
2704
+ }
2705
+ } catch {
2706
+ }
2707
+ }
2559
2708
  var loginCommand = new Command11("login").description("Login to Skillo platform").argument("[api-key]", "Your Skillo API key (optional, will use browser auth if not provided)").option("-u, --url <url>", "Platform URL (default: https://www.skillo.one)").option("-n, --no-browser", "Don't open browser automatically").action(async (apiKey, options) => {
2560
2709
  const client = getApiClient();
2561
2710
  logger_default.blank();
@@ -2578,6 +2727,7 @@ var loginCommand = new Command11("login").description("Login to Skillo platform"
2578
2727
  logger_default.info(`Welcome, ${result.data.user.name}!`);
2579
2728
  logger_default.dim(`Email: ${result.data.user.email}`);
2580
2729
  logger_default.blank();
2730
+ await autoStartDaemon();
2581
2731
  logger_default.dim("Your patterns and skills will now sync with the Skillo platform.");
2582
2732
  } else {
2583
2733
  client.clearApiKey();
@@ -2650,6 +2800,7 @@ var loginCommand = new Command11("login").description("Login to Skillo platform"
2650
2800
  logger_default.dim(`Email: ${authResult.data.user.email}`);
2651
2801
  }
2652
2802
  logger_default.blank();
2803
+ await autoStartDaemon();
2653
2804
  logger_default.dim("Your patterns and skills will now sync with the Skillo platform.");
2654
2805
  return;
2655
2806
  } else {
@@ -2717,8 +2868,8 @@ var whoamiCommand = new Command11("whoami").description("Show current login stat
2717
2868
  });
2718
2869
 
2719
2870
  // src/commands/sync.ts
2720
- import { existsSync as existsSync12, writeFileSync as writeFileSync6, mkdirSync } from "fs";
2721
- import { join as join5 } from "path";
2871
+ import { existsSync as existsSync12, writeFileSync as writeFileSync6, mkdirSync as mkdirSync2 } from "fs";
2872
+ import { join as join6 } from "path";
2722
2873
  import { Command as Command12 } from "commander";
2723
2874
  var syncCommand = new Command12("sync").description("Sync data with Skillo platform").option("--push", "Push local data to platform").option("--pull", "Pull data from platform").option("--patterns", "Sync patterns only").option("--skills", "Sync skills only").option("--commands", "Sync commands only").action(
2724
2875
  async (options) => {
@@ -2836,10 +2987,10 @@ async function pullSkills(skills) {
2836
2987
  let downloaded = 0;
2837
2988
  for (const skill of skills) {
2838
2989
  if (!skill.content) continue;
2839
- const skillDir = join5(skillsDir, skill.slug);
2840
- const skillFile = join5(skillDir, "SKILL.md");
2990
+ const skillDir = join6(skillsDir, skill.slug);
2991
+ const skillFile = join6(skillDir, "SKILL.md");
2841
2992
  if (!existsSync12(skillDir)) {
2842
- mkdirSync(skillDir, { recursive: true });
2993
+ mkdirSync2(skillDir, { recursive: true });
2843
2994
  }
2844
2995
  writeFileSync6(skillFile, skill.content, "utf-8");
2845
2996
  downloaded++;
@@ -2848,9 +2999,26 @@ async function pullSkills(skills) {
2848
2999
  logger_default.success(`Downloaded ${downloaded} skills to ~/.claude/skills/`);
2849
3000
  }
2850
3001
 
3002
+ // src/commands/tray.ts
3003
+ import { Command as Command13 } from "commander";
3004
+ var trayCommand = new Command13("tray").description("Start the system tray icon").action(async () => {
3005
+ try {
3006
+ const { startTray } = await import("./tray-62I5KIFE.js");
3007
+ await startTray();
3008
+ } catch (error) {
3009
+ if (error instanceof Error && error.message.includes("Cannot find module")) {
3010
+ logger_default.error("System tray not available. The systray2 package may not be installed.");
3011
+ logger_default.dim("Try: npm install -g skillo");
3012
+ } else {
3013
+ logger_default.error(`Tray error: ${error instanceof Error ? error.message : error}`);
3014
+ }
3015
+ process.exit(1);
3016
+ }
3017
+ });
3018
+
2851
3019
  // src/cli.ts
2852
- var VERSION = "0.1.0";
2853
- var program = new Command13();
3020
+ var VERSION = "0.2.2";
3021
+ var program = new Command14();
2854
3022
  program.name("skillo").description(
2855
3023
  `Skillo - Learn workflows by observation, not explanation.
2856
3024
 
@@ -2884,6 +3052,8 @@ program.addCommand(claudeCommand);
2884
3052
  program.addCommand(projectCommand);
2885
3053
  program.addCommand(trackCommand);
2886
3054
  program.addCommand(untrackCommand);
3055
+ program.addCommand(serviceCommand);
3056
+ program.addCommand(trayCommand);
2887
3057
  program.command("version").description("Show version information").action(() => {
2888
3058
  console.log(`Skillo v${VERSION}`);
2889
3059
  console.log("Autonomous workflow learning & skill generation system");