skillo 0.2.5 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +198 -198
  2. package/dist/api-client-BF6GDR7Q.js +12 -0
  3. package/dist/{chunk-WJKZWKER.js → chunk-63FVALWX.js} +1 -1
  4. package/dist/chunk-63FVALWX.js.map +1 -0
  5. package/dist/chunk-6GOJPFZ7.js +113 -0
  6. package/dist/chunk-6GOJPFZ7.js.map +1 -0
  7. package/dist/chunk-6UGTWBUW.js +89 -0
  8. package/dist/chunk-6UGTWBUW.js.map +1 -0
  9. package/dist/{chunk-2CVEPT6U.js → chunk-73NUWYUO.js} +2 -2
  10. package/dist/chunk-73NUWYUO.js.map +1 -0
  11. package/dist/chunk-QIV4VIXA.js +221 -0
  12. package/dist/chunk-QIV4VIXA.js.map +1 -0
  13. package/dist/{chunk-CPL3P2OF.js → chunk-QUXHHRRK.js} +2 -2
  14. package/dist/chunk-QUXHHRRK.js.map +1 -0
  15. package/dist/{chunk-ODOZM4QV.js → chunk-RQ2SC5HW.js} +72 -10
  16. package/dist/chunk-RQ2SC5HW.js.map +1 -0
  17. package/dist/chunk-U53QWUOR.js +727 -0
  18. package/dist/chunk-U53QWUOR.js.map +1 -0
  19. package/dist/chunk-VAQ73XPE.js +68 -0
  20. package/dist/chunk-VAQ73XPE.js.map +1 -0
  21. package/dist/chunk-XLJGCOVT.js +975 -0
  22. package/dist/chunk-XLJGCOVT.js.map +1 -0
  23. package/dist/{claude-watcher-N6GN6WHJ.js → claude-watcher-WKGBJYKN.js} +65 -3
  24. package/dist/claude-watcher-WKGBJYKN.js.map +1 -0
  25. package/dist/cli.js +499 -1807
  26. package/dist/cli.js.map +1 -1
  27. package/dist/{config-P5EM5L7N.js → config-ZOKAP2LJ.js} +3 -3
  28. package/dist/daemon-6DTCMOJB.js +28 -0
  29. package/dist/daemon-runner.js +352 -65
  30. package/dist/daemon-runner.js.map +1 -1
  31. package/dist/database-KQY5OSCS.js +9 -0
  32. package/dist/git-OGUSYBJS.js +16 -0
  33. package/dist/git-OGUSYBJS.js.map +1 -0
  34. package/dist/git-OUAHIOY2.js +110 -0
  35. package/dist/git-OUAHIOY2.js.map +1 -0
  36. package/dist/index.js.map +1 -1
  37. package/dist/{paths-INOKEM66.js → paths-MPOZBOKE.js} +2 -2
  38. package/dist/paths-MPOZBOKE.js.map +1 -0
  39. package/dist/project-OFU2W6MH.js +19 -0
  40. package/dist/project-OFU2W6MH.js.map +1 -0
  41. package/dist/shell-NZABRJLA.js +16 -0
  42. package/dist/shell-NZABRJLA.js.map +1 -0
  43. package/dist/skill-installer-F67OAOQN.js +121 -0
  44. package/dist/skill-installer-F67OAOQN.js.map +1 -0
  45. package/dist/{skill-usage-detector-EO26MRYV.js → skill-usage-detector-MSW5VWQZ.js} +2 -2
  46. package/dist/skill-usage-detector-MSW5VWQZ.js.map +1 -0
  47. package/dist/{tray-YOL4R2RH.js → tray-WKFGUUTO.js} +117 -179
  48. package/dist/tray-WKFGUUTO.js.map +1 -0
  49. package/package.json +62 -63
  50. package/scripts/postinstall.mjs +415 -364
  51. package/scripts/tray-helper-darwin +0 -0
  52. package/scripts/tray-helper-darwin.swift +180 -180
  53. package/scripts/tray-helper-linux.py +322 -0
  54. package/scripts/tray-helper-windows.cs +322 -0
  55. package/dist/api-client-KUQW7FSC.js +0 -12
  56. package/dist/chunk-2CVEPT6U.js.map +0 -1
  57. package/dist/chunk-CPL3P2OF.js.map +0 -1
  58. package/dist/chunk-ODOZM4QV.js.map +0 -1
  59. package/dist/chunk-WJKZWKER.js.map +0 -1
  60. package/dist/claude-watcher-N6GN6WHJ.js.map +0 -1
  61. package/dist/database-F3BFFZKG.js +0 -9
  62. package/dist/skill-usage-detector-EO26MRYV.js.map +0 -1
  63. package/dist/tray-YOL4R2RH.js.map +0 -1
  64. /package/dist/{api-client-KUQW7FSC.js.map → api-client-BF6GDR7Q.js.map} +0 -0
  65. /package/dist/{config-P5EM5L7N.js.map → config-ZOKAP2LJ.js.map} +0 -0
  66. /package/dist/{database-F3BFFZKG.js.map → daemon-6DTCMOJB.js.map} +0 -0
  67. /package/dist/{paths-INOKEM66.js.map → database-KQY5OSCS.js.map} +0 -0
package/dist/cli.js CHANGED
@@ -1,17 +1,42 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ recordCommand,
4
+ setupShellCommand,
5
+ shellCommand
6
+ } from "./chunk-U53QWUOR.js";
7
+ import {
8
+ projectCommand,
9
+ trackCommand,
10
+ untrackCommand
11
+ } from "./chunk-QIV4VIXA.js";
12
+ import {
13
+ getServiceStatus,
14
+ installService,
15
+ isDaemonRunning,
16
+ logsCommand,
17
+ serviceCommand,
18
+ startCommand,
19
+ startDaemonProcess,
20
+ stopCommand
21
+ } from "./chunk-XLJGCOVT.js";
22
+ import {
23
+ logger_default
24
+ } from "./chunk-VAQ73XPE.js";
25
+ import "./chunk-6UGTWBUW.js";
2
26
  import {
3
27
  SkilloDatabase
4
- } from "./chunk-2CVEPT6U.js";
28
+ } from "./chunk-73NUWYUO.js";
5
29
  import {
6
30
  getApiClient
7
- } from "./chunk-ODOZM4QV.js";
31
+ } from "./chunk-RQ2SC5HW.js";
8
32
  import {
9
33
  getConfigValue,
10
34
  getDefaultConfig,
11
35
  loadConfig,
12
36
  saveConfig,
13
37
  setConfigValue
14
- } from "./chunk-CPL3P2OF.js";
38
+ } from "./chunk-QUXHHRRK.js";
39
+ import "./chunk-6GOJPFZ7.js";
15
40
  import {
16
41
  ensureDirectory,
17
42
  getActiveSessionsDir,
@@ -21,564 +46,265 @@ import {
21
46
  getDbPath,
22
47
  getLogFile,
23
48
  getPidFile,
24
- getShellIntegrationDir,
25
49
  getSkillsDir,
26
50
  getTrackedProjectsCacheFile
27
- } from "./chunk-WJKZWKER.js";
51
+ } from "./chunk-63FVALWX.js";
28
52
 
29
53
  // src/cli.ts
30
- import { Command as Command14 } from "commander";
54
+ import { Command as Command12 } from "commander";
31
55
 
32
56
  // src/commands/init.ts
33
- import { existsSync } from "fs";
57
+ import { existsSync, readFileSync } from "fs";
58
+ import { homedir } from "os";
59
+ import { join } from "path";
60
+ import { createInterface } from "readline";
61
+ import { execSync } from "child_process";
34
62
  import { Command } from "commander";
