coolhand-cli 0.5.2 → 0.7.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.md +27 -121
- package/dist/api/last-sync.d.ts +5 -0
- package/dist/api/last-sync.d.ts.map +1 -1
- package/dist/api/last-sync.js +5 -0
- package/dist/api/last-sync.js.map +1 -1
- package/dist/auth/callback-server.js +3 -3
- package/dist/auth/callback-server.js.map +1 -1
- package/dist/cli.d.ts +10 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +320 -37
- package/dist/cli.js.map +1 -1
- package/dist/commands/analyze-claude-sessions.d.ts.map +1 -1
- package/dist/commands/analyze-claude-sessions.js +37 -6
- package/dist/commands/analyze-claude-sessions.js.map +1 -1
- package/dist/commands/claude.d.ts +10 -10
- package/dist/commands/claude.d.ts.map +1 -1
- package/dist/commands/claude.js +6 -77
- package/dist/commands/claude.js.map +1 -1
- package/dist/commands/clients.js +1 -1
- package/dist/commands/clients.js.map +1 -1
- package/dist/commands/flush-pending.d.ts +25 -0
- package/dist/commands/flush-pending.d.ts.map +1 -0
- package/dist/commands/flush-pending.js +100 -0
- package/dist/commands/flush-pending.js.map +1 -0
- package/dist/commands/get-workload.d.ts +3 -0
- package/dist/commands/get-workload.d.ts.map +1 -0
- package/dist/commands/get-workload.js +28 -0
- package/dist/commands/get-workload.js.map +1 -0
- package/dist/commands/list-workloads.d.ts +3 -0
- package/dist/commands/list-workloads.d.ts.map +1 -0
- package/dist/commands/list-workloads.js +52 -0
- package/dist/commands/list-workloads.js.map +1 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +41 -6
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/mcp-call.d.ts.map +1 -1
- package/dist/commands/mcp-call.js +57 -10
- package/dist/commands/mcp-call.js.map +1 -1
- package/dist/commands/monitor.d.ts +9 -0
- package/dist/commands/monitor.d.ts.map +1 -0
- package/dist/commands/monitor.js +10 -0
- package/dist/commands/monitor.js.map +1 -0
- package/dist/commands/status.js +2 -2
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/update-workload.d.ts +3 -0
- package/dist/commands/update-workload.d.ts.map +1 -0
- package/dist/commands/update-workload.js +35 -0
- package/dist/commands/update-workload.js.map +1 -0
- package/dist/commands/whoami.js +1 -1
- package/dist/commands/whoami.js.map +1 -1
- package/dist/commands/wildcard.d.ts.map +1 -1
- package/dist/commands/wildcard.js +118 -20
- package/dist/commands/wildcard.js.map +1 -1
- package/dist/config.d.ts +16 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +127 -6
- package/dist/config.js.map +1 -1
- package/dist/pending-store.d.ts +31 -0
- package/dist/pending-store.d.ts.map +1 -0
- package/dist/pending-store.js +81 -0
- package/dist/pending-store.js.map +1 -0
- package/dist/prompt.d.ts +13 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +30 -0
- package/dist/prompt.js.map +1 -0
- package/dist/proxy/certs.d.ts +14 -0
- package/dist/proxy/certs.d.ts.map +1 -0
- package/dist/proxy/certs.js +64 -0
- package/dist/proxy/certs.js.map +1 -0
- package/dist/proxy/interceptor.d.ts +16 -0
- package/dist/proxy/interceptor.d.ts.map +1 -0
- package/dist/proxy/interceptor.js +57 -0
- package/dist/proxy/interceptor.js.map +1 -0
- package/dist/proxy/proxy.d.ts +20 -0
- package/dist/proxy/proxy.d.ts.map +1 -0
- package/dist/proxy/proxy.js +114 -0
- package/dist/proxy/proxy.js.map +1 -0
- package/dist/proxy/sender.d.ts +32 -0
- package/dist/proxy/sender.d.ts.map +1 -0
- package/dist/proxy/sender.js +66 -0
- package/dist/proxy/sender.js.map +1 -0
- package/dist/proxy/wrap-runner.d.ts +36 -0
- package/dist/proxy/wrap-runner.d.ts.map +1 -0
- package/dist/proxy/wrap-runner.js +153 -0
- package/dist/proxy/wrap-runner.js.map +1 -0
- package/dist/types.d.ts +29 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/config.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { promises as fs } from 'fs';
|
|
2
|
+
import { createInterface } from 'readline';
|
|
2
3
|
import * as os from 'os';
|
|
3
4
|
import * as path from 'path';
|
|
4
5
|
import { randomBytes } from 'crypto';
|
|
5
6
|
import { CliError } from './errors.js';
|
|
7
|
+
import { logger } from './logger.js';
|
|
6
8
|
const CONFIG_FILENAME = 'config.json';
|
|
7
9
|
const DIR_MODE = 0o700;
|
|
8
10
|
const FILE_MODE = 0o600;
|
|
@@ -53,17 +55,20 @@ async function ensureDir(dir) {
|
|
|
53
55
|
// POSIX-only — Windows will reject chmod with the mode we passed; ignore.
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Write a file atomically with restrictive permissions: write to a temp file in the
|
|
60
|
+
* same directory, then rename it into place (rename is atomic on POSIX), so a reader
|
|
61
|
+
* never sees a half-written file. The caller is responsible for ensuring the parent
|
|
62
|
+
* directory exists. Shared by `saveConfig` and the pending-record store.
|
|
63
|
+
*/
|
|
64
|
+
export async function atomicWriteFile(filePath, data, mode = FILE_MODE) {
|
|
58
65
|
const dir = path.dirname(filePath);
|
|
59
|
-
await ensureDir(dir);
|
|
60
66
|
const tmpPath = path.join(dir, `.${path.basename(filePath)}.${randomBytes(6).toString('hex')}.tmp`);
|
|
61
|
-
const data = `${JSON.stringify(cfg, null, 2)}\n`;
|
|
62
67
|
try {
|
|
63
|
-
await fs.writeFile(tmpPath, data, { mode
|
|
68
|
+
await fs.writeFile(tmpPath, data, { mode });
|
|
64
69
|
await fs.rename(tmpPath, filePath);
|
|
65
70
|
try {
|
|
66
|
-
await fs.chmod(filePath,
|
|
71
|
+
await fs.chmod(filePath, mode);
|
|
67
72
|
}
|
|
68
73
|
catch {
|
|
69
74
|
// ignore on Windows
|
|
@@ -74,6 +79,11 @@ export async function saveConfig(cfg) {
|
|
|
74
79
|
throw new CliError('CONFIG_WRITE_FAILED', `Failed to write ${filePath}: ${err.message}`);
|
|
75
80
|
}
|
|
76
81
|
}
|
|
82
|
+
export async function saveConfig(cfg) {
|
|
83
|
+
const filePath = configPath();
|
|
84
|
+
await ensureDir(path.dirname(filePath));
|
|
85
|
+
await atomicWriteFile(filePath, `${JSON.stringify(cfg, null, 2)}\n`, FILE_MODE);
|
|
86
|
+
}
|
|
77
87
|
export async function deleteConfig() {
|
|
78
88
|
try {
|
|
79
89
|
await fs.rm(configPath(), { force: true });
|
|
@@ -124,4 +134,115 @@ export async function setDefault(clientId) {
|
|
|
124
134
|
await saveConfig(cfg);
|
|
125
135
|
return cfg;
|
|
126
136
|
}
|
|
137
|
+
const PROMPT_TIMEOUT_MS = 30000;
|
|
138
|
+
async function promptClientSelection(clients) {
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
const lines = clients.map((c, i) => ` ${i + 1}. ${c.client_name} (${c.client_id})`).join('\n');
|
|
141
|
+
process.stderr.write(`Multiple clients configured — which one?\n${lines}\nEnter number: `);
|
|
142
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
143
|
+
let settled = false;
|
|
144
|
+
const timer = setTimeout(() => {
|
|
145
|
+
if (settled) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
settled = true;
|
|
149
|
+
rl.close();
|
|
150
|
+
process.stdin.pause();
|
|
151
|
+
reject(new CliError('INVALID_ARGS', 'No selection made within 30 seconds — pass --client-id to skip this prompt.'));
|
|
152
|
+
}, PROMPT_TIMEOUT_MS);
|
|
153
|
+
timer.unref();
|
|
154
|
+
rl.once('line', (answer) => {
|
|
155
|
+
if (settled) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
settled = true;
|
|
159
|
+
clearTimeout(timer);
|
|
160
|
+
rl.close();
|
|
161
|
+
process.stdin.pause();
|
|
162
|
+
const n = parseInt(answer.trim(), 10);
|
|
163
|
+
if (isNaN(n) || n < 1 || n > clients.length) {
|
|
164
|
+
reject(new CliError('INVALID_ARGS', `Invalid selection "${answer.trim()}" — enter a number between 1 and ${clients.length}.`));
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
resolve(clients[n - 1]);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
rl.once('close', () => {
|
|
171
|
+
if (settled) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
settled = true;
|
|
175
|
+
clearTimeout(timer);
|
|
176
|
+
process.stdin.pause();
|
|
177
|
+
reject(new CliError('INVALID_ARGS', 'No client selected.'));
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Resolves which client to use for an API command. Priority:
|
|
183
|
+
* explicit clientId arg > COOLHAND_CLIENT_ID env > default_client_id config >
|
|
184
|
+
* auto-pick if only one client exists > interactive prompt (TTY) or error.
|
|
185
|
+
*
|
|
186
|
+
* Prints "Client: <name> (<id>)" to stderr after resolution so the user always
|
|
187
|
+
* knows which account's data they are looking at.
|
|
188
|
+
*/
|
|
189
|
+
export async function resolveClient(cfg, clientId) {
|
|
190
|
+
if (clientId === '') {
|
|
191
|
+
throw new CliError('INVALID_ARGS', 'clientId must not be empty');
|
|
192
|
+
}
|
|
193
|
+
// 1. Explicit --client-id
|
|
194
|
+
if (clientId !== undefined) {
|
|
195
|
+
const entry = cfg.clients[clientId];
|
|
196
|
+
if (!entry) {
|
|
197
|
+
throw new CliError('CLIENT_NOT_FOUND', `No client "${clientId}" is configured.`);
|
|
198
|
+
}
|
|
199
|
+
process.stderr.write(`Client: ${entry.client_name} (${entry.client_id})\n`);
|
|
200
|
+
return entry;
|
|
201
|
+
}
|
|
202
|
+
// 2. COOLHAND_CLIENT_ID env var
|
|
203
|
+
// An empty string env var (COOLHAND_CLIENT_ID="") is treated as unset and falls
|
|
204
|
+
// through to the default/auto-pick path. This differs from an explicit empty
|
|
205
|
+
// --client-id "" argument (caught above as INVALID_ARGS) because an empty env var
|
|
206
|
+
// is most likely an accidental mis-set in a script, not a deliberate selection.
|
|
207
|
+
const envClientId = process.env.COOLHAND_CLIENT_ID;
|
|
208
|
+
if (envClientId) {
|
|
209
|
+
const entry = cfg.clients[envClientId];
|
|
210
|
+
if (!entry) {
|
|
211
|
+
throw new CliError('CLIENT_NOT_FOUND', `COOLHAND_CLIENT_ID="${envClientId}" does not match any configured client.`);
|
|
212
|
+
}
|
|
213
|
+
process.stderr.write(`Client: ${entry.client_name} (${entry.client_id})\n`);
|
|
214
|
+
return entry;
|
|
215
|
+
}
|
|
216
|
+
// 3. Configured default
|
|
217
|
+
if (cfg.default_client_id) {
|
|
218
|
+
const entry = cfg.clients[cfg.default_client_id];
|
|
219
|
+
if (entry) {
|
|
220
|
+
process.stderr.write(`Client: ${entry.client_name} (${entry.client_id})\n`);
|
|
221
|
+
return entry;
|
|
222
|
+
}
|
|
223
|
+
logger.warn(`Configured default client "${cfg.default_client_id}" no longer exists. Run \`coolhand clients use <id>\` to reset it.`);
|
|
224
|
+
}
|
|
225
|
+
const clients = Object.values(cfg.clients);
|
|
226
|
+
// 4. No clients at all
|
|
227
|
+
if (clients.length === 0) {
|
|
228
|
+
throw new CliError('NOT_CONFIGURED', 'Not logged in. Run `coolhand login` to authenticate.');
|
|
229
|
+
}
|
|
230
|
+
// 5. Exactly one client — auto-pick without prompting
|
|
231
|
+
if (clients.length === 1) {
|
|
232
|
+
const entry = clients[0];
|
|
233
|
+
process.stderr.write(`Client: ${entry.client_name} (${entry.client_id})\n`);
|
|
234
|
+
return entry;
|
|
235
|
+
}
|
|
236
|
+
// 6. Multiple clients, no default — prompt interactively when both stdin and stderr are a
|
|
237
|
+
// TTY. The prompt writes to stderr, so checking stderr.isTTY (not stdout.isTTY) is the
|
|
238
|
+
// right signal that the user can see and respond to it. When stdin is not a TTY the user
|
|
239
|
+
// cannot type a selection, so the descriptive error path is cleaner.
|
|
240
|
+
if (process.stdin.isTTY && process.stderr.isTTY) {
|
|
241
|
+
const entry = await promptClientSelection(clients);
|
|
242
|
+
process.stderr.write(`Client: ${entry.client_name} (${entry.client_id})\n`);
|
|
243
|
+
return entry;
|
|
244
|
+
}
|
|
245
|
+
const list = clients.map((c) => ` ${c.client_id} ${c.client_name}`).join('\n');
|
|
246
|
+
throw new CliError('NOT_CONFIGURED', `Multiple clients configured but no default is set.\n\nConfigured clients:\n${list}\n\nRun \`coolhand clients use <id>\` to set a default, or pass --client-id.`);
|
|
247
|
+
}
|
|
127
248
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** What kind of SDK call the flush worker should make to upload a record. */
|
|
2
|
+
export type PendingKind = 'feedback';
|
|
3
|
+
export interface PendingRecord {
|
|
4
|
+
/** Human-readable command name, e.g. "report-blocker". Used in the filename. */
|
|
5
|
+
command: string;
|
|
6
|
+
/** Selects the SDK call used to upload this record on flush. */
|
|
7
|
+
kind: PendingKind;
|
|
8
|
+
/** The exact payload to send (e.g. an LLMRequestLogFeedback object). */
|
|
9
|
+
payload: unknown;
|
|
10
|
+
/** Optional stored-client id to resolve the api key/base url at flush time. */
|
|
11
|
+
clientId?: string;
|
|
12
|
+
/** ISO timestamp of when the record was saved locally. */
|
|
13
|
+
savedAt: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function pendingDir(): string;
|
|
16
|
+
/**
|
|
17
|
+
* Write a record to the pending store and return its absolute path. The filename
|
|
18
|
+
* combines the command, a filesystem-safe timestamp, and random bytes so concurrent
|
|
19
|
+
* saves never collide.
|
|
20
|
+
*/
|
|
21
|
+
export declare function savePendingRecord(record: PendingRecord): Promise<string>;
|
|
22
|
+
/** Absolute paths of every saved record, sorted oldest-first by filename. */
|
|
23
|
+
export declare function listPendingFiles(): Promise<string[]>;
|
|
24
|
+
export declare function countPending(): Promise<number>;
|
|
25
|
+
export declare function readPendingRecord(filePath: string): Promise<PendingRecord>;
|
|
26
|
+
export declare function removePendingRecord(filePath: string): Promise<void>;
|
|
27
|
+
/** Set the marker that tells the next command a previous flush failed. */
|
|
28
|
+
export declare function markFlushFailed(): Promise<void>;
|
|
29
|
+
export declare function clearFlushFailed(): Promise<void>;
|
|
30
|
+
export declare function flushFailed(): Promise<boolean>;
|
|
31
|
+
//# sourceMappingURL=pending-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-store.d.ts","sourceRoot":"","sources":["../src/pending-store.ts"],"names":[],"mappings":"AAiBA,6EAA6E;AAC7E,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC;AAErC,MAAM,WAAW,aAAa;IAC5B,gFAAgF;IAChF,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,IAAI,EAAE,WAAW,CAAC;IAClB,wEAAwE;IACxE,OAAO,EAAE,OAAO,CAAC;IACjB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAQD;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ9E;AAED,6EAA6E;AAC7E,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAc1D;AAED,wBAAsB,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAEpD;AAED,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAGhF;AAED,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEzE;AAED,0EAA0E;AAC1E,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAGrD;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAEtD;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAOpD"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { randomBytes } from 'crypto';
|
|
4
|
+
import { configDir, atomicWriteFile } from './config.js';
|
|
5
|
+
/**
|
|
6
|
+
* Local fallback store for records that could not be sent to Coolhand because the
|
|
7
|
+
* user was not logged in. Records live under `~/.coolhand/pending/` and are uploaded
|
|
8
|
+
* by the background flush on the next `coolhand login` (see `commands/flush-pending`).
|
|
9
|
+
*/
|
|
10
|
+
const PENDING_SUBDIR = 'pending';
|
|
11
|
+
const DIR_MODE = 0o700;
|
|
12
|
+
const FILE_MODE = 0o600;
|
|
13
|
+
// A dotfile so it is skipped by `listPendingFiles` (which only returns `*.json`).
|
|
14
|
+
const FLUSH_FAILED_MARKER = '.flush-failed';
|
|
15
|
+
export function pendingDir() {
|
|
16
|
+
return path.join(configDir(), PENDING_SUBDIR);
|
|
17
|
+
}
|
|
18
|
+
async function ensurePendingDir() {
|
|
19
|
+
const dir = pendingDir();
|
|
20
|
+
await fs.mkdir(dir, { recursive: true, mode: DIR_MODE });
|
|
21
|
+
return dir;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Write a record to the pending store and return its absolute path. The filename
|
|
25
|
+
* combines the command, a filesystem-safe timestamp, and random bytes so concurrent
|
|
26
|
+
* saves never collide.
|
|
27
|
+
*/
|
|
28
|
+
export async function savePendingRecord(record) {
|
|
29
|
+
const dir = await ensurePendingDir();
|
|
30
|
+
const safeCommand = record.command.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
31
|
+
const safeStamp = record.savedAt.replace(/[:.]/g, '-');
|
|
32
|
+
const fileName = `${safeCommand}-${safeStamp}-${randomBytes(4).toString('hex')}.json`;
|
|
33
|
+
const filePath = path.join(dir, fileName);
|
|
34
|
+
await atomicWriteFile(filePath, `${JSON.stringify(record, null, 2)}\n`, FILE_MODE);
|
|
35
|
+
return filePath;
|
|
36
|
+
}
|
|
37
|
+
/** Absolute paths of every saved record, sorted oldest-first by filename. */
|
|
38
|
+
export async function listPendingFiles() {
|
|
39
|
+
let entries;
|
|
40
|
+
try {
|
|
41
|
+
entries = await fs.readdir(pendingDir());
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
if (err.code === 'ENOENT') {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
return entries
|
|
50
|
+
.filter((name) => name.endsWith('.json') && !name.startsWith('.'))
|
|
51
|
+
.sort()
|
|
52
|
+
.map((name) => path.join(pendingDir(), name));
|
|
53
|
+
}
|
|
54
|
+
export async function countPending() {
|
|
55
|
+
return (await listPendingFiles()).length;
|
|
56
|
+
}
|
|
57
|
+
export async function readPendingRecord(filePath) {
|
|
58
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
59
|
+
return JSON.parse(raw);
|
|
60
|
+
}
|
|
61
|
+
export async function removePendingRecord(filePath) {
|
|
62
|
+
await fs.rm(filePath, { force: true });
|
|
63
|
+
}
|
|
64
|
+
/** Set the marker that tells the next command a previous flush failed. */
|
|
65
|
+
export async function markFlushFailed() {
|
|
66
|
+
await ensurePendingDir();
|
|
67
|
+
await atomicWriteFile(path.join(pendingDir(), FLUSH_FAILED_MARKER), '', FILE_MODE);
|
|
68
|
+
}
|
|
69
|
+
export async function clearFlushFailed() {
|
|
70
|
+
await fs.rm(path.join(pendingDir(), FLUSH_FAILED_MARKER), { force: true });
|
|
71
|
+
}
|
|
72
|
+
export async function flushFailed() {
|
|
73
|
+
try {
|
|
74
|
+
await fs.access(path.join(pendingDir(), FLUSH_FAILED_MARKER));
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=pending-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-store.js","sourceRoot":"","sources":["../src/pending-store.ts"],"names":[],"mappings":"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"}
|
package/dist/prompt.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True only when both stdin and stdout are attached to a real terminal. Used to gate
|
|
3
|
+
* every interactive prompt: in a sandbox, pipe, or CI run there is no terminal, so we
|
|
4
|
+
* must never block waiting for input.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isInteractive(): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Ask a yes/no question. Returns `false` immediately when not interactive (no TTY),
|
|
9
|
+
* so non-interactive callers — the main `report-blocker` audience — are never blocked.
|
|
10
|
+
* The prompt is written to stderr so stdout stays clean for machine-readable output.
|
|
11
|
+
*/
|
|
12
|
+
export declare function confirm(question: string): Promise<boolean>;
|
|
13
|
+
//# sourceMappingURL=prompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAahE"}
|
package/dist/prompt.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as readline from 'readline';
|
|
2
|
+
/**
|
|
3
|
+
* True only when both stdin and stdout are attached to a real terminal. Used to gate
|
|
4
|
+
* every interactive prompt: in a sandbox, pipe, or CI run there is no terminal, so we
|
|
5
|
+
* must never block waiting for input.
|
|
6
|
+
*/
|
|
7
|
+
export function isInteractive() {
|
|
8
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Ask a yes/no question. Returns `false` immediately when not interactive (no TTY),
|
|
12
|
+
* so non-interactive callers — the main `report-blocker` audience — are never blocked.
|
|
13
|
+
* The prompt is written to stderr so stdout stays clean for machine-readable output.
|
|
14
|
+
*/
|
|
15
|
+
export async function confirm(question) {
|
|
16
|
+
if (!isInteractive()) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
20
|
+
try {
|
|
21
|
+
const answer = await new Promise((resolve) => {
|
|
22
|
+
rl.question(`${question} [y/N] `, resolve);
|
|
23
|
+
});
|
|
24
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
rl.close();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,UAAU,CAAC;AAErC;;;;GAIG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9D,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,QAAgB;IAC5C,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACtF,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE;YACnD,EAAE,CAAC,QAAQ,CAAC,GAAG,QAAQ,SAAS,EAAE,OAAO,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QACH,OAAO,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface CACredentials {
|
|
2
|
+
key: string;
|
|
3
|
+
cert: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Get or create a CA certificate for MITM proxy interception.
|
|
7
|
+
* Certs are persisted to disk so they can be installed in the system trust store.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getOrCreateCA(certDir?: string): Promise<CACredentials>;
|
|
10
|
+
/**
|
|
11
|
+
* Returns the path to the CA certificate file.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getCertPath(certDir?: string): string;
|
|
14
|
+
//# sourceMappingURL=certs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"certs.d.ts","sourceRoot":"","sources":["../../src/proxy/certs.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,OAAO,GAAE,MAAyB,GACjC,OAAO,CAAC,aAAa,CAAC,CA6CxB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,MAAyB,GAAG,MAAM,CAEtE"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
import * as mockttp from "mockttp";
|
|
5
|
+
const DEFAULT_CERT_DIR = path.join(os.homedir(), ".coolhand", "proxy");
|
|
6
|
+
/**
|
|
7
|
+
* Get or create a CA certificate for MITM proxy interception.
|
|
8
|
+
* Certs are persisted to disk so they can be installed in the system trust store.
|
|
9
|
+
*/
|
|
10
|
+
export async function getOrCreateCA(certDir = DEFAULT_CERT_DIR) {
|
|
11
|
+
const keyPath = path.join(certDir, "ca-key.pem");
|
|
12
|
+
const certPath = path.join(certDir, "ca-cert.pem");
|
|
13
|
+
try {
|
|
14
|
+
// Known limitation: if both files exist but are from different generations
|
|
15
|
+
// (e.g. a partial write left mismatched files), the stale pair is loaded
|
|
16
|
+
// silently. The temp+rename write strategy below prevents this for new
|
|
17
|
+
// writes, but does not detect pre-existing mismatches.
|
|
18
|
+
return {
|
|
19
|
+
key: fs.readFileSync(keyPath, "utf8"),
|
|
20
|
+
cert: fs.readFileSync(certPath, "utf8"),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
const code = err.code;
|
|
25
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") {
|
|
26
|
+
throw err;
|
|
27
|
+
}
|
|
28
|
+
// One or both files absent — fall through to generate a fresh CA pair.
|
|
29
|
+
// If only one is missing, the installed CA cert will be invalidated; warn.
|
|
30
|
+
if (fs.existsSync(keyPath) || fs.existsSync(certPath)) {
|
|
31
|
+
process.stderr.write("[coolhand] Warning: CA certificate files are incomplete — regenerating key/cert pair." +
|
|
32
|
+
" If you installed the previous ca-cert.pem in your system trust store, re-install the new one.\n");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const ca = await mockttp.generateCACertificate();
|
|
36
|
+
fs.mkdirSync(certDir, { recursive: true, mode: 0o700 });
|
|
37
|
+
// Write via temp+rename so a crash between the two writes never leaves a
|
|
38
|
+
// mismatched key/cert pair on disk (TOCTOU guard).
|
|
39
|
+
const keyTmp = keyPath + ".tmp";
|
|
40
|
+
const certTmp = certPath + ".tmp";
|
|
41
|
+
try {
|
|
42
|
+
fs.writeFileSync(keyTmp, ca.key, { mode: 0o600 });
|
|
43
|
+
fs.writeFileSync(certTmp, ca.cert, { mode: 0o644 });
|
|
44
|
+
fs.renameSync(keyTmp, keyPath);
|
|
45
|
+
fs.renameSync(certTmp, certPath);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
for (const tmp of [keyTmp, certTmp]) {
|
|
49
|
+
try {
|
|
50
|
+
fs.unlinkSync(tmp);
|
|
51
|
+
}
|
|
52
|
+
catch { /* ignore */ }
|
|
53
|
+
}
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
return ca;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Returns the path to the CA certificate file.
|
|
60
|
+
*/
|
|
61
|
+
export function getCertPath(certDir = DEFAULT_CERT_DIR) {
|
|
62
|
+
return path.join(certDir, "ca-cert.pem");
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=certs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"certs.js","sourceRoot":"","sources":["../../src/proxy/certs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAEnC,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;AAOvE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,UAAkB,gBAAgB;IAElC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IAEnD,IAAI,CAAC;QACH,2EAA2E;QAC3E,yEAAyE;QACzE,uEAAuE;QACvE,uDAAuD;QACvD,OAAO;YACL,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC;YACrC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC;SACxC,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,GAAI,GAA6B,CAAC,IAAI,CAAC;QACjD,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YAAC,MAAM,GAAG,CAAC;QAAC,CAAC;QAC3D,uEAAuE;QACvE,2EAA2E;QAC3E,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YACtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,uFAAuF;gBACvF,kGAAkG,CACnG,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC,qBAAqB,EAAE,CAAC;IACjD,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACxD,yEAAyE;IACzE,mDAAmD;IACnD,MAAM,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IAChC,MAAM,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;IAClC,IAAI,CAAC;QACH,EAAE,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAClD,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACpD,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC/B,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,KAAK,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;YACpC,IAAI,CAAC;gBAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;QACpD,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,UAAkB,gBAAgB;IAC5D,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;AAC3C,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a URL matches a known AI API pattern.
|
|
3
|
+
* Uses coolhand-node's PatternMatchingService which covers
|
|
4
|
+
* OpenAI, Anthropic, Google AI, Cohere, Hugging Face, etc.
|
|
5
|
+
*/
|
|
6
|
+
export declare function shouldCapture(url: string): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Flatten a raw header map (values may be string[], string, or undefined)
|
|
9
|
+
* into Record<string, string>, joining multi-value headers with ", ".
|
|
10
|
+
*/
|
|
11
|
+
export declare function flattenHeaders(headers: Record<string, string | string[] | undefined>): Record<string, string>;
|
|
12
|
+
/**
|
|
13
|
+
* Sanitize headers by redacting sensitive values (API keys, auth tokens).
|
|
14
|
+
*/
|
|
15
|
+
export declare function sanitizeHeaders(headers: Record<string, string>): Record<string, string>;
|
|
16
|
+
//# sourceMappingURL=interceptor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../../src/proxy/interceptor.ts"],"names":[],"mappings":"AAYA;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,GACrD,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOxB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAmBxB"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { PatternMatchingService } from "coolhand-node";
|
|
2
|
+
// Lazily instantiated so a constructor error surfaces at shouldCapture() call
|
|
3
|
+
// time rather than at module load time. The proxy request handler wraps
|
|
4
|
+
// shouldCapture() in a try/catch so the error degrades gracefully (skip
|
|
5
|
+
// capture) rather than crashing mockttp's event dispatch.
|
|
6
|
+
let _patternService;
|
|
7
|
+
function getPatternService() {
|
|
8
|
+
if (!_patternService) {
|
|
9
|
+
_patternService = new PatternMatchingService({ silent: true });
|
|
10
|
+
}
|
|
11
|
+
return _patternService;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Check if a URL matches a known AI API pattern.
|
|
15
|
+
* Uses coolhand-node's PatternMatchingService which covers
|
|
16
|
+
* OpenAI, Anthropic, Google AI, Cohere, Hugging Face, etc.
|
|
17
|
+
*/
|
|
18
|
+
export function shouldCapture(url) {
|
|
19
|
+
return getPatternService().matchesAPIPatternFromURL(url) !== null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Flatten a raw header map (values may be string[], string, or undefined)
|
|
23
|
+
* into Record<string, string>, joining multi-value headers with ", ".
|
|
24
|
+
*/
|
|
25
|
+
export function flattenHeaders(headers) {
|
|
26
|
+
const result = {};
|
|
27
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
28
|
+
if (value === undefined) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
result[key] = Array.isArray(value) ? value.join(", ") : value;
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Sanitize headers by redacting sensitive values (API keys, auth tokens).
|
|
37
|
+
*/
|
|
38
|
+
export function sanitizeHeaders(headers) {
|
|
39
|
+
const sensitiveKeys = new Set([
|
|
40
|
+
"authorization",
|
|
41
|
+
"proxy-authorization",
|
|
42
|
+
"x-goog-api-key",
|
|
43
|
+
"x-api-key",
|
|
44
|
+
"api-key",
|
|
45
|
+
"cookie",
|
|
46
|
+
"set-cookie",
|
|
47
|
+
"openai-api-key",
|
|
48
|
+
]);
|
|
49
|
+
const sanitized = {};
|
|
50
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
51
|
+
sanitized[key] = sensitiveKeys.has(key.toLowerCase())
|
|
52
|
+
? "[REDACTED]"
|
|
53
|
+
: value;
|
|
54
|
+
}
|
|
55
|
+
return sanitized;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=interceptor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interceptor.js","sourceRoot":"","sources":["../../src/proxy/interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAEvD,8EAA8E;AAC9E,wEAAwE;AACxE,wEAAwE;AACxE,0DAA0D;AAC1D,IAAI,eAAmD,CAAC;AACxD,SAAS,iBAAiB;IACxB,IAAI,CAAC,eAAe,EAAE,CAAC;QAAC,eAAe,GAAG,IAAI,sBAAsB,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IAAC,CAAC;IACzF,OAAO,eAAe,CAAC;AACzB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,OAAO,iBAAiB,EAAE,CAAC,wBAAwB,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAC5B,OAAsD;IAEtD,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YAAC,SAAS;QAAC,CAAC;QACtC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAChE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,OAA+B;IAE/B,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC;QAC5B,eAAe;QACf,qBAAqB;QACrB,gBAAgB;QAChB,WAAW;QACX,SAAS;QACT,QAAQ;QACR,YAAY;QACZ,gBAAgB;KACjB,CAAC,CAAC;IAEH,MAAM,SAAS,GAA2B,EAAE,CAAC;IAC7C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,SAAS,CAAC,GAAG,CAAC,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;YACnD,CAAC,CAAC,YAAY;YACd,CAAC,CAAC,KAAK,CAAC;IACZ,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CACredentials } from "./certs.js";
|
|
2
|
+
export interface ProxyOptions {
|
|
3
|
+
/** The port to bind to. Omit or pass 0 to let the OS pick a free ephemeral port. */
|
|
4
|
+
port?: number;
|
|
5
|
+
apiKey: string;
|
|
6
|
+
apiEndpoint?: string;
|
|
7
|
+
silent?: boolean;
|
|
8
|
+
/** Overrides the default `<package>/claude` collector label sent with each captured request. */
|
|
9
|
+
collector?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ProxyInstance {
|
|
12
|
+
port: number;
|
|
13
|
+
stop: () => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Start an HTTPS MITM proxy that intercepts LLM API calls
|
|
17
|
+
* and forwards them to the Coolhand platform.
|
|
18
|
+
*/
|
|
19
|
+
export declare function startProxy(ca: CACredentials, options: ProxyOptions): Promise<ProxyInstance>;
|
|
20
|
+
//# sourceMappingURL=proxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/proxy/proxy.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,YAAY;IAC3B,oFAAoF;IACpF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,gGAAgG;IAChG,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAWD;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,aAAa,EACjB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,aAAa,CAAC,CA8GxB"}
|