terminalhire 0.42.44 → 0.42.45

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.
@@ -0,0 +1,584 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // src/state-dir.ts
13
+ import { closeSync, constants, fchmodSync, fstatSync, mkdirSync, openSync } from "fs";
14
+ function warnStateDirOnce(dir, message) {
15
+ if (warnedDirs.has(dir)) return;
16
+ warnedDirs.add(dir);
17
+ try {
18
+ process.stderr.write(message);
19
+ } catch {
20
+ }
21
+ }
22
+ function ensureStateDir(dir) {
23
+ mkdirSync(dir, { recursive: true, mode: STATE_DIR_MODE });
24
+ const noFollow = constants.O_NOFOLLOW ?? 0;
25
+ let fd;
26
+ try {
27
+ fd = openSync(dir, constants.O_RDONLY | noFollow);
28
+ } catch (err) {
29
+ if (err?.code === "ELOOP") {
30
+ warnStateDirOnce(
31
+ dir,
32
+ `terminalhire: ${dir} is a symlink \u2014 leaving its permissions alone; the 0700 guarantee on the state directory is NOT enforced.
33
+ `
34
+ );
35
+ return STATE_DIR_SYMLINK;
36
+ }
37
+ return STATE_DIR_UNVERIFIED;
38
+ }
39
+ try {
40
+ const currentMode = fstatSync(fd).mode & 511;
41
+ if ((currentMode & ~STATE_DIR_MODE) !== 0) {
42
+ fchmodSync(fd, currentMode & STATE_DIR_MODE);
43
+ }
44
+ return STATE_DIR_OK;
45
+ } catch {
46
+ return STATE_DIR_UNVERIFIED;
47
+ } finally {
48
+ try {
49
+ closeSync(fd);
50
+ } catch {
51
+ }
52
+ }
53
+ }
54
+ function applyStateDirSecretPolicy(dir, status) {
55
+ if (status === STATE_DIR_SYMLINK) {
56
+ throw new Error(
57
+ `terminalhire: refusing to write key material into ${dir} \u2014 it is a symlink, not a directory.
58
+ A write through it would FOLLOW THE LINK and place key/token material wherever the symlink points, outside our control and outside the "owner-only" (0700) guarantee this directory is supposed to carry.
59
+ Fix: remove the symlink so terminalhire can recreate it as a real directory \u2014
60
+ rm ${dir}
61
+ then re-run the command. If the symlink is intentional, point TERMINALHIRE_DIR at a real directory instead of routing it through this one.`
62
+ );
63
+ }
64
+ if (status === STATE_DIR_UNVERIFIED && !warnedUnverifiedSecretWriteThisProcess) {
65
+ warnedUnverifiedSecretWriteThisProcess = true;
66
+ try {
67
+ process.stderr.write(
68
+ `terminalhire: could not verify ${dir}'s permissions (expected on Windows \u2014 POSIX mode bits do not apply there) \u2014 proceeding, but the "owner-only" guarantee on key/token storage is NOT enforced on this platform.
69
+ `
70
+ );
71
+ } catch {
72
+ }
73
+ }
74
+ }
75
+ function ensureStateDirForSecret(dir) {
76
+ applyStateDirSecretPolicy(dir, ensureStateDir(dir));
77
+ }
78
+ var STATE_DIR_MODE, STATE_DIR_OK, STATE_DIR_SYMLINK, STATE_DIR_UNVERIFIED, warnedDirs, warnedUnverifiedSecretWriteThisProcess;
79
+ var init_state_dir = __esm({
80
+ "src/state-dir.ts"() {
81
+ "use strict";
82
+ STATE_DIR_MODE = 448;
83
+ STATE_DIR_OK = "ok";
84
+ STATE_DIR_SYMLINK = "symlink";
85
+ STATE_DIR_UNVERIFIED = "unverified";
86
+ warnedDirs = /* @__PURE__ */ new Set();
87
+ warnedUnverifiedSecretWriteThisProcess = false;
88
+ }
89
+ });
90
+
91
+ // src/test-race-barrier.ts
92
+ import { closeSync as closeSync2, constants as constants2, existsSync, lstatSync, openSync as openSync2 } from "fs";
93
+ import { join as join3 } from "path";
94
+ function syncSleepMs(ms) {
95
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
96
+ }
97
+ function waitForTestRaceBarrier(phase) {
98
+ const root = process.env[ENV_VAR];
99
+ if (!root) return;
100
+ const phaseDir = join3(root, phase);
101
+ if (!existsSync(phaseDir)) return;
102
+ const readyFile = join3(phaseDir, `ready-${process.pid}`);
103
+ const goFile = join3(phaseDir, "go");
104
+ const noFollow = constants2.O_NOFOLLOW ?? 0;
105
+ if (lstatSync(readyFile, { throwIfNoEntry: false })) {
106
+ throw new Error(
107
+ `terminalhire: test race barrier "${phase}" found something already at its ready marker path ${readyFile} (regular file or symlink) \u2014 refusing rather than following or overwriting whatever is already there (this only fires under ${ENV_VAR}, never in production).`
108
+ );
109
+ }
110
+ let readyFd;
111
+ try {
112
+ readyFd = openSync2(
113
+ readyFile,
114
+ constants2.O_CREAT | constants2.O_EXCL | constants2.O_WRONLY | noFollow
115
+ );
116
+ } catch (err) {
117
+ throw new Error(
118
+ `terminalhire: test race barrier "${phase}" could not create its ready marker at ${readyFile} (${err instanceof Error ? err.message : String(err)}) \u2014 refusing rather than blocking on or writing through whatever is already there (this only fires under ${ENV_VAR}, never in production).`
119
+ );
120
+ }
121
+ closeSync2(readyFd);
122
+ const deadline = Date.now() + 3e4;
123
+ while (!existsSync(goFile)) {
124
+ if (Date.now() > deadline) {
125
+ throw new Error(
126
+ `terminalhire: test race barrier "${phase}" timed out waiting for ${goFile} (the test process never released it \u2014 this only fires under ${ENV_VAR}, never in production).`
127
+ );
128
+ }
129
+ syncSleepMs(2);
130
+ }
131
+ }
132
+ var ENV_VAR;
133
+ var init_test_race_barrier = __esm({
134
+ "src/test-race-barrier.ts"() {
135
+ "use strict";
136
+ ENV_VAR = "TERMINALHIRE_TEST_RACE_BARRIER_DIR";
137
+ }
138
+ });
139
+
140
+ // src/shared-key.ts
141
+ import { randomBytes } from "crypto";
142
+ import { readFileSync, writeFileSync, existsSync as existsSync2, linkSync, unlinkSync } from "fs";
143
+ import { join as join4 } from "path";
144
+ import { homedir as homedir3 } from "os";
145
+ function isValidKeyHex(value) {
146
+ return KEY_HEX_RE.test(value);
147
+ }
148
+ function readKeyFileOrThrow() {
149
+ const raw = readFileSync(KEY_FILE, "utf8").trim();
150
+ if (!isValidKeyHex(raw)) {
151
+ throw new Error(
152
+ `terminalhire: the shared encryption key at ${KEY_FILE} is not in the expected format (expected exactly ${KEY_BYTES * 2} lowercase-hex characters \u2014 a ${KEY_BYTES}-byte key).
153
+ This key decrypts the GitHub token, local profile, and chat identity stores under ~/.terminalhire \u2014 it should never be hand-edited.
154
+ Recovery: if you intend to reset it, delete the file yourself (this INVALIDATES every encrypted store under ~/.terminalhire, which will need to be re-created/re-authenticated):
155
+ rm ${KEY_FILE}`
156
+ );
157
+ }
158
+ return Buffer.from(raw, "hex");
159
+ }
160
+ function publishKeyBlob(key) {
161
+ const tmpFile = `${KEY_FILE}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
162
+ try {
163
+ writeFileSync(tmpFile, key.toString("hex"), { encoding: "utf8", mode: 384, flag: "wx" });
164
+ try {
165
+ linkSync(tmpFile, KEY_FILE);
166
+ return true;
167
+ } catch (err) {
168
+ if (err?.code === "EEXIST") {
169
+ return false;
170
+ }
171
+ throw err;
172
+ }
173
+ } finally {
174
+ try {
175
+ unlinkSync(tmpFile);
176
+ } catch {
177
+ }
178
+ }
179
+ }
180
+ function loadOrCreateSharedKey() {
181
+ ensureStateDirForSecret(TERMINALHIRE_DIR);
182
+ if (existsSync2(KEY_FILE)) {
183
+ return readKeyFileOrThrow();
184
+ }
185
+ waitForTestRaceBarrier("key");
186
+ const key = randomBytes(KEY_BYTES);
187
+ if (publishKeyBlob(key)) {
188
+ return key;
189
+ }
190
+ return readKeyFileOrThrow();
191
+ }
192
+ var TERMINALHIRE_DIR, KEY_FILE, KEY_BYTES, KEY_HEX_RE;
193
+ var init_shared_key = __esm({
194
+ "src/shared-key.ts"() {
195
+ "use strict";
196
+ init_state_dir();
197
+ init_test_race_barrier();
198
+ TERMINALHIRE_DIR = process.env.TERMINALHIRE_DIR || join4(homedir3(), ".terminalhire");
199
+ KEY_FILE = join4(TERMINALHIRE_DIR, "key");
200
+ KEY_BYTES = 32;
201
+ KEY_HEX_RE = new RegExp(`^[0-9a-f]{${KEY_BYTES * 2}}$`);
202
+ }
203
+ });
204
+
205
+ // src/crypto-store.ts
206
+ import { createCipheriv, createDecipheriv, randomBytes as randomBytes2 } from "crypto";
207
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3, renameSync, rmSync, readdirSync } from "fs";
208
+ import { join as join5, dirname, basename as basename2 } from "path";
209
+ import { createRequire } from "module";
210
+ function encrypt(plaintext, key) {
211
+ const iv = randomBytes2(IV_BYTES);
212
+ const cipher = createCipheriv(ALGO, key, iv);
213
+ const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
214
+ const tag = cipher.getAuthTag();
215
+ return {
216
+ iv: iv.toString("hex"),
217
+ tag: tag.toString("hex"),
218
+ ciphertext: ct.toString("hex")
219
+ };
220
+ }
221
+ function decrypt(blob, key) {
222
+ const decipher = createDecipheriv(ALGO, key, Buffer.from(blob.iv, "hex"));
223
+ decipher.setAuthTag(Buffer.from(blob.tag, "hex"));
224
+ const plain = Buffer.concat([
225
+ decipher.update(Buffer.from(blob.ciphertext, "hex")),
226
+ decipher.final()
227
+ ]);
228
+ return plain.toString("utf8");
229
+ }
230
+ function skipKeychain() {
231
+ return process.env.TERMINALHIRE_NO_KEYCHAIN !== void 0 || process.env.CI !== void 0 || process.env.VITEST !== void 0 || process.env.NODE_ENV === "test";
232
+ }
233
+ async function tryLoadFromKeytar() {
234
+ if (forceKeytarUnavailableForTests || skipKeychain()) return null;
235
+ try {
236
+ const kt = createRequire(import.meta.url)("keytar");
237
+ const stored = await kt.getPassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT);
238
+ if (stored) {
239
+ return Buffer.from(stored, "hex");
240
+ }
241
+ const key = randomBytes2(KEY_BYTES);
242
+ await kt.setPassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT, key.toString("hex"));
243
+ return key;
244
+ } catch {
245
+ return null;
246
+ }
247
+ }
248
+ function warnStderr(message) {
249
+ process.stderr.write(`${message}
250
+ `);
251
+ }
252
+ function makeWarnOnce() {
253
+ const seen = /* @__PURE__ */ new Set();
254
+ return (message) => {
255
+ if (seen.has(message)) return;
256
+ seen.add(message);
257
+ warnStderr(message);
258
+ };
259
+ }
260
+ function atomicWriteFileSync(filePath, content) {
261
+ const dir = dirname(filePath);
262
+ ensureStateDirForSecret(dir);
263
+ const tmp = join5(
264
+ dir,
265
+ `.${basename2(filePath)}.tmp-${process.pid}-${randomBytes2(6).toString("hex")}`
266
+ );
267
+ writeFileSync2(tmp, content, { encoding: "utf8", mode: 384, flag: "wx" });
268
+ renameSync(tmp, filePath);
269
+ }
270
+ async function resolveKey(filePath, opts, warnOnce) {
271
+ if (opts.keyPolicy === "keychain-required") {
272
+ const key = await tryLoadFromKeytar();
273
+ if (!key) {
274
+ warnOnce(
275
+ `crypto-store: OS keychain unavailable \u2014 store at ${filePath} is disabled (no plaintext key file will be written)`
276
+ );
277
+ return null;
278
+ }
279
+ return key;
280
+ }
281
+ return loadOrCreateSharedKey();
282
+ }
283
+ function createEncryptedStore(filePath, opts) {
284
+ const warnOnce = makeWarnOnce();
285
+ async function read() {
286
+ const key = await resolveKey(filePath, opts, warnOnce);
287
+ if (!key) return opts.blank();
288
+ if (!existsSync3(filePath)) return opts.blank();
289
+ try {
290
+ const raw = readFileSync2(filePath, "utf8");
291
+ const blob = JSON.parse(raw);
292
+ const plaintext = decrypt(blob, key);
293
+ return JSON.parse(plaintext);
294
+ } catch {
295
+ warnOnce(`crypto-store: failed to decrypt ${filePath} \u2014 returning blank`);
296
+ return opts.blank();
297
+ }
298
+ }
299
+ async function write(value) {
300
+ const key = await resolveKey(filePath, opts, warnOnce);
301
+ if (!key) return;
302
+ const blob = encrypt(JSON.stringify(value), key);
303
+ atomicWriteFileSync(filePath, JSON.stringify(blob, null, 2));
304
+ }
305
+ return { read, write };
306
+ }
307
+ var KEYTAR_SERVICE, KEYTAR_ACCOUNT, ALGO, IV_BYTES, forceKeytarUnavailableForTests;
308
+ var init_crypto_store = __esm({
309
+ "src/crypto-store.ts"() {
310
+ "use strict";
311
+ init_state_dir();
312
+ init_shared_key();
313
+ KEYTAR_SERVICE = "terminalhire";
314
+ KEYTAR_ACCOUNT = "profile-key";
315
+ ALGO = "aes-256-gcm";
316
+ IV_BYTES = 12;
317
+ forceKeytarUnavailableForTests = false;
318
+ }
319
+ });
320
+
321
+ // src/founder-connector.ts
322
+ var founder_connector_exports = {};
323
+ __export(founder_connector_exports, {
324
+ FounderMcpError: () => FounderMcpError,
325
+ callFounderTool: () => callFounderTool,
326
+ clearFounderConnector: () => clearFounderConnector,
327
+ founderConnectorFilePath: () => founderConnectorFilePath,
328
+ readFounderConnector: () => readFounderConnector,
329
+ writeFounderConnector: () => writeFounderConnector
330
+ });
331
+ import { homedir as homedir4 } from "os";
332
+ import { join as join6 } from "path";
333
+ import { existsSync as existsSync4, rmSync as rmSync2 } from "fs";
334
+ function terminalhireDir() {
335
+ return process.env.TERMINALHIRE_DIR || join6(homedir4(), ".terminalhire");
336
+ }
337
+ function founderConnectorFilePath() {
338
+ return join6(terminalhireDir(), "founder-connector.enc");
339
+ }
340
+ function store() {
341
+ return createEncryptedStore(founderConnectorFilePath(), {
342
+ blank: () => ({ records: {} }),
343
+ keyPolicy: "keytar-first-file-fallback"
344
+ });
345
+ }
346
+ async function readAll() {
347
+ const data = await store().read();
348
+ const records = data?.records;
349
+ return records && typeof records === "object" ? { ...records } : {};
350
+ }
351
+ async function writeFounderConnector(record) {
352
+ const records = await readAll();
353
+ records[record.host] = record;
354
+ await store().write({ records });
355
+ }
356
+ async function readFounderConnector(host) {
357
+ const record = (await readAll())[host];
358
+ if (!record || typeof record.token !== "string" || record.host !== host) return null;
359
+ return record;
360
+ }
361
+ async function clearFounderConnector(host) {
362
+ const records = await readAll();
363
+ delete records[host];
364
+ if (Object.keys(records).length > 0) {
365
+ await store().write({ records });
366
+ return;
367
+ }
368
+ const p = founderConnectorFilePath();
369
+ if (existsSync4(p)) rmSync2(p, { force: true });
370
+ }
371
+ async function callFounderTool(base, token, name, args, fetchImpl = globalThis.fetch) {
372
+ const headers = { "Content-Type": "application/json" };
373
+ if (token) headers["Authorization"] = `Bearer ${token}`;
374
+ let res;
375
+ try {
376
+ res = await fetchImpl(`${base}/api/founder/mcp`, {
377
+ method: "POST",
378
+ headers,
379
+ body: JSON.stringify({
380
+ jsonrpc: "2.0",
381
+ id: 1,
382
+ method: "tools/call",
383
+ params: { name, arguments: args }
384
+ }),
385
+ signal: AbortSignal.timeout(3e4)
386
+ });
387
+ } catch (error2) {
388
+ throw new FounderMcpError(
389
+ `could not reach ${base}: ${error2 instanceof Error ? error2.message : String(error2)}`
390
+ );
391
+ }
392
+ if (res.status === 401) {
393
+ return { unauthorized: true, isError: true, text: "", structured: {} };
394
+ }
395
+ if (!res.ok) throw new FounderMcpError(`${base} answered ${res.status}`);
396
+ const body = await res.json().catch(() => null);
397
+ const error = body?.["error"];
398
+ if (error) {
399
+ throw new FounderMcpError(typeof error.message === "string" ? error.message : "protocol error");
400
+ }
401
+ const raw = body?.["result"];
402
+ if (typeof raw !== "object" || raw === null) {
403
+ throw new FounderMcpError(`${base} sent a response this CLI cannot read`);
404
+ }
405
+ const result = raw;
406
+ const content = Array.isArray(result["content"]) ? result["content"] : [];
407
+ const first = content[0];
408
+ return {
409
+ unauthorized: false,
410
+ isError: result["isError"] === true,
411
+ text: typeof first?.text === "string" ? first.text : "",
412
+ structured: typeof result["structuredContent"] === "object" && result["structuredContent"] !== null ? result["structuredContent"] : {}
413
+ };
414
+ }
415
+ var FounderMcpError;
416
+ var init_founder_connector = __esm({
417
+ "src/founder-connector.ts"() {
418
+ "use strict";
419
+ init_crypto_store();
420
+ FounderMcpError = class extends Error {
421
+ };
422
+ }
423
+ });
424
+
425
+ // src/api-base.ts
426
+ import { homedir } from "os";
427
+ import { basename, join, normalize } from "path";
428
+ var PROD_API_BASE = "https://terminalhire.com";
429
+ var DEV_API_BASE = "https://dev.terminalhire.com";
430
+ var DEV_STATE_DIR_NAME = ".terminalhire-dev";
431
+ var ApiBaseError = class extends Error {
432
+ constructor(message) {
433
+ super(message);
434
+ this.name = "ApiBaseError";
435
+ }
436
+ };
437
+ var ALLOWED_HOSTS = {
438
+ "terminalhire.com": "https:",
439
+ "www.terminalhire.com": "https:",
440
+ "dev.terminalhire.com": "https:",
441
+ localhost: "http:",
442
+ "127.0.0.1": "http:"
443
+ };
444
+ var ALLOW_LOCAL_API_KEY = "TERMINALHIRE_ALLOW_LOCAL_API";
445
+ var ALLOWED_DESCRIPTION = [
446
+ PROD_API_BASE,
447
+ DEV_API_BASE,
448
+ `http://localhost:<port> (requires ${ALLOW_LOCAL_API_KEY}=1)`,
449
+ `http://127.0.0.1:<port> (requires ${ALLOW_LOCAL_API_KEY}=1)`
450
+ ].join(", ");
451
+ var CANONICAL_REWRITES = {
452
+ "www.terminalhire.com": PROD_API_BASE
453
+ };
454
+ var ENV_KEYS = ["TERMINALHIRE_API_URL", "JPI_API_URL"];
455
+ function sanitizeOverrideForError(raw) {
456
+ try {
457
+ const url = new URL(raw);
458
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
459
+ return `(disallowed scheme: ${url.protocol.slice(0, -1)})`;
460
+ }
461
+ if (url.username !== "" || url.password !== "") {
462
+ return `${url.protocol}//***@${url.host}`;
463
+ }
464
+ return url.origin;
465
+ } catch {
466
+ return "(unparseable override)";
467
+ }
468
+ }
469
+ function isLoopbackOrigin(origin) {
470
+ try {
471
+ const host = new URL(origin).hostname;
472
+ return host === "localhost" || host === "127.0.0.1";
473
+ } catch {
474
+ return false;
475
+ }
476
+ }
477
+ function localApiAllowed(env) {
478
+ return env[ALLOW_LOCAL_API_KEY] === "1";
479
+ }
480
+ function resolveApiBase(env = process.env) {
481
+ for (const key of ENV_KEYS) {
482
+ const raw = env[key];
483
+ if (typeof raw !== "string") continue;
484
+ const trimmed = raw.trim();
485
+ if (trimmed === "") continue;
486
+ const normalized = normalizeOverride(trimmed);
487
+ if (normalized === null) {
488
+ throw new ApiBaseError(
489
+ `terminalhire: ${key}=${sanitizeOverrideForError(trimmed)} is not an allowed API host (allowed: ${ALLOWED_DESCRIPTION}). Refusing to continue so we do not silently hit production.`
490
+ );
491
+ }
492
+ if (isLoopbackOrigin(normalized) && !localApiAllowed(env)) {
493
+ throw new ApiBaseError(
494
+ `terminalhire: ${key}=${normalized} is a loopback origin. Set ${ALLOW_LOCAL_API_KEY}=1 to talk to a local web app on purpose. Refusing so stored credentials cannot be exfiltrated to localhost by a poisoned override.`
495
+ );
496
+ }
497
+ return normalized;
498
+ }
499
+ return PROD_API_BASE;
500
+ }
501
+ function normalizeOverride(raw) {
502
+ let url;
503
+ try {
504
+ url = new URL(raw);
505
+ } catch {
506
+ return null;
507
+ }
508
+ if (url.username !== "" || url.password !== "") return null;
509
+ const expectedProtocol = ALLOWED_HOSTS[url.hostname];
510
+ if (expectedProtocol === void 0) return null;
511
+ if (url.protocol !== expectedProtocol) return null;
512
+ if (url.hostname !== "localhost" && url.hostname !== "127.0.0.1" && url.port !== "") {
513
+ return null;
514
+ }
515
+ const rewrite = CANONICAL_REWRITES[url.hostname];
516
+ if (rewrite !== void 0) return rewrite;
517
+ return url.origin;
518
+ }
519
+
520
+ // src/state-dir-pin.ts
521
+ import { homedir as homedir2 } from "os";
522
+ import { join as join2 } from "path";
523
+ function pinStateDirToApiBase(env = process.env) {
524
+ if (env["TERMINALHIRE_DIR"]) return null;
525
+ let base;
526
+ try {
527
+ base = resolveApiBase(env);
528
+ } catch {
529
+ return null;
530
+ }
531
+ if (base !== DEV_API_BASE) return null;
532
+ env["TERMINALHIRE_DIR"] = env["TERMINALHIRE_DIR"] || join2(homedir2(), DEV_STATE_DIR_NAME);
533
+ return env["TERMINALHIRE_DIR"];
534
+ }
535
+
536
+ // bin/jpi-connector-header.js
537
+ function connectorHeaderHost(argv, env = process.env) {
538
+ const i = argv.indexOf("--host");
539
+ let raw = i !== -1 ? argv[i + 1] : env["CLAUDE_CODE_MCP_SERVER_URL"];
540
+ if (typeof raw === "string" && raw.trim() !== "") {
541
+ try {
542
+ raw = new URL(raw).origin;
543
+ } catch {
544
+ return null;
545
+ }
546
+ try {
547
+ return resolveApiBase({ ...env, TERMINALHIRE_API_URL: raw });
548
+ } catch {
549
+ return null;
550
+ }
551
+ }
552
+ try {
553
+ return resolveApiBase(env);
554
+ } catch {
555
+ return null;
556
+ }
557
+ }
558
+ async function run(argv = process.argv.slice(3)) {
559
+ const host = connectorHeaderHost(argv);
560
+ if (!host) {
561
+ process.stderr.write("terminalhire connector-header: no allowed host\n");
562
+ process.exitCode = 1;
563
+ return;
564
+ }
565
+ const pinned = { ...process.env, TERMINALHIRE_API_URL: host };
566
+ pinStateDirToApiBase(pinned);
567
+ if (pinned["TERMINALHIRE_DIR"]) process.env["TERMINALHIRE_DIR"] = pinned["TERMINALHIRE_DIR"];
568
+ const { readFounderConnector: readFounderConnector2 } = await Promise.resolve().then(() => (init_founder_connector(), founder_connector_exports));
569
+ const record = await readFounderConnector2(host).catch(() => null);
570
+ if (!record) {
571
+ process.stderr.write(
572
+ `terminalhire connector-header: no connector for ${host}; run \`terminalhire setup\`
573
+ `
574
+ );
575
+ process.exitCode = 1;
576
+ return;
577
+ }
578
+ process.stdout.write(`${JSON.stringify({ Authorization: `Bearer ${record.token}` })}
579
+ `);
580
+ }
581
+ export {
582
+ connectorHeaderHost,
583
+ run
584
+ };