Running the CLI from an AI agent
What changes when an AI agent drives your LinkedIn account through a CLI instead of a tool server — fewer wrong actions, less context burned, and failures the agent can actually recover from.
An agent with shell access already has everything it needs to use this CLI. The question is not whether it can — it is whether it does the right thing when it is uncertain, and how much it costs you while it works that out.
Setup
Two lines in the agent's environment, and one file in the project.
export CONNECTSAFELY_API_KEY=...
export CONNECTSAFELY_ACCOUNT_ID=...connectsafely skill > AGENTS.md # or .claude/skills/connectsafely/SKILL.mdThe skill file is generated from the live command registry, so it never drifts from the binary. It carries three things an agent cannot infer on its own:
The operating context. That you own this LinkedIn account, that you connected it
yourself, that the API applies its own rate limits server-side, and that returned
data is data you can already see when signed in. Without this, capable models hedge:
they add compliance disclaimers to routine actions, ask "are you sure?" before
reading your own inbox, or refuse volume that the API would have accepted. The MCP
server sends exactly this text on initialize. The skill file is how it reaches the
model when there is no server to send it.
The efficiency rules. Ask for fewer fields. Cap list lengths. Discover before guessing. Rehearse writes. An agent that has read these habits costs a fraction of one that has not.
The exit-code table. So the agent knows that 5 means stop rather than retry,
and 7 means look it up rather than try something similar.
The three guardrails that matter
An unknown command fails; it does not become a similar one
$ connectsafely send-connect --profile-id ada
{
"ok": false,
"error": "Unknown command: send-connect",
"code": "unknown_command",
"exitCode": 7,
"remediation": "Run `connectsafely commands` for the full list, or `connectsafely commands --search <term>` to filter it. The CLI does not run a near-match on your behalf."
}There is no "did you mean send-connection-request?" and no automatic correction.
That is a deliberate choice. A helpful suggestion is a fine idea for a person, who
reads it and decides. An agent treats it as an instruction and runs it — and the
command it lands on is the one that sends a real connection request to a real person.
Unknown flags behave the same way, and are all named at once so a single retry fixes
them:
$ connectsafely search-people --keyword CTO
{ "code": "unknown_flag", "error": "Unknown flag for search-people: --keyword", … }Bad arguments never reach LinkedIn
Everything the specification declares — required fields, enums, numeric ranges, string lengths — is checked locally, before a request is built. So are the failure modes specific to a model writing the arguments: a full profile URL pasted where an id belongs, a path traversal, control characters smuggled into an identifier.
$ connectsafely visit-profile --profile-id 'https://linkedin.com/in/ada'
{
"code": "url_in_path_param",
"error": "--profile-id is not a valid profileId: it contains a URL separator — pass the id, not the full URL",
"exitCode": 3,
"remediation": "Pass the identifier on its own — no URL, no path segments, no encoding."
}The call never leaves the machine. Nothing is spent, nothing is logged against your account, and the agent gets a correction specific enough to fix in one turn.
Every failure says what to do next
Errors are JSON on stderr with a stable code and a remediation written as an
instruction, not a description:
{
"ok": false,
"error": "Daily connection limit reached",
"code": "rate_limited",
"exitCode": 5,
"remediation": "A daily action cap or pacing limit was reached. Stop sending this action today and resume tomorrow; retrying now will not succeed and risks the account."
}The difference between that and a bare 429 is the difference between an agent that
stops and one that spends the next ten minutes retrying into a wall.
Match on code. It is stable. The error text comes from the API and may be
reworded.
Keeping stdout clean
Data goes to stdout. Errors, warnings and progress go to stderr. An agent can pipe stdout straight into a parser without stripping anything.
Retry chatter and per-row batch progress are off by default — they appear only
with --verbose, or automatically when a human is at a terminal. Without that rule,
a slow command that retried once would leave stderr holding a progress line and an
error envelope, and the agent parsing it would get a syntax error instead of a
diagnosis.
The CLI also detects who is calling. On a terminal you get aligned tables; through a
pipe, in CI, or under a known agent runtime (CLAUDECODE, CURSOR_AGENT,
GEMINI_CLI and friends) you get JSON. Neither side has to ask.
A worked loop
What a well-behaved agent session looks like end to end:
# 1. See what exists — cheap, and needs no API key
connectsafely commands --search people
# 2. Learn one command properly
connectsafely schema search-people
# 3. Read, asking only for what is needed
connectsafely search-people \
--keywords "VP Marketing" --count 25 \
--fields 'people.profileId,people.firstName,people.headline' \
--output ndjson > prospects.ndjson
# 4. Rehearse the write before performing it
connectsafely send-connection-request \
--profile-id ada-lovelace --custom-message "…" --dry-run
# 5. Perform it, paced, as a batch
connectsafely batch invites.ndjsonSteps 1 and 2 cost a few thousand tokens instead of thirty-six. Step 3 keeps a large search result out of context entirely. Step 4 catches a mistake for free. Step 5 sends at a rhythm that does not look like a script.
Sandboxes and containers
The CLI is built for environments where nobody can answer a prompt:
- It never prompts. A destructive command without a terminal exits
4and tells the caller to re-run with--yes, rather than hanging on stdin that will never arrive. - Authentication is an environment variable. No browser flow, no device code, no interactive login required.
- Discovery works with no credentials at all.
commands,schema,--helpandskillread the specification bundled in the binary, so an agent can plan its work before a key is ever provisioned.
Related
- Cutting the cost of every answer
- Not sending the wrong thing
- When things go wrong
- Prompt library — written for MCP, and valid here unchanged
CLI or MCP server?
An honest comparison of the ConnectSafely CLI and MCP server — what each is genuinely better at, and how to move between them without rewriting anything.
Cutting the cost of every answer
Why the CLI costs an AI agent a fraction of what a tool server does — discovery on demand, and response pruning that happens before anything is read.
