skillo 0.1.5 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{api-client-WO6NUCIJ.js → api-client-KUQW7FSC.js} +3 -2
- package/dist/chunk-2CVEPT6U.js +463 -0
- package/dist/chunk-2CVEPT6U.js.map +1 -0
- package/dist/chunk-CPL3P2OF.js +183 -0
- package/dist/chunk-CPL3P2OF.js.map +1 -0
- package/dist/{chunk-SYULMYPN.js → chunk-ODOZM4QV.js} +4 -178
- package/dist/chunk-ODOZM4QV.js.map +1 -0
- package/dist/cli.js +692 -522
- package/dist/cli.js.map +1 -1
- package/dist/config-P5EM5L7N.js +17 -0
- package/dist/config-P5EM5L7N.js.map +1 -0
- package/dist/daemon-runner.js +338 -22
- package/dist/daemon-runner.js.map +1 -1
- package/dist/database-F3BFFZKG.js +9 -0
- package/dist/database-F3BFFZKG.js.map +1 -0
- package/dist/tray-62I5KIFE.js +278 -0
- package/dist/tray-62I5KIFE.js.map +1 -0
- package/package.json +3 -1
- package/scripts/postinstall.mjs +65 -11
- package/dist/chunk-SYULMYPN.js.map +0 -1
- /package/dist/{api-client-WO6NUCIJ.js.map → api-client-KUQW7FSC.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
SkilloDatabase
|
|
4
|
+
} from "./chunk-2CVEPT6U.js";
|
|
5
|
+
import {
|
|
6
|
+
getApiClient
|
|
7
|
+
} from "./chunk-ODOZM4QV.js";
|
|
8
|
+
import {
|
|
4
9
|
getConfigValue,
|
|
5
10
|
getDefaultConfig,
|
|
6
11
|
loadConfig,
|
|
7
12
|
saveConfig,
|
|
8
13
|
setConfigValue
|
|
9
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-CPL3P2OF.js";
|
|
10
15
|
import {
|
|
11
16
|
ensureDirectory,
|
|
12
17
|
getActiveSessionsDir,
|
|
@@ -22,465 +27,12 @@ import {
|
|
|
22
27
|
} from "./chunk-WJKZWKER.js";
|
|
23
28
|
|
|
24
29
|
// src/cli.ts
|
|
25
|
-
import { Command as
|
|
30
|
+
import { Command as Command14 } from "commander";
|
|
26
31
|
|
|
27
32
|
// src/commands/init.ts
|
|
28
|
-
import { existsSync
|
|
33
|
+
import { existsSync } from "fs";
|
|
29
34
|
import { Command } from "commander";
|
|
30
35
|
|
|
31
|
-
// src/core/database.ts
|
|
32
|
-
import initSqlJs from "sql.js";
|
|
33
|
-
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
34
|
-
import { randomUUID } from "crypto";
|
|
35
|
-
import { dirname } from "path";
|
|
36
|
-
var SQL = null;
|
|
37
|
-
async function getSqlJs() {
|
|
38
|
-
if (!SQL) {
|
|
39
|
-
SQL = await initSqlJs();
|
|
40
|
-
}
|
|
41
|
-
return SQL;
|
|
42
|
-
}
|
|
43
|
-
var SkilloDatabase = class {
|
|
44
|
-
db = null;
|
|
45
|
-
dbPath;
|
|
46
|
-
initialized = false;
|
|
47
|
-
constructor(dbPath) {
|
|
48
|
-
this.dbPath = dbPath || getDbPath();
|
|
49
|
-
ensureDirectory(dirname(this.dbPath));
|
|
50
|
-
}
|
|
51
|
-
async getDb() {
|
|
52
|
-
if (!this.db) {
|
|
53
|
-
const SQL2 = await getSqlJs();
|
|
54
|
-
if (existsSync(this.dbPath)) {
|
|
55
|
-
const buffer = readFileSync(this.dbPath);
|
|
56
|
-
this.db = new SQL2.Database(buffer);
|
|
57
|
-
} else {
|
|
58
|
-
this.db = new SQL2.Database();
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return this.db;
|
|
62
|
-
}
|
|
63
|
-
save() {
|
|
64
|
-
if (this.db) {
|
|
65
|
-
const data = this.db.export();
|
|
66
|
-
const buffer = Buffer.from(data);
|
|
67
|
-
writeFileSync(this.dbPath, buffer);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
async initialize() {
|
|
71
|
-
if (this.initialized) return;
|
|
72
|
-
const db = await this.getDb();
|
|
73
|
-
const schema = `
|
|
74
|
-
-- Command history
|
|
75
|
-
CREATE TABLE IF NOT EXISTS commands (
|
|
76
|
-
id TEXT PRIMARY KEY,
|
|
77
|
-
timestamp DATETIME NOT NULL,
|
|
78
|
-
command TEXT NOT NULL,
|
|
79
|
-
normalized TEXT NOT NULL,
|
|
80
|
-
variables TEXT,
|
|
81
|
-
cwd TEXT NOT NULL,
|
|
82
|
-
exit_code INTEGER,
|
|
83
|
-
duration_ms INTEGER,
|
|
84
|
-
session_id TEXT NOT NULL,
|
|
85
|
-
project_id TEXT,
|
|
86
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
CREATE INDEX IF NOT EXISTS idx_commands_session ON commands(session_id);
|
|
90
|
-
CREATE INDEX IF NOT EXISTS idx_commands_timestamp ON commands(timestamp);
|
|
91
|
-
CREATE INDEX IF NOT EXISTS idx_commands_normalized ON commands(normalized);
|
|
92
|
-
|
|
93
|
-
-- Terminal sessions
|
|
94
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
95
|
-
id TEXT PRIMARY KEY,
|
|
96
|
-
started_at DATETIME NOT NULL,
|
|
97
|
-
ended_at DATETIME,
|
|
98
|
-
shell TEXT NOT NULL,
|
|
99
|
-
project_path TEXT,
|
|
100
|
-
command_count INTEGER DEFAULT 0,
|
|
101
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
-- Conversations (Claude Code)
|
|
105
|
-
CREATE TABLE IF NOT EXISTS conversations (
|
|
106
|
-
id TEXT PRIMARY KEY,
|
|
107
|
-
project_path TEXT,
|
|
108
|
-
started_at DATETIME,
|
|
109
|
-
updated_at DATETIME,
|
|
110
|
-
message_count INTEGER DEFAULT 0,
|
|
111
|
-
tool_calls TEXT,
|
|
112
|
-
files_touched TEXT,
|
|
113
|
-
intent_summary TEXT,
|
|
114
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
CREATE INDEX IF NOT EXISTS idx_conversations_project ON conversations(project_path);
|
|
118
|
-
|
|
119
|
-
-- Detected patterns
|
|
120
|
-
CREATE TABLE IF NOT EXISTS patterns (
|
|
121
|
-
id TEXT PRIMARY KEY,
|
|
122
|
-
source_type TEXT NOT NULL,
|
|
123
|
-
description TEXT NOT NULL,
|
|
124
|
-
count INTEGER DEFAULT 1,
|
|
125
|
-
first_seen DATETIME NOT NULL,
|
|
126
|
-
last_seen DATETIME NOT NULL,
|
|
127
|
-
data TEXT NOT NULL,
|
|
128
|
-
status TEXT DEFAULT 'active',
|
|
129
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
130
|
-
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
CREATE INDEX IF NOT EXISTS idx_patterns_status ON patterns(status);
|
|
134
|
-
CREATE INDEX IF NOT EXISTS idx_patterns_count ON patterns(count);
|
|
135
|
-
|
|
136
|
-
-- Generated skills
|
|
137
|
-
CREATE TABLE IF NOT EXISTS skills (
|
|
138
|
-
id TEXT PRIMARY KEY,
|
|
139
|
-
name TEXT UNIQUE NOT NULL,
|
|
140
|
-
path TEXT NOT NULL,
|
|
141
|
-
source_patterns TEXT,
|
|
142
|
-
source_type TEXT NOT NULL,
|
|
143
|
-
trigger_phrase TEXT,
|
|
144
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
145
|
-
usage_count INTEGER DEFAULT 0,
|
|
146
|
-
last_used_at DATETIME
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
CREATE INDEX IF NOT EXISTS idx_skills_name ON skills(name);
|
|
150
|
-
|
|
151
|
-
-- Configuration
|
|
152
|
-
CREATE TABLE IF NOT EXISTS config (
|
|
153
|
-
key TEXT PRIMARY KEY,
|
|
154
|
-
value TEXT NOT NULL,
|
|
155
|
-
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
-- Ignored patterns
|
|
159
|
-
CREATE TABLE IF NOT EXISTS ignored_patterns (
|
|
160
|
-
pattern_hash TEXT PRIMARY KEY,
|
|
161
|
-
reason TEXT,
|
|
162
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
-- Notifications
|
|
166
|
-
CREATE TABLE IF NOT EXISTS notifications (
|
|
167
|
-
id TEXT PRIMARY KEY,
|
|
168
|
-
type TEXT NOT NULL,
|
|
169
|
-
title TEXT NOT NULL,
|
|
170
|
-
message TEXT,
|
|
171
|
-
data TEXT,
|
|
172
|
-
is_read INTEGER DEFAULT 0,
|
|
173
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
174
|
-
);
|
|
175
|
-
`;
|
|
176
|
-
db.run(schema);
|
|
177
|
-
this.save();
|
|
178
|
-
this.initialized = true;
|
|
179
|
-
}
|
|
180
|
-
close() {
|
|
181
|
-
if (this.db) {
|
|
182
|
-
this.save();
|
|
183
|
-
this.db.close();
|
|
184
|
-
this.db = null;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
// Command operations
|
|
188
|
-
async addCommand(params) {
|
|
189
|
-
const db = await this.getDb();
|
|
190
|
-
const id = randomUUID();
|
|
191
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
192
|
-
db.run(
|
|
193
|
-
`INSERT INTO commands (id, timestamp, command, normalized, variables, cwd, exit_code, duration_ms, session_id, project_id)
|
|
194
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
195
|
-
[
|
|
196
|
-
id,
|
|
197
|
-
timestamp,
|
|
198
|
-
params.command,
|
|
199
|
-
params.normalized,
|
|
200
|
-
params.variables ? JSON.stringify(params.variables) : null,
|
|
201
|
-
params.cwd,
|
|
202
|
-
params.exitCode ?? null,
|
|
203
|
-
params.durationMs ?? null,
|
|
204
|
-
params.sessionId,
|
|
205
|
-
params.projectId ?? null
|
|
206
|
-
]
|
|
207
|
-
);
|
|
208
|
-
this.save();
|
|
209
|
-
return id;
|
|
210
|
-
}
|
|
211
|
-
async getRecentCommands(options) {
|
|
212
|
-
const db = await this.getDb();
|
|
213
|
-
const limit = options?.limit ?? 100;
|
|
214
|
-
let query = "SELECT * FROM commands";
|
|
215
|
-
const params = [];
|
|
216
|
-
const conditions = [];
|
|
217
|
-
if (options?.sessionId) {
|
|
218
|
-
conditions.push("session_id = ?");
|
|
219
|
-
params.push(options.sessionId);
|
|
220
|
-
}
|
|
221
|
-
if (options?.days) {
|
|
222
|
-
const cutoff = /* @__PURE__ */ new Date();
|
|
223
|
-
cutoff.setDate(cutoff.getDate() - options.days);
|
|
224
|
-
conditions.push("timestamp >= ?");
|
|
225
|
-
params.push(cutoff.toISOString());
|
|
226
|
-
}
|
|
227
|
-
if (conditions.length > 0) {
|
|
228
|
-
query += " WHERE " + conditions.join(" AND ");
|
|
229
|
-
}
|
|
230
|
-
query += " ORDER BY timestamp DESC LIMIT ?";
|
|
231
|
-
params.push(limit);
|
|
232
|
-
const results = db.exec(query, params);
|
|
233
|
-
if (results.length === 0) return [];
|
|
234
|
-
return results[0].values.map((row) => this.mapCommand(results[0].columns, row));
|
|
235
|
-
}
|
|
236
|
-
// Session operations
|
|
237
|
-
async createSession(shell, projectPath) {
|
|
238
|
-
const db = await this.getDb();
|
|
239
|
-
const id = randomUUID();
|
|
240
|
-
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
241
|
-
db.run(
|
|
242
|
-
`INSERT INTO sessions (id, started_at, shell, project_path) VALUES (?, ?, ?, ?)`,
|
|
243
|
-
[id, startedAt, shell, projectPath ?? null]
|
|
244
|
-
);
|
|
245
|
-
this.save();
|
|
246
|
-
return id;
|
|
247
|
-
}
|
|
248
|
-
async endSession(sessionId) {
|
|
249
|
-
const db = await this.getDb();
|
|
250
|
-
const endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
251
|
-
db.run(
|
|
252
|
-
`UPDATE sessions
|
|
253
|
-
SET ended_at = ?, command_count = (SELECT COUNT(*) FROM commands WHERE session_id = ?)
|
|
254
|
-
WHERE id = ?`,
|
|
255
|
-
[endedAt, sessionId, sessionId]
|
|
256
|
-
);
|
|
257
|
-
this.save();
|
|
258
|
-
}
|
|
259
|
-
async getSession(sessionId) {
|
|
260
|
-
const db = await this.getDb();
|
|
261
|
-
const results = db.exec("SELECT * FROM sessions WHERE id = ?", [sessionId]);
|
|
262
|
-
if (results.length === 0 || results[0].values.length === 0) return null;
|
|
263
|
-
return this.mapSession(results[0].columns, results[0].values[0]);
|
|
264
|
-
}
|
|
265
|
-
// Pattern operations
|
|
266
|
-
async addPattern(params) {
|
|
267
|
-
const db = await this.getDb();
|
|
268
|
-
const patternId = params.patternHash || randomUUID();
|
|
269
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
270
|
-
const existing = db.exec("SELECT id, count FROM patterns WHERE id = ?", [patternId]);
|
|
271
|
-
if (existing.length > 0 && existing[0].values.length > 0) {
|
|
272
|
-
db.run(`UPDATE patterns SET count = count + 1, last_seen = ?, updated_at = ? WHERE id = ?`, [
|
|
273
|
-
now,
|
|
274
|
-
now,
|
|
275
|
-
patternId
|
|
276
|
-
]);
|
|
277
|
-
} else {
|
|
278
|
-
db.run(
|
|
279
|
-
`INSERT INTO patterns (id, source_type, description, data, first_seen, last_seen)
|
|
280
|
-
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
281
|
-
[patternId, params.sourceType, params.description, JSON.stringify(params.data), now, now]
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
|
-
this.save();
|
|
285
|
-
return patternId;
|
|
286
|
-
}
|
|
287
|
-
async getPatterns(options) {
|
|
288
|
-
const db = await this.getDb();
|
|
289
|
-
const minCount = options?.minCount ?? 1;
|
|
290
|
-
const limit = options?.limit ?? 50;
|
|
291
|
-
let query = "SELECT * FROM patterns WHERE count >= ?";
|
|
292
|
-
const params = [minCount];
|
|
293
|
-
if (options?.type) {
|
|
294
|
-
query += " AND source_type = ?";
|
|
295
|
-
params.push(options.type);
|
|
296
|
-
}
|
|
297
|
-
if (options?.status) {
|
|
298
|
-
query += " AND status = ?";
|
|
299
|
-
params.push(options.status);
|
|
300
|
-
}
|
|
301
|
-
query += " ORDER BY count DESC, last_seen DESC LIMIT ?";
|
|
302
|
-
params.push(limit);
|
|
303
|
-
const results = db.exec(query, params);
|
|
304
|
-
if (results.length === 0) return [];
|
|
305
|
-
return results[0].values.map((row) => this.mapPattern(results[0].columns, row));
|
|
306
|
-
}
|
|
307
|
-
async getPattern(patternId) {
|
|
308
|
-
const db = await this.getDb();
|
|
309
|
-
let results = db.exec("SELECT * FROM patterns WHERE id = ?", [patternId]);
|
|
310
|
-
if (results.length === 0 || results[0].values.length === 0) {
|
|
311
|
-
results = db.exec("SELECT * FROM patterns WHERE id LIKE ?", [`${patternId}%`]);
|
|
312
|
-
}
|
|
313
|
-
if (results.length === 0 || results[0].values.length === 0) return null;
|
|
314
|
-
return this.mapPattern(results[0].columns, results[0].values[0]);
|
|
315
|
-
}
|
|
316
|
-
async updatePatternStatus(patternId, status) {
|
|
317
|
-
const db = await this.getDb();
|
|
318
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
319
|
-
db.run(`UPDATE patterns SET status = ?, updated_at = ? WHERE id = ? OR id LIKE ?`, [
|
|
320
|
-
status,
|
|
321
|
-
now,
|
|
322
|
-
patternId,
|
|
323
|
-
`${patternId}%`
|
|
324
|
-
]);
|
|
325
|
-
this.save();
|
|
326
|
-
}
|
|
327
|
-
async addIgnoredPattern(patternHash, reason) {
|
|
328
|
-
const db = await this.getDb();
|
|
329
|
-
db.run(`INSERT OR REPLACE INTO ignored_patterns (pattern_hash, reason) VALUES (?, ?)`, [
|
|
330
|
-
patternHash,
|
|
331
|
-
reason ?? null
|
|
332
|
-
]);
|
|
333
|
-
this.save();
|
|
334
|
-
}
|
|
335
|
-
async isPatternIgnored(patternHash) {
|
|
336
|
-
const db = await this.getDb();
|
|
337
|
-
const results = db.exec("SELECT 1 FROM ignored_patterns WHERE pattern_hash = ?", [patternHash]);
|
|
338
|
-
return results.length > 0 && results[0].values.length > 0;
|
|
339
|
-
}
|
|
340
|
-
// Skill operations
|
|
341
|
-
async registerSkill(params) {
|
|
342
|
-
const db = await this.getDb();
|
|
343
|
-
const id = randomUUID();
|
|
344
|
-
db.run(
|
|
345
|
-
`INSERT INTO skills (id, name, path, source_patterns, source_type, trigger_phrase)
|
|
346
|
-
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
347
|
-
[
|
|
348
|
-
id,
|
|
349
|
-
params.name,
|
|
350
|
-
params.path,
|
|
351
|
-
params.sourcePatterns ? JSON.stringify(params.sourcePatterns) : null,
|
|
352
|
-
params.sourceType,
|
|
353
|
-
params.triggerPhrase ?? null
|
|
354
|
-
]
|
|
355
|
-
);
|
|
356
|
-
this.save();
|
|
357
|
-
return id;
|
|
358
|
-
}
|
|
359
|
-
async getSkills(options) {
|
|
360
|
-
const db = await this.getDb();
|
|
361
|
-
const sortMap = {
|
|
362
|
-
name: "name",
|
|
363
|
-
created: "created_at",
|
|
364
|
-
usage: "usage_count DESC"
|
|
365
|
-
};
|
|
366
|
-
const order = sortMap[options?.sortBy ?? "name"];
|
|
367
|
-
let query = "SELECT * FROM skills";
|
|
368
|
-
const params = [];
|
|
369
|
-
if (options?.source) {
|
|
370
|
-
query += " WHERE source_type = ?";
|
|
371
|
-
params.push(options.source);
|
|
372
|
-
}
|
|
373
|
-
query += ` ORDER BY ${order}`;
|
|
374
|
-
const results = db.exec(query, params);
|
|
375
|
-
if (results.length === 0) return [];
|
|
376
|
-
return results[0].values.map((row) => this.mapSkill(results[0].columns, row));
|
|
377
|
-
}
|
|
378
|
-
async getSkill(name) {
|
|
379
|
-
const db = await this.getDb();
|
|
380
|
-
const results = db.exec("SELECT * FROM skills WHERE name = ?", [name]);
|
|
381
|
-
if (results.length === 0 || results[0].values.length === 0) return null;
|
|
382
|
-
return this.mapSkill(results[0].columns, results[0].values[0]);
|
|
383
|
-
}
|
|
384
|
-
async deleteSkill(name) {
|
|
385
|
-
const db = await this.getDb();
|
|
386
|
-
db.run("DELETE FROM skills WHERE name = ?", [name]);
|
|
387
|
-
this.save();
|
|
388
|
-
}
|
|
389
|
-
async recordSkillUsage(name) {
|
|
390
|
-
const db = await this.getDb();
|
|
391
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
392
|
-
db.run(`UPDATE skills SET usage_count = usage_count + 1, last_used_at = ? WHERE name = ?`, [
|
|
393
|
-
now,
|
|
394
|
-
name
|
|
395
|
-
]);
|
|
396
|
-
this.save();
|
|
397
|
-
}
|
|
398
|
-
// Statistics
|
|
399
|
-
async getStats() {
|
|
400
|
-
const db = await this.getDb();
|
|
401
|
-
const today = /* @__PURE__ */ new Date();
|
|
402
|
-
today.setHours(0, 0, 0, 0);
|
|
403
|
-
const todayIso = today.toISOString();
|
|
404
|
-
const getCount = (query, params = []) => {
|
|
405
|
-
const results = db.exec(query, params);
|
|
406
|
-
if (results.length === 0 || results[0].values.length === 0) return 0;
|
|
407
|
-
return results[0].values[0][0];
|
|
408
|
-
};
|
|
409
|
-
return {
|
|
410
|
-
commandsToday: getCount("SELECT COUNT(*) FROM commands WHERE timestamp >= ?", [todayIso]),
|
|
411
|
-
commandsTotal: getCount("SELECT COUNT(*) FROM commands"),
|
|
412
|
-
patternsActive: getCount("SELECT COUNT(*) FROM patterns WHERE status = 'active'"),
|
|
413
|
-
skillsTotal: getCount("SELECT COUNT(*) FROM skills"),
|
|
414
|
-
conversationsTotal: getCount("SELECT COUNT(*) FROM conversations")
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
// Mapping helpers
|
|
418
|
-
mapCommand(columns, values) {
|
|
419
|
-
const row = this.toObject(columns, values);
|
|
420
|
-
return {
|
|
421
|
-
id: row.id,
|
|
422
|
-
timestamp: row.timestamp,
|
|
423
|
-
command: row.command,
|
|
424
|
-
normalized: row.normalized,
|
|
425
|
-
variables: row.variables,
|
|
426
|
-
cwd: row.cwd,
|
|
427
|
-
exitCode: row.exit_code,
|
|
428
|
-
durationMs: row.duration_ms,
|
|
429
|
-
sessionId: row.session_id,
|
|
430
|
-
projectId: row.project_id,
|
|
431
|
-
createdAt: row.created_at
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
mapSession(columns, values) {
|
|
435
|
-
const row = this.toObject(columns, values);
|
|
436
|
-
return {
|
|
437
|
-
id: row.id,
|
|
438
|
-
startedAt: row.started_at,
|
|
439
|
-
endedAt: row.ended_at,
|
|
440
|
-
shell: row.shell,
|
|
441
|
-
projectPath: row.project_path,
|
|
442
|
-
commandCount: row.command_count,
|
|
443
|
-
createdAt: row.created_at
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
mapPattern(columns, values) {
|
|
447
|
-
const row = this.toObject(columns, values);
|
|
448
|
-
return {
|
|
449
|
-
id: row.id,
|
|
450
|
-
sourceType: row.source_type,
|
|
451
|
-
description: row.description,
|
|
452
|
-
count: row.count,
|
|
453
|
-
firstSeen: row.first_seen,
|
|
454
|
-
lastSeen: row.last_seen,
|
|
455
|
-
data: JSON.parse(row.data),
|
|
456
|
-
status: row.status,
|
|
457
|
-
createdAt: row.created_at,
|
|
458
|
-
updatedAt: row.updated_at
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
mapSkill(columns, values) {
|
|
462
|
-
const row = this.toObject(columns, values);
|
|
463
|
-
return {
|
|
464
|
-
id: row.id,
|
|
465
|
-
name: row.name,
|
|
466
|
-
path: row.path,
|
|
467
|
-
sourcePatterns: row.source_patterns,
|
|
468
|
-
sourceType: row.source_type,
|
|
469
|
-
triggerPhrase: row.trigger_phrase,
|
|
470
|
-
createdAt: row.created_at,
|
|
471
|
-
usageCount: row.usage_count,
|
|
472
|
-
lastUsedAt: row.last_used_at
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
toObject(columns, values) {
|
|
476
|
-
const obj = {};
|
|
477
|
-
columns.forEach((col, i) => {
|
|
478
|
-
obj[col] = values[i];
|
|
479
|
-
});
|
|
480
|
-
return obj;
|
|
481
|
-
}
|
|
482
|
-
};
|
|
483
|
-
|
|
484
36
|
// src/utils/logger.ts
|
|
485
37
|
import chalk from "chalk";
|
|
486
38
|
var logger = {
|
|
@@ -552,7 +104,7 @@ var initCommand = new Command("init").description("Initialize Skillo configurati
|
|
|
552
104
|
const configDir = getConfigDir();
|
|
553
105
|
const skillsDir = getSkillsDir();
|
|
554
106
|
const configFile = getConfigFile();
|
|
555
|
-
if (
|
|
107
|
+
if (existsSync(configFile) && !options.force) {
|
|
556
108
|
logger_default.warn("Skillo is already initialized.");
|
|
557
109
|
logger_default.dim("Use --force to reinitialize and overwrite configuration.");
|
|
558
110
|
return;
|
|
@@ -595,6 +147,435 @@ var initCommand = new Command("init").description("Initialize Skillo configurati
|
|
|
595
147
|
// src/commands/status.ts
|
|
596
148
|
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
597
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");
|
|
186
|
+
}
|
|
187
|
+
function findSkilloBin() {
|
|
188
|
+
try {
|
|
189
|
+
const whichCmd = isWin ? "where skillo" : "which skillo";
|
|
190
|
+
const result = execSync(whichCmd, { encoding: "utf-8" }).trim();
|
|
191
|
+
const firstLine = result.split(/\r?\n/)[0].trim();
|
|
192
|
+
if (firstLine) return firstLine;
|
|
193
|
+
} catch {
|
|
194
|
+
}
|
|
195
|
+
if (isWin) {
|
|
196
|
+
const appData = process.env.APPDATA || join(homedir(), "AppData", "Roaming");
|
|
197
|
+
const candidates = [
|
|
198
|
+
join(appData, "npm", "skillo.cmd"),
|
|
199
|
+
join(homedir(), "AppData", "Local", "fnm_multishells")
|
|
200
|
+
];
|
|
201
|
+
for (const c of candidates) {
|
|
202
|
+
if (existsSync2(c)) return c;
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
const candidates = [
|
|
206
|
+
"/usr/local/bin/skillo",
|
|
207
|
+
"/usr/bin/skillo"
|
|
208
|
+
];
|
|
209
|
+
for (const c of candidates) {
|
|
210
|
+
if (existsSync2(c)) return c;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return "skillo";
|
|
214
|
+
}
|
|
215
|
+
function buildPath() {
|
|
216
|
+
const paths = /* @__PURE__ */ new Set();
|
|
217
|
+
const home = homedir();
|
|
218
|
+
(process.env.PATH || "").split(pathSep).forEach((p) => paths.add(p));
|
|
219
|
+
if (isWin) {
|
|
220
|
+
const appData = process.env.APPDATA || join(home, "AppData", "Roaming");
|
|
221
|
+
paths.add(join(appData, "npm"));
|
|
222
|
+
paths.add(join(home, "AppData", "Local", "Programs", "nodejs"));
|
|
223
|
+
const nvmHome = process.env.NVM_HOME;
|
|
224
|
+
if (nvmHome) paths.add(nvmHome);
|
|
225
|
+
const nvmSymlink = process.env.NVM_SYMLINK;
|
|
226
|
+
if (nvmSymlink) paths.add(nvmSymlink);
|
|
227
|
+
const fnmDir = join(home, ".fnm");
|
|
228
|
+
if (existsSync2(fnmDir)) paths.add(fnmDir);
|
|
229
|
+
} else {
|
|
230
|
+
const nvmDir = process.env.NVM_DIR || join(home, ".nvm");
|
|
231
|
+
if (existsSync2(nvmDir)) {
|
|
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
|
+
}
|
|
242
|
+
}
|
|
243
|
+
} catch {
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
paths.add(join(home, ".fnm", "current", "bin"));
|
|
247
|
+
paths.add("/opt/homebrew/bin");
|
|
248
|
+
paths.add("/usr/local/bin");
|
|
249
|
+
paths.add("/usr/bin");
|
|
250
|
+
paths.add("/bin");
|
|
251
|
+
paths.add(join(home, ".npm-global", "bin"));
|
|
252
|
+
paths.add(join(home, ".local", "bin"));
|
|
253
|
+
}
|
|
254
|
+
return [...paths].filter(Boolean).join(pathSep);
|
|
255
|
+
}
|
|
256
|
+
function generateDaemonPlist(skilloBin, envPath) {
|
|
257
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
258
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
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 {
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function winRegExists(valueName) {
|
|
388
|
+
try {
|
|
389
|
+
execSync(`reg query "${WIN_REG_KEY}" /v "${valueName}"`, { stdio: "ignore" });
|
|
390
|
+
return true;
|
|
391
|
+
} catch {
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
async function installService(options) {
|
|
396
|
+
const os2 = platform();
|
|
397
|
+
const skilloBin = findSkilloBin();
|
|
398
|
+
const envPath = buildPath();
|
|
399
|
+
const includeTray = options?.includeTray ?? true;
|
|
400
|
+
try {
|
|
401
|
+
if (os2 === "darwin") {
|
|
402
|
+
const agentsDir = getLaunchAgentsDir();
|
|
403
|
+
if (!existsSync2(agentsDir)) mkdirSync(agentsDir, { recursive: true });
|
|
404
|
+
const daemonPlist = getDaemonPlistPath();
|
|
405
|
+
writeFileSync(daemonPlist, generateDaemonPlist(skilloBin, envPath), "utf-8");
|
|
406
|
+
try {
|
|
407
|
+
execSync(`launchctl unload "${daemonPlist}" 2>/dev/null`);
|
|
408
|
+
} catch {
|
|
409
|
+
}
|
|
410
|
+
execSync(`launchctl load "${daemonPlist}"`);
|
|
411
|
+
if (includeTray) {
|
|
412
|
+
const trayPlist = getTrayPlistPath();
|
|
413
|
+
writeFileSync(trayPlist, generateTrayPlist(skilloBin, envPath), "utf-8");
|
|
414
|
+
try {
|
|
415
|
+
execSync(`launchctl unload "${trayPlist}" 2>/dev/null`);
|
|
416
|
+
} catch {
|
|
417
|
+
}
|
|
418
|
+
execSync(`launchctl load "${trayPlist}"`);
|
|
419
|
+
}
|
|
420
|
+
return { success: true };
|
|
421
|
+
} else if (os2 === "linux") {
|
|
422
|
+
const systemdDir = getSystemdUserDir();
|
|
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
|
+
}
|
|
451
|
+
} catch (error) {
|
|
452
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
async function uninstallService() {
|
|
456
|
+
const os2 = platform();
|
|
457
|
+
try {
|
|
458
|
+
if (os2 === "darwin") {
|
|
459
|
+
const daemonPlist = getDaemonPlistPath();
|
|
460
|
+
if (existsSync2(daemonPlist)) {
|
|
461
|
+
try {
|
|
462
|
+
execSync(`launchctl unload "${daemonPlist}" 2>/dev/null`);
|
|
463
|
+
} catch {
|
|
464
|
+
}
|
|
465
|
+
unlinkSync(daemonPlist);
|
|
466
|
+
}
|
|
467
|
+
const trayPlist = getTrayPlistPath();
|
|
468
|
+
if (existsSync2(trayPlist)) {
|
|
469
|
+
try {
|
|
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
|
+
}
|
|
520
|
+
} catch (error) {
|
|
521
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function getServiceStatus() {
|
|
525
|
+
const os2 = platform();
|
|
526
|
+
if (os2 === "darwin") {
|
|
527
|
+
const daemonInstalled = existsSync2(getDaemonPlistPath());
|
|
528
|
+
const trayInstalled = existsSync2(getTrayPlistPath());
|
|
529
|
+
let daemonLoaded = false;
|
|
530
|
+
let trayLoaded = false;
|
|
531
|
+
try {
|
|
532
|
+
const output = execSync("launchctl list", { encoding: "utf-8" });
|
|
533
|
+
daemonLoaded = output.includes(DAEMON_LABEL);
|
|
534
|
+
trayLoaded = output.includes(TRAY_LABEL);
|
|
535
|
+
} catch {
|
|
536
|
+
}
|
|
537
|
+
return {
|
|
538
|
+
platform: "macos",
|
|
539
|
+
daemon: { installed: daemonInstalled, loaded: daemonLoaded },
|
|
540
|
+
tray: { installed: trayInstalled, loaded: trayLoaded }
|
|
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 {
|
|
556
|
+
}
|
|
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
|
+
}
|
|
571
|
+
return {
|
|
572
|
+
platform: "unsupported",
|
|
573
|
+
daemon: { installed: false, loaded: false },
|
|
574
|
+
tray: { installed: false, loaded: false }
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// src/commands/status.ts
|
|
598
579
|
function isDaemonRunning() {
|
|
599
580
|
const pidFile = getPidFile();
|
|
600
581
|
if (!existsSync3(pidFile)) {
|
|
@@ -625,8 +606,15 @@ var statusCommand = new Command2("status").description("Show daemon status and s
|
|
|
625
606
|
logger_default.stopped("Daemon is not running");
|
|
626
607
|
logger_default.blank();
|
|
627
608
|
logger_default.dim("Start with: skillo start");
|
|
628
|
-
return;
|
|
629
609
|
}
|
|
610
|
+
const serviceStatus = getServiceStatus();
|
|
611
|
+
if (serviceStatus.daemon.installed) {
|
|
612
|
+
logger_default.running("Auto-start: enabled");
|
|
613
|
+
} else {
|
|
614
|
+
logger_default.stopped("Auto-start: disabled");
|
|
615
|
+
logger_default.dim(" Enable with: skillo service install");
|
|
616
|
+
}
|
|
617
|
+
if (!running) return;
|
|
630
618
|
logger_default.blank();
|
|
631
619
|
const dbPath = getDbPath();
|
|
632
620
|
if (!existsSync3(dbPath)) {
|
|
@@ -851,9 +839,9 @@ patternsCommand.command("ignore <id>").description("Ignore a pattern (never sugg
|
|
|
851
839
|
}
|
|
852
840
|
});
|
|
853
841
|
patternsCommand.command("generate <id>").description("Generate a skill from a pattern").option("-n, --name <name>", "Custom skill name").option("--dry-run", "Preview without creating").action(async (id, options) => {
|
|
854
|
-
const { getApiClient: getApiClient2 } = await import("./api-client-
|
|
855
|
-
const { writeFileSync: writeFileSync7, mkdirSync:
|
|
856
|
-
const { join:
|
|
842
|
+
const { getApiClient: getApiClient2 } = await import("./api-client-KUQW7FSC.js");
|
|
843
|
+
const { writeFileSync: writeFileSync7, mkdirSync: mkdirSync3 } = await import("fs");
|
|
844
|
+
const { join: join7 } = await import("path");
|
|
857
845
|
const { getSkillsDir: getSkillsDir2, ensureDirectory: ensureDirectory2 } = await import("./paths-INOKEM66.js");
|
|
858
846
|
const dbPath = getDbPath();
|
|
859
847
|
if (!existsSync5(dbPath)) {
|
|
@@ -907,9 +895,9 @@ patternsCommand.command("generate <id>").description("Generate a skill from a pa
|
|
|
907
895
|
} else {
|
|
908
896
|
const skillsDir = getSkillsDir2();
|
|
909
897
|
ensureDirectory2(skillsDir);
|
|
910
|
-
const skillDir =
|
|
911
|
-
const skillFile =
|
|
912
|
-
|
|
898
|
+
const skillDir = join7(skillsDir, skill.slug);
|
|
899
|
+
const skillFile = join7(skillDir, "SKILL.md");
|
|
900
|
+
mkdirSync3(skillDir, { recursive: true });
|
|
913
901
|
writeFileSync7(skillFile, skill.content, "utf-8");
|
|
914
902
|
await db.updatePatternStatus(id, "converted");
|
|
915
903
|
logger_default.success(`Skill saved to: ~/.claude/skills/${skill.slug}/SKILL.md`);
|
|
@@ -933,8 +921,8 @@ patternsCommand.command("clear").description("Clear all patterns").option("-f, -
|
|
|
933
921
|
});
|
|
934
922
|
|
|
935
923
|
// src/commands/skills.ts
|
|
936
|
-
import { existsSync as existsSync6, readdirSync, rmSync } from "fs";
|
|
937
|
-
import { join } from "path";
|
|
924
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, rmSync } from "fs";
|
|
925
|
+
import { join as join2 } from "path";
|
|
938
926
|
import { Command as Command5 } from "commander";
|
|
939
927
|
import chalk3 from "chalk";
|
|
940
928
|
var skillsCommand = new Command5("skills").description(
|
|
@@ -956,9 +944,9 @@ skillsCommand.command("list").description("List all skills").option("-s, --sourc
|
|
|
956
944
|
const skillsDir = getSkillsDir();
|
|
957
945
|
const fsSkills = [];
|
|
958
946
|
if (existsSync6(skillsDir)) {
|
|
959
|
-
const entries =
|
|
947
|
+
const entries = readdirSync2(skillsDir, { withFileTypes: true });
|
|
960
948
|
entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).forEach((e) => {
|
|
961
|
-
const skillMd =
|
|
949
|
+
const skillMd = join2(skillsDir, e.name, "SKILL.md");
|
|
962
950
|
if (existsSync6(skillMd)) {
|
|
963
951
|
fsSkills.push(e.name);
|
|
964
952
|
}
|
|
@@ -1008,12 +996,12 @@ skillsCommand.command("show <name>").description("Show skill details").action(as
|
|
|
1008
996
|
const skill = await db.getSkill(name);
|
|
1009
997
|
db.close();
|
|
1010
998
|
if (!skill) {
|
|
1011
|
-
const skillPath =
|
|
999
|
+
const skillPath = join2(getSkillsDir(), name, "SKILL.md");
|
|
1012
1000
|
if (existsSync6(skillPath)) {
|
|
1013
1001
|
logger_default.blank();
|
|
1014
1002
|
logger_default.bold(name);
|
|
1015
1003
|
logger_default.blank();
|
|
1016
|
-
logger_default.dim(` Path: ${
|
|
1004
|
+
logger_default.dim(` Path: ${join2(getSkillsDir(), name)}`);
|
|
1017
1005
|
logger_default.dim(` Status: Not registered in database`);
|
|
1018
1006
|
logger_default.blank();
|
|
1019
1007
|
return;
|
|
@@ -1047,7 +1035,7 @@ skillsCommand.command("delete <name>").description("Delete a skill").option("-f,
|
|
|
1047
1035
|
const db = new SkilloDatabase();
|
|
1048
1036
|
await db.initialize();
|
|
1049
1037
|
const skill = await db.getSkill(name);
|
|
1050
|
-
const skillDir = skill?.path ||
|
|
1038
|
+
const skillDir = skill?.path || join2(getSkillsDir(), name);
|
|
1051
1039
|
if (!skill && !existsSync6(skillDir)) {
|
|
1052
1040
|
db.close();
|
|
1053
1041
|
logger_default.error(`Skill not found: ${name}`);
|
|
@@ -1090,8 +1078,8 @@ skillsCommand.command("open").description("Open skills directory in file manager
|
|
|
1090
1078
|
// src/commands/shell.ts
|
|
1091
1079
|
import { existsSync as existsSync7, writeFileSync as writeFileSync2, readFileSync as readFileSync4, appendFileSync } from "fs";
|
|
1092
1080
|
import { spawn } from "child_process";
|
|
1093
|
-
import { homedir } from "os";
|
|
1094
|
-
import { join as
|
|
1081
|
+
import { homedir as homedir2 } from "os";
|
|
1082
|
+
import { join as join3 } from "path";
|
|
1095
1083
|
import { Command as Command6 } from "commander";
|
|
1096
1084
|
function normalizeCommand(cmd) {
|
|
1097
1085
|
const variables = {};
|
|
@@ -1203,7 +1191,7 @@ var recordCommand = new Command6("record").description("Record a command (used b
|
|
|
1203
1191
|
db.close();
|
|
1204
1192
|
if (options.sync !== false) {
|
|
1205
1193
|
try {
|
|
1206
|
-
const { getApiClient: getApiClient2 } = await import("./api-client-
|
|
1194
|
+
const { getApiClient: getApiClient2 } = await import("./api-client-KUQW7FSC.js");
|
|
1207
1195
|
const client = getApiClient2();
|
|
1208
1196
|
if (client.hasApiKey()) {
|
|
1209
1197
|
await client.syncCommands([{
|
|
@@ -1224,7 +1212,7 @@ var recordCommand = new Command6("record").description("Record a command (used b
|
|
|
1224
1212
|
);
|
|
1225
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) => {
|
|
1226
1214
|
logger_default.blank();
|
|
1227
|
-
const home =
|
|
1215
|
+
const home = homedir2();
|
|
1228
1216
|
const dataDir = getDataDir();
|
|
1229
1217
|
ensureDirectory(dataDir);
|
|
1230
1218
|
let shell = null;
|
|
@@ -1272,7 +1260,7 @@ var setupShellCommand = new Command6("setup-shell").description("Set up shell in
|
|
|
1272
1260
|
logger_default.blank();
|
|
1273
1261
|
});
|
|
1274
1262
|
async function installShellIntegration(shell, home, dataDir) {
|
|
1275
|
-
const scriptPath =
|
|
1263
|
+
const scriptPath = join3(dataDir, "shell-integration");
|
|
1276
1264
|
ensureDirectory(scriptPath);
|
|
1277
1265
|
if (shell === "powershell") {
|
|
1278
1266
|
const psScript = `# Skillo CLI Integration
|
|
@@ -1420,11 +1408,11 @@ $null = Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action {
|
|
|
1420
1408
|
|
|
1421
1409
|
Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
|
|
1422
1410
|
`;
|
|
1423
|
-
const psScriptFile =
|
|
1411
|
+
const psScriptFile = join3(scriptPath, "skillo.ps1");
|
|
1424
1412
|
writeFileSync2(psScriptFile, psScript, "utf-8");
|
|
1425
1413
|
logger_default.success(`Created ${psScriptFile}`);
|
|
1426
|
-
const profilePath =
|
|
1427
|
-
const profileDir =
|
|
1414
|
+
const profilePath = join3(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
|
|
1415
|
+
const profileDir = join3(home, "Documents", "WindowsPowerShell");
|
|
1428
1416
|
ensureDirectory(profileDir);
|
|
1429
1417
|
const sourceLine = `
|
|
1430
1418
|
# Skillo CLI Integration
|
|
@@ -1509,10 +1497,10 @@ Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
|
|
|
1509
1497
|
"",
|
|
1510
1498
|
'echo -e "\\033[90mSkillo: Command tracking enabled\\033[0m"'
|
|
1511
1499
|
].join("\n");
|
|
1512
|
-
const bashScriptFile =
|
|
1500
|
+
const bashScriptFile = join3(scriptPath, "skillo.bash");
|
|
1513
1501
|
writeFileSync2(bashScriptFile, bashScript, "utf-8");
|
|
1514
1502
|
logger_default.success(`Created ${bashScriptFile}`);
|
|
1515
|
-
const bashrcPath =
|
|
1503
|
+
const bashrcPath = join3(home, ".bashrc");
|
|
1516
1504
|
const sourceLine = `
|
|
1517
1505
|
# Skillo CLI Integration
|
|
1518
1506
|
[ -f "${bashScriptFile}" ] && source "${bashScriptFile}"
|
|
@@ -1592,10 +1580,10 @@ Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
|
|
|
1592
1580
|
"",
|
|
1593
1581
|
'echo -e "\\033[90mSkillo: Command tracking enabled\\033[0m"'
|
|
1594
1582
|
].join("\n");
|
|
1595
|
-
const zshScriptFile =
|
|
1583
|
+
const zshScriptFile = join3(scriptPath, "skillo.zsh");
|
|
1596
1584
|
writeFileSync2(zshScriptFile, zshScript, "utf-8");
|
|
1597
1585
|
logger_default.success(`Created ${zshScriptFile}`);
|
|
1598
|
-
const zshrcPath =
|
|
1586
|
+
const zshrcPath = join3(home, ".zshrc");
|
|
1599
1587
|
const sourceLine = `
|
|
1600
1588
|
# Skillo CLI Integration
|
|
1601
1589
|
[ -f "${zshScriptFile}" ] && source "${zshScriptFile}"
|
|
@@ -1627,12 +1615,12 @@ Write-Host "Skillo: Command tracking enabled" -ForegroundColor DarkGray
|
|
|
1627
1615
|
"",
|
|
1628
1616
|
'echo -e "\\033[90mSkillo: Command tracking enabled\\033[0m"'
|
|
1629
1617
|
].join("\n");
|
|
1630
|
-
const fishScriptFile =
|
|
1618
|
+
const fishScriptFile = join3(scriptPath, "skillo.fish");
|
|
1631
1619
|
writeFileSync2(fishScriptFile, fishScript, "utf-8");
|
|
1632
1620
|
logger_default.success(`Created ${fishScriptFile}`);
|
|
1633
|
-
const fishConfigDir =
|
|
1621
|
+
const fishConfigDir = join3(home, ".config", "fish");
|
|
1634
1622
|
ensureDirectory(fishConfigDir);
|
|
1635
|
-
const fishConfigPath =
|
|
1623
|
+
const fishConfigPath = join3(fishConfigDir, "config.fish");
|
|
1636
1624
|
const sourceLine = `
|
|
1637
1625
|
# Skillo CLI Integration
|
|
1638
1626
|
if test -f "${fishScriptFile}"
|
|
@@ -1661,19 +1649,19 @@ async function uninstallShellIntegration(shell, home) {
|
|
|
1661
1649
|
writeFileSync2(filePath, content, "utf-8");
|
|
1662
1650
|
};
|
|
1663
1651
|
if (shell === "powershell") {
|
|
1664
|
-
const profilePath =
|
|
1652
|
+
const profilePath = join3(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
|
|
1665
1653
|
removeFromFile(profilePath);
|
|
1666
1654
|
} else if (shell === "bash") {
|
|
1667
|
-
removeFromFile(
|
|
1655
|
+
removeFromFile(join3(home, ".bashrc"));
|
|
1668
1656
|
} else if (shell === "zsh") {
|
|
1669
|
-
removeFromFile(
|
|
1657
|
+
removeFromFile(join3(home, ".zshrc"));
|
|
1670
1658
|
} else if (shell === "fish") {
|
|
1671
|
-
removeFromFile(
|
|
1659
|
+
removeFromFile(join3(home, ".config", "fish", "config.fish"));
|
|
1672
1660
|
}
|
|
1673
1661
|
}
|
|
1674
1662
|
|
|
1675
1663
|
// src/commands/daemon.ts
|
|
1676
|
-
import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync3, unlinkSync } from "fs";
|
|
1664
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs";
|
|
1677
1665
|
import { fork } from "child_process";
|
|
1678
1666
|
import { Command as Command7 } from "commander";
|
|
1679
1667
|
function isDaemonRunning2() {
|
|
@@ -1692,7 +1680,7 @@ function isDaemonRunning2() {
|
|
|
1692
1680
|
return { running: true, pid };
|
|
1693
1681
|
} catch {
|
|
1694
1682
|
try {
|
|
1695
|
-
|
|
1683
|
+
unlinkSync2(pidFile);
|
|
1696
1684
|
} catch {
|
|
1697
1685
|
}
|
|
1698
1686
|
return { running: false, pid: null };
|
|
@@ -1718,15 +1706,102 @@ function startDaemonProcess() {
|
|
|
1718
1706
|
}
|
|
1719
1707
|
return null;
|
|
1720
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
|
+
}
|
|
1721
1787
|
var startCommand = new Command7("start").description("Start the Skillo daemon").option("-f, --foreground", "Run in foreground (don't daemonize)").action(async (options) => {
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1788
|
+
await ensureInitialized();
|
|
1789
|
+
if (!options.foreground) {
|
|
1790
|
+
const loggedIn = await ensureLoggedIn();
|
|
1791
|
+
if (!loggedIn) {
|
|
1792
|
+
process.exit(1);
|
|
1793
|
+
}
|
|
1726
1794
|
}
|
|
1727
1795
|
const { running, pid } = isDaemonRunning2();
|
|
1728
1796
|
if (running) {
|
|
1729
|
-
logger_default.
|
|
1797
|
+
logger_default.success(`Daemon is already running (PID: ${pid})`);
|
|
1798
|
+
const serviceStatus = getServiceStatus();
|
|
1799
|
+
if (!serviceStatus.daemon.installed) {
|
|
1800
|
+
const serviceResult = await installService({ includeTray: true });
|
|
1801
|
+
if (serviceResult.success) {
|
|
1802
|
+
logger_default.dim("Auto-start service installed.");
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1730
1805
|
return;
|
|
1731
1806
|
}
|
|
1732
1807
|
if (options.foreground) {
|
|
@@ -1737,14 +1812,21 @@ var startCommand = new Command7("start").description("Start the Skillo daemon").
|
|
|
1737
1812
|
} else {
|
|
1738
1813
|
logger_default.info("Starting Skillo daemon...");
|
|
1739
1814
|
if (process.platform === "win32") {
|
|
1740
|
-
logger_default.
|
|
1741
|
-
logger_default.dim("Running in foreground mode...");
|
|
1815
|
+
logger_default.dim("Running in foreground mode on Windows...");
|
|
1742
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
|
+
}
|
|
1743
1821
|
await runDaemonForeground();
|
|
1744
1822
|
} else {
|
|
1745
1823
|
const daemonPid = startDaemonProcess();
|
|
1746
1824
|
if (daemonPid) {
|
|
1747
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
|
+
}
|
|
1748
1830
|
logger_default.blank();
|
|
1749
1831
|
logger_default.dim("Use 'skillo status' to check daemon status");
|
|
1750
1832
|
logger_default.dim("Use 'skillo stop' to stop the daemon");
|
|
@@ -1761,6 +1843,10 @@ var stopCommand = new Command7("stop").description("Stop the Skillo daemon").act
|
|
|
1761
1843
|
return;
|
|
1762
1844
|
}
|
|
1763
1845
|
logger_default.info(`Stopping daemon (PID: ${pid})...`);
|
|
1846
|
+
const serviceResult = await uninstallService();
|
|
1847
|
+
if (serviceResult.success) {
|
|
1848
|
+
logger_default.dim("Auto-start service removed.");
|
|
1849
|
+
}
|
|
1764
1850
|
try {
|
|
1765
1851
|
process.kill(pid, "SIGTERM");
|
|
1766
1852
|
logger_default.success("Daemon stopped");
|
|
@@ -1777,7 +1863,7 @@ var stopCommand = new Command7("stop").description("Stop the Skillo daemon").act
|
|
|
1777
1863
|
const pidFile = getPidFile();
|
|
1778
1864
|
if (existsSync8(pidFile)) {
|
|
1779
1865
|
try {
|
|
1780
|
-
|
|
1866
|
+
unlinkSync2(pidFile);
|
|
1781
1867
|
} catch {
|
|
1782
1868
|
}
|
|
1783
1869
|
}
|
|
@@ -1808,6 +1894,53 @@ function showLogs(logFile, numLines) {
|
|
|
1808
1894
|
const lastLines = lines.slice(-numLines);
|
|
1809
1895
|
lastLines.forEach((line) => console.log(line));
|
|
1810
1896
|
}
|
|
1897
|
+
var serviceCommand = new Command7("service").description("Manage auto-start service").addCommand(
|
|
1898
|
+
new Command7("install").description("Install auto-start service (LaunchAgent on macOS, systemd on Linux)").option("--no-tray", "Skip installing tray icon service").action(async (options) => {
|
|
1899
|
+
logger_default.info("Installing auto-start service...");
|
|
1900
|
+
const result = await installService({ includeTray: options.tray !== false });
|
|
1901
|
+
if (result.success) {
|
|
1902
|
+
logger_default.success("Auto-start service installed.");
|
|
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();
|
|
1915
|
+
if (result.success) {
|
|
1916
|
+
logger_default.success("Auto-start service removed.");
|
|
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)"}`);
|
|
1934
|
+
} else {
|
|
1935
|
+
logger_default.stopped("Tray service: not installed");
|
|
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
|
+
);
|
|
1811
1944
|
async function runDaemonForeground() {
|
|
1812
1945
|
const pidFile = getPidFile();
|
|
1813
1946
|
writeFileSync3(pidFile, String(process.pid));
|
|
@@ -1819,7 +1952,7 @@ async function runDaemonForeground() {
|
|
|
1819
1952
|
logger_default.info("Shutting down daemon...");
|
|
1820
1953
|
if (existsSync8(pidFile)) {
|
|
1821
1954
|
try {
|
|
1822
|
-
|
|
1955
|
+
unlinkSync2(pidFile);
|
|
1823
1956
|
} catch {
|
|
1824
1957
|
}
|
|
1825
1958
|
}
|
|
@@ -1828,7 +1961,7 @@ async function runDaemonForeground() {
|
|
|
1828
1961
|
process.on("SIGINT", cleanup);
|
|
1829
1962
|
process.on("SIGTERM", cleanup);
|
|
1830
1963
|
const config = loadConfig();
|
|
1831
|
-
const { getApiClient: getApiClient2 } = await import("./api-client-
|
|
1964
|
+
const { getApiClient: getApiClient2 } = await import("./api-client-KUQW7FSC.js");
|
|
1832
1965
|
const client = getApiClient2();
|
|
1833
1966
|
if (!client.hasApiKey()) {
|
|
1834
1967
|
logger_default.error("Not logged in. Run 'skillo login' first.");
|
|
@@ -1871,7 +2004,7 @@ async function runDaemonForeground() {
|
|
|
1871
2004
|
|
|
1872
2005
|
// src/commands/session.ts
|
|
1873
2006
|
import { Command as Command8 } from "commander";
|
|
1874
|
-
import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as
|
|
2007
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3 } from "fs";
|
|
1875
2008
|
var sessionCommand = new Command8("session").description("Manage terminal sessions");
|
|
1876
2009
|
sessionCommand.command("start").description("Start a new terminal session (for standalone terminals)").option("--shell <shell>", "Shell type (powershell, bash, zsh, cmd)", "powershell").action(async (options) => {
|
|
1877
2010
|
try {
|
|
@@ -1960,7 +2093,7 @@ var sessionAutoCommand = new Command8("session-auto").description("Auto-detect p
|
|
|
1960
2093
|
} catch {
|
|
1961
2094
|
}
|
|
1962
2095
|
try {
|
|
1963
|
-
|
|
2096
|
+
unlinkSync3(sessionFile);
|
|
1964
2097
|
} catch {
|
|
1965
2098
|
}
|
|
1966
2099
|
}
|
|
@@ -2270,8 +2403,8 @@ import { Command as Command10 } from "commander";
|
|
|
2270
2403
|
import { existsSync as existsSync11, writeFileSync as writeFileSync5 } from "fs";
|
|
2271
2404
|
|
|
2272
2405
|
// src/utils/git.ts
|
|
2273
|
-
import { execSync } from "child_process";
|
|
2274
|
-
import { join as
|
|
2406
|
+
import { execSync as execSync2 } from "child_process";
|
|
2407
|
+
import { join as join5, dirname as dirname2, basename as basename2 } from "path";
|
|
2275
2408
|
function getGitInfo(projectPath) {
|
|
2276
2409
|
const result = {
|
|
2277
2410
|
isGitRepo: false,
|
|
@@ -2282,7 +2415,7 @@ function getGitInfo(projectPath) {
|
|
|
2282
2415
|
projectName: null
|
|
2283
2416
|
};
|
|
2284
2417
|
try {
|
|
2285
|
-
const rootPath =
|
|
2418
|
+
const rootPath = execSync2("git rev-parse --show-toplevel", {
|
|
2286
2419
|
cwd: projectPath,
|
|
2287
2420
|
encoding: "utf-8",
|
|
2288
2421
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -2292,7 +2425,7 @@ function getGitInfo(projectPath) {
|
|
|
2292
2425
|
result.rootPath = rootPath;
|
|
2293
2426
|
result.projectName = basename2(rootPath);
|
|
2294
2427
|
try {
|
|
2295
|
-
result.branch =
|
|
2428
|
+
result.branch = execSync2("git rev-parse --abbrev-ref HEAD", {
|
|
2296
2429
|
cwd: projectPath,
|
|
2297
2430
|
encoding: "utf-8",
|
|
2298
2431
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -2300,20 +2433,20 @@ function getGitInfo(projectPath) {
|
|
|
2300
2433
|
} catch {
|
|
2301
2434
|
}
|
|
2302
2435
|
try {
|
|
2303
|
-
result.remote =
|
|
2436
|
+
result.remote = execSync2("git remote get-url origin", {
|
|
2304
2437
|
cwd: projectPath,
|
|
2305
2438
|
encoding: "utf-8",
|
|
2306
2439
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2307
2440
|
}).trim();
|
|
2308
2441
|
} catch {
|
|
2309
2442
|
try {
|
|
2310
|
-
const remotes =
|
|
2443
|
+
const remotes = execSync2("git remote", {
|
|
2311
2444
|
cwd: projectPath,
|
|
2312
2445
|
encoding: "utf-8",
|
|
2313
2446
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2314
2447
|
}).trim().split("\n");
|
|
2315
2448
|
if (remotes.length > 0 && remotes[0]) {
|
|
2316
|
-
result.remote =
|
|
2449
|
+
result.remote = execSync2(`git remote get-url ${remotes[0]}`, {
|
|
2317
2450
|
cwd: projectPath,
|
|
2318
2451
|
encoding: "utf-8",
|
|
2319
2452
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -2538,11 +2671,11 @@ async function openBrowser(url) {
|
|
|
2538
2671
|
const { exec } = await import("child_process");
|
|
2539
2672
|
const { promisify } = await import("util");
|
|
2540
2673
|
const execAsync = promisify(exec);
|
|
2541
|
-
const
|
|
2674
|
+
const platform2 = process.platform;
|
|
2542
2675
|
let command;
|
|
2543
|
-
if (
|
|
2676
|
+
if (platform2 === "darwin") {
|
|
2544
2677
|
command = `open "${url}"`;
|
|
2545
|
-
} else if (
|
|
2678
|
+
} else if (platform2 === "win32") {
|
|
2546
2679
|
command = `start "" "${url}"`;
|
|
2547
2680
|
} else {
|
|
2548
2681
|
command = `xdg-open "${url}"`;
|
|
@@ -2556,6 +2689,22 @@ async function openBrowser(url) {
|
|
|
2556
2689
|
function sleep(ms) {
|
|
2557
2690
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2558
2691
|
}
|
|
2692
|
+
async function autoStartDaemon() {
|
|
2693
|
+
try {
|
|
2694
|
+
const { running } = isDaemonRunning2();
|
|
2695
|
+
if (!running) {
|
|
2696
|
+
const pid = startDaemonProcess();
|
|
2697
|
+
if (pid) {
|
|
2698
|
+
logger_default.dim(`Background daemon started (PID: ${pid}).`);
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
const result = await installService();
|
|
2702
|
+
if (result.success) {
|
|
2703
|
+
logger_default.dim("Auto-start installed. Daemon will survive reboots.");
|
|
2704
|
+
}
|
|
2705
|
+
} catch {
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2559
2708
|
var loginCommand = new Command11("login").description("Login to Skillo platform").argument("[api-key]", "Your Skillo API key (optional, will use browser auth if not provided)").option("-u, --url <url>", "Platform URL (default: https://www.skillo.one)").option("-n, --no-browser", "Don't open browser automatically").action(async (apiKey, options) => {
|
|
2560
2709
|
const client = getApiClient();
|
|
2561
2710
|
logger_default.blank();
|
|
@@ -2578,6 +2727,7 @@ var loginCommand = new Command11("login").description("Login to Skillo platform"
|
|
|
2578
2727
|
logger_default.info(`Welcome, ${result.data.user.name}!`);
|
|
2579
2728
|
logger_default.dim(`Email: ${result.data.user.email}`);
|
|
2580
2729
|
logger_default.blank();
|
|
2730
|
+
await autoStartDaemon();
|
|
2581
2731
|
logger_default.dim("Your patterns and skills will now sync with the Skillo platform.");
|
|
2582
2732
|
} else {
|
|
2583
2733
|
client.clearApiKey();
|
|
@@ -2650,6 +2800,7 @@ var loginCommand = new Command11("login").description("Login to Skillo platform"
|
|
|
2650
2800
|
logger_default.dim(`Email: ${authResult.data.user.email}`);
|
|
2651
2801
|
}
|
|
2652
2802
|
logger_default.blank();
|
|
2803
|
+
await autoStartDaemon();
|
|
2653
2804
|
logger_default.dim("Your patterns and skills will now sync with the Skillo platform.");
|
|
2654
2805
|
return;
|
|
2655
2806
|
} else {
|
|
@@ -2717,8 +2868,8 @@ var whoamiCommand = new Command11("whoami").description("Show current login stat
|
|
|
2717
2868
|
});
|
|
2718
2869
|
|
|
2719
2870
|
// src/commands/sync.ts
|
|
2720
|
-
import { existsSync as existsSync12, writeFileSync as writeFileSync6, mkdirSync } from "fs";
|
|
2721
|
-
import { join as
|
|
2871
|
+
import { existsSync as existsSync12, writeFileSync as writeFileSync6, mkdirSync as mkdirSync2 } from "fs";
|
|
2872
|
+
import { join as join6 } from "path";
|
|
2722
2873
|
import { Command as Command12 } from "commander";
|
|
2723
2874
|
var syncCommand = new Command12("sync").description("Sync data with Skillo platform").option("--push", "Push local data to platform").option("--pull", "Pull data from platform").option("--patterns", "Sync patterns only").option("--skills", "Sync skills only").option("--commands", "Sync commands only").action(
|
|
2724
2875
|
async (options) => {
|
|
@@ -2836,10 +2987,10 @@ async function pullSkills(skills) {
|
|
|
2836
2987
|
let downloaded = 0;
|
|
2837
2988
|
for (const skill of skills) {
|
|
2838
2989
|
if (!skill.content) continue;
|
|
2839
|
-
const skillDir =
|
|
2840
|
-
const skillFile =
|
|
2990
|
+
const skillDir = join6(skillsDir, skill.slug);
|
|
2991
|
+
const skillFile = join6(skillDir, "SKILL.md");
|
|
2841
2992
|
if (!existsSync12(skillDir)) {
|
|
2842
|
-
|
|
2993
|
+
mkdirSync2(skillDir, { recursive: true });
|
|
2843
2994
|
}
|
|
2844
2995
|
writeFileSync6(skillFile, skill.content, "utf-8");
|
|
2845
2996
|
downloaded++;
|
|
@@ -2848,9 +2999,26 @@ async function pullSkills(skills) {
|
|
|
2848
2999
|
logger_default.success(`Downloaded ${downloaded} skills to ~/.claude/skills/`);
|
|
2849
3000
|
}
|
|
2850
3001
|
|
|
3002
|
+
// src/commands/tray.ts
|
|
3003
|
+
import { Command as Command13 } from "commander";
|
|
3004
|
+
var trayCommand = new Command13("tray").description("Start the system tray icon").action(async () => {
|
|
3005
|
+
try {
|
|
3006
|
+
const { startTray } = await import("./tray-62I5KIFE.js");
|
|
3007
|
+
await startTray();
|
|
3008
|
+
} catch (error) {
|
|
3009
|
+
if (error instanceof Error && error.message.includes("Cannot find module")) {
|
|
3010
|
+
logger_default.error("System tray not available. The systray2 package may not be installed.");
|
|
3011
|
+
logger_default.dim("Try: npm install -g skillo");
|
|
3012
|
+
} else {
|
|
3013
|
+
logger_default.error(`Tray error: ${error instanceof Error ? error.message : error}`);
|
|
3014
|
+
}
|
|
3015
|
+
process.exit(1);
|
|
3016
|
+
}
|
|
3017
|
+
});
|
|
3018
|
+
|
|
2851
3019
|
// src/cli.ts
|
|
2852
|
-
var VERSION = "0.
|
|
2853
|
-
var program = new
|
|
3020
|
+
var VERSION = "0.2.2";
|
|
3021
|
+
var program = new Command14();
|
|
2854
3022
|
program.name("skillo").description(
|
|
2855
3023
|
`Skillo - Learn workflows by observation, not explanation.
|
|
2856
3024
|
|
|
@@ -2884,6 +3052,8 @@ program.addCommand(claudeCommand);
|
|
|
2884
3052
|
program.addCommand(projectCommand);
|
|
2885
3053
|
program.addCommand(trackCommand);
|
|
2886
3054
|
program.addCommand(untrackCommand);
|
|
3055
|
+
program.addCommand(serviceCommand);
|
|
3056
|
+
program.addCommand(trayCommand);
|
|
2887
3057
|
program.command("version").description("Show version information").action(() => {
|
|
2888
3058
|
console.log(`Skillo v${VERSION}`);
|
|
2889
3059
|
console.log("Autonomous workflow learning & skill generation system");
|