Initial commit: clients resource scaffold over invoiceninja-sdk
A command-line client for the Invoice Ninja v5 API, built on the invoiceninja-sdk crate (consumed from crates.io, 0.2.0). Only the clients resource is wired up so far, as the template the rest follow - see CLAUDE.md and GOALS.md for the reasoning behind the shape of things (the update fetch-merge-write pattern, why there's no Cargo workspace with the SDK, the local-checkout override for co-development, and the Nix flake packaging via packages.default and overlays.default). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
# Goals
|
||||
|
||||
What this crate is trying to be, what it is deliberately not, and what is
|
||||
still open. Same purpose as `invoiceninjars-sdk`'s own `GOALS.md`: several
|
||||
choices here look like oversights until you know the reasoning, and that
|
||||
reasoning should live here rather than only in commit messages.
|
||||
|
||||
## What this is for
|
||||
|
||||
A command-line way to drive the Invoice Ninja v5 API through
|
||||
`invoiceninja-sdk`, for the cases Rust code is the wrong tool for: a quick
|
||||
"what does this endpoint actually return", a one-off fix from a terminal, or
|
||||
a shell script that doesn't want to be a Rust program. The SDK's own
|
||||
`GOALS.md` names this exact need — its `examples/` cover ad-hoc probing
|
||||
today, and this crate is the standalone version of that.
|
||||
|
||||
It is a client over the SDK, not a second implementation of anything the SDK
|
||||
already does. If a command needs logic beyond "turn flags and JSON into one
|
||||
SDK call", that is usually a sign the logic belongs in the SDK instead.
|
||||
|
||||
## Goals
|
||||
|
||||
### 1. Every command is a thin translation, with one documented exception
|
||||
|
||||
`list`, `get`, `create`, and `delete` do nothing but turn CLI flags into a
|
||||
`ListParams`/model/id and call the matching SDK method. **`update` is the
|
||||
one exception**, and it stays an exception rather than a pattern to extend
|
||||
casually: it fetches the record, overlays the caller's JSON on top, and
|
||||
writes the merged result back, because Invoice Ninja's `PUT` is a full
|
||||
replace and the SDK's models default an omitted field rather than leaving it
|
||||
alone. See the comment in `src/commands/clients.rs` and the matching section
|
||||
in `CLAUDE.md`. Any other deviation from "thin translation" should be
|
||||
written down the same way.
|
||||
|
||||
### 2. `--output json` is always the full, lossless record
|
||||
|
||||
The table view is a convenience subset — whatever columns a resource's
|
||||
`print_table` picks — and it is allowed to leave things out. `--output json`
|
||||
is not: it is always the model's own `Serialize` output, including
|
||||
`extra`, so it is a drop-in input to `--data`/`--file` for a later `update`
|
||||
or `create` and a reliable thing to pipe into `jq`. This is the CLI-level
|
||||
version of the SDK's "never lose data in a round trip" goal.
|
||||
|
||||
### 3. One pattern across resources
|
||||
|
||||
Adding a resource should be mechanical, not a new design each time: a
|
||||
`Command` variant, a `src/commands/<resource>.rs` with the same
|
||||
`run`/`list`/`get`/`create`/`update`/`delete`/`print_table` shape as
|
||||
`clients.rs`, and a registration line in two places. `CLAUDE.md`'s "Adding a
|
||||
resource" section is the checklist; if a resource needs something the
|
||||
template doesn't cover (bulk, uploads, entity actions, non-CRUD endpoints),
|
||||
that is scope for a deliberate addition to the template, not a one-off.
|
||||
|
||||
### 4. Scriptable by default
|
||||
|
||||
Errors go to stderr and a failure exits non-zero; nothing is printed to
|
||||
stdout on an error path that a script could mistake for a result. Secrets
|
||||
(`--token`, `--api-password`) never appear in `--help` or in any echoed
|
||||
default — see `hide_env_values` on each in `src/cli.rs`. The SDK already
|
||||
keeps both out of its own `Debug` output; this crate's job is not to undo
|
||||
that by printing them itself.
|
||||
|
||||
### 5. Inherit the SDK's safety defaults rather than re-deciding them
|
||||
|
||||
This crate does not expose its own retry/timeout policy or add a write path
|
||||
the SDK doesn't already consider safe. If the SDK decides `POST` isn't
|
||||
replayed on a 5xx because it might double-create an invoice, that reasoning
|
||||
applies here unchanged — the CLI is not a place to relax it for convenience.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- **No Cargo workspace with the SDK.** Explained in `CLAUDE.md`: the SDK is
|
||||
its own git repo with its own CI and crates.io release, mirrored read-only
|
||||
to GitHub. Now that the SDK is published (0.2.0, 2026-10-25), the
|
||||
dependency is a plain crates.io version requirement — there isn't even a
|
||||
path dependency linking the two build graphs anymore, let alone a
|
||||
workspace. This doesn't mean co-development across both crates is gone:
|
||||
`.cargo/config.toml.example` documents a per-developer `paths` override
|
||||
back onto a local `../invoiceninjars-sdk` checkout, gitignored so it never
|
||||
becomes the default for anyone else or for CI. See `CLAUDE.md`.
|
||||
- **No interactive mode or TUI.** Every command should work non-interactively
|
||||
from a script; a prompt-driven mode would be a different tool.
|
||||
- **No local state, cache, or config file — yet.** Credentials and the base
|
||||
URL come from flags or environment variables only, resolved fresh on every
|
||||
invocation. See the open question below on multi-account profiles before
|
||||
assuming this is permanent.
|
||||
- **No stability promise before 1.0.** Same posture as the SDK: flags are a
|
||||
compatibility surface too, and pre-1.0 they can change. Record breaking
|
||||
changes somewhere legible (a `CHANGELOG.md`, once there's enough history to
|
||||
need one) rather than letting them be discovered at the command line.
|
||||
|
||||
## Open questions
|
||||
|
||||
These are genuinely undecided; deciding one is a change to this file.
|
||||
|
||||
- **Bulk actions.** The SDK exposes `bulk_<resource>` (archive/restore/delete
|
||||
plus resource-specific actions) on every resource with a `/bulk` route.
|
||||
Nothing here calls it yet. Worth having, but a bulk `delete` from a CLI
|
||||
invites a worse mistake than a one-record `delete` — whether that needs a
|
||||
confirmation prompt, a `--yes` flag, or a dry-run preview is unresolved.
|
||||
- **`--dry-run` for `create`/`update`/`delete`.** Would print what the merged
|
||||
body or the target of a delete would be without sending the request.
|
||||
Cheap for `create`/`delete`; for `update` it would mean exposing the
|
||||
fetch-merge result for inspection before the write half runs.
|
||||
- **Multi-account profiles.** Flags and env vars cover one account per
|
||||
invocation. A config file keyed by profile name (base URL + token) would
|
||||
help anyone working against more than one instance regularly, at the cost
|
||||
of being the first place this crate stores anything on disk.
|
||||
- **How closely should list flags track `ListParams`?** Right now `clients
|
||||
list` exposes exactly what `ListParams` has typed setters for, plus
|
||||
`--param key=value` for the rest. A per-resource convenience filter (e.g.
|
||||
a `--unpaid` shorthand for `client_status=unpaid`) would read nicer but
|
||||
means hand-picking which filters earn a dedicated flag per resource.
|
||||
- **Whether this crate gets its own CI.** No longer blocked on needing a
|
||||
sibling checkout — the SDK dependency switched from a path to a crates.io
|
||||
version requirement (`invoiceninja-sdk = "0.2.0"`) once 0.2.0 was published
|
||||
on 2026-10-25. A workflow here can now just `cargo build` against the
|
||||
registry like any other Rust project; it doesn't need to check out
|
||||
`invoiceninjars-sdk` at all.
|
||||
- **Whether the Nix packaging goes further than this repo's own flake.**
|
||||
`flake.nix` now has `packages.default`/`devShells.default` (built with a
|
||||
pinned `rust-overlay` toolchain, for parity between `nix build` and
|
||||
`nix develop`) and `overlays.default` (for another flake to pull
|
||||
`invoiceninja-cli` into its own `pkgs`, built with *that* nixpkgs's own
|
||||
`rustPlatform` instead of the pin). The overlay path means this crate's
|
||||
`rust-version = "1.88"` has to be met by whatever rustc the consuming
|
||||
channel ships — there's no guard here if it isn't, just a build failure
|
||||
over there. Whether that's worth submitting to nixpkgs proper (and how an
|
||||
MSRV bump would then need to be communicated beyond this repo) is
|
||||
unresolved.
|
||||
Reference in New Issue
Block a user