@maskweaver/plugin 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/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@maskweaver/plugin",
3
+ "version": "0.1.0",
4
+ "description": "Maskweaver plugin for opencode - Expert AI personas",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "bun build ./src/index.ts --outdir dist --target node --external @opencode-ai/plugin",
16
+ "dev": "bun run --watch ./src/index.ts",
17
+ "test": "bun test"
18
+ },
19
+ "keywords": [
20
+ "opencode",
21
+ "opencode-plugin",
22
+ "ai",
23
+ "persona",
24
+ "mask",
25
+ "expert",
26
+ "maskweaver"
27
+ ],
28
+ "author": "ULJI SOFT <ulgerang@gmail.com>",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/ulgerang/maskweaver.git",
33
+ "directory": "packages/plugin"
34
+ },
35
+ "homepage": "https://github.com/ulgerang/maskweaver#readme",
36
+ "bugs": {
37
+ "url": "https://github.com/ulgerang/maskweaver/issues"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "dependencies": {
43
+ "@maskweaver/core": "^0.1.0"
44
+ },
45
+ "peerDependencies": {
46
+ "@opencode-ai/plugin": ">=0.1.0"
47
+ },
48
+ "peerDependenciesMeta": {
49
+ "@opencode-ai/plugin": {
50
+ "optional": true
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Session Created Hook
3
+ *
4
+ * Handles initialization when a new session is created.
5
+ */
6
+
7
+ import type { MaskweaverState } from '../index';
8
+
9
+ export async function handleSessionCreated(
10
+ session: any,
11
+ state: MaskweaverState,
12
+ client: any
13
+ ): Promise<void> {
14
+ try {
15
+ const categories = await state.maskLoader.listCategories();
16
+ const masks = await state.maskLoader.listAll();
17
+
18
+ // Log available masks
19
+ await client.app.log({
20
+ service: 'maskweaver',
21
+ level: 'info',
22
+ message: 'Session created - masks available',
23
+ extra: {
24
+ categories: categories.length,
25
+ masks: masks.length,
26
+ },
27
+ });
28
+
29
+ // If there's a default mask configured, apply it
30
+ // For now, just notify that masks are available
31
+
32
+ } catch (error) {
33
+ await client.app.log({
34
+ service: 'maskweaver',
35
+ level: 'error',
36
+ message: 'Failed to initialize masks for session',
37
+ extra: { error: String(error) },
38
+ });
39
+ }
40
+ }
package/src/index.ts ADDED
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Maskweaver Plugin for opencode
3
+ *
4
+ * Applies expert personas (masks) to AI assistants.
5
+ * 가면술사 - opencode 플러그인
6
+ */
7
+
8
+ import type { Plugin } from '@opencode-ai/plugin';
9
+ import { MaskLoader } from '@maskweaver/core';
10
+ import { handleSessionCreated } from './hooks/sessionCreated';
11
+ import { createSelectMaskTool } from './tools/selectMask';
12
+ import { createListMasksTool } from './tools/listMasks';
13
+
14
+ export interface MaskweaverState {
15
+ maskLoader: MaskLoader;
16
+ activeMask: any | null;
17
+ }
18
+
19
+ /**
20
+ * Maskweaver Plugin
21
+ *
22
+ * Give your AI coding assistant expert personalities.
23
+ */
24
+ export const MaskweaverPlugin: Plugin = async (ctx) => {
25
+ const { directory, client } = ctx;
26
+
27
+ // Initialize mask loader
28
+ const masksDir = `${directory}/.opencode/masks`;
29
+ const maskLoader = new MaskLoader({ masksDir });
30
+
31
+ try {
32
+ await maskLoader.loadCatalog();
33
+ await client.app.log({
34
+ service: 'maskweaver',
35
+ level: 'info',
36
+ message: 'Maskweaver initialized',
37
+ extra: { masksDir },
38
+ });
39
+ } catch (error) {
40
+ // Fallback to default masks directory
41
+ await client.app.log({
42
+ service: 'maskweaver',
43
+ level: 'warn',
44
+ message: 'Custom masks not found, using defaults',
45
+ });
46
+ }
47
+
48
+ // Store in context for tools
49
+ const state: MaskweaverState = {
50
+ maskLoader,
51
+ activeMask: null,
52
+ };
53
+
54
+ return {
55
+ /**
56
+ * Session Created Hook
57
+ *
58
+ * Called when a new session is created.
59
+ * Can inject initial system prompt or provide mask selection.
60
+ */
61
+ 'session.created': async (session) => {
62
+ await handleSessionCreated(session, state, client);
63
+ },
64
+
65
+ /**
66
+ * Tool Execute Hooks
67
+ *
68
+ * Track tool usage with masks for analytics.
69
+ */
70
+ 'tool.execute.after': async (input, output) => {
71
+ if (state.activeMask) {
72
+ await client.app.log({
73
+ service: 'maskweaver',
74
+ level: 'debug',
75
+ message: `Tool executed with mask`,
76
+ extra: {
77
+ tool: input.tool,
78
+ mask: state.activeMask.metadata.id,
79
+ },
80
+ });
81
+ }
82
+ },
83
+
84
+ /**
85
+ * Custom Tools
86
+ */
87
+ tool: {
88
+ select_mask: createSelectMaskTool(state),
89
+ list_masks: createListMasksTool(state),
90
+ },
91
+ };
92
+ };
93
+
94
+ export default MaskweaverPlugin;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * List Masks Tool
3
+ *
4
+ * Lists all available expert persona masks.
5
+ */
6
+
7
+ import { tool } from '@opencode-ai/plugin';
8
+ import type { MaskweaverState } from '../index';
9
+
10
+ export function createListMasksTool(state: MaskweaverState) {
11
+ return tool({
12
+ description: `List all available expert persona masks.
13
+ Returns masks grouped by category with their expertise areas.
14
+
15
+ Use this to discover which expert personas are available before applying one with select_mask.`,
16
+
17
+ args: {
18
+ category: tool.schema.string().optional().describe(
19
+ 'Filter by category (e.g., "software-engineering", "ai-ml")'
20
+ ),
21
+ tag: tool.schema.string().optional().describe(
22
+ 'Filter by tag (e.g., "tdd", "architecture", "systems")'
23
+ ),
24
+ },
25
+
26
+ async execute(args, context) {
27
+ const { category, tag } = args;
28
+ const { maskLoader } = state;
29
+
30
+ try {
31
+ const allMasks = await maskLoader.listAll();
32
+ const categories = await maskLoader.listCategories();
33
+
34
+ // Filter by category if specified
35
+ let filtered = allMasks;
36
+ if (category) {
37
+ filtered = allMasks.filter(m => m.category === category);
38
+ }
39
+
40
+ // Filter by tag if specified
41
+ if (tag) {
42
+ filtered = filtered.filter(m => m.tags.includes(tag));
43
+ }
44
+
45
+ // Group by category
46
+ const grouped: Record<string, Array<{
47
+ id: string;
48
+ name: string;
49
+ tags: string[];
50
+ }>> = {};
51
+
52
+ for (const mask of filtered) {
53
+ if (!grouped[mask.category]) {
54
+ grouped[mask.category] = [];
55
+ }
56
+ grouped[mask.category].push({
57
+ id: mask.id,
58
+ name: mask.name,
59
+ tags: mask.tags,
60
+ });
61
+ }
62
+
63
+ // Format summary
64
+ const summary = categories.map(cat =>
65
+ `${cat.name} (${cat.count} masks): ${cat.description}`
66
+ ).join('\n');
67
+
68
+ return {
69
+ success: true,
70
+ summary: `Found ${filtered.length} masks across ${Object.keys(grouped).length} categories`,
71
+ categories: summary,
72
+ masks: grouped,
73
+ activeMask: state.activeMask ? {
74
+ id: state.activeMask.metadata.id,
75
+ name: state.activeMask.profile.name,
76
+ } : null,
77
+ hint: 'Use select_mask with a mask ID to apply a persona (e.g., select_mask("linus-torvalds"))',
78
+ };
79
+ } catch (error) {
80
+ return {
81
+ success: false,
82
+ message: `Failed to list masks: ${error}`,
83
+ };
84
+ }
85
+ },
86
+ });
87
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Select Mask Tool
3
+ *
4
+ * Allows users to apply an expert persona (mask) to the AI.
5
+ */
6
+
7
+ import { tool } from '@opencode-ai/plugin';
8
+ import { promptBuilder } from '@maskweaver/core';
9
+ import type { MaskweaverState } from '../index';
10
+
11
+ export function createSelectMaskTool(state: MaskweaverState) {
12
+ return tool({
13
+ description: `Apply an expert persona (mask) to the AI assistant.
14
+ This transforms how the AI thinks, communicates, and approaches problems.
15
+
16
+ Example personas: linus-torvalds (systems programming), martin-fowler (architecture), kent-beck (TDD)
17
+
18
+ Use list_masks first to see all available masks.`,
19
+
20
+ args: {
21
+ maskId: tool.schema.string().describe(
22
+ 'ID of the mask to apply (e.g., "linus-torvalds")'
23
+ ),
24
+ mode: tool.schema.enum(['minimal', 'standard', 'rich']).optional().describe(
25
+ 'Prompt richness: minimal (core only), standard (default), rich (full context)'
26
+ ),
27
+ },
28
+
29
+ async execute(args, context) {
30
+ const { maskId, mode = 'standard' } = args;
31
+ const { maskLoader } = state;
32
+
33
+ try {
34
+ const mask = await maskLoader.load(maskId);
35
+
36
+ if (!mask) {
37
+ return {
38
+ success: false,
39
+ message: `Mask '${maskId}' not found. Use list_masks to see available options.`,
40
+ };
41
+ }
42
+
43
+ // Build system prompt based on mode
44
+ let systemPrompt: string;
45
+ switch (mode) {
46
+ case 'minimal':
47
+ systemPrompt = promptBuilder.minimal(mask);
48
+ break;
49
+ case 'rich':
50
+ systemPrompt = promptBuilder.rich(mask);
51
+ break;
52
+ default:
53
+ systemPrompt = promptBuilder.build(mask);
54
+ }
55
+
56
+ // Store active mask
57
+ state.activeMask = mask;
58
+
59
+ return {
60
+ success: true,
61
+ message: `🎭 Mask '${mask.profile.name}' applied.\n\n> ${mask.profile.tagline}`,
62
+ mask: {
63
+ id: mask.metadata.id,
64
+ name: mask.profile.name,
65
+ category: mask.category,
66
+ expertise: mask.profile.expertise,
67
+ tone: mask.behavior.communicationStyle.tone,
68
+ },
69
+ systemPrompt,
70
+ instruction: 'The system prompt above has been applied. The AI will now respond as this persona.',
71
+ };
72
+ } catch (error) {
73
+ return {
74
+ success: false,
75
+ message: `Failed to load mask: ${error}`,
76
+ };
77
+ }
78
+ },
79
+ });
80
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "skipLibCheck": true
7
+ },
8
+ "include": ["src/**/*"],
9
+ "exclude": ["node_modules", "dist"]
10
+ }