terminalhire 0.2.3 → 0.2.5
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 +372 -67
- package/dist/bin/jpi-jobs.js +220 -47
- package/dist/bin/jpi-learn.js +118 -0
- package/dist/bin/jpi-login.js +220 -47
- package/dist/bin/jpi-profile.js +118 -0
- package/dist/bin/jpi-refresh.js +249 -48
- package/dist/bin/jpi-save.js +118 -0
- package/dist/bin/jpi-sync.js +239 -17
- package/dist/bin/spinner.js +29 -1
- package/dist/src/profile.js +110 -0
- package/dist/src/signal.js +110 -0
- package/package.json +1 -1
package/dist/bin/jpi-dispatch.js
CHANGED
|
@@ -747,16 +747,102 @@ var init_ashby = __esm({
|
|
|
747
747
|
}
|
|
748
748
|
});
|
|
749
749
|
|
|
750
|
-
// ../../packages/core/src/feeds/
|
|
750
|
+
// ../../packages/core/src/feeds/lever.ts
|
|
751
751
|
function tokenize3(text) {
|
|
752
752
|
return text.toLowerCase().replace(/[^a-z0-9.\-+#]/g, " ").split(/\s+/).filter(Boolean);
|
|
753
753
|
}
|
|
754
|
-
function extractTags3(
|
|
754
|
+
function extractTags3(p) {
|
|
755
|
+
const cat = p.categories ?? {};
|
|
756
|
+
const texts = [
|
|
757
|
+
p.text,
|
|
758
|
+
cat.team ?? "",
|
|
759
|
+
cat.department ?? "",
|
|
760
|
+
cat.location ?? "",
|
|
761
|
+
...cat.allLocations ?? [],
|
|
762
|
+
p.descriptionPlain ?? ""
|
|
763
|
+
];
|
|
764
|
+
return normalize(texts.flatMap(tokenize3));
|
|
765
|
+
}
|
|
766
|
+
function mapCommitment(raw) {
|
|
767
|
+
if (!raw) return "full_time";
|
|
768
|
+
const lower = raw.toLowerCase();
|
|
769
|
+
if (lower.includes("contract") || lower.includes("contractor")) return "contract";
|
|
770
|
+
if (lower.includes("freelance")) return "freelance";
|
|
771
|
+
return "full_time";
|
|
772
|
+
}
|
|
773
|
+
function inferRemote3(p) {
|
|
774
|
+
if ((p.workplaceType ?? "").toLowerCase() === "remote") return true;
|
|
775
|
+
const cat = p.categories ?? {};
|
|
776
|
+
const haystack = [cat.location ?? "", ...cat.allLocations ?? []].join(" ").toLowerCase();
|
|
777
|
+
return haystack.includes("remote") || haystack.includes("anywhere");
|
|
778
|
+
}
|
|
779
|
+
function toIso(ms) {
|
|
780
|
+
if (typeof ms !== "number" || !Number.isFinite(ms)) return void 0;
|
|
781
|
+
try {
|
|
782
|
+
return new Date(ms).toISOString();
|
|
783
|
+
} catch {
|
|
784
|
+
return void 0;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
async function fetchSlug3(slug) {
|
|
788
|
+
const url = `https://api.lever.co/v0/postings/${slug}?mode=json`;
|
|
789
|
+
const res = await fetch(url, { headers: { Accept: "application/json" } });
|
|
790
|
+
if (!res.ok) {
|
|
791
|
+
throw new Error(`Lever ${slug}: HTTP ${res.status}`);
|
|
792
|
+
}
|
|
793
|
+
const data = await res.json();
|
|
794
|
+
const postings = Array.isArray(data) ? data : [];
|
|
795
|
+
if (postings.length === 0) {
|
|
796
|
+
console.warn(`[lever] ${slug}: 0 jobs returned (board may be private or slug invalid)`);
|
|
797
|
+
} else {
|
|
798
|
+
console.info(`[lever] ${slug}: ${postings.length} jobs`);
|
|
799
|
+
}
|
|
800
|
+
return postings.filter((p) => p && p.id && p.text).map((p) => ({
|
|
801
|
+
id: `lever:${p.id}`,
|
|
802
|
+
source: "lever",
|
|
803
|
+
title: p.text,
|
|
804
|
+
company: slug,
|
|
805
|
+
url: p.hostedUrl ?? p.applyUrl ?? `https://jobs.lever.co/${slug}/${p.id}`,
|
|
806
|
+
remote: inferRemote3(p),
|
|
807
|
+
location: p.categories?.location,
|
|
808
|
+
tags: extractTags3(p),
|
|
809
|
+
roleType: mapCommitment(p.categories?.commitment),
|
|
810
|
+
postedAt: toIso(p.createdAt),
|
|
811
|
+
applyMode: "direct",
|
|
812
|
+
raw: p
|
|
813
|
+
}));
|
|
814
|
+
}
|
|
815
|
+
var lever;
|
|
816
|
+
var init_lever = __esm({
|
|
817
|
+
"../../packages/core/src/feeds/lever.ts"() {
|
|
818
|
+
"use strict";
|
|
819
|
+
init_vocabulary();
|
|
820
|
+
lever = {
|
|
821
|
+
source: "lever",
|
|
822
|
+
async fetch(opts) {
|
|
823
|
+
const slugs = opts?.slugs ?? [];
|
|
824
|
+
const results = await Promise.allSettled(slugs.map(fetchSlug3));
|
|
825
|
+
const jobs = [];
|
|
826
|
+
for (const r of results) {
|
|
827
|
+
if (r.status === "fulfilled") jobs.push(...r.value);
|
|
828
|
+
else console.warn("[lever] slug fetch rejected:", r.reason);
|
|
829
|
+
}
|
|
830
|
+
return jobs;
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
// ../../packages/core/src/feeds/himalayas.ts
|
|
837
|
+
function tokenize4(text) {
|
|
838
|
+
return text.toLowerCase().replace(/[^a-z0-9.\-+#]/g, " ").split(/\s+/).filter(Boolean);
|
|
839
|
+
}
|
|
840
|
+
function extractTags4(job) {
|
|
755
841
|
const texts = [
|
|
756
842
|
job.title,
|
|
757
843
|
...job.tags ?? []
|
|
758
844
|
];
|
|
759
|
-
return normalize(texts.flatMap(
|
|
845
|
+
return normalize(texts.flatMap(tokenize4));
|
|
760
846
|
}
|
|
761
847
|
function mapJobType(raw) {
|
|
762
848
|
if (!raw) return "full_time";
|
|
@@ -802,7 +888,7 @@ var init_himalayas = __esm({
|
|
|
802
888
|
location: (j.locationRestrictions ?? []).join(", ") || "Remote",
|
|
803
889
|
compMin: j.salaryMin,
|
|
804
890
|
compMax: j.salaryMax,
|
|
805
|
-
tags:
|
|
891
|
+
tags: extractTags4(j),
|
|
806
892
|
roleType: mapJobType(j.jobType),
|
|
807
893
|
postedAt: j.pubDate ?? j.createdAt,
|
|
808
894
|
applyMode: "direct",
|
|
@@ -814,7 +900,7 @@ var init_himalayas = __esm({
|
|
|
814
900
|
});
|
|
815
901
|
|
|
816
902
|
// ../../packages/core/src/feeds/wwr.ts
|
|
817
|
-
function
|
|
903
|
+
function tokenize5(text) {
|
|
818
904
|
return text.toLowerCase().replace(/[^a-z0-9.\-+#]/g, " ").split(/\s+/).filter(Boolean);
|
|
819
905
|
}
|
|
820
906
|
function stripHtml(html) {
|
|
@@ -856,9 +942,9 @@ function parseRss(xml) {
|
|
|
856
942
|
}
|
|
857
943
|
return items;
|
|
858
944
|
}
|
|
859
|
-
function
|
|
945
|
+
function extractTags5(item) {
|
|
860
946
|
const text = [item.title, item.category, stripHtml(item.description)].join(" ");
|
|
861
|
-
return normalize(
|
|
947
|
+
return normalize(tokenize5(text));
|
|
862
948
|
}
|
|
863
949
|
var WWR_RSS_URL, wwr;
|
|
864
950
|
var init_wwr = __esm({
|
|
@@ -887,7 +973,7 @@ var init_wwr = __esm({
|
|
|
887
973
|
// WWR is a remote-only board
|
|
888
974
|
remote: true,
|
|
889
975
|
location: "Remote",
|
|
890
|
-
tags:
|
|
976
|
+
tags: extractTags5(item),
|
|
891
977
|
roleType: inferRoleType(item.category),
|
|
892
978
|
postedAt: item.pubDate ? new Date(item.pubDate).toISOString() : void 0,
|
|
893
979
|
applyMode: "direct",
|
|
@@ -899,7 +985,7 @@ var init_wwr = __esm({
|
|
|
899
985
|
});
|
|
900
986
|
|
|
901
987
|
// ../../packages/core/src/feeds/hn.ts
|
|
902
|
-
function
|
|
988
|
+
function tokenize6(text) {
|
|
903
989
|
return text.toLowerCase().replace(/[^a-z0-9.\-+#]/g, " ").split(/\s+/).filter(Boolean);
|
|
904
990
|
}
|
|
905
991
|
function stripHtml2(html) {
|
|
@@ -909,7 +995,7 @@ function extractUrl(text) {
|
|
|
909
995
|
const match2 = text.match(/https?:\/\/[^\s<>"']+/);
|
|
910
996
|
return match2?.[0] ?? "";
|
|
911
997
|
}
|
|
912
|
-
function
|
|
998
|
+
function inferRemote4(text) {
|
|
913
999
|
const lower = text.toLowerCase();
|
|
914
1000
|
return lower.includes("remote") || lower.includes("anywhere") || lower.includes("distributed");
|
|
915
1001
|
}
|
|
@@ -932,7 +1018,7 @@ function parseComment(item) {
|
|
|
932
1018
|
return null;
|
|
933
1019
|
}
|
|
934
1020
|
const url = extractUrl(raw) || `https://news.ycombinator.com/item?id=${item.id}`;
|
|
935
|
-
const tags =
|
|
1021
|
+
const tags = extractTags6(raw);
|
|
936
1022
|
if (tags.length === 0) return null;
|
|
937
1023
|
return {
|
|
938
1024
|
id: `hn:${item.id}`,
|
|
@@ -940,7 +1026,7 @@ function parseComment(item) {
|
|
|
940
1026
|
title: title.slice(0, 120),
|
|
941
1027
|
company: company.slice(0, 80),
|
|
942
1028
|
url,
|
|
943
|
-
remote:
|
|
1029
|
+
remote: inferRemote4(raw),
|
|
944
1030
|
location: location || void 0,
|
|
945
1031
|
tags,
|
|
946
1032
|
roleType: inferRoleType2(raw),
|
|
@@ -949,8 +1035,8 @@ function parseComment(item) {
|
|
|
949
1035
|
raw: item
|
|
950
1036
|
};
|
|
951
1037
|
}
|
|
952
|
-
function
|
|
953
|
-
return normalize(
|
|
1038
|
+
function extractTags6(text) {
|
|
1039
|
+
return normalize(tokenize6(text));
|
|
954
1040
|
}
|
|
955
1041
|
var ALGOLIA_SEARCH, ALGOLIA_ITEMS, hn;
|
|
956
1042
|
var init_hn = __esm({
|
|
@@ -994,20 +1080,25 @@ var init_hn = __esm({
|
|
|
994
1080
|
});
|
|
995
1081
|
|
|
996
1082
|
// ../../packages/core/src/feeds/index.ts
|
|
1083
|
+
function flattenTiers(t) {
|
|
1084
|
+
return [.../* @__PURE__ */ new Set([...t.bigco, ...t.scaleup, ...t.startup])];
|
|
1085
|
+
}
|
|
997
1086
|
async function aggregate(opts) {
|
|
998
1087
|
const ghSlugs = opts?.slugs?.["greenhouse"] ?? DEFAULT_GREENHOUSE_SLUGS;
|
|
999
1088
|
const ashbySlugs = opts?.slugs?.["ashby"] ?? DEFAULT_ASHBY_SLUGS;
|
|
1089
|
+
const leverSlugs = opts?.slugs?.["lever"] ?? DEFAULT_LEVER_SLUGS;
|
|
1000
1090
|
const limit = opts?.limit ?? 150;
|
|
1001
1091
|
const settled = await Promise.allSettled([
|
|
1002
1092
|
greenhouse.fetch({ slugs: ghSlugs, limit }),
|
|
1003
1093
|
ashby.fetch({ slugs: ashbySlugs, limit }),
|
|
1094
|
+
lever.fetch({ slugs: leverSlugs, limit }),
|
|
1004
1095
|
himalayas.fetch({ limit }),
|
|
1005
1096
|
wwr.fetch({ limit }),
|
|
1006
1097
|
hn.fetch({ limit })
|
|
1007
1098
|
]);
|
|
1008
1099
|
const seen = /* @__PURE__ */ new Set();
|
|
1009
1100
|
const jobs = [];
|
|
1010
|
-
const sourceNames = ["greenhouse", "ashby", "himalayas", "wwr", "hn"];
|
|
1101
|
+
const sourceNames = ["greenhouse", "ashby", "lever", "himalayas", "wwr", "hn"];
|
|
1011
1102
|
for (let i = 0; i < settled.length; i++) {
|
|
1012
1103
|
const result = settled[i];
|
|
1013
1104
|
if (result.status === "rejected") {
|
|
@@ -1023,46 +1114,122 @@ async function aggregate(opts) {
|
|
|
1023
1114
|
}
|
|
1024
1115
|
return jobs;
|
|
1025
1116
|
}
|
|
1026
|
-
var FEEDS, DEFAULT_GREENHOUSE_SLUGS, DEFAULT_ASHBY_SLUGS;
|
|
1117
|
+
var FEEDS, GREENHOUSE_SLUGS_BY_TIER, ASHBY_SLUGS_BY_TIER, LEVER_SLUGS_BY_TIER, DEFAULT_GREENHOUSE_SLUGS, DEFAULT_ASHBY_SLUGS, DEFAULT_LEVER_SLUGS;
|
|
1027
1118
|
var init_feeds = __esm({
|
|
1028
1119
|
"../../packages/core/src/feeds/index.ts"() {
|
|
1029
1120
|
"use strict";
|
|
1030
1121
|
init_greenhouse();
|
|
1031
1122
|
init_ashby();
|
|
1123
|
+
init_lever();
|
|
1032
1124
|
init_himalayas();
|
|
1033
1125
|
init_wwr();
|
|
1034
1126
|
init_hn();
|
|
1035
|
-
FEEDS = [greenhouse, ashby, himalayas, wwr, hn];
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1127
|
+
FEEDS = [greenhouse, ashby, lever, himalayas, wwr, hn];
|
|
1128
|
+
GREENHOUSE_SLUGS_BY_TIER = {
|
|
1129
|
+
bigco: [
|
|
1130
|
+
"stripe",
|
|
1131
|
+
"anthropic",
|
|
1132
|
+
"figma",
|
|
1133
|
+
"discord",
|
|
1134
|
+
"brex",
|
|
1135
|
+
"mercury",
|
|
1136
|
+
"plaid",
|
|
1137
|
+
"gusto",
|
|
1138
|
+
"scale",
|
|
1139
|
+
"databricks",
|
|
1140
|
+
"coinbase",
|
|
1141
|
+
"robinhood",
|
|
1142
|
+
"doordash",
|
|
1143
|
+
"airbnb",
|
|
1144
|
+
"dropbox",
|
|
1145
|
+
"datadog",
|
|
1146
|
+
"cloudflare",
|
|
1147
|
+
"reddit",
|
|
1148
|
+
"lyft",
|
|
1149
|
+
"instacart"
|
|
1150
|
+
],
|
|
1151
|
+
scaleup: [
|
|
1152
|
+
"samsara",
|
|
1153
|
+
"verkada",
|
|
1154
|
+
"affirm",
|
|
1155
|
+
"gitlab",
|
|
1156
|
+
"asana",
|
|
1157
|
+
"flexport",
|
|
1158
|
+
"faire",
|
|
1159
|
+
"twitch",
|
|
1160
|
+
"airtable",
|
|
1161
|
+
"retool"
|
|
1162
|
+
],
|
|
1163
|
+
startup: [
|
|
1164
|
+
"watershed"
|
|
1165
|
+
]
|
|
1166
|
+
};
|
|
1167
|
+
ASHBY_SLUGS_BY_TIER = {
|
|
1168
|
+
bigco: [
|
|
1169
|
+
"openai"
|
|
1170
|
+
],
|
|
1171
|
+
scaleup: [
|
|
1172
|
+
"harvey",
|
|
1173
|
+
"elevenlabs",
|
|
1174
|
+
"notion",
|
|
1175
|
+
"sierra",
|
|
1176
|
+
"cohere",
|
|
1177
|
+
"ramp",
|
|
1178
|
+
"vanta",
|
|
1179
|
+
"decagon",
|
|
1180
|
+
"cursor",
|
|
1181
|
+
"replit",
|
|
1182
|
+
"perplexity",
|
|
1183
|
+
"baseten",
|
|
1184
|
+
"drata",
|
|
1185
|
+
"writer",
|
|
1186
|
+
"temporal",
|
|
1187
|
+
"supabase"
|
|
1188
|
+
],
|
|
1189
|
+
startup: [
|
|
1190
|
+
"suno",
|
|
1191
|
+
"attio",
|
|
1192
|
+
"modal",
|
|
1193
|
+
"workos",
|
|
1194
|
+
"linear",
|
|
1195
|
+
"render",
|
|
1196
|
+
"warp",
|
|
1197
|
+
"plain",
|
|
1198
|
+
"posthog",
|
|
1199
|
+
"pylon",
|
|
1200
|
+
"resend",
|
|
1201
|
+
"langfuse",
|
|
1202
|
+
"railway",
|
|
1203
|
+
"mintlify",
|
|
1204
|
+
"neon",
|
|
1205
|
+
"browserbase",
|
|
1206
|
+
"knock",
|
|
1207
|
+
"speakeasy",
|
|
1208
|
+
"stytch",
|
|
1209
|
+
"runway",
|
|
1210
|
+
"doppler",
|
|
1211
|
+
"inngest",
|
|
1212
|
+
"hightouch",
|
|
1213
|
+
"zed"
|
|
1214
|
+
]
|
|
1215
|
+
};
|
|
1216
|
+
LEVER_SLUGS_BY_TIER = {
|
|
1217
|
+
bigco: [
|
|
1218
|
+
"palantir",
|
|
1219
|
+
"spotify"
|
|
1220
|
+
],
|
|
1221
|
+
scaleup: [
|
|
1222
|
+
"mistral",
|
|
1223
|
+
"ro",
|
|
1224
|
+
"secureframe"
|
|
1225
|
+
],
|
|
1226
|
+
startup: [
|
|
1227
|
+
"anyscale"
|
|
1228
|
+
]
|
|
1229
|
+
};
|
|
1230
|
+
DEFAULT_GREENHOUSE_SLUGS = flattenTiers(GREENHOUSE_SLUGS_BY_TIER);
|
|
1231
|
+
DEFAULT_ASHBY_SLUGS = flattenTiers(ASHBY_SLUGS_BY_TIER);
|
|
1232
|
+
DEFAULT_LEVER_SLUGS = flattenTiers(LEVER_SLUGS_BY_TIER);
|
|
1066
1233
|
}
|
|
1067
1234
|
});
|
|
1068
1235
|
|
|
@@ -1249,10 +1416,14 @@ var init_github = __esm({
|
|
|
1249
1416
|
// ../../packages/core/src/index.ts
|
|
1250
1417
|
var src_exports = {};
|
|
1251
1418
|
__export(src_exports, {
|
|
1419
|
+
ASHBY_SLUGS_BY_TIER: () => ASHBY_SLUGS_BY_TIER,
|
|
1252
1420
|
COASTAL_BUYER: () => COASTAL_BUYER,
|
|
1253
1421
|
DEFAULT_ASHBY_SLUGS: () => DEFAULT_ASHBY_SLUGS,
|
|
1254
1422
|
DEFAULT_GREENHOUSE_SLUGS: () => DEFAULT_GREENHOUSE_SLUGS,
|
|
1423
|
+
DEFAULT_LEVER_SLUGS: () => DEFAULT_LEVER_SLUGS,
|
|
1255
1424
|
FEEDS: () => FEEDS,
|
|
1425
|
+
GREENHOUSE_SLUGS_BY_TIER: () => GREENHOUSE_SLUGS_BY_TIER,
|
|
1426
|
+
LEVER_SLUGS_BY_TIER: () => LEVER_SLUGS_BY_TIER,
|
|
1256
1427
|
SYNONYMS: () => SYNONYMS,
|
|
1257
1428
|
VOCABULARY: () => VOCABULARY,
|
|
1258
1429
|
aggregate: () => aggregate,
|
|
@@ -1260,10 +1431,12 @@ __export(src_exports, {
|
|
|
1260
1431
|
buildIndex: () => buildIndex,
|
|
1261
1432
|
buildReason: () => buildReason,
|
|
1262
1433
|
fetchGitHubProfile: () => fetchGitHubProfile,
|
|
1434
|
+
flattenTiers: () => flattenTiers,
|
|
1263
1435
|
githubToFingerprint: () => githubToFingerprint,
|
|
1264
1436
|
greenhouse: () => greenhouse,
|
|
1265
1437
|
himalayas: () => himalayas,
|
|
1266
1438
|
hn: () => hn,
|
|
1439
|
+
lever: () => lever,
|
|
1267
1440
|
loadCoastalRoles: () => loadCoastalRoles,
|
|
1268
1441
|
match: () => match,
|
|
1269
1442
|
matchOne: () => matchOne,
|
|
@@ -2321,6 +2494,7 @@ __export(spinner_exports, {
|
|
|
2321
2494
|
clearSpinnerVerbs: () => clearSpinnerVerbs,
|
|
2322
2495
|
ctaVerb: () => ctaVerb,
|
|
2323
2496
|
formatVerbs: () => formatVerbs,
|
|
2497
|
+
interleaveBySource: () => interleaveBySource,
|
|
2324
2498
|
rankBySessionTags: () => rankBySessionTags,
|
|
2325
2499
|
readSpinnerConfig: () => readSpinnerConfig
|
|
2326
2500
|
});
|
|
@@ -2474,13 +2648,40 @@ function clearSpinnerVerbs() {
|
|
|
2474
2648
|
}
|
|
2475
2649
|
return { cleared: true, keptUserVerbs };
|
|
2476
2650
|
}
|
|
2651
|
+
function interleaveBySource(topMatches) {
|
|
2652
|
+
if (!Array.isArray(topMatches) || topMatches.length === 0) return topMatches;
|
|
2653
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
2654
|
+
const order = [];
|
|
2655
|
+
for (const m of topMatches) {
|
|
2656
|
+
const id = m && m.id ? String(m.id) : "";
|
|
2657
|
+
const idx = id.indexOf(":");
|
|
2658
|
+
const source = idx > 0 ? id.slice(0, idx) : "_";
|
|
2659
|
+
if (!buckets.has(source)) {
|
|
2660
|
+
buckets.set(source, []);
|
|
2661
|
+
order.push(source);
|
|
2662
|
+
}
|
|
2663
|
+
buckets.get(source).push(m);
|
|
2664
|
+
}
|
|
2665
|
+
const out = [];
|
|
2666
|
+
let remaining = topMatches.length;
|
|
2667
|
+
while (remaining > 0) {
|
|
2668
|
+
for (const source of order) {
|
|
2669
|
+
const b = buckets.get(source);
|
|
2670
|
+
if (b && b.length) {
|
|
2671
|
+
out.push(b.shift());
|
|
2672
|
+
remaining--;
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
}
|
|
2676
|
+
return out;
|
|
2677
|
+
}
|
|
2477
2678
|
function buildTips(topMatches, baseUrl, max = 8) {
|
|
2478
2679
|
const base = String(baseUrl || "https://terminalhire.com").replace(/\/+$/, "");
|
|
2479
2680
|
const out = [];
|
|
2480
2681
|
const seenRole = /* @__PURE__ */ new Set();
|
|
2481
2682
|
const perCompany = /* @__PURE__ */ new Map();
|
|
2482
2683
|
const COMPANY_CAP = 2;
|
|
2483
|
-
for (const m of Array.isArray(topMatches) ? topMatches : []) {
|
|
2684
|
+
for (const m of interleaveBySource(Array.isArray(topMatches) ? topMatches : [])) {
|
|
2484
2685
|
if (!m || !m.title || !m.company || !m.id) continue;
|
|
2485
2686
|
const idx = String(m.id).indexOf(":");
|
|
2486
2687
|
if (idx <= 0) continue;
|
|
@@ -2758,8 +2959,9 @@ __export(jpi_sync_exports, {
|
|
|
2758
2959
|
});
|
|
2759
2960
|
import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, existsSync as existsSync7, rmSync as rmSync2 } from "fs";
|
|
2760
2961
|
import { join as join9 } from "path";
|
|
2761
|
-
import { homedir as homedir7 } from "os";
|
|
2962
|
+
import { homedir as homedir7, hostname as osHostname } from "os";
|
|
2762
2963
|
import { createInterface as createInterface4 } from "readline";
|
|
2964
|
+
import { spawn } from "child_process";
|
|
2763
2965
|
function ask2(question) {
|
|
2764
2966
|
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
2765
2967
|
return new Promise((res) => {
|
|
@@ -2802,14 +3004,14 @@ function buildConsentFields(profile) {
|
|
|
2802
3004
|
}
|
|
2803
3005
|
return fields;
|
|
2804
3006
|
}
|
|
2805
|
-
function
|
|
3007
|
+
function renderPreview(fields) {
|
|
2806
3008
|
console.log("");
|
|
2807
3009
|
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
3010
|
console.log("\u2502 terminalhire \u2014 sync your profile (Tier-1, opt-in) \u2502");
|
|
2809
3011
|
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
3012
|
console.log("");
|
|
2811
|
-
console.log("
|
|
2812
|
-
console.log("
|
|
3013
|
+
console.log(" The following data will be shared with staqs (terminalhire.com)");
|
|
3014
|
+
console.log(" AFTER you authorize + consent in the browser:");
|
|
2813
3015
|
console.log("");
|
|
2814
3016
|
for (const f of fields) {
|
|
2815
3017
|
const shown = Array.isArray(f.value) ? JSON.stringify(f.value) : String(f.value ?? "(not set)");
|
|
@@ -2828,6 +3030,30 @@ function renderConsentCard(fields) {
|
|
|
2828
3030
|
console.log(" This is NOT required to use terminalhire.");
|
|
2829
3031
|
console.log("");
|
|
2830
3032
|
}
|
|
3033
|
+
function openInBrowser(url) {
|
|
3034
|
+
let cmd;
|
|
3035
|
+
let args2;
|
|
3036
|
+
if (process.platform === "darwin") {
|
|
3037
|
+
cmd = "open";
|
|
3038
|
+
args2 = [url];
|
|
3039
|
+
} else if (process.platform === "win32") {
|
|
3040
|
+
cmd = "cmd";
|
|
3041
|
+
args2 = ["/c", "start", "", url];
|
|
3042
|
+
} else {
|
|
3043
|
+
cmd = "xdg-open";
|
|
3044
|
+
args2 = [url];
|
|
3045
|
+
}
|
|
3046
|
+
try {
|
|
3047
|
+
const child = spawn(cmd, args2, { stdio: "ignore", detached: true });
|
|
3048
|
+
child.on("error", () => {
|
|
3049
|
+
});
|
|
3050
|
+
child.unref();
|
|
3051
|
+
} catch {
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
function sleep2(ms) {
|
|
3055
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
3056
|
+
}
|
|
2831
3057
|
async function runPush() {
|
|
2832
3058
|
const { readProfile: readProfile2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
|
|
2833
3059
|
const profile = await readProfile2();
|
|
@@ -2839,15 +3065,91 @@ async function runPush() {
|
|
|
2839
3065
|
process.exit(1);
|
|
2840
3066
|
}
|
|
2841
3067
|
const fields = buildConsentFields(profile);
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
3068
|
+
renderPreview(fields);
|
|
3069
|
+
await new Promise((resolve2) => {
|
|
3070
|
+
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
3071
|
+
rl.question(
|
|
3072
|
+
" Press Enter to open your browser to authorize + consent (or Ctrl-C to cancel)... ",
|
|
3073
|
+
() => {
|
|
3074
|
+
rl.close();
|
|
3075
|
+
resolve2();
|
|
3076
|
+
}
|
|
3077
|
+
);
|
|
3078
|
+
});
|
|
3079
|
+
console.log("");
|
|
3080
|
+
console.log(" Starting browser verification...");
|
|
3081
|
+
let begin;
|
|
3082
|
+
try {
|
|
3083
|
+
const r = await fetch(`${SYNC_BASE}/api/profile-sync/begin`, {
|
|
3084
|
+
method: "POST",
|
|
3085
|
+
headers: { "Content-Type": "application/json" },
|
|
3086
|
+
body: JSON.stringify({ hostname: osHostname() }),
|
|
3087
|
+
signal: AbortSignal.timeout(1e4)
|
|
3088
|
+
});
|
|
3089
|
+
if (!r.ok) {
|
|
3090
|
+
let detail = "";
|
|
3091
|
+
try {
|
|
3092
|
+
detail = (await r.json())?.message || "";
|
|
3093
|
+
} catch {
|
|
3094
|
+
}
|
|
3095
|
+
console.error(`
|
|
3096
|
+
Could not start sync: /api/profile-sync/begin returned ${r.status}. ${detail}`);
|
|
3097
|
+
if (r.status === 503) console.error(" (Tier-1 sync is not enabled on the server yet.)");
|
|
3098
|
+
process.exit(1);
|
|
3099
|
+
}
|
|
3100
|
+
begin = await r.json();
|
|
3101
|
+
} catch (err) {
|
|
3102
|
+
console.error(`
|
|
3103
|
+
Could not start sync: ${err instanceof Error ? err.message : String(err)}`);
|
|
3104
|
+
process.exit(1);
|
|
3105
|
+
}
|
|
3106
|
+
const { challenge, verifyUrl } = begin || {};
|
|
3107
|
+
if (!challenge || !verifyUrl) {
|
|
3108
|
+
console.error("\n Could not start sync: malformed begin response.");
|
|
3109
|
+
process.exit(1);
|
|
3110
|
+
}
|
|
3111
|
+
console.log("");
|
|
3112
|
+
console.log(" Open this URL in your browser to authorize + consent:");
|
|
3113
|
+
console.log(` ${verifyUrl}`);
|
|
3114
|
+
console.log("");
|
|
3115
|
+
console.log(" (Attempting to open it automatically...)");
|
|
3116
|
+
openInBrowser(verifyUrl);
|
|
3117
|
+
console.log(" Waiting for browser verification...");
|
|
3118
|
+
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
3119
|
+
let proofToken = null;
|
|
3120
|
+
while (Date.now() < deadline) {
|
|
3121
|
+
await sleep2(POLL_INTERVAL_MS);
|
|
3122
|
+
let statusRes;
|
|
3123
|
+
try {
|
|
3124
|
+
statusRes = await fetch(
|
|
3125
|
+
`${SYNC_BASE}/api/profile-sync/status?challenge=${encodeURIComponent(challenge)}`,
|
|
3126
|
+
{ signal: AbortSignal.timeout(1e4) }
|
|
3127
|
+
);
|
|
3128
|
+
} catch {
|
|
3129
|
+
continue;
|
|
3130
|
+
}
|
|
3131
|
+
if (!statusRes.ok) {
|
|
3132
|
+
if (statusRes.status === 503) {
|
|
3133
|
+
console.error("\n Tier-1 sync is not enabled on the server yet.\n");
|
|
3134
|
+
process.exit(1);
|
|
3135
|
+
}
|
|
3136
|
+
continue;
|
|
3137
|
+
}
|
|
3138
|
+
let body;
|
|
3139
|
+
try {
|
|
3140
|
+
body = await statusRes.json();
|
|
3141
|
+
} catch {
|
|
3142
|
+
continue;
|
|
3143
|
+
}
|
|
3144
|
+
if (body && body.status === "verified" && body.proofToken) {
|
|
3145
|
+
proofToken = body.proofToken;
|
|
3146
|
+
break;
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
if (!proofToken) {
|
|
3150
|
+
console.error("\n Timed out waiting for browser verification (10 min).");
|
|
3151
|
+
console.error(" Re-run `terminalhire sync --push` to try again.\n");
|
|
3152
|
+
process.exit(1);
|
|
2851
3153
|
}
|
|
2852
3154
|
const consentedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2853
3155
|
const consentToken = {
|
|
@@ -2866,14 +3168,14 @@ async function runPush() {
|
|
|
2866
3168
|
};
|
|
2867
3169
|
const priorMarker = readMarker();
|
|
2868
3170
|
const rowToken = priorMarker && priorMarker.deleteToken ? priorMarker.deleteToken : null;
|
|
2869
|
-
const requestBody = { consentToken, profile: payloadProfile };
|
|
3171
|
+
const requestBody = { consentToken, profile: payloadProfile, proofToken };
|
|
2870
3172
|
if (rowToken) {
|
|
2871
3173
|
requestBody.rowToken = rowToken;
|
|
2872
3174
|
}
|
|
2873
|
-
console.log("\n Sending one-time snapshot...");
|
|
3175
|
+
console.log("\n Verified. Sending one-time snapshot...");
|
|
2874
3176
|
let res;
|
|
2875
3177
|
try {
|
|
2876
|
-
res = await fetch(`${
|
|
3178
|
+
res = await fetch(`${SYNC_BASE}/api/profile-sync`, {
|
|
2877
3179
|
method: "POST",
|
|
2878
3180
|
headers: { "Content-Type": "application/json" },
|
|
2879
3181
|
body: JSON.stringify(requestBody),
|
|
@@ -2896,7 +3198,7 @@ async function runPush() {
|
|
|
2896
3198
|
console.error(" (Tier-1 sync is not enabled on the server yet.)");
|
|
2897
3199
|
}
|
|
2898
3200
|
if (res.status === 403) {
|
|
2899
|
-
console.error(" (
|
|
3201
|
+
console.error(" (Ownership proof rejected, expired, or already used \u2014 re-run sync --push.)");
|
|
2900
3202
|
}
|
|
2901
3203
|
process.exit(1);
|
|
2902
3204
|
}
|
|
@@ -3013,13 +3315,16 @@ async function run7() {
|
|
|
3013
3315
|
console.log(" This is NOT required to use terminalhire.");
|
|
3014
3316
|
console.log("");
|
|
3015
3317
|
}
|
|
3016
|
-
var TH_DIR3, TIER1_MARKER, API_URL2, CONSENT_VERSION;
|
|
3318
|
+
var TH_DIR3, TIER1_MARKER, API_URL2, SYNC_BASE, POLL_INTERVAL_MS, POLL_TIMEOUT_MS, CONSENT_VERSION;
|
|
3017
3319
|
var init_jpi_sync = __esm({
|
|
3018
3320
|
"bin/jpi-sync.js"() {
|
|
3019
3321
|
"use strict";
|
|
3020
3322
|
TH_DIR3 = process.env["TERMINALHIRE_DIR"] || join9(homedir7(), ".terminalhire");
|
|
3021
3323
|
TIER1_MARKER = join9(TH_DIR3, "tier1.json");
|
|
3022
3324
|
API_URL2 = process.env["TERMINALHIRE_API_URL"] || process.env["JPI_API_URL"] || "https://terminalhire.com";
|
|
3325
|
+
SYNC_BASE = "https://www.terminalhire.com";
|
|
3326
|
+
POLL_INTERVAL_MS = 2e3;
|
|
3327
|
+
POLL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
3023
3328
|
CONSENT_VERSION = 1;
|
|
3024
3329
|
}
|
|
3025
3330
|
});
|
|
@@ -3033,7 +3338,7 @@ import { existsSync as existsSync8 } from "fs";
|
|
|
3033
3338
|
import { join as join10, resolve } from "path";
|
|
3034
3339
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3035
3340
|
import { createInterface as createInterface5 } from "readline";
|
|
3036
|
-
import { spawnSync, spawn } from "child_process";
|
|
3341
|
+
import { spawnSync, spawn as spawn2 } from "child_process";
|
|
3037
3342
|
import { homedir as homedir8 } from "os";
|
|
3038
3343
|
function ask3(question) {
|
|
3039
3344
|
const rl = createInterface5({ input: process.stdin, output: process.stdout });
|