skillo 0.1.5 → 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
@@ -22,7 +22,7 @@ import {
22
22
  } from "./chunk-WJKZWKER.js";
23
23
 
24
24
  // src/cli.ts
25
- import { Command as Command13 } from "commander";
25
+ import { Command as Command14 } from "commander";
26
26
 
27
27
  // src/commands/init.ts
28
28
  import { existsSync as existsSync2 } from "fs";
@@ -593,15 +593,444 @@ var initCommand = new Command("init").description("Initialize Skillo configurati
593
593
  });
594
594
 
595
595
  // src/commands/status.ts
596
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
596
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
597
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
598
1027
  function isDaemonRunning() {
599
1028
  const pidFile = getPidFile();
600
- if (!existsSync3(pidFile)) {
1029
+ if (!existsSync4(pidFile)) {
601
1030
  return { running: false, pid: null };
602
1031
  }
603
1032
  try {
604
- const pidStr = readFileSync2(pidFile, "utf-8").trim();
1033
+ const pidStr = readFileSync3(pidFile, "utf-8").trim();
605
1034
  const pid = parseInt(pidStr, 10);
606
1035
  if (isNaN(pid)) {
607
1036
  return { running: false, pid: null };
@@ -625,11 +1054,18 @@ var statusCommand = new Command2("status").description("Show daemon status and s
625
1054
  logger_default.stopped("Daemon is not running");
626
1055
  logger_default.blank();
627
1056
  logger_default.dim("Start with: skillo start");
628
- return;
629
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;
630
1066
  logger_default.blank();
631
1067
  const dbPath = getDbPath();
632
- if (!existsSync3(dbPath)) {
1068
+ if (!existsSync4(dbPath)) {
633
1069
  logger_default.warn("Database not found. Run 'skillo init' first.");
634
1070
  return;
635
1071
  }
@@ -646,10 +1082,10 @@ var statusCommand = new Command2("status").description("Show daemon status and s
646
1082
  ]);
647
1083
  logger_default.blank();
648
1084
  const logFile = getLogFile();
649
- if (existsSync3(logFile)) {
1085
+ if (existsSync4(logFile)) {
650
1086
  logger_default.dim("Recent log entries:");
651
1087
  try {
652
- const content = readFileSync2(logFile, "utf-8");
1088
+ const content = readFileSync3(logFile, "utf-8");
653
1089
  const lines = content.split("\n").filter(Boolean).slice(-5);
654
1090
  lines.forEach((line) => {
655
1091
  logger_default.dim(` ${line}`);
@@ -661,7 +1097,7 @@ var statusCommand = new Command2("status").description("Show daemon status and s
661
1097
  });
662
1098
 
663
1099
  // src/commands/config.ts
664
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
1100
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
665
1101
  import { Command as Command3 } from "commander";
666
1102
  import YAML from "yaml";
667
1103
  var configCommand = new Command3("config").description(
@@ -669,12 +1105,12 @@ var configCommand = new Command3("config").description(
669
1105
  );
670
1106
  configCommand.command("show").description("Show current configuration").action(async () => {
671
1107
  const configFile = getConfigFile();
672
- if (!existsSync4(configFile)) {
1108
+ if (!existsSync5(configFile)) {
673
1109
  logger_default.error("Configuration not found.");
674
1110
  logger_default.dim("Run 'skillo init' first.");
675
1111
  process.exit(1);
676
1112
  }
677
- const content = readFileSync3(configFile, "utf-8");
1113
+ const content = readFileSync4(configFile, "utf-8");
678
1114
  logger_default.blank();
679
1115
  logger_default.bold(`Configuration file: ${configFile}`);
680
1116
  logger_default.blank();
@@ -682,7 +1118,7 @@ configCommand.command("show").description("Show current configuration").action(a
682
1118
  });
683
1119
  configCommand.command("get <key>").description("Get a configuration value (use dot notation, e.g., patternDetection.minCount)").action(async (key) => {
684
1120
  const configFile = getConfigFile();
685
- if (!existsSync4(configFile)) {
1121
+ if (!existsSync5(configFile)) {
686
1122
  logger_default.error("Configuration not found.");
687
1123
  process.exit(1);
688
1124
  }
@@ -700,7 +1136,7 @@ configCommand.command("get <key>").description("Get a configuration value (use d
700
1136
  });
701
1137
  configCommand.command("set <key> <value>").description("Set a configuration value").action(async (key, value) => {
702
1138
  const configFile = getConfigFile();
703
- if (!existsSync4(configFile)) {
1139
+ if (!existsSync5(configFile)) {
704
1140
  logger_default.error("Configuration not found.");
705
1141
  process.exit(1);
706
1142
  }
@@ -742,7 +1178,7 @@ configCommand.command("path").description("Show configuration file path").action
742
1178
  });
743
1179
 
744
1180
  // src/commands/patterns.ts
745
- import { existsSync as existsSync5 } from "fs";
1181
+ import { existsSync as existsSync6 } from "fs";
746
1182
  import { Command as Command4 } from "commander";
747
1183
  import chalk2 from "chalk";
748
1184
  var patternsCommand = new Command4("patterns").description(
@@ -750,7 +1186,7 @@ var patternsCommand = new Command4("patterns").description(
750
1186
  );
751
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) => {
752
1188
  const dbPath = getDbPath();
753
- if (!existsSync5(dbPath)) {
1189
+ if (!existsSync6(dbPath)) {
754
1190
  logger_default.error("Database not found. Run 'skillo init' first.");
755
1191
  process.exit(1);
756
1192
  }
@@ -787,7 +1223,7 @@ patternsCommand.command("list").description("List detected patterns").option("-t
787
1223
  });
788
1224
  patternsCommand.command("show <id>").description("Show pattern details").action(async (id) => {
789
1225
  const dbPath = getDbPath();
790
- if (!existsSync5(dbPath)) {
1226
+ if (!existsSync6(dbPath)) {
791
1227
  logger_default.error("Database not found. Run 'skillo init' first.");
792
1228
  process.exit(1);
793
1229
  }
@@ -830,7 +1266,7 @@ patternsCommand.command("show <id>").description("Show pattern details").action(
830
1266
  });
831
1267
  patternsCommand.command("ignore <id>").description("Ignore a pattern (never suggest again)").option("-r, --reason <reason>", "Reason for ignoring").action(async (id, options) => {
832
1268
  const dbPath = getDbPath();
833
- if (!existsSync5(dbPath)) {
1269
+ if (!existsSync6(dbPath)) {
834
1270
  logger_default.error("Database not found. Run 'skillo init' first.");
835
1271
  process.exit(1);
836
1272
  }
@@ -852,11 +1288,11 @@ patternsCommand.command("ignore <id>").description("Ignore a pattern (never sugg
852
1288
  });
853
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) => {
854
1290
  const { getApiClient: getApiClient2 } = await import("./api-client-WO6NUCIJ.js");
855
- const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync2 } = await import("fs");
856
- const { join: join6 } = await import("path");
1291
+ const { writeFileSync: writeFileSync8, mkdirSync: mkdirSync3 } = await import("fs");
1292
+ const { join: join7 } = await import("path");
857
1293
  const { getSkillsDir: getSkillsDir2, ensureDirectory: ensureDirectory2 } = await import("./paths-INOKEM66.js");
858
1294
  const dbPath = getDbPath();
859
- if (!existsSync5(dbPath)) {
1295
+ if (!existsSync6(dbPath)) {
860
1296
  logger_default.error("Database not found. Run 'skillo init' first.");
861
1297
  process.exit(1);
862
1298
  }
@@ -907,10 +1343,10 @@ patternsCommand.command("generate <id>").description("Generate a skill from a pa
907
1343
  } else {
908
1344
  const skillsDir = getSkillsDir2();
909
1345
  ensureDirectory2(skillsDir);
910
- const skillDir = join6(skillsDir, skill.slug);
911
- const skillFile = join6(skillDir, "SKILL.md");
912
- mkdirSync2(skillDir, { recursive: true });
913
- writeFileSync7(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");
914
1350
  await db.updatePatternStatus(id, "converted");
915
1351
  logger_default.success(`Skill saved to: ~/.claude/skills/${skill.slug}/SKILL.md`);
916
1352
  logger_default.blank();
@@ -925,7 +1361,7 @@ patternsCommand.command("clear").description("Clear all patterns").option("-f, -
925
1361
  return;
926
1362
  }
927
1363
  const dbPath = getDbPath();
928
- if (!existsSync5(dbPath)) {
1364
+ if (!existsSync6(dbPath)) {
929
1365
  logger_default.error("Database not found.");
930
1366
  process.exit(1);
931
1367
  }
@@ -933,8 +1369,8 @@ patternsCommand.command("clear").description("Clear all patterns").option("-f, -
933
1369
  });
934
1370
 
935
1371
  // src/commands/skills.ts
936
- import { existsSync as existsSync6, readdirSync, rmSync } from "fs";
937
- import { join } from "path";
1372
+ import { existsSync as existsSync7, readdirSync as readdirSync2, rmSync } from "fs";
1373
+ import { join as join2 } from "path";
938
1374
  import { Command as Command5 } from "commander";
939
1375
  import chalk3 from "chalk";
940
1376
  var skillsCommand = new Command5("skills").description(
@@ -942,7 +1378,7 @@ var skillsCommand = new Command5("skills").description(
942
1378
  );
943
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) => {
944
1380
  const dbPath = getDbPath();
945
- if (!existsSync6(dbPath)) {
1381
+ if (!existsSync7(dbPath)) {
946
1382
  logger_default.error("Database not found. Run 'skillo init' first.");
947
1383
  process.exit(1);
948
1384
  }
@@ -955,11 +1391,11 @@ skillsCommand.command("list").description("List all skills").option("-s, --sourc
955
1391
  db.close();
956
1392
  const skillsDir = getSkillsDir();
957
1393
  const fsSkills = [];
958
- if (existsSync6(skillsDir)) {
959
- const entries = readdirSync(skillsDir, { withFileTypes: true });
1394
+ if (existsSync7(skillsDir)) {
1395
+ const entries = readdirSync2(skillsDir, { withFileTypes: true });
960
1396
  entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).forEach((e) => {
961
- const skillMd = join(skillsDir, e.name, "SKILL.md");
962
- if (existsSync6(skillMd)) {
1397
+ const skillMd = join2(skillsDir, e.name, "SKILL.md");
1398
+ if (existsSync7(skillMd)) {
963
1399
  fsSkills.push(e.name);
964
1400
  }
965
1401
  });
@@ -999,7 +1435,7 @@ skillsCommand.command("list").description("List all skills").option("-s, --sourc
999
1435
  });
1000
1436
  skillsCommand.command("show <name>").description("Show skill details").action(async (name) => {
1001
1437
  const dbPath = getDbPath();
1002
- if (!existsSync6(dbPath)) {
1438
+ if (!existsSync7(dbPath)) {
1003
1439
  logger_default.error("Database not found. Run 'skillo init' first.");
1004
1440
  process.exit(1);
1005
1441
  }
@@ -1008,12 +1444,12 @@ skillsCommand.command("show <name>").description("Show skill details").action(as
1008
1444
  const skill = await db.getSkill(name);
1009
1445
  db.close();
1010
1446
  if (!skill) {
1011
- const skillPath = join(getSkillsDir(), name, "SKILL.md");
1012
- if (existsSync6(skillPath)) {
1447
+ const skillPath = join2(getSkillsDir(), name, "SKILL.md");
1448
+ if (existsSync7(skillPath)) {
1013
1449
  logger_default.blank();
1014
1450
  logger_default.bold(name);
1015
1451
  logger_default.blank();
1016
- logger_default.dim(` Path: ${join(getSkillsDir(), name)}`);
1452
+ logger_default.dim(` Path: ${join2(getSkillsDir(), name)}`);
1017
1453
  logger_default.dim(` Status: Not registered in database`);
1018
1454
  logger_default.blank();
1019
1455
  return;
@@ -1040,15 +1476,15 @@ skillsCommand.command("show <name>").description("Show skill details").action(as
1040
1476
  });
1041
1477
  skillsCommand.command("delete <name>").description("Delete a skill").option("-f, --force", "Skip confirmation").action(async (name, options) => {
1042
1478
  const dbPath = getDbPath();
1043
- if (!existsSync6(dbPath)) {
1479
+ if (!existsSync7(dbPath)) {
1044
1480
  logger_default.error("Database not found.");
1045
1481
  process.exit(1);
1046
1482
  }
1047
1483
  const db = new SkilloDatabase();
1048
1484
  await db.initialize();
1049
1485
  const skill = await db.getSkill(name);
1050
- const skillDir = skill?.path || join(getSkillsDir(), name);
1051
- if (!skill && !existsSync6(skillDir)) {
1486
+ const skillDir = skill?.path || join2(getSkillsDir(), name);
1487
+ if (!skill && !existsSync7(skillDir)) {
1052
1488
  db.close();
1053
1489
  logger_default.error(`Skill not found: ${name}`);
1054
1490
  process.exit(1);
@@ -1059,7 +1495,7 @@ skillsCommand.command("delete <name>").description("Delete a skill").option("-f,
1059
1495
  db.close();
1060
1496
  return;
1061
1497
  }
1062
- if (existsSync6(skillDir)) {
1498
+ if (existsSync7(skillDir)) {
1063
1499
  rmSync(skillDir, { recursive: true, force: true });
1064
1500
  }
1065
1501
  if (skill) {
@@ -1073,7 +1509,7 @@ skillsCommand.command("path").description("Show skills directory path").action(a
1073
1509
  });
1074
1510
  skillsCommand.command("open").description("Open skills directory in file manager").action(async () => {
1075
1511
  const skillsDir = getSkillsDir();
1076
- if (!existsSync6(skillsDir)) {
1512
+ if (!existsSync7(skillsDir)) {
1077
1513
  logger_default.error("Skills directory not found. Run 'skillo init' first.");
1078
1514
  process.exit(1);
1079
1515
  }
@@ -1088,10 +1524,10 @@ skillsCommand.command("open").description("Open skills directory in file manager
1088
1524
  });
1089
1525
 
1090
1526
  // src/commands/shell.ts
1091
- 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";
1092
1528
  import { spawn } from "child_process";
1093
- import { homedir } from "os";
1094
- import { join as join2 } from "path";
1529
+ import { homedir as homedir2 } from "os";
1530
+ import { join as join3 } from "path";
1095
1531
  import { Command as Command6 } from "commander";
1096
1532
  function normalizeCommand(cmd) {
1097
1533
  const variables = {};
@@ -1128,7 +1564,7 @@ function normalizeCommand(cmd) {
1128
1564
  return { normalized: normalized.trim(), variables };
1129
1565
  }
1130
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) => {
1131
- if (!existsSync7(getConfigFile())) {
1567
+ if (!existsSync8(getConfigFile())) {
1132
1568
  logger_default.error("Skillo not initialized. Run 'skillo init' first.");
1133
1569
  process.exit(1);
1134
1570
  }
@@ -1224,7 +1660,7 @@ var recordCommand = new Command6("record").description("Record a command (used b
1224
1660
  );
1225
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) => {
1226
1662
  logger_default.blank();
1227
- const home = homedir();
1663
+ const home = homedir2();
1228
1664
  const dataDir = getDataDir();
1229
1665
  ensureDirectory(dataDir);
1230
1666
  let shell = null;
@@ -1272,7 +1708,7 @@ var setupShellCommand = new Command6("setup-shell").description("Set up shell in
1272
1708
  logger_default.blank();
1273
1709
  });
1274
1710
  async function installShellIntegration(shell, home, dataDir) {
1275
- const scriptPath = join2(dataDir, "shell-integration");
1711
+ const scriptPath = join3(dataDir, "shell-integration");
1276
1712
  ensureDirectory(scriptPath);
1277
1713
  if (shell === "powershell") {
1278
1714
  const psScript = `# Skillo CLI Integration
@@ -1420,18 +1856,18 @@ $null = Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action {
1420
1856
 
1421
1857
  Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
1422
1858
  `;
1423
- const psScriptFile = join2(scriptPath, "skillo.ps1");
1424
- writeFileSync2(psScriptFile, psScript, "utf-8");
1859
+ const psScriptFile = join3(scriptPath, "skillo.ps1");
1860
+ writeFileSync3(psScriptFile, psScript, "utf-8");
1425
1861
  logger_default.success(`Created ${psScriptFile}`);
1426
- const profilePath = join2(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
1427
- const profileDir = join2(home, "Documents", "WindowsPowerShell");
1862
+ const profilePath = join3(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
1863
+ const profileDir = join3(home, "Documents", "WindowsPowerShell");
1428
1864
  ensureDirectory(profileDir);
1429
1865
  const sourceLine = `
1430
1866
  # Skillo CLI Integration
1431
1867
  . "${psScriptFile}"
1432
1868
  `;
1433
- if (existsSync7(profilePath)) {
1434
- const content = readFileSync4(profilePath, "utf-8");
1869
+ if (existsSync8(profilePath)) {
1870
+ const content = readFileSync5(profilePath, "utf-8");
1435
1871
  if (!content.includes("Skillo CLI Integration")) {
1436
1872
  appendFileSync(profilePath, sourceLine);
1437
1873
  logger_default.success("Added to PowerShell profile");
@@ -1439,7 +1875,7 @@ Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
1439
1875
  logger_default.dim("Already in PowerShell profile");
1440
1876
  }
1441
1877
  } else {
1442
- writeFileSync2(profilePath, sourceLine, "utf-8");
1878
+ writeFileSync3(profilePath, sourceLine, "utf-8");
1443
1879
  logger_default.success("Created PowerShell profile");
1444
1880
  }
1445
1881
  } else if (shell === "bash") {
@@ -1509,16 +1945,16 @@ Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
1509
1945
  "",
1510
1946
  'echo -e "\\033[90mSkillo: Command tracking enabled\\033[0m"'
1511
1947
  ].join("\n");
1512
- const bashScriptFile = join2(scriptPath, "skillo.bash");
1513
- writeFileSync2(bashScriptFile, bashScript, "utf-8");
1948
+ const bashScriptFile = join3(scriptPath, "skillo.bash");
1949
+ writeFileSync3(bashScriptFile, bashScript, "utf-8");
1514
1950
  logger_default.success(`Created ${bashScriptFile}`);
1515
- const bashrcPath = join2(home, ".bashrc");
1951
+ const bashrcPath = join3(home, ".bashrc");
1516
1952
  const sourceLine = `
1517
1953
  # Skillo CLI Integration
1518
1954
  [ -f "${bashScriptFile}" ] && source "${bashScriptFile}"
1519
1955
  `;
1520
- if (existsSync7(bashrcPath)) {
1521
- const content = readFileSync4(bashrcPath, "utf-8");
1956
+ if (existsSync8(bashrcPath)) {
1957
+ const content = readFileSync5(bashrcPath, "utf-8");
1522
1958
  if (!content.includes("Skillo CLI Integration")) {
1523
1959
  appendFileSync(bashrcPath, sourceLine);
1524
1960
  logger_default.success("Added to ~/.bashrc");
@@ -1526,7 +1962,7 @@ Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
1526
1962
  logger_default.dim("Already in ~/.bashrc");
1527
1963
  }
1528
1964
  } else {
1529
- writeFileSync2(bashrcPath, sourceLine, "utf-8");
1965
+ writeFileSync3(bashrcPath, sourceLine, "utf-8");
1530
1966
  logger_default.success("Created ~/.bashrc");
1531
1967
  }
1532
1968
  } else if (shell === "zsh") {
@@ -1592,16 +2028,16 @@ Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
1592
2028
  "",
1593
2029
  'echo -e "\\033[90mSkillo: Command tracking enabled\\033[0m"'
1594
2030
  ].join("\n");
1595
- const zshScriptFile = join2(scriptPath, "skillo.zsh");
1596
- writeFileSync2(zshScriptFile, zshScript, "utf-8");
2031
+ const zshScriptFile = join3(scriptPath, "skillo.zsh");
2032
+ writeFileSync3(zshScriptFile, zshScript, "utf-8");
1597
2033
  logger_default.success(`Created ${zshScriptFile}`);
1598
- const zshrcPath = join2(home, ".zshrc");
2034
+ const zshrcPath = join3(home, ".zshrc");
1599
2035
  const sourceLine = `
1600
2036
  # Skillo CLI Integration
1601
2037
  [ -f "${zshScriptFile}" ] && source "${zshScriptFile}"
1602
2038
  `;
1603
- if (existsSync7(zshrcPath)) {
1604
- const content = readFileSync4(zshrcPath, "utf-8");
2039
+ if (existsSync8(zshrcPath)) {
2040
+ const content = readFileSync5(zshrcPath, "utf-8");
1605
2041
  if (!content.includes("Skillo CLI Integration")) {
1606
2042
  appendFileSync(zshrcPath, sourceLine);
1607
2043
  logger_default.success("Added to ~/.zshrc");
@@ -1609,7 +2045,7 @@ Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
1609
2045
  logger_default.dim("Already in ~/.zshrc");
1610
2046
  }
1611
2047
  } else {
1612
- writeFileSync2(zshrcPath, sourceLine, "utf-8");
2048
+ writeFileSync3(zshrcPath, sourceLine, "utf-8");
1613
2049
  logger_default.success("Created ~/.zshrc");
1614
2050
  }
1615
2051
  } else if (shell === "fish") {
@@ -1627,20 +2063,20 @@ Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
1627
2063
  "",
1628
2064
  'echo -e "\\033[90mSkillo: Command tracking enabled\\033[0m"'
1629
2065
  ].join("\n");
1630
- const fishScriptFile = join2(scriptPath, "skillo.fish");
1631
- writeFileSync2(fishScriptFile, fishScript, "utf-8");
2066
+ const fishScriptFile = join3(scriptPath, "skillo.fish");
2067
+ writeFileSync3(fishScriptFile, fishScript, "utf-8");
1632
2068
  logger_default.success(`Created ${fishScriptFile}`);
1633
- const fishConfigDir = join2(home, ".config", "fish");
2069
+ const fishConfigDir = join3(home, ".config", "fish");
1634
2070
  ensureDirectory(fishConfigDir);
1635
- const fishConfigPath = join2(fishConfigDir, "config.fish");
2071
+ const fishConfigPath = join3(fishConfigDir, "config.fish");
1636
2072
  const sourceLine = `
1637
2073
  # Skillo CLI Integration
1638
2074
  if test -f "${fishScriptFile}"
1639
2075
  source "${fishScriptFile}"
1640
2076
  end
1641
2077
  `;
1642
- if (existsSync7(fishConfigPath)) {
1643
- const content = readFileSync4(fishConfigPath, "utf-8");
2078
+ if (existsSync8(fishConfigPath)) {
2079
+ const content = readFileSync5(fishConfigPath, "utf-8");
1644
2080
  if (!content.includes("Skillo CLI Integration")) {
1645
2081
  appendFileSync(fishConfigPath, sourceLine);
1646
2082
  logger_default.success("Added to ~/.config/fish/config.fish");
@@ -1648,41 +2084,41 @@ end
1648
2084
  logger_default.dim("Already in fish config");
1649
2085
  }
1650
2086
  } else {
1651
- writeFileSync2(fishConfigPath, sourceLine, "utf-8");
2087
+ writeFileSync3(fishConfigPath, sourceLine, "utf-8");
1652
2088
  logger_default.success("Created fish config");
1653
2089
  }
1654
2090
  }
1655
2091
  }
1656
2092
  async function uninstallShellIntegration(shell, home) {
1657
2093
  const removeFromFile = (filePath) => {
1658
- if (!existsSync7(filePath)) return;
1659
- let content = readFileSync4(filePath, "utf-8");
2094
+ if (!existsSync8(filePath)) return;
2095
+ let content = readFileSync5(filePath, "utf-8");
1660
2096
  content = content.replace(/\n# Skillo CLI Integration\n[^\n]*\n/g, "\n");
1661
- writeFileSync2(filePath, content, "utf-8");
2097
+ writeFileSync3(filePath, content, "utf-8");
1662
2098
  };
1663
2099
  if (shell === "powershell") {
1664
- const profilePath = join2(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
2100
+ const profilePath = join3(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
1665
2101
  removeFromFile(profilePath);
1666
2102
  } else if (shell === "bash") {
1667
- removeFromFile(join2(home, ".bashrc"));
2103
+ removeFromFile(join3(home, ".bashrc"));
1668
2104
  } else if (shell === "zsh") {
1669
- removeFromFile(join2(home, ".zshrc"));
2105
+ removeFromFile(join3(home, ".zshrc"));
1670
2106
  } else if (shell === "fish") {
1671
- removeFromFile(join2(home, ".config", "fish", "config.fish"));
2107
+ removeFromFile(join3(home, ".config", "fish", "config.fish"));
1672
2108
  }
1673
2109
  }
1674
2110
 
1675
2111
  // src/commands/daemon.ts
1676
- 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";
1677
2113
  import { fork } from "child_process";
1678
2114
  import { Command as Command7 } from "commander";
1679
2115
  function isDaemonRunning2() {
1680
2116
  const pidFile = getPidFile();
1681
- if (!existsSync8(pidFile)) {
2117
+ if (!existsSync9(pidFile)) {
1682
2118
  return { running: false, pid: null };
1683
2119
  }
1684
2120
  try {
1685
- const pidStr = readFileSync5(pidFile, "utf-8").trim();
2121
+ const pidStr = readFileSync6(pidFile, "utf-8").trim();
1686
2122
  const pid = parseInt(pidStr, 10);
1687
2123
  if (isNaN(pid)) {
1688
2124
  return { running: false, pid: null };
@@ -1692,7 +2128,7 @@ function isDaemonRunning2() {
1692
2128
  return { running: true, pid };
1693
2129
  } catch {
1694
2130
  try {
1695
- unlinkSync(pidFile);
2131
+ unlinkSync2(pidFile);
1696
2132
  } catch {
1697
2133
  }
1698
2134
  return { running: false, pid: null };
@@ -1713,13 +2149,13 @@ function startDaemonProcess() {
1713
2149
  });
1714
2150
  child.unref();
1715
2151
  if (child.pid) {
1716
- writeFileSync3(getPidFile(), String(child.pid));
2152
+ writeFileSync4(getPidFile(), String(child.pid));
1717
2153
  return child.pid;
1718
2154
  }
1719
2155
  return null;
1720
2156
  }
1721
2157
  var startCommand = new Command7("start").description("Start the Skillo daemon").option("-f, --foreground", "Run in foreground (don't daemonize)").action(async (options) => {
1722
- if (!existsSync8(getConfigFile())) {
2158
+ if (!existsSync9(getConfigFile())) {
1723
2159
  logger_default.error("Skillo not initialized.");
1724
2160
  logger_default.dim("Run 'skillo init' first.");
1725
2161
  process.exit(1);
@@ -1745,6 +2181,10 @@ var startCommand = new Command7("start").description("Start the Skillo daemon").
1745
2181
  const daemonPid = startDaemonProcess();
1746
2182
  if (daemonPid) {
1747
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.");
2187
+ }
1748
2188
  logger_default.blank();
1749
2189
  logger_default.dim("Use 'skillo status' to check daemon status");
1750
2190
  logger_default.dim("Use 'skillo stop' to stop the daemon");
@@ -1761,6 +2201,10 @@ var stopCommand = new Command7("stop").description("Stop the Skillo daemon").act
1761
2201
  return;
1762
2202
  }
1763
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
+ }
1764
2208
  try {
1765
2209
  process.kill(pid, "SIGTERM");
1766
2210
  logger_default.success("Daemon stopped");
@@ -1775,16 +2219,16 @@ var stopCommand = new Command7("stop").description("Stop the Skillo daemon").act
1775
2219
  }
1776
2220
  }
1777
2221
  const pidFile = getPidFile();
1778
- if (existsSync8(pidFile)) {
2222
+ if (existsSync9(pidFile)) {
1779
2223
  try {
1780
- unlinkSync(pidFile);
2224
+ unlinkSync2(pidFile);
1781
2225
  } catch {
1782
2226
  }
1783
2227
  }
1784
2228
  });
1785
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) => {
1786
2230
  const logFile = getLogFile();
1787
- if (!existsSync8(logFile)) {
2231
+ if (!existsSync9(logFile)) {
1788
2232
  logger_default.dim("No logs found.");
1789
2233
  return;
1790
2234
  }
@@ -1803,23 +2247,70 @@ var logsCommand = new Command7("logs").description("Show daemon logs").option("-
1803
2247
  }
1804
2248
  });
1805
2249
  function showLogs(logFile, numLines) {
1806
- const content = readFileSync5(logFile, "utf-8");
2250
+ const content = readFileSync6(logFile, "utf-8");
1807
2251
  const lines = content.split("\n").filter(Boolean);
1808
2252
  const lastLines = lines.slice(-numLines);
1809
2253
  lastLines.forEach((line) => console.log(line));
1810
2254
  }
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
+ );
1811
2302
  async function runDaemonForeground() {
1812
2303
  const pidFile = getPidFile();
1813
- writeFileSync3(pidFile, String(process.pid));
2304
+ writeFileSync4(pidFile, String(process.pid));
1814
2305
  logger_default.dim(`Daemon PID: ${process.pid}`);
1815
2306
  logger_default.dim(`Log file: ${getLogFile()}`);
1816
2307
  logger_default.blank();
1817
2308
  const cleanup = () => {
1818
2309
  logger_default.blank();
1819
2310
  logger_default.info("Shutting down daemon...");
1820
- if (existsSync8(pidFile)) {
2311
+ if (existsSync9(pidFile)) {
1821
2312
  try {
1822
- unlinkSync(pidFile);
2313
+ unlinkSync2(pidFile);
1823
2314
  } catch {
1824
2315
  }
1825
2316
  }
@@ -1871,7 +2362,7 @@ async function runDaemonForeground() {
1871
2362
 
1872
2363
  // src/commands/session.ts
1873
2364
  import { Command as Command8 } from "commander";
1874
- import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2 } from "fs";
2365
+ import { existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3 } from "fs";
1875
2366
  var sessionCommand = new Command8("session").description("Manage terminal sessions");
1876
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) => {
1877
2368
  try {
@@ -1924,18 +2415,18 @@ var sessionAutoCommand = new Command8("session-auto").description("Auto-detect p
1924
2415
  const sessionsDir = getActiveSessionsDir();
1925
2416
  const sessionFile = `${sessionsDir}/${terminalPid}.json`;
1926
2417
  let currentSession = null;
1927
- if (existsSync9(sessionFile)) {
2418
+ if (existsSync10(sessionFile)) {
1928
2419
  try {
1929
- currentSession = JSON.parse(readFileSync6(sessionFile, "utf-8"));
2420
+ currentSession = JSON.parse(readFileSync7(sessionFile, "utf-8"));
1930
2421
  } catch {
1931
2422
  currentSession = null;
1932
2423
  }
1933
2424
  }
1934
2425
  const cacheFile = getTrackedProjectsCacheFile();
1935
2426
  let matchedProject = null;
1936
- if (existsSync9(cacheFile)) {
2427
+ if (existsSync10(cacheFile)) {
1937
2428
  try {
1938
- const cache = JSON.parse(readFileSync6(cacheFile, "utf-8"));
2429
+ const cache = JSON.parse(readFileSync7(cacheFile, "utf-8"));
1939
2430
  const normalizedCwd = cwd.toLowerCase().replace(/\/+$/, "");
1940
2431
  for (const project of cache.projects || []) {
1941
2432
  const normalizedPath = project.path.toLowerCase().replace(/\/+$/, "");
@@ -1960,7 +2451,7 @@ var sessionAutoCommand = new Command8("session-auto").description("Auto-detect p
1960
2451
  } catch {
1961
2452
  }
1962
2453
  try {
1963
- unlinkSync2(sessionFile);
2454
+ unlinkSync3(sessionFile);
1964
2455
  } catch {
1965
2456
  }
1966
2457
  }
@@ -1969,7 +2460,7 @@ var sessionAutoCommand = new Command8("session-auto").description("Auto-detect p
1969
2460
  const result = await client.startSession(shell, matchedProject.path);
1970
2461
  if (result.success && result.data?.sessionId) {
1971
2462
  ensureDirectory(sessionsDir);
1972
- writeFileSync4(sessionFile, JSON.stringify({
2463
+ writeFileSync5(sessionFile, JSON.stringify({
1973
2464
  sessionId: result.data.sessionId,
1974
2465
  projectPath: matchedProject.path,
1975
2466
  projectName: matchedProject.name,
@@ -2267,11 +2758,11 @@ Synced to platform: ${response.data.sessions.length} recent sessions`);
2267
2758
 
2268
2759
  // src/commands/project.ts
2269
2760
  import { Command as Command10 } from "commander";
2270
- import { existsSync as existsSync11, writeFileSync as writeFileSync5 } from "fs";
2761
+ import { existsSync as existsSync12, writeFileSync as writeFileSync6 } from "fs";
2271
2762
 
2272
2763
  // src/utils/git.ts
2273
- import { execSync } from "child_process";
2274
- 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";
2275
2766
  function getGitInfo(projectPath) {
2276
2767
  const result = {
2277
2768
  isGitRepo: false,
@@ -2282,7 +2773,7 @@ function getGitInfo(projectPath) {
2282
2773
  projectName: null
2283
2774
  };
2284
2775
  try {
2285
- const rootPath = execSync("git rev-parse --show-toplevel", {
2776
+ const rootPath = execSync2("git rev-parse --show-toplevel", {
2286
2777
  cwd: projectPath,
2287
2778
  encoding: "utf-8",
2288
2779
  stdio: ["pipe", "pipe", "pipe"]
@@ -2292,7 +2783,7 @@ function getGitInfo(projectPath) {
2292
2783
  result.rootPath = rootPath;
2293
2784
  result.projectName = basename2(rootPath);
2294
2785
  try {
2295
- result.branch = execSync("git rev-parse --abbrev-ref HEAD", {
2786
+ result.branch = execSync2("git rev-parse --abbrev-ref HEAD", {
2296
2787
  cwd: projectPath,
2297
2788
  encoding: "utf-8",
2298
2789
  stdio: ["pipe", "pipe", "pipe"]
@@ -2300,20 +2791,20 @@ function getGitInfo(projectPath) {
2300
2791
  } catch {
2301
2792
  }
2302
2793
  try {
2303
- result.remote = execSync("git remote get-url origin", {
2794
+ result.remote = execSync2("git remote get-url origin", {
2304
2795
  cwd: projectPath,
2305
2796
  encoding: "utf-8",
2306
2797
  stdio: ["pipe", "pipe", "pipe"]
2307
2798
  }).trim();
2308
2799
  } catch {
2309
2800
  try {
2310
- const remotes = execSync("git remote", {
2801
+ const remotes = execSync2("git remote", {
2311
2802
  cwd: projectPath,
2312
2803
  encoding: "utf-8",
2313
2804
  stdio: ["pipe", "pipe", "pipe"]
2314
2805
  }).trim().split("\n");
2315
2806
  if (remotes.length > 0 && remotes[0]) {
2316
- result.remote = execSync(`git remote get-url ${remotes[0]}`, {
2807
+ result.remote = execSync2(`git remote get-url ${remotes[0]}`, {
2317
2808
  cwd: projectPath,
2318
2809
  encoding: "utf-8",
2319
2810
  stdio: ["pipe", "pipe", "pipe"]
@@ -2397,7 +2888,7 @@ var trackCommand = new Command10("track").description("Enable tracking for the c
2397
2888
  projects: listResult.data.projects.filter((p) => p.trackingEnabled).map((p) => ({ path: p.path, name: p.name }))
2398
2889
  };
2399
2890
  ensureDirectory(getDataDir());
2400
- writeFileSync5(getTrackedProjectsCacheFile(), JSON.stringify(cacheData, null, 2));
2891
+ writeFileSync6(getTrackedProjectsCacheFile(), JSON.stringify(cacheData, null, 2));
2401
2892
  }
2402
2893
  } catch {
2403
2894
  }
@@ -2412,7 +2903,7 @@ var trackCommand = new Command10("track").description("Enable tracking for the c
2412
2903
  }
2413
2904
  }
2414
2905
  const shellIntegrationDir = getShellIntegrationDir();
2415
- if (!existsSync11(shellIntegrationDir) || !existsSync11(resolve(shellIntegrationDir, "skillo.zsh"))) {
2906
+ if (!existsSync12(shellIntegrationDir) || !existsSync12(resolve(shellIntegrationDir, "skillo.zsh"))) {
2416
2907
  logger_default.blank();
2417
2908
  logger_default.dim("Tip: Run 'skillo setup-shell' to enable terminal auto-detection.");
2418
2909
  }
@@ -2538,11 +3029,11 @@ async function openBrowser(url) {
2538
3029
  const { exec } = await import("child_process");
2539
3030
  const { promisify } = await import("util");
2540
3031
  const execAsync = promisify(exec);
2541
- const platform = process.platform;
3032
+ const platform2 = process.platform;
2542
3033
  let command;
2543
- if (platform === "darwin") {
3034
+ if (platform2 === "darwin") {
2544
3035
  command = `open "${url}"`;
2545
- } else if (platform === "win32") {
3036
+ } else if (platform2 === "win32") {
2546
3037
  command = `start "" "${url}"`;
2547
3038
  } else {
2548
3039
  command = `xdg-open "${url}"`;
@@ -2556,6 +3047,22 @@ async function openBrowser(url) {
2556
3047
  function sleep(ms) {
2557
3048
  return new Promise((resolve2) => setTimeout(resolve2, ms));
2558
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
+ }
2559
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) => {
2560
3067
  const client = getApiClient();
2561
3068
  logger_default.blank();
@@ -2578,6 +3085,7 @@ var loginCommand = new Command11("login").description("Login to Skillo platform"
2578
3085
  logger_default.info(`Welcome, ${result.data.user.name}!`);
2579
3086
  logger_default.dim(`Email: ${result.data.user.email}`);
2580
3087
  logger_default.blank();
3088
+ await autoStartDaemon();
2581
3089
  logger_default.dim("Your patterns and skills will now sync with the Skillo platform.");
2582
3090
  } else {
2583
3091
  client.clearApiKey();
@@ -2650,6 +3158,7 @@ var loginCommand = new Command11("login").description("Login to Skillo platform"
2650
3158
  logger_default.dim(`Email: ${authResult.data.user.email}`);
2651
3159
  }
2652
3160
  logger_default.blank();
3161
+ await autoStartDaemon();
2653
3162
  logger_default.dim("Your patterns and skills will now sync with the Skillo platform.");
2654
3163
  return;
2655
3164
  } else {
@@ -2717,8 +3226,8 @@ var whoamiCommand = new Command11("whoami").description("Show current login stat
2717
3226
  });
2718
3227
 
2719
3228
  // src/commands/sync.ts
2720
- import { existsSync as existsSync12, writeFileSync as writeFileSync6, mkdirSync } from "fs";
2721
- 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";
2722
3231
  import { Command as Command12 } from "commander";
2723
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(
2724
3233
  async (options) => {
@@ -2734,7 +3243,7 @@ var syncCommand = new Command12("sync").description("Sync data with Skillo platf
2734
3243
  const syncSkills = options.skills || !options.patterns && !options.skills && !options.commands;
2735
3244
  const syncCommands = options.commands || !options.patterns && !options.skills && !options.commands;
2736
3245
  const dbPath = getDbPath();
2737
- if (!existsSync12(dbPath)) {
3246
+ if (!existsSync13(dbPath)) {
2738
3247
  logger_default.error("Database not found. Run 'skillo init' first.");
2739
3248
  return;
2740
3249
  }
@@ -2836,21 +3345,38 @@ async function pullSkills(skills) {
2836
3345
  let downloaded = 0;
2837
3346
  for (const skill of skills) {
2838
3347
  if (!skill.content) continue;
2839
- const skillDir = join5(skillsDir, skill.slug);
2840
- const skillFile = join5(skillDir, "SKILL.md");
2841
- if (!existsSync12(skillDir)) {
2842
- 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 });
2843
3352
  }
2844
- writeFileSync6(skillFile, skill.content, "utf-8");
3353
+ writeFileSync7(skillFile, skill.content, "utf-8");
2845
3354
  downloaded++;
2846
3355
  logger_default.dim(` Downloaded: ${skill.name}`);
2847
3356
  }
2848
3357
  logger_default.success(`Downloaded ${downloaded} skills to ~/.claude/skills/`);
2849
3358
  }
2850
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
+
2851
3377
  // src/cli.ts
2852
- var VERSION = "0.1.0";
2853
- var program = new Command13();
3378
+ var VERSION = "0.2.1";
3379
+ var program = new Command14();
2854
3380
  program.name("skillo").description(
2855
3381
  `Skillo - Learn workflows by observation, not explanation.
2856
3382
 
@@ -2884,6 +3410,8 @@ program.addCommand(claudeCommand);
2884
3410
  program.addCommand(projectCommand);
2885
3411
  program.addCommand(trackCommand);
2886
3412
  program.addCommand(untrackCommand);
3413
+ program.addCommand(serviceCommand);
3414
+ program.addCommand(trayCommand);
2887
3415
  program.command("version").description("Show version information").action(() => {
2888
3416
  console.log(`Skillo v${VERSION}`);
2889
3417
  console.log("Autonomous workflow learning & skill generation system");