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
4 changes: 3 additions & 1 deletion .ai-context/COMMANDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ Run the v2 financial intelligence CLI with:

```bash
bb --help
bb init
bb status
```

`bb` is the side-by-side command surface for new `BB_` schema work. It should
grow new document/entity/observation workflows while the legacy command
surfaces remain available.
surfaces remain available. Use `bb init` to apply current migrations and
prepare v2 `financial/` storage roots for the active data home.

## BankBuddy CLI

Expand Down
6 changes: 4 additions & 2 deletions .ai-context/STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ section is `Unreleased`.
- Local data homes for `prod`, `dev`, and named environments.
- Banking CLI commands for banks, accounts, statement refs, imports,
transactions, categories, reports, exports, storage migration, and status.
- Side-by-side `bb` CLI for v2 financial intelligence foundation status and
`BB_` schema visibility.
- Side-by-side `bb` CLI for v2 financial intelligence initialization,
foundation status, storage readiness, and `BB_` schema visibility.
- Supported banking imports for Bank of America PDF/CSV, Apple Card PDF, ICICI
`.xls`, and HDFC `.xls`.
- Statement inventory and statement coverage auditing.
Expand All @@ -30,6 +30,8 @@ section is `Unreleased`.
questions documents.
- Additive `bankbuddy.bb` source namespace and `bb` console script for v2 work,
leaving existing `bankbuddy` and `taxbuddy` commands in place.
- `bb init` for applying migrations and preparing v2 financial storage roots
without depending on the legacy `bankbuddy` command surface.
- Prospective relicensing from MIT to AGPL-3.0-or-later.
- Canonical data-home layout with `database/`, `bank/`, and `tax/` directories.
- First TaxBuddy CLI slice and `tax_documents` metadata index.
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ and versions are tracked in the repo-root `VERSION` file.

### Added

- Added `bb init` and v2 storage readiness output in `bb status`, so the v2
command surface can initialize migrations and financial storage roots
independently of the legacy `bankbuddy` CLI.
- Added v2 financial document storage roots, document object metadata,
generated human-readable document view metadata, path-resolution helpers,
and read-only managed-file guardrails under the `bankbuddy.bb` namespace.
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Activate the project shell once, then run the CLI directly:

```bash
basectl activate bankbuddy
bb init
bb status
bankbuddy --help
bankbuddy status
Expand Down Expand Up @@ -154,10 +155,15 @@ which maps to `~/BankBuddy-dev`.
Use `status` to confirm the active environment and database:

```bash
bb init
bb status
bankbuddy status
```

Use `bb init` for the v2 financial intelligence foundation. It applies the
current SQLite migrations and prepares the `financial/` storage roots used by
document-first workflows.

Switch the current shell by exporting `BANKBUDDY_ENV`:

```bash
Expand Down
44 changes: 43 additions & 1 deletion src/bankbuddy/bb/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import click

from bankbuddy import __version__
from bankbuddy.database import initialize_database
from bankbuddy.paths import resolve_app_paths
from bankbuddy.bb.storage import ensure_financial_storage_dirs
from bankbuddy.runtime import CliRuntime
from bankbuddy.runtime import RuntimeConfigError
from bankbuddy.runtime import create_runtime
Expand Down Expand Up @@ -108,17 +110,19 @@ def status(ctx: click.Context) -> None:
initialized = "yes" if paths.database.exists() else "no"
bb_table_count = len([name for name in table_names if name.startswith("BB_")])
has_v2_foundation = BB_FOUNDATION_TABLES.issubset(table_names)
has_v2_storage = _v2_storage_ready(paths)
has_legacy_tables = bool(LEGACY_TABLES.intersection(table_names))

runtime.log.debug(
"bb_status environment=%s data_home=%s database=%s initialized=%s "
"bb_table_count=%s v2_foundation=%s legacy_tables=%s",
"bb_table_count=%s v2_foundation=%s v2_storage=%s legacy_tables=%s",
paths.environment,
paths.root,
paths.database,
initialized,
bb_table_count,
has_v2_foundation,
has_v2_storage,
has_legacy_tables,
)

