terminalhire 0.30.1 → 0.30.2
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/dist/bin/jpi-dispatch.js +460 -476
- package/dist/bin/jpi-refresh.js +1 -6
- package/package.json +1 -1
package/dist/bin/jpi-dispatch.js
CHANGED
|
@@ -305,6 +305,315 @@ var init_version_nudge = __esm({
|
|
|
305
305
|
}
|
|
306
306
|
});
|
|
307
307
|
|
|
308
|
+
// src/open-url.js
|
|
309
|
+
var open_url_exports = {};
|
|
310
|
+
__export(open_url_exports, {
|
|
311
|
+
openInBrowser: () => openInBrowser
|
|
312
|
+
});
|
|
313
|
+
import { spawn } from "child_process";
|
|
314
|
+
function openInBrowser(url) {
|
|
315
|
+
let cmd;
|
|
316
|
+
let args5;
|
|
317
|
+
if (process.platform === "darwin") {
|
|
318
|
+
cmd = "open";
|
|
319
|
+
args5 = [url];
|
|
320
|
+
} else if (process.platform === "win32") {
|
|
321
|
+
cmd = "cmd";
|
|
322
|
+
args5 = ["/c", "start", "", url];
|
|
323
|
+
} else {
|
|
324
|
+
cmd = "xdg-open";
|
|
325
|
+
args5 = [url];
|
|
326
|
+
}
|
|
327
|
+
try {
|
|
328
|
+
const child = spawn(cmd, args5, { stdio: "ignore", detached: true });
|
|
329
|
+
child.on("error", () => {
|
|
330
|
+
});
|
|
331
|
+
child.unref();
|
|
332
|
+
} catch {
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
var init_open_url = __esm({
|
|
336
|
+
"src/open-url.js"() {
|
|
337
|
+
"use strict";
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// ../../node_modules/keytar/build/Release/keytar.node
|
|
342
|
+
var keytar_default;
|
|
343
|
+
var init_keytar = __esm({
|
|
344
|
+
"../../node_modules/keytar/build/Release/keytar.node"() {
|
|
345
|
+
keytar_default = "../keytar-KOAAH267.node";
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
|
|
350
|
+
var require_keytar = __commonJS({
|
|
351
|
+
"node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
|
|
352
|
+
"use strict";
|
|
353
|
+
init_keytar();
|
|
354
|
+
try {
|
|
355
|
+
module.exports = __require(keytar_default);
|
|
356
|
+
} catch {
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// ../../node_modules/keytar/lib/keytar.js
|
|
362
|
+
var require_keytar2 = __commonJS({
|
|
363
|
+
"../../node_modules/keytar/lib/keytar.js"(exports, module) {
|
|
364
|
+
"use strict";
|
|
365
|
+
var keytar = require_keytar();
|
|
366
|
+
function checkRequired(val, name) {
|
|
367
|
+
if (!val || val.length <= 0) {
|
|
368
|
+
throw new Error(name + " is required.");
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
module.exports = {
|
|
372
|
+
getPassword: function(service, account) {
|
|
373
|
+
checkRequired(service, "Service");
|
|
374
|
+
checkRequired(account, "Account");
|
|
375
|
+
return keytar.getPassword(service, account);
|
|
376
|
+
},
|
|
377
|
+
setPassword: function(service, account, password) {
|
|
378
|
+
checkRequired(service, "Service");
|
|
379
|
+
checkRequired(account, "Account");
|
|
380
|
+
checkRequired(password, "Password");
|
|
381
|
+
return keytar.setPassword(service, account, password);
|
|
382
|
+
},
|
|
383
|
+
deletePassword: function(service, account) {
|
|
384
|
+
checkRequired(service, "Service");
|
|
385
|
+
checkRequired(account, "Account");
|
|
386
|
+
return keytar.deletePassword(service, account);
|
|
387
|
+
},
|
|
388
|
+
findPassword: function(service) {
|
|
389
|
+
checkRequired(service, "Service");
|
|
390
|
+
return keytar.findPassword(service);
|
|
391
|
+
},
|
|
392
|
+
findCredentials: function(service) {
|
|
393
|
+
checkRequired(service, "Service");
|
|
394
|
+
return keytar.findCredentials(service);
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// src/github-auth.ts
|
|
401
|
+
var github_auth_exports = {};
|
|
402
|
+
__export(github_auth_exports, {
|
|
403
|
+
GITHUB_SCOPE: () => GITHUB_SCOPE,
|
|
404
|
+
decrypt: () => decrypt,
|
|
405
|
+
deleteGitHubToken: () => deleteGitHubToken,
|
|
406
|
+
encrypt: () => encrypt,
|
|
407
|
+
hasGitHubToken: () => hasGitHubToken,
|
|
408
|
+
loadKey: () => loadKey,
|
|
409
|
+
readGitHubToken: () => readGitHubToken,
|
|
410
|
+
resolveStoredLogin: () => resolveStoredLogin,
|
|
411
|
+
runDeviceFlow: () => runDeviceFlow,
|
|
412
|
+
writeGitHubToken: () => writeGitHubToken
|
|
413
|
+
});
|
|
414
|
+
import {
|
|
415
|
+
createCipheriv,
|
|
416
|
+
createDecipheriv,
|
|
417
|
+
randomBytes
|
|
418
|
+
} from "crypto";
|
|
419
|
+
import {
|
|
420
|
+
readFileSync as readFileSync4,
|
|
421
|
+
writeFileSync as writeFileSync4,
|
|
422
|
+
mkdirSync as mkdirSync4,
|
|
423
|
+
existsSync as existsSync4,
|
|
424
|
+
rmSync as rmSync2
|
|
425
|
+
} from "fs";
|
|
426
|
+
import { join as join4 } from "path";
|
|
427
|
+
import { homedir as homedir4 } from "os";
|
|
428
|
+
async function loadKey() {
|
|
429
|
+
try {
|
|
430
|
+
const kt = await Promise.resolve().then(() => __toESM(require_keytar2(), 1));
|
|
431
|
+
const stored = await kt.getPassword("terminalhire", "profile-key");
|
|
432
|
+
if (stored) return Buffer.from(stored, "hex");
|
|
433
|
+
const key2 = randomBytes(KEY_BYTES);
|
|
434
|
+
await kt.setPassword("terminalhire", "profile-key", key2.toString("hex"));
|
|
435
|
+
return key2;
|
|
436
|
+
} catch {
|
|
437
|
+
}
|
|
438
|
+
mkdirSync4(TERMINALHIRE_DIR2, { recursive: true });
|
|
439
|
+
if (existsSync4(KEY_FILE)) {
|
|
440
|
+
return Buffer.from(readFileSync4(KEY_FILE, "utf8").trim(), "hex");
|
|
441
|
+
}
|
|
442
|
+
const key = randomBytes(KEY_BYTES);
|
|
443
|
+
writeFileSync4(KEY_FILE, key.toString("hex"), { mode: 384, encoding: "utf8" });
|
|
444
|
+
return key;
|
|
445
|
+
}
|
|
446
|
+
function encrypt(plaintext, key) {
|
|
447
|
+
const iv = randomBytes(IV_BYTES);
|
|
448
|
+
const cipher = createCipheriv(ALGO, key, iv);
|
|
449
|
+
const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
450
|
+
const tag = cipher.getAuthTag();
|
|
451
|
+
return { iv: iv.toString("hex"), tag: tag.toString("hex"), ciphertext: ct.toString("hex") };
|
|
452
|
+
}
|
|
453
|
+
function decrypt(blob, key) {
|
|
454
|
+
const decipher = createDecipheriv(ALGO, key, Buffer.from(blob.iv, "hex"));
|
|
455
|
+
decipher.setAuthTag(Buffer.from(blob.tag, "hex"));
|
|
456
|
+
const plain = Buffer.concat([
|
|
457
|
+
decipher.update(Buffer.from(blob.ciphertext, "hex")),
|
|
458
|
+
decipher.final()
|
|
459
|
+
]);
|
|
460
|
+
return plain.toString("utf8");
|
|
461
|
+
}
|
|
462
|
+
async function readGitHubToken() {
|
|
463
|
+
if (!existsSync4(TOKEN_FILE)) return void 0;
|
|
464
|
+
try {
|
|
465
|
+
const key = await loadKey();
|
|
466
|
+
const raw = readFileSync4(TOKEN_FILE, "utf8");
|
|
467
|
+
const blob = JSON.parse(raw);
|
|
468
|
+
return decrypt(blob, key);
|
|
469
|
+
} catch {
|
|
470
|
+
return void 0;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
async function writeGitHubToken(token) {
|
|
474
|
+
mkdirSync4(TERMINALHIRE_DIR2, { recursive: true });
|
|
475
|
+
const key = await loadKey();
|
|
476
|
+
const blob = encrypt(token, key);
|
|
477
|
+
writeFileSync4(TOKEN_FILE, JSON.stringify(blob, null, 2), { encoding: "utf8" });
|
|
478
|
+
}
|
|
479
|
+
async function deleteGitHubToken() {
|
|
480
|
+
try {
|
|
481
|
+
rmSync2(TOKEN_FILE);
|
|
482
|
+
} catch {
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
async function hasGitHubToken() {
|
|
486
|
+
return existsSync4(TOKEN_FILE);
|
|
487
|
+
}
|
|
488
|
+
async function runDeviceFlow() {
|
|
489
|
+
if (process.env["TERMINALHIRE_GITHUB_MOCK"] === "1" || process.env["TERMINALHIRE_GITHUB_MOCK"] === "1" || process.env["JPI_GITHUB_MOCK"] === "1") {
|
|
490
|
+
console.log("\n[mock] GitHub OAuth skipped (JPI_GITHUB_MOCK=1)");
|
|
491
|
+
console.log(`[mock] Using fixture profile: ${MOCK_LOGIN}`);
|
|
492
|
+
await writeGitHubToken(MOCK_TOKEN);
|
|
493
|
+
return MOCK_LOGIN;
|
|
494
|
+
}
|
|
495
|
+
const clientId = process.env["GITHUB_DEVICE_CLIENT_ID"] ?? process.env["GITHUB_CLIENT_ID"] ?? BAKED_IN_CLIENT_ID;
|
|
496
|
+
if (clientId === "Iv1.PLACEHOLDER_REGISTER_YOUR_APP") {
|
|
497
|
+
console.warn("\nWarning: GITHUB_CLIENT_ID env var looks like a placeholder.");
|
|
498
|
+
console.warn("Remove it to use the baked-in client ID, or set it to your own OAuth App Client ID.\n");
|
|
499
|
+
}
|
|
500
|
+
const deviceRes = await fetch(DEVICE_CODE_URL, {
|
|
501
|
+
method: "POST",
|
|
502
|
+
headers: {
|
|
503
|
+
Accept: "application/json",
|
|
504
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
505
|
+
},
|
|
506
|
+
body: new URLSearchParams({ client_id: clientId, scope: GITHUB_SCOPE }).toString(),
|
|
507
|
+
signal: AbortSignal.timeout(15e3)
|
|
508
|
+
});
|
|
509
|
+
if (!deviceRes.ok) {
|
|
510
|
+
throw new Error(`GitHub device code request failed: HTTP ${deviceRes.status}`);
|
|
511
|
+
}
|
|
512
|
+
const deviceData = await deviceRes.json();
|
|
513
|
+
console.log("");
|
|
514
|
+
console.log(" GitHub sign-in (device flow)");
|
|
515
|
+
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
516
|
+
console.log(` 1. Open: ${deviceData.verification_uri}`);
|
|
517
|
+
console.log(` 2. Enter code: ${deviceData.user_code}`);
|
|
518
|
+
console.log(' 3. Authorize "Terminalhire" (scope: read:user \u2014 public data only)');
|
|
519
|
+
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
520
|
+
console.log(" Waiting for authorization...");
|
|
521
|
+
console.log("");
|
|
522
|
+
let intervalSecs = deviceData.interval ?? 5;
|
|
523
|
+
const expiresAt = Date.now() + (deviceData.expires_in ?? 900) * 1e3;
|
|
524
|
+
const clientSecret = process.env["GITHUB_CLIENT_SECRET"];
|
|
525
|
+
while (Date.now() < expiresAt) {
|
|
526
|
+
await sleep(intervalSecs * 1e3);
|
|
527
|
+
const body = new URLSearchParams({
|
|
528
|
+
client_id: clientId,
|
|
529
|
+
device_code: deviceData.device_code,
|
|
530
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
531
|
+
});
|
|
532
|
+
if (clientSecret) body.set("client_secret", clientSecret);
|
|
533
|
+
const tokenRes = await fetch(ACCESS_TOKEN_URL, {
|
|
534
|
+
method: "POST",
|
|
535
|
+
headers: {
|
|
536
|
+
Accept: "application/json",
|
|
537
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
538
|
+
},
|
|
539
|
+
body: body.toString(),
|
|
540
|
+
signal: AbortSignal.timeout(15e3)
|
|
541
|
+
});
|
|
542
|
+
if (!tokenRes.ok) {
|
|
543
|
+
throw new Error(`GitHub token poll failed: HTTP ${tokenRes.status}`);
|
|
544
|
+
}
|
|
545
|
+
const tokenData = await tokenRes.json();
|
|
546
|
+
if (tokenData.access_token) {
|
|
547
|
+
await writeGitHubToken(tokenData.access_token);
|
|
548
|
+
const login = await fetchAuthedLogin(tokenData.access_token);
|
|
549
|
+
console.log(` Authorized as: ${login}`);
|
|
550
|
+
return login;
|
|
551
|
+
}
|
|
552
|
+
if (tokenData.error === "authorization_pending") {
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
if (tokenData.error === "slow_down") {
|
|
556
|
+
intervalSecs = (tokenData.interval ?? intervalSecs) + 5;
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
if (tokenData.error === "expired_token") {
|
|
560
|
+
throw new Error("GitHub device code expired. Please run `terminalhire login` again.");
|
|
561
|
+
}
|
|
562
|
+
if (tokenData.error === "access_denied") {
|
|
563
|
+
throw new Error("GitHub authorization was denied by the user.");
|
|
564
|
+
}
|
|
565
|
+
throw new Error(
|
|
566
|
+
`GitHub device flow error: ${tokenData.error ?? "unknown"} \u2014 ${tokenData.error_description ?? ""}`
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
throw new Error("GitHub device code expired before authorization. Please run `terminalhire login` again.");
|
|
570
|
+
}
|
|
571
|
+
async function fetchAuthedLogin(token) {
|
|
572
|
+
if (token === MOCK_TOKEN) return MOCK_LOGIN;
|
|
573
|
+
const res = await fetch("https://api.github.com/user", {
|
|
574
|
+
headers: {
|
|
575
|
+
Authorization: `Bearer ${token}`,
|
|
576
|
+
Accept: "application/vnd.github+json",
|
|
577
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
578
|
+
},
|
|
579
|
+
signal: AbortSignal.timeout(1e4)
|
|
580
|
+
});
|
|
581
|
+
if (!res.ok) throw new Error(`GitHub /user: HTTP ${res.status}`);
|
|
582
|
+
const data = await res.json();
|
|
583
|
+
return data.login;
|
|
584
|
+
}
|
|
585
|
+
async function resolveStoredLogin() {
|
|
586
|
+
if (process.env["TERMINALHIRE_GITHUB_MOCK"] === "1" || process.env["TERMINALHIRE_GITHUB_MOCK"] === "1" || process.env["JPI_GITHUB_MOCK"] === "1") return MOCK_LOGIN;
|
|
587
|
+
const token = await readGitHubToken();
|
|
588
|
+
if (!token) return void 0;
|
|
589
|
+
try {
|
|
590
|
+
return await fetchAuthedLogin(token);
|
|
591
|
+
} catch {
|
|
592
|
+
return void 0;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
function sleep(ms) {
|
|
596
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
597
|
+
}
|
|
598
|
+
var TERMINALHIRE_DIR2, TOKEN_FILE, KEY_FILE, ALGO, KEY_BYTES, IV_BYTES, GITHUB_SCOPE, DEVICE_CODE_URL, ACCESS_TOKEN_URL, BAKED_IN_CLIENT_ID, MOCK_TOKEN, MOCK_LOGIN;
|
|
599
|
+
var init_github_auth = __esm({
|
|
600
|
+
"src/github-auth.ts"() {
|
|
601
|
+
"use strict";
|
|
602
|
+
TERMINALHIRE_DIR2 = join4(homedir4(), ".terminalhire");
|
|
603
|
+
TOKEN_FILE = join4(TERMINALHIRE_DIR2, "github-token.enc");
|
|
604
|
+
KEY_FILE = join4(TERMINALHIRE_DIR2, "key");
|
|
605
|
+
ALGO = "aes-256-gcm";
|
|
606
|
+
KEY_BYTES = 32;
|
|
607
|
+
IV_BYTES = 12;
|
|
608
|
+
GITHUB_SCOPE = "read:user";
|
|
609
|
+
DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
610
|
+
ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
611
|
+
BAKED_IN_CLIENT_ID = "Ov23lignE2ZSBe0J3a6B";
|
|
612
|
+
MOCK_TOKEN = "mock-github-token-jpi-dev";
|
|
613
|
+
MOCK_LOGIN = "janedev";
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
|
|
308
617
|
// ../../packages/core/src/types.ts
|
|
309
618
|
function isBounty(job) {
|
|
310
619
|
return job.source === "bounty" && job.bounty != null;
|
|
@@ -4312,21 +4621,21 @@ var init_contributions = __esm({
|
|
|
4312
4621
|
});
|
|
4313
4622
|
|
|
4314
4623
|
// ../../packages/core/src/partners.ts
|
|
4315
|
-
import { readFileSync as
|
|
4316
|
-
import { join as
|
|
4624
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
4625
|
+
import { join as join5 } from "path";
|
|
4317
4626
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4318
4627
|
function resolveDataPath() {
|
|
4319
4628
|
try {
|
|
4320
4629
|
const dir = fileURLToPath2(new URL("../../../data", import.meta.url));
|
|
4321
|
-
return
|
|
4630
|
+
return join5(dir, "partner-roles.json");
|
|
4322
4631
|
} catch {
|
|
4323
|
-
return
|
|
4632
|
+
return join5(process.cwd(), "data", "partner-roles.json");
|
|
4324
4633
|
}
|
|
4325
4634
|
}
|
|
4326
4635
|
function loadPartnerRoles() {
|
|
4327
4636
|
const filePath = resolveDataPath();
|
|
4328
4637
|
try {
|
|
4329
|
-
const raw =
|
|
4638
|
+
const raw = readFileSync5(filePath, "utf-8");
|
|
4330
4639
|
const parsed = JSON.parse(raw);
|
|
4331
4640
|
if (!Array.isArray(parsed)) {
|
|
4332
4641
|
console.warn("[partners] partner-roles.json is not an array \u2014 skipping");
|
|
@@ -4928,7 +5237,7 @@ function createHasher(hashCons) {
|
|
|
4928
5237
|
hashC.create = () => hashCons();
|
|
4929
5238
|
return hashC;
|
|
4930
5239
|
}
|
|
4931
|
-
function
|
|
5240
|
+
function randomBytes2(bytesLength = 32) {
|
|
4932
5241
|
if (crypto && typeof crypto.getRandomValues === "function") {
|
|
4933
5242
|
return crypto.getRandomValues(new Uint8Array(bytesLength));
|
|
4934
5243
|
}
|
|
@@ -6353,7 +6662,7 @@ function eddsa(Point, cHash, eddsaOpts = {}) {
|
|
|
6353
6662
|
});
|
|
6354
6663
|
const { prehash } = eddsaOpts;
|
|
6355
6664
|
const { BASE, Fp: Fp2, Fn: Fn2 } = Point;
|
|
6356
|
-
const randomBytes6 = eddsaOpts.randomBytes ||
|
|
6665
|
+
const randomBytes6 = eddsaOpts.randomBytes || randomBytes2;
|
|
6357
6666
|
const adjustScalarBytes2 = eddsaOpts.adjustScalarBytes || ((bytes) => bytes);
|
|
6358
6667
|
const domain = eddsaOpts.domain || ((data, ctx, phflag) => {
|
|
6359
6668
|
_abool2(phflag, "phflag");
|
|
@@ -6631,7 +6940,7 @@ function montgomery(curveDef) {
|
|
|
6631
6940
|
const is25519 = type === "x25519";
|
|
6632
6941
|
if (!is25519 && type !== "x448")
|
|
6633
6942
|
throw new Error("invalid type");
|
|
6634
|
-
const randomBytes_ = rand ||
|
|
6943
|
+
const randomBytes_ = rand || randomBytes2;
|
|
6635
6944
|
const montgomeryBits = is25519 ? 255 : 448;
|
|
6636
6945
|
const fieldLen = is25519 ? 32 : 56;
|
|
6637
6946
|
const Gu = is25519 ? BigInt(9) : BigInt(5);
|
|
@@ -7770,7 +8079,7 @@ var init_chacha = __esm({
|
|
|
7770
8079
|
});
|
|
7771
8080
|
|
|
7772
8081
|
// ../../packages/core/src/chatCrypto.ts
|
|
7773
|
-
import { hkdfSync, createHash, randomBytes as
|
|
8082
|
+
import { hkdfSync, createHash, randomBytes as randomBytes3 } from "crypto";
|
|
7774
8083
|
function bytesToHex2(bytes) {
|
|
7775
8084
|
return Buffer.from(bytes).toString("hex");
|
|
7776
8085
|
}
|
|
@@ -7799,7 +8108,7 @@ function deriveSharedKey(myPrivateKey, peerPublicKey) {
|
|
|
7799
8108
|
}
|
|
7800
8109
|
function encryptMessage(plaintext, myPrivateKey, peerPublicKey) {
|
|
7801
8110
|
const key = deriveSharedKey(myPrivateKey, peerPublicKey);
|
|
7802
|
-
const nonce = new Uint8Array(
|
|
8111
|
+
const nonce = new Uint8Array(randomBytes3(NONCE_BYTES));
|
|
7803
8112
|
const cipher = xchacha20poly1305(key, nonce);
|
|
7804
8113
|
const ct = cipher.encrypt(new Uint8Array(Buffer.from(plaintext, "utf8")));
|
|
7805
8114
|
return { ciphertext: bytesToB64(ct), nonce: bytesToB64(nonce) };
|
|
@@ -8613,437 +8922,6 @@ var init_src = __esm({
|
|
|
8613
8922
|
}
|
|
8614
8923
|
});
|
|
8615
8924
|
|
|
8616
|
-
// bin/job-status-store.js
|
|
8617
|
-
var job_status_store_exports = {};
|
|
8618
|
-
__export(job_status_store_exports, {
|
|
8619
|
-
markClicked: () => markClicked,
|
|
8620
|
-
markStatus: () => markStatus,
|
|
8621
|
-
readStatusMap: () => readStatusMap,
|
|
8622
|
-
statusFilePath: () => statusFilePath
|
|
8623
|
-
});
|
|
8624
|
-
import {
|
|
8625
|
-
readFileSync as readFileSync5,
|
|
8626
|
-
writeFileSync as writeFileSync4,
|
|
8627
|
-
renameSync,
|
|
8628
|
-
mkdirSync as mkdirSync4,
|
|
8629
|
-
existsSync as existsSync4,
|
|
8630
|
-
copyFileSync,
|
|
8631
|
-
openSync,
|
|
8632
|
-
closeSync,
|
|
8633
|
-
unlinkSync
|
|
8634
|
-
} from "fs";
|
|
8635
|
-
import { join as join5, dirname } from "path";
|
|
8636
|
-
import { homedir as homedir4 } from "os";
|
|
8637
|
-
function statusFilePath() {
|
|
8638
|
-
return STATUS_FILE;
|
|
8639
|
-
}
|
|
8640
|
-
function atomicWriteJson(path, obj) {
|
|
8641
|
-
mkdirSync4(dirname(path), { recursive: true });
|
|
8642
|
-
const tmp = `${path}.tmp-${process.pid}`;
|
|
8643
|
-
writeFileSync4(tmp, JSON.stringify(obj, null, 2) + "\n", "utf8");
|
|
8644
|
-
renameSync(tmp, path);
|
|
8645
|
-
}
|
|
8646
|
-
function sleepMs(ms) {
|
|
8647
|
-
try {
|
|
8648
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
8649
|
-
} catch {
|
|
8650
|
-
const end = Date.now() + ms;
|
|
8651
|
-
while (Date.now() < end) {
|
|
8652
|
-
}
|
|
8653
|
-
}
|
|
8654
|
-
}
|
|
8655
|
-
function withLock(fn) {
|
|
8656
|
-
const deadline = Date.now() + 2e3;
|
|
8657
|
-
for (; ; ) {
|
|
8658
|
-
let fd;
|
|
8659
|
-
try {
|
|
8660
|
-
mkdirSync4(dirname(LOCK_FILE), { recursive: true });
|
|
8661
|
-
fd = openSync(LOCK_FILE, "wx");
|
|
8662
|
-
} catch (err) {
|
|
8663
|
-
if (err && err.code === "EEXIST") {
|
|
8664
|
-
if (Date.now() > deadline) {
|
|
8665
|
-
try {
|
|
8666
|
-
unlinkSync(LOCK_FILE);
|
|
8667
|
-
} catch {
|
|
8668
|
-
}
|
|
8669
|
-
continue;
|
|
8670
|
-
}
|
|
8671
|
-
sleepMs(5);
|
|
8672
|
-
continue;
|
|
8673
|
-
}
|
|
8674
|
-
throw err;
|
|
8675
|
-
}
|
|
8676
|
-
try {
|
|
8677
|
-
return fn();
|
|
8678
|
-
} finally {
|
|
8679
|
-
try {
|
|
8680
|
-
closeSync(fd);
|
|
8681
|
-
} catch {
|
|
8682
|
-
}
|
|
8683
|
-
try {
|
|
8684
|
-
unlinkSync(LOCK_FILE);
|
|
8685
|
-
} catch {
|
|
8686
|
-
}
|
|
8687
|
-
}
|
|
8688
|
-
}
|
|
8689
|
-
}
|
|
8690
|
-
function readStatusMap() {
|
|
8691
|
-
if (!existsSync4(STATUS_FILE)) return {};
|
|
8692
|
-
let raw;
|
|
8693
|
-
try {
|
|
8694
|
-
raw = readFileSync5(STATUS_FILE, "utf8");
|
|
8695
|
-
} catch {
|
|
8696
|
-
return {};
|
|
8697
|
-
}
|
|
8698
|
-
try {
|
|
8699
|
-
const parsed = JSON.parse(raw);
|
|
8700
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
8701
|
-
throw new Error("status store is not an object");
|
|
8702
|
-
} catch {
|
|
8703
|
-
try {
|
|
8704
|
-
copyFileSync(STATUS_FILE, BAK_FILE);
|
|
8705
|
-
} catch {
|
|
8706
|
-
}
|
|
8707
|
-
return {};
|
|
8708
|
-
}
|
|
8709
|
-
}
|
|
8710
|
-
function markStatus(id, status) {
|
|
8711
|
-
return withLock(() => {
|
|
8712
|
-
const current = readStatusMap();
|
|
8713
|
-
const next = status === "claimed" ? { ...current, [id]: { ...current[id], status: "claimed", markedAt: (/* @__PURE__ */ new Date()).toISOString() } } : setStatus(current, id, status);
|
|
8714
|
-
atomicWriteJson(STATUS_FILE, next);
|
|
8715
|
-
return next[id];
|
|
8716
|
-
});
|
|
8717
|
-
}
|
|
8718
|
-
function markClicked(id) {
|
|
8719
|
-
return withLock(() => {
|
|
8720
|
-
const current = readStatusMap();
|
|
8721
|
-
const next = recordClick(current, id);
|
|
8722
|
-
atomicWriteJson(STATUS_FILE, next);
|
|
8723
|
-
return next[id];
|
|
8724
|
-
});
|
|
8725
|
-
}
|
|
8726
|
-
var TERMINALHIRE_DIR2, STATUS_FILE, LOCK_FILE, BAK_FILE;
|
|
8727
|
-
var init_job_status_store = __esm({
|
|
8728
|
-
"bin/job-status-store.js"() {
|
|
8729
|
-
"use strict";
|
|
8730
|
-
init_src();
|
|
8731
|
-
TERMINALHIRE_DIR2 = process.env.TERMINALHIRE_DIR || join5(homedir4(), ".terminalhire");
|
|
8732
|
-
STATUS_FILE = join5(TERMINALHIRE_DIR2, "job-status.json");
|
|
8733
|
-
LOCK_FILE = `${STATUS_FILE}.lock`;
|
|
8734
|
-
BAK_FILE = `${STATUS_FILE}.bak`;
|
|
8735
|
-
}
|
|
8736
|
-
});
|
|
8737
|
-
|
|
8738
|
-
// src/open-url.js
|
|
8739
|
-
var open_url_exports = {};
|
|
8740
|
-
__export(open_url_exports, {
|
|
8741
|
-
openInBrowser: () => openInBrowser
|
|
8742
|
-
});
|
|
8743
|
-
import { spawn } from "child_process";
|
|
8744
|
-
function openInBrowser(url) {
|
|
8745
|
-
let cmd;
|
|
8746
|
-
let args5;
|
|
8747
|
-
if (process.platform === "darwin") {
|
|
8748
|
-
cmd = "open";
|
|
8749
|
-
args5 = [url];
|
|
8750
|
-
} else if (process.platform === "win32") {
|
|
8751
|
-
cmd = "cmd";
|
|
8752
|
-
args5 = ["/c", "start", "", url];
|
|
8753
|
-
} else {
|
|
8754
|
-
cmd = "xdg-open";
|
|
8755
|
-
args5 = [url];
|
|
8756
|
-
}
|
|
8757
|
-
try {
|
|
8758
|
-
const child = spawn(cmd, args5, { stdio: "ignore", detached: true });
|
|
8759
|
-
child.on("error", () => {
|
|
8760
|
-
});
|
|
8761
|
-
child.unref();
|
|
8762
|
-
} catch {
|
|
8763
|
-
}
|
|
8764
|
-
}
|
|
8765
|
-
var init_open_url = __esm({
|
|
8766
|
-
"src/open-url.js"() {
|
|
8767
|
-
"use strict";
|
|
8768
|
-
}
|
|
8769
|
-
});
|
|
8770
|
-
|
|
8771
|
-
// ../../node_modules/keytar/build/Release/keytar.node
|
|
8772
|
-
var keytar_default;
|
|
8773
|
-
var init_keytar = __esm({
|
|
8774
|
-
"../../node_modules/keytar/build/Release/keytar.node"() {
|
|
8775
|
-
keytar_default = "../keytar-KOAAH267.node";
|
|
8776
|
-
}
|
|
8777
|
-
});
|
|
8778
|
-
|
|
8779
|
-
// node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
|
|
8780
|
-
var require_keytar = __commonJS({
|
|
8781
|
-
"node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
|
|
8782
|
-
"use strict";
|
|
8783
|
-
init_keytar();
|
|
8784
|
-
try {
|
|
8785
|
-
module.exports = __require(keytar_default);
|
|
8786
|
-
} catch {
|
|
8787
|
-
}
|
|
8788
|
-
}
|
|
8789
|
-
});
|
|
8790
|
-
|
|
8791
|
-
// ../../node_modules/keytar/lib/keytar.js
|
|
8792
|
-
var require_keytar2 = __commonJS({
|
|
8793
|
-
"../../node_modules/keytar/lib/keytar.js"(exports, module) {
|
|
8794
|
-
"use strict";
|
|
8795
|
-
var keytar = require_keytar();
|
|
8796
|
-
function checkRequired(val, name) {
|
|
8797
|
-
if (!val || val.length <= 0) {
|
|
8798
|
-
throw new Error(name + " is required.");
|
|
8799
|
-
}
|
|
8800
|
-
}
|
|
8801
|
-
module.exports = {
|
|
8802
|
-
getPassword: function(service, account) {
|
|
8803
|
-
checkRequired(service, "Service");
|
|
8804
|
-
checkRequired(account, "Account");
|
|
8805
|
-
return keytar.getPassword(service, account);
|
|
8806
|
-
},
|
|
8807
|
-
setPassword: function(service, account, password) {
|
|
8808
|
-
checkRequired(service, "Service");
|
|
8809
|
-
checkRequired(account, "Account");
|
|
8810
|
-
checkRequired(password, "Password");
|
|
8811
|
-
return keytar.setPassword(service, account, password);
|
|
8812
|
-
},
|
|
8813
|
-
deletePassword: function(service, account) {
|
|
8814
|
-
checkRequired(service, "Service");
|
|
8815
|
-
checkRequired(account, "Account");
|
|
8816
|
-
return keytar.deletePassword(service, account);
|
|
8817
|
-
},
|
|
8818
|
-
findPassword: function(service) {
|
|
8819
|
-
checkRequired(service, "Service");
|
|
8820
|
-
return keytar.findPassword(service);
|
|
8821
|
-
},
|
|
8822
|
-
findCredentials: function(service) {
|
|
8823
|
-
checkRequired(service, "Service");
|
|
8824
|
-
return keytar.findCredentials(service);
|
|
8825
|
-
}
|
|
8826
|
-
};
|
|
8827
|
-
}
|
|
8828
|
-
});
|
|
8829
|
-
|
|
8830
|
-
// src/github-auth.ts
|
|
8831
|
-
var github_auth_exports = {};
|
|
8832
|
-
__export(github_auth_exports, {
|
|
8833
|
-
GITHUB_SCOPE: () => GITHUB_SCOPE,
|
|
8834
|
-
decrypt: () => decrypt,
|
|
8835
|
-
deleteGitHubToken: () => deleteGitHubToken,
|
|
8836
|
-
encrypt: () => encrypt,
|
|
8837
|
-
hasGitHubToken: () => hasGitHubToken,
|
|
8838
|
-
loadKey: () => loadKey,
|
|
8839
|
-
readGitHubToken: () => readGitHubToken,
|
|
8840
|
-
resolveStoredLogin: () => resolveStoredLogin,
|
|
8841
|
-
runDeviceFlow: () => runDeviceFlow,
|
|
8842
|
-
writeGitHubToken: () => writeGitHubToken
|
|
8843
|
-
});
|
|
8844
|
-
import {
|
|
8845
|
-
createCipheriv,
|
|
8846
|
-
createDecipheriv,
|
|
8847
|
-
randomBytes as randomBytes3
|
|
8848
|
-
} from "crypto";
|
|
8849
|
-
import {
|
|
8850
|
-
readFileSync as readFileSync6,
|
|
8851
|
-
writeFileSync as writeFileSync5,
|
|
8852
|
-
mkdirSync as mkdirSync5,
|
|
8853
|
-
existsSync as existsSync5,
|
|
8854
|
-
rmSync as rmSync2
|
|
8855
|
-
} from "fs";
|
|
8856
|
-
import { join as join6 } from "path";
|
|
8857
|
-
import { homedir as homedir5 } from "os";
|
|
8858
|
-
async function loadKey() {
|
|
8859
|
-
try {
|
|
8860
|
-
const kt = await Promise.resolve().then(() => __toESM(require_keytar2(), 1));
|
|
8861
|
-
const stored = await kt.getPassword("terminalhire", "profile-key");
|
|
8862
|
-
if (stored) return Buffer.from(stored, "hex");
|
|
8863
|
-
const key2 = randomBytes3(KEY_BYTES);
|
|
8864
|
-
await kt.setPassword("terminalhire", "profile-key", key2.toString("hex"));
|
|
8865
|
-
return key2;
|
|
8866
|
-
} catch {
|
|
8867
|
-
}
|
|
8868
|
-
mkdirSync5(TERMINALHIRE_DIR3, { recursive: true });
|
|
8869
|
-
if (existsSync5(KEY_FILE)) {
|
|
8870
|
-
return Buffer.from(readFileSync6(KEY_FILE, "utf8").trim(), "hex");
|
|
8871
|
-
}
|
|
8872
|
-
const key = randomBytes3(KEY_BYTES);
|
|
8873
|
-
writeFileSync5(KEY_FILE, key.toString("hex"), { mode: 384, encoding: "utf8" });
|
|
8874
|
-
return key;
|
|
8875
|
-
}
|
|
8876
|
-
function encrypt(plaintext, key) {
|
|
8877
|
-
const iv = randomBytes3(IV_BYTES);
|
|
8878
|
-
const cipher = createCipheriv(ALGO, key, iv);
|
|
8879
|
-
const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
8880
|
-
const tag = cipher.getAuthTag();
|
|
8881
|
-
return { iv: iv.toString("hex"), tag: tag.toString("hex"), ciphertext: ct.toString("hex") };
|
|
8882
|
-
}
|
|
8883
|
-
function decrypt(blob, key) {
|
|
8884
|
-
const decipher = createDecipheriv(ALGO, key, Buffer.from(blob.iv, "hex"));
|
|
8885
|
-
decipher.setAuthTag(Buffer.from(blob.tag, "hex"));
|
|
8886
|
-
const plain = Buffer.concat([
|
|
8887
|
-
decipher.update(Buffer.from(blob.ciphertext, "hex")),
|
|
8888
|
-
decipher.final()
|
|
8889
|
-
]);
|
|
8890
|
-
return plain.toString("utf8");
|
|
8891
|
-
}
|
|
8892
|
-
async function readGitHubToken() {
|
|
8893
|
-
if (!existsSync5(TOKEN_FILE)) return void 0;
|
|
8894
|
-
try {
|
|
8895
|
-
const key = await loadKey();
|
|
8896
|
-
const raw = readFileSync6(TOKEN_FILE, "utf8");
|
|
8897
|
-
const blob = JSON.parse(raw);
|
|
8898
|
-
return decrypt(blob, key);
|
|
8899
|
-
} catch {
|
|
8900
|
-
return void 0;
|
|
8901
|
-
}
|
|
8902
|
-
}
|
|
8903
|
-
async function writeGitHubToken(token) {
|
|
8904
|
-
mkdirSync5(TERMINALHIRE_DIR3, { recursive: true });
|
|
8905
|
-
const key = await loadKey();
|
|
8906
|
-
const blob = encrypt(token, key);
|
|
8907
|
-
writeFileSync5(TOKEN_FILE, JSON.stringify(blob, null, 2), { encoding: "utf8" });
|
|
8908
|
-
}
|
|
8909
|
-
async function deleteGitHubToken() {
|
|
8910
|
-
try {
|
|
8911
|
-
rmSync2(TOKEN_FILE);
|
|
8912
|
-
} catch {
|
|
8913
|
-
}
|
|
8914
|
-
}
|
|
8915
|
-
async function hasGitHubToken() {
|
|
8916
|
-
return existsSync5(TOKEN_FILE);
|
|
8917
|
-
}
|
|
8918
|
-
async function runDeviceFlow() {
|
|
8919
|
-
if (process.env["TERMINALHIRE_GITHUB_MOCK"] === "1" || process.env["TERMINALHIRE_GITHUB_MOCK"] === "1" || process.env["JPI_GITHUB_MOCK"] === "1") {
|
|
8920
|
-
console.log("\n[mock] GitHub OAuth skipped (JPI_GITHUB_MOCK=1)");
|
|
8921
|
-
console.log(`[mock] Using fixture profile: ${MOCK_LOGIN}`);
|
|
8922
|
-
await writeGitHubToken(MOCK_TOKEN);
|
|
8923
|
-
return MOCK_LOGIN;
|
|
8924
|
-
}
|
|
8925
|
-
const clientId = process.env["GITHUB_DEVICE_CLIENT_ID"] ?? process.env["GITHUB_CLIENT_ID"] ?? BAKED_IN_CLIENT_ID;
|
|
8926
|
-
if (clientId === "Iv1.PLACEHOLDER_REGISTER_YOUR_APP") {
|
|
8927
|
-
console.warn("\nWarning: GITHUB_CLIENT_ID env var looks like a placeholder.");
|
|
8928
|
-
console.warn("Remove it to use the baked-in client ID, or set it to your own OAuth App Client ID.\n");
|
|
8929
|
-
}
|
|
8930
|
-
const deviceRes = await fetch(DEVICE_CODE_URL, {
|
|
8931
|
-
method: "POST",
|
|
8932
|
-
headers: {
|
|
8933
|
-
Accept: "application/json",
|
|
8934
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
8935
|
-
},
|
|
8936
|
-
body: new URLSearchParams({ client_id: clientId, scope: GITHUB_SCOPE }).toString(),
|
|
8937
|
-
signal: AbortSignal.timeout(15e3)
|
|
8938
|
-
});
|
|
8939
|
-
if (!deviceRes.ok) {
|
|
8940
|
-
throw new Error(`GitHub device code request failed: HTTP ${deviceRes.status}`);
|
|
8941
|
-
}
|
|
8942
|
-
const deviceData = await deviceRes.json();
|
|
8943
|
-
console.log("");
|
|
8944
|
-
console.log(" GitHub sign-in (device flow)");
|
|
8945
|
-
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
8946
|
-
console.log(` 1. Open: ${deviceData.verification_uri}`);
|
|
8947
|
-
console.log(` 2. Enter code: ${deviceData.user_code}`);
|
|
8948
|
-
console.log(' 3. Authorize "Terminalhire" (scope: read:user \u2014 public data only)');
|
|
8949
|
-
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
8950
|
-
console.log(" Waiting for authorization...");
|
|
8951
|
-
console.log("");
|
|
8952
|
-
let intervalSecs = deviceData.interval ?? 5;
|
|
8953
|
-
const expiresAt = Date.now() + (deviceData.expires_in ?? 900) * 1e3;
|
|
8954
|
-
const clientSecret = process.env["GITHUB_CLIENT_SECRET"];
|
|
8955
|
-
while (Date.now() < expiresAt) {
|
|
8956
|
-
await sleep(intervalSecs * 1e3);
|
|
8957
|
-
const body = new URLSearchParams({
|
|
8958
|
-
client_id: clientId,
|
|
8959
|
-
device_code: deviceData.device_code,
|
|
8960
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
8961
|
-
});
|
|
8962
|
-
if (clientSecret) body.set("client_secret", clientSecret);
|
|
8963
|
-
const tokenRes = await fetch(ACCESS_TOKEN_URL, {
|
|
8964
|
-
method: "POST",
|
|
8965
|
-
headers: {
|
|
8966
|
-
Accept: "application/json",
|
|
8967
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
8968
|
-
},
|
|
8969
|
-
body: body.toString(),
|
|
8970
|
-
signal: AbortSignal.timeout(15e3)
|
|
8971
|
-
});
|
|
8972
|
-
if (!tokenRes.ok) {
|
|
8973
|
-
throw new Error(`GitHub token poll failed: HTTP ${tokenRes.status}`);
|
|
8974
|
-
}
|
|
8975
|
-
const tokenData = await tokenRes.json();
|
|
8976
|
-
if (tokenData.access_token) {
|
|
8977
|
-
await writeGitHubToken(tokenData.access_token);
|
|
8978
|
-
const login = await fetchAuthedLogin(tokenData.access_token);
|
|
8979
|
-
console.log(` Authorized as: ${login}`);
|
|
8980
|
-
return login;
|
|
8981
|
-
}
|
|
8982
|
-
if (tokenData.error === "authorization_pending") {
|
|
8983
|
-
continue;
|
|
8984
|
-
}
|
|
8985
|
-
if (tokenData.error === "slow_down") {
|
|
8986
|
-
intervalSecs = (tokenData.interval ?? intervalSecs) + 5;
|
|
8987
|
-
continue;
|
|
8988
|
-
}
|
|
8989
|
-
if (tokenData.error === "expired_token") {
|
|
8990
|
-
throw new Error("GitHub device code expired. Please run `terminalhire login` again.");
|
|
8991
|
-
}
|
|
8992
|
-
if (tokenData.error === "access_denied") {
|
|
8993
|
-
throw new Error("GitHub authorization was denied by the user.");
|
|
8994
|
-
}
|
|
8995
|
-
throw new Error(
|
|
8996
|
-
`GitHub device flow error: ${tokenData.error ?? "unknown"} \u2014 ${tokenData.error_description ?? ""}`
|
|
8997
|
-
);
|
|
8998
|
-
}
|
|
8999
|
-
throw new Error("GitHub device code expired before authorization. Please run `terminalhire login` again.");
|
|
9000
|
-
}
|
|
9001
|
-
async function fetchAuthedLogin(token) {
|
|
9002
|
-
if (token === MOCK_TOKEN) return MOCK_LOGIN;
|
|
9003
|
-
const res = await fetch("https://api.github.com/user", {
|
|
9004
|
-
headers: {
|
|
9005
|
-
Authorization: `Bearer ${token}`,
|
|
9006
|
-
Accept: "application/vnd.github+json",
|
|
9007
|
-
"X-GitHub-Api-Version": "2022-11-28"
|
|
9008
|
-
},
|
|
9009
|
-
signal: AbortSignal.timeout(1e4)
|
|
9010
|
-
});
|
|
9011
|
-
if (!res.ok) throw new Error(`GitHub /user: HTTP ${res.status}`);
|
|
9012
|
-
const data = await res.json();
|
|
9013
|
-
return data.login;
|
|
9014
|
-
}
|
|
9015
|
-
async function resolveStoredLogin() {
|
|
9016
|
-
if (process.env["TERMINALHIRE_GITHUB_MOCK"] === "1" || process.env["TERMINALHIRE_GITHUB_MOCK"] === "1" || process.env["JPI_GITHUB_MOCK"] === "1") return MOCK_LOGIN;
|
|
9017
|
-
const token = await readGitHubToken();
|
|
9018
|
-
if (!token) return void 0;
|
|
9019
|
-
try {
|
|
9020
|
-
return await fetchAuthedLogin(token);
|
|
9021
|
-
} catch {
|
|
9022
|
-
return void 0;
|
|
9023
|
-
}
|
|
9024
|
-
}
|
|
9025
|
-
function sleep(ms) {
|
|
9026
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
9027
|
-
}
|
|
9028
|
-
var TERMINALHIRE_DIR3, TOKEN_FILE, KEY_FILE, ALGO, KEY_BYTES, IV_BYTES, GITHUB_SCOPE, DEVICE_CODE_URL, ACCESS_TOKEN_URL, BAKED_IN_CLIENT_ID, MOCK_TOKEN, MOCK_LOGIN;
|
|
9029
|
-
var init_github_auth = __esm({
|
|
9030
|
-
"src/github-auth.ts"() {
|
|
9031
|
-
"use strict";
|
|
9032
|
-
TERMINALHIRE_DIR3 = join6(homedir5(), ".terminalhire");
|
|
9033
|
-
TOKEN_FILE = join6(TERMINALHIRE_DIR3, "github-token.enc");
|
|
9034
|
-
KEY_FILE = join6(TERMINALHIRE_DIR3, "key");
|
|
9035
|
-
ALGO = "aes-256-gcm";
|
|
9036
|
-
KEY_BYTES = 32;
|
|
9037
|
-
IV_BYTES = 12;
|
|
9038
|
-
GITHUB_SCOPE = "read:user";
|
|
9039
|
-
DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
9040
|
-
ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
9041
|
-
BAKED_IN_CLIENT_ID = "Ov23lignE2ZSBe0J3a6B";
|
|
9042
|
-
MOCK_TOKEN = "mock-github-token-jpi-dev";
|
|
9043
|
-
MOCK_LOGIN = "janedev";
|
|
9044
|
-
}
|
|
9045
|
-
});
|
|
9046
|
-
|
|
9047
8925
|
// src/profile.ts
|
|
9048
8926
|
var profile_exports = {};
|
|
9049
8927
|
__export(profile_exports, {
|
|
@@ -9064,13 +8942,13 @@ import {
|
|
|
9064
8942
|
randomBytes as randomBytes4
|
|
9065
8943
|
} from "crypto";
|
|
9066
8944
|
import {
|
|
9067
|
-
readFileSync as
|
|
9068
|
-
writeFileSync as
|
|
9069
|
-
mkdirSync as
|
|
9070
|
-
existsSync as
|
|
8945
|
+
readFileSync as readFileSync6,
|
|
8946
|
+
writeFileSync as writeFileSync5,
|
|
8947
|
+
mkdirSync as mkdirSync5,
|
|
8948
|
+
existsSync as existsSync5
|
|
9071
8949
|
} from "fs";
|
|
9072
|
-
import { join as
|
|
9073
|
-
import { homedir as
|
|
8950
|
+
import { join as join6 } from "path";
|
|
8951
|
+
import { homedir as homedir5 } from "os";
|
|
9074
8952
|
async function loadKey2() {
|
|
9075
8953
|
try {
|
|
9076
8954
|
const kt = await Promise.resolve().then(() => __toESM(require_keytar2(), 1));
|
|
@@ -9083,12 +8961,12 @@ async function loadKey2() {
|
|
|
9083
8961
|
return key2;
|
|
9084
8962
|
} catch {
|
|
9085
8963
|
}
|
|
9086
|
-
|
|
9087
|
-
if (
|
|
9088
|
-
return Buffer.from(
|
|
8964
|
+
mkdirSync5(TERMINALHIRE_DIR3, { recursive: true });
|
|
8965
|
+
if (existsSync5(KEY_FILE2)) {
|
|
8966
|
+
return Buffer.from(readFileSync6(KEY_FILE2, "utf8").trim(), "hex");
|
|
9089
8967
|
}
|
|
9090
8968
|
const key = randomBytes4(KEY_BYTES2);
|
|
9091
|
-
|
|
8969
|
+
writeFileSync5(KEY_FILE2, key.toString("hex"), { mode: 384, encoding: "utf8" });
|
|
9092
8970
|
return key;
|
|
9093
8971
|
}
|
|
9094
8972
|
function encrypt2(plaintext, key) {
|
|
@@ -9146,10 +9024,10 @@ function migrateTagWeights(profile) {
|
|
|
9146
9024
|
}
|
|
9147
9025
|
}
|
|
9148
9026
|
async function readProfile() {
|
|
9149
|
-
if (!
|
|
9027
|
+
if (!existsSync5(PROFILE_FILE)) return blankProfile();
|
|
9150
9028
|
try {
|
|
9151
9029
|
const key = await loadKey2();
|
|
9152
|
-
const raw =
|
|
9030
|
+
const raw = readFileSync6(PROFILE_FILE, "utf8");
|
|
9153
9031
|
const blob = JSON.parse(raw);
|
|
9154
9032
|
const plaintext = decrypt2(blob, key);
|
|
9155
9033
|
const parsed = JSON.parse(plaintext);
|
|
@@ -9160,12 +9038,12 @@ async function readProfile() {
|
|
|
9160
9038
|
}
|
|
9161
9039
|
}
|
|
9162
9040
|
async function writeProfile(profile) {
|
|
9163
|
-
|
|
9041
|
+
mkdirSync5(TERMINALHIRE_DIR3, { recursive: true });
|
|
9164
9042
|
const key = await loadKey2();
|
|
9165
9043
|
profile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9166
9044
|
profile.skillTags = deriveSkillTags(profile.tagWeights);
|
|
9167
9045
|
const blob = encrypt2(JSON.stringify(profile), key);
|
|
9168
|
-
|
|
9046
|
+
writeFileSync5(PROFILE_FILE, JSON.stringify(blob, null, 2), { encoding: "utf8" });
|
|
9169
9047
|
}
|
|
9170
9048
|
function accumulateSession(profile, tags, isEmployerContext2, inferredSeniority, seniorityIsAuthoritative = false) {
|
|
9171
9049
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -9249,14 +9127,14 @@ function profileToFingerprint(profile) {
|
|
|
9249
9127
|
}
|
|
9250
9128
|
};
|
|
9251
9129
|
}
|
|
9252
|
-
var
|
|
9130
|
+
var TERMINALHIRE_DIR3, PROFILE_FILE, KEY_FILE2, ALGO2, KEY_BYTES2, IV_BYTES2, DECAY_HALF_LIFE_MS, LANGUAGE_TAGS, MIN_FINGERPRINT_SCORE;
|
|
9253
9131
|
var init_profile = __esm({
|
|
9254
9132
|
"src/profile.ts"() {
|
|
9255
9133
|
"use strict";
|
|
9256
9134
|
init_src();
|
|
9257
|
-
|
|
9258
|
-
PROFILE_FILE =
|
|
9259
|
-
KEY_FILE2 =
|
|
9135
|
+
TERMINALHIRE_DIR3 = join6(homedir5(), ".terminalhire");
|
|
9136
|
+
PROFILE_FILE = join6(TERMINALHIRE_DIR3, "profile.enc");
|
|
9137
|
+
KEY_FILE2 = join6(TERMINALHIRE_DIR3, "key");
|
|
9260
9138
|
ALGO2 = "aes-256-gcm";
|
|
9261
9139
|
KEY_BYTES2 = 32;
|
|
9262
9140
|
IV_BYTES2 = 12;
|
|
@@ -9549,6 +9427,128 @@ var init_jpi_login = __esm({
|
|
|
9549
9427
|
}
|
|
9550
9428
|
});
|
|
9551
9429
|
|
|
9430
|
+
// bin/job-status-store.js
|
|
9431
|
+
var job_status_store_exports = {};
|
|
9432
|
+
__export(job_status_store_exports, {
|
|
9433
|
+
markClicked: () => markClicked,
|
|
9434
|
+
markStatus: () => markStatus,
|
|
9435
|
+
readStatusMap: () => readStatusMap,
|
|
9436
|
+
statusFilePath: () => statusFilePath
|
|
9437
|
+
});
|
|
9438
|
+
import {
|
|
9439
|
+
readFileSync as readFileSync7,
|
|
9440
|
+
writeFileSync as writeFileSync6,
|
|
9441
|
+
renameSync,
|
|
9442
|
+
mkdirSync as mkdirSync6,
|
|
9443
|
+
existsSync as existsSync6,
|
|
9444
|
+
copyFileSync,
|
|
9445
|
+
openSync,
|
|
9446
|
+
closeSync,
|
|
9447
|
+
unlinkSync
|
|
9448
|
+
} from "fs";
|
|
9449
|
+
import { join as join7, dirname } from "path";
|
|
9450
|
+
import { homedir as homedir6 } from "os";
|
|
9451
|
+
function statusFilePath() {
|
|
9452
|
+
return STATUS_FILE;
|
|
9453
|
+
}
|
|
9454
|
+
function atomicWriteJson(path, obj) {
|
|
9455
|
+
mkdirSync6(dirname(path), { recursive: true });
|
|
9456
|
+
const tmp = `${path}.tmp-${process.pid}`;
|
|
9457
|
+
writeFileSync6(tmp, JSON.stringify(obj, null, 2) + "\n", "utf8");
|
|
9458
|
+
renameSync(tmp, path);
|
|
9459
|
+
}
|
|
9460
|
+
function sleepMs(ms) {
|
|
9461
|
+
try {
|
|
9462
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
9463
|
+
} catch {
|
|
9464
|
+
const end = Date.now() + ms;
|
|
9465
|
+
while (Date.now() < end) {
|
|
9466
|
+
}
|
|
9467
|
+
}
|
|
9468
|
+
}
|
|
9469
|
+
function withLock(fn) {
|
|
9470
|
+
const deadline = Date.now() + 2e3;
|
|
9471
|
+
for (; ; ) {
|
|
9472
|
+
let fd;
|
|
9473
|
+
try {
|
|
9474
|
+
mkdirSync6(dirname(LOCK_FILE), { recursive: true });
|
|
9475
|
+
fd = openSync(LOCK_FILE, "wx");
|
|
9476
|
+
} catch (err) {
|
|
9477
|
+
if (err && err.code === "EEXIST") {
|
|
9478
|
+
if (Date.now() > deadline) {
|
|
9479
|
+
try {
|
|
9480
|
+
unlinkSync(LOCK_FILE);
|
|
9481
|
+
} catch {
|
|
9482
|
+
}
|
|
9483
|
+
continue;
|
|
9484
|
+
}
|
|
9485
|
+
sleepMs(5);
|
|
9486
|
+
continue;
|
|
9487
|
+
}
|
|
9488
|
+
throw err;
|
|
9489
|
+
}
|
|
9490
|
+
try {
|
|
9491
|
+
return fn();
|
|
9492
|
+
} finally {
|
|
9493
|
+
try {
|
|
9494
|
+
closeSync(fd);
|
|
9495
|
+
} catch {
|
|
9496
|
+
}
|
|
9497
|
+
try {
|
|
9498
|
+
unlinkSync(LOCK_FILE);
|
|
9499
|
+
} catch {
|
|
9500
|
+
}
|
|
9501
|
+
}
|
|
9502
|
+
}
|
|
9503
|
+
}
|
|
9504
|
+
function readStatusMap() {
|
|
9505
|
+
if (!existsSync6(STATUS_FILE)) return {};
|
|
9506
|
+
let raw;
|
|
9507
|
+
try {
|
|
9508
|
+
raw = readFileSync7(STATUS_FILE, "utf8");
|
|
9509
|
+
} catch {
|
|
9510
|
+
return {};
|
|
9511
|
+
}
|
|
9512
|
+
try {
|
|
9513
|
+
const parsed = JSON.parse(raw);
|
|
9514
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
9515
|
+
throw new Error("status store is not an object");
|
|
9516
|
+
} catch {
|
|
9517
|
+
try {
|
|
9518
|
+
copyFileSync(STATUS_FILE, BAK_FILE);
|
|
9519
|
+
} catch {
|
|
9520
|
+
}
|
|
9521
|
+
return {};
|
|
9522
|
+
}
|
|
9523
|
+
}
|
|
9524
|
+
function markStatus(id, status) {
|
|
9525
|
+
return withLock(() => {
|
|
9526
|
+
const current = readStatusMap();
|
|
9527
|
+
const next = status === "claimed" ? { ...current, [id]: { ...current[id], status: "claimed", markedAt: (/* @__PURE__ */ new Date()).toISOString() } } : setStatus(current, id, status);
|
|
9528
|
+
atomicWriteJson(STATUS_FILE, next);
|
|
9529
|
+
return next[id];
|
|
9530
|
+
});
|
|
9531
|
+
}
|
|
9532
|
+
function markClicked(id) {
|
|
9533
|
+
return withLock(() => {
|
|
9534
|
+
const current = readStatusMap();
|
|
9535
|
+
const next = recordClick(current, id);
|
|
9536
|
+
atomicWriteJson(STATUS_FILE, next);
|
|
9537
|
+
return next[id];
|
|
9538
|
+
});
|
|
9539
|
+
}
|
|
9540
|
+
var TERMINALHIRE_DIR4, STATUS_FILE, LOCK_FILE, BAK_FILE;
|
|
9541
|
+
var init_job_status_store = __esm({
|
|
9542
|
+
"bin/job-status-store.js"() {
|
|
9543
|
+
"use strict";
|
|
9544
|
+
init_src();
|
|
9545
|
+
TERMINALHIRE_DIR4 = process.env.TERMINALHIRE_DIR || join7(homedir6(), ".terminalhire");
|
|
9546
|
+
STATUS_FILE = join7(TERMINALHIRE_DIR4, "job-status.json");
|
|
9547
|
+
LOCK_FILE = `${STATUS_FILE}.lock`;
|
|
9548
|
+
BAK_FILE = `${STATUS_FILE}.bak`;
|
|
9549
|
+
}
|
|
9550
|
+
});
|
|
9551
|
+
|
|
9552
9552
|
// bin/cache-store.js
|
|
9553
9553
|
var cache_store_exports = {};
|
|
9554
9554
|
__export(cache_store_exports, {
|
|
@@ -43097,12 +43097,7 @@ async function run22() {
|
|
|
43097
43097
|
unpushedClaims,
|
|
43098
43098
|
baseUrl: API_URL8,
|
|
43099
43099
|
seenHistory,
|
|
43100
|
-
widen
|
|
43101
|
-
// When the refresh monitor set up its loopback click-catcher, it threads
|
|
43102
|
-
// the base in via this env var so tip /j/ links record clicks locally.
|
|
43103
|
-
// Absent (a manual `terminalhire refresh` outside the monitor) ⇒ undefined
|
|
43104
|
-
// ⇒ public /j/ URLs, unchanged. baseUrl/API_URL are untouched.
|
|
43105
|
-
clickCatcher: process.env.TH_CLICK_CATCHER || void 0
|
|
43100
|
+
widen
|
|
43106
43101
|
});
|
|
43107
43102
|
} catch {
|
|
43108
43103
|
}
|
|
@@ -43686,17 +43681,6 @@ if (firstArg === "chat" || firstArg === "link" || firstArg === "claim") {
|
|
|
43686
43681
|
} catch {
|
|
43687
43682
|
}
|
|
43688
43683
|
}
|
|
43689
|
-
if (firstArg === "__record-click") {
|
|
43690
|
-
const id = process.argv[3];
|
|
43691
|
-
if (id && /^[a-z0-9._-]+:.+$/.test(id)) {
|
|
43692
|
-
try {
|
|
43693
|
-
const { markClicked: markClicked2 } = await Promise.resolve().then(() => (init_job_status_store(), job_status_store_exports));
|
|
43694
|
-
markClicked2(id);
|
|
43695
|
-
} catch {
|
|
43696
|
-
}
|
|
43697
|
-
}
|
|
43698
|
-
process.exit(0);
|
|
43699
|
-
}
|
|
43700
43684
|
if (firstArg === "login" || firstArg === "logout") {
|
|
43701
43685
|
const mod2 = await Promise.resolve().then(() => (init_jpi_login(), jpi_login_exports));
|
|
43702
43686
|
await mod2.run();
|
package/dist/bin/jpi-refresh.js
CHANGED
|
@@ -10484,12 +10484,7 @@ async function run() {
|
|
|
10484
10484
|
unpushedClaims,
|
|
10485
10485
|
baseUrl: API_URL2,
|
|
10486
10486
|
seenHistory,
|
|
10487
|
-
widen
|
|
10488
|
-
// When the refresh monitor set up its loopback click-catcher, it threads
|
|
10489
|
-
// the base in via this env var so tip /j/ links record clicks locally.
|
|
10490
|
-
// Absent (a manual `terminalhire refresh` outside the monitor) ⇒ undefined
|
|
10491
|
-
// ⇒ public /j/ URLs, unchanged. baseUrl/API_URL are untouched.
|
|
10492
|
-
clickCatcher: process.env.TH_CLICK_CATCHER || void 0
|
|
10487
|
+
widen
|
|
10493
10488
|
});
|
|
10494
10489
|
} catch {
|
|
10495
10490
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terminalhire",
|
|
3
|
-
"version": "0.30.
|
|
3
|
+
"version": "0.30.2",
|
|
4
4
|
"description": "Local-first job matching for developers — ambient job matches in the Claude Code spinner. Matching runs on your machine; your profile never leaves it.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|