Make external language-specific CLIs feel native in Flow.
The user-facing shape is:
f cli <id> [-- <args>...]f install <path-to-cli>f install <remote-cli-uuid>
This should sit on top of the existing local manifest bridge, not replace it.
Current grounding:
src/external_cli.rsalready discovers local manifests from fixed roots.src/install.rsandsrc/main.rsalready give Flow one install entrypoint.src/config.rsalready defines the global Flow config/state root under~/.config/flow.
f installis the intake path.f cliis the resolver and runner.- Human-facing invocation should use a stable tool id like
codex-session-browser. - Remote installation should use an immutable install identifier, not a mutable human alias.
- Local path installs should link to source, not copy it.
- Existing package install behavior for registry, parm, and flox must keep working.
f cli codex-session-browser -- browse --repo ~/repos/openai/codexRules:
f cli <id>resolves a registered or discovered external CLI byid.- Everything after
--is passed directly to the tool. - Flow does not reinterpret tool-specific args.
Add small management helpers:
f cli listf cli which <id>f cli doctor <id>
f install ~/code/lang/go/cli/codex-session-browserRules:
- If the argument is an existing directory or manifest path, Flow treats it as an external CLI source install.
- Flow validates
flow-tool.toml. - Flow creates a registration record that points at the source directory.
- Flow does not copy source into Flow-managed storage for this mode.
This is effectively link mode, but the user does not need a separate command.
f install cli_01jv...Rules:
- If the argument matches Flow's remote CLI install id format, Flow resolves it through a remote CLI catalog.
- Flow downloads a source snapshot or release bundle into Flow-managed storage.
- Flow registers the installed tool under its manifest
id.
I would not make bare UUIDs the only format. A prefix such as cli_<id> or cli:01jv... is safer because f install already accepts normal package names.
Use two identifiers with different jobs.
Human-facing, stable callable name from flow-tool.toml.
Example:
id = "codex-session-browser"This is what f cli uses.
Immutable remote catalog id.
Example:
cli_01jv8n...
This is what f install uses for remote installs.
Why both:
- the callable id should stay readable
- the remote catalog needs a collision-proof immutable identity
- the tool can be renamed in UX without breaking historic install references
f cli <id> should resolve in this order:
- registered local links
- registered remote installs
- dev-mode discovery roots from
src/external_cli.rs
That gives two important behaviors:
- explicitly installed tools win
- tools under
~/code/lang/go/cli/*and~/code/lang/rust/cli/*still work during development without an install step
Use Flow's global state/config root.
Suggested layout:
~/.config/flow/cli/links/<id>.toml~/.config/flow/cli/installs/<install_id>/...~/.config/flow/cli/index.json
A link record should contain:
idsource_rootmanifest_pathinstalled_at- optional
install_id - optional
description
This is the record created by f install <path>.
A remote install directory should contain:
- downloaded source or unpacked bundle
- a copy of the resolved manifest
- install metadata
- version / published-at metadata
This is the record created by f install <remote-cli-uuid>.
Keep flow-tool.toml as the tool-owned contract.
Current useful fields already exist:
version = 1
id = "codex-session-browser"
language = "go"
binary_name = "codex-session-browser"
description = "Browse Codex sessions for a repo and print the selected session ID."
[exec]
run = ["go", "run", "."]For this install/run design, the manifest should remain source-oriented.
The install registry should not redefine execution. It should point back to the manifest.
Not required for v1, but reasonable later:
aliases = ["csb"]min_flow_version = "0.1.0"[exec].build[install]metadata for remote packaging hints
f install <arg> should behave like this:
- if
<arg>is an existing path:- install as external CLI link
- else if
<arg>matches remote CLI install id format:- install from remote CLI catalog
- else:
- keep existing install behavior (
registry/parm/floxauto resolution)
- keep existing install behavior (
This preserves the current tool/package install surface while adding the new CLI path cleanly.
Evolve it from pure root scanning into a resolver module that can read:
- registrations under
~/.config/flow/cli/... - current dev roots
Suggested API shape:
resolve_external_cli_tool(id: &str) -> Result<ResolvedExternalCliTool>list_external_cli_tools() -> Result<Vec<ResolvedExternalCliTool>>install_external_cli_link(path: &Path) -> Result<InstalledCliRecord>install_external_cli_remote(install_id: &str) -> Result<InstalledCliRecord>
Add a new command family:
f cli <id> [-- <args>...]f cli listf cli which <id>f cli doctor <id>
f ai codex browse should then stop owning raw manifest resolution and instead call the same resolver/runner underneath.
Do not create a second install command.
Keep f install as the front door, but teach install::run to detect:
- local path installs
- remote CLI install ids
That can be implemented either as:
- a new
InstallBackend::Cli, plus auto-detection - or a pre-backend dispatch before current backend selection
I prefer pre-backend dispatch because local paths and remote CLI ids are not really "backends" in the same sense as flox or parm.
Do not overload the existing binary registry manifest in src/registry.rs.
That registry is target/binary oriented. External CLIs are source/manifest oriented.
Use a separate remote catalog document for CLIs.
Suggested remote record:
{
"install_id": "cli_01jv8n...",
"id": "codex-session-browser",
"version": "0.1.0",
"published_at": "2026-04-02T12:00:00Z",
"source": {
"kind": "tarball",
"url": "https://.../codex-session-browser.tgz",
"sha256": "..."
},
"manifest": {
"version": 1,
"id": "codex-session-browser",
"language": "go",
"binary_name": "codex-session-browser",
"exec": {
"run": ["go", "run", "."]
}
}
}The important boundary is:
- binary registry: compiled artifacts by target triple
- CLI catalog: manifest-driven source tools that Flow knows how to run
If f install <path> tries to register an id that already exists:
- same
source_root: report already installed - different
source_root: fail unless--forceor an explicit replace flag is passed
If a remote install provides an id that already exists:
- prefer the installed record marked active
- keep the conflict explicit in
f cli list - do not silently shadow an existing tool
Current state is enough for development, but not for ownership.
The missing pieces today are:
- no first-class installed-tool registry
- no single
f clirunner surface - no path-link install contract
- no remote install contract
This design fills those gaps without throwing away the working manifest bridge.
- keep current root scanning
- add
f cli <id>on top ofexternal_cli.rs - add
f install <path>as a link registration - add
f cli listandf cli which
- migrate
f ai codex browseto use the sharedf cliresolver path - keep direct resolver calls only as library reuse under the same module
- add remote CLI catalog
- support
f install <remote-cli-uuid>
- optional
f cli upgrade,f cli uninstall, and alias support
Yes, this should go through Flow.
But the right shape is:
f installregisters or fetches toolsf cliruns themflow-tool.tomlstays the source-of-truth execution contract- remote CLI install ids are immutable and distinct from callable tool ids
That gives Flow a clean tool story without collapsing local source tools, binary package installs, and remote catalog installs into one ambiguous mechanism.