skillo 0.1.4 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -6,20 +6,23 @@ import {
6
6
  loadConfig,
7
7
  saveConfig,
8
8
  setConfigValue
9
- } from "./chunk-QNRWQL6M.js";
9
+ } from "./chunk-SYULMYPN.js";
10
10
  import {
11
11
  ensureDirectory,
12
+ getActiveSessionsDir,
12
13
  getConfigDir,
13
14
  getConfigFile,
14
15
  getDataDir,
15
16
  getDbPath,
16
17
  getLogFile,
17
18
  getPidFile,
18
- getSkillsDir
19
- } from "./chunk-SWPZL2RI.js";
19
+ getShellIntegrationDir,
20
+ getSkillsDir,
21
+ getTrackedProjectsCacheFile
22
+ } from "./chunk-WJKZWKER.js";
20
23
 
21
24
  // src/cli.ts
22
- import { Command as Command13 } from "commander";
25
+ import { Command as Command14 } from "commander";
23
26
 
24
27
  // src/commands/init.ts
25
28
  import { existsSync as existsSync2 } from "fs";
@@ -590,15 +593,444 @@ var initCommand = new Command("init").description("Initialize Skillo configurati
590
593
  });
591
594
 
592
595
  // src/commands/status.ts
593
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
596
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
594
597
  import { Command as Command2 } from "commander";
