@maskweaver/plugin 0.2.0 → 0.3.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 +133 -237
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2
3
|
import * as fs from "node:fs";
|
|
3
4
|
import * as path from "node:path";
|
|
4
5
|
function parseSimpleYaml(content) {
|
|
@@ -17,9 +18,8 @@ function parseSimpleYaml(content) {
|
|
|
17
18
|
const line = lines[i];
|
|
18
19
|
const trimmed = line.trimStart();
|
|
19
20
|
if (!trimmed || trimmed.startsWith("#")) {
|
|
20
|
-
if (multilineKey)
|
|
21
|
+
if (multilineKey)
|
|
21
22
|
multilineValue.push("");
|
|
22
|
-
}
|
|
23
23
|
continue;
|
|
24
24
|
}
|
|
25
25
|
const indent = line.length - trimmed.length;
|
|
@@ -51,9 +51,8 @@ function parseSimpleYaml(content) {
|
|
|
51
51
|
const objKey = value.slice(0, colonIdx).trim();
|
|
52
52
|
const objVal = value.slice(colonIdx + 1).trim();
|
|
53
53
|
const arrayItem = {};
|
|
54
|
-
if (objVal)
|
|
54
|
+
if (objVal)
|
|
55
55
|
arrayItem[objKey] = parseValue(objVal);
|
|
56
|
-
}
|
|
57
56
|
let j = i + 1;
|
|
58
57
|
const itemIndent = indent + 2;
|
|
59
58
|
while (j < lines.length) {
|
|
@@ -64,16 +63,14 @@ function parseSimpleYaml(content) {
|
|
|
64
63
|
j++;
|
|
65
64
|
continue;
|
|
66
65
|
}
|
|
67
|
-
if (nextIndent < itemIndent || nextTrimmed.startsWith("- "))
|
|
66
|
+
if (nextIndent < itemIndent || nextTrimmed.startsWith("- "))
|
|
68
67
|
break;
|
|
69
|
-
}
|
|
70
68
|
if (nextTrimmed.includes(":")) {
|
|
71
69
|
const nColonIdx = nextTrimmed.indexOf(":");
|
|
72
70
|
const nKey = nextTrimmed.slice(0, nColonIdx).trim();
|
|
73
71
|
const nVal = nextTrimmed.slice(nColonIdx + 1).trim();
|
|
74
|
-
if (nVal)
|
|
72
|
+
if (nVal)
|
|
75
73
|
arrayItem[nKey] = parseValue(nVal);
|
|
76
|
-
}
|
|
77
74
|
}
|
|
78
75
|
j++;
|
|
79
76
|
}
|
|
@@ -110,8 +107,7 @@ function parseSimpleYaml(content) {
|
|
|
110
107
|
}
|
|
111
108
|
const colonIdx = trimmed.indexOf(":");
|
|
112
109
|
const key = trimmed.slice(0, colonIdx).trim();
|
|
113
|
-
const
|
|
114
|
-
const value = rawValue.trim();
|
|
110
|
+
const value = trimmed.slice(colonIdx + 1).trim();
|
|
115
111
|
const parent = stack[stack.length - 1];
|
|
116
112
|
if (!value) {
|
|
117
113
|
const nextLine = lines[i + 1];
|
|
@@ -169,21 +165,18 @@ class MaskLoader {
|
|
|
169
165
|
this.masksDir = masksDir;
|
|
170
166
|
}
|
|
171
167
|
async loadCatalog() {
|
|
172
|
-
if (this.catalog)
|
|
168
|
+
if (this.catalog)
|
|
173
169
|
return this.catalog;
|
|
174
|
-
}
|
|
175
170
|
const indexPath = path.join(this.masksDir, "index.json");
|
|
176
|
-
if (!fs.existsSync(indexPath))
|
|
171
|
+
if (!fs.existsSync(indexPath))
|
|
177
172
|
throw new Error(`Mask catalog not found: ${indexPath}`);
|
|
178
|
-
}
|
|
179
173
|
const content = fs.readFileSync(indexPath, "utf-8");
|
|
180
174
|
this.catalog = JSON.parse(content);
|
|
181
175
|
return this.catalog;
|
|
182
176
|
}
|
|
183
177
|
async load(maskId) {
|
|
184
|
-
if (this.cache.has(maskId))
|
|
178
|
+
if (this.cache.has(maskId))
|
|
185
179
|
return this.cache.get(maskId);
|
|
186
|
-
}
|
|
187
180
|
const catalog = await this.loadCatalog();
|
|
188
181
|
let entry = null;
|
|
189
182
|
let categoryId = null;
|
|
@@ -195,20 +188,14 @@ class MaskLoader {
|
|
|
195
188
|
break;
|
|
196
189
|
}
|
|
197
190
|
}
|
|
198
|
-
if (!entry || !categoryId)
|
|
191
|
+
if (!entry || !categoryId)
|
|
199
192
|
return null;
|
|
200
|
-
}
|
|
201
193
|
const filePath = path.join(this.masksDir, entry.file);
|
|
202
|
-
if (!fs.existsSync(filePath))
|
|
194
|
+
if (!fs.existsSync(filePath))
|
|
203
195
|
throw new Error(`Mask file not found: ${filePath}`);
|
|
204
|
-
}
|
|
205
196
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
206
197
|
const parsed = filePath.endsWith(".yaml") || filePath.endsWith(".yml") ? parseSimpleYaml(content) : JSON.parse(content);
|
|
207
|
-
const loadedMask = {
|
|
208
|
-
...parsed,
|
|
209
|
-
category: categoryId,
|
|
210
|
-
filePath
|
|
211
|
-
};
|
|
198
|
+
const loadedMask = { ...parsed, category: categoryId, filePath };
|
|
212
199
|
this.cache.set(maskId, loadedMask);
|
|
213
200
|
return loadedMask;
|
|
214
201
|
}
|
|
@@ -217,10 +204,7 @@ class MaskLoader {
|
|
|
217
204
|
const result = [];
|
|
218
205
|
for (const [categoryId, category] of Object.entries(catalog.categories)) {
|
|
219
206
|
for (const mask of category.masks) {
|
|
220
|
-
result.push({
|
|
221
|
-
...mask,
|
|
222
|
-
category: categoryId
|
|
223
|
-
});
|
|
207
|
+
result.push({ ...mask, category: categoryId });
|
|
224
208
|
}
|
|
225
209
|
}
|
|
226
210
|
return result;
|
|
@@ -244,9 +228,8 @@ function buildRichPrompt(mask) {
|
|
|
244
228
|
parts.push(mask.profile.background.trim());
|
|
245
229
|
parts.push("");
|
|
246
230
|
parts.push("YOUR EXPERTISE:");
|
|
247
|
-
for (const exp of mask.profile.expertise)
|
|
231
|
+
for (const exp of mask.profile.expertise)
|
|
248
232
|
parts.push(`- ${exp}`);
|
|
249
|
-
}
|
|
250
233
|
parts.push("");
|
|
251
234
|
parts.push("YOUR THINKING STYLE:");
|
|
252
235
|
parts.push(mask.profile.thinkingStyle.trim());
|
|
@@ -261,84 +244,74 @@ function buildRichPrompt(mask) {
|
|
|
261
244
|
parts.push(`- Technical depth: ${style.technicalDepth}`);
|
|
262
245
|
parts.push("");
|
|
263
246
|
parts.push("YOUR STRENGTHS:");
|
|
264
|
-
for (const strength of mask.profile.strengths)
|
|
247
|
+
for (const strength of mask.profile.strengths)
|
|
265
248
|
parts.push(`- ${strength}`);
|
|
266
|
-
|
|
267
|
-
if (mask.profile.limitations && mask.profile.limitations.length > 0) {
|
|
249
|
+
if (mask.profile.limitations?.length) {
|
|
268
250
|
parts.push("");
|
|
269
251
|
parts.push("ACKNOWLEDGE YOUR LIMITATIONS:");
|
|
270
|
-
for (const limitation of mask.profile.limitations)
|
|
252
|
+
for (const limitation of mask.profile.limitations)
|
|
271
253
|
parts.push(`- ${limitation}`);
|
|
272
|
-
}
|
|
273
254
|
}
|
|
274
|
-
if (mask.behavior.signaturePhrases
|
|
255
|
+
if (mask.behavior.signaturePhrases?.length) {
|
|
275
256
|
parts.push("");
|
|
276
257
|
parts.push("PHRASES YOU MIGHT USE:");
|
|
277
|
-
for (const phrase of mask.behavior.signaturePhrases)
|
|
258
|
+
for (const phrase of mask.behavior.signaturePhrases)
|
|
278
259
|
parts.push(`- "${phrase}"`);
|
|
279
|
-
}
|
|
280
260
|
}
|
|
281
261
|
return parts.join(`
|
|
282
262
|
`);
|
|
283
263
|
}
|
|
284
|
-
var
|
|
285
|
-
|
|
264
|
+
var state = null;
|
|
265
|
+
var MaskweaverPlugin = async ({ client, directory }) => {
|
|
266
|
+
const masksDir = path.join(directory, ".opencode", "masks");
|
|
267
|
+
state = {
|
|
268
|
+
maskLoader: null,
|
|
269
|
+
activeMask: null,
|
|
270
|
+
masksDir
|
|
271
|
+
};
|
|
286
272
|
client.app.log({
|
|
287
273
|
service: "maskweaver",
|
|
288
274
|
level: "info",
|
|
289
|
-
message: "Maskweaver plugin loaded v0.
|
|
275
|
+
message: "Maskweaver plugin loaded v0.3.0"
|
|
290
276
|
});
|
|
291
|
-
|
|
292
|
-
maskLoader
|
|
293
|
-
activeMask: null,
|
|
294
|
-
masksDir: path.join(directory, ".opencode", "masks")
|
|
295
|
-
};
|
|
296
|
-
if (fs.existsSync(state.masksDir)) {
|
|
297
|
-
state.maskLoader = new MaskLoader(state.masksDir);
|
|
277
|
+
if (fs.existsSync(masksDir)) {
|
|
278
|
+
state.maskLoader = new MaskLoader(masksDir);
|
|
298
279
|
try {
|
|
299
280
|
await state.maskLoader.loadCatalog();
|
|
300
281
|
client.app.log({
|
|
301
282
|
service: "maskweaver",
|
|
302
283
|
level: "info",
|
|
303
|
-
message: `Masks
|
|
284
|
+
message: `Masks found at: ${masksDir}`
|
|
304
285
|
});
|
|
305
286
|
} catch (e) {
|
|
306
287
|
client.app.log({
|
|
307
288
|
service: "maskweaver",
|
|
308
289
|
level: "warn",
|
|
309
|
-
message: `Failed to load
|
|
290
|
+
message: `Failed to load masks: ${e}`
|
|
310
291
|
});
|
|
311
292
|
state.maskLoader = null;
|
|
312
293
|
}
|
|
313
|
-
} else {
|
|
314
|
-
client.app.log({
|
|
315
|
-
service: "maskweaver",
|
|
316
|
-
level: "info",
|
|
317
|
-
message: "No masks directory found, using empty catalog"
|
|
318
|
-
});
|
|
319
294
|
}
|
|
320
295
|
return {
|
|
321
|
-
|
|
322
|
-
{
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
296
|
+
"experimental.chat.system.transform": async (_input, output) => {
|
|
297
|
+
if (state?.activeMask) {
|
|
298
|
+
const maskPrompt = `<ACTIVE_PERSONA>
|
|
299
|
+
You are currently embodying the "${state.activeMask.profile.name}" persona.
|
|
300
|
+
|
|
301
|
+
${buildRichPrompt(state.activeMask)}
|
|
302
|
+
</ACTIVE_PERSONA>`;
|
|
303
|
+
(output.system ||= []).push(maskPrompt);
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
tool: {
|
|
307
|
+
list_masks: tool({
|
|
308
|
+
description: "List all available expert persona masks. Shows mask IDs, names, categories, and tags.",
|
|
309
|
+
args: {
|
|
310
|
+
category: tool.schema.string().optional().describe("Filter by category (software-engineering, ai-ml, architecture)")
|
|
334
311
|
},
|
|
335
|
-
|
|
336
|
-
if (!state
|
|
337
|
-
return
|
|
338
|
-
success: false,
|
|
339
|
-
error: "No masks directory found",
|
|
340
|
-
hint: "Create a masks catalog at .opencode/masks/index.json"
|
|
341
|
-
};
|
|
312
|
+
async execute(args, _context) {
|
|
313
|
+
if (!state?.maskLoader) {
|
|
314
|
+
return "Error: No masks directory found. Create .opencode/masks/index.json";
|
|
342
315
|
}
|
|
343
316
|
try {
|
|
344
317
|
const masks = await state.maskLoader.listAll();
|
|
@@ -347,198 +320,121 @@ var MaskweaverPlugin = async (ctx) => {
|
|
|
347
320
|
if (args.category) {
|
|
348
321
|
filtered = masks.filter((m) => m.category === args.category);
|
|
349
322
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
323
|
+
const lines = [];
|
|
324
|
+
lines.push(`Maskweaver v0.3.0 - ${filtered.length} masks available`);
|
|
325
|
+
lines.push(`Active mask: ${state.activeMask?.metadata.id || "none"}`);
|
|
326
|
+
lines.push("");
|
|
327
|
+
lines.push("Categories:");
|
|
328
|
+
for (const cat of categories) {
|
|
329
|
+
lines.push(` - ${cat.id}: ${cat.name} (${cat.count} masks)`);
|
|
330
|
+
}
|
|
331
|
+
lines.push("");
|
|
332
|
+
lines.push("Masks:");
|
|
333
|
+
for (const mask of filtered) {
|
|
334
|
+
lines.push(` - ${mask.id}: ${mask.name} [${mask.category}]`);
|
|
335
|
+
lines.push(` Tags: ${mask.tags.join(", ")}`);
|
|
336
|
+
}
|
|
337
|
+
return lines.join(`
|
|
338
|
+
`);
|
|
362
339
|
} catch (e) {
|
|
363
|
-
return {
|
|
364
|
-
success: false,
|
|
365
|
-
error: `Failed to list masks: ${e}`
|
|
366
|
-
};
|
|
340
|
+
return `Error: ${e}`;
|
|
367
341
|
}
|
|
368
342
|
}
|
|
369
|
-
},
|
|
370
|
-
{
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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"]
|
|
343
|
+
}),
|
|
344
|
+
select_mask: tool({
|
|
345
|
+
description: "Select and apply an expert persona mask. The AI will embody this expert personality.",
|
|
346
|
+
args: {
|
|
347
|
+
maskId: tool.schema.string().describe('Mask ID (e.g., "kent-beck", "linus-torvalds", "martin-fowler")')
|
|
382
348
|
},
|
|
383
|
-
|
|
384
|
-
if (!state
|
|
385
|
-
return
|
|
386
|
-
success: false,
|
|
387
|
-
error: "No masks directory found",
|
|
388
|
-
hint: "Create a masks catalog at .opencode/masks/index.json"
|
|
389
|
-
};
|
|
349
|
+
async execute(args, _context) {
|
|
350
|
+
if (!state?.maskLoader) {
|
|
351
|
+
return "Error: No masks directory found.";
|
|
390
352
|
}
|
|
391
353
|
try {
|
|
392
354
|
const mask = await state.maskLoader.load(args.maskId);
|
|
393
355
|
if (!mask) {
|
|
394
356
|
const available = await state.maskLoader.listAll();
|
|
395
|
-
return {
|
|
396
|
-
|
|
397
|
-
error: `Mask not found: ${args.maskId}`,
|
|
398
|
-
availableMasks: available.map((m) => m.id)
|
|
399
|
-
};
|
|
357
|
+
return `Error: Mask "${args.maskId}" not found.
|
|
358
|
+
Available: ${available.map((m) => m.id).join(", ")}`;
|
|
400
359
|
}
|
|
401
360
|
state.activeMask = mask;
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
-
};
|
|
361
|
+
return `✓ Mask activated: ${mask.profile.name}
|
|
362
|
+
|
|
363
|
+
"${mask.profile.tagline}"
|
|
364
|
+
|
|
365
|
+
Expertise: ${mask.profile.expertise.join(", ")}
|
|
366
|
+
|
|
367
|
+
The mask prompt will be injected into all future messages in this session.`;
|
|
419
368
|
} catch (e) {
|
|
420
|
-
return {
|
|
421
|
-
success: false,
|
|
422
|
-
error: `Failed to select mask: ${e}`
|
|
423
|
-
};
|
|
369
|
+
return `Error: ${e}`;
|
|
424
370
|
}
|
|
425
371
|
}
|
|
426
|
-
},
|
|
427
|
-
{
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
};
|
|
372
|
+
}),
|
|
373
|
+
deselect_mask: tool({
|
|
374
|
+
description: "Remove the current mask and return to default AI behavior.",
|
|
375
|
+
args: {},
|
|
376
|
+
async execute(_args, _context) {
|
|
377
|
+
const prev = state?.activeMask;
|
|
378
|
+
if (state)
|
|
379
|
+
state.activeMask = null;
|
|
380
|
+
if (prev) {
|
|
381
|
+
return `✓ Mask removed: ${prev.profile.name}
|
|
382
|
+
Returned to default behavior.`;
|
|
449
383
|
}
|
|
450
|
-
return
|
|
451
|
-
success: true,
|
|
452
|
-
message: "No mask was active"
|
|
453
|
-
};
|
|
384
|
+
return "No mask was active.";
|
|
454
385
|
}
|
|
455
|
-
},
|
|
456
|
-
{
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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: []
|
|
386
|
+
}),
|
|
387
|
+
get_mask_prompt: tool({
|
|
388
|
+
description: "View the full system prompt for a mask.",
|
|
389
|
+
args: {
|
|
390
|
+
maskId: tool.schema.string().optional().describe("Mask ID. Uses active mask if not specified.")
|
|
468
391
|
},
|
|
469
|
-
|
|
470
|
-
if (!state
|
|
471
|
-
return
|
|
472
|
-
success: false,
|
|
473
|
-
error: "No masks directory found"
|
|
474
|
-
};
|
|
475
|
-
}
|
|
392
|
+
async execute(args, _context) {
|
|
393
|
+
if (!state?.maskLoader)
|
|
394
|
+
return "Error: No masks directory.";
|
|
476
395
|
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
|
-
}
|
|
396
|
+
if (!maskId)
|
|
397
|
+
return "Error: No mask specified and no active mask.";
|
|
483
398
|
try {
|
|
484
399
|
const mask = await state.maskLoader.load(maskId);
|
|
485
|
-
if (!mask)
|
|
486
|
-
return {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
}
|
|
491
|
-
return {
|
|
492
|
-
success: true,
|
|
493
|
-
maskId: mask.metadata.id,
|
|
494
|
-
name: mask.profile.name,
|
|
495
|
-
systemPrompt: buildRichPrompt(mask)
|
|
496
|
-
};
|
|
400
|
+
if (!mask)
|
|
401
|
+
return `Error: Mask "${maskId}" not found.`;
|
|
402
|
+
return `# ${mask.profile.name}
|
|
403
|
+
|
|
404
|
+
${buildRichPrompt(mask)}`;
|
|
497
405
|
} catch (e) {
|
|
498
|
-
return {
|
|
499
|
-
success: false,
|
|
500
|
-
error: `Failed to get mask prompt: ${e}`
|
|
501
|
-
};
|
|
406
|
+
return `Error: ${e}`;
|
|
502
407
|
}
|
|
503
408
|
}
|
|
504
|
-
},
|
|
505
|
-
{
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
type: "object",
|
|
510
|
-
properties: {},
|
|
511
|
-
required: []
|
|
512
|
-
},
|
|
513
|
-
execute: async () => {
|
|
409
|
+
}),
|
|
410
|
+
maskweaver_status: tool({
|
|
411
|
+
description: "Check Maskweaver status and active mask.",
|
|
412
|
+
args: {},
|
|
413
|
+
async execute(_args, _context) {
|
|
514
414
|
let masksCount = 0;
|
|
515
415
|
let categoriesCount = 0;
|
|
516
|
-
if (state
|
|
416
|
+
if (state?.maskLoader) {
|
|
517
417
|
try {
|
|
518
418
|
const masks = await state.maskLoader.listAll();
|
|
519
419
|
const categories = await state.maskLoader.listCategories();
|
|
520
420
|
masksCount = masks.length;
|
|
521
421
|
categoriesCount = categories.length;
|
|
522
|
-
} catch (
|
|
422
|
+
} catch (_e) {}
|
|
523
423
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
masks: masksCount,
|
|
536
|
-
categories: categoriesCount
|
|
537
|
-
}
|
|
538
|
-
};
|
|
424
|
+
const lines = [
|
|
425
|
+
"Maskweaver v0.3.0",
|
|
426
|
+
`Masks directory: ${state?.masksDir}`,
|
|
427
|
+
`Available: ${state?.maskLoader ? "yes" : "no"}`,
|
|
428
|
+
`Total masks: ${masksCount}`,
|
|
429
|
+
`Categories: ${categoriesCount}`,
|
|
430
|
+
"",
|
|
431
|
+
`Active mask: ${state?.activeMask ? `${state.activeMask.profile.name} (${state.activeMask.metadata.id})` : "none"}`
|
|
432
|
+
];
|
|
433
|
+
return lines.join(`
|
|
434
|
+
`);
|
|
539
435
|
}
|
|
540
|
-
}
|
|
541
|
-
|
|
436
|
+
})
|
|
437
|
+
}
|
|
542
438
|
};
|
|
543
439
|
};
|
|
544
440
|
var src_default = MaskweaverPlugin;
|