shell-logo 0.1.1 → 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 +1 -1
- package/src/config.js +87 -50
- package/src/generate.js +44 -6
- package/src/index.js +53 -53
- package/src/paths.js +44 -0
- package/src/renderer.js +13 -0
- package/src/shapes.js +239 -0
- package/src/themes.js +60 -15
- package/src/ui.js +107 -48
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -1,22 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config loading with XDG-first resolution and legacy migration.
|
|
3
|
+
*
|
|
4
|
+
* Load order:
|
|
5
|
+
* 1. XDG path (~/.config/shell-logo/folders/<hash>/config.json)
|
|
6
|
+
* 2. Legacy path (.shell-logo.json in the working directory)
|
|
7
|
+
*
|
|
8
|
+
* If a legacy config is found but no XDG config exists, the legacy config
|
|
9
|
+
* is automatically copied to XDG (the legacy file is left untouched).
|
|
10
|
+
*/
|
|
11
|
+
|
|
1
12
|
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
13
|
import chalk from 'chalk';
|
|
14
|
+
import { configPath, legacyConfigPath } from './paths.js';
|
|
15
|
+
import { writeConfig } from './generate.js';
|
|
16
|
+
import { SHAPE_NAMES } from './shapes.js';
|
|
4
17
|
|
|
5
18
|
export const DEFAULTS = {
|
|
6
19
|
colors: ['#ff6b6b', '#feca57', '#48dbfb'],
|
|
7
20
|
font: 'Standard',
|
|
8
21
|
};
|
|
9
22
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Parse a JSON string and validate it as a shell-logo config.
|
|
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
|
+
*
|
|
30
|
+
* Returns a normalized config object, or null if invalid.
|
|
31
|
+
*/
|
|
32
|
+
function parseAndValidate(raw) {
|
|
20
33
|
let config;
|
|
21
34
|
try {
|
|
22
35
|
config = JSON.parse(raw);
|
|
@@ -24,68 +37,92 @@ export function tryLoadConfig() {
|
|
|
24
37
|
return null;
|
|
25
38
|
}
|
|
26
39
|
|
|
27
|
-
if (!config.text || typeof config.text !== 'string' || config.text.trim() === '') {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
40
|
if (config.colors !== undefined) {
|
|
32
41
|
if (!Array.isArray(config.colors) || config.colors.length < 2) {
|
|
33
42
|
return null;
|
|
34
43
|
}
|
|
35
44
|
}
|
|
36
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
|
+
|
|
37
64
|
return {
|
|
65
|
+
mode: 'text',
|
|
38
66
|
text: config.text.trim(),
|
|
39
67
|
colors: config.colors ?? DEFAULTS.colors,
|
|
40
68
|
font: config.font ?? DEFAULTS.font,
|
|
41
69
|
};
|
|
42
70
|
}
|
|
43
71
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Try to load config for `cwd`. Returns the parsed config or null.
|
|
74
|
+
* Checks XDG first, then falls back to legacy .shell-logo.json.
|
|
75
|
+
* Auto-migrates legacy configs to XDG on first load.
|
|
76
|
+
*/
|
|
77
|
+
export function tryLoadConfig(cwd) {
|
|
78
|
+
// 1. Try XDG path
|
|
48
79
|
try {
|
|
49
|
-
raw = readFileSync(configPath, 'utf-8');
|
|
80
|
+
const raw = readFileSync(configPath(cwd), 'utf-8');
|
|
81
|
+
const config = parseAndValidate(raw);
|
|
82
|
+
if (config) return config;
|
|
50
83
|
} catch {
|
|
51
|
-
|
|
52
|
-
chalk.red('Error: ') +
|
|
53
|
-
'No .shell-logo.json found in the current directory.\n\n' +
|
|
54
|
-
'Create one with at least:\n\n' +
|
|
55
|
-
' { "text": "HELLO" }\n\n' +
|
|
56
|
-
'Or copy an example:\n\n' +
|
|
57
|
-
' cp node_modules/shell-logo/examples/.shell-logo.json .'
|
|
58
|
-
);
|
|
59
|
-
process.exit(1);
|
|
84
|
+
// Not found or unreadable — fall through to legacy
|
|
60
85
|
}
|
|
61
86
|
|
|
62
|
-
|
|
87
|
+
// 2. Try legacy .shell-logo.json in the working directory
|
|
88
|
+
let raw;
|
|
63
89
|
try {
|
|
64
|
-
|
|
90
|
+
raw = readFileSync(legacyConfigPath(cwd), 'utf-8');
|
|
65
91
|
} catch {
|
|
66
|
-
|
|
67
|
-
process.exit(1);
|
|
92
|
+
return null;
|
|
68
93
|
}
|
|
69
94
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
95
|
+
const config = parseAndValidate(raw);
|
|
96
|
+
if (!config) return null;
|
|
97
|
+
|
|
98
|
+
// 3. Auto-migrate legacy config to XDG (legacy file is left untouched)
|
|
99
|
+
try {
|
|
100
|
+
writeConfig(config, cwd);
|
|
101
|
+
const xdgPath = configPath(cwd);
|
|
102
|
+
console.log(
|
|
103
|
+
chalk.dim(`Migrated config from .shell-logo.json → ${xdgPath}`)
|
|
73
104
|
);
|
|
74
|
-
|
|
105
|
+
} catch {
|
|
106
|
+
// Migration failed — still usable from the legacy path
|
|
75
107
|
}
|
|
76
108
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
console.error(
|
|
80
|
-
chalk.red('Error: ') + '"colors" must be an array of at least 2 color strings.'
|
|
81
|
-
);
|
|
82
|
-
process.exit(1);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
109
|
+
return config;
|
|
110
|
+
}
|
|
85
111
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Load config for `cwd`, or exit with an error if none is found.
|
|
114
|
+
* Same resolution as tryLoadConfig but treats missing config as fatal.
|
|
115
|
+
*/
|
|
116
|
+
export function loadConfig(cwd) {
|
|
117
|
+
const config = tryLoadConfig(cwd);
|
|
118
|
+
if (config) return config;
|
|
119
|
+
|
|
120
|
+
const xdg = configPath(cwd);
|
|
121
|
+
console.error(
|
|
122
|
+
chalk.red('Error: ') +
|
|
123
|
+
`No config found for this folder.\n\n` +
|
|
124
|
+
`Expected at: ${xdg}\n\n` +
|
|
125
|
+
'Run shell-logo and choose "Generate" to create one.'
|
|
126
|
+
);
|
|
127
|
+
process.exit(1);
|
|
91
128
|
}
|
package/src/generate.js
CHANGED
|
@@ -1,8 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Config persistence — writes config to the XDG folder structure.
|
|
3
|
+
*
|
|
4
|
+
* Uses atomic writes (write to tmp file, then rename) to prevent corruption
|
|
5
|
+
* if two terminals write to the same folder config simultaneously.
|
|
6
|
+
*/
|
|
3
7
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
import { writeFileSync, renameSync, mkdirSync } from 'node:fs';
|
|
9
|
+
import { resolve, join, dirname } from 'node:path';
|
|
10
|
+
import { randomBytes } from 'node:crypto';
|
|
11
|
+
import { configPath, folderConfigDir, metaPath } from './paths.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Write data to a file atomically: write to a temp file in the same
|
|
15
|
+
* directory, then rename. This avoids partial reads if another process
|
|
16
|
+
* is reading the file at the same time.
|
|
17
|
+
*/
|
|
18
|
+
function atomicWriteSync(targetPath, data) {
|
|
19
|
+
const dir = dirname(targetPath);
|
|
20
|
+
const tmpPath = join(dir, `.tmp.${randomBytes(6).toString('hex')}`);
|
|
21
|
+
writeFileSync(tmpPath, data);
|
|
22
|
+
renameSync(tmpPath, targetPath);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Persist a config object to the XDG folder for `cwd`.
|
|
27
|
+
* Creates the directory tree if it doesn't exist yet.
|
|
28
|
+
* Also writes a meta.json with the original folder path (best-effort).
|
|
29
|
+
* Returns the path the config was written to.
|
|
30
|
+
*/
|
|
31
|
+
export function writeConfig(config, cwd = process.cwd()) {
|
|
32
|
+
const dir = folderConfigDir(cwd);
|
|
33
|
+
mkdirSync(dir, { recursive: true });
|
|
34
|
+
|
|
35
|
+
atomicWriteSync(configPath(cwd), JSON.stringify(config, null, 2) + '\n');
|
|
36
|
+
|
|
37
|
+
// meta.json stores the original folder path so humans can identify
|
|
38
|
+
// which hash directory belongs to which project.
|
|
39
|
+
try {
|
|
40
|
+
atomicWriteSync(metaPath(cwd), JSON.stringify({ path: resolve(cwd) }, null, 2) + '\n');
|
|
41
|
+
} catch {
|
|
42
|
+
// Best-effort — not critical if this fails
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return configPath(cwd);
|
|
8
46
|
}
|
package/src/index.js
CHANGED
|
@@ -1,67 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Entry point for shell-logo.
|
|
5
|
+
*
|
|
6
|
+
* 1. Run the interactive UI to get a config + session mode (persistent or temporary).
|
|
7
|
+
* 2. If the user chose "Generate", persist the new config to disk.
|
|
8
|
+
* 3. Start the fullscreen render loop with arrow-key theme/font cycling.
|
|
9
|
+
*/
|
|
10
|
+
|
|
5
11
|
import { runInteractiveUI } from './ui.js';
|
|
6
12
|
import { writeConfig } from './generate.js';
|
|
7
13
|
import { render } from './renderer.js';
|
|
8
14
|
import { getTerminalSize, clearScreen, hideCursor, showCursor, centerContent } from './terminal.js';
|
|
9
15
|
import { THEMES, FONTS } from './themes.js';
|
|
16
|
+
import { SHAPES } from './shapes.js';
|
|
10
17
|
import chalk from 'chalk';
|
|
11
18
|
import * as p from '@clack/prompts';
|
|
12
19
|
|
|
13
|
-
const { action, config } = await runInteractiveUI();
|
|
20
|
+
const { action, config, persistent } = await runInteractiveUI();
|
|
14
21
|
|
|
15
22
|
if (action === 'generate') {
|
|
16
23
|
const s = p.spinner();
|
|
17
|
-
s.start('
|
|
24
|
+
s.start('Saving config...');
|
|
18
25
|
writeConfig(config);
|
|
19
26
|
s.stop('Config saved!');
|
|
20
|
-
|
|
21
|
-
const isGitRepo = existsSync(join(process.cwd(), '.git'));
|
|
22
|
-
const gitignorePath = join(process.cwd(), '.gitignore');
|
|
23
|
-
let shouldPrompt = isGitRepo;
|
|
24
|
-
|
|
25
|
-
if (existsSync(gitignorePath)) {
|
|
26
|
-
const content = readFileSync(gitignorePath, 'utf-8');
|
|
27
|
-
const lines = content.split('\n').map(l => l.trim());
|
|
28
|
-
if (lines.includes('.shell-logo.json')) {
|
|
29
|
-
shouldPrompt = false;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (shouldPrompt) {
|
|
34
|
-
const addToGitignore = await p.select({
|
|
35
|
-
message: 'Add .shell-logo.json to .gitignore?',
|
|
36
|
-
options: [
|
|
37
|
-
{ value: true, label: 'Yes (recommended)' },
|
|
38
|
-
{ value: false, label: 'No' },
|
|
39
|
-
],
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
if (p.isCancel(addToGitignore)) {
|
|
43
|
-
p.cancel('Cancelled.');
|
|
44
|
-
process.exit(0);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (addToGitignore) {
|
|
48
|
-
if (existsSync(gitignorePath)) {
|
|
49
|
-
const existing = readFileSync(gitignorePath, 'utf-8');
|
|
50
|
-
const separator = existing.endsWith('\n') ? '' : '\n';
|
|
51
|
-
writeFileSync(gitignorePath, existing + separator + '.shell-logo.json\n');
|
|
52
|
-
} else {
|
|
53
|
-
writeFileSync(gitignorePath, '.shell-logo.json\n');
|
|
54
|
-
}
|
|
55
|
-
p.log.success('.gitignore updated.');
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
27
|
}
|
|
59
28
|
|
|
60
|
-
startRenderLoop(config);
|
|
61
|
-
|
|
62
|
-
|
|
29
|
+
startRenderLoop(config, persistent);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Fullscreen render loop. Draws the logo centered in the terminal and
|
|
33
|
+
* listens for arrow keys to cycle themes (up/down) and fonts (left/right).
|
|
34
|
+
*
|
|
35
|
+
* @param {object} config - Logo config: { text, colors, font }
|
|
36
|
+
* @param {boolean} persistent - When true, arrow key changes are saved to disk.
|
|
37
|
+
* When false (temporary session), changes are in-memory only.
|
|
38
|
+
*/
|
|
39
|
+
function startRenderLoop(config, persistent) {
|
|
63
40
|
let resizeTimer;
|
|
64
41
|
|
|
42
|
+
const isShapeMode = config.mode === 'shape';
|
|
43
|
+
|
|
65
44
|
// Find the current theme index by matching colors
|
|
66
45
|
let themeIndex = THEMES.findIndex(
|
|
67
46
|
(t) => JSON.stringify(t.colors) === JSON.stringify(config.colors)
|
|
@@ -71,6 +50,11 @@ function startRenderLoop(config) {
|
|
|
71
50
|
let fontIndex = FONTS.indexOf(config.font);
|
|
72
51
|
if (fontIndex === -1) fontIndex = 0;
|
|
73
52
|
|
|
53
|
+
let shapeIndex = isShapeMode
|
|
54
|
+
? SHAPES.findIndex((s) => s.name === config.shape)
|
|
55
|
+
: 0;
|
|
56
|
+
if (shapeIndex === -1) shapeIndex = 0;
|
|
57
|
+
|
|
74
58
|
let showStatus = false;
|
|
75
59
|
let statusTimer;
|
|
76
60
|
|
|
@@ -80,11 +64,15 @@ function startRenderLoop(config) {
|
|
|
80
64
|
clearScreen();
|
|
81
65
|
process.stdout.write(centerContent(art, columns, rows));
|
|
82
66
|
if (showStatus) {
|
|
83
|
-
const
|
|
67
|
+
const lrLabel = isShapeMode
|
|
68
|
+
? `${SHAPES[shapeIndex].name} · ←→ shape`
|
|
69
|
+
: `${FONTS[fontIndex]} · ←→ font`;
|
|
70
|
+
const status = ` ${THEMES[themeIndex].name} · ${lrLabel} · ↑↓ theme q quit`;
|
|
84
71
|
process.stdout.write(`\x1B[${rows};1H` + chalk.dim(status));
|
|
85
72
|
}
|
|
86
73
|
}
|
|
87
74
|
|
|
75
|
+
/** Briefly show the status bar (theme name, font, controls) for 2 seconds. */
|
|
88
76
|
function flashStatus() {
|
|
89
77
|
showStatus = true;
|
|
90
78
|
clearTimeout(statusTimer);
|
|
@@ -94,6 +82,7 @@ function startRenderLoop(config) {
|
|
|
94
82
|
}, 2000);
|
|
95
83
|
}
|
|
96
84
|
|
|
85
|
+
/** Debounce re-renders on terminal resize to avoid flickering. */
|
|
97
86
|
function debouncedRender() {
|
|
98
87
|
clearTimeout(resizeTimer);
|
|
99
88
|
resizeTimer = setTimeout(renderLoop, 50);
|
|
@@ -131,16 +120,27 @@ function startRenderLoop(config) {
|
|
|
131
120
|
} else if (key[2] === 66) { // Arrow Down — next theme
|
|
132
121
|
themeIndex = (themeIndex + 1) % THEMES.length;
|
|
133
122
|
config.colors = THEMES[themeIndex].colors;
|
|
134
|
-
} else if (key[2] === 67) { // Arrow Right — next font
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
+
}
|
|
140
139
|
} else {
|
|
141
140
|
return;
|
|
142
141
|
}
|
|
143
|
-
|
|
142
|
+
// Only persist theme/font changes in a persistent (non-temporary) session
|
|
143
|
+
if (persistent) writeConfig(config);
|
|
144
144
|
flashStatus();
|
|
145
145
|
renderLoop();
|
|
146
146
|
}
|
package/src/paths.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path resolution for XDG-based config storage.
|
|
3
|
+
*
|
|
4
|
+
* Configs are stored per-folder under:
|
|
5
|
+
* $XDG_CONFIG_HOME/shell-logo/folders/<hash>/config.json
|
|
6
|
+
*
|
|
7
|
+
* where <hash> is the first 16 hex chars of SHA-256(absolute CWD).
|
|
8
|
+
* Falls back to ~/.config/shell-logo/ when XDG_CONFIG_HOME is unset.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
import { resolve, join } from 'node:path';
|
|
13
|
+
import { homedir } from 'node:os';
|
|
14
|
+
|
|
15
|
+
/** Root directory for all shell-logo config: $XDG_CONFIG_HOME/shell-logo/ */
|
|
16
|
+
export function configRoot() {
|
|
17
|
+
const base = process.env.XDG_CONFIG_HOME || join(homedir(), '.config');
|
|
18
|
+
return join(base, 'shell-logo');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Deterministic 16-char hex hash of the absolute path for `cwd`. */
|
|
22
|
+
export function folderHash(cwd = process.cwd()) {
|
|
23
|
+
return createHash('sha256').update(resolve(cwd)).digest('hex').slice(0, 16);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Per-folder config directory: <configRoot>/folders/<hash>/ */
|
|
27
|
+
export function folderConfigDir(cwd = process.cwd()) {
|
|
28
|
+
return join(configRoot(), 'folders', folderHash(cwd));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Path to the per-folder config file (text, colors, font). */
|
|
32
|
+
export function configPath(cwd = process.cwd()) {
|
|
33
|
+
return join(folderConfigDir(cwd), 'config.json');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Path to the per-folder meta file (stores original absolute path for debugging). */
|
|
37
|
+
export function metaPath(cwd = process.cwd()) {
|
|
38
|
+
return join(folderConfigDir(cwd), 'meta.json');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Path to the legacy config file (.shell-logo.json in the working directory). */
|
|
42
|
+
export function legacyConfigPath(cwd = process.cwd()) {
|
|
43
|
+
return join(resolve(cwd), '.shell-logo.json');
|
|
44
|
+
}
|
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
|
-
|
|
9
|
-
{ name: '
|
|
10
|
-
{ name: '
|
|
11
|
-
{ name: '
|
|
12
|
-
{ name: '
|
|
13
|
-
{ name: '
|
|
14
|
-
{ name: '
|
|
15
|
-
{ name: '
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
{ name: '
|
|
19
|
-
{ name: 'Arctic',
|
|
20
|
-
{ name: '
|
|
21
|
-
{ name: '
|
|
22
|
-
|
|
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
|
@@ -1,6 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive CLI prompts for shell-logo.
|
|
3
|
+
*
|
|
4
|
+
* When a config already exists for the current folder, presents three options:
|
|
5
|
+
* - Run — display the logo, arrow key changes persist to disk
|
|
6
|
+
* - New session — display the logo, changes are in-memory only (temporary)
|
|
7
|
+
* - Generate — walk through the setup wizard to create a new config
|
|
8
|
+
*
|
|
9
|
+
* When no config exists, only the Generate option is shown.
|
|
10
|
+
*
|
|
11
|
+
* Returns { action, config, persistent } where `persistent` controls
|
|
12
|
+
* whether arrow key theme/font changes are saved back to disk.
|
|
13
|
+
*/
|
|
14
|
+
|
|
1
15
|
import * as p from '@clack/prompts';
|
|
2
16
|
import chalk from 'chalk';
|
|
3
17
|
import { tryLoadConfig } from './config.js';
|
|
18
|
+
import { SHAPES } from './shapes.js';
|
|
19
|
+
import { FONTS } from './themes.js';
|
|
4
20
|
|
|
5
21
|
const COLOR_PALETTE = [
|
|
6
22
|
{ value: '#ff6b6b', label: `${chalk.bgHex('#ff6b6b')(' ')} Coral` },
|
|
@@ -21,24 +37,9 @@ const COLOR_PALETTE = [
|
|
|
21
37
|
{ value: '#ffffff', label: `${chalk.bgHex('#ffffff')(' ')} White` },
|
|
22
38
|
];
|
|
23
39
|
|
|
24
|
-
const FONT_OPTIONS =
|
|
25
|
-
{ value: 'Standard', label: 'Standard' },
|
|
26
|
-
{ value: 'Big', label: 'Big' },
|
|
27
|
-
{ value: 'ANSI Shadow', label: 'ANSI Shadow' },
|
|
28
|
-
{ value: 'Slant', label: 'Slant' },
|
|
29
|
-
{ value: 'Small', label: 'Small' },
|
|
30
|
-
{ value: 'ANSI Regular', label: 'ANSI Regular' },
|
|
31
|
-
{ value: 'Bloody', label: 'Bloody' },
|
|
32
|
-
{ value: 'DOS Rebel', label: 'DOS Rebel' },
|
|
33
|
-
{ value: 'Graffiti', label: 'Graffiti' },
|
|
34
|
-
{ value: 'Larry 3D', label: 'Larry 3D' },
|
|
35
|
-
{ value: 'Star Wars', label: 'Star Wars' },
|
|
36
|
-
{ value: 'Doh', label: 'Doh' },
|
|
37
|
-
{ value: 'Ghost', label: 'Ghost' },
|
|
38
|
-
{ value: 'Fraktur', label: 'Fraktur' },
|
|
39
|
-
{ value: 'Fire Font-k', label: 'Fire Font-k' },
|
|
40
|
-
];
|
|
40
|
+
const FONT_OPTIONS = FONTS.map(f => ({ value: f, label: f }));
|
|
41
41
|
|
|
42
|
+
/** Exit gracefully if the user presses Ctrl+C / Escape during a prompt. */
|
|
42
43
|
function handleCancel(value) {
|
|
43
44
|
if (p.isCancel(value)) {
|
|
44
45
|
p.cancel('Cancelled.');
|
|
@@ -47,18 +48,8 @@ function handleCancel(value) {
|
|
|
47
48
|
return value;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
await p.text({
|
|
53
|
-
message: 'What text should the logo display?',
|
|
54
|
-
placeholder: 'HELLO',
|
|
55
|
-
validate: (val) => {
|
|
56
|
-
if (!val || val.trim() === '') return 'Text is required.';
|
|
57
|
-
},
|
|
58
|
-
})
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
// Pick 3 random unique colors as defaults
|
|
51
|
+
/** Prompt for gradient colors (shared by text and shape modes). */
|
|
52
|
+
async function promptColors() {
|
|
62
53
|
const shuffled = [...COLOR_PALETTE].sort(() => Math.random() - 0.5);
|
|
63
54
|
const initialColors = shuffled.slice(0, 3).map(c => c.value);
|
|
64
55
|
|
|
@@ -76,6 +67,40 @@ async function promptGenerate() {
|
|
|
76
67
|
if (colors.length >= 2) break;
|
|
77
68
|
p.log.warning('Please select at least 2 colors.');
|
|
78
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();
|
|
79
104
|
|
|
80
105
|
const font = handleCancel(
|
|
81
106
|
await p.select({
|
|
@@ -86,22 +111,70 @@ async function promptGenerate() {
|
|
|
86
111
|
|
|
87
112
|
return {
|
|
88
113
|
action: 'generate',
|
|
89
|
-
config: { text: text.trim(), colors, font },
|
|
114
|
+
config: { mode: 'text', text: text.trim(), colors, font },
|
|
115
|
+
persistent: true,
|
|
90
116
|
};
|
|
91
117
|
}
|
|
92
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
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Main entry point for the interactive CLI.
|
|
137
|
+
* Returns { action: 'generate'|'run', config, persistent }.
|
|
138
|
+
*/
|
|
93
139
|
export async function runInteractiveUI() {
|
|
94
140
|
p.intro(chalk.bold('terminal-logo'));
|
|
95
141
|
|
|
96
|
-
const
|
|
142
|
+
const existingConfig = tryLoadConfig();
|
|
143
|
+
|
|
144
|
+
if (existingConfig) {
|
|
145
|
+
const action = handleCancel(
|
|
146
|
+
await p.select({
|
|
147
|
+
message: 'What would you like to do?',
|
|
148
|
+
initialValue: 'run',
|
|
149
|
+
options: [
|
|
150
|
+
{ value: 'run', label: 'Run', hint: 'use saved config' },
|
|
151
|
+
{ value: 'temp', label: 'New session', hint: "temporary, changes won't be saved" },
|
|
152
|
+
{ value: 'generate', label: 'Generate', hint: 'create a new logo config' },
|
|
153
|
+
],
|
|
154
|
+
})
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
if (action === 'generate') {
|
|
158
|
+
return promptGenerate();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (action === 'temp') {
|
|
162
|
+
p.outro('Launching logo (temporary session)...');
|
|
163
|
+
return { action: 'run', config: existingConfig, persistent: false };
|
|
164
|
+
}
|
|
97
165
|
|
|
166
|
+
// action === 'run'
|
|
167
|
+
p.outro('Launching logo...');
|
|
168
|
+
return { action: 'run', config: existingConfig, persistent: true };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// No existing config — only offer Generate
|
|
98
172
|
const action = handleCancel(
|
|
99
173
|
await p.select({
|
|
100
174
|
message: 'What would you like to do?',
|
|
101
|
-
initialValue:
|
|
175
|
+
initialValue: 'generate',
|
|
102
176
|
options: [
|
|
103
|
-
{ value: 'generate', label: 'Generate', hint: 'create
|
|
104
|
-
{ value: 'run', label: 'Run', hint: 'display current logo' },
|
|
177
|
+
{ value: 'generate', label: 'Generate', hint: 'create a new logo config' },
|
|
105
178
|
],
|
|
106
179
|
})
|
|
107
180
|
);
|
|
@@ -109,18 +182,4 @@ export async function runInteractiveUI() {
|
|
|
109
182
|
if (action === 'generate') {
|
|
110
183
|
return promptGenerate();
|
|
111
184
|
}
|
|
112
|
-
|
|
113
|
-
// action === 'run'
|
|
114
|
-
const config = tryLoadConfig();
|
|
115
|
-
if (!config) {
|
|
116
|
-
p.log.error(
|
|
117
|
-
'No valid .shell-logo.json found in the current directory.\n' +
|
|
118
|
-
' Run again and choose "Generate" to create one.'
|
|
119
|
-
);
|
|
120
|
-
p.outro('Done');
|
|
121
|
-
process.exit(1);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
p.outro('Launching logo...');
|
|
125
|
-
return { action: 'run', config };
|
|
126
185
|
}
|