dream-wf 0.1.0 → 0.1.2

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 (30) hide show
  1. package/README.md +122 -55
  2. package/core/grill-prd-policy.md +2 -0
  3. package/core/workflow-profile.md +1 -0
  4. package/package.json +4 -3
  5. package/src/cli/index.js +192 -65
  6. package/src/deps/index.js +50 -0
  7. package/src/lib/catalog.js +82 -0
  8. package/src/lib/mcp.js +305 -0
  9. package/src/lib/platforms.js +13 -3
  10. package/src/lib/trellis.js +25 -2
  11. package/src/platforms/claude-code/index.js +7 -3
  12. package/src/platforms/codex/index.js +83 -0
  13. package/src/platforms/cursor/index.js +7 -3
  14. package/src/platforms/opencode/index.js +7 -3
  15. package/src/platforms/shared.js +11 -0
  16. package/src/tui/index.js +445 -0
  17. package/templates/hooks/claude-code/__pycache__/dream-wf-guard.cpython-314.pyc +0 -0
  18. package/templates/hooks/claude-code/dream-wf-guard.py +33 -11
  19. package/templates/hooks/codex/__pycache__/dream-wf-guard.cpython-314.pyc +0 -0
  20. package/templates/hooks/codex/dream-wf-guard.py +150 -0
  21. package/templates/hooks/cursor/__pycache__/dream-wf-guard.cpython-314.pyc +0 -0
  22. package/templates/hooks/cursor/dream-wf-guard.py +30 -11
  23. package/templates/hooks/opencode/dream-wf-guard.js +28 -10
  24. package/templates/rules/claude-code/dream-wf-block.md +20 -0
  25. package/templates/rules/codex/dream-wf-block.md +43 -0
  26. package/templates/rules/cursor/dream-wf.mdc +20 -1
  27. package/templates/rules/opencode/dream-wf-block.md +20 -0
  28. package/templates/skills/dream-wf-grill-prd/SKILL.md +54 -2
  29. package/templates/spec/guides/dream-wf-mcp-policy.md +6 -0
  30. package/templates/spec/guides/dream-wf-prd-policy.md +17 -0
package/src/cli/index.js CHANGED
@@ -1,91 +1,157 @@
1
- import process from 'node:process';
2
- import path from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
- import { assertSupportedPlatform, normalizePlatform } from '../lib/platforms.js';
5
- import { ensureTrellisInitialized, installTrellisProfile } from '../lib/trellis.js';
6
- import { formatRelative } from '../lib/files.js';
7
- import { installCursor } from '../platforms/cursor/index.js';
8
- import { installClaudeCode } from '../platforms/claude-code/index.js';
9
- import { installOpenCode } from '../platforms/opencode/index.js';
10
- import { runDoctor, formatDoctorReport } from '../doctor/index.js';
11
-
12
- const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
1
+ import process from "node:process";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import {
5
+ assertSupportedPlatform,
6
+ normalizePlatform,
7
+ } from "../lib/platforms.js";
8
+ import {
9
+ ensureTrellisInitialized,
10
+ installTrellisProfile,
11
+ } from "../lib/trellis.js";
12
+ import { formatRelative } from "../lib/files.js";
13
+ import {
14
+ resolveSkills,
15
+ resolveMcps,
16
+ defaultSkillIds,
17
+ defaultMcpIds,
18
+ } from "../lib/catalog.js";
19
+ import { installCursor } from "../platforms/cursor/index.js";
20
+ import { installClaudeCode } from "../platforms/claude-code/index.js";
21
+ import { installOpenCode } from "../platforms/opencode/index.js";
22
+ import { installCodex } from "../platforms/codex/index.js";
23
+ import { runDoctor, formatDoctorReport } from "../doctor/index.js";
24
+ import { runInteractive } from "../tui/index.js";
25
+
26
+ const packageRoot = path.resolve(
27
+ path.dirname(fileURLToPath(import.meta.url)),
28
+ "..",
29
+ "..",
30
+ );
13
31
 
