@synergenius/flow-weaver 0.16.0 → 0.17.1
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/dist/cli/commands/init-personas.d.ts +89 -0
- package/dist/cli/commands/init-personas.js +518 -0
- package/dist/cli/commands/init.d.ts +33 -2
- package/dist/cli/commands/init.js +341 -79
- package/dist/cli/commands/mcp-setup.d.ts +24 -0
- package/dist/cli/commands/mcp-setup.js +59 -0
- package/dist/cli/flow-weaver.mjs +1934 -1160
- package/dist/cli/index.js +5 -0
- package/dist/generated-version.d.ts +1 -1
- package/dist/generated-version.js +1 -1
- package/package.json +1 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
2
|
/**
|
|
3
|
-
* Init command — scaffolds a new flow-weaver project interactively
|
|
3
|
+
* Init command — scaffolds a new flow-weaver project interactively.
|
|
4
|
+
* Supports persona-aware onboarding for nocode, low-code, vibe-coder, and expert users.
|
|
4
5
|
*/
|
|
5
6
|
import * as fs from 'fs';
|
|
6
7
|
import * as path from 'path';
|
|
7
|
-
import { execSync } from 'child_process';
|
|
8
|
+
import { execSync, spawn } from 'child_process';
|
|
8
9
|
import input from '@inquirer/input';
|
|
9
10
|
import select, { Separator } from '@inquirer/select';
|
|
10
11
|
import confirm from '@inquirer/confirm';
|
|
@@ -12,6 +13,8 @@ import { ExitPromptError } from '@inquirer/core';
|
|
|
12
13
|
import { getWorkflowTemplate, workflowTemplates } from '../templates/index.js';
|
|
13
14
|
import { logger } from '../utils/logger.js';
|
|
14
15
|
import { compileCommand } from './compile.js';
|
|
16
|
+
import { runMcpSetupFromInit, CLI_TOOL_BINARY, detectCliTools } from './mcp-setup.js';
|
|
17
|
+
import { PERSONA_CHOICES, PERSONA_CONFIRMATIONS, USE_CASE_CHOICES, selectTemplateForPersona, getTemplateSubChoices, printNextSteps, generateReadme, generateExampleWorkflow, generateAgentPrompt, generateEditorPrompt, generateSetupPromptFile, printCopyablePrompt, AGENT_LAUNCH_DEFAULTS, } from './init-personas.js';
|
|
15
18
|
// ── Utilities ────────────────────────────────────────────────────────────────
|
|
16
19
|
const PROJECT_NAME_RE = /^[a-zA-Z0-9][-a-zA-Z0-9_.]*$/;
|
|
17
20
|
export function validateProjectName(name) {
|
|
@@ -35,11 +38,14 @@ export function isNonInteractive() {
|
|
|
35
38
|
return !process.stdin.isTTY;
|
|
36
39
|
}
|
|
37
40
|
const VALID_TEMPLATES = workflowTemplates.map((t) => t.id);
|
|
41
|
+
const VALID_PERSONAS = ['nocode', 'vibecoder', 'lowcode', 'expert'];
|
|
42
|
+
const VALID_USE_CASES = ['data', 'ai', 'api', 'automation', 'cicd', 'minimal'];
|
|
38
43
|
// ── Config resolution (prompts) ──────────────────────────────────────────────
|
|
39
44
|
export async function resolveInitConfig(dirArg, options) {
|
|
40
45
|
const skipPrompts = options.yes || isNonInteractive();
|
|
41
46
|
const force = options.force ?? false;
|
|
42
|
-
|
|
47
|
+
const hasExplicitTemplate = !!options.template;
|
|
48
|
+
// 1. Project name (unchanged)
|
|
43
49
|
let projectName;
|
|
44
50
|
if (options.name) {
|
|
45
51
|
projectName = options.name;
|
|
@@ -61,72 +67,163 @@ export async function resolveInitConfig(dirArg, options) {
|
|
|
61
67
|
if (valid !== true) {
|
|
62
68
|
throw new Error(valid);
|
|
63
69
|
}
|
|
64
|
-
// Target directory
|
|
65
70
|
const targetDir = path.resolve(dirArg ?? projectName);
|
|
66
|
-
// 2.
|
|
71
|
+
// 2. Persona
|
|
72
|
+
let persona;
|
|
73
|
+
if (options.preset) {
|
|
74
|
+
if (!VALID_PERSONAS.includes(options.preset)) {
|
|
75
|
+
throw new Error(`Unknown preset "${options.preset}". Available: ${VALID_PERSONAS.join(', ')}`);
|
|
76
|
+
}
|
|
77
|
+
persona = options.preset;
|
|
78
|
+
}
|
|
79
|
+
else if (skipPrompts || hasExplicitTemplate) {
|
|
80
|
+
persona = 'expert';
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
persona = await select({
|
|
84
|
+
message: 'How do you plan to build?',
|
|
85
|
+
choices: PERSONA_CHOICES,
|
|
86
|
+
default: 'vibecoder',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// Print persona confirmation (interactive only)
|
|
90
|
+
if (!skipPrompts) {
|
|
91
|
+
const confirmation = PERSONA_CONFIRMATIONS[persona];
|
|
92
|
+
if (confirmation) {
|
|
93
|
+
logger.log(` ${logger.dim(confirmation)}`);
|
|
94
|
+
logger.newline();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// 3. Template selection (persona-dependent)
|
|
67
98
|
let template;
|
|
68
|
-
|
|
99
|
+
let useCase;
|
|
100
|
+
if (hasExplicitTemplate) {
|
|
101
|
+
// Direct --template flag bypasses everything
|
|
69
102
|
template = options.template;
|
|
70
103
|
if (!VALID_TEMPLATES.includes(template)) {
|
|
71
104
|
throw new Error(`Unknown template "${template}". Available: ${VALID_TEMPLATES.join(', ')}`);
|
|
72
105
|
}
|
|
73
106
|
}
|
|
74
|
-
else if (
|
|
75
|
-
|
|
107
|
+
else if (persona === 'expert') {
|
|
108
|
+
// Expert: show today's flat template list
|
|
109
|
+
if (skipPrompts) {
|
|
110
|
+
template = 'sequential';
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
template = await select({
|
|
114
|
+
message: 'Workflow template:',
|
|
115
|
+
choices: [
|
|
116
|
+
new Separator('── Data Processing ──'),
|
|
117
|
+
{ value: 'sequential', name: 'sequential', description: 'Linear pipeline' },
|
|
118
|
+
{ value: 'foreach', name: 'foreach', description: 'Batch iteration' },
|
|
119
|
+
{ value: 'aggregator', name: 'aggregator', description: 'Collect and aggregate results' },
|
|
120
|
+
new Separator('── Automation ──'),
|
|
121
|
+
{ value: 'conditional', name: 'conditional', description: 'Route by condition' },
|
|
122
|
+
new Separator('── AI ──'),
|
|
123
|
+
{ value: 'ai-agent', name: 'ai-agent', description: 'LLM agent with tool calling' },
|
|
124
|
+
{ value: 'ai-react', name: 'ai-react', description: 'ReAct pattern' },
|
|
125
|
+
{ value: 'ai-rag', name: 'ai-rag', description: 'Retrieval-Augmented Generation' },
|
|
126
|
+
{ value: 'ai-chat', name: 'ai-chat', description: 'Conversational AI' },
|
|
127
|
+
new Separator('── Durable ──'),
|
|
128
|
+
{ value: 'ai-agent-durable', name: 'ai-agent-durable', description: 'Durable agent with approval gate' },
|
|
129
|
+
{ value: 'ai-pipeline-durable', name: 'ai-pipeline-durable', description: 'Durable data pipeline' },
|
|
130
|
+
new Separator('── Integration ──'),
|
|
131
|
+
{ value: 'webhook', name: 'webhook', description: 'HTTP webhook handler' },
|
|
132
|
+
new Separator('── Utility ──'),
|
|
133
|
+
{ value: 'error-handler', name: 'error-handler', description: 'Error handling and recovery' },
|
|
134
|
+
new Separator('── CI/CD ──'),
|
|
135
|
+
{ value: 'cicd-test-deploy', name: 'cicd-test-deploy', description: 'Test and deploy pipeline' },
|
|
136
|
+
{ value: 'cicd-docker', name: 'cicd-docker', description: 'Docker build and push' },
|
|
137
|
+
{ value: 'cicd-multi-env', name: 'cicd-multi-env', description: 'Multi-environment deploy' },
|
|
138
|
+
{ value: 'cicd-matrix', name: 'cicd-matrix', description: 'Matrix build strategy' },
|
|
139
|
+
],
|
|
140
|
+
default: 'sequential',
|
|
141
|
+
});
|
|
142
|
+
}
|
|
76
143
|
}
|
|
77
144
|
else {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
new
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
145
|
+
// Non-expert: use-case categories
|
|
146
|
+
if (options.useCase) {
|
|
147
|
+
if (!VALID_USE_CASES.includes(options.useCase)) {
|
|
148
|
+
throw new Error(`Unknown use case "${options.useCase}". Available: ${VALID_USE_CASES.join(', ')}`);
|
|
149
|
+
}
|
|
150
|
+
useCase = options.useCase;
|
|
151
|
+
}
|
|
152
|
+
else if (skipPrompts) {
|
|
153
|
+
useCase = 'data';
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
useCase = await select({
|
|
157
|
+
message: 'What are you building?',
|
|
158
|
+
choices: USE_CASE_CHOICES,
|
|
159
|
+
default: 'data',
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
const selection = selectTemplateForPersona(persona, useCase);
|
|
163
|
+
if (selection.choices && !skipPrompts) {
|
|
164
|
+
// Lowcode with multiple choices: show sub-select
|
|
165
|
+
template = await select({
|
|
166
|
+
message: 'Pick a template:',
|
|
167
|
+
choices: getTemplateSubChoices(selection.choices),
|
|
168
|
+
default: selection.template,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
template = selection.template;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// 3b. "Something else" follow-up: ask what they're building
|
|
176
|
+
let useCaseDescription;
|
|
177
|
+
if (useCase === 'minimal' && !skipPrompts && persona !== 'expert') {
|
|
178
|
+
useCaseDescription = await input({
|
|
179
|
+
message: 'Briefly describe what you want to build:',
|
|
180
|
+
});
|
|
181
|
+
if (useCaseDescription) {
|
|
182
|
+
useCaseDescription = useCaseDescription.trim();
|
|
183
|
+
}
|
|
184
|
+
if (!useCaseDescription)
|
|
185
|
+
useCaseDescription = undefined;
|
|
186
|
+
}
|
|
187
|
+
// 4. MCP setup (nocode, vibecoder, lowcode: prompt; expert: skip unless --mcp)
|
|
188
|
+
let mcp;
|
|
189
|
+
if (options.mcp !== undefined) {
|
|
190
|
+
mcp = options.mcp;
|
|
191
|
+
}
|
|
192
|
+
else if (skipPrompts) {
|
|
193
|
+
mcp = false;
|
|
194
|
+
}
|
|
195
|
+
else if (persona === 'nocode' || persona === 'vibecoder' || persona === 'lowcode') {
|
|
196
|
+
mcp = await confirm({
|
|
197
|
+
message: 'Set up AI editor integration? (Claude Code, Cursor, VS Code, etc.)',
|
|
198
|
+
default: true,
|
|
105
199
|
});
|
|
106
200
|
}
|
|
107
|
-
|
|
201
|
+
else {
|
|
202
|
+
mcp = false;
|
|
203
|
+
}
|
|
204
|
+
// 5. Install deps (expert: prompt, others: auto-yes)
|
|
108
205
|
let installDeps;
|
|
109
206
|
if (options.install !== undefined) {
|
|
110
207
|
installDeps = options.install;
|
|
111
208
|
}
|
|
112
|
-
else if (skipPrompts) {
|
|
209
|
+
else if (skipPrompts || persona !== 'expert') {
|
|
113
210
|
installDeps = true;
|
|
114
211
|
}
|
|
115
212
|
else {
|
|
116
213
|
installDeps = await confirm({ message: 'Install dependencies (npm install)?', default: true });
|
|
117
214
|
}
|
|
118
|
-
//
|
|
215
|
+
// 6. Git init (expert: prompt, others: auto-yes)
|
|
119
216
|
let gitInit;
|
|
120
217
|
if (options.git !== undefined) {
|
|
121
218
|
gitInit = options.git;
|
|
122
219
|
}
|
|
123
|
-
else if (skipPrompts) {
|
|
220
|
+
else if (skipPrompts || persona !== 'expert') {
|
|
124
221
|
gitInit = true;
|
|
125
222
|
}
|
|
126
223
|
else {
|
|
127
224
|
gitInit = await confirm({ message: 'Initialize a git repository?', default: true });
|
|
128
225
|
}
|
|
129
|
-
//
|
|
226
|
+
// 7. Module format (expert only)
|
|
130
227
|
let format;
|
|
131
228
|
if (options.format) {
|
|
132
229
|
format = options.format;
|
|
@@ -134,23 +231,15 @@ export async function resolveInitConfig(dirArg, options) {
|
|
|
134
231
|
throw new Error(`Invalid format "${format}". Use "esm" or "cjs".`);
|
|
135
232
|
}
|
|
136
233
|
}
|
|
137
|
-
else if (skipPrompts) {
|
|
234
|
+
else if (skipPrompts || persona !== 'expert') {
|
|
138
235
|
format = 'esm';
|
|
139
236
|
}
|
|
140
237
|
else {
|
|
141
238
|
format = await select({
|
|
142
239
|
message: 'Module format:',
|
|
143
240
|
choices: [
|
|
144
|
-
{
|
|
145
|
-
|
|
146
|
-
name: 'ESM (Recommended)',
|
|
147
|
-
description: 'ECMAScript modules (import/export)',
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
value: 'cjs',
|
|
151
|
-
name: 'CommonJS',
|
|
152
|
-
description: 'CommonJS modules (require/module.exports)',
|
|
153
|
-
},
|
|
241
|
+
{ value: 'esm', name: 'ESM (Recommended)', description: 'ECMAScript modules (import/export)' },
|
|
242
|
+
{ value: 'cjs', name: 'CommonJS', description: 'CommonJS modules (require/module.exports)' },
|
|
154
243
|
],
|
|
155
244
|
default: 'esm',
|
|
156
245
|
});
|
|
@@ -163,10 +252,14 @@ export async function resolveInitConfig(dirArg, options) {
|
|
|
163
252
|
install: installDeps,
|
|
164
253
|
git: gitInit,
|
|
165
254
|
force,
|
|
255
|
+
persona,
|
|
256
|
+
useCase,
|
|
257
|
+
useCaseDescription,
|
|
258
|
+
mcp,
|
|
166
259
|
};
|
|
167
260
|
}
|
|
168
261
|
// ── Pure file generation ─────────────────────────────────────────────────────
|
|
169
|
-
export function generateProjectFiles(projectName, template, format = 'esm') {
|
|
262
|
+
export function generateProjectFiles(projectName, template, format = 'esm', persona = 'expert') {
|
|
170
263
|
const workflowName = toWorkflowName(projectName);
|
|
171
264
|
const workflowFile = `${projectName}-workflow.ts`;
|
|
172
265
|
const tmpl = getWorkflowTemplate(template);
|
|
@@ -174,17 +267,22 @@ export function generateProjectFiles(projectName, template, format = 'esm') {
|
|
|
174
267
|
throw new Error(`Unknown template "${template}"`);
|
|
175
268
|
}
|
|
176
269
|
const workflowCode = tmpl.generate({ workflowName });
|
|
177
|
-
// Package.json
|
|
270
|
+
// Package.json
|
|
271
|
+
const scripts = {
|
|
272
|
+
dev: `npx flow-weaver compile src/${workflowFile} -o src && npx tsx src/main.ts`,
|
|
273
|
+
start: 'npx tsx src/main.ts',
|
|
274
|
+
compile: `npx flow-weaver compile src/${workflowFile} -o src`,
|
|
275
|
+
validate: `npx flow-weaver validate src/${workflowFile}`,
|
|
276
|
+
doctor: 'npx flow-weaver doctor',
|
|
277
|
+
};
|
|
278
|
+
// Add diagram script for non-expert personas
|
|
279
|
+
if (persona !== 'expert') {
|
|
280
|
+
scripts.diagram = `npx flow-weaver diagram src/${workflowFile} --format ascii-compact`;
|
|
281
|
+
}
|
|
178
282
|
const packageJsonContent = {
|
|
179
283
|
name: projectName,
|
|
180
284
|
version: '1.0.0',
|
|
181
|
-
scripts
|
|
182
|
-
dev: `npx flow-weaver compile src/${workflowFile} -o src && npx tsx src/main.ts`,
|
|
183
|
-
start: 'npx tsx src/main.ts',
|
|
184
|
-
compile: `npx flow-weaver compile src/${workflowFile} -o src`,
|
|
185
|
-
validate: `npx flow-weaver validate src/${workflowFile}`,
|
|
186
|
-
doctor: 'npx flow-weaver doctor',
|
|
187
|
-
},
|
|
285
|
+
scripts,
|
|
188
286
|
dependencies: {
|
|
189
287
|
'@synergenius/flow-weaver': 'latest',
|
|
190
288
|
},
|
|
@@ -194,13 +292,11 @@ export function generateProjectFiles(projectName, template, format = 'esm') {
|
|
|
194
292
|
tsx: '^4.21.0',
|
|
195
293
|
},
|
|
196
294
|
};
|
|
197
|
-
// ESM projects need "type": "module"
|
|
198
295
|
if (format === 'esm') {
|
|
199
296
|
packageJsonContent.type = 'module';
|
|
200
297
|
}
|
|
201
|
-
// CJS projects can optionally have "type": "commonjs" but it's the default
|
|
202
298
|
const packageJson = JSON.stringify(packageJsonContent, null, 2);
|
|
203
|
-
// tsconfig.json
|
|
299
|
+
// tsconfig.json
|
|
204
300
|
const tsconfigContent = {
|
|
205
301
|
compilerOptions: {
|
|
206
302
|
target: 'ES2020',
|
|
@@ -216,7 +312,7 @@ export function generateProjectFiles(projectName, template, format = 'esm') {
|
|
|
216
312
|
include: ['src'],
|
|
217
313
|
};
|
|
218
314
|
const tsconfigJson = JSON.stringify(tsconfigContent, null, 2);
|
|
219
|
-
// main.ts
|
|
315
|
+
// main.ts
|
|
220
316
|
const workflowJsFile = workflowFile.replace(/\.ts$/, '.js');
|
|
221
317
|
let mainTs;
|
|
222
318
|
if (format === 'esm') {
|
|
@@ -246,7 +342,6 @@ export function generateProjectFiles(projectName, template, format = 'esm') {
|
|
|
246
342
|
].join('\n');
|
|
247
343
|
}
|
|
248
344
|
else {
|
|
249
|
-
// CommonJS format
|
|
250
345
|
mainTs = [
|
|
251
346
|
'/**',
|
|
252
347
|
` * ${projectName} — workflow runner`,
|
|
@@ -274,7 +369,7 @@ export function generateProjectFiles(projectName, template, format = 'esm') {
|
|
|
274
369
|
}
|
|
275
370
|
const gitignore = `node_modules/\ndist/\n.tsbuildinfo\n`;
|
|
276
371
|
const configYaml = `defaultFileType: ts\n`;
|
|
277
|
-
|
|
372
|
+
const files = {
|
|
278
373
|
'package.json': packageJson,
|
|
279
374
|
'tsconfig.json': tsconfigJson,
|
|
280
375
|
[`src/${workflowFile}`]: workflowCode,
|
|
@@ -282,6 +377,13 @@ export function generateProjectFiles(projectName, template, format = 'esm') {
|
|
|
282
377
|
'.gitignore': gitignore,
|
|
283
378
|
'.flowweaver/config.yaml': configYaml,
|
|
284
379
|
};
|
|
380
|
+
// Add README for all personas
|
|
381
|
+
files['README.md'] = generateReadme(projectName, persona, template);
|
|
382
|
+
// Add example workflow for lowcode persona
|
|
383
|
+
if (persona === 'lowcode') {
|
|
384
|
+
files['examples/example-workflow.ts'] = generateExampleWorkflow(projectName);
|
|
385
|
+
}
|
|
386
|
+
return files;
|
|
285
387
|
}
|
|
286
388
|
// ── Filesystem writer ────────────────────────────────────────────────────────
|
|
287
389
|
export function scaffoldProject(targetDir, files, options) {
|
|
@@ -321,6 +423,74 @@ export function runGitInit(targetDir) {
|
|
|
321
423
|
return { success: false, error: message };
|
|
322
424
|
}
|
|
323
425
|
}
|
|
426
|
+
/**
|
|
427
|
+
* After init + MCP setup, offer to launch a CLI agent or generate a prompt for GUI editors.
|
|
428
|
+
* Returns true if a CLI agent was spawned (init should exit and let the agent take over).
|
|
429
|
+
*/
|
|
430
|
+
export async function handleAgentHandoff(opts) {
|
|
431
|
+
const { projectName, persona, template, targetDir, cliTools, guiTools, filesCreated, useCaseDescription } = opts;
|
|
432
|
+
// Step 1: If CLI agent available, offer to launch it
|
|
433
|
+
if (cliTools.length > 0) {
|
|
434
|
+
const toolId = cliTools[0]; // Prefer first (claude > codex)
|
|
435
|
+
const binary = CLI_TOOL_BINARY[toolId];
|
|
436
|
+
const displayName = toolId === 'claude' ? 'Claude Code' : 'Codex';
|
|
437
|
+
const launchDefault = AGENT_LAUNCH_DEFAULTS[persona];
|
|
438
|
+
const shouldLaunch = await confirm({
|
|
439
|
+
message: `Launch ${displayName} to set up your project?`,
|
|
440
|
+
default: launchDefault,
|
|
441
|
+
});
|
|
442
|
+
if (shouldLaunch && binary) {
|
|
443
|
+
const prompt = generateAgentPrompt(projectName, persona, template, useCaseDescription);
|
|
444
|
+
logger.newline();
|
|
445
|
+
logger.log(` ${logger.dim(`Starting ${displayName}...`)}`);
|
|
446
|
+
logger.newline();
|
|
447
|
+
const child = spawn(binary, [prompt], {
|
|
448
|
+
cwd: targetDir,
|
|
449
|
+
stdio: 'inherit',
|
|
450
|
+
env: { ...process.env },
|
|
451
|
+
});
|
|
452
|
+
child.on('error', (err) => {
|
|
453
|
+
logger.error(`Failed to start ${displayName}: ${err.message}`);
|
|
454
|
+
});
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
// Step 2: If GUI editors configured (or user declined CLI), offer prompt options
|
|
459
|
+
if (guiTools.length > 0 || cliTools.length > 0) {
|
|
460
|
+
const promptAction = await select({
|
|
461
|
+
message: 'Generate a setup prompt for your editor?',
|
|
462
|
+
choices: [
|
|
463
|
+
{ value: 'terminal', name: 'Print to terminal', description: 'Copy and paste into your editor' },
|
|
464
|
+
{ value: 'file', name: 'Save as file', description: 'Write PROJECT_SETUP.md to your project' },
|
|
465
|
+
{ value: 'both', name: 'Both' },
|
|
466
|
+
{ value: 'skip', name: 'Skip' },
|
|
467
|
+
],
|
|
468
|
+
default: 'terminal',
|
|
469
|
+
});
|
|
470
|
+
if (promptAction === 'skip')
|
|
471
|
+
return false;
|
|
472
|
+
const editorPrompt = generateEditorPrompt(projectName, persona, template, useCaseDescription);
|
|
473
|
+
if (promptAction === 'terminal' || promptAction === 'both') {
|
|
474
|
+
printCopyablePrompt(editorPrompt);
|
|
475
|
+
}
|
|
476
|
+
if (promptAction === 'file' || promptAction === 'both') {
|
|
477
|
+
const setupContent = generateSetupPromptFile(projectName, persona, template, filesCreated, useCaseDescription);
|
|
478
|
+
const setupPath = path.join(targetDir, 'PROJECT_SETUP.md');
|
|
479
|
+
fs.writeFileSync(setupPath, setupContent, 'utf8');
|
|
480
|
+
// Add to .gitignore
|
|
481
|
+
const gitignorePath = path.join(targetDir, '.gitignore');
|
|
482
|
+
if (fs.existsSync(gitignorePath)) {
|
|
483
|
+
const existing = fs.readFileSync(gitignorePath, 'utf8');
|
|
484
|
+
if (!existing.includes('PROJECT_SETUP.md')) {
|
|
485
|
+
fs.appendFileSync(gitignorePath, 'PROJECT_SETUP.md\n', 'utf8');
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
logger.newline();
|
|
489
|
+
logger.success(`Wrote ${logger.highlight('PROJECT_SETUP.md')} ${logger.dim('(delete after first setup)')}`);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
324
494
|
// ── CLI entrypoint ───────────────────────────────────────────────────────────
|
|
325
495
|
export async function initCommand(dirArg, options) {
|
|
326
496
|
try {
|
|
@@ -331,7 +501,7 @@ export async function initCommand(dirArg, options) {
|
|
|
331
501
|
throw new Error(`${config.targetDir} already contains a package.json. Use --force to overwrite.`);
|
|
332
502
|
}
|
|
333
503
|
// Generate and scaffold
|
|
334
|
-
const files = generateProjectFiles(config.projectName, config.template, config.format);
|
|
504
|
+
const files = generateProjectFiles(config.projectName, config.template, config.format, config.persona);
|
|
335
505
|
const { filesCreated, filesSkipped } = scaffoldProject(config.targetDir, files, {
|
|
336
506
|
force: config.force,
|
|
337
507
|
});
|
|
@@ -351,8 +521,7 @@ export async function initCommand(dirArg, options) {
|
|
|
351
521
|
if (config.git) {
|
|
352
522
|
gitResult = runGitInit(config.targetDir);
|
|
353
523
|
}
|
|
354
|
-
// Auto-compile the workflow so `npm start` works immediately
|
|
355
|
-
// Skip in JSON mode since compileCommand writes to logger.
|
|
524
|
+
// Auto-compile the workflow so `npm start` works immediately
|
|
356
525
|
const workflowFile = `${config.projectName}-workflow.ts`;
|
|
357
526
|
const workflowPath = path.join(config.targetDir, 'src', workflowFile);
|
|
358
527
|
let compileResult;
|
|
@@ -366,6 +535,45 @@ export async function initCommand(dirArg, options) {
|
|
|
366
535
|
compileResult = { success: false, error: message };
|
|
367
536
|
}
|
|
368
537
|
}
|
|
538
|
+
// MCP setup
|
|
539
|
+
let mcpConfigured;
|
|
540
|
+
let mcpResult;
|
|
541
|
+
if (config.mcp && !options.json) {
|
|
542
|
+
const spinner = logger.spinner('Detecting AI editors...');
|
|
543
|
+
try {
|
|
544
|
+
mcpResult = await runMcpSetupFromInit();
|
|
545
|
+
mcpConfigured = mcpResult.configured;
|
|
546
|
+
spinner.stop();
|
|
547
|
+
// Per-tool status lines
|
|
548
|
+
for (const t of mcpResult.detected) {
|
|
549
|
+
if (!t.detected)
|
|
550
|
+
continue;
|
|
551
|
+
const wasConfigured = mcpConfigured.includes(t.displayName);
|
|
552
|
+
if (wasConfigured) {
|
|
553
|
+
logger.success(`${t.displayName} configured`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
if (mcpResult.failed.length > 0) {
|
|
557
|
+
for (const name of mcpResult.failed) {
|
|
558
|
+
logger.warn(`${name} failed to configure`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (mcpResult.detected.every((t) => !t.detected)) {
|
|
562
|
+
logger.log(` ${logger.dim('No AI editors detected')}`);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
catch {
|
|
566
|
+
spinner.fail('MCP setup failed');
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
// Agent handoff
|
|
570
|
+
let agentLaunched = false;
|
|
571
|
+
let mcpCliTools = [];
|
|
572
|
+
let mcpGuiTools = [];
|
|
573
|
+
if (mcpResult) {
|
|
574
|
+
mcpCliTools = mcpResult.cliTools;
|
|
575
|
+
mcpGuiTools = mcpResult.guiTools;
|
|
576
|
+
}
|
|
369
577
|
// Build report
|
|
370
578
|
const report = {
|
|
371
579
|
projectDir: config.targetDir,
|
|
@@ -373,20 +581,30 @@ export async function initCommand(dirArg, options) {
|
|
|
373
581
|
filesSkipped,
|
|
374
582
|
template: config.template,
|
|
375
583
|
format: config.format,
|
|
584
|
+
persona: config.persona,
|
|
376
585
|
installResult,
|
|
377
586
|
gitResult,
|
|
587
|
+
mcpConfigured,
|
|
588
|
+
agentLaunched: false,
|
|
378
589
|
};
|
|
379
590
|
if (options.json) {
|
|
380
591
|
console.log(JSON.stringify(report, null, 2));
|
|
381
592
|
return;
|
|
382
593
|
}
|
|
383
|
-
// Human output
|
|
594
|
+
// Human output — status lines
|
|
384
595
|
logger.newline();
|
|
385
596
|
logger.success(`Created ${logger.highlight(config.projectName)} ${logger.dim(`(${config.template}, ${config.format.toUpperCase()})`)}`);
|
|
386
|
-
logger.log(` ${filesCreated.join(', ')}`);
|
|
387
597
|
if (filesSkipped.length > 0) {
|
|
388
598
|
logger.warn(`Skipped ${filesSkipped.length} existing file(s)`);
|
|
389
599
|
}
|
|
600
|
+
if (installResult) {
|
|
601
|
+
if (installResult.success) {
|
|
602
|
+
logger.success('Dependencies installed');
|
|
603
|
+
}
|
|
604
|
+
else {
|
|
605
|
+
logger.warn(`npm install failed: ${installResult.error}`);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
390
608
|
if (gitResult) {
|
|
391
609
|
if (gitResult.success) {
|
|
392
610
|
logger.success('Git initialized');
|
|
@@ -403,18 +621,62 @@ export async function initCommand(dirArg, options) {
|
|
|
403
621
|
logger.warn(`Compile failed: ${compileResult.error}`);
|
|
404
622
|
}
|
|
405
623
|
}
|
|
406
|
-
|
|
407
|
-
|
|
624
|
+
// Read the workflow code for preview
|
|
625
|
+
const workflowCode = files[`src/${workflowFile}`] ?? null;
|
|
626
|
+
// Persona-specific rich output
|
|
408
627
|
const relDir = path.relative(process.cwd(), config.targetDir);
|
|
409
628
|
const displayDir = !relDir || relDir === '.' ? null : relDir.startsWith('../../') ? config.targetDir : relDir;
|
|
410
|
-
|
|
411
|
-
|
|
629
|
+
// Agent handoff: offer to launch CLI agent or generate prompt for GUI editors.
|
|
630
|
+
// Decoupled from MCP: even if MCP wasn't run, check for available CLI tools.
|
|
631
|
+
const skipAgent = options.agent === false || options.yes || isNonInteractive();
|
|
632
|
+
if (!skipAgent) {
|
|
633
|
+
// If MCP didn't run or found no CLI tools, do a quick binary check
|
|
634
|
+
if (mcpCliTools.length === 0) {
|
|
635
|
+
try {
|
|
636
|
+
mcpCliTools = await detectCliTools();
|
|
637
|
+
}
|
|
638
|
+
catch {
|
|
639
|
+
// Non-fatal
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
const hasTools = mcpCliTools.length > 0 || mcpGuiTools.length > 0;
|
|
643
|
+
if (hasTools) {
|
|
644
|
+
try {
|
|
645
|
+
agentLaunched = await handleAgentHandoff({
|
|
646
|
+
projectName: config.projectName,
|
|
647
|
+
persona: config.persona,
|
|
648
|
+
template: config.template,
|
|
649
|
+
targetDir: config.targetDir,
|
|
650
|
+
cliTools: mcpCliTools,
|
|
651
|
+
guiTools: mcpGuiTools,
|
|
652
|
+
filesCreated,
|
|
653
|
+
useCaseDescription: config.useCaseDescription,
|
|
654
|
+
});
|
|
655
|
+
report.agentLaunched = agentLaunched;
|
|
656
|
+
}
|
|
657
|
+
catch (err) {
|
|
658
|
+
if (err instanceof ExitPromptError)
|
|
659
|
+
return;
|
|
660
|
+
// Non-fatal: just skip agent handoff
|
|
661
|
+
}
|
|
662
|
+
}
|
|
412
663
|
}
|
|
413
|
-
|
|
414
|
-
|
|
664
|
+
// If an agent was spawned, it takes over. Print minimal output.
|
|
665
|
+
if (agentLaunched) {
|
|
666
|
+
return;
|
|
415
667
|
}
|
|
416
|
-
|
|
417
|
-
|
|
668
|
+
printNextSteps({
|
|
669
|
+
projectName: config.projectName,
|
|
670
|
+
persona: config.persona,
|
|
671
|
+
template: config.template,
|
|
672
|
+
displayDir,
|
|
673
|
+
installSkipped: !config.install,
|
|
674
|
+
workflowCode,
|
|
675
|
+
workflowFile,
|
|
676
|
+
mcpConfigured,
|
|
677
|
+
agentLaunched,
|
|
678
|
+
compiled: compileResult?.success,
|
|
679
|
+
});
|
|
418
680
|
}
|
|
419
681
|
catch (err) {
|
|
420
682
|
// Clean exit on Ctrl+C during prompts
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* mcp-setup command — detect AI coding tools and configure the Flow Weaver MCP server.
|
|
3
3
|
*/
|
|
4
4
|
export type ToolId = 'claude' | 'cursor' | 'vscode' | 'windsurf' | 'codex' | 'openclaw';
|
|
5
|
+
/** Tools that can be spawned as interactive CLI sessions */
|
|
6
|
+
export declare const CLI_TOOL_IDS: ReadonlySet<ToolId>;
|
|
7
|
+
/** Binary name to spawn for each CLI tool */
|
|
8
|
+
export declare const CLI_TOOL_BINARY: Partial<Record<ToolId, string>>;
|
|
5
9
|
export interface McpSetupDeps {
|
|
6
10
|
execCommand: (cmd: string) => Promise<{
|
|
7
11
|
stdout: string;
|
|
@@ -40,6 +44,26 @@ export declare function mergeJsonConfig(deps: McpSetupDeps, filePath: string, ro
|
|
|
40
44
|
}>;
|
|
41
45
|
export declare const TOOL_REGISTRY: ToolDefinition[];
|
|
42
46
|
export declare function detectTools(deps: McpSetupDeps): Promise<DetectedTool[]>;
|
|
47
|
+
export interface McpSetupFromInitResult {
|
|
48
|
+
configured: string[];
|
|
49
|
+
failed: string[];
|
|
50
|
+
/** Full detection results for each tool (used by init for transparent feedback) */
|
|
51
|
+
detected: DetectedTool[];
|
|
52
|
+
/** Tool IDs that can be spawned as interactive CLI sessions (e.g. claude, codex) */
|
|
53
|
+
cliTools: ToolId[];
|
|
54
|
+
/** Tool IDs that are GUI-only editors (e.g. cursor, vscode, windsurf) */
|
|
55
|
+
guiTools: ToolId[];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Detect and configure all AI tools without interactive prompts.
|
|
59
|
+
* Used by the init command after the user has already consented.
|
|
60
|
+
*/
|
|
61
|
+
export declare function runMcpSetupFromInit(deps?: McpSetupDeps): Promise<McpSetupFromInitResult>;
|
|
62
|
+
/**
|
|
63
|
+
* Check which CLI agent tools are available on PATH.
|
|
64
|
+
* Lightweight: only runs `which` for known CLI binaries.
|
|
65
|
+
*/
|
|
66
|
+
export declare function detectCliTools(deps?: McpSetupDeps): Promise<ToolId[]>;
|
|
43
67
|
export declare function mcpSetupCommand(options: McpSetupOptions, deps?: McpSetupDeps): Promise<void>;
|
|
44
68
|
export {};
|
|
45
69
|
//# sourceMappingURL=mcp-setup.d.ts.map
|