Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ fireblocks-cli --help
| `init` | ✅ | ✅ | Initialize the default configuration files | Creates `~/.config/fireblocks-cli/config.toml` and `~/.config/fireblocks-cli/keys/` |
| `gen-keys` | ✅ | ✅ | Generate Fireblocks-compatible private key and CSR | Outputs to `.config/fireblocks-cli/keys/{name}.csr`, etc. |
| `list` | n/a | n/a | List all configured profiles | Displays `[profile]` sections from `config.toml` |
| `edit` | n/a | n/a | Open the config file in your default `$EDITOR` | Falls back to `vi` or `nano` if `$EDITOR` is not set |
| `edit` | ✅ | ✅ | Open the config file in your default `$EDITOR` | Falls back to `vi` or `nano` if `$EDITOR` is not set |
| `validate` | ✅ | n/a | Validate the structure and contents of the config file | Checks for invalid or missing keys and values |
| `add` | n/a | n/a | Append a new profile to the configuration file | Will add to the bottom of the file without auto-formatting |
| `remove` | n/a | n/a | Remove a profile from the configuration | Deletes the corresponding section from `config.toml` |
Expand Down
47 changes: 47 additions & 0 deletions fireblocks_cli/commands/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,50 @@ def validate_file(path: Path):
"This may cause unexpected behavior.",
fg=typer.colors.YELLOW,
)


@configure_app.command("edit")
def edit():
"""
Open the config.toml file in your default editor ($EDITOR).
"""
import os
import subprocess
from fireblocks_cli.config import get_config_file

config_path = get_config_file()

if not config_path.exists():
typer.secho(f"❌ Config file not found: {config_path}", fg=typer.colors.RED)
raise typer.Exit(code=1)

editor = os.environ.get("EDITOR")

if not editor:
# Fallbacks
for fallback in ["code", "nano", "vi"]:
if shutil.which(fallback):
editor = fallback
break

if not editor:
typer.secho(
"❌ No editor found. Please set the $EDITOR environment variable.",
fg=typer.colors.RED,
)
raise typer.Exit(code=1)

try:
subprocess.run([editor, str(config_path)])
except Exception as e:
typer.secho(f"❌ Failed to open editor: {e}", fg=typer.colors.RED)
raise typer.Exit(code=1)
# Validate after editing
typer.echo("\n🔍 Validating config.toml after editing...\n")
try:
from fireblocks_cli.commands.configure import validate

validate()
except Exception as e:
typer.secho(f"❌ Validation failed: {e}", fg=typer.colors.RED)
raise typer.Exit(code=1)
59 changes: 59 additions & 0 deletions tests/test_configure_edit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# SPDX-FileCopyrightText: 2025 Ethersecurity Inc.
#
# SPDX-License-Identifier: MPL-2.0
# Author: Shohei KAMON <cameong@stir.network>

import os
import pytest
from typer.testing import CliRunner
from fireblocks_cli.main import app
from fireblocks_cli.config import get_config_file
from pathlib import Path

runner = CliRunner()


@pytest.fixture
def mock_home(tmp_path, monkeypatch):
"""
Redirect HOME to a temporary directory to isolate test artifacts.

Creates:
- ~/.config/fireblocks-cli/config.toml with dummy valid content
"""
monkeypatch.setattr(Path, "home", lambda: tmp_path)
config_path = tmp_path / ".config" / "fireblocks-cli" / "config.toml"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text(
'[default]\napi_id = "test"\napi_secret_key = { type = "file", value = "xxx" }\n'
)
return tmp_path


def test_edit_invokes_editor_and_validates(mock_home, monkeypatch):
"""
Test that `configure edit`:
- Launches the configured editor via subprocess.run
- Validates config.toml after editing
"""

# Flags to confirm mock functions are called
called = {"editor": False, "validate": False}

# Mock subprocess.run (simulate editor launch)
def mock_run(cmd, *args, **kwargs):
assert get_config_file().name in cmd[-1]
called["editor"] = True

# Mock validate() call
def mock_validate():
called["validate"] = True

monkeypatch.setenv("EDITOR", "dummy-editor")
monkeypatch.setattr("subprocess.run", mock_run)
monkeypatch.setattr("fireblocks_cli.commands.configure.validate", mock_validate)

result = runner.invoke(app, ["configure", "edit"])
assert result.exit_code == 0
assert called["editor"] is True
assert called["validate"] is True