opencode-dux 1.0.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/LICENSE +21 -0
- package/README.md +452 -0
- package/dist/agents/descriptions.d.ts +6 -0
- package/dist/agents/designer.d.ts +2 -0
- package/dist/agents/explorer.d.ts +2 -0
- package/dist/agents/fixer.d.ts +2 -0
- package/dist/agents/index.d.ts +22 -0
- package/dist/agents/interpreter.d.ts +2 -0
- package/dist/agents/librarian.d.ts +2 -0
- package/dist/agents/oracle.d.ts +2 -0
- package/dist/agents/orchestrator.d.ts +27 -0
- package/dist/agents/overrides.d.ts +18 -0
- package/dist/agents/prompt-blocks.d.ts +97 -0
- package/dist/agents/steward.d.ts +3 -0
- package/dist/cli/config-io.d.ts +24 -0
- package/dist/cli/config-manager.d.ts +4 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +1006 -0
- package/dist/cli/install.d.ts +2 -0
- package/dist/cli/mcps.d.ts +13 -0
- package/dist/cli/model-key-normalization.d.ts +1 -0
- package/dist/cli/paths.d.ts +35 -0
- package/dist/cli/providers.d.ts +137 -0
- package/dist/cli/skills.d.ts +22 -0
- package/dist/cli/system.d.ts +5 -0
- package/dist/cli/types.d.ts +38 -0
- package/dist/config/constants.d.ts +12 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/loader.d.ts +40 -0
- package/dist/config/runtime-preset.d.ts +12 -0
- package/dist/config/schema.d.ts +281 -0
- package/dist/config/utils.d.ts +10 -0
- package/dist/discovery/local/types.d.ts +79 -0
- package/dist/discovery/local.d.ts +73 -0
- package/dist/discovery/mcp-servers.d.ts +88 -0
- package/dist/discovery/skills.d.ts +94 -0
- package/dist/hooks/apply-patch/codec.d.ts +7 -0
- package/dist/hooks/apply-patch/errors.d.ts +25 -0
- package/dist/hooks/apply-patch/execution-context.d.ts +27 -0
- package/dist/hooks/apply-patch/index.d.ts +15 -0
- package/dist/hooks/apply-patch/matching.d.ts +26 -0
- package/dist/hooks/apply-patch/operations.d.ts +3 -0
- package/dist/hooks/apply-patch/patch.d.ts +2 -0
- package/dist/hooks/apply-patch/prepared-changes.d.ts +17 -0
- package/dist/hooks/apply-patch/resolution.d.ts +19 -0
- package/dist/hooks/apply-patch/rewrite.d.ts +7 -0
- package/dist/hooks/apply-patch/test-helpers.d.ts +6 -0
- package/dist/hooks/apply-patch/types.d.ts +80 -0
- package/dist/hooks/auto-update-checker/cache.d.ts +11 -0
- package/dist/hooks/auto-update-checker/checker.d.ts +32 -0
- package/dist/hooks/auto-update-checker/constants.d.ts +11 -0
- package/dist/hooks/auto-update-checker/index.d.ts +18 -0
- package/dist/hooks/auto-update-checker/types.d.ts +22 -0
- package/dist/hooks/chat-headers.d.ts +16 -0
- package/dist/hooks/context-pressure-reminder/index.d.ts +33 -0
- package/dist/hooks/delegate-task-retry/guidance.d.ts +2 -0
- package/dist/hooks/delegate-task-retry/hook.d.ts +8 -0
- package/dist/hooks/delegate-task-retry/index.d.ts +4 -0
- package/dist/hooks/delegate-task-retry/patterns.d.ts +11 -0
- package/dist/hooks/filter-available-skills/index.d.ts +32 -0
- package/dist/hooks/foreground-fallback/index.d.ts +72 -0
- package/dist/hooks/image-hook.d.ts +5 -0
- package/dist/hooks/index.d.ts +14 -0
- package/dist/hooks/json-error-recovery/hook.d.ts +18 -0
- package/dist/hooks/json-error-recovery/index.d.ts +1 -0
- package/dist/hooks/phase-reminder/index.d.ts +26 -0
- package/dist/hooks/post-file-tool-nudge/index.d.ts +19 -0
- package/dist/hooks/task-session-manager/index.d.ts +52 -0
- package/dist/hooks/todo-continuation/index.d.ts +53 -0
- package/dist/hooks/todo-continuation/todo-hygiene.d.ts +35 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +31782 -0
- package/dist/mcp/context7.d.ts +6 -0
- package/dist/mcp/grep-app.d.ts +6 -0
- package/dist/mcp/index.d.ts +13 -0
- package/dist/mcp/types.d.ts +12 -0
- package/dist/mcp/websearch.d.ts +9 -0
- package/dist/skills/registry.d.ts +29 -0
- package/dist/subscriptions/accounts-store.d.ts +57 -0
- package/dist/subscriptions/index.d.ts +13 -0
- package/dist/subscriptions/neuralwatt-scraper.d.ts +14 -0
- package/dist/subscriptions/opencode-go-scraper.d.ts +27 -0
- package/dist/subscriptions/types.d.ts +115 -0
- package/dist/subscriptions/usage-service.d.ts +74 -0
- package/dist/tools/ast-grep/cli.d.ts +15 -0
- package/dist/tools/ast-grep/constants.d.ts +25 -0
- package/dist/tools/ast-grep/downloader.d.ts +5 -0
- package/dist/tools/ast-grep/index.d.ts +10 -0
- package/dist/tools/ast-grep/tools.d.ts +3 -0
- package/dist/tools/ast-grep/types.d.ts +30 -0
- package/dist/tools/ast-grep/utils.d.ts +4 -0
- package/dist/tools/delegate.d.ts +14 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/preset-manager.d.ts +27 -0
- package/dist/tools/smartfetch/binary.d.ts +3 -0
- package/dist/tools/smartfetch/cache.d.ts +6 -0
- package/dist/tools/smartfetch/constants.d.ts +12 -0
- package/dist/tools/smartfetch/index.d.ts +3 -0
- package/dist/tools/smartfetch/network.d.ts +38 -0
- package/dist/tools/smartfetch/secondary-model.d.ts +28 -0
- package/dist/tools/smartfetch/tool.d.ts +3 -0
- package/dist/tools/smartfetch/types.d.ts +122 -0
- package/dist/tools/smartfetch/utils.d.ts +18 -0
- package/dist/tui-state.d.ts +168 -0
- package/dist/tui.d.ts +37 -0
- package/dist/tui.js +1896 -0
- package/dist/utils/agent-variant.d.ts +63 -0
- package/dist/utils/compat.d.ts +30 -0
- package/dist/utils/env.d.ts +1 -0
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/internal-initiator.d.ts +6 -0
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/polling.d.ts +21 -0
- package/dist/utils/session-manager.d.ts +55 -0
- package/dist/utils/session.d.ts +90 -0
- package/dist/utils/subagent-depth.d.ts +35 -0
- package/dist/utils/system-collapse.d.ts +6 -0
- package/dist/utils/task.d.ts +4 -0
- package/dist/utils/zip-extractor.d.ts +1 -0
- package/index.ts +1 -0
- package/opencode-dux.schema.json +634 -0
- package/package.json +103 -0
- package/src/agents/descriptions.ts +55 -0
- package/src/agents/designer.test.ts +86 -0
- package/src/agents/designer.ts +154 -0
- package/src/agents/display-name.test.ts +186 -0
- package/src/agents/explorer.test.ts +79 -0
- package/src/agents/explorer.ts +144 -0
- package/src/agents/fixer.test.ts +79 -0
- package/src/agents/fixer.ts +145 -0
- package/src/agents/index.test.ts +472 -0
- package/src/agents/index.ts +248 -0
- package/src/agents/interpreter.ts +136 -0
- package/src/agents/librarian.test.ts +80 -0
- package/src/agents/librarian.ts +145 -0
- package/src/agents/oracle.test.ts +89 -0
- package/src/agents/oracle.ts +184 -0
- package/src/agents/orchestrator.test.ts +116 -0
- package/src/agents/orchestrator.ts +574 -0
- package/src/agents/overrides.ts +95 -0
- package/src/agents/prompt-blocks.test.ts +114 -0
- package/src/agents/prompt-blocks.ts +640 -0
- package/src/agents/steward.ts +146 -0
- package/src/cli/config-io.test.ts +536 -0
- package/src/cli/config-io.ts +473 -0
- package/src/cli/config-manager.test.ts +141 -0
- package/src/cli/config-manager.ts +4 -0
- package/src/cli/index.ts +88 -0
- package/src/cli/install.ts +282 -0
- package/src/cli/mcps.test.ts +62 -0
- package/src/cli/mcps.ts +39 -0
- package/src/cli/model-key-normalization.test.ts +21 -0
- package/src/cli/model-key-normalization.ts +60 -0
- package/src/cli/paths.test.ts +167 -0
- package/src/cli/paths.ts +144 -0
- package/src/cli/providers.test.ts +118 -0
- package/src/cli/providers.ts +141 -0
- package/src/cli/skills.test.ts +111 -0
- package/src/cli/skills.ts +103 -0
- package/src/cli/system.test.ts +91 -0
- package/src/cli/system.ts +180 -0
- package/src/cli/types.ts +43 -0
- package/src/config/constants.ts +58 -0
- package/src/config/index.ts +4 -0
- package/src/config/loader.test.ts +1194 -0
- package/src/config/loader.ts +269 -0
- package/src/config/model-resolution.test.ts +176 -0
- package/src/config/runtime-preset.test.ts +61 -0
- package/src/config/runtime-preset.ts +37 -0
- package/src/config/schema.ts +248 -0
- package/src/config/utils.test.ts +41 -0
- package/src/config/utils.ts +23 -0
- package/src/discovery/local/types.ts +85 -0
- package/src/discovery/local.ts +322 -0
- package/src/discovery/mcp-servers.ts +804 -0
- package/src/discovery/skills.ts +959 -0
- package/src/hooks/apply-patch/codec.test.ts +184 -0
- package/src/hooks/apply-patch/codec.ts +352 -0
- package/src/hooks/apply-patch/errors.ts +117 -0
- package/src/hooks/apply-patch/execution-context.ts +432 -0
- package/src/hooks/apply-patch/hook.test.ts +768 -0
- package/src/hooks/apply-patch/index.ts +126 -0
- package/src/hooks/apply-patch/matching.test.ts +215 -0
- package/src/hooks/apply-patch/matching.ts +586 -0
- package/src/hooks/apply-patch/operations.test.ts +1535 -0
- package/src/hooks/apply-patch/operations.ts +3 -0
- package/src/hooks/apply-patch/patch.ts +9 -0
- package/src/hooks/apply-patch/prepared-changes.ts +400 -0
- package/src/hooks/apply-patch/resolution.test.ts +420 -0
- package/src/hooks/apply-patch/resolution.ts +437 -0
- package/src/hooks/apply-patch/rewrite.ts +496 -0
- package/src/hooks/apply-patch/test-helpers.ts +52 -0
- package/src/hooks/apply-patch/types.ts +111 -0
- package/src/hooks/auto-update-checker/cache.test.ts +179 -0
- package/src/hooks/auto-update-checker/cache.ts +188 -0
- package/src/hooks/auto-update-checker/checker.test.ts +159 -0
- package/src/hooks/auto-update-checker/checker.ts +308 -0
- package/src/hooks/auto-update-checker/constants.ts +33 -0
- package/src/hooks/auto-update-checker/index.test.ts +282 -0
- package/src/hooks/auto-update-checker/index.ts +225 -0
- package/src/hooks/auto-update-checker/types.ts +26 -0
- package/src/hooks/chat-headers.test.ts +236 -0
- package/src/hooks/chat-headers.ts +97 -0
- package/src/hooks/context-pressure-reminder/index.test.ts +179 -0
- package/src/hooks/context-pressure-reminder/index.ts +137 -0
- package/src/hooks/delegate-task-retry/guidance.ts +41 -0
- package/src/hooks/delegate-task-retry/hook.ts +23 -0
- package/src/hooks/delegate-task-retry/index.test.ts +38 -0
- package/src/hooks/delegate-task-retry/index.ts +7 -0
- package/src/hooks/delegate-task-retry/patterns.ts +79 -0
- package/src/hooks/filter-available-skills/index.test.ts +297 -0
- package/src/hooks/filter-available-skills/index.ts +160 -0
- package/src/hooks/foreground-fallback/index.test.ts +624 -0
- package/src/hooks/foreground-fallback/index.ts +374 -0
- package/src/hooks/image-hook.ts +6 -0
- package/src/hooks/index.ts +17 -0
- package/src/hooks/json-error-recovery/hook.ts +73 -0
- package/src/hooks/json-error-recovery/index.test.ts +111 -0
- package/src/hooks/json-error-recovery/index.ts +6 -0
- package/src/hooks/phase-reminder/index.test.ts +74 -0
- package/src/hooks/phase-reminder/index.ts +85 -0
- package/src/hooks/post-file-tool-nudge/index.test.ts +94 -0
- package/src/hooks/post-file-tool-nudge/index.ts +63 -0
- package/src/hooks/task-session-manager/index.test.ts +833 -0
- package/src/hooks/task-session-manager/index.ts +434 -0
- package/src/hooks/todo-continuation/index.test.ts +3026 -0
- package/src/hooks/todo-continuation/index.ts +878 -0
- package/src/hooks/todo-continuation/todo-hygiene.test.ts +204 -0
- package/src/hooks/todo-continuation/todo-hygiene.ts +207 -0
- package/src/index.ts +1672 -0
- package/src/mcp/context7.ts +14 -0
- package/src/mcp/grep-app.ts +11 -0
- package/src/mcp/index.test.ts +96 -0
- package/src/mcp/index.ts +66 -0
- package/src/mcp/types.ts +16 -0
- package/src/mcp/websearch.ts +47 -0
- package/src/skills/codemap/README.md +60 -0
- package/src/skills/codemap/SKILL.md +174 -0
- package/src/skills/codemap/scripts/codemap.mjs +483 -0
- package/src/skills/codemap/scripts/codemap.test.ts +129 -0
- package/src/skills/registry.ts +218 -0
- package/src/skills/simplify/README.md +19 -0
- package/src/skills/simplify/SKILL.md +138 -0
- package/src/subscriptions/accounts-store.test.ts +236 -0
- package/src/subscriptions/accounts-store.ts +184 -0
- package/src/subscriptions/index.ts +30 -0
- package/src/subscriptions/neuralwatt-scraper.ts +108 -0
- package/src/subscriptions/opencode-go-scraper.ts +301 -0
- package/src/subscriptions/types.ts +145 -0
- package/src/subscriptions/usage-service.test.ts +202 -0
- package/src/subscriptions/usage-service.ts +651 -0
- package/src/tools/ast-grep/cli.ts +257 -0
- package/src/tools/ast-grep/constants.ts +214 -0
- package/src/tools/ast-grep/downloader.ts +131 -0
- package/src/tools/ast-grep/index.ts +24 -0
- package/src/tools/ast-grep/tools.ts +117 -0
- package/src/tools/ast-grep/types.ts +51 -0
- package/src/tools/ast-grep/utils.ts +126 -0
- package/src/tools/delegate-handoff.test.ts +18 -0
- package/src/tools/delegate.ts +508 -0
- package/src/tools/index.ts +8 -0
- package/src/tools/preset-manager.test.ts +795 -0
- package/src/tools/preset-manager.ts +332 -0
- package/src/tools/smartfetch/binary.ts +58 -0
- package/src/tools/smartfetch/cache.test.ts +34 -0
- package/src/tools/smartfetch/cache.ts +112 -0
- package/src/tools/smartfetch/constants.ts +29 -0
- package/src/tools/smartfetch/index.ts +8 -0
- package/src/tools/smartfetch/network.test.ts +178 -0
- package/src/tools/smartfetch/network.ts +614 -0
- package/src/tools/smartfetch/secondary-model.test.ts +85 -0
- package/src/tools/smartfetch/secondary-model.ts +276 -0
- package/src/tools/smartfetch/tool.test.ts +60 -0
- package/src/tools/smartfetch/tool.ts +832 -0
- package/src/tools/smartfetch/types.ts +135 -0
- package/src/tools/smartfetch/utils.test.ts +24 -0
- package/src/tools/smartfetch/utils.ts +456 -0
- package/src/tui-state.test.ts +867 -0
- package/src/tui-state.ts +1255 -0
- package/src/tui.test.ts +336 -0
- package/src/tui.ts +1539 -0
- package/src/utils/agent-variant.test.ts +244 -0
- package/src/utils/agent-variant.ts +187 -0
- package/src/utils/compat.ts +91 -0
- package/src/utils/env.ts +12 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/internal-initiator.ts +28 -0
- package/src/utils/logger.test.ts +220 -0
- package/src/utils/logger.ts +136 -0
- package/src/utils/polling.test.ts +191 -0
- package/src/utils/polling.ts +67 -0
- package/src/utils/session-manager.test.ts +173 -0
- package/src/utils/session-manager.ts +356 -0
- package/src/utils/session.test.ts +110 -0
- package/src/utils/session.ts +389 -0
- package/src/utils/subagent-depth.test.ts +170 -0
- package/src/utils/subagent-depth.ts +75 -0
- package/src/utils/system-collapse.test.ts +86 -0
- package/src/utils/system-collapse.ts +24 -0
- package/src/utils/task.test.ts +24 -0
- package/src/utils/task.ts +20 -0
- package/src/utils/zip-extractor.ts +102 -0
package/src/cli/paths.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
function getDefaultOpenCodeConfigDir(): string {
|
|
6
|
+
const userConfigDir = process.env.XDG_CONFIG_HOME
|
|
7
|
+
? process.env.XDG_CONFIG_HOME
|
|
8
|
+
: join(homedir(), '.config');
|
|
9
|
+
|
|
10
|
+
return join(userConfigDir, 'opencode');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getCustomOpenCodeConfigDir(): string | undefined {
|
|
14
|
+
const configDir = process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
15
|
+
return configDir || undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getCustomTuiConfigPath(): string | undefined {
|
|
19
|
+
const configPath = process.env.OPENCODE_TUI_CONFIG?.trim();
|
|
20
|
+
return configPath || undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the OpenCode plugin config directory.
|
|
25
|
+
*
|
|
26
|
+
* Resolution order:
|
|
27
|
+
* 1. OPENCODE_CONFIG_DIR (custom OpenCode directory)
|
|
28
|
+
* 2. XDG_CONFIG_HOME/opencode
|
|
29
|
+
* 3. ~/.config/opencode
|
|
30
|
+
*/
|
|
31
|
+
export function getConfigDir(): string {
|
|
32
|
+
const customConfigDir = getCustomOpenCodeConfigDir();
|
|
33
|
+
if (customConfigDir) {
|
|
34
|
+
return customConfigDir;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return getDefaultOpenCodeConfigDir();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get OpenCode config directories in read/search order.
|
|
42
|
+
*
|
|
43
|
+
* Resolution order:
|
|
44
|
+
* 1. OPENCODE_CONFIG_DIR (if set)
|
|
45
|
+
* 2. XDG_CONFIG_HOME/opencode or ~/.config/opencode
|
|
46
|
+
*
|
|
47
|
+
* Duplicate entries are removed.
|
|
48
|
+
*/
|
|
49
|
+
export function getConfigSearchDirs(): string[] {
|
|
50
|
+
const dirs = [getCustomOpenCodeConfigDir(), getDefaultOpenCodeConfigDir()];
|
|
51
|
+
|
|
52
|
+
return dirs.filter((dir, index): dir is string => {
|
|
53
|
+
return Boolean(dir) && dirs.indexOf(dir) === index;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getOpenCodeConfigPaths(): string[] {
|
|
58
|
+
const configDir = getDefaultOpenCodeConfigDir();
|
|
59
|
+
return [join(configDir, 'opencode.json'), join(configDir, 'opencode.jsonc')];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function getConfigJson(): string {
|
|
63
|
+
return getOpenCodeConfigPaths()[0];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getConfigJsonc(): string {
|
|
67
|
+
return getOpenCodeConfigPaths()[1];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getLiteConfig(): string {
|
|
71
|
+
return join(getConfigDir(), 'opencode-dux.json');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function getLiteConfigJsonc(): string {
|
|
75
|
+
return join(getConfigDir(), 'opencode-dux.jsonc');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function getTuiConfig(): string {
|
|
79
|
+
const customConfigPath = getCustomTuiConfigPath();
|
|
80
|
+
if (customConfigPath) return customConfigPath;
|
|
81
|
+
|
|
82
|
+
return join(getConfigDir(), 'tui.json');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function getTuiConfigJsonc(): string {
|
|
86
|
+
return join(getConfigDir(), 'tui.jsonc');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getExistingLiteConfigPath(): string {
|
|
90
|
+
const jsonPath = getLiteConfig();
|
|
91
|
+
if (existsSync(jsonPath)) return jsonPath;
|
|
92
|
+
|
|
93
|
+
const jsoncPath = getLiteConfigJsonc();
|
|
94
|
+
if (existsSync(jsoncPath)) return jsoncPath;
|
|
95
|
+
|
|
96
|
+
return jsonPath;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function getExistingTuiConfigPath(): string {
|
|
100
|
+
const customConfigPath = getCustomTuiConfigPath();
|
|
101
|
+
if (customConfigPath) return customConfigPath;
|
|
102
|
+
|
|
103
|
+
const jsonPath = join(getConfigDir(), 'tui.json');
|
|
104
|
+
if (existsSync(jsonPath)) return jsonPath;
|
|
105
|
+
|
|
106
|
+
const jsoncPath = getTuiConfigJsonc();
|
|
107
|
+
if (existsSync(jsoncPath)) return jsoncPath;
|
|
108
|
+
|
|
109
|
+
return jsonPath;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function getExistingConfigPath(): string {
|
|
113
|
+
const jsonPath = getConfigJson();
|
|
114
|
+
if (existsSync(jsonPath)) return jsonPath;
|
|
115
|
+
|
|
116
|
+
const jsoncPath = getConfigJsonc();
|
|
117
|
+
if (existsSync(jsoncPath)) return jsoncPath;
|
|
118
|
+
|
|
119
|
+
return jsonPath;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function ensureConfigDir(): void {
|
|
123
|
+
const configDir = getConfigDir();
|
|
124
|
+
if (!existsSync(configDir)) {
|
|
125
|
+
mkdirSync(configDir, { recursive: true });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function ensureTuiConfigDir(): void {
|
|
130
|
+
const configDir = dirname(getTuiConfig());
|
|
131
|
+
if (!existsSync(configDir)) {
|
|
132
|
+
mkdirSync(configDir, { recursive: true });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Ensure the directory for OpenCode's main config file exists.
|
|
138
|
+
*/
|
|
139
|
+
export function ensureOpenCodeConfigDir(): void {
|
|
140
|
+
const configDir = dirname(getConfigJson());
|
|
141
|
+
if (!existsSync(configDir)) {
|
|
142
|
+
mkdirSync(configDir, { recursive: true });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/// <reference types="bun-types" />
|
|
2
|
+
|
|
3
|
+
import { describe, expect, test } from 'bun:test';
|
|
4
|
+
import { generateLiteConfig, MODEL_MAPPINGS } from './providers';
|
|
5
|
+
|
|
6
|
+
describe('providers', () => {
|
|
7
|
+
test('MODEL_MAPPINGS includes supported providers', () => {
|
|
8
|
+
const keys = Object.keys(MODEL_MAPPINGS);
|
|
9
|
+
expect(keys.sort()).toEqual([
|
|
10
|
+
'copilot',
|
|
11
|
+
'kimi',
|
|
12
|
+
'openai',
|
|
13
|
+
'opencode-go',
|
|
14
|
+
'zai-plan',
|
|
15
|
+
]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('generateLiteConfig defaults to openai and includes generated presets', () => {
|
|
19
|
+
const config = generateLiteConfig({
|
|
20
|
+
installSkills: false,
|
|
21
|
+
installCustomSkills: false,
|
|
22
|
+
reset: false,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
expect(config.$schema).toBe(
|
|
26
|
+
'https://unpkg.com/opencode-dux@latest/opencode-dux.schema.json',
|
|
27
|
+
);
|
|
28
|
+
expect(config.preset).toBe('openai');
|
|
29
|
+
expect((config.presets as any)['opencode-go']).toBeDefined();
|
|
30
|
+
const agents = (config.presets as any).openai;
|
|
31
|
+
expect(agents).toBeDefined();
|
|
32
|
+
expect(agents.orchestrator.model).toBe('openai/gpt-5.5');
|
|
33
|
+
expect(agents.orchestrator.variant).toBeUndefined();
|
|
34
|
+
expect(agents.fixer.model).toBe('openai/gpt-5.4-mini');
|
|
35
|
+
expect(agents.fixer.variant).toBe('low');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('generateLiteConfig uses correct OpenAI models', () => {
|
|
39
|
+
const config = generateLiteConfig({
|
|
40
|
+
installSkills: false,
|
|
41
|
+
installCustomSkills: false,
|
|
42
|
+
reset: false,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const agents = (config.presets as any).openai;
|
|
46
|
+
expect(agents.orchestrator.model).toBe(
|
|
47
|
+
MODEL_MAPPINGS.openai.orchestrator.model,
|
|
48
|
+
);
|
|
49
|
+
expect(agents.oracle.model).toBe('openai/gpt-5.5');
|
|
50
|
+
expect(agents.oracle.variant).toBe('high');
|
|
51
|
+
expect(agents.librarian.model).toBe('openai/gpt-5.4-mini');
|
|
52
|
+
expect(agents.librarian.variant).toBe('low');
|
|
53
|
+
expect(agents.explorer.model).toBe('openai/gpt-5.4-mini');
|
|
54
|
+
expect(agents.explorer.variant).toBe('low');
|
|
55
|
+
expect(agents.designer.model).toBe('openai/gpt-5.4-mini');
|
|
56
|
+
expect(agents.designer.variant).toBe('medium');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('generateLiteConfig can set opencode-go as active preset', () => {
|
|
60
|
+
const config = generateLiteConfig({
|
|
61
|
+
installSkills: false,
|
|
62
|
+
installCustomSkills: false,
|
|
63
|
+
preset: 'opencode-go',
|
|
64
|
+
reset: false,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(config.preset).toBe('opencode-go');
|
|
68
|
+
expect((config.presets as any).openai).toBeDefined();
|
|
69
|
+
const agents = (config.presets as any)['opencode-go'];
|
|
70
|
+
expect(agents).toBeDefined();
|
|
71
|
+
expect(agents.orchestrator.model).toBe('neuralwatt/zai-org/GLM-5.1-FP8');
|
|
72
|
+
expect(agents.orchestrator.variant).toBe('medium');
|
|
73
|
+
expect(agents.oracle.model).toBe('opencode-go/deepseek-v4-flash');
|
|
74
|
+
expect(agents.oracle.variant).toBe('medium');
|
|
75
|
+
expect(agents.librarian.model).toBe('opencode-go/deepseek-v4-flash');
|
|
76
|
+
expect(agents.librarian.variant).toBe('low');
|
|
77
|
+
expect(agents.explorer.model).toBe('neuralwatt/qwen3.5-397b-fast');
|
|
78
|
+
expect(agents.explorer.variant).toBe('low');
|
|
79
|
+
expect(agents.designer.model).toBe('opencode-go/mimo-v2.5-pro');
|
|
80
|
+
expect(agents.fixer.model).toBe('opencode-go/deepseek-v4-flash');
|
|
81
|
+
expect(agents.fixer.variant).toBe('low');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('generateLiteConfig rejects unsupported preset', () => {
|
|
85
|
+
expect(() =>
|
|
86
|
+
generateLiteConfig({
|
|
87
|
+
installSkills: false,
|
|
88
|
+
installCustomSkills: false,
|
|
89
|
+
preset: 'not-real',
|
|
90
|
+
reset: false,
|
|
91
|
+
}),
|
|
92
|
+
).toThrow('Unsupported preset "not-real"');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('generateLiteConfig rejects non-generated model mappings as active presets', () => {
|
|
96
|
+
expect(() =>
|
|
97
|
+
generateLiteConfig({
|
|
98
|
+
installSkills: false,
|
|
99
|
+
installCustomSkills: false,
|
|
100
|
+
preset: 'kimi',
|
|
101
|
+
reset: false,
|
|
102
|
+
}),
|
|
103
|
+
).toThrow('Unsupported preset "kimi"');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('generateLiteConfig rejects inherited property names as presets', () => {
|
|
107
|
+
expect(() =>
|
|
108
|
+
generateLiteConfig({
|
|
109
|
+
installSkills: false,
|
|
110
|
+
installCustomSkills: false,
|
|
111
|
+
preset: 'toString',
|
|
112
|
+
reset: false,
|
|
113
|
+
}),
|
|
114
|
+
).toThrow('Unsupported preset "toString"');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { InstallConfig } from './types';
|
|
2
|
+
|
|
3
|
+
const SCHEMA_URL =
|
|
4
|
+
'https://unpkg.com/opencode-dux@latest/opencode-dux.schema.json';
|
|
5
|
+
|
|
6
|
+
export const GENERATED_PRESETS = ['openai', 'opencode-go'] as const;
|
|
7
|
+
|
|
8
|
+
// Model mappings by provider/preset.
|
|
9
|
+
export const MODEL_MAPPINGS = {
|
|
10
|
+
openai: {
|
|
11
|
+
orchestrator: { model: 'openai/gpt-5.5' },
|
|
12
|
+
oracle: { model: 'openai/gpt-5.5', variant: 'high' },
|
|
13
|
+
librarian: { model: 'openai/gpt-5.4-mini', variant: 'low' },
|
|
14
|
+
explorer: { model: 'openai/gpt-5.4-mini', variant: 'low' },
|
|
15
|
+
designer: { model: 'openai/gpt-5.4-mini', variant: 'medium' },
|
|
16
|
+
fixer: { model: 'openai/gpt-5.4-mini', variant: 'low' },
|
|
17
|
+
},
|
|
18
|
+
kimi: {
|
|
19
|
+
orchestrator: { model: 'kimi-for-coding/k2p5' },
|
|
20
|
+
oracle: { model: 'kimi-for-coding/k2p5', variant: 'high' },
|
|
21
|
+
librarian: { model: 'kimi-for-coding/k2p5', variant: 'low' },
|
|
22
|
+
explorer: { model: 'kimi-for-coding/k2p5', variant: 'low' },
|
|
23
|
+
designer: { model: 'kimi-for-coding/k2p5', variant: 'medium' },
|
|
24
|
+
fixer: { model: 'kimi-for-coding/k2p5', variant: 'low' },
|
|
25
|
+
},
|
|
26
|
+
copilot: {
|
|
27
|
+
orchestrator: { model: 'github-copilot/claude-opus-4.6' },
|
|
28
|
+
oracle: { model: 'github-copilot/claude-opus-4.6', variant: 'high' },
|
|
29
|
+
librarian: { model: 'github-copilot/grok-code-fast-1', variant: 'low' },
|
|
30
|
+
explorer: { model: 'github-copilot/grok-code-fast-1', variant: 'low' },
|
|
31
|
+
designer: {
|
|
32
|
+
model: 'github-copilot/gemini-3.1-pro-preview',
|
|
33
|
+
variant: 'medium',
|
|
34
|
+
},
|
|
35
|
+
fixer: { model: 'github-copilot/claude-sonnet-4.6', variant: 'low' },
|
|
36
|
+
},
|
|
37
|
+
'zai-plan': {
|
|
38
|
+
orchestrator: { model: 'zai-coding-plan/glm-5' },
|
|
39
|
+
oracle: { model: 'zai-coding-plan/glm-5', variant: 'high' },
|
|
40
|
+
librarian: { model: 'zai-coding-plan/glm-5', variant: 'low' },
|
|
41
|
+
explorer: { model: 'zai-coding-plan/glm-5', variant: 'low' },
|
|
42
|
+
designer: { model: 'zai-coding-plan/glm-5', variant: 'medium' },
|
|
43
|
+
fixer: { model: 'zai-coding-plan/glm-5', variant: 'low' },
|
|
44
|
+
},
|
|
45
|
+
'opencode-go': {
|
|
46
|
+
orchestrator: {
|
|
47
|
+
model: 'neuralwatt/zai-org/GLM-5.1-FP8',
|
|
48
|
+
variant: 'medium',
|
|
49
|
+
},
|
|
50
|
+
oracle: { model: 'opencode-go/deepseek-v4-flash', variant: 'medium' },
|
|
51
|
+
librarian: { model: 'opencode-go/deepseek-v4-flash', variant: 'low' },
|
|
52
|
+
explorer: { model: 'neuralwatt/qwen3.5-397b-fast', variant: 'low' },
|
|
53
|
+
designer: { model: 'opencode-go/mimo-v2.5-pro', variant: 'medium' },
|
|
54
|
+
fixer: { model: 'opencode-go/deepseek-v4-flash', variant: 'low' },
|
|
55
|
+
},
|
|
56
|
+
} as const;
|
|
57
|
+
|
|
58
|
+
export type PresetName = keyof typeof MODEL_MAPPINGS;
|
|
59
|
+
export type GeneratedPresetName = (typeof GENERATED_PRESETS)[number];
|
|
60
|
+
|
|
61
|
+
export function isPresetName(value: string): value is PresetName {
|
|
62
|
+
return Object.hasOwn(MODEL_MAPPINGS, value);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getPresetNames(): PresetName[] {
|
|
66
|
+
return Object.keys(MODEL_MAPPINGS) as PresetName[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function isGeneratedPresetName(
|
|
70
|
+
value: string,
|
|
71
|
+
): value is GeneratedPresetName {
|
|
72
|
+
return GENERATED_PRESETS.includes(value as GeneratedPresetName);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getGeneratedPresetNames(): GeneratedPresetName[] {
|
|
76
|
+
return [...GENERATED_PRESETS];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function generateLiteConfig(
|
|
80
|
+
installConfig: InstallConfig,
|
|
81
|
+
): Record<string, unknown> {
|
|
82
|
+
const preset = installConfig.preset ?? 'openai';
|
|
83
|
+
if (!isGeneratedPresetName(preset)) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Unsupported preset "${preset}". Available generated presets: ${getGeneratedPresetNames().join(', ')}`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const config: Record<string, unknown> = {
|
|
90
|
+
$schema: SCHEMA_URL,
|
|
91
|
+
preset,
|
|
92
|
+
presets: {},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const createAgentConfig = (
|
|
96
|
+
agentName: string,
|
|
97
|
+
modelInfo: { model: string; variant?: string },
|
|
98
|
+
) => {
|
|
99
|
+
return {
|
|
100
|
+
model: modelInfo.model,
|
|
101
|
+
variant: modelInfo.variant,
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const buildPreset = (mappingName: PresetName) => {
|
|
106
|
+
const mapping = MODEL_MAPPINGS[mappingName];
|
|
107
|
+
return Object.fromEntries(
|
|
108
|
+
Object.entries(mapping).map(([agentName, modelInfo]) => [
|
|
109
|
+
agentName,
|
|
110
|
+
createAgentConfig(agentName, modelInfo),
|
|
111
|
+
]),
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const presets = config.presets as Record<string, unknown>;
|
|
116
|
+
for (const presetName of GENERATED_PRESETS) {
|
|
117
|
+
presets[presetName] = buildPreset(presetName);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Pre-populate skills section so new installs don't hit the fallback path.
|
|
121
|
+
// Permissions are controlled via always-load + wildcard per agent.
|
|
122
|
+
config.agents = {
|
|
123
|
+
orchestrator: {
|
|
124
|
+
skills: { 'always-load': ['find-skills'], wildcard: true },
|
|
125
|
+
},
|
|
126
|
+
designer: {
|
|
127
|
+
skills: {
|
|
128
|
+
'always-load': ['agent-browser', 'web-design-guidelines'],
|
|
129
|
+
wildcard: false,
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
oracle: {
|
|
133
|
+
skills: { 'always-load': ['requesting-code-review'], wildcard: false },
|
|
134
|
+
},
|
|
135
|
+
librarian: {
|
|
136
|
+
skills: { 'always-load': ['find-skills'], wildcard: false },
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
return config;
|
|
141
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import { getSkillPermissionsForAgent, normalizeSkillConfig } from './skills';
|
|
3
|
+
|
|
4
|
+
describe('skills permissions', () => {
|
|
5
|
+
it('should deny all skills by default when no config provided', async () => {
|
|
6
|
+
const permissions = await getSkillPermissionsForAgent('orchestrator');
|
|
7
|
+
expect(permissions['*']).toBe('deny');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should deny all skills for other agents by default', async () => {
|
|
11
|
+
const permissions = await getSkillPermissionsForAgent('designer');
|
|
12
|
+
expect(permissions['*']).toBe('deny');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should honor explicit skill list overrides', async () => {
|
|
16
|
+
// Override with empty list
|
|
17
|
+
const emptyPerms = await getSkillPermissionsForAgent('orchestrator', []);
|
|
18
|
+
expect(emptyPerms['*']).toBe('deny');
|
|
19
|
+
expect(Object.keys(emptyPerms).length).toBe(1);
|
|
20
|
+
|
|
21
|
+
// Override with specific list
|
|
22
|
+
const specificPerms = await getSkillPermissionsForAgent('designer', [
|
|
23
|
+
'my-skill',
|
|
24
|
+
'!bad-skill',
|
|
25
|
+
]);
|
|
26
|
+
expect(specificPerms['*']).toBe('deny');
|
|
27
|
+
expect(specificPerms['my-skill']).toBe('allow');
|
|
28
|
+
expect(specificPerms['bad-skill']).toBe('deny');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should honor wildcard in explicit list', async () => {
|
|
32
|
+
const wildcardPerms = await getSkillPermissionsForAgent('designer', ['*']);
|
|
33
|
+
expect(wildcardPerms['*']).toBe('allow');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('normalizeSkillConfig', () => {
|
|
38
|
+
it('should return defaults for undefined input', () => {
|
|
39
|
+
expect(normalizeSkillConfig(undefined)).toEqual({
|
|
40
|
+
alwaysLoad: [],
|
|
41
|
+
wildcard: false,
|
|
42
|
+
excluded: [],
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should handle empty array', () => {
|
|
47
|
+
expect(normalizeSkillConfig([])).toEqual({
|
|
48
|
+
alwaysLoad: [],
|
|
49
|
+
wildcard: false,
|
|
50
|
+
excluded: [],
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should handle array with wildcard', () => {
|
|
55
|
+
expect(normalizeSkillConfig(['skill-a', '*'])).toEqual({
|
|
56
|
+
alwaysLoad: ['skill-a'],
|
|
57
|
+
wildcard: true,
|
|
58
|
+
excluded: [],
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should handle array with exclusion', () => {
|
|
63
|
+
expect(normalizeSkillConfig(['skill-a', '!bad-skill'])).toEqual({
|
|
64
|
+
alwaysLoad: ['skill-a'],
|
|
65
|
+
wildcard: false,
|
|
66
|
+
excluded: ['bad-skill'],
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should handle object syntax with always-load', () => {
|
|
71
|
+
expect(
|
|
72
|
+
normalizeSkillConfig({ 'always-load': ['a'], wildcard: true }),
|
|
73
|
+
).toEqual({
|
|
74
|
+
alwaysLoad: ['a'],
|
|
75
|
+
wildcard: true,
|
|
76
|
+
excluded: [],
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should handle deprecated mandatory fallback', () => {
|
|
81
|
+
expect(normalizeSkillConfig({ mandatory: ['a'], wildcard: false })).toEqual(
|
|
82
|
+
{
|
|
83
|
+
alwaysLoad: ['a'],
|
|
84
|
+
wildcard: false,
|
|
85
|
+
excluded: [],
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should prefer always-load over deprecated mandatory', () => {
|
|
91
|
+
expect(
|
|
92
|
+
normalizeSkillConfig({
|
|
93
|
+
'always-load': ['new'],
|
|
94
|
+
mandatory: ['old'],
|
|
95
|
+
wildcard: false,
|
|
96
|
+
}),
|
|
97
|
+
).toEqual({
|
|
98
|
+
alwaysLoad: ['new'],
|
|
99
|
+
wildcard: false,
|
|
100
|
+
excluded: [],
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should handle object syntax with defaults', () => {
|
|
105
|
+
expect(normalizeSkillConfig({})).toEqual({
|
|
106
|
+
alwaysLoad: [],
|
|
107
|
+
wildcard: false,
|
|
108
|
+
excluded: [],
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { PluginInput } from '@opencode-ai/plugin';
|
|
2
|
+
import type { SkillOrMcpConfig } from '../config';
|
|
3
|
+
import { getLocalDiscovery } from '../discovery/local';
|
|
4
|
+
|
|
5
|
+
export interface NormalizedSkillConfig {
|
|
6
|
+
alwaysLoad: string[];
|
|
7
|
+
wildcard: boolean;
|
|
8
|
+
excluded: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function normalizeSkillConfig(
|
|
12
|
+
raw: SkillOrMcpConfig | undefined,
|
|
13
|
+
): NormalizedSkillConfig {
|
|
14
|
+
if (!raw) {
|
|
15
|
+
return { alwaysLoad: [], wildcard: false, excluded: [] };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (Array.isArray(raw)) {
|
|
19
|
+
const alwaysLoad: string[] = [];
|
|
20
|
+
const excluded: string[] = [];
|
|
21
|
+
let wildcard = false;
|
|
22
|
+
|
|
23
|
+
for (const item of raw) {
|
|
24
|
+
if (item === '*') {
|
|
25
|
+
wildcard = true;
|
|
26
|
+
} else if (item.startsWith('!')) {
|
|
27
|
+
excluded.push(item.slice(1));
|
|
28
|
+
} else {
|
|
29
|
+
alwaysLoad.push(item);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { alwaysLoad, wildcard, excluded };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Object syntax - read always-load first, fall back to deprecated mandatory
|
|
37
|
+
return {
|
|
38
|
+
alwaysLoad: raw['always-load'] ?? raw.mandatory ?? [],
|
|
39
|
+
wildcard: raw.wildcard ?? false,
|
|
40
|
+
excluded: [],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get permission presets for a specific agent based on recommended skills.
|
|
46
|
+
* @param agentName - The name of the agent
|
|
47
|
+
* @param skillList - Optional explicit list of skills to allow (overrides recommendations)
|
|
48
|
+
* @param ctx - Plugin input context for SDK discovery when wildcard=true
|
|
49
|
+
* @returns Permission rules for the skill permission type
|
|
50
|
+
*/
|
|
51
|
+
export async function getSkillPermissionsForAgent(
|
|
52
|
+
_agentName: string,
|
|
53
|
+
skillList?: SkillOrMcpConfig,
|
|
54
|
+
ctx?: PluginInput,
|
|
55
|
+
): Promise<Record<string, 'allow' | 'ask' | 'deny'>> {
|
|
56
|
+
const permissions: Record<string, 'allow' | 'ask' | 'deny'> = {
|
|
57
|
+
'*': 'deny', // Default: deny all
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
if (skillList !== undefined) {
|
|
61
|
+
const normalized = normalizeSkillConfig(skillList);
|
|
62
|
+
permissions['*'] = normalized.wildcard ? 'allow' : 'deny';
|
|
63
|
+
|
|
64
|
+
// Always-load skills always allowed
|
|
65
|
+
for (const name of normalized.alwaysLoad) {
|
|
66
|
+
permissions[name] = 'allow';
|
|
67
|
+
}
|
|
68
|
+
for (const name of normalized.excluded) {
|
|
69
|
+
permissions[name] = 'deny';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Wildcard: allow all installed skills (from SDK)
|
|
73
|
+
if (normalized.wildcard && ctx) {
|
|
74
|
+
try {
|
|
75
|
+
const local = await getLocalDiscovery(ctx);
|
|
76
|
+
for (const skill of local.skills) {
|
|
77
|
+
permissions[skill.name] = 'allow';
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
// wildcard with no SDK access = only always-load
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return permissions;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// No config provided - fallback to deny all
|
|
88
|
+
return permissions;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Scan installed skills from the filesystem via SDK discovery.
|
|
93
|
+
* @param ctx - Plugin input context for SDK discovery
|
|
94
|
+
* @returns Array of skill names that are installed
|
|
95
|
+
*/
|
|
96
|
+
export async function scanInstalledSkills(ctx: PluginInput): Promise<string[]> {
|
|
97
|
+
try {
|
|
98
|
+
const local = await getLocalDiscovery(ctx);
|
|
99
|
+
return local.skills.map((skill) => skill.name);
|
|
100
|
+
} catch {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
}
|