@aptivus/connector

中文文档
0.1.12发布于 2026/10/30SEE LICENSE IN LICENSE.mdTypeScriptES Module近 30 天下载 2328

Aptivus connector for Claude Code: hooks, local spool, redaction, flush.

原始使用文档
文档目录12 节
  1. Platform support — read this before installing
  2. Windows
  3. What it installs, and where
  4. Removing the connector tells the organization
  5. Several accounts on one machine
  6. What leaves the machine
  7. How the pipeline runs
  8. Correlation
  9. Compatibility matrix
  10. Auth server contract
  11. Token storage
  12. Develop

@aptivus/connector

The Aptivus agent for Claude Code. Binary: aptivus. Runs on the developer's machine and is the only Aptivus component that ever sees unredacted text.

npx @aptivus/connector login      # authorize this machine in the browser
npx @aptivus/connector install    # add the hooks; prints exactly what it wrote

Platform support — read this before installing

Linux is supported and verified end to end.

macOS is supported; verified by DIR on a real machine on 2026-10-23.

Windows is supported through Git for Windows, and is the one platform that has not yet been run by a person: it was built against the documented behaviour of Git Bash and is unit-tested by injection on Linux (D-052). See Windows below for what it needs and how to check it.

Any other platform is declined by aptivus install rather than given hook entries that would never fire; --force overrides that for whoever wants to find out. (D-046, D-052.)

Windows

The hook is still the same POSIX sh script; on Windows it runs under the bash that Git for Windows ships, which Claude Code on Windows requires anyway. aptivus install finds that bash.exe and registers each hook entry as

"C:/Program Files/Git/bin/bash.exe" "C:/Users/<you>/.aptivus/bin/aptivus-hook" prompt

Double quotes, because cmd.exe cannot read single ones and bash reads both. Forward slashes, because every party involved — cmd.exe, bash, Node — accepts them, and a backslash is an escape character inside the script. The paths the script holds (C:/Users/<you>/.aptivus) name exactly the directories the Node side reaches as C:\Users\<you>\.aptivus; ~/.aptivus/current and the per-profile spool work unchanged.

