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.
Files changed (37) hide show
  1. package/LICENSE +14 -0
  2. package/README.md +275 -24
  3. package/framework/.agent/skills/skill-writer/SKILL.md +385 -0
  4. package/framework/.amazonq/skills/skill-writer/SKILL.md +385 -0
  5. package/framework/.augment/skills/skill-writer/SKILL.md +385 -0
  6. package/framework/.claude/skills/skill-writer/SKILL.md +385 -0
  7. package/framework/.cline/skills/skill-writer/SKILL.md +385 -0
  8. package/framework/.codebuddy/skills/skill-writer/SKILL.md +385 -0
  9. package/framework/.codex/skills/skill-writer/SKILL.md +385 -0
  10. package/framework/.continue/skills/skill-writer/SKILL.md +385 -0
  11. package/framework/.cospec/skills/skill-writer/SKILL.md +385 -0
  12. package/framework/.crush/skills/skill-writer/SKILL.md +385 -0
  13. package/framework/.cursor/skills/skill-writer/SKILL.md +385 -0
  14. package/framework/.factory/skills/skill-writer/SKILL.md +385 -0
  15. package/framework/.gemini/skills/skill-writer/SKILL.md +385 -0
  16. package/framework/.github/skills/skill-writer/SKILL.md +385 -0
  17. package/framework/.iflow/skills/skill-writer/SKILL.md +385 -0
  18. package/framework/.kilocode/skills/skill-writer/SKILL.md +385 -0
  19. package/framework/.opencode/skills/skill-writer/SKILL.md +385 -0
  20. package/framework/.qoder/skills/skill-writer/SKILL.md +385 -0
  21. package/framework/.qwen/skills/skill-writer/SKILL.md +385 -0
  22. package/framework/.roo/skills/skill-writer/SKILL.md +385 -0
  23. package/framework/.trae/skills/skill-writer/SKILL.md +385 -0
  24. package/framework/.windsurf/skills/skill-writer/SKILL.md +385 -0
  25. package/framework/AGENTS.md +0 -0
  26. package/framework/CLAUDE.md +0 -0
  27. package/framework/GEMINI.md +0 -0
  28. package/framework/QWEN.md +0 -0
  29. package/framework/copilot-instructions.md +0 -0
  30. package/package.json +24 -4
  31. package/src/commands/init.js +169 -83
  32. package/src/config/constants.js +64 -0
  33. package/src/config/ide-mapping.js +47 -0
  34. package/src/index.js +4 -0
  35. package/src/services/detector.js +30 -0
  36. package/src/services/installer.js +77 -0
  37. package/src/utils/helpers.js +0 -28
@@ -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, formatFolderName, sleep } from '../utils/helpers.js';
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 = getAssistantIcon(folder);
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
- function getAssistantIcon(folder) {
64
- const icons = {
65
- '.agent': '⊡',
66
- '.amazonq': '◈',
67
- '.augment': '◇',
68
- '.claude': '◉',
69
- '.cline': '◎',
70
- '.clinerules': '◎',
71
- '.codebuddy': '◈',
72
- '.codex': '⊞',
73
- '.continue': '▹',
74
- '.cospec': '⊙',
75
- '.crush': '◆',
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
- async function checkExisting(targetDir, folder) {
93
- try {
94
- await access(join(targetDir, folder));
95
- return true;
96
- } catch {
97
- return false;
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, 3, 'Scanning framework modules');
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 getFrameworkFolders();
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, 3, 'Select your assistants');
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 allForCheck = [...selected, ...ALWAYS_INSTALL];
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 checkExisting(targetDir, folder)) {
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
- // ── Confirm selection with animated reveal ─────────────────────
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 3: Install ────────────────────────────────────────────
330
+ // ── Step 4/4: Install ───────────────────────────────────────────
246
331
  console.log();
247
332
  await animatedSeparator(60);
248
333
  console.log();
249
- await stepHeader(3, 3, 'Installing modules');
334
+ await stepHeader(4, 4, 'Installing modules');
250
335
 
251
- const frameworkPath = resolve(__dirname, '../../framework');
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
- const src = join(frameworkPath, folder);
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
+ }
@@ -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
- };