terminalhire 0.6.0 → 0.7.1
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-bounties.js +237 -0
- package/dist/bin/jpi-devs.js +3309 -0
- package/dist/bin/jpi-dispatch.js +1174 -241
- package/dist/bin/jpi-intro.js +1400 -0
- package/dist/bin/jpi-jobs.js +237 -0
- package/dist/bin/jpi-learn.js +27 -0
- package/dist/bin/jpi-login.js +237 -0
- package/dist/bin/jpi-profile.js +27 -0
- package/dist/bin/jpi-project.js +2995 -0
- package/dist/bin/jpi-refresh.js +237 -0
- package/dist/bin/jpi-save.js +27 -0
- package/dist/bin/jpi-sync.js +27 -0
- package/dist/bin/jpi-trajectory.js +27 -0
- package/dist/src/intro.js +1348 -0
- package/dist/src/profile.js +12 -0
- package/dist/src/signal.js +12 -0
- package/dist/src/trajectory.js +27 -0
- package/package.json +1 -1
package/dist/bin/jpi-bounties.js
CHANGED
|
@@ -2268,6 +2268,51 @@ var init_workable = __esm({
|
|
|
2268
2268
|
}
|
|
2269
2269
|
});
|
|
2270
2270
|
|
|
2271
|
+
// ../../packages/core/src/feeds/directory.ts
|
|
2272
|
+
function personCardToJob(row) {
|
|
2273
|
+
const tags = [...row.skill_tags];
|
|
2274
|
+
return {
|
|
2275
|
+
id: `dev:${row.login}`,
|
|
2276
|
+
source: "person",
|
|
2277
|
+
title: row.name ?? row.login,
|
|
2278
|
+
company: row.login,
|
|
2279
|
+
url: `/r/${row.login}`,
|
|
2280
|
+
remote: true,
|
|
2281
|
+
tags,
|
|
2282
|
+
coreTags: tags.slice(0, TOP_CORE_TAGS),
|
|
2283
|
+
roleType: "full_time",
|
|
2284
|
+
applyMode: "direct"
|
|
2285
|
+
};
|
|
2286
|
+
}
|
|
2287
|
+
function buildDirectoryIndex(people, opts) {
|
|
2288
|
+
return {
|
|
2289
|
+
builtAt: opts?.builtAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2290
|
+
cards: people.map(personCardToJob)
|
|
2291
|
+
};
|
|
2292
|
+
}
|
|
2293
|
+
function projectCardToJob(row) {
|
|
2294
|
+
const tags = [...row.needed_skills];
|
|
2295
|
+
return {
|
|
2296
|
+
id: `proj:${row.id}`,
|
|
2297
|
+
source: "project",
|
|
2298
|
+
title: row.title,
|
|
2299
|
+
company: row.owner_login,
|
|
2300
|
+
url: `/r/${row.owner_login}`,
|
|
2301
|
+
remote: true,
|
|
2302
|
+
tags,
|
|
2303
|
+
coreTags: tags.slice(0, TOP_CORE_TAGS),
|
|
2304
|
+
roleType: "full_time",
|
|
2305
|
+
applyMode: "direct"
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
2308
|
+
var TOP_CORE_TAGS;
|
|
2309
|
+
var init_directory = __esm({
|
|
2310
|
+
"../../packages/core/src/feeds/directory.ts"() {
|
|
2311
|
+
"use strict";
|
|
2312
|
+
TOP_CORE_TAGS = 4;
|
|
2313
|
+
}
|
|
2314
|
+
});
|
|
2315
|
+
|
|
2271
2316
|
// ../../packages/core/src/feeds/index.ts
|
|
2272
2317
|
async function aggregateBounties(opts) {
|
|
2273
2318
|
const [gh, op] = await Promise.all([
|
|
@@ -2384,6 +2429,7 @@ var init_feeds = __esm({
|
|
|
2384
2429
|
init_github_bounties();
|
|
2385
2430
|
init_opire();
|
|
2386
2431
|
init_workable();
|
|
2432
|
+
init_directory();
|
|
2387
2433
|
init_bounty_gate();
|
|
2388
2434
|
init_bounty_gate();
|
|
2389
2435
|
FEEDS = [greenhouse, ashby, lever, workable, himalayas, wwr, hn];
|
|
@@ -2584,6 +2630,176 @@ var init_indexer = __esm({
|
|
|
2584
2630
|
}
|
|
2585
2631
|
});
|
|
2586
2632
|
|
|
2633
|
+
// ../../packages/core/src/intro.ts
|
|
2634
|
+
function buildIntroPayload(input) {
|
|
2635
|
+
const payload = {
|
|
2636
|
+
requesterLogin: input.requesterLogin,
|
|
2637
|
+
requesterDisplayName: input.requesterDisplayName,
|
|
2638
|
+
requesterContact: input.requesterContact,
|
|
2639
|
+
targetLogin: input.targetLogin
|
|
2640
|
+
};
|
|
2641
|
+
const note = input.note?.trim();
|
|
2642
|
+
if (note) payload.note = note;
|
|
2643
|
+
return payload;
|
|
2644
|
+
}
|
|
2645
|
+
function rejectExtraIntroFields(body) {
|
|
2646
|
+
for (const key of Object.keys(body)) {
|
|
2647
|
+
if (!INTRO_ALLOWED_SET.has(key)) {
|
|
2648
|
+
return `intro payload contains disallowed field: "${key}"`;
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
return null;
|
|
2652
|
+
}
|
|
2653
|
+
function validateIntroPayload(body) {
|
|
2654
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
2655
|
+
return { ok: false, reason: "intro payload must be a JSON object" };
|
|
2656
|
+
}
|
|
2657
|
+
const b = body;
|
|
2658
|
+
const extra = rejectExtraIntroFields(b);
|
|
2659
|
+
if (extra) return { ok: false, reason: extra };
|
|
2660
|
+
const ok = (v, max) => typeof v === "string" && v.trim().length > 0 && v.length <= max;
|
|
2661
|
+
if (!ok(b.requesterLogin, MAX_SHORT)) return { ok: false, reason: "requesterLogin is required" };
|
|
2662
|
+
if (!ok(b.requesterDisplayName, MAX_SHORT)) return { ok: false, reason: "requesterDisplayName is required" };
|
|
2663
|
+
if (!ok(b.requesterContact, MAX_SHORT)) return { ok: false, reason: "requesterContact is required" };
|
|
2664
|
+
if (!ok(b.targetLogin, MAX_SHORT)) return { ok: false, reason: "targetLogin is required" };
|
|
2665
|
+
if (b.note !== void 0 && (typeof b.note !== "string" || b.note.length > MAX_NOTE)) {
|
|
2666
|
+
return { ok: false, reason: "note must be a string of at most 500 chars" };
|
|
2667
|
+
}
|
|
2668
|
+
const value = {
|
|
2669
|
+
requesterLogin: b.requesterLogin.trim(),
|
|
2670
|
+
requesterDisplayName: b.requesterDisplayName.trim(),
|
|
2671
|
+
requesterContact: b.requesterContact.trim(),
|
|
2672
|
+
targetLogin: b.targetLogin.trim()
|
|
2673
|
+
};
|
|
2674
|
+
const note = typeof b.note === "string" ? b.note.trim() : "";
|
|
2675
|
+
if (note) value.note = note;
|
|
2676
|
+
return { ok: true, value };
|
|
2677
|
+
}
|
|
2678
|
+
function introRateLimitCheck(history, now, opts) {
|
|
2679
|
+
const cutoff = now - opts.windowMs;
|
|
2680
|
+
const recent = history.filter((t) => t > cutoff);
|
|
2681
|
+
if (recent.length >= opts.max) {
|
|
2682
|
+
const oldest = recent[0] ?? now;
|
|
2683
|
+
return { allowed: false, retained: recent, retryAfterMs: Math.max(0, oldest + opts.windowMs - now) };
|
|
2684
|
+
}
|
|
2685
|
+
return { allowed: true, retained: [...recent, now], retryAfterMs: 0 };
|
|
2686
|
+
}
|
|
2687
|
+
function isOverIntroLimit(recentCount, max) {
|
|
2688
|
+
return recentCount >= max;
|
|
2689
|
+
}
|
|
2690
|
+
function composeIntroEmail(args2) {
|
|
2691
|
+
const subject = `New intro request from @${args2.requesterLogin} \xB7 terminalhire`;
|
|
2692
|
+
const text = `@${args2.requesterLogin} wants an intro to you on terminalhire.
|
|
2693
|
+
|
|
2694
|
+
Sign in to view the request and choose whether to share your contact back:
|
|
2695
|
+
${args2.dashboardUrl}
|
|
2696
|
+
|
|
2697
|
+
You control whether this connects \u2014 no contact details are shared unless you accept.
|
|
2698
|
+
|
|
2699
|
+
\u2014 Terminalhire`;
|
|
2700
|
+
return { subject, text };
|
|
2701
|
+
}
|
|
2702
|
+
function introActorRole(intro, actorLogin) {
|
|
2703
|
+
const a = actorLogin.trim().toLowerCase();
|
|
2704
|
+
if (a && a === intro.targetLogin.trim().toLowerCase()) return "target";
|
|
2705
|
+
if (a && a === intro.requesterLogin.trim().toLowerCase()) return "requester";
|
|
2706
|
+
return "other";
|
|
2707
|
+
}
|
|
2708
|
+
function authorizeIntroDecision(intro, actorLogin) {
|
|
2709
|
+
const role = introActorRole(intro, actorLogin);
|
|
2710
|
+
if (role === "target") return { ok: true };
|
|
2711
|
+
if (role === "requester") {
|
|
2712
|
+
return { ok: false, status: 403, reason: "the requester cannot accept or decline their own intro request" };
|
|
2713
|
+
}
|
|
2714
|
+
return { ok: false, status: 404, reason: "intro not found" };
|
|
2715
|
+
}
|
|
2716
|
+
function authorizeIntroDeletion(intro, actorLogin) {
|
|
2717
|
+
const role = introActorRole(intro, actorLogin);
|
|
2718
|
+
if (role === "other") return { ok: false, status: 404, reason: "intro not found" };
|
|
2719
|
+
return { ok: true };
|
|
2720
|
+
}
|
|
2721
|
+
function revealIntroContacts(intro) {
|
|
2722
|
+
if (intro.status !== "accepted") return { toRequester: null, toTarget: null };
|
|
2723
|
+
return { toRequester: intro.targetContact ?? null, toTarget: intro.requesterContact };
|
|
2724
|
+
}
|
|
2725
|
+
function validateTargetContact(v) {
|
|
2726
|
+
if (typeof v !== "string" || v.trim().length === 0) return { ok: false, reason: "targetContact is required" };
|
|
2727
|
+
if (v.length > MAX_SHORT) return { ok: false, reason: `targetContact must be at most ${MAX_SHORT} chars` };
|
|
2728
|
+
return { ok: true, value: v.trim() };
|
|
2729
|
+
}
|
|
2730
|
+
function buildIntroListItem(intro, viewerLogin) {
|
|
2731
|
+
const role = introActorRole(intro, viewerLogin);
|
|
2732
|
+
if (role === "other") return null;
|
|
2733
|
+
const reveal = revealIntroContacts(intro);
|
|
2734
|
+
if (role === "target") {
|
|
2735
|
+
return {
|
|
2736
|
+
id: intro.id,
|
|
2737
|
+
role: "incoming",
|
|
2738
|
+
counterpartyLogin: intro.requesterLogin,
|
|
2739
|
+
status: intro.status,
|
|
2740
|
+
note: intro.note ?? null,
|
|
2741
|
+
contact: reveal.toTarget
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
return {
|
|
2745
|
+
id: intro.id,
|
|
2746
|
+
role: "outgoing",
|
|
2747
|
+
counterpartyLogin: intro.targetLogin,
|
|
2748
|
+
status: intro.status,
|
|
2749
|
+
note: intro.note ?? null,
|
|
2750
|
+
contact: reveal.toRequester
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
function composeIntroAcceptedEmail(args2) {
|
|
2754
|
+
const subject = `Intro connected with @${args2.counterpartyLogin} \xB7 terminalhire`;
|
|
2755
|
+
const lead = args2.recipientRole === "requester" ? `@${args2.counterpartyLogin} accepted your intro request on terminalhire.` : `You accepted @${args2.counterpartyLogin}'s intro request on terminalhire.`;
|
|
2756
|
+
const text = `${lead}
|
|
2757
|
+
|
|
2758
|
+
You can now reach them directly:
|
|
2759
|
+
@${args2.counterpartyLogin} \u2014 ${args2.counterpartyContact}
|
|
2760
|
+
|
|
2761
|
+
Take it from here.
|
|
2762
|
+
|
|
2763
|
+
\u2014 Terminalhire`;
|
|
2764
|
+
return { subject, text };
|
|
2765
|
+
}
|
|
2766
|
+
function introRetentionAction(row, now) {
|
|
2767
|
+
if (row.status === "pending") {
|
|
2768
|
+
const created = Date.parse(row.createdAt);
|
|
2769
|
+
if (Number.isFinite(created) && now - created > INTRO_PENDING_TTL_MS) return "purge";
|
|
2770
|
+
return "keep";
|
|
2771
|
+
}
|
|
2772
|
+
if (row.status === "declined") {
|
|
2773
|
+
return row.hasContact ? "scrub-declined" : "keep";
|
|
2774
|
+
}
|
|
2775
|
+
if (row.status === "accepted") {
|
|
2776
|
+
const updated = Date.parse(row.updatedAt);
|
|
2777
|
+
if (row.hasContact && Number.isFinite(updated) && now - updated > INTRO_ACCEPTED_TTL_MS) {
|
|
2778
|
+
return "expire-accepted";
|
|
2779
|
+
}
|
|
2780
|
+
return "keep";
|
|
2781
|
+
}
|
|
2782
|
+
return "keep";
|
|
2783
|
+
}
|
|
2784
|
+
var INTRO_ALLOWED_FIELDS, INTRO_ALLOWED_SET, MAX_SHORT, MAX_NOTE, INTRO_PENDING_TTL_MS, INTRO_ACCEPTED_TTL_MS;
|
|
2785
|
+
var init_intro = __esm({
|
|
2786
|
+
"../../packages/core/src/intro.ts"() {
|
|
2787
|
+
"use strict";
|
|
2788
|
+
INTRO_ALLOWED_FIELDS = [
|
|
2789
|
+
"requesterLogin",
|
|
2790
|
+
"requesterDisplayName",
|
|
2791
|
+
"requesterContact",
|
|
2792
|
+
"note",
|
|
2793
|
+
"targetLogin"
|
|
2794
|
+
];
|
|
2795
|
+
INTRO_ALLOWED_SET = new Set(INTRO_ALLOWED_FIELDS);
|
|
2796
|
+
MAX_SHORT = 200;
|
|
2797
|
+
MAX_NOTE = 500;
|
|
2798
|
+
INTRO_PENDING_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
2799
|
+
INTRO_ACCEPTED_TTL_MS = 365 * 24 * 60 * 60 * 1e3;
|
|
2800
|
+
}
|
|
2801
|
+
});
|
|
2802
|
+
|
|
2587
2803
|
// ../../packages/core/src/index.ts
|
|
2588
2804
|
var src_exports = {};
|
|
2589
2805
|
__export(src_exports, {
|
|
@@ -2599,6 +2815,9 @@ __export(src_exports, {
|
|
|
2599
2815
|
GRAPH: () => GRAPH,
|
|
2600
2816
|
GREENHOUSE_SLUGS_BY_TIER: () => GREENHOUSE_SLUGS_BY_TIER,
|
|
2601
2817
|
IDF_BACKGROUND: () => IDF_BACKGROUND,
|
|
2818
|
+
INTRO_ACCEPTED_TTL_MS: () => INTRO_ACCEPTED_TTL_MS,
|
|
2819
|
+
INTRO_ALLOWED_FIELDS: () => INTRO_ALLOWED_FIELDS,
|
|
2820
|
+
INTRO_PENDING_TTL_MS: () => INTRO_PENDING_TTL_MS,
|
|
2602
2821
|
LEVER_SLUGS_BY_TIER: () => LEVER_SLUGS_BY_TIER,
|
|
2603
2822
|
SYNONYMS: () => SYNONYMS,
|
|
2604
2823
|
VOCABULARY: () => VOCABULARY,
|
|
@@ -2607,10 +2826,17 @@ __export(src_exports, {
|
|
|
2607
2826
|
aggregate: () => aggregate,
|
|
2608
2827
|
aggregateBounties: () => aggregateBounties,
|
|
2609
2828
|
ashby: () => ashby,
|
|
2829
|
+
authorizeIntroDecision: () => authorizeIntroDecision,
|
|
2830
|
+
authorizeIntroDeletion: () => authorizeIntroDeletion,
|
|
2610
2831
|
bestAcceptanceDomain: () => bestAcceptanceDomain,
|
|
2832
|
+
buildDirectoryIndex: () => buildDirectoryIndex,
|
|
2611
2833
|
buildGraph: () => buildGraph,
|
|
2612
2834
|
buildIndex: () => buildIndex,
|
|
2835
|
+
buildIntroListItem: () => buildIntroListItem,
|
|
2836
|
+
buildIntroPayload: () => buildIntroPayload,
|
|
2613
2837
|
buildReason: () => buildReason,
|
|
2838
|
+
composeIntroAcceptedEmail: () => composeIntroAcceptedEmail,
|
|
2839
|
+
composeIntroEmail: () => composeIntroEmail,
|
|
2614
2840
|
computeAcceptanceCredential: () => computeAcceptanceCredential,
|
|
2615
2841
|
computeAcceptanceCredentialPublic: () => computeAcceptanceCredentialPublic,
|
|
2616
2842
|
coreTagsFromTitle: () => coreTagsFromTitle,
|
|
@@ -2627,7 +2853,11 @@ __export(src_exports, {
|
|
|
2627
2853
|
greenhouse: () => greenhouse,
|
|
2628
2854
|
himalayas: () => himalayas,
|
|
2629
2855
|
hn: () => hn,
|
|
2856
|
+
introActorRole: () => introActorRole,
|
|
2857
|
+
introRateLimitCheck: () => introRateLimitCheck,
|
|
2858
|
+
introRetentionAction: () => introRetentionAction,
|
|
2630
2859
|
isBounty: () => isBounty,
|
|
2860
|
+
isOverIntroLimit: () => isOverIntroLimit,
|
|
2631
2861
|
lever: () => lever,
|
|
2632
2862
|
loadPartnerRoles: () => loadPartnerRoles,
|
|
2633
2863
|
looksLikeEngRole: () => looksLikeEngRole,
|
|
@@ -2635,8 +2865,14 @@ __export(src_exports, {
|
|
|
2635
2865
|
normalize: () => normalize,
|
|
2636
2866
|
opire: () => opire,
|
|
2637
2867
|
passesMaturityGate: () => passesMaturityGate,
|
|
2868
|
+
personCardToJob: () => personCardToJob,
|
|
2869
|
+
projectCardToJob: () => projectCardToJob,
|
|
2870
|
+
rejectExtraIntroFields: () => rejectExtraIntroFields,
|
|
2871
|
+
revealIntroContacts: () => revealIntroContacts,
|
|
2638
2872
|
tokenize: () => tokenize,
|
|
2639
2873
|
validateGraph: () => validateGraph,
|
|
2874
|
+
validateIntroPayload: () => validateIntroPayload,
|
|
2875
|
+
validateTargetContact: () => validateTargetContact,
|
|
2640
2876
|
workable: () => workable,
|
|
2641
2877
|
wwr: () => wwr
|
|
2642
2878
|
});
|
|
@@ -2650,6 +2886,7 @@ var init_src = __esm({
|
|
|
2650
2886
|
init_indexer();
|
|
2651
2887
|
init_partners();
|
|
2652
2888
|
init_github();
|
|
2889
|
+
init_intro();
|
|
2653
2890
|
}
|
|
2654
2891
|
});
|
|
2655
2892
|
|