shell-logo 0.1.2 → 0.1.3

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": "shell-logo",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "Render colorful ASCII art logos in the terminal from a config file",
6
6
  "license": "MIT",
package/src/config.js CHANGED
@@ -13,6 +13,7 @@ import { readFileSync } from 'node:fs';
13
13
  import chalk from 'chalk';
14
14
  import { configPath, legacyConfigPath } from './paths.js';
15
15
  import { writeConfig } from './generate.js';
16
+ import { SHAPE_NAMES } from './shapes.js';
16
17
 
17
18
  export const DEFAULTS = {
18
19
  colors: ['#ff6b6b', '#feca57', '#48dbfb'],
@@ -21,7 +22,11 @@ export const DEFAULTS = {
21
22
 
22
23
  /**
23
24
  * Parse a JSON string and validate it as a shell-logo config.
24
- * Requires a non-empty `text` field. Colors must be an array of 2+ if present.
25
+ *
26
+ * Supports two modes:
27
+ * - "text" (default): requires non-empty `text`, optional `colors`/`font`
28
+ * - "shape": requires valid `shape` name, optional `colors`, no `text`/`font` needed
29
+ *
25
30
  * Returns a normalized config object, or null if invalid.
26
31
  */
27
32
  function parseAndValidate(raw) {
@@ -32,17 +37,32 @@ function parseAndValidate(raw) {
32
37
  return null;
33
38
  }
34
39
 
35
- if (!config.text || typeof config.text !== 'string' || config.text.trim() === '') {
36
- return null;
37
- }
38
-
39
40
  if (config.colors !== undefined) {
40
41
  if (!Array.isArray(config.colors) || config.colors.length < 2) {
41
42
  return null;
42
43
  }
43
44
  }
44
45
 
46
+ const mode = config.mode ?? 'text';
47
+
48
+ if (mode === 'shape') {
49
+ if (!config.shape || !SHAPE_NAMES.includes(config.shape)) {
50
+ return null;
51
+ }
52
+ return {
53
+ mode: 'shape',
54
+ shape: config.shape,
55
+ colors: config.colors ?? DEFAULTS.colors,
56
+ };
57
+ }
58
+
59
+ // mode === 'text' (or absent for backward compatibility)
60
+ if (!config.text || typeof config.text !== 'string' || config.text.trim() === '') {
61
+ return null;
62
+ }
63
+
45
64
  return {
65
+ mode: 'text',
46
66
  text: config.text.trim(),
47
67
  colors: config.colors ?? DEFAULTS.colors,
48
68
  font: config.font ?? DEFAULTS.font,
package/src/index.js CHANGED
@@ -13,6 +13,7 @@ import { writeConfig } from './generate.js';
13
13
  import { render } from './renderer.js';
14
14
  import { getTerminalSize, clearScreen, hideCursor, showCursor, centerContent } from './terminal.js';
15
15
  import { THEMES, FONTS } from './themes.js';
16
+ import { SHAPES } from './shapes.js';
16
17
  import chalk from 'chalk';
17
18
  import * as p from '@clack/prompts';
18
19
 
@@ -38,6 +39,8 @@ startRenderLoop(config, persistent);
38
39
  function startRenderLoop(config, persistent) {
39
40
  let resizeTimer;
40
41
 
42
+ const isShapeMode = config.mode === 'shape';
43
+
41
44
  // Find the current theme index by matching colors
42
45
  let themeIndex = THEMES.findIndex(
43
46
  (t) => JSON.stringify(t.colors) === JSON.stringify(config.colors)
@@ -47,6 +50,11 @@ function startRenderLoop(config, persistent) {
47
50
  let fontIndex = FONTS.indexOf(config.font);
48
51
  if (fontIndex === -1) fontIndex = 0;
49
52
 
53
+ let shapeIndex = isShapeMode
54
+ ? SHAPES.findIndex((s) => s.name === config.shape)
55
+ : 0;
56
+ if (shapeIndex === -1) shapeIndex = 0;
57
+
50
58
  let showStatus = false;
51
59
  let statusTimer;
52
60
 
@@ -56,7 +64,10 @@ function startRenderLoop(config, persistent) {
56
64
  clearScreen();
57
65
  process.stdout.write(centerContent(art, columns, rows));
58
66
  if (showStatus) {
59
- const status = ` ${THEMES[themeIndex].name} · ${FONTS[fontIndex]} · ↑↓ theme ←→ font q quit`;
67
+ const lrLabel = isShapeMode
68
+ ? `${SHAPES[shapeIndex].name} · ←→ shape`
69
+ : `${FONTS[fontIndex]} · ←→ font`;
70
+ const status = ` ${THEMES[themeIndex].name} · ${lrLabel} · ↑↓ theme q quit`;
60
71
  process.stdout.write(`\x1B[${rows};1H` + chalk.dim(status));
61
72
  }
62
73
  }
@@ -109,12 +120,22 @@ function startRenderLoop(config, persistent) {
109
120
  } else if (key[2] === 66) { // Arrow Down — next theme
110
121
  themeIndex = (themeIndex + 1) % THEMES.length;
111
122
  config.colors = THEMES[themeIndex].colors;
112
- } else if (key[2] === 67) { // Arrow Right — next font
113
- fontIndex = (fontIndex + 1) % FONTS.length;
114
- config.font = FONTS[fontIndex];
115
- } else if (key[2] === 68) { // Arrow Left — prev font
116
- fontIndex = (fontIndex - 1 + FONTS.length) % FONTS.length;
117
- config.font = FONTS[fontIndex];
123
+ } else if (key[2] === 67) { // Arrow Right — next font/shape
124
+ if (isShapeMode) {
125
+ shapeIndex = (shapeIndex + 1) % SHAPES.length;
126
+ config.shape = SHAPES[shapeIndex].name;
127
+ } else {
128
+ fontIndex = (fontIndex + 1) % FONTS.length;
129
+ config.font = FONTS[fontIndex];
130
+ }
131
+ } else if (key[2] === 68) { // Arrow Left — prev font/shape
132
+ if (isShapeMode) {
133
+ shapeIndex = (shapeIndex - 1 + SHAPES.length) % SHAPES.length;
134
+ config.shape = SHAPES[shapeIndex].name;
135
+ } else {
136
+ fontIndex = (fontIndex - 1 + FONTS.length) % FONTS.length;
137
+ config.font = FONTS[fontIndex];
138
+ }
118
139
  } else {
119
140
  return;
120
141
  }
package/src/renderer.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import figlet from 'figlet';
2
2
  import gradient from 'gradient-string';
3
3
  import { scaleArt } from './scaling.js';
4
+ import { SHAPES } from './shapes.js';
4
5
 
5
6
  function figletSync(text, font) {
6
7
  try {
@@ -11,6 +12,18 @@ function figletSync(text, font) {
11
12
  }
12
13
 
13
14
  export function render(config, columns, rows) {
15
+ if (config.mode === 'shape') {
16
+ const shape = SHAPES.find((s) => s.name === config.shape);
17
+ if (!shape) {
18
+ const grad = gradient(config.colors);
19
+ return grad(config.shape);
20
+ }
21
+ const trimmed = shape.art.trim();
22
+ const scaled = scaleArt(trimmed, columns, rows - 2);
23
+ const grad = gradient(config.colors);
24
+ return grad.multiline(scaled);
25
+ }
26
+
14
27
  const art = figletSync(config.text, config.font);
15
28
  if (!art) {
16
29
  const grad = gradient(config.colors);
package/src/shapes.js ADDED
@@ -0,0 +1,239 @@
1
+ export const SHAPES = [
2
+ {
3
+ name: 'Pixel',
4
+ art: `
5
+ ▄█▄ ▄█▄
6
+ █████ ▄▄▄ █████
7
+ ████████████████
8
+ ██ ▀ █████ ▀ ██
9
+ ████████████████
10
+ █████ ▀▀▀ █████
11
+ ██████████████
12
+ ████████████
13
+ ██ █████ ██
14
+ ▀▀ ▀▀
15
+ `,
16
+ },
17
+ {
18
+ name: 'Bloop',
19
+ art: `
20
+ ▄████████▄
21
+ ██████████████
22
+ ████████████████
23
+ ███▀██████▀█████
24
+ ████████████████
25
+ █████ ▀▀▀▀ █████
26
+ ████████████████
27
+ ██████████████
28
+ ▀██████████▀
29
+ ▀▀ ▀▀
30
+ `,
31
+ },
32
+ {
33
+ name: 'Octo',
34
+ art: `
35
+ ▄████████▄
36
+ ██████████████
37
+ ████████████████
38
+ ███▀██████▀█████
39
+ ████████████████
40
+ ██████████████
41
+ ▐█▌▐█▌ ██ ▐█▌▐█▌
42
+ ▐█▌▐█▌ ██ ▐█▌▐█▌
43
+ ▀ ▀ ▀▀ ▀ ▀
44
+ `,
45
+ },
46
+ {
47
+ name: 'Ghosty',
48
+ art: `
49
+ ▄████████▄
50
+ ██████████████
51
+ ████████████████
52
+ ████▀████▀██████
53
+ ████████████████
54
+ ████████████████
55
+ ████████████████
56
+ ████████████████
57
+ █▀█ █▀██ █▀█ █▀
58
+ `,
59
+ },
60
+ {
61
+ name: 'Foxie',
62
+ art: `
63
+ ▄██▄ ▄██▄
64
+ ████▄ ▄████
65
+ ██████▄▄▄▄▄▄▄▄██████
66
+ ████ ▀████████▀ ████
67
+ █████ ▀██████▀ █████
68
+ ████████████████████
69
+ ██████████████████
70
+ ████████████████
71
+ ██████████████
72
+ ▀██████████▀
73
+ `,
74
+ },
75
+ {
76
+ name: 'Ducky',
77
+ art: `
78
+ ▄████▄
79
+ ████████▄
80
+ ██▀████████▄
81
+ ██████████████
82
+ ████████████████
83
+ █████████████▀
84
+ ▀██████████
85
+ ▀████████▀
86
+ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
87
+ `,
88
+ },
89
+ {
90
+ name: 'Robo',
91
+ art: `
92
+ ▄█▄
93
+ ▄█████▄
94
+ ██████████
95
+ ███▀██▀███
96
+ ██████████
97
+ ███▄▀▀▄███
98
+ ██████████
99
+ ▐██████████▌
100
+ ███ ████ ██
101
+ ██ ██
102
+ ▀▀ ▀▀
103
+ `,
104
+ },
105
+ {
106
+ name: 'Cactus',
107
+ art: `
108
+ ▄██▄
109
+ ████
110
+ ▄██▄ ████
111
+ ████ ████ ▄██▄
112
+ ████ ████ ████
113
+ ▀███▄████▄███▀
114
+ ████
115
+ ████
116
+ ██████
117
+ ▄████████▄
118
+ ██████████
119
+ `,
120
+ },
121
+ {
122
+ name: 'Rocket',
123
+ art: `
124
+ ▄▄
125
+ ████
126
+ ██████
127
+ ██████
128
+ ████████
129
+ ██ ██ ██
130
+ ████████
131
+ ██████████
132
+ ▄██████████▄
133
+ ██▀██████▀██
134
+ ██████
135
+ ▐██████▌
136
+ ▄██▀ ▀██▄
137
+ ▀▀ ▀▀
138
+ `,
139
+ },
140
+ {
141
+ name: 'Skull',
142
+ art: `
143
+ ▄██████▄
144
+ ████████████
145
+ ██████████████
146
+ ███▀██████▀███
147
+ ██████████████
148
+ █████▄▄█████
149
+ ████████████
150
+ █▀█ ▀▀ █▀█
151
+ █ █ ██ █ █
152
+ ▀ ▀▀ ▀
153
+ `,
154
+ },
155
+ {
156
+ name: 'Penguin',
157
+ art: `
158
+ ▄████▄
159
+ ████████████
160
+ ██████████████
161
+ ████▀████▀████
162
+ █████▀▀▀▀█████
163
+ ██▌ ▄████▄ ▐██
164
+ ██▌ ██████ ▐██
165
+ ██▌ ██████ ▐██
166
+ ▀▌ ██████ ▐▀
167
+ ██████
168
+ ▄██ ██▄
169
+ ▀▀▀ ▀▀▀
170
+ `,
171
+ },
172
+ {
173
+ name: 'Mushroom',
174
+ art: `
175
+ ▄████▄
176
+ ▄████████▄
177
+ ██ ▀████▀ ██
178
+ ██████████████
179
+ ██ ▀████▀ ██
180
+ ██████████████
181
+ ▀██████████▀
182
+ ██████
183
+ ██████
184
+ ██████
185
+ ████████
186
+ ▀▀▀▀▀▀▀▀
187
+ `,
188
+ },
189
+ {
190
+ name: 'Alien',
191
+ art: `
192
+ ▄██████████▄
193
+ ██████████████
194
+ ██ ▄████████▄ ██
195
+ ██ █▀██████▀█ ██
196
+ ██ ██████████ ██
197
+ ████ ▀▀▀▀ ████
198
+ ▀██████████▀
199
+ ████████
200
+ ██████
201
+ ████
202
+ ████
203
+ `,
204
+ },
205
+ {
206
+ name: 'Coffee',
207
+ art: `
208
+ ▐█▌ ▐█▌ ▐█▌
209
+ ▐█▌ ▐█▌ ▐█▌
210
+ ▐█▌ ▐█▌ ▐█▌
211
+ ████████████████
212
+ ████████████████▄
213
+ █████████████████
214
+ █████████████████
215
+ ████████████████▀
216
+ ████████████████
217
+ ▀████████████▀
218
+ ████████████████
219
+ `,
220
+ },
221
+ {
222
+ name: 'Diamond',
223
+ art: `
224
+ ▄██▄
225
+ ██████
226
+ ████████
227
+ ██████████
228
+ ████████████
229
+ ██████████████
230
+ ████████████
231
+ ██████████
232
+ ████████
233
+ ██████
234
+ ▀██▀
235
+ `,
236
+ },
237
+ ];
238
+
239
+ export const SHAPE_NAMES = SHAPES.map((s) => s.name);
package/src/themes.js CHANGED
@@ -1,23 +1,68 @@
1
+ /**
2
+ * Font and gradient theme definitions for shell-logo.
3
+ *
4
+ * FONTS — Figlet font names available for text-mode logos.
5
+ * The render loop in index.js cycles through these with arrow keys.
6
+ * Indices 0–14 are the original set; new entries are appended so
7
+ * saved configs that store a font name keep working.
8
+ *
9
+ * THEMES — Named gradient color palettes (3 hex stops each).
10
+ * Used by gradient-string to colorize the rendered ASCII art.
11
+ */
12
+
13
+ // ── Fonts ──────────────────────────────────────────────────────────────────
14
+
1
15
  export const FONTS = [
16
+ // Classic / general-purpose
2
17
  'Standard', 'Big', 'ANSI Shadow', 'Slant', 'Small',
3
18
  'ANSI Regular', 'Bloody', 'DOS Rebel', 'Graffiti', 'Larry 3D',
4
19
  'Star Wars', 'Doh', 'Ghost', 'Fraktur', 'Fire Font-k',
20
+
21
+ // Block / pixel style — use filled Unicode characters (▄█▀) instead of
22
+ // typical ASCII slashes and pipes, giving a "painted pixels" look.
23
+ 'Block', 'Blocks', 'Shaded Blocky', 'Small Block',
24
+ 'Dot Matrix', 'Tiles', 'Rectangles',
25
+
26
+ // Popular classic figlet fonts
27
+ 'Doom', 'Banner3-D', 'Colossal', 'Epic',
28
+ 'Calvin S', 'Speed', '3D-ASCII', 'Isometric1',
5
29
  ];
6
30
 
31
+ // ── Gradient themes ────────────────────────────────────────────────────────
32
+
7
33
  export const THEMES = [
8
- { name: 'Sunset', colors: ['#ff6b6b', '#ff9f43', '#feca57'] },
9
- { name: 'Ocean', colors: ['#0abde3', '#48dbfb', '#54a0ff'] },
10
- { name: 'Forest', colors: ['#1dd1a1', '#2ed573', '#26de81'] },
11
- { name: 'Neon', colors: ['#c56cf0', '#fd79a8', '#ff6b6b'] },
12
- { name: 'Twilight', colors: ['#5f27cd', '#c56cf0', '#48dbfb'] },
13
- { name: 'Fire', colors: ['#ff4757', '#ff6348', '#ffdd59'] },
14
- { name: 'Pastel', colors: ['#fd79a8', '#feca57', '#48dbfb'] },
15
- { name: 'Monochrome', colors: ['#ffffff', '#54a0ff', '#5f27cd'] },
16
- { name: 'Aurora', colors: ['#00d2ff', '#3a7bd5', '#7b2ff7'] },
17
- { name: 'Cherry', colors: ['#eb3349', '#f45c43', '#ff8a80'] },
18
- { name: 'Cyberpunk', colors: ['#f72585', '#7209b7', '#4cc9f0'] },
19
- { name: 'Arctic', colors: ['#e0eafc', '#cfdef3', '#74b9ff'] },
20
- { name: 'Emerald', colors: ['#11998e', '#38ef7d', '#b8ff96'] },
21
- { name: 'Midnight', colors: ['#0f0c29', '#302b63', '#24c6dc'] },
22
- { name: 'Rose Gold', colors: ['#f4c4f3', '#fc5c7d', '#fda085'] },
34
+ // Warm
35
+ { name: 'Sunset', colors: ['#ff6b6b', '#ff9f43', '#feca57'] },
36
+ { name: 'Fire', colors: ['#ff4757', '#ff6348', '#ffdd59'] },
37
+ { name: 'Cherry', colors: ['#eb3349', '#f45c43', '#ff8a80'] },
38
+ { name: 'Rose Gold', colors: ['#f4c4f3', '#fc5c7d', '#fda085'] },
39
+ { name: 'Magma', colors: ['#f83600', '#f9d423', '#fe8c00'] },
40
+ { name: 'Sunrise', colors: ['#ff512f', '#f09819', '#ffed4a'] },
41
+ { name: 'Copper', colors: ['#b87333', '#da9855', '#f0c27f'] },
42
+
43
+ // Cool
44
+ { name: 'Ocean', colors: ['#0abde3', '#48dbfb', '#54a0ff'] },
45
+ { name: 'Arctic', colors: ['#e0eafc', '#cfdef3', '#74b9ff'] },
46
+ { name: 'Frost', colors: ['#e8f0ff', '#b8d4f0', '#88b8e0'] },
47
+ { name: 'Electric', colors: ['#00f2fe', '#4facfe', '#667eea'] },
48
+
49
+ // Green
50
+ { name: 'Forest', colors: ['#1dd1a1', '#2ed573', '#26de81'] },
51
+ { name: 'Emerald', colors: ['#11998e', '#38ef7d', '#b8ff96'] },
52
+ { name: 'Toxic', colors: ['#a8ff78', '#78ffd6', '#00e676'] },
53
+ { name: 'Matrix', colors: ['#00ff41', '#008f11', '#003b00'] },
54
+
55
+ // Purple / pink
56
+ { name: 'Neon', colors: ['#c56cf0', '#fd79a8', '#ff6b6b'] },
57
+ { name: 'Twilight', colors: ['#5f27cd', '#c56cf0', '#48dbfb'] },
58
+ { name: 'Cyberpunk', colors: ['#f72585', '#7209b7', '#4cc9f0'] },
59
+ { name: 'Grape', colors: ['#6a0572', '#ab83a1', '#e0aaff'] },
60
+ { name: 'Lavender Dream', colors: ['#a18cd1', '#fbc2eb', '#f6d5f7'] },
61
+ { name: 'Bubblegum', colors: ['#ff77ab', '#ff99cc', '#ffbbdd'] },
62
+
63
+ // Multi / neutral
64
+ { name: 'Pastel', colors: ['#fd79a8', '#feca57', '#48dbfb'] },
65
+ { name: 'Monochrome', colors: ['#ffffff', '#54a0ff', '#5f27cd'] },
66
+ { name: 'Aurora', colors: ['#00d2ff', '#3a7bd5', '#7b2ff7'] },
67
+ { name: 'Midnight', colors: ['#0f0c29', '#302b63', '#24c6dc'] },
23
68
  ];
package/src/ui.js CHANGED
@@ -15,6 +15,8 @@
15
15
  import * as p from '@clack/prompts';
16
16
  import chalk from 'chalk';
17
17
  import { tryLoadConfig } from './config.js';
18
+ import { SHAPES } from './shapes.js';
19
+ import { FONTS } from './themes.js';
18
20
 
19
21
  const COLOR_PALETTE = [
20
22
  { value: '#ff6b6b', label: `${chalk.bgHex('#ff6b6b')(' ')} Coral` },
@@ -35,23 +37,7 @@ const COLOR_PALETTE = [
35
37
  { value: '#ffffff', label: `${chalk.bgHex('#ffffff')(' ')} White` },
36
38
  ];
37
39
 
38
- const FONT_OPTIONS = [
39
- { value: 'Standard', label: 'Standard' },
40
- { value: 'Big', label: 'Big' },
41
- { value: 'ANSI Shadow', label: 'ANSI Shadow' },
42
- { value: 'Slant', label: 'Slant' },
43
- { value: 'Small', label: 'Small' },
44
- { value: 'ANSI Regular', label: 'ANSI Regular' },
45
- { value: 'Bloody', label: 'Bloody' },
46
- { value: 'DOS Rebel', label: 'DOS Rebel' },
47
- { value: 'Graffiti', label: 'Graffiti' },
48
- { value: 'Larry 3D', label: 'Larry 3D' },
49
- { value: 'Star Wars', label: 'Star Wars' },
50
- { value: 'Doh', label: 'Doh' },
51
- { value: 'Ghost', label: 'Ghost' },
52
- { value: 'Fraktur', label: 'Fraktur' },
53
- { value: 'Fire Font-k', label: 'Fire Font-k' },
54
- ];
40
+ const FONT_OPTIONS = FONTS.map(f => ({ value: f, label: f }));
55
41
 
56
42
  /** Exit gracefully if the user presses Ctrl+C / Escape during a prompt. */
57
43
  function handleCancel(value) {
@@ -62,19 +48,8 @@ function handleCancel(value) {
62
48
  return value;
63
49
  }
64
50
 
65
- /** Walk the user through the Generate wizard: text, colors, font. */
66
- async function promptGenerate() {
67
- const text = handleCancel(
68
- await p.text({
69
- message: 'What text should the logo display?',
70
- placeholder: 'HELLO',
71
- validate: (val) => {
72
- if (!val || val.trim() === '') return 'Text is required.';
73
- },
74
- })
75
- );
76
-
77
- // Pick 3 random unique colors as defaults
51
+ /** Prompt for gradient colors (shared by text and shape modes). */
52
+ async function promptColors() {
78
53
  const shuffled = [...COLOR_PALETTE].sort(() => Math.random() - 0.5);
79
54
  const initialColors = shuffled.slice(0, 3).map(c => c.value);
80
55
 
@@ -92,6 +67,40 @@ async function promptGenerate() {
92
67
  if (colors.length >= 2) break;
93
68
  p.log.warning('Please select at least 2 colors.');
94
69
  }
70
+ return colors;
71
+ }
72
+
73
+ /** Walk the user through the Shape wizard: shape picker, colors. */
74
+ async function promptShapeGenerate() {
75
+ const shape = handleCancel(
76
+ await p.select({
77
+ message: 'Pick a mascot:',
78
+ options: SHAPES.map((s) => ({ value: s.name, label: s.name })),
79
+ })
80
+ );
81
+
82
+ const colors = await promptColors();
83
+
84
+ return {
85
+ action: 'generate',
86
+ config: { mode: 'shape', shape, colors },
87
+ persistent: true,
88
+ };
89
+ }
90
+
91
+ /** Walk the user through the Text wizard: text, colors, font. */
92
+ async function promptTextGenerate() {
93
+ const text = handleCancel(
94
+ await p.text({
95
+ message: 'What text should the logo display?',
96
+ placeholder: 'HELLO',
97
+ validate: (val) => {
98
+ if (!val || val.trim() === '') return 'Text is required.';
99
+ },
100
+ })
101
+ );
102
+
103
+ const colors = await promptColors();
95
104
 
96
105
  const font = handleCancel(
97
106
  await p.select({
@@ -102,11 +111,27 @@ async function promptGenerate() {
102
111
 
103
112
  return {
104
113
  action: 'generate',
105
- config: { text: text.trim(), colors, font },
114
+ config: { mode: 'text', text: text.trim(), colors, font },
106
115
  persistent: true,
107
116
  };
108
117
  }
109
118
 
119
+ /** Walk the user through the Generate wizard: mode selection, then mode-specific prompts. */
120
+ async function promptGenerate() {
121
+ const mode = handleCancel(
122
+ await p.select({
123
+ message: 'What kind of logo?',
124
+ options: [
125
+ { value: 'text', label: 'Text', hint: 'type your own text' },
126
+ { value: 'shape', label: 'Shape', hint: 'pick a mascot/pet' },
127
+ ],
128
+ })
129
+ );
130
+
131
+ if (mode === 'shape') return promptShapeGenerate();
132
+ return promptTextGenerate();
133
+ }
134
+
110
135
  /**
111
136
  * Main entry point for the interactive CLI.
112
137
  * Returns { action: 'generate'|'run', config, persistent }.