Where install looks for bash.exe, in order — it takes the first that exists:

  1. CLAUDE_CODE_GIT_BASH_PATH, the variable Claude Code itself honours
  2. where bash.exe, keeping only a Git for Windows bash (C:\Windows\System32\bash.exe is WSL's launcher and is skipped)
  3. git --exec-path, walking up to the install's bin\bash.exe
  4. %ProgramFiles%\Git\bin\bash.exe
  5. %ProgramFiles(x86)%\Git\bin\bash.exe
  6. %LOCALAPPDATA%\Programs\Git\bin\bash.exe (a per-user install)

If none is found, install refuses, says how to install Git for Windows (winget install --id Git.Git -e --source winget), and writes nothing. If Git lives somewhere unusual, set CLAUDE_CODE_GIT_BASH_PATH to the full path of bash.exe and re-run. aptivus status and aptivus doctor print the platform and the bash the hook runs through; config.json records it as bash_path.

File modes on Windows. NTFS has no 0700/0600. The modes in the table below are written and ignored; the secrecy of credentials.json and of the spool rests on the user profile's ACL, which keeps C:\Users\<you> to that user, administrators and SYSTEM. doctor reports those entries as "user profile ACL" rather than failing a check that cannot pass.

What the hook needs from Git Bash: tr, cut, date, cat, mv, mkdir, nohup and an emulated /dev/null — all shipped. Git Bash has no setsid, so the flusher is started with nohup … &, the script's existing fallback. WSL, Cygwin and a bare MSYS2 are not used even when present.

Checklist for the first run on a Windows machine (DIR, pending):

  1. Install Git for Windows (winget install --id Git.Git -e --source winget), then open a new PowerShell.
  2. npx @aptivus/connector@latest install — it must print the "…bash.exe" "…aptivus-hook" entries it wrote, then npx @aptivus/connector login.
  3. Open Claude Code, send one prompt, run aptivus status: the spool inbox count goes to 1 and, after the response, aptivus flush (or the detached flusher) sends it — the session appears in the app.

What it installs, and where

aptivus install prints this list; it is reproduced here so you can read it before running anything.

Everything below profiles/<name>/ belongs to one account. <name> is the active profile — see Several accounts on one machine.

PathWhatMode
~/.aptivus/bin/aptivus-hookA POSIX sh script. Reads current to find the active profile, appends the hook payload to that profile's spool, exits 0. No Node, no network0755
~/.aptivus/currentThe active profile's name0600
~/.aptivus/unattributed/Events the hook could not attribute to any profile. Never sent on their own0700
~/.aptivus/profiles/<name>/spool/inbox/One small file per hook event, written by the script0700
~/.aptivus/profiles/<name>/spool/outbox/Redacted batches waiting to be sent0700
~/.aptivus/profiles/<name>/spool/quarantine/Batches the server rejected as invalid, and inbox files the flusher could not read. Both are redacted before they land here (an inbox file that cannot be redacted is deleted, never kept in the clear); never retried, kept for inspection0700
~/.aptivus/profiles/<name>/spool/state.jsonSequence counters, transcript cursors, the account those cursors belong to, and sha256 hashes of prompt text (for joining hook and transcript prompts). No text0600
~/.aptivus/profiles/<name>/credentials.jsonThat account's device token0600
~/.aptivus/profiles/<name>/config.jsonURLs, org, capture mode. No secrets0600
~/.aptivus/profiles/<name>/device.jsonThat profile's install_id0600
~/.aptivus/profiles/<name>/logs/connector.logCounts, rule ids, errors. Never prompt text, tokens or keys0600
~/.claude/settings.jsonFive hook entries, listed below. Everything else in the file is untouched; a timestamped backup is written first. On Windows each entry names Git Bash explicitly (see Windows)—

Hook entries added (each runs the same script with a different argument):

EventArgumentTimeoutWhat the script does
UserPromptSubmitprompt5sspool the payload
SessionStartstart10sspool the payload
Stopstop10sspool the payload, start the flusher detached
SubagentStopstop10ssame as Stop: a subagent finishing is when its transcript is worth reading
SessionEndend10sspool the payload, start the flusher detached

The script prints nothing (a UserPromptSubmit hook's stdout would be injected into the model's context) and exits 0 on every path, including a full disk or a missing spool directory. Measured: ~7ms per invocation, p95 8ms, including the one extra read that resolves the active profile; the budget is 50ms and the test suite asserts it.

aptivus uninstall removes exactly those five entries and nothing else. aptivus install --managed prints the fragment for an org-wide managed-settings.json instead of writing anything.

Removing the connector tells the organization

aptivus uninstall and aptivus logout both say, before they do anything, that the organization's owners and admins will learn that capture stopped on this machine, and when. They ask you to confirm and offer you a reason, which the organization sees as you typed it. Then one small message goes to the ingest server — which command ran, the reason, the connector version, the time — and only after that are the hooks or the token removed.

$ aptivus uninstall
Capture stops on this machine once the hooks are removed.
Acme's owners and admins will be told that it stopped, when, and the reason you give below.
Continue with uninstall? [y/N] y
Reason (optional, shown to the organization): laptop handed back
Acme has been told.
Removed UserPromptSubmit, SessionStart, Stop, SessionEnd entries from ~/.claude/settings.json

--yes skips the question and --reason "<text>" supplies the reason, for scripts. Without a terminal and without --yes the command refuses rather than guesses. If the server cannot be reached, the command says so and continues: this is your machine, and nothing here waits on a server to let you leave. A personal workspace notifies nobody — its only owner is you.

What the connector will never do: keep running after you removed it, put itself back, or hide. If you delete the package or the hook entries by hand, nothing is sent, because nothing is left to send it; the organization then sees the machine go quiet (three working days without a batch while you keep signing in to the web app) on its Settings → Agent page, which is the honest version of the same fact. Organizations that need removal to be impossible get that from Claude Code's managed settings, not from this package — see docs/MANAGED-SETTINGS.md.

APTIVUS_HOME overrides ~/.aptivus. CLAUDE_CONFIG_DIR overrides ~/.claude. APTIVUS_PROFILE overrides the active profile for one command.

Several accounts on one machine

A device belongs to one person: the server refuses to let a second account claim an install_id that already belongs to someone else, and that guard is not negotiable. So a second account gets its own profile, with its own device identity, token, settings and spool (D-039).

aptivus login --profile work      # sign in, creating the profile
aptivus profile list              # every account on this machine
aptivus profile use personal      # switch
aptivus profile                   # which one is active

Your first aptivus login needs none of this: it creates a profile named after the organization you signed into.

A profile keeps its account for life, and --profile is how a second one arrives. Plain aptivus login renews the token for the account the profile already holds; sign a different account in and it is refused, with the command to run instead. Picking your other organization at the approve screen counts as a different account too, because a device row exists per organization.

This is enforced rather than advised because the alternative is invisible. state.json records how much of each session has already been sent to that organization. Reuse the directory for another account and a session already in progress splits: the first organization keeps what it had, the second receives the rest under the same session id and start time, and nothing marks either half as partial. The counts agree at every layer because each layer counts its own half. Measured before the guard existed: 1843 events in one organization and 1 in the other, for one conversation (D-049).

The spool also records the account it belongs to, so the flusher stops rather than sending from another account's cursors — aptivus status and aptivus doctor both report it, and nothing is read or drained while it holds.

What you type under one profile can only ever be sent to that account's organization. The spool lives inside the profile, so a flusher running while personal is active cannot see — let alone send — anything captured under work. Switching profiles leaves the other profile's queue where it is; aptivus status tells you it is waiting, and it is sent the next time that profile is active. Sharing one spool between accounts would have been simpler to build and is exactly the leak this design exists to prevent.

If the hook runs when no profile can be resolved — during an upgrade, say — the event goes to ~/.aptivus/unattributed/ rather than being guessed into a profile. Nothing is lost, and nothing is attributed to the wrong organization. Those events are adopted automatically when exactly one profile exists, because then there is no ambiguity; with two or more, aptivus status reports them and you decide.

Upgrading from a version before profiles needs no action: the first command, or the first flush, moves your existing setup into a profile named after your organization, token and queued spool included.

Each profile is a separate device row in the dashboard, which is honest — they are separate identities. Grouping them under one machine is a later refinement.

What leaves the machine

Only what @aptivus/schema defines, and only after redaction on this machine. Per session: the working directory, the normalised git remote (host/owner/repo, credentials stripped) and branch, the Claude Code version. Per prompt: the redacted text and its original length. Per model response: the redacted visible text, its length, the model, token usage, the names of tools called. Per tool call: the redacted, size-capped input and the length of the result. Never, by default: thinking content or tool results — lengths only. Never, in metadata mode (aptivus config capture_mode metadata): anything beyond lengths and counts.

Tool results and thinking are opt-in per organization (connector 0.1.6, migration 0034). An owner or admin turns each on under Settings → Organization; the connector learns it the same way it learns image capture (GET /v1/policy, cached ten minutes, shown by aptivus status) and only then asks the transcript parser to keep the text. Both go through the same redaction as prompts on this machine. A tool result over 64 KB or a message's thinking over 32 KB is sent as its length only, never truncated. Ingest re-checks the organization's switch on every batch and stores nothing the organization has not allowed, whatever a connector sends. Metadata mode overrides both switches.

Agent runs are part of the session (connector 0.1.7, migration 0035, D-059). When Claude Code spins up a subagent — a fork, an Explore agent — it writes that agent's conversation to its own file next to the session's (<session>/subagents/agent-<id>.jsonl). The flusher parses those files too, from where it left off each time, and every event from one carries an agent reference: the run's id, the Agent call in the parent that spawned it, its type and description. What is captured is exactly what is captured for the main conversation — the brief it was given as its prompt, its replies and token usage, its tool calls with inputs, and tool results or thinking only under the same organization switches. An agent's brief is not counted as one of the developer's prompts; its turns and tool calls are counted as work.

Every event records which redaction rules fired, never what they matched.

How the pipeline runs

hook ──▶ spool/inbox ──▶ flusher: ──▶ spool/outbox ──▶ POST /v1/ingest
                          · read inbox, assign seq per session
                          · parse the transcript (version-aware)
                          · join prompts to responses (prompt id, then text)
                          · redact
                          · write batches, then state, then delete inbox
                          · send with backoff; quarantine 4xx
  A hook-started flusher waits up to 60s (APTIVUS_FLUSH_LOCK_WAIT_MS) for a
  running one to finish, then runs; after each pass it re-scans the inbox
  and runs again while new files exist. So the SessionEnd that lands one
  second after the Stop hook's flusher started is never left for the next
  session. `aptivus flush` waits 5s and says if another flusher is running.

The flusher is started detached by the stop and end hooks so Claude Code never waits for it. It takes a lock, so two never run at once. Everything up to "send" works offline; a developer on a plane loses nothing, and a retry re-sends the identical bytes under the same client_batch_id so the server collapses it.

aptivus flush runs it by hand. aptivus doctor checks the installation.

Correlation

A prompt arrives twice: from the hook (immediately, with the text) and from the transcript (later, with Claude Code's promptId). The flusher joins them by prompt id when the hook provides one, else by text. A response is attributed to its prompt through the transcript's parent chain; correlation on the event says how sure we are (exact, inferred, unmatched). A prompt the hook never saw — the hook was installed mid-session, say — is recovered from the transcript instead of lost.

Compatibility matrix

The hook contract is documented and stable. The transcript format is internal to Claude Code and drifts; the parser dispatches on the version recorded in the transcript and keeps raw lines it cannot parse (parse_failure events) so they can be reprocessed later. See src/transcript/ and its fixtures.

Claude CodeStatusNotes
2.1.278verifiedcanary fixture in test/fixtures/transcripts/
2.1.x (other)expected to worksame parser; add a fixture when verifying
< 2.1unsupportedno fixture; data is kept as parse_failure events

A new release follows the runbook in docs/AGENT-ROLES.md §Version compatibility.

Shell coverage. The hook is written for POSIX sh. The suite here runs it under whatever sh the machine has (bash on this one; dash is not installed). Before the first external release, CI must run test/hook-script.test.ts under dash (Debian/Ubuntu /bin/sh), macOS /bin/sh and Git for Windows' bash, including the hostile payload set. "Reads as POSIX" is not a test. The Windows pieces that can be tested without Windows — bash discovery, the settings entry, path spelling — are in test/windows.test.ts.

Auth server contract

aptivus login is OAuth 2.0 Authorization Code + PKCE with a loopback redirect. The dashboard (apps/web) must implement:

GET <auth_url>/cli/authorize — a page. Query parameters: response_type=code, client_id=aptivus-connector, code_challenge, code_challenge_method=S256, redirect_uri (always http://127.0.0.1:<port>/callback), state, install_id, device_name, connector_version. The page authenticates the user, lets them pick an org, records the device, and redirects to redirect_uri?code=…&state=… (or ?error=access_denied&state=…).

POST <auth_url>/api/cli/token — JSON body { grant_type: "authorization_code", client_id, code, code_verifier, redirect_uri, install_id }. The server verifies S256(code_verifier) equals the stored challenge, mints a device-scoped token, stores only its hash (agent_tokens), and returns:

{
  "token": "apt_live_<32 base64url chars>",
  "token_hint": "apt_live_3f9a2b…",
  "org":  { "id": "uuid", "slug": "acme", "name": "Acme" },
  "user": { "email": "[email protected]" },
  "ingest_url": "https://ingest.aptivus.dev",
  "expires_at": null
}

Errors use the IngestError shape from @aptivus/schema so the CLI can show the request_id. The CLI prints the authorize URL in every case, because SSH sessions have no browser to open.

Token storage

Phase 1 stores the token in ~/.aptivus/profiles/<name>/credentials.json with mode 0600. The OS keychain backends sit behind the same CredentialStore interface and are not implemented yet, for two concrete reasons: macOS security add-generic-password -w puts the secret on the process argv, visible in ps for the life of the call, and Linux secret-tool blocks on a keyring-unlock prompt that a detached background flusher can never answer. Both need a design, not an afternoon. Tracked as a Phase 1 follow-up.

Develop

npm test -w @aptivus/connector      # runs on source via Node's type stripping
npm run build                        # from the repo root
APTIVUS_HOME=/tmp/x CLAUDE_CONFIG_DIR=/tmp/c node -e 'import("./src/cli.ts").then(m=>m.main(["install"]))'

One runtime dependency: zod (D-017). The wire contract, @aptivus/schema, is compiled into the published bundle rather than installed alongside it — one package, one version, nothing for a user to resolve.

Platform support is stated at the top of this file and enforced by aptivus install, not left to be discovered.

版本与发布记录

@aptivus/connector 共发布 13 个版本,首次发布于 2026/10/21;过去 12 个月发布了 13 个版本。

版本发布日期距今
0.1.12latest2026/10/308 天前
0.1.112026/10/299 天前
0.1.102026/10/2513 天前
0.1.92026/10/2414 天前
0.1.82026/10/2414 天前
0.1.72026/10/2414 天前

关于 @aptivus/connector 的常见问题

@aptivus/connector 是什么?

Aptivus connector for Claude Code: hooks, local spool, redaction, flush.

如何安装 @aptivus/connector?

在项目目录运行 npm install @aptivus/connector,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 aptivus,可以用 npx aptivus 直接运行。

@aptivus/connector 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@aptivus/connector 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@aptivus/connector 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.6。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@aptivus/connector 可以商用吗?

@aptivus/connector 使用 SEE LICENSE IN LICENSE.md 许可。许可条款写在包内的独立文件中,请阅读该文件确认可否商用。以上为概括,不构成法律意见,请以许可原文为准。

@aptivus/connector 的下载量有多少?

根据 npm 官方下载统计,@aptivus/connector 在 2026/10/07 – 2026/11/05 这 30 天内被下载 2,328 次,日均约 78 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@aptivus/connector 还在维护吗?

最新版本 0.1.12 发布于 2026/10/30(8 天前);至今共发布 13 个版本,其中过去 12 个月发布了 13 个;首次发布于 2026/10/21。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。