Skip to content

Add a minimal callstack CLI for SMS send and modem status #22

Description

@Justinabox

Motivation

The README install path now suggests pip install -e ".[sqlite]", but there is no installed callstack command yet. Operators still need to write Python or run server.py directly for common tasks. A small CLI would make the developer experience much better for Raspberry Pi deployments: smoke-test the modem, send a manual SMS, inspect signal/registration, and tail events without inventing one-off scripts.

This issue scopes a minimal installable CLI slice for send and status first. monitor can be included if it stays small, but it should not block the first PR.

User journey

  1. A developer installs Callstack in editable mode on a Pi.
  2. They run callstack status --at-port /dev/ttyUSB2 and see modem/network readiness, signal quality, registration state, and operator if available.
  3. They run callstack send --to <number> --body "test" --at-port /dev/ttyUSB2 to send a one-off SMS.
  4. The command exits non-zero with a clear error when the modem/SIM/network is not ready.
  5. The command output is usable in scripts and does not reveal secrets or full private identifiers in logs.

API / UX sketch

Add a console script in pyproject.toml:

[project.scripts]
callstack = "callstack.cli:main"

Proposed commands:

callstack status \
  --at-port /dev/ttyUSB2 \
  --audio-port /dev/ttyUSB4 \
  --baudrate 115200 \
  --json

callstack send \
  --to '+15555550100' \
  --body 'hello from Callstack' \
  --at-port /dev/ttyUSB2

Human status output should be concise:

Modem: connected
SIM: ready
Registration: registered (home)
Operator: <operator or unknown>
Signal: good (-77 dBm), BER: good

JSON output should be stable for automation:

{
  "connected": true,
  "registration": {"registered": true, "roaming": false, "description": "registered (home)"},
  "signal": {"rssi": 18, "dbm": -77, "description": "good", "ber": 2, "ber_description": "good"},
  "operator": "example"
}

Technical approach

  • Implement callstack/cli.py using stdlib argparse and asyncio.run(); avoid adding a CLI dependency unless there is a strong reason.
  • Build ModemConfig from shared global flags: --at-port, --audio-port, --baudrate, --sms-db-path, --sim-pin-env (optional; read a PIN from an env var name without printing it), --log-level.
  • For status, open Modem, query modem.network.registration(), modem.network.signal_quality(), and modem.network.operator(), then print human or JSON output.
  • For send, open Modem, call modem.sms.send(to, body), print the SMS reference/status, and return non-zero on SMSSendError/modem setup errors.
  • Keep command-side validation small: require --to and --body; do not implement phone-number normalization in this PR.
  • Tests should patch/construct a fake modem or CLI runner so no real serial ports are opened.

Affected modules and tests

Likely files:

  • Create: callstack/cli.py
  • Modify: pyproject.toml
  • Modify (optional): callstack/__init__.py only if a clean public helper is needed
  • Create: tests/test_cli.py
  • Docs follow-up: README installation/CLI usage section after implementation

Existing context:

  • pyproject.toml already scopes package discovery to callstack* and has optional sqlite/dev dependencies.
  • ModemConfig already exposes port, baudrate, SMS DB path, SIM PIN, and log level knobs.
  • NetworkService already exposes signal_quality(), registration(), operator(), and emits signal events.
  • SMSService.send() already returns an SMS object with recipient/body/status/reference.

Hardware / modem caveats

  • The default port examples are SIMCOM/Raspberry Pi oriented (/dev/ttyUSB2, /dev/ttyUSB4), but users must be able to override both ports.
  • status must tolerate missing optional operator info and unknown signal values without crashing.
  • send must not log full destination numbers or message bodies on errors; stdout can print the destination only if explicitly requested or masked.
  • Tests must use mock/fake modem behavior only; no serial devices, SIM cards, or carrier network access.

Acceptance criteria

  • pip install -e . or uv run ... callstack --help exposes a callstack command through [project.scripts].
  • callstack --help, callstack status --help, and callstack send --help show useful examples/flags.
  • callstack status --json prints valid JSON with connected, registration, signal, and operator fields.
  • callstack status human output masks or omits private identifiers and handles unknown operator/signal values.
  • callstack send --to ... --body ... calls SMSService.send() once and prints a concise success message containing the message reference.
  • CLI failures return non-zero and print actionable errors without tracebacks by default.
  • Unit tests cover argument parsing, JSON status output, send success, send failure, and config flag mapping.
  • No real hardware is required for tests.

Exact verification gates

Run these before opening the PR:

git diff --check
PYTHONPATH=. uv run --no-project --with pytest --with pytest-asyncio --with pytest-aiohttp --with pyserial-asyncio --with aiosqlite pytest tests/test_cli.py -q
PYTHONPATH=. uv run --no-project --with pytest --with pytest-asyncio --with pytest-aiohttp --with pyserial-asyncio --with aiosqlite pytest tests/ -q

Because this touches packaging/installability, also run one packaging gate and record the exact result:

uv run --with pytest --with pytest-asyncio --with pytest-aiohttp --with pyserial-asyncio --with aiosqlite pytest tests/ -q

Optional if build tooling is available:

python3 -m build

Non-goals

  • Full daemon/process manager for the HTTP server.
  • Auto-detection UI; Add safe modem discovery reports and capability profiles #11 covers modem discovery/capability profiles.
  • Rich TUI/dashboard.
  • UCS2/PDU SMS send support.
  • monitor if it turns this PR into a large event-streaming feature; create a follow-up instead.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions