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
12 changes: 12 additions & 0 deletions .hallmark/log.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[
{
"date": "2026-08-08",
"macrostructure": "Workbench",
"theme": "Studio",
"theme_axes": "light / high-contrast-serif / chromatic-green",
"enrichment": "none",
"nav": "N9 Edge-aligned minimal",
"footer": "Ft4 Dense typographic",
"brief": "whatsapp-cli — landing page with live terminal demo (sync + sample query script) and openclaw/wacli credits nod"
}
]
11 changes: 11 additions & 0 deletions .hallmark/preflight.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"scanned": "2026-08-08",
"findings": {
"font_stack": null,
"palette": null,
"motion_library": null,
"spacing_scale": null,
"framework": "none (pure Go CLI repo; no prior web assets)"
},
"note": "First Hallmark run for this project. Nothing to preserve — full Hallmark stack introduced under website/."
}
112 changes: 112 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# AGENTS.md

WhatsApp CLI is a single Go binary. It reads, searches, and sends WhatsApp
messages from the command line. It talks to WhatsApp through the `whatsmeow`
library, not through an official WhatsApp API.

## Build, run, test

```bash
go mod download # install dependencies
go build -o whatsapp-cli . # build the binary
go test ./... # run all tests (the command CI runs)
go test -race ./... # run tests with the race detector
go test -cover ./... # run tests with coverage
go test ./internal/store # run one package
```

If the sandbox cannot write to the global Go module cache, point
`GOMODCACHE` at a folder inside the repo:

```bash
GOMODCACHE=$(pwd)/.gomodcache go build ./...
```

To embed a version string at build time, use `-ldflags`:

```bash
go build -ldflags "-X main.version=X.Y.Z" -o whatsapp-cli .
```

Run the binary like this:

```bash
./whatsapp-cli auth
./whatsapp-cli sync
./whatsapp-cli --store /custom/path chats list
```

CI (`.github/workflows/ci.yml`) runs only `go test ./...`, on every push and
pull request. There is no linter config and no Makefile in this repo.

## Code style

