@skilljack/mcp 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/github-config.d.ts +83 -0
- package/dist/github-config.js +191 -0
- package/dist/github-polling.d.ts +35 -0
- package/dist/github-polling.js +108 -0
- package/dist/github-sync.d.ts +49 -0
- package/dist/github-sync.js +259 -0
- package/dist/index.d.ts +12 -3
- package/dist/index.js +309 -53
- package/dist/skill-config-tool.d.ts +22 -0
- package/dist/skill-config-tool.js +495 -0
- package/dist/skill-config.d.ts +163 -0
- package/dist/skill-config.js +450 -0
- package/dist/skill-discovery.d.ts +52 -1
- package/dist/skill-discovery.js +70 -1
- package/dist/skill-display-tool.d.ts +22 -0
- package/dist/skill-display-tool.js +302 -0
- package/dist/skill-prompts.d.ts +4 -0
- package/dist/skill-prompts.js +62 -10
- package/dist/skill-resources.d.ts +6 -3
- package/dist/skill-resources.js +10 -94
- package/dist/skill-tool.js +4 -3
- package/dist/subscriptions.d.ts +1 -1
- package/dist/subscriptions.js +1 -1
- package/dist/ui/mcp-app.d.ts +1 -0
- package/dist/ui/mcp-app.html +278 -0
- package/dist/ui/mcp-app.js +484 -0
- package/dist/ui/skill-display.d.ts +1 -0
- package/dist/ui/skill-display.html +188 -0
- package/dist/ui/skill-display.js +269 -0
- package/package.json +1 -1
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP App tool registration for skill directory configuration.
|
|
3
|
+
*
|
|
4
|
+
* Registers:
|
|
5
|
+
* - skill-config: Opens the configuration UI
|
|
6
|
+
* - skill-config-add-directory: Adds a directory (UI-only)
|
|
7
|
+
* - skill-config-remove-directory: Removes a directory (UI-only)
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as fsPromises from "node:fs/promises";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server";
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
import { getAllDirectoriesWithSources, getConfigState, addDirectoryToConfig, removeDirectoryFromConfig, getGitHubAllowedOrgs, getGitHubAllowedUsers, addGitHubAllowedOrg, removeGitHubAllowedOrg, getStaticModeFromConfig, setStaticModeInConfig, } from "./skill-config.js";
|
|
15
|
+
import { isGitHubUrl, parseGitHubUrl } from "./github-config.js";
|
|
16
|
+
/**
|
|
17
|
+
* Resource URI for the skill-config UI.
|
|
18
|
+
*/
|
|
19
|
+
const RESOURCE_URI = "ui://skill-config/mcp-app.html";
|
|
20
|
+
/**
|
|
21
|
+
* Get the path to the bundled UI HTML file.
|
|
22
|
+
*/
|
|
23
|
+
function getUIPath() {
|
|
24
|
+
// In production (dist/), the UI is at dist/ui/mcp-app.html
|
|
25
|
+
// In development, check multiple locations
|
|
26
|
+
const possiblePaths = [
|
|
27
|
+
// From dist/skill-config-tool.js
|
|
28
|
+
path.join(import.meta.dirname, "ui", "mcp-app.html"),
|
|
29
|
+
// From src/ during development
|
|
30
|
+
path.join(import.meta.dirname, "..", "dist", "ui", "mcp-app.html"),
|
|
31
|
+
];
|
|
32
|
+
for (const p of possiblePaths) {
|
|
33
|
+
if (fs.existsSync(p)) {
|
|
34
|
+
return p;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
throw new Error(`UI file not found. Tried: ${possiblePaths.join(", ")}. ` +
|
|
38
|
+
"Run 'npm run build:ui' to build the UI.");
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Schema shape for add-directory tool input.
|
|
42
|
+
*/
|
|
43
|
+
const AddDirectoryInputSchema = {
|
|
44
|
+
directory: z.string().describe("Absolute path to the skills directory to add"),
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Schema shape for remove-directory tool input.
|
|
48
|
+
*/
|
|
49
|
+
const RemoveDirectoryInputSchema = {
|
|
50
|
+
directory: z.string().describe("Absolute path to the skills directory to remove"),
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Register skill-config MCP App tools and resource.
|
|
54
|
+
*
|
|
55
|
+
* @param server - The MCP server instance
|
|
56
|
+
* @param skillState - Shared skill state for getting skill counts
|
|
57
|
+
* @param onDirectoriesChanged - Callback when directories are added/removed
|
|
58
|
+
*/
|
|
59
|
+
export function registerSkillConfigTool(server, skillState, onDirectoriesChanged) {
|
|
60
|
+
/**
|
|
61
|
+
* Get directory info with skill counts from skillState.
|
|
62
|
+
*/
|
|
63
|
+
function getDirectoriesWithCounts() {
|
|
64
|
+
const dirs = getAllDirectoriesWithSources();
|
|
65
|
+
// Count skills per directory
|
|
66
|
+
for (const dir of dirs) {
|
|
67
|
+
let count = 0;
|
|
68
|
+
if (isGitHubUrl(dir.path)) {
|
|
69
|
+
// For GitHub directories, match by owner/repo from skill source
|
|
70
|
+
try {
|
|
71
|
+
const spec = parseGitHubUrl(dir.path);
|
|
72
|
+
for (const skill of skillState.skillMap.values()) {
|
|
73
|
+
if (skill.source.type === "github" &&
|
|
74
|
+
skill.source.owner?.toLowerCase() === spec.owner.toLowerCase() &&
|
|
75
|
+
skill.source.repo?.toLowerCase() === spec.repo.toLowerCase()) {
|
|
76
|
+
count++;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Invalid GitHub URL, count stays 0
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// For local directories, match by path prefix
|
|
86
|
+
for (const skill of skillState.skillMap.values()) {
|
|
87
|
+
const skillDir = path.dirname(skill.path);
|
|
88
|
+
if (skillDir.startsWith(dir.path)) {
|
|
89
|
+
count++;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
dir.skillCount = count;
|
|
94
|
+
}
|
|
95
|
+
return dirs;
|
|
96
|
+
}
|
|
97
|
+
// Main config tool - opens UI
|
|
98
|
+
registerAppTool(server, "skill-config", {
|
|
99
|
+
title: "Configure Skills",
|
|
100
|
+
description: "Open the skills directory configuration UI. " +
|
|
101
|
+
"Use when user wants to configure, add, or remove skill directories.",
|
|
102
|
+
inputSchema: {},
|
|
103
|
+
outputSchema: {
|
|
104
|
+
directories: z.array(z.object({
|
|
105
|
+
path: z.string(),
|
|
106
|
+
source: z.string(),
|
|
107
|
+
type: z.string(),
|
|
108
|
+
valid: z.boolean(),
|
|
109
|
+
allowed: z.boolean(),
|
|
110
|
+
skillCount: z.number().optional(),
|
|
111
|
+
})),
|
|
112
|
+
activeSource: z.string(),
|
|
113
|
+
isOverridden: z.boolean(),
|
|
114
|
+
staticMode: z.boolean(),
|
|
115
|
+
allowedOrgs: z.array(z.string()),
|
|
116
|
+
allowedUsers: z.array(z.string()),
|
|
117
|
+
},
|
|
118
|
+
_meta: { ui: { resourceUri: RESOURCE_URI } },
|
|
119
|
+
annotations: {
|
|
120
|
+
readOnlyHint: true,
|
|
121
|
+
destructiveHint: false,
|
|
122
|
+
idempotentHint: true,
|
|
123
|
+
openWorldHint: false,
|
|
124
|
+
},
|
|
125
|
+
}, async () => {
|
|
126
|
+
const configState = getConfigState();
|
|
127
|
+
const directories = getDirectoriesWithCounts();
|
|
128
|
+
return {
|
|
129
|
+
content: [
|
|
130
|
+
{
|
|
131
|
+
type: "text",
|
|
132
|
+
text: "Skills configuration UI opened.",
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
structuredContent: {
|
|
136
|
+
directories,
|
|
137
|
+
activeSource: configState.activeSource,
|
|
138
|
+
isOverridden: configState.isOverridden,
|
|
139
|
+
staticMode: getStaticModeFromConfig(),
|
|
140
|
+
allowedOrgs: getGitHubAllowedOrgs(),
|
|
141
|
+
allowedUsers: getGitHubAllowedUsers(),
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
// Add directory tool (UI-only, hidden from model)
|
|
146
|
+
registerAppTool(server, "skill-config-add-directory", {
|
|
147
|
+
title: "Add Skills Directory",
|
|
148
|
+
description: "Add a skills directory to the configuration.",
|
|
149
|
+
inputSchema: AddDirectoryInputSchema,
|
|
150
|
+
outputSchema: {
|
|
151
|
+
success: z.boolean(),
|
|
152
|
+
directories: z.array(z.object({
|
|
153
|
+
path: z.string(),
|
|
154
|
+
source: z.string(),
|
|
155
|
+
type: z.string(),
|
|
156
|
+
valid: z.boolean(),
|
|
157
|
+
allowed: z.boolean(),
|
|
158
|
+
skillCount: z.number().optional(),
|
|
159
|
+
})).optional(),
|
|
160
|
+
activeSource: z.string().optional(),
|
|
161
|
+
isOverridden: z.boolean().optional(),
|
|
162
|
+
error: z.string().optional(),
|
|
163
|
+
},
|
|
164
|
+
_meta: {
|
|
165
|
+
ui: {
|
|
166
|
+
resourceUri: RESOURCE_URI,
|
|
167
|
+
visibility: ["app"], // Hidden from model, UI can call it
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
annotations: {
|
|
171
|
+
readOnlyHint: false,
|
|
172
|
+
destructiveHint: false,
|
|
173
|
+
idempotentHint: false,
|
|
174
|
+
openWorldHint: false,
|
|
175
|
+
},
|
|
176
|
+
}, async (args) => {
|
|
177
|
+
const { directory } = args;
|
|
178
|
+
try {
|
|
179
|
+
addDirectoryToConfig(directory);
|
|
180
|
+
onDirectoriesChanged();
|
|
181
|
+
const directories = getDirectoriesWithCounts();
|
|
182
|
+
return {
|
|
183
|
+
content: [
|
|
184
|
+
{
|
|
185
|
+
type: "text",
|
|
186
|
+
text: `Added directory: ${directory}`,
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
structuredContent: {
|
|
190
|
+
success: true,
|
|
191
|
+
directories,
|
|
192
|
+
activeSource: getConfigState().activeSource,
|
|
193
|
+
isOverridden: getConfigState().isOverridden,
|
|
194
|
+
staticMode: getStaticModeFromConfig(),
|
|
195
|
+
allowedOrgs: getGitHubAllowedOrgs(),
|
|
196
|
+
allowedUsers: getGitHubAllowedUsers(),
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
202
|
+
return {
|
|
203
|
+
content: [
|
|
204
|
+
{
|
|
205
|
+
type: "text",
|
|
206
|
+
text: `Failed to add directory: ${message}`,
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
structuredContent: {
|
|
210
|
+
success: false,
|
|
211
|
+
error: message,
|
|
212
|
+
},
|
|
213
|
+
isError: true,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
// Remove directory tool (UI-only, hidden from model)
|
|
218
|
+
registerAppTool(server, "skill-config-remove-directory", {
|
|
219
|
+
title: "Remove Skills Directory",
|
|
220
|
+
description: "Remove a skills directory from the configuration.",
|
|
221
|
+
inputSchema: RemoveDirectoryInputSchema,
|
|
222
|
+
outputSchema: {
|
|
223
|
+
success: z.boolean(),
|
|
224
|
+
directories: z.array(z.object({
|
|
225
|
+
path: z.string(),
|
|
226
|
+
source: z.string(),
|
|
227
|
+
type: z.string(),
|
|
228
|
+
valid: z.boolean(),
|
|
229
|
+
allowed: z.boolean(),
|
|
230
|
+
skillCount: z.number().optional(),
|
|
231
|
+
})).optional(),
|
|
232
|
+
activeSource: z.string().optional(),
|
|
233
|
+
isOverridden: z.boolean().optional(),
|
|
234
|
+
error: z.string().optional(),
|
|
235
|
+
},
|
|
236
|
+
_meta: {
|
|
237
|
+
ui: {
|
|
238
|
+
resourceUri: RESOURCE_URI,
|
|
239
|
+
visibility: ["app"], // Hidden from model, UI can call it
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
annotations: {
|
|
243
|
+
readOnlyHint: false,
|
|
244
|
+
destructiveHint: true,
|
|
245
|
+
idempotentHint: true,
|
|
246
|
+
openWorldHint: false,
|
|
247
|
+
},
|
|
248
|
+
}, async (args) => {
|
|
249
|
+
const { directory } = args;
|
|
250
|
+
try {
|
|
251
|
+
removeDirectoryFromConfig(directory);
|
|
252
|
+
onDirectoriesChanged();
|
|
253
|
+
const directories = getDirectoriesWithCounts();
|
|
254
|
+
return {
|
|
255
|
+
content: [
|
|
256
|
+
{
|
|
257
|
+
type: "text",
|
|
258
|
+
text: `Removed directory: ${directory}`,
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
structuredContent: {
|
|
262
|
+
success: true,
|
|
263
|
+
directories,
|
|
264
|
+
activeSource: getConfigState().activeSource,
|
|
265
|
+
isOverridden: getConfigState().isOverridden,
|
|
266
|
+
staticMode: getStaticModeFromConfig(),
|
|
267
|
+
allowedOrgs: getGitHubAllowedOrgs(),
|
|
268
|
+
allowedUsers: getGitHubAllowedUsers(),
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
274
|
+
return {
|
|
275
|
+
content: [
|
|
276
|
+
{
|
|
277
|
+
type: "text",
|
|
278
|
+
text: `Failed to remove directory: ${message}`,
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
structuredContent: {
|
|
282
|
+
success: false,
|
|
283
|
+
error: message,
|
|
284
|
+
},
|
|
285
|
+
isError: true,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
// Add allowed org tool (UI-only)
|
|
290
|
+
registerAppTool(server, "skill-config-add-allowed-org", {
|
|
291
|
+
title: "Add Allowed GitHub Org",
|
|
292
|
+
description: "Add a GitHub organization to the allowed list for skill repos.",
|
|
293
|
+
inputSchema: {
|
|
294
|
+
org: z.string().describe("GitHub organization name to allow"),
|
|
295
|
+
},
|
|
296
|
+
outputSchema: {
|
|
297
|
+
success: z.boolean(),
|
|
298
|
+
allowedOrgs: z.array(z.string()),
|
|
299
|
+
error: z.string().optional(),
|
|
300
|
+
},
|
|
301
|
+
_meta: {
|
|
302
|
+
ui: {
|
|
303
|
+
resourceUri: RESOURCE_URI,
|
|
304
|
+
visibility: ["app"],
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
annotations: {
|
|
308
|
+
readOnlyHint: false,
|
|
309
|
+
destructiveHint: false,
|
|
310
|
+
idempotentHint: true,
|
|
311
|
+
openWorldHint: false,
|
|
312
|
+
},
|
|
313
|
+
}, async (args) => {
|
|
314
|
+
const { org } = args;
|
|
315
|
+
try {
|
|
316
|
+
addGitHubAllowedOrg(org);
|
|
317
|
+
onDirectoriesChanged(); // Trigger GitHub resync
|
|
318
|
+
// Return full state so UI can update directories' allowed status
|
|
319
|
+
const directories = getDirectoriesWithCounts();
|
|
320
|
+
return {
|
|
321
|
+
content: [
|
|
322
|
+
{
|
|
323
|
+
type: "text",
|
|
324
|
+
text: `Added allowed org: ${org}`,
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
structuredContent: {
|
|
328
|
+
success: true,
|
|
329
|
+
directories,
|
|
330
|
+
activeSource: getConfigState().activeSource,
|
|
331
|
+
isOverridden: getConfigState().isOverridden,
|
|
332
|
+
staticMode: getStaticModeFromConfig(),
|
|
333
|
+
allowedOrgs: getGitHubAllowedOrgs(),
|
|
334
|
+
allowedUsers: getGitHubAllowedUsers(),
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
340
|
+
return {
|
|
341
|
+
content: [
|
|
342
|
+
{
|
|
343
|
+
type: "text",
|
|
344
|
+
text: `Failed to add allowed org: ${message}`,
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
structuredContent: {
|
|
348
|
+
success: false,
|
|
349
|
+
allowedOrgs: getGitHubAllowedOrgs(),
|
|
350
|
+
error: message,
|
|
351
|
+
},
|
|
352
|
+
isError: true,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
// Remove allowed org tool (UI-only)
|
|
357
|
+
registerAppTool(server, "skill-config-remove-allowed-org", {
|
|
358
|
+
title: "Remove Allowed GitHub Org",
|
|
359
|
+
description: "Remove a GitHub organization from the allowed list.",
|
|
360
|
+
inputSchema: {
|
|
361
|
+
org: z.string().describe("GitHub organization name to remove"),
|
|
362
|
+
},
|
|
363
|
+
outputSchema: {
|
|
364
|
+
success: z.boolean(),
|
|
365
|
+
allowedOrgs: z.array(z.string()),
|
|
366
|
+
error: z.string().optional(),
|
|
367
|
+
},
|
|
368
|
+
_meta: {
|
|
369
|
+
ui: {
|
|
370
|
+
resourceUri: RESOURCE_URI,
|
|
371
|
+
visibility: ["app"],
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
annotations: {
|
|
375
|
+
readOnlyHint: false,
|
|
376
|
+
destructiveHint: true,
|
|
377
|
+
idempotentHint: true,
|
|
378
|
+
openWorldHint: false,
|
|
379
|
+
},
|
|
380
|
+
}, async (args) => {
|
|
381
|
+
const { org } = args;
|
|
382
|
+
try {
|
|
383
|
+
removeGitHubAllowedOrg(org);
|
|
384
|
+
onDirectoriesChanged(); // Trigger GitHub resync
|
|
385
|
+
// Return full state so UI can update directories' allowed status
|
|
386
|
+
const directories = getDirectoriesWithCounts();
|
|
387
|
+
return {
|
|
388
|
+
content: [
|
|
389
|
+
{
|
|
390
|
+
type: "text",
|
|
391
|
+
text: `Removed allowed org: ${org}`,
|
|
392
|
+
},
|
|
393
|
+
],
|
|
394
|
+
structuredContent: {
|
|
395
|
+
success: true,
|
|
396
|
+
directories,
|
|
397
|
+
activeSource: getConfigState().activeSource,
|
|
398
|
+
isOverridden: getConfigState().isOverridden,
|
|
399
|
+
staticMode: getStaticModeFromConfig(),
|
|
400
|
+
allowedOrgs: getGitHubAllowedOrgs(),
|
|
401
|
+
allowedUsers: getGitHubAllowedUsers(),
|
|
402
|
+
},
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
407
|
+
return {
|
|
408
|
+
content: [
|
|
409
|
+
{
|
|
410
|
+
type: "text",
|
|
411
|
+
text: `Failed to remove allowed org: ${message}`,
|
|
412
|
+
},
|
|
413
|
+
],
|
|
414
|
+
structuredContent: {
|
|
415
|
+
success: false,
|
|
416
|
+
allowedOrgs: getGitHubAllowedOrgs(),
|
|
417
|
+
error: message,
|
|
418
|
+
},
|
|
419
|
+
isError: true,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
// Set static mode tool (UI-only, hidden from model)
|
|
424
|
+
registerAppTool(server, "skill-config-set-static-mode", {
|
|
425
|
+
title: "Set Static Mode",
|
|
426
|
+
description: "Enable or disable static mode (freezes skills list at startup).",
|
|
427
|
+
inputSchema: {
|
|
428
|
+
enabled: z.boolean().describe("Whether to enable static mode"),
|
|
429
|
+
},
|
|
430
|
+
outputSchema: {
|
|
431
|
+
success: z.boolean(),
|
|
432
|
+
staticMode: z.boolean().optional(),
|
|
433
|
+
error: z.string().optional(),
|
|
434
|
+
},
|
|
435
|
+
_meta: {
|
|
436
|
+
ui: {
|
|
437
|
+
resourceUri: RESOURCE_URI,
|
|
438
|
+
visibility: ["app"], // Hidden from model, UI can call it
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
annotations: {
|
|
442
|
+
readOnlyHint: false,
|
|
443
|
+
destructiveHint: false,
|
|
444
|
+
idempotentHint: true,
|
|
445
|
+
openWorldHint: false,
|
|
446
|
+
},
|
|
447
|
+
}, async (args) => {
|
|
448
|
+
const { enabled } = args;
|
|
449
|
+
try {
|
|
450
|
+
setStaticModeInConfig(enabled);
|
|
451
|
+
return {
|
|
452
|
+
content: [
|
|
453
|
+
{
|
|
454
|
+
type: "text",
|
|
455
|
+
text: `Static mode ${enabled ? "enabled" : "disabled"}. Restart server for changes to take effect.`,
|
|
456
|
+
},
|
|
457
|
+
],
|
|
458
|
+
structuredContent: {
|
|
459
|
+
success: true,
|
|
460
|
+
staticMode: enabled,
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
466
|
+
return {
|
|
467
|
+
content: [
|
|
468
|
+
{
|
|
469
|
+
type: "text",
|
|
470
|
+
text: `Failed to set static mode: ${message}`,
|
|
471
|
+
},
|
|
472
|
+
],
|
|
473
|
+
structuredContent: {
|
|
474
|
+
success: false,
|
|
475
|
+
error: message,
|
|
476
|
+
},
|
|
477
|
+
isError: true,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
// Register the HTML UI resource
|
|
482
|
+
registerAppResource(server, RESOURCE_URI, RESOURCE_URI, { mimeType: RESOURCE_MIME_TYPE }, async () => {
|
|
483
|
+
const uiPath = getUIPath();
|
|
484
|
+
const html = await fsPromises.readFile(uiPath, "utf-8");
|
|
485
|
+
return {
|
|
486
|
+
contents: [
|
|
487
|
+
{
|
|
488
|
+
uri: RESOURCE_URI,
|
|
489
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
490
|
+
text: html,
|
|
491
|
+
},
|
|
492
|
+
],
|
|
493
|
+
};
|
|
494
|
+
});
|
|
495
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration management for skill directories.
|
|
3
|
+
*
|
|
4
|
+
* Handles loading/saving skill directory configuration from:
|
|
5
|
+
* 1. CLI args (highest priority)
|
|
6
|
+
* 2. SKILLS_DIR environment variable
|
|
7
|
+
* 3. Config file (~/.skilljack/config.json)
|
|
8
|
+
*
|
|
9
|
+
* Supports both local directories and GitHub repository URLs.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Invocation settings that can be overridden per skill.
|
|
13
|
+
*/
|
|
14
|
+
export interface SkillInvocationOverrides {
|
|
15
|
+
assistant?: boolean;
|
|
16
|
+
user?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Configuration file schema.
|
|
20
|
+
*/
|
|
21
|
+
export interface SkillConfig {
|
|
22
|
+
skillDirectories: string[];
|
|
23
|
+
staticMode?: boolean;
|
|
24
|
+
skillInvocationOverrides?: Record<string, SkillInvocationOverrides>;
|
|
25
|
+
githubAllowedOrgs?: string[];
|
|
26
|
+
githubAllowedUsers?: string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Source of a skill directory configuration.
|
|
30
|
+
*/
|
|
31
|
+
export type DirectorySource = "cli" | "env" | "config";
|
|
32
|
+
/**
|
|
33
|
+
* Type of skill source (local directory or GitHub repo).
|
|
34
|
+
*/
|
|
35
|
+
export type SourceType = "local" | "github";
|
|
36
|
+
/**
|
|
37
|
+
* A skill directory with its source information.
|
|
38
|
+
*/
|
|
39
|
+
export interface DirectoryInfo {
|
|
40
|
+
path: string;
|
|
41
|
+
source: DirectorySource;
|
|
42
|
+
type: SourceType;
|
|
43
|
+
skillCount: number;
|
|
44
|
+
valid: boolean;
|
|
45
|
+
allowed: boolean;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Configuration state tracking active directories and their sources.
|
|
49
|
+
*/
|
|
50
|
+
export interface ConfigState {
|
|
51
|
+
/** All active directories with source info */
|
|
52
|
+
directories: DirectoryInfo[];
|
|
53
|
+
/** Which source is currently providing directories (cli > env > config) */
|
|
54
|
+
activeSource: DirectorySource;
|
|
55
|
+
/** Whether directories are overridden by CLI or env (config file edits won't take effect) */
|
|
56
|
+
isOverridden: boolean;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get the platform-appropriate config directory path.
|
|
60
|
+
* Returns ~/.skilljack on Unix, %USERPROFILE%\.skilljack on Windows.
|
|
61
|
+
*/
|
|
62
|
+
export declare function getConfigDir(): string;
|
|
63
|
+
/**
|
|
64
|
+
* Get the full path to the config file.
|
|
65
|
+
*/
|
|
66
|
+
export declare function getConfigPath(): string;
|
|
67
|
+
/**
|
|
68
|
+
* Load config from the config file.
|
|
69
|
+
* Returns empty config if file doesn't exist.
|
|
70
|
+
*/
|
|
71
|
+
export declare function loadConfigFile(): SkillConfig;
|
|
72
|
+
/**
|
|
73
|
+
* Save config to the config file.
|
|
74
|
+
*/
|
|
75
|
+
export declare function saveConfigFile(config: SkillConfig): void;
|
|
76
|
+
/**
|
|
77
|
+
* Parse CLI arguments for skill directories.
|
|
78
|
+
* Returns resolved absolute paths for local dirs, unchanged for GitHub URLs.
|
|
79
|
+
*/
|
|
80
|
+
export declare function parseCLIArgs(): string[];
|
|
81
|
+
/**
|
|
82
|
+
* Parse SKILLS_DIR environment variable.
|
|
83
|
+
* Returns resolved absolute paths for local dirs, unchanged for GitHub URLs.
|
|
84
|
+
*/
|
|
85
|
+
export declare function parseEnvVar(): string[];
|
|
86
|
+
/**
|
|
87
|
+
* Get all skill directories with their source information.
|
|
88
|
+
* Priority: CLI args > env var > config file
|
|
89
|
+
*/
|
|
90
|
+
export declare function getConfigState(): ConfigState;
|
|
91
|
+
/**
|
|
92
|
+
* Get skill directories from all sources combined.
|
|
93
|
+
* Used for the UI to show all configured directories.
|
|
94
|
+
*/
|
|
95
|
+
export declare function getAllDirectoriesWithSources(): DirectoryInfo[];
|
|
96
|
+
/**
|
|
97
|
+
* Add a directory or GitHub URL to the config file.
|
|
98
|
+
* Does not affect CLI or env var configurations.
|
|
99
|
+
*/
|
|
100
|
+
export declare function addDirectoryToConfig(directory: string): void;
|
|
101
|
+
/**
|
|
102
|
+
* Remove a directory or GitHub URL from the config file.
|
|
103
|
+
* Only removes from config file, not CLI or env var.
|
|
104
|
+
*/
|
|
105
|
+
export declare function removeDirectoryFromConfig(directory: string): void;
|
|
106
|
+
/**
|
|
107
|
+
* Get only the active skill directories (respecting priority).
|
|
108
|
+
* This is what the server should use for skill discovery.
|
|
109
|
+
*/
|
|
110
|
+
export declare function getActiveDirectories(): string[];
|
|
111
|
+
/**
|
|
112
|
+
* Get static mode setting from config file.
|
|
113
|
+
*/
|
|
114
|
+
export declare function getStaticModeFromConfig(): boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Set static mode setting in config file.
|
|
117
|
+
*/
|
|
118
|
+
export declare function setStaticModeInConfig(enabled: boolean): void;
|
|
119
|
+
/**
|
|
120
|
+
* Get all skill invocation overrides from the config file.
|
|
121
|
+
*/
|
|
122
|
+
export declare function getSkillInvocationOverrides(): Record<string, SkillInvocationOverrides>;
|
|
123
|
+
/**
|
|
124
|
+
* Set an invocation override for a skill.
|
|
125
|
+
* @param skillName - The name of the skill
|
|
126
|
+
* @param setting - Which setting to override ("assistant" or "user")
|
|
127
|
+
* @param value - The new value for the setting
|
|
128
|
+
*/
|
|
129
|
+
export declare function setSkillInvocationOverride(skillName: string, setting: "assistant" | "user", value: boolean): void;
|
|
130
|
+
/**
|
|
131
|
+
* Clear an invocation override for a skill (revert to frontmatter default).
|
|
132
|
+
* @param skillName - The name of the skill
|
|
133
|
+
* @param setting - Which setting to clear (omit to clear both)
|
|
134
|
+
*/
|
|
135
|
+
export declare function clearSkillInvocationOverride(skillName: string, setting?: "assistant" | "user"): void;
|
|
136
|
+
/**
|
|
137
|
+
* Get the GitHub allowed orgs from config file.
|
|
138
|
+
*/
|
|
139
|
+
export declare function getGitHubAllowedOrgs(): string[];
|
|
140
|
+
/**
|
|
141
|
+
* Get the GitHub allowed users from config file.
|
|
142
|
+
*/
|
|
143
|
+
export declare function getGitHubAllowedUsers(): string[];
|
|
144
|
+
/**
|
|
145
|
+
* Add a GitHub org to the allowed list.
|
|
146
|
+
* @param org - The org name to allow
|
|
147
|
+
*/
|
|
148
|
+
export declare function addGitHubAllowedOrg(org: string): void;
|
|
149
|
+
/**
|
|
150
|
+
* Remove a GitHub org from the allowed list.
|
|
151
|
+
* @param org - The org name to remove
|
|
152
|
+
*/
|
|
153
|
+
export declare function removeGitHubAllowedOrg(org: string): void;
|
|
154
|
+
/**
|
|
155
|
+
* Add a GitHub user to the allowed list.
|
|
156
|
+
* @param user - The user name to allow
|
|
157
|
+
*/
|
|
158
|
+
export declare function addGitHubAllowedUser(user: string): void;
|
|
159
|
+
/**
|
|
160
|
+
* Remove a GitHub user from the allowed list.
|
|
161
|
+
* @param user - The user name to remove
|
|
162
|
+
*/
|
|
163
|
+
export declare function removeGitHubAllowedUser(user: string): void;
|