2026-01-14 01:08:15 +00:00
import { resolveControlUiLinks } from "../../commands/onboard-helpers.js" ;
import {
resolveGatewayLaunchAgentLabel ,
resolveGatewaySystemdServiceName ,
} from "../../daemon/constants.js" ;
import { renderGatewayServiceCleanupHints } from "../../daemon/inspect.js" ;
import { resolveGatewayLogPaths } from "../../daemon/launchd.js" ;
import { getResolvedLoggerSettings } from "../../logging.js" ;
import { defaultRuntime } from "../../runtime.js" ;
import { colorize , isRich , theme } from "../../terminal/theme.js" ;
2026-01-14 14:31:43 +00:00
import { formatRuntimeStatus , renderRuntimeHints , safeDaemonEnv } from "./shared.js" ;
2026-01-14 01:08:15 +00:00
import {
type DaemonStatus ,
renderPortDiagnosticsForCli ,
resolvePortListeningAddresses ,
} from "./status.gather.js" ;
2026-01-14 14:31:43 +00:00
export function printDaemonStatus ( status : DaemonStatus , opts : { json : boolean } ) {
2026-01-14 01:08:15 +00:00
if ( opts . json ) {
defaultRuntime . log ( JSON . stringify ( status , null , 2 ) ) ;
return ;
}
const rich = isRich ( ) ;
const label = ( value : string ) = > colorize ( rich , theme . muted , value ) ;
const accent = ( value : string ) = > colorize ( rich , theme . accent , value ) ;
const infoText = ( value : string ) = > colorize ( rich , theme . info , value ) ;
const okText = ( value : string ) = > colorize ( rich , theme . success , value ) ;
const warnText = ( value : string ) = > colorize ( rich , theme . warn , value ) ;
const errorText = ( value : string ) = > colorize ( rich , theme . error , value ) ;
const spacer = ( ) = > defaultRuntime . log ( "" ) ;
const { service , rpc , legacyServices , extraServices } = status ;
const serviceStatus = service . loaded
? okText ( service . loadedText )
: warnText ( service . notLoadedText ) ;
2026-01-14 14:31:43 +00:00
defaultRuntime . log ( ` ${ label ( "Service:" ) } ${ accent ( service . label ) } ( ${ serviceStatus } ) ` ) ;
2026-01-14 01:08:15 +00:00
try {
const logFile = getResolvedLoggerSettings ( ) . file ;
defaultRuntime . log ( ` ${ label ( "File logs:" ) } ${ infoText ( logFile ) } ` ) ;
} catch {
// ignore missing config/log resolution
}
if ( service . command ? . programArguments ? . length ) {
defaultRuntime . log (
` ${ label ( "Command:" ) } ${ infoText ( service . command . programArguments . join ( " " ) ) } ` ,
) ;
}
if ( service . command ? . sourcePath ) {
2026-01-14 14:31:43 +00:00
defaultRuntime . log ( ` ${ label ( "Service file:" ) } ${ infoText ( service . command . sourcePath ) } ` ) ;
2026-01-14 01:08:15 +00:00
}
if ( service . command ? . workingDirectory ) {
2026-01-14 14:31:43 +00:00
defaultRuntime . log ( ` ${ label ( "Working dir:" ) } ${ infoText ( service . command . workingDirectory ) } ` ) ;
2026-01-14 01:08:15 +00:00
}
const daemonEnvLines = safeDaemonEnv ( service . command ? . environment ) ;
if ( daemonEnvLines . length > 0 ) {
defaultRuntime . log ( ` ${ label ( "Daemon env:" ) } ${ daemonEnvLines . join ( " " ) } ` ) ;
}
spacer ( ) ;
if ( service . configAudit ? . issues . length ) {
2026-01-14 14:31:43 +00:00
defaultRuntime . error ( warnText ( "Service config looks out of date or non-standard." ) ) ;
2026-01-14 01:08:15 +00:00
for ( const issue of service . configAudit . issues ) {
const detail = issue . detail ? ` ( ${ issue . detail } ) ` : "" ;
2026-01-14 14:31:43 +00:00
defaultRuntime . error ( ` ${ warnText ( "Service config issue:" ) } ${ issue . message } ${ detail } ` ) ;
2026-01-14 01:08:15 +00:00
}
defaultRuntime . error (
2026-01-14 14:31:43 +00:00
warnText ( 'Recommendation: run "clawdbot doctor" (or "clawdbot doctor --repair").' ) ,
2026-01-14 01:08:15 +00:00
) ;
}
if ( status . config ) {
const cliCfg = ` ${ status . config . cli . path } ${ status . config . cli . exists ? "" : " (missing)" } ${ status . config . cli . valid ? "" : " (invalid)" } ` ;
defaultRuntime . log ( ` ${ label ( "Config (cli):" ) } ${ infoText ( cliCfg ) } ` ) ;
if ( ! status . config . cli . valid && status . config . cli . issues ? . length ) {
for ( const issue of status . config . cli . issues . slice ( 0 , 5 ) ) {
defaultRuntime . error (
` ${ errorText ( "Config issue:" ) } ${ issue . path || "<root>" } : ${ issue . message } ` ,
) ;
}
}
if ( status . config . daemon ) {
const daemonCfg = ` ${ status . config . daemon . path } ${ status . config . daemon . exists ? "" : " (missing)" } ${ status . config . daemon . valid ? "" : " (invalid)" } ` ;
defaultRuntime . log ( ` ${ label ( "Config (daemon):" ) } ${ infoText ( daemonCfg ) } ` ) ;
if ( ! status . config . daemon . valid && status . config . daemon . issues ? . length ) {
for ( const issue of status . config . daemon . issues . slice ( 0 , 5 ) ) {
defaultRuntime . error (
` ${ errorText ( "Daemon config issue:" ) } ${ issue . path || "<root>" } : ${ issue . message } ` ,
) ;
}
}
}
if ( status . config . mismatch ) {
defaultRuntime . error (
errorText (
"Root cause: CLI and daemon are using different config paths (likely a profile/state-dir mismatch)." ,
) ,
) ;
defaultRuntime . error (
errorText (
"Fix: rerun `clawdbot daemon install --force` from the same --profile / CLAWDBOT_STATE_DIR you expect." ,
) ,
) ;
}
spacer ( ) ;
}
if ( status . gateway ) {
const bindHost = status . gateway . bindHost ? ? "n/a" ;
defaultRuntime . log (
` ${ label ( "Gateway:" ) } bind= ${ infoText ( status . gateway . bindMode ) } ( ${ infoText ( bindHost ) } ), port= ${ infoText ( String ( status . gateway . port ) ) } ( ${ infoText ( status . gateway . portSource ) } ) ` ,
) ;
2026-01-14 14:31:43 +00:00
defaultRuntime . log ( ` ${ label ( "Probe target:" ) } ${ infoText ( status . gateway . probeUrl ) } ` ) ;
2026-01-14 01:08:15 +00:00
const controlUiEnabled = status . config ? . daemon ? . controlUi ? . enabled ? ? true ;
if ( ! controlUiEnabled ) {
defaultRuntime . log ( ` ${ label ( "Dashboard:" ) } ${ warnText ( "disabled" ) } ` ) ;
} else {
const links = resolveControlUiLinks ( {
port : status.gateway.port ,
bind : status.gateway.bindMode ,
customBindHost : status.gateway.customBindHost ,
basePath : status.config?.daemon?.controlUi?.basePath ,
} ) ;
defaultRuntime . log ( ` ${ label ( "Dashboard:" ) } ${ infoText ( links . httpUrl ) } ` ) ;
}
if ( status . gateway . probeNote ) {
2026-01-14 14:31:43 +00:00
defaultRuntime . log ( ` ${ label ( "Probe note:" ) } ${ infoText ( status . gateway . probeNote ) } ` ) ;
2026-01-14 01:08:15 +00:00
}
spacer ( ) ;
}
const runtimeLine = formatRuntimeStatus ( service . runtime ) ;
if ( runtimeLine ) {
const runtimeStatus = service . runtime ? . status ? ? "unknown" ;
const runtimeColor =
runtimeStatus === "running"
? theme . success
: runtimeStatus === "stopped"
? theme . error
: runtimeStatus === "unknown"
? theme . muted
: theme . warn ;
2026-01-14 14:31:43 +00:00
defaultRuntime . log ( ` ${ label ( "Runtime:" ) } ${ colorize ( rich , runtimeColor , runtimeLine ) } ` ) ;
2026-01-14 01:08:15 +00:00
}
2026-01-14 14:31:43 +00:00
if ( rpc && ! rpc . ok && service . loaded && service . runtime ? . status === "running" ) {
2026-01-14 01:08:15 +00:00
defaultRuntime . log (
2026-01-14 14:31:43 +00:00
warnText ( "Warm-up: launch agents can take a few seconds. Try again shortly." ) ,
2026-01-14 01:08:15 +00:00
) ;
}
if ( rpc ) {
if ( rpc . ok ) {
defaultRuntime . log ( ` ${ label ( "RPC probe:" ) } ${ okText ( "ok" ) } ` ) ;
} else {
defaultRuntime . error ( ` ${ label ( "RPC probe:" ) } ${ errorText ( "failed" ) } ` ) ;
if ( rpc . url ) defaultRuntime . error ( ` ${ label ( "RPC target:" ) } ${ rpc . url } ` ) ;
const lines = String ( rpc . error ? ? "unknown" )
. split ( /\r?\n/ )
. filter ( Boolean ) ;
for ( const line of lines . slice ( 0 , 12 ) ) {
defaultRuntime . error ( ` ${ errorText ( line ) } ` ) ;
}
}
spacer ( ) ;
}
if ( service . runtime ? . missingUnit ) {
defaultRuntime . error ( errorText ( "Service unit not found." ) ) ;
for ( const hint of renderRuntimeHints ( service . runtime ) ) {
defaultRuntime . error ( errorText ( hint ) ) ;
}
} else if ( service . loaded && service . runtime ? . status === "stopped" ) {
defaultRuntime . error (
2026-01-14 14:31:43 +00:00
errorText ( "Service is loaded but not running (likely exited immediately)." ) ,
2026-01-14 01:08:15 +00:00
) ;
for ( const hint of renderRuntimeHints (
service . runtime ,
( service . command ? . environment ? ? process . env ) as NodeJS . ProcessEnv ,
) ) {
defaultRuntime . error ( errorText ( hint ) ) ;
}
spacer ( ) ;
}
if ( service . runtime ? . cachedLabel ) {
2026-01-14 14:31:43 +00:00
const env = ( service . command ? . environment ? ? process . env ) as NodeJS . ProcessEnv ;
2026-01-14 01:08:15 +00:00
const labelValue = resolveGatewayLaunchAgentLabel ( env . CLAWDBOT_PROFILE ) ;
defaultRuntime . error (
errorText (
` LaunchAgent label cached but plist missing. Clear with: launchctl bootout gui/ $ UID/ ${ labelValue } ` ,
) ,
) ;
defaultRuntime . error ( errorText ( "Then reinstall: clawdbot daemon install" ) ) ;
spacer ( ) ;
}
for ( const line of renderPortDiagnosticsForCli ( status , rpc ? . ok ) ) {
defaultRuntime . error ( errorText ( line ) ) ;
}
if ( status . port ) {
const addrs = resolvePortListeningAddresses ( status ) ;
if ( addrs . length > 0 ) {
2026-01-14 14:31:43 +00:00
defaultRuntime . log ( ` ${ label ( "Listening:" ) } ${ infoText ( addrs . join ( ", " ) ) } ` ) ;
2026-01-14 01:08:15 +00:00
}
}
if ( status . portCli && status . portCli . port !== status . port ? . port ) {
defaultRuntime . log (
` ${ label ( "Note:" ) } CLI config resolves gateway port= ${ status . portCli . port } ( ${ status . portCli . status } ). ` ,
) ;
}
if (
service . loaded &&
service . runtime ? . status === "running" &&
status . port &&
status . port . status !== "busy"
) {
defaultRuntime . error (
2026-01-14 14:31:43 +00:00
errorText ( ` Gateway port ${ status . port . port } is not listening (service appears running). ` ) ,
2026-01-14 01:08:15 +00:00
) ;
if ( status . lastError ) {
2026-01-14 14:31:43 +00:00
defaultRuntime . error ( ` ${ errorText ( "Last gateway error:" ) } ${ status . lastError } ` ) ;
2026-01-14 01:08:15 +00:00
}
if ( process . platform === "linux" ) {
2026-01-14 14:31:43 +00:00
const env = ( service . command ? . environment ? ? process . env ) as NodeJS . ProcessEnv ;
2026-01-14 01:08:15 +00:00
const unit = resolveGatewaySystemdServiceName ( env . CLAWDBOT_PROFILE ) ;
defaultRuntime . error (
2026-01-14 14:31:43 +00:00
errorText ( ` Logs: journalctl --user -u ${ unit } .service -n 200 --no-pager ` ) ,
2026-01-14 01:08:15 +00:00
) ;
} else if ( process . platform === "darwin" ) {
const logs = resolveGatewayLogPaths (
( service . command ? . environment ? ? process . env ) as NodeJS . ProcessEnv ,
) ;
defaultRuntime . error ( ` ${ errorText ( "Logs:" ) } ${ logs . stdoutPath } ` ) ;
defaultRuntime . error ( ` ${ errorText ( "Errors:" ) } ${ logs . stderrPath } ` ) ;
}
spacer ( ) ;
}
if ( legacyServices . length > 0 ) {
2026-01-15 06:18:34 +00:00
defaultRuntime . error ( errorText ( "Legacy gateway services detected:" ) ) ;
2026-01-14 01:08:15 +00:00
for ( const svc of legacyServices ) {
defaultRuntime . error ( ` - ${ errorText ( svc . label ) } ( ${ svc . detail } ) ` ) ;
}
defaultRuntime . error ( errorText ( "Cleanup: clawdbot doctor" ) ) ;
spacer ( ) ;
}
if ( extraServices . length > 0 ) {
2026-01-14 14:31:43 +00:00
defaultRuntime . error ( errorText ( "Other gateway-like services detected (best effort):" ) ) ;
2026-01-14 01:08:15 +00:00
for ( const svc of extraServices ) {
2026-01-14 14:31:43 +00:00
defaultRuntime . error ( ` - ${ errorText ( svc . label ) } ( ${ svc . scope } , ${ svc . detail } ) ` ) ;
2026-01-14 01:08:15 +00:00
}
for ( const hint of renderGatewayServiceCleanupHints ( ) ) {
defaultRuntime . error ( ` ${ errorText ( "Cleanup hint:" ) } ${ hint } ` ) ;
}
spacer ( ) ;
}
if ( legacyServices . length > 0 || extraServices . length > 0 ) {
defaultRuntime . error (
errorText (
2026-01-15 11:34:27 +01:00
"Recommendation: run a single gateway per machine for most setups. One gateway supports multiple agents (see docs: /gateway#multiple-gateways-same-host)." ,
2026-01-14 01:08:15 +00:00
) ,
) ;
defaultRuntime . error (
errorText (
2026-01-15 18:08:20 +01:00
"If you need multiple gateways (e.g., a rescue bot on the same host), isolate ports + config/state (see docs: /gateway#multiple-gateways-same-host)." ,
2026-01-14 01:08:15 +00:00
) ,
) ;
spacer ( ) ;
}
defaultRuntime . log ( ` ${ label ( "Troubles:" ) } run clawdbot status ` ) ;
2026-01-14 14:31:43 +00:00
defaultRuntime . log ( ` ${ label ( "Troubleshooting:" ) } https://docs.clawd.bot/troubleshooting ` ) ;
2026-01-14 01:08:15 +00:00
}