Expand All @@ -129,8 +133,31 @@ def status(ctx: click.Context) -> None:
click.echo(f"Database: {paths.database}")
click.echo(f"Initialized: {initialized}")
click.echo(f"V2 foundation: {_yes_no(has_v2_foundation)}")
click.echo(f"V2 storage: {_yes_no(has_v2_storage)}")
click.echo(f"BB tables: {bb_table_count}")
click.echo(f"Legacy tables: {'present' if has_legacy_tables else 'absent'}")
if has_v2_storage:
click.echo(f"Financial canonical: {paths.financial_canonical}")
click.echo(f"Financial views: {paths.financial_views}")


@main.command("init")
@click.pass_context
def init_command(ctx: click.Context) -> None:
"""Initialize the local BankBuddy v2 app directory and database."""

runtime = runtime_from_context(ctx)
paths = resolve_app_paths(environment=runtime.environment)
initialize_database(paths)
ensure_financial_storage_dirs(paths)
runtime.log.debug(
"bb_init home=%s database=%s financial_canonical=%s financial_views=%s",
paths.root,
paths.database,
paths.financial_canonical,
paths.financial_views,
)
click.echo(f"Initialized BankBuddy v2 at {paths.root}")


def _database_table_names(database_path) -> set[str]:
Expand All @@ -149,6 +176,21 @@ def _yes_no(value: bool) -> str:
return "yes" if value else "no"


def _v2_storage_ready(paths) -> bool:
return all(
path.is_dir()
for path in (
paths.financial_inbox,
paths.financial_canonical,
paths.financial_failed,
paths.financial_duplicates,
paths.financial_review,
paths.financial_views,
paths.financial_exports,
)
)


def runtime_from_context(ctx: click.Context) -> CliRuntime:
"""Return the root bb runtime context."""

Expand Down
34 changes: 34 additions & 0 deletions tests/test_bb_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def test_bb_status_reports_environment_data_home_and_v2_schema(tmp_path) -> None
assert f"Database: {tmp_path / 'database' / 'bankbuddy.sqlite3'}" in result.output
assert "Initialized: yes" in result.output
assert "V2 foundation: yes" in result.output
assert "V2 storage: yes" in result.output
assert "BB tables: 20" in result.output
assert "Legacy tables: present" in result.output

Expand All @@ -42,4 +43,37 @@ def test_bb_status_reports_missing_v2_schema(tmp_path) -> None:
assert "CLI: bb" in result.output
assert "Initialized: no" in result.output
assert "V2 foundation: no" in result.output
assert "V2 storage: no" in result.output
assert "BB tables: 0" in result.output


def test_bb_init_creates_database_and_v2_storage_dirs(tmp_path) -> None:
home = tmp_path / "home"

result = CliRunner().invoke(main, ["init"], env={"BANKBUDDY_HOME": str(home)})

assert result.exit_code == 0
assert f"Initialized BankBuddy v2 at {home}" in result.output
assert (home / "database" / "bankbuddy.sqlite3").is_file()
assert (home / "financial" / "canonical").is_dir()
assert (home / "financial" / "views").is_dir()
assert (home / "financial" / "inbox").is_dir()


def test_bb_init_is_idempotent_and_status_reports_ready_storage(tmp_path) -> None:
home = tmp_path / "home"
runner = CliRunner()
env = {"BANKBUDDY_HOME": str(home)}

first = runner.invoke(main, ["init"], env=env)
second = runner.invoke(main, ["init"], env=env)
status = runner.invoke(main, ["status"], env=env)

assert first.exit_code == 0
assert second.exit_code == 0
assert status.exit_code == 0
assert "Initialized: yes" in status.output
assert "V2 foundation: yes" in status.output
assert "V2 storage: yes" in status.output
assert f"Financial canonical: {home / 'financial' / 'canonical'}" in status.output
assert f"Financial views: {home / 'financial' / 'views'}" in status.output
Loading