@swttch/extend-kit 0.4.0 → 0.6.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/README.ko.md +29 -1
- package/README.md +50 -1
- package/dist/battery/api/client.d.ts +10 -1
- package/dist/battery/api/client.js +23 -6
- package/dist/battery/api/client.js.map +1 -1
- package/dist/battery/api/http.d.ts +17 -0
- package/dist/battery/api/http.js +61 -0
- package/dist/battery/api/http.js.map +1 -0
- package/dist/battery/api/index.d.ts +1 -0
- package/dist/battery/api/index.js.map +1 -1
- package/dist/battery/auth/keychain.d.ts +1 -1
- package/dist/battery/auth/keychain.js +3 -2
- package/dist/battery/auth/keychain.js.map +1 -1
- package/dist/cli/account-credentials.d.ts +3 -0
- package/dist/cli/account-credentials.js +43 -0
- package/dist/cli/account-credentials.js.map +1 -0
- package/dist/cli/index.js +12 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/proxy.d.ts +32 -0
- package/dist/proxy.js +121 -0
- package/dist/proxy.js.map +1 -0
- package/dist/stt/stream.js +8 -1
- package/dist/stt/stream.js.map +1 -1
- package/package.json +3 -1
package/README.ko.md
CHANGED
|
@@ -29,6 +29,23 @@ npm install @swttch/extend-kit
|
|
|
29
29
|
|
|
30
30
|
Node.js 버전 20 이상이 필요합니다.
|
|
31
31
|
|
|
32
|
+
## 프록시 지원
|
|
33
|
+
|
|
34
|
+
프록시를 거쳐야 인터넷에 나가는 환경이라면, API 요청과 받아쓰기 모두 그 프록시를 지납니다. 읽는 환경변수는 curl과 `claude` CLI가 이미 읽는 것과 같으므로, 그 도구들이 동작하는 머신이라면 따로 설정할 것이 없습니다.
|
|
35
|
+
|
|
36
|
+
| 환경변수 | 적용 대상 |
|
|
37
|
+
|----------|-----------|
|
|
38
|
+
| `HTTPS_PROXY` / `https_proxy` | `https://` 와 `wss://` 연결 |
|
|
39
|
+
| `HTTP_PROXY` / `http_proxy` | `http://` 와 `ws://` 연결 |
|
|
40
|
+
| `ALL_PROXY` / `all_proxy` | 위 둘이 없을 때 사용 |
|
|
41
|
+
| `NO_PROXY` / `no_proxy` | 프록시를 거치지 않을 호스트. `*` 는 프록시를 통째로 끕니다 |
|
|
42
|
+
|
|
43
|
+
프록시 URL은 `http://`, `https://`, `socks://`, `socks4://`, `socks4a://`, `socks5://`, `socks5h://` 를 지원합니다. 따라서 사내 프록시뿐 아니라 `ssh -D` 로 연 터널도 그대로 동작합니다. URL에 담긴 자격증명(`http://사용자:비밀번호@호스트:포트`)은 프록시 인증에 사용됩니다.
|
|
44
|
+
|
|
45
|
+
`NO_PROXY` 는 통용되는 규칙을 따릅니다. 쉼표로 구분하고, 맨 앞의 점은 무시하며, 도메인만 적으면 그 호스트와 하위 도메인이 모두 해당됩니다. 뒤에 `:포트` 를 붙이면 그 포트에만 적용됩니다.
|
|
46
|
+
|
|
47
|
+
프록시가 아니라 게이트웨이를 통해 API에 접속한다면 `ANTHROPIC_BASE_URL` 을 설정하십시오. `claude` CLI가 읽는 환경변수와 같습니다.
|
|
48
|
+
|
|
32
49
|
## 빠른 시작
|
|
33
50
|
|
|
34
51
|
### SDK 사용
|
|
@@ -129,7 +146,7 @@ if (isTokenExpired(credentials)) {
|
|
|
129
146
|
|
|
130
147
|
API 호출을 위한 클라이언트 클래스입니다. 서브모듈을 통해 API 엔드포인트에 접근합니다.
|
|
131
148
|
|
|
132
|
-
#### `constructor(auth?: string | { apiKey: string })`
|
|
149
|
+
#### `constructor(auth?: string | { apiKey: string }, options?: ClaudeCodeClientOptions)`
|
|
133
150
|
|
|
134
151
|
OAuth 접근 토큰이나 API Key로 클라이언트를 초기화합니다. 인수를 전달하지 않으면 자동으로 크레덴셜을 읽어옵니다.
|
|
135
152
|
|
|
@@ -142,8 +159,17 @@ const client = new ClaudeCodeClient(myOAuthAccessToken);
|
|
|
142
159
|
|
|
143
160
|
// API Key
|
|
144
161
|
const client = new ClaudeCodeClient({ apiKey: process.env.ANTHROPIC_API_KEY });
|
|
162
|
+
|
|
163
|
+
// API에 직접 붙지 않고 게이트웨이를 거치는 경우
|
|
164
|
+
const client = new ClaudeCodeClient(myOAuthAccessToken, { baseUrl: 'https://gateway.internal' });
|
|
145
165
|
```
|
|
146
166
|
|
|
167
|
+
| 옵션 | 기본값 | 설명 |
|
|
168
|
+
|------|--------|------|
|
|
169
|
+
| `baseUrl` | `ANTHROPIC_BASE_URL`, 없으면 `https://api.anthropic.com` | 요청을 보낼 API 주소 |
|
|
170
|
+
|
|
171
|
+
프록시 설정은 환경변수에서 읽으므로 여기에 옵션으로 넘길 필요가 없습니다. [프록시 지원](#프록시-지원)을 참고하십시오.
|
|
172
|
+
|
|
147
173
|
#### `oauth: OAuthApi`
|
|
148
174
|
|
|
149
175
|
OAuth 관련 API를 제공하는 서브모듈입니다.
|
|
@@ -214,6 +240,8 @@ try {
|
|
|
214
240
|
| `token_expired` | OAuth 토큰이 만료됨 | `claude login` 명령 실행으로 토큰 갱신 |
|
|
215
241
|
| `api_error` | API 요청 실패 (HTTP 상태 포함) | — |
|
|
216
242
|
| `unsupported_platform` | 지원하지 않는 플랫폼 | — |
|
|
243
|
+
| `network_error` | API에 접속하지 못함 | 프록시를 거친 경우 그 사실을 알려줍니다 |
|
|
244
|
+
| `invalid_proxy` | 프록시 환경변수가 URL이 아니거나 지원하지 않는 스킴 | 기대하는 형식을 알려줍니다 |
|
|
217
245
|
|
|
218
246
|
## 타입 정의
|
|
219
247
|
|
package/README.md
CHANGED
|
@@ -29,6 +29,31 @@ npm install @swttch/extend-kit
|
|
|
29
29
|
|
|
30
30
|
Node.js version 20 or higher is required.
|
|
31
31
|
|
|
32
|
+
## Proxy Support
|
|
33
|
+
|
|
34
|
+
If your machine reaches the internet through a proxy, both the API requests and
|
|
35
|
+
dictation go through it. The variables are the ones curl and the `claude` CLI
|
|
36
|
+
already read, so a machine set up for those needs nothing extra here.
|
|
37
|
+
|
|
38
|
+
| Variable | Applies to |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| `HTTPS_PROXY` / `https_proxy` | `https://` and `wss://` connections |
|
|
41
|
+
| `HTTP_PROXY` / `http_proxy` | `http://` and `ws://` connections |
|
|
42
|
+
| `ALL_PROXY` / `all_proxy` | Used when neither of the above is set |
|
|
43
|
+
| `NO_PROXY` / `no_proxy` | Hosts to connect to directly. `*` disables the proxy entirely |
|
|
44
|
+
|
|
45
|
+
`http://`, `https://`, `socks://`, `socks4://`, `socks4a://`, `socks5://` and
|
|
46
|
+
`socks5h://` proxy URLs are supported, so an `ssh -D` tunnel works as well as a
|
|
47
|
+
corporate egress proxy. Credentials embedded in the URL
|
|
48
|
+
(`http://user:pass@host:port`) are used for proxy authentication.
|
|
49
|
+
|
|
50
|
+
`NO_PROXY` follows the usual conventions: a comma-separated list, a leading dot
|
|
51
|
+
ignored, a bare entry matching that host and its subdomains, and an optional
|
|
52
|
+
`:port` to limit the entry to one port.
|
|
53
|
+
|
|
54
|
+
If you talk to the API through a gateway rather than a proxy, set
|
|
55
|
+
`ANTHROPIC_BASE_URL` — the same variable the `claude` CLI reads.
|
|
56
|
+
|
|
32
57
|
## Quick Start
|
|
33
58
|
|
|
34
59
|
### SDK Usage
|
|
@@ -129,7 +154,7 @@ if (isTokenExpired(credentials)) {
|
|
|
129
154
|
|
|
130
155
|
A client class for making API calls. Access API endpoints through sub-modules.
|
|
131
156
|
|
|
132
|
-
#### `constructor(auth?: string | { apiKey: string })`
|
|
157
|
+
#### `constructor(auth?: string | { apiKey: string }, options?: ClaudeCodeClientOptions)`
|
|
133
158
|
|
|
134
159
|
Initializes the client with OAuth access token or API Key. If no argument is provided, credentials are resolved automatically.
|
|
135
160
|
|
|
@@ -142,8 +167,17 @@ const client = new ClaudeCodeClient(myAccessToken);
|
|
|
142
167
|
|
|
143
168
|
// API Key
|
|
144
169
|
const client = new ClaudeCodeClient({ apiKey: process.env.ANTHROPIC_API_KEY });
|
|
170
|
+
|
|
171
|
+
// Talk to a gateway instead of the API directly
|
|
172
|
+
const client = new ClaudeCodeClient(myAccessToken, { baseUrl: 'https://gateway.internal' });
|
|
145
173
|
```
|
|
146
174
|
|
|
175
|
+
| Option | Default | Description |
|
|
176
|
+
| --- | --- | --- |
|
|
177
|
+
| `baseUrl` | `ANTHROPIC_BASE_URL`, else `https://api.anthropic.com` | API origin to send requests to |
|
|
178
|
+
|
|
179
|
+
Proxy settings are read from the environment and need no option here — see [Proxy Support](#proxy-support).
|
|
180
|
+
|
|
147
181
|
#### `oauth: OAuthApi`
|
|
148
182
|
|
|
149
183
|
A sub-module that provides OAuth-related API methods.
|
|
@@ -214,6 +248,8 @@ try {
|
|
|
214
248
|
| `token_expired` | OAuth token has expired | Run `claude login` to refresh your token |
|
|
215
249
|
| `api_error` | API request failed (includes HTTP status) | — |
|
|
216
250
|
| `unsupported_platform` | Platform not supported | — |
|
|
251
|
+
| `network_error` | The API could not be reached | Names the proxy when one was in use |
|
|
252
|
+
| `invalid_proxy` | A proxy variable is not a URL, or its scheme is not supported | Shows the expected form |
|
|
217
253
|
|
|
218
254
|
## Type Definitions
|
|
219
255
|
|
|
@@ -504,3 +540,16 @@ MIT
|
|
|
504
540
|
## Author
|
|
505
541
|
|
|
506
542
|
yhk1038
|
|
543
|
+
|
|
544
|
+
## Saved-account usage (CLI)
|
|
545
|
+
|
|
546
|
+
`ccb --capabilities --json` reports `oauth.usage.account-file` when supported.
|
|
547
|
+
`ccb oauth usage --account-file="/path/to/account-snapshot.json" --json` reads a
|
|
548
|
+
CCG account snapshot (`credentials` is a JSON string, `oauthAccount.emailAddress`
|
|
549
|
+
identifies the account) and queries that account without switching the live login.
|
|
550
|
+
Credential values are never command arguments or CLI output. The CLI may use a
|
|
551
|
+
newer live credential when the live email matches the selected snapshot; otherwise
|
|
552
|
+
it uses the snapshot. Expired tokens return `token_expired`. No token refresh,
|
|
553
|
+
credential writes, caching, retries or automatic account switching are performed.
|
|
554
|
+
The caller owns process timeout and recovery policy. Plain `ccb oauth usage`
|
|
555
|
+
retains its existing behaviour.
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { OAuthApi } from './oauth.js';
|
|
2
2
|
import type { ClientAuth } from '../auth/index.js';
|
|
3
|
+
/** Options that change where or how requests go out. */
|
|
4
|
+
export interface ClaudeCodeClientOptions {
|
|
5
|
+
/**
|
|
6
|
+
* API origin to talk to. Defaults to ANTHROPIC_BASE_URL when set, and to the
|
|
7
|
+
* real API otherwise.
|
|
8
|
+
*/
|
|
9
|
+
baseUrl?: string;
|
|
10
|
+
}
|
|
3
11
|
export declare class ClaudeCodeClient {
|
|
4
12
|
private auth?;
|
|
5
13
|
private accessToken?;
|
|
6
14
|
private _oauth?;
|
|
7
|
-
|
|
15
|
+
private readonly baseUrl;
|
|
16
|
+
constructor(auth?: ClientAuth, options?: ClaudeCodeClientOptions);
|
|
8
17
|
get isApiKeyMode(): boolean;
|
|
9
18
|
get oauth(): OAuthApi;
|
|
10
19
|
private resolveToken;
|
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
import { OAuthApi } from './oauth.js';
|
|
2
|
+
import { getJson } from './http.js';
|
|
2
3
|
import { getCredentials, getAccessToken, isApiKeyAuth } from '../auth/index.js';
|
|
3
4
|
import { CcbError } from '../errors.js';
|
|
4
5
|
const BASE_URL = 'https://api.anthropic.com';
|
|
6
|
+
/**
|
|
7
|
+
* The origin to use, with any trailing slash removed so that joining a path that
|
|
8
|
+
* starts with "/" cannot produce a double slash.
|
|
9
|
+
*
|
|
10
|
+
* ANTHROPIC_BASE_URL is honored because the `claude` CLI honors it: a user
|
|
11
|
+
* behind a corporate gateway points both at the same place, and `ccb` claiming
|
|
12
|
+
* not to know the setting the rest of their toolchain uses would be a gap for no
|
|
13
|
+
* reason. It also lets the tests here talk to a local server over the real
|
|
14
|
+
* request path instead of stubbing the transport.
|
|
15
|
+
*/
|
|
16
|
+
function resolveBaseUrl(explicit) {
|
|
17
|
+
const chosen = explicit ?? process.env.ANTHROPIC_BASE_URL?.trim() ?? '';
|
|
18
|
+
return (chosen || BASE_URL).replace(/\/+$/, '');
|
|
19
|
+
}
|
|
5
20
|
export class ClaudeCodeClient {
|
|
6
21
|
auth;
|
|
7
22
|
accessToken;
|
|
8
23
|
_oauth;
|
|
9
|
-
|
|
24
|
+
baseUrl;
|
|
25
|
+
constructor(auth, options = {}) {
|
|
10
26
|
this.auth = auth;
|
|
27
|
+
this.baseUrl = resolveBaseUrl(options.baseUrl);
|
|
11
28
|
if (typeof auth === 'string') {
|
|
12
29
|
this.accessToken = auth;
|
|
13
30
|
}
|
|
@@ -41,14 +58,14 @@ export class ClaudeCodeClient {
|
|
|
41
58
|
...headers,
|
|
42
59
|
};
|
|
43
60
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
});
|
|
61
|
+
// getJson rather than fetch: fetch cannot be routed through a proxy, and on a
|
|
62
|
+
// machine that only reaches the internet through one, every call here fails.
|
|
63
|
+
// See src/proxy.ts.
|
|
64
|
+
const response = await getJson(`${this.baseUrl}${path}`, requestHeaders);
|
|
48
65
|
if (!response.ok) {
|
|
49
66
|
throw new CcbError(`API error ${response.status}: ${response.statusText}`, 'api_error');
|
|
50
67
|
}
|
|
51
|
-
return response.
|
|
68
|
+
return response.body;
|
|
52
69
|
}
|
|
53
70
|
}
|
|
54
71
|
//# sourceMappingURL=client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/battery/api/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhF,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,MAAM,QAAQ,GAAG,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/battery/api/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhF,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,MAAM,QAAQ,GAAG,2BAA2B,CAAC;AAW7C;;;;;;;;;GASG;AACH,SAAS,cAAc,CAAC,QAA4B;IAClD,MAAM,MAAM,GAAG,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACxE,OAAO,CAAC,MAAM,IAAI,QAAQ,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,OAAO,gBAAgB;IACnB,IAAI,CAAc;IAClB,WAAW,CAAU;IACrB,MAAM,CAAY;IACT,OAAO,CAAS;IAEjC,YAAY,IAAiB,EAAE,UAAmC,EAAE;QAClE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC/C,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,IAAI,YAAY;QACd,OAAO,YAAY,CAAC,IAAI,CAAC,IAAkB,CAAC,CAAC;IAC/C,CAAC;IAED,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC5C,CAAC;IAEO,KAAK,CAAC,YAAY;QACxB,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,MAAM,WAAW,GAAG,MAAM,cAAc,EAAE,CAAC;YAC3C,IAAI,CAAC,WAAW,GAAG,cAAc,CAAC,WAAW,CAAC,CAAC;QACjD,CAAC;QACD,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,QAAQ,CAAI,IAAY,EAAE,OAAgC;QAC9D,IAAI,cAAsC,CAAC;QAE3C,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACvD,cAAc,GAAG;gBACf,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM;gBAC7B,mBAAmB,EAAE,YAAY;gBACjC,GAAG,OAAO;aACX,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;YACxC,cAAc,GAAG;gBACf,aAAa,EAAE,UAAU,KAAK,EAAE;gBAChC,GAAG,OAAO;aACX,CAAC;QACJ,CAAC;QAED,8EAA8E;QAC9E,6EAA6E;QAC7E,oBAAoB;QACpB,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAI,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE,EAAE,cAAc,CAAC,CAAC;QAE5E,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,QAAQ,CAAC,aAAa,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,UAAU,EAAE,EAAE,WAAW,CAAC,CAAC;QAC1F,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,CAAC;IACvB,CAAC;CACF"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Agent } from 'node:http';
|
|
2
|
+
/** What a caller needs to know about the response, mirroring the shape `fetch` returns. */
|
|
3
|
+
export interface JsonResponse<T> {
|
|
4
|
+
ok: boolean;
|
|
5
|
+
status: number;
|
|
6
|
+
statusText: string;
|
|
7
|
+
body: T;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* GET a JSON document, through a proxy when one is configured.
|
|
11
|
+
*
|
|
12
|
+
* A dead or misconfigured proxy fails at connect time, and the raw errno that
|
|
13
|
+
* surfaces ("ECONNREFUSED 127.0.0.1:8080") names the proxy without saying that a
|
|
14
|
+
* proxy is what failed. Since the proxy is invisible in the URL the user asked
|
|
15
|
+
* for, the error says so explicitly.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getJson<T>(url: string, headers: Record<string, string>, agentOverride?: Agent): Promise<JsonResponse<T>>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { request as httpsRequest } from 'node:https';
|
|
2
|
+
import { request as httpRequest } from 'node:http';
|
|
3
|
+
import { proxyAgentFor } from '../../proxy.js';
|
|
4
|
+
import { CcbError } from '../errors.js';
|
|
5
|
+
/**
|
|
6
|
+
* Global `fetch` cannot be pointed at a proxy — it ignores the proxy environment
|
|
7
|
+
* variables and takes no `agent` — so it is unusable here. `node:https` accepts
|
|
8
|
+
* the very agent `ws` accepts, which lets one proxy implementation serve both
|
|
9
|
+
* transports. See {@link proxyAgentFor}.
|
|
10
|
+
*/
|
|
11
|
+
const SOCKET_TIMEOUT_MS = 60_000;
|
|
12
|
+
/**
|
|
13
|
+
* GET a JSON document, through a proxy when one is configured.
|
|
14
|
+
*
|
|
15
|
+
* A dead or misconfigured proxy fails at connect time, and the raw errno that
|
|
16
|
+
* surfaces ("ECONNREFUSED 127.0.0.1:8080") names the proxy without saying that a
|
|
17
|
+
* proxy is what failed. Since the proxy is invisible in the URL the user asked
|
|
18
|
+
* for, the error says so explicitly.
|
|
19
|
+
*/
|
|
20
|
+
export async function getJson(url, headers, agentOverride) {
|
|
21
|
+
const agent = agentOverride ?? proxyAgentFor(url);
|
|
22
|
+
const viaProxy = agent !== undefined;
|
|
23
|
+
// The API is always https; plain http is here so tests can run against a local
|
|
24
|
+
// server without a certificate, and it costs one branch.
|
|
25
|
+
const request = new URL(url).protocol === 'http:' ? httpRequest : httpsRequest;
|
|
26
|
+
const { status, statusText, text } = await new Promise((resolve, reject) => {
|
|
27
|
+
const req = request(url, { method: 'GET', headers, agent }, (res) => {
|
|
28
|
+
const chunks = [];
|
|
29
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
30
|
+
res.on('end', () => resolve({
|
|
31
|
+
status: res.statusCode ?? 0,
|
|
32
|
+
statusText: res.statusMessage ?? '',
|
|
33
|
+
text: Buffer.concat(chunks).toString('utf-8'),
|
|
34
|
+
}));
|
|
35
|
+
res.on('error', reject);
|
|
36
|
+
});
|
|
37
|
+
// `https.request` waits forever by default; a proxy that accepts the socket
|
|
38
|
+
// and then goes quiet would hang the CLI with no output at all.
|
|
39
|
+
req.setTimeout(SOCKET_TIMEOUT_MS, () => {
|
|
40
|
+
req.destroy(new CcbError(`Request to ${url} timed out after ${SOCKET_TIMEOUT_MS / 1000}s`, 'network_error', viaProxy ? 'The configured proxy accepted the connection but never replied.' : undefined));
|
|
41
|
+
});
|
|
42
|
+
req.on('error', (err) => {
|
|
43
|
+
if (err instanceof CcbError)
|
|
44
|
+
return reject(err);
|
|
45
|
+
reject(new CcbError(`Could not reach ${new URL(url).host}: ${err.message}`, 'network_error', viaProxy
|
|
46
|
+
? 'This connection went through the proxy from HTTP_PROXY/HTTPS_PROXY/ALL_PROXY. Check that the proxy is running and reachable.'
|
|
47
|
+
: undefined));
|
|
48
|
+
});
|
|
49
|
+
req.end();
|
|
50
|
+
});
|
|
51
|
+
if (status < 200 || status >= 300) {
|
|
52
|
+
return { ok: false, status, statusText, body: undefined };
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
return { ok: true, status, statusText, body: JSON.parse(text) };
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
throw new CcbError(`Expected JSON from ${url} but got something else`, 'api_error', viaProxy ? 'A proxy that returns an error page instead of the response looks like this.' : undefined);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/battery/api/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { ClaudeCodeClient } from './client.js';
|
|
2
|
+
export type { ClaudeCodeClientOptions } from './client.js';
|
|
2
3
|
export { OAuthApi } from './oauth.js';
|
|
3
4
|
export type { UsageResponse, UsageBucket, ExtraUsage, ProfileResponse, AccountInfo, OrganizationInfo, ApplicationInfo, } from './types.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/battery/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/battery/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ClaudeCredentials } from './types.js';
|
|
2
|
-
export declare function readKeychainCredentials(): Promise<ClaudeCredentials>;
|
|
2
|
+
export declare function readKeychainCredentials(configDir?: string): Promise<ClaudeCredentials>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import { promisify } from 'node:util';
|
|
3
4
|
const execFileAsync = promisify(execFile);
|
|
4
5
|
const SERVICE_NAME = 'Claude Code-credentials';
|
|
@@ -6,11 +7,11 @@ async function getOsUsername() {
|
|
|
6
7
|
const { stdout } = await execFileAsync('whoami');
|
|
7
8
|
return stdout.trim();
|
|
8
9
|
}
|
|
9
|
-
export async function readKeychainCredentials() {
|
|
10
|
+
export async function readKeychainCredentials(configDir) {
|
|
10
11
|
const account = await getOsUsername();
|
|
11
12
|
const { stdout } = await execFileAsync('security', [
|
|
12
13
|
'find-generic-password',
|
|
13
|
-
'-s', SERVICE_NAME,
|
|
14
|
+
'-s', configDir ? `${SERVICE_NAME}-${createHash('sha256').update(configDir).digest('hex').slice(0, 8)}` : SERVICE_NAME,
|
|
14
15
|
'-a', account,
|
|
15
16
|
'-w',
|
|
16
17
|
]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keychain.js","sourceRoot":"","sources":["../../../src/battery/auth/keychain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,MAAM,YAAY,GAAG,yBAAyB,CAAC;AAE/C,KAAK,UAAU,aAAa;IAC1B,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;IACjD,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;AACvB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB;
|
|
1
|
+
{"version":3,"file":"keychain.js","sourceRoot":"","sources":["../../../src/battery/auth/keychain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,MAAM,YAAY,GAAG,yBAAyB,CAAC;AAE/C,KAAK,UAAU,aAAa;IAC1B,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;IACjD,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;AACvB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,SAAkB;IAC9D,MAAM,OAAO,GAAG,MAAM,aAAa,EAAE,CAAC;IACtC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,UAAU,EAAE;QACjD,uBAAuB;QACvB,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,YAAY,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY;QACtH,IAAI,EAAE,OAAO;QACb,IAAI;KACL,CAAC,CAAC;IAEH,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAC1B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAsB,CAAC;IACpD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { getCredentials } from '../battery/auth/index.js';
|
|
5
|
+
import { readKeychainCredentials } from '../battery/auth/keychain.js';
|
|
6
|
+
import { CcbError } from '../battery/errors.js';
|
|
7
|
+
/** Read a CCG saved-account snapshot without replacing the active credential slot. */
|
|
8
|
+
export async function getAccountCredentials(file) {
|
|
9
|
+
let snapshot;
|
|
10
|
+
let credentials;
|
|
11
|
+
try {
|
|
12
|
+
snapshot = JSON.parse(await readFile(file, 'utf8'));
|
|
13
|
+
credentials = JSON.parse(snapshot.credentials ?? '');
|
|
14
|
+
if (!credentials.claudeAiOauth?.accessToken)
|
|
15
|
+
throw new Error('Missing access token');
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Parsing errors must never echo a credential blob in stderr.
|
|
19
|
+
throw new CcbError('Saved account credentials are unavailable or invalid.', 'credentials_not_found');
|
|
20
|
+
}
|
|
21
|
+
const email = snapshot.oauthAccount?.emailAddress;
|
|
22
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR;
|
|
23
|
+
const liveEmail = async () => {
|
|
24
|
+
const metadata = JSON.parse(await readFile(join(configDir ?? homedir(), '.claude.json'), 'utf8'));
|
|
25
|
+
return metadata.oauthAccount?.emailAddress;
|
|
26
|
+
};
|
|
27
|
+
// A currently active account can have a newer token than its saved snapshot.
|
|
28
|
+
// Never use the live slot for a different account. No writes or token refresh.
|
|
29
|
+
try {
|
|
30
|
+
if (email && await liveEmail() === email) {
|
|
31
|
+
const live = process.platform === 'darwin'
|
|
32
|
+
? await readKeychainCredentials(configDir) : await getCredentials();
|
|
33
|
+
if (await liveEmail() === email && live.claudeAiOauth?.accessToken)
|
|
34
|
+
credentials = live;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch { /* The saved snapshot remains the fallback. */ }
|
|
38
|
+
if (Number.isFinite(credentials.claudeAiOauth.expiresAt) && Date.now() >= credentials.claudeAiOauth.expiresAt) {
|
|
39
|
+
throw new CcbError('Saved account token has expired. Log in to this account again.', 'token_expired');
|
|
40
|
+
}
|
|
41
|
+
return credentials;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=account-credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-credentials.js","sourceRoot":"","sources":["../../src/cli/account-credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAEtE,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD,sFAAsF;AACtF,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAAY;IACtD,IAAI,QAA4E,CAAC;IACjF,IAAI,WAA8B,CAAC;IACnC,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QACpD,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;QACrD,IAAI,CAAC,WAAW,CAAC,aAAa,EAAE,WAAW;YAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;IACvF,CAAC;IAAC,MAAM,CAAC;QACP,8DAA8D;QAC9D,MAAM,IAAI,QAAQ,CAAC,uDAAuD,EAAE,uBAAuB,CAAC,CAAC;IACvG,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IAClD,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IAChD,MAAM,SAAS,GAAG,KAAK,IAAiC,EAAE;QACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,SAAS,IAAI,OAAO,EAAE,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAE/F,CAAC;QACF,OAAO,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IAC7C,CAAC,CAAC;IACF,6EAA6E;IAC7E,+EAA+E;IAC/E,IAAI,CAAC;QACH,IAAI,KAAK,IAAI,MAAM,SAAS,EAAE,KAAK,KAAK,EAAE,CAAC;YACzC,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,KAAK,QAAQ;gBACxC,CAAC,CAAC,MAAM,uBAAuB,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,cAAc,EAAE,CAAC;YACtE,IAAI,MAAM,SAAS,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC,aAAa,EAAE,WAAW;gBAAE,WAAW,GAAG,IAAI,CAAC;QACzF,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAC,8CAA8C,CAAC,CAAC;IAC1D,IAAI,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,aAAa,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,WAAW,CAAC,aAAa,CAAC,SAAS,EAAE,CAAC;QAC9G,MAAM,IAAI,QAAQ,CAAC,gEAAgE,EAAE,eAAe,CAAC,CAAC;IACxG,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC"}
|
package/dist/cli/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { getCredentials, getAccessToken } from '../battery/auth/index.js';
|
|
3
3
|
import { ClaudeCodeClient } from '../battery/api/index.js';
|
|
4
4
|
import { oauthCommand } from './oauth.js';
|
|
5
|
+
import { getAccountCredentials } from './account-credentials.js';
|
|
5
6
|
import { CcbError } from '../battery/errors.js';
|
|
6
7
|
import { readFileSync } from 'node:fs';
|
|
7
8
|
import { fileURLToPath } from 'node:url';
|
|
@@ -13,13 +14,21 @@ const args = process.argv.slice(2);
|
|
|
13
14
|
const command = args.filter((a) => !a.startsWith('-'));
|
|
14
15
|
const flags = new Set(args.filter((a) => a.startsWith('-')));
|
|
15
16
|
const jsonOutput = flags.has('--json');
|
|
17
|
+
const accountFile = args.find(arg => arg.startsWith('--account-file='))?.slice('--account-file='.length);
|
|
16
18
|
async function createClient() {
|
|
17
|
-
const credentials = await getCredentials();
|
|
19
|
+
const credentials = accountFile ? await getAccountCredentials(accountFile) : await getCredentials();
|
|
18
20
|
const token = getAccessToken(credentials);
|
|
19
21
|
return new ClaudeCodeClient(token);
|
|
20
22
|
}
|
|
21
23
|
async function run() {
|
|
22
24
|
const [module, ...subcommand] = command;
|
|
25
|
+
if (flags.has('--capabilities')) {
|
|
26
|
+
console.log(JSON.stringify({ capabilities: ['oauth.usage.account-file'] }));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (args.some(arg => arg.startsWith('--account-file')) && (!accountFile || module !== 'oauth' || subcommand[0] !== 'usage')) {
|
|
30
|
+
throw new CcbError('Use oauth usage --account-file=<snapshot path>.', 'invalid_argument');
|
|
31
|
+
}
|
|
23
32
|
if (flags.has('-v') || flags.has('--version')) {
|
|
24
33
|
console.log(VERSION);
|
|
25
34
|
return;
|
|
@@ -34,6 +43,8 @@ Commands:
|
|
|
34
43
|
oauth profile Show account profile
|
|
35
44
|
|
|
36
45
|
Options:
|
|
46
|
+
--account-file=<path> Read usage for a CCG saved-account snapshot (no account switch)
|
|
47
|
+
--capabilities Output supported CLI capabilities as JSON
|
|
37
48
|
--json Output as JSON
|
|
38
49
|
-h, --help Show help
|
|
39
50
|
-v, --version Show version`);
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE7C,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,oBAAoB,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AACxF,MAAM,OAAO,GAAW,GAAG,CAAC,OAAO,CAAC;AAEpC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;AAC/D,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACrE,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
package/dist/proxy.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Agent } from 'node:http';
|
|
2
|
+
/**
|
|
3
|
+
* Whether NO_PROXY exempts this host, following the de-facto rules curl uses:
|
|
4
|
+
* a comma-separated list, `*` meaning everything, a leading dot ignored, and a
|
|
5
|
+
* bare entry matching that host plus its subdomains. An entry may pin a port,
|
|
6
|
+
* in which case the port must match too.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isProxyBypassed(host: string, port: string, noProxy: string | undefined): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* The proxy URL that applies to `targetUrl`, or undefined for a direct connection.
|
|
11
|
+
*
|
|
12
|
+
* `wss:`/`ws:` are resolved as their HTTP equivalents because that is how the
|
|
13
|
+
* connection is actually made — a WebSocket starts life as an HTTP upgrade, so
|
|
14
|
+
* a user who set HTTPS_PROXY expects `wss://` to go through it.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveProxyUrl(targetUrl: string, env?: NodeJS.ProcessEnv): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* An agent that routes a connection to `targetUrl` through the configured proxy,
|
|
19
|
+
* or undefined when no proxy applies and the connection should go out directly.
|
|
20
|
+
*
|
|
21
|
+
* The result is an `http.Agent`, which is the one shape both callers accept:
|
|
22
|
+
* `https.request` takes it as `agent`, and `ws` takes the same object under the
|
|
23
|
+
* same name. That is why this package talks to the API over `node:https` rather
|
|
24
|
+
* than global `fetch` — `fetch` cannot be handed one of these, so using it would
|
|
25
|
+
* mean two separate proxy implementations for the two transports.
|
|
26
|
+
*
|
|
27
|
+
* A malformed proxy URL throws rather than falling back to a direct connection.
|
|
28
|
+
* On a machine that can only reach the internet through a proxy the direct
|
|
29
|
+
* attempt fails anyway, and it fails with a connection error that says nothing
|
|
30
|
+
* about the real cause; naming the bad setting is far more useful.
|
|
31
|
+
*/
|
|
32
|
+
export declare function proxyAgentFor(targetUrl: string, env?: NodeJS.ProcessEnv): Agent | undefined;
|
package/dist/proxy.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
2
|
+
import { SocksProxyAgent } from 'socks-proxy-agent';
|
|
3
|
+
import { CcbError } from './battery/errors.js';
|
|
4
|
+
/**
|
|
5
|
+
* Proxy support for every outbound connection this package makes.
|
|
6
|
+
*
|
|
7
|
+
* Node does not route outbound requests through a proxy on its own. Its global
|
|
8
|
+
* `fetch` ignores HTTP_PROXY entirely (Node 24 added an opt-in flag for it, but
|
|
9
|
+
* this package supports Node 20+, and that flag does not cover SOCKS at all), and
|
|
10
|
+
* `ws` only proxies when handed an agent. So a user whose machine reaches the
|
|
11
|
+
* internet only through a proxy — a corporate egress proxy, or an `ssh -D`
|
|
12
|
+
* tunnel — got a plain connection failure from `ccb`, while `claude` itself
|
|
13
|
+
* worked because the CLI does its own proxying. That gap is what this module
|
|
14
|
+
* closes: see Swttch/swttch#181.
|
|
15
|
+
*
|
|
16
|
+
* The variables read here are the ones curl and the claude CLI already read, so
|
|
17
|
+
* a machine that is set up for those needs no extra configuration for `ccb`.
|
|
18
|
+
*/
|
|
19
|
+
/** Proxy URL schemes that mean SOCKS rather than an HTTP CONNECT proxy. */
|
|
20
|
+
const SOCKS_PROTOCOLS = new Set(['socks:', 'socks4:', 'socks4a:', 'socks5:', 'socks5h:']);
|
|
21
|
+
/**
|
|
22
|
+
* Read a proxy variable in either spelling.
|
|
23
|
+
*
|
|
24
|
+
* Both cases are in real use — most tooling documents the uppercase form, while
|
|
25
|
+
* curl has historically honored the lowercase one — and users copy whichever
|
|
26
|
+
* their last set of instructions showed. Reading only one spelling silently
|
|
27
|
+
* ignores a proxy the user believes they configured, which is the whole failure
|
|
28
|
+
* this module exists to prevent, so both are accepted with uppercase winning a
|
|
29
|
+
* conflict.
|
|
30
|
+
*/
|
|
31
|
+
function readProxyEnv(env, name) {
|
|
32
|
+
const upper = env[name.toUpperCase()];
|
|
33
|
+
if (typeof upper === 'string' && upper.trim())
|
|
34
|
+
return upper.trim();
|
|
35
|
+
const lower = env[name.toLowerCase()];
|
|
36
|
+
if (typeof lower === 'string' && lower.trim())
|
|
37
|
+
return lower.trim();
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Whether NO_PROXY exempts this host, following the de-facto rules curl uses:
|
|
42
|
+
* a comma-separated list, `*` meaning everything, a leading dot ignored, and a
|
|
43
|
+
* bare entry matching that host plus its subdomains. An entry may pin a port,
|
|
44
|
+
* in which case the port must match too.
|
|
45
|
+
*/
|
|
46
|
+
export function isProxyBypassed(host, port, noProxy) {
|
|
47
|
+
if (!noProxy)
|
|
48
|
+
return false;
|
|
49
|
+
const entries = noProxy.split(',').map((e) => e.trim().toLowerCase()).filter(Boolean);
|
|
50
|
+
if (entries.includes('*'))
|
|
51
|
+
return true;
|
|
52
|
+
const target = host.toLowerCase();
|
|
53
|
+
return entries.some((entry) => {
|
|
54
|
+
let pattern = entry;
|
|
55
|
+
let entryPort;
|
|
56
|
+
// Only split a port off when it is unambiguous; a bare IPv6 literal is full
|
|
57
|
+
// of colons and must not be chopped at the first one.
|
|
58
|
+
const lastColon = pattern.lastIndexOf(':');
|
|
59
|
+
if (lastColon > 0 && !pattern.slice(lastColon + 1).includes(']') && /^\d+$/.test(pattern.slice(lastColon + 1))) {
|
|
60
|
+
entryPort = pattern.slice(lastColon + 1);
|
|
61
|
+
pattern = pattern.slice(0, lastColon);
|
|
62
|
+
}
|
|
63
|
+
if (entryPort !== undefined && entryPort !== port)
|
|
64
|
+
return false;
|
|
65
|
+
if (pattern.startsWith('.'))
|
|
66
|
+
pattern = pattern.slice(1);
|
|
67
|
+
return target === pattern || target.endsWith(`.${pattern}`);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The proxy URL that applies to `targetUrl`, or undefined for a direct connection.
|
|
72
|
+
*
|
|
73
|
+
* `wss:`/`ws:` are resolved as their HTTP equivalents because that is how the
|
|
74
|
+
* connection is actually made — a WebSocket starts life as an HTTP upgrade, so
|
|
75
|
+
* a user who set HTTPS_PROXY expects `wss://` to go through it.
|
|
76
|
+
*/
|
|
77
|
+
export function resolveProxyUrl(targetUrl, env = process.env) {
|
|
78
|
+
const target = new URL(targetUrl);
|
|
79
|
+
const isSecure = target.protocol === 'https:' || target.protocol === 'wss:';
|
|
80
|
+
const port = target.port || (isSecure ? '443' : '80');
|
|
81
|
+
if (isProxyBypassed(target.hostname, port, readProxyEnv(env, 'NO_PROXY')))
|
|
82
|
+
return undefined;
|
|
83
|
+
const specific = isSecure ? readProxyEnv(env, 'HTTPS_PROXY') : readProxyEnv(env, 'HTTP_PROXY');
|
|
84
|
+
return specific ?? readProxyEnv(env, 'ALL_PROXY');
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* An agent that routes a connection to `targetUrl` through the configured proxy,
|
|
88
|
+
* or undefined when no proxy applies and the connection should go out directly.
|
|
89
|
+
*
|
|
90
|
+
* The result is an `http.Agent`, which is the one shape both callers accept:
|
|
91
|
+
* `https.request` takes it as `agent`, and `ws` takes the same object under the
|
|
92
|
+
* same name. That is why this package talks to the API over `node:https` rather
|
|
93
|
+
* than global `fetch` — `fetch` cannot be handed one of these, so using it would
|
|
94
|
+
* mean two separate proxy implementations for the two transports.
|
|
95
|
+
*
|
|
96
|
+
* A malformed proxy URL throws rather than falling back to a direct connection.
|
|
97
|
+
* On a machine that can only reach the internet through a proxy the direct
|
|
98
|
+
* attempt fails anyway, and it fails with a connection error that says nothing
|
|
99
|
+
* about the real cause; naming the bad setting is far more useful.
|
|
100
|
+
*/
|
|
101
|
+
export function proxyAgentFor(targetUrl, env = process.env) {
|
|
102
|
+
const proxyUrl = resolveProxyUrl(targetUrl, env);
|
|
103
|
+
if (!proxyUrl)
|
|
104
|
+
return undefined;
|
|
105
|
+
let protocol;
|
|
106
|
+
try {
|
|
107
|
+
protocol = new URL(proxyUrl).protocol;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
throw new CcbError(`Proxy setting is not a valid URL: ${proxyUrl}`, 'invalid_proxy', 'Expected something like http://host:port or socks5://host:port');
|
|
111
|
+
}
|
|
112
|
+
if (SOCKS_PROTOCOLS.has(protocol))
|
|
113
|
+
return new SocksProxyAgent(proxyUrl);
|
|
114
|
+
if (protocol === 'http:' || protocol === 'https:') {
|
|
115
|
+
// HttpsProxyAgent covers both, because every endpoint this package talks to
|
|
116
|
+
// is https/wss and therefore always reached by a CONNECT tunnel.
|
|
117
|
+
return new HttpsProxyAgent(proxyUrl);
|
|
118
|
+
}
|
|
119
|
+
throw new CcbError(`Unsupported proxy scheme "${protocol.replace(':', '')}" in ${proxyUrl}`, 'invalid_proxy', 'Supported schemes: http, https, socks, socks4, socks4a, socks5, socks5h');
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
|
package/dist/stt/stream.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import WebSocket from 'ws';
|
|
2
2
|
import { getCredentials, getAccessToken } from '../battery/auth/index.js';
|
|
3
3
|
import { CcbError } from '../battery/errors.js';
|
|
4
|
+
import { proxyAgentFor } from '../proxy.js';
|
|
4
5
|
import { DEFAULT_KEYTERMS, packKeyterms } from './keyterms.js';
|
|
5
6
|
import { AUDIO_FORMAT, TranscriptMessageType, } from './types.js';
|
|
6
7
|
const VOICE_STREAM_URL = 'wss://api.anthropic.com/api/ws/speech_to_text/voice_stream';
|
|
@@ -43,13 +44,19 @@ export async function openSpeechToTextStream(handlers, options = {}, token) {
|
|
|
43
44
|
stt_provider: 'deepgram-nova3',
|
|
44
45
|
});
|
|
45
46
|
const keyterms = packKeyterms([...DEFAULT_KEYTERMS, ...(options.extraKeyterms ?? [])]);
|
|
46
|
-
const
|
|
47
|
+
const streamUrl = `${VOICE_STREAM_URL}?${query.toString()}`;
|
|
48
|
+
const socket = new WebSocket(streamUrl, {
|
|
47
49
|
headers: {
|
|
48
50
|
Authorization: `Bearer ${accessToken}`,
|
|
49
51
|
'x-app': 'swttch',
|
|
50
52
|
'anthropic-client-platform': 'claude_code_swttch',
|
|
51
53
|
...(keyterms ? { 'x-config-keyterms': keyterms } : {}),
|
|
52
54
|
},
|
|
55
|
+
// A WebSocket opens as an HTTP upgrade, so it needs the same proxy the usage
|
|
56
|
+
// requests use. Without this, dictation is dead on any machine that reaches
|
|
57
|
+
// the internet only through a proxy, even though the usage panel works.
|
|
58
|
+
// undefined here means "no proxy configured" and ws connects directly.
|
|
59
|
+
agent: proxyAgentFor(streamUrl),
|
|
53
60
|
});
|
|
54
61
|
let keepalive = null;
|
|
55
62
|
let closing = false;
|
package/dist/stt/stream.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../../src/stt/stream.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EACL,YAAY,EACZ,qBAAqB,GAKtB,MAAM,YAAY,CAAC;AAEpB,MAAM,gBAAgB,GAAG,4DAA4D,CAAC;AAEtF,sEAAsE;AACtE,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAEnC,4EAA4E;AAC5E,MAAM,cAAc,GAAG,IAAI,CAAC;AAE5B,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;AACxD,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC;AAE7D;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,QAA8B,EAC9B,UAA+B,EAAE,EACjC,KAAc;IAEd,MAAM,WAAW,GAAG,KAAK,IAAI,cAAc,CAAC,MAAM,cAAc,EAAE,CAAC,CAAC;IAEpE,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC;QAChC,QAAQ,EAAE,YAAY,CAAC,QAAQ;QAC/B,WAAW,EAAE,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC;QAC5C,QAAQ,EAAE,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC;QACvC,4EAA4E;QAC5E,cAAc,EAAE,KAAK;QACrB,gBAAgB,EAAE,MAAM;QACxB,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,IAAI;QAClC,uBAAuB,EAAE,MAAM;QAC/B,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,YAAY,EAAE,gBAAgB;KAC/B,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,GAAG,gBAAgB,EAAE,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAEvF,MAAM,
|
|
1
|
+
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../../src/stt/stream.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EACL,YAAY,EACZ,qBAAqB,GAKtB,MAAM,YAAY,CAAC;AAEpB,MAAM,gBAAgB,GAAG,4DAA4D,CAAC;AAEtF,sEAAsE;AACtE,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAEnC,4EAA4E;AAC5E,MAAM,cAAc,GAAG,IAAI,CAAC;AAE5B,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;AACxD,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC;AAE7D;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,QAA8B,EAC9B,UAA+B,EAAE,EACjC,KAAc;IAEd,MAAM,WAAW,GAAG,KAAK,IAAI,cAAc,CAAC,MAAM,cAAc,EAAE,CAAC,CAAC;IAEpE,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC;QAChC,QAAQ,EAAE,YAAY,CAAC,QAAQ;QAC/B,WAAW,EAAE,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC;QAC5C,QAAQ,EAAE,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC;QACvC,4EAA4E;QAC5E,cAAc,EAAE,KAAK;QACrB,gBAAgB,EAAE,MAAM;QACxB,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,IAAI;QAClC,uBAAuB,EAAE,MAAM;QAC/B,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,YAAY,EAAE,gBAAgB;KAC/B,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,GAAG,gBAAgB,EAAE,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAEvF,MAAM,SAAS,GAAG,GAAG,gBAAgB,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC;IAE5D,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,SAAS,EAAE;QACtC,OAAO,EAAE;YACP,aAAa,EAAE,UAAU,WAAW,EAAE;YACtC,OAAO,EAAE,QAAQ;YACjB,2BAA2B,EAAE,oBAAoB;YACjD,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,mBAAmB,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACvD;QACD,6EAA6E;QAC7E,4EAA4E;QAC5E,wEAAwE;QACxE,uEAAuE;QACvE,KAAK,EAAE,aAAa,CAAC,SAAS,CAAC;KAChC,CAAC,CAAC;IAEH,IAAI,SAAS,GAA0B,IAAI,CAAC;IAC5C,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB;;;;;;;;;OASG;IACH,MAAM,YAAY,GAAiB,EAAE,CAAC;IACtC,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,MAAM,iBAAiB,GAAG,YAAY,CAAC,UAAU,GAAG,CAAC,GAAG,CAAC,CAAC;IAC1D;;;;;;;;;OASG;IACH,IAAI,OAAO,GAAG,EAAE,CAAC;IAEjB,SAAS,aAAa;QACpB,OAAO,GAAG,IAAI,CAAC;QACf,IAAI,SAAS,EAAE,CAAC;YACd,aAAa,CAAC,SAAS,CAAC,CAAC;YACzB,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,SAAS,YAAY;QACnB,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,MAAM,IAAI,GAAG,OAAO,CAAC;QACrB,OAAO,GAAG,EAAE,CAAC;QACb,QAAQ,CAAC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;QACrB,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;QACpB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACvB,uEAAuE;QACvE,qEAAqE;QACrE,KAAK,MAAM,KAAK,IAAI,YAAY;YAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrD,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC;QACxB,YAAY,GAAG,CAAC,CAAC;QACjB,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE;YAC3B,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI;gBAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACnE,CAAC,EAAE,qBAAqB,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE;QAC3B,IAAI,GAAsB,CAAC;QAC3B,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAsB,CAAC;QACxD,CAAC;QAAC,MAAM,CAAC;YACP,oEAAoE;YACpE,OAAO;QACT,CAAC;QAED,QAAQ,GAAG,CAAC,IAAI,EAAE,CAAC;YACjB,KAAK,qBAAqB,CAAC,iBAAiB,CAAC;YAC7C,KAAK,qBAAqB,CAAC,cAAc;gBACvC,uEAAuE;gBACvE,sEAAsE;gBACtE,qEAAqE;gBACrE,mEAAmE;gBACnE,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;oBACb,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM;wBAAE,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC;oBAC1D,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;gBACzC,CAAC;gBACD,MAAM;YAER,KAAK,qBAAqB,CAAC,kBAAkB;gBAC3C,gDAAgD;gBAChD,YAAY,EAAE,CAAC;gBACf,MAAM;YAER,KAAK,qBAAqB,CAAC,eAAe;gBACxC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,qBAAqB,CAAC,CAAC;gBAC3D,MAAM;YAER,KAAK,qBAAqB,CAAC,KAAK;gBAC9B,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,cAAc,CAAC,CAAC;gBAChD,MAAM;YAER;gBACE,sEAAsE;gBACtE,2EAA2E;gBAC3E,MAAM;QACV,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;QAChC,IAAI,OAAO;YAAE,OAAO;QACpB,6EAA6E;QAC7E,yEAAyE;QACzE,4DAA4D;QAC5D,MAAM,MAAM,GAAG,oCAAoC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC3E,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACjD,QAAQ,CAAC,OAAO,CAAC,oBAAoB,GAAG,CAAC,OAAO,EAAE,EAAE;YAClD,KAAK,EAAE,IAAI,KAAK,SAAS,IAAI,IAAI,IAAI,GAAG,IAAI,IAAI,GAAG,GAAG;SACvD,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QACtB,YAAY,EAAE,CAAC;QACf,aAAa,EAAE,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,SAAS,CAAC,KAAiB;YACzB,IAAI,OAAO;gBAAE,OAAO;YACpB,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;gBACzC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACnB,OAAO;YACT,CAAC;YACD,yEAAyE;YACzE,yEAAyE;YACzE,iCAAiC;YACjC,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,UAAU,EAAE,CAAC;gBAC/C,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACzB,YAAY,IAAI,KAAK,CAAC,UAAU,CAAC;gBACjC,OAAO,YAAY,GAAG,iBAAiB,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACnE,YAAY,IAAI,YAAY,CAAC,KAAK,EAAG,CAAC,UAAU,CAAC;gBACnD,CAAC;YACH,CAAC;QACH,CAAC;QAED,KAAK;YACH,IAAI,OAAO,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,MAAM;gBAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;YAEhF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;gBAC7B,oEAAoE;gBACpE,oEAAoE;gBACpE,8CAA8C;gBAC9C,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,EAAE;oBAC7B,aAAa,EAAE,CAAC;oBAChB,YAAY,EAAE,CAAC;oBACf,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,MAAM;wBAAE,MAAM,CAAC,KAAK,EAAE,CAAC;oBAC3D,OAAO,EAAE,CAAC;gBACZ,CAAC,EAAE,cAAc,CAAC,CAAC;gBAEnB,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,IAAI;oBAAE,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;gBAEpE,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;oBACxB,YAAY,CAAC,MAAM,CAAC,CAAC;oBACrB,aAAa,EAAE,CAAC;oBAChB,OAAO,EAAE,CAAC;gBACZ,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB;IAC3C,IAAI,CAAC;QACH,cAAc,CAAC,MAAM,cAAc,EAAE,CAAC,CAAC;QACvC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC1C,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swttch/extend-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Companion tools for Swttch that live outside the plugin bundle: Claude Code account/usage SDK, the ccb CLI, and speech-to-text.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -60,6 +60,8 @@
|
|
|
60
60
|
"typescript": "^6.0.2"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
+
"https-proxy-agent": "^9.1.0",
|
|
64
|
+
"socks-proxy-agent": "^10.1.0",
|
|
63
65
|
"ws": "^8.21.3"
|
|
64
66
|
}
|
|
65
67
|
}
|