@every-env/spiral-cli
v1.11.0
Published
CLI for Spiral API - generate writing in your voice
Readme
@every-env/spiral-cli
A command-line interface for Spiral, the agent that writes in your voice.
Quickstart
npx @every-env/spiral-cli@latest login
spiral write "a tweet about shipping fast" --instantThat's it. login opens a browser to authenticate, write produces a draft in your voice.
Installation
# Run directly (no install needed)
npx @every-env/spiral-cli@latest --help
# Or install globally
npm install -g @every-env/spiral-cliRequires Node.js >= 18.
Authentication
Browser sign-in (recommended)
# Print the sign-in link and open it in your browser
spiral login
# Or pass an API key directly
spiral login --token spiral_sk_...
# Check status
spiral auth status
# Logout
spiral logoutspiral login prints the sign-in URL and opens it in your browser, then waits
for you to approve. If the browser can't open (e.g. over SSH), just paste the
printed URL. No code to copy back — the token is delivered straight to the CLI
once you approve.
spiral login and spiral logout are shortcuts for spiral auth login and
spiral auth logout — use whichever reads better.
Get your API key at app.writewithspiral.com/settings/api-keys.
Agent sign-in (no API key in the transcript)
When an AI coding agent runs the CLI, it can sign its human in without ever
handling an API key — so no spiral_sk_... key leaks into the chat
transcript, tool logs, or session history.
# 1. Agent starts a sign-in and gets a shareable link (non-blocking, exits 0).
spiral login --json
# → { "status": "pending",
# "auth_url": "https://app.writewithspiral.com/connect?user_code=ABCD-2345",
# "user_code": "ABCD-2345",
# "expires_in": 900,
# "poll_command": "spiral auth status --json" }
# 2. Agent hands `auth_url` to its human, who opens it and approves in a browser.
# 3. Agent polls status until the token lands (it claims the token for you).
spiral auth status --json
# → { "authenticated": false, "status": "pending", "auth_url": "..." } (still waiting)
# → { "authenticated": true, "status": "authenticated", "source": "stored" } (done)spiral auth status opportunistically completes a pending sign-in: once the
human approves, the next status call claims the token, stores it locally, and
reports authenticated: true. Subsequent spiral write calls then just work.
If a credential is already present (a prior spiral login or SPIRAL_TOKEN),
spiral login --json returns { "status": "already_authenticated",
"authenticated": true, "prefix": "..." } and starts no new flow — there's
nothing to approve. To sign in as a different account, run spiral logout
first. So an agent should treat both already_authenticated and a later
authenticated: true from auth status as "done."
This path is auto-selected whenever the CLI isn't attached to an interactive
terminal (which is how agents run it). Use --no-browser to force it from a
real terminal too (prints the link as text instead of opening a browser), and
--json for machine-readable output. The existing --token, SPIRAL_TOKEN,
and spiral setup --pairing-code paths are unchanged.
Environment variable
For CI/CD and agent environments:
export SPIRAL_TOKEN=spiral_sk_...SPIRAL_TOKEN takes precedence over any token stored by spiral login, so
you can override locally-stored credentials without logging out.
Usage
Write content
spiral write "a blog post about shipping fast" # Interactive (may ask questions)
spiral write "a tweet about AI" --instant # Skip clarifying questions
spiral write "make it shorter" --session <id> # Refine existing drafts
spiral write "..." --style <id> --workspace <id> # With style and workspace
spiral write "..." --num-drafts 3 # Multiple variations
spiral write "..." --instant --json # Machine-readable output
echo "prompt" | spiral write --instant --json # Piped inputSet up your voice
Spiral writes in your voice. Give it a few writing samples so drafts sound like you from the first one — no per-request style wrangling.
spiral onboard # Check voice setup and how to finish it
spiral samples # List your writing samples
spiral samples add "a paragraph you actually wrote" # Add a text sample
spiral samples add https://yourblog.com/a-post # A URL → one sample
spiral samples add https://yourblog.com --site # --site crawls the whole site into samples
cat draft.md | spiral samples add # Pipe text inSamples added at the workspace root feed Auto Style, so every spiral write
leans on them automatically — pin a specific --style only when you want a
particular voice. Target a named style or team workspace with --style <id> /
--workspace <id>. spiral onboard reports whether you have enough to write in
voice yet and what to add next (add --json for agents).
Create a named style or a new workspace to organize voices, samples, and prompts:
spiral styles create "Punchy LinkedIn" --channel linkedin # New empty style
spiral samples add "a post you wrote" --style <id> # then add samples to it
spiral workspaces create "Client A" --description "Work for A"
spiral workspaces create "Acme" --team <team-id> # team workspace (needs a team plan)A new style starts empty; add the user's writing with spiral samples add
--style <id>. A workspace groups styles, samples, and prompts (one per client
or project); pass its id to --workspace on later commands (e.g. spiral
samples add "..." --workspace <id>). A style id goes to --style, a workspace
id to --workspace; they are not interchangeable.
Personalize and humanize existing text
Two single-shot commands for transforming text you already have. Both never ask clarifying questions, take ~3-8 seconds, and accept positional input or piped stdin. Use them when you don't need a full draft — just a voice swap or a clean-up.
# Rewrite text in your unique writing voice (uses your Spiral style guide).
spiral personalize "their text here"
spiral personalize "their text" --style <id> # Use a specific style
spiral personalize "their text" --channel x # Hint a channel-specific style
echo "their text" | spiral personalize # Or pipe from anywhere
# Apply your writing rules + remove AI tells (em-dashes, "delve",
# hype words). No voice rewrite — just mechanical cleanup.
spiral humanize "their text here"
pbpaste | spiral humanize | pbcopy # Clean what's on the clipboardBy default both print plain text to stdout — perfect for piping. Add --json
for the full response — personalize returns { text, style_used, quota_remaining },
humanize returns { text, quota_remaining } (no style_used, since there's
no voice rewrite).
When in doubt: personalize includes the humanize cleanup as its final step,
so reach for humanize only when you specifically don't want a voice rewrite.
Browse resources
spiral styles # List writing styles
spiral workspaces # List workspaces
spiral prompts # List saved prompts (reusable templates)
spiral prompts get <command> # Print a saved prompt's text
spiral sessions # List past conversations
spiral drafts --session <id> # View drafts from a session
spiral quota # Check session quota and plan
spiral prime # Full API documentationSaved prompts are reusable templates you create in the Spiral app (they appear
there as /slash-commands). Write with one by passing its command to --prompt;
Spiral prepends the template server-side, and an unknown command fails fast
with the list of available commands:
spiral write "This week: our v2 launch." --prompt blog-introspiral prompts get <command> prints a template's raw text when you want to
pipe it somewhere else.
Agent setup
For AI agents (Claude Code, Plus One, etc.), copy the install command from Settings → Agents and paste it into your agent. The command looks like:
spiral setup --pairing-code <single-use-code>The pairing code is single-use and expires in 15 minutes. The CLI exchanges it for a fresh API key the first time it runs — no long-lived secret in the install command itself.
The legacy spiral setup --token spiral_sk_... form still works for older
install commands that already embedded a raw key.
This validates the resulting key and prints a memory block for the agent to save. The
block tells the installing agent when to reach for each command — write
for new content, personalize for "rewrite in my voice" requests, humanize
for "remove AI tells" requests — so the agent picks the right tool without the
user having to spell it out every time.
File attachments
Pass reference documents directly via --file (repeatable):
spiral write "Turn this into a blog post" --file quarterly-report.xlsx --instant
spiral write "Compare these two decks" --file deck-a.pdf --file deck-b.pdf --instantSupported formats: PDF, DOCX, PPTX, XLSX, HTML, EPub, TXT, MD, CSV, JSON, XML. Max 5 files, 10MB each. Spiral extracts text automatically — no need to paste file contents into the prompt.
Flags
| Flag | Description |
|------|-------------|
| --json | Machine-readable JSON output |
| --session <id> | Session ID for multi-turn / refinement |
| --style <id> | Writing style ID |
| --workspace <id> | Workspace ID |
| --file <path> | Attach a file (PDF, DOCX, XLSX, etc.) — repeatable |
| --instant | Skip clarifying questions |
| --num-drafts <n> | Number of drafts (1-5, default 1) |
| --channel <c> | Channel hint (x, linkedin, blog, ...) for personalize and styles create |
| --description <t> | Description for styles create / workspaces create |
| --team <id> | Team UUID for workspaces create (creates a team workspace) |
| --token <key> | API key for auth/setup |
| --no-browser | login: print the sign-in link and exit instead of opening a browser (agent path) |
| --debug | Show debug info on errors |
| --help, -h | Show help |
| --version, -v | Show version |
Agent-native usage
spiral-cli is designed to work in any environment — terminals, Docker containers, subprocess shells, CI pipelines. In non-interactive environments (no TTY), it automatically:
- Skips spinners and terminal animations
- Outputs plain text without ANSI color codes
- Returns raw markdown instead of terminal-formatted text
- Times out stdin reads to avoid hanging
# Typical agent workflow
spiral prime # Get API docs
spiral onboard --json # Confirm the user's voice is set up first
spiral styles --json # Discover styles
spiral write "a launch tweet" --instant --json # Generate content
spiral write "make it punchier" --session <id> --json # Refine
spiral personalize "$USER_TEXT" --json # Rewrite in user's voice
spiral humanize "$USER_TEXT" --json # Strip AI tells, no voice rewriteExit codes
| Code | Meaning | |------|---------| | 0 | Success | | 1 | General error | | 2 | Authentication required | | 3 | API error (quota, rate limit, timeout) | | 4 | Network error | | 5 | Invalid arguments |
Subscription required
The Spiral API is paid-only. Each call to spiral write, spiral personalize, or spiral humanize requires:
- An authenticated session (
spiral login). - A paid Spiral plan (Personal, Team, or an active Every Bundle subscription).
If a request can't be served, the CLI prints a clear next step:
- Invalid or expired API key (HTTP 401):
Invalid or expired API key. Run \spiral auth login` to re-authenticate.` - Free-tier account (HTTP 402): a Stripe Checkout link to subscribe — open it in your browser, complete payment, and re-run your command.
- Paid plan over quota (HTTP 402, Personal only): a Stripe Checkout link for a one-time top-up. Team and Trial accounts get a clear quota message but no top-up link (those tiers don't support one-off top-ups; subscribe to a Personal plan, wait for the period to reset, or contact your team admin).
In --json mode the same information is returned in structured form so agents can parse it:
{
"error": "ApiError",
"message": "A Spiral subscription is required to use the API. Visit app.writewithspiral.com and upgrade to a paid plan.",
"statusCode": 402,
"upgrade_url": "https://checkout.stripe.com/c/pay/cs_test_..."
}When the URL points at Stripe Checkout, the post-payment redirect lands on a public confirmation page that tells the user to return to the CLI — useful when the browser isn't signed in to Spiral, or is signed in as a different account than the CLI's API key.
Environment variables
| Variable | Description |
|----------|-------------|
| SPIRAL_TOKEN | Auth token (alternative to spiral login; takes precedence when set) |
| SPIRAL_API_URL | Override API endpoint (default: https://api.writewithspiral.com) |
Writing styles
Create writing styles at app.writewithspiral.com/writing-styles to customize Spiral's output to match your voice.
License
MIT
