@veolab/discoverylab 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/.claude-plugin/marketplace.json +15 -0
- package/.claude-plugin/plugin.json +12 -0
- package/.mcp.json +6 -0
- package/README.md +214 -0
- package/assets/applab-discovery.jpeg +0 -0
- package/assets/backgrounds/abstract-colorful-gradient-orange-background.jpg +0 -0
- package/assets/backgrounds/blurred-colorful-luxury-gradient-rainbow-abstract.jpg +0 -0
- package/assets/backgrounds/glowing-neon-moving-continuously-looking-bright.jpg +0 -0
- package/assets/backgrounds/glowing-neon-moving-continuously-looking-bright2.jpg +0 -0
- package/assets/backgrounds/macos-big-sur-apple-layers-fluidic-colorful-wwdc-stock-4096x2304-1455.jpg +0 -0
- package/assets/backgrounds/macos-sierra-mountain-peak-sunset-evening-stock-5k-5120x3684-3987.jpg +0 -0
- package/assets/backgrounds/macos-tahoe-26-5120x2880-22674.jpg +0 -0
- package/assets/backgrounds/macos-tahoe-26-5120x2880-22675.jpg +0 -0
- package/assets/backgrounds/view-of-the-sea-from-the-window-of-an-airplane-2024-10-21-11-25-30-utc.jpg +0 -0
- package/assets/cursor/cursor-blue.png +0 -0
- package/assets/icons/android-head_3D.png +0 -0
- package/assets/icons/apple-logo.png +0 -0
- package/assets/icons/apple-logo.svg +4 -0
- package/assets/icons/claude-ai-icon.svg +1 -0
- package/assets/icons/icons8-apple-intelligence-48.png +0 -0
- package/assets/icons/icons8-apple-intelligence-96.png +0 -0
- package/dist/chunk-7IDQLLBW.js +311 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-MN6LCZHZ.js +1320 -0
- package/dist/chunk-PBHUHSC3.js +6002 -0
- package/dist/chunk-QJXXHOV7.js +205 -0
- package/dist/chunk-SSRXIO2V.js +6822 -0
- package/dist/chunk-VY3BLXBW.js +329 -0
- package/dist/chunk-W3WJGYR6.js +354 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +120 -0
- package/dist/db-IWIL65EX.js +33 -0
- package/dist/gridCompositor-ENKLFPWR.js +409 -0
- package/dist/index.d.ts +1648 -0
- package/dist/index.js +869 -0
- package/dist/ocr-UTWC7537.js +21 -0
- package/dist/server-3FBHBA7L.js +15 -0
- package/dist/server-NM5CKDUU.js +13 -0
- package/dist/setup-27CQAX6K.js +17 -0
- package/dist/tools-75BAPCUM.js +177 -0
- package/package.json +84 -0
- package/skills/generate-assets/SKILL.md +44 -0
- package/skills/mobile-test/SKILL.md +33 -0
- package/skills/open-ui/SKILL.md +24 -0
- package/skills/quick-capture/SKILL.md +28 -0
- package/skills/task-hub/SKILL.md +44 -0
- package/skills/web-test/SKILL.md +41 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,869 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
analyzeTools,
|
|
4
|
+
canvasTools,
|
|
5
|
+
captureTools,
|
|
6
|
+
exportTools,
|
|
7
|
+
integrationTools,
|
|
8
|
+
projectTools,
|
|
9
|
+
taskHubTools,
|
|
10
|
+
testingTools,
|
|
11
|
+
uiTools
|
|
12
|
+
} from "./chunk-SSRXIO2V.js";
|
|
13
|
+
import "./chunk-W3WJGYR6.js";
|
|
14
|
+
import {
|
|
15
|
+
setupTools
|
|
16
|
+
} from "./chunk-7IDQLLBW.js";
|
|
17
|
+
import {
|
|
18
|
+
mcpServer
|
|
19
|
+
} from "./chunk-QJXXHOV7.js";
|
|
20
|
+
import {
|
|
21
|
+
startServer,
|
|
22
|
+
stopServer
|
|
23
|
+
} from "./chunk-PBHUHSC3.js";
|
|
24
|
+
import "./chunk-MN6LCZHZ.js";
|
|
25
|
+
import {
|
|
26
|
+
closeDatabase,
|
|
27
|
+
exportDestinations,
|
|
28
|
+
exportRules,
|
|
29
|
+
frames,
|
|
30
|
+
getDatabase,
|
|
31
|
+
projectExports,
|
|
32
|
+
projects,
|
|
33
|
+
settings
|
|
34
|
+
} from "./chunk-VY3BLXBW.js";
|
|
35
|
+
import "./chunk-MLKGABMK.js";
|
|
36
|
+
|
|
37
|
+
// src/core/protocol/types.ts
|
|
38
|
+
function isWSRequest(msg) {
|
|
39
|
+
return typeof msg === "object" && msg !== null && msg.type === "req" && typeof msg.id === "string" && typeof msg.method === "string";
|
|
40
|
+
}
|
|
41
|
+
function isWSResponse(msg) {
|
|
42
|
+
return typeof msg === "object" && msg !== null && msg.type === "res" && typeof msg.id === "string" && typeof msg.ok === "boolean";
|
|
43
|
+
}
|
|
44
|
+
function isWSEvent(msg) {
|
|
45
|
+
return typeof msg === "object" && msg !== null && msg.type === "event" && typeof msg.event === "string";
|
|
46
|
+
}
|
|
47
|
+
function isWSMessage(msg) {
|
|
48
|
+
return isWSRequest(msg) || isWSResponse(msg) || isWSEvent(msg);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/core/protocol/validator.ts
|
|
52
|
+
var schemas = {
|
|
53
|
+
request: {
|
|
54
|
+
type: "object",
|
|
55
|
+
required: ["type", "id", "method"],
|
|
56
|
+
properties: {
|
|
57
|
+
type: { const: "req" },
|
|
58
|
+
id: { type: "string" },
|
|
59
|
+
method: { type: "string" },
|
|
60
|
+
params: { type: "object" }
|
|
61
|
+
},
|
|
62
|
+
additionalProperties: false
|
|
63
|
+
},
|
|
64
|
+
response: {
|
|
65
|
+
type: "object",
|
|
66
|
+
required: ["type", "id", "ok"],
|
|
67
|
+
properties: {
|
|
68
|
+
type: { const: "res" },
|
|
69
|
+
id: { type: "string" },
|
|
70
|
+
ok: { type: "boolean" },
|
|
71
|
+
payload: {},
|
|
72
|
+
error: { type: "string" }
|
|
73
|
+
},
|
|
74
|
+
additionalProperties: false
|
|
75
|
+
},
|
|
76
|
+
event: {
|
|
77
|
+
type: "object",
|
|
78
|
+
required: ["type", "event", "payload"],
|
|
79
|
+
properties: {
|
|
80
|
+
type: { const: "event" },
|
|
81
|
+
event: { type: "string" },
|
|
82
|
+
payload: {},
|
|
83
|
+
seq: { type: "number" },
|
|
84
|
+
timestamp: { type: "number" }
|
|
85
|
+
},
|
|
86
|
+
additionalProperties: false
|
|
87
|
+
},
|
|
88
|
+
// Method-specific param schemas
|
|
89
|
+
methods: {
|
|
90
|
+
"ping": {
|
|
91
|
+
type: "object",
|
|
92
|
+
properties: {},
|
|
93
|
+
additionalProperties: false
|
|
94
|
+
},
|
|
95
|
+
"recorder.start": {
|
|
96
|
+
type: "object",
|
|
97
|
+
required: ["name", "url"],
|
|
98
|
+
properties: {
|
|
99
|
+
name: { type: "string" },
|
|
100
|
+
url: { type: "string" },
|
|
101
|
+
resolution: {
|
|
102
|
+
type: "object",
|
|
103
|
+
properties: {
|
|
104
|
+
width: { type: "number" },
|
|
105
|
+
height: { type: "number" }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
additionalProperties: false
|
|
110
|
+
},
|
|
111
|
+
"recorder.stop": {
|
|
112
|
+
type: "object",
|
|
113
|
+
properties: {},
|
|
114
|
+
additionalProperties: false
|
|
115
|
+
},
|
|
116
|
+
"recorder.status": {
|
|
117
|
+
type: "object",
|
|
118
|
+
properties: {},
|
|
119
|
+
additionalProperties: false
|
|
120
|
+
},
|
|
121
|
+
"liveStream.start": {
|
|
122
|
+
type: "object",
|
|
123
|
+
required: ["platform"],
|
|
124
|
+
properties: {
|
|
125
|
+
platform: { enum: ["ios", "android"] },
|
|
126
|
+
deviceId: { type: "string" },
|
|
127
|
+
interactive: { type: "boolean" }
|
|
128
|
+
},
|
|
129
|
+
additionalProperties: false
|
|
130
|
+
},
|
|
131
|
+
"liveStream.stop": {
|
|
132
|
+
type: "object",
|
|
133
|
+
properties: {},
|
|
134
|
+
additionalProperties: false
|
|
135
|
+
},
|
|
136
|
+
"liveStream.tap": {
|
|
137
|
+
type: "object",
|
|
138
|
+
required: ["x", "y"],
|
|
139
|
+
properties: {
|
|
140
|
+
x: { type: "number" },
|
|
141
|
+
y: { type: "number" }
|
|
142
|
+
},
|
|
143
|
+
additionalProperties: false
|
|
144
|
+
},
|
|
145
|
+
"project.list": {
|
|
146
|
+
type: "object",
|
|
147
|
+
properties: {},
|
|
148
|
+
additionalProperties: false
|
|
149
|
+
},
|
|
150
|
+
"project.get": {
|
|
151
|
+
type: "object",
|
|
152
|
+
required: ["id"],
|
|
153
|
+
properties: {
|
|
154
|
+
id: { type: "string" }
|
|
155
|
+
},
|
|
156
|
+
additionalProperties: false
|
|
157
|
+
},
|
|
158
|
+
"project.create": {
|
|
159
|
+
type: "object",
|
|
160
|
+
required: ["name"],
|
|
161
|
+
properties: {
|
|
162
|
+
name: { type: "string" },
|
|
163
|
+
packageName: { type: "string" }
|
|
164
|
+
},
|
|
165
|
+
additionalProperties: false
|
|
166
|
+
},
|
|
167
|
+
"project.delete": {
|
|
168
|
+
type: "object",
|
|
169
|
+
required: ["id"],
|
|
170
|
+
properties: {
|
|
171
|
+
id: { type: "string" }
|
|
172
|
+
},
|
|
173
|
+
additionalProperties: false
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
function validateSchema(data, schema, path = "") {
|
|
178
|
+
const errors = [];
|
|
179
|
+
if (schema.const !== void 0) {
|
|
180
|
+
if (data !== schema.const) {
|
|
181
|
+
errors.push({
|
|
182
|
+
path: path || "root",
|
|
183
|
+
message: `Expected constant value`,
|
|
184
|
+
expected: String(schema.const),
|
|
185
|
+
received: String(data)
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return errors;
|
|
189
|
+
}
|
|
190
|
+
if (schema.enum !== void 0) {
|
|
191
|
+
if (!schema.enum.includes(data)) {
|
|
192
|
+
errors.push({
|
|
193
|
+
path: path || "root",
|
|
194
|
+
message: `Value must be one of: ${schema.enum.join(", ")}`,
|
|
195
|
+
received: String(data)
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
return errors;
|
|
199
|
+
}
|
|
200
|
+
if (schema.type === "object") {
|
|
201
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
202
|
+
errors.push({
|
|
203
|
+
path: path || "root",
|
|
204
|
+
message: "Expected object",
|
|
205
|
+
expected: "object",
|
|
206
|
+
received: typeof data
|
|
207
|
+
});
|
|
208
|
+
return errors;
|
|
209
|
+
}
|
|
210
|
+
const obj = data;
|
|
211
|
+
if (schema.required) {
|
|
212
|
+
for (const key of schema.required) {
|
|
213
|
+
if (!(key in obj)) {
|
|
214
|
+
errors.push({
|
|
215
|
+
path: `${path}.${key}`,
|
|
216
|
+
message: `Missing required property: ${key}`
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (schema.properties) {
|
|
222
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
223
|
+
if (key in obj) {
|
|
224
|
+
const propErrors = validateSchema(obj[key], propSchema, `${path}.${key}`);
|
|
225
|
+
errors.push(...propErrors);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (schema.additionalProperties === false && schema.properties) {
|
|
230
|
+
for (const key of Object.keys(obj)) {
|
|
231
|
+
if (!(key in schema.properties)) {
|
|
232
|
+
errors.push({
|
|
233
|
+
path: `${path}.${key}`,
|
|
234
|
+
message: `Unexpected property: ${key}`
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
} else if (schema.type === "string") {
|
|
240
|
+
if (typeof data !== "string") {
|
|
241
|
+
errors.push({
|
|
242
|
+
path: path || "root",
|
|
243
|
+
message: "Expected string",
|
|
244
|
+
expected: "string",
|
|
245
|
+
received: typeof data
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
} else if (schema.type === "number") {
|
|
249
|
+
if (typeof data !== "number") {
|
|
250
|
+
errors.push({
|
|
251
|
+
path: path || "root",
|
|
252
|
+
message: "Expected number",
|
|
253
|
+
expected: "number",
|
|
254
|
+
received: typeof data
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
} else if (schema.type === "boolean") {
|
|
258
|
+
if (typeof data !== "boolean") {
|
|
259
|
+
errors.push({
|
|
260
|
+
path: path || "root",
|
|
261
|
+
message: "Expected boolean",
|
|
262
|
+
expected: "boolean",
|
|
263
|
+
received: typeof data
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return errors;
|
|
268
|
+
}
|
|
269
|
+
function validateRequest(msg) {
|
|
270
|
+
const errors = validateSchema(msg, schemas.request);
|
|
271
|
+
return { valid: errors.length === 0, errors };
|
|
272
|
+
}
|
|
273
|
+
function validateMethodParams(method, params) {
|
|
274
|
+
const schema = schemas.methods[method];
|
|
275
|
+
if (!schema) {
|
|
276
|
+
return {
|
|
277
|
+
valid: false,
|
|
278
|
+
errors: [{ path: "method", message: `Unknown method: ${method}` }]
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
const errors = validateSchema(params ?? {}, schema);
|
|
282
|
+
return { valid: errors.length === 0, errors };
|
|
283
|
+
}
|
|
284
|
+
function validateResponse(msg) {
|
|
285
|
+
const errors = validateSchema(msg, schemas.response);
|
|
286
|
+
return { valid: errors.length === 0, errors };
|
|
287
|
+
}
|
|
288
|
+
function validateEvent(msg) {
|
|
289
|
+
const errors = validateSchema(msg, schemas.event);
|
|
290
|
+
return { valid: errors.length === 0, errors };
|
|
291
|
+
}
|
|
292
|
+
function formatValidationErrors(errors) {
|
|
293
|
+
return errors.map((e) => {
|
|
294
|
+
let msg = `${e.path}: ${e.message}`;
|
|
295
|
+
if (e.expected) msg += ` (expected: ${e.expected})`;
|
|
296
|
+
if (e.received) msg += ` (received: ${e.received})`;
|
|
297
|
+
return msg;
|
|
298
|
+
}).join("\n");
|
|
299
|
+
}
|
|
300
|
+
var availableMethods = [
|
|
301
|
+
"ping",
|
|
302
|
+
"recorder.start",
|
|
303
|
+
"recorder.stop",
|
|
304
|
+
"recorder.status",
|
|
305
|
+
"liveStream.start",
|
|
306
|
+
"liveStream.stop",
|
|
307
|
+
"liveStream.tap",
|
|
308
|
+
"project.list",
|
|
309
|
+
"project.get",
|
|
310
|
+
"project.create",
|
|
311
|
+
"project.delete"
|
|
312
|
+
];
|
|
313
|
+
var availableEvents = [
|
|
314
|
+
"action",
|
|
315
|
+
"screenshot",
|
|
316
|
+
"status",
|
|
317
|
+
"stopped",
|
|
318
|
+
"session",
|
|
319
|
+
"liveFrame",
|
|
320
|
+
"error",
|
|
321
|
+
"connected"
|
|
322
|
+
];
|
|
323
|
+
|
|
324
|
+
// src/core/protocol/index.ts
|
|
325
|
+
import { randomUUID } from "crypto";
|
|
326
|
+
function createRequest(method, params) {
|
|
327
|
+
return {
|
|
328
|
+
type: "req",
|
|
329
|
+
id: randomUUID(),
|
|
330
|
+
method,
|
|
331
|
+
params
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function createResponse(id, ok, payloadOrError) {
|
|
335
|
+
if (ok) {
|
|
336
|
+
return {
|
|
337
|
+
type: "res",
|
|
338
|
+
id,
|
|
339
|
+
ok: true,
|
|
340
|
+
payload: payloadOrError
|
|
341
|
+
};
|
|
342
|
+
} else {
|
|
343
|
+
return {
|
|
344
|
+
type: "res",
|
|
345
|
+
id,
|
|
346
|
+
ok: false,
|
|
347
|
+
error: payloadOrError
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
function createEvent(event, payload, seq) {
|
|
352
|
+
return {
|
|
353
|
+
type: "event",
|
|
354
|
+
event,
|
|
355
|
+
payload,
|
|
356
|
+
seq,
|
|
357
|
+
timestamp: Date.now()
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
var eventSequence = 0;
|
|
361
|
+
function nextEventSeq() {
|
|
362
|
+
return ++eventSequence;
|
|
363
|
+
}
|
|
364
|
+
function resetEventSeq() {
|
|
365
|
+
eventSequence = 0;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/core/skills/parser.ts
|
|
369
|
+
import { readFile } from "fs/promises";
|
|
370
|
+
function parseSimpleYaml(yaml) {
|
|
371
|
+
const result = {};
|
|
372
|
+
const lines = yaml.split("\n");
|
|
373
|
+
const stack = [{ obj: result, indent: -1 }];
|
|
374
|
+
for (const line of lines) {
|
|
375
|
+
if (!line.trim() || line.trim().startsWith("#")) continue;
|
|
376
|
+
const match = line.match(/^(\s*)([^:]+):\s*(.*)$/);
|
|
377
|
+
if (!match) continue;
|
|
378
|
+
const [, spaces, key, rawValue] = match;
|
|
379
|
+
const indent = spaces.length;
|
|
380
|
+
const trimmedKey = key.trim();
|
|
381
|
+
const value = rawValue.trim();
|
|
382
|
+
while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
|
|
383
|
+
stack.pop();
|
|
384
|
+
}
|
|
385
|
+
const current = stack[stack.length - 1].obj;
|
|
386
|
+
if (value === "") {
|
|
387
|
+
const nested = {};
|
|
388
|
+
current[trimmedKey] = nested;
|
|
389
|
+
stack.push({ obj: nested, indent });
|
|
390
|
+
} else if (value.startsWith("[") && value.endsWith("]")) {
|
|
391
|
+
const arrayContent = value.slice(1, -1);
|
|
392
|
+
current[trimmedKey] = arrayContent.split(",").map((s) => s.trim()).filter((s) => s.length > 0).map((s) => parseYamlValue(s));
|
|
393
|
+
} else {
|
|
394
|
+
current[trimmedKey] = parseYamlValue(value);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return result;
|
|
398
|
+
}
|
|
399
|
+
function parseYamlValue(value) {
|
|
400
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
401
|
+
return value.slice(1, -1);
|
|
402
|
+
}
|
|
403
|
+
if (value === "true") return true;
|
|
404
|
+
if (value === "false") return false;
|
|
405
|
+
if (value === "null" || value === "~") return null;
|
|
406
|
+
const num = Number(value);
|
|
407
|
+
if (!isNaN(num) && value !== "") return num;
|
|
408
|
+
return value;
|
|
409
|
+
}
|
|
410
|
+
var FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
411
|
+
function parseSkillMd(fileContent, filePath) {
|
|
412
|
+
const match = fileContent.match(FRONTMATTER_REGEX);
|
|
413
|
+
if (!match) {
|
|
414
|
+
throw new Error(`Invalid SKILL.md format: missing YAML frontmatter in ${filePath}`);
|
|
415
|
+
}
|
|
416
|
+
const [, yamlContent, markdownContent] = match;
|
|
417
|
+
let metadata;
|
|
418
|
+
try {
|
|
419
|
+
const parsed = parseSimpleYaml(yamlContent);
|
|
420
|
+
metadata = parsed;
|
|
421
|
+
} catch (error) {
|
|
422
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
423
|
+
throw new Error(`Failed to parse YAML frontmatter in ${filePath}: ${msg}`);
|
|
424
|
+
}
|
|
425
|
+
if (!metadata.name) {
|
|
426
|
+
throw new Error(`Missing required field 'name' in ${filePath}`);
|
|
427
|
+
}
|
|
428
|
+
if (!metadata.description) {
|
|
429
|
+
throw new Error(`Missing required field 'description' in ${filePath}`);
|
|
430
|
+
}
|
|
431
|
+
if (metadata.requires) {
|
|
432
|
+
if (metadata.requires.bins && !Array.isArray(metadata.requires.bins)) {
|
|
433
|
+
metadata.requires.bins = [metadata.requires.bins];
|
|
434
|
+
}
|
|
435
|
+
if (metadata.requires.env && !Array.isArray(metadata.requires.env)) {
|
|
436
|
+
metadata.requires.env = [metadata.requires.env];
|
|
437
|
+
}
|
|
438
|
+
if (metadata.requires.packages && !Array.isArray(metadata.requires.packages)) {
|
|
439
|
+
metadata.requires.packages = [metadata.requires.packages];
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if (metadata.os && !Array.isArray(metadata.os)) {
|
|
443
|
+
metadata.os = [metadata.os];
|
|
444
|
+
}
|
|
445
|
+
if (metadata.tools && !Array.isArray(metadata.tools)) {
|
|
446
|
+
metadata.tools = [metadata.tools];
|
|
447
|
+
}
|
|
448
|
+
if (metadata.tags && !Array.isArray(metadata.tags)) {
|
|
449
|
+
metadata.tags = [metadata.tags];
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
metadata,
|
|
453
|
+
content: markdownContent.trim()
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
async function readSkillMd(filePath) {
|
|
457
|
+
const content = await readFile(filePath, "utf-8");
|
|
458
|
+
return parseSkillMd(content, filePath);
|
|
459
|
+
}
|
|
460
|
+
function generateSkillMd(metadata, content) {
|
|
461
|
+
const yaml = generateYamlFrontmatter(metadata);
|
|
462
|
+
return `---
|
|
463
|
+
${yaml}---
|
|
464
|
+
|
|
465
|
+
${content}`;
|
|
466
|
+
}
|
|
467
|
+
function generateYamlFrontmatter(metadata) {
|
|
468
|
+
const lines = [];
|
|
469
|
+
lines.push(`name: ${metadata.name}`);
|
|
470
|
+
lines.push(`description: "${metadata.description}"`);
|
|
471
|
+
if (metadata.emoji) {
|
|
472
|
+
lines.push(`emoji: "${metadata.emoji}"`);
|
|
473
|
+
}
|
|
474
|
+
if (metadata.version) {
|
|
475
|
+
lines.push(`version: "${metadata.version}"`);
|
|
476
|
+
}
|
|
477
|
+
if (metadata.author) {
|
|
478
|
+
lines.push(`author: "${metadata.author}"`);
|
|
479
|
+
}
|
|
480
|
+
if (metadata.category) {
|
|
481
|
+
lines.push(`category: ${metadata.category}`);
|
|
482
|
+
}
|
|
483
|
+
if (metadata.requires) {
|
|
484
|
+
lines.push("requires:");
|
|
485
|
+
if (metadata.requires.bins?.length) {
|
|
486
|
+
lines.push(` bins: [${metadata.requires.bins.join(", ")}]`);
|
|
487
|
+
}
|
|
488
|
+
if (metadata.requires.env?.length) {
|
|
489
|
+
lines.push(` env: [${metadata.requires.env.join(", ")}]`);
|
|
490
|
+
}
|
|
491
|
+
if (metadata.requires.packages?.length) {
|
|
492
|
+
lines.push(` packages: [${metadata.requires.packages.join(", ")}]`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (metadata.os?.length) {
|
|
496
|
+
lines.push(`os: [${metadata.os.join(", ")}]`);
|
|
497
|
+
}
|
|
498
|
+
if (metadata.always) {
|
|
499
|
+
lines.push("always: true");
|
|
500
|
+
}
|
|
501
|
+
if (metadata.install) {
|
|
502
|
+
lines.push("install:");
|
|
503
|
+
if (metadata.install.brew) {
|
|
504
|
+
lines.push(` brew: ${metadata.install.brew}`);
|
|
505
|
+
}
|
|
506
|
+
if (metadata.install.apt) {
|
|
507
|
+
lines.push(` apt: ${metadata.install.apt}`);
|
|
508
|
+
}
|
|
509
|
+
if (metadata.install.npm) {
|
|
510
|
+
lines.push(` npm: ${metadata.install.npm}`);
|
|
511
|
+
}
|
|
512
|
+
if (metadata.install.manual) {
|
|
513
|
+
lines.push(` manual: "${metadata.install.manual}"`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (metadata.tools?.length) {
|
|
517
|
+
lines.push(`tools: [${metadata.tools.join(", ")}]`);
|
|
518
|
+
}
|
|
519
|
+
if (metadata.tags?.length) {
|
|
520
|
+
lines.push(`tags: [${metadata.tags.join(", ")}]`);
|
|
521
|
+
}
|
|
522
|
+
return lines.join("\n") + "\n";
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// src/core/skills/gating.ts
|
|
526
|
+
import { execSync } from "child_process";
|
|
527
|
+
import { platform } from "os";
|
|
528
|
+
function checkBinary(name) {
|
|
529
|
+
try {
|
|
530
|
+
const cmd = platform() === "win32" ? `where ${name}` : `which ${name}`;
|
|
531
|
+
execSync(cmd, { stdio: "ignore" });
|
|
532
|
+
return true;
|
|
533
|
+
} catch {
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
function checkBinaries(bins) {
|
|
538
|
+
return bins.filter((bin) => !checkBinary(bin));
|
|
539
|
+
}
|
|
540
|
+
function checkEnvVar(name) {
|
|
541
|
+
const value = process.env[name];
|
|
542
|
+
return value !== void 0 && value !== "";
|
|
543
|
+
}
|
|
544
|
+
function checkEnvVars(envs) {
|
|
545
|
+
return envs.filter((env) => !checkEnvVar(env));
|
|
546
|
+
}
|
|
547
|
+
function getCurrentOS() {
|
|
548
|
+
const os = platform();
|
|
549
|
+
if (os === "darwin" || os === "linux" || os === "win32") {
|
|
550
|
+
return os;
|
|
551
|
+
}
|
|
552
|
+
return "linux";
|
|
553
|
+
}
|
|
554
|
+
function checkOS(supportedOS) {
|
|
555
|
+
const currentOS = getCurrentOS();
|
|
556
|
+
return supportedOS.includes(currentOS);
|
|
557
|
+
}
|
|
558
|
+
function checkSkillRequirements(metadata) {
|
|
559
|
+
if (metadata.always) {
|
|
560
|
+
return {
|
|
561
|
+
satisfied: true,
|
|
562
|
+
missingBins: [],
|
|
563
|
+
missingEnv: [],
|
|
564
|
+
osUnsupported: false,
|
|
565
|
+
summary: "Skill is always available"
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
const missingBins = metadata.requires?.bins ? checkBinaries(metadata.requires.bins) : [];
|
|
569
|
+
const missingEnv = metadata.requires?.env ? checkEnvVars(metadata.requires.env) : [];
|
|
570
|
+
const osUnsupported = metadata.os ? !checkOS(metadata.os) : false;
|
|
571
|
+
const satisfied = missingBins.length === 0 && missingEnv.length === 0 && !osUnsupported;
|
|
572
|
+
const reasons = [];
|
|
573
|
+
if (missingBins.length > 0) {
|
|
574
|
+
reasons.push(`Missing binaries: ${missingBins.join(", ")}`);
|
|
575
|
+
}
|
|
576
|
+
if (missingEnv.length > 0) {
|
|
577
|
+
reasons.push(`Missing env vars: ${missingEnv.join(", ")}`);
|
|
578
|
+
}
|
|
579
|
+
if (osUnsupported) {
|
|
580
|
+
const currentOS = getCurrentOS();
|
|
581
|
+
reasons.push(`OS '${currentOS}' not supported (requires: ${metadata.os?.join(", ")})`);
|
|
582
|
+
}
|
|
583
|
+
const summary = satisfied ? "All requirements satisfied" : reasons.join("; ");
|
|
584
|
+
return {
|
|
585
|
+
satisfied,
|
|
586
|
+
missingBins,
|
|
587
|
+
missingEnv,
|
|
588
|
+
osUnsupported,
|
|
589
|
+
summary
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
function getInstallInstructions(metadata, result) {
|
|
593
|
+
const instructions = [];
|
|
594
|
+
if (result.missingBins.length > 0 && metadata.install) {
|
|
595
|
+
const os = getCurrentOS();
|
|
596
|
+
if (os === "darwin" && metadata.install.brew) {
|
|
597
|
+
instructions.push(`brew install ${metadata.install.brew}`);
|
|
598
|
+
} else if (os === "linux" && metadata.install.apt) {
|
|
599
|
+
instructions.push(`apt install ${metadata.install.apt}`);
|
|
600
|
+
} else if (metadata.install.npm) {
|
|
601
|
+
instructions.push(`npm install -g ${metadata.install.npm}`);
|
|
602
|
+
} else if (metadata.install.manual) {
|
|
603
|
+
instructions.push(metadata.install.manual);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
if (result.missingEnv.length > 0) {
|
|
607
|
+
for (const env of result.missingEnv) {
|
|
608
|
+
instructions.push(`Set environment variable: export ${env}=<value>`);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return instructions;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// src/core/skills/loader.ts
|
|
615
|
+
import { readdir, access } from "fs/promises";
|
|
616
|
+
import { join, dirname } from "path";
|
|
617
|
+
import { homedir } from "os";
|
|
618
|
+
import { fileURLToPath } from "url";
|
|
619
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
620
|
+
var BUNDLED_SKILLS_DIR = join(__dirname, "../../mcp/tools");
|
|
621
|
+
var USER_SKILLS_DIR = join(homedir(), ".discoverylab", "skills");
|
|
622
|
+
var WORKSPACE_SKILLS_DIR = join(process.cwd(), ".discoverylab", "skills");
|
|
623
|
+
async function pathExists(path) {
|
|
624
|
+
try {
|
|
625
|
+
await access(path);
|
|
626
|
+
return true;
|
|
627
|
+
} catch {
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
async function findSkillFiles(baseDir) {
|
|
632
|
+
const skillFiles = [];
|
|
633
|
+
if (!await pathExists(baseDir)) {
|
|
634
|
+
return skillFiles;
|
|
635
|
+
}
|
|
636
|
+
try {
|
|
637
|
+
const entries = await readdir(baseDir, { withFileTypes: true });
|
|
638
|
+
for (const entry of entries) {
|
|
639
|
+
if (entry.isDirectory()) {
|
|
640
|
+
const skillPath = join(baseDir, entry.name, "SKILL.md");
|
|
641
|
+
if (await pathExists(skillPath)) {
|
|
642
|
+
skillFiles.push(skillPath);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
} catch {
|
|
647
|
+
}
|
|
648
|
+
return skillFiles;
|
|
649
|
+
}
|
|
650
|
+
async function discoverSkillFiles() {
|
|
651
|
+
const [bundled, user, workspace] = await Promise.all([
|
|
652
|
+
findSkillFiles(BUNDLED_SKILLS_DIR),
|
|
653
|
+
findSkillFiles(USER_SKILLS_DIR),
|
|
654
|
+
findSkillFiles(WORKSPACE_SKILLS_DIR)
|
|
655
|
+
]);
|
|
656
|
+
return { bundled, user, workspace };
|
|
657
|
+
}
|
|
658
|
+
async function loadSkill(skillPath) {
|
|
659
|
+
const { metadata, content } = await readSkillMd(skillPath);
|
|
660
|
+
const gatingResult = checkSkillRequirements(metadata);
|
|
661
|
+
const skill = {
|
|
662
|
+
metadata,
|
|
663
|
+
content,
|
|
664
|
+
path: skillPath,
|
|
665
|
+
available: gatingResult.satisfied
|
|
666
|
+
};
|
|
667
|
+
if (!gatingResult.satisfied) {
|
|
668
|
+
skill.unavailableReasons = [];
|
|
669
|
+
if (gatingResult.missingBins.length > 0) {
|
|
670
|
+
skill.unavailableReasons.push(`Missing binaries: ${gatingResult.missingBins.join(", ")}`);
|
|
671
|
+
}
|
|
672
|
+
if (gatingResult.missingEnv.length > 0) {
|
|
673
|
+
skill.unavailableReasons.push(`Missing env vars: ${gatingResult.missingEnv.join(", ")}`);
|
|
674
|
+
}
|
|
675
|
+
if (gatingResult.osUnsupported) {
|
|
676
|
+
skill.unavailableReasons.push(gatingResult.summary);
|
|
677
|
+
}
|
|
678
|
+
const instructions = getInstallInstructions(metadata, gatingResult);
|
|
679
|
+
if (instructions.length > 0) {
|
|
680
|
+
skill.unavailableReasons.push(`Install: ${instructions.join(" && ")}`);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return skill;
|
|
684
|
+
}
|
|
685
|
+
async function loadSkills() {
|
|
686
|
+
const result = {
|
|
687
|
+
loaded: [],
|
|
688
|
+
failed: [],
|
|
689
|
+
unavailable: []
|
|
690
|
+
};
|
|
691
|
+
const { bundled, user, workspace } = await discoverSkillFiles();
|
|
692
|
+
const allPaths = [...bundled, ...user, ...workspace];
|
|
693
|
+
const skillsByName = /* @__PURE__ */ new Map();
|
|
694
|
+
for (const path of allPaths) {
|
|
695
|
+
try {
|
|
696
|
+
const skill = await loadSkill(path);
|
|
697
|
+
skillsByName.set(skill.metadata.name, skill);
|
|
698
|
+
} catch (error) {
|
|
699
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
700
|
+
result.failed.push({ path, error: msg });
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
for (const skill of skillsByName.values()) {
|
|
704
|
+
if (skill.available) {
|
|
705
|
+
result.loaded.push(skill);
|
|
706
|
+
} else {
|
|
707
|
+
result.unavailable.push(skill);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return result;
|
|
711
|
+
}
|
|
712
|
+
function createSkillRegistry() {
|
|
713
|
+
const skills = /* @__PURE__ */ new Map();
|
|
714
|
+
return {
|
|
715
|
+
skills,
|
|
716
|
+
getAvailable() {
|
|
717
|
+
return Array.from(skills.values()).filter((s) => s.available);
|
|
718
|
+
},
|
|
719
|
+
get(name) {
|
|
720
|
+
return skills.get(name);
|
|
721
|
+
},
|
|
722
|
+
isAvailable(name) {
|
|
723
|
+
const skill = skills.get(name);
|
|
724
|
+
return skill?.available ?? false;
|
|
725
|
+
},
|
|
726
|
+
async reload() {
|
|
727
|
+
skills.clear();
|
|
728
|
+
const result = await loadSkills();
|
|
729
|
+
for (const skill of [...result.loaded, ...result.unavailable]) {
|
|
730
|
+
skills.set(skill.metadata.name, skill);
|
|
731
|
+
}
|
|
732
|
+
return result;
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
var globalRegistry = null;
|
|
737
|
+
async function getSkillRegistry() {
|
|
738
|
+
if (!globalRegistry) {
|
|
739
|
+
globalRegistry = createSkillRegistry();
|
|
740
|
+
await globalRegistry.reload();
|
|
741
|
+
}
|
|
742
|
+
return globalRegistry;
|
|
743
|
+
}
|
|
744
|
+
async function reloadSkillRegistry() {
|
|
745
|
+
if (!globalRegistry) {
|
|
746
|
+
globalRegistry = createSkillRegistry();
|
|
747
|
+
}
|
|
748
|
+
return globalRegistry.reload();
|
|
749
|
+
}
|
|
750
|
+
function formatSkillInfo(skill) {
|
|
751
|
+
const lines = [];
|
|
752
|
+
const statusIcon = skill.available ? "\u2713" : "\u2717";
|
|
753
|
+
const emoji = skill.metadata.emoji || "\u{1F4E6}";
|
|
754
|
+
lines.push(`${emoji} ${skill.metadata.name} [${statusIcon}]`);
|
|
755
|
+
lines.push(` ${skill.metadata.description}`);
|
|
756
|
+
if (skill.metadata.version) {
|
|
757
|
+
lines.push(` Version: ${skill.metadata.version}`);
|
|
758
|
+
}
|
|
759
|
+
if (skill.metadata.requires) {
|
|
760
|
+
if (skill.metadata.requires.bins?.length) {
|
|
761
|
+
lines.push(` Requires: ${skill.metadata.requires.bins.join(", ")}`);
|
|
762
|
+
}
|
|
763
|
+
if (skill.metadata.requires.env?.length) {
|
|
764
|
+
lines.push(` Env vars: ${skill.metadata.requires.env.join(", ")}`);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
if (!skill.available && skill.unavailableReasons?.length) {
|
|
768
|
+
lines.push(` \u26A0 ${skill.unavailableReasons.join("\n \u26A0 ")}`);
|
|
769
|
+
}
|
|
770
|
+
return lines.join("\n");
|
|
771
|
+
}
|
|
772
|
+
function formatSkillList(skills) {
|
|
773
|
+
if (skills.length === 0) {
|
|
774
|
+
return "No skills found";
|
|
775
|
+
}
|
|
776
|
+
const available = skills.filter((s) => s.available);
|
|
777
|
+
const unavailable = skills.filter((s) => !s.available);
|
|
778
|
+
const lines = [];
|
|
779
|
+
if (available.length > 0) {
|
|
780
|
+
lines.push("Available Skills:");
|
|
781
|
+
for (const skill of available) {
|
|
782
|
+
const emoji = skill.metadata.emoji || "\u{1F4E6}";
|
|
783
|
+
lines.push(` ${emoji} ${skill.metadata.name} - ${skill.metadata.description}`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
if (unavailable.length > 0) {
|
|
787
|
+
if (lines.length > 0) lines.push("");
|
|
788
|
+
lines.push("Unavailable Skills (missing dependencies):");
|
|
789
|
+
for (const skill of unavailable) {
|
|
790
|
+
const emoji = skill.metadata.emoji || "\u{1F4E6}";
|
|
791
|
+
lines.push(` ${emoji} ${skill.metadata.name} - ${skill.metadata.description}`);
|
|
792
|
+
if (skill.unavailableReasons?.length) {
|
|
793
|
+
lines.push(` \u26A0 ${skill.unavailableReasons[0]}`);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
return lines.join("\n");
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// src/index.ts
|
|
801
|
+
async function main() {
|
|
802
|
+
try {
|
|
803
|
+
getDatabase();
|
|
804
|
+
mcpServer.registerTools([
|
|
805
|
+
...uiTools,
|
|
806
|
+
...projectTools,
|
|
807
|
+
...setupTools,
|
|
808
|
+
...captureTools,
|
|
809
|
+
...analyzeTools,
|
|
810
|
+
...canvasTools,
|
|
811
|
+
...exportTools,
|
|
812
|
+
...testingTools,
|
|
813
|
+
...integrationTools,
|
|
814
|
+
...taskHubTools
|
|
815
|
+
]);
|
|
816
|
+
await mcpServer.runStdio();
|
|
817
|
+
} catch (error) {
|
|
818
|
+
console.error("Failed to start MCP server:", error);
|
|
819
|
+
process.exit(1);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
main();
|
|
823
|
+
export {
|
|
824
|
+
availableEvents,
|
|
825
|
+
availableMethods,
|
|
826
|
+
checkBinaries,
|
|
827
|
+
checkBinary,
|
|
828
|
+
checkEnvVar,
|
|
829
|
+
checkEnvVars,
|
|
830
|
+
checkOS,
|
|
831
|
+
checkSkillRequirements,
|
|
832
|
+
closeDatabase,
|
|
833
|
+
createEvent,
|
|
834
|
+
createRequest,
|
|
835
|
+
createResponse,
|
|
836
|
+
createSkillRegistry,
|
|
837
|
+
exportDestinations,
|
|
838
|
+
exportRules,
|
|
839
|
+
formatSkillInfo,
|
|
840
|
+
formatSkillList,
|
|
841
|
+
formatValidationErrors,
|
|
842
|
+
frames,
|
|
843
|
+
generateSkillMd,
|
|
844
|
+
getCurrentOS,
|
|
845
|
+
getDatabase,
|
|
846
|
+
getInstallInstructions,
|
|
847
|
+
getSkillRegistry,
|
|
848
|
+
isWSEvent,
|
|
849
|
+
isWSMessage,
|
|
850
|
+
isWSRequest,
|
|
851
|
+
isWSResponse,
|
|
852
|
+
loadSkills,
|
|
853
|
+
mcpServer,
|
|
854
|
+
nextEventSeq,
|
|
855
|
+
parseSkillMd,
|
|
856
|
+
projectExports,
|
|
857
|
+
projects,
|
|
858
|
+
readSkillMd,
|
|
859
|
+
reloadSkillRegistry,
|
|
860
|
+
resetEventSeq,
|
|
861
|
+
schemas,
|
|
862
|
+
settings,
|
|
863
|
+
startServer,
|
|
864
|
+
stopServer,
|
|
865
|
+
validateEvent,
|
|
866
|
+
validateMethodParams,
|
|
867
|
+
validateRequest,
|
|
868
|
+
validateResponse
|
|
869
|
+
};
|