mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
12 KiB
12 KiB
CLI CommandContext And Server Access Refactor Plan
Summary
Refactor fabro-cli around an invocation-scoped CommandContext that centralizes local settings loading and server connection setup for user-facing server-backed commands. This is a greenfield codebase with no backward-compatibility constraints, so the refactor should be done in one full pass for the in-scope command surface rather than preserving a long-lived mixed model. Keep config-layer composition command-local where commands genuinely need it, keep the generated fabro_api::Client as the main HTTP interface, and reuse the existing ServerStoreClient type instead of layering a second handwritten endpoint facade on top of it.
Public Types And Interfaces
- Add eager, invocation-scoped
CommandContext:- Holds
cwd,base_config_path,machine_settings,server_mode, and a cached server client cell. cwdis the invocation working directory and replaces repeated inlinestd::env::current_dir()lookups in migrated commands such aspreflight,validate,graph,fabro settings, and workflow-oriented run creation paths.base_config_pathis the local settings file path chosen by--config,FABRO_CONFIG, or the default path.machine_settingsis the result of the existing local settings loaders for the command:- base settings for commands using
load_settings() - base settings plus storage-dir override for commands using
load_settings_with_storage_dir(...)
- base settings for commands using
machine_settingsdoes not include workflow or project config layers.- Do not store
ConfigLayeronCommandContext.
- Holds
- Keep config-layer composition command-local:
fabro settingscontinues to buildEffectiveSettingsLayerswith its existing helpers.- workflow and manifest code continues to use
ConfigLayer::for_workflow(...),discover_project_config(...), and existing workflow resolution structs where individual layers matter. CommandContextshould not attempt to reconstruct or cache workflow/project config layers.
- Use two explicit server access modes that match the real connection paths in the codebase:
ServerMode::NoneServerMode::ByTarget { target_override: Option<String> }ServerMode::ByStorageDir { target_override: Option<String>, storage_dir_override: Option<PathBuf> }ByTargetmaps to the currentconnect_server_only(...)behavior.ByTargetalso covers the currentServerSummaryLookup::connect(...)resolution path used by run, pr, runs, and artifact lookup commands.ByStorageDirmaps to the currentconnect_server_backed_api_client_with_storage_dir(...)behavior when a real storage-dir-aware connection mode is needed.- current callers of
connect_server_backed_api_client(...)migrate toByTargetin this refactor because their existingNonestorage-dir path collapses to the same local settings load asconnect_server_only(...). - Do not collapse these two behaviors into one variant with optional target and storage fields.
- Reuse existing target concepts instead of introducing a second target enum:
- keep
ServerTargetArgs,ServerConnectionArgs, and the resolvedServerTargetmodel already used byuser_configandserver_client - do not introduce
ServerTargetInputin this pass
- keep
- Keep
ServerStoreClient:- do not rename it during the same refactor
- add narrow accessors as needed for:
- the generated
fabro_api::Client - raw
reqwest::Client base_url
- the generated
- this preserves the current
execadapter path without adding a second server session type
CommandContext::server().await?:- available only for
ServerMode::ByTargetandServerMode::ByStorageDir - returns
Arc<ServerStoreClient> - caches the first successful connection in a
OnceCell - does not cache failures; a later retry should attempt a fresh connection
ByTargetperforms current target-based resolution and Unix-socket auto-start behaviorByStorageDirperforms current storage-dir-backed daemon resolution and startup behavior- to make both modes uniform, refactor the current storage-dir-backed path, which now returns a bare
fabro_api::Client, to construct aServerStoreClientfirst and expose the generated client through an accessor - migrated connection logic must use
machine_settingsandbase_config_pathalready loaded onCommandContext; it should stop re-callingload_settings()andload_settings_with_storage_dir(...)insideserver_client.rs - HTTP and HTTPS targets never auto-start
- available only for
- Keep the existing run-summary lookup pattern, but separate lookup construction from connection:
- add
ServerSummaryLookup::from_client(client: Arc<ServerStoreClient>) -> Result<Self> - make the existing
ServerSummaryLookup::connect(...)a compatibility wrapper during migration, then remove direct call sites from migrated commands - migrated commands that currently do
ServerSummaryLookup::connect(...)should instead doServerSummaryLookup::from_client(ctx.server().await?) - this keeps summary listing, sorting, and selector resolution behavior intact while moving connection ownership into
CommandContext
- add
Implementation Changes
- Keep
mainbootstrap ordering intact:- continue loading enough local settings before tracing init to determine log level and upgrade-check behavior
CommandContextbegins after tracing is initialized; it does not replace that earlier bootstrap phase
- Do not introduce
Servicesin this pass:- the current review surfaced that a
Serviceswrapper does not pull enough independent process-scoped concerns to justify the extra indirection - if a later refactor reveals multiple real process-scoped services, add that separately
- the current review surfaced that a
- Keep style construction local for now:
- several current command paths still rely on
&'static Styles - this refactor should not add a style ownership change on top of settings/server wiring cleanup
- several current command paths still rely on
- Make
map_api_errordeduplication a firm deliverable:- migrated commands should reuse the shared
server_client::map_api_error - remove remaining verbatim local copies in the in-scope command surface
- migrated commands should reuse the shared
- Implement in this order:
- Step 1: add
CommandContext,ServerMode,ctx.server()caching semantics, convert the storage-dir-backed connect path to constructServerStoreClient, addServerSummaryLookup::from_client(...), and thread preloadedmachine_settings/base_config_pathinto server resolution so migrated commands stop re-loading settings inside connection helpers - Step 2: migrate the workflow-oriented server commands:
- the main
runcommand incommands/run/command.rs run createrun startrun attachrun diffrun logsrun previewrun sshrun resumerun rewindrun forkrun waitrun cp- include the existing internal create → start → attach chain where multiple settings loads and server connects exist today
preflightvalidategraph
- the main
- Step 3: migrate the remaining user-facing commands that use target-based resolution or resolved-target lookup:
modelsecretprovider loginrepo initdoctorpr(treat separately inside this step because it mixes settings for app ID andServerSummaryLookup::connect)runsartifact- these commands should use
ServerMode::ByTargetafter migration, even when they currently callconnect_server_backed_api_client(...), because their existingNonestorage-dir path collapses to the same local settings load asconnect_server_only(...)
- Step 4: migrate the user-facing commands that genuinely resolve through a storage-dir-aware server mode:
system infosystem dfsystem eventssystem prune- these commands should use
ServerMode::ByStorageDir
- Step 5: adapt
fabro settingsto useCommandContextonly for base local settings inputs while keeping its existing layer-building and effective-settings logic - Step 6: remove obsolete helper entrypoints from migrated call sites and reduce
server_client.rsto the minimal shared surface still needed by explicit out-of-scope and internal commands:- migrated commands should stop calling
connect_server_only(...),connect_server_backed_api_client(...),connect_server_backed_api_client_with_storage_dir(...), andServerSummaryLookup::connect(...)directly - migrated commands that need run lookup/resolve behavior should use
ServerSummaryLookup::from_client(ctx.server().await?) - keep
connect_server(...),connect_api_client(...), andconnect_server_target_direct(...)only for direct storage-dir or direct-target flows that remain explicit out-of-scope or internal
- migrated commands should stop calling
- Step 1: add
- Stage dependencies:
- Steps 2, 3, and 4 all depend on Step 1
- Step 5 depends on Step 1 but not on the migration of other command groups
- Step 6 happens only after the other steps are complete
- Explicitly out of scope:
execserverlifecycle commands- hidden
run worker store dumpworkflowparserepo deinitinstallsandboxupgrade- hidden analytics and panic upload commands
- direct storage-dir run lookup flows, including
ServerRunLookup, remain unchanged in this pass
- Cleanup target after the pass:
- the remaining old helper surface should exist only for those explicitly out-of-scope or internal commands
- migrated user-facing commands should no longer call settings loaders or top-level server connect helpers directly
Test Plan
- New unit tests for
CommandContextconstruction:- base config path precedence remains
--config>FABRO_CONFIG> default path ($FABRO_HOME/settings.tomlifFABRO_HOMEis set, else$HOME/.fabro/settings.toml) - missing default base config path is allowed; missing explicit config path still errors
machine_settingsreflect only the command's existing local settings load path:- base settings for target-based commands
- base settings plus storage-dir override for storage-backed commands
- base config path precedence remains
- New unit tests for server access modes:
ServerMode::ByTargetmatches current target-based resolutionServerMode::ByStorageDirmatches current storage-dir-backed resolution- Unix-socket targets may auto-start; HTTP and HTTPS targets never auto-start
ctx.server()caches a successful client and retries after failures
- Existing regression coverage that must keep passing for config-layer commands:
- workflow and manifest code still layer workflow and project config exactly as before
fabro settingslocal, daemon, and remote effective-settings modes remain unchanged
- Existing regression coverage that must keep passing for special cases:
execwith no explicit server target still runs directly against providersexecwith an explicit server target still constructs the server-backed adapter path correctly usingServerStoreClientaccessors
- Existing integration coverage that must keep passing for migrated command groups:
- Step 2: the main
runcommand,run create,run start,run attach,run diff,run logs,run preview,run ssh,run resume,run rewind,run fork,run wait,run cp,preflight,validate, andgraph - Step 3: representative
model,secret,provider login,repo init,doctor,pr,artifact, andrunscommands - Step 4: representative
system info,system df,system events, andsystem prunecommands - Step 5:
fabro settingsbase-settings-input path adopted fromCommandContextwhile layer-building and effective-settings logic stay unchanged
- Step 2: the main
Assumptions And Defaults
- This is a greenfield codebase with no production compatibility constraints, so one full-pass refactor across the in-scope user-facing command surface is acceptable.
CommandContextis for shared local settings loading and server access only; it is not a universal repository for every config layer or every command concern.- Commands that need workflow/project layering continue to compute those layers locally from the existing config helpers.
ServerStoreClientremains the shared server connection type in this pass and may gain accessors, but not a second handwritten endpoint layer.execremains outside the main abstraction because it still has a real direct-provider fallback mode that is different from normal server-backed commands.- "Resolved path" means the same path shape produced by current helpers: expanded and made absolute where the helpers already do so, but not canonicalized in a way that would require the file to exist.