@@ -15,6 +15,7 @@ import { enableConsoleCapture } from "../logging.js";
1515import type { PluginManifestCommandAliasRegistry } from "../plugins/manifest-command-aliases.js" ;
1616import { resolveManifestCommandAliasOwner } from "../plugins/manifest-command-aliases.runtime.js" ;
1717import { hasMemoryRuntime } from "../plugins/memory-state.js" ;
18+ import { createCliProgress } from "./progress.js" ;
1819import { maybeWarnAboutDebugProxyCoverage } from "../proxy-capture/coverage.js" ;
1920import {
2021 finalizeDebugProxyCapture ,
@@ -248,7 +249,25 @@ export async function runCli(argv: string[] = process.argv) {
248249 return ;
249250 }
250251 const { runCrestodian } = await import ( "../crestodian/crestodian.js" ) ;
251- await runCrestodian ( ) ;
252+ const progress = createCliProgress ( {
253+ label : "Starting Crestodian…" ,
254+ indeterminate : true ,
255+ delayMs : 0 ,
256+ fallback : "none" ,
257+ } ) ;
258+ let progressStopped = false ;
259+ const stopProgress = ( ) => {
260+ if ( progressStopped ) {
261+ return ;
262+ }
263+ progressStopped = true ;
264+ progress . done ( ) ;
265+ } ;
266+ try {
267+ await runCrestodian ( { onReady : stopProgress } ) ;
268+ } finally {
269+ stopProgress ( ) ;
270+ }
252271 return ;
253272 }
254273
@@ -268,95 +287,116 @@ export async function runCli(argv: string[] = process.argv) {
268287 return ;
269288 }
270289
271- // Capture all console output into structured logs while keeping stdout/stderr behavior.
272- enableConsoleCapture ( ) ;
290+ const startupProgress = createCliProgress ( {
291+ label : "Loading OpenClaw CLI…" ,
292+ indeterminate : true ,
293+ delayMs : 0 ,
294+ fallback : "none" ,
295+ } ) ;
296+ let startupProgressStopped = false ;
297+ const stopStartupProgress = ( ) => {
298+ if ( startupProgressStopped ) {
299+ return ;
300+ }
301+ startupProgressStopped = true ;
302+ startupProgress . done ( ) ;
303+ } ;
304+
305+ try {
306+ // Capture all console output into structured logs while keeping stdout/stderr behavior.
307+ enableConsoleCapture ( ) ;
273308
274- const [
275- { buildProgram } ,
276- { runFatalErrorHooks } ,
277- { installUnhandledRejectionHandler } ,
278- { restoreTerminalState } ,
279- ] = await Promise . all ( [
280- import ( "./program.js" ) ,
281- import ( "../infra/fatal-error-hooks.js" ) ,
282- import ( "../infra/unhandled-rejections.js" ) ,
283- import ( "../terminal/restore.js" ) ,
284- ] ) ;
285- const program = buildProgram ( ) ;
309+ const [
310+ { buildProgram } ,
311+ { runFatalErrorHooks } ,
312+ { installUnhandledRejectionHandler } ,
313+ { restoreTerminalState } ,
314+ ] = await Promise . all ( [
315+ import ( "./program.js" ) ,
316+ import ( "../infra/fatal-error-hooks.js" ) ,
317+ import ( "../infra/unhandled-rejections.js" ) ,
318+ import ( "../terminal/restore.js" ) ,
319+ ] ) ;
320+ const program = buildProgram ( ) ;
286321
287- // Global error handlers to prevent silent crashes from unhandled rejections/exceptions.
288- // These log the error and exit gracefully instead of crashing without trace.
289- installUnhandledRejectionHandler ( ) ;
322+ // Global error handlers to prevent silent crashes from unhandled rejections/exceptions.
323+ // These log the error and exit gracefully instead of crashing without trace.
324+ installUnhandledRejectionHandler ( ) ;
290325
291- process . on ( "uncaughtException" , ( error ) => {
292- console . error ( "[openclaw] Uncaught exception:" , formatUncaughtError ( error ) ) ;
293- for ( const message of runFatalErrorHooks ( { reason : "uncaught_exception" , error } ) ) {
294- console . error ( "[openclaw]" , message ) ;
295- }
296- restoreTerminalState ( "uncaught exception" , { resumeStdinIfPaused : false } ) ;
297- process . exit ( 1 ) ;
298- } ) ;
326+ process . on ( "uncaughtException" , ( error ) => {
327+ console . error ( "[openclaw] Uncaught exception:" , formatUncaughtError ( error ) ) ;
328+ for ( const message of runFatalErrorHooks ( { reason : "uncaught_exception" , error } ) ) {
329+ console . error ( "[openclaw]" , message ) ;
330+ }
331+ restoreTerminalState ( "uncaught exception" , { resumeStdinIfPaused : false } ) ;
332+ process . exit ( 1 ) ;
333+ } ) ;
299334
300- const parseArgv = rewriteUpdateFlagArgv ( normalizedArgv ) ;
301- const invocation = resolveCliArgvInvocation ( parseArgv ) ;
302- // Register the primary command (builtin or subcli) so help and command parsing
303- // are correct even with lazy command registration.
304- const { primary } = invocation ;
305- if ( primary && shouldRegisterPrimaryCommandOnly ( parseArgv ) ) {
306- const { getProgramContext } = await import ( "./program/program-context.js" ) ;
307- const ctx = getProgramContext ( program ) ;
308- if ( ctx ) {
309- const { registerCoreCliByName } = await import ( "./program/command-registry.js" ) ;
310- await registerCoreCliByName ( program , ctx , primary , parseArgv ) ;
335+ const parseArgv = rewriteUpdateFlagArgv ( normalizedArgv ) ;
336+ const invocation = resolveCliArgvInvocation ( parseArgv ) ;
337+ // Register the primary command (builtin or subcli) so help and command parsing
338+ // are correct even with lazy command registration.
339+ const { primary } = invocation ;
340+ if ( primary && shouldRegisterPrimaryCommandOnly ( parseArgv ) ) {
341+ const { getProgramContext } = await import ( "./program/program-context.js" ) ;
342+ const ctx = getProgramContext ( program ) ;
343+ if ( ctx ) {
344+ const { registerCoreCliByName } = await import ( "./program/command-registry.js" ) ;
345+ await registerCoreCliByName ( program , ctx , primary , parseArgv ) ;
346+ }
347+ const { registerSubCliByName } = await import ( "./program/register.subclis.js" ) ;
348+ await registerSubCliByName ( program , primary ) ;
311349 }
312- const { registerSubCliByName } = await import ( "./program/register.subclis.js" ) ;
313- await registerSubCliByName ( program , primary ) ;
314- }
315350
316- const hasBuiltinPrimary =
317- primary !== null &&
318- program . commands . some (
319- ( command ) => command . name ( ) === primary || command . aliases ( ) . includes ( primary ) ,
320- ) ;
321- const shouldSkipPluginRegistration = shouldSkipPluginCommandRegistration ( {
322- argv : parseArgv ,
323- primary,
324- hasBuiltinPrimary,
325- } ) ;
326- if ( ! shouldSkipPluginRegistration ) {
327- // Register plugin CLI commands before parsing
328- const { registerPluginCliCommandsFromValidatedConfig } = await import ( "../plugins/cli.js" ) ;
329- const config = await registerPluginCliCommandsFromValidatedConfig (
330- program ,
331- undefined ,
332- undefined ,
333- {
334- mode : "lazy" ,
335- primary,
336- } ,
337- ) ;
338- if ( config ) {
339- if (
340- primary &&
341- ! program . commands . some (
342- ( command ) => command . name ( ) === primary || command . aliases ( ) . includes ( primary ) ,
343- )
344- ) {
345- const missingPluginCommandMessage = resolveMissingPluginCommandMessage ( primary , config ) ;
346- if ( missingPluginCommandMessage ) {
347- throw new Error ( missingPluginCommandMessage ) ;
351+ const hasBuiltinPrimary =
352+ primary !== null &&
353+ program . commands . some (
354+ ( command ) => command . name ( ) === primary || command . aliases ( ) . includes ( primary ) ,
355+ ) ;
356+ const shouldSkipPluginRegistration = shouldSkipPluginCommandRegistration ( {
357+ argv : parseArgv ,
358+ primary,
359+ hasBuiltinPrimary,
360+ } ) ;
361+ if ( ! shouldSkipPluginRegistration ) {
362+ // Register plugin CLI commands before parsing
363+ const { registerPluginCliCommandsFromValidatedConfig } = await import ( "../plugins/cli.js" ) ;
364+ const config = await registerPluginCliCommandsFromValidatedConfig (
365+ program ,
366+ undefined ,
367+ undefined ,
368+ {
369+ mode : "lazy" ,
370+ primary,
371+ } ,
372+ ) ;
373+ if ( config ) {
374+ if (
375+ primary &&
376+ ! program . commands . some (
377+ ( command ) => command . name ( ) === primary || command . aliases ( ) . includes ( primary ) ,
378+ )
379+ ) {
380+ const missingPluginCommandMessage = resolveMissingPluginCommandMessage ( primary , config ) ;
381+ if ( missingPluginCommandMessage ) {
382+ throw new Error ( missingPluginCommandMessage ) ;
383+ }
348384 }
349385 }
350386 }
351- }
352387
353- try {
354- await program . parseAsync ( parseArgv ) ;
355- } catch ( error ) {
356- if ( ! ( error instanceof CommanderError ) ) {
357- throw error ;
388+ stopStartupProgress ( ) ;
389+
390+ try {
391+ await program . parseAsync ( parseArgv ) ;
392+ } catch ( error ) {
393+ if ( ! ( error instanceof CommanderError ) ) {
394+ throw error ;
395+ }
396+ process . exitCode = error . exitCode ;
358397 }
359- process . exitCode = error . exitCode ;
398+ } finally {
399+ stopStartupProgress ( ) ;
360400 }
361401 } finally {
362402 await closeCliMemoryManagers ( ) ;
0 commit comments