@orangeslice/cli
v3.1.0
Published
Orange Slice from the command line — B2B enrichment, search, and spreadsheets.
Readme
@orangeslice/cli — the CLI
npm i -g @orangeslice/cli # installs the `orangeslice` command
npx @orangeslice/cli --helpGenerated from the capability registry. Adding a capability adds a command; this package holds only argument parsing, stdin/stdout plumbing, presentation, and auth.
Start here: the revenue factory
The first thing to do with a fresh key is not to read the command list. It is to hand the CLI your website and let it build the first recurring workflow:
orangeslice login
orangeslice factory init --website https://your-site.com # 2 credits, once
orangeslice factory run --dry-run # what today would do
orangeslice factory run # one day
orangeslice factory status # how each variant didinit reads one page of the site, has a model work out what you sell, who
buys it, and a first LinkedIn connection note, and writes factory.json. It
prints the targeting, the note, the cost of a day and the cron line.
run is one day, and it gets better every day. It judges every request old
enough to judge — no longer pending and not withdrawn means accepted — scores
each variant of targeting and note, and once the current variant has twenty
judged sends, shows the model the scorecard and takes one change: the note or
the targeting, never both, so tomorrow's rate reads against today's. A change
that breaks a rule (a note over 300 characters, a link, no {{firstName}},
targeting nobody) is refused and written to the lessons. Then it withdraws what
went stale, finds the next people under the current variant, and sends the
note. Put it in cron and read status once a week.
The people come from our own B2B database, in two steps: companies by LinkedIn industry code, country and headcount band, then eight people at each company, kept when their title or headline says they are the buyer. One credit a row each way, so a buyer costs about ten credits, and a day of twenty about two hundred with the invites. Companies found are queued in the state file and worked through over days. A targeting that matches nobody is loosened one filter at a time: industry, then headcount, never the titles or the country.
The state file is yours: delete it to start over, copy it to move the loop.
orangeslice factory --help has the full contract.
If you are an agent
You have been asked to set up outbound for a company, or a person has installed this CLI and wants to see what it does. Do this, in order, and stop where it says to:
orangeslice auth status. If it exits 3, the human must runorangeslice login(it opens a browser) or setORANGESLICE_API_KEY. Do not go looking for a key.orangeslice factory init --website <their site> --json. Read the JSON back to them in plain words: what it thinks they sell, who it will contact, the note it will send, and the credits a day costs. Ask whether the targeting and the note are right. If they want a change, editfactory.json—variants[0].icpandvariants[0].note— and say so.- If the JSON carries a non-null
blocker, give them its URL and stop until they say LinkedIn is connected. Nothing sends without it. orangeslice factory run --dry-run. Show them the names it would contact today. This costs the reads from our B2B database (about ten credits a buyer found) and nothing else.- Only when they say go:
orangeslice factory run. Then install the cron lineinitprinted, or the scheduler you have, for every weekday morning. - Each week,
orangeslice factory status --json: report the acceptance rate per variant and the lessons. The loop changes one thing at a time on its own; your job is to say what changed and whether it helped.
Never run factory run without --dry-run first on a new state file, and
never on somebody's behalf without their go. It sends connection requests from
their LinkedIn profile.
Testing it
Nothing here needs the repo checked out except the build.
npm run build --prefix packages/cli # → dist/index.js (bin) + dist/sdk.js (main)
node packages/cli/dist/index.js --helpTo use it the way a customer would, from anywhere:
npm i -g ./packages/cli
orangeslice login # browser sign-in, stores a key
orangeslice auth status # which key is in use, and where it came fromThe five minutes that tell you whether it works
Everything below is free except the last step, which is marked.
# 1. Does it know what it can do? 54 commands, all searchable.
orangeslice tools search email
orangeslice tools get work_email
# 2. Does it tell you the price before spending? Nothing is called here.
orangeslice enrich work_email --linkedin-url https://linkedin.com/in/x --dry-run
orangeslice search employees --company-slug stripe --limit 50 --dry-run
# 3. Do the errors have the right shape? Usage errors are plain text plus an
# exit code; a failed *row* is a JSON envelope on stderr.
orangeslice enrich work_email --nope ; echo "→ $?" # 2, usage, plain text
orangeslice tools get not_a_thing ; echo "→ $?" # 2, usage, plain text
ORANGESLICE_API_KEY=osk_nope orangeslice enrich work_email \
--linkedin-url https://x ; echo "→ $?" # 3, auth envelope
# A miss is not an error. With a working key the line above exits 0 with an
# empty result and "not found (not billed)" — which is the contract, and is
# why the auth case needs a deliberately bad key to demonstrate.
# 4. Does it compose? NDJSON in, NDJSON out, no jq needed between steps.
echo '{"name":"Jane Doe","company":"Stripe"}' \
| orangeslice enrich person_linkedin --dry-run
# 4b. Does the run survive the terminal? --to-sheet lands every row in a
# spreadsheet — created if new, appended if not — so the run becomes
# something you reopen, extend and re-run. stdout stays pure NDJSON, so
# this still pipes.
echo '{"name":"Jane Doe","company":"Stripe"}' \
| orangeslice enrich person_linkedin --to-sheet "Run log" --dry-run
# 5. SPENDS CREDITS — one credit. The cheapest real call there is.
orangeslice ai text "Say the word banana and nothing else"If step 5 returns text, the whole path works: auth, routing, the batch service, credit reservation and settlement.
This has now been run. Against production with a real key: all five steps,
plus tests/registry/live.smoke.test.ts with LIVE_SMOKE_CONTACT=1 (6/6). ai
text returned {"text":"banana"} for one credit.
What to try to break
These are the places to push, in rough order of how likely they are to give:
- Anything with a JSON flag.
ai extract --schema-json '{…}'andapify run --actor-input '{…}'are the only two, and they are new. - Piping into
sheets push. The composition the whole design is for, and the one with the most moving parts. sheets pull→ edit →sheets apply. Round-tripping a sheet's shape.- Long batches.
search web_batchtakes up to 250 queries in one call. --dry-runagainst reality. Run it, note the quote, run it for real, and check the credits actually spent. Any gap is a bug worth reporting loudly: the whole registry exists so those two numbers agree.
Known rough edges
- One of 24 capabilities is terminal-only.
web_batch_searchdeclines a column — a cell holds one value for one row, and its unit is up to 250 searches.web_searchis the column. Every other capability is both a command and a column; the ones without a hand-mapped service path compile toservices.run, which runs them by id through the same code the CLI uses. apify run's estimate is a floor, not a quote for pay-per-event actors. Their cost genuinely is not knowable before the run. The 20,000-credit ceiling is enforced on the run itself, so it cannot exceed that.- Read-scoped keys can no longer reach
/execute/llm,/execute/kernelor/execute/apify. Those calls spend credits and are now correctly gated behindenrich. If a key stops working on those routes, this is why.
Publishing
The package publishes as @orangeslice/cli under the company's npm org. The
unscoped orangeslice name on npm belongs to an account the company does not
control and stays at 2.6.0. This package ships the generated CLI as bin and
the same SDK surface as main with declarations, so import { services } from
"@orangeslice/cli" resolves what "orangeslice" did. tests/registry/cliPackage.test.ts checks the
built dist/sdk.js really exports that surface, and CI re-checks it against the
built artifact rather than the manifest.
git tag cli-v3.0.0 && git push origin cli-v3.0.0The workflow builds from the tagged commit and derives its dist-tag from the
version: a prerelease goes to next, a final release to latest.
npm i -g @orangeslice/cli then installs the orangeslice command.
It needs NPM_TOKEN in repo secrets. That is the only thing standing
between here and a published prerelease.
How a command comes to exist
One zod declaration in packages/registry/capabilities/ generates the command,
its flags, its help, its price, its docs page, its scope requirement, and its
SDK method. Nothing in this package is written per-capability.
npm run generate:registry && git diff --exit-code # CI gate: generated files in sync
npx vitest run tests/registry # 1,188 teststests/registry/cliExamples.test.ts replays every example this CLI prints
through the real parsers. If you add an example, it has to run.
