Skip to content

Add configuration file support to Fedify CLI #265

@dahlia

Description

@dahlia

Description

Add support for configuration files to the Fedify CLI tools to allow users to set default options and preferences. This will improve the developer experience by eliminating the need to repeatedly specify common options and allowing for project-specific configurations.

Implementation details

Configuration file format and location

Use TOML format for configuration files. TOML was chosen over JSON because it supports comments, has clearer type representation, and is more human-friendly for configuration files. The parser overhead is minimal with the smol-toml library.

Configuration files are searched and merged in the following order (later files override earlier ones):

  1. /etc/fedify/config.toml — System-wide defaults
  2. $XDG_CONFIG_HOME/fedify/config.toml (or ~/.config/fedify/config.toml) — User-specific settings
  3. ./.fedify.toml — Project-specific settings (current working directory)
  4. --config PATH — Additional config file specified via CLI (merged on top)

The single filename .fedify.toml in CWD was chosen for clarity and consistency with modern CLI tools. The leading dot keeps it hidden from ls output.

Configuration merging strategy

Configuration files are deep-merged at the field level:

  • Objects: Recursively merged
  • Arrays: Completely replaced (no merge)
  • Primitives: Replaced

Example:

# /etc/fedify/config.toml
user_agent = "Fedify/1.0"
[lookup]
timeout = 30.0
authorized_fetch = false
# ./.fedify.toml
user_agent = "MyBot/1.0"  # overrides system config
[lookup]
timeout = 60.0  # overrides system config
# authorized_fetch stays false from system config

Arrays are completely replaced rather than merged because merging behavior would be ambiguous (append? deduplicate? replace?) and complete replacement is more predictable for configuration.

CLI options

Add two global options to all commands:

  • --config PATH — Load an additional configuration file and merge it on top of the standard config chain
  • --ignore-config — Ignore all configuration files and use only CLI options and defaults

These two options are mutually exclusive and should produce an error if both are specified.

The --ignore-config flag is useful for debugging configuration issues and for ensuring reproducible behavior in CI environments.

Integration with Optique

Use Optique 0.10.0's @optique/config package with Valibot for schema validation. (Note: As of January 26, 2026, Optique 0.10.0 has not yet been released, but the config integration feature is documented and will be available in that version.) Since Optique's config integration only supports single-file loading, implement custom multi-file merging logic before passing the merged configuration to Optique's bindConfig().

Use Valibot instead of Zod for schema validation because it has better tree-shaking characteristics and smaller bundle size, which matters for CLI tools.

Error handling

  • If a configuration file exists but fails to parse, print an error message and skip that file (continue loading other configs)
  • If /etc/fedify/config.toml is not readable (permission denied), silently skip it
  • If a file specified via --config doesn't exist or fails to parse, exit with an error
  • If both --config and --ignore-config are specified, exit with an error explaining they are mutually exclusive

Configuration file structure

# Fedify CLI configuration file
# Place at: ./.fedify.toml (project), ~/.config/fedify/config.toml (user),
#           or /etc/fedify/config.toml (system)

# Global settings
debug = false
user_agent = "MyBot/1.0 (+https://example.com/bot)"
log_file = "/var/log/fedify.log"
tunnel_service = "localhost.run"  # "localhost.run" | "serveo.net" | "pinggy.io"

# WebFinger command
[webfinger]
max_redirection = 5
allow_private_addresses = false

# Lookup command
[lookup]
timeout = 30.0  # seconds
default_format = "default"  # "default" | "raw" | "compact" | "expand"
separator = "----"
authorized_fetch = false
first_knock = "draft-cavage-http-signatures-12"
traverse = false
suppress_errors = false

# Inbox command
[inbox]
actor_name = "Fedify Ephemeral Inbox"
actor_summary = "An ephemeral ActivityPub inbox for testing purposes."
authorized_fetch = false
no_tunnel = false
follow = ["@user@example.com"]
accept_follow = ["*"]

# Relay command
[relay]
protocol = "mastodon"  # "mastodon" | "litepub"
port = 8000
name = "Fedify Relay"
persistent = "/path/to/relay.db"  # optional
no_tunnel = false
accept_follow = []
reject_follow = []

# NodeInfo command
[nodeinfo]
raw = false
best_effort = false
show_favicon = true
show_metadata = false

Why this is a good first issue

  • Clear requirements: Well-defined feature with obvious use cases
  • Modern tooling: Leverages Optique's built-in config support (available in 0.10.0)
  • Incremental implementation: Can start with basic options and expand gradually
  • High impact: Significantly improves developer experience for regular CLI users
  • Safe changes: Additive feature that doesn't break existing functionality
  • Good documentation: Optique config integration is documented at https://unstable.optique.dev/integrations/config

Acceptance criteria

  • Support TOML configuration files using smol-toml parser
  • Implement hierarchical config loading: system → user → project → custom
  • Implement deep merge for objects, complete replacement for arrays and primitives
  • Add --config PATH option to specify additional config file
  • Add --ignore-config flag to skip all config files
  • Validate that --config and --ignore-config are mutually exclusive
  • Use Valibot for type-safe schema validation
  • Integrate with Optique 0.10.0's bindConfig() for seamless CLI option merging
  • Handle config file errors gracefully with helpful error messages
  • Silently skip /etc/fedify/config.toml if not readable
  • Update documentation with config file format and available options

Files to create/modify

  • packages/cli/src/config.ts — New file: config schema (Valibot), merging logic, file loading
  • packages/cli/src/globals.ts — Add configOptions with --config and --ignore-config
  • packages/cli/src/mod.ts — Call loadMergedConfig() and pass to commands
  • packages/cli/src/webfinger/command.ts — Wrap options with bindConfig()
  • packages/cli/src/lookup.ts — Wrap options with bindConfig()
  • packages/cli/src/inbox.tsx — Wrap options with bindConfig()
  • packages/cli/src/relay.ts — Wrap options with bindConfig()
  • packages/cli/src/nodeinfo.ts — Wrap options with bindConfig()
  • packages/cli/src/tunnel.ts — Wrap tunnel service option with bindConfig()
  • packages/cli/deno.json — Add valibot and smol-toml imports
  • packages/cli/package.json — Add valibot and smol-toml dependencies

Dependencies to add

Add to both deno.json and package.json (project supports both Deno and Node.js/Bun):

{
  "dependencies": {
    "valibot": "^0.42.0",
    "smol-toml": "^1.6.0"
  }
}

Note: @optique/config should already be available in Optique 0.10.0, but verify the version is at least 0.10.0.

Example usage

# Use default config chain (system → user → project)
fedify lookup @user@example.com

# Add an extra config file on top
fedify lookup @user@example.com --config ./test-config.toml

# Ignore all config files
fedify lookup @user@example.com --ignore-config

# Override specific options (even with config files)
fedify lookup @user@example.com --user-agent "TestBot/1.0"

# Create project-specific config
cat > .fedify.toml << 'EOF'
debug = true

[lookup]
authorized_fetch = true
default_format = "compact"
EOF

# Create user-wide config
mkdir -p ~/.config/fedify
cat > ~/.config/fedify/config.toml << 'EOF'
user_agent = "MyPersonalAgent/1.0"
EOF

Sub-issues

Metadata

Metadata

Assignees

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions