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/api-client-WO6NUCIJ.js +11 -0
- package/dist/{chunk-QNRWQL6M.js → chunk-SYULMYPN.js} +13 -4
- package/dist/chunk-SYULMYPN.js.map +1 -0
- package/dist/{chunk-SWPZL2RI.js → chunk-WJKZWKER.js} +14 -2
- package/dist/{chunk-SWPZL2RI.js.map → chunk-WJKZWKER.js.map} +1 -1
- package/dist/claude-watcher-N6GN6WHJ.js +176 -0
- package/dist/claude-watcher-N6GN6WHJ.js.map +1 -0
- package/dist/cli.js +894 -237
- package/dist/cli.js.map +1 -1
- package/dist/daemon-runner.js +1147 -0
- package/dist/daemon-runner.js.map +1 -0
- package/dist/index.js.map +1 -1
- package/dist/{paths-YODCFIEO.js → paths-INOKEM66.js} +10 -4
- package/dist/skill-usage-detector-EO26MRYV.js +217 -0
- package/dist/skill-usage-detector-EO26MRYV.js.map +1 -0
- package/dist/tray-62I5KIFE.js +278 -0
- package/dist/tray-62I5KIFE.js.map +1 -0
- package/package.json +5 -1
- package/scripts/postinstall.mjs +348 -0
- package/dist/api-client-HTWGTHC2.js +0 -11
- package/dist/chunk-QNRWQL6M.js.map +0 -1
- /package/dist/{api-client-HTWGTHC2.js.map → api-client-WO6NUCIJ.js.map} +0 -0
- /package/dist/{paths-YODCFIEO.js.map → paths-INOKEM66.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -6,20 +6,23 @@ import {
|
|
|
6
6
|
loadConfig,
|
|
7
7
|
saveConfig,
|
|
8
8
|
setConfigValue
|
|
9
|
-
} from "./chunk-
|
|
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
|
-
|
|
19
|
-
|
|
19
|
+
getShellIntegrationDir,
|
|
20
|
+
getSkillsDir,
|
|
21
|
+
getTrackedProjectsCacheFile
|
|
22
|
+
} from "./chunk-WJKZWKER.js";
|
|
20
23
|
|
|
21
24
|
// src/cli.ts
|
|
22
|
-
import { Command as
|
|
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
|
|
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 (!
|
|
1029
|
+
if (!existsSync4(pidFile)) {
|
|
598
1030
|
return { running: false, pid: null };
|
|
599
1031
|
}
|
|
600
1032
|
try {
|
|
601
|
-
const pidStr =
|
|
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 (!
|
|
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 (
|
|
1085
|
+
if (existsSync4(logFile)) {
|
|
647
1086
|
logger_default.dim("Recent log entries:");
|
|
648
1087
|
try {
|
|
649
|
-
const content =
|
|
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
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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-
|
|
852
|
-
const { writeFileSync:
|
|
853
|
-
const { join:
|
|
854
|
-
const { getSkillsDir: getSkillsDir2, ensureDirectory: ensureDirectory2 } = await import("./paths-
|
|
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 (!
|
|
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 =
|
|
908
|
-
const skillFile =
|
|
909
|
-
|
|
910
|
-
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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 (
|
|
956
|
-
const entries =
|
|
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 =
|
|
959
|
-
if (
|
|
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 (!
|
|
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 =
|
|
1009
|
-
if (
|
|
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: ${
|
|
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 (!
|
|
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 ||
|
|
1048
|
-
if (!skill && !
|
|
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 (
|
|
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 (!
|
|
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
|
|
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
|
|
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 (!
|
|
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-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1421
|
-
|
|
1859
|
+
const psScriptFile = join3(scriptPath, "skillo.ps1");
|
|
1860
|
+
writeFileSync3(psScriptFile, psScript, "utf-8");
|
|
1422
1861
|
logger_default.success(`Created ${psScriptFile}`);
|
|
1423
|
-
const profilePath =
|
|
1424
|
-
const profileDir =
|
|
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 (
|
|
1431
|
-
const content =
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
1478
|
-
|
|
1948
|
+
const bashScriptFile = join3(scriptPath, "skillo.bash");
|
|
1949
|
+
writeFileSync3(bashScriptFile, bashScript, "utf-8");
|
|
1479
1950
|
logger_default.success(`Created ${bashScriptFile}`);
|
|
1480
|
-
const bashrcPath =
|
|
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 (
|
|
1486
|
-
const content =
|
|
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
|
-
|
|
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
|
|
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=$(
|
|
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=$(
|
|
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 =
|
|
1529
|
-
|
|
2031
|
+
const zshScriptFile = join3(scriptPath, "skillo.zsh");
|
|
2032
|
+
writeFileSync3(zshScriptFile, zshScript, "utf-8");
|
|
1530
2033
|
logger_default.success(`Created ${zshScriptFile}`);
|
|
1531
|
-
const zshrcPath =
|
|
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 (
|
|
1537
|
-
const content =
|
|
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
|
-
|
|
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 =
|
|
1564
|
-
|
|
2066
|
+
const fishScriptFile = join3(scriptPath, "skillo.fish");
|
|
2067
|
+
writeFileSync3(fishScriptFile, fishScript, "utf-8");
|
|
1565
2068
|
logger_default.success(`Created ${fishScriptFile}`);
|
|
1566
|
-
const fishConfigDir =
|
|
2069
|
+
const fishConfigDir = join3(home, ".config", "fish");
|
|
1567
2070
|
ensureDirectory(fishConfigDir);
|
|
1568
|
-
const fishConfigPath =
|
|
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 (
|
|
1576
|
-
const content =
|
|
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
|
-
|
|
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 (!
|
|
1592
|
-
let content =
|
|
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
|
-
|
|
2097
|
+
writeFileSync3(filePath, content, "utf-8");
|
|
1595
2098
|
};
|
|
1596
2099
|
if (shell === "powershell") {
|
|
1597
|
-
const profilePath =
|
|
2100
|
+
const profilePath = join3(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
|
|
1598
2101
|
removeFromFile(profilePath);
|
|
1599
2102
|
} else if (shell === "bash") {
|
|
1600
|
-
removeFromFile(
|
|
2103
|
+
removeFromFile(join3(home, ".bashrc"));
|
|
1601
2104
|
} else if (shell === "zsh") {
|
|
1602
|
-
removeFromFile(
|
|
2105
|
+
removeFromFile(join3(home, ".zshrc"));
|
|
1603
2106
|
} else if (shell === "fish") {
|
|
1604
|
-
removeFromFile(
|
|
2107
|
+
removeFromFile(join3(home, ".config", "fish", "config.fish"));
|
|
1605
2108
|
}
|
|
1606
2109
|
}
|
|
1607
2110
|
|
|
1608
2111
|
// src/commands/daemon.ts
|
|
1609
|
-
import { existsSync as
|
|
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 (!
|
|
2117
|
+
if (!existsSync9(pidFile)) {
|
|
1615
2118
|
return { running: false, pid: null };
|
|
1616
2119
|
}
|
|
1617
2120
|
try {
|
|
1618
|
-
const pidStr =
|
|
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
|
-
|
|
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 (!
|
|
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
|
|
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
|
|
2179
|
+
await runDaemonForeground();
|
|
1661
2180
|
} else {
|
|
1662
|
-
const
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
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 (
|
|
2222
|
+
if (existsSync9(pidFile)) {
|
|
1704
2223
|
try {
|
|
1705
|
-
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
2311
|
+
if (existsSync9(pidFile)) {
|
|
1746
2312
|
try {
|
|
1747
|
-
|
|
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
|
|
1756
|
-
};
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
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").
|
|
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
|
|
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
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
}
|
|
2041
|
-
|
|
2042
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
3032
|
+
const platform2 = process.platform;
|
|
2414
3033
|
let command;
|
|
2415
|
-
if (
|
|
3034
|
+
if (platform2 === "darwin") {
|
|
2416
3035
|
command = `open "${url}"`;
|
|
2417
|
-
} else if (
|
|
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
|
|
2593
|
-
import { join as
|
|
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 (!
|
|
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 =
|
|
2712
|
-
const skillFile =
|
|
2713
|
-
if (!
|
|
2714
|
-
|
|
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
|
-
|
|
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
|
|
2725
|
-
var program = new
|
|
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");
|