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.
Files changed (302) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +452 -0
  3. package/dist/agents/descriptions.d.ts +6 -0
  4. package/dist/agents/designer.d.ts +2 -0
  5. package/dist/agents/explorer.d.ts +2 -0
  6. package/dist/agents/fixer.d.ts +2 -0
  7. package/dist/agents/index.d.ts +22 -0
  8. package/dist/agents/interpreter.d.ts +2 -0
  9. package/dist/agents/librarian.d.ts +2 -0
  10. package/dist/agents/oracle.d.ts +2 -0
  11. package/dist/agents/orchestrator.d.ts +27 -0
  12. package/dist/agents/overrides.d.ts +18 -0
  13. package/dist/agents/prompt-blocks.d.ts +97 -0
  14. package/dist/agents/steward.d.ts +3 -0
  15. package/dist/cli/config-io.d.ts +24 -0
  16. package/dist/cli/config-manager.d.ts +4 -0
  17. package/dist/cli/index.d.ts +2 -0
  18. package/dist/cli/index.js +1006 -0
  19. package/dist/cli/install.d.ts +2 -0
  20. package/dist/cli/mcps.d.ts +13 -0
  21. package/dist/cli/model-key-normalization.d.ts +1 -0
  22. package/dist/cli/paths.d.ts +35 -0
  23. package/dist/cli/providers.d.ts +137 -0
  24. package/dist/cli/skills.d.ts +22 -0
  25. package/dist/cli/system.d.ts +5 -0
  26. package/dist/cli/types.d.ts +38 -0
  27. package/dist/config/constants.d.ts +12 -0
  28. package/dist/config/index.d.ts +4 -0
  29. package/dist/config/loader.d.ts +40 -0
  30. package/dist/config/runtime-preset.d.ts +12 -0
  31. package/dist/config/schema.d.ts +281 -0
  32. package/dist/config/utils.d.ts +10 -0
  33. package/dist/discovery/local/types.d.ts +79 -0
  34. package/dist/discovery/local.d.ts +73 -0
  35. package/dist/discovery/mcp-servers.d.ts +88 -0
  36. package/dist/discovery/skills.d.ts +94 -0
  37. package/dist/hooks/apply-patch/codec.d.ts +7 -0
  38. package/dist/hooks/apply-patch/errors.d.ts +25 -0
  39. package/dist/hooks/apply-patch/execution-context.d.ts +27 -0
  40. package/dist/hooks/apply-patch/index.d.ts +15 -0
  41. package/dist/hooks/apply-patch/matching.d.ts +26 -0
  42. package/dist/hooks/apply-patch/operations.d.ts +3 -0
  43. package/dist/hooks/apply-patch/patch.d.ts +2 -0
  44. package/dist/hooks/apply-patch/prepared-changes.d.ts +17 -0
  45. package/dist/hooks/apply-patch/resolution.d.ts +19 -0
  46. package/dist/hooks/apply-patch/rewrite.d.ts +7 -0
  47. package/dist/hooks/apply-patch/test-helpers.d.ts +6 -0
  48. package/dist/hooks/apply-patch/types.d.ts +80 -0
  49. package/dist/hooks/auto-update-checker/cache.d.ts +11 -0
  50. package/dist/hooks/auto-update-checker/checker.d.ts +32 -0
  51. package/dist/hooks/auto-update-checker/constants.d.ts +11 -0
  52. package/dist/hooks/auto-update-checker/index.d.ts +18 -0
  53. package/dist/hooks/auto-update-checker/types.d.ts +22 -0
  54. package/dist/hooks/chat-headers.d.ts +16 -0
  55. package/dist/hooks/context-pressure-reminder/index.d.ts +33 -0
  56. package/dist/hooks/delegate-task-retry/guidance.d.ts +2 -0
  57. package/dist/hooks/delegate-task-retry/hook.d.ts +8 -0
  58. package/dist/hooks/delegate-task-retry/index.d.ts +4 -0
  59. package/dist/hooks/delegate-task-retry/patterns.d.ts +11 -0
  60. package/dist/hooks/filter-available-skills/index.d.ts +32 -0
  61. package/dist/hooks/foreground-fallback/index.d.ts +72 -0
  62. package/dist/hooks/image-hook.d.ts +5 -0
  63. package/dist/hooks/index.d.ts +14 -0
  64. package/dist/hooks/json-error-recovery/hook.d.ts +18 -0
  65. package/dist/hooks/json-error-recovery/index.d.ts +1 -0
  66. package/dist/hooks/phase-reminder/index.d.ts +26 -0
  67. package/dist/hooks/post-file-tool-nudge/index.d.ts +19 -0
  68. package/dist/hooks/task-session-manager/index.d.ts +52 -0
  69. package/dist/hooks/todo-continuation/index.d.ts +53 -0
  70. package/dist/hooks/todo-continuation/todo-hygiene.d.ts +35 -0
  71. package/dist/index.d.ts +5 -0
  72. package/dist/index.js +31782 -0
  73. package/dist/mcp/context7.d.ts +6 -0
  74. package/dist/mcp/grep-app.d.ts +6 -0
  75. package/dist/mcp/index.d.ts +13 -0
  76. package/dist/mcp/types.d.ts +12 -0
  77. package/dist/mcp/websearch.d.ts +9 -0
  78. package/dist/skills/registry.d.ts +29 -0
  79. package/dist/subscriptions/accounts-store.d.ts +57 -0
  80. package/dist/subscriptions/index.d.ts +13 -0
  81. package/dist/subscriptions/neuralwatt-scraper.d.ts +14 -0
  82. package/dist/subscriptions/opencode-go-scraper.d.ts +27 -0
  83. package/dist/subscriptions/types.d.ts +115 -0
  84. package/dist/subscriptions/usage-service.d.ts +74 -0
  85. package/dist/tools/ast-grep/cli.d.ts +15 -0
  86. package/dist/tools/ast-grep/constants.d.ts +25 -0
  87. package/dist/tools/ast-grep/downloader.d.ts +5 -0
  88. package/dist/tools/ast-grep/index.d.ts +10 -0
  89. package/dist/tools/ast-grep/tools.d.ts +3 -0
  90. package/dist/tools/ast-grep/types.d.ts +30 -0
  91. package/dist/tools/ast-grep/utils.d.ts +4 -0
  92. package/dist/tools/delegate.d.ts +14 -0
  93. package/dist/tools/index.d.ts +5 -0
  94. package/dist/tools/preset-manager.d.ts +27 -0
  95. package/dist/tools/smartfetch/binary.d.ts +3 -0
  96. package/dist/tools/smartfetch/cache.d.ts +6 -0
  97. package/dist/tools/smartfetch/constants.d.ts +12 -0
  98. package/dist/tools/smartfetch/index.d.ts +3 -0
  99. package/dist/tools/smartfetch/network.d.ts +38 -0
  100. package/dist/tools/smartfetch/secondary-model.d.ts +28 -0
  101. package/dist/tools/smartfetch/tool.d.ts +3 -0
  102. package/dist/tools/smartfetch/types.d.ts +122 -0
  103. package/dist/tools/smartfetch/utils.d.ts +18 -0
  104. package/dist/tui-state.d.ts +168 -0
  105. package/dist/tui.d.ts +37 -0
  106. package/dist/tui.js +1896 -0
  107. package/dist/utils/agent-variant.d.ts +63 -0
  108. package/dist/utils/compat.d.ts +30 -0
  109. package/dist/utils/env.d.ts +1 -0
  110. package/dist/utils/index.d.ts +9 -0
  111. package/dist/utils/internal-initiator.d.ts +6 -0
  112. package/dist/utils/logger.d.ts +8 -0
  113. package/dist/utils/polling.d.ts +21 -0
  114. package/dist/utils/session-manager.d.ts +55 -0
  115. package/dist/utils/session.d.ts +90 -0
  116. package/dist/utils/subagent-depth.d.ts +35 -0
  117. package/dist/utils/system-collapse.d.ts +6 -0
  118. package/dist/utils/task.d.ts +4 -0
  119. package/dist/utils/zip-extractor.d.ts +1 -0
  120. package/index.ts +1 -0
  121. package/opencode-dux.schema.json +634 -0
  122. package/package.json +103 -0
  123. package/src/agents/descriptions.ts +55 -0
  124. package/src/agents/designer.test.ts +86 -0
  125. package/src/agents/designer.ts +154 -0
  126. package/src/agents/display-name.test.ts +186 -0
  127. package/src/agents/explorer.test.ts +79 -0
  128. package/src/agents/explorer.ts +144 -0
  129. package/src/agents/fixer.test.ts +79 -0
  130. package/src/agents/fixer.ts +145 -0
  131. package/src/agents/index.test.ts +472 -0
  132. package/src/agents/index.ts +248 -0
  133. package/src/agents/interpreter.ts +136 -0
  134. package/src/agents/librarian.test.ts +80 -0
  135. package/src/agents/librarian.ts +145 -0
  136. package/src/agents/oracle.test.ts +89 -0
  137. package/src/agents/oracle.ts +184 -0
  138. package/src/agents/orchestrator.test.ts +116 -0
  139. package/src/agents/orchestrator.ts +574 -0
  140. package/src/agents/overrides.ts +95 -0
  141. package/src/agents/prompt-blocks.test.ts +114 -0
  142. package/src/agents/prompt-blocks.ts +640 -0
  143. package/src/agents/steward.ts +146 -0
  144. package/src/cli/config-io.test.ts +536 -0
  145. package/src/cli/config-io.ts +473 -0
  146. package/src/cli/config-manager.test.ts +141 -0
  147. package/src/cli/config-manager.ts +4 -0
  148. package/src/cli/index.ts +88 -0
  149. package/src/cli/install.ts +282 -0
  150. package/src/cli/mcps.test.ts +62 -0
  151. package/src/cli/mcps.ts +39 -0
  152. package/src/cli/model-key-normalization.test.ts +21 -0
  153. package/src/cli/model-key-normalization.ts +60 -0
  154. package/src/cli/paths.test.ts +167 -0
  155. package/src/cli/paths.ts +144 -0
  156. package/src/cli/providers.test.ts +118 -0
  157. package/src/cli/providers.ts +141 -0
  158. package/src/cli/skills.test.ts +111 -0
  159. package/src/cli/skills.ts +103 -0
  160. package/src/cli/system.test.ts +91 -0
  161. package/src/cli/system.ts +180 -0
  162. package/src/cli/types.ts +43 -0
  163. package/src/config/constants.ts +58 -0
  164. package/src/config/index.ts +4 -0
  165. package/src/config/loader.test.ts +1194 -0
  166. package/src/config/loader.ts +269 -0
  167. package/src/config/model-resolution.test.ts +176 -0
  168. package/src/config/runtime-preset.test.ts +61 -0
  169. package/src/config/runtime-preset.ts +37 -0
  170. package/src/config/schema.ts +248 -0
  171. package/src/config/utils.test.ts +41 -0
  172. package/src/config/utils.ts +23 -0
  173. package/src/discovery/local/types.ts +85 -0
  174. package/src/discovery/local.ts +322 -0
  175. package/src/discovery/mcp-servers.ts +804 -0
  176. package/src/discovery/skills.ts +959 -0
  177. package/src/hooks/apply-patch/codec.test.ts +184 -0
  178. package/src/hooks/apply-patch/codec.ts +352 -0
  179. package/src/hooks/apply-patch/errors.ts +117 -0
  180. package/src/hooks/apply-patch/execution-context.ts +432 -0
  181. package/src/hooks/apply-patch/hook.test.ts +768 -0
  182. package/src/hooks/apply-patch/index.ts +126 -0
  183. package/src/hooks/apply-patch/matching.test.ts +215 -0
  184. package/src/hooks/apply-patch/matching.ts +586 -0
  185. package/src/hooks/apply-patch/operations.test.ts +1535 -0
  186. package/src/hooks/apply-patch/operations.ts +3 -0
  187. package/src/hooks/apply-patch/patch.ts +9 -0
  188. package/src/hooks/apply-patch/prepared-changes.ts +400 -0
  189. package/src/hooks/apply-patch/resolution.test.ts +420 -0
  190. package/src/hooks/apply-patch/resolution.ts +437 -0
  191. package/src/hooks/apply-patch/rewrite.ts +496 -0
  192. package/src/hooks/apply-patch/test-helpers.ts +52 -0
  193. package/src/hooks/apply-patch/types.ts +111 -0
  194. package/src/hooks/auto-update-checker/cache.test.ts +179 -0
  195. package/src/hooks/auto-update-checker/cache.ts +188 -0
  196. package/src/hooks/auto-update-checker/checker.test.ts +159 -0
  197. package/src/hooks/auto-update-checker/checker.ts +308 -0
  198. package/src/hooks/auto-update-checker/constants.ts +33 -0
  199. package/src/hooks/auto-update-checker/index.test.ts +282 -0
  200. package/src/hooks/auto-update-checker/index.ts +225 -0
  201. package/src/hooks/auto-update-checker/types.ts +26 -0
  202. package/src/hooks/chat-headers.test.ts +236 -0
  203. package/src/hooks/chat-headers.ts +97 -0
  204. package/src/hooks/context-pressure-reminder/index.test.ts +179 -0
  205. package/src/hooks/context-pressure-reminder/index.ts +137 -0
  206. package/src/hooks/delegate-task-retry/guidance.ts +41 -0
  207. package/src/hooks/delegate-task-retry/hook.ts +23 -0
  208. package/src/hooks/delegate-task-retry/index.test.ts +38 -0
  209. package/src/hooks/delegate-task-retry/index.ts +7 -0
  210. package/src/hooks/delegate-task-retry/patterns.ts +79 -0
  211. package/src/hooks/filter-available-skills/index.test.ts +297 -0
  212. package/src/hooks/filter-available-skills/index.ts +160 -0
  213. package/src/hooks/foreground-fallback/index.test.ts +624 -0
  214. package/src/hooks/foreground-fallback/index.ts +374 -0
  215. package/src/hooks/image-hook.ts +6 -0
  216. package/src/hooks/index.ts +17 -0
  217. package/src/hooks/json-error-recovery/hook.ts +73 -0
  218. package/src/hooks/json-error-recovery/index.test.ts +111 -0
  219. package/src/hooks/json-error-recovery/index.ts +6 -0
  220. package/src/hooks/phase-reminder/index.test.ts +74 -0
  221. package/src/hooks/phase-reminder/index.ts +85 -0
  222. package/src/hooks/post-file-tool-nudge/index.test.ts +94 -0
  223. package/src/hooks/post-file-tool-nudge/index.ts +63 -0
  224. package/src/hooks/task-session-manager/index.test.ts +833 -0
  225. package/src/hooks/task-session-manager/index.ts +434 -0
  226. package/src/hooks/todo-continuation/index.test.ts +3026 -0
  227. package/src/hooks/todo-continuation/index.ts +878 -0
  228. package/src/hooks/todo-continuation/todo-hygiene.test.ts +204 -0
  229. package/src/hooks/todo-continuation/todo-hygiene.ts +207 -0
  230. package/src/index.ts +1672 -0
  231. package/src/mcp/context7.ts +14 -0
  232. package/src/mcp/grep-app.ts +11 -0
  233. package/src/mcp/index.test.ts +96 -0
  234. package/src/mcp/index.ts +66 -0
  235. package/src/mcp/types.ts +16 -0
  236. package/src/mcp/websearch.ts +47 -0
  237. package/src/skills/codemap/README.md +60 -0
  238. package/src/skills/codemap/SKILL.md +174 -0
  239. package/src/skills/codemap/scripts/codemap.mjs +483 -0
  240. package/src/skills/codemap/scripts/codemap.test.ts +129 -0
  241. package/src/skills/registry.ts +218 -0
  242. package/src/skills/simplify/README.md +19 -0
  243. package/src/skills/simplify/SKILL.md +138 -0
  244. package/src/subscriptions/accounts-store.test.ts +236 -0
  245. package/src/subscriptions/accounts-store.ts +184 -0
  246. package/src/subscriptions/index.ts +30 -0
  247. package/src/subscriptions/neuralwatt-scraper.ts +108 -0
  248. package/src/subscriptions/opencode-go-scraper.ts +301 -0
  249. package/src/subscriptions/types.ts +145 -0
  250. package/src/subscriptions/usage-service.test.ts +202 -0
  251. package/src/subscriptions/usage-service.ts +651 -0
  252. package/src/tools/ast-grep/cli.ts +257 -0
  253. package/src/tools/ast-grep/constants.ts +214 -0
  254. package/src/tools/ast-grep/downloader.ts +131 -0
  255. package/src/tools/ast-grep/index.ts +24 -0
  256. package/src/tools/ast-grep/tools.ts +117 -0
  257. package/src/tools/ast-grep/types.ts +51 -0
  258. package/src/tools/ast-grep/utils.ts +126 -0
  259. package/src/tools/delegate-handoff.test.ts +18 -0
  260. package/src/tools/delegate.ts +508 -0
  261. package/src/tools/index.ts +8 -0
  262. package/src/tools/preset-manager.test.ts +795 -0
  263. package/src/tools/preset-manager.ts +332 -0
  264. package/src/tools/smartfetch/binary.ts +58 -0
  265. package/src/tools/smartfetch/cache.test.ts +34 -0
  266. package/src/tools/smartfetch/cache.ts +112 -0
  267. package/src/tools/smartfetch/constants.ts +29 -0
  268. package/src/tools/smartfetch/index.ts +8 -0
  269. package/src/tools/smartfetch/network.test.ts +178 -0
  270. package/src/tools/smartfetch/network.ts +614 -0
  271. package/src/tools/smartfetch/secondary-model.test.ts +85 -0
  272. package/src/tools/smartfetch/secondary-model.ts +276 -0
  273. package/src/tools/smartfetch/tool.test.ts +60 -0
  274. package/src/tools/smartfetch/tool.ts +832 -0
  275. package/src/tools/smartfetch/types.ts +135 -0
  276. package/src/tools/smartfetch/utils.test.ts +24 -0
  277. package/src/tools/smartfetch/utils.ts +456 -0
  278. package/src/tui-state.test.ts +867 -0
  279. package/src/tui-state.ts +1255 -0
  280. package/src/tui.test.ts +336 -0
  281. package/src/tui.ts +1539 -0
  282. package/src/utils/agent-variant.test.ts +244 -0
  283. package/src/utils/agent-variant.ts +187 -0
  284. package/src/utils/compat.ts +91 -0
  285. package/src/utils/env.ts +12 -0
  286. package/src/utils/index.ts +9 -0
  287. package/src/utils/internal-initiator.ts +28 -0
  288. package/src/utils/logger.test.ts +220 -0
  289. package/src/utils/logger.ts +136 -0
  290. package/src/utils/polling.test.ts +191 -0
  291. package/src/utils/polling.ts +67 -0
  292. package/src/utils/session-manager.test.ts +173 -0
  293. package/src/utils/session-manager.ts +356 -0
  294. package/src/utils/session.test.ts +110 -0
  295. package/src/utils/session.ts +389 -0
  296. package/src/utils/subagent-depth.test.ts +170 -0
  297. package/src/utils/subagent-depth.ts +75 -0
  298. package/src/utils/system-collapse.test.ts +86 -0
  299. package/src/utils/system-collapse.ts +24 -0
  300. package/src/utils/task.test.ts +24 -0
  301. package/src/utils/task.ts +20 -0
  302. package/src/utils/zip-extractor.ts +102 -0
@@ -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
+ }