ac-framework 1.0.0 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ac-framework",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Agentic Coding Framework - Multi-assistant configuration system with OpenSpec workflows",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -25,8 +25,7 @@
25
25
  "commander": "^12.1.0",
26
26
  "gradient-string": "^3.0.0",
27
27
  "inquirer": "^12.3.2",
28
- "nanospinner": "^1.2.2",
29
- "figlet": "^1.8.0"
28
+ "nanospinner": "^1.2.2"
30
29
  },
31
30
  "type": "module",
32
31
  "engines": {
package/src/cli.js CHANGED
@@ -1,15 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { Command } from 'commander';
4
+ import { readFile } from 'node:fs/promises';
5
+ import { dirname, resolve } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
4
7
  import { initCommand } from './commands/init.js';
5
8
  import { showBanner } from './ui/banner.js';
6
9
 
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+ const pkg = JSON.parse(await readFile(resolve(__dirname, '../package.json'), 'utf-8'));
12
+
7
13
  const program = new Command();
8
14
 
9
15
  program
10
16
  .name('acfm')
11
17
  .description('AC Framework - Agentic Coding Framework CLI')
12
- .version('1.0.0');
18
+ .version(pkg.version);
13
19
 
14
20
  program
15
21
  .command('init')
@@ -1,73 +1,94 @@
1
- import { readdir, cp, access } from 'node:fs/promises';
1
+ import { readdir, cp, access, rm } from 'node:fs/promises';
2
2
  import { join, resolve, dirname } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import chalk from 'chalk';
5
5
  import gradient from 'gradient-string';
6
6
  import inquirer from 'inquirer';
7
- import { createSpinner } from 'nanospinner';
8
7
  import { DESCRIPTIONS, formatFolderName, sleep } from '../utils/helpers.js';
9
- import { matrixRain, loadingAnimation, progressBar } from '../ui/animations.js';
8
+ import {
9
+ matrixRain,
10
+ scanAnimation,
11
+ animatedSeparator,
12
+ revealList,
13
+ progressBar,
14
+ installWithAnimation,
15
+ celebrateSuccess,
16
+ showFailureSummary,
17
+ stepHeader,
18
+ } from '../ui/animations.js';
10
19
 
11
20
  const __filename = fileURLToPath(import.meta.url);
12
21
  const __dirname = dirname(__filename);
13
22
 
14
23
  const acGradient = gradient(['#6C5CE7', '#00CEC9', '#0984E3']);
15
- const successGradient = gradient(['#00CEC9', '#00B894', '#55EFC4']);
24
+
25
+ const ALWAYS_INSTALL = ['openspec'];
16
26
 
17
27
  async function getFrameworkFolders() {
18
28
  const frameworkPath = resolve(__dirname, '../../framework');
19
29
  const entries = await readdir(frameworkPath, { withFileTypes: true });
20
30
 
21
31
  return entries
22
- .filter((entry) => entry.isDirectory())
32
+ .filter((entry) => entry.isDirectory() && !ALWAYS_INSTALL.includes(entry.name))
23
33
  .map((entry) => entry.name)
24
- .sort((a, b) => {
25
- // Hidden folders first, then normal
26
- const aHidden = a.startsWith('.');
27
- const bHidden = b.startsWith('.');
28
- if (aHidden && !bHidden) return -1;
29
- if (!aHidden && bHidden) return 1;
30
- return a.localeCompare(b);
31
- });
34
+ .sort((a, b) => a.localeCompare(b));
32
35
  }
33
36
 
34
37
  function buildChoices(folders) {
35
38
  const choices = [];
36
39
 
37
- const hidden = folders.filter((f) => f.startsWith('.'));
38
- const visible = folders.filter((f) => !f.startsWith('.'));
39
-
40
- if (hidden.length > 0) {
41
- choices.push(new inquirer.Separator(chalk.hex('#636E72')(' ── AI Assistants ──────────────────────────')));
42
- for (const folder of hidden) {
43
- const desc = DESCRIPTIONS[folder] || '';
44
- const displayName = formatFolderName(folder);
45
- const label = `${chalk.hex('#DFE6E9').bold(displayName)}${desc ? chalk.hex('#636E72')(` — ${desc}`) : ''}`;
46
- choices.push({
47
- name: label,
48
- value: folder,
49
- short: displayName,
50
- });
51
- }
52
- }
40
+ choices.push(new inquirer.Separator(
41
+ chalk.hex('#636E72')(' ── ') +
42
+ chalk.hex('#6C5CE7').bold('AI Assistants') +
43
+ chalk.hex('#636E72')(' ─────────────────────────────')
44
+ ));
53
45
 
54
- if (visible.length > 0) {
55
- choices.push(new inquirer.Separator(chalk.hex('#636E72')(' ── Configuration ─────────────────────────')));
56
- for (const folder of visible) {
57
- const desc = DESCRIPTIONS[folder] || '';
58
- const displayName = formatFolderName(folder);
59
- const label = `${chalk.hex('#DFE6E9').bold(displayName)}${desc ? chalk.hex('#636E72')(` — ${desc}`) : ''}`;
60
- choices.push({
61
- name: label,
62
- value: folder,
63
- short: displayName,
64
- });
65
- }
46
+ for (const folder of folders) {
47
+ const desc = DESCRIPTIONS[folder] || '';
48
+ const displayName = formatFolderName(folder);
49
+ const icon = getAssistantIcon(folder);
50
+ const label =
51
+ `${chalk.hex('#636E72')(icon)} ${chalk.hex('#DFE6E9').bold(displayName)}` +
52
+ (desc ? chalk.hex('#636E72')(` · ${desc}`) : '');
53
+ choices.push({
54
+ name: label,
55
+ value: folder,
56
+ short: displayName,
57
+ });
66
58
  }
67
59
 
68
60
  return choices;
69
61
  }
70
62
 
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] || '◦';
90
+ }
91
+
71
92
  async function checkExisting(targetDir, folder) {
72
93
  try {
73
94
  await access(join(targetDir, folder));
@@ -80,41 +101,47 @@ async function checkExisting(targetDir, folder) {
80
101
  export async function initCommand() {
81
102
  const targetDir = process.cwd();
82
103
 
83
- // Scanning animation
84
- await loadingAnimation('Scanning available modules...', 800);
104
+ // ── Step 1: Scan ───────────────────────────────────────────────
105
+ await stepHeader(1, 3, 'Scanning framework modules');
106
+ await scanAnimation('Indexing available modules', 1000);
85
107
  console.log();
86
108
 
87
- // Matrix rain effect
88
- console.log('\n'.repeat(6));
89
- await matrixRain(1500);
109
+ // Matrix rain transition
110
+ await matrixRain(1800);
90
111
 
91
112
  let folders;
92
113
  try {
93
114
  folders = await getFrameworkFolders();
94
115
  } catch {
95
- console.log(chalk.red(' Error: Could not read framework directory.'));
116
+ console.log(chalk.hex('#D63031')(' Error: Could not read framework directory.'));
96
117
  console.log(chalk.hex('#636E72')(' Make sure ac-framework is installed correctly.'));
97
118
  process.exit(1);
98
119
  }
99
120
 
100
121
  if (folders.length === 0) {
101
- console.log(chalk.yellow(' No modules found in framework directory.'));
122
+ console.log(chalk.hex('#FDCB6E')(' No modules found in framework directory.'));
102
123
  process.exit(0);
103
124
  }
104
125
 
105
- console.log(
106
- chalk.hex('#B2BEC3')(` Found ${chalk.hex('#00CEC9').bold(folders.length)} modules available\n`)
107
- );
126
+ // Module count display
127
+ const countBadge = chalk.hex('#2D3436').bgHex('#00CEC9').bold(` ${folders.length} `);
128
+ const autoBadge = chalk.hex('#2D3436').bgHex('#6C5CE7').bold(' +openspec ');
129
+ console.log(` ${countBadge} ${chalk.hex('#B2BEC3')('assistant modules found')} ${autoBadge} ${chalk.hex('#636E72')('auto-included')}`);
130
+ console.log();
131
+ await animatedSeparator(60);
132
+ console.log();
133
+
134
+ // ── Step 2: Select ─────────────────────────────────────────────
135
+ await stepHeader(2, 3, 'Select your assistants');
108
136
 
137
+ // Controls hint with styled keys
138
+ const key = (k) => chalk.hex('#2D3436').bgHex('#636E72')(` ${k} `);
109
139
  console.log(
110
- chalk.hex('#636E72')(' Use ') +
111
- chalk.hex('#00CEC9')('↑↓') +
112
- chalk.hex('#636E72')(' to navigate, ') +
113
- chalk.hex('#00CEC9')('Space') +
114
- chalk.hex('#636E72')(' to select, ') +
115
- chalk.hex('#00CEC9')('Enter') +
116
- chalk.hex('#636E72')(' to confirm\n')
140
+ ` ${key('↑↓')} ${chalk.hex('#636E72')('navigate')} ` +
141
+ `${key('Space')} ${chalk.hex('#636E72')('toggle')} ` +
142
+ `${key('Enter')} ${chalk.hex('#636E72')('confirm')}`
117
143
  );
144
+ console.log();
118
145
 
119
146
  const choices = buildChoices(folders);
120
147
 
@@ -122,13 +149,13 @@ export async function initCommand() {
122
149
  {
123
150
  type: 'checkbox',
124
151
  name: 'selected',
125
- message: acGradient('Select modules to install:'),
152
+ message: acGradient('Choose modules to install:'),
126
153
  choices,
127
154
  pageSize: 15,
128
155
  loop: false,
129
156
  validate(answer) {
130
157
  if (answer.length === 0) {
131
- return chalk.hex('#D63031')('You must select at least one module.');
158
+ return chalk.hex('#D63031')('Select at least one module. Use Space to toggle.');
132
159
  }
133
160
  return true;
134
161
  },
@@ -137,9 +164,10 @@ export async function initCommand() {
137
164
 
138
165
  console.log();
139
166
 
140
- // Check for existing folders
167
+ // ── Check conflicts ────────────────────────────────────────────
168
+ const allForCheck = [...selected, ...ALWAYS_INSTALL];
141
169
  const existing = [];
142
- for (const folder of selected) {
170
+ for (const folder of allForCheck) {
143
171
  if (await checkExisting(targetDir, folder)) {
144
172
  existing.push(folder);
145
173
  }
@@ -147,10 +175,14 @@ export async function initCommand() {
147
175
 
148
176
  if (existing.length > 0) {
149
177
  console.log(
150
- chalk.hex('#FDCB6E')(' ⚠ The following modules already exist in your project:\n')
178
+ chalk.hex('#FDCB6E')(' ⚠ These modules already exist in your project:\n')
151
179
  );
152
180
  for (const folder of existing) {
153
- console.log(chalk.hex('#FDCB6E')(` • ${formatFolderName(folder)} (${folder})`));
181
+ console.log(
182
+ chalk.hex('#FDCB6E')(' ▸ ') +
183
+ chalk.hex('#DFE6E9')(formatFolderName(folder)) +
184
+ chalk.hex('#636E72')(` (${folder})`)
185
+ );
154
186
  }
155
187
  console.log();
156
188
 
@@ -165,28 +197,35 @@ export async function initCommand() {
165
197
 
166
198
  if (!overwrite) {
167
199
  const filtered = selected.filter((f) => !existing.includes(f));
168
- if (filtered.length === 0) {
169
- console.log(chalk.hex('#636E72')('\n No new modules to install. Exiting.\n'));
200
+ const autoFiltered = ALWAYS_INSTALL.filter((f) => !existing.includes(f));
201
+ if (filtered.length === 0 && autoFiltered.length === 0) {
202
+ console.log(chalk.hex('#636E72')('\n Nothing new to install. Exiting.\n'));
170
203
  process.exit(0);
171
204
  }
172
205
  selected.length = 0;
173
206
  selected.push(...filtered);
174
207
  console.log(
175
- chalk.hex('#B2BEC3')(`\n Proceeding with ${chalk.hex('#00CEC9').bold(selected.length)} new module(s)...\n`)
208
+ chalk.hex('#B2BEC3')(`\n Continuing with ${chalk.hex('#00CEC9').bold(filtered.length + autoFiltered.length)} new module(s)...\n`)
176
209
  );
177
210
  }
178
211
  }
179
212
 
180
- // Confirm selection
181
- console.log(chalk.hex('#B2BEC3')(' Modules to install:\n'));
182
- for (const folder of selected) {
213
+ // ── Confirm selection with animated reveal ─────────────────────
214
+ console.log(chalk.hex('#B2BEC3')(' Selected modules:\n'));
215
+
216
+ const selectedItems = selected.map((folder) => {
183
217
  const desc = DESCRIPTIONS[folder] || '';
184
- console.log(
185
- chalk.hex('#00CEC9')(' ◆ ') +
186
- chalk.hex('#DFE6E9').bold(formatFolderName(folder)) +
187
- (desc ? chalk.hex('#636E72')(` — ${desc}`) : '')
188
- );
189
- }
218
+ return chalk.hex('#DFE6E9').bold(formatFolderName(folder)) +
219
+ (desc ? chalk.hex('#636E72')(` · ${desc}`) : '');
220
+ });
221
+ selectedItems.push(
222
+ chalk.hex('#DFE6E9').bold('Openspec') +
223
+ chalk.hex('#636E72')(` · ${DESCRIPTIONS['openspec']}`) +
224
+ chalk.hex('#6C5CE7').italic(' (auto)')
225
+ );
226
+
227
+ await revealList(selectedItems, { prefix: '◆', color: '#00CEC9', delay: 40 });
228
+
190
229
  console.log();
191
230
 
192
231
  const { confirm } = await inquirer.prompt([
@@ -203,73 +242,46 @@ export async function initCommand() {
203
242
  process.exit(0);
204
243
  }
205
244
 
245
+ // ── Step 3: Install ────────────────────────────────────────────
246
+ console.log();
247
+ await animatedSeparator(60);
206
248
  console.log();
249
+ await stepHeader(3, 3, 'Installing modules');
207
250
 
208
- // Install modules with progress
209
251
  const frameworkPath = resolve(__dirname, '../../framework');
252
+ const allToInstall = [...selected, ...ALWAYS_INSTALL];
210
253
  let installed = 0;
211
254
  const errors = [];
212
255
 
213
- for (const folder of selected) {
214
- const spinner = createSpinner(
215
- chalk.hex('#B2BEC3')(`Installing ${chalk.hex('#DFE6E9').bold(formatFolderName(folder))}...`)
216
- ).start();
256
+ for (const folder of allToInstall) {
257
+ const displayName = formatFolderName(folder);
217
258
 
218
259
  try {
219
- const src = join(frameworkPath, folder);
220
- const dest = join(targetDir, folder);
221
-
222
- await cp(src, dest, { recursive: true, force: true });
223
-
224
- // Skip node_modules if copied
225
- const nmPath = join(dest, 'node_modules');
226
- try {
227
- const { rm } = await import('node:fs/promises');
228
- await rm(nmPath, { recursive: true, force: true });
229
- } catch {
230
- // node_modules didn't exist, that's fine
231
- }
260
+ await installWithAnimation(displayName, async () => {
261
+ const src = join(frameworkPath, folder);
262
+ const dest = join(targetDir, folder);
232
263
 
233
- installed++;
234
- spinner.success({
235
- text:
236
- chalk.hex('#00CEC9')(` ${formatFolderName(folder)}`) +
237
- chalk.hex('#636E72')(` ${folder}/`),
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
+ }
238
272
  });
273
+ installed++;
239
274
  } catch (err) {
240
275
  errors.push({ folder, error: err.message });
241
- spinner.error({
242
- text: chalk.hex('#D63031')(` Failed: ${formatFolderName(folder)} — ${err.message}`),
243
- });
244
276
  }
245
277
 
246
- await sleep(150);
278
+ await sleep(80);
247
279
  }
248
280
 
249
- // Final summary
250
- console.log();
251
- console.log(
252
- gradient(['#636E72', '#B2BEC3'])(' ─────────────────────────────────────────────────────────────────')
253
- );
254
- console.log();
255
-
281
+ // ── Final result ───────────────────────────────────────────────
256
282
  if (errors.length === 0) {
257
- await progressBar('Finalizing', 20, 600);
258
- console.log();
259
- console.log(successGradient(' ✓ Installation complete!'));
260
- console.log();
261
- console.log(
262
- chalk.hex('#B2BEC3')(` ${chalk.hex('#00CEC9').bold(installed)} module(s) installed successfully in ${chalk.hex('#DFE6E9')(targetDir)}`)
263
- );
283
+ await celebrateSuccess(installed, targetDir);
264
284
  } else {
265
- console.log(
266
- chalk.hex('#FDCB6E')(` ${installed} installed, ${errors.length} failed.`)
267
- );
285
+ await showFailureSummary(installed, errors);
268
286
  }
269
-
270
- console.log();
271
- console.log(
272
- chalk.hex('#636E72')(' Your project is ready. Happy coding!')
273
- );
274
- console.log();
275
287
  }
@@ -1,44 +1,57 @@
1
1
  import chalk from 'chalk';
2
+ import gradient from 'gradient-string';
2
3
  import { createSpinner } from 'nanospinner';
3
4
  import { sleep } from '../utils/helpers.js';
4
5
 
5
- const frames = ['', '', '', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
6
+ const acGradient = gradient(['#6C5CE7', '#00CEC9', '#0984E3']);
7
+ const successGradient = gradient(['#00CEC9', '#00B894', '#55EFC4']);
8
+ const warmGradient = gradient(['#FDCB6E', '#E17055', '#D63031']);
9
+ const glowGradient = gradient(['#0984E3', '#00CEC9', '#55EFC4', '#00CEC9', '#0984E3']);
6
10
 
7
- export async function matrixRain(durationMs = 1500) {
11
+ // ── Matrix Rain ──────────────────────────────────────────────────
12
+
13
+ export async function matrixRain(durationMs = 1800) {
8
14
  const cols = Math.min(process.stdout.columns || 80, 80);
15
+ const rows = 8;
9
16
  const drops = Array.from({ length: cols }, () => ({
10
- y: Math.floor(Math.random() * 6),
11
- speed: 1 + Math.floor(Math.random() * 3),
12
- char: '',
17
+ y: Math.floor(Math.random() * rows),
18
+ speed: 1 + Math.floor(Math.random() * 2),
19
+ trail: 2 + Math.floor(Math.random() * 3),
13
20
  }));
14
- const chars = '01アイウエオカキクケコサシスセソタチツテトABCDEFacfm';
15
- const colors = ['#00CEC9', '#0984E3', '#6C5CE7', '#00FF41'];
16
- const rows = 6;
17
- const frameTime = 60;
21
+ const chars = '01アイウエオカキクケコサシスセソ>>=</>{}[]ACFM';
22
+ const colors = ['#00CEC9', '#0984E3', '#6C5CE7', '#00FF41', '#55EFC4'];
23
+ const frameTime = 50;
18
24
  const totalFrames = Math.floor(durationMs / frameTime);
19
25
 
26
+ // Print initial empty rows
27
+ for (let i = 0; i < rows; i++) console.log();
28
+
20
29
  for (let frame = 0; frame < totalFrames; frame++) {
21
- let output = '';
22
30
  const grid = Array.from({ length: rows }, () => Array(cols).fill(' '));
23
31
 
24
32
  for (let c = 0; c < cols; c++) {
25
33
  const drop = drops[c];
26
- if (drop.y < rows) {
27
- const ch = chars[Math.floor(Math.random() * chars.length)];
28
- grid[drop.y][c] = chalk.hex(colors[Math.floor(Math.random() * colors.length)])(ch);
29
- if (drop.y > 0) {
30
- grid[drop.y - 1][c] = chalk.hex('#2D3436')(
31
- chars[Math.floor(Math.random() * chars.length)]
32
- );
34
+ // Draw trail
35
+ for (let t = 0; t < drop.trail; t++) {
36
+ const ty = drop.y - t;
37
+ if (ty >= 0 && ty < rows) {
38
+ const ch = chars[Math.floor(Math.random() * chars.length)];
39
+ const brightness = t === 0 ? '#FFFFFF' : colors[Math.floor(Math.random() * colors.length)];
40
+ const opacity = t === 0 ? 1 : Math.max(0.2, 1 - t * 0.3);
41
+ grid[ty][c] = t === 0
42
+ ? chalk.hex(brightness).bold(ch)
43
+ : chalk.hex(brightness)(ch);
33
44
  }
34
45
  }
35
46
  drop.y += drop.speed;
36
- if (drop.y > rows + 3) {
37
- drop.y = -Math.floor(Math.random() * 4);
38
- drop.speed = 1 + Math.floor(Math.random() * 3);
47
+ if (drop.y - drop.trail > rows) {
48
+ drop.y = -Math.floor(Math.random() * 6);
49
+ drop.speed = 1 + Math.floor(Math.random() * 2);
50
+ drop.trail = 2 + Math.floor(Math.random() * 3);
39
51
  }
40
52
  }
41
53
 
54
+ let output = '';
42
55
  for (const row of grid) {
43
56
  output += ' ' + row.join('') + '\n';
44
57
  }
@@ -48,7 +61,25 @@ export async function matrixRain(durationMs = 1500) {
48
61
  await sleep(frameTime);
49
62
  }
50
63
 
51
- // Clear the matrix area
64
+ // Fade out effect
65
+ for (let fade = 0; fade < 4; fade++) {
66
+ process.stdout.write(`\x1B[${rows}A`);
67
+ for (let r = 0; r < rows; r++) {
68
+ let line = ' ';
69
+ for (let c = 0; c < cols; c++) {
70
+ if (Math.random() < 0.3 - fade * 0.07) {
71
+ const ch = chars[Math.floor(Math.random() * chars.length)];
72
+ line += chalk.hex('#2D3436')(ch);
73
+ } else {
74
+ line += ' ';
75
+ }
76
+ }
77
+ console.log(line);
78
+ }
79
+ await sleep(60);
80
+ }
81
+
82
+ // Clear the area
52
83
  process.stdout.write(`\x1B[${rows}A`);
53
84
  for (let i = 0; i < rows; i++) {
54
85
  process.stdout.write('\x1B[2K\n');
@@ -56,27 +87,243 @@ export async function matrixRain(durationMs = 1500) {
56
87
  process.stdout.write(`\x1B[${rows}A`);
57
88
  }
58
89
 
59
- export async function loadingAnimation(text, durationMs = 1200) {
60
- const spinner = createSpinner(chalk.hex('#B2BEC3')(text)).start();
61
- await sleep(durationMs);
62
- spinner.success({ text: chalk.hex('#00CEC9')(text) });
90
+ // ── Scanning / Loading ───────────────────────────────────────────
91
+
92
+ export async function scanAnimation(text, durationMs = 1000) {
93
+ const frames = ['', '◠', '◝', '◞', '◡', '◟'];
94
+ const totalFrames = Math.floor(durationMs / 80);
95
+
96
+ for (let i = 0; i < totalFrames; i++) {
97
+ const frame = frames[i % frames.length];
98
+ const dots = '.'.repeat((i % 3) + 1).padEnd(3);
99
+ process.stdout.write(
100
+ `\r ${chalk.hex('#00CEC9')(frame)} ${chalk.hex('#B2BEC3')(text)}${chalk.hex('#636E72')(dots)}`
101
+ );
102
+ await sleep(80);
103
+ }
104
+ process.stdout.write(
105
+ `\r ${chalk.hex('#00CEC9')('◉')} ${chalk.hex('#00CEC9')(text)} \n`
106
+ );
107
+ }
108
+
109
+ // ── Animated Separator ───────────────────────────────────────────
110
+
111
+ export async function animatedSeparator(width = 60) {
112
+ const ch = '─';
113
+ for (let i = 0; i <= width; i++) {
114
+ const before = ch.repeat(i);
115
+ const dot = '●';
116
+ const after = ch.repeat(Math.max(0, width - i));
117
+ process.stdout.write(
118
+ `\r ${glowGradient(before)}${chalk.hex('#00CEC9')(dot)}${chalk.hex('#2D3436')(after)}`
119
+ );
120
+ await sleep(4);
121
+ }
122
+ process.stdout.write(`\r ${glowGradient(ch.repeat(width))} \n`);
123
+ }
124
+
125
+ // ── Staggered List Reveal ────────────────────────────────────────
126
+
127
+ export async function revealList(items, { prefix = '◆', color = '#00CEC9', delay = 60 } = {}) {
128
+ for (const item of items) {
129
+ // Slide in from left
130
+ const maxSlide = 6;
131
+ for (let s = maxSlide; s >= 0; s--) {
132
+ const pad = ' '.repeat(s);
133
+ process.stdout.write(
134
+ `\r ${pad}${chalk.hex(color)(prefix)} ${item}`
135
+ );
136
+ await sleep(15);
137
+ }
138
+ console.log();
139
+ await sleep(delay);
140
+ }
63
141
  }
64
142
 
65
- export async function progressBar(label, steps = 20, durationMs = 800) {
66
- const filled = '█';
67
- const empty = '░';
68
- const barWidth = 30;
143
+ // ── Progress Bar (enhanced) ──────────────────────────────────────
144
+
145
+ export async function progressBar(label, steps = 30, durationMs = 1000) {
146
+ const barWidth = 35;
147
+ const blocks = ['░', '▒', '▓', '█'];
69
148
 
70
149
  for (let i = 0; i <= steps; i++) {
71
- const progress = Math.round((i / steps) * barWidth);
72
- const bar =
73
- chalk.hex('#6C5CE7')(filled.repeat(progress)) +
74
- chalk.hex('#2D3436')(empty.repeat(barWidth - progress));
150
+ const progress = (i / steps) * barWidth;
151
+ const full = Math.floor(progress);
152
+ const partial = progress - full;
153
+
154
+ let bar = '';
155
+ for (let b = 0; b < barWidth; b++) {
156
+ if (b < full) {
157
+ bar += chalk.hex('#6C5CE7')('█');
158
+ } else if (b === full) {
159
+ const blockIdx = Math.floor(partial * blocks.length);
160
+ bar += chalk.hex('#A29BFE')(blocks[Math.min(blockIdx, blocks.length - 1)]);
161
+ } else {
162
+ bar += chalk.hex('#2D3436')('░');
163
+ }
164
+ }
165
+
75
166
  const pct = Math.round((i / steps) * 100);
167
+ const pctStr = `${pct}%`.padStart(4);
168
+
169
+ // Spinner character
170
+ const spinChars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
171
+ const spin = i < steps
172
+ ? chalk.hex('#00CEC9')(spinChars[i % spinChars.length])
173
+ : chalk.hex('#00CEC9')('✓');
174
+
76
175
  process.stdout.write(
77
- `\r ${chalk.hex('#B2BEC3')(label)} ${bar} ${chalk.hex('#00CEC9')(`${pct}%`)}`
176
+ `\r ${spin} ${chalk.hex('#B2BEC3')(label)} ${bar} ${chalk.hex('#00CEC9')(pctStr)}`
78
177
  );
79
178
  await sleep(durationMs / steps);
80
179
  }
81
180
  process.stdout.write('\n');
82
181
  }
182
+
183
+ // ── Installation Spinner (enhanced) ──────────────────────────────
184
+
185
+ export async function installWithAnimation(name, task) {
186
+ const frames = ['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷'];
187
+ let frameIdx = 0;
188
+ let running = true;
189
+
190
+ const animate = async () => {
191
+ while (running) {
192
+ const frame = chalk.hex('#6C5CE7')(frames[frameIdx % frames.length]);
193
+ process.stdout.write(
194
+ `\r ${frame} ${chalk.hex('#B2BEC3')('Installing')} ${chalk.hex('#DFE6E9').bold(name)}${chalk.hex('#636E72')('...')}`
195
+ );
196
+ frameIdx++;
197
+ await sleep(60);
198
+ }
199
+ };
200
+
201
+ const animPromise = animate();
202
+
203
+ try {
204
+ await task();
205
+ running = false;
206
+ await sleep(70);
207
+ process.stdout.write(
208
+ `\r ${chalk.hex('#00CEC9')('✓')} ${chalk.hex('#00CEC9')(name)}${chalk.hex('#636E72')(' installed successfully')} \n`
209
+ );
210
+ } catch (err) {
211
+ running = false;
212
+ await sleep(70);
213
+ process.stdout.write(
214
+ `\r ${chalk.hex('#D63031')('✗')} ${chalk.hex('#D63031')(name)}${chalk.hex('#636E72')(` — ${err.message}`)} \n`
215
+ );
216
+ throw err;
217
+ }
218
+ }
219
+
220
+ // ── Success Celebration ──────────────────────────────────────────
221
+
222
+ export async function celebrateSuccess(moduleCount, targetDir) {
223
+ console.log();
224
+ await animatedSeparator(60);
225
+ console.log();
226
+
227
+ // Big checkmark animation
228
+ const check = [
229
+ ' ██╗',
230
+ ' ██╔╝',
231
+ ' ██╔╝ ',
232
+ ' ██╗ ██╔╝ ',
233
+ ' ╚████╔╝ ',
234
+ ' ╚═══╝ ',
235
+ ];
236
+
237
+ for (const line of check) {
238
+ console.log(successGradient(line));
239
+ await sleep(50);
240
+ }
241
+
242
+ console.log();
243
+ await progressBar('Finalizing', 30, 800);
244
+ console.log();
245
+
246
+ // Sparkle animation on the success message
247
+ const msg = ' Installation complete!';
248
+ const sparkles = ['✦', '✧', '⊹', '✶', '⋆'];
249
+ for (let i = 0; i < 3; i++) {
250
+ const s1 = sparkles[Math.floor(Math.random() * sparkles.length)];
251
+ const s2 = sparkles[Math.floor(Math.random() * sparkles.length)];
252
+ const s3 = sparkles[Math.floor(Math.random() * sparkles.length)];
253
+ process.stdout.write(
254
+ `\r ${chalk.hex('#FDCB6E')(s1)} ${successGradient(msg.trim())} ${chalk.hex('#FDCB6E')(s2)} ${chalk.hex('#00CEC9')(s3)}`
255
+ );
256
+ await sleep(200);
257
+ }
258
+ console.log();
259
+ console.log();
260
+
261
+ // Module count badge
262
+ const countBadge = chalk.hex('#2D3436').bgHex('#00CEC9').bold(` ${moduleCount} modules `);
263
+ const pathBadge = chalk.hex('#636E72')(targetDir);
264
+ console.log(` ${countBadge} ${chalk.hex('#636E72')('installed in')} ${pathBadge}`);
265
+
266
+ console.log();
267
+
268
+ // Tips box
269
+ const boxW = 52;
270
+ const topBorder = chalk.hex('#636E72')(' ┌' + '─'.repeat(boxW) + '┐');
271
+ const botBorder = chalk.hex('#636E72')(' └' + '─'.repeat(boxW) + '┘');
272
+ const line = (content, raw) => {
273
+ const pad = boxW - raw.length;
274
+ return chalk.hex('#636E72')(' │') + content + ' '.repeat(Math.max(0, pad)) + chalk.hex('#636E72')('│');
275
+ };
276
+
277
+ console.log(topBorder);
278
+ console.log(line(
279
+ chalk.hex('#FDCB6E')(' ⚡ Quick Start'),
280
+ ' ⚡ Quick Start'
281
+ ));
282
+ console.log(line(
283
+ chalk.hex('#636E72')(' '),
284
+ ' '
285
+ ));
286
+ console.log(line(
287
+ chalk.hex('#B2BEC3')(' Your AI assistants are ready to use.'),
288
+ ' Your AI assistants are ready to use.'
289
+ ));
290
+ console.log(line(
291
+ chalk.hex('#B2BEC3')(' Open your project in your preferred IDE.'),
292
+ ' Open your project in your preferred IDE.'
293
+ ));
294
+ console.log(botBorder);
295
+
296
+ console.log();
297
+ console.log(chalk.hex('#636E72')(' Happy coding! ') + chalk.hex('#00CEC9')('→') + chalk.hex('#636E72')(' ac-framework'));
298
+ console.log();
299
+ }
300
+
301
+ // ── Failure Summary ──────────────────────────────────────────────
302
+
303
+ export async function showFailureSummary(installed, errors) {
304
+ console.log();
305
+ await animatedSeparator(60);
306
+ console.log();
307
+
308
+ console.log(
309
+ warmGradient(` ⚠ ${installed} installed, ${errors.length} failed`)
310
+ );
311
+ console.log();
312
+
313
+ for (const { folder, error } of errors) {
314
+ console.log(
315
+ chalk.hex('#D63031')(' ✗ ') +
316
+ chalk.hex('#DFE6E9')(folder) +
317
+ chalk.hex('#636E72')(` — ${error}`)
318
+ );
319
+ }
320
+ console.log();
321
+ }
322
+
323
+ // ── Step Header ──────────────────────────────────────────────────
324
+
325
+ export async function stepHeader(stepNum, totalSteps, label) {
326
+ const stepBadge = chalk.hex('#2D3436').bgHex('#6C5CE7').bold(` ${stepNum}/${totalSteps} `);
327
+ console.log(` ${stepBadge} ${chalk.hex('#DFE6E9')(label)}`);
328
+ console.log();
329
+ }
package/src/ui/banner.js CHANGED
@@ -1,44 +1,101 @@
1
- import figlet from 'figlet';
2
1
  import gradient from 'gradient-string';
3
2
  import chalk from 'chalk';
4
3
  import { sleep } from '../utils/helpers.js';
5
4
 
6
- const acGradient = gradient(['#6C5CE7', '#00CEC9', '#0984E3', '#6C5CE7']);
7
- const subtleGradient = gradient(['#636E72', '#B2BEC3']);
5
+ const acGradient = gradient(['#6C5CE7', '#A29BFE', '#00CEC9', '#0984E3', '#6C5CE7']);
6
+ const dimGradient = gradient(['#2D3436', '#636E72', '#2D3436']);
7
+ const glowGradient = gradient(['#0984E3', '#00CEC9', '#55EFC4', '#00CEC9', '#0984E3']);
8
+
9
+ const LOGO = [
10
+ ' ██████╗ ██████╗ ███████╗██████╗ █████╗ ███╗ ███╗███████╗',
11
+ ' ██╔══██╗██╔════╝ ██╔════╝██╔══██╗██╔══██╗████╗ ████║██╔════╝',
12
+ ' ███████║██║ █████╗ ██████╔╝███████║██╔████╔██║█████╗ ',
13
+ ' ██╔══██║██║ ██╔══╝ ██╔══██╗██╔══██║██║╚██╔╝██║██╔══╝ ',
14
+ ' ██║ ██║╚██████╗ ██║ ██║ ██║██║ ██║██║ ╚═╝ ██║███████╗',
15
+ ' ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝',
16
+ ];
8
17
 
9
18
  export async function showBanner() {
10
19
  console.clear();
11
20
  console.log();
12
21
 
13
- const asciiArt = figlet.textSync('AC Framework', {
14
- font: 'ANSI Shadow',
15
- horizontalLayout: 'fitted',
16
- });
22
+ // Phase 1: Glitch-in effect — show random noise then resolve to the logo
23
+ const maxWidth = Math.max(...LOGO.map((l) => l.length));
24
+ const glitchChars = '█▓▒░╗╔╝╚═║╬╣╠╩╦';
25
+ const glitchSteps = 6;
26
+
27
+ for (let step = 0; step < glitchSteps; step++) {
28
+ const ratio = step / (glitchSteps - 1); // 0 → 1
29
+ const output = [];
30
+ for (const line of LOGO) {
31
+ let result = '';
32
+ for (let i = 0; i < line.length; i++) {
33
+ if (line[i] === ' ') {
34
+ result += ' ';
35
+ } else if (Math.random() < ratio) {
36
+ result += line[i];
37
+ } else {
38
+ result += glitchChars[Math.floor(Math.random() * glitchChars.length)];
39
+ }
40
+ }
41
+ output.push(result);
42
+ }
17
43
 
18
- // Typing animation for the ASCII art
19
- const lines = asciiArt.split('\n');
20
- for (const line of lines) {
21
- console.log(acGradient(line));
22
- await sleep(40);
44
+ if (step > 0) {
45
+ process.stdout.write(`\x1B[${LOGO.length}A`);
46
+ }
47
+ for (const line of output) {
48
+ const colored = step < glitchSteps - 1
49
+ ? dimGradient(line)
50
+ : acGradient(line);
51
+ console.log(colored);
52
+ }
53
+ await sleep(step < glitchSteps - 1 ? 80 : 0);
23
54
  }
24
55
 
25
56
  console.log();
26
- console.log(
27
- subtleGradient(' ─────────────────────────────────────────────────────────────────')
28
- );
57
+
58
+ // Phase 2: Animated separator with scanning effect
59
+ const sepWidth = 68;
60
+ const sepChars = '─';
61
+ for (let i = 0; i <= sepWidth; i++) {
62
+ const line =
63
+ chalk.hex('#2D3436')(' ') +
64
+ glowGradient(sepChars.repeat(i)) +
65
+ chalk.hex('#00CEC9')('●') +
66
+ chalk.hex('#2D3436')(sepChars.repeat(Math.max(0, sepWidth - i)));
67
+ process.stdout.write(`\r${line}`);
68
+ await sleep(6);
69
+ }
70
+ process.stdout.write(`\r ${glowGradient(sepChars.repeat(sepWidth))} \n`);
71
+
29
72
  console.log();
30
73
 
31
- // Typewriter effect for tagline
74
+ // Phase 3: Typewriter tagline with cursor blink
32
75
  const tagline = ' Agentic Coding Framework — Multi-assistant configuration system';
76
+ const cursor = '▌';
33
77
  for (let i = 0; i <= tagline.length; i++) {
34
- process.stdout.write(`\r${chalk.hex('#DFE6E9')(tagline.slice(0, i))}${chalk.hex('#00CEC9')('█')}`);
35
- await sleep(12);
78
+ const text = tagline.slice(0, i);
79
+ process.stdout.write(`\r${chalk.hex('#DFE6E9')(text)}${chalk.hex('#00CEC9')(cursor)}`);
80
+ await sleep(i % 4 === 0 ? 18 : 10);
81
+ }
82
+ // Blink cursor 3 times then remove
83
+ for (let b = 0; b < 3; b++) {
84
+ process.stdout.write(`\r${chalk.hex('#DFE6E9')(tagline)}${chalk.hex('#00CEC9')(cursor)}`);
85
+ await sleep(120);
86
+ process.stdout.write(`\r${chalk.hex('#DFE6E9')(tagline)} `);
87
+ await sleep(120);
36
88
  }
37
89
  process.stdout.write(`\r${chalk.hex('#DFE6E9')(tagline)} \n`);
38
90
 
91
+ // Phase 4: Info badges
92
+ console.log();
93
+ const version = chalk.hex('#2D3436').bgHex('#00CEC9').bold(' v1.x ');
94
+ const badge = chalk.hex('#2D3436').bgHex('#6C5CE7').bold(' CLI ');
95
+ const badge2 = chalk.hex('#2D3436').bgHex('#FDCB6E').bold(' 23 Assistants ');
96
+ console.log(` ${version} ${badge} ${badge2}`);
97
+
39
98
  console.log();
40
- console.log(
41
- subtleGradient(' ─────────────────────────────────────────────────────────────────')
42
- );
99
+ process.stdout.write(` ${glowGradient(sepChars.repeat(sepWidth))} \n`);
43
100
  console.log();
44
101
  }