- Keep the CLI as the primary interface.
- Maintain the JSON response envelope for agent consumption (
--jsonoutput). - Put shared portfolio logic in
src/core/portfolio_service.py. - Put shared market logic in
src/core/market_service.py. - Keep CLI commands in
src/schwab_client/cli/commands/. - Use
context.pyfor client access (cached singletons).
src/schwab_client/cli/
├── __init__.py # Entry point, argparse
├── context.py # get_client(), get_cached_market_client()
├── output.py # JSON envelope, formatters
└── commands/ # One file per command group
├── portfolio.py
├── market.py
├── trade.py
├── admin.py
└── report.py
- Add handler function to appropriate
commands/*.pyfile - Import in
commands/__init__.py - Add argparse subparser in
cli/__init__.py - Add routing in
main()function
- Use
get_client()fromcontext.py- never instantiate clients directly - Use
handle_cli_error()fromoutput.pyfor consistent error handling - Use
print_json_response()fromoutput.pyfor JSON output - Use
format_header()fromoutput.pyfor text section headers
- Never commit
.env,config/accounts.json, or anything underprivate/ortokens/. - Keep the repo matching upstream; put local data/artifacts in
private/. - Avoid hardcoding account numbers or API keys.
- Live trading is disabled by default. Never use
--liveorSCHWAB_ALLOW_LIVE_TRADESin automation. - For headless/SSH auth, use
schwab-auth --manual(copy-paste URL flow). - Trading requires thinkorswim enablement on schwab.com for each account. Without it, orders fail with "No trades are currently allowed".
# Run all tests
uv run pytest
# Run with mock clients (no credentials needed)
uv run pytest tests/unit/
# Use mock fixtures
def test_portfolio(mock_schwab_client):
# Client is mocked, no real API calls
...- Use
uvfor installs and scripts. - Prefer targeted tests:
uv run pytest tests/unit/ -v. - Use command aliases for quick testing:
schwab p,schwab dr,schwab snap.