Skip to content
Merged
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
200 changes: 200 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"llmist": "^16.0.4",
"marklassian": "^1.1.0",
"open": "^11.0.0",
"ora": "^9.3.0",
"pg": "^8.18.0",
"trello.js": "^1.2.8",
"zangief": "^1.0.5",
Expand Down
26 changes: 26 additions & 0 deletions src/cli/dashboard/_shared/base.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Command, Flags } from '@oclif/core';
import { TRPCClientError } from '@trpc/client';
import chalk from 'chalk';
import { type DashboardClient, createDashboardClient } from './client.js';
import { type CliConfig, loadConfig } from './config.js';
import { printDetail, printTable } from './format.js';
import { withSpinner } from './spinner.js';

export function extractBaseFlags(argv: string[]): { server?: string; org?: string } | undefined {
let server: string | undefined;
Expand Down Expand Up @@ -83,6 +85,30 @@ export abstract class DashboardCommand extends Command {
printDetail(obj, fields);
}

/**
* Print a success message with a green ✓ prefix.
*/
protected success(message: string): void {
console.log(chalk.green(`✓ ${message}`));
}

/**
* Print an informational message with a blue ℹ prefix.
*/
protected info(message: string): void {
console.log(chalk.blue(`ℹ ${message}`));
}

/**
* Wrap an async function with an animated spinner.
* Automatically suppressed when --json flag is active, NO_COLOR=1, or CI=1.
*/
protected withSpinner<T>(message: string, fn: () => Promise<T>): Promise<T> {
// Suppress spinner when --json flag is present
const isJson = this.argv.includes('--json');
return withSpinner(message, fn, { silent: isJson });
}

protected handleError(err: unknown): never {
if (err instanceof TRPCClientError) {
const code = (err.data as { code?: string } | undefined)?.code;
Expand Down
48 changes: 48 additions & 0 deletions src/cli/dashboard/_shared/spinner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import ora from 'ora';

/**
* Returns true if spinners should be suppressed (silent mode).
* Spinners are suppressed when:
* - --json flag would be passed (NO_COLOR env var is set)
* - CI environment detected
* - NO_COLOR env var set (convention for disabling colors/animations)
* - Explicitly requested via `silent` option
*/
export function isSilentMode(options?: { silent?: boolean }): boolean {
if (options?.silent) return true;
if (process.env.NO_COLOR) return true;
if (process.env.CI) return true;
return false;
}

/**
* Wraps an async function with an animated spinner.
* Clears the spinner on success or failure.
* Spinner is automatically suppressed in CI, NO_COLOR, or when `silent` is true.
*
* @param message - The spinner text to display while `fn` is running
* @param fn - The async function to execute
* @param options - Optional configuration
* @returns The result of `fn`
*/
export async function withSpinner<T>(
message: string,
fn: () => Promise<T>,
options?: { silent?: boolean },
): Promise<T> {
const silent = isSilentMode(options);

if (silent) {
return fn();
}

const spinner = ora(message).start();
try {
const result = await fn();
spinner.stop();
return result;
} catch (err) {
spinner.stop();
throw err;
}
}
Loading
Loading