terminalhire 0.2.0 → 0.2.3
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 +356 -66
- package/dist/bin/jpi-init.js +5 -5
- package/dist/bin/jpi-refresh.js +18 -10
- package/dist/bin/jpi-sync.js +837 -0
- package/dist/bin/spinner.js +16 -8
- package/install.js +81 -257
- package/package.json +3 -3
package/dist/bin/jpi-dispatch.js
CHANGED
|
@@ -1461,13 +1461,13 @@ async function removeSavedJob(id) {
|
|
|
1461
1461
|
return true;
|
|
1462
1462
|
}
|
|
1463
1463
|
async function deleteProfile() {
|
|
1464
|
-
const { rmSync:
|
|
1464
|
+
const { rmSync: rmSync3 } = await import("fs");
|
|
1465
1465
|
try {
|
|
1466
|
-
|
|
1466
|
+
rmSync3(PROFILE_FILE);
|
|
1467
1467
|
} catch {
|
|
1468
1468
|
}
|
|
1469
1469
|
try {
|
|
1470
|
-
|
|
1470
|
+
rmSync3(KEY_FILE2);
|
|
1471
1471
|
} catch {
|
|
1472
1472
|
}
|
|
1473
1473
|
}
|
|
@@ -1553,11 +1553,11 @@ async function runLogin() {
|
|
|
1553
1553
|
if (process.env["TERMINALHIRE_GITHUB_MOCK"] === "1" || process.env["TERMINALHIRE_GITHUB_MOCK"] === "1" || process.env["JPI_GITHUB_MOCK"] === "1") {
|
|
1554
1554
|
const { createRequire: createRequire2 } = await import("module");
|
|
1555
1555
|
const { fileURLToPath: fileURLToPath7 } = await import("url");
|
|
1556
|
-
const { join:
|
|
1556
|
+
const { join: join14, dirname: dirname3 } = await import("path");
|
|
1557
1557
|
const __dirname6 = fileURLToPath7(new URL(".", import.meta.url));
|
|
1558
|
-
const fixturePath =
|
|
1559
|
-
const { readFileSync:
|
|
1560
|
-
ghProfile = JSON.parse(
|
|
1558
|
+
const fixturePath = join14(__dirname6, "../../fixtures/github-sample.json");
|
|
1559
|
+
const { readFileSync: readFileSync13 } = await import("fs");
|
|
1560
|
+
ghProfile = JSON.parse(readFileSync13(fixturePath, "utf8"));
|
|
1561
1561
|
} else {
|
|
1562
1562
|
ghProfile = await fetchGitHubProfile2(login, token);
|
|
1563
1563
|
}
|
|
@@ -2474,10 +2474,12 @@ function clearSpinnerVerbs() {
|
|
|
2474
2474
|
}
|
|
2475
2475
|
return { cleared: true, keptUserVerbs };
|
|
2476
2476
|
}
|
|
2477
|
-
function buildTips(topMatches, baseUrl, max =
|
|
2477
|
+
function buildTips(topMatches, baseUrl, max = 8) {
|
|
2478
2478
|
const base = String(baseUrl || "https://terminalhire.com").replace(/\/+$/, "");
|
|
2479
2479
|
const out = [];
|
|
2480
|
-
const
|
|
2480
|
+
const seenRole = /* @__PURE__ */ new Set();
|
|
2481
|
+
const perCompany = /* @__PURE__ */ new Map();
|
|
2482
|
+
const COMPANY_CAP = 2;
|
|
2481
2483
|
for (const m of Array.isArray(topMatches) ? topMatches : []) {
|
|
2482
2484
|
if (!m || !m.title || !m.company || !m.id) continue;
|
|
2483
2485
|
const idx = String(m.id).indexOf(":");
|
|
@@ -2485,14 +2487,20 @@ function buildTips(topMatches, baseUrl, max = 3) {
|
|
|
2485
2487
|
const source = String(m.id).slice(0, idx);
|
|
2486
2488
|
const ext = String(m.id).slice(idx + 1);
|
|
2487
2489
|
if (!source || !ext) continue;
|
|
2488
|
-
const
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2490
|
+
const companyRaw = String(m.company).trim().replace(/\s+/g, " ");
|
|
2491
|
+
const titleRaw = String(m.title).trim().replace(/\s+/g, " ");
|
|
2492
|
+
const roleKey = `${titleRaw.toLowerCase()}@${companyRaw.toLowerCase()}`;
|
|
2493
|
+
const coKey = companyRaw.toLowerCase();
|
|
2494
|
+
if (seenRole.has(roleKey)) continue;
|
|
2495
|
+
if ((perCompany.get(coKey) || 0) >= COMPANY_CAP) continue;
|
|
2496
|
+
seenRole.add(roleKey);
|
|
2497
|
+
perCompany.set(coKey, (perCompany.get(coKey) || 0) + 1);
|
|
2498
|
+
let title = titleRaw;
|
|
2492
2499
|
if (title.length > 34) title = title.slice(0, 33).trimEnd() + "\u2026";
|
|
2493
|
-
const company = titleCase(
|
|
2500
|
+
const company = titleCase(companyRaw);
|
|
2494
2501
|
const pct = Math.max(1, Math.min(99, Math.round((Number(m.score) || 0) * 100)));
|
|
2495
|
-
const
|
|
2502
|
+
const token = Buffer.from(String(m.id)).toString("base64url");
|
|
2503
|
+
const url = `${base}/j/${token}`;
|
|
2496
2504
|
out.push(`\u2197 ${title} @ ${company} \xB7 ${pct}% \u2014 ${url}`);
|
|
2497
2505
|
if (out.length >= max) break;
|
|
2498
2506
|
}
|
|
@@ -2743,19 +2751,292 @@ var init_jpi_spinner = __esm({
|
|
|
2743
2751
|
}
|
|
2744
2752
|
});
|
|
2745
2753
|
|
|
2754
|
+
// bin/jpi-sync.js
|
|
2755
|
+
var jpi_sync_exports = {};
|
|
2756
|
+
__export(jpi_sync_exports, {
|
|
2757
|
+
run: () => run7
|
|
2758
|
+
});
|
|
2759
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, existsSync as existsSync7, rmSync as rmSync2 } from "fs";
|
|
2760
|
+
import { join as join9 } from "path";
|
|
2761
|
+
import { homedir as homedir7 } from "os";
|
|
2762
|
+
import { createInterface as createInterface4 } from "readline";
|
|
2763
|
+
function ask2(question) {
|
|
2764
|
+
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
2765
|
+
return new Promise((res) => {
|
|
2766
|
+
rl.question(question, (answer) => {
|
|
2767
|
+
rl.close();
|
|
2768
|
+
res(answer.trim().toLowerCase());
|
|
2769
|
+
});
|
|
2770
|
+
});
|
|
2771
|
+
}
|
|
2772
|
+
function readMarker() {
|
|
2773
|
+
try {
|
|
2774
|
+
return existsSync7(TIER1_MARKER) ? JSON.parse(readFileSync9(TIER1_MARKER, "utf8")) : null;
|
|
2775
|
+
} catch {
|
|
2776
|
+
return null;
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
function writeMarker(marker) {
|
|
2780
|
+
mkdirSync7(TH_DIR3, { recursive: true });
|
|
2781
|
+
writeFileSync7(TIER1_MARKER, JSON.stringify(marker, null, 2) + "\n", "utf8");
|
|
2782
|
+
}
|
|
2783
|
+
function clearMarker() {
|
|
2784
|
+
try {
|
|
2785
|
+
rmSync2(TIER1_MARKER);
|
|
2786
|
+
} catch {
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
function buildConsentFields(profile) {
|
|
2790
|
+
const gh = profile.github || {};
|
|
2791
|
+
const fields = [
|
|
2792
|
+
{ key: "login", label: "GitHub login", value: gh.login },
|
|
2793
|
+
{ key: "publicEmail", label: "Public email", value: gh.publicEmail || null },
|
|
2794
|
+
{ key: "topLanguages", label: "Top languages", value: gh.topLanguages || [] },
|
|
2795
|
+
{ key: "skillTags", label: "Skill tags", value: profile.skillTags || [] }
|
|
2796
|
+
];
|
|
2797
|
+
if (profile.displayName) {
|
|
2798
|
+
fields.push({ key: "displayName", label: "Display name", value: profile.displayName });
|
|
2799
|
+
}
|
|
2800
|
+
if (profile.contactEmail) {
|
|
2801
|
+
fields.push({ key: "contactEmail", label: "Contact email", value: profile.contactEmail });
|
|
2802
|
+
}
|
|
2803
|
+
return fields;
|
|
2804
|
+
}
|
|
2805
|
+
function renderConsentCard(fields) {
|
|
2806
|
+
console.log("");
|
|
2807
|
+
console.log("\u250C\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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
2808
|
+
console.log("\u2502 terminalhire \u2014 sync your profile (Tier-1, opt-in) \u2502");
|
|
2809
|
+
console.log("\u2514\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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
2810
|
+
console.log("");
|
|
2811
|
+
console.log(" You are about to send the following data to");
|
|
2812
|
+
console.log(" staqs (terminalhire.com):");
|
|
2813
|
+
console.log("");
|
|
2814
|
+
for (const f of fields) {
|
|
2815
|
+
const shown = Array.isArray(f.value) ? JSON.stringify(f.value) : String(f.value ?? "(not set)");
|
|
2816
|
+
console.log(` ${f.label.padEnd(16)}: ${shown}`);
|
|
2817
|
+
}
|
|
2818
|
+
console.log("");
|
|
2819
|
+
console.log(" What is NEVER sent:");
|
|
2820
|
+
console.log(" - Private repos, employer repos, raw code");
|
|
2821
|
+
console.log(" - Employer-repo-derived tags (filtered at source)");
|
|
2822
|
+
console.log(" - Session history, file paths, project names");
|
|
2823
|
+
console.log("");
|
|
2824
|
+
console.log(" This is a ONE-TIME snapshot. Nothing is sent automatically;");
|
|
2825
|
+
console.log(" re-run `terminalhire sync --push` to update it later.");
|
|
2826
|
+
console.log(" Delete it any time: terminalhire sync --delete");
|
|
2827
|
+
console.log("");
|
|
2828
|
+
console.log(" This is NOT required to use terminalhire.");
|
|
2829
|
+
console.log("");
|
|
2830
|
+
}
|
|
2831
|
+
async function runPush() {
|
|
2832
|
+
const { readProfile: readProfile2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
|
|
2833
|
+
const profile = await readProfile2();
|
|
2834
|
+
if (!profile.github || !profile.github.login) {
|
|
2835
|
+
console.log("");
|
|
2836
|
+
console.log(" No GitHub data in your local profile yet.");
|
|
2837
|
+
console.log(" Run `terminalhire login` first, then re-run `terminalhire sync --push`.");
|
|
2838
|
+
console.log("");
|
|
2839
|
+
process.exit(1);
|
|
2840
|
+
}
|
|
2841
|
+
const fields = buildConsentFields(profile);
|
|
2842
|
+
renderConsentCard(fields);
|
|
2843
|
+
let consentConfirmed = false;
|
|
2844
|
+
const answer = await ask2(' Sync your profile to staqs (terminalhire.com)? Type "yes" to continue: ');
|
|
2845
|
+
if (answer === "yes") {
|
|
2846
|
+
consentConfirmed = true;
|
|
2847
|
+
}
|
|
2848
|
+
if (!consentConfirmed) {
|
|
2849
|
+
console.log("\n Aborted \u2014 nothing was sent.\n");
|
|
2850
|
+
process.exit(0);
|
|
2851
|
+
}
|
|
2852
|
+
const consentedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2853
|
+
const consentToken = {
|
|
2854
|
+
consentedAt,
|
|
2855
|
+
version: CONSENT_VERSION,
|
|
2856
|
+
fields: fields.map((f) => f.key)
|
|
2857
|
+
};
|
|
2858
|
+
const payloadProfile = {
|
|
2859
|
+
login: profile.github.login,
|
|
2860
|
+
name: profile.displayName || null,
|
|
2861
|
+
publicEmail: profile.github.publicEmail || null,
|
|
2862
|
+
topLanguages: profile.github.topLanguages || [],
|
|
2863
|
+
skillTags: profile.skillTags || [],
|
|
2864
|
+
displayName: profile.displayName || null,
|
|
2865
|
+
contactEmail: profile.contactEmail || null
|
|
2866
|
+
};
|
|
2867
|
+
const priorMarker = readMarker();
|
|
2868
|
+
const rowToken = priorMarker && priorMarker.deleteToken ? priorMarker.deleteToken : null;
|
|
2869
|
+
const requestBody = { consentToken, profile: payloadProfile };
|
|
2870
|
+
if (rowToken) {
|
|
2871
|
+
requestBody.rowToken = rowToken;
|
|
2872
|
+
}
|
|
2873
|
+
console.log("\n Sending one-time snapshot...");
|
|
2874
|
+
let res;
|
|
2875
|
+
try {
|
|
2876
|
+
res = await fetch(`${API_URL2}/api/profile-sync`, {
|
|
2877
|
+
method: "POST",
|
|
2878
|
+
headers: { "Content-Type": "application/json" },
|
|
2879
|
+
body: JSON.stringify(requestBody),
|
|
2880
|
+
signal: AbortSignal.timeout(1e4)
|
|
2881
|
+
});
|
|
2882
|
+
} catch (err) {
|
|
2883
|
+
console.error(`
|
|
2884
|
+
Sync failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2885
|
+
process.exit(1);
|
|
2886
|
+
}
|
|
2887
|
+
if (!res.ok) {
|
|
2888
|
+
let detail = "";
|
|
2889
|
+
try {
|
|
2890
|
+
detail = (await res.json())?.message || "";
|
|
2891
|
+
} catch {
|
|
2892
|
+
}
|
|
2893
|
+
console.error(`
|
|
2894
|
+
Sync failed: /api/profile-sync returned ${res.status}. ${detail}`);
|
|
2895
|
+
if (res.status === 503) {
|
|
2896
|
+
console.error(" (Tier-1 sync is not enabled on the server yet.)");
|
|
2897
|
+
}
|
|
2898
|
+
if (res.status === 403) {
|
|
2899
|
+
console.error(" (This GitHub login was already claimed by a different push.)");
|
|
2900
|
+
}
|
|
2901
|
+
process.exit(1);
|
|
2902
|
+
}
|
|
2903
|
+
let deleteToken = null;
|
|
2904
|
+
try {
|
|
2905
|
+
deleteToken = (await res.json())?.deleteToken || null;
|
|
2906
|
+
} catch {
|
|
2907
|
+
}
|
|
2908
|
+
writeMarker({ consentedAt, login: profile.github.login, deleteToken });
|
|
2909
|
+
console.log("\n Profile synced. A local consent marker was written to ~/.terminalhire/tier1.json");
|
|
2910
|
+
console.log(" Delete your synced profile any time: terminalhire sync --delete\n");
|
|
2911
|
+
}
|
|
2912
|
+
function runStatus() {
|
|
2913
|
+
const marker = readMarker();
|
|
2914
|
+
console.log("");
|
|
2915
|
+
if (marker && marker.consentedAt) {
|
|
2916
|
+
console.log(" Tier-1 sync: CONSENTED (local marker present)");
|
|
2917
|
+
console.log(` login: ${marker.login || "(unknown)"}`);
|
|
2918
|
+
console.log(` consented at: ${marker.consentedAt}`);
|
|
2919
|
+
console.log("");
|
|
2920
|
+
console.log(" This reflects your local marker only (no network call was made).");
|
|
2921
|
+
console.log(" Update: terminalhire sync --push | Delete: terminalhire sync --delete");
|
|
2922
|
+
} else {
|
|
2923
|
+
console.log(" Tier-1 sync: NOT CONSENTED (no local marker).");
|
|
2924
|
+
console.log(" Your profile has not been synced. Enable: terminalhire sync --push");
|
|
2925
|
+
}
|
|
2926
|
+
console.log("");
|
|
2927
|
+
}
|
|
2928
|
+
async function runDelete() {
|
|
2929
|
+
const marker = readMarker();
|
|
2930
|
+
console.log("");
|
|
2931
|
+
console.log(" This will hard-delete your synced profile from staqs (terminalhire.com)");
|
|
2932
|
+
console.log(" and remove the local consent marker. There is no soft-delete.");
|
|
2933
|
+
console.log("");
|
|
2934
|
+
const answer = await ask2(' Delete your synced profile? Type "yes" to confirm: ');
|
|
2935
|
+
if (answer !== "yes") {
|
|
2936
|
+
console.log("\n Aborted \u2014 nothing was deleted.\n");
|
|
2937
|
+
process.exit(0);
|
|
2938
|
+
}
|
|
2939
|
+
let login = marker && marker.login ? marker.login : null;
|
|
2940
|
+
if (!login) {
|
|
2941
|
+
const { readProfile: readProfile2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
|
|
2942
|
+
const profile = await readProfile2();
|
|
2943
|
+
login = profile.github && profile.github.login ? profile.github.login : null;
|
|
2944
|
+
}
|
|
2945
|
+
if (!login) {
|
|
2946
|
+
console.log("\n No github login to delete (no marker, no GitHub profile). Clearing local marker.\n");
|
|
2947
|
+
clearMarker();
|
|
2948
|
+
process.exit(0);
|
|
2949
|
+
}
|
|
2950
|
+
const deleteToken = marker && marker.deleteToken ? marker.deleteToken : null;
|
|
2951
|
+
if (!deleteToken) {
|
|
2952
|
+
console.log("\n No delete token found in ~/.terminalhire/tier1.json.");
|
|
2953
|
+
console.log(" Deletion must be run from the machine that originally pushed your profile");
|
|
2954
|
+
console.log(" (the delete token is stored there), or re-run `terminalhire sync --push`");
|
|
2955
|
+
console.log(" first to obtain a fresh token, then `terminalhire sync --delete`.\n");
|
|
2956
|
+
process.exit(1);
|
|
2957
|
+
}
|
|
2958
|
+
const consentToken = {
|
|
2959
|
+
consentedAt: marker && marker.consentedAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
2960
|
+
version: CONSENT_VERSION,
|
|
2961
|
+
fields: ["login"]
|
|
2962
|
+
};
|
|
2963
|
+
console.log("\n Requesting deletion...");
|
|
2964
|
+
let res;
|
|
2965
|
+
try {
|
|
2966
|
+
res = await fetch(`${API_URL2}/api/profile-sync`, {
|
|
2967
|
+
method: "DELETE",
|
|
2968
|
+
headers: { "Content-Type": "application/json" },
|
|
2969
|
+
body: JSON.stringify({ consentToken, login, deleteToken }),
|
|
2970
|
+
signal: AbortSignal.timeout(1e4)
|
|
2971
|
+
});
|
|
2972
|
+
} catch (err) {
|
|
2973
|
+
console.error(`
|
|
2974
|
+
Delete failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2975
|
+
console.error(" Local marker NOT cleared (server state unknown). Re-run to retry.\n");
|
|
2976
|
+
process.exit(1);
|
|
2977
|
+
}
|
|
2978
|
+
if (!res.ok) {
|
|
2979
|
+
console.error(`
|
|
2980
|
+
Delete failed: /api/profile-sync returned ${res.status}.`);
|
|
2981
|
+
if (res.status === 503) {
|
|
2982
|
+
console.error(" (Tier-1 sync is not enabled on the server yet.) Clearing local marker.");
|
|
2983
|
+
clearMarker();
|
|
2984
|
+
}
|
|
2985
|
+
process.exit(1);
|
|
2986
|
+
}
|
|
2987
|
+
clearMarker();
|
|
2988
|
+
console.log("\n Synced profile deleted and local marker cleared.\n");
|
|
2989
|
+
}
|
|
2990
|
+
async function run7() {
|
|
2991
|
+
const args2 = process.argv.slice(2).filter((a) => a !== "sync");
|
|
2992
|
+
const has = (f) => args2.includes(f);
|
|
2993
|
+
if (has("--push") || has("--enable")) {
|
|
2994
|
+
await runPush();
|
|
2995
|
+
return;
|
|
2996
|
+
}
|
|
2997
|
+
if (has("--status")) {
|
|
2998
|
+
runStatus();
|
|
2999
|
+
return;
|
|
3000
|
+
}
|
|
3001
|
+
if (has("--delete")) {
|
|
3002
|
+
await runDelete();
|
|
3003
|
+
return;
|
|
3004
|
+
}
|
|
3005
|
+
console.log("");
|
|
3006
|
+
console.log(" terminalhire sync \u2014 opt-in Tier-1 profile sync (one-time snapshot)");
|
|
3007
|
+
console.log("");
|
|
3008
|
+
console.log(' terminalhire sync --push Send your profile (shows a consent card, requires typed "yes")');
|
|
3009
|
+
console.log(" terminalhire sync --status Show whether you have consented (local read only)");
|
|
3010
|
+
console.log(" terminalhire sync --delete Hard-delete your synced profile (revocation)");
|
|
3011
|
+
console.log("");
|
|
3012
|
+
console.log(' Your profile is NEVER sent without an explicit typed "yes".');
|
|
3013
|
+
console.log(" This is NOT required to use terminalhire.");
|
|
3014
|
+
console.log("");
|
|
3015
|
+
}
|
|
3016
|
+
var TH_DIR3, TIER1_MARKER, API_URL2, CONSENT_VERSION;
|
|
3017
|
+
var init_jpi_sync = __esm({
|
|
3018
|
+
"bin/jpi-sync.js"() {
|
|
3019
|
+
"use strict";
|
|
3020
|
+
TH_DIR3 = process.env["TERMINALHIRE_DIR"] || join9(homedir7(), ".terminalhire");
|
|
3021
|
+
TIER1_MARKER = join9(TH_DIR3, "tier1.json");
|
|
3022
|
+
API_URL2 = process.env["TERMINALHIRE_API_URL"] || process.env["JPI_API_URL"] || "https://terminalhire.com";
|
|
3023
|
+
CONSENT_VERSION = 1;
|
|
3024
|
+
}
|
|
3025
|
+
});
|
|
3026
|
+
|
|
2746
3027
|
// bin/jpi-init.js
|
|
2747
3028
|
var jpi_init_exports = {};
|
|
2748
3029
|
__export(jpi_init_exports, {
|
|
2749
|
-
run: () =>
|
|
3030
|
+
run: () => run8
|
|
2750
3031
|
});
|
|
2751
|
-
import { existsSync as
|
|
2752
|
-
import { join as
|
|
3032
|
+
import { existsSync as existsSync8 } from "fs";
|
|
3033
|
+
import { join as join10, resolve } from "path";
|
|
2753
3034
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
2754
|
-
import { createInterface as
|
|
3035
|
+
import { createInterface as createInterface5 } from "readline";
|
|
2755
3036
|
import { spawnSync, spawn } from "child_process";
|
|
2756
|
-
import { homedir as
|
|
2757
|
-
function
|
|
2758
|
-
const rl =
|
|
3037
|
+
import { homedir as homedir8 } from "os";
|
|
3038
|
+
function ask3(question) {
|
|
3039
|
+
const rl = createInterface5({ input: process.stdin, output: process.stdout });
|
|
2759
3040
|
return new Promise((resolve2) => {
|
|
2760
3041
|
rl.question(question, (answer) => {
|
|
2761
3042
|
rl.close();
|
|
@@ -2764,18 +3045,18 @@ function ask2(question) {
|
|
|
2764
3045
|
});
|
|
2765
3046
|
}
|
|
2766
3047
|
function resolveScript(name) {
|
|
2767
|
-
const distPath = resolve(
|
|
2768
|
-
const legacyPath = resolve(
|
|
2769
|
-
return
|
|
3048
|
+
const distPath = resolve(join10(__dirname2, "..", "..", "dist", "bin", `${name}.js`));
|
|
3049
|
+
const legacyPath = resolve(join10(__dirname2, `${name}.js`));
|
|
3050
|
+
return existsSync8(distPath) ? distPath : legacyPath;
|
|
2770
3051
|
}
|
|
2771
3052
|
function resolveInstallJs() {
|
|
2772
|
-
const fromDist = resolve(
|
|
2773
|
-
const fromBin = resolve(
|
|
2774
|
-
if (
|
|
2775
|
-
if (
|
|
3053
|
+
const fromDist = resolve(join10(__dirname2, "..", "..", "install.js"));
|
|
3054
|
+
const fromBin = resolve(join10(__dirname2, "..", "install.js"));
|
|
3055
|
+
if (existsSync8(fromDist)) return fromDist;
|
|
3056
|
+
if (existsSync8(fromBin)) return fromBin;
|
|
2776
3057
|
return fromBin;
|
|
2777
3058
|
}
|
|
2778
|
-
async function
|
|
3059
|
+
async function run8() {
|
|
2779
3060
|
console.log("");
|
|
2780
3061
|
console.log("\u250C\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\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\u2510");
|
|
2781
3062
|
console.log("\u2502 terminalhire init \u2014 one-command onboarding \u2502");
|
|
@@ -2785,7 +3066,7 @@ async function run7() {
|
|
|
2785
3066
|
console.log("This will:");
|
|
2786
3067
|
console.log(" 1. Optionally sign you in with GitHub (public profile only, read:user)");
|
|
2787
3068
|
console.log(" 2. Seed your local job cache (anonymous index download)");
|
|
2788
|
-
console.log(" 3.
|
|
3069
|
+
console.log(" 3. Enable the ambient spinner job surface in ~/.claude/settings.json");
|
|
2789
3070
|
console.log(" (with backup + your explicit consent before any file is touched)");
|
|
2790
3071
|
console.log("");
|
|
2791
3072
|
console.log('You can stop at any step. Nothing is changed until you say "yes".');
|
|
@@ -2797,7 +3078,7 @@ async function run7() {
|
|
|
2797
3078
|
console.log(" GitHub data enriches your local profile. Nothing leaves your machine");
|
|
2798
3079
|
console.log(" until you explicitly consent to a specific lead.");
|
|
2799
3080
|
console.log("");
|
|
2800
|
-
const githubAnswer = await
|
|
3081
|
+
const githubAnswer = await ask3("Sign in with GitHub now? [Y/n] (Enter = yes, n = stay local): ");
|
|
2801
3082
|
const doGitHub = githubAnswer === "" || githubAnswer === "y" || githubAnswer === "yes";
|
|
2802
3083
|
if (doGitHub) {
|
|
2803
3084
|
console.log("");
|
|
@@ -2838,11 +3119,11 @@ async function run7() {
|
|
|
2838
3119
|
console.log(" Run `terminalhire jobs` after a few Claude Code sessions to populate it.");
|
|
2839
3120
|
}
|
|
2840
3121
|
console.log("");
|
|
2841
|
-
console.log("Step 3/3 \u2014
|
|
3122
|
+
console.log("Step 3/3 \u2014 Enable the ambient spinner job surface in ~/.claude/settings.json");
|
|
2842
3123
|
console.log("");
|
|
2843
3124
|
console.log(" This is the only step that modifies a system file.");
|
|
2844
3125
|
console.log(" A timestamped backup is created before any change.");
|
|
2845
|
-
console.log("
|
|
3126
|
+
console.log(" Disable at any time: node install.js --uninstall (or terminalhire spinner --off)");
|
|
2846
3127
|
console.log("");
|
|
2847
3128
|
const installJs = resolveInstallJs();
|
|
2848
3129
|
const installChild = spawnSync(process.execPath, [installJs], {
|
|
@@ -2857,11 +3138,11 @@ async function run7() {
|
|
|
2857
3138
|
console.log("\u250C\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\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\u2510");
|
|
2858
3139
|
console.log("\u2502 terminalhire init complete! \u2502");
|
|
2859
3140
|
console.log("\u2502 \u2502");
|
|
2860
|
-
console.log("\u2502 Restart Claude Code to see the
|
|
3141
|
+
console.log("\u2502 Restart Claude Code to see the ambient spinner job surface. \u2502");
|
|
2861
3142
|
console.log("\u2502 \u2502");
|
|
2862
3143
|
console.log("\u2502 Quick reference: \u2502");
|
|
2863
3144
|
console.log("\u2502 terminalhire jobs \u2014 browse matching roles \u2502");
|
|
2864
|
-
console.log("\u2502 terminalhire
|
|
3145
|
+
console.log("\u2502 terminalhire spinner --off \u2014 disable the spinner surface \u2502");
|
|
2865
3146
|
console.log("\u2502 terminalhire login \u2014 sign in with GitHub \u2502");
|
|
2866
3147
|
console.log("\u2502 terminalhire profile --show \u2014 inspect your local profile \u2502");
|
|
2867
3148
|
console.log("\u2514\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\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\u2518");
|
|
@@ -2878,17 +3159,17 @@ var init_jpi_init = __esm({
|
|
|
2878
3159
|
// bin/jpi-refresh.js
|
|
2879
3160
|
var jpi_refresh_exports = {};
|
|
2880
3161
|
__export(jpi_refresh_exports, {
|
|
2881
|
-
run: () =>
|
|
3162
|
+
run: () => run9
|
|
2882
3163
|
});
|
|
2883
|
-
import { readFileSync as
|
|
2884
|
-
import { join as
|
|
2885
|
-
import { homedir as
|
|
3164
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync8, existsSync as existsSync9, mkdirSync as mkdirSync8 } from "fs";
|
|
3165
|
+
import { join as join11 } from "path";
|
|
3166
|
+
import { homedir as homedir9 } from "os";
|
|
2886
3167
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
2887
|
-
async function
|
|
3168
|
+
async function run9() {
|
|
2888
3169
|
try {
|
|
2889
3170
|
let index;
|
|
2890
3171
|
try {
|
|
2891
|
-
const res = await fetch(`${
|
|
3172
|
+
const res = await fetch(`${API_URL3}/api/index`, {
|
|
2892
3173
|
signal: AbortSignal.timeout(15e3),
|
|
2893
3174
|
headers: { "Accept": "application/json" }
|
|
2894
3175
|
});
|
|
@@ -2915,7 +3196,7 @@ async function run8() {
|
|
|
2915
3196
|
const fp = profileToFingerprint2(profile);
|
|
2916
3197
|
const results = match2(fp, jobs, jobs.length);
|
|
2917
3198
|
matchCount = results.length;
|
|
2918
|
-
topMatches = results.slice(0,
|
|
3199
|
+
topMatches = results.slice(0, 25).map((r) => ({
|
|
2919
3200
|
id: r.job.id,
|
|
2920
3201
|
title: r.job.title,
|
|
2921
3202
|
company: r.job.company,
|
|
@@ -2926,14 +3207,14 @@ async function run8() {
|
|
|
2926
3207
|
}
|
|
2927
3208
|
} catch {
|
|
2928
3209
|
}
|
|
2929
|
-
|
|
3210
|
+
mkdirSync8(TERMINALHIRE_DIR5, { recursive: true });
|
|
2930
3211
|
const cacheEntry = {
|
|
2931
3212
|
ts: Date.now(),
|
|
2932
3213
|
index,
|
|
2933
3214
|
matchCount,
|
|
2934
3215
|
topMatches
|
|
2935
3216
|
};
|
|
2936
|
-
|
|
3217
|
+
writeFileSync8(INDEX_CACHE_FILE2, JSON.stringify(cacheEntry), "utf8");
|
|
2937
3218
|
try {
|
|
2938
3219
|
const {
|
|
2939
3220
|
readSpinnerConfig: readSpinnerConfig2,
|
|
@@ -2960,7 +3241,7 @@ async function run8() {
|
|
|
2960
3241
|
const verbs = buildSpinnerPool2(ranked, sc.max, { sessionTags, frequency: sc.frequency });
|
|
2961
3242
|
if (verbs.length > 0) applySpinnerVerbs2(verbs, sc.mode);
|
|
2962
3243
|
else clearSpinnerVerbs2();
|
|
2963
|
-
const tips = buildTips2(ranked,
|
|
3244
|
+
const tips = buildTips2(ranked, API_URL3, 8);
|
|
2964
3245
|
if (tips.length > 0) applySpinnerTips2(tips);
|
|
2965
3246
|
else clearSpinnerTips2();
|
|
2966
3247
|
} else {
|
|
@@ -2977,30 +3258,30 @@ async function run8() {
|
|
|
2977
3258
|
process.exit(1);
|
|
2978
3259
|
}
|
|
2979
3260
|
}
|
|
2980
|
-
var __dirname3, TERMINALHIRE_DIR5, INDEX_CACHE_FILE2,
|
|
3261
|
+
var __dirname3, TERMINALHIRE_DIR5, INDEX_CACHE_FILE2, API_URL3;
|
|
2981
3262
|
var init_jpi_refresh = __esm({
|
|
2982
3263
|
"bin/jpi-refresh.js"() {
|
|
2983
3264
|
"use strict";
|
|
2984
3265
|
__dirname3 = fileURLToPath4(new URL(".", import.meta.url));
|
|
2985
|
-
TERMINALHIRE_DIR5 =
|
|
2986
|
-
INDEX_CACHE_FILE2 =
|
|
2987
|
-
|
|
3266
|
+
TERMINALHIRE_DIR5 = join11(homedir9(), ".terminalhire");
|
|
3267
|
+
INDEX_CACHE_FILE2 = join11(TERMINALHIRE_DIR5, "index-cache.json");
|
|
3268
|
+
API_URL3 = process.env["TERMINALHIRE_API_URL"] ?? process.env["JPI_API_URL"] ?? "https://terminalhire.com";
|
|
2988
3269
|
}
|
|
2989
3270
|
});
|
|
2990
3271
|
|
|
2991
3272
|
// bin/jpi-save.js
|
|
2992
3273
|
var jpi_save_exports = {};
|
|
2993
3274
|
__export(jpi_save_exports, {
|
|
2994
|
-
run: () =>
|
|
3275
|
+
run: () => run10
|
|
2995
3276
|
});
|
|
2996
|
-
import { readFileSync as
|
|
2997
|
-
import { join as
|
|
2998
|
-
import { homedir as
|
|
3277
|
+
import { readFileSync as readFileSync11, existsSync as existsSync10 } from "fs";
|
|
3278
|
+
import { join as join12 } from "path";
|
|
3279
|
+
import { homedir as homedir10 } from "os";
|
|
2999
3280
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
3000
3281
|
function findJobInCache(jobId) {
|
|
3001
3282
|
try {
|
|
3002
|
-
if (!
|
|
3003
|
-
const raw =
|
|
3283
|
+
if (!existsSync10(INDEX_CACHE_FILE3)) return null;
|
|
3284
|
+
const raw = readFileSync11(INDEX_CACHE_FILE3, "utf8");
|
|
3004
3285
|
const entry = JSON.parse(raw);
|
|
3005
3286
|
const jobs = entry?.index?.jobs ?? [];
|
|
3006
3287
|
return jobs.find((j) => j.id === jobId) ?? null;
|
|
@@ -3069,7 +3350,7 @@ async function cmdUnsave(jobId) {
|
|
|
3069
3350
|
process.exit(1);
|
|
3070
3351
|
}
|
|
3071
3352
|
}
|
|
3072
|
-
async function
|
|
3353
|
+
async function run10() {
|
|
3073
3354
|
const verb = process.argv[2];
|
|
3074
3355
|
const jobId = process.argv[3];
|
|
3075
3356
|
try {
|
|
@@ -3093,26 +3374,26 @@ var init_jpi_save = __esm({
|
|
|
3093
3374
|
"bin/jpi-save.js"() {
|
|
3094
3375
|
"use strict";
|
|
3095
3376
|
__dirname4 = fileURLToPath5(new URL(".", import.meta.url));
|
|
3096
|
-
TERMINALHIRE_DIR6 =
|
|
3097
|
-
INDEX_CACHE_FILE3 =
|
|
3377
|
+
TERMINALHIRE_DIR6 = join12(homedir10(), ".terminalhire");
|
|
3378
|
+
INDEX_CACHE_FILE3 = join12(TERMINALHIRE_DIR6, "index-cache.json");
|
|
3098
3379
|
}
|
|
3099
3380
|
});
|
|
3100
3381
|
|
|
3101
3382
|
// bin/jpi-dispatch.js
|
|
3102
3383
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
3103
|
-
import { join as
|
|
3104
|
-
import { existsSync as
|
|
3384
|
+
import { join as join13, dirname as dirname2 } from "path";
|
|
3385
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
|
|
3105
3386
|
import { createRequire } from "module";
|
|
3106
3387
|
var __dirname5 = fileURLToPath6(new URL(".", import.meta.url));
|
|
3107
3388
|
function readPackageVersion() {
|
|
3108
3389
|
try {
|
|
3109
3390
|
const candidates = [
|
|
3110
|
-
|
|
3111
|
-
|
|
3391
|
+
join13(__dirname5, "..", "..", "package.json"),
|
|
3392
|
+
join13(__dirname5, "..", "package.json")
|
|
3112
3393
|
];
|
|
3113
3394
|
for (const p of candidates) {
|
|
3114
|
-
if (
|
|
3115
|
-
const pkg = JSON.parse(
|
|
3395
|
+
if (existsSync11(p)) {
|
|
3396
|
+
const pkg = JSON.parse(readFileSync12(p, "utf8"));
|
|
3116
3397
|
if (pkg.version) return pkg.version;
|
|
3117
3398
|
}
|
|
3118
3399
|
}
|
|
@@ -3123,7 +3404,7 @@ function readPackageVersion() {
|
|
|
3123
3404
|
var firstArg = process.argv[2];
|
|
3124
3405
|
if (!firstArg && !process.stdin.isTTY) {
|
|
3125
3406
|
const { default: childProcess } = await import("child_process");
|
|
3126
|
-
const nudgeScript =
|
|
3407
|
+
const nudgeScript = join13(__dirname5, "jpi.js");
|
|
3127
3408
|
const child = childProcess.spawnSync(process.execPath, [nudgeScript], {
|
|
3128
3409
|
stdio: ["inherit", "inherit", "inherit"]
|
|
3129
3410
|
});
|
|
@@ -3154,6 +3435,9 @@ if (!firstArg || firstArg === "help" || firstArg === "--help" || firstArg === "-
|
|
|
3154
3435
|
console.log(" terminalhire save <jobId> Save a job locally (id shown in `jobs` output)");
|
|
3155
3436
|
console.log(" terminalhire saved List all locally-saved jobs");
|
|
3156
3437
|
console.log(" terminalhire unsave <jobId> Remove a saved job");
|
|
3438
|
+
console.log(" terminalhire sync --push Opt-in: send your profile to staqs (typed-yes consent)");
|
|
3439
|
+
console.log(" terminalhire sync --status Show whether you have consented (local read only)");
|
|
3440
|
+
console.log(" terminalhire sync --delete Hard-delete your synced profile (revocation)");
|
|
3157
3441
|
console.log("");
|
|
3158
3442
|
console.log("Install / uninstall the Claude Code statusLine nudge:");
|
|
3159
3443
|
console.log(" terminalhire init (preferred \u2014 full guided onboarding)");
|
|
@@ -3163,6 +3447,7 @@ if (!firstArg || firstArg === "help" || firstArg === "--help" || firstArg === "-
|
|
|
3163
3447
|
console.log("Privacy: your profile never leaves your device.");
|
|
3164
3448
|
console.log(" GET /api/index \u2014 anonymous index download (no dev data)");
|
|
3165
3449
|
console.log(" POST /api/lead \u2014 only after explicit per-role named-entity consent");
|
|
3450
|
+
console.log(" POST /api/profile-sync \u2014 only via `terminalhire sync --push` with a typed-yes consent token");
|
|
3166
3451
|
console.log(" GitHub token \u2014 encrypted at ~/.terminalhire/github-token.enc, scope: read:user");
|
|
3167
3452
|
console.log("");
|
|
3168
3453
|
process.exit(0);
|
|
@@ -3202,6 +3487,11 @@ if (firstArg === "spinner") {
|
|
|
3202
3487
|
await mod.run();
|
|
3203
3488
|
process.exit(0);
|
|
3204
3489
|
}
|
|
3490
|
+
if (firstArg === "sync") {
|
|
3491
|
+
const mod = await Promise.resolve().then(() => (init_jpi_sync(), jpi_sync_exports));
|
|
3492
|
+
await mod.run();
|
|
3493
|
+
process.exit(0);
|
|
3494
|
+
}
|
|
3205
3495
|
if (firstArg === "init") {
|
|
3206
3496
|
const mod = await Promise.resolve().then(() => (init_jpi_init(), jpi_init_exports));
|
|
3207
3497
|
await mod.run();
|
package/dist/bin/jpi-init.js
CHANGED
|
@@ -39,7 +39,7 @@ async function run() {
|
|
|
39
39
|
console.log("This will:");
|
|
40
40
|
console.log(" 1. Optionally sign you in with GitHub (public profile only, read:user)");
|
|
41
41
|
console.log(" 2. Seed your local job cache (anonymous index download)");
|
|
42
|
-
console.log(" 3.
|
|
42
|
+
console.log(" 3. Enable the ambient spinner job surface in ~/.claude/settings.json");
|
|
43
43
|
console.log(" (with backup + your explicit consent before any file is touched)");
|
|
44
44
|
console.log("");
|
|
45
45
|
console.log('You can stop at any step. Nothing is changed until you say "yes".');
|
|
@@ -92,11 +92,11 @@ async function run() {
|
|
|
92
92
|
console.log(" Run `terminalhire jobs` after a few Claude Code sessions to populate it.");
|
|
93
93
|
}
|
|
94
94
|
console.log("");
|
|
95
|
-
console.log("Step 3/3 \u2014
|
|
95
|
+
console.log("Step 3/3 \u2014 Enable the ambient spinner job surface in ~/.claude/settings.json");
|
|
96
96
|
console.log("");
|
|
97
97
|
console.log(" This is the only step that modifies a system file.");
|
|
98
98
|
console.log(" A timestamped backup is created before any change.");
|
|
99
|
-
console.log("
|
|
99
|
+
console.log(" Disable at any time: node install.js --uninstall (or terminalhire spinner --off)");
|
|
100
100
|
console.log("");
|
|
101
101
|
const installJs = resolveInstallJs();
|
|
102
102
|
const installChild = spawnSync(process.execPath, [installJs], {
|
|
@@ -111,11 +111,11 @@ async function run() {
|
|
|
111
111
|
console.log("\u250C\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\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\u2510");
|
|
112
112
|
console.log("\u2502 terminalhire init complete! \u2502");
|
|
113
113
|
console.log("\u2502 \u2502");
|
|
114
|
-
console.log("\u2502 Restart Claude Code to see the
|
|
114
|
+
console.log("\u2502 Restart Claude Code to see the ambient spinner job surface. \u2502");
|
|
115
115
|
console.log("\u2502 \u2502");
|
|
116
116
|
console.log("\u2502 Quick reference: \u2502");
|
|
117
117
|
console.log("\u2502 terminalhire jobs \u2014 browse matching roles \u2502");
|
|
118
|
-
console.log("\u2502 terminalhire
|
|
118
|
+
console.log("\u2502 terminalhire spinner --off \u2014 disable the spinner surface \u2502");
|
|
119
119
|
console.log("\u2502 terminalhire login \u2014 sign in with GitHub \u2502");
|
|
120
120
|
console.log("\u2502 terminalhire profile --show \u2014 inspect your local profile \u2502");
|
|
121
121
|
console.log("\u2514\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\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\u2518");
|