skillo 0.2.6 → 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 (68) 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 +495 -1846
  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 +338 -71
  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-WKFGUUTO.js +346 -0
  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-UCAI2U2C.js +0 -408
  64. package/dist/tray-UCAI2U2C.js.map +0 -1
  65. /package/dist/{api-client-KUQW7FSC.js.map → api-client-BF6GDR7Q.js.map} +0 -0
  66. /package/dist/{config-P5EM5L7N.js.map → config-ZOKAP2LJ.js.map} +0 -0
  67. /package/dist/{database-F3BFFZKG.js.map → daemon-6DTCMOJB.js.map} +0 -0
  68. /package/dist/{paths-INOKEM66.js.map → database-KQY5OSCS.js.map} +0 -0
@@ -0,0 +1,727 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ logger_default
4
+ } from "./chunk-VAQ73XPE.js";
5
+ import {
6
+ SkilloDatabase
7
+ } from "./chunk-73NUWYUO.js";
8
+ import {
9
+ loadConfig
10
+ } from "./chunk-QUXHHRRK.js";
11
+ import {
12
+ ensureDirectory,
13
+ getConfigFile,
14
+ getDataDir
15
+ } from "./chunk-63FVALWX.js";
16
+
17
+ // src/commands/shell.ts
18
+ import { existsSync, writeFileSync, readFileSync, appendFileSync } from "fs";
19
+ import { spawn } from "child_process";
20
+ import { homedir } from "os";
21
+ import { join } from "path";
22
+ import { Command } from "commander";
23
+ var UNSYNCED_REMINDER_THRESHOLD = 50;
24
+ async function checkLoginStatus() {
25
+ try {
26
+ const { getApiClient } = await import("./api-client-BF6GDR7Q.js");
27
+ const client = getApiClient();
28
+ return client.hasApiKey();
29
+ } catch {
30
+ return false;
31
+ }
32
+ }
33
+ function getUnsyncedCounterPath() {
34
+ return join(getDataDir(), "unsynced-count");
35
+ }
36
+ function incrementUnsyncedCounter() {
37
+ const counterPath = getUnsyncedCounterPath();
38
+ let count = 0;
39
+ try {
40
+ if (existsSync(counterPath)) {
41
+ const parsed = parseInt(readFileSync(counterPath, "utf-8").trim(), 10);
42
+ count = Number.isFinite(parsed) && parsed >= 0 ? parsed : 0;
43
+ }
44
+ } catch {
45
+ }
46
+ count++;
47
+ try {
48
+ writeFileSync(counterPath, String(count), "utf-8");
49
+ } catch {
50
+ }
51
+ return count;
52
+ }
53
+ function resetUnsyncedCounter() {
54
+ try {
55
+ writeFileSync(getUnsyncedCounterPath(), "0", "utf-8");
56
+ } catch {
57
+ }
58
+ }
59
+ function normalizeCommand(cmd) {
60
+ const variables = {};
61
+ let normalized = cmd;
62
+ let varIndex = 0;
63
+ normalized = normalized.replace(/(?:^|\s)(\/[\w\-.\/]+)/g, (match, path) => {
64
+ const key = `$PATH${varIndex++}`;
65
+ variables[key] = path;
66
+ return match.replace(path, key);
67
+ });
68
+ normalized = normalized.replace(
69
+ /https?:\/\/[^\s]+/g,
70
+ (match) => {
71
+ const key = `$URL${varIndex++}`;
72
+ variables[key] = match;
73
+ return key;
74
+ }
75
+ );
76
+ normalized = normalized.replace(/\b\d{4,}\b/g, (match) => {
77
+ const key = `$NUM${varIndex++}`;
78
+ variables[key] = match;
79
+ return key;
80
+ });
81
+ normalized = normalized.replace(/"[^"]+"/g, (match) => {
82
+ const key = `$STR${varIndex++}`;
83
+ variables[key] = match;
84
+ return key;
85
+ });
86
+ normalized = normalized.replace(/'[^']+'/g, (match) => {
87
+ const key = `$STR${varIndex++}`;
88
+ variables[key] = match;
89
+ return key;
90
+ });
91
+ return { normalized: normalized.trim(), variables };
92
+ }
93
+ var shellCommand = new Command("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) => {
94
+ if (!existsSync(getConfigFile())) {
95
+ logger_default.error("Skillo not initialized. Run 'skillo init' first.");
96
+ process.exit(1);
97
+ }
98
+ const config = loadConfig();
99
+ let shell = options.shell || config.defaultShell;
100
+ if (!shell) {
101
+ if (process.platform === "win32") {
102
+ shell = process.env.COMSPEC || "cmd.exe";
103
+ } else {
104
+ shell = process.env.SHELL || "/bin/bash";
105
+ }
106
+ }
107
+ const projectPath = options.project || process.cwd();
108
+ logger_default.blank();
109
+ logger_default.info("Starting tracked shell session...");
110
+ logger_default.dim(`Shell: ${shell}`);
111
+ logger_default.dim(`Project: ${projectPath}`);
112
+ logger_default.blank();
113
+ logger_default.dim("All commands will be tracked. Type 'exit' to end the session.");
114
+ logger_default.blank();
115
+ const db = new SkilloDatabase();
116
+ await db.initialize();
117
+ const sessionId = await db.createSession(shell, projectPath);
118
+ let commandCount = 0;
119
+ let currentCommand = "";
120
+ let commandStartTime = null;
121
+ const shellArgs = process.platform === "win32" ? [] : ["-i"];
122
+ const child = spawn(shell, shellArgs, {
123
+ stdio: ["inherit", "inherit", "inherit"],
124
+ shell: false,
125
+ cwd: projectPath,
126
+ env: {
127
+ ...process.env,
128
+ SKILLO_SESSION: sessionId,
129
+ SKILLO_TRACKING: "1"
130
+ }
131
+ });
132
+ child.on("exit", async (code) => {
133
+ await db.endSession(sessionId);
134
+ db.close();
135
+ logger_default.blank();
136
+ logger_default.success(`Session ended. Tracked ${commandCount} command(s).`);
137
+ logger_default.dim(`Session ID: ${sessionId.slice(0, 8)}`);
138
+ logger_default.blank();
139
+ process.exit(code || 0);
140
+ });
141
+ child.on("error", async (error) => {
142
+ logger_default.error(`Failed to start shell: ${error.message}`);
143
+ await db.endSession(sessionId);
144
+ db.close();
145
+ process.exit(1);
146
+ });
147
+ });
148
+ var recordCommand = new Command("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(
149
+ async (command, options) => {
150
+ const cwd = options.cwd || process.cwd();
151
+ const exitCode = options.exitCode ? parseInt(options.exitCode, 10) : null;
152
+ const durationMs = options.duration ? parseInt(options.duration, 10) : null;
153
+ const sessionId = options.session || process.env.SKILLO_SESSION || "default";
154
+ const { normalized, variables } = normalizeCommand(command);
155
+ const db = new SkilloDatabase();
156
+ await db.initialize();
157
+ await db.addCommand({
158
+ command,
159
+ normalized,
160
+ cwd,
161
+ sessionId,
162
+ exitCode,
163
+ durationMs,
164
+ variables: Object.keys(variables).length > 0 ? variables : null
165
+ });
166
+ db.close();
167
+ if (options.sync !== false) {
168
+ try {
169
+ const isLoggedIn = await checkLoginStatus();
170
+ if (isLoggedIn) {
171
+ const { getApiClient } = await import("./api-client-BF6GDR7Q.js");
172
+ const client = getApiClient();
173
+ await client.syncCommands([{
174
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
175
+ command,
176
+ normalized,
177
+ cwd,
178
+ exitCode,
179
+ durationMs,
180
+ sessionId: sessionId !== "default" ? sessionId : void 0,
181
+ variables: Object.keys(variables).length > 0 ? variables : null
182
+ }]);
183
+ resetUnsyncedCounter();
184
+ } else {
185
+ const count = incrementUnsyncedCounter();
186
+ if (count === UNSYNCED_REMINDER_THRESHOLD) {
187
+ process.stderr.write(
188
+ `\x1B[33m[!] You have ${UNSYNCED_REMINDER_THRESHOLD}+ commands tracked locally but not synced. Run 'skillo login' to enable cloud sync.\x1B[0m
189
+ `
190
+ );
191
+ }
192
+ }
193
+ } catch {
194
+ }
195
+ }
196
+ }
197
+ );
198
+ var setupShellCommand = new Command("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) => {
199
+ logger_default.blank();
200
+ const home = homedir();
201
+ const dataDir = getDataDir();
202
+ ensureDirectory(dataDir);
203
+ let shell = null;
204
+ if (options.bash) shell = "bash";
205
+ else if (options.zsh) shell = "zsh";
206
+ else if (options.powershell) shell = "powershell";
207
+ else if (options.fish) shell = "fish";
208
+ else {
209
+ if (process.platform === "win32") {
210
+ if (process.env.MSYSTEM || process.env.SHELL?.includes("bash") || process.env.TERM === "mintty") {
211
+ shell = "bash";
212
+ } else {
213
+ shell = "powershell";
214
+ }
215
+ } else {
216
+ const currentShell = process.env.SHELL || "";
217
+ if (currentShell.includes("zsh")) shell = "zsh";
218
+ else if (currentShell.includes("fish")) shell = "fish";
219
+ else shell = "bash";
220
+ }
221
+ logger_default.info(`Detected shell: ${shell}`);
222
+ }
223
+ if (options.uninstall) {
224
+ logger_default.info(`Removing ${shell} integration...`);
225
+ await uninstallShellIntegration(shell, home);
226
+ logger_default.success("Shell integration removed.");
227
+ logger_default.dim("Restart your terminal for changes to take effect.");
228
+ logger_default.blank();
229
+ return;
230
+ }
231
+ const isLoggedIn = await checkLoginStatus();
232
+ logger_default.info(`Setting up ${shell} integration...`);
233
+ logger_default.blank();
234
+ await installShellIntegration(shell, home, dataDir);
235
+ logger_default.blank();
236
+ logger_default.success("Shell integration installed!");
237
+ logger_default.blank();
238
+ if (!isLoggedIn) {
239
+ logger_default.warn("You're not logged in. Commands will be tracked locally only.");
240
+ logger_default.dim("Run 'skillo login' to enable cloud sync.");
241
+ logger_default.blank();
242
+ }
243
+ logger_default.dim("Restart your terminal or run:");
244
+ if (shell === "powershell") {
245
+ logger_default.highlight(" . $PROFILE");
246
+ } else if (shell === "bash") {
247
+ logger_default.highlight(" source ~/.bashrc");
248
+ } else if (shell === "zsh") {
249
+ logger_default.highlight(" source ~/.zshrc");
250
+ } else if (shell === "fish") {
251
+ logger_default.highlight(" source ~/.config/fish/config.fish");
252
+ }
253
+ logger_default.blank();
254
+ if (isLoggedIn) {
255
+ logger_default.dim("All commands you run will now be tracked and synced to Skillo.");
256
+ } else {
257
+ logger_default.dim("All commands will be tracked locally. Login to sync to Skillo.");
258
+ }
259
+ logger_default.blank();
260
+ });
261
+ function generatePosixSyncStatusLines() {
262
+ return [
263
+ "# Show sync status on startup (respects SKILLO_CONFIG_DIR and XDG_CONFIG_HOME)",
264
+ 'if [ -n "$SKILLO_CONFIG_DIR" ]; then',
265
+ ' _skillo_config="$SKILLO_CONFIG_DIR/config.yaml"',
266
+ 'elif [ -n "$XDG_CONFIG_HOME" ]; then',
267
+ ' _skillo_config="$XDG_CONFIG_HOME/skillo/config.yaml"',
268
+ "else",
269
+ ' _skillo_config="$HOME/.config/skillo/config.yaml"',
270
+ "fi",
271
+ 'if [ -f "$_skillo_config" ] && grep -q "api_key:" "$_skillo_config" 2>/dev/null; then',
272
+ ' echo -e "\\033[90mSkillo: Command tracking enabled (syncing to platform)\\033[0m"',
273
+ "else",
274
+ ` echo -e "\\033[33mSkillo: Command tracking enabled (local only \u2014 run 'skillo login' to sync)\\033[0m"`,
275
+ "fi"
276
+ ];
277
+ }
278
+ async function installShellIntegration(shell, home, dataDir) {
279
+ const scriptPath = join(dataDir, "shell-integration");
280
+ ensureDirectory(scriptPath);
281
+ if (shell === "powershell") {
282
+ const psScript = `# Skillo CLI Integration
283
+ # Records commands to Skillo platform
284
+ # Works in two modes:
285
+ # 1. Platform mode: Uses SKILLO_SESSION and SKILLO_USER_ID env vars (set by platform launch)
286
+ # 2. Standalone mode: Uses skillo CLI which handles auth via API key
287
+
288
+ $Global:SkilloLastHistoryId = 0
289
+ $Global:SkilloStandaloneSessionId = $null
290
+ $Global:SkilloSessionStartTime = $null
291
+
292
+ # Check if launched from platform (has session/user env vars) or standalone
293
+ $Global:SkilloPlatformMode = $false
294
+ if ($env:SKILLO_SESSION -and $env:SKILLO_USER_ID) {
295
+ $Global:SkilloPlatformMode = $true
296
+ $Global:SkilloSessionId = $env:SKILLO_SESSION
297
+ $Global:SkilloUserId = $env:SKILLO_USER_ID
298
+ $Global:SkilloBaseUrl = if ($env:SKILLO_API_URL) { $env:SKILLO_API_URL } else { 'http://localhost:3000' }
299
+ }
300
+
301
+ function Send-SkilloCommandPlatform {
302
+ param($Command, $Duration, $ExitCode, $Cwd)
303
+
304
+ try {
305
+ $body = @{
306
+ type = 'commands'
307
+ data = @(@{
308
+ timestamp = (Get-Date).ToString('o')
309
+ command = $Command
310
+ normalized = $Command
311
+ cwd = $Cwd
312
+ exitCode = $ExitCode
313
+ durationMs = $Duration
314
+ sessionId = $Global:SkilloSessionId
315
+ })
316
+ } | ConvertTo-Json -Depth 3 -Compress
317
+
318
+ $uri = "$($Global:SkilloBaseUrl)/api/cli/sync"
319
+
320
+ $webRequest = [System.Net.HttpWebRequest]::Create($uri)
321
+ $webRequest.Method = 'POST'
322
+ $webRequest.ContentType = 'application/json'
323
+ $webRequest.Headers.Add('x-skillo-session', $Global:SkilloSessionId)
324
+ $webRequest.Headers.Add('x-skillo-user-id', $Global:SkilloUserId)
325
+ $webRequest.Timeout = 5000
326
+
327
+ $bytes = [System.Text.Encoding]::UTF8.GetBytes($body)
328
+ $webRequest.ContentLength = $bytes.Length
329
+ $stream = $webRequest.GetRequestStream()
330
+ $stream.Write($bytes, 0, $bytes.Length)
331
+ $stream.Close()
332
+
333
+ $response = $webRequest.GetResponse()
334
+ $response.Close()
335
+ } catch {
336
+ # Silently ignore errors
337
+ }
338
+ }
339
+
340
+ function Send-SkilloCommandCLI {
341
+ param($Command, $Duration, $ExitCode, $Cwd)
342
+
343
+ # Create session on first command if not exists
344
+ if (-not $Global:SkilloStandaloneSessionId) {
345
+ $Global:SkilloSessionStartTime = (Get-Date).ToString('o')
346
+ try {
347
+ $result = & skillo session start --shell "PowerShell" 2>$null
348
+ if ($result -match 'Session ID: ([a-f0-9-]+)') {
349
+ $Global:SkilloStandaloneSessionId = $Matches[1]
350
+ # Write PID file for session cleanup
351
+ $_pidDir = Join-Path (Join-Path $env:USERPROFILE '.skillo') 'active-sessions'
352
+ if (-not (Test-Path $_pidDir)) { New-Item -ItemType Directory -Path $_pidDir -Force | Out-Null }
353
+ $_json = '{"sessionId":"' + $Global:SkilloStandaloneSessionId + '","pid":' + $PID + '}'
354
+ [IO.File]::WriteAllText((Join-Path $_pidDir "$PID.json"), $_json)
355
+ }
356
+ } catch {
357
+ # If session command doesn't exist, generate a local ID
358
+ $Global:SkilloStandaloneSessionId = [guid]::NewGuid().ToString()
359
+ }
360
+ }
361
+
362
+ # Run skillo record in background with session ID
363
+ Start-Job -ScriptBlock {
364
+ param($cmd, $cwd, $exit, $dur, $sessionId)
365
+ & skillo record $cmd --cwd $cwd --exit-code $exit --duration $dur --session $sessionId 2>$null
366
+ } -ArgumentList $Command, $Cwd, $ExitCode, $Duration, $Global:SkilloStandaloneSessionId | Out-Null
367
+ }
368
+
369
+ # Override prompt to capture commands after execution
370
+ $Global:SkilloOriginalPrompt = $function:prompt
371
+ function Global:prompt {
372
+ $lastCmd = Get-History -Count 1 -ErrorAction SilentlyContinue
373
+
374
+ if ($lastCmd -and $lastCmd.Id -gt $Global:SkilloLastHistoryId) {
375
+ $Global:SkilloLastHistoryId = $lastCmd.Id
376
+
377
+ $duration = 0
378
+ if ($lastCmd.EndExecutionTime -and $lastCmd.StartExecutionTime) {
379
+ $duration = [int]($lastCmd.EndExecutionTime - $lastCmd.StartExecutionTime).TotalMilliseconds
380
+ }
381
+ $exit = if ($null -eq $LASTEXITCODE) { 0 } else { $LASTEXITCODE }
382
+ $cwd = (Get-Location).Path
383
+
384
+ if ($Global:SkilloPlatformMode) {
385
+ Send-SkilloCommandPlatform -Command $lastCmd.CommandLine -Duration $duration -ExitCode $exit -Cwd $cwd
386
+ } else {
387
+ Send-SkilloCommandCLI -Command $lastCmd.CommandLine -Duration $duration -ExitCode $exit -Cwd $cwd
388
+ }
389
+ }
390
+
391
+ & $Global:SkilloOriginalPrompt
392
+ }
393
+
394
+ # Register exit handler to end session when terminal closes
395
+ $null = Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action {
396
+ if ($Global:SkilloPlatformMode -and $Global:SkilloSessionId) {
397
+ # End platform session
398
+ try {
399
+ $body = @{ sessionId = $Global:SkilloSessionId } | ConvertTo-Json -Compress
400
+ $uri = "$($Global:SkilloBaseUrl)/api/sessions"
401
+
402
+ $webRequest = [System.Net.HttpWebRequest]::Create($uri)
403
+ $webRequest.Method = 'PATCH'
404
+ $webRequest.ContentType = 'application/json'
405
+ $webRequest.Headers.Add('x-skillo-session', $Global:SkilloSessionId)
406
+ $webRequest.Headers.Add('x-skillo-user-id', $Global:SkilloUserId)
407
+ $webRequest.Timeout = 3000
408
+
409
+ $bytes = [System.Text.Encoding]::UTF8.GetBytes($body)
410
+ $webRequest.ContentLength = $bytes.Length
411
+ $stream = $webRequest.GetRequestStream()
412
+ $stream.Write($bytes, 0, $bytes.Length)
413
+ $stream.Close()
414
+
415
+ $response = $webRequest.GetResponse()
416
+ $response.Close()
417
+ } catch {
418
+ # Ignore errors on exit
419
+ }
420
+ } elseif ($Global:SkilloStandaloneSessionId) {
421
+ # End standalone session via CLI
422
+ try {
423
+ & skillo session end --session $Global:SkilloStandaloneSessionId 2>$null
424
+ } catch {
425
+ # Ignore errors on exit
426
+ }
427
+ # Clean up PID file
428
+ $_pidFile = Join-Path (Join-Path (Join-Path $env:USERPROFILE '.skillo') 'active-sessions') "$PID.json"
429
+ if (Test-Path $_pidFile) { Remove-Item $_pidFile -Force -ErrorAction SilentlyContinue }
430
+ }
431
+ }
432
+
433
+ # Show sync status on startup
434
+ if ($Global:SkilloPlatformMode) {
435
+ Write-Host "Skillo: Command tracking enabled (syncing to platform)" -ForegroundColor DarkGray
436
+ } else {
437
+ # Check if logged in by looking for api_key in config (respects SKILLO_CONFIG_DIR and XDG_CONFIG_HOME)
438
+ if ($env:SKILLO_CONFIG_DIR) {
439
+ $skilloConfigPath = Join-Path $env:SKILLO_CONFIG_DIR "config.yaml"
440
+ } elseif ($env:XDG_CONFIG_HOME) {
441
+ $skilloConfigPath = Join-Path $env:XDG_CONFIG_HOME "skillo" "config.yaml"
442
+ } else {
443
+ $skilloConfigPath = Join-Path $HOME ".config" "skillo" "config.yaml"
444
+ }
445
+ $skilloLoggedIn = $false
446
+ if (Test-Path $skilloConfigPath) {
447
+ $skilloLoggedIn = (Select-String -Path $skilloConfigPath -Pattern "api_key:" -Quiet) -eq $true
448
+ }
449
+ if ($skilloLoggedIn) {
450
+ Write-Host "Skillo: Command tracking enabled (syncing to platform)" -ForegroundColor DarkGray
451
+ } else {
452
+ Write-Host "Skillo: Command tracking enabled (local only - run 'skillo login' to sync)" -ForegroundColor Yellow
453
+ }
454
+ }
455
+ `;
456
+ const psScriptFile = join(scriptPath, "skillo.ps1");
457
+ writeFileSync(psScriptFile, psScript, "utf-8");
458
+ logger_default.success(`Created ${psScriptFile}`);
459
+ const profilePath = join(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
460
+ const profileDir = join(home, "Documents", "WindowsPowerShell");
461
+ ensureDirectory(profileDir);
462
+ const sourceLine = `
463
+ # Skillo CLI Integration
464
+ . "${psScriptFile}"
465
+ `;
466
+ if (existsSync(profilePath)) {
467
+ const content = readFileSync(profilePath, "utf-8");
468
+ if (!content.includes("Skillo CLI Integration")) {
469
+ appendFileSync(profilePath, sourceLine);
470
+ logger_default.success("Added to PowerShell profile");
471
+ } else {
472
+ logger_default.dim("Already in PowerShell profile");
473
+ }
474
+ } else {
475
+ writeFileSync(profilePath, sourceLine, "utf-8");
476
+ logger_default.success("Created PowerShell profile");
477
+ }
478
+ } else if (shell === "bash") {
479
+ const bashScript = [
480
+ "# Skillo CLI Integration",
481
+ "# Records commands and auto-detects tracked projects",
482
+ "",
483
+ '_skillo_session="${SKILLO_SESSION:-default}"',
484
+ "_skillo_last_cwd=",
485
+ "",
486
+ "_skillo_preexec() {",
487
+ ' _skillo_cmd="$1"',
488
+ " _skillo_start_time=$(date +%s)",
489
+ "}",
490
+ "",
491
+ "_skillo_check_project() {",
492
+ " # Fast path: skip if CWD unchanged",
493
+ ' if [ "$PWD" = "$_skillo_last_cwd" ]; then',
494
+ " return",
495
+ " fi",
496
+ ' _skillo_last_cwd="$PWD"',
497
+ "",
498
+ " # Run session-auto (handles create/end via cache file)",
499
+ " local result _pid",
500
+ " _pid=$(cat /proc/$$/winpid 2>/dev/null || echo $$)",
501
+ ' result=$(skillo session-auto "$PWD" --pid $_pid --shell bash 2>/dev/null)',
502
+ ' if [ -n "$result" ]; then',
503
+ ' export SKILLO_SESSION="$result"',
504
+ ' _skillo_session="$result"',
505
+ " fi",
506
+ "}",
507
+ "",
508
+ "_skillo_precmd() {",
509
+ " local exit_code=$?",
510
+ "",
511
+ " # Check for project change",
512
+ " _skillo_check_project",
513
+ "",
514
+ ' if [ -n "$_skillo_cmd" ]; then',
515
+ " local duration=0",
516
+ ' if [ -n "$_skillo_start_time" ]; then',
517
+ " local end_time=$(date +%s)",
518
+ " duration=$(( (end_time - _skillo_start_time) * 1000 ))",
519
+ " fi",
520
+ ' (skillo record "$_skillo_cmd" --cwd "$PWD" --exit-code "$exit_code" --duration "$duration" --session "$_skillo_session" >/dev/null &)',
521
+ ' _skillo_cmd=""',
522
+ " fi",
523
+ "}",
524
+ "",
525
+ "# Clean up session on terminal exit",
526
+ "_skillo_cleanup() {",
527
+ ' if [ -n "$SKILLO_SESSION" ] && [ "$SKILLO_SESSION" != "default" ]; then',
528
+ ' skillo session end --session "$SKILLO_SESSION" >/dev/null 2>&1',
529
+ " fi",
530
+ "}",
531
+ "trap _skillo_cleanup EXIT",
532
+ "",
533
+ "# Use DEBUG trap for preexec",
534
+ `trap '_skillo_preexec "$BASH_COMMAND"' DEBUG`,
535
+ "",
536
+ "# Add precmd to PROMPT_COMMAND",
537
+ 'if [[ ! "$PROMPT_COMMAND" =~ _skillo_precmd ]]; then',
538
+ ' PROMPT_COMMAND="_skillo_precmd${PROMPT_COMMAND:+;$PROMPT_COMMAND}"',
539
+ "fi",
540
+ "",
541
+ "# Initial project check",
542
+ "_skillo_check_project",
543
+ "",
544
+ ...generatePosixSyncStatusLines()
545
+ ].join("\n");
546
+ const bashScriptFile = join(scriptPath, "skillo.bash");
547
+ writeFileSync(bashScriptFile, bashScript, "utf-8");
548
+ logger_default.success(`Created ${bashScriptFile}`);
549
+ const bashrcPath = join(home, ".bashrc");
550
+ const sourceLine = `
551
+ # Skillo CLI Integration
552
+ [ -f "${bashScriptFile}" ] && source "${bashScriptFile}"
553
+ `;
554
+ if (existsSync(bashrcPath)) {
555
+ const content = readFileSync(bashrcPath, "utf-8");
556
+ if (!content.includes("Skillo CLI Integration")) {
557
+ appendFileSync(bashrcPath, sourceLine);
558
+ logger_default.success("Added to ~/.bashrc");
559
+ } else {
560
+ logger_default.dim("Already in ~/.bashrc");
561
+ }
562
+ } else {
563
+ writeFileSync(bashrcPath, sourceLine, "utf-8");
564
+ logger_default.success("Created ~/.bashrc");
565
+ }
566
+ } else if (shell === "zsh") {
567
+ const zshScript = [
568
+ "# Skillo CLI Integration",
569
+ "# Records commands and auto-detects tracked projects",
570
+ "",
571
+ '_skillo_session="${SKILLO_SESSION:-default}"',
572
+ "_skillo_last_cwd=",
573
+ "",
574
+ "_skillo_preexec() {",
575
+ ' _skillo_cmd="$1"',
576
+ " _skillo_start_time=$(date +%s)",
577
+ "}",
578
+ "",
579
+ "_skillo_check_project() {",
580
+ " # Fast path: skip if CWD unchanged",
581
+ ' if [[ "$PWD" == "$_skillo_last_cwd" ]]; then',
582
+ " return",
583
+ " fi",
584
+ ' _skillo_last_cwd="$PWD"',
585
+ "",
586
+ " # Run session-auto (handles create/end via cache file)",
587
+ " local result _pid",
588
+ " _pid=$(cat /proc/$$/winpid 2>/dev/null || echo $$)",
589
+ ' result=$(skillo session-auto "$PWD" --pid $_pid --shell zsh 2>/dev/null)',
590
+ ' if [[ -n "$result" ]]; then',
591
+ ' export SKILLO_SESSION="$result"',
592
+ ' _skillo_session="$result"',
593
+ " fi",
594
+ "}",
595
+ "",
596
+ "_skillo_precmd() {",
597
+ " local exit_code=$?",
598
+ "",
599
+ " # Check for project change",
600
+ " _skillo_check_project",
601
+ "",
602
+ ' if [[ -n "$_skillo_cmd" ]]; then',
603
+ " local duration=0",
604
+ ' if [[ -n "$_skillo_start_time" ]]; then',
605
+ " local end_time=$(date +%s)",
606
+ " duration=$(( (end_time - _skillo_start_time) * 1000 ))",
607
+ " fi",
608
+ ' (skillo record "$_skillo_cmd" --cwd "$PWD" --exit-code "$exit_code" --duration "$duration" --session "$_skillo_session" >/dev/null &)',
609
+ ' _skillo_cmd=""',
610
+ " fi",
611
+ "}",
612
+ "",
613
+ "# Clean up session on terminal exit",
614
+ "_skillo_cleanup() {",
615
+ ' if [[ -n "$SKILLO_SESSION" ]] && [[ "$SKILLO_SESSION" != "default" ]]; then',
616
+ ' skillo session end --session "$SKILLO_SESSION" >/dev/null 2>&1',
617
+ " fi",
618
+ "}",
619
+ "trap _skillo_cleanup EXIT",
620
+ "",
621
+ "autoload -Uz add-zsh-hook",
622
+ "add-zsh-hook preexec _skillo_preexec",
623
+ "add-zsh-hook precmd _skillo_precmd",
624
+ "",
625
+ "# Initial project check",
626
+ "_skillo_check_project",
627
+ "",
628
+ ...generatePosixSyncStatusLines()
629
+ ].join("\n");
630
+ const zshScriptFile = join(scriptPath, "skillo.zsh");
631
+ writeFileSync(zshScriptFile, zshScript, "utf-8");
632
+ logger_default.success(`Created ${zshScriptFile}`);
633
+ const zshrcPath = join(home, ".zshrc");
634
+ const sourceLine = `
635
+ # Skillo CLI Integration
636
+ [ -f "${zshScriptFile}" ] && source "${zshScriptFile}"
637
+ `;
638
+ if (existsSync(zshrcPath)) {
639
+ const content = readFileSync(zshrcPath, "utf-8");
640
+ if (!content.includes("Skillo CLI Integration")) {
641
+ appendFileSync(zshrcPath, sourceLine);
642
+ logger_default.success("Added to ~/.zshrc");
643
+ } else {
644
+ logger_default.dim("Already in ~/.zshrc");
645
+ }
646
+ } else {
647
+ writeFileSync(zshrcPath, sourceLine, "utf-8");
648
+ logger_default.success("Created ~/.zshrc");
649
+ }
650
+ } else if (shell === "fish") {
651
+ const fishScript = [
652
+ "# Skillo CLI Integration",
653
+ "# Records commands to Skillo platform",
654
+ "",
655
+ 'set -q SKILLO_SESSION; or set -g SKILLO_SESSION "default"',
656
+ "",
657
+ "function _skillo_postexec --on-event fish_postexec",
658
+ " set -l cmd $argv[1]",
659
+ " set -l exit_code $status",
660
+ ' skillo record "$cmd" --cwd (pwd) --exit-code $exit_code --session $SKILLO_SESSION >/dev/null &',
661
+ "end",
662
+ "",
663
+ "# Show sync status on startup (respects SKILLO_CONFIG_DIR and XDG_CONFIG_HOME)",
664
+ "if set -q SKILLO_CONFIG_DIR",
665
+ ' set _skillo_config "$SKILLO_CONFIG_DIR/config.yaml"',
666
+ "else if set -q XDG_CONFIG_HOME",
667
+ ' set _skillo_config "$XDG_CONFIG_HOME/skillo/config.yaml"',
668
+ "else",
669
+ ' set _skillo_config "$HOME/.config/skillo/config.yaml"',
670
+ "end",
671
+ 'if test -f "$_skillo_config"; and grep -q "api_key:" "$_skillo_config" 2>/dev/null',
672
+ ' echo -e "\\033[90mSkillo: Command tracking enabled (syncing to platform)\\033[0m"',
673
+ "else",
674
+ ` echo -e "\\033[33mSkillo: Command tracking enabled (local only \u2014 run 'skillo login' to sync)\\033[0m"`,
675
+ "end"
676
+ ].join("\n");
677
+ const fishScriptFile = join(scriptPath, "skillo.fish");
678
+ writeFileSync(fishScriptFile, fishScript, "utf-8");
679
+ logger_default.success(`Created ${fishScriptFile}`);
680
+ const fishConfigDir = join(home, ".config", "fish");
681
+ ensureDirectory(fishConfigDir);
682
+ const fishConfigPath = join(fishConfigDir, "config.fish");
683
+ const sourceLine = `
684
+ # Skillo CLI Integration
685
+ if test -f "${fishScriptFile}"
686
+ source "${fishScriptFile}"
687
+ end
688
+ `;
689
+ if (existsSync(fishConfigPath)) {
690
+ const content = readFileSync(fishConfigPath, "utf-8");
691
+ if (!content.includes("Skillo CLI Integration")) {
692
+ appendFileSync(fishConfigPath, sourceLine);
693
+ logger_default.success("Added to ~/.config/fish/config.fish");
694
+ } else {
695
+ logger_default.dim("Already in fish config");
696
+ }
697
+ } else {
698
+ writeFileSync(fishConfigPath, sourceLine, "utf-8");
699
+ logger_default.success("Created fish config");
700
+ }
701
+ }
702
+ }
703
+ async function uninstallShellIntegration(shell, home) {
704
+ const removeFromFile = (filePath) => {
705
+ if (!existsSync(filePath)) return;
706
+ let content = readFileSync(filePath, "utf-8");
707
+ content = content.replace(/\n# Skillo CLI Integration\n[^\n]*\n/g, "\n");
708
+ writeFileSync(filePath, content, "utf-8");
709
+ };
710
+ if (shell === "powershell") {
711
+ const profilePath = join(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
712
+ removeFromFile(profilePath);
713
+ } else if (shell === "bash") {
714
+ removeFromFile(join(home, ".bashrc"));
715
+ } else if (shell === "zsh") {
716
+ removeFromFile(join(home, ".zshrc"));
717
+ } else if (shell === "fish") {
718
+ removeFromFile(join(home, ".config", "fish", "config.fish"));
719
+ }
720
+ }
721
+
722
+ export {
723
+ shellCommand,
724
+ recordCommand,
725
+ setupShellCommand
726
+ };
727
+ //# sourceMappingURL=chunk-U53QWUOR.js.map