blok0 0.1.2 โ†’ 0.1.4

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/src/index.ts CHANGED
@@ -7,6 +7,7 @@ import { generateStarter } from './handlers/generate';
7
7
  import { handleLogin, handleLogout } from './handlers/login';
8
8
  import { handleAddBlock } from './handlers/add-block';
9
9
  import { createEmptyRegistry } from './registry';
10
+ import { setUIFlags } from './ui';
10
11
 
11
12
  function prompt(question: string): Promise<string> {
12
13
  return new Promise((resolve) => {
@@ -43,6 +44,9 @@ OPTIONS:
43
44
  --version, -v Show version information
44
45
  --verbose Enable verbose logging
45
46
  --dry-run Preview changes without applying them
47
+ --no-animation Disable animations and spinners
48
+ --no-emoji Disable emoji in output
49
+ --ci Optimize for CI environments (implies --no-animation and --no-emoji)
46
50
 
47
51
  EXAMPLES:
48
52
  blok0 login
@@ -56,18 +60,30 @@ For more information, visit: https://github.com/blok0-payload/cli
56
60
  async function main() {
57
61
  const args = process.argv.slice(2);
58
62
 
59
- if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
63
+ // Parse global UI flags
64
+ const noAnimation = args.includes('--no-animation');
65
+ const noEmoji = args.includes('--no-emoji');
66
+ const ciMode = args.includes('--ci');
67
+
68
+ setUIFlags({ noAnimation, noEmoji, ci: ciMode });
69
+
70
+ // Filter out global flags from args
71
+ const filteredArgs = args.filter(arg =>
72
+ !['--no-animation', '--no-emoji', '--ci'].includes(arg)
73
+ );
74
+
75
+ if (filteredArgs.length === 0 || filteredArgs.includes('--help') || filteredArgs.includes('-h')) {
60
76
  showHelp();
61
77
  process.exit(0);
62
78
  }
63
79
 
64
- if (args.includes('--version') || args.includes('-v')) {
80
+ if (filteredArgs.includes('--version') || filteredArgs.includes('-v')) {
65
81
  const pkg = require('../package.json');
66
82
  console.log(`blok0 v${pkg.version}`);
67
83
  process.exit(0);
68
84
  }
69
85
 
70
- const [command, ...restArgs] = args;
86
+ const [command, ...restArgs] = filteredArgs;
71
87
 
72
88
  try {
73
89
  switch (command) {
@@ -107,7 +123,7 @@ async function main() {
107
123
  case 'add':
108
124
  const [addSubcommand, ...addRestArgs] = restArgs;
109
125
  if (addSubcommand === 'block') {
110
- const blockUrl = `https://www.blok0.com/api/cli/sections/${addRestArgs[0]}`;
126
+ const blockUrl = `https://www.blok0.xyz/api/cli/sections/${addRestArgs[0]}`;
111
127
  if (!blockUrl) {
112
128
  console.error('Error: Block Slug is required. Use: blok0 add block <slug>');
113
129
  process.exit(1);
@@ -118,7 +134,7 @@ async function main() {
118
134
  };
119
135
  await handleAddBlock(blockUrl, options);
120
136
  } else {
121
- console.error('Error: Invalid subcommand. Use: blok0 add block <url>');
137
+ console.error('Error: Invalid subcommand. Use: blok0 add block <slug>');
122
138
  process.exit(1);
123
139
  }
124
140
  break;
@@ -167,16 +183,16 @@ async function handleGenerateStarter(args: string[]) {
167
183
  }
168
184
 
169
185
  async function handleDebug() {
170
- console.log('๐Ÿ” Blok0 CLI Debug Information');
171
- console.log('==============================');
172
- console.log('');
186
+ const { showSection, log, withSpinner, EMOJIS } = await import('./ui');
187
+
188
+ showSection('๐Ÿ” Blok0 CLI Debug Information', EMOJIS.SEARCH);
173
189
 
174
190
  // Check stored token
175
191
  const { getAccessToken, isAuthenticated } = await import('./auth');
176
192
  const token = await getAccessToken();
177
193
  const isAuth = await isAuthenticated();
178
194
 
179
- console.log('๐Ÿ” Authentication Status:');
195
+ log.header('๐Ÿ” Authentication Status:');
180
196
  console.log(` Authenticated: ${isAuth ? 'โœ… Yes' : 'โŒ No'}`);
181
197
  console.log(` Token Stored: ${token ? 'โœ… Yes' : 'โŒ No'}`);
182
198
 
@@ -185,25 +201,25 @@ async function handleDebug() {
185
201
  console.log(` Authorization Header: Bearer ${token}`);
186
202
  }
187
203
 
188
- console.log('');
189
- console.log('๐ŸŒ API Configuration:');
204
+ log.header('๐ŸŒ API Configuration:');
190
205
  console.log(' Base URL: https://www.blok0.xyz');
191
206
  console.log(' User Agent: blok0-cli/1.0.0');
192
207
 
193
- console.log('');
194
- console.log('๐Ÿงช Test API Connection:');
208
+ log.header('๐Ÿงช Test API Connection:');
195
209
 
196
210
  // Test API connection
197
211
  const { apiClient } = await import('./api');
198
212
  try {
199
- const connectionTest = await apiClient.testConnection();
213
+ const connectionTest = await withSpinner(
214
+ 'Testing API connection',
215
+ () => apiClient.testConnection()
216
+ );
200
217
  console.log(` Connection Test: ${connectionTest ? 'โœ… Passed' : 'โŒ Failed'}`);
201
218
  } catch (error) {
202
219
  console.log(` Connection Test: โŒ Failed - ${(error as Error).message}`);
203
220
  }
204
221
 
205
- console.log('');
206
- console.log('๐Ÿ’ก Next Steps:');
222
+ log.header('๐Ÿ’ก Next Steps:');
207
223
  console.log(' 1. If no token, run: blok0 login');
208
224
  console.log(' 2. Test API with: blok0 add block <url>');
209
225
  console.log(' 3. Check server logs for detailed request info');
package/src/ui.ts ADDED
@@ -0,0 +1,234 @@
1
+ import ora, { Ora } from 'ora';
2
+ import chalk from 'chalk';
3
+ import { SingleBar } from 'cli-progress';
4
+
5
+ // Emoji constants for consistent usage
6
+ export const EMOJIS = {
7
+ SUCCESS: 'โœ…',
8
+ ERROR: 'โŒ',
9
+ WARNING: 'โš ๏ธ',
10
+ INFO: 'โ„น๏ธ',
11
+ LOCK: '๐Ÿ”',
12
+ PACKAGE: '๐Ÿ“ฆ',
13
+ FOLDER: '๐Ÿ“',
14
+ GEAR: '๐Ÿ”ง',
15
+ SEARCH: '๐Ÿ”',
16
+ ROCKET: '๐Ÿš€',
17
+ DOWNLOAD: '๐Ÿ“ฅ',
18
+ PARTY: '๐ŸŽ‰',
19
+ WRENCH: '๐Ÿ”ง',
20
+ CHECK: 'โœ…',
21
+ CROSS: 'โŒ',
22
+ ARROW: 'โ†’',
23
+ } as const;
24
+
25
+ // Color utilities
26
+ export const colors = {
27
+ success: chalk.green,
28
+ error: chalk.red,
29
+ warning: chalk.yellow,
30
+ info: chalk.blue,
31
+ accent: chalk.cyan,
32
+ muted: chalk.gray,
33
+ };
34
+
35
+ // Check if we're in a TTY environment
36
+ export const isTTY = process.stdout.isTTY;
37
+
38
+ // Global flags for disabling features
39
+ export let noAnimation = false;
40
+ export let noEmoji = false;
41
+ export let ciMode = false;
42
+
43
+ // Set global flags
44
+ export function setUIFlags(flags: { noAnimation?: boolean; noEmoji?: boolean; ci?: boolean }) {
45
+ noAnimation = flags.noAnimation || ciMode;
46
+ noEmoji = flags.noEmoji || ciMode;
47
+ ciMode = flags.ci || false;
48
+ }
49
+
50
+ // Apply emoji settings to text
51
+ function applyEmoji(text: string, emoji?: string): string {
52
+ if (noEmoji || !emoji) return text;
53
+ return `${emoji} ${text}`;
54
+ }
55
+
56
+ // Spinner wrapper with TTY detection
57
+ export class Spinner {
58
+ private spinner: Ora | null = null;
59
+ private startTime: number = 0;
60
+
61
+ constructor(private text: string, private emoji?: string) {}
62
+
63
+ start(): this {
64
+ if (!isTTY || noAnimation) {
65
+ console.log(applyEmoji(this.text, this.emoji));
66
+ this.startTime = Date.now();
67
+ return this;
68
+ }
69
+
70
+ this.spinner = ora({
71
+ text: applyEmoji(this.text, this.emoji),
72
+ spinner: 'dots',
73
+ }).start();
74
+ this.startTime = Date.now();
75
+ return this;
76
+ }
77
+
78
+ update(text: string, emoji?: string): this {
79
+ if (!this.spinner) {
80
+ if (isTTY && !noAnimation) {
81
+ this.spinner = ora(applyEmoji(text, emoji)).start();
82
+ } else {
83
+ console.log(applyEmoji(text, emoji));
84
+ }
85
+ return this;
86
+ }
87
+
88
+ this.spinner.text = applyEmoji(text, emoji || this.emoji);
89
+ return this;
90
+ }
91
+
92
+ succeed(text?: string): this {
93
+ const duration = this.getDuration();
94
+ const successText = text || this.text;
95
+ const durationInfo = duration > 1000 ? ` (${duration}ms)` : '';
96
+
97
+ if (this.spinner) {
98
+ this.spinner.succeed(applyEmoji(successText, EMOJIS.SUCCESS) + durationInfo);
99
+ } else {
100
+ console.log(applyEmoji(successText + durationInfo, EMOJIS.SUCCESS));
101
+ }
102
+ return this;
103
+ }
104
+
105
+ fail(text?: string): this {
106
+ const failText = text || this.text;
107
+ if (this.spinner) {
108
+ this.spinner.fail(applyEmoji(failText, EMOJIS.ERROR));
109
+ } else {
110
+ console.error(applyEmoji(failText, EMOJIS.ERROR));
111
+ }
112
+ return this;
113
+ }
114
+
115
+ stop(): this {
116
+ if (this.spinner) {
117
+ this.spinner.stop();
118
+ }
119
+ return this;
120
+ }
121
+
122
+ private getDuration(): number {
123
+ return Date.now() - this.startTime;
124
+ }
125
+ }
126
+
127
+ // Utility function to wrap async operations with spinner
128
+ export async function withSpinner<T>(
129
+ text: string,
130
+ operation: () => Promise<T>,
131
+ options: { emoji?: string; successText?: string; failText?: string } = {}
132
+ ): Promise<T> {
133
+ const spinner = new Spinner(text, options.emoji);
134
+ spinner.start();
135
+
136
+ try {
137
+ const result = await operation();
138
+ spinner.succeed(options.successText);
139
+ return result;
140
+ } catch (error) {
141
+ spinner.fail(options.failText);
142
+ throw error;
143
+ }
144
+ }
145
+
146
+ // Progress bar utilities
147
+ export class ProgressBar {
148
+ private bar: SingleBar | null = null;
149
+
150
+ constructor(private options: {
151
+ title?: string;
152
+ total: number;
153
+ format?: string;
154
+ }) {}
155
+
156
+ start(): this {
157
+ if (!isTTY || noAnimation) {
158
+ if (this.options.title) {
159
+ console.log(this.options.title);
160
+ }
161
+ return this;
162
+ }
163
+
164
+ this.bar = new SingleBar({
165
+ format: this.options.format || '{bar} {percentage}% | {value}/{total} | {eta}s',
166
+ barCompleteChar: '\u2588',
167
+ barIncompleteChar: '\u2591',
168
+ hideCursor: true,
169
+ });
170
+
171
+ if (this.options.title) {
172
+ console.log(this.options.title);
173
+ }
174
+
175
+ this.bar.start(this.options.total, 0);
176
+ return this;
177
+ }
178
+
179
+ update(current: number): this {
180
+ if (this.bar) {
181
+ this.bar.update(current);
182
+ } else if (isTTY && !noAnimation) {
183
+ // Fallback to simple progress if bar not initialized
184
+ const percent = Math.round((current / this.options.total) * 100);
185
+ process.stdout.write(`\r${this.options.title || 'Progress'}: ${percent}% (${current}/${this.options.total})`);
186
+ }
187
+ return this;
188
+ }
189
+
190
+ increment(amount: number = 1): this {
191
+ if (this.bar) {
192
+ this.bar.increment(amount);
193
+ }
194
+ return this;
195
+ }
196
+
197
+ stop(): this {
198
+ if (this.bar) {
199
+ this.bar.stop();
200
+ } else if (isTTY && !noAnimation) {
201
+ process.stdout.write('\n');
202
+ }
203
+ return this;
204
+ }
205
+ }
206
+
207
+ // Enhanced console methods
208
+ export const log = {
209
+ success: (text: string) => console.log(colors.success(applyEmoji(text, EMOJIS.SUCCESS))),
210
+ error: (text: string) => console.error(colors.error(applyEmoji(text, EMOJIS.ERROR))),
211
+ warning: (text: string) => console.warn(colors.warning(applyEmoji(text, EMOJIS.WARNING))),
212
+ info: (text: string) => console.log(colors.info(applyEmoji(text, EMOJIS.INFO))),
213
+ plain: (text: string) => console.log(text),
214
+ header: (text: string) => console.log(colors.accent(`\n${text}\n`)),
215
+ step: (step: number, total: number, text: string) => {
216
+ const stepText = `${step}/${total}`;
217
+ console.log(colors.muted(`[${stepText}]`) + ' ' + text);
218
+ },
219
+ };
220
+
221
+ // Utility to show a section header
222
+ export function showSection(title: string, emoji?: string) {
223
+ console.log('\n' + colors.accent('='.repeat(50)));
224
+ console.log(applyEmoji(title, emoji));
225
+ console.log(colors.accent('='.repeat(50)) + '\n');
226
+ }
227
+
228
+ // Utility to show next steps
229
+ export function showNextSteps(steps: string[]) {
230
+ console.log(colors.accent('\nNext steps:'));
231
+ steps.forEach((step, index) => {
232
+ console.log(` ${index + 1}. ${step}`);
233
+ });
234
+ }