35
-
36
- // src/utils/logger.ts
37
- import chalk from "chalk";
38
- var logger = {
39
- info: (message) => {
40
- console.log(chalk.cyan(message));
41
- },
42
- success: (message) => {
43
- console.log(chalk.green(`[+] ${message}`));
44
- },
45
- warn: (message) => {
46
- console.log(chalk.yellow(`[!] ${message}`));
47
- },
48
- error: (message) => {
49
- console.log(chalk.red(`[x] ${message}`));
50
- },
51
- dim: (message) => {
52
- console.log(chalk.dim(message));
53
- },
54
- bold: (message) => {
55
- console.log(chalk.bold(message));
56
- },
57
- highlight: (message) => {
58
- console.log(chalk.bold.cyan(message));
59
- },
60
- // Status indicators
61
- running: (message) => {
62
- console.log(chalk.green(`(*) ${message}`));
63
- },
64
- stopped: (message) => {
65
- console.log(chalk.dim(`(-) ${message}`));
66
- },
67
- // For lists and tables
68
- item: (label, value) => {
69
- console.log(` ${chalk.dim(label)}: ${chalk.white(value)}`);
70
- },
71
- // Blank line
72
- blank: () => {
73
- console.log();
74
- },
75
- // Box/panel output
76
- box: (title, content) => {
77
- const lines = content.split("\n");
78
- const maxLen = Math.max(title.length, ...lines.map((l) => l.length));
79
- const border = "\u2500".repeat(maxLen + 2);
80
- console.log(chalk.dim(`\u250C${border}\u2510`));
81
- console.log(chalk.dim("\u2502 ") + chalk.bold(title.padEnd(maxLen)) + chalk.dim(" \u2502"));
82
- console.log(chalk.dim(`\u251C${border}\u2524`));
83
- lines.forEach((line) => {
84
- console.log(chalk.dim("\u2502 ") + line.padEnd(maxLen) + chalk.dim(" \u2502"));
63
+ function statusIcon(status) {
64
+ if (status === "success") return "\u2713";
65
+ if (status === "failure") return "\u2717";
66
+ return "\u2298";
67
+ }
68
+ function askQuestion(prompt) {
69
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
70
+ return new Promise((resolve, reject) => {
71
+ rl.question(prompt, (answer) => {
72
+ rl.close();
73
+ resolve(answer.trim());
85
74
  });
86
- console.log(chalk.dim(`\u2514${border}\u2518`));
87
- },
88
- // Table output
89
- table: (rows) => {
90
- const maxLabel = Math.max(...rows.map(([label]) => label.length));
91
- rows.forEach(([label, value]) => {
92
- console.log(` ${chalk.dim(label.padEnd(maxLabel))} ${chalk.white(value)}`);
75
+ rl.on("error", (err) => {
76
+ rl.close();
77
+ reject(err);
93
78
  });
79
+ });
80
+ }
81
+ function isGitRepo() {
82
+ try {
83
+ execSync("git rev-parse --is-inside-work-tree", { stdio: "pipe" });
84
+ return true;
85
+ } catch {
86
+ return false;
94
87
  }
95
- };
96
- var logger_default = logger;
97
-
98
- // src/commands/init.ts
99
- var initCommand = new Command("init").description("Initialize Skillo configuration and directories").option("-f, --force", "Overwrite existing configuration").action(async (options) => {
100
- logger_default.blank();
101
- logger_default.bold("Initializing Skillo...");
102
- logger_default.blank();
103
- const dataDir = getDataDir();
104
- const configDir = getConfigDir();
105
- const skillsDir = getSkillsDir();
106
- const configFile = getConfigFile();
107
- if (existsSync(configFile) && !options.force) {
108
- logger_default.warn("Skillo is already initialized.");
109
- logger_default.dim("Use --force to reinitialize and overwrite configuration.");
110
- return;
88
+ }
89
+ function detectShell() {
90
+ if (process.platform === "win32") return "powershell";
91
+ const currentShell = process.env.SHELL || "";
92
+ if (currentShell.includes("zsh")) return "zsh";
93
+ if (currentShell.includes("fish")) return "fish";
94
+ return "bash";
95
+ }
96
+ function isShellIntegrationInstalled() {
97
+ const home = homedir();
98
+ const shell = detectShell();
99
+ const filesToCheck = [];
100
+ if (shell === "bash") {
101
+ filesToCheck.push({ path: join(home, ".bashrc"), marker: "Skillo CLI Integration" });
102
+ } else if (shell === "zsh") {
103
+ filesToCheck.push({ path: join(home, ".zshrc"), marker: "Skillo CLI Integration" });
104
+ } else if (shell === "powershell") {
105
+ filesToCheck.push({
106
+ path: join(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1"),
107
+ marker: "Skillo CLI Integration"
108
+ });
109
+ } else if (shell === "fish") {
110
+ filesToCheck.push({
111
+ path: join(home, ".config", "fish", "config.fish"),
112
+ marker: "Skillo CLI Integration"
113
+ });
111
114
  }
112
- const directories = [
113
- { path: dataDir, name: "~/.skillo/" },
114
- { path: configDir, name: "~/.config/skillo/" },
115
- { path: skillsDir, name: "~/.claude/skills/" }
116
- ];
117
- for (const { path: path2, name } of directories) {
118
- if (ensureDirectory(path2)) {
119
- logger_default.success(`Created ${name}`);
120
- } else {
121
- logger_default.dim(` [-] ${name} already exists`);
115
+ for (const { path: path3, marker } of filesToCheck) {
116
+ try {
117
+ if (existsSync(path3) && readFileSync(path3, "utf-8").includes(marker)) {
118
+ return true;
119
+ }
120
+ } catch {
122
121
  }
123
122
  }
124
- const config = getDefaultConfig();
125
- saveConfig(config);
126
- logger_default.success("Created default configuration");
127
- const db = new SkilloDatabase();
128
- await db.initialize();
129
- db.close();
130
- logger_default.success("Initialized database");
131
- logger_default.blank();
132
- logger_default.bold("Skillo initialized successfully!");
133
- logger_default.blank();
134
- logger_default.box(
135
- "Next steps:",
136
- `1. Start the daemon:
137
- skillo start
138
-
139
- 2. Use the tracked shell:
140
- skillo shell
141
-
142
- 3. Work normally - Skillo will notify you of patterns!`
143
- );
144
- logger_default.blank();
145
- });
146
-
147
- // src/commands/status.ts
148
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
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");
123
+ return false;
186
124
  }
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 {
125
+ function isDaemonActuallyRunning() {
126
+ const pidFile = getPidFile();
127
+ if (!existsSync(pidFile)) {
128
+ return { running: false, pid: null };
194
129
  }
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;
130
+ try {
131
+ const pidStr = readFileSync(pidFile, "utf-8").trim();
132
+ const pid = parseInt(pidStr, 10);
133
+ if (isNaN(pid)) {
134
+ return { running: false, pid: null };
203
135
  }
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;
136
+ try {
137
+ process.kill(pid, 0);
138
+ return { running: true, pid };
139
+ } catch {
140
+ return { running: false, pid: null };
211
141
  }
142
+ } catch {
143
+ return { running: false, pid: null };
212
144
  }
213
- return "skillo";
214
145
  }
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
- }
146
+ async function stepAuth() {
147
+ try {
148
+ const { getApiClient: getApiClient2 } = await import("./api-client-BF6GDR7Q.js");
149
+ const client = getApiClient2();
150
+ const isAuthUser = (obj) => typeof obj === "object" && obj !== null && "id" in obj && "email" in obj && "name" in obj;
151
+ const getUserFromAuth = (result) => {
152
+ const data = result.data;
153
+ const candidate = data?.user || result.user;
154
+ return isAuthUser(candidate) ? candidate : void 0;
155
+ };
156
+ if (client.hasApiKey()) {
157
+ const authResult = await client.authenticate();
158
+ if (authResult.success) {
159
+ const user = getUserFromAuth(authResult);
160
+ if (user) {
161
+ logger_default.success(`Already logged in as ${user.name}`);
162
+ return { status: "success", message: `logged in as ${user.email}`, email: user.email };
242
163
  }
243
- } catch {
244
164
  }
245
165
  }
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 {
166
+ const { ensureLoggedIn } = await import("./daemon-6DTCMOJB.js");
167
+ const loggedIn = await ensureLoggedIn();
168
+ if (loggedIn) {
169
+ const authResult = await client.authenticate();
170
+ const user = getUserFromAuth(authResult);
171
+ const email = user?.email || "unknown";
172
+ return { status: "success", message: `logged in as ${email}`, email };
173
+ }
174
+ return { status: "failure", message: "login failed" };
175
+ } catch (error) {
176
+ const msg = error instanceof Error ? error.message : "unknown error";
177
+ logger_default.error(`Authentication error: ${msg}`);
178
+ return { status: "failure", message: msg };
385
179
  }
386
180
  }
387
- function winRegExists(valueName) {
181
+ async function stepShellIntegration() {
388
182
  try {
389
- execSync(`reg query "${WIN_REG_KEY}" /v "${valueName}"`, { stdio: "ignore" });
390
- return true;
391
- } catch {
392
- return false;
183
+ if (isShellIntegrationInstalled()) {
184
+ const shell2 = detectShell();
185
+ logger_default.success(`Shell integration already configured (${shell2})`);
186
+ return { status: "success", message: `${shell2} hooks installed` };
187
+ }
188
+ const { setupShellCommand: setupShellCommand2 } = await import("./shell-NZABRJLA.js");
189
+ await setupShellCommand2.parseAsync([], { from: "user" });
190
+ const shell = detectShell();
191
+ return { status: "success", message: `${shell} hooks installed` };
192
+ } catch (error) {
193
+ const msg = error instanceof Error ? error.message : "unknown error";
194
+ logger_default.error(`Shell integration error: ${msg}`);
195
+ return { status: "failure", message: msg };
393
196
  }
394
197
  }
395
- async function installService(options) {
396
- const os2 = platform();
397
- const skilloBin = findSkilloBin();
398
- const envPath = buildPath();
399
- const includeTray = options?.includeTray ?? true;
198
+ async function stepTrackProject(loggedIn) {
400
199
  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
- }
200
+ if (!loggedIn) {
201
+ logger_default.dim(" Skipping project tracking (login required)");
202
+ return { status: "skipped", message: "login required" };
203
+ }
204
+ if (!isGitRepo()) {
205
+ logger_default.dim(" Not a git repository, skipping project tracking");
206
+ return { status: "skipped", message: "not a git repository" };
207
+ }
208
+ const { getApiClient: getApiClient2 } = await import("./api-client-BF6GDR7Q.js");
209
+ const client = getApiClient2();
210
+ const trackStatus = await client.isProjectTracked(process.cwd());
211
+ if (trackStatus.tracked) {
212
+ logger_default.success(`Project already tracked: ${trackStatus.project?.name || process.cwd()}`);
213
+ return { status: "success", message: `${process.cwd()} tracked` };
214
+ }
215
+ const answer = await askQuestion(" Track this project? (Y/n) ");
216
+ if (answer.toLowerCase() === "n") {
217
+ return { status: "skipped", message: "user declined" };
218
+ }
219
+ const { trackCommand: trackCommand2 } = await import("./project-OFU2W6MH.js");
220
+ await trackCommand2.parseAsync([], { from: "user" });
221
+ return { status: "success", message: `${process.cwd()} tracked` };
451
222
  } catch (error) {
452
- return { success: false, error: error instanceof Error ? error.message : String(error) };
223
+ const msg = error instanceof Error ? error.message : "unknown error";
224
+ logger_default.error(`Project tracking error: ${msg}`);
225
+ return { status: "failure", message: msg };
453
226
  }
454
227
  }
455
- async function uninstallService() {
456
- const os2 = platform();
228
+ async function stepDaemon() {
457
229
  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
- }
230
+ const { running, pid } = isDaemonActuallyRunning();
231
+ if (running && pid) {
232
+ logger_default.success(`Daemon already running (PID: ${pid})`);
233
+ return { status: "success", message: `running (PID ${pid})` };
234
+ }
235
+ const { startDaemonProcess: startDaemonProcess2 } = await import("./daemon-6DTCMOJB.js");
236
+ const newPid = startDaemonProcess2();
237
+ if (newPid) {
238
+ logger_default.success(`Daemon started (PID: ${newPid})`);
239
+ return { status: "success", message: `running (PID ${newPid})` };
240
+ }
241
+ return { status: "failure", message: "failed to start daemon" };
520
242
  } catch (error) {
521
- return { success: false, error: error instanceof Error ? error.message : String(error) };
243
+ const msg = error instanceof Error ? error.message : "unknown error";
244
+ logger_default.error(`Daemon error: ${msg}`);
245
+ return { status: "failure", message: msg };
522
246
  }
523
247
  }
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 {
248
+ var initCommand = new Command("init").description("Initialize Skillo configuration and interactive onboarding").option("-f, --force", "Overwrite existing configuration").action(async (options) => {
249
+ logger_default.blank();
250
+ logger_default.bold("Initializing Skillo...");
251
+ logger_default.blank();
252
+ const dataDir = getDataDir();
253
+ const configDir = getConfigDir();
254
+ const skillsDir = getSkillsDir();
255
+ const configFile = getConfigFile();
256
+ const directories = [
257
+ { path: dataDir, name: "~/.skillo/" },
258
+ { path: configDir, name: "~/.config/skillo/" },
259
+ { path: skillsDir, name: "~/.claude/skills/" }
260
+ ];
261
+ for (const { path: path3, name } of directories) {
262
+ if (ensureDirectory(path3)) {
263
+ logger_default.success(`Created ${name}`);
264
+ } else {
265
+ logger_default.dim(` [-] ${name} already exists`);
556
266
  }
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
267
  }
571
- return {
572
- platform: "unsupported",
573
- daemon: { installed: false, loaded: false },
574
- tray: { installed: false, loaded: false }
575
- };
576
- }
268
+ if (!existsSync(configFile) || options.force) {
269
+ const config = getDefaultConfig();
270
+ saveConfig(config);
271
+ logger_default.success("Created default configuration");
272
+ }
273
+ const db = new SkilloDatabase();
274
+ await db.initialize();
275
+ db.close();
276
+ logger_default.success("Initialized database");
277
+ logger_default.blank();
278
+ logger_default.bold("Starting onboarding...");
279
+ logger_default.blank();
280
+ logger_default.info("Step 1/4: Authentication");
281
+ const authResult = await stepAuth();
282
+ const loggedIn = authResult.status === "success";
283
+ logger_default.blank();
284
+ logger_default.info("Step 2/4: Shell Integration");
285
+ const shellResult = await stepShellIntegration();
286
+ logger_default.blank();
287
+ logger_default.info("Step 3/4: Project Tracking");
288
+ const trackResult = await stepTrackProject(loggedIn);
289
+ logger_default.blank();
290
+ logger_default.info("Step 4/4: Daemon");
291
+ const daemonResult = await stepDaemon();
292
+ logger_default.blank();
293
+ const pad = (s, len) => s.padEnd(len);
294
+ logger_default.bold(" Setup Complete!");
295
+ logger_default.info(` ${statusIcon(authResult.status)} ${pad("Authentication", 20)} \u2014 ${authResult.message}`);
296
+ logger_default.info(` ${statusIcon(shellResult.status)} ${pad("Shell Integration", 20)} \u2014 ${shellResult.message}`);
297
+ logger_default.info(` ${statusIcon(trackResult.status)} ${pad("Project Tracking", 20)} \u2014 ${trackResult.message}`);
298
+ logger_default.info(` ${statusIcon(daemonResult.status)} ${pad("Daemon", 20)} \u2014 ${daemonResult.message}`);
299
+ logger_default.blank();
300
+ });
577
301
 
578
302
  // src/commands/status.ts
579
- function isDaemonRunning() {
303
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
304
+ import { Command as Command2 } from "commander";
305
+ function isDaemonRunning2() {
580
306
  const pidFile = getPidFile();
581
- if (!existsSync3(pidFile)) {
307
+ if (!existsSync2(pidFile)) {
582
308
  return { running: false, pid: null };
583
309
  }
584
310
  try {
@@ -599,7 +325,7 @@ function isDaemonRunning() {
599
325
  }
600
326
  var statusCommand = new Command2("status").description("Show daemon status and statistics").action(async () => {
601
327
  logger_default.blank();
602
- const { running, pid } = isDaemonRunning();
328
+ const { running, pid } = isDaemonRunning2();
603
329
  if (running) {
604
330
  logger_default.running(`Daemon is running (PID: ${pid})`);
605
331
  } else {
@@ -617,7 +343,7 @@ var statusCommand = new Command2("status").description("Show daemon status and s
617
343
  if (!running) return;
618
344
  logger_default.blank();
619
345
  const dbPath = getDbPath();
620
- if (!existsSync3(dbPath)) {
346
+ if (!existsSync2(dbPath)) {
621
347
  logger_default.warn("Database not found. Run 'skillo init' first.");
622
348
  return;
623
349
  }
@@ -634,7 +360,7 @@ var statusCommand = new Command2("status").description("Show daemon status and s
634
360
  ]);
635
361
  logger_default.blank();
636
362
  const logFile = getLogFile();
637
- if (existsSync3(logFile)) {
363
+ if (existsSync2(logFile)) {
638
364
  logger_default.dim("Recent log entries:");
639
365
  try {
640
366
  const content = readFileSync2(logFile, "utf-8");
@@ -649,7 +375,7 @@ var statusCommand = new Command2("status").description("Show daemon status and s
649
375
  });
650
376
 
651
377
  // src/commands/config.ts
652
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
378
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
653
379
  import { Command as Command3 } from "commander";
654
380
  import YAML from "yaml";
655
381
  var configCommand = new Command3("config").description(
@@ -657,7 +383,7 @@ var configCommand = new Command3("config").description(
657
383
  );
658
384
  configCommand.command("show").description("Show current configuration").action(async () => {
659
385
  const configFile = getConfigFile();
660
- if (!existsSync4(configFile)) {
386
+ if (!existsSync3(configFile)) {
661
387
  logger_default.error("Configuration not found.");
662
388
  logger_default.dim("Run 'skillo init' first.");
663
389
  process.exit(1);
@@ -670,7 +396,7 @@ configCommand.command("show").description("Show current configuration").action(a
670
396
  });
671
397
  configCommand.command("get <key>").description("Get a configuration value (use dot notation, e.g., patternDetection.minCount)").action(async (key) => {
672
398
  const configFile = getConfigFile();
673
- if (!existsSync4(configFile)) {
399
+ if (!existsSync3(configFile)) {
674
400
  logger_default.error("Configuration not found.");
675
401
  process.exit(1);
676
402
  }
@@ -688,7 +414,7 @@ configCommand.command("get <key>").description("Get a configuration value (use d
688
414
  });
689
415
  configCommand.command("set <key> <value>").description("Set a configuration value").action(async (key, value) => {
690
416
  const configFile = getConfigFile();
691
- if (!existsSync4(configFile)) {
417
+ if (!existsSync3(configFile)) {
692
418
  logger_default.error("Configuration not found.");
693
419
  process.exit(1);
694
420
  }
@@ -730,15 +456,15 @@ configCommand.command("path").description("Show configuration file path").action
730
456
  });
731
457
 
732
458
  // src/commands/patterns.ts
733
- import { existsSync as existsSync5 } from "fs";
459
+ import { existsSync as existsSync4 } from "fs";
734
460
  import { Command as Command4 } from "commander";
735
- import chalk2 from "chalk";
461
+ import chalk from "chalk";
736
462
  var patternsCommand = new Command4("patterns").description(
737
463
  "Manage detected workflow patterns"
738
464
  );
739
465
  patternsCommand.command("list").description("List detected patterns").option("-t, --type <type>", "Filter by type (terminal, conversation)").option("-s, --status <status>", "Filter by status (active, converted, ignored)").option("-n, --limit <number>", "Number of patterns to show", "20").action(async (options) => {
740
466
  const dbPath = getDbPath();
741
- if (!existsSync5(dbPath)) {
467
+ if (!existsSync4(dbPath)) {
742
468
  logger_default.error("Database not found. Run 'skillo init' first.");
743
469
  process.exit(1);
744
470
  }
@@ -763,9 +489,9 @@ patternsCommand.command("list").description("List detected patterns").option("-t
763
489
  logger_default.blank();
764
490
  patterns.forEach((pattern, index) => {
765
491
  const shortId = pattern.id.slice(0, 8);
766
- const statusColor = pattern.status === "active" ? chalk2.green : pattern.status === "converted" ? chalk2.blue : chalk2.dim;
492
+ const statusColor = pattern.status === "active" ? chalk.green : pattern.status === "converted" ? chalk.blue : chalk.dim;
767
493
  console.log(
768
- ` ${chalk2.cyan(shortId)} ${chalk2.white(pattern.description)} ${chalk2.dim(`x${pattern.count}`)} ${statusColor(pattern.status)}`
494
+ ` ${chalk.cyan(shortId)} ${chalk.white(pattern.description)} ${chalk.dim(`x${pattern.count}`)} ${statusColor(pattern.status)}`
769
495
  );
770
496
  });
771
497
  logger_default.blank();
@@ -775,7 +501,7 @@ patternsCommand.command("list").description("List detected patterns").option("-t
775
501
  });
776
502
  patternsCommand.command("show <id>").description("Show pattern details").action(async (id) => {
777
503
  const dbPath = getDbPath();
778
- if (!existsSync5(dbPath)) {
504
+ if (!existsSync4(dbPath)) {
779
505
  logger_default.error("Database not found. Run 'skillo init' first.");
780
506
  process.exit(1);
781
507
  }
@@ -802,7 +528,7 @@ patternsCommand.command("show <id>").description("Show pattern details").action(
802
528
  if (pattern.data.commands && Array.isArray(pattern.data.commands)) {
803
529
  logger_default.bold("Commands:");
804
530
  pattern.data.commands.forEach((cmd, i) => {
805
- console.log(` ${chalk2.dim(`${i + 1}.`)} ${cmd}`);
531
+ console.log(` ${chalk.dim(`${i + 1}.`)} ${cmd}`);
806
532
  });
807
533
  logger_default.blank();
808
534
  }
@@ -818,7 +544,7 @@ patternsCommand.command("show <id>").description("Show pattern details").action(
818
544
  });
819
545
  patternsCommand.command("ignore <id>").description("Ignore a pattern (never suggest again)").option("-r, --reason <reason>", "Reason for ignoring").action(async (id, options) => {
820
546
  const dbPath = getDbPath();
821
- if (!existsSync5(dbPath)) {
547
+ if (!existsSync4(dbPath)) {
822
548
  logger_default.error("Database not found. Run 'skillo init' first.");
823
549
  process.exit(1);
824
550
  }
@@ -839,12 +565,12 @@ patternsCommand.command("ignore <id>").description("Ignore a pattern (never sugg
839
565
  }
840
566
  });
841
567
  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) => {
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");
845
- const { getSkillsDir: getSkillsDir2, ensureDirectory: ensureDirectory2 } = await import("./paths-INOKEM66.js");
568
+ const { getApiClient: getApiClient2 } = await import("./api-client-BF6GDR7Q.js");
569
+ const { writeFileSync: writeFileSync5, mkdirSync: mkdirSync3 } = await import("fs");
570
+ const { join: join6 } = await import("path");
571
+ const { getSkillsDir: getSkillsDir2, ensureDirectory: ensureDirectory2 } = await import("./paths-MPOZBOKE.js");
846
572
  const dbPath = getDbPath();
847
- if (!existsSync5(dbPath)) {
573
+ if (!existsSync4(dbPath)) {
848
574
  logger_default.error("Database not found. Run 'skillo init' first.");
849
575
  process.exit(1);
850
576
  }
@@ -895,10 +621,10 @@ patternsCommand.command("generate <id>").description("Generate a skill from a pa
895
621
  } else {
896
622
  const skillsDir = getSkillsDir2();
897
623
  ensureDirectory2(skillsDir);
898
- const skillDir = join7(skillsDir, skill.slug);
899
- const skillFile = join7(skillDir, "SKILL.md");
624
+ const skillDir = join6(skillsDir, skill.slug);
625
+ const skillFile = join6(skillDir, "SKILL.md");
900
626
  mkdirSync3(skillDir, { recursive: true });
901
- writeFileSync7(skillFile, skill.content, "utf-8");
627
+ writeFileSync5(skillFile, skill.content, "utf-8");
902
628
  await db.updatePatternStatus(id, "converted");
903
629
  logger_default.success(`Skill saved to: ~/.claude/skills/${skill.slug}/SKILL.md`);
904
630
  logger_default.blank();
@@ -913,7 +639,7 @@ patternsCommand.command("clear").description("Clear all patterns").option("-f, -
913
639
  return;
914
640
  }
915
641
  const dbPath = getDbPath();
916
- if (!existsSync5(dbPath)) {
642
+ if (!existsSync4(dbPath)) {
917
643
  logger_default.error("Database not found.");
918
644
  process.exit(1);
919
645
  }
@@ -921,16 +647,16 @@ patternsCommand.command("clear").description("Clear all patterns").option("-f, -
921
647
  });
922
648
 
923
649
  // src/commands/skills.ts
924
- import { existsSync as existsSync6, readdirSync as readdirSync2, rmSync } from "fs";
650
+ import { existsSync as existsSync5, readdirSync, rmSync, mkdirSync, writeFileSync } from "fs";
925
651
  import { join as join2 } from "path";
926
652
  import { Command as Command5 } from "commander";
927
- import chalk3 from "chalk";
653
+ import chalk2 from "chalk";
928
654
  var skillsCommand = new Command5("skills").description(
929
655
  "Manage generated skills"
930
656
  );
931
657
  skillsCommand.command("list").description("List all skills").option("-s, --source <source>", "Filter by source (pattern, imported, manual)").option("--sort <by>", "Sort by (name, created, usage)", "name").action(async (options) => {
932
658
  const dbPath = getDbPath();
933
- if (!existsSync6(dbPath)) {
659
+ if (!existsSync5(dbPath)) {
934
660
  logger_default.error("Database not found. Run 'skillo init' first.");
935
661
  process.exit(1);
936
662
  }
@@ -943,11 +669,11 @@ skillsCommand.command("list").description("List all skills").option("-s, --sourc
943
669
  db.close();
944
670
  const skillsDir = getSkillsDir();
945
671
  const fsSkills = [];
946
- if (existsSync6(skillsDir)) {
947
- const entries = readdirSync2(skillsDir, { withFileTypes: true });
672
+ if (existsSync5(skillsDir)) {
673
+ const entries = readdirSync(skillsDir, { withFileTypes: true });
948
674
  entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).forEach((e) => {
949
675
  const skillMd = join2(skillsDir, e.name, "SKILL.md");
950
- if (existsSync6(skillMd)) {
676
+ if (existsSync5(skillMd)) {
951
677
  fsSkills.push(e.name);
952
678
  }
953
679
  });
@@ -966,19 +692,19 @@ skillsCommand.command("list").description("List all skills").option("-s, --sourc
966
692
  logger_default.bold(`Skills (${skills.length + unregisteredSkills.length}):`);
967
693
  logger_default.blank();
968
694
  skills.forEach((skill) => {
969
- const sourceColor = skill.sourceType === "pattern" ? chalk3.green : skill.sourceType === "imported" ? chalk3.blue : chalk3.dim;
695
+ const sourceColor = skill.sourceType === "pattern" ? chalk2.green : skill.sourceType === "imported" ? chalk2.blue : chalk2.dim;
970
696
  console.log(
971
- ` ${chalk3.cyan(skill.name)} ${sourceColor(skill.sourceType)} ${chalk3.dim(`used ${skill.usageCount}x`)}`
697
+ ` ${chalk2.cyan(skill.name)} ${sourceColor(skill.sourceType)} ${chalk2.dim(`used ${skill.usageCount}x`)}`
972
698
  );
973
699
  if (skill.triggerPhrase) {
974
- console.log(` ${chalk3.dim(skill.triggerPhrase)}`);
700
+ console.log(` ${chalk2.dim(skill.triggerPhrase)}`);
975
701
  }
976
702
  });
977
703
  if (unregisteredSkills.length > 0) {
978
704
  logger_default.blank();
979
705
  logger_default.dim(" Unregistered skills (found in ~/.claude/skills/):");
980
706
  unregisteredSkills.forEach((name) => {
981
- console.log(` ${chalk3.yellow(name)} ${chalk3.dim("filesystem only")}`);
707
+ console.log(` ${chalk2.yellow(name)} ${chalk2.dim("filesystem only")}`);
982
708
  });
983
709
  }
984
710
  logger_default.blank();
@@ -987,7 +713,7 @@ skillsCommand.command("list").description("List all skills").option("-s, --sourc
987
713
  });
988
714
  skillsCommand.command("show <name>").description("Show skill details").action(async (name) => {
989
715
  const dbPath = getDbPath();
990
- if (!existsSync6(dbPath)) {
716
+ if (!existsSync5(dbPath)) {
991
717
  logger_default.error("Database not found. Run 'skillo init' first.");
992
718
  process.exit(1);
993
719
  }
@@ -997,7 +723,7 @@ skillsCommand.command("show <name>").description("Show skill details").action(as
997
723
  db.close();
998
724
  if (!skill) {
999
725
  const skillPath = join2(getSkillsDir(), name, "SKILL.md");
1000
- if (existsSync6(skillPath)) {
726
+ if (existsSync5(skillPath)) {
1001
727
  logger_default.blank();
1002
728
  logger_default.bold(name);
1003
729
  logger_default.blank();
@@ -1028,984 +754,101 @@ skillsCommand.command("show <name>").description("Show skill details").action(as
1028
754
  });
1029
755
  skillsCommand.command("delete <name>").description("Delete a skill").option("-f, --force", "Skip confirmation").action(async (name, options) => {
1030
756
  const dbPath = getDbPath();
1031
- if (!existsSync6(dbPath)) {
1032
- logger_default.error("Database not found.");
1033
- process.exit(1);
1034
- }
1035
- const db = new SkilloDatabase();
1036
- await db.initialize();
1037
- const skill = await db.getSkill(name);
1038
- const skillDir = skill?.path || join2(getSkillsDir(), name);
1039
- if (!skill && !existsSync6(skillDir)) {
1040
- db.close();
1041
- logger_default.error(`Skill not found: ${name}`);
1042
- process.exit(1);
1043
- }
1044
- if (!options.force) {
1045
- logger_default.warn(`This will delete skill '${name}'. This cannot be undone.`);
1046
- logger_default.dim("Use --force to confirm.");
1047
- db.close();
1048
- return;
1049
- }
1050
- if (existsSync6(skillDir)) {
1051
- rmSync(skillDir, { recursive: true, force: true });
1052
- }
1053
- if (skill) {
1054
- await db.deleteSkill(name);
1055
- }
1056
- db.close();
1057
- logger_default.success(`Skill deleted: ${name}`);
1058
- });
1059
- skillsCommand.command("path").description("Show skills directory path").action(async () => {
1060
- console.log(getSkillsDir());
1061
- });
1062
- skillsCommand.command("open").description("Open skills directory in file manager").action(async () => {
1063
- const skillsDir = getSkillsDir();
1064
- if (!existsSync6(skillsDir)) {
1065
- logger_default.error("Skills directory not found. Run 'skillo init' first.");
1066
- process.exit(1);
1067
- }
1068
- const { exec } = await import("child_process");
1069
- const command = process.platform === "win32" ? `explorer "${skillsDir}"` : process.platform === "darwin" ? `open "${skillsDir}"` : `xdg-open "${skillsDir}"`;
1070
- exec(command, (error) => {
1071
- if (error) {
1072
- logger_default.error(`Failed to open directory: ${error.message}`);
1073
- logger_default.dim(`Path: ${skillsDir}`);
1074
- }
1075
- });
1076
- });
1077
-
1078
- // src/commands/shell.ts
1079
- import { existsSync as existsSync7, writeFileSync as writeFileSync2, readFileSync as readFileSync4, appendFileSync } from "fs";
1080
- import { spawn } from "child_process";
1081
- import { homedir as homedir2 } from "os";
1082
- import { join as join3 } from "path";
1083
- import { Command as Command6 } from "commander";
1084
- function normalizeCommand(cmd) {
1085
- const variables = {};
1086
- let normalized = cmd;
1087
- let varIndex = 0;
1088
- normalized = normalized.replace(/(?:^|\s)(\/[\w\-.\/]+)/g, (match, path2) => {
1089
- const key = `$PATH${varIndex++}`;
1090
- variables[key] = path2;
1091
- return match.replace(path2, key);
1092
- });
1093
- normalized = normalized.replace(
1094
- /https?:\/\/[^\s]+/g,
1095
- (match) => {
1096
- const key = `$URL${varIndex++}`;
1097
- variables[key] = match;
1098
- return key;
1099
- }
1100
- );
1101
- normalized = normalized.replace(/\b\d{4,}\b/g, (match) => {
1102
- const key = `$NUM${varIndex++}`;
1103
- variables[key] = match;
1104
- return key;
1105
- });
1106
- normalized = normalized.replace(/"[^"]+"/g, (match) => {
1107
- const key = `$STR${varIndex++}`;
1108
- variables[key] = match;
1109
- return key;
1110
- });
1111
- normalized = normalized.replace(/'[^']+'/g, (match) => {
1112
- const key = `$STR${varIndex++}`;
1113
- variables[key] = match;
1114
- return key;
1115
- });
1116
- return { normalized: normalized.trim(), variables };
1117
- }
1118
- var shellCommand = new Command6("shell").description("Start a tracked terminal session").option("-s, --shell <shell>", "Shell to use (default: $SHELL or cmd on Windows)").option("-p, --project <path>", "Project path to track").action(async (options) => {
1119
- if (!existsSync7(getConfigFile())) {
1120
- logger_default.error("Skillo not initialized. Run 'skillo init' first.");
1121
- process.exit(1);
1122
- }
1123
- const config = loadConfig();
1124
- let shell = options.shell || config.defaultShell;
1125
- if (!shell) {
1126
- if (process.platform === "win32") {
1127
- shell = process.env.COMSPEC || "cmd.exe";
1128
- } else {
1129
- shell = process.env.SHELL || "/bin/bash";
1130
- }
1131
- }
1132
- const projectPath = options.project || process.cwd();
1133
- logger_default.blank();
1134
- logger_default.info("Starting tracked shell session...");
1135
- logger_default.dim(`Shell: ${shell}`);
1136
- logger_default.dim(`Project: ${projectPath}`);
1137
- logger_default.blank();
1138
- logger_default.dim("All commands will be tracked. Type 'exit' to end the session.");
1139
- logger_default.blank();
1140
- const db = new SkilloDatabase();
1141
- await db.initialize();
1142
- const sessionId = await db.createSession(shell, projectPath);
1143
- let commandCount = 0;
1144
- let currentCommand = "";
1145
- let commandStartTime = null;
1146
- const shellArgs = process.platform === "win32" ? [] : ["-i"];
1147
- const child = spawn(shell, shellArgs, {
1148
- stdio: ["inherit", "inherit", "inherit"],
1149
- shell: false,
1150
- cwd: projectPath,
1151
- env: {
1152
- ...process.env,
1153
- SKILLO_SESSION: sessionId,
1154
- SKILLO_TRACKING: "1"
1155
- }
1156
- });
1157
- child.on("exit", async (code) => {
1158
- await db.endSession(sessionId);
1159
- db.close();
1160
- logger_default.blank();
1161
- logger_default.success(`Session ended. Tracked ${commandCount} command(s).`);
1162
- logger_default.dim(`Session ID: ${sessionId.slice(0, 8)}`);
1163
- logger_default.blank();
1164
- process.exit(code || 0);
1165
- });
1166
- child.on("error", async (error) => {
1167
- logger_default.error(`Failed to start shell: ${error.message}`);
1168
- await db.endSession(sessionId);
1169
- db.close();
1170
- process.exit(1);
1171
- });
1172
- });
1173
- var recordCommand = new Command6("record").description("Record a command (used by shell integration)").argument("<command>", "Command that was executed").option("--session <id>", "Session ID").option("--cwd <path>", "Working directory").option("--exit-code <code>", "Exit code").option("--duration <ms>", "Duration in milliseconds").option("--no-sync", "Don't sync to platform").action(
1174
- async (command, options) => {
1175
- const cwd = options.cwd || process.cwd();
1176
- const exitCode = options.exitCode ? parseInt(options.exitCode, 10) : null;
1177
- const durationMs = options.duration ? parseInt(options.duration, 10) : null;
1178
- const sessionId = options.session || process.env.SKILLO_SESSION || "default";
1179
- const { normalized, variables } = normalizeCommand(command);
1180
- const db = new SkilloDatabase();
1181
- await db.initialize();
1182
- await db.addCommand({
1183
- command,
1184
- normalized,
1185
- cwd,
1186
- sessionId,
1187
- exitCode,
1188
- durationMs,
1189
- variables: Object.keys(variables).length > 0 ? variables : null
1190
- });
1191
- db.close();
1192
- if (options.sync !== false) {
1193
- try {
1194
- const { getApiClient: getApiClient2 } = await import("./api-client-KUQW7FSC.js");
1195
- const client = getApiClient2();
1196
- if (client.hasApiKey()) {
1197
- await client.syncCommands([{
1198
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1199
- command,
1200
- normalized,
1201
- cwd,
1202
- exitCode,
1203
- durationMs,
1204
- sessionId: sessionId !== "default" ? sessionId : void 0,
1205
- variables: Object.keys(variables).length > 0 ? variables : null
1206
- }]);
1207
- }
1208
- } catch {
1209
- }
1210
- }
1211
- }
1212
- );
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) => {
1214
- logger_default.blank();
1215
- const home = homedir2();
1216
- const dataDir = getDataDir();
1217
- ensureDirectory(dataDir);
1218
- let shell = null;
1219
- if (options.bash) shell = "bash";
1220
- else if (options.zsh) shell = "zsh";
1221
- else if (options.powershell) shell = "powershell";
1222
- else if (options.fish) shell = "fish";
1223
- else {
1224
- if (process.platform === "win32") {
1225
- shell = "powershell";
1226
- } else {
1227
- const currentShell = process.env.SHELL || "";
1228
- if (currentShell.includes("zsh")) shell = "zsh";
1229
- else if (currentShell.includes("fish")) shell = "fish";
1230
- else shell = "bash";
1231
- }
1232
- logger_default.info(`Detected shell: ${shell}`);
1233
- }
1234
- if (options.uninstall) {
1235
- logger_default.info(`Removing ${shell} integration...`);
1236
- await uninstallShellIntegration(shell, home);
1237
- logger_default.success("Shell integration removed.");
1238
- logger_default.dim("Restart your terminal for changes to take effect.");
1239
- logger_default.blank();
1240
- return;
1241
- }
1242
- logger_default.info(`Setting up ${shell} integration...`);
1243
- logger_default.blank();
1244
- await installShellIntegration(shell, home, dataDir);
1245
- logger_default.blank();
1246
- logger_default.success("Shell integration installed!");
1247
- logger_default.blank();
1248
- logger_default.dim("Restart your terminal or run:");
1249
- if (shell === "powershell") {
1250
- logger_default.highlight(" . $PROFILE");
1251
- } else if (shell === "bash") {
1252
- logger_default.highlight(" source ~/.bashrc");
1253
- } else if (shell === "zsh") {
1254
- logger_default.highlight(" source ~/.zshrc");
1255
- } else if (shell === "fish") {
1256
- logger_default.highlight(" source ~/.config/fish/config.fish");
1257
- }
1258
- logger_default.blank();
1259
- logger_default.dim("All commands you run will now be tracked and synced to Skillo.");
1260
- logger_default.blank();
1261
- });
1262
- async function installShellIntegration(shell, home, dataDir) {
1263
- const scriptPath = join3(dataDir, "shell-integration");
1264
- ensureDirectory(scriptPath);
1265
- if (shell === "powershell") {
1266
- const psScript = `# Skillo CLI Integration
1267
- # Records commands to Skillo platform
1268
- # Works in two modes:
1269
- # 1. Platform mode: Uses SKILLO_SESSION and SKILLO_USER_ID env vars (set by platform launch)
1270
- # 2. Standalone mode: Uses skillo CLI which handles auth via API key
1271
-
1272
- $Global:SkilloLastHistoryId = 0
1273
- $Global:SkilloStandaloneSessionId = $null
1274
- $Global:SkilloSessionStartTime = $null
1275
-
1276
- # Check if launched from platform (has session/user env vars) or standalone
1277
- $Global:SkilloPlatformMode = $false
1278
- if ($env:SKILLO_SESSION -and $env:SKILLO_USER_ID) {
1279
- $Global:SkilloPlatformMode = $true
1280
- $Global:SkilloSessionId = $env:SKILLO_SESSION
1281
- $Global:SkilloUserId = $env:SKILLO_USER_ID
1282
- $Global:SkilloBaseUrl = if ($env:SKILLO_API_URL) { $env:SKILLO_API_URL } else { 'http://localhost:3000' }
1283
- }
1284
-
1285
- function Send-SkilloCommandPlatform {
1286
- param($Command, $Duration, $ExitCode, $Cwd)
1287
-
1288
- try {
1289
- $body = @{
1290
- type = 'commands'
1291
- data = @(@{
1292
- timestamp = (Get-Date).ToString('o')
1293
- command = $Command
1294
- normalized = $Command
1295
- cwd = $Cwd
1296
- exitCode = $ExitCode
1297
- durationMs = $Duration
1298
- sessionId = $Global:SkilloSessionId
1299
- })
1300
- } | ConvertTo-Json -Depth 3 -Compress
1301
-
1302
- $uri = "$($Global:SkilloBaseUrl)/api/cli/sync"
1303
-
1304
- $webRequest = [System.Net.HttpWebRequest]::Create($uri)
1305
- $webRequest.Method = 'POST'
1306
- $webRequest.ContentType = 'application/json'
1307
- $webRequest.Headers.Add('x-skillo-session', $Global:SkilloSessionId)
1308
- $webRequest.Headers.Add('x-skillo-user-id', $Global:SkilloUserId)
1309
- $webRequest.Timeout = 5000
1310
-
1311
- $bytes = [System.Text.Encoding]::UTF8.GetBytes($body)
1312
- $webRequest.ContentLength = $bytes.Length
1313
- $stream = $webRequest.GetRequestStream()
1314
- $stream.Write($bytes, 0, $bytes.Length)
1315
- $stream.Close()
1316
-
1317
- $response = $webRequest.GetResponse()
1318
- $response.Close()
1319
- } catch {
1320
- # Silently ignore errors
1321
- }
1322
- }
1323
-
1324
- function Send-SkilloCommandCLI {
1325
- param($Command, $Duration, $ExitCode, $Cwd)
1326
-
1327
- # Create session on first command if not exists
1328
- if (-not $Global:SkilloStandaloneSessionId) {
1329
- $Global:SkilloSessionStartTime = (Get-Date).ToString('o')
1330
- try {
1331
- $result = & skillo session start --shell "PowerShell" 2>$null
1332
- if ($result -match 'Session ID: ([a-f0-9-]+)') {
1333
- $Global:SkilloStandaloneSessionId = $Matches[1]
1334
- }
1335
- } catch {
1336
- # If session command doesn't exist, generate a local ID
1337
- $Global:SkilloStandaloneSessionId = [guid]::NewGuid().ToString()
1338
- }
1339
- }
1340
-
1341
- # Run skillo record in background with session ID
1342
- Start-Job -ScriptBlock {
1343
- param($cmd, $cwd, $exit, $dur, $sessionId)
1344
- & skillo record $cmd --cwd $cwd --exit-code $exit --duration $dur --session $sessionId 2>$null
1345
- } -ArgumentList $Command, $Cwd, $ExitCode, $Duration, $Global:SkilloStandaloneSessionId | Out-Null
1346
- }
1347
-
1348
- # Override prompt to capture commands after execution
1349
- $Global:SkilloOriginalPrompt = $function:prompt
1350
- function Global:prompt {
1351
- $lastCmd = Get-History -Count 1 -ErrorAction SilentlyContinue
1352
-
1353
- if ($lastCmd -and $lastCmd.Id -gt $Global:SkilloLastHistoryId) {
1354
- $Global:SkilloLastHistoryId = $lastCmd.Id
1355
-
1356
- $duration = 0
1357
- if ($lastCmd.EndExecutionTime -and $lastCmd.StartExecutionTime) {
1358
- $duration = [int]($lastCmd.EndExecutionTime - $lastCmd.StartExecutionTime).TotalMilliseconds
1359
- }
1360
- $exit = if ($null -eq $LASTEXITCODE) { 0 } else { $LASTEXITCODE }
1361
- $cwd = (Get-Location).Path
1362
-
1363
- if ($Global:SkilloPlatformMode) {
1364
- Send-SkilloCommandPlatform -Command $lastCmd.CommandLine -Duration $duration -ExitCode $exit -Cwd $cwd
1365
- } else {
1366
- Send-SkilloCommandCLI -Command $lastCmd.CommandLine -Duration $duration -ExitCode $exit -Cwd $cwd
1367
- }
1368
- }
1369
-
1370
- & $Global:SkilloOriginalPrompt
1371
- }
1372
-
1373
- # Register exit handler to end session when terminal closes
1374
- $null = Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action {
1375
- if ($Global:SkilloPlatformMode -and $Global:SkilloSessionId) {
1376
- # End platform session
1377
- try {
1378
- $body = @{ sessionId = $Global:SkilloSessionId } | ConvertTo-Json -Compress
1379
- $uri = "$($Global:SkilloBaseUrl)/api/sessions"
1380
-
1381
- $webRequest = [System.Net.HttpWebRequest]::Create($uri)
1382
- $webRequest.Method = 'PATCH'
1383
- $webRequest.ContentType = 'application/json'
1384
- $webRequest.Headers.Add('x-skillo-session', $Global:SkilloSessionId)
1385
- $webRequest.Headers.Add('x-skillo-user-id', $Global:SkilloUserId)
1386
- $webRequest.Timeout = 3000
1387
-
1388
- $bytes = [System.Text.Encoding]::UTF8.GetBytes($body)
1389
- $webRequest.ContentLength = $bytes.Length
1390
- $stream = $webRequest.GetRequestStream()
1391
- $stream.Write($bytes, 0, $bytes.Length)
1392
- $stream.Close()
1393
-
1394
- $response = $webRequest.GetResponse()
1395
- $response.Close()
1396
- } catch {
1397
- # Ignore errors on exit
1398
- }
1399
- } elseif ($Global:SkilloStandaloneSessionId) {
1400
- # End standalone session via CLI
1401
- try {
1402
- & skillo session end --session $Global:SkilloStandaloneSessionId 2>$null
1403
- } catch {
1404
- # Ignore errors on exit
1405
- }
1406
- }
1407
- }
1408
-
1409
- Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
1410
- `;
1411
- const psScriptFile = join3(scriptPath, "skillo.ps1");
1412
- writeFileSync2(psScriptFile, psScript, "utf-8");
1413
- logger_default.success(`Created ${psScriptFile}`);
1414
- const profilePath = join3(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
1415
- const profileDir = join3(home, "Documents", "WindowsPowerShell");
1416
- ensureDirectory(profileDir);
1417
- const sourceLine = `
1418
- # Skillo CLI Integration
1419
- . "${psScriptFile}"
1420
- `;
1421
- if (existsSync7(profilePath)) {
1422
- const content = readFileSync4(profilePath, "utf-8");
1423
- if (!content.includes("Skillo CLI Integration")) {
1424
- appendFileSync(profilePath, sourceLine);
1425
- logger_default.success("Added to PowerShell profile");
1426
- } else {
1427
- logger_default.dim("Already in PowerShell profile");
1428
- }
1429
- } else {
1430
- writeFileSync2(profilePath, sourceLine, "utf-8");
1431
- logger_default.success("Created PowerShell profile");
1432
- }
1433
- } else if (shell === "bash") {
1434
- const bashScript = [
1435
- "# Skillo CLI Integration",
1436
- "# Records commands and auto-detects tracked projects",
1437
- "",
1438
- '_skillo_session="${SKILLO_SESSION:-default}"',
1439
- "_skillo_last_cwd=",
1440
- "",
1441
- "_skillo_preexec() {",
1442
- ' _skillo_cmd="$1"',
1443
- " _skillo_start_time=$(date +%s)",
1444
- "}",
1445
- "",
1446
- "_skillo_check_project() {",
1447
- " # Fast path: skip if CWD unchanged",
1448
- ' if [ "$PWD" = "$_skillo_last_cwd" ]; then',
1449
- " return",
1450
- " fi",
1451
- ' _skillo_last_cwd="$PWD"',
1452
- "",
1453
- " # Run session-auto in background (handles create/end via cache file)",
1454
- " local result",
1455
- ' result=$(skillo session-auto "$PWD" --pid $$ --shell bash 2>/dev/null)',
1456
- ' if [ -n "$result" ]; then',
1457
- ' export SKILLO_SESSION="$result"',
1458
- ' _skillo_session="$result"',
1459
- " fi",
1460
- "}",
1461
- "",
1462
- "_skillo_precmd() {",
1463
- " local exit_code=$?",
1464
- "",
1465
- " # Check for project change",
1466
- " _skillo_check_project",
1467
- "",
1468
- ' if [ -n "$_skillo_cmd" ]; then',
1469
- " local duration=0",
1470
- ' if [ -n "$_skillo_start_time" ]; then',
1471
- " local end_time=$(date +%s)",
1472
- " duration=$(( (end_time - _skillo_start_time) * 1000 ))",
1473
- " fi",
1474
- ' (skillo record "$_skillo_cmd" --cwd "$PWD" --exit-code "$exit_code" --duration "$duration" --session "$_skillo_session" &>/dev/null &)',
1475
- ' _skillo_cmd=""',
1476
- " fi",
1477
- "}",
1478
- "",
1479
- "# Clean up session on terminal exit",
1480
- "_skillo_cleanup() {",
1481
- ' if [ -n "$SKILLO_SESSION" ] && [ "$SKILLO_SESSION" != "default" ]; then',
1482
- ' skillo session end --session "$SKILLO_SESSION" &>/dev/null',
1483
- " fi",
1484
- "}",
1485
- "trap _skillo_cleanup EXIT",
1486
- "",
1487
- "# Use DEBUG trap for preexec",
1488
- `trap '_skillo_preexec "$BASH_COMMAND"' DEBUG`,
1489
- "",
1490
- "# Add precmd to PROMPT_COMMAND",
1491
- 'if [[ ! "$PROMPT_COMMAND" =~ _skillo_precmd ]]; then',
1492
- ' PROMPT_COMMAND="_skillo_precmd${PROMPT_COMMAND:+;$PROMPT_COMMAND}"',
1493
- "fi",
1494
- "",
1495
- "# Initial project check",
1496
- "_skillo_check_project",
1497
- "",
1498
- 'echo -e "\\033[90mSkillo: Command tracking enabled\\033[0m"'
1499
- ].join("\n");
1500
- const bashScriptFile = join3(scriptPath, "skillo.bash");
1501
- writeFileSync2(bashScriptFile, bashScript, "utf-8");
1502
- logger_default.success(`Created ${bashScriptFile}`);
1503
- const bashrcPath = join3(home, ".bashrc");
1504
- const sourceLine = `
1505
- # Skillo CLI Integration
1506
- [ -f "${bashScriptFile}" ] && source "${bashScriptFile}"
1507
- `;
1508
- if (existsSync7(bashrcPath)) {
1509
- const content = readFileSync4(bashrcPath, "utf-8");
1510
- if (!content.includes("Skillo CLI Integration")) {
1511
- appendFileSync(bashrcPath, sourceLine);
1512
- logger_default.success("Added to ~/.bashrc");
1513
- } else {
1514
- logger_default.dim("Already in ~/.bashrc");
1515
- }
1516
- } else {
1517
- writeFileSync2(bashrcPath, sourceLine, "utf-8");
1518
- logger_default.success("Created ~/.bashrc");
1519
- }
1520
- } else if (shell === "zsh") {
1521
- const zshScript = [
1522
- "# Skillo CLI Integration",
1523
- "# Records commands and auto-detects tracked projects",
1524
- "",
1525
- '_skillo_session="${SKILLO_SESSION:-default}"',
1526
- "_skillo_last_cwd=",
1527
- "",
1528
- "_skillo_preexec() {",
1529
- ' _skillo_cmd="$1"',
1530
- " _skillo_start_time=$(date +%s)",
1531
- "}",
1532
- "",
1533
- "_skillo_check_project() {",
1534
- " # Fast path: skip if CWD unchanged",
1535
- ' if [[ "$PWD" == "$_skillo_last_cwd" ]]; then',
1536
- " return",
1537
- " fi",
1538
- ' _skillo_last_cwd="$PWD"',
1539
- "",
1540
- " # Run session-auto in background (handles create/end via cache file)",
1541
- " local result",
1542
- ' result=$(skillo session-auto "$PWD" --pid $$ --shell zsh 2>/dev/null)',
1543
- ' if [[ -n "$result" ]]; then',
1544
- ' export SKILLO_SESSION="$result"',
1545
- ' _skillo_session="$result"',
1546
- " fi",
1547
- "}",
1548
- "",
1549
- "_skillo_precmd() {",
1550
- " local exit_code=$?",
1551
- "",
1552
- " # Check for project change",
1553
- " _skillo_check_project",
1554
- "",
1555
- ' if [[ -n "$_skillo_cmd" ]]; then',
1556
- " local duration=0",
1557
- ' if [[ -n "$_skillo_start_time" ]]; then',
1558
- " local end_time=$(date +%s)",
1559
- " duration=$(( (end_time - _skillo_start_time) * 1000 ))",
1560
- " fi",
1561
- ' (skillo record "$_skillo_cmd" --cwd "$PWD" --exit-code "$exit_code" --duration "$duration" --session "$_skillo_session" &>/dev/null &)',
1562
- ' _skillo_cmd=""',
1563
- " fi",
1564
- "}",
1565
- "",
1566
- "# Clean up session on terminal exit",
1567
- "_skillo_cleanup() {",
1568
- ' if [[ -n "$SKILLO_SESSION" ]] && [[ "$SKILLO_SESSION" != "default" ]]; then',
1569
- ' skillo session end --session "$SKILLO_SESSION" &>/dev/null',
1570
- " fi",
1571
- "}",
1572
- "trap _skillo_cleanup EXIT",
1573
- "",
1574
- "autoload -Uz add-zsh-hook",
1575
- "add-zsh-hook preexec _skillo_preexec",
1576
- "add-zsh-hook precmd _skillo_precmd",
1577
- "",
1578
- "# Initial project check",
1579
- "_skillo_check_project",
1580
- "",
1581
- 'echo -e "\\033[90mSkillo: Command tracking enabled\\033[0m"'
1582
- ].join("\n");
1583
- const zshScriptFile = join3(scriptPath, "skillo.zsh");
1584
- writeFileSync2(zshScriptFile, zshScript, "utf-8");
1585
- logger_default.success(`Created ${zshScriptFile}`);
1586
- const zshrcPath = join3(home, ".zshrc");
1587
- const sourceLine = `
1588
- # Skillo CLI Integration
1589
- [ -f "${zshScriptFile}" ] && source "${zshScriptFile}"
1590
- `;
1591
- if (existsSync7(zshrcPath)) {
1592
- const content = readFileSync4(zshrcPath, "utf-8");
1593
- if (!content.includes("Skillo CLI Integration")) {
1594
- appendFileSync(zshrcPath, sourceLine);
1595
- logger_default.success("Added to ~/.zshrc");
1596
- } else {
1597
- logger_default.dim("Already in ~/.zshrc");
1598
- }
1599
- } else {
1600
- writeFileSync2(zshrcPath, sourceLine, "utf-8");
1601
- logger_default.success("Created ~/.zshrc");
1602
- }
1603
- } else if (shell === "fish") {
1604
- const fishScript = [
1605
- "# Skillo CLI Integration",
1606
- "# Records commands to Skillo platform",
1607
- "",
1608
- 'set -q SKILLO_SESSION; or set -g SKILLO_SESSION "default"',
1609
- "",
1610
- "function _skillo_postexec --on-event fish_postexec",
1611
- " set -l cmd $argv[1]",
1612
- " set -l exit_code $status",
1613
- ' skillo record "$cmd" --cwd (pwd) --exit-code $exit_code --session $SKILLO_SESSION &>/dev/null &',
1614
- "end",
1615
- "",
1616
- 'echo -e "\\033[90mSkillo: Command tracking enabled\\033[0m"'
1617
- ].join("\n");
1618
- const fishScriptFile = join3(scriptPath, "skillo.fish");
1619
- writeFileSync2(fishScriptFile, fishScript, "utf-8");
1620
- logger_default.success(`Created ${fishScriptFile}`);
1621
- const fishConfigDir = join3(home, ".config", "fish");
1622
- ensureDirectory(fishConfigDir);
1623
- const fishConfigPath = join3(fishConfigDir, "config.fish");
1624
- const sourceLine = `
1625
- # Skillo CLI Integration
1626
- if test -f "${fishScriptFile}"
1627
- source "${fishScriptFile}"
1628
- end
1629
- `;
1630
- if (existsSync7(fishConfigPath)) {
1631
- const content = readFileSync4(fishConfigPath, "utf-8");
1632
- if (!content.includes("Skillo CLI Integration")) {
1633
- appendFileSync(fishConfigPath, sourceLine);
1634
- logger_default.success("Added to ~/.config/fish/config.fish");
1635
- } else {
1636
- logger_default.dim("Already in fish config");
1637
- }
1638
- } else {
1639
- writeFileSync2(fishConfigPath, sourceLine, "utf-8");
1640
- logger_default.success("Created fish config");
1641
- }
1642
- }
1643
- }
1644
- async function uninstallShellIntegration(shell, home) {
1645
- const removeFromFile = (filePath) => {
1646
- if (!existsSync7(filePath)) return;
1647
- let content = readFileSync4(filePath, "utf-8");
1648
- content = content.replace(/\n# Skillo CLI Integration\n[^\n]*\n/g, "\n");
1649
- writeFileSync2(filePath, content, "utf-8");
1650
- };
1651
- if (shell === "powershell") {
1652
- const profilePath = join3(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
1653
- removeFromFile(profilePath);
1654
- } else if (shell === "bash") {
1655
- removeFromFile(join3(home, ".bashrc"));
1656
- } else if (shell === "zsh") {
1657
- removeFromFile(join3(home, ".zshrc"));
1658
- } else if (shell === "fish") {
1659
- removeFromFile(join3(home, ".config", "fish", "config.fish"));
1660
- }
1661
- }
1662
-
1663
- // src/commands/daemon.ts
1664
- import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs";
1665
- import { fork } from "child_process";
1666
- import { Command as Command7 } from "commander";
1667
- function isDaemonRunning2() {
1668
- const pidFile = getPidFile();
1669
- if (!existsSync8(pidFile)) {
1670
- return { running: false, pid: null };
1671
- }
1672
- try {
1673
- const pidStr = readFileSync5(pidFile, "utf-8").trim();
1674
- const pid = parseInt(pidStr, 10);
1675
- if (isNaN(pid)) {
1676
- return { running: false, pid: null };
1677
- }
1678
- try {
1679
- process.kill(pid, 0);
1680
- return { running: true, pid };
1681
- } catch {
1682
- try {
1683
- unlinkSync2(pidFile);
1684
- } catch {
1685
- }
1686
- return { running: false, pid: null };
1687
- }
1688
- } catch {
1689
- return { running: false, pid: null };
1690
- }
1691
- }
1692
- function startDaemonProcess() {
1693
- const daemonScript = new URL("./daemon-runner.js", import.meta.url).pathname;
1694
- const child = fork(daemonScript, [], {
1695
- detached: true,
1696
- stdio: "ignore",
1697
- env: {
1698
- ...process.env,
1699
- SKILLO_DAEMON: "1"
1700
- }
1701
- });
1702
- child.unref();
1703
- if (child.pid) {
1704
- writeFileSync3(getPidFile(), String(child.pid));
1705
- return child.pid;
1706
- }
1707
- return null;
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
- }
1787
- var startCommand = new Command7("start").description("Start the Skillo daemon").option("-f, --foreground", "Run in foreground (don't daemonize)").action(async (options) => {
1788
- await ensureInitialized();
1789
- if (!options.foreground) {
1790
- const loggedIn = await ensureLoggedIn();
1791
- if (!loggedIn) {
1792
- process.exit(1);
1793
- }
757
+ if (!existsSync5(dbPath)) {
758
+ logger_default.error("Database not found.");
759
+ process.exit(1);
1794
760
  }
1795
- const { running, pid } = isDaemonRunning2();
1796
- if (running) {
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
- }
761
+ const db = new SkilloDatabase();
762
+ await db.initialize();
763
+ const skill = await db.getSkill(name);
764
+ const skillDir = skill?.path || join2(getSkillsDir(), name);
765
+ if (!skill && !existsSync5(skillDir)) {
766
+ db.close();
767
+ logger_default.error(`Skill not found: ${name}`);
768
+ process.exit(1);
769
+ }
770
+ if (!options.force) {
771
+ logger_default.warn(`This will delete skill '${name}'. This cannot be undone.`);
772
+ logger_default.dim("Use --force to confirm.");
773
+ db.close();
1805
774
  return;
1806
775
  }
1807
- if (options.foreground) {
1808
- logger_default.info("Starting Skillo daemon in foreground...");
1809
- logger_default.dim("Press Ctrl+C to stop");
1810
- logger_default.blank();
1811
- await runDaemonForeground();
1812
- } else {
1813
- logger_default.info("Starting Skillo daemon...");
1814
- if (process.platform === "win32") {
1815
- logger_default.dim("Running in foreground mode on Windows...");
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
- }
1821
- await runDaemonForeground();
1822
- } else {
1823
- const daemonPid = startDaemonProcess();
1824
- if (daemonPid) {
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
- }
1830
- logger_default.blank();
1831
- logger_default.dim("Use 'skillo status' to check daemon status");
1832
- logger_default.dim("Use 'skillo stop' to stop the daemon");
1833
- } else {
1834
- logger_default.error("Failed to start daemon");
1835
- }
1836
- }
776
+ if (existsSync5(skillDir)) {
777
+ rmSync(skillDir, { recursive: true, force: true });
1837
778
  }
1838
- });
1839
- var stopCommand = new Command7("stop").description("Stop the Skillo daemon").action(async () => {
1840
- const { running, pid } = isDaemonRunning2();
1841
- if (!running) {
1842
- logger_default.dim("Daemon is not running.");
1843
- return;
779
+ if (skill) {
780
+ await db.deleteSkill(name);
1844
781
  }
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.");
782
+ db.close();
783
+ logger_default.success(`Skill deleted: ${name}`);
784
+ });
785
+ skillsCommand.command("path").description("Show skills directory path").action(async () => {
786
+ console.log(getSkillsDir());
787
+ });
788
+ skillsCommand.command("open").description("Open skills directory in file manager").action(async () => {
789
+ const skillsDir = getSkillsDir();
790
+ if (!existsSync5(skillsDir)) {
791
+ logger_default.error("Skills directory not found. Run 'skillo init' first.");
792
+ process.exit(1);
1849
793
  }
1850
- try {
1851
- process.kill(pid, "SIGTERM");
1852
- logger_default.success("Daemon stopped");
1853
- } catch (error) {
1854
- if (error.code === "ESRCH") {
1855
- logger_default.dim("Daemon already stopped");
1856
- } else if (error.code === "EPERM") {
1857
- logger_default.error("Permission denied. Try with sudo.");
1858
- process.exit(1);
1859
- } else {
1860
- throw error;
794
+ const { exec } = await import("child_process");
795
+ const command = process.platform === "win32" ? `explorer "${skillsDir}"` : process.platform === "darwin" ? `open "${skillsDir}"` : `xdg-open "${skillsDir}"`;
796
+ exec(command, (error) => {
797
+ if (error) {
798
+ logger_default.error(`Failed to open directory: ${error.message}`);
799
+ logger_default.dim(`Path: ${skillsDir}`);
1861
800
  }
801
+ });
802
+ });
803
+ skillsCommand.command("install <slug>").description("Install a shared/team skill from the platform").action(async (slug) => {
804
+ const client = getApiClient();
805
+ if (!client.hasApiKey()) {
806
+ logger_default.error("Not authenticated. Run 'skillo login' first.");
807
+ process.exit(1);
1862
808
  }
1863
- const pidFile = getPidFile();
1864
- if (existsSync8(pidFile)) {
1865
- try {
1866
- unlinkSync2(pidFile);
1867
- } catch {
1868
- }
809
+ logger_default.dim(`Installing skill: ${slug}...`);
810
+ const result = await client.installSkill(slug);
811
+ if (!result.success || !result.data) {
812
+ logger_default.error(result.error || "Failed to install skill");
813
+ process.exit(1);
1869
814
  }
1870
- });
1871
- var logsCommand = new Command7("logs").description("Show daemon logs").option("-n, --lines <number>", "Number of lines to show", "50").option("-f, --follow", "Follow log output").action(async (options) => {
1872
- const logFile = getLogFile();
1873
- if (!existsSync8(logFile)) {
1874
- logger_default.dim("No logs found.");
1875
- return;
815
+ const { slug: skillSlug, name, content } = result.data;
816
+ const skillsDir = getSkillsDir();
817
+ const skillDir = join2(skillsDir, skillSlug);
818
+ if (!existsSync5(skillsDir)) {
819
+ mkdirSync(skillsDir, { recursive: true });
1876
820
  }
1877
- const numLines = parseInt(options.lines, 10);
1878
- if (options.follow) {
1879
- const { spawn: spawn2 } = await import("child_process");
1880
- const tail = spawn2("tail", ["-f", "-n", String(numLines), logFile], {
1881
- stdio: "inherit"
1882
- });
1883
- tail.on("error", () => {
1884
- logger_default.warn("Follow mode not supported on this platform.");
1885
- showLogs(logFile, numLines);
1886
- });
1887
- } else {
1888
- showLogs(logFile, numLines);
821
+ if (!existsSync5(skillDir)) {
822
+ mkdirSync(skillDir, { recursive: true });
1889
823
  }
824
+ writeFileSync(join2(skillDir, "SKILL.md"), content, "utf-8");
825
+ logger_default.success(`Installed skill: ${name} (${skillSlug})`);
826
+ logger_default.dim(` Path: ${skillDir}`);
1890
827
  });
1891
- function showLogs(logFile, numLines) {
1892
- const content = readFileSync5(logFile, "utf-8");
1893
- const lines = content.split("\n").filter(Boolean);
1894
- const lastLines = lines.slice(-numLines);
1895
- lastLines.forEach((line) => console.log(line));
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();
828
+ skillsCommand.command("uninstall <slug>").description("Uninstall a shared/team skill").action(async (slug) => {
829
+ const skillsDir = getSkillsDir();
830
+ const skillDir = join2(skillsDir, slug);
831
+ if (existsSync5(skillDir)) {
832
+ rmSync(skillDir, { recursive: true, force: true });
833
+ logger_default.success(`Removed skill files: ${slug}`);
834
+ } else {
835
+ logger_default.warn(`Skill directory not found: ${slug}`);
836
+ }
837
+ const client = getApiClient();
838
+ if (client.hasApiKey()) {
839
+ const result = await client.uninstallSkill(slug);
1915
840
  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)"}`);
841
+ logger_default.dim(" Platform subscription updated");
1934
842
  } 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
- );
1944
- async function runDaemonForeground() {
1945
- const pidFile = getPidFile();
1946
- writeFileSync3(pidFile, String(process.pid));
1947
- logger_default.dim(`Daemon PID: ${process.pid}`);
1948
- logger_default.dim(`Log file: ${getLogFile()}`);
1949
- logger_default.blank();
1950
- const cleanup = () => {
1951
- logger_default.blank();
1952
- logger_default.info("Shutting down daemon...");
1953
- if (existsSync8(pidFile)) {
1954
- try {
1955
- unlinkSync2(pidFile);
1956
- } catch {
1957
- }
843
+ logger_default.dim(` Platform notification failed: ${result.error}`);
1958
844
  }
1959
- process.exit(0);
1960
- };
1961
- process.on("SIGINT", cleanup);
1962
- process.on("SIGTERM", cleanup);
1963
- const config = loadConfig();
1964
- const { getApiClient: getApiClient2 } = await import("./api-client-KUQW7FSC.js");
1965
- const client = getApiClient2();
1966
- if (!client.hasApiKey()) {
1967
- logger_default.error("Not logged in. Run 'skillo login' first.");
1968
- process.exit(1);
1969
845
  }
1970
- const { ClaudeWatcher } = await import("./claude-watcher-N6GN6WHJ.js");
1971
- ensureDirectory(getDataDir());
1972
- const watchInterval = (config.daemon?.conversationCheckInterval || 5) * 1e3;
1973
- const watcher = new ClaudeWatcher(client, {
1974
- intervalMs: watchInterval,
1975
- callbacks: {
1976
- onSync: (count) => logger_default.success(`Synced ${count} Claude prompt(s)`),
1977
- onError: (err) => logger_default.error(`Watcher error: ${err.message}`),
1978
- log: (level, msg) => {
1979
- if (level === "ERROR") logger_default.error(msg);
1980
- else if (level === "WARN") logger_default.warn(msg);
1981
- else logger_default.dim(msg);
1982
- }
1983
- }
1984
- });
1985
- await watcher.start();
1986
- const { SkillUsageDetector } = await import("./skill-usage-detector-EO26MRYV.js");
1987
- const skillDetector = new SkillUsageDetector(client, {
1988
- intervalMs: 3e4,
1989
- callbacks: {
1990
- onDetection: (count) => logger_default.success(`Detected ${count} skill usage(s)`),
1991
- onError: (err) => logger_default.error(`Skill detection error: ${err.message}`),
1992
- log: (level, msg) => {
1993
- if (level === "ERROR") logger_default.error(msg);
1994
- else if (level === "WARN") logger_default.warn(msg);
1995
- else logger_default.dim(msg);
1996
- }
1997
- }
1998
- });
1999
- await skillDetector.start();
2000
- logger_default.success("Daemon is running. Watching Claude conversations and skill usage...");
2001
- await new Promise(() => {
2002
- });
2003
- }
846
+ });
2004
847
 
2005
848
  // src/commands/session.ts
2006
- import { Command as Command8 } from "commander";
2007
- import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3 } from "fs";
2008
- var sessionCommand = new Command8("session").description("Manage terminal sessions");
849
+ import { Command as Command6 } from "commander";
850
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync2, unlinkSync } from "fs";
851
+ var sessionCommand = new Command6("session").description("Manage terminal sessions");
2009
852
  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) => {
2010
853
  try {
2011
854
  const client = getApiClient();
@@ -2051,32 +894,50 @@ sessionCommand.command("list").description("List active sessions").action(async
2051
894
  }
2052
895
  logger_default.info("Check your sessions at: https://www.skillo.one/terminal");
2053
896
  });
2054
- var sessionAutoCommand = new Command8("session-auto").description("Auto-detect project and manage session (used by shell hooks)").argument("<cwd>", "Current working directory").option("--pid <pid>", "Terminal PID").option("--shell <shell>", "Shell type").action(async (cwd, options) => {
897
+ var sessionAutoCommand = new Command6("session-auto").description("Auto-detect project and manage session (used by shell hooks)").argument("<cwd>", "Current working directory").option("--pid <pid>", "Terminal PID").option("--shell <shell>", "Shell type").action(async (cwd, options) => {
2055
898
  const terminalPid = options.pid || String(process.ppid || process.pid);
2056
899
  const shell = options.shell || process.env.SHELL?.split("/").pop() || "unknown";
2057
900
  const sessionsDir = getActiveSessionsDir();
2058
901
  const sessionFile = `${sessionsDir}/${terminalPid}.json`;
2059
902
  let currentSession = null;
2060
- if (existsSync9(sessionFile)) {
903
+ if (existsSync6(sessionFile)) {
2061
904
  try {
2062
- currentSession = JSON.parse(readFileSync6(sessionFile, "utf-8"));
905
+ currentSession = JSON.parse(readFileSync4(sessionFile, "utf-8"));
2063
906
  } catch {
2064
907
  currentSession = null;
2065
908
  }
2066
909
  }
2067
910
  const cacheFile = getTrackedProjectsCacheFile();
2068
911
  let matchedProject = null;
2069
- if (existsSync9(cacheFile)) {
912
+ if (existsSync6(cacheFile)) {
2070
913
  try {
2071
- const cache = JSON.parse(readFileSync6(cacheFile, "utf-8"));
2072
- const normalizedCwd = cwd.toLowerCase().replace(/\/+$/, "");
914
+ const cache = JSON.parse(readFileSync4(cacheFile, "utf-8"));
915
+ const normalizedCwd = cwd.replace(/\\/g, "/").toLowerCase().replace(/\/+$/, "");
2073
916
  for (const project of cache.projects || []) {
2074
- const normalizedPath = project.path.toLowerCase().replace(/\/+$/, "");
917
+ const normalizedPath = project.path.replace(/\\/g, "/").toLowerCase().replace(/\/+$/, "");
2075
918
  if (normalizedCwd === normalizedPath || normalizedCwd.startsWith(normalizedPath + "/")) {
2076
919
  matchedProject = project;
2077
920
  break;
2078
921
  }
2079
922
  }
923
+ if (!matchedProject) {
924
+ try {
925
+ const { getGitInfo } = await import("./git-OGUSYBJS.js");
926
+ const gitInfo = getGitInfo(cwd);
927
+ if (gitInfo.remoteNormalized) {
928
+ for (const project of cache.projects || []) {
929
+ if (project.gitRemoteNormalized === gitInfo.remoteNormalized) {
930
+ matchedProject = project;
931
+ break;
932
+ }
933
+ }
934
+ }
935
+ } catch (error) {
936
+ if (process.env.DEBUG) {
937
+ logger_default.dim(`[skillo] git detection failed: ${error}`);
938
+ }
939
+ }
940
+ }
2080
941
  } catch {
2081
942
  }
2082
943
  }
@@ -2093,7 +954,7 @@ var sessionAutoCommand = new Command8("session-auto").description("Auto-detect p
2093
954
  } catch {
2094
955
  }
2095
956
  try {
2096
- unlinkSync3(sessionFile);
957
+ unlinkSync(sessionFile);
2097
958
  } catch {
2098
959
  }
2099
960
  }
@@ -2102,7 +963,7 @@ var sessionAutoCommand = new Command8("session-auto").description("Auto-detect p
2102
963
  const result = await client.startSession(shell, matchedProject.path);
2103
964
  if (result.success && result.data?.sessionId) {
2104
965
  ensureDirectory(sessionsDir);
2105
- writeFileSync4(sessionFile, JSON.stringify({
966
+ writeFileSync2(sessionFile, JSON.stringify({
2106
967
  sessionId: result.data.sessionId,
2107
968
  projectPath: matchedProject.path,
2108
969
  projectName: matchedProject.name,
@@ -2118,13 +979,14 @@ var sessionAutoCommand = new Command8("session-auto").description("Auto-detect p
2118
979
  });
2119
980
 
2120
981
  // src/commands/claude.ts
2121
- import { Command as Command9 } from "commander";
982
+ import { Command as Command7 } from "commander";
2122
983
  import * as fs from "fs";
2123
984
  import * as path from "path";
2124
985
  import * as os from "os";
2125
986
  import * as readline from "readline";
2126
987
  var projectCache = {
2127
988
  projects: /* @__PURE__ */ new Map(),
989
+ gitRemoteIndex: /* @__PURE__ */ new Map(),
2128
990
  lastFetched: 0
2129
991
  };
2130
992
  var CACHE_TTL_MS = 6e4;
@@ -2154,12 +1016,17 @@ async function loadTrackedProjects() {
2154
1016
  const result = await client.listProjects(true);
2155
1017
  if (result.success && result.data?.projects) {
2156
1018
  projectCache.projects.clear();
1019
+ projectCache.gitRemoteIndex.clear();
2157
1020
  for (const project of result.data.projects) {
2158
1021
  const normalizedPath = normalizePath(project.path);
2159
- projectCache.projects.set(normalizedPath, {
1022
+ const info = {
2160
1023
  tracked: project.trackingEnabled,
2161
1024
  name: project.name
2162
- });
1025
+ };
1026
+ projectCache.projects.set(normalizedPath, info);
1027
+ if (project.gitRemoteNormalized) {
1028
+ projectCache.gitRemoteIndex.set(project.gitRemoteNormalized, info);
1029
+ }
2163
1030
  }
2164
1031
  projectCache.lastFetched = now;
2165
1032
  }
@@ -2179,9 +1046,23 @@ async function isProjectTracked(projectPath) {
2179
1046
  return info;
2180
1047
  }
2181
1048
  }
1049
+ if (projectCache.gitRemoteIndex.size > 0) {
1050
+ try {
1051
+ const { getGitInfo } = await import("./git-OGUSYBJS.js");
1052
+ const gitInfo = getGitInfo(projectPath);
1053
+ if (gitInfo.remoteNormalized) {
1054
+ const remoteMatch = projectCache.gitRemoteIndex.get(gitInfo.remoteNormalized);
1055
+ if (remoteMatch) return remoteMatch;
1056
+ }
1057
+ } catch (error) {
1058
+ if (process.env.DEBUG) {
1059
+ logger_default.dim(`[skillo] git detection failed: ${error}`);
1060
+ }
1061
+ }
1062
+ }
2182
1063
  return { tracked: false };
2183
1064
  }
2184
- var claudeCommand = new Command9("claude").description("Sync and monitor Claude Code activity");
1065
+ var claudeCommand = new Command7("claude").description("Sync and monitor Claude Code activity");
2185
1066
  claudeCommand.command("sync").description("Sync Claude Code history to the platform (only tracked projects)").option("--since <timestamp>", "Only sync entries after this timestamp (ms)").option("--all", "Sync all history (ignore last sync position)").option("--project-path <path>", "Only sync prompts for this project path").option("--session <id>", "Link prompts to this terminal session").option("--session-start <iso>", "Session start time (ISO 8601) for filtering").option("--ignore-tracking", "Sync all projects regardless of tracking status (not recommended)").action(async (options) => {
2186
1067
  const client = getApiClient();
2187
1068
  if (!client.hasApiKey()) {
@@ -2304,7 +1185,7 @@ claudeCommand.command("watch").description("Watch Claude Code history and sync i
2304
1185
  logger_default.error("Not logged in. Run 'skillo login' first.");
2305
1186
  process.exit(1);
2306
1187
  }
2307
- const { ClaudeWatcher } = await import("./claude-watcher-N6GN6WHJ.js");
1188
+ const { ClaudeWatcher } = await import("./claude-watcher-WKGBJYKN.js");
2308
1189
  const interval = parseInt(options.interval, 10);
2309
1190
  const projectFilter = options.projectPath || process.env.SKILLO_PROJECT_PATH;
2310
1191
  if (projectFilter) {
@@ -2398,284 +1279,93 @@ Synced to platform: ${response.data.sessions.length} recent sessions`);
2398
1279
  }
2399
1280
  });
2400
1281
 
2401
- // src/commands/project.ts
2402
- import { Command as Command10 } from "commander";
2403
- import { existsSync as existsSync11, writeFileSync as writeFileSync5 } from "fs";
2404
-
2405
- // src/utils/git.ts
2406
- import { execSync as execSync2 } from "child_process";
2407
- import { join as join5, dirname as dirname2, basename as basename2 } from "path";
2408
- function getGitInfo(projectPath) {
2409
- const result = {
2410
- isGitRepo: false,
2411
- rootPath: null,
2412
- remote: null,
2413
- remoteNormalized: null,
2414
- branch: null,
2415
- projectName: null
2416
- };
1282
+ // src/commands/add.ts
1283
+ import { Command as Command8 } from "commander";
1284
+ import * as fs2 from "fs";
1285
+ import * as path2 from "path";
1286
+ var DEFAULT_API_BASE = "https://www.skillo.one";
1287
+ var addCommand = new Command8("add").description("Install a public skill from the Skillo Marketplace (no login required)").argument("<slug>", "Skill slug (e.g., git-conventional-commits)").option("--api-url <url>", "Override API base URL").action(async (slug, options) => {
1288
+ const config = loadConfig();
1289
+ const baseUrl = options.apiUrl || config.api?.baseUrl?.replace(/\/api\/cli\/?$/, "") || DEFAULT_API_BASE;
1290
+ const downloadUrl = `${baseUrl}/api/marketplace/${encodeURIComponent(slug)}/download`;
1291
+ console.log(`
1292
+ Fetching skill "${slug}" from marketplace...`);
2417
1293
  try {
2418
- const rootPath = execSync2("git rev-parse --show-toplevel", {
2419
- cwd: projectPath,
2420
- encoding: "utf-8",
2421
- stdio: ["pipe", "pipe", "pipe"]
2422
- }).trim();
2423
- if (!rootPath) return result;
2424
- result.isGitRepo = true;
2425
- result.rootPath = rootPath;
2426
- result.projectName = basename2(rootPath);
2427
- try {
2428
- result.branch = execSync2("git rev-parse --abbrev-ref HEAD", {
2429
- cwd: projectPath,
2430
- encoding: "utf-8",
2431
- stdio: ["pipe", "pipe", "pipe"]
2432
- }).trim();
2433
- } catch {
2434
- }
2435
- try {
2436
- result.remote = execSync2("git remote get-url origin", {
2437
- cwd: projectPath,
2438
- encoding: "utf-8",
2439
- stdio: ["pipe", "pipe", "pipe"]
2440
- }).trim();
2441
- } catch {
2442
- try {
2443
- const remotes = execSync2("git remote", {
2444
- cwd: projectPath,
2445
- encoding: "utf-8",
2446
- stdio: ["pipe", "pipe", "pipe"]
2447
- }).trim().split("\n");
2448
- if (remotes.length > 0 && remotes[0]) {
2449
- result.remote = execSync2(`git remote get-url ${remotes[0]}`, {
2450
- cwd: projectPath,
2451
- encoding: "utf-8",
2452
- stdio: ["pipe", "pipe", "pipe"]
2453
- }).trim();
2454
- }
2455
- } catch {
1294
+ const response = await fetch(downloadUrl);
1295
+ if (!response.ok) {
1296
+ if (response.status === 404) {
1297
+ console.error(`
1298
+ Error: Skill "${slug}" not found in the marketplace.`);
1299
+ console.error(` Check the slug and try again.
1300
+ `);
1301
+ process.exit(1);
2456
1302
  }
2457
- }
2458
- if (result.remote) {
2459
- result.remoteNormalized = normalizeGitRemote(result.remote);
2460
- }
2461
- } catch {
2462
- }
2463
- return result;
2464
- }
2465
- function normalizeGitRemote(remoteUrl) {
2466
- let url = remoteUrl.trim();
2467
- url = url.replace(/\.git$/, "");
2468
- const sshMatch = url.match(/^git@([^:]+):(.+)$/);
2469
- if (sshMatch) {
2470
- return `${sshMatch[1]}/${sshMatch[2]}`;
2471
- }
2472
- try {
2473
- let normalized = url.replace(/^(https?|git|ssh):\/\//, "").replace(/^git@/, "");
2474
- normalized = normalized.replace(/^[^@]+@/, "");
2475
- normalized = normalized.replace(/:\d+\//, "/");
2476
- normalized = normalized.replace(/\/+/g, "/");
2477
- normalized = normalized.replace(/^\/|\/$/g, "");
2478
- return normalized.toLowerCase();
2479
- } catch {
2480
- return url.toLowerCase();
2481
- }
2482
- }
2483
-
2484
- // src/commands/project.ts
2485
- import { resolve } from "path";
2486
- var projectCommand = new Command10("project").description("Manage project tracking settings");
2487
- var trackCommand = new Command10("track").description("Enable tracking for the current project").argument("[path]", "Project path (defaults to current directory)").option("-n, --name <name>", "Custom project name").action(async (pathArg, options) => {
2488
- const client = getApiClient();
2489
- logger_default.blank();
2490
- if (!client.hasApiKey()) {
2491
- logger_default.error("Not logged in. Run 'skillo login' first.");
2492
- return;
2493
- }
2494
- const projectPath = resolve(pathArg || process.cwd());
2495
- logger_default.info(`Tracking project: ${projectPath}`);
2496
- logger_default.blank();
2497
- const gitInfo = getGitInfo(projectPath);
2498
- if (gitInfo.isGitRepo) {
2499
- logger_default.dim(`Git repository detected`);
2500
- if (gitInfo.remote) {
2501
- logger_default.dim(`Remote: ${gitInfo.remoteNormalized || gitInfo.remote}`);
2502
- }
2503
- if (gitInfo.branch) {
2504
- logger_default.dim(`Branch: ${gitInfo.branch}`);
2505
- }
2506
- logger_default.blank();
2507
- } else {
2508
- logger_default.warn("No git repository detected in this directory.");
2509
- logger_default.dim("Project will be tracked by path only (no team sharing).");
2510
- logger_default.blank();
2511
- }
2512
- const projectName = options?.name || gitInfo.projectName || projectPath.split("/").pop() || "Unknown";
2513
- try {
2514
- const result = await client.connectProject({
2515
- path: projectPath,
2516
- name: projectName,
2517
- gitRemote: gitInfo.remote || void 0,
2518
- gitRemoteNormalized: gitInfo.remoteNormalized || void 0
2519
- });
2520
- if (result.success) {
2521
- logger_default.success(`Project "${projectName}" is now being tracked!`);
2522
- logger_default.blank();
2523
- logger_default.dim("Your Claude Code prompts in this directory will now be synced.");
2524
- logger_default.dim("Run 'skillo untrack' to stop tracking.");
1303
+ const text = await response.text();
1304
+ let errorMsg = `Request failed with status ${response.status}`;
2525
1305
  try {
2526
- const listResult = await client.listProjects(true);
2527
- if (listResult.success && listResult.data?.projects) {
2528
- const cacheData = {
2529
- updatedAt: Date.now(),
2530
- projects: listResult.data.projects.filter((p) => p.trackingEnabled).map((p) => ({ path: p.path, name: p.name }))
2531
- };
2532
- ensureDirectory(getDataDir());
2533
- writeFileSync5(getTrackedProjectsCacheFile(), JSON.stringify(cacheData, null, 2));
2534
- }
1306
+ const json2 = JSON.parse(text);
1307
+ if (json2.error) errorMsg = json2.error;
2535
1308
  } catch {
2536
1309
  }
2537
- const { running } = isDaemonRunning2();
2538
- if (!running) {
2539
- logger_default.blank();
2540
- const pid = startDaemonProcess();
2541
- if (pid) {
2542
- logger_default.success(`Background sync daemon started (PID: ${pid})`);
2543
- } else {
2544
- logger_default.dim("Tip: Run 'skillo start' to enable background sync.");
2545
- }
2546
- }
2547
- const shellIntegrationDir = getShellIntegrationDir();
2548
- if (!existsSync11(shellIntegrationDir) || !existsSync11(resolve(shellIntegrationDir, "skillo.zsh"))) {
2549
- logger_default.blank();
2550
- logger_default.dim("Tip: Run 'skillo setup-shell' to enable terminal auto-detection.");
2551
- }
2552
- } else {
2553
- logger_default.error("Failed to track project: " + (result.error || "Unknown error"));
2554
- }
2555
- } catch (error) {
2556
- logger_default.error("Failed to track project: " + (error instanceof Error ? error.message : "Unknown error"));
2557
- }
2558
- logger_default.blank();
2559
- });
2560
- var untrackCommand = new Command10("untrack").description("Disable tracking for the current project").argument("[path]", "Project path (defaults to current directory)").action(async (pathArg) => {
2561
- const client = getApiClient();
2562
- logger_default.blank();
2563
- if (!client.hasApiKey()) {
2564
- logger_default.error("Not logged in. Run 'skillo login' first.");
2565
- return;
2566
- }
2567
- const projectPath = resolve(pathArg || process.cwd());
2568
- logger_default.info(`Untracking project: ${projectPath}`);
2569
- try {
2570
- const result = await client.disconnectProject(projectPath);
2571
- if (result.success) {
2572
- logger_default.blank();
2573
- logger_default.success("Project is no longer being tracked.");
2574
- logger_default.blank();
2575
- logger_default.dim("Your Claude Code prompts in this directory will no longer be synced.");
2576
- logger_default.dim("Existing data remains in your account.");
2577
- } else {
2578
- logger_default.blank();
2579
- logger_default.error("Failed to untrack project: " + (result.error || "Unknown error"));
1310
+ console.error(`
1311
+ Error: ${errorMsg}
1312
+ `);
1313
+ process.exit(1);
2580
1314
  }
2581
- } catch (error) {
2582
- logger_default.blank();
2583
- logger_default.error("Failed to untrack project: " + (error instanceof Error ? error.message : "Unknown error"));
2584
- }
2585
- logger_default.blank();
2586
- });
2587
- projectCommand.command("status").description("Show tracking status for the current project").argument("[path]", "Project path (defaults to current directory)").action(async (pathArg) => {
2588
- const client = getApiClient();
2589
- logger_default.blank();
2590
- if (!client.hasApiKey()) {
2591
- logger_default.error("Not logged in. Run 'skillo login' first.");
2592
- return;
2593
- }
2594
- const projectPath = resolve(pathArg || process.cwd());
2595
- logger_default.info(`Project: ${projectPath}`);
2596
- logger_default.blank();
2597
- const gitInfo = getGitInfo(projectPath);
2598
- if (gitInfo.isGitRepo) {
2599
- logger_default.dim(`Git: ${gitInfo.remoteNormalized || gitInfo.remote || "no remote"}`);
2600
- if (gitInfo.branch) {
2601
- logger_default.dim(`Branch: ${gitInfo.branch}`);
1315
+ const json = await response.json();
1316
+ if (!json.success || !json.data) {
1317
+ console.error("\n Error: Unexpected response from server.\n");
1318
+ process.exit(1);
2602
1319
  }
2603
- } else {
2604
- logger_default.dim("Git: not a repository");
2605
- }
2606
- logger_default.blank();
2607
- try {
2608
- const result = await client.getProjectStatus(projectPath);
2609
- if (result.success && result.data) {
2610
- const { connected, connectedAt } = result.data;
2611
- if (connected) {
2612
- logger_default.success("Tracking ENABLED");
2613
- if (connectedAt) {
2614
- logger_default.dim(`Since: ${new Date(connectedAt).toLocaleDateString()}`);
2615
- }
2616
- } else {
2617
- logger_default.warn("Tracking DISABLED");
2618
- logger_default.dim("Run 'skillo track' to enable tracking.");
2619
- }
2620
- } else {
2621
- logger_default.warn("Not tracked");
2622
- logger_default.dim("Run 'skillo track' to enable tracking for this project.");
1320
+ const { name, content, version } = json.data;
1321
+ if (!content) {
1322
+ console.error(`
1323
+ Error: Skill "${slug}" has no content.
1324
+ `);
1325
+ process.exit(1);
2623
1326
  }
1327
+ const skillsDir = getSkillsDir();
1328
+ const skillDir = path2.join(skillsDir, slug);
1329
+ ensureDirectory(skillsDir);
1330
+ ensureDirectory(skillDir);
1331
+ const skillPath = path2.join(skillDir, "SKILL.md");
1332
+ fs2.writeFileSync(skillPath, content, "utf-8");
1333
+ console.log(`
1334
+ Installed: ${name} (v${version || "1.0.0"})`);
1335
+ console.log(` Location: ${skillPath}`);
1336
+ console.log(`
1337
+ The skill is now available in Claude Code.
1338
+ `);
2624
1339
  } catch (error) {
2625
- logger_default.error("Failed to get project status: " + (error instanceof Error ? error.message : "Unknown error"));
2626
- }
2627
- logger_default.blank();
2628
- });
2629
- projectCommand.command("list").alias("ls").description("List all projects (tracked and untracked)").option("-a, --all", "Include untracked projects").action(async (options) => {
2630
- const client = getApiClient();
2631
- logger_default.blank();
2632
- if (!client.hasApiKey()) {
2633
- logger_default.error("Not logged in. Run 'skillo login' first.");
2634
- return;
2635
- }
2636
- try {
2637
- const result = await client.listProjects(options?.all || false);
2638
- if (result.success && result.data) {
2639
- const { projects, totalTracked, totalProjects } = result.data;
2640
- logger_default.info(`Projects (${totalTracked} tracked / ${totalProjects} total)`);
2641
- logger_default.blank();
2642
- if (projects.length === 0) {
2643
- logger_default.dim("No projects found.");
2644
- logger_default.dim("Run 'skillo track' in a project directory to start tracking.");
2645
- } else {
2646
- for (const project of projects) {
2647
- const status = project.trackingEnabled ? "\u25CF" : "\u25CB";
2648
- const statusColor = project.trackingEnabled ? "\x1B[32m" : "\x1B[90m";
2649
- const resetColor = "\x1B[0m";
2650
- console.log(` ${statusColor}${status}${resetColor} ${project.name}`);
2651
- logger_default.dim(` ${project.path}`);
2652
- if (project.gitRemoteNormalized) {
2653
- logger_default.dim(` ${project.gitRemoteNormalized}`);
2654
- }
2655
- }
1340
+ if (error instanceof Error) {
1341
+ if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
1342
+ console.error("\n Error: Cannot connect to Skillo platform.");
1343
+ console.error(" Check your network connection and try again.\n");
1344
+ process.exit(1);
2656
1345
  }
1346
+ console.error(`
1347
+ Error: ${error.message}
1348
+ `);
2657
1349
  } else {
2658
- logger_default.error("Failed to list projects: " + (result.error || "Unknown error"));
1350
+ console.error("\n Error: An unknown error occurred.\n");
2659
1351
  }
2660
- } catch (error) {
2661
- logger_default.error("Failed to list projects: " + (error instanceof Error ? error.message : "Unknown error"));
1352
+ process.exit(1);
2662
1353
  }
2663
- logger_default.blank();
2664
1354
  });
2665
1355
 
2666
1356
  // src/commands/auth.ts
2667
- import { Command as Command11 } from "commander";
1357
+ import { Command as Command9 } from "commander";
2668
1358
  import { hostname } from "os";
2669
1359
  async function openBrowser(url) {
2670
1360
  try {
2671
1361
  const { exec } = await import("child_process");
2672
1362
  const { promisify } = await import("util");
2673
1363
  const execAsync = promisify(exec);
2674
- const platform2 = process.platform;
1364
+ const platform = process.platform;
2675
1365
  let command;
2676
- if (platform2 === "darwin") {
1366
+ if (platform === "darwin") {
2677
1367
  command = `open "${url}"`;
2678
- } else if (platform2 === "win32") {
1368
+ } else if (platform === "win32") {
2679
1369
  command = `start "" "${url}"`;
2680
1370
  } else {
2681
1371
  command = `xdg-open "${url}"`;
@@ -2687,11 +1377,11 @@ async function openBrowser(url) {
2687
1377
  }
2688
1378
  }
2689
1379
  function sleep(ms) {
2690
- return new Promise((resolve2) => setTimeout(resolve2, ms));
1380
+ return new Promise((resolve) => setTimeout(resolve, ms));
2691
1381
  }
2692
1382
  async function autoStartDaemon() {
2693
1383
  try {
2694
- const { running } = isDaemonRunning2();
1384
+ const { running } = isDaemonRunning();
2695
1385
  if (!running) {
2696
1386
  const pid = startDaemonProcess();
2697
1387
  if (pid) {
@@ -2705,7 +1395,7 @@ async function autoStartDaemon() {
2705
1395
  } catch {
2706
1396
  }
2707
1397
  }
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) => {
1398
+ var loginCommand = new Command9("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) => {
2709
1399
  const client = getApiClient();
2710
1400
  logger_default.blank();
2711
1401
  if (options?.url) {
@@ -2825,7 +1515,7 @@ var loginCommand = new Command11("login").description("Login to Skillo platform"
2825
1515
  logger_default.blank();
2826
1516
  logger_default.error("Authorization timed out. Please try again.");
2827
1517
  });
2828
- var logoutCommand = new Command11("logout").description("Logout from Skillo platform").action(async () => {
1518
+ var logoutCommand = new Command9("logout").description("Logout from Skillo platform").action(async () => {
2829
1519
  const client = getApiClient();
2830
1520
  logger_default.blank();
2831
1521
  if (!client.hasApiKey()) {
@@ -2837,7 +1527,7 @@ var logoutCommand = new Command11("logout").description("Logout from Skillo plat
2837
1527
  logger_default.blank();
2838
1528
  logger_default.dim("Your local data remains intact. Use 'skillo login' to reconnect.");
2839
1529
  });
2840
- var whoamiCommand = new Command11("whoami").description("Show current login status").action(async () => {
1530
+ var whoamiCommand = new Command9("whoami").description("Show current login status").action(async () => {
2841
1531
  const client = getApiClient();
2842
1532
  logger_default.blank();
2843
1533
  if (!client.hasApiKey()) {
@@ -2868,10 +1558,10 @@ var whoamiCommand = new Command11("whoami").description("Show current login stat
2868
1558
  });
2869
1559
 
2870
1560
  // src/commands/sync.ts
2871
- import { existsSync as existsSync12, writeFileSync as writeFileSync6, mkdirSync as mkdirSync2 } from "fs";
2872
- import { join as join6 } from "path";
2873
- import { Command as Command12 } from "commander";
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(
1561
+ import { existsSync as existsSync8, writeFileSync as writeFileSync4, mkdirSync as mkdirSync2 } from "fs";
1562
+ import { join as join5 } from "path";
1563
+ import { Command as Command10 } from "commander";
1564
+ var syncCommand = new Command10("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(
2875
1565
  async (options) => {
2876
1566
  const client = getApiClient();
2877
1567
  logger_default.blank();
@@ -2885,7 +1575,7 @@ var syncCommand = new Command12("sync").description("Sync data with Skillo platf
2885
1575
  const syncSkills = options.skills || !options.patterns && !options.skills && !options.commands;
2886
1576
  const syncCommands = options.commands || !options.patterns && !options.skills && !options.commands;
2887
1577
  const dbPath = getDbPath();
2888
- if (!existsSync12(dbPath)) {
1578
+ if (!existsSync8(dbPath)) {
2889
1579
  logger_default.error("Database not found. Run 'skillo init' first.");
2890
1580
  return;
2891
1581
  }
@@ -2987,12 +1677,12 @@ async function pullSkills(skills) {
2987
1677
  let downloaded = 0;
2988
1678
  for (const skill of skills) {
2989
1679
  if (!skill.content) continue;
2990
- const skillDir = join6(skillsDir, skill.slug);
2991
- const skillFile = join6(skillDir, "SKILL.md");
2992
- if (!existsSync12(skillDir)) {
1680
+ const skillDir = join5(skillsDir, skill.slug);
1681
+ const skillFile = join5(skillDir, "SKILL.md");
1682
+ if (!existsSync8(skillDir)) {
2993
1683
  mkdirSync2(skillDir, { recursive: true });
2994
1684
  }
2995
- writeFileSync6(skillFile, skill.content, "utf-8");
1685
+ writeFileSync4(skillFile, skill.content, "utf-8");
2996
1686
  downloaded++;
2997
1687
  logger_default.dim(` Downloaded: ${skill.name}`);
2998
1688
  }
@@ -3000,15 +1690,15 @@ async function pullSkills(skills) {
3000
1690
  }
3001
1691
 
3002
1692
  // 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 () => {
1693
+ import { Command as Command11 } from "commander";
1694
+ var trayCommand = new Command11("tray").description("Start the system tray icon").action(async () => {
3005
1695
  try {
3006
- const { startTray } = await import("./tray-YOL4R2RH.js");
1696
+ const { startTray } = await import("./tray-WKFGUUTO.js");
3007
1697
  await startTray();
3008
1698
  } catch (error) {
3009
1699
  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");
1700
+ logger_default.error("System tray not available. Native tray helper could not be loaded.");
1701
+ logger_default.dim("Try reinstalling: npm install -g skillo");
3012
1702
  } else {
3013
1703
  logger_default.error(`Tray error: ${error instanceof Error ? error.message : error}`);
3014
1704
  }
@@ -3017,8 +1707,8 @@ var trayCommand = new Command13("tray").description("Start the system tray icon"
3017
1707
  });
3018
1708
 
3019
1709
  // src/cli.ts
3020
- var VERSION = "0.2.5";
3021
- var program = new Command14();
1710
+ var VERSION = "0.2.7";
1711
+ var program = new Command12();
3022
1712
  program.name("skillo").description(
3023
1713
  `Skillo - Learn workflows by observation, not explanation.
3024
1714
 
@@ -3029,8 +1719,10 @@ Quick Start:
3029
1719
  $ skillo init # Initialize Skillo
3030
1720
  $ skillo login <key> # Connect to Skillo platform
3031
1721
  $ skillo shell # Launch a tracked shell session
3032
- $ skillo sync # Sync skills from platform`
1722
+ $ skillo sync # Sync skills from platform
1723
+ $ skillo add <slug> # Install a public skill from Marketplace`
3033
1724
  ).version(VERSION, "-v, --version", "Show version number").option("--debug", "Enable debug output");
1725
+ program.addCommand(addCommand);
3034
1726
  program.addCommand(initCommand);
3035
1727
  program.addCommand(statusCommand);
3036
1728
  program.addCommand(configCommand);