blok0 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.
@@ -1,6 +1,7 @@
1
1
  import { isAuthenticated, clearCredentials, storeAccessToken, AuthCallback } from '../auth';
2
2
  import { AuthServer } from '../auth/server';
3
3
  import open from 'open';
4
+ import { withSpinner, log, showSection, EMOJIS } from '../ui';
4
5
 
5
6
  // Add SIGINT handler for graceful cleanup
6
7
  process.on('SIGINT', () => {
@@ -15,13 +16,15 @@ export async function handleLogin(token?: string, manual?: boolean): Promise<voi
15
16
  // Direct token authentication (CI/CD)
16
17
  if (token) {
17
18
  try {
18
- console.log('🔐 Saving authentication token...');
19
- await storeAccessToken(token);
20
- console.log('✅ Successfully authenticated!');
19
+ await withSpinner(
20
+ 'Saving authentication token',
21
+ () => storeAccessToken(token),
22
+ { emoji: EMOJIS.LOCK, successText: 'Successfully authenticated!' }
23
+ );
21
24
  console.log('');
22
- console.log('You can now use blok0 commands that require authentication.');
25
+ log.info('You can now use blok0 commands that require authentication.');
23
26
  } catch (error) {
24
- console.error('Failed to save authentication token:', (error as Error).message);
27
+ log.error('Failed to save authentication token: ' + (error as Error).message);
25
28
  process.exit(1);
26
29
  }
27
30
  return;
@@ -49,36 +52,40 @@ export async function handleLogin(token?: string, manual?: boolean): Promise<voi
49
52
  * Handle browser-based authentication flow
50
53
  */
51
54
  async function handleBrowserLogin(): Promise<void> {
52
- console.log('🔐 Blok0 Authentication');
53
- console.log('======================');
54
- console.log('');
55
+ showSection('🔐 Blok0 Authentication', EMOJIS.LOCK);
55
56
 
56
57
  // Create authentication server
57
58
  const authServer = new AuthServer();
58
59
 
59
60
  try {
60
61
  // Initialize server (find available port)
61
- console.log('🚀 Starting authentication server...');
62
- await authServer.initialize();
62
+ await withSpinner(
63
+ 'Starting authentication server',
64
+ () => authServer.initialize(),
65
+ { emoji: EMOJIS.ROCKET }
66
+ );
63
67
 
64
68
  // Get the authorization URL (now port is available)
65
69
  const authUrl = authServer.getAuthorizationUrl();
66
70
 
67
- console.log('🌐 Opening browser for authentication...');
71
+ log.info('Opening browser for authentication...');
68
72
  await open(authUrl);
69
73
 
70
- console.log('📱 Please complete authentication in your browser.');
71
- console.log('⏳ Waiting for authentication to complete...');
74
+ log.info('Please complete authentication in your browser.');
75
+ log.plain('⏳ Waiting for authentication to complete...');
72
76
 
73
77
  // Start server and wait for callback
74
78
  const authCallback: AuthCallback = await authServer.start();
75
79
 
76
80
  // Store the token
77
- console.log('🔐 Saving authentication token...');
78
- await storeAccessToken(authCallback.token);
79
- console.log('✅ Successfully authenticated!');
81
+ await withSpinner(
82
+ 'Saving authentication token',
83
+ () => storeAccessToken(authCallback.token),
84
+ { emoji: EMOJIS.LOCK, successText: 'Successfully authenticated!' }
85
+ );
86
+
80
87
  console.log('');
81
- console.log('You can now use blok0 commands that require authentication.');
88
+ log.info('You can now use blok0 commands that require authentication.');
82
89
 
83
90
  } catch (error) {
84
91
  authServer.stop();
@@ -90,23 +97,21 @@ async function handleBrowserLogin(): Promise<void> {
90
97
  * Show manual authentication instructions
91
98
  */
92
99
  function showManualInstructions(): void {
93
- console.log('🔐 Blok0 Manual Authentication');
94
- console.log('==============================');
95
- console.log('');
100
+ showSection('🔐 Blok0 Manual Authentication', EMOJIS.LOCK);
96
101
  console.log('To authenticate with the Blok0 API, make a POST request to:');
97
102
  console.log('https://www.blok0.xyz/api/customers/login');
98
103
  console.log('');
99
- console.log('Example using curl:');
104
+ log.info('Example using curl:');
100
105
  console.log('curl -X POST https://www.blok0.xyz/api/customers/login \\');
101
106
  console.log(' -H "Content-Type: application/json" \\');
102
107
  console.log(' -d \'{"email": "your-email@example.com", "password": "your-password"}\'');
103
108
  console.log('');
104
- console.log('Then copy the access token and run:');
109
+ log.info('Then copy the access token and run:');
105
110
  console.log('blok0 login --token <your-token>');
106
111
  console.log('');
107
- console.log('For CI/CD environments, set the BLOK0_TOKEN environment variable.');
112
+ log.info('For CI/CD environments, set the BLOK0_TOKEN environment variable.');
108
113
  console.log('');
109
- console.log('💡 For browser-based login, run: blok0 login');
114
+ log.info('For browser-based login, run: blok0 login');
110
115
  }
111
116
 
112
117
  /**
@@ -114,17 +119,23 @@ function showManualInstructions(): void {
114
119
  */
115
120
  export async function handleLogout(): Promise<void> {
116
121
  try {
117
- const wasAuthenticated = await isAuthenticated();
122
+ const wasAuthenticated = await withSpinner(
123
+ 'Checking authentication status',
124
+ () => isAuthenticated()
125
+ );
118
126
 
119
127
  if (!wasAuthenticated) {
120
- console.log('You are not currently logged in.');
128
+ log.warning('You are not currently logged in.');
121
129
  return;
122
130
  }
123
131
 
124
- await clearCredentials();
125
- console.log(' Successfully logged out and cleared stored credentials.');
132
+ await withSpinner(
133
+ 'Clearing stored credentials',
134
+ () => clearCredentials(),
135
+ { emoji: EMOJIS.LOCK, successText: 'Successfully logged out and cleared stored credentials.' }
136
+ );
126
137
  } catch (error) {
127
- console.error('Failed to logout:', (error as Error).message);
138
+ log.error('Failed to logout: ' + (error as Error).message);
128
139
  process.exit(1);
129
140
  }
130
141
  }
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,11 +44,14 @@ 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
49
53
  blok0 generate starter my-project
50
- blok0 add block https://api.example.com/blocks/123
54
+ blok0 add block https://www.blok0.com/api/cli/sections/123
51
55
 
52
56
  For more information, visit: https://github.com/blok0-payload/cli
53
57
  `);
@@ -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,9 +123,9 @@ async function main() {
107
123
  case 'add':
108
124
  const [addSubcommand, ...addRestArgs] = restArgs;
109
125
  if (addSubcommand === 'block') {
110
- const blockUrl = addRestArgs[0];
126
+ const blockUrl = `https://www.blok0.com/api/cli/sections/${addRestArgs[0]}`;
111
127
  if (!blockUrl) {
112
- console.error('Error: Block URL is required. Use: blok0 add block <url>');
128
+ console.error('Error: Block Slug is required. Use: blok0 add block <slug>');
113
129
  process.exit(1);
114
130
  }
115
131
  const options = {
@@ -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
+ }
package/tsconfig.json CHANGED
@@ -1,16 +1,16 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "commonjs",
5
- "outDir": "./dist",
6
- "rootDir": "./src",
7
- "strict": true,
8
- "esModuleInterop": true,
9
- "skipLibCheck": true,
10
- "forceConsistentCasingInFileNames": true,
11
- "declaration": true,
12
- "resolveJsonModule": true
13
- },
14
- "include": ["src/**/*.ts"],
15
- "exclude": ["node_modules", "dist", "src/templates/**"]
16
- }
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "commonjs",
5
+ "outDir": "./dist",
6
+ "rootDir": "./src",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "declaration": true,
12
+ "resolveJsonModule": true
13
+ },
14
+ "include": ["src/**/*.ts"],
15
+ "exclude": ["node_modules", "dist", "src/templates/**"]
16
+ }