maskweaver 0.7.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/assets/agents/dummy-human.md +31 -0
- package/assets/agents/dummy-template.md +57 -0
- package/assets/agents/mask-master.md +225 -0
- package/assets/masks/ai-ml/andrew-ng.yaml +207 -0
- package/assets/masks/architecture/jeff-dean.yaml +208 -0
- package/assets/masks/index.json +65 -0
- package/assets/masks/software-engineering/dan-abramov.yaml +188 -0
- package/assets/masks/software-engineering/kent-beck.yaml +191 -0
- package/assets/masks/software-engineering/linus-torvalds.yaml +152 -0
- package/assets/masks/software-engineering/martin-fowler.yaml +173 -0
- package/dist/cli/install.d.ts +11 -0
- package/dist/cli/install.d.ts.map +1 -0
- package/dist/cli/install.js +299 -0
- package/dist/cli/install.js.map +1 -0
- package/dist/context/config.d.ts +38 -0
- package/dist/context/config.d.ts.map +1 -0
- package/dist/context/config.js +55 -0
- package/dist/context/config.js.map +1 -0
- package/dist/context/feature.d.ts +49 -0
- package/dist/context/feature.d.ts.map +1 -0
- package/dist/context/feature.js +290 -0
- package/dist/context/feature.js.map +1 -0
- package/dist/context/files.d.ts +17 -0
- package/dist/context/files.d.ts.map +1 -0
- package/dist/context/files.js +50 -0
- package/dist/context/files.js.map +1 -0
- package/dist/context/index.d.ts +14 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +18 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/project.d.ts +26 -0
- package/dist/context/project.d.ts.map +1 -0
- package/dist/context/project.js +95 -0
- package/dist/context/project.js.map +1 -0
- package/dist/context/types.d.ts +72 -0
- package/dist/context/types.d.ts.map +1 -0
- package/dist/context/types.js +14 -0
- package/dist/context/types.js.map +1 -0
- package/dist/context/utils.d.ts +22 -0
- package/dist/context/utils.d.ts.map +1 -0
- package/dist/context/utils.js +40 -0
- package/dist/context/utils.js.map +1 -0
- package/dist/core/engine/promptBuilder.d.ts +32 -0
- package/dist/core/engine/promptBuilder.d.ts.map +1 -0
- package/dist/core/engine/promptBuilder.js +117 -0
- package/dist/core/engine/promptBuilder.js.map +1 -0
- package/dist/core/index.d.ts +11 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +13 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/loader/MaskLoader.d.ts +47 -0
- package/dist/core/loader/MaskLoader.d.ts.map +1 -0
- package/dist/core/loader/MaskLoader.js +132 -0
- package/dist/core/loader/MaskLoader.js.map +1 -0
- package/dist/core/schema/types.d.ts +128 -0
- package/dist/core/schema/types.d.ts.map +1 -0
- package/dist/core/schema/types.js +8 -0
- package/dist/core/schema/types.js.map +1 -0
- package/dist/core/schema/validator.d.ts +290 -0
- package/dist/core/schema/validator.d.ts.map +1 -0
- package/dist/core/schema/validator.js +105 -0
- package/dist/core/schema/validator.js.map +1 -0
- package/dist/i18n/index.d.ts +33 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +57 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/locales/en.json +16 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/chunking.d.ts +35 -0
- package/dist/memory/chunking.d.ts.map +1 -0
- package/dist/memory/chunking.js +168 -0
- package/dist/memory/chunking.js.map +1 -0
- package/dist/memory/core.d.ts +73 -0
- package/dist/memory/core.d.ts.map +1 -0
- package/dist/memory/core.js +186 -0
- package/dist/memory/core.js.map +1 -0
- package/dist/memory/index.d.ts +21 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +22 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/indexer.d.ts +37 -0
- package/dist/memory/indexer.d.ts.map +1 -0
- package/dist/memory/indexer.js +162 -0
- package/dist/memory/indexer.js.map +1 -0
- package/dist/memory/providers/examples.d.ts +17 -0
- package/dist/memory/providers/examples.d.ts.map +1 -0
- package/dist/memory/providers/examples.js +271 -0
- package/dist/memory/providers/examples.js.map +1 -0
- package/dist/memory/providers/factory.d.ts +49 -0
- package/dist/memory/providers/factory.d.ts.map +1 -0
- package/dist/memory/providers/factory.js +111 -0
- package/dist/memory/providers/factory.js.map +1 -0
- package/dist/memory/providers/index.d.ts +34 -0
- package/dist/memory/providers/index.d.ts.map +1 -0
- package/dist/memory/providers/index.js +35 -0
- package/dist/memory/providers/index.js.map +1 -0
- package/dist/memory/providers/ollama.d.ts +18 -0
- package/dist/memory/providers/ollama.d.ts.map +1 -0
- package/dist/memory/providers/ollama.js +68 -0
- package/dist/memory/providers/ollama.js.map +1 -0
- package/dist/memory/providers/openai.d.ts +21 -0
- package/dist/memory/providers/openai.d.ts.map +1 -0
- package/dist/memory/providers/openai.js +91 -0
- package/dist/memory/providers/openai.js.map +1 -0
- package/dist/memory/providers/openrouter.d.ts +22 -0
- package/dist/memory/providers/openrouter.d.ts.map +1 -0
- package/dist/memory/providers/openrouter.js +89 -0
- package/dist/memory/providers/openrouter.js.map +1 -0
- package/dist/memory/providers/text-only.d.ts +20 -0
- package/dist/memory/providers/text-only.d.ts.map +1 -0
- package/dist/memory/providers/text-only.js +35 -0
- package/dist/memory/providers/text-only.js.map +1 -0
- package/dist/memory/providers/types.d.ts +63 -0
- package/dist/memory/providers/types.d.ts.map +1 -0
- package/dist/memory/providers/types.js +9 -0
- package/dist/memory/providers/types.js.map +1 -0
- package/dist/memory/providers/voyage.d.ts +39 -0
- package/dist/memory/providers/voyage.d.ts.map +1 -0
- package/dist/memory/providers/voyage.js +127 -0
- package/dist/memory/providers/voyage.js.map +1 -0
- package/dist/memory/search/hybrid.d.ts +12 -0
- package/dist/memory/search/hybrid.d.ts.map +1 -0
- package/dist/memory/search/hybrid.js +59 -0
- package/dist/memory/search/hybrid.js.map +1 -0
- package/dist/memory/store/sqlite.d.ts +86 -0
- package/dist/memory/store/sqlite.d.ts.map +1 -0
- package/dist/memory/store/sqlite.js +390 -0
- package/dist/memory/store/sqlite.js.map +1 -0
- package/dist/plugin/config/index.d.ts +148 -0
- package/dist/plugin/config/index.d.ts.map +1 -0
- package/dist/plugin/config/index.js +236 -0
- package/dist/plugin/config/index.js.map +1 -0
- package/dist/plugin/index.d.ts +19 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +811 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/plugin/tools/context.d.ts +36 -0
- package/dist/plugin/tools/context.d.ts.map +1 -0
- package/dist/plugin/tools/context.js +332 -0
- package/dist/plugin/tools/context.js.map +1 -0
- package/dist/plugin/tools/maskSave.d.ts +6 -0
- package/dist/plugin/tools/maskSave.d.ts.map +1 -0
- package/dist/plugin/tools/maskSave.js +42 -0
- package/dist/plugin/tools/maskSave.js.map +1 -0
- package/dist/plugin/tools/memoryGet.d.ts +21 -0
- package/dist/plugin/tools/memoryGet.d.ts.map +1 -0
- package/dist/plugin/tools/memoryGet.js +40 -0
- package/dist/plugin/tools/memoryGet.js.map +1 -0
- package/dist/plugin/tools/memoryIndexer.d.ts +6 -0
- package/dist/plugin/tools/memoryIndexer.d.ts.map +1 -0
- package/dist/plugin/tools/memoryIndexer.js +75 -0
- package/dist/plugin/tools/memoryIndexer.js.map +1 -0
- package/dist/plugin/tools/memorySearch.d.ts +74 -0
- package/dist/plugin/tools/memorySearch.d.ts.map +1 -0
- package/dist/plugin/tools/memorySearch.js +172 -0
- package/dist/plugin/tools/memorySearch.js.map +1 -0
- package/dist/plugin/tools/memoryWrite.d.ts +11 -0
- package/dist/plugin/tools/memoryWrite.d.ts.map +1 -0
- package/dist/plugin/tools/memoryWrite.js +161 -0
- package/dist/plugin/tools/memoryWrite.js.map +1 -0
- package/dist/plugin/tools/retrospect.d.ts +6 -0
- package/dist/plugin/tools/retrospect.d.ts.map +1 -0
- package/dist/plugin/tools/retrospect.js +46 -0
- package/dist/plugin/tools/retrospect.js.map +1 -0
- package/dist/plugin/types.d.ts +34 -0
- package/dist/plugin/types.d.ts.map +1 -0
- package/dist/plugin/types.js +7 -0
- package/dist/plugin/types.js.map +1 -0
- package/dist/retrospect/index.d.ts +14 -0
- package/dist/retrospect/index.d.ts.map +1 -0
- package/dist/retrospect/index.js +13 -0
- package/dist/retrospect/index.js.map +1 -0
- package/dist/retrospect/mask-save.d.ts +31 -0
- package/dist/retrospect/mask-save.d.ts.map +1 -0
- package/dist/retrospect/mask-save.js +263 -0
- package/dist/retrospect/mask-save.js.map +1 -0
- package/dist/retrospect/retrospect.d.ts +24 -0
- package/dist/retrospect/retrospect.d.ts.map +1 -0
- package/dist/retrospect/retrospect.js +165 -0
- package/dist/retrospect/retrospect.js.map +1 -0
- package/dist/retrospect/strategies/base.d.ts +20 -0
- package/dist/retrospect/strategies/base.d.ts.map +1 -0
- package/dist/retrospect/strategies/base.js +9 -0
- package/dist/retrospect/strategies/base.js.map +1 -0
- package/dist/retrospect/strategies/deep.d.ts +18 -0
- package/dist/retrospect/strategies/deep.d.ts.map +1 -0
- package/dist/retrospect/strategies/deep.js +105 -0
- package/dist/retrospect/strategies/deep.js.map +1 -0
- package/dist/retrospect/strategies/index.d.ts +20 -0
- package/dist/retrospect/strategies/index.d.ts.map +1 -0
- package/dist/retrospect/strategies/index.js +27 -0
- package/dist/retrospect/strategies/index.js.map +1 -0
- package/dist/retrospect/strategies/quick.d.ts +18 -0
- package/dist/retrospect/strategies/quick.d.ts.map +1 -0
- package/dist/retrospect/strategies/quick.js +55 -0
- package/dist/retrospect/strategies/quick.js.map +1 -0
- package/dist/retrospect/strategies/standard.d.ts +18 -0
- package/dist/retrospect/strategies/standard.d.ts.map +1 -0
- package/dist/retrospect/strategies/standard.js +66 -0
- package/dist/retrospect/strategies/standard.js.map +1 -0
- package/dist/retrospect/types.d.ts +34 -0
- package/dist/retrospect/types.d.ts.map +1 -0
- package/dist/retrospect/types.js +9 -0
- package/dist/retrospect/types.js.map +1 -0
- package/dist/shared/config.d.ts +130 -0
- package/dist/shared/config.d.ts.map +1 -0
- package/dist/shared/config.js +12 -0
- package/dist/shared/config.js.map +1 -0
- package/dist/shared/errors.d.ts +36 -0
- package/dist/shared/errors.d.ts.map +1 -0
- package/dist/shared/errors.js +57 -0
- package/dist/shared/errors.js.map +1 -0
- package/dist/shared/index.d.ts +10 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/shared/index.js +9 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/shared/types.d.ts +34 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +5 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/shared-context/index.d.ts +11 -0
- package/dist/shared-context/index.d.ts.map +1 -0
- package/dist/shared-context/index.js +16 -0
- package/dist/shared-context/index.js.map +1 -0
- package/dist/shared-context/logger.d.ts +10 -0
- package/dist/shared-context/logger.d.ts.map +1 -0
- package/dist/shared-context/logger.js +28 -0
- package/dist/shared-context/logger.js.map +1 -0
- package/dist/shared-context/session.d.ts +23 -0
- package/dist/shared-context/session.d.ts.map +1 -0
- package/dist/shared-context/session.js +34 -0
- package/dist/shared-context/session.js.map +1 -0
- package/dist/shared-context/squad.d.ts +30 -0
- package/dist/shared-context/squad.d.ts.map +1 -0
- package/dist/shared-context/squad.js +66 -0
- package/dist/shared-context/squad.js.map +1 -0
- package/dist/shared-context/storage.d.ts +25 -0
- package/dist/shared-context/storage.d.ts.map +1 -0
- package/dist/shared-context/storage.js +66 -0
- package/dist/shared-context/storage.js.map +1 -0
- package/dist/shared-context/types.d.ts +107 -0
- package/dist/shared-context/types.d.ts.map +1 -0
- package/dist/shared-context/types.js +18 -0
- package/dist/shared-context/types.js.map +1 -0
- package/dist/verify/budget.d.ts +45 -0
- package/dist/verify/budget.d.ts.map +1 -0
- package/dist/verify/budget.js +89 -0
- package/dist/verify/budget.js.map +1 -0
- package/dist/verify/critical-files.d.ts +22 -0
- package/dist/verify/critical-files.d.ts.map +1 -0
- package/dist/verify/critical-files.js +130 -0
- package/dist/verify/critical-files.js.map +1 -0
- package/dist/verify/escalation.d.ts +27 -0
- package/dist/verify/escalation.d.ts.map +1 -0
- package/dist/verify/escalation.js +68 -0
- package/dist/verify/escalation.js.map +1 -0
- package/dist/verify/index.d.ts +13 -0
- package/dist/verify/index.d.ts.map +1 -0
- package/dist/verify/index.js +18 -0
- package/dist/verify/index.js.map +1 -0
- package/dist/verify/prompts.d.ts +27 -0
- package/dist/verify/prompts.d.ts.map +1 -0
- package/dist/verify/prompts.js +158 -0
- package/dist/verify/prompts.js.map +1 -0
- package/dist/verify/types.d.ts +80 -0
- package/dist/verify/types.d.ts.map +1 -0
- package/dist/verify/types.js +22 -0
- package/dist/verify/types.js.map +1 -0
- package/dist/verify/verifier.d.ts +47 -0
- package/dist/verify/verifier.d.ts.map +1 -0
- package/dist/verify/verifier.js +180 -0
- package/dist/verify/verifier.js.map +1 -0
- package/masks/ai-ml/andrew-ng.yaml +207 -0
- package/masks/architecture/jeff-dean.yaml +208 -0
- package/masks/index.json +65 -0
- package/masks/orchestration/squad-operator.yaml +205 -0
- package/masks/software-engineering/dan-abramov.yaml +188 -0
- package/masks/software-engineering/kent-beck.yaml +191 -0
- package/masks/software-engineering/linus-torvalds.yaml +152 -0
- package/masks/software-engineering/martin-fowler.yaml +173 -0
- package/package.json +111 -0
|
@@ -0,0 +1,811 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maskweaver Plugin for opencode
|
|
3
|
+
*
|
|
4
|
+
* v0.6.0 - Memory, Context, and Retrospect tools integration
|
|
5
|
+
*
|
|
6
|
+
* Key features:
|
|
7
|
+
* - Configuration-driven tool activation/deactivation
|
|
8
|
+
* - Auto-activation of default masks
|
|
9
|
+
* - Agent configuration overrides
|
|
10
|
+
* - Event-based lifecycle hooks
|
|
11
|
+
* - Memory and context management tools
|
|
12
|
+
* - Clean plugin architecture
|
|
13
|
+
*
|
|
14
|
+
* Based on oh-my-opencode plugin development patterns.
|
|
15
|
+
*/
|
|
16
|
+
import * as fs from 'node:fs';
|
|
17
|
+
import * as path from 'node:path';
|
|
18
|
+
import * as os from 'node:os';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
import { loadPluginConfig, isMaskEnabled, isToolEnabled, getDefaultMask, isAutoActivateEnabled, getAgentOverride, isVerboseLoggingEnabled, validateConfig, } from './config';
|
|
22
|
+
// New tool imports
|
|
23
|
+
import { createMemorySearchTool } from './tools/memorySearch.js';
|
|
24
|
+
import { createMemoryWriteTool } from './tools/memoryWrite.js';
|
|
25
|
+
import { createMemoryGetTool } from './tools/memoryGet.js';
|
|
26
|
+
import { createMemoryIndexerTool } from './tools/memoryIndexer.js';
|
|
27
|
+
import { createContextTool } from './tools/context.js';
|
|
28
|
+
import { createRetrospectTool } from './tools/retrospect.js';
|
|
29
|
+
import { createMaskSaveTool } from './tools/maskSave.js';
|
|
30
|
+
function getAssetsDir() {
|
|
31
|
+
// In ESM, use import.meta.url to find the assets directory
|
|
32
|
+
// When installed via npm: node_modules/@maskweaver/plugin/dist/index.js -> ../assets
|
|
33
|
+
// When local file: plugins/maskweaver.js -> ./assets (same directory)
|
|
34
|
+
try {
|
|
35
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
36
|
+
const __dirname = path.dirname(__filename);
|
|
37
|
+
// First try: assets as sibling (npm package structure: dist/../assets)
|
|
38
|
+
const npmAssets = path.join(__dirname, '..', 'assets');
|
|
39
|
+
if (fs.existsSync(npmAssets)) {
|
|
40
|
+
return npmAssets;
|
|
41
|
+
}
|
|
42
|
+
// Second try: assets as subdirectory (local file: plugins/assets)
|
|
43
|
+
const localAssets = path.join(__dirname, 'assets');
|
|
44
|
+
if (fs.existsSync(localAssets)) {
|
|
45
|
+
return localAssets;
|
|
46
|
+
}
|
|
47
|
+
// Fallback to npm structure
|
|
48
|
+
return npmAssets;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Fallback for CommonJS or other environments
|
|
52
|
+
return path.join(__dirname, '..', 'assets');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function copyDirRecursive(src, dest, result) {
|
|
56
|
+
if (!fs.existsSync(src))
|
|
57
|
+
return;
|
|
58
|
+
if (!fs.existsSync(dest)) {
|
|
59
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
60
|
+
}
|
|
61
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
const srcPath = path.join(src, entry.name);
|
|
64
|
+
const destPath = path.join(dest, entry.name);
|
|
65
|
+
if (entry.isDirectory()) {
|
|
66
|
+
copyDirRecursive(srcPath, destPath, result);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// Only copy if destination doesn't exist (don't overwrite user customizations)
|
|
70
|
+
if (!fs.existsSync(destPath)) {
|
|
71
|
+
try {
|
|
72
|
+
fs.copyFileSync(srcPath, destPath);
|
|
73
|
+
result.installed.push(destPath);
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
result.errors.push(`Failed to copy ${entry.name}: ${e}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
result.skipped.push(destPath);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function installAssets(projectDir) {
|
|
86
|
+
const result = {
|
|
87
|
+
installed: [],
|
|
88
|
+
skipped: [],
|
|
89
|
+
errors: [],
|
|
90
|
+
};
|
|
91
|
+
const assetsDir = getAssetsDir();
|
|
92
|
+
const opencodeDir = path.join(projectDir, '.opencode');
|
|
93
|
+
// Ensure .opencode directory exists
|
|
94
|
+
if (!fs.existsSync(opencodeDir)) {
|
|
95
|
+
fs.mkdirSync(opencodeDir, { recursive: true });
|
|
96
|
+
}
|
|
97
|
+
// Install agents
|
|
98
|
+
const agentsSrc = path.join(assetsDir, 'agents');
|
|
99
|
+
const agentsDest = path.join(opencodeDir, 'agents');
|
|
100
|
+
copyDirRecursive(agentsSrc, agentsDest, result);
|
|
101
|
+
// Install masks
|
|
102
|
+
const masksSrc = path.join(assetsDir, 'masks');
|
|
103
|
+
const masksDest = path.join(opencodeDir, 'masks');
|
|
104
|
+
copyDirRecursive(masksSrc, masksDest, result);
|
|
105
|
+
// Install commands (if any)
|
|
106
|
+
const commandsSrc = path.join(assetsDir, 'commands');
|
|
107
|
+
const commandsDest = path.join(opencodeDir, 'commands');
|
|
108
|
+
if (fs.existsSync(commandsSrc)) {
|
|
109
|
+
copyDirRecursive(commandsSrc, commandsDest, result);
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
// ============================================================================
|
|
114
|
+
// Simple YAML Parser
|
|
115
|
+
// ============================================================================
|
|
116
|
+
function parseSimpleYaml(content) {
|
|
117
|
+
const lines = content.split('\n');
|
|
118
|
+
const result = {};
|
|
119
|
+
const stack = [
|
|
120
|
+
{ indent: -2, obj: result },
|
|
121
|
+
];
|
|
122
|
+
let currentArrayKey = undefined;
|
|
123
|
+
let currentArray = [];
|
|
124
|
+
let multilineKey = null;
|
|
125
|
+
let multilineValue = [];
|
|
126
|
+
let multilineIndent = 0;
|
|
127
|
+
for (let i = 0; i < lines.length; i++) {
|
|
128
|
+
const line = lines[i];
|
|
129
|
+
const trimmed = line.trimStart();
|
|
130
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
131
|
+
if (multilineKey)
|
|
132
|
+
multilineValue.push('');
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const indent = line.length - trimmed.length;
|
|
136
|
+
if (multilineKey) {
|
|
137
|
+
if (indent > multilineIndent || (indent === multilineIndent && !trimmed.includes(':'))) {
|
|
138
|
+
multilineValue.push(trimmed);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
const parent = stack[stack.length - 1];
|
|
143
|
+
parent.obj[multilineKey] = multilineValue.join('\n').trim();
|
|
144
|
+
multilineKey = null;
|
|
145
|
+
multilineValue = [];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (trimmed.startsWith('- ')) {
|
|
149
|
+
const value = trimmed.slice(2).trim();
|
|
150
|
+
while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
|
|
151
|
+
const popped = stack.pop();
|
|
152
|
+
if (popped.key && currentArrayKey === popped.key) {
|
|
153
|
+
const parent = stack[stack.length - 1];
|
|
154
|
+
parent.obj[popped.key] = currentArray;
|
|
155
|
+
currentArrayKey = undefined;
|
|
156
|
+
currentArray = [];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (value.includes(':')) {
|
|
160
|
+
const colonIdx = value.indexOf(':');
|
|
161
|
+
const objKey = value.slice(0, colonIdx).trim();
|
|
162
|
+
const objVal = value.slice(colonIdx + 1).trim();
|
|
163
|
+
const arrayItem = {};
|
|
164
|
+
if (objVal)
|
|
165
|
+
arrayItem[objKey] = parseValue(objVal);
|
|
166
|
+
let j = i + 1;
|
|
167
|
+
const itemIndent = indent + 2;
|
|
168
|
+
while (j < lines.length) {
|
|
169
|
+
const nextLine = lines[j];
|
|
170
|
+
const nextTrimmed = nextLine.trimStart();
|
|
171
|
+
const nextIndent = nextLine.length - nextTrimmed.length;
|
|
172
|
+
if (!nextTrimmed || nextTrimmed.startsWith('#')) {
|
|
173
|
+
j++;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (nextIndent < itemIndent || nextTrimmed.startsWith('- '))
|
|
177
|
+
break;
|
|
178
|
+
if (nextTrimmed.includes(':')) {
|
|
179
|
+
const nColonIdx = nextTrimmed.indexOf(':');
|
|
180
|
+
const nKey = nextTrimmed.slice(0, nColonIdx).trim();
|
|
181
|
+
const nVal = nextTrimmed.slice(nColonIdx + 1).trim();
|
|
182
|
+
if (nVal)
|
|
183
|
+
arrayItem[nKey] = parseValue(nVal);
|
|
184
|
+
}
|
|
185
|
+
j++;
|
|
186
|
+
}
|
|
187
|
+
i = j - 1;
|
|
188
|
+
currentArray.push(arrayItem);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
currentArray.push(parseValue(value));
|
|
192
|
+
}
|
|
193
|
+
if (!currentArrayKey) {
|
|
194
|
+
for (let s = stack.length - 1; s >= 0; s--) {
|
|
195
|
+
if (stack[s].key) {
|
|
196
|
+
currentArrayKey = stack[s].key;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (trimmed.includes(':')) {
|
|
204
|
+
while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
|
|
205
|
+
const popped = stack.pop();
|
|
206
|
+
if (popped.key && currentArrayKey === popped.key) {
|
|
207
|
+
const parent = stack[stack.length - 1];
|
|
208
|
+
parent.obj[popped.key] = currentArray;
|
|
209
|
+
currentArrayKey = undefined;
|
|
210
|
+
currentArray = [];
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (currentArrayKey) {
|
|
214
|
+
const parent = stack[stack.length - 1];
|
|
215
|
+
parent.obj[currentArrayKey] = currentArray;
|
|
216
|
+
currentArrayKey = undefined;
|
|
217
|
+
currentArray = [];
|
|
218
|
+
}
|
|
219
|
+
const colonIdx = trimmed.indexOf(':');
|
|
220
|
+
const key = trimmed.slice(0, colonIdx).trim();
|
|
221
|
+
const value = trimmed.slice(colonIdx + 1).trim();
|
|
222
|
+
const parent = stack[stack.length - 1];
|
|
223
|
+
if (!value) {
|
|
224
|
+
const nextLine = lines[i + 1];
|
|
225
|
+
if (nextLine && nextLine.trimStart().startsWith('|')) {
|
|
226
|
+
multilineKey = key;
|
|
227
|
+
multilineIndent = indent;
|
|
228
|
+
i++;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const newObj = {};
|
|
232
|
+
parent.obj[key] = newObj;
|
|
233
|
+
stack.push({ indent, obj: newObj, key });
|
|
234
|
+
}
|
|
235
|
+
else if (value === '|' || value === '>') {
|
|
236
|
+
multilineKey = key;
|
|
237
|
+
multilineIndent = indent;
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
parent.obj[key] = parseValue(value);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (multilineKey) {
|
|
245
|
+
const parent = stack[stack.length - 1];
|
|
246
|
+
parent.obj[multilineKey] = multilineValue.join('\n').trim();
|
|
247
|
+
}
|
|
248
|
+
if (currentArrayKey) {
|
|
249
|
+
const parent = stack[stack.length - 1];
|
|
250
|
+
parent.obj[currentArrayKey] = currentArray;
|
|
251
|
+
}
|
|
252
|
+
return result;
|
|
253
|
+
}
|
|
254
|
+
function parseValue(value) {
|
|
255
|
+
if (!value)
|
|
256
|
+
return '';
|
|
257
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
258
|
+
return value.slice(1, -1);
|
|
259
|
+
}
|
|
260
|
+
if (value === 'true')
|
|
261
|
+
return true;
|
|
262
|
+
if (value === 'false')
|
|
263
|
+
return false;
|
|
264
|
+
if (value === 'null' || value === '~')
|
|
265
|
+
return null;
|
|
266
|
+
const num = Number(value);
|
|
267
|
+
if (!isNaN(num) && value !== '')
|
|
268
|
+
return num;
|
|
269
|
+
return value;
|
|
270
|
+
}
|
|
271
|
+
// ============================================================================
|
|
272
|
+
// Mask Loader
|
|
273
|
+
// ============================================================================
|
|
274
|
+
class MaskLoader {
|
|
275
|
+
masksDir;
|
|
276
|
+
catalog = null;
|
|
277
|
+
cache = new Map();
|
|
278
|
+
config;
|
|
279
|
+
constructor(masksDir, config) {
|
|
280
|
+
this.masksDir = masksDir;
|
|
281
|
+
this.config = config;
|
|
282
|
+
}
|
|
283
|
+
async loadCatalog() {
|
|
284
|
+
if (this.catalog)
|
|
285
|
+
return this.catalog;
|
|
286
|
+
const indexPath = path.join(this.masksDir, 'index.json');
|
|
287
|
+
if (!fs.existsSync(indexPath))
|
|
288
|
+
throw new Error(`Catalog not found: ${indexPath}`);
|
|
289
|
+
const content = fs.readFileSync(indexPath, 'utf-8');
|
|
290
|
+
this.catalog = JSON.parse(content);
|
|
291
|
+
return this.catalog;
|
|
292
|
+
}
|
|
293
|
+
async load(maskId) {
|
|
294
|
+
// Check if mask is disabled in configuration
|
|
295
|
+
if (!isMaskEnabled(this.config, maskId)) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
if (this.cache.has(maskId))
|
|
299
|
+
return this.cache.get(maskId);
|
|
300
|
+
const catalog = await this.loadCatalog();
|
|
301
|
+
let entry = null;
|
|
302
|
+
let categoryId = null;
|
|
303
|
+
for (const [catId, category] of Object.entries(catalog.categories)) {
|
|
304
|
+
const found = category.masks.find(m => m.id === maskId);
|
|
305
|
+
if (found) {
|
|
306
|
+
entry = found;
|
|
307
|
+
categoryId = catId;
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (!entry || !categoryId)
|
|
312
|
+
return null;
|
|
313
|
+
const filePath = path.join(this.masksDir, entry.file);
|
|
314
|
+
if (!fs.existsSync(filePath))
|
|
315
|
+
throw new Error(`File not found: ${filePath}`);
|
|
316
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
317
|
+
const parsed = filePath.endsWith('.yaml') || filePath.endsWith('.yml')
|
|
318
|
+
? parseSimpleYaml(content) : JSON.parse(content);
|
|
319
|
+
const loadedMask = { ...parsed, category: categoryId, filePath };
|
|
320
|
+
this.cache.set(maskId, loadedMask);
|
|
321
|
+
return loadedMask;
|
|
322
|
+
}
|
|
323
|
+
async listAll() {
|
|
324
|
+
const catalog = await this.loadCatalog();
|
|
325
|
+
const result = [];
|
|
326
|
+
for (const [categoryId, category] of Object.entries(catalog.categories)) {
|
|
327
|
+
for (const mask of category.masks) {
|
|
328
|
+
// Filter out disabled masks
|
|
329
|
+
if (isMaskEnabled(this.config, mask.id)) {
|
|
330
|
+
result.push({ ...mask, category: categoryId });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return result;
|
|
335
|
+
}
|
|
336
|
+
async listCategories() {
|
|
337
|
+
const catalog = await this.loadCatalog();
|
|
338
|
+
return Object.entries(catalog.categories).map(([id, cat]) => {
|
|
339
|
+
// Count only enabled masks
|
|
340
|
+
const enabledMasks = cat.masks.filter(m => isMaskEnabled(this.config, m.id));
|
|
341
|
+
return {
|
|
342
|
+
id,
|
|
343
|
+
name: cat.name,
|
|
344
|
+
description: cat.description,
|
|
345
|
+
count: enabledMasks.length,
|
|
346
|
+
};
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// ============================================================================
|
|
351
|
+
// Prompt Builder
|
|
352
|
+
// ============================================================================
|
|
353
|
+
function buildRichPrompt(mask) {
|
|
354
|
+
const parts = [];
|
|
355
|
+
parts.push(`You are ${mask.profile.name}.`);
|
|
356
|
+
parts.push(`${mask.profile.tagline}`);
|
|
357
|
+
parts.push('');
|
|
358
|
+
parts.push('BACKGROUND:');
|
|
359
|
+
parts.push(mask.profile.background.trim());
|
|
360
|
+
parts.push('');
|
|
361
|
+
parts.push('YOUR EXPERTISE:');
|
|
362
|
+
for (const exp of mask.profile.expertise)
|
|
363
|
+
parts.push(`- ${exp}`);
|
|
364
|
+
parts.push('');
|
|
365
|
+
parts.push('YOUR THINKING STYLE:');
|
|
366
|
+
parts.push(mask.profile.thinkingStyle.trim());
|
|
367
|
+
parts.push('');
|
|
368
|
+
parts.push('INSTRUCTIONS:');
|
|
369
|
+
parts.push(mask.behavior.systemPrompt.trim());
|
|
370
|
+
parts.push('');
|
|
371
|
+
const style = mask.behavior.communicationStyle;
|
|
372
|
+
parts.push('COMMUNICATION STYLE:');
|
|
373
|
+
parts.push(`- Tone: ${style.tone}`);
|
|
374
|
+
parts.push(`- Verbosity: ${style.verbosity}`);
|
|
375
|
+
parts.push(`- Technical depth: ${style.technicalDepth}`);
|
|
376
|
+
parts.push('');
|
|
377
|
+
parts.push('YOUR STRENGTHS:');
|
|
378
|
+
for (const strength of mask.profile.strengths)
|
|
379
|
+
parts.push(`- ${strength}`);
|
|
380
|
+
if (mask.profile.limitations?.length) {
|
|
381
|
+
parts.push('');
|
|
382
|
+
parts.push('ACKNOWLEDGE YOUR LIMITATIONS:');
|
|
383
|
+
for (const limitation of mask.profile.limitations)
|
|
384
|
+
parts.push(`- ${limitation}`);
|
|
385
|
+
}
|
|
386
|
+
if (mask.behavior.signaturePhrases?.length) {
|
|
387
|
+
parts.push('');
|
|
388
|
+
parts.push('PHRASES YOU MIGHT USE:');
|
|
389
|
+
for (const phrase of mask.behavior.signaturePhrases)
|
|
390
|
+
parts.push(`- "${phrase}"`);
|
|
391
|
+
}
|
|
392
|
+
return parts.join('\n');
|
|
393
|
+
}
|
|
394
|
+
// ============================================================================
|
|
395
|
+
// Tool Factory Functions (oh-my-opencode pattern)
|
|
396
|
+
// ============================================================================
|
|
397
|
+
function createListMasksTool(maskLoader, activeMask) {
|
|
398
|
+
return {
|
|
399
|
+
description: 'List all available expert persona masks.',
|
|
400
|
+
args: z.object({
|
|
401
|
+
category: z.string().optional().describe('Filter by category'),
|
|
402
|
+
}),
|
|
403
|
+
async execute(args) {
|
|
404
|
+
try {
|
|
405
|
+
const masks = await maskLoader.listAll();
|
|
406
|
+
const categories = await maskLoader.listCategories();
|
|
407
|
+
let filtered = masks;
|
|
408
|
+
if (args.category) {
|
|
409
|
+
filtered = masks.filter(m => m.category === args.category);
|
|
410
|
+
}
|
|
411
|
+
const lines = [];
|
|
412
|
+
lines.push(`Maskweaver v0.6.0 - ${filtered.length} masks available`);
|
|
413
|
+
const active = activeMask();
|
|
414
|
+
lines.push(`Active mask: ${active?.metadata.id || 'none'}`);
|
|
415
|
+
lines.push('');
|
|
416
|
+
lines.push('Categories:');
|
|
417
|
+
for (const cat of categories) {
|
|
418
|
+
lines.push(` - ${cat.id}: ${cat.name} (${cat.count} masks)`);
|
|
419
|
+
}
|
|
420
|
+
lines.push('');
|
|
421
|
+
lines.push('Masks:');
|
|
422
|
+
for (const mask of filtered) {
|
|
423
|
+
lines.push(` - ${mask.id}: ${mask.name} [${mask.category}]`);
|
|
424
|
+
}
|
|
425
|
+
return lines.join('\n');
|
|
426
|
+
}
|
|
427
|
+
catch (e) {
|
|
428
|
+
return `Error: ${e}`;
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
function createSelectMaskTool(maskLoader, activeMask, setActiveMask) {
|
|
434
|
+
return {
|
|
435
|
+
description: 'Select and apply an expert persona mask.',
|
|
436
|
+
args: z.object({
|
|
437
|
+
maskId: z.string().describe('Mask ID (e.g., "kent-beck")'),
|
|
438
|
+
}),
|
|
439
|
+
async execute(args) {
|
|
440
|
+
try {
|
|
441
|
+
const mask = await maskLoader.load(args.maskId);
|
|
442
|
+
if (!mask) {
|
|
443
|
+
const available = await maskLoader.listAll();
|
|
444
|
+
return `Error: Mask "${args.maskId}" not found.\nAvailable: ${available.map(m => m.id).join(', ')}`;
|
|
445
|
+
}
|
|
446
|
+
setActiveMask(mask);
|
|
447
|
+
return `✓ Mask activated: ${mask.profile.name}
|
|
448
|
+
|
|
449
|
+
"${mask.profile.tagline}"
|
|
450
|
+
|
|
451
|
+
Expertise: ${mask.profile.expertise.join(', ')}
|
|
452
|
+
|
|
453
|
+
The mask prompt will be injected into all future messages.`;
|
|
454
|
+
}
|
|
455
|
+
catch (e) {
|
|
456
|
+
return `Error: ${e}`;
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
function createDeselectMaskTool(activeMask, setActiveMask) {
|
|
462
|
+
return {
|
|
463
|
+
description: 'Remove the current mask and return to default behavior.',
|
|
464
|
+
args: z.object({}),
|
|
465
|
+
async execute() {
|
|
466
|
+
const prev = activeMask();
|
|
467
|
+
setActiveMask(null);
|
|
468
|
+
if (prev) {
|
|
469
|
+
return `✓ Mask removed: ${prev.profile.name}\nReturned to default behavior.`;
|
|
470
|
+
}
|
|
471
|
+
return 'No mask was active.';
|
|
472
|
+
},
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function createGetMaskPromptTool(maskLoader, activeMask) {
|
|
476
|
+
return {
|
|
477
|
+
description: 'View the full system prompt for a mask.',
|
|
478
|
+
args: z.object({
|
|
479
|
+
maskId: z.string().optional().describe('Mask ID (uses active mask if not specified)'),
|
|
480
|
+
}),
|
|
481
|
+
async execute(args) {
|
|
482
|
+
const maskId = args.maskId || activeMask()?.metadata.id;
|
|
483
|
+
if (!maskId)
|
|
484
|
+
return 'Error: No mask specified and no active mask.';
|
|
485
|
+
try {
|
|
486
|
+
const mask = await maskLoader.load(maskId);
|
|
487
|
+
if (!mask)
|
|
488
|
+
return `Error: Mask "${maskId}" not found.`;
|
|
489
|
+
return `# ${mask.profile.name}\n\n${buildRichPrompt(mask)}`;
|
|
490
|
+
}
|
|
491
|
+
catch (e) {
|
|
492
|
+
return `Error: ${e}`;
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
function createMaskweaverStatusTool(maskLoader, masksDir, activeMask) {
|
|
498
|
+
return {
|
|
499
|
+
description: 'Check Maskweaver status.',
|
|
500
|
+
args: z.object({}),
|
|
501
|
+
async execute() {
|
|
502
|
+
let masksCount = 0;
|
|
503
|
+
let categoriesCount = 0;
|
|
504
|
+
if (maskLoader) {
|
|
505
|
+
try {
|
|
506
|
+
const masks = await maskLoader.listAll();
|
|
507
|
+
const categories = await maskLoader.listCategories();
|
|
508
|
+
masksCount = masks.length;
|
|
509
|
+
categoriesCount = categories.length;
|
|
510
|
+
}
|
|
511
|
+
catch (_e) { /* ignore */ }
|
|
512
|
+
}
|
|
513
|
+
const active = activeMask();
|
|
514
|
+
return `Maskweaver v0.6.0
|
|
515
|
+
Masks directory: ${masksDir}
|
|
516
|
+
Available: ${maskLoader ? 'yes' : 'no'}
|
|
517
|
+
Total masks: ${masksCount}
|
|
518
|
+
Categories: ${categoriesCount}
|
|
519
|
+
Active mask: ${active ? `${active.profile.name} (${active.metadata.id})` : 'none'}`;
|
|
520
|
+
},
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
let state = null;
|
|
524
|
+
// ============================================================================
|
|
525
|
+
// Plugin (oh-my-opencode pattern)
|
|
526
|
+
// ============================================================================
|
|
527
|
+
export const MaskweaverPlugin = async ({ client, directory }) => {
|
|
528
|
+
// ==========================================================================
|
|
529
|
+
// 1. Load Configuration (oh-my-opencode pattern)
|
|
530
|
+
// ==========================================================================
|
|
531
|
+
const pluginConfig = loadPluginConfig(directory, { client, verbose: false });
|
|
532
|
+
// Validate configuration
|
|
533
|
+
const configErrors = validateConfig(pluginConfig);
|
|
534
|
+
if (configErrors.length > 0) {
|
|
535
|
+
client.app.log({
|
|
536
|
+
service: 'maskweaver',
|
|
537
|
+
level: 'warn',
|
|
538
|
+
message: `Configuration validation errors: ${configErrors.join(', ')}`,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
const verbose = isVerboseLoggingEnabled(pluginConfig);
|
|
542
|
+
// ==========================================================================
|
|
543
|
+
// 2. Auto-install assets on first run
|
|
544
|
+
// ==========================================================================
|
|
545
|
+
const installResult = installAssets(directory);
|
|
546
|
+
if (installResult.installed.length > 0) {
|
|
547
|
+
client.app.log({
|
|
548
|
+
service: 'maskweaver',
|
|
549
|
+
level: 'info',
|
|
550
|
+
message: `Installed ${installResult.installed.length} files to .opencode/ (agents, masks)`,
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
if (installResult.errors.length > 0) {
|
|
554
|
+
client.app.log({
|
|
555
|
+
service: 'maskweaver',
|
|
556
|
+
level: 'warn',
|
|
557
|
+
message: `Asset errors: ${installResult.errors.join(', ')}`,
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
// ==========================================================================
|
|
561
|
+
// 3. Initialize masks
|
|
562
|
+
// ==========================================================================
|
|
563
|
+
const homeDir = os.homedir();
|
|
564
|
+
const globalMasksDir = path.join(homeDir, '.config', 'opencode', 'masks');
|
|
565
|
+
const projectMasksDir = path.join(directory, '.opencode', 'masks');
|
|
566
|
+
// Priority: project masks > global masks
|
|
567
|
+
const masksDir = fs.existsSync(projectMasksDir) ? projectMasksDir : globalMasksDir;
|
|
568
|
+
state = {
|
|
569
|
+
maskLoader: null,
|
|
570
|
+
activeMask: null,
|
|
571
|
+
masksDir,
|
|
572
|
+
config: pluginConfig,
|
|
573
|
+
};
|
|
574
|
+
// Log plugin loaded
|
|
575
|
+
client.app.log({
|
|
576
|
+
service: 'maskweaver',
|
|
577
|
+
level: 'info',
|
|
578
|
+
message: `Maskweaver plugin loaded v0.6.0 (oh-my-opencode pattern)`,
|
|
579
|
+
});
|
|
580
|
+
if (fs.existsSync(masksDir)) {
|
|
581
|
+
state.maskLoader = new MaskLoader(masksDir, pluginConfig);
|
|
582
|
+
try {
|
|
583
|
+
await state.maskLoader.loadCatalog();
|
|
584
|
+
if (verbose) {
|
|
585
|
+
client.app.log({
|
|
586
|
+
service: 'maskweaver',
|
|
587
|
+
level: 'info',
|
|
588
|
+
message: `Masks found at: ${masksDir}`,
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
catch (e) {
|
|
593
|
+
client.app.log({
|
|
594
|
+
service: 'maskweaver',
|
|
595
|
+
level: 'warn',
|
|
596
|
+
message: `Failed to load masks: ${e}`,
|
|
597
|
+
});
|
|
598
|
+
state.maskLoader = null;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
// ==========================================================================
|
|
602
|
+
// 4. Auto-activate default mask (oh-my-opencode pattern)
|
|
603
|
+
// ==========================================================================
|
|
604
|
+
const defaultMaskId = getDefaultMask(pluginConfig);
|
|
605
|
+
const autoActivate = isAutoActivateEnabled(pluginConfig);
|
|
606
|
+
if (defaultMaskId && autoActivate && state.maskLoader) {
|
|
607
|
+
try {
|
|
608
|
+
const defaultMask = await state.maskLoader.load(defaultMaskId);
|
|
609
|
+
if (defaultMask) {
|
|
610
|
+
state.activeMask = defaultMask;
|
|
611
|
+
client.app.log({
|
|
612
|
+
service: 'maskweaver',
|
|
613
|
+
level: 'info',
|
|
614
|
+
message: `Auto-activated default mask: ${defaultMaskId} (${defaultMask.profile.name})`,
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
else {
|
|
618
|
+
client.app.log({
|
|
619
|
+
service: 'maskweaver',
|
|
620
|
+
level: 'warn',
|
|
621
|
+
message: `Default mask "${defaultMaskId}" not found or disabled`,
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
catch (e) {
|
|
626
|
+
client.app.log({
|
|
627
|
+
service: 'maskweaver',
|
|
628
|
+
level: 'warn',
|
|
629
|
+
message: `Failed to auto-activate default mask: ${e}`,
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
// ==========================================================================
|
|
634
|
+
// 5. Helper functions for tool factories
|
|
635
|
+
// ==========================================================================
|
|
636
|
+
const getActiveMask = () => state?.activeMask || null;
|
|
637
|
+
const setActiveMask = (mask) => {
|
|
638
|
+
if (state)
|
|
639
|
+
state.activeMask = mask;
|
|
640
|
+
};
|
|
641
|
+
// ==========================================================================
|
|
642
|
+
// 6. Conditional tool registration (oh-my-opencode pattern)
|
|
643
|
+
// ==========================================================================
|
|
644
|
+
const isToolActive = (toolName) => isToolEnabled(pluginConfig, toolName);
|
|
645
|
+
const tools = {};
|
|
646
|
+
if (state.maskLoader) {
|
|
647
|
+
if (isToolActive('list_masks')) {
|
|
648
|
+
tools.list_masks = createListMasksTool(state.maskLoader, getActiveMask);
|
|
649
|
+
}
|
|
650
|
+
if (isToolActive('select_mask')) {
|
|
651
|
+
tools.select_mask = createSelectMaskTool(state.maskLoader, getActiveMask, setActiveMask);
|
|
652
|
+
}
|
|
653
|
+
if (isToolActive('deselect_mask')) {
|
|
654
|
+
tools.deselect_mask = createDeselectMaskTool(getActiveMask, setActiveMask);
|
|
655
|
+
}
|
|
656
|
+
if (isToolActive('get_mask_prompt')) {
|
|
657
|
+
tools.get_mask_prompt = createGetMaskPromptTool(state.maskLoader, getActiveMask);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
if (isToolActive('maskweaver_status')) {
|
|
661
|
+
tools.maskweaver_status = createMaskweaverStatusTool(state.maskLoader, masksDir, getActiveMask);
|
|
662
|
+
}
|
|
663
|
+
// Memory tools
|
|
664
|
+
if (isToolActive('memory_search')) {
|
|
665
|
+
const memorySearchTool = createMemorySearchTool();
|
|
666
|
+
tools.memory_search = {
|
|
667
|
+
description: memorySearchTool.description,
|
|
668
|
+
args: memorySearchTool.args,
|
|
669
|
+
execute: (args) => memorySearchTool.execute(args, { worktree: directory }),
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
if (isToolActive('memory_write')) {
|
|
673
|
+
const memoryWriteTool = createMemoryWriteTool();
|
|
674
|
+
tools.memory_write = {
|
|
675
|
+
description: memoryWriteTool.description,
|
|
676
|
+
args: memoryWriteTool.args,
|
|
677
|
+
execute: (args) => memoryWriteTool.execute(args, { worktree: directory }),
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
if (isToolActive('memory_get')) {
|
|
681
|
+
const memoryGetTool = createMemoryGetTool();
|
|
682
|
+
tools.memory_get = {
|
|
683
|
+
description: memoryGetTool.description,
|
|
684
|
+
args: memoryGetTool.args,
|
|
685
|
+
execute: (args) => memoryGetTool.execute(args, { worktree: directory }),
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
if (isToolActive('memory_indexer')) {
|
|
689
|
+
const memoryIndexerTool = createMemoryIndexerTool();
|
|
690
|
+
tools.memory_indexer = {
|
|
691
|
+
description: memoryIndexerTool.description,
|
|
692
|
+
args: memoryIndexerTool.args,
|
|
693
|
+
execute: (args) => memoryIndexerTool.execute(args, { worktree: directory }),
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
// Context tool
|
|
697
|
+
if (isToolActive('context')) {
|
|
698
|
+
const contextTool = createContextTool();
|
|
699
|
+
tools.context = {
|
|
700
|
+
description: contextTool.description,
|
|
701
|
+
args: contextTool.args,
|
|
702
|
+
execute: (args) => contextTool.execute(args, { worktree: directory }),
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
// Retrospect tool
|
|
706
|
+
if (isToolActive('retrospect')) {
|
|
707
|
+
const retrospectTool = createRetrospectTool();
|
|
708
|
+
tools.retrospect = {
|
|
709
|
+
description: retrospectTool.description,
|
|
710
|
+
args: retrospectTool.args,
|
|
711
|
+
execute: (args) => retrospectTool.execute(args, { worktree: directory }),
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
// Mask save tool
|
|
715
|
+
if (isToolActive('mask_save')) {
|
|
716
|
+
const maskSaveTool = createMaskSaveTool();
|
|
717
|
+
tools.mask_save = {
|
|
718
|
+
description: maskSaveTool.description,
|
|
719
|
+
args: maskSaveTool.args,
|
|
720
|
+
execute: (args) => maskSaveTool.execute(args, { worktree: directory }),
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
// ==========================================================================
|
|
724
|
+
// 7. Return plugin hooks
|
|
725
|
+
// ==========================================================================
|
|
726
|
+
return {
|
|
727
|
+
// System prompt transform - inject active mask
|
|
728
|
+
'experimental.chat.system.transform': async (_input, output) => {
|
|
729
|
+
if (state?.activeMask) {
|
|
730
|
+
const maskPrompt = `<ACTIVE_PERSONA>
|
|
731
|
+
You are currently embodying the "${state.activeMask.profile.name}" persona.
|
|
732
|
+
|
|
733
|
+
${buildRichPrompt(state.activeMask)}
|
|
734
|
+
</ACTIVE_PERSONA>`;
|
|
735
|
+
(output.system ||= []).push(maskPrompt);
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
// Conditional tools
|
|
739
|
+
tool: tools,
|
|
740
|
+
// Event hooks (oh-my-opencode pattern)
|
|
741
|
+
event: async ({ event }) => {
|
|
742
|
+
// Session created - log available masks
|
|
743
|
+
if (event.type === 'session.created') {
|
|
744
|
+
if (state?.maskLoader && verbose) {
|
|
745
|
+
try {
|
|
746
|
+
const masks = await state.maskLoader.listAll();
|
|
747
|
+
const categories = await state.maskLoader.listCategories();
|
|
748
|
+
client.app.log({
|
|
749
|
+
service: 'maskweaver',
|
|
750
|
+
level: 'info',
|
|
751
|
+
message: `Session started - ${masks.length} masks available across ${categories.length} categories`,
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
catch (_e) {
|
|
755
|
+
// Ignore errors
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
// Session deleted - cleanup
|
|
760
|
+
if (event.type === 'session.deleted') {
|
|
761
|
+
if (state && verbose) {
|
|
762
|
+
const wasActive = state.activeMask !== null;
|
|
763
|
+
state.activeMask = null;
|
|
764
|
+
if (wasActive) {
|
|
765
|
+
client.app.log({
|
|
766
|
+
service: 'maskweaver',
|
|
767
|
+
level: 'info',
|
|
768
|
+
message: 'Session ended - active mask cleared',
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
},
|
|
774
|
+
// Config hook - agent overrides (oh-my-opencode pattern)
|
|
775
|
+
config: {
|
|
776
|
+
agent: async (agentConfig, context) => {
|
|
777
|
+
// Apply agent overrides from configuration
|
|
778
|
+
const agentName = context?.name;
|
|
779
|
+
if (!agentName)
|
|
780
|
+
return agentConfig;
|
|
781
|
+
const override = getAgentOverride(pluginConfig, agentName);
|
|
782
|
+
if (!override)
|
|
783
|
+
return agentConfig;
|
|
784
|
+
const modified = { ...agentConfig };
|
|
785
|
+
if (override.model) {
|
|
786
|
+
modified.model = override.model;
|
|
787
|
+
if (verbose) {
|
|
788
|
+
client.app.log({
|
|
789
|
+
service: 'maskweaver',
|
|
790
|
+
level: 'info',
|
|
791
|
+
message: `Agent "${agentName}" model overridden: ${override.model}`,
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (override.systemPrompt) {
|
|
796
|
+
modified.systemPrompt = override.systemPrompt;
|
|
797
|
+
if (verbose) {
|
|
798
|
+
client.app.log({
|
|
799
|
+
service: 'maskweaver',
|
|
800
|
+
level: 'info',
|
|
801
|
+
message: `Agent "${agentName}" system prompt overridden`,
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return modified;
|
|
806
|
+
},
|
|
807
|
+
},
|
|
808
|
+
};
|
|
809
|
+
};
|
|
810
|
+
export default MaskweaverPlugin;
|
|
811
|
+
//# sourceMappingURL=index.js.map
|