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
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ Set up every coding agent from one prompt (`init --all --yes --api-key`, `status
- **Live progress on a terminal.** A spinner with a label and elapsed time covers every wait over about 300 ms (`scrape`, `scrape -`, `batch submit`/`wait`, and any other API call), and `scrape`, `scrape -` and `batch wait` end with a `✓`/`✗` summary line (page, status, credits, duration) on stderr. API errors keep their `ERR::` code and hint in a short block. Everything is on stderr, only when stderr is a terminal, and off with `--json`, `-q`, `NO_COLOR`, `TERM=dumb` or `CI`: stdout and the plain output are unchanged. New dependency: `golang.org/x/term`, for the terminal width.
- **A polished terminal UI.** On a terminal only; piped, redirected, in CI, under `NO_COLOR`/`CLICOLOR=0`/`TERM=dumb`, and with `--json` or `-q`, every byte is what it was. `spicrawl --help` groups the commands by purpose (extract data, set up, monitor, reference), colours headings and the names you type, mutes defaults and API routes, wraps long descriptions under their own column and ends with the docs link; `spicrawl x --help` gets the same layout. Colour adapts to the terminal: the 16-colour palette (which follows your theme) by default, a 256-colour or 24-bit accent where `TERM`/`COLORTERM` say it is there, and a deeper accent on a light background (`COLORFGBG`). Outside a UTF-8 locale the glyphs fall back to ASCII (`+ x ! >` and a `| / - \` spinner). The error block mutes the `ERR::` code so the sentence leads, accents the hint, aligns its keys, wraps long text under its own column and makes URLs clickable (OSC 8) in iTerm2, WezTerm, kitty, Windows Terminal, VS Code, GNOME Terminal and other terminals that have them (`FORCE_HYPERLINK=0/1` overrides). The spinner measures the terminal on every frame, so it follows a resize and never wraps. New in `internal/ui`: a determinate progress bar with a count and an ETA (`Spinner.Progress(done, total)`), used by `batch wait` and `batch submit --wait`.
- **Retries are safe.** A connection that drops after the request was sent now exits 12 ("may have run and been billed"), not 10 ("nothing was sent, safe to retry"). A gateway 5xx without an API problem document is retried only for GET/HEAD, and is no longer mistaken for the target's status under `--original-status`. A 301/302/303 on a POST is an error naming the Location instead of a silent GET. `Retry-After` waits are capped at 60 s and announced on stderr.
- **Ctrl-C exits 130** with "interrupted", including while `scrape -` waits on stdin. A mistyped subcommand (`spicrawl batch wiat`) is a usage error with a suggestion, not help and exit 0.
- **Ctrl-C exits 130** with "interrupted" on stderr (human mode only; in JSON mode stderr stays empty and only the exit code says it), including while `scrape -` waits on stdin. A mistyped subcommand (`spicrawl batch wiat`) is a usage error with a suggestion, not help and exit 0.
- **JSON mode keeps stderr clean** (**behaviour change**). With `--json`, or whenever stdout is not a terminal, stderr is now the problem document of a failure and nothing else; it is empty on success. Warnings and status lines ("wrote N bytes", "more results: --cursor ...", the `--engine` deprecation notice, the `ws://` warning) used to print there as prose, sometimes before the problem document, so `cmd --json 2>err.json; jq . err.json` failed. They still print in human mode. The API's `warnings` field is still in the JSON on stdout.
- **Keys:** a trailing CR/LF or space on the key (flag, env or file) is trimmed. The config file is written via a private temp file, short keys are fully masked, and commands that get the key and base URL from flags work without `$HOME`.
- **scrape:** `--body null` is a usage error, not a panic (also `batch submit`). Big integers survive `--json` (**key order now follows the API**, as `--jsonl` already did). Stdout write errors stop the run instead of exiting 0. `-o` writes atomically and **a target error page no longer overwrites the file**. `-o -` means stdout. Screenshot labels never overwrite each other. `--body` merges deeply. A BOM on stdin is stripped. Human output no longer HTML-escapes `<>&`. The multi-URL summary counts correctly. `--impersonate` help says it is on by default.
- **Empty results and `--extract` order** (**behaviour change**, human mode only). `batch list` ("no batch jobs") and `batch results` ("no finished items yet") now print that message on stderr, as `sessions list` and `logs` already did, so stdout is empty and `spicrawl batch list | wc -l` is 0; `-q` silences it. `scrape --extract` (and the file `-o` writes for it) lists the extracted fields in the API's order instead of sorted alphabetically. `--json` and `--jsonl` are unchanged.
- **Empty results and `--extract` order** (**behaviour change**, human mode only). `batch list` ("no batch jobs") and `batch results` ("no finished items yet") now print that message on stderr, as `sessions list` and `logs` already did, so stdout is empty and `-q` silences it. Piped output is JSON mode and is unchanged: `spicrawl batch list | wc -l` still counts the empty collection (`{"batches": []}`), and stderr stays empty. `scrape --extract` (and the file `-o` writes for it) lists the extracted fields in the API's order instead of sorted alphabetically. `--json` and `--jsonl` are unchanged.
- **batch:** `wait` and `submit --wait` exit 4 when the job failed or was cancelled. Polling survives transient 5xx and network errors. `results -o` no longer empties an existing file on failure.
- **browser:** a server without the cloud browser says so (exit 8) instead of `NOT_FOUND`. `$(spicrawl browser url -q)` yields the bare URL.
- **usage summary** shows the enforced monthly allowance. **status** fails when the key check cannot reach the API.
Expand All @@ -23,7 +23,7 @@ Set up every coding agent from one prompt (`init --all --yes --api-key`, `status

**Set up from one prompt.** `spicrawl init --all --yes --api-key <key>` then `spicrawl status --json` is enough for an agent to set Spicrawl up with no questions asked; the README has the prompt to paste ("Set up with one prompt").

- **init:** `--all` connects every detected client (conflicts with `--client`). `--api-key` is checked with the API and saved as `login` does, before any client file is written; a rejected key writes nothing. The key is then written into each client's MCP config as is, in the user-level files (`--global` is implied; `--global=false` picks the project files, which are added to the project's `.gitignore` when it has one; `--use-env=true` keeps the `${SPICRAWL_API_KEY}` reference), so the agents work with nothing exported. A key already saved in the config file (by `login` or an earlier `--api-key`) is embedded the same way when `init` runs without `--api-key`, with no new check; a key that is only in `$SPICRAWL_API_KEY` keeps today's `${SPICRAWL_API_KEY}` references. The key is never printed, only masked, and the error output masks it too. After the result list init prints a report on stderr in human mode: `connected:`, `skipped:` with the reason, `api key:` and the `next:` steps. In JSON mode (silent on stderr) the same summary is the last element of the result array: `{"kind":"report","connected":[ids],"skipped":[{"client","reason"}],"api_key":"<masked>","next":[...]}`, so an agent running the one-prompt command can tell the user what connected and what to do next (restart the agents). The other elements are unchanged, so consumers that filter by `kind` still work; one that treats every element as a file result will see one more. `$SPICRAWL_API_KEY` is used as before and is not checked or saved.
- **init:** `--all` connects every detected client (conflicts with `--client`). `--api-key` is checked with the API first (a rejected key writes nothing), then the plan is made and confirmed, the client files are written, and the key is saved as `login` does, after those changes are applied; a refused or declined plan saves nothing. The key is written into each client's MCP config as is, in the user-level files (`--global` is implied; `--global=false` picks the project files, which are added to the project's `.gitignore` when it has one; `--use-env=true` keeps the `${SPICRAWL_API_KEY}` reference), so the agents work with nothing exported. A key already saved in the config file (by `login` or an earlier `--api-key`) is embedded the same way when `init` runs without `--api-key`, with no new check; a key that is only in `$SPICRAWL_API_KEY` keeps today's `${SPICRAWL_API_KEY}` references. The key is never printed, only masked, and the error output masks it too. After the result list init prints a report on stderr in human mode: `connected:`, `skipped:` with the reason, `api key:` and the `next:` steps. In JSON mode (silent on stderr) the same summary is the last element of the result array: `{"kind":"report","connected":[ids],"skipped":[{"client","reason"}],"api_key":"<masked>","next":[...]}`, so an agent running the one-prompt command can tell the user what connected and what to do next (restart the agents). The other elements are unchanged, so consumers that filter by `kind` still work; one that treats every element as a file result will see one more. `$SPICRAWL_API_KEY` is used as before and is not checked or saved.
- **status** (**behaviour change**): it prints a repair command for each failed check (`reachable`, `ready`, `key`, `workers.<kind>`, `mcp`), each marked `unattended_safe: true|false`; `--json` has them in a `repairs` array of `{check, command, unattended_safe, why}`, plus an `mcp` object (`detected`, `connected`, `needs_env`). It checks that each agent client found in this directory or your home directory has a Spicrawl MCP server that can authenticate: an entry that reads `${SPICRAWL_API_KEY}` does not count while that variable is unset in the environment `status` runs in (`mcp.needs_env`, with a repair that rewrites the entry using the saved key), and a project-level entry shadows a user-level one. **It now exits 1 when any check fails** (the API reports not ready, a worker kind has none ready, an MCP server is missing), where a not-ready API used to exit 0. Key and network failures still exit 3 and 10.
- **init, fixes from QA.** (1) The plan is made and the key checked before anything is saved: a plan that is refused (a customised `spicrawl` entry without `--force`) saves and writes nothing, where it used to leave the new key saved. The key is saved after the changes are confirmed (or with `--yes`) and applied; the plan lists it as `save key`. (2) An existing `spicrawl` entry that differs from the one to write only in its `Authorization: Bearer ...` token (same URL and shape, in `.mcp.json`, `.cursor/mcp.json`, VS Code's `mcp.json` and Codex's `config.toml`) is replaced without `--force`, so a new `--api-key` or a switch from a reference to the key works on a re-run; the old file is kept as `.bak`. (3) With `--global=false`, the `.bak` copy of each project file that holds the key is added to `.gitignore` as well (it can hold the previous key), and a key file that git already tracks is flagged: a warning for a person, and `tracked by git: git rm --cached <file>` in the result's `reason`. `git` is only asked when it is installed.
- **status, fixes from QA.** An MCP entry with a key written into it that is not the key in use (compared by fingerprint, neither printed) is reported as `mcp.stale` and counts as a failed `mcp` check. The repair for `needs_env` and `stale` entries is now `init --client <ids> --yes` (plus `--global=false` for a project entry), without `--force`.
Expand Down
6 changes: 3 additions & 3 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ never invent a field the API does not accept (the API rejects unknown fields).
`internal/cmd/json_stderr_test.go` runs every golden case against this.
An empty result is not data either: in human mode an empty list
(`sessions list`, `logs`, `batch list`, `batch results`) prints nothing on
stdout and its one-line message ("no sessions") on stderr, so
`spicrawl sessions list | wc -l` is 0 and `-q` silences the message. JSON
mode prints the empty collection on stdout.
stdout and its one-line message ("no sessions") on stderr, and `-q`
silences the message. JSON mode, which is what a pipe gets, prints the
empty collection on stdout, so `spicrawl sessions list | wc -l` is not 0.
The same gate (plus `CLICOLOR=0`) covers the grouped, coloured `--help`,
which is written to stdout and so tests stdout; piped, the help is cobra's own.
Inside the gate the UI adapts to what the terminal can do (`ui.Caps`): colour
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ Set up Spicrawl for yourself. My API key is <YOUR_API_KEY>. Only if your shell r

What the two commands do, with no questions asked:

- `init --all --yes --api-key <key>` connects every detected client (`--all`; it conflicts with `--client`) without confirming (`--yes`). The key is checked with the API and saved to the config file as `spicrawl login` does, before any client file is written; a rejected key exits 3 and writes nothing. Then the key is written into each client's MCP config as is, in your **user-level** files (`~/.claude.json`, `~/.cursor/mcp.json`, ... with mode 0600; `--global` is implied), so the agents authenticate right after a restart with nothing to export. A key already saved in the config file (by `login` or an earlier `--api-key`) is embedded the same way when you run `init` without `--api-key`; a key that is only in `$SPICRAWL_API_KEY` stays a reference. Pass `--use-env=true` to keep the `${SPICRAWL_API_KEY}` reference instead, or `--global=false` to write the project files: those get the same key, a warning, and a line in the project's `.gitignore` when it has one that does not ignore them yet. The key is never printed, only masked (`spicrawl_live_ab12…cd34`), and API errors are masked too. The result list is on stdout (JSON when it is not a terminal). It ends with a summary of the run. In JSON mode, which is what an agent gets, that is one more element of the array, `{"kind": "report", "connected": ["claude", "cursor"], "skipped": [{"client": "vscode", "reason": "not detected"}], "api_key": "spicrawl_live_ab12…cd34", "next": ["restart Claude Code, Cursor to load the MCP server and the skill", ...]}`: `connected` and `skipped` are client ids, `api_key` is masked (empty when none is configured), and `next` is what the user still has to do. Every other element is what it was (`mcp`, `skill`, `agents_md` or `gitignore` in `kind`), so a consumer that filters by `kind` is unaffected, and stderr stays empty. In human mode (a terminal, or `NO_COLOR`/`CI`) the same summary follows on stderr:
- `init --all --yes --api-key <key>` connects every detected client (`--all`; it conflicts with `--client`) without confirming (`--yes`). The key is checked with the API first (a rejected key exits 3 and writes nothing), then the plan is made and confirmed (`--yes` confirms it), the client files are written, and the key is saved to the config file, as `spicrawl login` does, after those changes are applied; a refused or declined plan saves nothing. The key is written into each client's MCP config as is, in your **user-level** files (`~/.claude.json`, `~/.cursor/mcp.json`, ... with mode 0600; `--global` is implied), so the agents authenticate right after a restart with nothing to export. A key already saved in the config file (by `login` or an earlier `--api-key`) is embedded the same way when you run `init` without `--api-key`; a key that is only in `$SPICRAWL_API_KEY` stays a reference. Pass `--use-env=true` to keep the `${SPICRAWL_API_KEY}` reference instead, or `--global=false` to write the project files: those get the same key, a warning, and a line in the project's `.gitignore` when it has one that does not ignore them yet. The key is never printed, only masked (`spicrawl_live_ab12…cd34`), and API errors are masked too. The result list is on stdout (JSON when it is not a terminal). It ends with a summary of the run. In JSON mode, which is what an agent gets, that is one more element of the array, `{"kind": "report", "connected": ["claude", "cursor"], "skipped": [{"client": "vscode", "reason": "not detected"}], "api_key": "spicrawl_live_ab12…cd34", "next": ["restart Claude Code, Cursor to load the MCP server and the skill", ...]}`: `connected` and `skipped` are client ids, `api_key` is masked (empty when none is configured), and `next` is what the user still has to do. Every other element is what it was (`mcp`, `skill`, `agents_md` or `gitignore` in `kind`), so a consumer that filters by `kind` is unaffected, and stderr stays empty. In human mode (a terminal, or `NO_COLOR`/`CI`) the same summary follows on stderr:

```text
connected: Claude Code, Cursor
Expand Down
4 changes: 2 additions & 2 deletions internal/cmd/empty_results_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
)

// An empty list in human mode is not data (DESIGN.md: stdout is data only):
// stdout stays empty, so `cmd | wc -l` is 0, and the message goes to stderr.
// JSON mode is unchanged: it prints the empty collection.
// stdout stays empty and the message goes to stderr. JSON mode (what a pipe
// gets) is unchanged: it prints the empty collection.
func TestHumanEmptyResultsAreStderrOnly(t *testing.T) {
cases := []struct {
name string
Expand Down
8 changes: 5 additions & 3 deletions internal/cmd/initcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,11 @@ Without a terminal on stdin, --yes is required. See "spicrawl mcp install

Unattended, in one command: "spicrawl init --all --yes --api-key <key>". --all
connects every detected client (and conflicts with --client); --yes skips the
confirmation. --api-key is checked with the API and saved to the config file as
"spicrawl login" does, before any client file is written; a rejected key writes
nothing. The key is then written into each client's MCP config as is, in your
confirmation. --api-key is checked with the API first (a rejected key writes
nothing), then the plan is made and confirmed, the client files are written, and
the key is saved to the config file, as "spicrawl login" does, after those
changes are applied; a refused or declined plan saves nothing. The key is
written into each client's MCP config as is, in your
user-level files (~/.claude.json, ~/.cursor/mcp.json, ...) so the agents work
with nothing exported: --global is implied, and --global=false picks the
project files, which are added to .gitignore when the project has one.
Expand Down
Loading