ac-framework 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +14 -0
- package/README.md +275 -24
- package/framework/.agent/skills/skill-writer/SKILL.md +385 -0
- package/framework/.amazonq/skills/skill-writer/SKILL.md +385 -0
- package/framework/.augment/skills/skill-writer/SKILL.md +385 -0
- package/framework/.claude/skills/skill-writer/SKILL.md +385 -0
- package/framework/.cline/skills/skill-writer/SKILL.md +385 -0
- package/framework/.codebuddy/skills/skill-writer/SKILL.md +385 -0
- package/framework/.codex/skills/skill-writer/SKILL.md +385 -0
- package/framework/.continue/skills/skill-writer/SKILL.md +385 -0
- package/framework/.cospec/skills/skill-writer/SKILL.md +385 -0
- package/framework/.crush/skills/skill-writer/SKILL.md +385 -0
- package/framework/.cursor/skills/skill-writer/SKILL.md +385 -0
- package/framework/.factory/skills/skill-writer/SKILL.md +385 -0
- package/framework/.gemini/skills/skill-writer/SKILL.md +385 -0
- package/framework/.github/skills/skill-writer/SKILL.md +385 -0
- package/framework/.iflow/skills/skill-writer/SKILL.md +385 -0
- package/framework/.kilocode/skills/skill-writer/SKILL.md +385 -0
- package/framework/.opencode/skills/skill-writer/SKILL.md +385 -0
- package/framework/.qoder/skills/skill-writer/SKILL.md +385 -0
- package/framework/.qwen/skills/skill-writer/SKILL.md +385 -0
- package/framework/.roo/skills/skill-writer/SKILL.md +385 -0
- package/framework/.trae/skills/skill-writer/SKILL.md +385 -0
- package/framework/.windsurf/skills/skill-writer/SKILL.md +385 -0
- package/framework/AGENTS.md +0 -0
- package/framework/CLAUDE.md +0 -0
- package/framework/GEMINI.md +0 -0
- package/framework/QWEN.md +0 -0
- package/framework/copilot-instructions.md +0 -0
- package/package.json +24 -4
- package/src/commands/init.js +169 -83
- package/src/config/constants.js +64 -0
- package/src/config/ide-mapping.js +47 -0
- package/src/index.js +4 -0
- package/src/services/detector.js +30 -0
- package/src/services/installer.js +77 -0
- package/src/utils/helpers.js +0 -28
package/src/commands/init.js
CHANGED
|
@@ -1,39 +1,30 @@
|
|
|
1
|
-
import { readdir, cp, access, rm } from 'node:fs/promises';
|
|
2
|
-
import { join, resolve, dirname } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
1
|
import chalk from 'chalk';
|
|
5
2
|
import gradient from 'gradient-string';
|
|
6
3
|
import inquirer from 'inquirer';
|
|
7
|
-
import { DESCRIPTIONS,
|
|
4
|
+
import { DESCRIPTIONS, ASSISTANT_ICONS, BUNDLED, ALWAYS_INSTALL } from '../config/constants.js';
|
|
5
|
+
import { IDE_MD_MAP, AVAILABLE_MD_FILES, MD_DESCRIPTIONS } from '../config/ide-mapping.js';
|
|
6
|
+
import { formatFolderName, sleep } from '../utils/helpers.js';
|
|
7
|
+
import { detectIDE } from '../services/detector.js';
|
|
8
|
+
import {
|
|
9
|
+
getSelectableModules,
|
|
10
|
+
expandWithBundled,
|
|
11
|
+
existsInTarget,
|
|
12
|
+
copyModule,
|
|
13
|
+
copyMdFile,
|
|
14
|
+
} from '../services/installer.js';
|
|
8
15
|
import {
|
|
9
16
|
matrixRain,
|
|
10
17
|
scanAnimation,
|
|
11
18
|
animatedSeparator,
|
|
12
19
|
revealList,
|
|
13
|
-
progressBar,
|
|
14
20
|
installWithAnimation,
|
|
15
21
|
celebrateSuccess,
|
|
16
22
|
showFailureSummary,
|
|
17
23
|
stepHeader,
|
|
18
24
|
} from '../ui/animations.js';
|
|
19
25
|
|
|
20
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
-
const __dirname = dirname(__filename);
|
|
22
|
-
|
|
23
26
|
const acGradient = gradient(['#6C5CE7', '#00CEC9', '#0984E3']);
|
|
24
27
|
|
|
25
|
-
const ALWAYS_INSTALL = ['openspec'];
|
|
26
|
-
|
|
27
|
-
async function getFrameworkFolders() {
|
|
28
|
-
const frameworkPath = resolve(__dirname, '../../framework');
|
|
29
|
-
const entries = await readdir(frameworkPath, { withFileTypes: true });
|
|
30
|
-
|
|
31
|
-
return entries
|
|
32
|
-
.filter((entry) => entry.isDirectory() && !ALWAYS_INSTALL.includes(entry.name))
|
|
33
|
-
.map((entry) => entry.name)
|
|
34
|
-
.sort((a, b) => a.localeCompare(b));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
28
|
function buildChoices(folders) {
|
|
38
29
|
const choices = [];
|
|
39
30
|
|
|
@@ -46,7 +37,7 @@ function buildChoices(folders) {
|
|
|
46
37
|
for (const folder of folders) {
|
|
47
38
|
const desc = DESCRIPTIONS[folder] || '';
|
|
48
39
|
const displayName = formatFolderName(folder);
|
|
49
|
-
const icon =
|
|
40
|
+
const icon = ASSISTANT_ICONS[folder] || '◦';
|
|
50
41
|
const label =
|
|
51
42
|
`${chalk.hex('#636E72')(icon)} ${chalk.hex('#DFE6E9').bold(displayName)}` +
|
|
52
43
|
(desc ? chalk.hex('#636E72')(` · ${desc}`) : '');
|
|
@@ -60,58 +51,128 @@ function buildChoices(folders) {
|
|
|
60
51
|
return choices;
|
|
61
52
|
}
|
|
62
53
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
'.cursor': '▸',
|
|
77
|
-
'.factory': '⊟',
|
|
78
|
-
'.gemini': '◇',
|
|
79
|
-
'.github': '◈',
|
|
80
|
-
'.iflow': '▹',
|
|
81
|
-
'.kilocode': '◎',
|
|
82
|
-
'.opencode': '⊡',
|
|
83
|
-
'.qoder': '◇',
|
|
84
|
-
'.qwen': '◈',
|
|
85
|
-
'.roo': '◆',
|
|
86
|
-
'.trae': '▸',
|
|
87
|
-
'.windsurf': '◇',
|
|
88
|
-
};
|
|
89
|
-
return icons[folder] || '◦';
|
|
54
|
+
/**
|
|
55
|
+
* Given the user's selected modules, determine which unique .md files
|
|
56
|
+
* are needed. Returns a de-duplicated sorted array.
|
|
57
|
+
*/
|
|
58
|
+
function getMdFilesForSelection(selectedModules) {
|
|
59
|
+
const mdSet = new Set();
|
|
60
|
+
for (const mod of selectedModules) {
|
|
61
|
+
const md = IDE_MD_MAP[mod];
|
|
62
|
+
if (md) {
|
|
63
|
+
mdSet.add(md);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return [...mdSet].sort();
|
|
90
67
|
}
|
|
91
68
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Step 3: Handle .md instruction file selection.
|
|
71
|
+
* Returns the final list of .md files to install.
|
|
72
|
+
*/
|
|
73
|
+
async function selectMdFiles(selected, targetDir) {
|
|
74
|
+
// Determine required .md files based on module selection
|
|
75
|
+
let requiredMd = getMdFilesForSelection(selected);
|
|
76
|
+
|
|
77
|
+
// Attempt IDE auto-detection
|
|
78
|
+
const detected = detectIDE();
|
|
79
|
+
if (detected.ide) {
|
|
80
|
+
const detectedBadge = chalk.hex('#2D3436').bgHex('#00CEC9').bold(` ${detected.ide} `);
|
|
81
|
+
console.log(` ${chalk.hex('#636E72')('Auto-detected:')} ${detectedBadge}`);
|
|
82
|
+
console.log();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Show which .md files will be installed
|
|
86
|
+
if (requiredMd.length > 0) {
|
|
87
|
+
console.log(chalk.hex('#B2BEC3')(' Instruction files for your selection:\n'));
|
|
88
|
+
for (const md of requiredMd) {
|
|
89
|
+
const desc = MD_DESCRIPTIONS[md] || '';
|
|
90
|
+
console.log(
|
|
91
|
+
chalk.hex('#00CEC9')(' ◆ ') +
|
|
92
|
+
chalk.hex('#DFE6E9').bold(md) +
|
|
93
|
+
(desc ? chalk.hex('#636E72')(` · ${desc}`) : '')
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
console.log();
|
|
97
|
+
} else {
|
|
98
|
+
console.log(chalk.hex('#636E72')(' No instruction files needed for your selection.\n'));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Offer additional .md files
|
|
102
|
+
const available = AVAILABLE_MD_FILES.filter((f) => !requiredMd.includes(f));
|
|
103
|
+
if (available.length > 0) {
|
|
104
|
+
const { wantMore } = await inquirer.prompt([{
|
|
105
|
+
type: 'confirm',
|
|
106
|
+
name: 'wantMore',
|
|
107
|
+
message: chalk.hex('#B2BEC3')('Install additional instruction files?'),
|
|
108
|
+
default: false,
|
|
109
|
+
}]);
|
|
110
|
+
|
|
111
|
+
if (wantMore) {
|
|
112
|
+
const { additional } = await inquirer.prompt([{
|
|
113
|
+
type: 'checkbox',
|
|
114
|
+
name: 'additional',
|
|
115
|
+
message: acGradient('Select additional files:'),
|
|
116
|
+
choices: available.map((f) => ({
|
|
117
|
+
name: chalk.hex('#DFE6E9').bold(f) +
|
|
118
|
+
chalk.hex('#636E72')(` · ${MD_DESCRIPTIONS[f] || ''}`),
|
|
119
|
+
value: f,
|
|
120
|
+
short: f,
|
|
121
|
+
})),
|
|
122
|
+
pageSize: 10,
|
|
123
|
+
}]);
|
|
124
|
+
requiredMd = [...requiredMd, ...additional];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check for existing .md files
|
|
129
|
+
const existingMd = [];
|
|
130
|
+
for (const md of requiredMd) {
|
|
131
|
+
if (await existsInTarget(targetDir, md)) {
|
|
132
|
+
existingMd.push(md);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (existingMd.length > 0) {
|
|
137
|
+
console.log(
|
|
138
|
+
chalk.hex('#FDCB6E')(' ⚠ These instruction files already exist:\n')
|
|
139
|
+
);
|
|
140
|
+
for (const md of existingMd) {
|
|
141
|
+
console.log(
|
|
142
|
+
chalk.hex('#FDCB6E')(' ▸ ') +
|
|
143
|
+
chalk.hex('#DFE6E9')(md)
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
console.log();
|
|
147
|
+
|
|
148
|
+
const { overwriteMd } = await inquirer.prompt([{
|
|
149
|
+
type: 'confirm',
|
|
150
|
+
name: 'overwriteMd',
|
|
151
|
+
message: chalk.hex('#FDCB6E')('Overwrite existing instruction files?'),
|
|
152
|
+
default: false,
|
|
153
|
+
}]);
|
|
154
|
+
|
|
155
|
+
if (!overwriteMd) {
|
|
156
|
+
requiredMd = requiredMd.filter((f) => !existingMd.includes(f));
|
|
157
|
+
}
|
|
98
158
|
}
|
|
159
|
+
|
|
160
|
+
return requiredMd;
|
|
99
161
|
}
|
|
100
162
|
|
|
101
163
|
export async function initCommand() {
|
|
102
164
|
const targetDir = process.cwd();
|
|
103
165
|
|
|
104
|
-
// ── Step 1: Scan
|
|
105
|
-
await stepHeader(1,
|
|
166
|
+
// ── Step 1/4: Scan ──────────────────────────────────────────────
|
|
167
|
+
await stepHeader(1, 4, 'Scanning framework modules');
|
|
106
168
|
await scanAnimation('Indexing available modules', 1000);
|
|
107
169
|
console.log();
|
|
108
170
|
|
|
109
|
-
// Matrix rain transition
|
|
110
171
|
await matrixRain(1800);
|
|
111
172
|
|
|
112
173
|
let folders;
|
|
113
174
|
try {
|
|
114
|
-
folders = await
|
|
175
|
+
folders = await getSelectableModules();
|
|
115
176
|
} catch {
|
|
116
177
|
console.log(chalk.hex('#D63031')(' ✗ Error: Could not read framework directory.'));
|
|
117
178
|
console.log(chalk.hex('#636E72')(' Make sure ac-framework is installed correctly.'));
|
|
@@ -123,7 +184,6 @@ export async function initCommand() {
|
|
|
123
184
|
process.exit(0);
|
|
124
185
|
}
|
|
125
186
|
|
|
126
|
-
// Module count display
|
|
127
187
|
const countBadge = chalk.hex('#2D3436').bgHex('#00CEC9').bold(` ${folders.length} `);
|
|
128
188
|
const autoBadge = chalk.hex('#2D3436').bgHex('#6C5CE7').bold(' +openspec ');
|
|
129
189
|
console.log(` ${countBadge} ${chalk.hex('#B2BEC3')('assistant modules found')} ${autoBadge} ${chalk.hex('#636E72')('auto-included')}`);
|
|
@@ -131,10 +191,9 @@ export async function initCommand() {
|
|
|
131
191
|
await animatedSeparator(60);
|
|
132
192
|
console.log();
|
|
133
193
|
|
|
134
|
-
// ── Step 2: Select
|
|
135
|
-
await stepHeader(2,
|
|
194
|
+
// ── Step 2/4: Select ────────────────────────────────────────────
|
|
195
|
+
await stepHeader(2, 4, 'Select your assistants');
|
|
136
196
|
|
|
137
|
-
// Controls hint with styled keys
|
|
138
197
|
const key = (k) => chalk.hex('#2D3436').bgHex('#636E72')(` ${k} `);
|
|
139
198
|
console.log(
|
|
140
199
|
` ${key('↑↓')} ${chalk.hex('#636E72')('navigate')} ` +
|
|
@@ -164,11 +223,17 @@ export async function initCommand() {
|
|
|
164
223
|
|
|
165
224
|
console.log();
|
|
166
225
|
|
|
167
|
-
// ── Check conflicts
|
|
168
|
-
const
|
|
226
|
+
// ── Check module conflicts ──────────────────────────────────────
|
|
227
|
+
const bundledForCheck = [];
|
|
228
|
+
for (const folder of selected) {
|
|
229
|
+
if (BUNDLED[folder]) {
|
|
230
|
+
bundledForCheck.push(...BUNDLED[folder]);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const allForCheck = [...selected, ...bundledForCheck, ...ALWAYS_INSTALL];
|
|
169
234
|
const existing = [];
|
|
170
235
|
for (const folder of allForCheck) {
|
|
171
|
-
if (await
|
|
236
|
+
if (await existsInTarget(targetDir, folder)) {
|
|
172
237
|
existing.push(folder);
|
|
173
238
|
}
|
|
174
239
|
}
|
|
@@ -210,7 +275,7 @@ export async function initCommand() {
|
|
|
210
275
|
}
|
|
211
276
|
}
|
|
212
277
|
|
|
213
|
-
// ──
|
|
278
|
+
// ── Reveal selection ────────────────────────────────────────────
|
|
214
279
|
console.log(chalk.hex('#B2BEC3')(' Selected modules:\n'));
|
|
215
280
|
|
|
216
281
|
const selectedItems = selected.map((folder) => {
|
|
@@ -228,6 +293,26 @@ export async function initCommand() {
|
|
|
228
293
|
|
|
229
294
|
console.log();
|
|
230
295
|
|
|
296
|
+
// ── Step 3/4: Instruction Files ─────────────────────────────────
|
|
297
|
+
await animatedSeparator(60);
|
|
298
|
+
console.log();
|
|
299
|
+
await stepHeader(3, 4, 'Instruction files');
|
|
300
|
+
|
|
301
|
+
const mdFiles = await selectMdFiles(selected, targetDir);
|
|
302
|
+
|
|
303
|
+
// Show combined summary if there are .md files
|
|
304
|
+
if (mdFiles.length > 0) {
|
|
305
|
+
console.log(chalk.hex('#B2BEC3')(' Instruction files to install:\n'));
|
|
306
|
+
const mdItems = mdFiles.map((md) => {
|
|
307
|
+
const desc = MD_DESCRIPTIONS[md] || '';
|
|
308
|
+
return chalk.hex('#DFE6E9').bold(md) +
|
|
309
|
+
(desc ? chalk.hex('#636E72')(` · ${desc}`) : '');
|
|
310
|
+
});
|
|
311
|
+
await revealList(mdItems, { prefix: '◆', color: '#6C5CE7', delay: 40 });
|
|
312
|
+
console.log();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// ── Final confirmation ──────────────────────────────────────────
|
|
231
316
|
const { confirm } = await inquirer.prompt([
|
|
232
317
|
{
|
|
233
318
|
type: 'confirm',
|
|
@@ -242,43 +327,44 @@ export async function initCommand() {
|
|
|
242
327
|
process.exit(0);
|
|
243
328
|
}
|
|
244
329
|
|
|
245
|
-
// ── Step
|
|
330
|
+
// ── Step 4/4: Install ───────────────────────────────────────────
|
|
246
331
|
console.log();
|
|
247
332
|
await animatedSeparator(60);
|
|
248
333
|
console.log();
|
|
249
|
-
await stepHeader(
|
|
334
|
+
await stepHeader(4, 4, 'Installing modules');
|
|
250
335
|
|
|
251
|
-
const
|
|
252
|
-
const allToInstall = [...selected, ...ALWAYS_INSTALL];
|
|
336
|
+
const allToInstall = expandWithBundled(selected);
|
|
253
337
|
let installed = 0;
|
|
254
338
|
const errors = [];
|
|
255
339
|
|
|
340
|
+
// Install module folders
|
|
256
341
|
for (const folder of allToInstall) {
|
|
257
342
|
const displayName = formatFolderName(folder);
|
|
258
|
-
|
|
259
343
|
try {
|
|
260
344
|
await installWithAnimation(displayName, async () => {
|
|
261
|
-
|
|
262
|
-
const dest = join(targetDir, folder);
|
|
263
|
-
|
|
264
|
-
await cp(src, dest, { recursive: true, force: true });
|
|
265
|
-
|
|
266
|
-
// Remove node_modules if present (e.g. .opencode)
|
|
267
|
-
try {
|
|
268
|
-
await rm(join(dest, 'node_modules'), { recursive: true, force: true });
|
|
269
|
-
} catch {
|
|
270
|
-
// Fine if it doesn't exist
|
|
271
|
-
}
|
|
345
|
+
await copyModule(folder, targetDir);
|
|
272
346
|
});
|
|
273
347
|
installed++;
|
|
274
348
|
} catch (err) {
|
|
275
349
|
errors.push({ folder, error: err.message });
|
|
276
350
|
}
|
|
351
|
+
await sleep(80);
|
|
352
|
+
}
|
|
277
353
|
|
|
354
|
+
// Install .md instruction files
|
|
355
|
+
for (const md of mdFiles) {
|
|
356
|
+
try {
|
|
357
|
+
await installWithAnimation(md, async () => {
|
|
358
|
+
await copyMdFile(md, targetDir);
|
|
359
|
+
});
|
|
360
|
+
installed++;
|
|
361
|
+
} catch (err) {
|
|
362
|
+
errors.push({ folder: md, error: err.message });
|
|
363
|
+
}
|
|
278
364
|
await sleep(80);
|
|
279
365
|
}
|
|
280
366
|
|
|
281
|
-
// ── Final result
|
|
367
|
+
// ── Final result ────────────────────────────────────────────────
|
|
282
368
|
if (errors.length === 0) {
|
|
283
369
|
await celebrateSuccess(installed, targetDir);
|
|
284
370
|
} else {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Modules that are always installed regardless of user selection.
|
|
2
|
+
export const ALWAYS_INSTALL = ['openspec'];
|
|
3
|
+
|
|
4
|
+
// When a key module is selected, its bundled companions are auto-installed.
|
|
5
|
+
export const BUNDLED = {
|
|
6
|
+
'.cline': ['.clinerules'],
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// Folders that should never appear in the selection list because they are
|
|
10
|
+
// installed automatically as part of a bundled assistant.
|
|
11
|
+
export const HIDDEN_FOLDERS = new Set(Object.values(BUNDLED).flat());
|
|
12
|
+
|
|
13
|
+
// Human-readable descriptions for each module, used in the selection UI.
|
|
14
|
+
export const DESCRIPTIONS = {
|
|
15
|
+
'.agent': 'Generic Agent Framework',
|
|
16
|
+
'.amazonq': 'AWS Amazon Q',
|
|
17
|
+
'.augment': 'Augment Code Assistant',
|
|
18
|
+
'.claude': 'Anthropic Claude Code',
|
|
19
|
+
'.cline': 'Cline VS Code Extension',
|
|
20
|
+
'.codebuddy': 'CodeBuddy Assistant',
|
|
21
|
+
'.codex': 'OpenAI Codex CLI',
|
|
22
|
+
'.continue': 'Continue.dev IDE Extension',
|
|
23
|
+
'.cospec': 'OpenSpec Native Framework',
|
|
24
|
+
'.crush': 'Crush Assistant',
|
|
25
|
+
'.cursor': 'Cursor IDE',
|
|
26
|
+
'.factory': 'Factory Assistant',
|
|
27
|
+
'.gemini': 'Google Gemini',
|
|
28
|
+
'.github': 'GitHub Copilot',
|
|
29
|
+
'.iflow': 'iFlow Assistant',
|
|
30
|
+
'.kilocode': 'Kilo Code',
|
|
31
|
+
'.opencode': 'OpenCode Framework',
|
|
32
|
+
'.qoder': 'Qoder Assistant',
|
|
33
|
+
'.qwen': 'Qwen (Alibaba Cloud)',
|
|
34
|
+
'.roo': 'Roo Code Extension',
|
|
35
|
+
'.trae': 'Trae IDE',
|
|
36
|
+
'.windsurf': 'Windsurf IDE',
|
|
37
|
+
'openspec': 'OpenSpec Configuration',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Icons for each assistant, used in the selection UI.
|
|
41
|
+
export const ASSISTANT_ICONS = {
|
|
42
|
+
'.agent': '⊡',
|
|
43
|
+
'.amazonq': '◈',
|
|
44
|
+
'.augment': '◇',
|
|
45
|
+
'.claude': '◉',
|
|
46
|
+
'.cline': '◎',
|
|
47
|
+
'.codebuddy': '◈',
|
|
48
|
+
'.codex': '⊞',
|
|
49
|
+
'.continue': '▹',
|
|
50
|
+
'.cospec': '⊙',
|
|
51
|
+
'.crush': '◆',
|
|
52
|
+
'.cursor': '▸',
|
|
53
|
+
'.factory': '⊟',
|
|
54
|
+
'.gemini': '◇',
|
|
55
|
+
'.github': '◈',
|
|
56
|
+
'.iflow': '▹',
|
|
57
|
+
'.kilocode': '◎',
|
|
58
|
+
'.opencode': '⊡',
|
|
59
|
+
'.qoder': '◇',
|
|
60
|
+
'.qwen': '◈',
|
|
61
|
+
'.roo': '◆',
|
|
62
|
+
'.trae': '▸',
|
|
63
|
+
'.windsurf': '◇',
|
|
64
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps each IDE/assistant module folder to the .md instruction file
|
|
3
|
+
* it expects in the project root. `null` means no .md file is needed
|
|
4
|
+
* (the assistant uses a different mechanism, like .clinerules).
|
|
5
|
+
*/
|
|
6
|
+
export const IDE_MD_MAP = {
|
|
7
|
+
'.agent': 'AGENTS.md',
|
|
8
|
+
'.amazonq': 'AGENTS.md',
|
|
9
|
+
'.augment': 'AGENTS.md',
|
|
10
|
+
'.claude': 'CLAUDE.md',
|
|
11
|
+
'.cline': null,
|
|
12
|
+
'.codebuddy': 'AGENTS.md',
|
|
13
|
+
'.codex': 'AGENTS.md',
|
|
14
|
+
'.continue': 'AGENTS.md',
|
|
15
|
+
'.cospec': 'AGENTS.md',
|
|
16
|
+
'.crush': 'AGENTS.md',
|
|
17
|
+
'.cursor': 'AGENTS.md',
|
|
18
|
+
'.factory': 'AGENTS.md',
|
|
19
|
+
'.gemini': 'GEMINI.md',
|
|
20
|
+
'.github': 'copilot-instructions.md',
|
|
21
|
+
'.iflow': 'AGENTS.md',
|
|
22
|
+
'.kilocode': 'AGENTS.md',
|
|
23
|
+
'.opencode': 'AGENTS.md',
|
|
24
|
+
'.qoder': 'AGENTS.md',
|
|
25
|
+
'.qwen': 'QWEN.md',
|
|
26
|
+
'.roo': 'AGENTS.md',
|
|
27
|
+
'.trae': 'AGENTS.md',
|
|
28
|
+
'.windsurf': 'AGENTS.md',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* All unique .md files available for installation.
|
|
33
|
+
*/
|
|
34
|
+
export const AVAILABLE_MD_FILES = [
|
|
35
|
+
...new Set(Object.values(IDE_MD_MAP).filter(Boolean)),
|
|
36
|
+
].sort();
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Human-readable descriptions for the .md instruction files.
|
|
40
|
+
*/
|
|
41
|
+
export const MD_DESCRIPTIONS = {
|
|
42
|
+
'AGENTS.md': 'Generic agent instructions (used by most assistants)',
|
|
43
|
+
'CLAUDE.md': 'Anthropic Claude Code instructions',
|
|
44
|
+
'GEMINI.md': 'Google Gemini instructions',
|
|
45
|
+
'QWEN.md': 'Qwen (Alibaba Cloud) instructions',
|
|
46
|
+
'copilot-instructions.md': 'GitHub Copilot instructions',
|
|
47
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { getSelectableModules, expandWithBundled, copyModule, copyMdFile } from './services/installer.js';
|
|
2
|
+
export { detectIDE } from './services/detector.js';
|
|
3
|
+
export { ALWAYS_INSTALL, BUNDLED, DESCRIPTIONS } from './config/constants.js';
|
|
4
|
+
export { IDE_MD_MAP, AVAILABLE_MD_FILES } from './config/ide-mapping.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attempts to detect the current IDE/editor from environment variables.
|
|
3
|
+
* Returns { ide, module } where `ide` is a display name and `module` is
|
|
4
|
+
* the corresponding framework folder. Both are null when undetectable.
|
|
5
|
+
*/
|
|
6
|
+
export function detectIDE() {
|
|
7
|
+
const env = process.env;
|
|
8
|
+
|
|
9
|
+
// Cursor: sets CURSOR_TRACE_DIR or other CURSOR_* vars
|
|
10
|
+
if (env.CURSOR_TRACE_DIR || Object.keys(env).some((k) => k.startsWith('CURSOR_'))) {
|
|
11
|
+
return { ide: 'Cursor', module: '.cursor' };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Windsurf: sets WINDSURF_* vars
|
|
15
|
+
if (Object.keys(env).some((k) => k.startsWith('WINDSURF_'))) {
|
|
16
|
+
return { ide: 'Windsurf', module: '.windsurf' };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Trae: sets TRAE_* vars
|
|
20
|
+
if (Object.keys(env).some((k) => k.startsWith('TRAE_'))) {
|
|
21
|
+
return { ide: 'Trae', module: '.trae' };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// VS Code / GitHub Copilot (without Cursor/Windsurf/Trae)
|
|
25
|
+
if (env.TERM_PROGRAM === 'vscode' || env.VSCODE_PID || env.VSCODE_CWD) {
|
|
26
|
+
return { ide: 'VS Code (Copilot)', module: '.github' };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { ide: null, module: null };
|
|
30
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { cp, rm, access, readdir } from 'node:fs/promises';
|
|
2
|
+
import { join, resolve, dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { ALWAYS_INSTALL, BUNDLED, HIDDEN_FOLDERS } from '../config/constants.js';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
/** Absolute path to the bundled framework/ directory inside this package. */
|
|
9
|
+
export const FRAMEWORK_PATH = resolve(__dirname, '../../framework');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Returns sorted list of selectable assistant folder names from framework/.
|
|
13
|
+
* Excludes ALWAYS_INSTALL items, HIDDEN_FOLDERS, and non-directories.
|
|
14
|
+
*/
|
|
15
|
+
export async function getSelectableModules() {
|
|
16
|
+
const entries = await readdir(FRAMEWORK_PATH, { withFileTypes: true });
|
|
17
|
+
return entries
|
|
18
|
+
.filter(
|
|
19
|
+
(e) =>
|
|
20
|
+
e.isDirectory() &&
|
|
21
|
+
!ALWAYS_INSTALL.includes(e.name) &&
|
|
22
|
+
!HIDDEN_FOLDERS.has(e.name),
|
|
23
|
+
)
|
|
24
|
+
.map((e) => e.name)
|
|
25
|
+
.sort((a, b) => a.localeCompare(b));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Expand a list of selected modules to include bundled companions
|
|
30
|
+
* and ALWAYS_INSTALL items. Returns the full installation list.
|
|
31
|
+
*/
|
|
32
|
+
export function expandWithBundled(selected) {
|
|
33
|
+
const bundled = [];
|
|
34
|
+
for (const folder of selected) {
|
|
35
|
+
if (BUNDLED[folder]) {
|
|
36
|
+
bundled.push(...BUNDLED[folder]);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return [...selected, ...bundled, ...ALWAYS_INSTALL];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check whether a folder or file already exists in the target directory.
|
|
44
|
+
*/
|
|
45
|
+
export async function existsInTarget(targetDir, name) {
|
|
46
|
+
try {
|
|
47
|
+
await access(join(targetDir, name));
|
|
48
|
+
return true;
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Copy a single module folder from framework/ to targetDir.
|
|
56
|
+
* Removes node_modules inside the destination if present.
|
|
57
|
+
*/
|
|
58
|
+
export async function copyModule(folder, targetDir) {
|
|
59
|
+
const src = join(FRAMEWORK_PATH, folder);
|
|
60
|
+
const dest = join(targetDir, folder);
|
|
61
|
+
await cp(src, dest, { recursive: true, force: true });
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
await rm(join(dest, 'node_modules'), { recursive: true, force: true });
|
|
65
|
+
} catch {
|
|
66
|
+
// Fine if it doesn't exist
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Copy a single .md instruction file from framework/ to targetDir root.
|
|
72
|
+
*/
|
|
73
|
+
export async function copyMdFile(mdFileName, targetDir) {
|
|
74
|
+
const src = join(FRAMEWORK_PATH, mdFileName);
|
|
75
|
+
const dest = join(targetDir, mdFileName);
|
|
76
|
+
await cp(src, dest, { force: true });
|
|
77
|
+
}
|
package/src/utils/helpers.js
CHANGED
|
@@ -3,34 +3,6 @@ export function sleep(ms) {
|
|
|
3
3
|
}
|
|
4
4
|
|
|
5
5
|
export function formatFolderName(name) {
|
|
6
|
-
// Remove leading dot for display
|
|
7
6
|
const clean = name.startsWith('.') ? name.slice(1) : name;
|
|
8
7
|
return clean.charAt(0).toUpperCase() + clean.slice(1);
|
|
9
8
|
}
|
|
10
|
-
|
|
11
|
-
export const DESCRIPTIONS = {
|
|
12
|
-
'.agent': 'Generic Agent Framework',
|
|
13
|
-
'.amazonq': 'AWS Amazon Q',
|
|
14
|
-
'.augment': 'Augment Code Assistant',
|
|
15
|
-
'.claude': 'Anthropic Claude Code',
|
|
16
|
-
'.cline': 'Cline VS Code Extension',
|
|
17
|
-
'.clinerules': 'Cline Shared Rules',
|
|
18
|
-
'.codebuddy': 'CodeBuddy Assistant',
|
|
19
|
-
'.codex': 'OpenAI Codex CLI',
|
|
20
|
-
'.continue': 'Continue.dev IDE Extension',
|
|
21
|
-
'.cospec': 'OpenSpec Native Framework',
|
|
22
|
-
'.crush': 'Crush Assistant',
|
|
23
|
-
'.cursor': 'Cursor IDE',
|
|
24
|
-
'.factory': 'Factory Assistant',
|
|
25
|
-
'.gemini': 'Google Gemini',
|
|
26
|
-
'.github': 'GitHub Copilot',
|
|
27
|
-
'.iflow': 'iFlow Assistant',
|
|
28
|
-
'.kilocode': 'Kilo Code',
|
|
29
|
-
'.opencode': 'OpenCode Framework',
|
|
30
|
-
'.qoder': 'Qoder Assistant',
|
|
31
|
-
'.qwen': 'Qwen (Alibaba Cloud)',
|
|
32
|
-
'.roo': 'Roo Code Extension',
|
|
33
|
-
'.trae': 'Trae IDE',
|
|
34
|
-
'.windsurf': 'Windsurf IDE',
|
|
35
|
-
'openspec': 'OpenSpec Configuration',
|
|
36
|
-
};
|