Guides
Reusing the CLI's registry patterns

Reusing the CLI's registry patterns

The CLI ships two small modules that map a friendly filesystem name to a backend file path (and to one workspace inside it). If you want the same ergonomics in your own binary, the simplest path is to copy the shapes — they're deliberately tiny and dependency-light. Extracting a shared fslite-registry crate is a separate decision; this page is about getting the patterns.

Why these live in fslite-cli

fslite-core and fslite-sqlite are deliberately name-free. They operate on concrete file paths and WorkspaceIds. The mapping from "a string the user typed" to "a concrete database file" is a fslite-cli-side concern. The actual code is about 200 lines total across three files; that's small enough to copy rather than crateify.

The data shapes

The two data structures live at registry.rs (opens in a new tab) and context.rs (opens in a new tab).

use std::collections::BTreeMap;
use std::path::PathBuf;
use fslite_core::WorkspaceId;
 
/// On-disk mapping: name → SQLite database file,
///                  name → workspace in that database.
#[derive(Default, Serialize, Deserialize)]
pub struct Registry {
    pub filesystems: BTreeMap<String, PathBuf>,
    pub workspaces:  BTreeMap<String, BTreeMap<String, WorkspaceId>>,
}
 
/// Persisted default for the next bare-verb invocation.
#[derive(Default, Serialize, Deserialize)]
pub struct Context {
    pub filesystem: Option<String>,
    pub workspace:  Option<String>,
}

BTreeMap instead of HashMap so the JSON output is deterministic.

The config-directory resolver

CLI persisted state lives under a per-user config directory. The resolver at paths.rs (opens in a new tab) honours, in priority order:

  1. FSLITE_CONFIG_DIR (env var, manually settable).
  2. XDG_CONFIG_HOME (set by XDG-compliant desktops).
  3. $HOME/.config/fslite (the Linux default).

To get the same behaviour in your own binary, copy the function verbatim. It's ~25 lines including the env-var mutex that serialises test mutations.

How to copy these patterns

The easiest, most explicit, lowest-friction route:

# In your own repo:
mkdir -p src/cli_state
curl -fsSL \
    https://raw.githubusercontent.com/seanrobmerriam/fslite/main/crates/fslite-cli/src/registry.rs \
    -o src/cli_state/registry.rs
curl -fsSL \
    https://raw.githubusercontent.com/seanrobmerriam/fslite/main/crates/fslite-cli/src/context.rs \
    -o src/cli_state/context.rs
curl -fsSL \
    https://raw.githubusercontent.com/seanrobmerriam/fslite/main/crates/fslite-cli/src/paths.rs \
    -o src/cli_state/paths.rs

Adjust the mod declarations and crate-internal field types (WorkspaceId is just re-exported from fslite-core). The serialisation JSON shape and the resolve-priority order are part of the contract; the rest is internal.

You'll also need to drop in serde and serde_json to your Cargo.toml; the original paths.rs adds tempfile only for tests.

When to ask for extraction

The shapes pull into a shared crate when at least one of these is true:

  • You have two or more binaries in the same repo that need to share the registry (e.g., a server and a CLI that should agree on friendly names).
  • The registry shapes need their own versioned migration support (the in-tree version is "rewrite on save").
  • You want third-party tools to read the same registry (fslite-doctor, a TUI, etc.).

If none of these apply, copying is the right call — adding a published crate before its API is stable costs every consumer a Cargo.toml edit.

When NOT to copy

If your app's notion of "registered filesystem" is materially different from the CLI's — for example, if your friendly names must round-trip through DNS, or if your workspaces aren't backed by SQLite — then the shapes here won't be a clean fit. Use them as a reference, but build your own.