- Package layout: `internal/client` wraps `whatsmeow`. `internal/store`
wraps SQLite. `internal/commands` holds the `App` type, with one method
per CLI command. `internal/output` formats the JSON output.
`internal/types` holds types shared between `client` and `commands`, to
avoid an import cycle.
- Define interfaces at the consumer, not at the implementation.
`internal/commands/interfaces.go` defines the `WAClient` and
`MessageStore` interfaces. The concrete types live in `internal/client`
and `internal/store`.
- Give each component two constructors. `NewApp()` builds it with real
dependencies. `NewAppWithDeps()` takes injected mocks, for tests.
- Wrap errors with context: `fmt.Errorf("failed to X: %v", err)`.
- Command methods on `App` (`Auth`, `Sync`, `ListMessages`, `SendMessage`,
and so on) return a JSON string, not a Go `error`. Each method calls
`output.Success(data)` or `output.Error(err)` at its boundary.
- Every CLI command must print this JSON shape to stdout:
```json
{"success": true, "data": ..., "error": null}
```
- Go's `flag` package stops parsing at the first non-flag argument. For a
subcommand like `messages list --chat X`, parse flags from `args[2:]`,
skipping both the command and the subcommand name. See `main.go` and the
regression tests in `main_test.go` (GitHub issue #5).

## Architecture

See `docs/architecture.md` for the full design. In short: `main.go` parses
flags and calls into `internal/commands.App`. `App` holds a `WAClient`
(talks to WhatsApp through `whatsmeow`) and a `MessageStore` (reads and
writes SQLite). Session data lives in `store/whatsapp.db`. Message history
lives in `store/messages.db`. Downloaded media lives under `store/media/`.

## Testing

- Tests use `testify/require`. There is no VCR setup and no mocking
framework. Mocks are hand-written structs in `*_test.go` files, for
example `internal/commands/mocks_test.go`, that implement the `WAClient`
and `MessageStore` interfaces.
- Tests run against these mocks. They never open a real WhatsApp
connection.
- Name regression tests after the bug they fix. `main_test.go` and
`internal/commands/behavioral_test.go` name the GitHub issue or pull
request in the test name and comments. Follow this pattern for new
regression tests.
- `testdata/` exists but is empty. It is gitignored, so this repo does not
currently ship fixture files.

## Constraints

- `whatsmeow` uses the unofficial WhatsApp Web protocol, not a
WhatsApp-published API. A protocol change on WhatsApp's side can break
the client without warning. Commit `bf602c5` fixed one such break (a
"Client outdated (405)" auth error). Check `CHANGELOG.md` for the
history.
- The session lives in `store/whatsapp.db`, created by scanning a QR code.
It normally lasts about 20 days.
- `store/` holds the session and the full message history. Never commit
it. `.gitignore` already excludes `/store/`.
- The `go-sqlite3` driver needs CGO. Cross-compiling for another platform
needs `CGO_ENABLED=1` and, for Linux ARM64, a cross-compiler. See
`.github/workflows/release.yml` for the exact build matrix.

## More docs

- `README.md` — full command reference, FAQ, troubleshooting
- `docs/architecture.md` — design and data flow
- `docs/RELEASE.md` — how to cut and publish a release
- `QUICKSTART.md` — five-minute setup
- `CHANGELOG.md` — release history
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ All notable changes to this project will be documented in this file.

### Improved

- Added dependency injection and sturdier CLI command wiring to reduce runtime brittleness.
- Added dependency injection and more reliable CLI command wiring to reduce runtime failures.
- Added image sending support to `send`.
- Added standard CI on pushes and pull requests so test coverage runs outside release tags.

Expand Down
65 changes: 65 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Contributing

Thanks for your interest in whatsapp-cli. This project is maintained by one person in spare time. PRs are welcome and get reviewed. Feature requests without a PR wait until a contributor picks them up.

## Before you file an issue

Check the "Known issues" section below first. Your problem might already have a fix.

If it doesn't, include:

- The CLI version (`whatsapp-cli --version`).
- Your OS and architecture (e.g. macOS ARM64, Linux amd64).
- The exact command you ran.
- The full error output, including any log lines above the error.

Issue [#15](https://github.com/vicentereig/whatsapp-cli/issues/15) is a good model: it states the bug, gives exact repro steps, shows database evidence, and proposes a fix. Reports at that level get fixed fast.

## Known issues

### "Client outdated (405)" auth failures

This is the most common report ([#2](https://github.com/vicentereig/whatsapp-cli/issues/2), [#13](https://github.com/vicentereig/whatsapp-cli/issues/13), [#25](https://github.com/vicentereig/whatsapp-cli/issues/25)). WhatsApp periodically rotates the web client version it accepts. When that happens, the server sends `ConnectFailureClientOutdated (405)` and closes the connection.

The fix: bump the `whatsmeow` dependency and cut a new release. This recurs every few months — it is not a bug in this CLI. See the README's Connection Issues section for the exact steps. A PR that bumps `whatsmeow` and updates `go.sum` is always welcome when this happens; you don't need to ask first.

### "database is locked" during sync

The local store allows one writer at a time ([#19](https://github.com/vicentereig/whatsapp-cli/issues/19)). Don't run `sync` in two terminals at once, and don't run another store-writing command (like a history backfill) while `sync` is active. Doing so can corrupt the session, not just error out ([#20](https://github.com/vicentereig/whatsapp-cli/issues/20)).

### Auth succeeds but sync then fails or stalls

Run `sync` right after `auth` reports success. Waiting between the two steps has caused connection failures for some users ([#12](https://github.com/vicentereig/whatsapp-cli/issues/12)).

### `chats list` / `messages list` show only 20 results

That's the default `--limit`, not a bug ([#6](https://github.com/vicentereig/whatsapp-cli/issues/6), [#20](https://github.com/vicentereig/whatsapp-cli/issues/20)). Pass a higher `--limit` for scripts that expect the full store.

## Proposing a feature

1. Open an issue first. State the problem, then propose a design. Note any prior art (other projects that solved the same problem) if you know of any.
2. Wait for a green light on the design before you write the PR.
3. Send the PR. Mention any other open issue or PR your change might overlap with; the maintainer will coordinate a merge order or close the duplicate.

Issue [#20](https://github.com/vicentereig/whatsapp-cli/issues/20) is a model feature proposal: it states the problem, proposes a concrete CLI shape, cites prior art from other WhatsApp clients, links a working proof-of-concept branch, and lists caveats up front. Proposals at that level get accepted fast.

## Using AI coding agents

Agent-assisted PRs (Claude, Codex, or similar) are welcome. Point your agent at [AGENTS.md](./AGENTS.md) for build, test, and style conventions before it starts.

## What this project will and won't do for you

- PRs get reviewed and merged.
- Feature requests without a PR sit in the backlog until someone contributes one.
- Paid consulting is available if you want a feature built for you rather than contributing it yourself. Contact hey@vicente.services.

## Development setup

```bash
git clone https://github.com/vicentereig/whatsapp-cli.git
cd whatsapp-cli
go build
go test ./...
```

CI runs `go test ./...` on every push and pull request. Make sure it passes locally before you open a PR.
12 changes: 6 additions & 6 deletions QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,16 @@ if (result.success) {

## Storage

Session and messages stored in `./store/`:
- `whatsapp.db` - WhatsApp session (don't share!)
The CLI stores the session and messages in `./store/`:
- `whatsapp.db` - WhatsApp session (keep this file private)
- `messages.db` - Message history

## Tips

- **Authentication lasts ~20 days** - no need to scan QR every time
- **Phone numbers**: Can use just the number (e.g., `1234567890`) for individuals
- **Groups**: Must use full JID (e.g., `123456789@g.us`) - get from chats list
- **JSON parsing**: All commands return JSON - use `jq` or parse programmatically
- **Authentication lasts about 20 days.** You do not need to scan the QR code each time.
- **Phone numbers**: For an individual contact, you can use the number alone, for example `1234567890`.
- **Groups**: You must use the full JID, for example `123456789@g.us`. Get the JID from the chat list.
- **JSON parsing**: Every command returns JSON. Use `jq`, or parse the output in your own code.

## Next Steps

Expand Down
Loading