skillo 0.1.0
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 +261 -0
- package/dist/api-client-6OWIJNGI.js +11 -0
- package/dist/api-client-6OWIJNGI.js.map +1 -0
- package/dist/chunk-H2VPNNUK.js +536 -0
- package/dist/chunk-H2VPNNUK.js.map +1 -0
- package/dist/chunk-SWPZL2RI.js +73 -0
- package/dist/chunk-SWPZL2RI.js.map +1 -0
- package/dist/cli.js +2763 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.js +746 -0
- package/dist/index.js.map +1 -0
- package/dist/paths-YODCFIEO.js +30 -0
- package/dist/paths-YODCFIEO.js.map +1 -0
- package/package.json +66 -0
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ensureDirectory,
|
|
4
|
+
getConfigFile
|
|
5
|
+
} from "./chunk-SWPZL2RI.js";
|
|
6
|
+
|
|
7
|
+
// src/core/config.ts
|
|
8
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
9
|
+
import { dirname } from "path";
|
|
10
|
+
import YAML from "yaml";
|
|
11
|
+
function getDefaultConfig() {
|
|
12
|
+
return {
|
|
13
|
+
defaultShell: null,
|
|
14
|
+
patternDetection: {
|
|
15
|
+
minCount: 3,
|
|
16
|
+
sessionTimeout: 30,
|
|
17
|
+
similarityThreshold: 0.8,
|
|
18
|
+
maxSequenceLength: 10
|
|
19
|
+
},
|
|
20
|
+
privacy: {
|
|
21
|
+
autoRedact: true,
|
|
22
|
+
trackOutput: false,
|
|
23
|
+
neverTrack: [
|
|
24
|
+
"*password*",
|
|
25
|
+
"*secret*",
|
|
26
|
+
"vault *",
|
|
27
|
+
"1password *",
|
|
28
|
+
"op *",
|
|
29
|
+
"export *_KEY=*",
|
|
30
|
+
"export *_SECRET=*",
|
|
31
|
+
"export *_TOKEN=*"
|
|
32
|
+
],
|
|
33
|
+
redactionPatterns: [
|
|
34
|
+
"password[=:]\\s*\\S+",
|
|
35
|
+
"token[=:]\\s*\\S+",
|
|
36
|
+
"secret[=:]\\s*\\S+",
|
|
37
|
+
"api[_-]?key[=:]\\s*\\S+",
|
|
38
|
+
"auth[=:]\\s*\\S+",
|
|
39
|
+
"bearer\\s+\\S+",
|
|
40
|
+
"-----BEGIN.*PRIVATE KEY-----",
|
|
41
|
+
"AKIA[0-9A-Z]{16}",
|
|
42
|
+
`["'][A-Za-z0-9+/]{40,}["']`
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
notifications: {
|
|
46
|
+
enabled: true,
|
|
47
|
+
style: "inline",
|
|
48
|
+
sound: false
|
|
49
|
+
},
|
|
50
|
+
skillGeneration: {
|
|
51
|
+
outputDir: null,
|
|
52
|
+
includeScripts: true,
|
|
53
|
+
includeExamples: true,
|
|
54
|
+
autoGenerate: false
|
|
55
|
+
},
|
|
56
|
+
claudeCode: {
|
|
57
|
+
watchConversations: true,
|
|
58
|
+
conversationDir: null
|
|
59
|
+
},
|
|
60
|
+
daemon: {
|
|
61
|
+
logLevel: "INFO",
|
|
62
|
+
patternCheckInterval: 60,
|
|
63
|
+
conversationCheckInterval: 5
|
|
64
|
+
},
|
|
65
|
+
team: {
|
|
66
|
+
enabled: false,
|
|
67
|
+
slug: null,
|
|
68
|
+
autoSync: true,
|
|
69
|
+
syncInterval: 300
|
|
70
|
+
},
|
|
71
|
+
api: {
|
|
72
|
+
baseUrl: null,
|
|
73
|
+
timeout: 3e4
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function deepMerge(base, override) {
|
|
78
|
+
const result = { ...base };
|
|
79
|
+
for (const key of Object.keys(override)) {
|
|
80
|
+
const value = override[key];
|
|
81
|
+
if (value !== void 0 && typeof result[key] === "object" && result[key] !== null && typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
82
|
+
result[key] = deepMerge(
|
|
83
|
+
result[key],
|
|
84
|
+
value
|
|
85
|
+
);
|
|
86
|
+
} else if (value !== void 0) {
|
|
87
|
+
result[key] = value;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
function loadConfig(path) {
|
|
93
|
+
const configPath = path || getConfigFile();
|
|
94
|
+
if (!existsSync(configPath)) {
|
|
95
|
+
return getDefaultConfig();
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const content = readFileSync(configPath, "utf-8");
|
|
99
|
+
const parsed = YAML.parse(content) || {};
|
|
100
|
+
const converted = convertKeysToCamelCase(parsed);
|
|
101
|
+
const defaultConfig = getDefaultConfig();
|
|
102
|
+
return deepMerge(defaultConfig, converted);
|
|
103
|
+
} catch {
|
|
104
|
+
return getDefaultConfig();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function saveConfig(config, path) {
|
|
108
|
+
const configPath = path || getConfigFile();
|
|
109
|
+
ensureDirectory(dirname(configPath));
|
|
110
|
+
const converted = convertKeysToSnakeCase(config);
|
|
111
|
+
const content = YAML.stringify(converted, {
|
|
112
|
+
indent: 2,
|
|
113
|
+
lineWidth: 0
|
|
114
|
+
});
|
|
115
|
+
writeFileSync(configPath, content, "utf-8");
|
|
116
|
+
}
|
|
117
|
+
function getConfigValue(config, key) {
|
|
118
|
+
const keys = key.split(".");
|
|
119
|
+
let current = config;
|
|
120
|
+
for (const k of keys) {
|
|
121
|
+
if (current && typeof current === "object" && k in current) {
|
|
122
|
+
current = current[k];
|
|
123
|
+
} else {
|
|
124
|
+
return void 0;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return current;
|
|
128
|
+
}
|
|
129
|
+
function setConfigValue(config, key, value) {
|
|
130
|
+
const keys = key.split(".");
|
|
131
|
+
const result = JSON.parse(JSON.stringify(config));
|
|
132
|
+
let current = result;
|
|
133
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
134
|
+
const k = keys[i];
|
|
135
|
+
if (!(k in current)) {
|
|
136
|
+
current[k] = {};
|
|
137
|
+
}
|
|
138
|
+
current = current[k];
|
|
139
|
+
}
|
|
140
|
+
current[keys[keys.length - 1]] = value;
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
function toCamelCase(str) {
|
|
144
|
+
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
145
|
+
}
|
|
146
|
+
function toSnakeCase(str) {
|
|
147
|
+
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
148
|
+
}
|
|
149
|
+
function convertKeysToCamelCase(obj) {
|
|
150
|
+
if (Array.isArray(obj)) {
|
|
151
|
+
return obj.map(convertKeysToCamelCase);
|
|
152
|
+
}
|
|
153
|
+
if (obj !== null && typeof obj === "object") {
|
|
154
|
+
const result = {};
|
|
155
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
156
|
+
result[toCamelCase(key)] = convertKeysToCamelCase(value);
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
return obj;
|
|
161
|
+
}
|
|
162
|
+
function convertKeysToSnakeCase(obj) {
|
|
163
|
+
if (Array.isArray(obj)) {
|
|
164
|
+
return obj.map(convertKeysToSnakeCase);
|
|
165
|
+
}
|
|
166
|
+
if (obj !== null && typeof obj === "object") {
|
|
167
|
+
const result = {};
|
|
168
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
169
|
+
result[toSnakeCase(key)] = convertKeysToSnakeCase(value);
|
|
170
|
+
}
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
return obj;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/core/api-client.ts
|
|
177
|
+
var DEFAULT_API_URL = "http://localhost:3000/api/cli";
|
|
178
|
+
var ApiClient = class {
|
|
179
|
+
baseUrl;
|
|
180
|
+
apiKey;
|
|
181
|
+
constructor() {
|
|
182
|
+
const config = loadConfig();
|
|
183
|
+
this.baseUrl = config.api?.baseUrl || DEFAULT_API_URL;
|
|
184
|
+
this.apiKey = this.loadApiKey();
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Load API key from config
|
|
188
|
+
*/
|
|
189
|
+
loadApiKey() {
|
|
190
|
+
const config = loadConfig();
|
|
191
|
+
return config.apiKey || null;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Save API key to config
|
|
195
|
+
*/
|
|
196
|
+
saveApiKey(key) {
|
|
197
|
+
const config = loadConfig();
|
|
198
|
+
config.apiKey = key;
|
|
199
|
+
saveConfig(config);
|
|
200
|
+
this.apiKey = key;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Clear API key from config
|
|
204
|
+
*/
|
|
205
|
+
clearApiKey() {
|
|
206
|
+
const config = loadConfig();
|
|
207
|
+
delete config.apiKey;
|
|
208
|
+
saveConfig(config);
|
|
209
|
+
this.apiKey = null;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Check if API key is configured
|
|
213
|
+
*/
|
|
214
|
+
hasApiKey() {
|
|
215
|
+
return !!this.apiKey;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Get the configured API key (masked)
|
|
219
|
+
*/
|
|
220
|
+
getMaskedApiKey() {
|
|
221
|
+
if (!this.apiKey) return null;
|
|
222
|
+
return `${this.apiKey.substring(0, 12)}...${this.apiKey.substring(this.apiKey.length - 4)}`;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Set the API base URL
|
|
226
|
+
*/
|
|
227
|
+
setBaseUrl(url) {
|
|
228
|
+
const config = loadConfig();
|
|
229
|
+
if (!config.api) {
|
|
230
|
+
config.api = { baseUrl: url };
|
|
231
|
+
} else {
|
|
232
|
+
config.api.baseUrl = url;
|
|
233
|
+
}
|
|
234
|
+
saveConfig(config);
|
|
235
|
+
this.baseUrl = url;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Make an authenticated API request
|
|
239
|
+
*/
|
|
240
|
+
async request(endpoint, options = {}) {
|
|
241
|
+
if (!this.apiKey) {
|
|
242
|
+
return { success: false, error: "No API key configured. Run 'skillo login' first." };
|
|
243
|
+
}
|
|
244
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
245
|
+
const headers = {
|
|
246
|
+
"Content-Type": "application/json",
|
|
247
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
248
|
+
...options.headers
|
|
249
|
+
};
|
|
250
|
+
try {
|
|
251
|
+
const response = await fetch(url, {
|
|
252
|
+
...options,
|
|
253
|
+
headers
|
|
254
|
+
});
|
|
255
|
+
const data = await response.json();
|
|
256
|
+
if (process.env.SKILLO_DEBUG) {
|
|
257
|
+
console.log("[DEBUG] Request:", url);
|
|
258
|
+
console.log("[DEBUG] Status:", response.status);
|
|
259
|
+
console.log("[DEBUG] Response:", JSON.stringify(data));
|
|
260
|
+
}
|
|
261
|
+
if (!response.ok) {
|
|
262
|
+
return {
|
|
263
|
+
success: false,
|
|
264
|
+
error: data.error || `Request failed with status ${response.status}`
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
if (data.success !== void 0 && data.data !== void 0) {
|
|
268
|
+
return { success: data.success, data: data.data, message: data.message };
|
|
269
|
+
}
|
|
270
|
+
if (data.success !== void 0) {
|
|
271
|
+
return data;
|
|
272
|
+
}
|
|
273
|
+
return { success: true, data };
|
|
274
|
+
} catch (error) {
|
|
275
|
+
if (process.env.SKILLO_DEBUG) {
|
|
276
|
+
console.log("[DEBUG] Error:", error);
|
|
277
|
+
}
|
|
278
|
+
if (error instanceof Error) {
|
|
279
|
+
if (error.message.includes("ECONNREFUSED")) {
|
|
280
|
+
return {
|
|
281
|
+
success: false,
|
|
282
|
+
error: "Cannot connect to Skillo platform. Is the server running?"
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
return { success: false, error: error.message };
|
|
286
|
+
}
|
|
287
|
+
return { success: false, error: "Unknown error occurred" };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Authenticate with the platform
|
|
292
|
+
*/
|
|
293
|
+
async authenticate() {
|
|
294
|
+
return this.request("/auth", { method: "POST" });
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Sync commands to platform
|
|
298
|
+
*/
|
|
299
|
+
async syncCommands(commands) {
|
|
300
|
+
return this.request("/sync", {
|
|
301
|
+
method: "POST",
|
|
302
|
+
body: JSON.stringify({
|
|
303
|
+
type: "commands",
|
|
304
|
+
data: commands
|
|
305
|
+
})
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Sync a pattern to platform
|
|
310
|
+
*/
|
|
311
|
+
async syncPattern(pattern) {
|
|
312
|
+
return this.request("/sync", {
|
|
313
|
+
method: "POST",
|
|
314
|
+
body: JSON.stringify({
|
|
315
|
+
type: "pattern",
|
|
316
|
+
data: pattern
|
|
317
|
+
})
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Create a session on the platform
|
|
322
|
+
*/
|
|
323
|
+
async createSession(session) {
|
|
324
|
+
return this.request("/sync", {
|
|
325
|
+
method: "POST",
|
|
326
|
+
body: JSON.stringify({
|
|
327
|
+
type: "session",
|
|
328
|
+
data: session
|
|
329
|
+
})
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* End a session on the platform
|
|
334
|
+
*/
|
|
335
|
+
async endSession(sessionId) {
|
|
336
|
+
return this.request("/sessions", {
|
|
337
|
+
method: "PATCH",
|
|
338
|
+
body: JSON.stringify({ sessionId })
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Start a new session on the platform (for standalone terminals)
|
|
343
|
+
*/
|
|
344
|
+
async startSession(shell) {
|
|
345
|
+
return this.request("/sessions", {
|
|
346
|
+
method: "POST",
|
|
347
|
+
body: JSON.stringify({ shell })
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Download skills and patterns from platform
|
|
352
|
+
*/
|
|
353
|
+
async downloadData(type = "all") {
|
|
354
|
+
return this.request(`/sync?type=${type}`, { method: "GET" });
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Generate a skill from a pattern
|
|
358
|
+
*/
|
|
359
|
+
async generateSkill(options) {
|
|
360
|
+
return this.request("/generate", {
|
|
361
|
+
method: "POST",
|
|
362
|
+
body: JSON.stringify(options)
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Start device authorization flow (no auth required)
|
|
367
|
+
*/
|
|
368
|
+
async startDeviceAuth(deviceName) {
|
|
369
|
+
const url = `${this.baseUrl}/auth/device`;
|
|
370
|
+
try {
|
|
371
|
+
const response = await fetch(url, {
|
|
372
|
+
method: "POST",
|
|
373
|
+
headers: { "Content-Type": "application/json" },
|
|
374
|
+
body: JSON.stringify({ device_name: deviceName })
|
|
375
|
+
});
|
|
376
|
+
const data = await response.json();
|
|
377
|
+
if (!response.ok) {
|
|
378
|
+
return {
|
|
379
|
+
success: false,
|
|
380
|
+
error: data.error || `Request failed with status ${response.status}`
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
return { success: true, data };
|
|
384
|
+
} catch (error) {
|
|
385
|
+
if (error instanceof Error) {
|
|
386
|
+
if (error.message.includes("ECONNREFUSED")) {
|
|
387
|
+
return {
|
|
388
|
+
success: false,
|
|
389
|
+
error: "Cannot connect to Skillo platform. Is the server running?"
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
return { success: false, error: error.message };
|
|
393
|
+
}
|
|
394
|
+
return { success: false, error: "Unknown error occurred" };
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Check token status (polling)
|
|
399
|
+
*/
|
|
400
|
+
async checkTokenStatus(code) {
|
|
401
|
+
const url = `${this.baseUrl}/token?code=${encodeURIComponent(code)}`;
|
|
402
|
+
try {
|
|
403
|
+
const response = await fetch(url, { method: "GET" });
|
|
404
|
+
const data = await response.json();
|
|
405
|
+
if (!response.ok) {
|
|
406
|
+
return {
|
|
407
|
+
success: false,
|
|
408
|
+
error: data.error || `Request failed with status ${response.status}`
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
return { success: true, data };
|
|
412
|
+
} catch (error) {
|
|
413
|
+
if (error instanceof Error) {
|
|
414
|
+
return { success: false, error: error.message };
|
|
415
|
+
}
|
|
416
|
+
return { success: false, error: "Unknown error occurred" };
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Exchange code for API key
|
|
421
|
+
*/
|
|
422
|
+
async exchangeToken(code, deviceName) {
|
|
423
|
+
const url = `${this.baseUrl}/token`;
|
|
424
|
+
try {
|
|
425
|
+
const response = await fetch(url, {
|
|
426
|
+
method: "POST",
|
|
427
|
+
headers: { "Content-Type": "application/json" },
|
|
428
|
+
body: JSON.stringify({ code, device_name: deviceName })
|
|
429
|
+
});
|
|
430
|
+
const data = await response.json();
|
|
431
|
+
if (!response.ok) {
|
|
432
|
+
return {
|
|
433
|
+
success: false,
|
|
434
|
+
error: data.error || `Request failed with status ${response.status}`
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
return { success: true, data };
|
|
438
|
+
} catch (error) {
|
|
439
|
+
if (error instanceof Error) {
|
|
440
|
+
return { success: false, error: error.message };
|
|
441
|
+
}
|
|
442
|
+
return { success: false, error: "Unknown error occurred" };
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Sync Claude Code prompts to platform
|
|
447
|
+
*/
|
|
448
|
+
async syncClaudePrompts(prompts, options) {
|
|
449
|
+
return this.request("/claude", {
|
|
450
|
+
method: "POST",
|
|
451
|
+
body: JSON.stringify({
|
|
452
|
+
prompts,
|
|
453
|
+
terminalSessionId: options?.terminalSessionId,
|
|
454
|
+
projectPath: options?.projectPath
|
|
455
|
+
})
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Get Claude Code sessions from platform
|
|
460
|
+
*/
|
|
461
|
+
async getClaudeSessions(limit = 20) {
|
|
462
|
+
return this.request(`/claude?limit=${limit}`, { method: "GET" });
|
|
463
|
+
}
|
|
464
|
+
// ============================================================================
|
|
465
|
+
// PROJECT TRACKING
|
|
466
|
+
// ============================================================================
|
|
467
|
+
/**
|
|
468
|
+
* Connect a project for tracking
|
|
469
|
+
*/
|
|
470
|
+
async connectProject(params) {
|
|
471
|
+
return this.request("/projects/connect", {
|
|
472
|
+
method: "POST",
|
|
473
|
+
body: JSON.stringify(params)
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Disconnect a project from tracking
|
|
478
|
+
*/
|
|
479
|
+
async disconnectProject(path) {
|
|
480
|
+
return this.request(`/projects/connect?path=${encodeURIComponent(path)}`, {
|
|
481
|
+
method: "DELETE"
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Get tracking status for a project
|
|
486
|
+
*/
|
|
487
|
+
async getProjectStatus(path) {
|
|
488
|
+
return this.request(`/projects/connect?path=${encodeURIComponent(path)}`, {
|
|
489
|
+
method: "GET"
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* List all tracked projects
|
|
494
|
+
*/
|
|
495
|
+
async listProjects(includeDisabled = false) {
|
|
496
|
+
return this.request(`/projects/tracked?includeDisabled=${includeDisabled}`, {
|
|
497
|
+
method: "GET"
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Check if a path is in a tracked project
|
|
502
|
+
* Returns the project if tracked, null if not
|
|
503
|
+
*/
|
|
504
|
+
async isProjectTracked(path) {
|
|
505
|
+
const result = await this.getProjectStatus(path);
|
|
506
|
+
if (result.success && result.data?.connected) {
|
|
507
|
+
return {
|
|
508
|
+
tracked: true,
|
|
509
|
+
project: result.data.project ? {
|
|
510
|
+
id: result.data.project.id,
|
|
511
|
+
name: result.data.project.name,
|
|
512
|
+
path: result.data.project.path
|
|
513
|
+
} : void 0
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
return { tracked: false };
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
var clientInstance = null;
|
|
520
|
+
function getApiClient() {
|
|
521
|
+
if (!clientInstance) {
|
|
522
|
+
clientInstance = new ApiClient();
|
|
523
|
+
}
|
|
524
|
+
return clientInstance;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
export {
|
|
528
|
+
getDefaultConfig,
|
|
529
|
+
loadConfig,
|
|
530
|
+
saveConfig,
|
|
531
|
+
getConfigValue,
|
|
532
|
+
setConfigValue,
|
|
533
|
+
ApiClient,
|
|
534
|
+
getApiClient
|
|
535
|
+
};
|
|
536
|
+
//# sourceMappingURL=chunk-H2VPNNUK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/config.ts","../src/core/api-client.ts"],"sourcesContent":["/**\n * Configuration management for Skillo.\n */\n\nimport { readFileSync, writeFileSync, existsSync } from \"fs\";\nimport { dirname } from \"path\";\nimport YAML from \"yaml\";\nimport { ensureDirectory, getConfigFile } from \"../utils/paths.js\";\n\nexport interface SkilloConfig {\n // Shell settings\n defaultShell: string | null;\n\n // Pattern detection\n patternDetection: {\n minCount: number;\n sessionTimeout: number;\n similarityThreshold: number;\n maxSequenceLength: number;\n };\n\n // Privacy settings\n privacy: {\n autoRedact: boolean;\n trackOutput: boolean;\n neverTrack: string[];\n redactionPatterns: string[];\n };\n\n // Notification settings\n notifications: {\n enabled: boolean;\n style: \"inline\" | \"desktop\" | \"both\";\n sound: boolean;\n };\n\n // Skill generation\n skillGeneration: {\n outputDir: string | null;\n includeScripts: boolean;\n includeExamples: boolean;\n autoGenerate: boolean;\n };\n\n // Claude Code integration\n claudeCode: {\n watchConversations: boolean;\n conversationDir: string | null;\n };\n\n // Daemon settings\n daemon: {\n logLevel: \"DEBUG\" | \"INFO\" | \"WARN\" | \"ERROR\";\n patternCheckInterval: number;\n conversationCheckInterval: number;\n };\n\n // Team settings\n team: {\n enabled: boolean;\n slug: string | null;\n autoSync: boolean;\n syncInterval: number;\n };\n\n // API settings\n api: {\n baseUrl: string | null;\n timeout: number;\n };\n\n // API key (stored separately, not in YAML for security)\n apiKey?: string;\n}\n\nexport function getDefaultConfig(): SkilloConfig {\n return {\n defaultShell: null,\n\n patternDetection: {\n minCount: 3,\n sessionTimeout: 30,\n similarityThreshold: 0.8,\n maxSequenceLength: 10,\n },\n\n privacy: {\n autoRedact: true,\n trackOutput: false,\n neverTrack: [\n \"*password*\",\n \"*secret*\",\n \"vault *\",\n \"1password *\",\n \"op *\",\n \"export *_KEY=*\",\n \"export *_SECRET=*\",\n \"export *_TOKEN=*\",\n ],\n redactionPatterns: [\n \"password[=:]\\\\s*\\\\S+\",\n \"token[=:]\\\\s*\\\\S+\",\n \"secret[=:]\\\\s*\\\\S+\",\n \"api[_-]?key[=:]\\\\s*\\\\S+\",\n \"auth[=:]\\\\s*\\\\S+\",\n \"bearer\\\\s+\\\\S+\",\n \"-----BEGIN.*PRIVATE KEY-----\",\n \"AKIA[0-9A-Z]{16}\",\n '[\"\\'][A-Za-z0-9+/]{40,}[\"\\']',\n ],\n },\n\n notifications: {\n enabled: true,\n style: \"inline\",\n sound: false,\n },\n\n skillGeneration: {\n outputDir: null,\n includeScripts: true,\n includeExamples: true,\n autoGenerate: false,\n },\n\n claudeCode: {\n watchConversations: true,\n conversationDir: null,\n },\n\n daemon: {\n logLevel: \"INFO\",\n patternCheckInterval: 60,\n conversationCheckInterval: 5,\n },\n\n team: {\n enabled: false,\n slug: null,\n autoSync: true,\n syncInterval: 300,\n },\n\n api: {\n baseUrl: null,\n timeout: 30000,\n },\n };\n}\n\nfunction deepMerge<T extends object>(base: T, override: Partial<T>): T {\n const result = { ...base };\n\n for (const key of Object.keys(override) as Array<keyof T>) {\n const value = override[key];\n if (\n value !== undefined &&\n typeof result[key] === \"object\" &&\n result[key] !== null &&\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value)\n ) {\n result[key] = deepMerge(\n result[key] as Record<string, unknown>,\n value as Record<string, unknown>\n ) as T[keyof T];\n } else if (value !== undefined) {\n result[key] = value as T[keyof T];\n }\n }\n\n return result;\n}\n\nexport function loadConfig(path?: string): SkilloConfig {\n const configPath = path || getConfigFile();\n\n if (!existsSync(configPath)) {\n return getDefaultConfig();\n }\n\n try {\n const content = readFileSync(configPath, \"utf-8\");\n const parsed = YAML.parse(content) || {};\n\n // Convert snake_case to camelCase for compatibility\n const converted = convertKeysToCamelCase(parsed);\n const defaultConfig = getDefaultConfig();\n\n // Deep merge with default config\n return deepMerge(defaultConfig, converted as Partial<SkilloConfig>);\n } catch {\n return getDefaultConfig();\n }\n}\n\nexport function saveConfig(config: SkilloConfig, path?: string): void {\n const configPath = path || getConfigFile();\n\n ensureDirectory(dirname(configPath));\n\n // Convert camelCase to snake_case for YAML file\n const converted = convertKeysToSnakeCase(config);\n\n const content = YAML.stringify(converted, {\n indent: 2,\n lineWidth: 0,\n });\n\n writeFileSync(configPath, content, \"utf-8\");\n}\n\nexport function getConfigValue(config: SkilloConfig, key: string): unknown {\n const keys = key.split(\".\");\n let current: unknown = config;\n\n for (const k of keys) {\n if (current && typeof current === \"object\" && k in current) {\n current = (current as Record<string, unknown>)[k];\n } else {\n return undefined;\n }\n }\n\n return current;\n}\n\nexport function setConfigValue(config: SkilloConfig, key: string, value: unknown): SkilloConfig {\n const keys = key.split(\".\");\n const result = JSON.parse(JSON.stringify(config)) as SkilloConfig;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let current: any = result;\n\n for (let i = 0; i < keys.length - 1; i++) {\n const k = keys[i];\n if (!(k in current)) {\n current[k] = {};\n }\n current = current[k] as Record<string, unknown>;\n }\n\n current[keys[keys.length - 1]] = value;\n return result;\n}\n\n// Helper functions for key conversion\nfunction toCamelCase(str: string): string {\n return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());\n}\n\nfunction toSnakeCase(str: string): string {\n return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n}\n\nfunction convertKeysToCamelCase(obj: unknown): unknown {\n if (Array.isArray(obj)) {\n return obj.map(convertKeysToCamelCase);\n }\n\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[toCamelCase(key)] = convertKeysToCamelCase(value);\n }\n return result;\n }\n\n return obj;\n}\n\nfunction convertKeysToSnakeCase(obj: unknown): unknown {\n if (Array.isArray(obj)) {\n return obj.map(convertKeysToSnakeCase);\n }\n\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[toSnakeCase(key)] = convertKeysToSnakeCase(value);\n }\n return result;\n }\n\n return obj;\n}\n","/**\n * Skillo Platform API Client\n *\n * Handles communication with the Skillo platform API.\n */\n\nimport { loadConfig, saveConfig } from \"./config.js\";\n\nconst DEFAULT_API_URL = \"http://localhost:3000/api/cli\";\n\ninterface ApiResponse<T = unknown> {\n success: boolean;\n data?: T;\n error?: string;\n message?: string;\n}\n\ninterface AuthResponse {\n success: boolean;\n user: {\n id: string;\n email: string;\n name: string;\n };\n}\n\ninterface SyncResponse {\n success: boolean;\n skills?: Array<{\n id: string;\n name: string;\n slug: string;\n description: string;\n content: string;\n commands: string[];\n }>;\n patterns?: Array<{\n id: string;\n name: string;\n description: string;\n commands: string[];\n status: string;\n }>;\n}\n\ninterface GenerateResponse {\n success: boolean;\n skill: {\n id: string;\n name: string;\n slug: string;\n description: string;\n content: string;\n commands: string[];\n };\n}\n\ninterface DeviceAuthResponse {\n code: string;\n verification_url: string;\n expires_in: number;\n interval: number;\n}\n\ninterface TokenStatusResponse {\n status: \"pending\" | \"ready\" | \"expired\" | \"used\" | \"not_found\";\n}\n\ninterface TokenExchangeResponse {\n success: boolean;\n api_key: string;\n user: {\n id: string;\n };\n}\n\nexport class ApiClient {\n private baseUrl: string;\n private apiKey: string | null;\n\n constructor() {\n const config = loadConfig();\n this.baseUrl = config.api?.baseUrl || DEFAULT_API_URL;\n this.apiKey = this.loadApiKey();\n }\n\n /**\n * Load API key from config\n */\n private loadApiKey(): string | null {\n const config = loadConfig();\n return (config as { apiKey?: string }).apiKey || null;\n }\n\n /**\n * Save API key to config\n */\n saveApiKey(key: string): void {\n const config = loadConfig();\n (config as { apiKey?: string }).apiKey = key;\n saveConfig(config);\n this.apiKey = key;\n }\n\n /**\n * Clear API key from config\n */\n clearApiKey(): void {\n const config = loadConfig();\n delete (config as { apiKey?: string }).apiKey;\n saveConfig(config);\n this.apiKey = null;\n }\n\n /**\n * Check if API key is configured\n */\n hasApiKey(): boolean {\n return !!this.apiKey;\n }\n\n /**\n * Get the configured API key (masked)\n */\n getMaskedApiKey(): string | null {\n if (!this.apiKey) return null;\n return `${this.apiKey.substring(0, 12)}...${this.apiKey.substring(this.apiKey.length - 4)}`;\n }\n\n /**\n * Set the API base URL\n */\n setBaseUrl(url: string): void {\n const config = loadConfig();\n if (!config.api) {\n (config as { api?: { baseUrl: string } }).api = { baseUrl: url };\n } else {\n config.api.baseUrl = url;\n }\n saveConfig(config);\n this.baseUrl = url;\n }\n\n /**\n * Make an authenticated API request\n */\n private async request<T>(\n endpoint: string,\n options: RequestInit = {}\n ): Promise<ApiResponse<T>> {\n if (!this.apiKey) {\n return { success: false, error: \"No API key configured. Run 'skillo login' first.\" };\n }\n\n const url = `${this.baseUrl}${endpoint}`;\n const headers: HeadersInit = {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${this.apiKey}`,\n ...options.headers,\n };\n\n try {\n const response = await fetch(url, {\n ...options,\n headers,\n });\n\n const data = await response.json();\n\n if (process.env.SKILLO_DEBUG) {\n console.log('[DEBUG] Request:', url);\n console.log('[DEBUG] Status:', response.status);\n console.log('[DEBUG] Response:', JSON.stringify(data));\n }\n\n if (!response.ok) {\n return {\n success: false,\n error: data.error || `Request failed with status ${response.status}`,\n };\n }\n\n // If the response already has success/data structure, return as-is\n // Otherwise wrap the data\n if (data.success !== undefined && data.data !== undefined) {\n return { success: data.success, data: data.data as T, message: data.message };\n }\n\n // If response just has success field (no data wrapper), return as-is\n if (data.success !== undefined) {\n return data;\n }\n\n // Legacy: wrap raw data\n return { success: true, data: data as T };\n } catch (error) {\n if (process.env.SKILLO_DEBUG) {\n console.log('[DEBUG] Error:', error);\n }\n if (error instanceof Error) {\n if (error.message.includes(\"ECONNREFUSED\")) {\n return {\n success: false,\n error: \"Cannot connect to Skillo platform. Is the server running?\",\n };\n }\n return { success: false, error: error.message };\n }\n return { success: false, error: \"Unknown error occurred\" };\n }\n }\n\n /**\n * Authenticate with the platform\n */\n async authenticate(): Promise<ApiResponse<AuthResponse>> {\n return this.request<AuthResponse>(\"/auth\", { method: \"POST\" });\n }\n\n /**\n * Sync commands to platform\n */\n async syncCommands(\n commands: Array<{\n timestamp: string;\n command: string;\n normalized: string;\n cwd: string;\n exitCode?: number | null;\n durationMs?: number | null;\n sessionId?: string;\n variables?: Record<string, string> | null;\n }>\n ): Promise<ApiResponse> {\n return this.request(\"/sync\", {\n method: \"POST\",\n body: JSON.stringify({\n type: \"commands\",\n data: commands,\n }),\n });\n }\n\n /**\n * Sync a pattern to platform\n */\n async syncPattern(pattern: {\n sourceType: string;\n name?: string;\n description?: string;\n commands: string[];\n category?: string;\n frequency?: number;\n score?: number;\n firstSeen?: string;\n lastSeen?: string;\n context?: Record<string, unknown>;\n }): Promise<ApiResponse<{ pattern: { id: string; name: string; status: string } }>> {\n return this.request(\"/sync\", {\n method: \"POST\",\n body: JSON.stringify({\n type: \"pattern\",\n data: pattern,\n }),\n });\n }\n\n /**\n * Create a session on the platform\n */\n async createSession(session: {\n startedAt: string;\n shell: string;\n endedAt?: string;\n commandCount?: number;\n }): Promise<ApiResponse<{ session: { id: string } }>> {\n return this.request(\"/sync\", {\n method: \"POST\",\n body: JSON.stringify({\n type: \"session\",\n data: session,\n }),\n });\n }\n\n /**\n * End a session on the platform\n */\n async endSession(sessionId: string): Promise<ApiResponse<{ success: boolean }>> {\n return this.request(\"/sessions\", {\n method: \"PATCH\",\n body: JSON.stringify({ sessionId }),\n });\n }\n\n /**\n * Start a new session on the platform (for standalone terminals)\n */\n async startSession(shell: string): Promise<ApiResponse<{ sessionId: string }>> {\n return this.request(\"/sessions\", {\n method: \"POST\",\n body: JSON.stringify({ shell }),\n });\n }\n\n /**\n * Download skills and patterns from platform\n */\n async downloadData(type: \"all\" | \"skills\" | \"patterns\" = \"all\"): Promise<ApiResponse<SyncResponse>> {\n return this.request<SyncResponse>(`/sync?type=${type}`, { method: \"GET\" });\n }\n\n /**\n * Generate a skill from a pattern\n */\n async generateSkill(options: {\n patternId?: string;\n commands?: string[];\n name?: string;\n description?: string;\n category?: string;\n context?: Record<string, unknown>;\n }): Promise<ApiResponse<GenerateResponse>> {\n return this.request<GenerateResponse>(\"/generate\", {\n method: \"POST\",\n body: JSON.stringify(options),\n });\n }\n\n /**\n * Start device authorization flow (no auth required)\n */\n async startDeviceAuth(deviceName?: string): Promise<ApiResponse<DeviceAuthResponse>> {\n const url = `${this.baseUrl}/auth/device`;\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ device_name: deviceName }),\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n return {\n success: false,\n error: data.error || `Request failed with status ${response.status}`,\n };\n }\n\n return { success: true, data };\n } catch (error) {\n if (error instanceof Error) {\n if (error.message.includes(\"ECONNREFUSED\")) {\n return {\n success: false,\n error: \"Cannot connect to Skillo platform. Is the server running?\",\n };\n }\n return { success: false, error: error.message };\n }\n return { success: false, error: \"Unknown error occurred\" };\n }\n }\n\n /**\n * Check token status (polling)\n */\n async checkTokenStatus(code: string): Promise<ApiResponse<TokenStatusResponse>> {\n const url = `${this.baseUrl}/token?code=${encodeURIComponent(code)}`;\n try {\n const response = await fetch(url, { method: \"GET\" });\n const data = await response.json();\n\n if (!response.ok) {\n return {\n success: false,\n error: data.error || `Request failed with status ${response.status}`,\n };\n }\n\n return { success: true, data };\n } catch (error) {\n if (error instanceof Error) {\n return { success: false, error: error.message };\n }\n return { success: false, error: \"Unknown error occurred\" };\n }\n }\n\n /**\n * Exchange code for API key\n */\n async exchangeToken(code: string, deviceName?: string): Promise<ApiResponse<TokenExchangeResponse>> {\n const url = `${this.baseUrl}/token`;\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ code, device_name: deviceName }),\n });\n\n const data = await response.json();\n\n if (!response.ok) {\n return {\n success: false,\n error: data.error || `Request failed with status ${response.status}`,\n };\n }\n\n return { success: true, data };\n } catch (error) {\n if (error instanceof Error) {\n return { success: false, error: error.message };\n }\n return { success: false, error: \"Unknown error occurred\" };\n }\n }\n\n /**\n * Sync Claude Code prompts to platform\n */\n async syncClaudePrompts(\n prompts: Array<{\n display: string;\n timestamp: number;\n project: string;\n sessionId: string;\n pastedContents?: Record<string, unknown>;\n }>,\n options?: {\n terminalSessionId?: string;\n projectPath?: string;\n }\n ): Promise<ApiResponse<{\n sessionsCreated: number;\n promptsCreated: number;\n promptsSkipped: number;\n }>> {\n return this.request(\"/claude\", {\n method: \"POST\",\n body: JSON.stringify({\n prompts,\n terminalSessionId: options?.terminalSessionId,\n projectPath: options?.projectPath,\n }),\n });\n }\n\n /**\n * Get Claude Code sessions from platform\n */\n async getClaudeSessions(limit = 20): Promise<ApiResponse<{\n sessions: Array<{\n id: string;\n claudeSessionId: string;\n projectPath: string;\n projectName: string;\n startedAt: string;\n endedAt?: string;\n promptCount: number;\n prompts?: Array<{\n id: string;\n prompt: string;\n timestamp: string;\n category?: string;\n }>;\n }>;\n }>> {\n return this.request(`/claude?limit=${limit}`, { method: \"GET\" });\n }\n\n // ============================================================================\n // PROJECT TRACKING\n // ============================================================================\n\n /**\n * Connect a project for tracking\n */\n async connectProject(params: {\n path: string;\n name?: string;\n gitRemote?: string;\n gitRemoteNormalized?: string;\n language?: string;\n framework?: string;\n }): Promise<ApiResponse<{\n id: string;\n name: string;\n path: string;\n trackingEnabled: boolean;\n connectedAt: string;\n }>> {\n return this.request(\"/projects/connect\", {\n method: \"POST\",\n body: JSON.stringify(params),\n });\n }\n\n /**\n * Disconnect a project from tracking\n */\n async disconnectProject(path: string): Promise<ApiResponse<{\n id: string;\n name: string;\n trackingEnabled: boolean;\n }>> {\n return this.request(`/projects/connect?path=${encodeURIComponent(path)}`, {\n method: \"DELETE\",\n });\n }\n\n /**\n * Get tracking status for a project\n */\n async getProjectStatus(path: string): Promise<ApiResponse<{\n connected: boolean;\n tracked: boolean;\n connectedAt?: string;\n project?: {\n id: string;\n name: string;\n path: string;\n gitRemote?: string;\n gitRemoteNormalized?: string;\n trackingEnabled: boolean;\n };\n }>> {\n return this.request(`/projects/connect?path=${encodeURIComponent(path)}`, {\n method: \"GET\",\n });\n }\n\n /**\n * List all tracked projects\n */\n async listProjects(includeDisabled = false): Promise<ApiResponse<{\n projects: Array<{\n id: string;\n name: string;\n path: string;\n gitRemote?: string;\n gitRemoteNormalized?: string;\n trackingEnabled: boolean;\n connectedAt?: string;\n }>;\n totalTracked: number;\n totalProjects: number;\n }>> {\n return this.request(`/projects/tracked?includeDisabled=${includeDisabled}`, {\n method: \"GET\",\n });\n }\n\n /**\n * Check if a path is in a tracked project\n * Returns the project if tracked, null if not\n */\n async isProjectTracked(path: string): Promise<{\n tracked: boolean;\n project?: {\n id: string;\n name: string;\n path: string;\n };\n }> {\n const result = await this.getProjectStatus(path);\n if (result.success && result.data?.connected) {\n return {\n tracked: true,\n project: result.data.project ? {\n id: result.data.project.id,\n name: result.data.project.name,\n path: result.data.project.path,\n } : undefined,\n };\n }\n return { tracked: false };\n }\n}\n\n// Singleton instance\nlet clientInstance: ApiClient | null = null;\n\nexport function getApiClient(): ApiClient {\n if (!clientInstance) {\n clientInstance = new ApiClient();\n }\n return clientInstance;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils/paths.ts
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { mkdirSync, existsSync } from "fs";
|
|
7
|
+
function getHomeDir() {
|
|
8
|
+
return homedir();
|
|
9
|
+
}
|
|
10
|
+
function getDataDir() {
|
|
11
|
+
const envPath = process.env.SKILLO_DATA_DIR;
|
|
12
|
+
if (envPath) return envPath;
|
|
13
|
+
return join(getHomeDir(), ".skillo");
|
|
14
|
+
}
|
|
15
|
+
function getConfigDir() {
|
|
16
|
+
const envPath = process.env.SKILLO_CONFIG_DIR;
|
|
17
|
+
if (envPath) return envPath;
|
|
18
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME;
|
|
19
|
+
if (xdgConfig) return join(xdgConfig, "skillo");
|
|
20
|
+
return join(getHomeDir(), ".config", "skillo");
|
|
21
|
+
}
|
|
22
|
+
function getSkillsDir() {
|
|
23
|
+
const envPath = process.env.SKILLO_SKILLS_DIR;
|
|
24
|
+
if (envPath) return envPath;
|
|
25
|
+
return join(getClaudeDir(), "skills");
|
|
26
|
+
}
|
|
27
|
+
function getClaudeDir() {
|
|
28
|
+
return join(getHomeDir(), ".claude");
|
|
29
|
+
}
|
|
30
|
+
function getProjectSkillsDir(projectPath) {
|
|
31
|
+
const project = projectPath || process.cwd();
|
|
32
|
+
return join(project, ".claude", "skills");
|
|
33
|
+
}
|
|
34
|
+
function getTeamSkillsDir(teamSlug) {
|
|
35
|
+
const base = join(getSkillsDir(), "team");
|
|
36
|
+
if (teamSlug) return join(base, teamSlug);
|
|
37
|
+
return base;
|
|
38
|
+
}
|
|
39
|
+
function ensureDirectory(path) {
|
|
40
|
+
if (existsSync(path)) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
mkdirSync(path, { recursive: true });
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
function getLogFile() {
|
|
47
|
+
return join(getDataDir(), "daemon.log");
|
|
48
|
+
}
|
|
49
|
+
function getPidFile() {
|
|
50
|
+
return join(getDataDir(), "daemon.pid");
|
|
51
|
+
}
|
|
52
|
+
function getDbPath() {
|
|
53
|
+
return join(getDataDir(), "skillo.db");
|
|
54
|
+
}
|
|
55
|
+
function getConfigFile() {
|
|
56
|
+
return join(getConfigDir(), "config.yaml");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
getHomeDir,
|
|
61
|
+
getDataDir,
|
|
62
|
+
getConfigDir,
|
|
63
|
+
getSkillsDir,
|
|
64
|
+
getClaudeDir,
|
|
65
|
+
getProjectSkillsDir,
|
|
66
|
+
getTeamSkillsDir,
|
|
67
|
+
ensureDirectory,
|
|
68
|
+
getLogFile,
|
|
69
|
+
getPidFile,
|
|
70
|
+
getDbPath,
|
|
71
|
+
getConfigFile
|
|
72
|
+
};
|
|
73
|
+
//# sourceMappingURL=chunk-SWPZL2RI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/paths.ts"],"sourcesContent":["/**\n * Path utilities for Skillo.\n *\n * Handles platform-specific path resolution for data directories,\n * configuration files, and skill storage locations.\n */\n\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { mkdirSync, existsSync } from \"fs\";\n\nexport function getHomeDir(): string {\n return homedir();\n}\n\n/**\n * Get Skillo data directory.\n * This is where the database and other data files are stored.\n *\n * - Linux/macOS: ~/.skillo/\n * - Windows: %USERPROFILE%/.skillo/\n */\nexport function getDataDir(): string {\n const envPath = process.env.SKILLO_DATA_DIR;\n if (envPath) return envPath;\n\n return join(getHomeDir(), \".skillo\");\n}\n\n/**\n * Get Skillo configuration directory.\n * This is where config.yaml and other configuration files are stored.\n *\n * - Linux: ~/.config/skillo/\n * - macOS: ~/.config/skillo/\n * - Windows: %USERPROFILE%/.config/skillo/\n */\nexport function getConfigDir(): string {\n const envPath = process.env.SKILLO_CONFIG_DIR;\n if (envPath) return envPath;\n\n const xdgConfig = process.env.XDG_CONFIG_HOME;\n if (xdgConfig) return join(xdgConfig, \"skillo\");\n\n return join(getHomeDir(), \".config\", \"skillo\");\n}\n\n/**\n * Get personal skills directory.\n * This is where generated skills are stored and where Claude Code\n * reads personal skills from.\n *\n * - All platforms: ~/.claude/skills/\n */\nexport function getSkillsDir(): string {\n const envPath = process.env.SKILLO_SKILLS_DIR;\n if (envPath) return envPath;\n\n return join(getClaudeDir(), \"skills\");\n}\n\n/**\n * Get Claude Code directory.\n * This is where Claude Code stores its data, including conversations.\n *\n * - All platforms: ~/.claude/\n */\nexport function getClaudeDir(): string {\n return join(getHomeDir(), \".claude\");\n}\n\n/**\n * Get project-specific skills directory.\n * This is where project-specific skills are stored.\n *\n * - Path: <project>/.claude/skills/\n */\nexport function getProjectSkillsDir(projectPath?: string): string {\n const project = projectPath || process.cwd();\n return join(project, \".claude\", \"skills\");\n}\n\n/**\n * Get team skills directory.\n * This is where team skills are synced to.\n *\n * - Path: ~/.claude/skills/team/<team-slug>/\n */\nexport function getTeamSkillsDir(teamSlug?: string): string {\n const base = join(getSkillsDir(), \"team\");\n if (teamSlug) return join(base, teamSlug);\n return base;\n}\n\n/**\n * Ensure a directory exists, creating it if necessary.\n *\n * @returns True if directory was created, False if it already existed.\n */\nexport function ensureDirectory(path: string): boolean {\n if (existsSync(path)) {\n return false;\n }\n\n mkdirSync(path, { recursive: true });\n return true;\n}\n\n/**\n * Get path to daemon log file.\n */\nexport function getLogFile(): string {\n return join(getDataDir(), \"daemon.log\");\n}\n\n/**\n * Get path to daemon PID file.\n */\nexport function getPidFile(): string {\n return join(getDataDir(), \"daemon.pid\");\n}\n\n/**\n * Get path to SQLite database file.\n */\nexport function getDbPath(): string {\n return join(getDataDir(), \"skillo.db\");\n}\n\n/**\n * Get path to config file.\n */\nexport function getConfigFile(): string {\n return join(getConfigDir(), \"config.yaml\");\n}\n"],"mappings":";;;AAOA,SAAS,eAAe;AACxB,SAAS,YAAY;AACrB,SAAS,WAAW,kBAAkB;AAE/B,SAAS,aAAqB;AACnC,SAAO,QAAQ;AACjB;AASO,SAAS,aAAqB;AACnC,QAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,QAAS,QAAO;AAEpB,SAAO,KAAK,WAAW,GAAG,SAAS;AACrC;AAUO,SAAS,eAAuB;AACrC,QAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,QAAS,QAAO;AAEpB,QAAM,YAAY,QAAQ,IAAI;AAC9B,MAAI,UAAW,QAAO,KAAK,WAAW,QAAQ;AAE9C,SAAO,KAAK,WAAW,GAAG,WAAW,QAAQ;AAC/C;AASO,SAAS,eAAuB;AACrC,QAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,QAAS,QAAO;AAEpB,SAAO,KAAK,aAAa,GAAG,QAAQ;AACtC;AAQO,SAAS,eAAuB;AACrC,SAAO,KAAK,WAAW,GAAG,SAAS;AACrC;AAQO,SAAS,oBAAoB,aAA8B;AAChE,QAAM,UAAU,eAAe,QAAQ,IAAI;AAC3C,SAAO,KAAK,SAAS,WAAW,QAAQ;AAC1C;AAQO,SAAS,iBAAiB,UAA2B;AAC1D,QAAM,OAAO,KAAK,aAAa,GAAG,MAAM;AACxC,MAAI,SAAU,QAAO,KAAK,MAAM,QAAQ;AACxC,SAAO;AACT;AAOO,SAAS,gBAAgB,MAAuB;AACrD,MAAI,WAAW,IAAI,GAAG;AACpB,WAAO;AAAA,EACT;AAEA,YAAU,MAAM,EAAE,WAAW,KAAK,CAAC;AACnC,SAAO;AACT;AAKO,SAAS,aAAqB;AACnC,SAAO,KAAK,WAAW,GAAG,YAAY;AACxC;AAKO,SAAS,aAAqB;AACnC,SAAO,KAAK,WAAW,GAAG,YAAY;AACxC;AAKO,SAAS,YAAoB;AAClC,SAAO,KAAK,WAAW,GAAG,WAAW;AACvC;AAKO,SAAS,gBAAwB;AACtC,SAAO,KAAK,aAAa,GAAG,aAAa;AAC3C;","names":[]}
|