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