@gullabs/claude-cli 0.6.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/NOTICE ADDED
@@ -0,0 +1,6 @@
1
+ any-llm
2
+ Copyright 2026 Gul Labs
3
+
4
+ This product includes software developed at Gul Labs (https://github.com/gul-labs).
5
+
6
+ Licensed under the Apache License, Version 2.0. See LICENSE.
package/README.md CHANGED
@@ -10,34 +10,42 @@
10
10
  [`@gullabs/core`](../core) that routes LLM calls through the `claude`
11
11
  (Claude Code) CLI instead of an API key. Because the CLI owns its own
12
12
  OAuth/keychain-backed session, calls made through this adapter cost $0 in API
13
- spend — the CLI reports its own cost for observability, but that number is
13
+ spend (the runner scrubs `ANTHROPIC_API_KEY` and the other credential variables from
14
+ the child's environment so that stays true; see below) — the CLI reports its own cost for observability, but that number is
14
15
  never fed into `@gullabs/core`'s cost engine (`Cost.microUsd` naturally
15
16
  resolves to `null` because these models are unpriced).
16
17
 
17
18
  ## Install
18
19
 
19
20
  ```sh
20
- pnpm add -D @gullabs/claude-cli
21
+ pnpm add -D @gullabs/claude-cli @gullabs/core
21
22
  ```
22
23
 
23
24
  ## Key exports
24
25
 
25
26
  | Export | Kind | Description |
26
27
  | --------------------------- | -------- | --------------------------------------------------------------- |
28
+ | `claudeCliProvider` | function | The `ProviderPlugin` for `composeProviders` (adapter + models). |
27
29
  | `claudeCliAdapter` | function | Creates the `ProviderAdapter` (`id: 'claude-cli'`). |
28
- | `ClaudeCliAdapterOptions` | type | `{ runner?, claudePath?, maxConcurrency? }`. |
30
+ | `ClaudeCliAdapterOptions` | type | `{ runner?, claudePath?, maxConcurrency?, env? }`. |
29
31
  | `buildClaudeCliRunner` | function | The real `node:child_process`-backed `ClaudeCliRunner` factory. |
30
32
  | `ClaudeCliRunner` | type | The process-execution seam; inject a fake in tests. |
33
+ | `ClaudeCliRunOptions` | type | `{ cwd, timeoutMs?, signal?, env? }`, the options of `run`. |
34
+ | `ClaudeCliRunResult` | type | `{ stdout, stderr, exitCode }`. |
35
+ | `ClaudeCliEnvelope` | type | The `--output-format json` result envelope the adapter reads. |
31
36
  | `claudeCliModelDescriptors` | value | `ModelDescriptor[]` for the 4 supported model ids. |
32
37
  | `claudeCliRegistry` | value | `ModelRegistry` built from `claudeCliModelDescriptors`. |
38
+ | `CLAUDE_CLI_MODEL_IDS` | value | The four registered model ids. |
39
+ | `CLAUDE_CLI_EFFORTS` | value | `['low', 'medium', 'high', 'xhigh', 'max']`. |
40
+ | `Claude…ConfigSchema` (x4) | value | The strict zod config schema of each model. |
33
41
 
34
42
  ## Quick example
35
43
 
36
44
  ```ts
37
- import { claudeCliAdapter } from '@gullabs/claude-cli'
38
- import { createClient } from '@gullabs/core'
45
+ import { composeProviders, createClient } from '@gullabs/core'
46
+ import { claudeCliProvider } from '@gullabs/claude-cli'
39
47
 
40
- const client = createClient({ adapters: [claudeCliAdapter()] })
48
+ const client = createClient({ ...composeProviders([claudeCliProvider()]) })
41
49
 
42
50
  const result = await client.generate(
43
51
  {
@@ -62,14 +70,85 @@ would break subscription-based Claude Code auth and defeat the entire point
62
70
  of this package (working with **zero** API-key configuration). The full
63
71
  invariant argv (never caller-configurable) is:
64
72
 
73
+ The quotes below illustrate argument boundaries; the runner passes an argv
74
+ array directly without a shell.
75
+
65
76
  ```
66
- -p --output-format json --safe-mode --tools "" --disable-slash-commands --no-session-persistence
77
+ -p --output-format json --safe-mode --tools "" --disable-slash-commands --no-session-persistence --settings '{"switchModelsOnFlag":false}'
67
78
  ```
68
79
 
80
+ `--settings` requests that the CLI disable silent model switches on a safety
81
+ flag; the CLI does not provide strict validation for this setting. Enforcement
82
+ comes from the post-run `modelUsage` check: a successful response must report
83
+ the requested id and no other model. Any other model id throws
84
+ `LlmError` kind `server` (not retryable). A successful envelope with
85
+ `stop_reason: "refusal"` returns `finishReason: 'content_filter'` and preserves
86
+ its billed usage; an error envelope throws `content_filter`.
87
+
88
+ The live capture of 2026-09-26 (Claude Code 2.1.282) shows it: the invariant argv
89
+ and `--json-schema` completed for all four registered ids, and each success
90
+ envelope contained only its requested id as a `modelUsage` key. The sanitized
91
+ responses are in `src/__fixtures__/model-refresh-p-a1.json`. The full installed CLI version and
92
+ envelope shape are runtime dependencies; an absent `modelUsage` fails closed.
93
+
94
+ Usage follows Anthropic's accounting, in which `input_tokens` excludes both cache lanes: `inputTokens` is
95
+ `input_tokens + cache_read_input_tokens + cache_creation_input_tokens`, `cachedInputTokens` is the cache-read
96
+ part, `details.cacheWrite` is the cache-creation part, and `thinkingTokens` is
97
+ `output_tokens_details.thinking_tokens` (inside `outputTokens`). The adapter is unpriced, so none of this
98
+ becomes a cost.
99
+
69
100
  `--model`, `--effort`, `--system-prompt`, and `--json-schema` are appended
70
101
  from the request when applicable; the prompt itself is always sent over
71
102
  stdin, never as a positional argv entry.
72
103
 
104
+ `output.jsonSchema` is passed to `--json-schema` as written. Unlike the Google and
105
+ xAI adapters (ADR-034), this adapter does not check the schema against a keyword
106
+ profile: `nullable`, uppercase type names and keywords the CLI may ignore are not
107
+ rejected here, so validate the result yourself.
108
+
109
+ ## Environment: the subscription login, never an API key
110
+
111
+ Claude Code's own documentation says `ANTHROPIC_API_KEY` is "used instead of your
112
+ Claude Pro, Max, Team, or Enterprise subscription even if you are logged in. In
113
+ non-interactive mode (`-p`), the key is always used when present"
114
+ (<https://code.claude.com/docs/en/env-vars>), and the adapter always runs `-p`. A
115
+ host that has the key exported for an Anthropic SDK would therefore have every call
116
+ billed to it while the ledger books the call as unpriced, and nothing in the result
117
+ would say so. So the real runner does not hand the host environment to the child. It
118
+ builds an allowlisted copy of `process.env`: `PATH`, `HOME`, `USER`, `LOGNAME`,
119
+ `LANG`/`LC_*`, `TERM`, `TZ`, `TMPDIR`/`TEMP`/`TMP`, `SHELL`, `XDG_*`, the Windows
120
+ profile variables, the proxy variables (`HTTPS_PROXY`, `HTTP_PROXY`, `ALL_PROXY`,
121
+ `NO_PROXY`, either case), `NODE_EXTRA_CA_CERTS`, `SSL_CERT_FILE`/`SSL_CERT_DIR`, and
122
+ the CLI's own documented settings for a subscription login: `CLAUDE_CONFIG_DIR`,
123
+ `CLAUDE_CODE_OAUTH_TOKEN` (the long-lived subscription token from
124
+ `claude setup-token`) and the mTLS variables `CLAUDE_CODE_CLIENT_CERT`,
125
+ `CLAUDE_CODE_CLIENT_KEY`, `CLAUDE_CODE_CLIENT_KEY_PASSPHRASE`. `ANTHROPIC_API_KEY`,
126
+ `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_BASE_URL`, `CLAUDE_CODE_USE_BEDROCK`/`_VERTEX`/
127
+ `_FOUNDRY` and everything else are dropped.
128
+
129
+ The list does keep credentials: the library never interprets an environment credential,
130
+ but `CLAUDE_CODE_OAUTH_TOKEN` and the mTLS variables `CLAUDE_CODE_CLIENT_CERT`,
131
+ `CLAUDE_CODE_CLIENT_KEY` and `CLAUDE_CODE_CLIENT_KEY_PASSPHRASE` are forwarded from the
132
+ host environment to the CLI, so a host that has them exported is using them (and a proxy
133
+ URL in `HTTPS_PROXY` can carry credentials too). `env` adds to the copy and cannot remove
134
+ from it: to keep the CLI off an ambient token, unset the variable before the call.
135
+
136
+ `claudeCliAdapter({ env })` adds variables on top, and they win over the allowlisted
137
+ ones. It is the one way to pass anything else on. Putting an API key there is the
138
+ explicit opt-in: the call is then billed to it. `env` is validated at construction
139
+ (string names without `=`, string values without NUL), else `bad_request`. A custom
140
+ runner receives it as `ClaudeCliRunOptions.env` and decides for itself.
141
+
142
+ ## Queued calls and killed processes
143
+
144
+ A call waits for a slot before it makes its scratch directory, and leaves the queue
145
+ (rejecting `aborted`) when its signal fires, so calls the engine already gave up on
146
+ cost nothing while they wait. When a timeout, abort or the stdout cap kills the call,
147
+ the whole process group gets SIGTERM, and SIGKILL after five seconds. When the CLI
148
+ itself exits on the SIGTERM the group gets one more SIGKILL at that moment, so a tool
149
+ process that ignored SIGTERM and holds none of the runner's pipes does not outlive
150
+ the call.
151
+
73
152
  ## Concurrency
74
153
 
75
154
  The adapter caps concurrent `claude` CLI invocations with an internal
@@ -78,9 +157,14 @@ semaphore, defaulting to `maxConcurrency: 2`. Override via
78
157
 
79
158
  ## Supported models
80
159
 
81
- `claude-fable-5`, `claude-opus-4-8`, `claude-sonnet-5`,
82
- `claude-haiku-4-5-20251001`. Each accepts an optional
83
- `{ reasoning: { effort }, timeoutMs }` config — no sampling knobs
84
- (`temperature`/`topP`/`topK`/`maxOutputTokens`/`stopSequences`) are accepted;
85
- the CLI does not support tuning any of them, and the strict config schema
86
- rejects unknown keys.
160
+ `claude-fable-5-1`, `claude-opus-5-5`, `claude-sonnet-5`,
161
+ `claude-haiku-4-5-20251001`. Fable 5.1, Opus 5.5, and Sonnet 5 accept
162
+ `{ reasoning: { effort }, timeoutMs }` with effort `low | medium | high |
163
+ xhigh | max`. Haiku 4.5 has no `reasoning` key — the CLI drops `--effort`.
164
+ No sampling knobs (`temperature`/`topP`/`topK`/`maxOutputTokens`/`stopSequences`)
165
+ are accepted; the strict config schema rejects unknown keys. `claude-fable-5`
166
+ and `claude-opus-4-8` are deleted with no alias.
167
+
168
+ The descriptors' `limits` are what the CLI reports for its own run (the captured
169
+ `modelUsage.contextWindow` and `maxOutputTokens`), not the API maxima: Fable 5.1
170
+ 64 000 and Haiku 4.5 32 000 output tokens, Opus 5.5 and Sonnet 5 128 000.