@insitue/companion
v0.7.7
Published
Local companion process: loopback WS, project sandbox, agent edit gateway.
Maintainers
Readme
@insitue/companion
The local InSitue companion — the loopback WebSocket bridge
between a browser running @insitue/sdk and a claude session
running /insitue:connect (from @insitue/claude-plugin).
Most users don't run this directly. The
@insitue/claude-plugin
MCP server auto-spawns the companion when claude starts, and
cleans it up when claude exits. You only reach for this package
manually if you want to:
- See the companion's logs directly
- Pipe picks to a non-claude tool (Aider, Cursor, your own
script) via
insitue connect - Run a long-lived companion that survives across multiple claude sessions
Run it directly
# Auto-installs from npm if you don't have it locally
npx @insitue/companion@latest devdev is the default command — bare npx @insitue/companion@latest (with no
subcommand) runs dev. Pass -p, --port <n> to bind a port other than the
default 5747.
The companion:
- Binds to
127.0.0.1:5747(loopback ONLY). - Writes
.insitue/session.jsoncontaining a per-session token. - Accepts any
http://localhost:*orhttp://127.0.0.1:*Origin by default (--allow-localhostis on fordev). - Allows additional Origins via
-o https://my-tunnel.cf.
That's it. Your browser-side <InSitueCapture /> finds the
session token via .insitue/session.json, opens a WS, and starts
streaming picks.
Pipe to a tool
# In a separate terminal:
npx @insitue/companion@latest connectconnect opens a WS to the running companion and emits one
formatted block per pick to stdout. Pipe it into any tool:
# Pretty text (default)
insitue connect
# NDJSON for machine consumers
insitue connect --json | jq .CLI flags
| Flag | Default | What it does |
|---|---|---|
| -p, --port <n> | 5747 | Loopback port to bind |
| -r, --root <path> | cwd | Project root the companion scopes file resolution to |
| -o, --origin <url...> | (none) | Additional Origins to allow, ON TOP of the localhost wildcard |
| --strict-origins | off | Disable the localhost wildcard; require explicit -o allowlist |
| -t, --agent-transport <t> | cli-headless | cli-headless, mcp, or sdk for the legacy in-companion agent loop (unused with the claude-plugin flow) |
| --allow-api-key | off | Permit ANTHROPIC_API_KEY to reach a spawned headless claude (bills API, not Pro/Max) |
InSitue Cloud — fix issues locally
Requires a paid InSitue Cloud plan and a signed-in token (see
insitue loginbelow). The core dev-tool commands (dev,connect) remain free and accountless.
The companion CLI includes a set of commands for pulling open issues from your InSitue Cloud project, claiming them, fixing locally, and marking them resolved — all without leaving your terminal.
For the full walkthrough (linking your repo, agent integration, widget inbox) see ../../docs/fix-cloud-issues-locally.md.
Authentication
insitue loginBare insitue login is a tokenless browser login (PKCE) — no token to copy
or paste. It opens your browser, prints the authorize URL plus a short user code
to confirm, and on success saves credentials to ~/.insitue/auth.json and prints
✓ Signed in as <login>. If the token is project-scoped it also prints
Token scoped to project: <id>. It then best-effort auto-links the current repo
to its InSitue project, so an explicit insitue link is usually unnecessary.
By default login runs in "auto" mode: loopback locally, automatically switching
to the device flow when an SSH session is detected (SSH_CONNECTION /
SSH_TTY) or no loopback port is available. You can force either mode:
# Force the device flow (RFC 8628) — best for SSH/remote where the CLI
# can't open a browser or receive a loopback redirect. Prints a URL to
# open in any browser plus a code to confirm, then waits for approval.
insitue login --device
# Force loopback (skip the SSH auto-detect)
insitue login --loopbackPAT fallback (CI / headless / non-interactive) — save a Personal Access Token directly:
insitue login --token pat_live_…Mint PATs at https://app.insitue.com/app/settings/developer. The
~/.insitue/auth.json file is created with 0600 permissions and is never read
by the companion server — only by these cloud-issue CLI commands.
# Optional: point at a self-hosted or staging cloud instance
insitue login --token pat_live_… --host https://your-cloud.example.comINSITUE_API_HOST (env var) overrides the cloud base URL at runtime without
re-running login. Default: https://app.insitue.com. (--host is the only host
override; there is no INSITUE_TOKEN env var.)
Log out
insitue logoutRevokes the token server-side and clears ~/.insitue/auth.json. If the
server-side revoke fails, your local credentials are still cleared and the
command warns that the token may still be active.
Link a repo to a project
insitue link <projectId>Writes <root>/.insitue/project.json (defaults --root to cwd). The .insitue/
directory is auto-gitignored — the companion writes a .gitignore entry on
first use. Find your projectId in the project's Cloud dashboard URL or
settings page.
# Explicit root if you're running from outside the repo:
insitue link proj_abc123 --root /path/to/repoList open issues
insitue issuesPrints the linked project's open issues — ID, title, status, and captured
source location. Reads <root>/.insitue/project.json for the project binding
(pass -r, --root <path> if cwd differs from the repo root).
Claim an issue
insitue claim <id>Marks the issue as claimed (takes it off the queue for other developers) and
prints its full repro to stdout: the captured note and the resolved
file:line (or file:line:col) so you know exactly where to look. Use this
as the starting point for your fix.
insitue claim iss_789xyz --root /path/to/repoResolve an issue
insitue resolve <id> --pr <url>Marks the issue resolved and attaches the pull request URL. Optionally record the branch name:
insitue resolve iss_789xyz --pr https://github.com/org/repo/pull/42 --branch fix/button-overflowRelease an issue
insitue release <id>Returns a claimed issue to the open queue — use this if you claimed something but can't fix it right now and don't want to block others.
Reject an issue
insitue reject <id>Marks the issue won't-fix (status → rejected) and prints
issue <id> rejected. Consumes no agent run. An optional reason can be
recorded:
insitue reject iss_789xyz --reason "by design — not a bug"Config files
| Path | Contents |
|---|---|
| ~/.insitue/auth.json | Auth — { "token": "...", "host": "...", "login": "...", "projectId": "..." }. Created by insitue login. |
| <root>/.insitue/project.json | Per-repo binding — { "projectId": "..." }. Created by insitue link. |
In auth.json, projectId: null means the token is account-wide; a non-null
value means it's scoped to that single project. A token minted by running
insitue login from inside a repo is project-scoped (it can only act on that
one project's issues); dashboard-minted and --token PATs are account-wide.
Both paths live inside .insitue/ directories. The companion auto-writes a
.gitignore entry so neither file is accidentally committed.
Security boundary
The companion is dev infrastructure — it edits files, runs git, and spawns processes. Its trust boundary is:
- Loopback bind only. It refuses any non-127.0.0.1 connection at the socket layer.
- Per-session token written to
.insitue/session.json. Every WS connection must present the token in itshellomessage. The file is gitignored. - Origin pin (defense in depth, not the primary auth).
Allows the localhost wildcard by default for friction-free
dev; pass
--strict-originswith-ofor hardened use.
The companion will refuse to start if NODE_ENV=production.
It is a localhost dev tool by design.
Architecture
┌──────────────────┐ ┌──────────────────┐
│ Browser │ ws://127.0.0.1:5747 │ Companion │
│ @insitue/sdk │ ◄────────────────────────►│ this process │
│ widget │ │ │
└──────────────────┘ │ - resolves │
│ pick → src │
┌──────────────────┐ ws://127.0.0.1:5747/ │ - broadcasts │
│ Subscribers │ insitue/cli │ to subs │
│ (claude-plugin, │ ◄────────────────────────►│ │
│ insitue │ │ │
│ connect, …) │ └──────────────────┘
└──────────────────┘Browser sends { t: "capture", bundle }. Companion resolves
bundle.target.source against .insitue/session.json's root
directory (read filesystem, get the real file:line:col plus
a snippet), broadcasts { t: "broadcast-capture", bundle,
resolved } to all subscribers. Subscribers (e.g. the
claude-plugin MCP server) deliver to claude.
Public API
The package is primarily a CLI but also exports a programmatic surface for embedders / tests:
import { startCompanion, COMPANION_VERSION } from "@insitue/companion";
const server = startCompanion({
port: 5747,
root: process.cwd(),
origins: [],
allowLocalhost: true,
});
// server is a node:http Server — call .close() to stop.Exports:
startCompanion(opts: CompanionOptions): Server— bind a companion to a port. Throws ifNODE_ENV=production.COMPANION_VERSION: string— build-time-inlined package version.CompanionOptions— the full options shape.AgentTransport—"cli-headless" | "mcp" | "sdk".AgentProvider— re-exported from@insitue/agent-corefor tests that want to inject a deterministic provider.
Closed-source dependency disclosure
@insitue/companion depends on
@insitue/agent-core
— a closed-source package that holds the agent edit loop
(claude-code transports, proposal building, edit gateway, git
checkpoint mechanics). The companion ships thin shims under
src/agent/ and src/edit/ that re-export from agent-core so
import paths and the regression test layout stay stable. The
trust boundary documented above (loopback, token, Origin pin,
NODE_ENV-prod refusal) is entirely in this MIT-licensed package
— what crosses into agent-core is already-authenticated frames
on the WS.
If full source-level audit matters for your use case, you can
audit everything inbound to agent-core here (server.ts),
treat the agent edit loop as a black box, and trust that the
file operations it triggers go through your normal git review.
Stability
startCompanion()signature,CompanionOptions, and theinsitue dev/insitue connectCLI flags are the stable consumer surface.- The WS wire format is pinned by
PROTOCOL_VERSIONin@insitue/capture-coreand rejects mismatches at the handshake. Bumping it is a breaking change for the SDK, claude-plugin, and any subscriber. agent-coreexports re-exported through the local shims (src/agent/**,src/edit/**) are NOT a stable surface — preferstartCompanion()if you're embedding.
Versioning
- Major — breaking changes to
startCompanion()/CompanionOptions, CLI flag removals/renames, orPROTOCOL_VERSIONbump (forces consumer pins). - Minor — new CLI flags, additive options, additive WS message types (server backward-compatible).
- Patch — bug fixes, doc updates, transitive dep bumps.
Tests
pnpm testNative Node --test suite covering the WS handshake, capture
resolution, proposal flow, terminal-pipe broadcast, and #162
external-ask routing. Trust-boundary regressions land here
first — if you're changing server.ts, run the suite and add
to it.
Security
Report vulnerabilities privately — see SECURITY.md in the repo root. Especially relevant for this package:
- Any path that lets a non-loopback connection complete the handshake.
- Any path that lets a foreign Origin send authenticated frames (the per-session token + Origin pin are the auth boundary).
- Any path that lets the edit gateway write outside
opts.root. - Token leakage from
.insitue/session.json(the auto-emitted.gitignoreis what stops it from committing — the file's permission scope is the user's session).
License
MIT. See LICENSE.
