@maskweaver/plugin 0.1.11 → 0.2.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/dist/index.js +529 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,18 +1,541 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
function parseSimpleYaml(content) {
|
|
5
|
+
const lines = content.split(`
|
|
6
|
+
`);
|
|
7
|
+
const result = {};
|
|
8
|
+
const stack = [
|
|
9
|
+
{ indent: -2, obj: result }
|
|
10
|
+
];
|
|
11
|
+
let currentArrayKey = null;
|
|
12
|
+
let currentArray = [];
|
|
13
|
+
let multilineKey = null;
|
|
14
|
+
let multilineValue = [];
|
|
15
|
+
let multilineIndent = 0;
|
|
16
|
+
for (let i = 0;i < lines.length; i++) {
|
|
17
|
+
const line = lines[i];
|
|
18
|
+
const trimmed = line.trimStart();
|
|
19
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
20
|
+
if (multilineKey) {
|
|
21
|
+
multilineValue.push("");
|
|
22
|
+
}
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const indent = line.length - trimmed.length;
|
|
26
|
+
if (multilineKey) {
|
|
27
|
+
if (indent > multilineIndent || indent === multilineIndent && !trimmed.includes(":")) {
|
|
28
|
+
multilineValue.push(trimmed);
|
|
29
|
+
continue;
|
|
30
|
+
} else {
|
|
31
|
+
const parent = stack[stack.length - 1];
|
|
32
|
+
parent.obj[multilineKey] = multilineValue.join(`
|
|
33
|
+
`).trim();
|
|
34
|
+
multilineKey = null;
|
|
35
|
+
multilineValue = [];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (trimmed.startsWith("- ")) {
|
|
39
|
+
const value = trimmed.slice(2).trim();
|
|
40
|
+
while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
|
|
41
|
+
const popped = stack.pop();
|
|
42
|
+
if (popped.key && currentArrayKey === popped.key) {
|
|
43
|
+
const parent = stack[stack.length - 1];
|
|
44
|
+
parent.obj[popped.key] = currentArray;
|
|
45
|
+
currentArrayKey = null;
|
|
46
|
+
currentArray = [];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (value.includes(":")) {
|
|
50
|
+
const colonIdx = value.indexOf(":");
|
|
51
|
+
const objKey = value.slice(0, colonIdx).trim();
|
|
52
|
+
const objVal = value.slice(colonIdx + 1).trim();
|
|
53
|
+
const arrayItem = {};
|
|
54
|
+
if (objVal) {
|
|
55
|
+
arrayItem[objKey] = parseValue(objVal);
|
|
56
|
+
}
|
|
57
|
+
let j = i + 1;
|
|
58
|
+
const itemIndent = indent + 2;
|
|
59
|
+
while (j < lines.length) {
|
|
60
|
+
const nextLine = lines[j];
|
|
61
|
+
const nextTrimmed = nextLine.trimStart();
|
|
62
|
+
const nextIndent = nextLine.length - nextTrimmed.length;
|
|
63
|
+
if (!nextTrimmed || nextTrimmed.startsWith("#")) {
|
|
64
|
+
j++;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (nextIndent < itemIndent || nextTrimmed.startsWith("- ")) {
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
if (nextTrimmed.includes(":")) {
|
|
71
|
+
const nColonIdx = nextTrimmed.indexOf(":");
|
|
72
|
+
const nKey = nextTrimmed.slice(0, nColonIdx).trim();
|
|
73
|
+
const nVal = nextTrimmed.slice(nColonIdx + 1).trim();
|
|
74
|
+
if (nVal) {
|
|
75
|
+
arrayItem[nKey] = parseValue(nVal);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
j++;
|
|
79
|
+
}
|
|
80
|
+
i = j - 1;
|
|
81
|
+
currentArray.push(arrayItem);
|
|
82
|
+
} else {
|
|
83
|
+
currentArray.push(parseValue(value));
|
|
84
|
+
}
|
|
85
|
+
if (!currentArrayKey) {
|
|
86
|
+
for (let s = stack.length - 1;s >= 0; s--) {
|
|
87
|
+
if (stack[s].key) {
|
|
88
|
+
currentArrayKey = stack[s].key;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (trimmed.includes(":")) {
|
|
96
|
+
while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
|
|
97
|
+
const popped = stack.pop();
|
|
98
|
+
if (popped.key && currentArrayKey === popped.key) {
|
|
99
|
+
const parent2 = stack[stack.length - 1];
|
|
100
|
+
parent2.obj[popped.key] = currentArray;
|
|
101
|
+
currentArrayKey = null;
|
|
102
|
+
currentArray = [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (currentArrayKey) {
|
|
106
|
+
const parent2 = stack[stack.length - 1];
|
|
107
|
+
parent2.obj[currentArrayKey] = currentArray;
|
|
108
|
+
currentArrayKey = null;
|
|
109
|
+
currentArray = [];
|
|
110
|
+
}
|
|
111
|
+
const colonIdx = trimmed.indexOf(":");
|
|
112
|
+
const key = trimmed.slice(0, colonIdx).trim();
|
|
113
|
+
const rawValue = trimmed.slice(colonIdx + 1);
|
|
114
|
+
const value = rawValue.trim();
|
|
115
|
+
const parent = stack[stack.length - 1];
|
|
116
|
+
if (!value) {
|
|
117
|
+
const nextLine = lines[i + 1];
|
|
118
|
+
if (nextLine && nextLine.trimStart().startsWith("|")) {
|
|
119
|
+
multilineKey = key;
|
|
120
|
+
multilineIndent = indent;
|
|
121
|
+
i++;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const newObj = {};
|
|
125
|
+
parent.obj[key] = newObj;
|
|
126
|
+
stack.push({ indent, obj: newObj, key });
|
|
127
|
+
} else if (value === "|" || value === ">") {
|
|
128
|
+
multilineKey = key;
|
|
129
|
+
multilineIndent = indent;
|
|
130
|
+
} else {
|
|
131
|
+
parent.obj[key] = parseValue(value);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (multilineKey) {
|
|
136
|
+
const parent = stack[stack.length - 1];
|
|
137
|
+
parent.obj[multilineKey] = multilineValue.join(`
|
|
138
|
+
`).trim();
|
|
139
|
+
}
|
|
140
|
+
if (currentArrayKey) {
|
|
141
|
+
const parent = stack[stack.length - 1];
|
|
142
|
+
parent.obj[currentArrayKey] = currentArray;
|
|
143
|
+
}
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
function parseValue(value) {
|
|
147
|
+
if (!value)
|
|
148
|
+
return "";
|
|
149
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
150
|
+
return value.slice(1, -1);
|
|
151
|
+
}
|
|
152
|
+
if (value === "true")
|
|
153
|
+
return true;
|
|
154
|
+
if (value === "false")
|
|
155
|
+
return false;
|
|
156
|
+
if (value === "null" || value === "~")
|
|
157
|
+
return null;
|
|
158
|
+
const num = Number(value);
|
|
159
|
+
if (!isNaN(num) && value !== "")
|
|
160
|
+
return num;
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
class MaskLoader {
|
|
165
|
+
masksDir;
|
|
166
|
+
catalog = null;
|
|
167
|
+
cache = new Map;
|
|
168
|
+
constructor(masksDir) {
|
|
169
|
+
this.masksDir = masksDir;
|
|
170
|
+
}
|
|
171
|
+
async loadCatalog() {
|
|
172
|
+
if (this.catalog) {
|
|
173
|
+
return this.catalog;
|
|
174
|
+
}
|
|
175
|
+
const indexPath = path.join(this.masksDir, "index.json");
|
|
176
|
+
if (!fs.existsSync(indexPath)) {
|
|
177
|
+
throw new Error(`Mask catalog not found: ${indexPath}`);
|
|
178
|
+
}
|
|
179
|
+
const content = fs.readFileSync(indexPath, "utf-8");
|
|
180
|
+
this.catalog = JSON.parse(content);
|
|
181
|
+
return this.catalog;
|
|
182
|
+
}
|
|
183
|
+
async load(maskId) {
|
|
184
|
+
if (this.cache.has(maskId)) {
|
|
185
|
+
return this.cache.get(maskId);
|
|
186
|
+
}
|
|
187
|
+
const catalog = await this.loadCatalog();
|
|
188
|
+
let entry = null;
|
|
189
|
+
let categoryId = null;
|
|
190
|
+
for (const [catId, category] of Object.entries(catalog.categories)) {
|
|
191
|
+
const found = category.masks.find((m) => m.id === maskId);
|
|
192
|
+
if (found) {
|
|
193
|
+
entry = found;
|
|
194
|
+
categoryId = catId;
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (!entry || !categoryId) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
const filePath = path.join(this.masksDir, entry.file);
|
|
202
|
+
if (!fs.existsSync(filePath)) {
|
|
203
|
+
throw new Error(`Mask file not found: ${filePath}`);
|
|
204
|
+
}
|
|
205
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
206
|
+
const parsed = filePath.endsWith(".yaml") || filePath.endsWith(".yml") ? parseSimpleYaml(content) : JSON.parse(content);
|
|
207
|
+
const loadedMask = {
|
|
208
|
+
...parsed,
|
|
209
|
+
category: categoryId,
|
|
210
|
+
filePath
|
|
211
|
+
};
|
|
212
|
+
this.cache.set(maskId, loadedMask);
|
|
213
|
+
return loadedMask;
|
|
214
|
+
}
|
|
215
|
+
async listAll() {
|
|
216
|
+
const catalog = await this.loadCatalog();
|
|
217
|
+
const result = [];
|
|
218
|
+
for (const [categoryId, category] of Object.entries(catalog.categories)) {
|
|
219
|
+
for (const mask of category.masks) {
|
|
220
|
+
result.push({
|
|
221
|
+
...mask,
|
|
222
|
+
category: categoryId
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
async listCategories() {
|
|
229
|
+
const catalog = await this.loadCatalog();
|
|
230
|
+
return Object.entries(catalog.categories).map(([id, cat]) => ({
|
|
231
|
+
id,
|
|
232
|
+
name: cat.name,
|
|
233
|
+
description: cat.description,
|
|
234
|
+
count: cat.masks.length
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function buildRichPrompt(mask) {
|
|
239
|
+
const parts = [];
|
|
240
|
+
parts.push(`You are ${mask.profile.name}.`);
|
|
241
|
+
parts.push(`${mask.profile.tagline}`);
|
|
242
|
+
parts.push("");
|
|
243
|
+
parts.push("BACKGROUND:");
|
|
244
|
+
parts.push(mask.profile.background.trim());
|
|
245
|
+
parts.push("");
|
|
246
|
+
parts.push("YOUR EXPERTISE:");
|
|
247
|
+
for (const exp of mask.profile.expertise) {
|
|
248
|
+
parts.push(`- ${exp}`);
|
|
249
|
+
}
|
|
250
|
+
parts.push("");
|
|
251
|
+
parts.push("YOUR THINKING STYLE:");
|
|
252
|
+
parts.push(mask.profile.thinkingStyle.trim());
|
|
253
|
+
parts.push("");
|
|
254
|
+
parts.push("INSTRUCTIONS:");
|
|
255
|
+
parts.push(mask.behavior.systemPrompt.trim());
|
|
256
|
+
parts.push("");
|
|
257
|
+
const style = mask.behavior.communicationStyle;
|
|
258
|
+
parts.push("COMMUNICATION STYLE:");
|
|
259
|
+
parts.push(`- Tone: ${style.tone}`);
|
|
260
|
+
parts.push(`- Verbosity: ${style.verbosity}`);
|
|
261
|
+
parts.push(`- Technical depth: ${style.technicalDepth}`);
|
|
262
|
+
parts.push("");
|
|
263
|
+
parts.push("YOUR STRENGTHS:");
|
|
264
|
+
for (const strength of mask.profile.strengths) {
|
|
265
|
+
parts.push(`- ${strength}`);
|
|
266
|
+
}
|
|
267
|
+
if (mask.profile.limitations && mask.profile.limitations.length > 0) {
|
|
268
|
+
parts.push("");
|
|
269
|
+
parts.push("ACKNOWLEDGE YOUR LIMITATIONS:");
|
|
270
|
+
for (const limitation of mask.profile.limitations) {
|
|
271
|
+
parts.push(`- ${limitation}`);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (mask.behavior.signaturePhrases && mask.behavior.signaturePhrases.length > 0) {
|
|
275
|
+
parts.push("");
|
|
276
|
+
parts.push("PHRASES YOU MIGHT USE:");
|
|
277
|
+
for (const phrase of mask.behavior.signaturePhrases) {
|
|
278
|
+
parts.push(`- "${phrase}"`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return parts.join(`
|
|
282
|
+
`);
|
|
283
|
+
}
|
|
2
284
|
var MaskweaverPlugin = async (ctx) => {
|
|
3
|
-
ctx
|
|
285
|
+
const { directory, client } = ctx;
|
|
286
|
+
client.app.log({
|
|
4
287
|
service: "maskweaver",
|
|
5
288
|
level: "info",
|
|
6
|
-
message: "Maskweaver plugin loaded v0.
|
|
289
|
+
message: "Maskweaver plugin loaded v0.2.0"
|
|
7
290
|
});
|
|
291
|
+
const state = {
|
|
292
|
+
maskLoader: null,
|
|
293
|
+
activeMask: null,
|
|
294
|
+
masksDir: path.join(directory, ".opencode", "masks")
|
|
295
|
+
};
|
|
296
|
+
if (fs.existsSync(state.masksDir)) {
|
|
297
|
+
state.maskLoader = new MaskLoader(state.masksDir);
|
|
298
|
+
try {
|
|
299
|
+
await state.maskLoader.loadCatalog();
|
|
300
|
+
client.app.log({
|
|
301
|
+
service: "maskweaver",
|
|
302
|
+
level: "info",
|
|
303
|
+
message: `Masks directory found: ${state.masksDir}`
|
|
304
|
+
});
|
|
305
|
+
} catch (e) {
|
|
306
|
+
client.app.log({
|
|
307
|
+
service: "maskweaver",
|
|
308
|
+
level: "warn",
|
|
309
|
+
message: `Failed to load mask catalog: ${e}`
|
|
310
|
+
});
|
|
311
|
+
state.maskLoader = null;
|
|
312
|
+
}
|
|
313
|
+
} else {
|
|
314
|
+
client.app.log({
|
|
315
|
+
service: "maskweaver",
|
|
316
|
+
level: "info",
|
|
317
|
+
message: "No masks directory found, using empty catalog"
|
|
318
|
+
});
|
|
319
|
+
}
|
|
8
320
|
return {
|
|
9
321
|
tools: [
|
|
10
322
|
{
|
|
11
|
-
name: "
|
|
12
|
-
description: "
|
|
13
|
-
parameters: {
|
|
323
|
+
name: "list_masks",
|
|
324
|
+
description: "List all available expert persona masks. Returns mask IDs, names, categories, and tags.",
|
|
325
|
+
parameters: {
|
|
326
|
+
type: "object",
|
|
327
|
+
properties: {
|
|
328
|
+
category: {
|
|
329
|
+
type: "string",
|
|
330
|
+
description: 'Optional: filter by category (e.g., "software-engineering", "ai-ml", "architecture")'
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
required: []
|
|
334
|
+
},
|
|
335
|
+
execute: async (args) => {
|
|
336
|
+
if (!state.maskLoader) {
|
|
337
|
+
return {
|
|
338
|
+
success: false,
|
|
339
|
+
error: "No masks directory found",
|
|
340
|
+
hint: "Create a masks catalog at .opencode/masks/index.json"
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
try {
|
|
344
|
+
const masks = await state.maskLoader.listAll();
|
|
345
|
+
const categories = await state.maskLoader.listCategories();
|
|
346
|
+
let filtered = masks;
|
|
347
|
+
if (args.category) {
|
|
348
|
+
filtered = masks.filter((m) => m.category === args.category);
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
success: true,
|
|
352
|
+
activeMask: state.activeMask?.metadata.id || null,
|
|
353
|
+
categories,
|
|
354
|
+
masks: filtered.map((m) => ({
|
|
355
|
+
id: m.id,
|
|
356
|
+
name: m.name,
|
|
357
|
+
category: m.category,
|
|
358
|
+
tags: m.tags
|
|
359
|
+
})),
|
|
360
|
+
total: filtered.length
|
|
361
|
+
};
|
|
362
|
+
} catch (e) {
|
|
363
|
+
return {
|
|
364
|
+
success: false,
|
|
365
|
+
error: `Failed to list masks: ${e}`
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
name: "select_mask",
|
|
372
|
+
description: "Select and apply an expert persona mask. This changes the AI behavior to match the selected expert.",
|
|
373
|
+
parameters: {
|
|
374
|
+
type: "object",
|
|
375
|
+
properties: {
|
|
376
|
+
maskId: {
|
|
377
|
+
type: "string",
|
|
378
|
+
description: 'The mask ID to select (e.g., "kent-beck", "linus-torvalds", "martin-fowler")'
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
required: ["maskId"]
|
|
382
|
+
},
|
|
383
|
+
execute: async (args) => {
|
|
384
|
+
if (!state.maskLoader) {
|
|
385
|
+
return {
|
|
386
|
+
success: false,
|
|
387
|
+
error: "No masks directory found",
|
|
388
|
+
hint: "Create a masks catalog at .opencode/masks/index.json"
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
const mask = await state.maskLoader.load(args.maskId);
|
|
393
|
+
if (!mask) {
|
|
394
|
+
const available = await state.maskLoader.listAll();
|
|
395
|
+
return {
|
|
396
|
+
success: false,
|
|
397
|
+
error: `Mask not found: ${args.maskId}`,
|
|
398
|
+
availableMasks: available.map((m) => m.id)
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
state.activeMask = mask;
|
|
402
|
+
client.app.log({
|
|
403
|
+
service: "maskweaver",
|
|
404
|
+
level: "info",
|
|
405
|
+
message: `Mask selected: ${mask.profile.name}`
|
|
406
|
+
});
|
|
407
|
+
return {
|
|
408
|
+
success: true,
|
|
409
|
+
mask: {
|
|
410
|
+
id: mask.metadata.id,
|
|
411
|
+
name: mask.profile.name,
|
|
412
|
+
tagline: mask.profile.tagline,
|
|
413
|
+
category: mask.category,
|
|
414
|
+
expertise: mask.profile.expertise
|
|
415
|
+
},
|
|
416
|
+
systemPromptPreview: mask.behavior.systemPrompt.slice(0, 200) + "...",
|
|
417
|
+
message: `Now embodying: ${mask.profile.name} - ${mask.profile.tagline}`
|
|
418
|
+
};
|
|
419
|
+
} catch (e) {
|
|
420
|
+
return {
|
|
421
|
+
success: false,
|
|
422
|
+
error: `Failed to select mask: ${e}`
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
name: "deselect_mask",
|
|
429
|
+
description: "Deselect the current mask and return to default AI behavior.",
|
|
430
|
+
parameters: {
|
|
431
|
+
type: "object",
|
|
432
|
+
properties: {},
|
|
433
|
+
required: []
|
|
434
|
+
},
|
|
435
|
+
execute: async () => {
|
|
436
|
+
const previousMask = state.activeMask;
|
|
437
|
+
state.activeMask = null;
|
|
438
|
+
if (previousMask) {
|
|
439
|
+
client.app.log({
|
|
440
|
+
service: "maskweaver",
|
|
441
|
+
level: "info",
|
|
442
|
+
message: `Mask deselected: ${previousMask.profile.name}`
|
|
443
|
+
});
|
|
444
|
+
return {
|
|
445
|
+
success: true,
|
|
446
|
+
previousMask: previousMask.metadata.id,
|
|
447
|
+
message: "Returned to default behavior"
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
return {
|
|
451
|
+
success: true,
|
|
452
|
+
message: "No mask was active"
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
name: "get_mask_prompt",
|
|
458
|
+
description: "Get the full system prompt for a mask. Useful for understanding what a mask does.",
|
|
459
|
+
parameters: {
|
|
460
|
+
type: "object",
|
|
461
|
+
properties: {
|
|
462
|
+
maskId: {
|
|
463
|
+
type: "string",
|
|
464
|
+
description: "The mask ID to get prompt for. If not provided, uses the active mask."
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
required: []
|
|
468
|
+
},
|
|
469
|
+
execute: async (args) => {
|
|
470
|
+
if (!state.maskLoader) {
|
|
471
|
+
return {
|
|
472
|
+
success: false,
|
|
473
|
+
error: "No masks directory found"
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
const maskId = args.maskId || state.activeMask?.metadata.id;
|
|
477
|
+
if (!maskId) {
|
|
478
|
+
return {
|
|
479
|
+
success: false,
|
|
480
|
+
error: "No mask specified and no active mask"
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
try {
|
|
484
|
+
const mask = await state.maskLoader.load(maskId);
|
|
485
|
+
if (!mask) {
|
|
486
|
+
return {
|
|
487
|
+
success: false,
|
|
488
|
+
error: `Mask not found: ${maskId}`
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
return {
|
|
492
|
+
success: true,
|
|
493
|
+
maskId: mask.metadata.id,
|
|
494
|
+
name: mask.profile.name,
|
|
495
|
+
systemPrompt: buildRichPrompt(mask)
|
|
496
|
+
};
|
|
497
|
+
} catch (e) {
|
|
498
|
+
return {
|
|
499
|
+
success: false,
|
|
500
|
+
error: `Failed to get mask prompt: ${e}`
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
name: "maskweaver_status",
|
|
507
|
+
description: "Get the current Maskweaver status including active mask and available masks count.",
|
|
508
|
+
parameters: {
|
|
509
|
+
type: "object",
|
|
510
|
+
properties: {},
|
|
511
|
+
required: []
|
|
512
|
+
},
|
|
14
513
|
execute: async () => {
|
|
15
|
-
|
|
514
|
+
let masksCount = 0;
|
|
515
|
+
let categoriesCount = 0;
|
|
516
|
+
if (state.maskLoader) {
|
|
517
|
+
try {
|
|
518
|
+
const masks = await state.maskLoader.listAll();
|
|
519
|
+
const categories = await state.maskLoader.listCategories();
|
|
520
|
+
masksCount = masks.length;
|
|
521
|
+
categoriesCount = categories.length;
|
|
522
|
+
} catch (e) {}
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
success: true,
|
|
526
|
+
version: "0.2.0",
|
|
527
|
+
masksDir: state.masksDir,
|
|
528
|
+
masksAvailable: state.maskLoader !== null,
|
|
529
|
+
activeMask: state.activeMask ? {
|
|
530
|
+
id: state.activeMask.metadata.id,
|
|
531
|
+
name: state.activeMask.profile.name,
|
|
532
|
+
category: state.activeMask.category
|
|
533
|
+
} : null,
|
|
534
|
+
stats: {
|
|
535
|
+
masks: masksCount,
|
|
536
|
+
categories: categoriesCount
|
|
537
|
+
}
|
|
538
|
+
};
|
|
16
539
|
}
|
|
17
540
|
}
|
|
18
541
|
]
|