598
+
599
+ // src/utils/os-service.ts
600
+ import { existsSync as existsSync3, writeFileSync as writeFileSync2, unlinkSync, mkdirSync, readdirSync } from "fs";
601
+ import { join } from "path";
602
+ import { homedir, platform } from "os";
603
+ import { execSync } from "child_process";
604
+ var DAEMON_LABEL = "one.skillo.daemon";
605
+ var TRAY_LABEL = "one.skillo.tray";
606
+ var WIN_REG_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
607
+ var WIN_DAEMON_VALUE = "SkilloDaemon";
608
+ var WIN_TRAY_VALUE = "SkilloTray";
609
+ var isWin = platform() === "win32";
610
+ var pathSep = isWin ? ";" : ":";
611
+ function getLaunchAgentsDir() {
612
+ return join(homedir(), "Library", "LaunchAgents");
613
+ }
614
+ function getSystemdUserDir() {
615
+ return join(homedir(), ".config", "systemd", "user");
616
+ }
617
+ function getDaemonPlistPath() {
618
+ return join(getLaunchAgentsDir(), `${DAEMON_LABEL}.plist`);
619
+ }
620
+ function getTrayPlistPath() {
621
+ return join(getLaunchAgentsDir(), `${TRAY_LABEL}.plist`);
622
+ }
623
+ function getDaemonServicePath() {
624
+ return join(getSystemdUserDir(), "skillo-daemon.service");
625
+ }
626
+ function getTrayServicePath() {
627
+ return join(getSystemdUserDir(), "skillo-tray.service");
628
+ }
629
+ function getSkilloDataDir() {
630
+ return join(homedir(), ".skillo");
631
+ }
632
+ function getWinDaemonVbsPath() {
633
+ return join(getSkilloDataDir(), "skillo-daemon.vbs");
634
+ }
635
+ function findSkilloBin() {
636
+ try {
637
+ const whichCmd = isWin ? "where skillo" : "which skillo";
638
+ const result = execSync(whichCmd, { encoding: "utf-8" }).trim();
639
+ const firstLine = result.split(/\r?\n/)[0].trim();
640
+ if (firstLine) return firstLine;
641
+ } catch {
642
+ }
643
+ if (isWin) {
644
+ const appData = process.env.APPDATA || join(homedir(), "AppData", "Roaming");
645
+ const candidates = [
646
+ join(appData, "npm", "skillo.cmd"),
647
+ join(homedir(), "AppData", "Local", "fnm_multishells")
648
+ ];
649
+ for (const c of candidates) {
650
+ if (existsSync3(c)) return c;
651
+ }
652
+ } else {
653
+ const candidates = [
654
+ "/usr/local/bin/skillo",
655
+ "/usr/bin/skillo"
656
+ ];
657
+ for (const c of candidates) {
658
+ if (existsSync3(c)) return c;
659
+ }
660
+ }
661
+ return "skillo";
662
+ }
663
+ function buildPath() {
664
+ const paths = /* @__PURE__ */ new Set();
665
+ const home = homedir();
666
+ (process.env.PATH || "").split(pathSep).forEach((p) => paths.add(p));
667
+ if (isWin) {
668
+ const appData = process.env.APPDATA || join(home, "AppData", "Roaming");
669
+ paths.add(join(appData, "npm"));
670
+ paths.add(join(home, "AppData", "Local", "Programs", "nodejs"));
671
+ const nvmHome = process.env.NVM_HOME;
672
+ if (nvmHome) paths.add(nvmHome);
673
+ const nvmSymlink = process.env.NVM_SYMLINK;
674
+ if (nvmSymlink) paths.add(nvmSymlink);
675
+ const fnmDir = join(home, ".fnm");
676
+ if (existsSync3(fnmDir)) paths.add(fnmDir);
677
+ } else {
678
+ const nvmDir = process.env.NVM_DIR || join(home, ".nvm");
679
+ if (existsSync3(nvmDir)) {
680
+ try {
681
+ const defaultAlias = join(nvmDir, "alias", "default");
682
+ if (existsSync3(defaultAlias)) {
683
+ const versionsDir = join(nvmDir, "versions", "node");
684
+ if (existsSync3(versionsDir)) {
685
+ const versions = readdirSync(versionsDir);
686
+ for (const v of versions) {
687
+ paths.add(join(versionsDir, v, "bin"));
688
+ }
689
+ }
690
+ }
691
+ } catch {
692
+ }
693
+ }
694
+ paths.add(join(home, ".fnm", "current", "bin"));
695
+ paths.add("/opt/homebrew/bin");
696
+ paths.add("/usr/local/bin");
697
+ paths.add("/usr/bin");
698
+ paths.add("/bin");
699
+ paths.add(join(home, ".npm-global", "bin"));
700
+ paths.add(join(home, ".local", "bin"));
701
+ }
702
+ return [...paths].filter(Boolean).join(pathSep);
703
+ }
704
+ function generateDaemonPlist(skilloBin, envPath) {
705
+ return `<?xml version="1.0" encoding="UTF-8"?>
706
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
707
+ <plist version="1.0">
708
+ <dict>
709
+ <key>Label</key>
710
+ <string>${DAEMON_LABEL}</string>
711
+ <key>ProgramArguments</key>
712
+ <array>
713
+ <string>${skilloBin}</string>
714
+ <string>start</string>
715
+ <string>--foreground</string>
716
+ </array>
717
+ <key>RunAtLoad</key>
718
+ <true/>
719
+ <key>KeepAlive</key>
720
+ <dict>
721
+ <key>SuccessfulExit</key>
722
+ <false/>
723
+ </dict>
724
+ <key>ThrottleInterval</key>
725
+ <integer>10</integer>
726
+ <key>EnvironmentVariables</key>
727
+ <dict>
728
+ <key>PATH</key>
729
+ <string>${envPath}</string>
730
+ <key>HOME</key>
731
+ <string>${homedir()}</string>
732
+ </dict>
733
+ <key>StandardOutPath</key>
734
+ <string>${join(homedir(), ".skillo", "launchd-stdout.log")}</string>
735
+ <key>StandardErrorPath</key>
736
+ <string>${join(homedir(), ".skillo", "launchd-stderr.log")}</string>
737
+ </dict>
738
+ </plist>`;
739
+ }
740
+ function generateTrayPlist(skilloBin, envPath) {
741
+ return `<?xml version="1.0" encoding="UTF-8"?>
742
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
743
+ <plist version="1.0">
744
+ <dict>
745
+ <key>Label</key>
746
+ <string>${TRAY_LABEL}</string>
747
+ <key>ProgramArguments</key>
748
+ <array>
749
+ <string>${skilloBin}</string>
750
+ <string>tray</string>
751
+ </array>
752
+ <key>RunAtLoad</key>
753
+ <true/>
754
+ <key>LimitLoadToSessionType</key>
755
+ <string>Aqua</string>
756
+ <key>KeepAlive</key>
757
+ <dict>
758
+ <key>SuccessfulExit</key>
759
+ <false/>
760
+ </dict>
761
+ <key>ThrottleInterval</key>
762
+ <integer>10</integer>
763
+ <key>EnvironmentVariables</key>
764
+ <dict>
765
+ <key>PATH</key>
766
+ <string>${envPath}</string>
767
+ <key>HOME</key>
768
+ <string>${homedir()}</string>
769
+ </dict>
770
+ <key>StandardOutPath</key>
771
+ <string>${join(homedir(), ".skillo", "launchd-tray-stdout.log")}</string>
772
+ <key>StandardErrorPath</key>
773
+ <string>${join(homedir(), ".skillo", "launchd-tray-stderr.log")}</string>
774
+ </dict>
775
+ </plist>`;
776
+ }
777
+ function generateDaemonUnit(skilloBin, envPath) {
778
+ return `[Unit]
779
+ Description=Skillo Daemon
780
+ After=network.target
781
+
782
+ [Service]
783
+ Type=simple
784
+ ExecStart=${skilloBin} start --foreground
785
+ Restart=on-failure
786
+ RestartSec=10
787
+ Environment=PATH=${envPath}
788
+ Environment=HOME=${homedir()}
789
+
790
+ [Install]
791
+ WantedBy=default.target
792
+ `;
793
+ }
794
+ function generateTrayUnit(skilloBin, envPath) {
795
+ return `[Unit]
796
+ Description=Skillo Tray Icon
797
+ After=graphical-session.target
798
+ PartOf=graphical-session.target
799
+
800
+ [Service]
801
+ Type=simple
802
+ ExecStart=${skilloBin} tray
803
+ Restart=on-failure
804
+ RestartSec=10
805
+ Environment=PATH=${envPath}
806
+ Environment=HOME=${homedir()}
807
+ Environment=DISPLAY=:0
808
+
809
+ [Install]
810
+ WantedBy=graphical-session.target
811
+ `;
812
+ }
813
+ function generateDaemonVbs(skilloBin) {
814
+ const escaped = skilloBin.replace(/\\/g, "\\\\");
815
+ return `' Skillo Daemon \u2014 hidden launcher\r
816
+ Set WshShell = CreateObject("WScript.Shell")\r
817
+ WshShell.Run """${escaped}"" start --foreground", 0, False\r
818
+ `;
819
+ }
820
+ function winRegAdd(valueName, data) {
821
+ execSync(
822
+ `reg add "${WIN_REG_KEY}" /v "${valueName}" /t REG_SZ /d "${data}" /f`,
823
+ { stdio: "ignore" }
824
+ );
825
+ }
826
+ function winRegDelete(valueName) {
827
+ try {
828
+ execSync(
829
+ `reg delete "${WIN_REG_KEY}" /v "${valueName}" /f`,
830
+ { stdio: "ignore" }
831
+ );
832
+ } catch {
833
+ }
834
+ }
835
+ function winRegExists(valueName) {
836
+ try {
837
+ execSync(`reg query "${WIN_REG_KEY}" /v "${valueName}"`, { stdio: "ignore" });
838
+ return true;
839
+ } catch {
840
+ return false;
841
+ }
842
+ }
843
+ async function installService(options) {
844
+ const os2 = platform();
845
+ const skilloBin = findSkilloBin();
846
+ const envPath = buildPath();
847
+ const includeTray = options?.includeTray ?? true;
848
+ try {
849
+ if (os2 === "darwin") {
850
+ const agentsDir = getLaunchAgentsDir();
851
+ if (!existsSync3(agentsDir)) mkdirSync(agentsDir, { recursive: true });
852
+ const daemonPlist = getDaemonPlistPath();
853
+ writeFileSync2(daemonPlist, generateDaemonPlist(skilloBin, envPath), "utf-8");
854
+ try {
855
+ execSync(`launchctl unload "${daemonPlist}" 2>/dev/null`);
856
+ } catch {
857
+ }
858
+ execSync(`launchctl load "${daemonPlist}"`);
859
+ if (includeTray) {
860
+ const trayPlist = getTrayPlistPath();
861
+ writeFileSync2(trayPlist, generateTrayPlist(skilloBin, envPath), "utf-8");
862
+ try {
863
+ execSync(`launchctl unload "${trayPlist}" 2>/dev/null`);
864
+ } catch {
865
+ }
866
+ execSync(`launchctl load "${trayPlist}"`);
867
+ }
868
+ return { success: true };
869
+ } else if (os2 === "linux") {
870
+ const systemdDir = getSystemdUserDir();
871
+ if (!existsSync3(systemdDir)) mkdirSync(systemdDir, { recursive: true });
872
+ writeFileSync2(getDaemonServicePath(), generateDaemonUnit(skilloBin, envPath), "utf-8");
873
+ execSync("systemctl --user daemon-reload");
874
+ execSync("systemctl --user enable skillo-daemon.service");
875
+ execSync("systemctl --user start skillo-daemon.service");
876
+ if (includeTray) {
877
+ writeFileSync2(getTrayServicePath(), generateTrayUnit(skilloBin, envPath), "utf-8");
878
+ execSync("systemctl --user daemon-reload");
879
+ execSync("systemctl --user enable skillo-tray.service");
880
+ try {
881
+ execSync("systemctl --user start skillo-tray.service");
882
+ } catch {
883
+ }
884
+ }
885
+ return { success: true };
886
+ } else if (os2 === "win32") {
887
+ const dataDir = getSkilloDataDir();
888
+ if (!existsSync3(dataDir)) mkdirSync(dataDir, { recursive: true });
889
+ const vbsPath = getWinDaemonVbsPath();
890
+ writeFileSync2(vbsPath, generateDaemonVbs(skilloBin), "utf-8");
891
+ winRegAdd(WIN_DAEMON_VALUE, `wscript.exe "${vbsPath}"`);
892
+ if (includeTray) {
893
+ winRegAdd(WIN_TRAY_VALUE, `"${skilloBin}" tray`);
894
+ }
895
+ return { success: true };
896
+ } else {
897
+ return { success: false, error: `Unsupported platform: ${os2}. Auto-start is available on macOS, Linux, and Windows.` };
898
+ }
899
+ } catch (error) {
900
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
901
+ }
902
+ }
903
+ async function uninstallService() {
904
+ const os2 = platform();
905
+ try {
906
+ if (os2 === "darwin") {
907
+ const daemonPlist = getDaemonPlistPath();
908
+ if (existsSync3(daemonPlist)) {
909
+ try {
910
+ execSync(`launchctl unload "${daemonPlist}" 2>/dev/null`);
911
+ } catch {
912
+ }
913
+ unlinkSync(daemonPlist);
914
+ }
915
+ const trayPlist = getTrayPlistPath();
916
+ if (existsSync3(trayPlist)) {
917
+ try {
918
+ execSync(`launchctl unload "${trayPlist}" 2>/dev/null`);
919
+ } catch {
920
+ }
921
+ unlinkSync(trayPlist);
922
+ }
923
+ return { success: true };
924
+ } else if (os2 === "linux") {
925
+ const daemonService = getDaemonServicePath();
926
+ if (existsSync3(daemonService)) {
927
+ try {
928
+ execSync("systemctl --user stop skillo-daemon.service 2>/dev/null");
929
+ } catch {
930
+ }
931
+ try {
932
+ execSync("systemctl --user disable skillo-daemon.service 2>/dev/null");
933
+ } catch {
934
+ }
935
+ unlinkSync(daemonService);
936
+ }
937
+ const trayService = getTrayServicePath();
938
+ if (existsSync3(trayService)) {
939
+ try {
940
+ execSync("systemctl --user stop skillo-tray.service 2>/dev/null");
941
+ } catch {
942
+ }
943
+ try {
944
+ execSync("systemctl --user disable skillo-tray.service 2>/dev/null");
945
+ } catch {
946
+ }
947
+ unlinkSync(trayService);
948
+ }
949
+ try {
950
+ execSync("systemctl --user daemon-reload");
951
+ } catch {
952
+ }
953
+ return { success: true };
954
+ } else if (os2 === "win32") {
955
+ winRegDelete(WIN_DAEMON_VALUE);
956
+ winRegDelete(WIN_TRAY_VALUE);
957
+ const vbsPath = getWinDaemonVbsPath();
958
+ if (existsSync3(vbsPath)) {
959
+ try {
960
+ unlinkSync(vbsPath);
961
+ } catch {
962
+ }
963
+ }
964
+ return { success: true };
965
+ } else {
966
+ return { success: false, error: `Unsupported platform: ${os2}` };
967
+ }
968
+ } catch (error) {
969
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
970
+ }
971
+ }
972
+ function getServiceStatus() {
973
+ const os2 = platform();
974
+ if (os2 === "darwin") {
975
+ const daemonInstalled = existsSync3(getDaemonPlistPath());
976
+ const trayInstalled = existsSync3(getTrayPlistPath());
977
+ let daemonLoaded = false;
978
+ let trayLoaded = false;
979
+ try {
980
+ const output = execSync("launchctl list", { encoding: "utf-8" });
981
+ daemonLoaded = output.includes(DAEMON_LABEL);
982
+ trayLoaded = output.includes(TRAY_LABEL);
983
+ } catch {
984
+ }
985
+ return {
986
+ platform: "macos",
987
+ daemon: { installed: daemonInstalled, loaded: daemonLoaded },
988
+ tray: { installed: trayInstalled, loaded: trayLoaded }
989
+ };
990
+ } else if (os2 === "linux") {
991
+ const daemonInstalled = existsSync3(getDaemonServicePath());
992
+ const trayInstalled = existsSync3(getTrayServicePath());
993
+ let daemonLoaded = false;
994
+ let trayLoaded = false;
995
+ try {
996
+ execSync("systemctl --user is-active skillo-daemon.service", { encoding: "utf-8" });
997
+ daemonLoaded = true;
998
+ } catch {
999
+ }
1000
+ try {
1001
+ execSync("systemctl --user is-active skillo-tray.service", { encoding: "utf-8" });
1002
+ trayLoaded = true;
1003
+ } catch {
1004
+ }
1005
+ return {
1006
+ platform: "linux",
1007
+ daemon: { installed: daemonInstalled, loaded: daemonLoaded },
1008
+ tray: { installed: trayInstalled, loaded: trayLoaded }
1009
+ };
1010
+ } else if (os2 === "win32") {
1011
+ const daemonInstalled = winRegExists(WIN_DAEMON_VALUE);
1012
+ const trayInstalled = winRegExists(WIN_TRAY_VALUE);
1013
+ return {
1014
+ platform: "windows",
1015
+ daemon: { installed: daemonInstalled, loaded: daemonInstalled },
1016
+ tray: { installed: trayInstalled, loaded: trayInstalled }
1017
+ };
1018
+ }
1019
+ return {
1020
+ platform: "unsupported",
1021
+ daemon: { installed: false, loaded: false },
1022
+ tray: { installed: false, loaded: false }
1023
+ };
1024
+ }
1025
+
1026
+ // src/commands/status.ts
595
1027
  function isDaemonRunning() {
596
1028
  const pidFile = getPidFile();
597
- if (!existsSync3(pidFile)) {
1029
+ if (!existsSync4(pidFile)) {
598
1030
  return { running: false, pid: null };
599
1031
  }
600
1032
  try {
601
- const pidStr = readFileSync2(pidFile, "utf-8").trim();
1033
+ const pidStr = readFileSync3(pidFile, "utf-8").trim();
602
1034
  const pid = parseInt(pidStr, 10);
603
1035
  if (isNaN(pid)) {
604
1036
  return { running: false, pid: null };
@@ -622,11 +1054,18 @@ var statusCommand = new Command2("status").description("Show daemon status and s
622
1054
  logger_default.stopped("Daemon is not running");
623
1055
  logger_default.blank();
624
1056
  logger_default.dim("Start with: skillo start");
625
- return;
626
1057
  }
1058
+ const serviceStatus = getServiceStatus();
1059
+ if (serviceStatus.daemon.installed) {
1060
+ logger_default.running("Auto-start: enabled");
1061
+ } else {
1062
+ logger_default.stopped("Auto-start: disabled");
1063
+ logger_default.dim(" Enable with: skillo service install");
1064
+ }
1065
+ if (!running) return;
627
1066
  logger_default.blank();
628
1067
  const dbPath = getDbPath();
629
- if (!existsSync3(dbPath)) {
1068
+ if (!existsSync4(dbPath)) {
630
1069
  logger_default.warn("Database not found. Run 'skillo init' first.");
631
1070
  return;
632
1071
  }
@@ -643,10 +1082,10 @@ var statusCommand = new Command2("status").description("Show daemon status and s
643
1082
  ]);
644
1083
  logger_default.blank();
645
1084
  const logFile = getLogFile();
646
- if (existsSync3(logFile)) {
1085
+ if (existsSync4(logFile)) {
647
1086
  logger_default.dim("Recent log entries:");
648
1087
  try {
649
- const content = readFileSync2(logFile, "utf-8");
1088
+ const content = readFileSync3(logFile, "utf-8");
650
1089
  const lines = content.split("\n").filter(Boolean).slice(-5);
651
1090
  lines.forEach((line) => {
652
1091
  logger_default.dim(` ${line}`);
@@ -658,7 +1097,7 @@ var statusCommand = new Command2("status").description("Show daemon status and s
658
1097
  });
659
1098
 
660
1099
  // src/commands/config.ts
661
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
1100
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
662
1101
  import { Command as Command3 } from "commander";
663
1102
  import YAML from "yaml";
664
1103
  var configCommand = new Command3("config").description(
@@ -666,12 +1105,12 @@ var configCommand = new Command3("config").description(
666
1105
  );
667
1106
  configCommand.command("show").description("Show current configuration").action(async () => {
668
1107
  const configFile = getConfigFile();
669
- if (!existsSync4(configFile)) {
1108
+ if (!existsSync5(configFile)) {
670
1109
  logger_default.error("Configuration not found.");
671
1110
  logger_default.dim("Run 'skillo init' first.");
672
1111
  process.exit(1);
673
1112
  }
674
- const content = readFileSync3(configFile, "utf-8");
1113
+ const content = readFileSync4(configFile, "utf-8");
675
1114
  logger_default.blank();
676
1115
  logger_default.bold(`Configuration file: ${configFile}`);
677
1116
  logger_default.blank();
@@ -679,7 +1118,7 @@ configCommand.command("show").description("Show current configuration").action(a
679
1118
  });
680
1119
  configCommand.command("get <key>").description("Get a configuration value (use dot notation, e.g., patternDetection.minCount)").action(async (key) => {
681
1120
  const configFile = getConfigFile();
682
- if (!existsSync4(configFile)) {
1121
+ if (!existsSync5(configFile)) {
683
1122
  logger_default.error("Configuration not found.");
684
1123
  process.exit(1);
685
1124
  }
@@ -697,7 +1136,7 @@ configCommand.command("get <key>").description("Get a configuration value (use d
697
1136
  });
698
1137
  configCommand.command("set <key> <value>").description("Set a configuration value").action(async (key, value) => {
699
1138
  const configFile = getConfigFile();
700
- if (!existsSync4(configFile)) {
1139
+ if (!existsSync5(configFile)) {
701
1140
  logger_default.error("Configuration not found.");
702
1141
  process.exit(1);
703
1142
  }
@@ -739,7 +1178,7 @@ configCommand.command("path").description("Show configuration file path").action
739
1178
  });
740
1179
 
741
1180
  // src/commands/patterns.ts
742
- import { existsSync as existsSync5 } from "fs";
1181
+ import { existsSync as existsSync6 } from "fs";
743
1182
  import { Command as Command4 } from "commander";
744
1183
  import chalk2 from "chalk";
745
1184
  var patternsCommand = new Command4("patterns").description(
@@ -747,7 +1186,7 @@ var patternsCommand = new Command4("patterns").description(
747
1186
  );
748
1187
  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) => {
749
1188
  const dbPath = getDbPath();
750
- if (!existsSync5(dbPath)) {
1189
+ if (!existsSync6(dbPath)) {
751
1190
  logger_default.error("Database not found. Run 'skillo init' first.");
752
1191
  process.exit(1);
753
1192
  }
@@ -784,7 +1223,7 @@ patternsCommand.command("list").description("List detected patterns").option("-t
784
1223
  });
785
1224
  patternsCommand.command("show <id>").description("Show pattern details").action(async (id) => {
786
1225
  const dbPath = getDbPath();
787
- if (!existsSync5(dbPath)) {
1226
+ if (!existsSync6(dbPath)) {
788
1227
  logger_default.error("Database not found. Run 'skillo init' first.");
789
1228
  process.exit(1);
790
1229
  }
@@ -827,7 +1266,7 @@ patternsCommand.command("show <id>").description("Show pattern details").action(
827
1266
  });
828
1267
  patternsCommand.command("ignore <id>").description("Ignore a pattern (never suggest again)").option("-r, --reason <reason>", "Reason for ignoring").action(async (id, options) => {
829
1268
  const dbPath = getDbPath();
830
- if (!existsSync5(dbPath)) {
1269
+ if (!existsSync6(dbPath)) {
831
1270
  logger_default.error("Database not found. Run 'skillo init' first.");
832
1271
  process.exit(1);
833
1272
  }
@@ -848,12 +1287,12 @@ patternsCommand.command("ignore <id>").description("Ignore a pattern (never sugg
848
1287
  }
849
1288
  });
850
1289
  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) => {
851
- const { getApiClient: getApiClient2 } = await import("./api-client-HTWGTHC2.js");
852
- const { writeFileSync: writeFileSync5, mkdirSync: mkdirSync2 } = await import("fs");
853
- const { join: join6 } = await import("path");
854
- const { getSkillsDir: getSkillsDir2, ensureDirectory: ensureDirectory2 } = await import("./paths-YODCFIEO.js");
1290
+ const { getApiClient: getApiClient2 } = await import("./api-client-WO6NUCIJ.js");
1291
+ const { writeFileSync: writeFileSync8, mkdirSync: mkdirSync3 } = await import("fs");
1292
+ const { join: join7 } = await import("path");
1293
+ const { getSkillsDir: getSkillsDir2, ensureDirectory: ensureDirectory2 } = await import("./paths-INOKEM66.js");
855
1294
  const dbPath = getDbPath();
856
- if (!existsSync5(dbPath)) {
1295
+ if (!existsSync6(dbPath)) {
857
1296
  logger_default.error("Database not found. Run 'skillo init' first.");
858
1297
  process.exit(1);
859
1298
  }
@@ -904,10 +1343,10 @@ patternsCommand.command("generate <id>").description("Generate a skill from a pa
904
1343
  } else {
905
1344
  const skillsDir = getSkillsDir2();
906
1345
  ensureDirectory2(skillsDir);
907
- const skillDir = join6(skillsDir, skill.slug);
908
- const skillFile = join6(skillDir, "SKILL.md");
909
- mkdirSync2(skillDir, { recursive: true });
910
- writeFileSync5(skillFile, skill.content, "utf-8");
1346
+ const skillDir = join7(skillsDir, skill.slug);
1347
+ const skillFile = join7(skillDir, "SKILL.md");
1348
+ mkdirSync3(skillDir, { recursive: true });
1349
+ writeFileSync8(skillFile, skill.content, "utf-8");
911
1350
  await db.updatePatternStatus(id, "converted");
912
1351
  logger_default.success(`Skill saved to: ~/.claude/skills/${skill.slug}/SKILL.md`);
913
1352
  logger_default.blank();
@@ -922,7 +1361,7 @@ patternsCommand.command("clear").description("Clear all patterns").option("-f, -
922
1361
  return;
923
1362
  }
924
1363
  const dbPath = getDbPath();
925
- if (!existsSync5(dbPath)) {
1364
+ if (!existsSync6(dbPath)) {
926
1365
  logger_default.error("Database not found.");
927
1366
  process.exit(1);
928
1367
  }
@@ -930,8 +1369,8 @@ patternsCommand.command("clear").description("Clear all patterns").option("-f, -
930
1369
  });
931
1370
 
932
1371
  // src/commands/skills.ts
933
- import { existsSync as existsSync6, readdirSync, rmSync } from "fs";
934
- import { join } from "path";
1372
+ import { existsSync as existsSync7, readdirSync as readdirSync2, rmSync } from "fs";
1373
+ import { join as join2 } from "path";
935
1374
  import { Command as Command5 } from "commander";
936
1375
  import chalk3 from "chalk";
937
1376
  var skillsCommand = new Command5("skills").description(
@@ -939,7 +1378,7 @@ var skillsCommand = new Command5("skills").description(
939
1378
  );
940
1379
  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) => {
941
1380
  const dbPath = getDbPath();
942
- if (!existsSync6(dbPath)) {
1381
+ if (!existsSync7(dbPath)) {
943
1382
  logger_default.error("Database not found. Run 'skillo init' first.");
944
1383
  process.exit(1);
945
1384
  }
@@ -952,11 +1391,11 @@ skillsCommand.command("list").description("List all skills").option("-s, --sourc
952
1391
  db.close();
953
1392
  const skillsDir = getSkillsDir();
954
1393
  const fsSkills = [];
955
- if (existsSync6(skillsDir)) {
956
- const entries = readdirSync(skillsDir, { withFileTypes: true });
1394
+ if (existsSync7(skillsDir)) {
1395
+ const entries = readdirSync2(skillsDir, { withFileTypes: true });
957
1396
  entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).forEach((e) => {
958
- const skillMd = join(skillsDir, e.name, "SKILL.md");
959
- if (existsSync6(skillMd)) {
1397
+ const skillMd = join2(skillsDir, e.name, "SKILL.md");
1398
+ if (existsSync7(skillMd)) {
960
1399
  fsSkills.push(e.name);
961
1400
  }
962
1401
  });
@@ -996,7 +1435,7 @@ skillsCommand.command("list").description("List all skills").option("-s, --sourc
996
1435
  });
997
1436
  skillsCommand.command("show <name>").description("Show skill details").action(async (name) => {
998
1437
  const dbPath = getDbPath();
999
- if (!existsSync6(dbPath)) {
1438
+ if (!existsSync7(dbPath)) {
1000
1439
  logger_default.error("Database not found. Run 'skillo init' first.");
1001
1440
  process.exit(1);
1002
1441
  }
@@ -1005,12 +1444,12 @@ skillsCommand.command("show <name>").description("Show skill details").action(as
1005
1444
  const skill = await db.getSkill(name);
1006
1445
  db.close();
1007
1446
  if (!skill) {
1008
- const skillPath = join(getSkillsDir(), name, "SKILL.md");
1009
- if (existsSync6(skillPath)) {
1447
+ const skillPath = join2(getSkillsDir(), name, "SKILL.md");
1448
+ if (existsSync7(skillPath)) {
1010
1449
  logger_default.blank();
1011
1450
  logger_default.bold(name);
1012
1451
  logger_default.blank();
1013
- logger_default.dim(` Path: ${join(getSkillsDir(), name)}`);
1452
+ logger_default.dim(` Path: ${join2(getSkillsDir(), name)}`);
1014
1453
  logger_default.dim(` Status: Not registered in database`);
1015
1454
  logger_default.blank();
1016
1455
  return;
@@ -1037,15 +1476,15 @@ skillsCommand.command("show <name>").description("Show skill details").action(as
1037
1476
  });
1038
1477
  skillsCommand.command("delete <name>").description("Delete a skill").option("-f, --force", "Skip confirmation").action(async (name, options) => {
1039
1478
  const dbPath = getDbPath();
1040
- if (!existsSync6(dbPath)) {
1479
+ if (!existsSync7(dbPath)) {
1041
1480
  logger_default.error("Database not found.");
1042
1481
  process.exit(1);
1043
1482
  }
1044
1483
  const db = new SkilloDatabase();
1045
1484
  await db.initialize();
1046
1485
  const skill = await db.getSkill(name);
1047
- const skillDir = skill?.path || join(getSkillsDir(), name);
1048
- if (!skill && !existsSync6(skillDir)) {
1486
+ const skillDir = skill?.path || join2(getSkillsDir(), name);
1487
+ if (!skill && !existsSync7(skillDir)) {
1049
1488
  db.close();
1050
1489
  logger_default.error(`Skill not found: ${name}`);
1051
1490
  process.exit(1);
@@ -1056,7 +1495,7 @@ skillsCommand.command("delete <name>").description("Delete a skill").option("-f,
1056
1495
  db.close();
1057
1496
  return;
1058
1497
  }
1059
- if (existsSync6(skillDir)) {
1498
+ if (existsSync7(skillDir)) {
1060
1499
  rmSync(skillDir, { recursive: true, force: true });
1061
1500
  }
1062
1501
  if (skill) {
@@ -1070,7 +1509,7 @@ skillsCommand.command("path").description("Show skills directory path").action(a
1070
1509
  });
1071
1510
  skillsCommand.command("open").description("Open skills directory in file manager").action(async () => {
1072
1511
  const skillsDir = getSkillsDir();
1073
- if (!existsSync6(skillsDir)) {
1512
+ if (!existsSync7(skillsDir)) {
1074
1513
  logger_default.error("Skills directory not found. Run 'skillo init' first.");
1075
1514
  process.exit(1);
1076
1515
  }
@@ -1085,10 +1524,10 @@ skillsCommand.command("open").description("Open skills directory in file manager
1085
1524
  });
1086
1525
 
1087
1526
  // src/commands/shell.ts
1088
- import { existsSync as existsSync7, writeFileSync as writeFileSync2, readFileSync as readFileSync4, appendFileSync } from "fs";
1527
+ import { existsSync as existsSync8, writeFileSync as writeFileSync3, readFileSync as readFileSync5, appendFileSync } from "fs";
1089
1528
  import { spawn } from "child_process";
1090
- import { homedir } from "os";
1091
- import { join as join2 } from "path";
1529
+ import { homedir as homedir2 } from "os";
1530
+ import { join as join3 } from "path";
1092
1531
  import { Command as Command6 } from "commander";
1093
1532
  function normalizeCommand(cmd) {
1094
1533
  const variables = {};
@@ -1125,7 +1564,7 @@ function normalizeCommand(cmd) {
1125
1564
  return { normalized: normalized.trim(), variables };
1126
1565
  }
1127
1566
  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) => {
1128
- if (!existsSync7(getConfigFile())) {
1567
+ if (!existsSync8(getConfigFile())) {
1129
1568
  logger_default.error("Skillo not initialized. Run 'skillo init' first.");
1130
1569
  process.exit(1);
1131
1570
  }
@@ -1200,7 +1639,7 @@ var recordCommand = new Command6("record").description("Record a command (used b
1200
1639
  db.close();
1201
1640
  if (options.sync !== false) {
1202
1641
  try {
1203
- const { getApiClient: getApiClient2 } = await import("./api-client-HTWGTHC2.js");
1642
+ const { getApiClient: getApiClient2 } = await import("./api-client-WO6NUCIJ.js");
1204
1643
  const client = getApiClient2();
1205
1644
  if (client.hasApiKey()) {
1206
1645
  await client.syncCommands([{
@@ -1221,7 +1660,7 @@ var recordCommand = new Command6("record").description("Record a command (used b
1221
1660
  );
1222
1661
  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) => {
1223
1662
  logger_default.blank();
1224
- const home = homedir();
1663
+ const home = homedir2();
1225
1664
  const dataDir = getDataDir();
1226
1665
  ensureDirectory(dataDir);
1227
1666
  let shell = null;
@@ -1269,7 +1708,7 @@ var setupShellCommand = new Command6("setup-shell").description("Set up shell in
1269
1708
  logger_default.blank();
1270
1709
  });
1271
1710
  async function installShellIntegration(shell, home, dataDir) {
1272
- const scriptPath = join2(dataDir, "shell-integration");
1711
+ const scriptPath = join3(dataDir, "shell-integration");
1273
1712
  ensureDirectory(scriptPath);
1274
1713
  if (shell === "powershell") {
1275
1714
  const psScript = `# Skillo CLI Integration
@@ -1417,18 +1856,18 @@ $null = Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action {
1417
1856
 
1418
1857
  Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
1419
1858
  `;
1420
- const psScriptFile = join2(scriptPath, "skillo.ps1");
1421
- writeFileSync2(psScriptFile, psScript, "utf-8");
1859
+ const psScriptFile = join3(scriptPath, "skillo.ps1");
1860
+ writeFileSync3(psScriptFile, psScript, "utf-8");
1422
1861
  logger_default.success(`Created ${psScriptFile}`);
1423
- const profilePath = join2(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
1424
- const profileDir = join2(home, "Documents", "WindowsPowerShell");
1862
+ const profilePath = join3(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
1863
+ const profileDir = join3(home, "Documents", "WindowsPowerShell");
1425
1864
  ensureDirectory(profileDir);
1426
1865
  const sourceLine = `
1427
1866
  # Skillo CLI Integration
1428
1867
  . "${psScriptFile}"
1429
1868
  `;
1430
- if (existsSync7(profilePath)) {
1431
- const content = readFileSync4(profilePath, "utf-8");
1869
+ if (existsSync8(profilePath)) {
1870
+ const content = readFileSync5(profilePath, "utf-8");
1432
1871
  if (!content.includes("Skillo CLI Integration")) {
1433
1872
  appendFileSync(profilePath, sourceLine);
1434
1873
  logger_default.success("Added to PowerShell profile");
@@ -1436,34 +1875,63 @@ Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
1436
1875
  logger_default.dim("Already in PowerShell profile");
1437
1876
  }
1438
1877
  } else {
1439
- writeFileSync2(profilePath, sourceLine, "utf-8");
1878
+ writeFileSync3(profilePath, sourceLine, "utf-8");
1440
1879
  logger_default.success("Created PowerShell profile");
1441
1880
  }
1442
1881
  } else if (shell === "bash") {
1443
1882
  const bashScript = [
1444
1883
  "# Skillo CLI Integration",
1445
- "# Records commands to Skillo platform",
1884
+ "# Records commands and auto-detects tracked projects",
1446
1885
  "",
1447
1886
  '_skillo_session="${SKILLO_SESSION:-default}"',
1887
+ "_skillo_last_cwd=",
1448
1888
  "",
1449
1889
  "_skillo_preexec() {",
1450
1890
  ' _skillo_cmd="$1"',
1451
- " _skillo_start_time=$(date +%s%3N 2>/dev/null || date +%s)",
1891
+ " _skillo_start_time=$(date +%s)",
1892
+ "}",
1893
+ "",
1894
+ "_skillo_check_project() {",
1895
+ " # Fast path: skip if CWD unchanged",
1896
+ ' if [ "$PWD" = "$_skillo_last_cwd" ]; then',
1897
+ " return",
1898
+ " fi",
1899
+ ' _skillo_last_cwd="$PWD"',
1900
+ "",
1901
+ " # Run session-auto in background (handles create/end via cache file)",
1902
+ " local result",
1903
+ ' result=$(skillo session-auto "$PWD" --pid $$ --shell bash 2>/dev/null)',
1904
+ ' if [ -n "$result" ]; then',
1905
+ ' export SKILLO_SESSION="$result"',
1906
+ ' _skillo_session="$result"',
1907
+ " fi",
1452
1908
  "}",
1453
1909
  "",
1454
1910
  "_skillo_precmd() {",
1455
1911
  " local exit_code=$?",
1912
+ "",
1913
+ " # Check for project change",
1914
+ " _skillo_check_project",
1915
+ "",
1456
1916
  ' if [ -n "$_skillo_cmd" ]; then',
1457
1917
  " local duration=0",
1458
1918
  ' if [ -n "$_skillo_start_time" ]; then',
1459
- " local end_time=$(date +%s%3N 2>/dev/null || date +%s)",
1460
- " duration=$((end_time - _skillo_start_time))",
1919
+ " local end_time=$(date +%s)",
1920
+ " duration=$(( (end_time - _skillo_start_time) * 1000 ))",
1461
1921
  " fi",
1462
1922
  ' (skillo record "$_skillo_cmd" --cwd "$PWD" --exit-code "$exit_code" --duration "$duration" --session "$_skillo_session" &>/dev/null &)',
1463
1923
  ' _skillo_cmd=""',
1464
1924
  " fi",
1465
1925
  "}",
1466
1926
  "",
1927
+ "# Clean up session on terminal exit",
1928
+ "_skillo_cleanup() {",
1929
+ ' if [ -n "$SKILLO_SESSION" ] && [ "$SKILLO_SESSION" != "default" ]; then',
1930
+ ' skillo session end --session "$SKILLO_SESSION" &>/dev/null',
1931
+ " fi",
1932
+ "}",
1933
+ "trap _skillo_cleanup EXIT",
1934
+ "",
1467
1935
  "# Use DEBUG trap for preexec",
1468
1936
  `trap '_skillo_preexec "$BASH_COMMAND"' DEBUG`,
1469
1937
  "",
@@ -1472,18 +1940,21 @@ Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
1472
1940
  ' PROMPT_COMMAND="_skillo_precmd${PROMPT_COMMAND:+;$PROMPT_COMMAND}"',
1473
1941
  "fi",
1474
1942
  "",
1943
+ "# Initial project check",
1944
+ "_skillo_check_project",
1945
+ "",
1475
1946
  'echo -e "\\033[90mSkillo: Command tracking enabled\\033[0m"'
1476
1947
  ].join("\n");
1477
- const bashScriptFile = join2(scriptPath, "skillo.bash");
1478
- writeFileSync2(bashScriptFile, bashScript, "utf-8");
1948
+ const bashScriptFile = join3(scriptPath, "skillo.bash");
1949
+ writeFileSync3(bashScriptFile, bashScript, "utf-8");
1479
1950
  logger_default.success(`Created ${bashScriptFile}`);
1480
- const bashrcPath = join2(home, ".bashrc");
1951
+ const bashrcPath = join3(home, ".bashrc");
1481
1952
  const sourceLine = `
1482
1953
  # Skillo CLI Integration
1483
1954
  [ -f "${bashScriptFile}" ] && source "${bashScriptFile}"
1484
1955
  `;
1485
- if (existsSync7(bashrcPath)) {
1486
- const content = readFileSync4(bashrcPath, "utf-8");
1956
+ if (existsSync8(bashrcPath)) {
1957
+ const content = readFileSync5(bashrcPath, "utf-8");
1487
1958
  if (!content.includes("Skillo CLI Integration")) {
1488
1959
  appendFileSync(bashrcPath, sourceLine);
1489
1960
  logger_default.success("Added to ~/.bashrc");
@@ -1491,50 +1962,82 @@ Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
1491
1962
  logger_default.dim("Already in ~/.bashrc");
1492
1963
  }
1493
1964
  } else {
1494
- writeFileSync2(bashrcPath, sourceLine, "utf-8");
1965
+ writeFileSync3(bashrcPath, sourceLine, "utf-8");
1495
1966
  logger_default.success("Created ~/.bashrc");
1496
1967
  }
1497
1968
  } else if (shell === "zsh") {
1498
1969
  const zshScript = [
1499
1970
  "# Skillo CLI Integration",
1500
- "# Records commands to Skillo platform",
1971
+ "# Records commands and auto-detects tracked projects",
1501
1972
  "",
1502
1973
  '_skillo_session="${SKILLO_SESSION:-default}"',
1974
+ "_skillo_last_cwd=",
1503
1975
  "",
1504
1976
  "_skillo_preexec() {",
1505
1977
  ' _skillo_cmd="$1"',
1506
- " _skillo_start_time=$(($(date +%s%N)/1000000))",
1978
+ " _skillo_start_time=$(date +%s)",
1979
+ "}",
1980
+ "",
1981
+ "_skillo_check_project() {",
1982
+ " # Fast path: skip if CWD unchanged",
1983
+ ' if [[ "$PWD" == "$_skillo_last_cwd" ]]; then',
1984
+ " return",
1985
+ " fi",
1986
+ ' _skillo_last_cwd="$PWD"',
1987
+ "",
1988
+ " # Run session-auto in background (handles create/end via cache file)",
1989
+ " local result",
1990
+ ' result=$(skillo session-auto "$PWD" --pid $$ --shell zsh 2>/dev/null)',
1991
+ ' if [[ -n "$result" ]]; then',
1992
+ ' export SKILLO_SESSION="$result"',
1993
+ ' _skillo_session="$result"',
1994
+ " fi",
1507
1995
  "}",
1508
1996
  "",
1509
1997
  "_skillo_precmd() {",
1510
1998
  " local exit_code=$?",
1999
+ "",
2000
+ " # Check for project change",
2001
+ " _skillo_check_project",
2002
+ "",
1511
2003
  ' if [[ -n "$_skillo_cmd" ]]; then',
1512
2004
  " local duration=0",
1513
2005
  ' if [[ -n "$_skillo_start_time" ]]; then',
1514
- " local end_time=$(($(date +%s%N)/1000000))",
1515
- " duration=$((end_time - _skillo_start_time))",
2006
+ " local end_time=$(date +%s)",
2007
+ " duration=$(( (end_time - _skillo_start_time) * 1000 ))",
1516
2008
  " fi",
1517
2009
  ' (skillo record "$_skillo_cmd" --cwd "$PWD" --exit-code "$exit_code" --duration "$duration" --session "$_skillo_session" &>/dev/null &)',
1518
2010
  ' _skillo_cmd=""',
1519
2011
  " fi",
1520
2012
  "}",
1521
2013
  "",
2014
+ "# Clean up session on terminal exit",
2015
+ "_skillo_cleanup() {",
2016
+ ' if [[ -n "$SKILLO_SESSION" ]] && [[ "$SKILLO_SESSION" != "default" ]]; then',
2017
+ ' skillo session end --session "$SKILLO_SESSION" &>/dev/null',
2018
+ " fi",
2019
+ "}",
2020
+ "trap _skillo_cleanup EXIT",
2021
+ "",
1522
2022
  "autoload -Uz add-zsh-hook",
1523
2023
  "add-zsh-hook preexec _skillo_preexec",
1524
2024
  "add-zsh-hook precmd _skillo_precmd",
1525
2025
  "",
2026
+ "# Initial project check",
2027
+ "_skillo_check_project",
2028
+ "",
1526
2029
  'echo -e "\\033[90mSkillo: Command tracking enabled\\033[0m"'
1527
2030
  ].join("\n");
1528
- const zshScriptFile = join2(scriptPath, "skillo.zsh");
1529
- writeFileSync2(zshScriptFile, zshScript, "utf-8");
2031
+ const zshScriptFile = join3(scriptPath, "skillo.zsh");
2032
+ writeFileSync3(zshScriptFile, zshScript, "utf-8");
1530
2033
  logger_default.success(`Created ${zshScriptFile}`);
1531
- const zshrcPath = join2(home, ".zshrc");
2034
+ const zshrcPath = join3(home, ".zshrc");
1532
2035
  const sourceLine = `
1533
2036
  # Skillo CLI Integration
1534
2037
  [ -f "${zshScriptFile}" ] && source "${zshScriptFile}"
1535
2038
  `;
1536
- if (existsSync7(zshrcPath)) {
1537
- const content = readFileSync4(zshrcPath, "utf-8");
2039
+ if (existsSync8(zshrcPath)) {
2040
+ const content = readFileSync5(zshrcPath, "utf-8");
1538
2041
  if (!content.includes("Skillo CLI Integration")) {
1539
2042
  appendFileSync(zshrcPath, sourceLine);
1540
2043
  logger_default.success("Added to ~/.zshrc");
@@ -1542,7 +2045,7 @@ Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
1542
2045
  logger_default.dim("Already in ~/.zshrc");
1543
2046
  }
1544
2047
  } else {
1545
- writeFileSync2(zshrcPath, sourceLine, "utf-8");
2048
+ writeFileSync3(zshrcPath, sourceLine, "utf-8");
1546
2049
  logger_default.success("Created ~/.zshrc");
1547
2050
  }
1548
2051
  } else if (shell === "fish") {
@@ -1560,20 +2063,20 @@ Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
1560
2063
  "",
1561
2064
  'echo -e "\\033[90mSkillo: Command tracking enabled\\033[0m"'
1562
2065
  ].join("\n");
1563
- const fishScriptFile = join2(scriptPath, "skillo.fish");
1564
- writeFileSync2(fishScriptFile, fishScript, "utf-8");
2066
+ const fishScriptFile = join3(scriptPath, "skillo.fish");
2067
+ writeFileSync3(fishScriptFile, fishScript, "utf-8");
1565
2068
  logger_default.success(`Created ${fishScriptFile}`);
1566
- const fishConfigDir = join2(home, ".config", "fish");
2069
+ const fishConfigDir = join3(home, ".config", "fish");
1567
2070
  ensureDirectory(fishConfigDir);
1568
- const fishConfigPath = join2(fishConfigDir, "config.fish");
2071
+ const fishConfigPath = join3(fishConfigDir, "config.fish");
1569
2072
  const sourceLine = `
1570
2073
  # Skillo CLI Integration
1571
2074
  if test -f "${fishScriptFile}"
1572
2075
  source "${fishScriptFile}"
1573
2076
  end
1574
2077
  `;
1575
- if (existsSync7(fishConfigPath)) {
1576
- const content = readFileSync4(fishConfigPath, "utf-8");
2078
+ if (existsSync8(fishConfigPath)) {
2079
+ const content = readFileSync5(fishConfigPath, "utf-8");
1577
2080
  if (!content.includes("Skillo CLI Integration")) {
1578
2081
  appendFileSync(fishConfigPath, sourceLine);
1579
2082
  logger_default.success("Added to ~/.config/fish/config.fish");
@@ -1581,41 +2084,41 @@ end
1581
2084
  logger_default.dim("Already in fish config");
1582
2085
  }
1583
2086
  } else {
1584
- writeFileSync2(fishConfigPath, sourceLine, "utf-8");
2087
+ writeFileSync3(fishConfigPath, sourceLine, "utf-8");
1585
2088
  logger_default.success("Created fish config");
1586
2089
  }
1587
2090
  }
1588
2091
  }
1589
2092
  async function uninstallShellIntegration(shell, home) {
1590
2093
  const removeFromFile = (filePath) => {
1591
- if (!existsSync7(filePath)) return;
1592
- let content = readFileSync4(filePath, "utf-8");
2094
+ if (!existsSync8(filePath)) return;
2095
+ let content = readFileSync5(filePath, "utf-8");
1593
2096
  content = content.replace(/\n# Skillo CLI Integration\n[^\n]*\n/g, "\n");
1594
- writeFileSync2(filePath, content, "utf-8");
2097
+ writeFileSync3(filePath, content, "utf-8");
1595
2098
  };
1596
2099
  if (shell === "powershell") {
1597
- const profilePath = join2(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
2100
+ const profilePath = join3(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
1598
2101
  removeFromFile(profilePath);
1599
2102
  } else if (shell === "bash") {
1600
- removeFromFile(join2(home, ".bashrc"));
2103
+ removeFromFile(join3(home, ".bashrc"));
1601
2104
  } else if (shell === "zsh") {
1602
- removeFromFile(join2(home, ".zshrc"));
2105
+ removeFromFile(join3(home, ".zshrc"));
1603
2106
  } else if (shell === "fish") {
1604
- removeFromFile(join2(home, ".config", "fish", "config.fish"));
2107
+ removeFromFile(join3(home, ".config", "fish", "config.fish"));
1605
2108
  }
1606
2109
  }
1607
2110
 
1608
2111
  // src/commands/daemon.ts
1609
- import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync3, unlinkSync } from "fs";
2112
+ import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2 } from "fs";
1610
2113
  import { fork } from "child_process";
1611
2114
  import { Command as Command7 } from "commander";
1612
2115
  function isDaemonRunning2() {
1613
2116
  const pidFile = getPidFile();
1614
- if (!existsSync8(pidFile)) {
2117
+ if (!existsSync9(pidFile)) {
1615
2118
  return { running: false, pid: null };
1616
2119
  }
1617
2120
  try {
1618
- const pidStr = readFileSync5(pidFile, "utf-8").trim();
2121
+ const pidStr = readFileSync6(pidFile, "utf-8").trim();
1619
2122
  const pid = parseInt(pidStr, 10);
1620
2123
  if (isNaN(pid)) {
1621
2124
  return { running: false, pid: null };
@@ -1625,7 +2128,7 @@ function isDaemonRunning2() {
1625
2128
  return { running: true, pid };
1626
2129
  } catch {
1627
2130
  try {
1628
- unlinkSync(pidFile);
2131
+ unlinkSync2(pidFile);
1629
2132
  } catch {
1630
2133
  }
1631
2134
  return { running: false, pid: null };
@@ -1634,8 +2137,25 @@ function isDaemonRunning2() {
1634
2137
  return { running: false, pid: null };
1635
2138
  }
1636
2139
  }
2140
+ function startDaemonProcess() {
2141
+ const daemonScript = new URL("./daemon-runner.js", import.meta.url).pathname;
2142
+ const child = fork(daemonScript, [], {
2143
+ detached: true,
2144
+ stdio: "ignore",
2145
+ env: {
2146
+ ...process.env,
2147
+ SKILLO_DAEMON: "1"
2148
+ }
2149
+ });
2150
+ child.unref();
2151
+ if (child.pid) {
2152
+ writeFileSync4(getPidFile(), String(child.pid));
2153
+ return child.pid;
2154
+ }
2155
+ return null;
2156
+ }
1637
2157
  var startCommand = new Command7("start").description("Start the Skillo daemon").option("-f, --foreground", "Run in foreground (don't daemonize)").action(async (options) => {
1638
- if (!existsSync8(getConfigFile())) {
2158
+ if (!existsSync9(getConfigFile())) {
1639
2159
  logger_default.error("Skillo not initialized.");
1640
2160
  logger_default.dim("Run 'skillo init' first.");
1641
2161
  process.exit(1);
@@ -1645,36 +2165,31 @@ var startCommand = new Command7("start").description("Start the Skillo daemon").
1645
2165
  logger_default.warn(`Daemon is already running (PID: ${pid})`);
1646
2166
  return;
1647
2167
  }
1648
- const config = loadConfig();
1649
2168
  if (options.foreground) {
1650
2169
  logger_default.info("Starting Skillo daemon in foreground...");
1651
2170
  logger_default.dim("Press Ctrl+C to stop");
1652
2171
  logger_default.blank();
1653
- await runDaemon(config);
2172
+ await runDaemonForeground();
1654
2173
  } else {
1655
2174
  logger_default.info("Starting Skillo daemon...");
1656
2175
  if (process.platform === "win32") {
1657
2176
  logger_default.warn("Background daemon not fully supported on Windows.");
1658
2177
  logger_default.dim("Running in foreground mode...");
1659
2178
  logger_default.blank();
1660
- await runDaemon(config);
2179
+ await runDaemonForeground();
1661
2180
  } else {
1662
- const daemonScript = new URL("../daemon.js", import.meta.url).pathname;
1663
- const child = fork(daemonScript, [], {
1664
- detached: true,
1665
- stdio: "ignore",
1666
- env: {
1667
- ...process.env,
1668
- SKILLO_DAEMON: "1"
2181
+ const daemonPid = startDaemonProcess();
2182
+ if (daemonPid) {
2183
+ logger_default.success(`Daemon started (PID: ${daemonPid})`);
2184
+ const serviceResult = await installService({ includeTray: false });
2185
+ if (serviceResult.success) {
2186
+ logger_default.dim("Auto-start service installed. Daemon will survive reboots.");
1669
2187
  }
1670
- });
1671
- child.unref();
1672
- if (child.pid) {
1673
- writeFileSync3(getPidFile(), String(child.pid));
1674
- logger_default.success(`Daemon started (PID: ${child.pid})`);
1675
2188
  logger_default.blank();
1676
2189
  logger_default.dim("Use 'skillo status' to check daemon status");
1677
2190
  logger_default.dim("Use 'skillo stop' to stop the daemon");
2191
+ } else {
2192
+ logger_default.error("Failed to start daemon");
1678
2193
  }
1679
2194
  }
1680
2195
  }
@@ -1686,6 +2201,10 @@ var stopCommand = new Command7("stop").description("Stop the Skillo daemon").act
1686
2201
  return;
1687
2202
  }
1688
2203
  logger_default.info(`Stopping daemon (PID: ${pid})...`);
2204
+ const serviceResult = await uninstallService();
2205
+ if (serviceResult.success) {
2206
+ logger_default.dim("Auto-start service removed.");
2207
+ }
1689
2208
  try {
1690
2209
  process.kill(pid, "SIGTERM");
1691
2210
  logger_default.success("Daemon stopped");
@@ -1700,16 +2219,16 @@ var stopCommand = new Command7("stop").description("Stop the Skillo daemon").act
1700
2219
  }
1701
2220
  }
1702
2221
  const pidFile = getPidFile();
1703
- if (existsSync8(pidFile)) {
2222
+ if (existsSync9(pidFile)) {
1704
2223
  try {
1705
- unlinkSync(pidFile);
2224
+ unlinkSync2(pidFile);
1706
2225
  } catch {
1707
2226
  }
1708
2227
  }
1709
2228
  });
1710
2229
  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) => {
1711
2230
  const logFile = getLogFile();
1712
- if (!existsSync8(logFile)) {
2231
+ if (!existsSync9(logFile)) {
1713
2232
  logger_default.dim("No logs found.");
1714
2233
  return;
1715
2234
  }
@@ -1728,23 +2247,70 @@ var logsCommand = new Command7("logs").description("Show daemon logs").option("-
1728
2247
  }
1729
2248
  });
1730
2249
  function showLogs(logFile, numLines) {
1731
- const content = readFileSync5(logFile, "utf-8");
2250
+ const content = readFileSync6(logFile, "utf-8");
1732
2251
  const lines = content.split("\n").filter(Boolean);
1733
2252
  const lastLines = lines.slice(-numLines);
1734
2253
  lastLines.forEach((line) => console.log(line));
1735
2254
  }
1736
- async function runDaemon(_config) {
2255
+ var serviceCommand = new Command7("service").description("Manage auto-start service").addCommand(
2256
+ 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) => {
2257
+ logger_default.info("Installing auto-start service...");
2258
+ const result = await installService({ includeTray: options.tray !== false });
2259
+ if (result.success) {
2260
+ logger_default.success("Auto-start service installed.");
2261
+ logger_default.dim("Daemon will start automatically on login and restart on crash.");
2262
+ if (options.tray !== false) {
2263
+ logger_default.dim("Tray icon will appear in GUI sessions.");
2264
+ }
2265
+ } else {
2266
+ logger_default.error(`Failed to install service: ${result.error}`);
2267
+ }
2268
+ })
2269
+ ).addCommand(
2270
+ new Command7("uninstall").description("Remove auto-start service").action(async () => {
2271
+ logger_default.info("Removing auto-start service...");
2272
+ const result = await uninstallService();
2273
+ if (result.success) {
2274
+ logger_default.success("Auto-start service removed.");
2275
+ } else {
2276
+ logger_default.error(`Failed to remove service: ${result.error}`);
2277
+ }
2278
+ })
2279
+ ).addCommand(
2280
+ new Command7("status").description("Show auto-start service status").action(async () => {
2281
+ const status = getServiceStatus();
2282
+ logger_default.blank();
2283
+ logger_default.info(`Platform: ${status.platform}`);
2284
+ logger_default.blank();
2285
+ if (status.daemon.installed) {
2286
+ logger_default.running(`Daemon service: installed${status.daemon.loaded ? " & loaded" : " (not loaded)"}`);
2287
+ } else {
2288
+ logger_default.stopped("Daemon service: not installed");
2289
+ }
2290
+ if (status.tray.installed) {
2291
+ logger_default.running(`Tray service: installed${status.tray.loaded ? " & loaded" : " (not loaded)"}`);
2292
+ } else {
2293
+ logger_default.stopped("Tray service: not installed");
2294
+ }
2295
+ logger_default.blank();
2296
+ if (!status.daemon.installed) {
2297
+ logger_default.dim("Run 'skillo service install' to enable auto-start.");
2298
+ }
2299
+ logger_default.blank();
2300
+ })
2301
+ );
2302
+ async function runDaemonForeground() {
1737
2303
  const pidFile = getPidFile();
1738
- writeFileSync3(pidFile, String(process.pid));
2304
+ writeFileSync4(pidFile, String(process.pid));
1739
2305
  logger_default.dim(`Daemon PID: ${process.pid}`);
1740
2306
  logger_default.dim(`Log file: ${getLogFile()}`);
1741
2307
  logger_default.blank();
1742
2308
  const cleanup = () => {
1743
2309
  logger_default.blank();
1744
2310
  logger_default.info("Shutting down daemon...");
1745
- if (existsSync8(pidFile)) {
2311
+ if (existsSync9(pidFile)) {
1746
2312
  try {
1747
- unlinkSync(pidFile);
2313
+ unlinkSync2(pidFile);
1748
2314
  } catch {
1749
2315
  }
1750
2316
  }
@@ -1752,17 +2318,51 @@ async function runDaemon(_config) {
1752
2318
  };
1753
2319
  process.on("SIGINT", cleanup);
1754
2320
  process.on("SIGTERM", cleanup);
1755
- const heartbeat = () => {
1756
- };
1757
- setInterval(heartbeat, 6e4);
1758
- heartbeat();
1759
- logger_default.success("Daemon is running. Watching for patterns...");
2321
+ const config = loadConfig();
2322
+ const { getApiClient: getApiClient2 } = await import("./api-client-WO6NUCIJ.js");
2323
+ const client = getApiClient2();
2324
+ if (!client.hasApiKey()) {
2325
+ logger_default.error("Not logged in. Run 'skillo login' first.");
2326
+ process.exit(1);
2327
+ }
2328
+ const { ClaudeWatcher } = await import("./claude-watcher-N6GN6WHJ.js");
2329
+ ensureDirectory(getDataDir());
2330
+ const watchInterval = (config.daemon?.conversationCheckInterval || 5) * 1e3;
2331
+ const watcher = new ClaudeWatcher(client, {
2332
+ intervalMs: watchInterval,
2333
+ callbacks: {
2334
+ onSync: (count) => logger_default.success(`Synced ${count} Claude prompt(s)`),
2335
+ onError: (err) => logger_default.error(`Watcher error: ${err.message}`),
2336
+ log: (level, msg) => {
2337
+ if (level === "ERROR") logger_default.error(msg);
2338
+ else if (level === "WARN") logger_default.warn(msg);
2339
+ else logger_default.dim(msg);
2340
+ }
2341
+ }
2342
+ });
2343
+ await watcher.start();
2344
+ const { SkillUsageDetector } = await import("./skill-usage-detector-EO26MRYV.js");
2345
+ const skillDetector = new SkillUsageDetector(client, {
2346
+ intervalMs: 3e4,
2347
+ callbacks: {
2348
+ onDetection: (count) => logger_default.success(`Detected ${count} skill usage(s)`),
2349
+ onError: (err) => logger_default.error(`Skill detection error: ${err.message}`),
2350
+ log: (level, msg) => {
2351
+ if (level === "ERROR") logger_default.error(msg);
2352
+ else if (level === "WARN") logger_default.warn(msg);
2353
+ else logger_default.dim(msg);
2354
+ }
2355
+ }
2356
+ });
2357
+ await skillDetector.start();
2358
+ logger_default.success("Daemon is running. Watching Claude conversations and skill usage...");
1760
2359
  await new Promise(() => {
1761
2360
  });
1762
2361
  }
1763
2362
 
1764
2363
  // src/commands/session.ts
1765
2364
  import { Command as Command8 } from "commander";
2365
+ import { existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3 } from "fs";
1766
2366
  var sessionCommand = new Command8("session").description("Manage terminal sessions");
1767
2367
  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) => {
1768
2368
  try {
@@ -1809,6 +2409,71 @@ sessionCommand.command("list").description("List active sessions").action(async
1809
2409
  }
1810
2410
  logger_default.info("Check your sessions at: https://www.skillo.one/terminal");
1811
2411
  });
2412
+ 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) => {
2413
+ const terminalPid = options.pid || String(process.ppid || process.pid);
2414
+ const shell = options.shell || process.env.SHELL?.split("/").pop() || "unknown";
2415
+ const sessionsDir = getActiveSessionsDir();
2416
+ const sessionFile = `${sessionsDir}/${terminalPid}.json`;
2417
+ let currentSession = null;
2418
+ if (existsSync10(sessionFile)) {
2419
+ try {
2420
+ currentSession = JSON.parse(readFileSync7(sessionFile, "utf-8"));
2421
+ } catch {
2422
+ currentSession = null;
2423
+ }
2424
+ }
2425
+ const cacheFile = getTrackedProjectsCacheFile();
2426
+ let matchedProject = null;
2427
+ if (existsSync10(cacheFile)) {
2428
+ try {
2429
+ const cache = JSON.parse(readFileSync7(cacheFile, "utf-8"));
2430
+ const normalizedCwd = cwd.toLowerCase().replace(/\/+$/, "");
2431
+ for (const project of cache.projects || []) {
2432
+ const normalizedPath = project.path.toLowerCase().replace(/\/+$/, "");
2433
+ if (normalizedCwd === normalizedPath || normalizedCwd.startsWith(normalizedPath + "/")) {
2434
+ matchedProject = project;
2435
+ break;
2436
+ }
2437
+ }
2438
+ } catch {
2439
+ }
2440
+ }
2441
+ if (currentSession && matchedProject && currentSession.projectPath === matchedProject.path) {
2442
+ process.exit(0);
2443
+ }
2444
+ const client = getApiClient();
2445
+ if (!client.hasApiKey()) {
2446
+ process.exit(0);
2447
+ }
2448
+ if (currentSession) {
2449
+ try {
2450
+ await client.endSession(currentSession.sessionId);
2451
+ } catch {
2452
+ }
2453
+ try {
2454
+ unlinkSync3(sessionFile);
2455
+ } catch {
2456
+ }
2457
+ }
2458
+ if (matchedProject) {
2459
+ try {
2460
+ const result = await client.startSession(shell, matchedProject.path);
2461
+ if (result.success && result.data?.sessionId) {
2462
+ ensureDirectory(sessionsDir);
2463
+ writeFileSync5(sessionFile, JSON.stringify({
2464
+ sessionId: result.data.sessionId,
2465
+ projectPath: matchedProject.path,
2466
+ projectName: matchedProject.name,
2467
+ pid: terminalPid,
2468
+ startedAt: Date.now()
2469
+ }));
2470
+ console.log(result.data.sessionId);
2471
+ }
2472
+ } catch {
2473
+ }
2474
+ }
2475
+ process.exit(0);
2476
+ });
1812
2477
 
1813
2478
  // src/commands/claude.ts
1814
2479
  import { Command as Command9 } from "commander";
@@ -1991,112 +2656,38 @@ claudeCommand.command("sync").description("Sync Claude Code history to the platf
1991
2656
  process.exit(1);
1992
2657
  }
1993
2658
  });
1994
- claudeCommand.command("watch").description("Watch Claude Code history and sync in real-time (only tracked projects)").option("--interval <ms>", "Check interval in milliseconds", "5000").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").action(async (options) => {
2659
+ claudeCommand.command("watch").description("Watch Claude Code history and sync in real-time (only tracked projects)").option("--interval <ms>", "Check interval in milliseconds", "5000").option("--project-path <path>", "Only sync prompts for this project path").option("--session <id>", "Link prompts to this terminal session").action(async (options) => {
1995
2660
  const client = getApiClient();
1996
2661
  if (!client.hasApiKey()) {
1997
2662
  logger_default.error("Not logged in. Run 'skillo login' first.");
1998
2663
  process.exit(1);
1999
2664
  }
2000
- const historyPath = getClaudeHistoryPath();
2665
+ const { ClaudeWatcher } = await import("./claude-watcher-N6GN6WHJ.js");
2001
2666
  const interval = parseInt(options.interval, 10);
2002
2667
  const projectFilter = options.projectPath || process.env.SKILLO_PROJECT_PATH;
2003
- const ignoreTracking = options.ignoreTracking || false;
2004
- if (!fs.existsSync(historyPath)) {
2005
- logger_default.warn(`Claude Code history file not found at: ${historyPath}`);
2006
- logger_default.info("Waiting for file to be created...");
2007
- }
2008
- if (!ignoreTracking) {
2009
- logger_default.info("Loading tracked projects...");
2010
- await loadTrackedProjects();
2011
- const trackedCount = Array.from(projectCache.projects.values()).filter((p) => p.tracked).length;
2012
- if (trackedCount === 0) {
2013
- logger_default.warn("No projects are connected for tracking.");
2014
- logger_default.info("Run 'skillo connect' in your project directories first.");
2015
- process.exit(0);
2016
- }
2017
- logger_default.dim(`Watching ${trackedCount} tracked project(s)`);
2018
- } else {
2019
- logger_default.warn("Privacy filter disabled - watching all projects");
2020
- }
2021
- let lastSize = 0;
2022
- let lastMtime = 0;
2023
- try {
2024
- const stats = fs.statSync(historyPath);
2025
- lastSize = stats.size;
2026
- lastMtime = stats.mtimeMs;
2027
- } catch {
2028
- }
2029
2668
  if (projectFilter) {
2030
2669
  logger_default.info(`Watching for project: ${projectFilter}`);
2031
2670
  }
2032
2671
  logger_default.info(`Checking every ${interval}ms`);
2033
2672
  logger_default.info("Press Ctrl+C to stop");
2034
- const checkAndSync = async () => {
2035
- try {
2036
- if (!fs.existsSync(historyPath)) return;
2037
- const stats = fs.statSync(historyPath);
2038
- if (stats.size === lastSize && stats.mtimeMs === lastMtime) {
2039
- return;
2040
- }
2041
- if (!ignoreTracking) {
2042
- await loadTrackedProjects();
2673
+ const watcher = new ClaudeWatcher(client, {
2674
+ intervalMs: interval,
2675
+ projectFilter,
2676
+ sessionFilter: options.session,
2677
+ callbacks: {
2678
+ onSync: (count) => logger_default.success(`Synced ${count} prompt(s)`),
2679
+ onError: (err) => logger_default.error(`Watch error: ${err.message}`),
2680
+ log: (level, msg) => {
2681
+ if (level === "ERROR") logger_default.error(msg);
2682
+ else if (level === "WARN") logger_default.warn(msg);
2683
+ else logger_default.dim(msg);
2043
2684
  }
2044
- const newPrompts = [];
2045
- if (stats.size > lastSize) {
2046
- const stream = fs.createReadStream(historyPath, {
2047
- start: lastSize,
2048
- encoding: "utf-8"
2049
- });
2050
- const rl = readline.createInterface({
2051
- input: stream,
2052
- crlfDelay: Infinity
2053
- });
2054
- for await (const line of rl) {
2055
- if (!line.trim()) continue;
2056
- try {
2057
- const entry = JSON.parse(line);
2058
- if (!entry.display || !entry.timestamp || !entry.project || !entry.sessionId) {
2059
- continue;
2060
- }
2061
- if (projectFilter && !matchesProjectPath(entry.project, projectFilter)) {
2062
- continue;
2063
- }
2064
- if (!isWithinSession(entry.timestamp, options.sessionStart)) {
2065
- continue;
2066
- }
2067
- if (!ignoreTracking) {
2068
- const trackingStatus = await isProjectTracked(entry.project);
2069
- if (!trackingStatus.tracked) {
2070
- continue;
2071
- }
2072
- }
2073
- newPrompts.push(entry);
2074
- } catch {
2075
- }
2076
- }
2077
- }
2078
- lastSize = stats.size;
2079
- lastMtime = stats.mtimeMs;
2080
- if (newPrompts.length > 0) {
2081
- logger_default.info(`Detected ${newPrompts.length} new prompt(s), syncing...`);
2082
- const response = await client.syncClaudePrompts(newPrompts, {
2083
- terminalSessionId: options.session,
2084
- projectPath: projectFilter
2085
- });
2086
- if (response.success && response.data) {
2087
- logger_default.success(`Synced ${response.data.promptsCreated} prompt(s)`);
2088
- } else {
2089
- logger_default.error(`Sync failed: ${response.error}`);
2090
- }
2091
- }
2092
- } catch (error) {
2093
- logger_default.error(`Watch error: ${error}`);
2094
2685
  }
2095
- };
2096
- await checkAndSync();
2097
- setInterval(checkAndSync, interval);
2686
+ });
2687
+ await watcher.start();
2098
2688
  process.on("SIGINT", () => {
2099
2689
  logger_default.info("\nStopping watcher...");
2690
+ watcher.stop();
2100
2691
  process.exit(0);
2101
2692
  });
2102
2693
  });
@@ -2167,10 +2758,11 @@ Synced to platform: ${response.data.sessions.length} recent sessions`);
2167
2758
 
2168
2759
  // src/commands/project.ts
2169
2760
  import { Command as Command10 } from "commander";
2761
+ import { existsSync as existsSync12, writeFileSync as writeFileSync6 } from "fs";
2170
2762
 
2171
2763
  // src/utils/git.ts
2172
- import { execSync } from "child_process";
2173
- import { join as join4, dirname as dirname2, basename as basename2 } from "path";
2764
+ import { execSync as execSync2 } from "child_process";
2765
+ import { join as join5, dirname as dirname3, basename as basename2 } from "path";
2174
2766
  function getGitInfo(projectPath) {
2175
2767
  const result = {
2176
2768
  isGitRepo: false,
@@ -2181,7 +2773,7 @@ function getGitInfo(projectPath) {
2181
2773
  projectName: null
2182
2774
  };
2183
2775
  try {
2184
- const rootPath = execSync("git rev-parse --show-toplevel", {
2776
+ const rootPath = execSync2("git rev-parse --show-toplevel", {
2185
2777
  cwd: projectPath,
2186
2778
  encoding: "utf-8",
2187
2779
  stdio: ["pipe", "pipe", "pipe"]
@@ -2191,7 +2783,7 @@ function getGitInfo(projectPath) {
2191
2783
  result.rootPath = rootPath;
2192
2784
  result.projectName = basename2(rootPath);
2193
2785
  try {
2194
- result.branch = execSync("git rev-parse --abbrev-ref HEAD", {
2786
+ result.branch = execSync2("git rev-parse --abbrev-ref HEAD", {
2195
2787
  cwd: projectPath,
2196
2788
  encoding: "utf-8",
2197
2789
  stdio: ["pipe", "pipe", "pipe"]
@@ -2199,20 +2791,20 @@ function getGitInfo(projectPath) {
2199
2791
  } catch {
2200
2792
  }
2201
2793
  try {
2202
- result.remote = execSync("git remote get-url origin", {
2794
+ result.remote = execSync2("git remote get-url origin", {
2203
2795
  cwd: projectPath,
2204
2796
  encoding: "utf-8",
2205
2797
  stdio: ["pipe", "pipe", "pipe"]
2206
2798
  }).trim();
2207
2799
  } catch {
2208
2800
  try {
2209
- const remotes = execSync("git remote", {
2801
+ const remotes = execSync2("git remote", {
2210
2802
  cwd: projectPath,
2211
2803
  encoding: "utf-8",
2212
2804
  stdio: ["pipe", "pipe", "pipe"]
2213
2805
  }).trim().split("\n");
2214
2806
  if (remotes.length > 0 && remotes[0]) {
2215
- result.remote = execSync(`git remote get-url ${remotes[0]}`, {
2807
+ result.remote = execSync2(`git remote get-url ${remotes[0]}`, {
2216
2808
  cwd: projectPath,
2217
2809
  encoding: "utf-8",
2218
2810
  stdio: ["pipe", "pipe", "pipe"]
@@ -2288,6 +2880,33 @@ var trackCommand = new Command10("track").description("Enable tracking for the c
2288
2880
  logger_default.blank();
2289
2881
  logger_default.dim("Your Claude Code prompts in this directory will now be synced.");
2290
2882
  logger_default.dim("Run 'skillo untrack' to stop tracking.");
2883
+ try {
2884
+ const listResult = await client.listProjects(true);
2885
+ if (listResult.success && listResult.data?.projects) {
2886
+ const cacheData = {
2887
+ updatedAt: Date.now(),
2888
+ projects: listResult.data.projects.filter((p) => p.trackingEnabled).map((p) => ({ path: p.path, name: p.name }))
2889
+ };
2890
+ ensureDirectory(getDataDir());
2891
+ writeFileSync6(getTrackedProjectsCacheFile(), JSON.stringify(cacheData, null, 2));
2892
+ }
2893
+ } catch {
2894
+ }
2895
+ const { running } = isDaemonRunning2();
2896
+ if (!running) {
2897
+ logger_default.blank();
2898
+ const pid = startDaemonProcess();
2899
+ if (pid) {
2900
+ logger_default.success(`Background sync daemon started (PID: ${pid})`);
2901
+ } else {
2902
+ logger_default.dim("Tip: Run 'skillo start' to enable background sync.");
2903
+ }
2904
+ }
2905
+ const shellIntegrationDir = getShellIntegrationDir();
2906
+ if (!existsSync12(shellIntegrationDir) || !existsSync12(resolve(shellIntegrationDir, "skillo.zsh"))) {
2907
+ logger_default.blank();
2908
+ logger_default.dim("Tip: Run 'skillo setup-shell' to enable terminal auto-detection.");
2909
+ }
2291
2910
  } else {
2292
2911
  logger_default.error("Failed to track project: " + (result.error || "Unknown error"));
2293
2912
  }
@@ -2410,11 +3029,11 @@ async function openBrowser(url) {
2410
3029
  const { exec } = await import("child_process");
2411
3030
  const { promisify } = await import("util");
2412
3031
  const execAsync = promisify(exec);
2413
- const platform = process.platform;
3032
+ const platform2 = process.platform;
2414
3033
  let command;
2415
- if (platform === "darwin") {
3034
+ if (platform2 === "darwin") {
2416
3035
  command = `open "${url}"`;
2417
- } else if (platform === "win32") {
3036
+ } else if (platform2 === "win32") {
2418
3037
  command = `start "" "${url}"`;
2419
3038
  } else {
2420
3039
  command = `xdg-open "${url}"`;
@@ -2428,6 +3047,22 @@ async function openBrowser(url) {
2428
3047
  function sleep(ms) {
2429
3048
  return new Promise((resolve2) => setTimeout(resolve2, ms));
2430
3049
  }
3050
+ async function autoStartDaemon() {
3051
+ try {
3052
+ const { running } = isDaemonRunning2();
3053
+ if (!running) {
3054
+ const pid = startDaemonProcess();
3055
+ if (pid) {
3056
+ logger_default.dim(`Background daemon started (PID: ${pid}).`);
3057
+ }
3058
+ }
3059
+ const result = await installService();
3060
+ if (result.success) {
3061
+ logger_default.dim("Auto-start installed. Daemon will survive reboots.");
3062
+ }
3063
+ } catch {
3064
+ }
3065
+ }
2431
3066
  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) => {
2432
3067
  const client = getApiClient();
2433
3068
  logger_default.blank();
@@ -2450,6 +3085,7 @@ var loginCommand = new Command11("login").description("Login to Skillo platform"
2450
3085
  logger_default.info(`Welcome, ${result.data.user.name}!`);
2451
3086
  logger_default.dim(`Email: ${result.data.user.email}`);
2452
3087
  logger_default.blank();
3088
+ await autoStartDaemon();
2453
3089
  logger_default.dim("Your patterns and skills will now sync with the Skillo platform.");
2454
3090
  } else {
2455
3091
  client.clearApiKey();
@@ -2522,6 +3158,7 @@ var loginCommand = new Command11("login").description("Login to Skillo platform"
2522
3158
  logger_default.dim(`Email: ${authResult.data.user.email}`);
2523
3159
  }
2524
3160
  logger_default.blank();
3161
+ await autoStartDaemon();
2525
3162
  logger_default.dim("Your patterns and skills will now sync with the Skillo platform.");
2526
3163
  return;
2527
3164
  } else {
@@ -2589,8 +3226,8 @@ var whoamiCommand = new Command11("whoami").description("Show current login stat
2589
3226
  });
2590
3227
 
2591
3228
  // src/commands/sync.ts
2592
- import { existsSync as existsSync10, writeFileSync as writeFileSync4, mkdirSync } from "fs";
2593
- import { join as join5 } from "path";
3229
+ import { existsSync as existsSync13, writeFileSync as writeFileSync7, mkdirSync as mkdirSync2 } from "fs";
3230
+ import { join as join6 } from "path";
2594
3231
  import { Command as Command12 } from "commander";
2595
3232
  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(
2596
3233
  async (options) => {
@@ -2606,7 +3243,7 @@ var syncCommand = new Command12("sync").description("Sync data with Skillo platf
2606
3243
  const syncSkills = options.skills || !options.patterns && !options.skills && !options.commands;
2607
3244
  const syncCommands = options.commands || !options.patterns && !options.skills && !options.commands;
2608
3245
  const dbPath = getDbPath();
2609
- if (!existsSync10(dbPath)) {
3246
+ if (!existsSync13(dbPath)) {
2610
3247
  logger_default.error("Database not found. Run 'skillo init' first.");
2611
3248
  return;
2612
3249
  }
@@ -2708,21 +3345,38 @@ async function pullSkills(skills) {
2708
3345
  let downloaded = 0;
2709
3346
  for (const skill of skills) {
2710
3347
  if (!skill.content) continue;
2711
- const skillDir = join5(skillsDir, skill.slug);
2712
- const skillFile = join5(skillDir, "SKILL.md");
2713
- if (!existsSync10(skillDir)) {
2714
- mkdirSync(skillDir, { recursive: true });
3348
+ const skillDir = join6(skillsDir, skill.slug);
3349
+ const skillFile = join6(skillDir, "SKILL.md");
3350
+ if (!existsSync13(skillDir)) {
3351
+ mkdirSync2(skillDir, { recursive: true });
2715
3352
  }
2716
- writeFileSync4(skillFile, skill.content, "utf-8");
3353
+ writeFileSync7(skillFile, skill.content, "utf-8");
2717
3354
  downloaded++;
2718
3355
  logger_default.dim(` Downloaded: ${skill.name}`);
2719
3356
  }
2720
3357
  logger_default.success(`Downloaded ${downloaded} skills to ~/.claude/skills/`);
2721
3358
  }
2722
3359
 
3360
+ // src/commands/tray.ts
3361
+ import { Command as Command13 } from "commander";
3362
+ var trayCommand = new Command13("tray").description("Start the system tray icon").action(async () => {
3363
+ try {
3364
+ const { startTray } = await import("./tray-62I5KIFE.js");
3365
+ await startTray();
3366
+ } catch (error) {
3367
+ if (error instanceof Error && error.message.includes("Cannot find module")) {
3368
+ logger_default.error("System tray not available. The systray2 package may not be installed.");
3369
+ logger_default.dim("Try: npm install -g skillo");
3370
+ } else {
3371
+ logger_default.error(`Tray error: ${error instanceof Error ? error.message : error}`);
3372
+ }
3373
+ process.exit(1);
3374
+ }
3375
+ });
3376
+
2723
3377
  // src/cli.ts
2724
- var VERSION = "0.1.0";
2725
- var program = new Command13();
3378
+ var VERSION = "0.2.1";
3379
+ var program = new Command14();
2726
3380
  program.name("skillo").description(
2727
3381
  `Skillo - Learn workflows by observation, not explanation.
2728
3382
 
@@ -2751,10 +3405,13 @@ program.addCommand(logoutCommand);
2751
3405
  program.addCommand(whoamiCommand);
2752
3406
  program.addCommand(syncCommand);
2753
3407
  program.addCommand(sessionCommand);
3408
+ program.addCommand(sessionAutoCommand);
2754
3409
  program.addCommand(claudeCommand);
2755
3410
  program.addCommand(projectCommand);
2756
3411
  program.addCommand(trackCommand);
2757
3412
  program.addCommand(untrackCommand);
3413
+ program.addCommand(serviceCommand);
3414
+ program.addCommand(trayCommand);
2758
3415
  program.command("version").description("Show version information").action(() => {
2759
3416
  console.log(`Skillo v${VERSION}`);
2760
3417
  console.log("Autonomous workflow learning & skill generation system");