@relipa/ai-flow-kit 0.0.3 → 0.0.4-beta.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.
- package/AIFLOW.md +27 -1
- package/QUICK_START.md +20 -6
- package/README.md +27 -0
- package/bin/aiflow.js +5 -0
- package/custom/templates/laravel.md +15 -90
- package/custom/templates/nestjs.md +72 -0
- package/custom/templates/nextjs.md +14 -89
- package/custom/templates/nodejs-express.md +73 -0
- package/custom/templates/python-django.md +71 -0
- package/custom/templates/python-fastapi.md +54 -0
- package/custom/templates/reactjs.md +492 -567
- package/custom/templates/shared/gate-workflow.md +75 -0
- package/custom/templates/spring-boot.md +523 -598
- package/custom/templates/tools/copilot.md +8 -0
- package/custom/templates/tools/cursor.md +8 -0
- package/custom/templates/tools/gemini.md +8 -0
- package/custom/templates/tools/generic.md +12 -0
- package/custom/templates/vue-nuxt.md +14 -89
- package/docs/ai-integration.md +53 -0
- package/docs/developer-overview.md +126 -0
- package/package.json +1 -1
- package/scripts/hooks/session-start.js +2 -1
- package/scripts/init.js +460 -453
- package/scripts/prompt.js +12 -2
- package/scripts/use.js +594 -570
package/scripts/init.js
CHANGED
|
@@ -1,453 +1,460 @@
|
|
|
1
|
-
const fs = require('fs-extra');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const chalk = require('chalk');
|
|
4
|
-
const { input } = require('@inquirer/prompts');
|
|
5
|
-
|
|
6
|
-
const PKG_DIR = path.join(__dirname, '..');
|
|
7
|
-
const PKG_VERSION = require('../package.json').version;
|
|
8
|
-
|
|
9
|
-
// Map framework → language family for picking the right rules
|
|
10
|
-
const FRAMEWORK_LANGUAGE = {
|
|
11
|
-
'laravel': 'php',
|
|
12
|
-
'spring-boot': 'java',
|
|
13
|
-
'reactjs': 'javascript',
|
|
14
|
-
'nextjs': 'javascript',
|
|
15
|
-
'vue-nuxt': 'javascript',
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
await fs.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
req.
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
const
|
|
409
|
-
const
|
|
410
|
-
const
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
await
|
|
421
|
-
await
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
for (const
|
|
430
|
-
await
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
state
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
console.log(
|
|
446
|
-
console.log(
|
|
447
|
-
console.log(chalk.
|
|
448
|
-
console.log(
|
|
449
|
-
console.log(chalk.
|
|
450
|
-
|
|
451
|
-
console.
|
|
452
|
-
|
|
453
|
-
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const { input } = require('@inquirer/prompts');
|
|
5
|
+
|
|
6
|
+
const PKG_DIR = path.join(__dirname, '..');
|
|
7
|
+
const PKG_VERSION = require('../package.json').version;
|
|
8
|
+
|
|
9
|
+
// Map framework → language family for picking the right rules
|
|
10
|
+
const FRAMEWORK_LANGUAGE = {
|
|
11
|
+
'laravel': 'php',
|
|
12
|
+
'spring-boot': 'java',
|
|
13
|
+
'reactjs': 'javascript',
|
|
14
|
+
'nextjs': 'javascript',
|
|
15
|
+
'vue-nuxt': 'javascript',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const AI_TOOL_FILES = {
|
|
19
|
+
'claude': 'CLAUDE.md',
|
|
20
|
+
'cursor': '.cursorrules',
|
|
21
|
+
'gemini': 'GEMINI.md',
|
|
22
|
+
'copilot': '.github/copilot-instructions.md',
|
|
23
|
+
'generic': 'AI_INSTRUCTIONS.md'
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Copy documentation files and the docs/ folder to the project
|
|
28
|
+
*/
|
|
29
|
+
async function copyDocsToProject(projectDir) {
|
|
30
|
+
const aiflowDocsDir = path.join(projectDir, '.aiflow', 'docs');
|
|
31
|
+
const srcDocsDir = path.join(PKG_DIR, 'docs');
|
|
32
|
+
|
|
33
|
+
// 1. Copy the entire docs/ folder to .aiflow/docs
|
|
34
|
+
if (await fs.pathExists(srcDocsDir)) {
|
|
35
|
+
await fs.ensureDir(aiflowDocsDir);
|
|
36
|
+
await fs.copy(srcDocsDir, aiflowDocsDir, { overwrite: true });
|
|
37
|
+
console.log(chalk.green(`✓ Documentation folder copied to .aiflow/docs`));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 2. Copy key markdown files to project root for easy access
|
|
41
|
+
const keyFiles = ['QUICK_START.md', 'AIFLOW.md', 'CONTRIBUTING.md'];
|
|
42
|
+
for (const file of keyFiles) {
|
|
43
|
+
const srcPath = path.join(PKG_DIR, file);
|
|
44
|
+
const destPath = path.join(projectDir, file);
|
|
45
|
+
if (await fs.pathExists(srcPath)) {
|
|
46
|
+
// We overwrite these to ensure they are up to date with the installed version
|
|
47
|
+
await fs.copy(srcPath, destPath, { overwrite: true });
|
|
48
|
+
console.log(chalk.gray(` ✓ ${file} updated in project root`));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function copyAssetsToVersion(versionDir, framework) {
|
|
54
|
+
// ── Skills: merge upstream + custom ──────────────────────────────
|
|
55
|
+
const upstreamSkills = path.join(PKG_DIR, 'upstream', 'skills');
|
|
56
|
+
const customSkills = path.join(PKG_DIR, 'custom', 'skills');
|
|
57
|
+
|
|
58
|
+
await fs.ensureDir(path.join(versionDir, 'skills'));
|
|
59
|
+
if (await fs.pathExists(upstreamSkills)) {
|
|
60
|
+
await fs.copy(upstreamSkills, path.join(versionDir, 'skills'), { overwrite: true });
|
|
61
|
+
}
|
|
62
|
+
if (await fs.pathExists(customSkills)) {
|
|
63
|
+
await fs.copy(customSkills, path.join(versionDir, 'skills'), { overwrite: true });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Rules: common first, then language-specific override ─────────
|
|
67
|
+
const rulesTarget = path.join(versionDir, 'rules');
|
|
68
|
+
const commonRules = path.join(PKG_DIR, 'custom', 'rules', 'common');
|
|
69
|
+
const allRules = path.join(PKG_DIR, 'custom', 'rules');
|
|
70
|
+
|
|
71
|
+
await fs.ensureDir(rulesTarget);
|
|
72
|
+
|
|
73
|
+
if (await fs.pathExists(commonRules)) {
|
|
74
|
+
await fs.copy(commonRules, rulesTarget, { overwrite: true });
|
|
75
|
+
} else {
|
|
76
|
+
const entries = await fs.readdir(allRules, { withFileTypes: true });
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
79
|
+
await fs.copy(
|
|
80
|
+
path.join(allRules, entry.name),
|
|
81
|
+
path.join(rulesTarget, entry.name),
|
|
82
|
+
{ overwrite: true }
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (framework) {
|
|
89
|
+
const lang = FRAMEWORK_LANGUAGE[framework];
|
|
90
|
+
if (lang) {
|
|
91
|
+
const langRules = path.join(PKG_DIR, 'custom', 'rules', lang);
|
|
92
|
+
if (await fs.pathExists(langRules)) {
|
|
93
|
+
await fs.copy(langRules, rulesTarget, { overwrite: true });
|
|
94
|
+
console.log(chalk.gray(` ✓ Applied ${lang} rules`));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function updateSymlinks(projectDir, versionDir) {
|
|
101
|
+
const claudeDir = path.join(projectDir, '.claude');
|
|
102
|
+
await fs.emptyDir(claudeDir);
|
|
103
|
+
await fs.copy(path.join(versionDir, 'skills'), path.join(claudeDir, 'skills'), { overwrite: true });
|
|
104
|
+
|
|
105
|
+
const rulesDir = path.join(projectDir, '.rules');
|
|
106
|
+
await fs.emptyDir(rulesDir);
|
|
107
|
+
await fs.copy(path.join(versionDir, 'rules'), rulesDir, { overwrite: true });
|
|
108
|
+
|
|
109
|
+
await setupSuperpowersHook(projectDir, claudeDir);
|
|
110
|
+
|
|
111
|
+
await fs.writeJson(path.join(projectDir, '.aiflow', 'state.json'), {
|
|
112
|
+
current_version: PKG_VERSION,
|
|
113
|
+
framework: null,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function setupSuperpowersHook(_projectDir, claudeDir) {
|
|
118
|
+
const hooksDir = path.join(claudeDir, 'hooks');
|
|
119
|
+
await fs.ensureDir(hooksDir);
|
|
120
|
+
|
|
121
|
+
const hookSrc = path.join(PKG_DIR, 'scripts', 'hooks', 'session-start.js');
|
|
122
|
+
const hookDest = path.join(hooksDir, 'session-start.js');
|
|
123
|
+
await fs.copy(hookSrc, hookDest, { overwrite: true });
|
|
124
|
+
|
|
125
|
+
const settingsFile = path.join(claudeDir, 'settings.json');
|
|
126
|
+
let settings = {};
|
|
127
|
+
if (await fs.pathExists(settingsFile)) {
|
|
128
|
+
settings = await fs.readJson(settingsFile).catch(() => ({}));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!settings.hooks) settings.hooks = {};
|
|
132
|
+
if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
|
|
133
|
+
|
|
134
|
+
settings.hooks.SessionStart = settings.hooks.SessionStart.filter(
|
|
135
|
+
h => !(h._aiflowKit)
|
|
136
|
+
);
|
|
137
|
+
settings.hooks.SessionStart.push({
|
|
138
|
+
_aiflowKit: true,
|
|
139
|
+
hooks: [
|
|
140
|
+
{
|
|
141
|
+
type: 'command',
|
|
142
|
+
command: `node "${hookDest.replace(/\\/g, '/')}"`,
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
await fs.writeJson(settingsFile, settings, { spaces: 2 });
|
|
148
|
+
console.log(chalk.green(`✓ Superpowers SessionStart hook configured`));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function verifySuperpowersSkills(versionDir) {
|
|
152
|
+
const usingSuperpowers = path.join(versionDir, 'skills', 'using-superpowers', 'SKILL.md');
|
|
153
|
+
if (await fs.pathExists(usingSuperpowers)) {
|
|
154
|
+
console.log(chalk.green(`✓ Superpowers skills ready (built-in)`));
|
|
155
|
+
} else {
|
|
156
|
+
console.log(chalk.yellow(`! Superpowers skills missing — upstream folder may be empty`));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function setupFramework(projectDir, framework, multi = false, selectedTools = Object.keys(AI_TOOL_FILES)) {
|
|
161
|
+
if (!framework) return;
|
|
162
|
+
const frameworkTemplatePath = path.join(PKG_DIR, 'custom', 'templates', `${framework}.md`);
|
|
163
|
+
if (!(await fs.pathExists(frameworkTemplatePath))) {
|
|
164
|
+
console.log(chalk.yellow(`! No template found for framework: ${framework}`));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const frameworkContent = await fs.readFile(frameworkTemplatePath, 'utf-8');
|
|
169
|
+
const workflowPath = path.join(PKG_DIR, 'custom', 'templates', 'shared', 'gate-workflow.md');
|
|
170
|
+
const workflowContent = (await fs.pathExists(workflowPath))
|
|
171
|
+
? await fs.readFile(workflowPath, 'utf-8')
|
|
172
|
+
: '';
|
|
173
|
+
|
|
174
|
+
const separator = `\n\n---\n\n`;
|
|
175
|
+
|
|
176
|
+
for (const tool of selectedTools) {
|
|
177
|
+
const targetPath = path.join(projectDir, AI_TOOL_FILES[tool]);
|
|
178
|
+
if (!targetPath) continue;
|
|
179
|
+
|
|
180
|
+
await fs.ensureDir(path.dirname(targetPath));
|
|
181
|
+
|
|
182
|
+
let finalContent = '';
|
|
183
|
+
|
|
184
|
+
const toolTemplatePath = path.join(PKG_DIR, 'custom', 'templates', 'tools', `${tool}.md`);
|
|
185
|
+
if (await fs.pathExists(toolTemplatePath)) {
|
|
186
|
+
finalContent += await fs.readFile(toolTemplatePath, 'utf-8') + '\n\n';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (workflowContent) {
|
|
190
|
+
finalContent += workflowContent + '\n\n';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
finalContent += frameworkContent;
|
|
194
|
+
|
|
195
|
+
if (!multi) {
|
|
196
|
+
await fs.writeFile(targetPath, finalContent);
|
|
197
|
+
} else {
|
|
198
|
+
if (await fs.pathExists(targetPath)) {
|
|
199
|
+
await fs.appendFile(targetPath, separator + frameworkContent);
|
|
200
|
+
} else {
|
|
201
|
+
await fs.writeFile(targetPath, finalContent);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
console.log(chalk.green(`✓ AI instruction files updated for framework: ${framework} (${selectedTools.join(', ')})`));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function setupAdapter(projectDir, adapter) {
|
|
210
|
+
if (!adapter) return;
|
|
211
|
+
const presetPath = path.join(PKG_DIR, 'custom', 'mcp-presets', `${adapter}.json`);
|
|
212
|
+
if (!(await fs.pathExists(presetPath))) {
|
|
213
|
+
console.log(chalk.yellow(`! No MCP preset found for adapter: ${adapter}`));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const { mcpServers } = await fs.readJson(presetPath);
|
|
218
|
+
const serverKey = Object.keys(mcpServers)[0];
|
|
219
|
+
const serverConfig = mcpServers[serverKey];
|
|
220
|
+
|
|
221
|
+
const credsFile = path.join(projectDir, '.aiflow', 'credentials.json');
|
|
222
|
+
let existingCreds = {};
|
|
223
|
+
if (await fs.pathExists(credsFile)) {
|
|
224
|
+
existingCreds = await fs.readJson(credsFile).catch(() => ({}));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
console.log(chalk.cyan(`\nSetup MCP adapter for ${adapter}:`));
|
|
228
|
+
console.log(chalk.gray('Press Enter to keep existing value shown in [brackets]\n'));
|
|
229
|
+
|
|
230
|
+
const credentials = {};
|
|
231
|
+
for (const [envKey] of Object.entries(serverConfig.env)) {
|
|
232
|
+
const current = existingCreds[envKey];
|
|
233
|
+
const hint = current
|
|
234
|
+
? `[${maskSecret(current)}]`
|
|
235
|
+
: chalk.gray('[not set — required]');
|
|
236
|
+
|
|
237
|
+
let resolved = '';
|
|
238
|
+
while (true) {
|
|
239
|
+
const value = await input({
|
|
240
|
+
message: `${envKey} ${hint}:`,
|
|
241
|
+
default: '',
|
|
242
|
+
});
|
|
243
|
+
resolved = value.trim() || current || '';
|
|
244
|
+
if (resolved) break;
|
|
245
|
+
console.log(chalk.red(` ✗ ${envKey} is required. Please enter a value.`));
|
|
246
|
+
}
|
|
247
|
+
serverConfig.env[envKey] = resolved;
|
|
248
|
+
credentials[envKey] = resolved;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
console.log(chalk.cyan(`\nVerifying ${adapter} credentials...`));
|
|
252
|
+
const verifyResult = await verifyAdapterCredentials(adapter, credentials);
|
|
253
|
+
if (!verifyResult.ok) {
|
|
254
|
+
console.log(chalk.red(`✗ Verification failed: ${verifyResult.error}`));
|
|
255
|
+
console.log(chalk.yellow(' Credentials saved but may not work. Re-run `aiflow init --adapter ' + adapter + '` to update.'));
|
|
256
|
+
} else {
|
|
257
|
+
console.log(chalk.green(`✓ Credentials verified successfully`));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const claudeConfigDir = path.join(
|
|
261
|
+
process.env.APPDATA ||
|
|
262
|
+
(process.platform === 'darwin'
|
|
263
|
+
? process.env.HOME + '/Library/Application Support'
|
|
264
|
+
: process.env.HOME + '/.config'),
|
|
265
|
+
'Claude'
|
|
266
|
+
);
|
|
267
|
+
const mcpConfigFile = path.join(claudeConfigDir, 'claude_desktop_config.json');
|
|
268
|
+
let existingConfig = { mcpServers: {} };
|
|
269
|
+
if (await fs.pathExists(mcpConfigFile)) {
|
|
270
|
+
existingConfig = await fs.readJson(mcpConfigFile).catch(() => ({ mcpServers: {} }));
|
|
271
|
+
}
|
|
272
|
+
if (!existingConfig.mcpServers) existingConfig.mcpServers = {};
|
|
273
|
+
existingConfig.mcpServers[serverKey] = serverConfig;
|
|
274
|
+
await fs.ensureDir(path.dirname(mcpConfigFile));
|
|
275
|
+
await fs.writeJson(mcpConfigFile, existingConfig, { spaces: 2 });
|
|
276
|
+
|
|
277
|
+
await fs.writeJson(
|
|
278
|
+
path.join(projectDir, '.mcp.json'),
|
|
279
|
+
{ mcpServers: { [serverKey]: serverConfig } },
|
|
280
|
+
{ spaces: 2 }
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
Object.assign(existingCreds, credentials);
|
|
284
|
+
await fs.writeJson(credsFile, existingCreds, { spaces: 2 });
|
|
285
|
+
await ensureCredentialsGitignored(projectDir);
|
|
286
|
+
|
|
287
|
+
console.log(chalk.green(`✓ MCP configuration saved (global + local + credentials).`));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function verifyAdapterCredentials(adapter, credentials) {
|
|
291
|
+
try {
|
|
292
|
+
if (adapter === 'backlog') return await verifyBacklog(credentials);
|
|
293
|
+
if (adapter === 'jira') return await verifyJira(credentials);
|
|
294
|
+
if (adapter === 'figma') return await verifyFigma(credentials);
|
|
295
|
+
return { ok: true };
|
|
296
|
+
} catch (err) {
|
|
297
|
+
return { ok: false, error: err.message };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function verifyBacklog(credentials) {
|
|
302
|
+
const https = require('https');
|
|
303
|
+
const apiKey = credentials.BACKLOG_API_KEY;
|
|
304
|
+
const spaceKey = credentials.BACKLOG_SPACE_KEY;
|
|
305
|
+
const domain = spaceKey.includes('.') ? spaceKey : `${spaceKey}.backlog.com`;
|
|
306
|
+
|
|
307
|
+
return new Promise((resolve) => {
|
|
308
|
+
const url = `https://${domain}/api/v2/users/myself?apiKey=${apiKey}`;
|
|
309
|
+
const req = https.get(url, (res) => {
|
|
310
|
+
let data = '';
|
|
311
|
+
res.on('data', chunk => data += chunk);
|
|
312
|
+
res.on('end', () => {
|
|
313
|
+
if (res.statusCode === 200) {
|
|
314
|
+
const user = JSON.parse(data);
|
|
315
|
+
resolve({ ok: true, user: user.name });
|
|
316
|
+
} else if (res.statusCode === 401) {
|
|
317
|
+
resolve({ ok: false, error: 'Invalid API key' });
|
|
318
|
+
} else if (res.statusCode === 404) {
|
|
319
|
+
resolve({ ok: false, error: `Space "${spaceKey}" not found` });
|
|
320
|
+
} else {
|
|
321
|
+
resolve({ ok: false, error: `HTTP ${res.statusCode}` });
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
req.on('error', (err) => resolve({ ok: false, error: err.message }));
|
|
326
|
+
req.setTimeout(8000, () => { req.destroy(); resolve({ ok: false, error: 'Connection timeout' }); });
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function verifyJira(credentials) {
|
|
331
|
+
const https = require('https');
|
|
332
|
+
const { JIRA_API_TOKEN, JIRA_EMAIL, JIRA_DOMAIN } = credentials;
|
|
333
|
+
const auth = Buffer.from(`${JIRA_EMAIL}:${JIRA_API_TOKEN}`).toString('base64');
|
|
334
|
+
|
|
335
|
+
return new Promise((resolve) => {
|
|
336
|
+
const options = {
|
|
337
|
+
hostname: `${JIRA_DOMAIN}.atlassian.net`,
|
|
338
|
+
path: '/rest/api/3/myself',
|
|
339
|
+
headers: { 'Authorization': `Basic ${auth}`, 'Accept': 'application/json' },
|
|
340
|
+
};
|
|
341
|
+
const req = https.get(options, (res) => {
|
|
342
|
+
let data = '';
|
|
343
|
+
res.on('data', chunk => data += chunk);
|
|
344
|
+
res.on('end', () => {
|
|
345
|
+
if (res.statusCode === 200) {
|
|
346
|
+
resolve({ ok: true });
|
|
347
|
+
} else if (res.statusCode === 401) {
|
|
348
|
+
resolve({ ok: false, error: 'Invalid email or API token' });
|
|
349
|
+
} else {
|
|
350
|
+
resolve({ ok: false, error: `HTTP ${res.statusCode}` });
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
req.on('error', (err) => resolve({ ok: false, error: err.message }));
|
|
355
|
+
req.setTimeout(8000, () => { req.destroy(); resolve({ ok: false, error: 'Connection timeout' }); });
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function verifyFigma(credentials) {
|
|
360
|
+
const https = require('https');
|
|
361
|
+
const { FIGMA_API_TOKEN } = credentials;
|
|
362
|
+
|
|
363
|
+
return new Promise((resolve) => {
|
|
364
|
+
const options = {
|
|
365
|
+
hostname: 'api.figma.com',
|
|
366
|
+
path: '/v1/me',
|
|
367
|
+
headers: { 'Authorization': `Bearer ${FIGMA_API_TOKEN}` },
|
|
368
|
+
};
|
|
369
|
+
const req = https.get(options, (res) => {
|
|
370
|
+
let data = '';
|
|
371
|
+
res.on('data', chunk => data += chunk);
|
|
372
|
+
res.on('end', () => {
|
|
373
|
+
if (res.statusCode === 200) {
|
|
374
|
+
const user = JSON.parse(data);
|
|
375
|
+
resolve({ ok: true, user: user.name || user.email });
|
|
376
|
+
} else if (res.statusCode === 403 || res.statusCode === 401) {
|
|
377
|
+
resolve({ ok: false, error: 'Invalid Figma API token' });
|
|
378
|
+
} else {
|
|
379
|
+
resolve({ ok: false, error: `HTTP ${res.statusCode}` });
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
req.on('error', (err) => resolve({ ok: false, error: err.message }));
|
|
384
|
+
req.setTimeout(8000, () => { req.destroy(); resolve({ ok: false, error: 'Connection timeout' }); });
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async function ensureCredentialsGitignored(projectDir) {
|
|
389
|
+
const gitignorePath = path.join(projectDir, '.gitignore');
|
|
390
|
+
const entry = '.aiflow/credentials.json';
|
|
391
|
+
let content = '';
|
|
392
|
+
if (await fs.pathExists(gitignorePath)) {
|
|
393
|
+
content = await fs.readFile(gitignorePath, 'utf-8');
|
|
394
|
+
}
|
|
395
|
+
const lines = content.split('\n').map(l => l.trim());
|
|
396
|
+
if (lines.includes(entry)) return;
|
|
397
|
+
const separator = content.length > 0 && !content.endsWith('\n') ? '\n' : '';
|
|
398
|
+
await fs.appendFile(gitignorePath, `${separator}${entry}\n`);
|
|
399
|
+
console.log(chalk.yellow(` ⚠ Added ${entry} to .gitignore (contains API keys)`));
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function maskSecret(value) {
|
|
403
|
+
if (!value || value.length <= 8) return '***';
|
|
404
|
+
return `${value.slice(0, 4)}***${value.slice(-4)}`;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async function init(options) {
|
|
408
|
+
const projectDir = process.cwd();
|
|
409
|
+
const aiflowDir = path.join(projectDir, '.aiflow');
|
|
410
|
+
const frameworks = options.frameworks || (options.framework ? [options.framework] : []);
|
|
411
|
+
const adapters = options.adapters || (options.adapter ? [options.adapter] : []);
|
|
412
|
+
const primaryFramework = frameworks[0] || null;
|
|
413
|
+
const versionDir = path.join(aiflowDir, 'versions', PKG_VERSION);
|
|
414
|
+
|
|
415
|
+
try {
|
|
416
|
+
console.log(chalk.blue(`Initializing ai-flow-kit (v${PKG_VERSION})...`));
|
|
417
|
+
if (frameworks.length) console.log(chalk.gray(` Frameworks: ${frameworks.join(', ')}`));
|
|
418
|
+
if (adapters.length) console.log(chalk.gray(` Adapters: ${adapters.join(', ')}`));
|
|
419
|
+
|
|
420
|
+
await fs.ensureDir(versionDir);
|
|
421
|
+
await copyAssetsToVersion(versionDir, primaryFramework);
|
|
422
|
+
await verifySuperpowersSkills(versionDir);
|
|
423
|
+
await updateSymlinks(projectDir, versionDir);
|
|
424
|
+
|
|
425
|
+
// --- Added: Copy docs to project ---
|
|
426
|
+
await copyDocsToProject(projectDir);
|
|
427
|
+
|
|
428
|
+
const selectedTools = options.aiTools || Object.keys(AI_TOOL_FILES);
|
|
429
|
+
for (const fw of frameworks) {
|
|
430
|
+
await setupFramework(projectDir, fw, frameworks.length > 1, selectedTools);
|
|
431
|
+
}
|
|
432
|
+
for (const adapter of adapters) {
|
|
433
|
+
await setupAdapter(projectDir, adapter);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const stateFile = path.join(projectDir, '.aiflow', 'state.json');
|
|
437
|
+
if (await fs.pathExists(stateFile)) {
|
|
438
|
+
const state = await fs.readJson(stateFile);
|
|
439
|
+
state.frameworks = frameworks;
|
|
440
|
+
state.adapters = adapters;
|
|
441
|
+
state.aiTools = selectedTools;
|
|
442
|
+
await fs.writeJson(stateFile, state);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
console.log(chalk.green('\n✨ Initialized AI Flow Kit successfully!'));
|
|
446
|
+
console.log(chalk.blue('\n' + '─'.repeat(62)));
|
|
447
|
+
console.log(chalk.bold.white(' Next steps:'));
|
|
448
|
+
console.log(` ${chalk.green('aiflow use PROJ-33')} ${chalk.gray('← load ticket context')}`);
|
|
449
|
+
console.log(` ${chalk.green('claude')} ${chalk.gray('← Use Claude Code CLI (recommended)')}`);
|
|
450
|
+
console.log(` ${chalk.green('Cursor / Gemini')} ${chalk.gray('← Instructions loaded in .cursorrules / GEMINI.md')}`);
|
|
451
|
+
console.log(chalk.blue('─'.repeat(62)));
|
|
452
|
+
console.log(chalk.gray(` Full guide: ${chalk.green('aiflow guide')} | Command list: ${chalk.green('aiflow guide --commands')}`));
|
|
453
|
+
console.log(chalk.blue('─'.repeat(62)) + '\n');
|
|
454
|
+
} catch (err) {
|
|
455
|
+
console.error(chalk.red(`Error during initialization: ${err.message}`));
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
module.exports = init;
|
|
460
|
+
module.exports.AI_TOOL_FILES = AI_TOOL_FILES;
|