Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions typescript/packages/cli/src/commands/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
spacer,
brand,
nextSteps,
showFooter
showFooter,
icons,
} from '../ui/branding.js';
import { trackEvent, shutdownAnalytics } from '../analytics/posthog.js';

Expand Down Expand Up @@ -75,11 +76,10 @@ class DevUI {

stopSpinner(success: boolean = true, text?: string): void {
if (this.currentSpinner) {
if (success) {
this.currentSpinner.succeed(text);
} else {
this.currentSpinner.fail(text);
}
this.currentSpinner.stopAndPersist({
symbol: success ? icons.ok : icons.err,
text,
});
this.currentSpinner = null;
}
}
Expand All @@ -93,7 +93,7 @@ class DevUI {
const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1);

const lines = [
chalk.green.bold('✓ Development Server Ready'),
chalk.green.bold(`${icons.ok} Development Server Ready`),
'',
`${chalk.white.bold('MCP Server')} ${chalk.dim('Running (STDIO transport)')}`,
...(config.hasWidgets ? [`${chalk.white.bold('Widgets')} ${chalk.cyan(`http://localhost:${config.widgetsPort}`)}`] : []),
Expand All @@ -118,10 +118,10 @@ class DevUI {

log(message: string, type: 'info' | 'success' | 'warn' | 'error' = 'info'): void {
const prefix = {
info: chalk.blue('ℹ'),
success: chalk.green('✓'),
warn: chalk.yellow('⚠'),
error: chalk.red('✗'),
info: chalk.blue(icons.info),
success: chalk.green(icons.ok),
warn: chalk.yellow(icons.warn),
error: chalk.red(icons.err),
}[type];
console.log(`${prefix} ${message}`);
}
Expand Down Expand Up @@ -211,7 +211,7 @@ export async function devCommand(options: DevOptions) {
}

setTimeout(() => {
console.log(chalk.dim('\nGoodbye! 👋\n'));
console.log(chalk.dim(`\nGoodbye!${icons.wave}\n`));
process.exit(code);
}, 500);
};
Expand Down Expand Up @@ -320,10 +320,10 @@ export async function devCommand(options: DevOptions) {
if (!tscReady) {
tscReady = true;
} else {
console.log(chalk.green('✓') + chalk.dim(' MCP server recompiled'));
console.log(chalk.green(icons.ok) + chalk.dim(' MCP server recompiled'));
}
} else if (output.includes('error TS')) {
console.log(chalk.red('✗') + chalk.dim(' TypeScript error - check your code'));
console.log(chalk.red(icons.err) + chalk.dim(' TypeScript error - check your code'));
}
});
}
Expand Down Expand Up @@ -381,7 +381,7 @@ export async function devCommand(options: DevOptions) {
const now = Date.now();
if (now - lastRestart < RESTART_COOLDOWN) return;

console.log(chalk.yellow('⚡') + chalk.dim(' New widget detected, restarting...'));
console.log(chalk.yellow(icons.bolt) + chalk.dim(' New widget detected, restarting...'));

lastRestart = Date.now();

Expand Down Expand Up @@ -423,7 +423,7 @@ export async function devCommand(options: DevOptions) {
widgetsDevProcess?.stderr?.on('data', (data) => {
const output = data.toString();
if (output.includes('error')) {
console.log(chalk.red('✗') + chalk.dim(' Widgets: ') + output.trim());
console.log(chalk.red(icons.err) + chalk.dim(' Widgets: ') + output.trim());
}
});

Expand Down
5 changes: 3 additions & 2 deletions typescript/packages/cli/src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
spacer,
nextSteps,
brand,
showFooter
showFooter,
icons,
} from '../ui/branding.js';
import { trackEvent, shutdownAnalytics } from '../analytics/posthog.js';

Expand Down Expand Up @@ -273,7 +274,7 @@ export async function initCommand(projectName: string | undefined, options: Init
console.log(chalk.dim(' Mapbox (optional): Get free key from mapbox.com\n'));
}

console.log(chalk.dim(' Happy coding! 🎉\n'));
console.log(chalk.dim(` Happy coding!${icons.party}\n`));
showFooter();

trackEvent('cli_init_completed', {
Expand Down
5 changes: 3 additions & 2 deletions typescript/packages/cli/src/commands/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
spacer,
brand,
NITRO_BANNER_FULL,
showFooter
showFooter,
icons,
} from '../ui/branding.js';
import { trackEvent, shutdownAnalytics } from '../analytics/posthog.js';

Expand Down Expand Up @@ -97,7 +98,7 @@ export async function startCommand(options: StartOptions) {
serverProcess.kill('SIGTERM');
setTimeout(() => {
serverProcess.kill('SIGKILL');
console.log(chalk.dim('\nGoodbye! 👋\n'));
console.log(chalk.dim(`\nGoodbye!${icons.wave}\n`));
process.exit(0);
}, 5000);
};
Expand Down
84 changes: 55 additions & 29 deletions typescript/packages/cli/src/ui/branding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ import ora, { Ora } from 'ora';
// OFFICIAL MCP BRANDING (Wekan Enterprise Solutions)
// ═══════════════════════════════════════════════════════════════════════════

export const noColor = !!process.env.NO_COLOR;

// ASCII fallback chars for box-drawing and emoji
const BOX = noColor
? { TL: '+', TR: '+', BL: '+', BR: '+', TH: '=', TV: '|', tl: '+', tr: '+', bl: '+', br: '+', h: '-', v: '|' }
: { TL: '╔', TR: '╗', BL: '╚', BR: '╝', TH: '═', TV: '║', tl: '┌', tr: '┐', bl: '└', br: '┘', h: '─', v: '│' };

export const icons = {
ok: noColor ? '[ok]' : '✓',
err: noColor ? '[!!]' : '✗',
info: noColor ? '[i]' : 'ℹ',
warn: noColor ? '[!]' : '⚠',
dot: noColor ? '.' : '·',
bolt: noColor ? '!' : '⚡',
wave: noColor ? '' : ' 👋',
party: noColor ? '' : ' 🎉',
};

// Core Colors
const SIGNAL_BLUE = '#187CF4'; // Primary
const SKY_BLUE = '#05A3FD'; // Secondary
Expand Down Expand Up @@ -54,14 +72,22 @@ function boxLine(content: string, borderColor: (s: string) => string = brand.sig
const paddingSize = Math.max(0, TOTAL_WIDTH - visualLength - 2);
const padding = ' '.repeat(paddingSize);

return borderColor('║') + content + padding + borderColor('║');
return borderColor(BOX.TV) + content + padding + borderColor(BOX.TV);
}

/**
* Redesigned Banner with Restored ASCII NITRO
*/
export const NITRO_BANNER_FULL = `
${brand.signalBold('╔' + '═'.repeat(TOTAL_WIDTH - 2) + '╗')}
export const NITRO_BANNER_FULL = noColor
? `
+${'='.repeat(TOTAL_WIDTH - 2)}+
|${' '.repeat(TOTAL_WIDTH - 2)}|
| NITROSTACK -- Official MCP Framework${' '.repeat(TOTAL_WIDTH - 44)}|
|${' '.repeat(TOTAL_WIDTH - 2)}|
+${'='.repeat(TOTAL_WIDTH - 2)}+
`
: `
${brand.signalBold(BOX.TL + BOX.TH.repeat(TOTAL_WIDTH - 2) + BOX.TR)}
${boxLine('')}
${boxLine(' ' + brand.signalBold('███╗ ██╗██╗████████╗██████╗ ██████╗ '))}
${boxLine(' ' + brand.signalBold('████╗ ██║██║╚══██╔══╝██╔══██╗██╔═══██╗'))}
Expand All @@ -72,21 +98,21 @@ ${boxLine(' ' + chalk.dim('╚═╝ ╚═══╝╚═╝ ╚═╝
${boxLine('')}
${boxLine(' ' + brand.signalBold('NITROSTACK') + ' ' + chalk.dim('─ Official MCP Framework'))}
${boxLine('')}
${brand.signalBold('╚' + '═'.repeat(TOTAL_WIDTH - 2) + '╝')}
${brand.signalBold(BOX.BL + BOX.TH.repeat(TOTAL_WIDTH - 2) + BOX.BR)}
`;

export function createHeader(title: string, subtitle?: string): string {
const content = ' ' + brand.signalBold('NITROSTACK') + ' ' + chalk.dim('─') + ' ' + chalk.white.bold(title);
const subContent = subtitle ? ' ' + chalk.dim(subtitle) : '';

const borderTop = brand.signalBold('┌' + '─'.repeat(TOTAL_WIDTH - 2) + '┐');
const borderBottom = brand.signalBold('└' + '─'.repeat(TOTAL_WIDTH - 2) + '┘');
const borderTop = brand.signalBold(BOX.tl + BOX.h.repeat(TOTAL_WIDTH - 2) + BOX.tr);
const borderBottom = brand.signalBold(BOX.bl + BOX.h.repeat(TOTAL_WIDTH - 2) + BOX.br);

const line = (c: string) => {
const visualLength = stripAnsi(c).length;
const paddingSize = Math.max(0, TOTAL_WIDTH - visualLength - 2);
const padding = ' '.repeat(paddingSize);
return brand.signalBold('│') + c + padding + brand.signalBold('│');
return brand.signalBold(BOX.v) + c + padding + brand.signalBold(BOX.v);
};

let header = `\n${borderTop}\n${line(content)}\n`;
Expand All @@ -100,15 +126,15 @@ export function createHeader(title: string, subtitle?: string): string {

export function createBox(lines: string[], type: 'success' | 'error' | 'info' | 'warning' = 'info'): string {
const colors = {
success: { border: brand.mint, bTop: '┌', bSide: '│', bBot: '└' },
error: { border: brand.error, bTop: '┌', bSide: '│', bBot: '└' },
info: { border: brand.signal, bTop: '┌', bSide: '│', bBot: '└' },
warning: { border: brand.warning, bTop: '┌', bSide: '│', bBot: '└' },
success: { border: brand.mint },
error: { border: brand.error },
info: { border: brand.signal },
warning: { border: brand.warning },
};

const { border, bTop, bSide, bBot } = colors[type];
const { border } = colors[type];

let output = border(bTop + '─'.repeat(TOTAL_WIDTH - 2) + '┐\n');
let output = border(BOX.tl + BOX.h.repeat(TOTAL_WIDTH - 2) + BOX.tr + '\n');

for (let line of lines) {
const maxInnerWidth = TOTAL_WIDTH - 6;
Expand All @@ -120,17 +146,17 @@ export function createBox(lines: string[], type: 'success' | 'error' | 'info' |

const finalVisualLength = stripAnsi(line).length;
const padding = ' '.repeat(Math.max(0, TOTAL_WIDTH - finalVisualLength - 6));
output += border(bSide) + ' ' + line + padding + ' ' + border(bSide) + '\n';
output += border(BOX.v) + ' ' + line + padding + ' ' + border(BOX.v) + '\n';
}

output += border(bBot + '─'.repeat(TOTAL_WIDTH - 2) + '┘');
output += border(BOX.bl + BOX.h.repeat(TOTAL_WIDTH - 2) + BOX.br);

return output;
}

export function createSuccessBox(title: string, items: string[]): string {
const lines = [
brand.mintBold(` ${title}`),
brand.mintBold(`${icons.ok} ${title}`),
'',
...items.map(item => chalk.dim(` ${item}`)),
'',
Expand All @@ -140,7 +166,7 @@ export function createSuccessBox(title: string, items: string[]): string {

export function createErrorBox(title: string, message: string): string {
const lines = [
brand.error.bold(` ${title}`),
brand.error.bold(`${icons.err} ${title}`),
'',
chalk.white(message.substring(0, TOTAL_WIDTH - 10)),
'',
Expand Down Expand Up @@ -177,22 +203,22 @@ export class NitroSpinner {
}

succeed(text?: string): this {
this.spinner.succeed(text ? brand.mint('✓ ') + chalk.dim(text) : undefined);
this.spinner.stopAndPersist({ symbol: brand.mint(icons.ok), text: text ? chalk.dim(text) : undefined });
return this;
}

fail(text?: string): this {
this.spinner.fail(text ? brand.error('✗ ') + chalk.dim(text) : undefined);
this.spinner.stopAndPersist({ symbol: brand.error(icons.err), text: text ? chalk.dim(text) : undefined });
return this;
}

info(text?: string): this {
this.spinner.info(text ? brand.signal('ℹ ') + chalk.dim(text) : undefined);
this.spinner.stopAndPersist({ symbol: brand.signal(icons.info), text: text ? chalk.dim(text) : undefined });
return this;
}

warn(text?: string): this {
this.spinner.warn(text ? brand.warning('⚠ ') + chalk.dim(text) : undefined);
this.spinner.stopAndPersist({ symbol: brand.warning(icons.warn), text: text ? chalk.dim(text) : undefined });
return this;
}

Expand All @@ -203,19 +229,19 @@ export class NitroSpinner {
}

export function log(message: string, type: 'success' | 'error' | 'info' | 'warning' | 'dim' = 'info'): void {
const icons = {
success: brand.mint('✓'),
error: brand.error('✗'),
info: brand.signal('ℹ'),
warning: brand.warning('⚠'),
dim: chalk.dim('·'),
const logIcons = {
success: brand.mint(icons.ok),
error: brand.error(icons.err),
info: brand.signal(icons.info),
warning: brand.warning(icons.warn),
dim: chalk.dim(icons.dot),
};

console.log(` ${icons[type]} ${type === 'dim' ? chalk.dim(message) : message}`);
console.log(` ${logIcons[type]} ${type === 'dim' ? chalk.dim(message) : message}`);
}

export function divider(): void {
console.log(chalk.dim(' ' + '─'.repeat(TOTAL_WIDTH - 4)));
console.log(chalk.dim(' ' + BOX.h.repeat(TOTAL_WIDTH - 4)));
}

export function spacer(): void {
Expand Down