ac-framework 1.0.1 → 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.1",
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": {
@@ -1,18 +1,26 @@
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']);
16
24
 
17
25
  const ALWAYS_INSTALL = ['openspec'];
18
26
 
@@ -29,11 +37,19 @@ async function getFrameworkFolders() {
29
37
  function buildChoices(folders) {
30
38
  const choices = [];
31
39
 
32
- choices.push(new inquirer.Separator(chalk.hex('#636E72')(' ── AI Assistants ──────────────────────────')));
40
+ choices.push(new inquirer.Separator(
41
+ chalk.hex('#636E72')(' ── ') +
42
+ chalk.hex('#6C5CE7').bold('AI Assistants') +
43
+ chalk.hex('#636E72')(' ─────────────────────────────')
44
+ ));
45
+
33
46
  for (const folder of folders) {
34
47
  const desc = DESCRIPTIONS[folder] || '';
35
48
  const displayName = formatFolderName(folder);
36
- const label = `${chalk.hex('#DFE6E9').bold(displayName)}${desc ? chalk.hex('#636E72')(` — ${desc}`) : ''}`;
49
+ const icon = getAssistantIcon(folder);
50
+ const label =
51
+ `${chalk.hex('#636E72')(icon)} ${chalk.hex('#DFE6E9').bold(displayName)}` +
52
+ (desc ? chalk.hex('#636E72')(` · ${desc}`) : '');
37
53
  choices.push({
38
54
  name: label,
39
55
  value: folder,
@@ -44,6 +60,35 @@ function buildChoices(folders) {
44
60
  return choices;
45
61
  }
46
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
+
47
92
  async function checkExisting(targetDir, folder) {
48
93
  try {
49
94
  await access(join(targetDir, folder));
@@ -56,44 +101,47 @@ async function checkExisting(targetDir, folder) {
56
101
  export async function initCommand() {
57
102
  const targetDir = process.cwd();
58
103
 
59
- // Scanning animation
60
- 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);
61
107
  console.log();
62
108
 
63
- // Matrix rain effect
64
- console.log('\n'.repeat(6));
65
- await matrixRain(1500);
109
+ // Matrix rain transition
110
+ await matrixRain(1800);
66
111
 
67
112
  let folders;
68
113
  try {
69
114
  folders = await getFrameworkFolders();
70
115
  } catch {
71
- console.log(chalk.red(' Error: Could not read framework directory.'));
116
+ console.log(chalk.hex('#D63031')(' Error: Could not read framework directory.'));
72
117
  console.log(chalk.hex('#636E72')(' Make sure ac-framework is installed correctly.'));
73
118
  process.exit(1);
74
119
  }
75
120
 
76
121
  if (folders.length === 0) {
77
- console.log(chalk.yellow(' No modules found in framework directory.'));
122
+ console.log(chalk.hex('#FDCB6E')(' No modules found in framework directory.'));
78
123
  process.exit(0);
79
124
  }
80
125
 
81
- console.log(
82
- chalk.hex('#B2BEC3')(` Found ${chalk.hex('#00CEC9').bold(folders.length)} assistant modules available`)
83
- );
84
- console.log(
85
- chalk.hex('#636E72')(` + ${chalk.hex('#6C5CE7').bold('openspec')} will be included automatically\n`)
86
- );
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();
87
133
 
134
+ // ── Step 2: Select ─────────────────────────────────────────────
135
+ await stepHeader(2, 3, 'Select your assistants');
136
+
137
+ // Controls hint with styled keys
138
+ const key = (k) => chalk.hex('#2D3436').bgHex('#636E72')(` ${k} `);
88
139
  console.log(
89
- chalk.hex('#636E72')(' Use ') +
90
- chalk.hex('#00CEC9')('↑↓') +
91
- chalk.hex('#636E72')(' to navigate, ') +
92
- chalk.hex('#00CEC9')('Space') +
93
- chalk.hex('#636E72')(' to select, ') +
94
- chalk.hex('#00CEC9')('Enter') +
95
- 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')}`
96
143
  );
144
+ console.log();
97
145
 
98
146
  const choices = buildChoices(folders);
99
147
 
@@ -101,13 +149,13 @@ export async function initCommand() {
101
149
  {
102
150
  type: 'checkbox',
103
151
  name: 'selected',
104
- message: acGradient('Select modules to install:'),
152
+ message: acGradient('Choose modules to install:'),
105
153
  choices,
106
154
  pageSize: 15,
107
155
  loop: false,
108
156
  validate(answer) {
109
157
  if (answer.length === 0) {
110
- 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.');
111
159
  }
112
160
  return true;
113
161
  },
@@ -116,7 +164,7 @@ export async function initCommand() {
116
164
 
117
165
  console.log();
118
166
 
119
- // Check for existing folders (includes openspec)
167
+ // ── Check conflicts ────────────────────────────────────────────
120
168
  const allForCheck = [...selected, ...ALWAYS_INSTALL];
121
169
  const existing = [];
122
170
  for (const folder of allForCheck) {
@@ -127,10 +175,14 @@ export async function initCommand() {
127
175
 
128
176
  if (existing.length > 0) {
129
177
  console.log(
130
- chalk.hex('#FDCB6E')(' ⚠ The following modules already exist in your project:\n')
178
+ chalk.hex('#FDCB6E')(' ⚠ These modules already exist in your project:\n')
131
179
  );
132
180
  for (const folder of existing) {
133
- 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
+ );
134
186
  }
135
187
  console.log();
136
188
 
@@ -147,33 +199,33 @@ export async function initCommand() {
147
199
  const filtered = selected.filter((f) => !existing.includes(f));
148
200
  const autoFiltered = ALWAYS_INSTALL.filter((f) => !existing.includes(f));
149
201
  if (filtered.length === 0 && autoFiltered.length === 0) {
150
- console.log(chalk.hex('#636E72')('\n No new modules to install. Exiting.\n'));
202
+ console.log(chalk.hex('#636E72')('\n Nothing new to install. Exiting.\n'));
151
203
  process.exit(0);
152
204
  }
153
205
  selected.length = 0;
154
206
  selected.push(...filtered);
155
207
  console.log(
156
- chalk.hex('#B2BEC3')(`\n Proceeding with ${chalk.hex('#00CEC9').bold(filtered.length + autoFiltered.length)} new module(s)...\n`)
208
+ chalk.hex('#B2BEC3')(`\n Continuing with ${chalk.hex('#00CEC9').bold(filtered.length + autoFiltered.length)} new module(s)...\n`)
157
209
  );
158
210
  }
159
211
  }
160
212
 
161
- // Confirm selection show selected + openspec
162
- console.log(chalk.hex('#B2BEC3')(' Modules to install:\n'));
163
- 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) => {
164
217
  const desc = DESCRIPTIONS[folder] || '';
165
- console.log(
166
- chalk.hex('#00CEC9')(' ◆ ') +
167
- chalk.hex('#DFE6E9').bold(formatFolderName(folder)) +
168
- (desc ? chalk.hex('#636E72')(` — ${desc}`) : '')
169
- );
170
- }
171
- console.log(
172
- chalk.hex('#6C5CE7')(' ◆ ') +
218
+ return chalk.hex('#DFE6E9').bold(formatFolderName(folder)) +
219
+ (desc ? chalk.hex('#636E72')(` · ${desc}`) : '');
220
+ });
221
+ selectedItems.push(
173
222
  chalk.hex('#DFE6E9').bold('Openspec') +
174
- chalk.hex('#636E72')(` ${DESCRIPTIONS['openspec']}`) +
175
- chalk.hex('#636E72').italic(' (auto)')
223
+ chalk.hex('#636E72')(` · ${DESCRIPTIONS['openspec']}`) +
224
+ chalk.hex('#6C5CE7').italic(' (auto)')
176
225
  );
226
+
227
+ await revealList(selectedItems, { prefix: '◆', color: '#00CEC9', delay: 40 });
228
+
177
229
  console.log();
178
230
 
179
231
  const { confirm } = await inquirer.prompt([
@@ -190,74 +242,46 @@ export async function initCommand() {
190
242
  process.exit(0);
191
243
  }
192
244
 
245
+ // ── Step 3: Install ────────────────────────────────────────────
193
246
  console.log();
247
+ await animatedSeparator(60);
248
+ console.log();
249
+ await stepHeader(3, 3, 'Installing modules');
194
250
 
195
- // Install modules with progress — always include openspec
196
251
  const frameworkPath = resolve(__dirname, '../../framework');
197
252
  const allToInstall = [...selected, ...ALWAYS_INSTALL];
198
253
  let installed = 0;
199
254
  const errors = [];
200
255
 
201
256
  for (const folder of allToInstall) {
202
- const spinner = createSpinner(
203
- chalk.hex('#B2BEC3')(`Installing ${chalk.hex('#DFE6E9').bold(formatFolderName(folder))}...`)
204
- ).start();
257
+ const displayName = formatFolderName(folder);
205
258
 
206
259
  try {
207
- const src = join(frameworkPath, folder);
208
- const dest = join(targetDir, folder);
209
-
210
- await cp(src, dest, { recursive: true, force: true });
211
-
212
- // Skip node_modules if copied
213
- const nmPath = join(dest, 'node_modules');
214
- try {
215
- const { rm } = await import('node:fs/promises');
216
- await rm(nmPath, { recursive: true, force: true });
217
- } catch {
218
- // node_modules didn't exist, that's fine
219
- }
260
+ await installWithAnimation(displayName, async () => {
261
+ const src = join(frameworkPath, folder);
262
+ const dest = join(targetDir, folder);
220
263
 
221
- installed++;
222
- spinner.success({
223
- text:
224
- chalk.hex('#00CEC9')(` ${formatFolderName(folder)}`) +
225
- 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
+ }
226
272
  });
273
+ installed++;
227
274
  } catch (err) {
228
275
  errors.push({ folder, error: err.message });
229
- spinner.error({
230
- text: chalk.hex('#D63031')(` Failed: ${formatFolderName(folder)} — ${err.message}`),
231
- });
232
276
  }
233
277
 
234
- await sleep(150);
278
+ await sleep(80);
235
279
  }
236
280
 
237
- // Final summary
238
- console.log();
239
- console.log(
240
- gradient(['#636E72', '#B2BEC3'])(' ─────────────────────────────────────────────────────────────────')
241
- );
242
- console.log();
243
-
281
+ // ── Final result ───────────────────────────────────────────────
244
282
  if (errors.length === 0) {
245
- await progressBar('Finalizing', 20, 600);
246
- console.log();
247
- console.log(successGradient(' ✓ Installation complete!'));
248
- console.log();
249
- console.log(
250
- chalk.hex('#B2BEC3')(` ${chalk.hex('#00CEC9').bold(installed)} module(s) installed successfully in ${chalk.hex('#DFE6E9')(targetDir)}`)
251
- );
283
+ await celebrateSuccess(installed, targetDir);
252
284
  } else {
253
- console.log(
254
- chalk.hex('#FDCB6E')(` ${installed} installed, ${errors.length} failed.`)
255
- );
285
+ await showFailureSummary(installed, errors);
256
286
  }
257
-
258
- console.log();
259
- console.log(
260
- chalk.hex('#636E72')(' Your project is ready. Happy coding!')
261
- );
262
- console.log();
263
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 >= 0 && 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
  }