This directory is the agent-tty command-line front end. main.ts is the bin entrypoint and the public CLI contract: it builds the Commander program, registers every command, resolves a CommandContext before each action, and maps command failures to output envelopes and exit codes. The command handlers live in ./commands/. The other files hold shared pieces: context resolution, output envelopes, session target lookup, session status guards, exit codes and the CliError class.
./commands/README.md: one handler module per CLI command.main.tshas no exports. It builds the program namedagent-ttyand runsmain()at module top level. [main.ts:main]- Global options:
--home <path>,--timeout-ms <n>,--no-color,--log-level <level>,--profile <name>,--renderer <name>. Each command exceptdashboard(aliasd) and the hidden_hostregisters its own--jsonoption. [main.ts:main] - Commands:
version,doctor,create [command...],list,inspect,destroy,gc,type,paste,run,batch,mark,send-keys,mouse,resize,signal,snapshot,screenshot,dashboard,wait. Groups:skills list|get|path,home list|forget,record diff|export. A hidden_host <session-id> <creator-pid>command importssrc/host/hostMain.tsand runsrunHost(sessionId, Number(creatorPid)). [main.ts:main] resolveCommandContext(options, env?, deps?),getCommandContext,setCommandContext,parseTimeoutMsOption,resolveRendererDefault,resolveLogLevel,clearRendererDefaultProbeCacheForTests, and the typesGlobalCliOptionsandCommandContext. [context.ts:resolveCommandContext] [context.ts:CommandContext]emitSuccess,emitSuccessSync,emitFailure,emitCliError,argvHasFlag,exitAfterOutputFlush,formatSuccessOutput,writeJsonEnvelope,writeHumanLines,writeHumanErrorLines,writeStdoutSync,setColorEnabled. [output.ts:emitSuccess] [output.ts:emitFailure] [output.ts:writeStdoutSync] [output.ts:argvHasFlag] [output.ts:exitAfterOutputFlush]resolveCommandTarget(options)returns aCommandTarget(sessionId,sessionDirectory,manifestPath,socketPath,manifest). Also exportsResolveCommandTargetOptionsandCommandTargetManifest. [commandTarget.ts:resolveCommandTarget] [commandTarget.ts:CommandTarget]assertSessionCommandable(manifest, sessionId)andcheckedSessionDir(home, sessionId). [sessionGuards.ts:assertSessionCommandable] [sessionGuards.ts:checkedSessionDir]describeEventLogStop(stop)words where the stored log stopped, asat seq <n> (byte limit),at seq <n> (event limit)orat seq <n> (reason unknown).eventLogStopTimeoutNote(stop)is the sentence that aWAIT_TIMEOUTmessage adds. Theunknownreason reads(reason unknown).inspect,waitandbatchuse them. None of them reads the event log for a wait: the stop comes with the host's wait result, or from the events of an offline replay. [eventLogStop.ts:describeEventLogStop] [eventLogStop.ts:eventLogStopTimeoutNote]exitCodeForError(code). [exitCodes.ts:exitCodeForError]warnIfUnsupportedNode(logger, version). [nodeRuntime.ts:warnIfUnsupportedNode]CliError(code,retryable,details,result).emitCliErrorcopiesresultinto the error envelope when it is set. [errors.ts:CliError] [output.ts:emitCliError]
EXIT_CODE_BY_ERROR_CODEmaps error codes to exit codes: input errors 2,SESSION_NOT_FOUND3,SESSION_NOT_RUNNINGandSESSION_ALREADY_DESTROYED4,HOST_TIMEOUT5,HOST_UNREACHABLE6,EXPORT_ERROR7, storage and manifest errors 8,PROTOCOL_ERRORandRPC_ERROR9,REPLAY_ERROR10,WAIT_TIMEOUT11,CAPABILITY_UNAVAILABLE12,INTERNAL_ERROR1. Unknown codes give 1. A code comment marks this as a public contract documented indocs/USAGE.md. [exitCodes.ts:exitCodeForError]wrapActionturns every error into an error envelope and exit code. An error that is not aCliErrorbecomesINTERNAL_ERRORwith the error's message, and its stack goes to the debug log. It picks JSON or text output from the command's parsed--jsonoption, not from argv. [main.ts:wrapAction] [main.ts:toCliError]- After an
INTERNAL_ERROR,exitOnInternalErrorexits the process once stdout and stderr have flushed, so a socket, browser or timer that the failed code left open cannot keep the CLI running. Other error codes let the event loop drain. [main.ts:exitOnInternalError] [output.ts:exitAfterOutputFlush] - When parsing fails with a
CommanderError,mainsets exit code 0 when Commander's own exit code is 0 (help,help <command>,--help) and 2 otherwise (usage errors, and no command at all). ACommanderErrorthat is not help or version output is also emitted asINVALID_INPUTunder the command nameagent-tty, with Commander's message minus itserror:prefix, when--jsonis in argv before the first--or Commander already parsed--jsonon a command. The second case covers a--that an option took as its value. [main.ts:main] [main.ts:parsedJsonOption] - The
preActionhook turns a context resolution error that is not aCliErrorintoINTERNAL_ERROR. Its stack is not logged, because the log level is not resolved yet. [main.ts:main] [main.ts:toCliError] - At top level, a
CliErrorthrown outsidewrapAction(for example from context resolution in thepreActionhook) is emitted under the command nameagent-tty. Its--jsonand--no-colorcome from the options Commander parsed, which thepreActionhook records first. When parsing did not finish, they come from argv before the first--. [main.ts:main] [output.ts:argvHasFlag] emitCliErrorwrites the error to stderr ascode: messagewhen a JSON envelope is already on stdout, so stdout never holds two envelopes. [output.ts:emitCliError]- Other errors at top level, such as a failure of the hidden
_hostaction, are rethrown. [main.ts:main] - The envelope shapes come from
src/protocol/envelope.ts(createSuccessEnvelope,createErrorEnvelope).output.tsdefines no schema. [output.ts:emitSuccess] - Home precedence is
--home, thenAGENT_TTY_HOME, thenresolveHome. An explicit Home must be non-empty and absolute, elseINVALID_INPUT. The returned context is frozen withObject.freeze. [context.ts:resolveCommandContext] --timeout-msmust match/^\d+$/, elseINVALID_DURATION. [context.ts:parseTimeoutMsOption]resolveCommandTargetthrowsINVALID_SESSION_IDfor an invalid ID andSESSION_NOT_FOUNDfor a missing manifest. It then requires a commandable status throughassertSessionCommandableand assertsstatus === 'running'. [commandTarget.ts:resolveCommandTarget]assertSessionCommandablethrowsSESSION_ALREADY_DESTROYEDfor destroyed sessions andSESSION_NOT_RUNNINGfor other non-commandable statuses. [sessionGuards.ts:assertSessionCommandable]checkedSessionDirthrowsINVALID_SESSION_IDwhensessionDirrejects the ID (empty,.,.., or a path separator). Every command that takes a session ID resolves it through this guard. [sessionGuards.ts:checkedSessionDir]- With color disabled, human output has ANSI escapes stripped. [output.ts:writeHumanLines]
emitSuccesswrites to stdout.emitFailurewrites the error envelope to stdout with--json, orcode: messageto stderr without it. [output.ts:emitSuccess] [output.ts:emitFailure]writeStdoutSyncwrites to fd 1 withfs.writeSync. [output.ts:writeStdoutSync]emitCliErrorsetsprocess.exitCode.exitAfterOutputFlushcallsprocess.exit()from the stderr write callback.argvHasFlagreadsprocess.argv. [output.ts:emitCliError] [output.ts:exitAfterOutputFlush] [output.ts:argvHasFlag]- The
preActionhook writesAGENT_TTY_HOME,AGENT_TTY_LOG_LEVELand the host renderer key intoprocess.env. It sets or deletesAGENT_TTY_RENDERER. It callswarnIfUnsupportedNode, which readspackage.jsonand logs a warning when the Node version is outside itsengines.noderange. Whenpackage.jsoncannot be read or its range uses a comparator thatsatisfiesNodeRangedoes not support, it logs that at debug and skips the check. [main.ts:main] resolveCommandContextreadsAGENT_TTY_HOME,AGENT_TTY_LOG_LEVEL,AGENT_TTY_PROFILEandAGENT_TTY_RENDERER. It reads the config file throughloadConfigFile(home)(src/config/resolveConfig.ts). When no renderer is configured, it dynamically imports libghostty-vt throughprobeLibghosttyVtand caches the result for the whole process. [context.ts:resolveCommandContext] [src/renderer/readiness.ts:probeLibghosttyVt]resolveCommandTargetreads the session manifest throughreadManifestIfExists. [commandTarget.ts:resolveCommandTarget]
- Run:
npx vitest run test/unit/cli test/unit/commands - Integration (spawns
src/cli/main.tsthroughtest/helpers.ts):npx vitest run --maxWorkers=1 test/integration/cli.test.ts - Run:
npm run typecheck