14
32
  export async function run(argv) {
33
+ // 无参数或仅 --help 以外无 subcommand 时,进入交互式 TUI。
34
+ if (argv.length === 0) {
35
+ const interactive = await runInteractive();
36
+ if (!interactive) {
37
+ return;
38
+ }
39
+ await init(process.cwd(), interactive);
40
+ return;
41
+ }
42
+
43
+ // 优先处理全局 help 标志,避免被当作 command 或要求 -p。
44
+ if (argv.includes("--help") || argv.includes("-h")) {
45
+ writeOutput(helpText());
46
+ return;
47
+ }
48
+
15
49
  const { command, options } = parseArgs(argv);
16
50
 
17
- if (options.help || command === 'help') {
51
+ if (!command || command === "help") {
18
52
  writeOutput(helpText());
19
53
  return;
20
54
  }
21
55
 
22
- if (!command) {
23
- throw new Error(`Missing command.\n\n${helpText()}`);
56
+ if (command === "interactive" || command === "tui") {
57
+ const interactive = await runInteractive();
58
+ if (!interactive) {
59
+ return;
60
+ }
61
+ await init(process.cwd(), interactive);
62
+ return;
24
63
  }
25
64
 
26
65
  const platform = normalizePlatform(options.platform);
27
66
  assertSupportedPlatform(platform);
28
67
 
29
68
  const rootDir = process.cwd();
30
- const mode = options.mode ?? 'strict';
31
- if (!['strict', 'advisory'].includes(mode)) {
32
- throw new Error('Invalid --mode. Use strict or advisory.');
69
+ const mode = options.mode ?? "strict";
70
+ if (!["strict", "advisory"].includes(mode)) {
71
+ throw new Error("Invalid --mode. Use strict or advisory.");
33
72
  }
34
73
 
35
- if (command === 'init') {
74
+ if (command === "init") {
36
75
  await init(rootDir, { ...options, platform, mode });
37
76
  return;
38
77
  }
39
78
 
40
- if (command === 'doctor') {
79
+ if (command === "doctor") {
41
80
  const report = await runDoctor(rootDir, platform);
42
81
  writeOutput(formatDoctorReport(report));
43
82
  return;
44
83
  }
45
84
 
46
- if (command === 'update') {
85
+ if (command === "update") {
47
86
  await init(rootDir, { ...options, platform, mode });
48
87
  return;
49
88
  }
50
89
 
51
- if (command === 'uninstall') {
52
- throw new Error('uninstall is planned but not implemented in this MVP. Remove dream-wf generated files manually if needed.');
90
+ if (command === "uninstall") {
91
+ throw new Error(
92
+ "uninstall is planned but not implemented in this MVP. Remove dream-wf generated files manually if needed.",
93
+ );
53
94
  }
54
95
 
55
96
  throw new Error(`Unknown command "${command}".\n\n${helpText()}`);
56
97
  }
57
98
 
58
99
  async function init(rootDir, options) {
100
+ // 来自 TUI 的 options 已带 skills/mcps;来自 CLI 的 options 需要解析。
101
+ const platform = options.platform;
102
+ const mode = options.mode ?? "strict";
103
+
104
+ const skillIds = options.skillIds ?? defaultSkillIds();
105
+ const mcpIds = options.mcpIds ?? defaultMcpIds();
106
+ const skills = resolveSkills(
107
+ options.skills ? options.skills.map((s) => s.id) : skillIds,
108
+ );
109
+ const mcps = resolveMcps(
110
+ options.mcps ? options.mcps.map((m) => m.id) : mcpIds,
111
+ );
112
+
113
+ const initOptions = { ...options, platform, mode, skills, mcps };
114
+
59
115
  writeOutput(formatBanner());
60
116
 
61
117
  const results = [];
62
- const trellis = await ensureTrellisInitialized(rootDir, options);
118
+ const trellis = await ensureTrellisInitialized(rootDir, initOptions);
63
119
 
64
120
  if (!trellis.initialized) {
65
- writeOutput([
66
- 'Trellis is not initialized in this project.',
67
- `Run: ${trellis.initCommand}`,
68
- 'Then rerun dream-wf init.'
69
- ].join('\n'));
121
+ writeOutput(
122
+ [
123
+ "Trellis is not initialized in this project.",
124
+ `Run: ${trellis.initCommand}`,
125
+ "Then rerun dream-wf init.",
126
+ ].join("\n"),
127
+ );
70
128
  return;
71
129
  }
72
130
 
73
131
  results.push(await installTrellisProfile(rootDir));
74
132
 
75
- if (options.platform === 'cursor') {
76
- results.push(...await installCursor(packageRoot, rootDir, options));
133
+ if (platform === "cursor") {
134
+ results.push(...(await installCursor(packageRoot, rootDir, initOptions)));
135
+ }
136
+
137
+ if (platform === "claude") {
138
+ results.push(
139
+ ...(await installClaudeCode(packageRoot, rootDir, initOptions)),
140
+ );
77
141
  }
78
142
 
79
- if (options.platform === 'claude') {
80
- results.push(...await installClaudeCode(packageRoot, rootDir, options));
143
+ if (platform === "opencode") {
144
+ results.push(...(await installOpenCode(packageRoot, rootDir, initOptions)));
81
145
  }
82
146
 
83
- if (options.platform === 'opencode') {
84
- results.push(...await installOpenCode(packageRoot, rootDir, options));
147
+ if (platform === "codex") {
148
+ results.push(...(await installCodex(packageRoot, rootDir, initOptions)));
85
149
  }
86
150
 
87
- const report = await runDoctor(rootDir, options.platform);
88
- writeOutput(`${formatInstallReport(rootDir, results)}\n\n${formatDoctorReport(report)}`);
151
+ const report = await runDoctor(rootDir, platform);
152
+ writeOutput(
153
+ `${formatInstallReport(rootDir, results)}\n\n${formatDoctorReport(report)}`,
154
+ );
89
155
  }
90
156
 
91
157
  function parseArgs(argv) {
@@ -95,55 +161,85 @@ function parseArgs(argv) {
95
161
  for (let index = 0; index < rest.length; index += 1) {
96
162
  const arg = rest[index];
97
163
 
98
- if (arg === '--help' || arg === '-h') {
164
+ if (arg === "--help" || arg === "-h") {
99
165
  options.help = true;
100
166
  continue;
101
167
  }
102
168
 
103
- if (arg === '--install-deps') {
169
+ if (arg === "--install-deps") {
104
170
  options.installDeps = true;
105
171
  continue;
106
172
  }
107
173
 
108
- if (arg === '--skip-deps') {
174
+ if (arg === "--skip-deps") {
109
175
  options.installDeps = false;
110
176
  continue;
111
177
  }
112
178
 
113
- if (arg === '--yes' || arg === '-y') {
179
+ if (arg === "--yes" || arg === "-y") {
114
180
  options.yes = true;
115
181
  continue;
116
182
  }
117
183
 
118
- if (arg === '-p') {
184
+ if (arg === "--skip-skills") {
185
+ options.skillIds = [];
186
+ continue;
187
+ }
188
+
189
+ if (arg === "--skip-mcps") {
190
+ options.mcpIds = [];
191
+ continue;
192
+ }
193
+
194
+ if (arg === "-p") {
119
195
  const value = rest[index + 1];
120
- if (!value || value.startsWith('-')) {
121
- throw new Error('Missing value for -p. Use -p <cursor|claude|opencode>.');
196
+ if (!value || value.startsWith("-")) {
197
+ throw new Error(
198
+ "Missing value for -p. Use -p <cursor|claude|opencode|codex>.",
199
+ );
122
200
  }
123
201
  options.platform = value;
124
202
  index += 1;
125
203
  continue;
126
204
  }
127
205
 
128
- if (arg === '--mode') {
206
+ if (arg === "--mode") {
129
207
  options.mode = readOptionValue(arg, rest, index);
130
208
  index += 1;
131
209
  continue;
132
210
  }
133
211
 
134
- if (arg.startsWith('--mode=')) {
135
- options.mode = arg.slice('--mode='.length);
212
+ if (arg.startsWith("--mode=")) {
213
+ options.mode = arg.slice("--mode=".length);
136
214
  continue;
137
215
  }
138
216
 
139
- if (arg === '--developer') {
217
+ if (arg === "--developer") {
140
218
  options.developer = readOptionValue(arg, rest, index);
141
219
  index += 1;
142
220
  continue;
143
221
  }
144
222
 
145
- if (arg.startsWith('--developer=')) {
146
- options.developer = arg.slice('--developer='.length);
223
+ if (arg.startsWith("--developer=")) {
224
+ options.developer = arg.slice("--developer=".length);
225
+ continue;
226
+ }
227
+
228
+ if (arg === "--skills") {
229
+ options.skillIds = readOptionValue(arg, rest, index)
230
+ .split(",")
231
+ .map((s) => s.trim())
232
+ .filter(Boolean);
233
+ index += 1;
234
+ continue;
235
+ }
236
+
237
+ if (arg === "--mcps") {
238
+ options.mcpIds = readOptionValue(arg, rest, index)
239
+ .split(",")
240
+ .map((s) => s.trim())
241
+ .filter(Boolean);
242
+ index += 1;
147
243
  continue;
148
244
  }
149
245
 
@@ -155,19 +251,21 @@ function parseArgs(argv) {
155
251
 
156
252
  function readOptionValue(arg, rest, index) {
157
253
  const value = rest[index + 1];
158
- if (!value || value.startsWith('-')) {
254
+ if (!value || value.startsWith("-")) {
159
255
  throw new Error(`Missing value for ${arg}.`);
160
256
  }
161
257
  return value;
162
258
  }
163
259
 
164
260
  function formatInstallReport(rootDir, results) {
165
- const lines = ['dream-wf install report:'];
261
+ const lines = ["dream-wf install report:"];
166
262
  for (const result of results.flat().filter(Boolean)) {
167
- const suffix = result.reason ? ` (${result.reason})` : '';
168
- lines.push(`- ${result.action}: ${formatRelative(rootDir, result.path)}${suffix}`);
263
+ const suffix = result.reason ? ` (${result.reason})` : "";
264
+ lines.push(
265
+ `- ${result.action}: ${formatRelative(rootDir, result.path)}${suffix}`,
266
+ );
169
267
  }
170
- return lines.join('\n');
268
+ return lines.join("\n");
171
269
  }
172
270
 
173
271
  function writeOutput(message) {
@@ -176,15 +274,14 @@ function writeOutput(message) {
176
274
 
177
275
  function formatBanner() {
178
276
  const banner = [
179
- '██████╗ ██████╗ ███████╗ █████╗ ███╗ ███╗',
180
- '██╔══██╗██╔══██╗██╔════╝██╔══██╗████╗ ████║',
181
- '██║ ██║██████╔╝█████╗ ███████║██╔████╔██║',
182
- '██║ ██║██╔══██╗██╔══╝ ██╔══██║██║╚██╔╝██║',
183
- '██████╔╝██║ ██║███████╗██║ ██║██║ ╚═╝ ██║',
184
- '╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝',
185
- '',
186
- ' Dream WF · Trellis workflow profile installer'
187
- ].join('\n');
277
+ "███████╗ ███████╗ ███████╗ ███████╗ ███╗ ███╗",
278
+ "██╔═══██╗ ██╔═══██╗ ██╔═════╝ ██╔═══██╗ ████╗ ████║",
279
+ "██║ ██║██████╔═╝ ███████╗ ███████╔╝ ██╔████╔██║",
280
+ "██║ ██║██╔═══██╗ ██╔════╝ ██╔═══██╗ ██║╚██╔╝██║",
281
+ "███████╔╝ ██║ ██║ ███████╗ ██║ ██║ ██║ ╚═╝ ██║",
282
+ "╚══════╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝",
283
+ " Dream WorkFlow v0.1.2",
284
+ ].join("\n");
188
285
 
189
286
  if (!process.stdout.isTTY || process.env.NO_COLOR) {
190
287
  return banner;
@@ -194,5 +291,35 @@ function formatBanner() {
194
291
  }
195
292
 
196
293
  function helpText() {
197
- return `dream-wf\n\nUsage:\n dream-wf init -p <cursor|claude|opencode> [--mode strict|advisory] [--install-deps --developer <name>]\n dream-wf doctor -p <cursor|claude|opencode>\n dream-wf update -p <cursor|claude|opencode>\n\nDefaults:\n --mode strict\n project-level install\n\nExamples:\n npx dream-wf init -p cursor\n npx dream-wf init -p claude --install-deps --developer ashe\n npx dream-wf doctor -p opencode`;
294
+ return [
295
+ "dream-wf v0.1.2 · Trellis workflow 安装聚合器",
296
+ "",
297
+ "Usage:",
298
+ " dream-wf # 交互式 TUI(推荐)",
299
+ " dream-wf interactive # 同上",
300
+ " dream-wf init -p <cursor|claude|opencode|codex> [options]",
301
+ " dream-wf doctor -p <cursor|claude|opencode|codex>",
302
+ " dream-wf update -p <cursor|claude|opencode|codex>",
303
+ "",
304
+ "Options:",
305
+ " -p <platform> cursor|claude|opencode|codex",
306
+ " --mode strict|advisory 默认 strict",
307
+ " --skills <id,id,...> 指定要安装的 skill id(默认全部)",
308
+ " --mcps <id,id,...> 指定要配置的 mcp id(默认全部)",
309
+ " --skip-skills 不安装任何 skill",
310
+ " --skip-mcps 不配置任何 mcp",
311
+ " --install-deps --developer <n> 自动初始化 Trellis",
312
+ "",
313
+ "Skill ids:",
314
+ " trellis-dream-wf-patch, dream-wf-mcp-policy",
315
+ "",
316
+ "MCP ids:",
317
+ " fast-context, grok-search",
318
+ "",
319
+ "Examples:",
320
+ " npx dream-wf",
321
+ " npx dream-wf init -p cursor",
322
+ " npx dream-wf init -p claude --skills trellis-dream-wf-patch --mcps fast-context",
323
+ " npx dream-wf doctor -p codex",
324
+ ].join("\n");
198
325
  }
package/src/deps/index.js CHANGED
@@ -2,6 +2,8 @@ import { spawnSync } from 'node:child_process';
2
2
  import path from 'node:path';
3
3
  import { pathExists, readTextIfExists } from '../lib/files.js';
4
4
  import { commandExists } from '../lib/trellis.js';
5
+ import { readMcpServers, mcpConfigExists } from '../lib/mcp.js';
6
+ import { MCP_CATALOG } from '../lib/catalog.js';
5
7
 
6
8
  export async function checkDependencies(rootDir, platform) {
7
9
  const checks = [];
@@ -18,18 +20,31 @@ export async function checkDependencies(rootDir, platform) {
18
20
  checks.push(await fileCheck(path.join(rootDir, '.cursor', 'rules', 'dream-wf.mdc'), 'Cursor dream-wf always-on rule'));
19
21
  checks.push(await fileCheck(path.join(rootDir, '.cursor', 'skills', 'dream-wf-grill-prd', 'SKILL.md'), 'Cursor dream-wf grill PRD skill'));
20
22
  checks.push(await fileCheck(path.join(rootDir, '.cursor', 'skills', 'dream-wf-mcp-policy', 'SKILL.md'), 'Cursor dream-wf MCP policy skill'));
23
+ checks.push(await mcpConfigCheck(rootDir, 'cursor'));
21
24
  }
22
25
 
23
26
  if (platform === 'claude') {
24
27
  checks.push(await contentCheck(path.join(rootDir, 'CLAUDE.md'), '<!-- DREAM-WF:START -->', 'Claude Code dream-wf entry block'));
25
28
  checks.push(await fileCheck(path.join(rootDir, '.claude', 'skills', 'dream-wf-grill-prd', 'SKILL.md'), 'Claude Code dream-wf grill PRD skill'));
26
29
  checks.push(await fileCheck(path.join(rootDir, '.claude', 'skills', 'dream-wf-mcp-policy', 'SKILL.md'), 'Claude Code dream-wf MCP policy skill'));
30
+ checks.push(await mcpConfigCheck(rootDir, 'claude'));
27
31
  }
28
32
 
29
33
  if (platform === 'opencode') {
30
34
  checks.push(await contentCheck(path.join(rootDir, 'AGENTS.md'), '<!-- DREAM-WF:START -->', 'OpenCode dream-wf entry block'));
31
35
  checks.push(await fileCheck(path.join(rootDir, '.opencode', 'skills', 'dream-wf-grill-prd', 'SKILL.md'), 'OpenCode dream-wf grill PRD skill'));
32
36
  checks.push(await fileCheck(path.join(rootDir, '.opencode', 'skills', 'dream-wf-mcp-policy', 'SKILL.md'), 'OpenCode dream-wf MCP policy skill'));
37
+ checks.push(await mcpConfigCheck(rootDir, 'opencode'));
38
+ }
39
+
40
+ if (platform === 'codex') {
41
+ checks.push(await contentCheck(path.join(rootDir, 'AGENTS.md'), '<!-- DREAM-WF:START -->', 'Codex dream-wf entry block'));
42
+ checks.push(await fileCheck(path.join(rootDir, '.codex', 'skills', 'dream-wf-grill-prd', 'SKILL.md'), 'Codex dream-wf grill PRD skill'));
43
+ checks.push(await fileCheck(path.join(rootDir, '.codex', 'skills', 'dream-wf-mcp-policy', 'SKILL.md'), 'Codex dream-wf MCP policy skill'));
44
+ checks.push(await fileCheck(path.join(rootDir, '.codex', 'hooks', 'dream-wf-guard.py'), 'Codex dream-wf guard hook'));
45
+ checks.push(await contentCheck(path.join(rootDir, '.codex', 'hooks.json'), 'dream-wf-guard.py', 'Codex hooks.json registration'));
46
+ checks.push(await contentCheck(path.join(rootDir, '.codex', 'config.toml'), 'hooks = true', 'Codex hooks feature enabled'));
47
+ checks.push(await mcpConfigCheck(rootDir, 'codex'));
33
48
  }
34
49
 
35
50
  checks.push(await secretScan(rootDir));
@@ -37,6 +52,41 @@ export async function checkDependencies(rootDir, platform) {
37
52
  return checks;
38
53
  }
39
54
 
55
+ // 检查 MCP 配置文件存在性以及是否包含 catalog 里的默认 MCP 条目。
56
+ async function mcpConfigCheck(rootDir, platform) {
57
+ const configPaths = {
58
+ cursor: '.cursor/mcp.json',
59
+ claude: '.mcp.json',
60
+ opencode: 'opencode.json',
61
+ codex: '.codex/config.toml'
62
+ };
63
+
64
+ const exists = await mcpConfigExists(rootDir, platform);
65
+ if (!exists) {
66
+ return {
67
+ name: `MCP config (${configPaths[platform]})`,
68
+ ok: false,
69
+ hint: `Missing ${configPaths[platform]}. Run dream-wf init or TUI to configure MCP servers.`
70
+ };
71
+ }
72
+
73
+ const servers = await readMcpServers(rootDir, platform);
74
+ const missing = MCP_CATALOG.filter((entry) => !servers[entry.name]).map((entry) => entry.name);
75
+ if (missing.length > 0) {
76
+ return {
77
+ name: `MCP config (${configPaths[platform]})`,
78
+ ok: false,
79
+ hint: `Missing MCP servers: ${missing.join(', ')}.`
80
+ };
81
+ }
82
+
83
+ return {
84
+ name: `MCP config (${configPaths[platform]})`,
85
+ ok: true,
86
+ hint: `MCP config OK (${MCP_CATALOG.map((entry) => entry.name).join(', ')}).`
87
+ };
88
+ }
89
+
40
90
  function binaryCheck(command, hint) {
41
91
  return {
42
92
  name: command,
@@ -0,0 +1,82 @@
1
+ // 安装聚合器的可选 skill 和 mcp 条目。
2
+ // 每个 skill 对应一个模板目录 templates/skills/<name>/,由 shared.installSkill 装入各平台 skills 目录。
3
+ // 每个 mcp 对应一个独立 server 配置,由 mcp.installMcpServer 写入各平台 mcp 配置文件。
4
+
5
+ export const SKILL_CATALOG = [
6
+ {
7
+ id: 'trellis-dream-wf-patch',
8
+ name: 'dream-wf-grill-prd',
9
+ label: 'dream-wf-grill-prd (Trellis patch · grill-me style PRD)',
10
+ description: 'grill-me 风格的 PRD 澄清 skill,dream-wf 的核心 patch。',
11
+ templateDir: 'dream-wf-grill-prd',
12
+ default: true
13
+ },
14
+ {
15
+ id: 'dream-wf-mcp-policy',
16
+ name: 'dream-wf-mcp-policy',
17
+ label: 'dream-wf-mcp-policy (MCP 优先级策略 skill)',
18
+ description: '强制 fast-context-mcp / grok-search-mcp 优先级的策略 skill。',
19
+ templateDir: 'dream-wf-mcp-policy',
20
+ default: true
21
+ }
22
+ ];
23
+
24
+ export const MCP_CATALOG = [
25
+ {
26
+ id: 'fast-context',
27
+ name: 'fast-context',
28
+ label: 'fast-context-mcp (代码语义检索)',
29
+ description: '代码库语义理解优先 MCP,来源 SammySnake-d/fast-context-mcp。',
30
+ server: {
31
+ command: 'npx',
32
+ args: ['-y', '--prefer-online', 'fast-context-mcp@latest'],
33
+ env: {
34
+ WINDSURF_API_KEY: 'devin-session-xx'
35
+ }
36
+ },
37
+ default: true,
38
+ requires: {
39
+ binaries: ['npx']
40
+ }
41
+ },
42
+ {
43
+ id: 'grok-search',
44
+ name: 'grok-search',
45
+ label: 'grok-search-mcp (外部文档/实时网络检索)',
46
+ description: '外部文档和实时网络检索优先 MCP,来源 GuDaStudio/GrokSearch。',
47
+ server: {
48
+ type: 'stdio',
49
+ command: 'uvx',
50
+ args: ['--from', 'git+https://github.com/GuDaStudio/GrokSearch@grok-with-tavily', 'grok-search'],
51
+ env: {
52
+ GROK_API_URL: 'https://your-api-endpoint.com/v1',
53
+ GROK_API_KEY: 'your-grok-api-key',
54
+ GROK_MODEL: 'your-model-name',
55
+ TAVILY_API_KEY: 'optional-tavily-key',
56
+ TAVILY_API_URL: 'https://api.tavily.com'
57
+ }
58
+ },
59
+ default: true,
60
+ requires: {
61
+ binaries: ['uvx']
62
+ }
63
+ }
64
+ ];
65
+
66
+ export function defaultSkillIds() {
67
+ return SKILL_CATALOG.filter((item) => item.default).map((item) => item.id);
68
+ }
69
+
70
+ export function defaultMcpIds() {
71
+ return MCP_CATALOG.filter((item) => item.default).map((item) => item.id);
72
+ }
73
+
74
+ export function resolveSkills(ids) {
75
+ const set = new Set(ids);
76
+ return SKILL_CATALOG.filter((item) => set.has(item.id));
77
+ }
78
+
79
+ export function resolveMcps(ids) {
80
+ const set = new Set(ids);
81
+ return MCP_CATALOG.filter((item) => set.has(item.id));
82
+ }