terminalhire 0.6.0 → 0.7.0
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-dispatch.js
CHANGED
|
@@ -17,19 +17,19 @@ __export(open_url_exports, {
|
|
|
17
17
|
import { spawn } from "child_process";
|
|
18
18
|
function openInBrowser(url) {
|
|
19
19
|
let cmd;
|
|
20
|
-
let
|
|
20
|
+
let args5;
|
|
21
21
|
if (process.platform === "darwin") {
|
|
22
22
|
cmd = "open";
|
|
23
|
-
|
|
23
|
+
args5 = [url];
|
|
24
24
|
} else if (process.platform === "win32") {
|
|
25
25
|
cmd = "cmd";
|
|
26
|
-
|
|
26
|
+
args5 = ["/c", "start", "", url];
|
|
27
27
|
} else {
|
|
28
28
|
cmd = "xdg-open";
|
|
29
|
-
|
|
29
|
+
args5 = [url];
|
|
30
30
|
}
|
|
31
31
|
try {
|
|
32
|
-
const child = spawn(cmd,
|
|
32
|
+
const child = spawn(cmd, args5, { stdio: "ignore", detached: true });
|
|
33
33
|
child.on("error", () => {
|
|
34
34
|
});
|
|
35
35
|
child.unref();
|
|
@@ -1940,14 +1940,14 @@ async function mapWithConcurrency(items, limit, fn) {
|
|
|
1940
1940
|
if (items.length === 0) return results;
|
|
1941
1941
|
const workers = Math.max(1, Math.min(Math.floor(limit) || 1, items.length));
|
|
1942
1942
|
let next = 0;
|
|
1943
|
-
async function
|
|
1943
|
+
async function run17() {
|
|
1944
1944
|
for (; ; ) {
|
|
1945
1945
|
const i = next++;
|
|
1946
1946
|
if (i >= items.length) return;
|
|
1947
1947
|
results[i] = await fn(items[i], i);
|
|
1948
1948
|
}
|
|
1949
1949
|
}
|
|
1950
|
-
await Promise.all(Array.from({ length: workers },
|
|
1950
|
+
await Promise.all(Array.from({ length: workers }, run17));
|
|
1951
1951
|
return results;
|
|
1952
1952
|
}
|
|
1953
1953
|
var init_concurrency = __esm({
|
|
@@ -2515,6 +2515,51 @@ var init_workable = __esm({
|
|
|
2515
2515
|
}
|
|
2516
2516
|
});
|
|
2517
2517
|
|
|
2518
|
+
// ../../packages/core/src/feeds/directory.ts
|
|
2519
|
+
function personCardToJob(row) {
|
|
2520
|
+
const tags = [...row.skill_tags];
|
|
2521
|
+
return {
|
|
2522
|
+
id: `dev:${row.login}`,
|
|
2523
|
+
source: "person",
|
|
2524
|
+
title: row.name ?? row.login,
|
|
2525
|
+
company: row.login,
|
|
2526
|
+
url: `/r/${row.login}`,
|
|
2527
|
+
remote: true,
|
|
2528
|
+
tags,
|
|
2529
|
+
coreTags: tags.slice(0, TOP_CORE_TAGS),
|
|
2530
|
+
roleType: "full_time",
|
|
2531
|
+
applyMode: "direct"
|
|
2532
|
+
};
|
|
2533
|
+
}
|
|
2534
|
+
function buildDirectoryIndex(people, opts) {
|
|
2535
|
+
return {
|
|
2536
|
+
builtAt: opts?.builtAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2537
|
+
cards: people.map(personCardToJob)
|
|
2538
|
+
};
|
|
2539
|
+
}
|
|
2540
|
+
function projectCardToJob(row) {
|
|
2541
|
+
const tags = [...row.needed_skills];
|
|
2542
|
+
return {
|
|
2543
|
+
id: `proj:${row.id}`,
|
|
2544
|
+
source: "project",
|
|
2545
|
+
title: row.title,
|
|
2546
|
+
company: row.owner_login,
|
|
2547
|
+
url: `/r/${row.owner_login}`,
|
|
2548
|
+
remote: true,
|
|
2549
|
+
tags,
|
|
2550
|
+
coreTags: tags.slice(0, TOP_CORE_TAGS),
|
|
2551
|
+
roleType: "full_time",
|
|
2552
|
+
applyMode: "direct"
|
|
2553
|
+
};
|
|
2554
|
+
}
|
|
2555
|
+
var TOP_CORE_TAGS;
|
|
2556
|
+
var init_directory = __esm({
|
|
2557
|
+
"../../packages/core/src/feeds/directory.ts"() {
|
|
2558
|
+
"use strict";
|
|
2559
|
+
TOP_CORE_TAGS = 4;
|
|
2560
|
+
}
|
|
2561
|
+
});
|
|
2562
|
+
|
|
2518
2563
|
// ../../packages/core/src/feeds/index.ts
|
|
2519
2564
|
async function aggregateBounties(opts) {
|
|
2520
2565
|
const [gh, op] = await Promise.all([
|
|
@@ -2631,6 +2676,7 @@ var init_feeds = __esm({
|
|
|
2631
2676
|
init_github_bounties();
|
|
2632
2677
|
init_opire();
|
|
2633
2678
|
init_workable();
|
|
2679
|
+
init_directory();
|
|
2634
2680
|
init_bounty_gate();
|
|
2635
2681
|
init_bounty_gate();
|
|
2636
2682
|
FEEDS = [greenhouse, ashby, lever, workable, himalayas, wwr, hn];
|
|
@@ -2831,6 +2877,176 @@ var init_indexer = __esm({
|
|
|
2831
2877
|
}
|
|
2832
2878
|
});
|
|
2833
2879
|
|
|
2880
|
+
// ../../packages/core/src/intro.ts
|
|
2881
|
+
function buildIntroPayload(input) {
|
|
2882
|
+
const payload = {
|
|
2883
|
+
requesterLogin: input.requesterLogin,
|
|
2884
|
+
requesterDisplayName: input.requesterDisplayName,
|
|
2885
|
+
requesterContact: input.requesterContact,
|
|
2886
|
+
targetLogin: input.targetLogin
|
|
2887
|
+
};
|
|
2888
|
+
const note = input.note?.trim();
|
|
2889
|
+
if (note) payload.note = note;
|
|
2890
|
+
return payload;
|
|
2891
|
+
}
|
|
2892
|
+
function rejectExtraIntroFields(body) {
|
|
2893
|
+
for (const key of Object.keys(body)) {
|
|
2894
|
+
if (!INTRO_ALLOWED_SET.has(key)) {
|
|
2895
|
+
return `intro payload contains disallowed field: "${key}"`;
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
return null;
|
|
2899
|
+
}
|
|
2900
|
+
function validateIntroPayload(body) {
|
|
2901
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
2902
|
+
return { ok: false, reason: "intro payload must be a JSON object" };
|
|
2903
|
+
}
|
|
2904
|
+
const b = body;
|
|
2905
|
+
const extra = rejectExtraIntroFields(b);
|
|
2906
|
+
if (extra) return { ok: false, reason: extra };
|
|
2907
|
+
const ok = (v, max) => typeof v === "string" && v.trim().length > 0 && v.length <= max;
|
|
2908
|
+
if (!ok(b.requesterLogin, MAX_SHORT)) return { ok: false, reason: "requesterLogin is required" };
|
|
2909
|
+
if (!ok(b.requesterDisplayName, MAX_SHORT)) return { ok: false, reason: "requesterDisplayName is required" };
|
|
2910
|
+
if (!ok(b.requesterContact, MAX_SHORT)) return { ok: false, reason: "requesterContact is required" };
|
|
2911
|
+
if (!ok(b.targetLogin, MAX_SHORT)) return { ok: false, reason: "targetLogin is required" };
|
|
2912
|
+
if (b.note !== void 0 && (typeof b.note !== "string" || b.note.length > MAX_NOTE)) {
|
|
2913
|
+
return { ok: false, reason: "note must be a string of at most 500 chars" };
|
|
2914
|
+
}
|
|
2915
|
+
const value = {
|
|
2916
|
+
requesterLogin: b.requesterLogin.trim(),
|
|
2917
|
+
requesterDisplayName: b.requesterDisplayName.trim(),
|
|
2918
|
+
requesterContact: b.requesterContact.trim(),
|
|
2919
|
+
targetLogin: b.targetLogin.trim()
|
|
2920
|
+
};
|
|
2921
|
+
const note = typeof b.note === "string" ? b.note.trim() : "";
|
|
2922
|
+
if (note) value.note = note;
|
|
2923
|
+
return { ok: true, value };
|
|
2924
|
+
}
|
|
2925
|
+
function introRateLimitCheck(history, now, opts) {
|
|
2926
|
+
const cutoff = now - opts.windowMs;
|
|
2927
|
+
const recent = history.filter((t) => t > cutoff);
|
|
2928
|
+
if (recent.length >= opts.max) {
|
|
2929
|
+
const oldest = recent[0] ?? now;
|
|
2930
|
+
return { allowed: false, retained: recent, retryAfterMs: Math.max(0, oldest + opts.windowMs - now) };
|
|
2931
|
+
}
|
|
2932
|
+
return { allowed: true, retained: [...recent, now], retryAfterMs: 0 };
|
|
2933
|
+
}
|
|
2934
|
+
function isOverIntroLimit(recentCount, max) {
|
|
2935
|
+
return recentCount >= max;
|
|
2936
|
+
}
|
|
2937
|
+
function composeIntroEmail(args5) {
|
|
2938
|
+
const subject = `New intro request from @${args5.requesterLogin} \xB7 terminalhire`;
|
|
2939
|
+
const text = `@${args5.requesterLogin} wants an intro to you on terminalhire.
|
|
2940
|
+
|
|
2941
|
+
Sign in to view the request and choose whether to share your contact back:
|
|
2942
|
+
${args5.dashboardUrl}
|
|
2943
|
+
|
|
2944
|
+
You control whether this connects \u2014 no contact details are shared unless you accept.
|
|
2945
|
+
|
|
2946
|
+
\u2014 Terminalhire`;
|
|
2947
|
+
return { subject, text };
|
|
2948
|
+
}
|
|
2949
|
+
function introActorRole(intro, actorLogin) {
|
|
2950
|
+
const a = actorLogin.trim().toLowerCase();
|
|
2951
|
+
if (a && a === intro.targetLogin.trim().toLowerCase()) return "target";
|
|
2952
|
+
if (a && a === intro.requesterLogin.trim().toLowerCase()) return "requester";
|
|
2953
|
+
return "other";
|
|
2954
|
+
}
|
|
2955
|
+
function authorizeIntroDecision(intro, actorLogin) {
|
|
2956
|
+
const role = introActorRole(intro, actorLogin);
|
|
2957
|
+
if (role === "target") return { ok: true };
|
|
2958
|
+
if (role === "requester") {
|
|
2959
|
+
return { ok: false, status: 403, reason: "the requester cannot accept or decline their own intro request" };
|
|
2960
|
+
}
|
|
2961
|
+
return { ok: false, status: 404, reason: "intro not found" };
|
|
2962
|
+
}
|
|
2963
|
+
function authorizeIntroDeletion(intro, actorLogin) {
|
|
2964
|
+
const role = introActorRole(intro, actorLogin);
|
|
2965
|
+
if (role === "other") return { ok: false, status: 404, reason: "intro not found" };
|
|
2966
|
+
return { ok: true };
|
|
2967
|
+
}
|
|
2968
|
+
function revealIntroContacts(intro) {
|
|
2969
|
+
if (intro.status !== "accepted") return { toRequester: null, toTarget: null };
|
|
2970
|
+
return { toRequester: intro.targetContact ?? null, toTarget: intro.requesterContact };
|
|
2971
|
+
}
|
|
2972
|
+
function validateTargetContact(v) {
|
|
2973
|
+
if (typeof v !== "string" || v.trim().length === 0) return { ok: false, reason: "targetContact is required" };
|
|
2974
|
+
if (v.length > MAX_SHORT) return { ok: false, reason: `targetContact must be at most ${MAX_SHORT} chars` };
|
|
2975
|
+
return { ok: true, value: v.trim() };
|
|
2976
|
+
}
|
|
2977
|
+
function buildIntroListItem(intro, viewerLogin) {
|
|
2978
|
+
const role = introActorRole(intro, viewerLogin);
|
|
2979
|
+
if (role === "other") return null;
|
|
2980
|
+
const reveal = revealIntroContacts(intro);
|
|
2981
|
+
if (role === "target") {
|
|
2982
|
+
return {
|
|
2983
|
+
id: intro.id,
|
|
2984
|
+
role: "incoming",
|
|
2985
|
+
counterpartyLogin: intro.requesterLogin,
|
|
2986
|
+
status: intro.status,
|
|
2987
|
+
note: intro.note ?? null,
|
|
2988
|
+
contact: reveal.toTarget
|
|
2989
|
+
};
|
|
2990
|
+
}
|
|
2991
|
+
return {
|
|
2992
|
+
id: intro.id,
|
|
2993
|
+
role: "outgoing",
|
|
2994
|
+
counterpartyLogin: intro.targetLogin,
|
|
2995
|
+
status: intro.status,
|
|
2996
|
+
note: intro.note ?? null,
|
|
2997
|
+
contact: reveal.toRequester
|
|
2998
|
+
};
|
|
2999
|
+
}
|
|
3000
|
+
function composeIntroAcceptedEmail(args5) {
|
|
3001
|
+
const subject = `Intro connected with @${args5.counterpartyLogin} \xB7 terminalhire`;
|
|
3002
|
+
const lead = args5.recipientRole === "requester" ? `@${args5.counterpartyLogin} accepted your intro request on terminalhire.` : `You accepted @${args5.counterpartyLogin}'s intro request on terminalhire.`;
|
|
3003
|
+
const text = `${lead}
|
|
3004
|
+
|
|
3005
|
+
You can now reach them directly:
|
|
3006
|
+
@${args5.counterpartyLogin} \u2014 ${args5.counterpartyContact}
|
|
3007
|
+
|
|
3008
|
+
Take it from here.
|
|
3009
|
+
|
|
3010
|
+
\u2014 Terminalhire`;
|
|
3011
|
+
return { subject, text };
|
|
3012
|
+
}
|
|
3013
|
+
function introRetentionAction(row, now) {
|
|
3014
|
+
if (row.status === "pending") {
|
|
3015
|
+
const created = Date.parse(row.createdAt);
|
|
3016
|
+
if (Number.isFinite(created) && now - created > INTRO_PENDING_TTL_MS) return "purge";
|
|
3017
|
+
return "keep";
|
|
3018
|
+
}
|
|
3019
|
+
if (row.status === "declined") {
|
|
3020
|
+
return row.hasContact ? "scrub-declined" : "keep";
|
|
3021
|
+
}
|
|
3022
|
+
if (row.status === "accepted") {
|
|
3023
|
+
const updated = Date.parse(row.updatedAt);
|
|
3024
|
+
if (row.hasContact && Number.isFinite(updated) && now - updated > INTRO_ACCEPTED_TTL_MS) {
|
|
3025
|
+
return "expire-accepted";
|
|
3026
|
+
}
|
|
3027
|
+
return "keep";
|
|
3028
|
+
}
|
|
3029
|
+
return "keep";
|
|
3030
|
+
}
|
|
3031
|
+
var INTRO_ALLOWED_FIELDS, INTRO_ALLOWED_SET, MAX_SHORT, MAX_NOTE, INTRO_PENDING_TTL_MS, INTRO_ACCEPTED_TTL_MS;
|
|
3032
|
+
var init_intro = __esm({
|
|
3033
|
+
"../../packages/core/src/intro.ts"() {
|
|
3034
|
+
"use strict";
|
|
3035
|
+
INTRO_ALLOWED_FIELDS = [
|
|
3036
|
+
"requesterLogin",
|
|
3037
|
+
"requesterDisplayName",
|
|
3038
|
+
"requesterContact",
|
|
3039
|
+
"note",
|
|
3040
|
+
"targetLogin"
|
|
3041
|
+
];
|
|
3042
|
+
INTRO_ALLOWED_SET = new Set(INTRO_ALLOWED_FIELDS);
|
|
3043
|
+
MAX_SHORT = 200;
|
|
3044
|
+
MAX_NOTE = 500;
|
|
3045
|
+
INTRO_PENDING_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
3046
|
+
INTRO_ACCEPTED_TTL_MS = 365 * 24 * 60 * 60 * 1e3;
|
|
3047
|
+
}
|
|
3048
|
+
});
|
|
3049
|
+
|
|
2834
3050
|
// ../../packages/core/src/index.ts
|
|
2835
3051
|
var src_exports = {};
|
|
2836
3052
|
__export(src_exports, {
|
|
@@ -2846,6 +3062,9 @@ __export(src_exports, {
|
|
|
2846
3062
|
GRAPH: () => GRAPH,
|
|
2847
3063
|
GREENHOUSE_SLUGS_BY_TIER: () => GREENHOUSE_SLUGS_BY_TIER,
|
|
2848
3064
|
IDF_BACKGROUND: () => IDF_BACKGROUND,
|
|
3065
|
+
INTRO_ACCEPTED_TTL_MS: () => INTRO_ACCEPTED_TTL_MS,
|
|
3066
|
+
INTRO_ALLOWED_FIELDS: () => INTRO_ALLOWED_FIELDS,
|
|
3067
|
+
INTRO_PENDING_TTL_MS: () => INTRO_PENDING_TTL_MS,
|
|
2849
3068
|
LEVER_SLUGS_BY_TIER: () => LEVER_SLUGS_BY_TIER,
|
|
2850
3069
|
SYNONYMS: () => SYNONYMS,
|
|
2851
3070
|
VOCABULARY: () => VOCABULARY,
|
|
@@ -2854,10 +3073,17 @@ __export(src_exports, {
|
|
|
2854
3073
|
aggregate: () => aggregate,
|
|
2855
3074
|
aggregateBounties: () => aggregateBounties,
|
|
2856
3075
|
ashby: () => ashby,
|
|
3076
|
+
authorizeIntroDecision: () => authorizeIntroDecision,
|
|
3077
|
+
authorizeIntroDeletion: () => authorizeIntroDeletion,
|
|
2857
3078
|
bestAcceptanceDomain: () => bestAcceptanceDomain,
|
|
3079
|
+
buildDirectoryIndex: () => buildDirectoryIndex,
|
|
2858
3080
|
buildGraph: () => buildGraph,
|
|
2859
3081
|
buildIndex: () => buildIndex,
|
|
3082
|
+
buildIntroListItem: () => buildIntroListItem,
|
|
3083
|
+
buildIntroPayload: () => buildIntroPayload,
|
|
2860
3084
|
buildReason: () => buildReason,
|
|
3085
|
+
composeIntroAcceptedEmail: () => composeIntroAcceptedEmail,
|
|
3086
|
+
composeIntroEmail: () => composeIntroEmail,
|
|
2861
3087
|
computeAcceptanceCredential: () => computeAcceptanceCredential,
|
|
2862
3088
|
computeAcceptanceCredentialPublic: () => computeAcceptanceCredentialPublic,
|
|
2863
3089
|
coreTagsFromTitle: () => coreTagsFromTitle,
|
|
@@ -2874,7 +3100,11 @@ __export(src_exports, {
|
|
|
2874
3100
|
greenhouse: () => greenhouse,
|
|
2875
3101
|
himalayas: () => himalayas,
|
|
2876
3102
|
hn: () => hn,
|
|
3103
|
+
introActorRole: () => introActorRole,
|
|
3104
|
+
introRateLimitCheck: () => introRateLimitCheck,
|
|
3105
|
+
introRetentionAction: () => introRetentionAction,
|
|
2877
3106
|
isBounty: () => isBounty,
|
|
3107
|
+
isOverIntroLimit: () => isOverIntroLimit,
|
|
2878
3108
|
lever: () => lever,
|
|
2879
3109
|
loadPartnerRoles: () => loadPartnerRoles,
|
|
2880
3110
|
looksLikeEngRole: () => looksLikeEngRole,
|
|
@@ -2882,8 +3112,14 @@ __export(src_exports, {
|
|
|
2882
3112
|
normalize: () => normalize,
|
|
2883
3113
|
opire: () => opire,
|
|
2884
3114
|
passesMaturityGate: () => passesMaturityGate,
|
|
3115
|
+
personCardToJob: () => personCardToJob,
|
|
3116
|
+
projectCardToJob: () => projectCardToJob,
|
|
3117
|
+
rejectExtraIntroFields: () => rejectExtraIntroFields,
|
|
3118
|
+
revealIntroContacts: () => revealIntroContacts,
|
|
2885
3119
|
tokenize: () => tokenize,
|
|
2886
3120
|
validateGraph: () => validateGraph,
|
|
3121
|
+
validateIntroPayload: () => validateIntroPayload,
|
|
3122
|
+
validateTargetContact: () => validateTargetContact,
|
|
2887
3123
|
workable: () => workable,
|
|
2888
3124
|
wwr: () => wwr
|
|
2889
3125
|
});
|
|
@@ -2897,6 +3133,7 @@ var init_src = __esm({
|
|
|
2897
3133
|
init_indexer();
|
|
2898
3134
|
init_partners();
|
|
2899
3135
|
init_github();
|
|
3136
|
+
init_intro();
|
|
2900
3137
|
}
|
|
2901
3138
|
});
|
|
2902
3139
|
|
|
@@ -3174,11 +3411,11 @@ async function runLogin() {
|
|
|
3174
3411
|
if (process.env["TERMINALHIRE_GITHUB_MOCK"] === "1" || process.env["JPI_GITHUB_MOCK"] === "1") {
|
|
3175
3412
|
const { createRequire: createRequire2 } = await import("module");
|
|
3176
3413
|
const { fileURLToPath: fileURLToPath7 } = await import("url");
|
|
3177
|
-
const { join:
|
|
3414
|
+
const { join: join20, dirname: dirname3 } = await import("path");
|
|
3178
3415
|
const __dirname6 = fileURLToPath7(new URL(".", import.meta.url));
|
|
3179
|
-
const fixturePath =
|
|
3180
|
-
const { readFileSync:
|
|
3181
|
-
ghProfile = JSON.parse(
|
|
3416
|
+
const fixturePath = join20(__dirname6, "../../fixtures/github-sample.json");
|
|
3417
|
+
const { readFileSync: readFileSync19 } = await import("fs");
|
|
3418
|
+
ghProfile = JSON.parse(readFileSync19(fixturePath, "utf8"));
|
|
3182
3419
|
} else {
|
|
3183
3420
|
ghProfile = await fetchGitHubProfile2(login, token);
|
|
3184
3421
|
}
|
|
@@ -3543,39 +3780,335 @@ var init_jpi_jobs = __esm({
|
|
|
3543
3780
|
}
|
|
3544
3781
|
});
|
|
3545
3782
|
|
|
3546
|
-
// bin/jpi-
|
|
3547
|
-
var
|
|
3548
|
-
__export(
|
|
3783
|
+
// bin/jpi-devs.js
|
|
3784
|
+
var jpi_devs_exports = {};
|
|
3785
|
+
__export(jpi_devs_exports, {
|
|
3786
|
+
reportMatched: () => reportMatched,
|
|
3549
3787
|
run: () => run3
|
|
3550
3788
|
});
|
|
3551
3789
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
|
|
3552
3790
|
import { join as join5 } from "path";
|
|
3553
3791
|
import { homedir as homedir4 } from "os";
|
|
3554
3792
|
import { createInterface as createInterface2 } from "readline";
|
|
3793
|
+
function readDirectoryCache() {
|
|
3794
|
+
try {
|
|
3795
|
+
const entry = JSON.parse(readFileSync5(DIRECTORY_CACHE_FILE, "utf8"));
|
|
3796
|
+
if (typeof entry.ts === "number" && Number.isFinite(entry.ts) && Date.now() - entry.ts < INDEX_TTL_MS2) {
|
|
3797
|
+
return { index: entry.index, ts: entry.ts };
|
|
3798
|
+
}
|
|
3799
|
+
return null;
|
|
3800
|
+
} catch {
|
|
3801
|
+
return null;
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
function writeDirectoryCache(index) {
|
|
3805
|
+
mkdirSync4(TERMINALHIRE_DIR4, { recursive: true });
|
|
3806
|
+
writeFileSync4(DIRECTORY_CACHE_FILE, JSON.stringify({ ts: Date.now(), index }), "utf8");
|
|
3807
|
+
}
|
|
3808
|
+
function readProject() {
|
|
3809
|
+
try {
|
|
3810
|
+
return JSON.parse(readFileSync5(PROJECT_FILE, "utf8"));
|
|
3811
|
+
} catch {
|
|
3812
|
+
return null;
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3815
|
+
function relativeTime(ts) {
|
|
3816
|
+
const secs = Math.max(0, Math.round((Date.now() - ts) / 1e3));
|
|
3817
|
+
if (secs < 60) return `${secs}s ago`;
|
|
3818
|
+
const mins = Math.round(secs / 60);
|
|
3819
|
+
return mins < 60 ? `${mins}m ago` : `${Math.round(mins / 60)}h ago`;
|
|
3820
|
+
}
|
|
3821
|
+
async function fetchDirectory() {
|
|
3822
|
+
const cached = readDirectoryCache();
|
|
3823
|
+
if (cached) {
|
|
3824
|
+
console.log(`\u2713 Using cached directory (updated ${relativeTime(cached.ts)})`);
|
|
3825
|
+
return cached.index;
|
|
3826
|
+
}
|
|
3827
|
+
console.log(`\u21BB Refreshing builder directory from ${API_URL2}/api/directory...`);
|
|
3828
|
+
const res = await fetch(`${API_URL2}/api/directory`, { signal: AbortSignal.timeout(1e4) });
|
|
3829
|
+
if (!res.ok) throw new Error(`/api/directory returned ${res.status}`);
|
|
3830
|
+
const index = await res.json();
|
|
3831
|
+
writeDirectoryCache(index);
|
|
3832
|
+
return index;
|
|
3833
|
+
}
|
|
3834
|
+
function reportMatched(results, fetchImpl = fetch) {
|
|
3835
|
+
try {
|
|
3836
|
+
const logins = [
|
|
3837
|
+
...new Set(
|
|
3838
|
+
results.map((r) => r?.job?.company).filter((login) => typeof login === "string" && login.length > 0)
|
|
3839
|
+
)
|
|
3840
|
+
];
|
|
3841
|
+
if (logins.length === 0) return;
|
|
3842
|
+
Promise.resolve(
|
|
3843
|
+
fetchImpl(`${API_URL2}/api/directory/matched`, {
|
|
3844
|
+
method: "POST",
|
|
3845
|
+
headers: { "Content-Type": "application/json" },
|
|
3846
|
+
body: JSON.stringify({ matched: logins }),
|
|
3847
|
+
signal: AbortSignal.timeout(3e3)
|
|
3848
|
+
})
|
|
3849
|
+
).catch(() => {
|
|
3850
|
+
});
|
|
3851
|
+
} catch {
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3854
|
+
function prompt2(question) {
|
|
3855
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
3856
|
+
return new Promise((resolve2) => {
|
|
3857
|
+
rl.question(question, (answer) => {
|
|
3858
|
+
rl.close();
|
|
3859
|
+
resolve2(answer.trim().toLowerCase());
|
|
3860
|
+
});
|
|
3861
|
+
});
|
|
3862
|
+
}
|
|
3863
|
+
function credentialUrl(job) {
|
|
3864
|
+
const u = job.url ?? "";
|
|
3865
|
+
return u.startsWith("http") ? u : `${API_URL2}${u}`;
|
|
3866
|
+
}
|
|
3867
|
+
function linkTitle2(title, url) {
|
|
3868
|
+
const isTTY = process.stdout.isTTY;
|
|
3869
|
+
const noColor = process.env["NO_COLOR"] !== void 0;
|
|
3870
|
+
if (isTTY && !noColor && url) return `\x1B]8;;${url}\x1B\\${title}\x1B]8;;\x1B\\`;
|
|
3871
|
+
return url ? `${title} (${url})` : title;
|
|
3872
|
+
}
|
|
3873
|
+
function printCard(i, result) {
|
|
3874
|
+
const { job, score, matchedTags, reason } = result;
|
|
3875
|
+
const url = credentialUrl(job);
|
|
3876
|
+
const kind = job.source === "project" ? "project" : "developer";
|
|
3877
|
+
const byline = job.source === "project" ? ` \xB7 by @${job.company}` : "";
|
|
3878
|
+
const scoreStr = score > 0 ? ` \xB7 match ${Math.round(score * 100)}%` : "";
|
|
3879
|
+
console.log(`
|
|
3880
|
+
${i + 1}. ${linkTitle2(job.title, url)} \u2014 ${kind}${byline}${scoreStr}`);
|
|
3881
|
+
console.log(` id: ${job.id}`);
|
|
3882
|
+
if (reason) console.log(` ${reason}`);
|
|
3883
|
+
if (matchedTags && matchedTags.length) console.log(` Tags matched: ${matchedTags.slice(0, 5).join(", ")}`);
|
|
3884
|
+
console.log(` Profile: ${url}`);
|
|
3885
|
+
}
|
|
3886
|
+
async function run3() {
|
|
3887
|
+
try {
|
|
3888
|
+
const { match: match2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
3889
|
+
let fp;
|
|
3890
|
+
if (AS_PROJECT) {
|
|
3891
|
+
const project = readProject();
|
|
3892
|
+
if (!project) {
|
|
3893
|
+
console.log("\u2726 terminalhire devs --as-project: no project declared yet.");
|
|
3894
|
+
console.log(' Run `terminalhire project "<title>: <skills it needs>"` first, then come back.');
|
|
3895
|
+
return;
|
|
3896
|
+
}
|
|
3897
|
+
if (!project.skillTags || project.skillTags.length === 0) {
|
|
3898
|
+
console.log("\u2726 Your declared project has no recognized skills yet.");
|
|
3899
|
+
console.log(" Re-run `terminalhire project` with known stack names (react, go, postgres, ...).");
|
|
3900
|
+
return;
|
|
3901
|
+
}
|
|
3902
|
+
fp = { skillTags: project.skillTags, prefs: project.prefs ?? {} };
|
|
3903
|
+
console.log(`Ranking builders for your project: ${project.title}`);
|
|
3904
|
+
} else {
|
|
3905
|
+
const { readProfile: readProfile2, profileToFingerprint: profileToFingerprint2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
|
|
3906
|
+
const profile = await readProfile2();
|
|
3907
|
+
if (profile.skillTags.length === 0) {
|
|
3908
|
+
console.log("\u2726 terminalhire devs: no skill tags in your local profile yet.");
|
|
3909
|
+
console.log(" Run `terminalhire init` first to set up your profile, then come back.");
|
|
3910
|
+
console.log(" (Tags also accumulate as you work in Claude Code sessions.)");
|
|
3911
|
+
return;
|
|
3912
|
+
}
|
|
3913
|
+
fp = profileToFingerprint2(profile);
|
|
3914
|
+
}
|
|
3915
|
+
const index = await fetchDirectory();
|
|
3916
|
+
const cards = index.cards ?? [];
|
|
3917
|
+
if (cards.length === 0) {
|
|
3918
|
+
console.log("\nNo builders or projects published yet. Check back soon \u2014 the directory fills as devs publish.");
|
|
3919
|
+
return;
|
|
3920
|
+
}
|
|
3921
|
+
const results = match2(fp, cards, SHOW_ALL2 ? cards.length : LIMIT2);
|
|
3922
|
+
if (results.length === 0) {
|
|
3923
|
+
console.log(`No matching builders or projects for your current ${AS_PROJECT ? "project" : "profile"}.`);
|
|
3924
|
+
console.log(" Your tags: " + fp.skillTags.join(", "));
|
|
3925
|
+
return;
|
|
3926
|
+
}
|
|
3927
|
+
reportMatched(results);
|
|
3928
|
+
console.log(
|
|
3929
|
+
`
|
|
3930
|
+
\u2726 ${results.length} match${results.length === 1 ? "" : "es"} in the builder directory (local match \u2014 no data sent)
|
|
3931
|
+
`
|
|
3932
|
+
);
|
|
3933
|
+
for (let i = 0; i < results.length; i++) {
|
|
3934
|
+
printCard(i, results[i]);
|
|
3935
|
+
}
|
|
3936
|
+
if (!process.stdin.isTTY) return;
|
|
3937
|
+
console.log("\n" + "\u2500".repeat(70));
|
|
3938
|
+
const pick = await prompt2(`
|
|
3939
|
+
Enter a number to open a profile, or press Enter to exit: `);
|
|
3940
|
+
const idx = parseInt(pick, 10) - 1;
|
|
3941
|
+
if (Number.isNaN(idx) || idx < 0 || idx >= results.length) return;
|
|
3942
|
+
console.log(`
|
|
3943
|
+
Open this public credential (no data shared):
|
|
3944
|
+
${credentialUrl(results[idx].job)}`);
|
|
3945
|
+
} catch (err) {
|
|
3946
|
+
console.error("terminalhire devs error:", err.message ?? err);
|
|
3947
|
+
process.exit(1);
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
var TERMINALHIRE_DIR4, DIRECTORY_CACHE_FILE, PROJECT_FILE, INDEX_TTL_MS2, API_URL2, DEFAULT_LIMIT2, args2, limitArg2, LIMIT2, SHOW_ALL2, AS_PROJECT;
|
|
3951
|
+
var init_jpi_devs = __esm({
|
|
3952
|
+
"bin/jpi-devs.js"() {
|
|
3953
|
+
"use strict";
|
|
3954
|
+
TERMINALHIRE_DIR4 = join5(homedir4(), ".terminalhire");
|
|
3955
|
+
DIRECTORY_CACHE_FILE = join5(TERMINALHIRE_DIR4, "directory-cache.json");
|
|
3956
|
+
PROJECT_FILE = join5(TERMINALHIRE_DIR4, "project.json");
|
|
3957
|
+
INDEX_TTL_MS2 = 15 * 60 * 1e3;
|
|
3958
|
+
API_URL2 = process.env["TERMINALHIRE_API_URL"] ?? process.env["JPI_API_URL"] ?? "https://terminalhire.com";
|
|
3959
|
+
DEFAULT_LIMIT2 = 10;
|
|
3960
|
+
args2 = process.argv.slice(2);
|
|
3961
|
+
limitArg2 = args2.indexOf("--limit");
|
|
3962
|
+
LIMIT2 = limitArg2 !== -1 ? parseInt(args2[limitArg2 + 1] ?? "10", 10) : DEFAULT_LIMIT2;
|
|
3963
|
+
SHOW_ALL2 = args2.includes("--all");
|
|
3964
|
+
AS_PROJECT = args2.includes("--as-project");
|
|
3965
|
+
}
|
|
3966
|
+
});
|
|
3967
|
+
|
|
3968
|
+
// bin/jpi-project.js
|
|
3969
|
+
var jpi_project_exports = {};
|
|
3970
|
+
__export(jpi_project_exports, {
|
|
3971
|
+
run: () => run4
|
|
3972
|
+
});
|
|
3973
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5 } from "fs";
|
|
3974
|
+
import { join as join6 } from "path";
|
|
3975
|
+
import { homedir as homedir5 } from "os";
|
|
3976
|
+
import { createInterface as createInterface3 } from "readline";
|
|
3977
|
+
function readProject2() {
|
|
3978
|
+
try {
|
|
3979
|
+
return JSON.parse(readFileSync6(PROJECT_FILE2, "utf8"));
|
|
3980
|
+
} catch {
|
|
3981
|
+
return null;
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
function promptRaw(question) {
|
|
3985
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
3986
|
+
return new Promise((resolve2) => {
|
|
3987
|
+
rl.question(question, (answer) => {
|
|
3988
|
+
rl.close();
|
|
3989
|
+
resolve2(answer.trim());
|
|
3990
|
+
});
|
|
3991
|
+
});
|
|
3992
|
+
}
|
|
3993
|
+
function splitDeclaration(line) {
|
|
3994
|
+
const m = line.match(/\s*[::—]|\s-\s/);
|
|
3995
|
+
if (m && m.index !== void 0) {
|
|
3996
|
+
const title = line.slice(0, m.index).trim();
|
|
3997
|
+
const skillsRaw = line.slice(m.index + m[0].length).trim();
|
|
3998
|
+
return { title: title || line, skillsRaw: skillsRaw || line };
|
|
3999
|
+
}
|
|
4000
|
+
return { title: line, skillsRaw: line };
|
|
4001
|
+
}
|
|
4002
|
+
function tokenize4(skillsRaw) {
|
|
4003
|
+
return skillsRaw.split(/[,/]|\s+/).map((t) => t.trim()).filter(Boolean);
|
|
4004
|
+
}
|
|
4005
|
+
async function run4() {
|
|
4006
|
+
try {
|
|
4007
|
+
if (SHOW) {
|
|
4008
|
+
const existing = readProject2();
|
|
4009
|
+
if (!existing) {
|
|
4010
|
+
console.log("\u2726 terminalhire project: nothing declared yet.");
|
|
4011
|
+
console.log(' Run `terminalhire project "<title>: <skills it needs>"` to declare one.');
|
|
4012
|
+
return;
|
|
4013
|
+
}
|
|
4014
|
+
console.log(`
|
|
4015
|
+
\u2726 Your project (local only \u2014 never sent):
|
|
4016
|
+
`);
|
|
4017
|
+
console.log(` ${existing.title}`);
|
|
4018
|
+
console.log(` Skills: ${(existing.skillTags ?? []).join(", ") || "(none recognized)"}`);
|
|
4019
|
+
console.log(` Declared: ${existing.createdAt ?? "unknown"}`);
|
|
4020
|
+
console.log(`
|
|
4021
|
+
Rank builders for it: terminalhire devs --as-project`);
|
|
4022
|
+
return;
|
|
4023
|
+
}
|
|
4024
|
+
const { normalize: normalize2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
4025
|
+
let declaration = declarationArg;
|
|
4026
|
+
if (!declaration) {
|
|
4027
|
+
if (!process.stdin.isTTY) {
|
|
4028
|
+
console.log("\u2726 terminalhire project: declare what you are building, e.g.");
|
|
4029
|
+
console.log(' terminalhire project "Realtime collab editor: react, typescript, websockets, postgres"');
|
|
4030
|
+
return;
|
|
4031
|
+
}
|
|
4032
|
+
console.log("Declare your project in one line \u2014 a short title and the stack/skills it needs.");
|
|
4033
|
+
console.log("Example: Realtime collab editor: react, typescript, websockets, postgres\n");
|
|
4034
|
+
declaration = await promptRaw("Project: ");
|
|
4035
|
+
}
|
|
4036
|
+
if (!declaration) {
|
|
4037
|
+
console.log("Nothing declared \u2014 run again with a one-line project.");
|
|
4038
|
+
return;
|
|
4039
|
+
}
|
|
4040
|
+
const { title, skillsRaw } = splitDeclaration(declaration);
|
|
4041
|
+
const skillTags = normalize2(tokenize4(skillsRaw));
|
|
4042
|
+
if (skillTags.length === 0) {
|
|
4043
|
+
console.log("\n\u2726 No recognized skills in that declaration.");
|
|
4044
|
+
console.log(" Use known stack names (e.g. react, typescript, go, postgres, kubernetes).");
|
|
4045
|
+
console.log(" Nothing was saved.");
|
|
4046
|
+
return;
|
|
4047
|
+
}
|
|
4048
|
+
const project = {
|
|
4049
|
+
title,
|
|
4050
|
+
declaration,
|
|
4051
|
+
skillTags,
|
|
4052
|
+
prefs: {},
|
|
4053
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4054
|
+
};
|
|
4055
|
+
mkdirSync5(TERMINALHIRE_DIR5, { recursive: true });
|
|
4056
|
+
writeFileSync5(PROJECT_FILE2, JSON.stringify(project, null, 2), "utf8");
|
|
4057
|
+
console.log(`
|
|
4058
|
+
\u2726 Project saved locally (never sent): ${title}`);
|
|
4059
|
+
console.log(` Skills: ${skillTags.join(", ")}`);
|
|
4060
|
+
console.log(`
|
|
4061
|
+
Rank builders for it: terminalhire devs --as-project`);
|
|
4062
|
+
} catch (err) {
|
|
4063
|
+
console.error("terminalhire project error:", err.message ?? err);
|
|
4064
|
+
process.exit(1);
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
var TERMINALHIRE_DIR5, PROJECT_FILE2, args3, SHOW, declarationArg;
|
|
4068
|
+
var init_jpi_project = __esm({
|
|
4069
|
+
"bin/jpi-project.js"() {
|
|
4070
|
+
"use strict";
|
|
4071
|
+
TERMINALHIRE_DIR5 = join6(homedir5(), ".terminalhire");
|
|
4072
|
+
PROJECT_FILE2 = join6(TERMINALHIRE_DIR5, "project.json");
|
|
4073
|
+
args3 = process.argv.slice(2);
|
|
4074
|
+
SHOW = args3.includes("--show");
|
|
4075
|
+
declarationArg = args3.filter((a) => !a.startsWith("--")).join(" ").trim();
|
|
4076
|
+
}
|
|
4077
|
+
});
|
|
4078
|
+
|
|
4079
|
+
// bin/jpi-bounties.js
|
|
4080
|
+
var jpi_bounties_exports = {};
|
|
4081
|
+
__export(jpi_bounties_exports, {
|
|
4082
|
+
run: () => run5
|
|
4083
|
+
});
|
|
4084
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
|
|
4085
|
+
import { join as join7 } from "path";
|
|
4086
|
+
import { homedir as homedir6 } from "os";
|
|
4087
|
+
import { createInterface as createInterface4 } from "readline";
|
|
3555
4088
|
function readIndexCache2() {
|
|
3556
4089
|
try {
|
|
3557
|
-
const entry = JSON.parse(
|
|
3558
|
-
if (Date.now() - entry.ts <
|
|
4090
|
+
const entry = JSON.parse(readFileSync7(INDEX_CACHE_FILE2, "utf8"));
|
|
4091
|
+
if (Date.now() - entry.ts < INDEX_TTL_MS3) return entry.index;
|
|
3559
4092
|
return null;
|
|
3560
4093
|
} catch {
|
|
3561
4094
|
return null;
|
|
3562
4095
|
}
|
|
3563
4096
|
}
|
|
3564
4097
|
function writeIndexCache2(index) {
|
|
3565
|
-
|
|
3566
|
-
|
|
4098
|
+
mkdirSync6(TERMINALHIRE_DIR6, { recursive: true });
|
|
4099
|
+
writeFileSync6(INDEX_CACHE_FILE2, JSON.stringify({ ts: Date.now(), index }), "utf8");
|
|
3567
4100
|
}
|
|
3568
4101
|
async function fetchIndex2() {
|
|
3569
4102
|
const cached = readIndexCache2();
|
|
3570
4103
|
if (cached) return cached;
|
|
3571
|
-
const res = await fetch(`${
|
|
4104
|
+
const res = await fetch(`${API_URL3}/api/index`, { signal: AbortSignal.timeout(1e4) });
|
|
3572
4105
|
if (!res.ok) throw new Error(`/api/index returned ${res.status}`);
|
|
3573
4106
|
const index = await res.json();
|
|
3574
4107
|
writeIndexCache2(index);
|
|
3575
4108
|
return index;
|
|
3576
4109
|
}
|
|
3577
|
-
function
|
|
3578
|
-
const rl =
|
|
4110
|
+
function prompt3(question) {
|
|
4111
|
+
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
3579
4112
|
return new Promise((resolve2) => {
|
|
3580
4113
|
rl.question(question, (answer) => {
|
|
3581
4114
|
rl.close();
|
|
@@ -3586,7 +4119,7 @@ function prompt2(question) {
|
|
|
3586
4119
|
function formatAmount(b) {
|
|
3587
4120
|
return b.amountUSD != null ? "$" + b.amountUSD.toLocaleString() : "$\u2014";
|
|
3588
4121
|
}
|
|
3589
|
-
function
|
|
4122
|
+
function linkTitle3(title, url) {
|
|
3590
4123
|
const isTTY = process.stdout.isTTY;
|
|
3591
4124
|
const noColor = process.env["NO_COLOR"] !== void 0;
|
|
3592
4125
|
if (isTTY && !noColor && url) return `\x1B]8;;${url}\x1B\\${title}\x1B]8;;\x1B\\`;
|
|
@@ -3600,16 +4133,16 @@ function printBounty(i, job, score, reason, matchedTags) {
|
|
|
3600
4133
|
const prs = b.competingOpenPRs;
|
|
3601
4134
|
const contend = prs != null && prs > 0 ? ` \xB7 \u26A0 ${prs} PR${prs === 1 ? "" : "s"} in flight` : "";
|
|
3602
4135
|
console.log(`
|
|
3603
|
-
${i + 1}. ${
|
|
4136
|
+
${i + 1}. ${linkTitle3(job.title, job.url)}`);
|
|
3604
4137
|
console.log(` ${formatAmount(b)}${effort} \xB7 ${b.repoFullName ?? job.company}${stars}${scoreStr}${contend}`);
|
|
3605
4138
|
if (reason) console.log(` ${reason}`);
|
|
3606
4139
|
if (matchedTags && matchedTags.length) console.log(` Tags matched: ${matchedTags.slice(0, 5).join(", ")}`);
|
|
3607
4140
|
console.log(` id: ${job.id}`);
|
|
3608
4141
|
console.log(` Claim: ${b.claimUrl ?? job.url}`);
|
|
3609
4142
|
}
|
|
3610
|
-
async function
|
|
4143
|
+
async function run5() {
|
|
3611
4144
|
try {
|
|
3612
|
-
console.log(`Fetching bounty index from ${
|
|
4145
|
+
console.log(`Fetching bounty index from ${API_URL3}/api/index...`);
|
|
3613
4146
|
const index = await fetchIndex2();
|
|
3614
4147
|
let bounties = (index.jobs ?? []).filter((j) => j.source === "bounty");
|
|
3615
4148
|
if (PRICED_ONLY) bounties = bounties.filter((j) => j.bounty?.amountUSD != null);
|
|
@@ -3635,7 +4168,7 @@ async function run3() {
|
|
|
3635
4168
|
const amt = (j) => j.bounty?.amountUSD ?? -1;
|
|
3636
4169
|
const contested = (j) => (j.bounty?.competingOpenPRs ?? 0) > 0 ? 1 : 0;
|
|
3637
4170
|
bounties.sort((a, b) => contested(a) - contested(b) || score(b) - score(a) || amt(b) - amt(a));
|
|
3638
|
-
const shown =
|
|
4171
|
+
const shown = SHOW_ALL3 ? bounties : bounties.slice(0, LIMIT3);
|
|
3639
4172
|
const matchedCount = bounties.filter((j) => score(j) > 0).length;
|
|
3640
4173
|
console.log(
|
|
3641
4174
|
`
|
|
@@ -3646,13 +4179,13 @@ async function run3() {
|
|
|
3646
4179
|
const r = ranked.get(shown[i].id);
|
|
3647
4180
|
printBounty(i, shown[i], r?.score ?? 0, r?.reason, r?.matchedTags);
|
|
3648
4181
|
}
|
|
3649
|
-
if (!
|
|
4182
|
+
if (!SHOW_ALL3 && bounties.length > shown.length) {
|
|
3650
4183
|
console.log(`
|
|
3651
4184
|
\u2026and ${bounties.length - shown.length} more \u2014 run with --all to see every bounty.`);
|
|
3652
4185
|
}
|
|
3653
4186
|
if (!process.stdin.isTTY) return;
|
|
3654
4187
|
console.log("\n" + "\u2500".repeat(70));
|
|
3655
|
-
const pick = await
|
|
4188
|
+
const pick = await prompt3(`
|
|
3656
4189
|
Enter a number to open a bounty's claim page, or press Enter to exit: `);
|
|
3657
4190
|
const idx = parseInt(pick, 10) - 1;
|
|
3658
4191
|
if (Number.isNaN(idx) || idx < 0 || idx >= shown.length) return;
|
|
@@ -3667,21 +4200,21 @@ Open this to claim/work the bounty (you go straight to the source \u2014 we neve
|
|
|
3667
4200
|
process.exit(1);
|
|
3668
4201
|
}
|
|
3669
4202
|
}
|
|
3670
|
-
var
|
|
4203
|
+
var TERMINALHIRE_DIR6, INDEX_CACHE_FILE2, INDEX_TTL_MS3, API_URL3, DEFAULT_LIMIT3, args4, limitArg3, LIMIT3, PRICED_ONLY, SHOW_ALL3, WINNABLE_ONLY, EFFORT_LABEL;
|
|
3671
4204
|
var init_jpi_bounties = __esm({
|
|
3672
4205
|
"bin/jpi-bounties.js"() {
|
|
3673
4206
|
"use strict";
|
|
3674
|
-
|
|
3675
|
-
INDEX_CACHE_FILE2 =
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
PRICED_ONLY =
|
|
3683
|
-
|
|
3684
|
-
WINNABLE_ONLY =
|
|
4207
|
+
TERMINALHIRE_DIR6 = join7(homedir6(), ".terminalhire");
|
|
4208
|
+
INDEX_CACHE_FILE2 = join7(TERMINALHIRE_DIR6, "index-cache.json");
|
|
4209
|
+
INDEX_TTL_MS3 = 15 * 60 * 1e3;
|
|
4210
|
+
API_URL3 = process.env["TERMINALHIRE_API_URL"] ?? process.env["JPI_API_URL"] ?? "https://terminalhire.com";
|
|
4211
|
+
DEFAULT_LIMIT3 = 15;
|
|
4212
|
+
args4 = process.argv.slice(2);
|
|
4213
|
+
limitArg3 = args4.indexOf("--limit");
|
|
4214
|
+
LIMIT3 = limitArg3 !== -1 ? parseInt(args4[limitArg3 + 1] ?? "15", 10) : DEFAULT_LIMIT3;
|
|
4215
|
+
PRICED_ONLY = args4.includes("--priced");
|
|
4216
|
+
SHOW_ALL3 = args4.includes("--all");
|
|
4217
|
+
WINNABLE_ONLY = args4.includes("--winnable");
|
|
3685
4218
|
EFFORT_LABEL = { small: "small (~\xBD day)", medium: "medium (~1 day)", large: "large (multi-day)" };
|
|
3686
4219
|
}
|
|
3687
4220
|
});
|
|
@@ -3697,26 +4230,26 @@ __export(claims_exports, {
|
|
|
3697
4230
|
removeClaim: () => removeClaim,
|
|
3698
4231
|
updateClaim: () => updateClaim
|
|
3699
4232
|
});
|
|
3700
|
-
import { readFileSync as
|
|
3701
|
-
import { join as
|
|
3702
|
-
import { homedir as
|
|
4233
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, renameSync, existsSync as existsSync4 } from "fs";
|
|
4234
|
+
import { join as join8 } from "path";
|
|
4235
|
+
import { homedir as homedir7 } from "os";
|
|
3703
4236
|
function nowISO() {
|
|
3704
4237
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
3705
4238
|
}
|
|
3706
4239
|
function readClaims() {
|
|
3707
4240
|
try {
|
|
3708
4241
|
if (!existsSync4(CLAIMS_FILE)) return [];
|
|
3709
|
-
const data = JSON.parse(
|
|
4242
|
+
const data = JSON.parse(readFileSync8(CLAIMS_FILE, "utf8"));
|
|
3710
4243
|
return Array.isArray(data?.claims) ? data.claims : [];
|
|
3711
4244
|
} catch {
|
|
3712
4245
|
return [];
|
|
3713
4246
|
}
|
|
3714
4247
|
}
|
|
3715
4248
|
function writeClaims(claims) {
|
|
3716
|
-
|
|
4249
|
+
mkdirSync7(TERMINALHIRE_DIR7, { recursive: true });
|
|
3717
4250
|
const tmp = `${CLAIMS_FILE}.tmp`;
|
|
3718
4251
|
const payload = { claims };
|
|
3719
|
-
|
|
4252
|
+
writeFileSync7(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
3720
4253
|
renameSync(tmp, CLAIMS_FILE);
|
|
3721
4254
|
}
|
|
3722
4255
|
function findClaim(id) {
|
|
@@ -3769,12 +4302,12 @@ function acceptedPRRate(claims = readClaims()) {
|
|
|
3769
4302
|
const merged = claims.filter((c) => c.state === "merged").length;
|
|
3770
4303
|
return { merged, total, rate: total === 0 ? 0 : merged / total };
|
|
3771
4304
|
}
|
|
3772
|
-
var
|
|
4305
|
+
var TERMINALHIRE_DIR7, CLAIMS_FILE, TERMINAL_STATES;
|
|
3773
4306
|
var init_claims = __esm({
|
|
3774
4307
|
"src/claims.ts"() {
|
|
3775
4308
|
"use strict";
|
|
3776
|
-
|
|
3777
|
-
CLAIMS_FILE =
|
|
4309
|
+
TERMINALHIRE_DIR7 = join8(homedir7(), ".terminalhire");
|
|
4310
|
+
CLAIMS_FILE = join8(TERMINALHIRE_DIR7, "claims.json");
|
|
3778
4311
|
TERMINAL_STATES = /* @__PURE__ */ new Set(["merged", "abandoned"]);
|
|
3779
4312
|
}
|
|
3780
4313
|
});
|
|
@@ -3782,20 +4315,20 @@ var init_claims = __esm({
|
|
|
3782
4315
|
// bin/jpi-claim.js
|
|
3783
4316
|
var jpi_claim_exports = {};
|
|
3784
4317
|
__export(jpi_claim_exports, {
|
|
3785
|
-
run: () =>
|
|
4318
|
+
run: () => run6
|
|
3786
4319
|
});
|
|
3787
|
-
import { readFileSync as
|
|
3788
|
-
import { join as
|
|
3789
|
-
import { homedir as
|
|
4320
|
+
import { readFileSync as readFileSync9, existsSync as existsSync5 } from "fs";
|
|
4321
|
+
import { join as join9 } from "path";
|
|
4322
|
+
import { homedir as homedir8 } from "os";
|
|
3790
4323
|
import { execFile } from "child_process";
|
|
3791
4324
|
import { promisify } from "util";
|
|
3792
|
-
import { createInterface as
|
|
3793
|
-
async function sh(cmd,
|
|
3794
|
-
const { stdout } = await pExecFile(cmd,
|
|
4325
|
+
import { createInterface as createInterface5 } from "readline";
|
|
4326
|
+
async function sh(cmd, args5, opts = {}) {
|
|
4327
|
+
const { stdout } = await pExecFile(cmd, args5, { ...opts, shell: false, maxBuffer: 16 * 1024 * 1024 });
|
|
3795
4328
|
return String(stdout).trim();
|
|
3796
4329
|
}
|
|
3797
4330
|
async function confirm(question) {
|
|
3798
|
-
const rl =
|
|
4331
|
+
const rl = createInterface5({ input: process.stdin, output: process.stdout });
|
|
3799
4332
|
try {
|
|
3800
4333
|
const ans = await new Promise((resolve2) => rl.question(question, resolve2));
|
|
3801
4334
|
return /^y(es)?$/i.test(String(ans).trim());
|
|
@@ -3834,7 +4367,7 @@ function parseRepoFromRemote(url) {
|
|
|
3834
4367
|
function findBountyInCache(bountyId) {
|
|
3835
4368
|
try {
|
|
3836
4369
|
if (!existsSync5(INDEX_CACHE_FILE3)) return null;
|
|
3837
|
-
const entry = JSON.parse(
|
|
4370
|
+
const entry = JSON.parse(readFileSync9(INDEX_CACHE_FILE3, "utf8"));
|
|
3838
4371
|
const jobs = entry?.index?.jobs ?? [];
|
|
3839
4372
|
const job = jobs.find((j) => j.id === bountyId && j.source === "bounty");
|
|
3840
4373
|
return job ?? null;
|
|
@@ -4298,7 +4831,7 @@ async function cmdSubmit(id, worktreeOverride) {
|
|
|
4298
4831
|
\u2713 Submitted ${id} \u2192 ${prUrl}`);
|
|
4299
4832
|
console.log(` Run 'terminalhire claim status ${id}' after the maintainer acts to fold the merge into your accepted-PR rate.`);
|
|
4300
4833
|
}
|
|
4301
|
-
async function
|
|
4834
|
+
async function run6() {
|
|
4302
4835
|
const verb = process.argv[2];
|
|
4303
4836
|
const { flags, positional } = parseArgs(process.argv.slice(3));
|
|
4304
4837
|
const active = Boolean(flags.active);
|
|
@@ -4338,12 +4871,12 @@ async function run4() {
|
|
|
4338
4871
|
process.exit(1);
|
|
4339
4872
|
}
|
|
4340
4873
|
}
|
|
4341
|
-
var
|
|
4874
|
+
var TERMINALHIRE_DIR8, INDEX_CACHE_FILE3, GH_API, GH_HEADERS, pExecFile, VALUE_FLAGS;
|
|
4342
4875
|
var init_jpi_claim = __esm({
|
|
4343
4876
|
"bin/jpi-claim.js"() {
|
|
4344
4877
|
"use strict";
|
|
4345
|
-
|
|
4346
|
-
INDEX_CACHE_FILE3 =
|
|
4878
|
+
TERMINALHIRE_DIR8 = join9(homedir8(), ".terminalhire");
|
|
4879
|
+
INDEX_CACHE_FILE3 = join9(TERMINALHIRE_DIR8, "index-cache.json");
|
|
4347
4880
|
GH_API = "https://api.github.com";
|
|
4348
4881
|
GH_HEADERS = { "User-Agent": "terminalhire-claim", Accept: "application/vnd.github+json" };
|
|
4349
4882
|
pExecFile = promisify(execFile);
|
|
@@ -4627,7 +5160,7 @@ function finalize(build) {
|
|
|
4627
5160
|
};
|
|
4628
5161
|
}
|
|
4629
5162
|
function reconstruct(files, opts = {}) {
|
|
4630
|
-
const
|
|
5163
|
+
const join20 = opts.joinSidechains !== false;
|
|
4631
5164
|
const mains = [];
|
|
4632
5165
|
const sidechains = [];
|
|
4633
5166
|
for (const file of files) {
|
|
@@ -4652,7 +5185,7 @@ function reconstruct(files, opts = {}) {
|
|
|
4652
5185
|
}
|
|
4653
5186
|
const orphanedSidechainPaths = [];
|
|
4654
5187
|
const joinedPaths = /* @__PURE__ */ new Set();
|
|
4655
|
-
if (
|
|
5188
|
+
if (join20) {
|
|
4656
5189
|
const sidechainsBySession = /* @__PURE__ */ new Map();
|
|
4657
5190
|
for (const sc of sidechains) {
|
|
4658
5191
|
const acc = sidechainsBySession.get(sc.sessionId) ?? [];
|
|
@@ -5139,12 +5672,12 @@ function deriveRecoveryDepth(episodes, nodesByUuid) {
|
|
|
5139
5672
|
continue;
|
|
5140
5673
|
}
|
|
5141
5674
|
spansConsidered++;
|
|
5142
|
-
let
|
|
5675
|
+
let run17 = 0;
|
|
5143
5676
|
const closeChain = () => {
|
|
5144
|
-
if (
|
|
5677
|
+
if (run17 > 0) {
|
|
5145
5678
|
recoveryChains++;
|
|
5146
|
-
totalDepth +=
|
|
5147
|
-
|
|
5679
|
+
totalDepth += run17;
|
|
5680
|
+
run17 = 0;
|
|
5148
5681
|
}
|
|
5149
5682
|
};
|
|
5150
5683
|
for (const uuid of episode.mainNodeUuids) {
|
|
@@ -5153,9 +5686,9 @@ function deriveRecoveryDepth(episodes, nodesByUuid) {
|
|
|
5153
5686
|
continue;
|
|
5154
5687
|
}
|
|
5155
5688
|
if (node.kind === "tool_result" /* ToolResult */ && node.isError) {
|
|
5156
|
-
|
|
5157
|
-
if (
|
|
5158
|
-
maxConsecutiveErrors =
|
|
5689
|
+
run17++;
|
|
5690
|
+
if (run17 > maxConsecutiveErrors) {
|
|
5691
|
+
maxConsecutiveErrors = run17;
|
|
5159
5692
|
}
|
|
5160
5693
|
} else if (node.kind === "tool_result" /* ToolResult */ && !node.isError) {
|
|
5161
5694
|
closeChain();
|
|
@@ -5207,13 +5740,13 @@ __export(trajectory_exports, {
|
|
|
5207
5740
|
});
|
|
5208
5741
|
import {
|
|
5209
5742
|
existsSync as existsSync6,
|
|
5210
|
-
mkdirSync as
|
|
5211
|
-
readFileSync as
|
|
5743
|
+
mkdirSync as mkdirSync8,
|
|
5744
|
+
readFileSync as readFileSync10,
|
|
5212
5745
|
readdirSync,
|
|
5213
|
-
writeFileSync as
|
|
5746
|
+
writeFileSync as writeFileSync8
|
|
5214
5747
|
} from "fs";
|
|
5215
|
-
import { homedir as
|
|
5216
|
-
import { join as
|
|
5748
|
+
import { homedir as homedir9 } from "os";
|
|
5749
|
+
import { join as join10 } from "path";
|
|
5217
5750
|
function isRecord4(value) {
|
|
5218
5751
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5219
5752
|
}
|
|
@@ -5245,7 +5778,7 @@ function findJsonlFiles(dir) {
|
|
|
5245
5778
|
return out;
|
|
5246
5779
|
}
|
|
5247
5780
|
for (const entry of entries) {
|
|
5248
|
-
const full =
|
|
5781
|
+
const full = join10(dir, entry.name);
|
|
5249
5782
|
if (entry.isDirectory()) {
|
|
5250
5783
|
out.push(...findJsonlFiles(full));
|
|
5251
5784
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
@@ -5259,7 +5792,7 @@ function loadCorpus(paths) {
|
|
|
5259
5792
|
for (const path of paths) {
|
|
5260
5793
|
let text;
|
|
5261
5794
|
try {
|
|
5262
|
-
text =
|
|
5795
|
+
text = readFileSync10(path, "utf8");
|
|
5263
5796
|
} catch {
|
|
5264
5797
|
continue;
|
|
5265
5798
|
}
|
|
@@ -5358,12 +5891,12 @@ function renderMarkdown(view) {
|
|
|
5358
5891
|
return lines.join("\n");
|
|
5359
5892
|
}
|
|
5360
5893
|
function writeExportArtifacts(score, markdown) {
|
|
5361
|
-
const dir =
|
|
5362
|
-
|
|
5363
|
-
const jsonPath =
|
|
5364
|
-
const mdPath =
|
|
5365
|
-
|
|
5366
|
-
|
|
5894
|
+
const dir = join10(homedir9(), ".terminalhire");
|
|
5895
|
+
mkdirSync8(dir, { recursive: true });
|
|
5896
|
+
const jsonPath = join10(dir, "trajectory-export.json");
|
|
5897
|
+
const mdPath = join10(dir, "trajectory-export.md");
|
|
5898
|
+
writeFileSync8(jsonPath, JSON.stringify(score, null, 2) + "\n", "utf8");
|
|
5899
|
+
writeFileSync8(mdPath, markdown, "utf8");
|
|
5367
5900
|
return { jsonPath, mdPath };
|
|
5368
5901
|
}
|
|
5369
5902
|
function renderInward(allNodes, view, files) {
|
|
@@ -5382,7 +5915,7 @@ function renderInward(allNodes, view, files) {
|
|
|
5382
5915
|
console.log("");
|
|
5383
5916
|
}
|
|
5384
5917
|
function buildTrajectory() {
|
|
5385
|
-
const projectsDir =
|
|
5918
|
+
const projectsDir = join10(homedir9(), ".claude", "projects");
|
|
5386
5919
|
if (!existsSync6(projectsDir)) return null;
|
|
5387
5920
|
const paths = findJsonlFiles(projectsDir);
|
|
5388
5921
|
if (paths.length === 0) return null;
|
|
@@ -5456,8 +5989,8 @@ function defaultPushDeps() {
|
|
|
5456
5989
|
}
|
|
5457
5990
|
},
|
|
5458
5991
|
prompt: async (question) => {
|
|
5459
|
-
const { createInterface:
|
|
5460
|
-
const rl =
|
|
5992
|
+
const { createInterface: createInterface10 } = await import("readline");
|
|
5993
|
+
const rl = createInterface10({ input: process.stdin, output: process.stdout });
|
|
5461
5994
|
return new Promise((res) => {
|
|
5462
5995
|
rl.question(question, (answer) => {
|
|
5463
5996
|
rl.close();
|
|
@@ -5465,7 +5998,7 @@ function defaultPushDeps() {
|
|
|
5465
5998
|
});
|
|
5466
5999
|
});
|
|
5467
6000
|
},
|
|
5468
|
-
fetchImpl: (...
|
|
6001
|
+
fetchImpl: (...args5) => globalThis.fetch(...args5),
|
|
5469
6002
|
openBrowser: (url) => {
|
|
5470
6003
|
void Promise.resolve().then(() => (init_open_url(), open_url_exports)).then((m) => m.openInBrowser(url)).catch(() => {
|
|
5471
6004
|
});
|
|
@@ -5653,19 +6186,19 @@ var init_trajectory = __esm({
|
|
|
5653
6186
|
// bin/jpi-trajectory.js
|
|
5654
6187
|
var jpi_trajectory_exports = {};
|
|
5655
6188
|
__export(jpi_trajectory_exports, {
|
|
5656
|
-
run: () =>
|
|
6189
|
+
run: () => run7
|
|
5657
6190
|
});
|
|
5658
|
-
async function
|
|
6191
|
+
async function run7() {
|
|
5659
6192
|
try {
|
|
5660
|
-
const
|
|
5661
|
-
if (
|
|
5662
|
-
const doDelete =
|
|
6193
|
+
const args5 = process.argv.slice(2);
|
|
6194
|
+
if (args5.includes("--push")) {
|
|
6195
|
+
const doDelete = args5.includes("--delete") || args5.includes("--unlink");
|
|
5663
6196
|
const { runTrajectoryPush: runTrajectoryPush2 } = await Promise.resolve().then(() => (init_trajectory(), trajectory_exports));
|
|
5664
6197
|
await runTrajectoryPush2({ delete: doDelete });
|
|
5665
6198
|
return;
|
|
5666
6199
|
}
|
|
5667
|
-
const doExport =
|
|
5668
|
-
const inward =
|
|
6200
|
+
const doExport = args5.includes("--export");
|
|
6201
|
+
const inward = args5.includes("--inward");
|
|
5669
6202
|
const { runTrajectory: runTrajectory2 } = await Promise.resolve().then(() => (init_trajectory(), trajectory_exports));
|
|
5670
6203
|
await runTrajectory2({ export: doExport, inward });
|
|
5671
6204
|
} catch (err) {
|
|
@@ -5679,25 +6212,402 @@ var init_jpi_trajectory = __esm({
|
|
|
5679
6212
|
}
|
|
5680
6213
|
});
|
|
5681
6214
|
|
|
5682
|
-
//
|
|
5683
|
-
var
|
|
5684
|
-
__export(
|
|
5685
|
-
|
|
6215
|
+
// src/intro.ts
|
|
6216
|
+
var intro_exports = {};
|
|
6217
|
+
__export(intro_exports, {
|
|
6218
|
+
runIntroDecision: () => runIntroDecision,
|
|
6219
|
+
runIntroList: () => runIntroList,
|
|
6220
|
+
runIntroRequest: () => runIntroRequest
|
|
5686
6221
|
});
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
}
|
|
5697
|
-
|
|
6222
|
+
function defaultIntroDeps() {
|
|
6223
|
+
return {
|
|
6224
|
+
readGithubLogin: async () => {
|
|
6225
|
+
try {
|
|
6226
|
+
const { readProfile: readProfile2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
|
|
6227
|
+
const profile = await readProfile2();
|
|
6228
|
+
return profile?.github?.login ?? null;
|
|
6229
|
+
} catch {
|
|
6230
|
+
return null;
|
|
6231
|
+
}
|
|
6232
|
+
},
|
|
6233
|
+
readProfileContact: async () => {
|
|
6234
|
+
try {
|
|
6235
|
+
const { readProfile: readProfile2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
|
|
6236
|
+
const profile = await readProfile2();
|
|
6237
|
+
return { displayName: profile?.displayName, contactEmail: profile?.contactEmail };
|
|
6238
|
+
} catch {
|
|
6239
|
+
return {};
|
|
6240
|
+
}
|
|
6241
|
+
},
|
|
6242
|
+
prompt: async (question) => {
|
|
6243
|
+
const { createInterface: createInterface10 } = await import("readline");
|
|
6244
|
+
const rl = createInterface10({ input: process.stdin, output: process.stdout });
|
|
6245
|
+
return new Promise((res) => {
|
|
6246
|
+
rl.question(question, (answer) => {
|
|
6247
|
+
rl.close();
|
|
6248
|
+
res(answer.trim().toLowerCase());
|
|
6249
|
+
});
|
|
6250
|
+
});
|
|
6251
|
+
},
|
|
6252
|
+
fetchImpl: (...args5) => globalThis.fetch(...args5),
|
|
6253
|
+
openBrowser: (url) => {
|
|
6254
|
+
void Promise.resolve().then(() => (init_open_url(), open_url_exports)).then((m) => m.openInBrowser(url)).catch(() => {
|
|
6255
|
+
});
|
|
6256
|
+
},
|
|
6257
|
+
sessionCookie: () => {
|
|
6258
|
+
const v = process.env["TERMINALHIRE_WEB_SESSION"];
|
|
6259
|
+
return typeof v === "string" && v.length > 0 ? v : null;
|
|
6260
|
+
},
|
|
6261
|
+
log: (msg) => console.log(msg),
|
|
6262
|
+
errorLog: (msg) => console.error(msg),
|
|
6263
|
+
exit: (code) => process.exit(code)
|
|
6264
|
+
};
|
|
6265
|
+
}
|
|
6266
|
+
function renderConsentCard2(payload, deps) {
|
|
6267
|
+
const { log } = deps;
|
|
6268
|
+
log("");
|
|
6269
|
+
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");
|
|
6270
|
+
log("\u2502 terminalhire \u2014 request an intro (opt-in) \u2502");
|
|
6271
|
+
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");
|
|
6272
|
+
log("");
|
|
6273
|
+
log(" This sends the following \u2014 and ONLY the following \u2014 to terminalhire,");
|
|
6274
|
+
log(` to ask @${payload.targetLogin} for an intro:`);
|
|
6275
|
+
log("");
|
|
6276
|
+
log(` Your GitHub login : @${payload.requesterLogin}`);
|
|
6277
|
+
log(` Your display name : ${payload.requesterDisplayName}`);
|
|
6278
|
+
log(` Your contact : ${payload.requesterContact}`);
|
|
6279
|
+
log(` Note : ${payload.note ?? "(none)"}`);
|
|
6280
|
+
log(` To developer : @${payload.targetLogin}`);
|
|
6281
|
+
log("");
|
|
6282
|
+
log(" What is NEVER sent: your fingerprint, trajectory, repos, or any other");
|
|
6283
|
+
log(" profile field. @" + payload.targetLogin + " sees only your public login");
|
|
6284
|
+
log(" until they accept \u2014 your contact is shared only on their acceptance.");
|
|
6285
|
+
log("");
|
|
6286
|
+
}
|
|
6287
|
+
async function runIntroRequest(args5, overrides) {
|
|
6288
|
+
const deps = { ...defaultIntroDeps(), ...overrides };
|
|
6289
|
+
const targetLogin = args5.targetLogin?.trim();
|
|
6290
|
+
if (!targetLogin) {
|
|
6291
|
+
deps.errorLog('\n Usage: terminalhire intro <github-login> [--note "..."] [--contact <email>] [--name "..."]\n');
|
|
6292
|
+
deps.exit(1);
|
|
6293
|
+
return;
|
|
6294
|
+
}
|
|
6295
|
+
const requesterLogin = await deps.readGithubLogin();
|
|
6296
|
+
if (!requesterLogin) {
|
|
6297
|
+
deps.log("\n Not signed in. Run `terminalhire login` first, then re-run this command.\n");
|
|
6298
|
+
deps.exit(1);
|
|
6299
|
+
return;
|
|
6300
|
+
}
|
|
6301
|
+
if (targetLogin.toLowerCase() === requesterLogin.toLowerCase()) {
|
|
6302
|
+
deps.errorLog("\n You cannot request an intro to yourself.\n");
|
|
6303
|
+
deps.exit(1);
|
|
6304
|
+
return;
|
|
6305
|
+
}
|
|
6306
|
+
const profile = await deps.readProfileContact();
|
|
6307
|
+
const displayName = (args5.name ?? profile.displayName ?? requesterLogin).trim();
|
|
6308
|
+
const contact = (args5.contact ?? profile.contactEmail ?? "").trim();
|
|
6309
|
+
if (!contact) {
|
|
6310
|
+
deps.errorLog("\n No contact on file. Set one with `terminalhire profile --edit`,");
|
|
6311
|
+
deps.errorLog(" or pass `--contact <email-or-handle>`. Nothing was sent.\n");
|
|
6312
|
+
deps.exit(1);
|
|
6313
|
+
return;
|
|
6314
|
+
}
|
|
6315
|
+
const payload = buildIntroPayload({
|
|
6316
|
+
requesterLogin,
|
|
6317
|
+
requesterDisplayName: displayName,
|
|
6318
|
+
requesterContact: contact,
|
|
6319
|
+
targetLogin,
|
|
6320
|
+
note: args5.note
|
|
6321
|
+
});
|
|
6322
|
+
renderConsentCard2(payload, deps);
|
|
6323
|
+
const answer = await deps.prompt(' Type "yes" to send this intro request (anything else cancels): ');
|
|
6324
|
+
if (answer !== "yes") {
|
|
6325
|
+
deps.log("\n Cancelled \u2014 nothing was sent.\n");
|
|
6326
|
+
deps.exit(0);
|
|
6327
|
+
return;
|
|
6328
|
+
}
|
|
6329
|
+
const cookie = deps.sessionCookie();
|
|
6330
|
+
if (!cookie) {
|
|
6331
|
+
deps.log("\n No linked web session found on this machine.");
|
|
6332
|
+
deps.log(" Sign in at your dashboard first, then re-run with a bridged session.");
|
|
6333
|
+
deps.log(` \u2192 ${LINK_BASE2}/dashboard
|
|
6334
|
+
`);
|
|
6335
|
+
deps.openBrowser(`${LINK_BASE2}/dashboard`);
|
|
6336
|
+
deps.exit(0);
|
|
6337
|
+
return;
|
|
6338
|
+
}
|
|
6339
|
+
let res;
|
|
6340
|
+
try {
|
|
6341
|
+
res = await deps.fetchImpl(`${LINK_BASE2}/api/intro/request`, {
|
|
6342
|
+
method: "POST",
|
|
6343
|
+
headers: { "Content-Type": "application/json", Cookie: `${GH_SESSION_COOKIE2}=${cookie}` },
|
|
6344
|
+
body: JSON.stringify(payload),
|
|
6345
|
+
signal: AbortSignal.timeout(1e4)
|
|
6346
|
+
});
|
|
6347
|
+
} catch (err) {
|
|
6348
|
+
deps.errorLog(`
|
|
6349
|
+
Request failed: ${err instanceof Error ? err.message : String(err)}
|
|
6350
|
+
`);
|
|
6351
|
+
deps.exit(1);
|
|
6352
|
+
return;
|
|
6353
|
+
}
|
|
6354
|
+
if (res.status === 401) {
|
|
6355
|
+
deps.log("\n Your web session expired \u2014 sign in again at your dashboard, then re-run.");
|
|
6356
|
+
deps.log(` \u2192 ${LINK_BASE2}/dashboard
|
|
6357
|
+
`);
|
|
6358
|
+
deps.exit(1);
|
|
6359
|
+
return;
|
|
6360
|
+
}
|
|
6361
|
+
if (res.status === 429) {
|
|
6362
|
+
deps.errorLog("\n Too many intro requests right now \u2014 please try again later.\n");
|
|
6363
|
+
deps.exit(1);
|
|
6364
|
+
return;
|
|
6365
|
+
}
|
|
6366
|
+
if (!res.ok) {
|
|
6367
|
+
deps.errorLog(`
|
|
6368
|
+
Request failed: /api/intro/request returned ${res.status}.
|
|
6369
|
+
`);
|
|
6370
|
+
deps.exit(1);
|
|
6371
|
+
return;
|
|
6372
|
+
}
|
|
6373
|
+
deps.log(`
|
|
6374
|
+
Intro request sent to @${targetLogin}. They will see only your public login`);
|
|
6375
|
+
deps.log(" until they accept; your contact is shared only if they do.\n");
|
|
6376
|
+
}
|
|
6377
|
+
async function runIntroDecision(args5, overrides) {
|
|
6378
|
+
const deps = { ...defaultIntroDeps(), ...overrides };
|
|
6379
|
+
const id = args5.id?.trim();
|
|
6380
|
+
if (!id) {
|
|
6381
|
+
deps.errorLog("\n Usage: terminalhire intro --accept <id> | --decline <id>\n");
|
|
6382
|
+
deps.exit(1);
|
|
6383
|
+
return;
|
|
6384
|
+
}
|
|
6385
|
+
const cookie = deps.sessionCookie();
|
|
6386
|
+
if (!cookie) {
|
|
6387
|
+
deps.log("\n No linked web session found on this machine.");
|
|
6388
|
+
deps.log(" Sign in at your dashboard first, then re-run.");
|
|
6389
|
+
deps.log(` \u2192 ${LINK_BASE2}/dashboard
|
|
6390
|
+
`);
|
|
6391
|
+
deps.openBrowser(`${LINK_BASE2}/dashboard`);
|
|
6392
|
+
deps.exit(0);
|
|
6393
|
+
return;
|
|
6394
|
+
}
|
|
6395
|
+
let contact = "";
|
|
6396
|
+
if (args5.action === "accept") {
|
|
6397
|
+
const profile = await deps.readProfileContact();
|
|
6398
|
+
contact = (args5.contact ?? profile.contactEmail ?? "").trim();
|
|
6399
|
+
if (!contact) {
|
|
6400
|
+
deps.errorLog("\n No contact on file to share back. Set one with `terminalhire profile --edit`,");
|
|
6401
|
+
deps.errorLog(" or pass `--contact <email-or-handle>`. Nothing was sent.\n");
|
|
6402
|
+
deps.exit(1);
|
|
6403
|
+
return;
|
|
6404
|
+
}
|
|
6405
|
+
deps.log("");
|
|
6406
|
+
deps.log(" Accepting shares your contact with the requester so they can reach you:");
|
|
6407
|
+
deps.log(` Your contact : ${contact}`);
|
|
6408
|
+
deps.log(" Nothing else is shared. Declining shares nothing.");
|
|
6409
|
+
deps.log("");
|
|
6410
|
+
const answer = await deps.prompt(' Type "yes" to accept and share your contact (anything else cancels): ');
|
|
6411
|
+
if (answer !== "yes") {
|
|
6412
|
+
deps.log("\n Cancelled \u2014 nothing was sent.\n");
|
|
6413
|
+
deps.exit(0);
|
|
6414
|
+
return;
|
|
6415
|
+
}
|
|
6416
|
+
}
|
|
6417
|
+
const body = args5.action === "accept" ? { introId: id, action: "accept", targetContact: contact } : { introId: id, action: "decline" };
|
|
6418
|
+
let res;
|
|
6419
|
+
try {
|
|
6420
|
+
res = await deps.fetchImpl(`${LINK_BASE2}/api/intro/accept`, {
|
|
6421
|
+
method: "POST",
|
|
6422
|
+
headers: { "Content-Type": "application/json", Cookie: `${GH_SESSION_COOKIE2}=${cookie}` },
|
|
6423
|
+
body: JSON.stringify(body),
|
|
6424
|
+
signal: AbortSignal.timeout(1e4)
|
|
6425
|
+
});
|
|
6426
|
+
} catch (err) {
|
|
6427
|
+
deps.errorLog(`
|
|
6428
|
+
Request failed: ${err instanceof Error ? err.message : String(err)}
|
|
6429
|
+
`);
|
|
6430
|
+
deps.exit(1);
|
|
6431
|
+
return;
|
|
6432
|
+
}
|
|
6433
|
+
if (res.status === 401) {
|
|
6434
|
+
deps.log("\n Your web session expired \u2014 sign in again at your dashboard, then re-run.");
|
|
6435
|
+
deps.log(` \u2192 ${LINK_BASE2}/dashboard
|
|
6436
|
+
`);
|
|
6437
|
+
deps.exit(1);
|
|
6438
|
+
return;
|
|
6439
|
+
}
|
|
6440
|
+
if (res.status === 403) {
|
|
6441
|
+
deps.errorLog("\n You are not the developer this intro was sent to.\n");
|
|
6442
|
+
deps.exit(1);
|
|
6443
|
+
return;
|
|
6444
|
+
}
|
|
6445
|
+
if (res.status === 404) {
|
|
6446
|
+
deps.errorLog("\n Intro not found.\n");
|
|
6447
|
+
deps.exit(1);
|
|
6448
|
+
return;
|
|
6449
|
+
}
|
|
6450
|
+
if (!res.ok) {
|
|
6451
|
+
deps.errorLog(`
|
|
6452
|
+
Request failed: /api/intro/accept returned ${res.status}.
|
|
6453
|
+
`);
|
|
6454
|
+
deps.exit(1);
|
|
6455
|
+
return;
|
|
6456
|
+
}
|
|
6457
|
+
let data = {};
|
|
6458
|
+
try {
|
|
6459
|
+
data = await res.json();
|
|
6460
|
+
} catch {
|
|
6461
|
+
}
|
|
6462
|
+
if (args5.action === "decline") {
|
|
6463
|
+
deps.log("\n Declined \u2014 no contact was shared.\n");
|
|
6464
|
+
return;
|
|
6465
|
+
}
|
|
6466
|
+
deps.log("\n Accepted. Your contact was shared with the requester.");
|
|
6467
|
+
if (data.contact) deps.log(` You can reach them at: ${data.contact}`);
|
|
6468
|
+
deps.log("");
|
|
6469
|
+
}
|
|
6470
|
+
async function runIntroList(overrides) {
|
|
6471
|
+
const deps = { ...defaultIntroDeps(), ...overrides };
|
|
6472
|
+
const cookie = deps.sessionCookie();
|
|
6473
|
+
if (!cookie) {
|
|
6474
|
+
deps.log("\n No linked web session found on this machine.");
|
|
6475
|
+
deps.log(" Sign in at your dashboard first, then re-run.");
|
|
6476
|
+
deps.log(` \u2192 ${LINK_BASE2}/dashboard
|
|
6477
|
+
`);
|
|
6478
|
+
deps.openBrowser(`${LINK_BASE2}/dashboard`);
|
|
6479
|
+
deps.exit(0);
|
|
6480
|
+
return;
|
|
6481
|
+
}
|
|
6482
|
+
let res;
|
|
6483
|
+
try {
|
|
6484
|
+
res = await deps.fetchImpl(`${LINK_BASE2}/api/intro/list`, {
|
|
6485
|
+
method: "GET",
|
|
6486
|
+
headers: { Cookie: `${GH_SESSION_COOKIE2}=${cookie}` },
|
|
6487
|
+
signal: AbortSignal.timeout(1e4)
|
|
6488
|
+
});
|
|
6489
|
+
} catch (err) {
|
|
6490
|
+
deps.errorLog(`
|
|
6491
|
+
Request failed: ${err instanceof Error ? err.message : String(err)}
|
|
6492
|
+
`);
|
|
6493
|
+
deps.exit(1);
|
|
6494
|
+
return;
|
|
6495
|
+
}
|
|
6496
|
+
if (res.status === 401) {
|
|
6497
|
+
deps.log("\n Your web session expired \u2014 sign in again at your dashboard, then re-run.");
|
|
6498
|
+
deps.log(` \u2192 ${LINK_BASE2}/dashboard
|
|
6499
|
+
`);
|
|
6500
|
+
deps.exit(1);
|
|
6501
|
+
return;
|
|
6502
|
+
}
|
|
6503
|
+
if (!res.ok) {
|
|
6504
|
+
deps.errorLog(`
|
|
6505
|
+
Request failed: /api/intro/list returned ${res.status}.
|
|
6506
|
+
`);
|
|
6507
|
+
deps.exit(1);
|
|
6508
|
+
return;
|
|
6509
|
+
}
|
|
6510
|
+
let data = {};
|
|
6511
|
+
try {
|
|
6512
|
+
data = await res.json();
|
|
6513
|
+
} catch {
|
|
6514
|
+
}
|
|
6515
|
+
const intros = data.intros ?? [];
|
|
6516
|
+
if (intros.length === 0) {
|
|
6517
|
+
deps.log("\n No intros yet.\n");
|
|
6518
|
+
return;
|
|
6519
|
+
}
|
|
6520
|
+
deps.log("");
|
|
6521
|
+
for (const it of intros) {
|
|
6522
|
+
const dir = it.role === "incoming" ? "from" : "to";
|
|
6523
|
+
deps.log(` [${it.status}] ${dir} @${it.counterpartyLogin}`);
|
|
6524
|
+
if (it.note) deps.log(` note: ${it.note}`);
|
|
6525
|
+
if (it.contact) deps.log(` contact: ${it.contact}`);
|
|
6526
|
+
else if (it.role === "incoming" && it.status === "pending") {
|
|
6527
|
+
deps.log(` \u2192 accept: terminalhire intro --accept ${it.id}`);
|
|
6528
|
+
}
|
|
6529
|
+
}
|
|
6530
|
+
deps.log("");
|
|
6531
|
+
}
|
|
6532
|
+
var LINK_BASE2, GH_SESSION_COOKIE2;
|
|
6533
|
+
var init_intro2 = __esm({
|
|
6534
|
+
"src/intro.ts"() {
|
|
6535
|
+
"use strict";
|
|
6536
|
+
init_src();
|
|
6537
|
+
LINK_BASE2 = process.env["TERMINALHIRE_API_URL"] || "https://www.terminalhire.com";
|
|
6538
|
+
GH_SESSION_COOKIE2 = "__jpi_gh_session";
|
|
6539
|
+
}
|
|
6540
|
+
});
|
|
6541
|
+
|
|
6542
|
+
// bin/jpi-intro.js
|
|
6543
|
+
var jpi_intro_exports = {};
|
|
6544
|
+
__export(jpi_intro_exports, {
|
|
6545
|
+
run: () => run8
|
|
6546
|
+
});
|
|
6547
|
+
function readOption(args5, name) {
|
|
6548
|
+
const i = args5.indexOf(name);
|
|
6549
|
+
if (i === -1) return void 0;
|
|
6550
|
+
const v = args5[i + 1];
|
|
6551
|
+
return typeof v === "string" && !v.startsWith("--") ? v : void 0;
|
|
6552
|
+
}
|
|
6553
|
+
async function run8() {
|
|
6554
|
+
try {
|
|
6555
|
+
const args5 = process.argv.slice(2);
|
|
6556
|
+
if (args5.includes("--list")) {
|
|
6557
|
+
const { runIntroList: runIntroList2 } = await Promise.resolve().then(() => (init_intro2(), intro_exports));
|
|
6558
|
+
await runIntroList2();
|
|
6559
|
+
return;
|
|
6560
|
+
}
|
|
6561
|
+
const acceptId = readOption(args5, "--accept");
|
|
6562
|
+
const declineId = readOption(args5, "--decline");
|
|
6563
|
+
if (acceptId || declineId) {
|
|
6564
|
+
const contact2 = readOption(args5, "--contact");
|
|
6565
|
+
const name2 = readOption(args5, "--name");
|
|
6566
|
+
const { runIntroDecision: runIntroDecision2 } = await Promise.resolve().then(() => (init_intro2(), intro_exports));
|
|
6567
|
+
await runIntroDecision2({
|
|
6568
|
+
id: acceptId ?? declineId,
|
|
6569
|
+
action: acceptId ? "accept" : "decline",
|
|
6570
|
+
contact: contact2,
|
|
6571
|
+
name: name2
|
|
6572
|
+
});
|
|
6573
|
+
return;
|
|
6574
|
+
}
|
|
6575
|
+
const targetLogin = args5.find((a) => !a.startsWith("--"));
|
|
6576
|
+
const note = readOption(args5, "--note");
|
|
6577
|
+
const contact = readOption(args5, "--contact");
|
|
6578
|
+
const name = readOption(args5, "--name");
|
|
6579
|
+
const { runIntroRequest: runIntroRequest2 } = await Promise.resolve().then(() => (init_intro2(), intro_exports));
|
|
6580
|
+
await runIntroRequest2({ targetLogin, note, contact, name });
|
|
6581
|
+
} catch (err) {
|
|
6582
|
+
console.error("terminalhire intro error:", err?.message ?? err);
|
|
6583
|
+
process.exit(1);
|
|
6584
|
+
}
|
|
6585
|
+
}
|
|
6586
|
+
var init_jpi_intro = __esm({
|
|
6587
|
+
"bin/jpi-intro.js"() {
|
|
6588
|
+
"use strict";
|
|
6589
|
+
}
|
|
6590
|
+
});
|
|
6591
|
+
|
|
6592
|
+
// bin/jpi-profile.js
|
|
6593
|
+
var jpi_profile_exports = {};
|
|
6594
|
+
__export(jpi_profile_exports, {
|
|
6595
|
+
run: () => run9
|
|
6596
|
+
});
|
|
6597
|
+
import { createInterface as createInterface6 } from "readline";
|
|
6598
|
+
function prompt4(question) {
|
|
6599
|
+
const rl = createInterface6({ input: process.stdin, output: process.stdout });
|
|
6600
|
+
return new Promise((resolve2) => {
|
|
6601
|
+
rl.question(question, (answer) => {
|
|
6602
|
+
rl.close();
|
|
6603
|
+
resolve2(answer.trim());
|
|
6604
|
+
});
|
|
6605
|
+
});
|
|
6606
|
+
}
|
|
6607
|
+
async function run9() {
|
|
5698
6608
|
const { readProfile: readProfile2, writeProfile: writeProfile2, deleteProfile: deleteProfile2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
|
|
5699
|
-
const
|
|
5700
|
-
if (
|
|
6609
|
+
const args5 = process.argv.slice(2);
|
|
6610
|
+
if (args5.includes("--show")) {
|
|
5701
6611
|
const profile = await readProfile2();
|
|
5702
6612
|
console.log("\n\u2726 terminalhire local profile (encrypted at rest \u2014 shown here for your review only)\n");
|
|
5703
6613
|
console.log(" Skill tags: " + (profile.skillTags.length > 0 ? profile.skillTags.join(", ") : "(none yet)"));
|
|
@@ -5727,9 +6637,9 @@ async function run6() {
|
|
|
5727
6637
|
console.log("\nThis profile NEVER leaves your machine except in a consented lead payload.");
|
|
5728
6638
|
return;
|
|
5729
6639
|
}
|
|
5730
|
-
if (
|
|
6640
|
+
if (args5.includes("--delete")) {
|
|
5731
6641
|
console.log("\nThis will permanently delete your local terminalhire profile and encryption key.");
|
|
5732
|
-
const answer = await
|
|
6642
|
+
const answer = await prompt4('Type "yes" to confirm: ');
|
|
5733
6643
|
if (answer !== "yes") {
|
|
5734
6644
|
console.log("Aborted.");
|
|
5735
6645
|
process.exit(0);
|
|
@@ -5738,17 +6648,17 @@ async function run6() {
|
|
|
5738
6648
|
console.log("Profile and key deleted from ~/.terminalhire/");
|
|
5739
6649
|
return;
|
|
5740
6650
|
}
|
|
5741
|
-
if (
|
|
6651
|
+
if (args5.includes("--edit")) {
|
|
5742
6652
|
const profile = await readProfile2();
|
|
5743
6653
|
console.log("\n\u2726 terminalhire profile editor (press Enter to keep current value)\n");
|
|
5744
|
-
const name = await
|
|
6654
|
+
const name = await prompt4(`Display name [${profile.displayName ?? "not set"}]: `);
|
|
5745
6655
|
if (name) profile.displayName = name;
|
|
5746
|
-
const email = await
|
|
6656
|
+
const email = await prompt4(`Contact email [${profile.contactEmail ?? "not set"}]: `);
|
|
5747
6657
|
if (email) profile.contactEmail = email;
|
|
5748
|
-
const remote = await
|
|
6658
|
+
const remote = await prompt4(`Remote only? (y/n) [${profile.remoteOnly ? "y" : "n"}]: `);
|
|
5749
6659
|
if (remote === "y") profile.remoteOnly = true;
|
|
5750
6660
|
if (remote === "n") profile.remoteOnly = false;
|
|
5751
|
-
const floor = await
|
|
6661
|
+
const floor = await prompt4(`Comp floor USD [${profile.compFloorUsd ?? "not set"}]: `);
|
|
5752
6662
|
if (floor && !isNaN(parseInt(floor, 10))) profile.compFloorUsd = parseInt(floor, 10);
|
|
5753
6663
|
await writeProfile2(profile);
|
|
5754
6664
|
console.log("\nProfile updated (encrypted at ~/.terminalhire/profile.enc)");
|
|
@@ -5767,12 +6677,12 @@ var signal_exports = {};
|
|
|
5767
6677
|
__export(signal_exports, {
|
|
5768
6678
|
extractFingerprint: () => extractFingerprint
|
|
5769
6679
|
});
|
|
5770
|
-
import { readFileSync as
|
|
6680
|
+
import { readFileSync as readFileSync11, readdirSync as readdirSync2 } from "fs";
|
|
5771
6681
|
import { execFileSync } from "child_process";
|
|
5772
|
-
import { join as
|
|
5773
|
-
function safeGit(
|
|
6682
|
+
import { join as join11 } from "path";
|
|
6683
|
+
function safeGit(args5, cwd) {
|
|
5774
6684
|
try {
|
|
5775
|
-
return execFileSync("git", ["-C", cwd, ...
|
|
6685
|
+
return execFileSync("git", ["-C", cwd, ...args5], {
|
|
5776
6686
|
timeout: 2e3,
|
|
5777
6687
|
stdio: ["ignore", "pipe", "ignore"]
|
|
5778
6688
|
}).toString().trim();
|
|
@@ -5797,20 +6707,20 @@ function isEmployerContext(cwd) {
|
|
|
5797
6707
|
}
|
|
5798
6708
|
function readJsonSafe(path) {
|
|
5799
6709
|
try {
|
|
5800
|
-
return JSON.parse(
|
|
6710
|
+
return JSON.parse(readFileSync11(path, "utf8"));
|
|
5801
6711
|
} catch {
|
|
5802
6712
|
return null;
|
|
5803
6713
|
}
|
|
5804
6714
|
}
|
|
5805
6715
|
function readFileSafe(path) {
|
|
5806
6716
|
try {
|
|
5807
|
-
return
|
|
6717
|
+
return readFileSync11(path, "utf8");
|
|
5808
6718
|
} catch {
|
|
5809
6719
|
return "";
|
|
5810
6720
|
}
|
|
5811
6721
|
}
|
|
5812
6722
|
function tokensFromPackageJson(cwd) {
|
|
5813
|
-
const pkg = readJsonSafe(
|
|
6723
|
+
const pkg = readJsonSafe(join11(cwd, "package.json"));
|
|
5814
6724
|
if (!pkg || typeof pkg !== "object") return [];
|
|
5815
6725
|
const p = pkg;
|
|
5816
6726
|
const deps = {
|
|
@@ -5824,9 +6734,9 @@ function workspaceMemberDirs(cwd) {
|
|
|
5824
6734
|
const dirs = [cwd];
|
|
5825
6735
|
for (const group of ["apps", "packages"]) {
|
|
5826
6736
|
try {
|
|
5827
|
-
const groupDir =
|
|
6737
|
+
const groupDir = join11(cwd, group);
|
|
5828
6738
|
for (const e of readdirSync2(groupDir, { withFileTypes: true })) {
|
|
5829
|
-
if (e.isDirectory() && !e.isSymbolicLink()) dirs.push(
|
|
6739
|
+
if (e.isDirectory() && !e.isSymbolicLink()) dirs.push(join11(groupDir, e.name));
|
|
5830
6740
|
}
|
|
5831
6741
|
} catch {
|
|
5832
6742
|
}
|
|
@@ -5834,18 +6744,18 @@ function workspaceMemberDirs(cwd) {
|
|
|
5834
6744
|
return dirs;
|
|
5835
6745
|
}
|
|
5836
6746
|
function tokensFromRequirementsTxt(cwd) {
|
|
5837
|
-
const content = readFileSafe(
|
|
6747
|
+
const content = readFileSafe(join11(cwd, "requirements.txt"));
|
|
5838
6748
|
if (!content) return [];
|
|
5839
6749
|
return content.split("\n").map((l) => l.trim().split(/[>=<!\[;]/)[0].trim().toLowerCase()).filter(Boolean);
|
|
5840
6750
|
}
|
|
5841
6751
|
function tokensFromGoMod(cwd) {
|
|
5842
|
-
const content = readFileSafe(
|
|
6752
|
+
const content = readFileSafe(join11(cwd, "go.mod"));
|
|
5843
6753
|
if (!content) return [];
|
|
5844
6754
|
const requires = Array.from(content.matchAll(/^\s+([^\s]+)\s+v/gm)).map((m) => m[1].split("/").pop() ?? "").filter(Boolean);
|
|
5845
6755
|
return ["go", ...requires];
|
|
5846
6756
|
}
|
|
5847
6757
|
function tokensFromCargoToml(cwd) {
|
|
5848
|
-
const content = readFileSafe(
|
|
6758
|
+
const content = readFileSafe(join11(cwd, "Cargo.toml"));
|
|
5849
6759
|
if (!content) return [];
|
|
5850
6760
|
const deps = [];
|
|
5851
6761
|
let inDeps = false;
|
|
@@ -5866,7 +6776,7 @@ function tokensFromFileExtensions(cwd) {
|
|
|
5866
6776
|
const tokens = [];
|
|
5867
6777
|
const scanDirs = [cwd];
|
|
5868
6778
|
try {
|
|
5869
|
-
const srcDir =
|
|
6779
|
+
const srcDir = join11(cwd, "src");
|
|
5870
6780
|
readdirSync2(srcDir);
|
|
5871
6781
|
scanDirs.push(srcDir);
|
|
5872
6782
|
} catch {
|
|
@@ -6027,13 +6937,13 @@ var init_signal = __esm({
|
|
|
6027
6937
|
// bin/jpi-learn.js
|
|
6028
6938
|
var jpi_learn_exports = {};
|
|
6029
6939
|
__export(jpi_learn_exports, {
|
|
6030
|
-
run: () =>
|
|
6940
|
+
run: () => run10
|
|
6031
6941
|
});
|
|
6032
|
-
async function
|
|
6942
|
+
async function run10() {
|
|
6033
6943
|
try {
|
|
6034
|
-
const
|
|
6035
|
-
const cwdIdx =
|
|
6036
|
-
const cwd = cwdIdx !== -1 &&
|
|
6944
|
+
const args5 = process.argv.slice(2);
|
|
6945
|
+
const cwdIdx = args5.indexOf("--cwd");
|
|
6946
|
+
const cwd = cwdIdx !== -1 && args5[cwdIdx + 1] ? args5[cwdIdx + 1] : process.cwd();
|
|
6037
6947
|
const { extractFingerprint: extractFingerprint2 } = await Promise.resolve().then(() => (init_signal(), signal_exports));
|
|
6038
6948
|
const { readProfile: readProfile2, writeProfile: writeProfile2, accumulateSession: accumulateSession2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
|
|
6039
6949
|
const fingerprint = extractFingerprint2(cwd);
|
|
@@ -6056,7 +6966,7 @@ var init_jpi_learn = __esm({
|
|
|
6056
6966
|
"use strict";
|
|
6057
6967
|
isMain = process.argv[1]?.endsWith("jpi-learn.js") || process.argv[1]?.endsWith("jpi-learn");
|
|
6058
6968
|
if (isMain) {
|
|
6059
|
-
|
|
6969
|
+
run10();
|
|
6060
6970
|
}
|
|
6061
6971
|
}
|
|
6062
6972
|
});
|
|
@@ -6064,23 +6974,23 @@ var init_jpi_learn = __esm({
|
|
|
6064
6974
|
// bin/jpi-config.js
|
|
6065
6975
|
var jpi_config_exports = {};
|
|
6066
6976
|
__export(jpi_config_exports, {
|
|
6067
|
-
run: () =>
|
|
6977
|
+
run: () => run11
|
|
6068
6978
|
});
|
|
6069
|
-
import { readFileSync as
|
|
6070
|
-
import { join as
|
|
6071
|
-
import { homedir as
|
|
6979
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, mkdirSync as mkdirSync9, existsSync as existsSync7 } from "fs";
|
|
6980
|
+
import { join as join12 } from "path";
|
|
6981
|
+
import { homedir as homedir10 } from "os";
|
|
6072
6982
|
function readConfig() {
|
|
6073
6983
|
try {
|
|
6074
6984
|
if (!existsSync7(CONFIG_FILE)) return { ...DEFAULT_CONFIG };
|
|
6075
|
-
return { ...DEFAULT_CONFIG, ...JSON.parse(
|
|
6985
|
+
return { ...DEFAULT_CONFIG, ...JSON.parse(readFileSync12(CONFIG_FILE, "utf8")) };
|
|
6076
6986
|
} catch {
|
|
6077
6987
|
return { ...DEFAULT_CONFIG };
|
|
6078
6988
|
}
|
|
6079
6989
|
}
|
|
6080
6990
|
function writeConfig(patch) {
|
|
6081
|
-
|
|
6991
|
+
mkdirSync9(TERMINALHIRE_DIR9, { recursive: true });
|
|
6082
6992
|
const merged = { ...readConfig(), ...patch };
|
|
6083
|
-
|
|
6993
|
+
writeFileSync9(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
6084
6994
|
}
|
|
6085
6995
|
function parseNudgeMode(raw) {
|
|
6086
6996
|
if (raw === "session" || raw === "always") return raw;
|
|
@@ -6088,9 +6998,9 @@ function parseNudgeMode(raw) {
|
|
|
6088
6998
|
if (m && parseInt(m[1], 10) >= 1) return raw;
|
|
6089
6999
|
return null;
|
|
6090
7000
|
}
|
|
6091
|
-
async function
|
|
6092
|
-
const
|
|
6093
|
-
const filtered =
|
|
7001
|
+
async function run11() {
|
|
7002
|
+
const args5 = process.argv.slice(2);
|
|
7003
|
+
const filtered = args5[0] === "config" ? args5.slice(1) : args5;
|
|
6094
7004
|
if (filtered.includes("--show") || filtered.length === 0) {
|
|
6095
7005
|
const cfg = readConfig();
|
|
6096
7006
|
const envOverride = process.env["TERMINALHIRE_NUDGE"];
|
|
@@ -6131,12 +7041,12 @@ async function run8() {
|
|
|
6131
7041
|
console.error(" terminalhire config --show");
|
|
6132
7042
|
process.exit(1);
|
|
6133
7043
|
}
|
|
6134
|
-
var
|
|
7044
|
+
var TERMINALHIRE_DIR9, CONFIG_FILE, DEFAULT_CONFIG;
|
|
6135
7045
|
var init_jpi_config = __esm({
|
|
6136
7046
|
"bin/jpi-config.js"() {
|
|
6137
7047
|
"use strict";
|
|
6138
|
-
|
|
6139
|
-
CONFIG_FILE =
|
|
7048
|
+
TERMINALHIRE_DIR9 = join12(homedir10(), ".terminalhire");
|
|
7049
|
+
CONFIG_FILE = join12(TERMINALHIRE_DIR9, "config.json");
|
|
6140
7050
|
DEFAULT_CONFIG = { nudge: "session" };
|
|
6141
7051
|
}
|
|
6142
7052
|
});
|
|
@@ -6159,25 +7069,25 @@ __export(spinner_exports, {
|
|
|
6159
7069
|
readSpinnerConfig: () => readSpinnerConfig
|
|
6160
7070
|
});
|
|
6161
7071
|
import {
|
|
6162
|
-
readFileSync as
|
|
6163
|
-
writeFileSync as
|
|
7072
|
+
readFileSync as readFileSync13,
|
|
7073
|
+
writeFileSync as writeFileSync10,
|
|
6164
7074
|
existsSync as existsSync8,
|
|
6165
|
-
mkdirSync as
|
|
7075
|
+
mkdirSync as mkdirSync10,
|
|
6166
7076
|
renameSync as renameSync2
|
|
6167
7077
|
} from "fs";
|
|
6168
|
-
import { join as
|
|
6169
|
-
import { homedir as
|
|
7078
|
+
import { join as join13, dirname } from "path";
|
|
7079
|
+
import { homedir as homedir11 } from "os";
|
|
6170
7080
|
function readJson(path, fallback) {
|
|
6171
7081
|
try {
|
|
6172
|
-
return existsSync8(path) ? JSON.parse(
|
|
7082
|
+
return existsSync8(path) ? JSON.parse(readFileSync13(path, "utf8")) : fallback;
|
|
6173
7083
|
} catch {
|
|
6174
7084
|
return fallback;
|
|
6175
7085
|
}
|
|
6176
7086
|
}
|
|
6177
7087
|
function atomicWriteJson(path, obj) {
|
|
6178
|
-
|
|
7088
|
+
mkdirSync10(dirname(path), { recursive: true });
|
|
6179
7089
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
6180
|
-
|
|
7090
|
+
writeFileSync10(tmp, JSON.stringify(obj, null, 2) + "\n", "utf8");
|
|
6181
7091
|
renameSync2(tmp, path);
|
|
6182
7092
|
}
|
|
6183
7093
|
function titleCase(s) {
|
|
@@ -6429,10 +7339,10 @@ var TH_DIR, CLAUDE_SETTINGS, CONFIG_FILE2, SPINNER_STATE_FILE, SPINNER_DEFAULTS,
|
|
|
6429
7339
|
var init_spinner = __esm({
|
|
6430
7340
|
"bin/spinner.js"() {
|
|
6431
7341
|
"use strict";
|
|
6432
|
-
TH_DIR = process.env["TERMINALHIRE_DIR"] ||
|
|
6433
|
-
CLAUDE_SETTINGS = process.env["TERMINALHIRE_CLAUDE_SETTINGS"] ||
|
|
6434
|
-
CONFIG_FILE2 =
|
|
6435
|
-
SPINNER_STATE_FILE =
|
|
7342
|
+
TH_DIR = process.env["TERMINALHIRE_DIR"] || join13(homedir11(), ".terminalhire");
|
|
7343
|
+
CLAUDE_SETTINGS = process.env["TERMINALHIRE_CLAUDE_SETTINGS"] || join13(homedir11(), ".claude", "settings.json");
|
|
7344
|
+
CONFIG_FILE2 = join13(TH_DIR, "config.json");
|
|
7345
|
+
SPINNER_STATE_FILE = join13(TH_DIR, "spinner-state.json");
|
|
6436
7346
|
SPINNER_DEFAULTS = { enabled: false, mode: "append", max: 6, frequency: "sometimes" };
|
|
6437
7347
|
VERB_INTROS = ["Matched:", "You\u2019d fit:", "Worth a look:", "On your radar:", "Fits your stack:"];
|
|
6438
7348
|
}
|
|
@@ -6441,29 +7351,29 @@ var init_spinner = __esm({
|
|
|
6441
7351
|
// bin/jpi-spinner.js
|
|
6442
7352
|
var jpi_spinner_exports = {};
|
|
6443
7353
|
__export(jpi_spinner_exports, {
|
|
6444
|
-
run: () =>
|
|
7354
|
+
run: () => run12
|
|
6445
7355
|
});
|
|
6446
7356
|
import {
|
|
6447
|
-
readFileSync as
|
|
6448
|
-
writeFileSync as
|
|
7357
|
+
readFileSync as readFileSync14,
|
|
7358
|
+
writeFileSync as writeFileSync11,
|
|
6449
7359
|
copyFileSync,
|
|
6450
7360
|
existsSync as existsSync9,
|
|
6451
|
-
mkdirSync as
|
|
7361
|
+
mkdirSync as mkdirSync11
|
|
6452
7362
|
} from "fs";
|
|
6453
|
-
import { join as
|
|
6454
|
-
import { homedir as
|
|
6455
|
-
import { createInterface as
|
|
7363
|
+
import { join as join14 } from "path";
|
|
7364
|
+
import { homedir as homedir12 } from "os";
|
|
7365
|
+
import { createInterface as createInterface7 } from "readline";
|
|
6456
7366
|
function readConfig2() {
|
|
6457
7367
|
try {
|
|
6458
|
-
return existsSync9(CONFIG_FILE3) ? JSON.parse(
|
|
7368
|
+
return existsSync9(CONFIG_FILE3) ? JSON.parse(readFileSync14(CONFIG_FILE3, "utf8")) : {};
|
|
6459
7369
|
} catch {
|
|
6460
7370
|
return {};
|
|
6461
7371
|
}
|
|
6462
7372
|
}
|
|
6463
7373
|
function writeConfig2(patch) {
|
|
6464
|
-
|
|
7374
|
+
mkdirSync11(TH_DIR2, { recursive: true });
|
|
6465
7375
|
const merged = { ...readConfig2(), ...patch };
|
|
6466
|
-
|
|
7376
|
+
writeFileSync11(CONFIG_FILE3, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
6467
7377
|
}
|
|
6468
7378
|
function backupSettings() {
|
|
6469
7379
|
if (!existsSync9(SETTINGS_PATH)) return null;
|
|
@@ -6473,7 +7383,7 @@ function backupSettings() {
|
|
|
6473
7383
|
return backupPath;
|
|
6474
7384
|
}
|
|
6475
7385
|
function ask(question) {
|
|
6476
|
-
const rl =
|
|
7386
|
+
const rl = createInterface7({ input: process.stdin, output: process.stdout });
|
|
6477
7387
|
return new Promise((res) => {
|
|
6478
7388
|
rl.question(question, (answer) => {
|
|
6479
7389
|
rl.close();
|
|
@@ -6483,20 +7393,20 @@ function ask(question) {
|
|
|
6483
7393
|
}
|
|
6484
7394
|
function readTopMatches() {
|
|
6485
7395
|
try {
|
|
6486
|
-
const c = JSON.parse(
|
|
7396
|
+
const c = JSON.parse(readFileSync14(CACHE_FILE, "utf8"));
|
|
6487
7397
|
return Array.isArray(c.topMatches) ? c.topMatches : [];
|
|
6488
7398
|
} catch {
|
|
6489
7399
|
return [];
|
|
6490
7400
|
}
|
|
6491
7401
|
}
|
|
6492
|
-
async function
|
|
6493
|
-
const
|
|
6494
|
-
const has = (f) =>
|
|
7402
|
+
async function run12() {
|
|
7403
|
+
const args5 = process.argv.slice(2).filter((a) => a !== "spinner");
|
|
7404
|
+
const has = (f) => args5.includes(f);
|
|
6495
7405
|
const val = (f) => {
|
|
6496
|
-
const i =
|
|
6497
|
-
return i >= 0 ?
|
|
7406
|
+
const i = args5.indexOf(f);
|
|
7407
|
+
return i >= 0 ? args5[i + 1] : void 0;
|
|
6498
7408
|
};
|
|
6499
|
-
if (has("--show") ||
|
|
7409
|
+
if (has("--show") || args5.length === 0) {
|
|
6500
7410
|
const sc = readSpinnerConfig();
|
|
6501
7411
|
console.log("");
|
|
6502
7412
|
console.log("terminalhire spinner \u2014 job matches in the Claude Code spinner line");
|
|
@@ -6626,24 +7536,24 @@ var init_jpi_spinner = __esm({
|
|
|
6626
7536
|
"bin/jpi-spinner.js"() {
|
|
6627
7537
|
"use strict";
|
|
6628
7538
|
init_spinner();
|
|
6629
|
-
TH_DIR2 = process.env["TERMINALHIRE_DIR"] ||
|
|
6630
|
-
CONFIG_FILE3 =
|
|
6631
|
-
SETTINGS_PATH = process.env["TERMINALHIRE_CLAUDE_SETTINGS"] ||
|
|
6632
|
-
CACHE_FILE =
|
|
7539
|
+
TH_DIR2 = process.env["TERMINALHIRE_DIR"] || join14(homedir12(), ".terminalhire");
|
|
7540
|
+
CONFIG_FILE3 = join14(TH_DIR2, "config.json");
|
|
7541
|
+
SETTINGS_PATH = process.env["TERMINALHIRE_CLAUDE_SETTINGS"] || join14(homedir12(), ".claude", "settings.json");
|
|
7542
|
+
CACHE_FILE = join14(TH_DIR2, "index-cache.json");
|
|
6633
7543
|
}
|
|
6634
7544
|
});
|
|
6635
7545
|
|
|
6636
7546
|
// bin/jpi-sync.js
|
|
6637
7547
|
var jpi_sync_exports = {};
|
|
6638
7548
|
__export(jpi_sync_exports, {
|
|
6639
|
-
run: () =>
|
|
7549
|
+
run: () => run13
|
|
6640
7550
|
});
|
|
6641
|
-
import { readFileSync as
|
|
6642
|
-
import { join as
|
|
6643
|
-
import { homedir as
|
|
6644
|
-
import { createInterface as
|
|
7551
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync12, mkdirSync as mkdirSync12, existsSync as existsSync10, rmSync as rmSync2 } from "fs";
|
|
7552
|
+
import { join as join15 } from "path";
|
|
7553
|
+
import { homedir as homedir13, hostname as osHostname } from "os";
|
|
7554
|
+
import { createInterface as createInterface8 } from "readline";
|
|
6645
7555
|
function ask2(question) {
|
|
6646
|
-
const rl =
|
|
7556
|
+
const rl = createInterface8({ input: process.stdin, output: process.stdout });
|
|
6647
7557
|
return new Promise((res) => {
|
|
6648
7558
|
rl.question(question, (answer) => {
|
|
6649
7559
|
rl.close();
|
|
@@ -6653,14 +7563,14 @@ function ask2(question) {
|
|
|
6653
7563
|
}
|
|
6654
7564
|
function readMarker() {
|
|
6655
7565
|
try {
|
|
6656
|
-
return existsSync10(TIER1_MARKER) ? JSON.parse(
|
|
7566
|
+
return existsSync10(TIER1_MARKER) ? JSON.parse(readFileSync15(TIER1_MARKER, "utf8")) : null;
|
|
6657
7567
|
} catch {
|
|
6658
7568
|
return null;
|
|
6659
7569
|
}
|
|
6660
7570
|
}
|
|
6661
7571
|
function writeMarker(marker) {
|
|
6662
|
-
|
|
6663
|
-
|
|
7572
|
+
mkdirSync12(TH_DIR3, { recursive: true });
|
|
7573
|
+
writeFileSync12(TIER1_MARKER, JSON.stringify(marker, null, 2) + "\n", "utf8");
|
|
6664
7574
|
}
|
|
6665
7575
|
function clearMarker() {
|
|
6666
7576
|
try {
|
|
@@ -6726,7 +7636,7 @@ async function runPush() {
|
|
|
6726
7636
|
const fields = buildConsentFields(profile);
|
|
6727
7637
|
renderPreview(fields);
|
|
6728
7638
|
await new Promise((resolve2) => {
|
|
6729
|
-
const rl =
|
|
7639
|
+
const rl = createInterface8({ input: process.stdin, output: process.stdout });
|
|
6730
7640
|
rl.question(
|
|
6731
7641
|
" Press Enter to open your browser to authorize + consent (or Ctrl-C to cancel)... ",
|
|
6732
7642
|
() => {
|
|
@@ -6924,7 +7834,7 @@ async function runDelete() {
|
|
|
6924
7834
|
console.log("\n Requesting deletion...");
|
|
6925
7835
|
let res;
|
|
6926
7836
|
try {
|
|
6927
|
-
res = await fetch(`${
|
|
7837
|
+
res = await fetch(`${API_URL4}/api/profile-sync`, {
|
|
6928
7838
|
method: "DELETE",
|
|
6929
7839
|
headers: { "Content-Type": "application/json" },
|
|
6930
7840
|
body: JSON.stringify({ consentToken, login, deleteToken }),
|
|
@@ -6948,9 +7858,9 @@ async function runDelete() {
|
|
|
6948
7858
|
clearMarker();
|
|
6949
7859
|
console.log("\n Synced profile deleted and local marker cleared.\n");
|
|
6950
7860
|
}
|
|
6951
|
-
async function
|
|
6952
|
-
const
|
|
6953
|
-
const has = (f) =>
|
|
7861
|
+
async function run13() {
|
|
7862
|
+
const args5 = process.argv.slice(2).filter((a) => a !== "sync");
|
|
7863
|
+
const has = (f) => args5.includes(f);
|
|
6954
7864
|
if (has("--push") || has("--enable")) {
|
|
6955
7865
|
await runPush();
|
|
6956
7866
|
return;
|
|
@@ -6974,14 +7884,14 @@ async function run10() {
|
|
|
6974
7884
|
console.log(" This is NOT required to use terminalhire.");
|
|
6975
7885
|
console.log("");
|
|
6976
7886
|
}
|
|
6977
|
-
var TH_DIR3, TIER1_MARKER,
|
|
7887
|
+
var TH_DIR3, TIER1_MARKER, API_URL4, SYNC_BASE, POLL_INTERVAL_MS, POLL_TIMEOUT_MS, CONSENT_VERSION;
|
|
6978
7888
|
var init_jpi_sync = __esm({
|
|
6979
7889
|
"bin/jpi-sync.js"() {
|
|
6980
7890
|
"use strict";
|
|
6981
7891
|
init_open_url();
|
|
6982
|
-
TH_DIR3 = process.env["TERMINALHIRE_DIR"] ||
|
|
6983
|
-
TIER1_MARKER =
|
|
6984
|
-
|
|
7892
|
+
TH_DIR3 = process.env["TERMINALHIRE_DIR"] || join15(homedir13(), ".terminalhire");
|
|
7893
|
+
TIER1_MARKER = join15(TH_DIR3, "tier1.json");
|
|
7894
|
+
API_URL4 = process.env["TERMINALHIRE_API_URL"] || process.env["JPI_API_URL"] || "https://terminalhire.com";
|
|
6985
7895
|
SYNC_BASE = "https://www.terminalhire.com";
|
|
6986
7896
|
POLL_INTERVAL_MS = 2e3;
|
|
6987
7897
|
POLL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
@@ -6992,16 +7902,16 @@ var init_jpi_sync = __esm({
|
|
|
6992
7902
|
// bin/jpi-init.js
|
|
6993
7903
|
var jpi_init_exports = {};
|
|
6994
7904
|
__export(jpi_init_exports, {
|
|
6995
|
-
run: () =>
|
|
7905
|
+
run: () => run14
|
|
6996
7906
|
});
|
|
6997
7907
|
import { existsSync as existsSync11 } from "fs";
|
|
6998
|
-
import { join as
|
|
7908
|
+
import { join as join16, resolve } from "path";
|
|
6999
7909
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7000
|
-
import { createInterface as
|
|
7910
|
+
import { createInterface as createInterface9 } from "readline";
|
|
7001
7911
|
import { spawnSync, spawn as spawn2 } from "child_process";
|
|
7002
|
-
import { homedir as
|
|
7912
|
+
import { homedir as homedir14 } from "os";
|
|
7003
7913
|
function ask3(question) {
|
|
7004
|
-
const rl =
|
|
7914
|
+
const rl = createInterface9({ input: process.stdin, output: process.stdout });
|
|
7005
7915
|
return new Promise((resolve2) => {
|
|
7006
7916
|
rl.question(question, (answer) => {
|
|
7007
7917
|
rl.close();
|
|
@@ -7010,18 +7920,18 @@ function ask3(question) {
|
|
|
7010
7920
|
});
|
|
7011
7921
|
}
|
|
7012
7922
|
function resolveScript(name) {
|
|
7013
|
-
const distPath = resolve(
|
|
7014
|
-
const legacyPath = resolve(
|
|
7923
|
+
const distPath = resolve(join16(__dirname2, "..", "..", "dist", "bin", `${name}.js`));
|
|
7924
|
+
const legacyPath = resolve(join16(__dirname2, `${name}.js`));
|
|
7015
7925
|
return existsSync11(distPath) ? distPath : legacyPath;
|
|
7016
7926
|
}
|
|
7017
7927
|
function resolveInstallJs() {
|
|
7018
|
-
const fromDist = resolve(
|
|
7019
|
-
const fromBin = resolve(
|
|
7928
|
+
const fromDist = resolve(join16(__dirname2, "..", "..", "install.js"));
|
|
7929
|
+
const fromBin = resolve(join16(__dirname2, "..", "install.js"));
|
|
7020
7930
|
if (existsSync11(fromDist)) return fromDist;
|
|
7021
7931
|
if (existsSync11(fromBin)) return fromBin;
|
|
7022
7932
|
return fromBin;
|
|
7023
7933
|
}
|
|
7024
|
-
async function
|
|
7934
|
+
async function run14() {
|
|
7025
7935
|
console.log("");
|
|
7026
7936
|
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");
|
|
7027
7937
|
console.log("\u2502 terminalhire init \u2014 one-command onboarding \u2502");
|
|
@@ -7124,17 +8034,17 @@ var init_jpi_init = __esm({
|
|
|
7124
8034
|
// bin/jpi-refresh.js
|
|
7125
8035
|
var jpi_refresh_exports = {};
|
|
7126
8036
|
__export(jpi_refresh_exports, {
|
|
7127
|
-
run: () =>
|
|
8037
|
+
run: () => run15
|
|
7128
8038
|
});
|
|
7129
|
-
import { readFileSync as
|
|
7130
|
-
import { join as
|
|
7131
|
-
import { homedir as
|
|
8039
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync13, existsSync as existsSync12, mkdirSync as mkdirSync13 } from "fs";
|
|
8040
|
+
import { join as join17 } from "path";
|
|
8041
|
+
import { homedir as homedir15 } from "os";
|
|
7132
8042
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
7133
|
-
async function
|
|
8043
|
+
async function run15() {
|
|
7134
8044
|
try {
|
|
7135
8045
|
let index;
|
|
7136
8046
|
try {
|
|
7137
|
-
const res = await fetch(`${
|
|
8047
|
+
const res = await fetch(`${API_URL5}/api/index`, {
|
|
7138
8048
|
signal: AbortSignal.timeout(15e3),
|
|
7139
8049
|
headers: { "Accept": "application/json" }
|
|
7140
8050
|
});
|
|
@@ -7182,14 +8092,14 @@ async function run12() {
|
|
|
7182
8092
|
}
|
|
7183
8093
|
} catch {
|
|
7184
8094
|
}
|
|
7185
|
-
|
|
8095
|
+
mkdirSync13(TERMINALHIRE_DIR10, { recursive: true });
|
|
7186
8096
|
const cacheEntry = {
|
|
7187
8097
|
ts: Date.now(),
|
|
7188
8098
|
index,
|
|
7189
8099
|
matchCount,
|
|
7190
8100
|
topMatches
|
|
7191
8101
|
};
|
|
7192
|
-
|
|
8102
|
+
writeFileSync13(INDEX_CACHE_FILE4, JSON.stringify(cacheEntry), "utf8");
|
|
7193
8103
|
try {
|
|
7194
8104
|
const {
|
|
7195
8105
|
readSpinnerConfig: readSpinnerConfig2,
|
|
@@ -7216,7 +8126,7 @@ async function run12() {
|
|
|
7216
8126
|
const verbs = buildSpinnerPool2(ranked, sc.max, { sessionTags, frequency: sc.frequency });
|
|
7217
8127
|
if (verbs.length > 0) applySpinnerVerbs2(verbs, sc.mode);
|
|
7218
8128
|
else clearSpinnerVerbs2();
|
|
7219
|
-
const tips = buildTips2(ranked,
|
|
8129
|
+
const tips = buildTips2(ranked, API_URL5, 8);
|
|
7220
8130
|
if (tips.length > 0) applySpinnerTips2(tips);
|
|
7221
8131
|
else clearSpinnerTips2();
|
|
7222
8132
|
} else {
|
|
@@ -7233,30 +8143,30 @@ async function run12() {
|
|
|
7233
8143
|
process.exit(1);
|
|
7234
8144
|
}
|
|
7235
8145
|
}
|
|
7236
|
-
var __dirname3,
|
|
8146
|
+
var __dirname3, TERMINALHIRE_DIR10, INDEX_CACHE_FILE4, API_URL5;
|
|
7237
8147
|
var init_jpi_refresh = __esm({
|
|
7238
8148
|
"bin/jpi-refresh.js"() {
|
|
7239
8149
|
"use strict";
|
|
7240
8150
|
__dirname3 = fileURLToPath4(new URL(".", import.meta.url));
|
|
7241
|
-
|
|
7242
|
-
INDEX_CACHE_FILE4 =
|
|
7243
|
-
|
|
8151
|
+
TERMINALHIRE_DIR10 = join17(homedir15(), ".terminalhire");
|
|
8152
|
+
INDEX_CACHE_FILE4 = join17(TERMINALHIRE_DIR10, "index-cache.json");
|
|
8153
|
+
API_URL5 = process.env["TERMINALHIRE_API_URL"] ?? process.env["JPI_API_URL"] ?? "https://terminalhire.com";
|
|
7244
8154
|
}
|
|
7245
8155
|
});
|
|
7246
8156
|
|
|
7247
8157
|
// bin/jpi-save.js
|
|
7248
8158
|
var jpi_save_exports = {};
|
|
7249
8159
|
__export(jpi_save_exports, {
|
|
7250
|
-
run: () =>
|
|
8160
|
+
run: () => run16
|
|
7251
8161
|
});
|
|
7252
|
-
import { readFileSync as
|
|
7253
|
-
import { join as
|
|
7254
|
-
import { homedir as
|
|
8162
|
+
import { readFileSync as readFileSync17, existsSync as existsSync13 } from "fs";
|
|
8163
|
+
import { join as join18 } from "path";
|
|
8164
|
+
import { homedir as homedir16 } from "os";
|
|
7255
8165
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
7256
8166
|
function findJobInCache(jobId) {
|
|
7257
8167
|
try {
|
|
7258
8168
|
if (!existsSync13(INDEX_CACHE_FILE5)) return null;
|
|
7259
|
-
const raw =
|
|
8169
|
+
const raw = readFileSync17(INDEX_CACHE_FILE5, "utf8");
|
|
7260
8170
|
const entry = JSON.parse(raw);
|
|
7261
8171
|
const jobs = entry?.index?.jobs ?? [];
|
|
7262
8172
|
return jobs.find((j) => j.id === jobId) ?? null;
|
|
@@ -7325,7 +8235,7 @@ async function cmdUnsave(jobId) {
|
|
|
7325
8235
|
process.exit(1);
|
|
7326
8236
|
}
|
|
7327
8237
|
}
|
|
7328
|
-
async function
|
|
8238
|
+
async function run16() {
|
|
7329
8239
|
const verb = process.argv[2];
|
|
7330
8240
|
const jobId = process.argv[3];
|
|
7331
8241
|
try {
|
|
@@ -7344,31 +8254,31 @@ async function run13() {
|
|
|
7344
8254
|
process.exit(1);
|
|
7345
8255
|
}
|
|
7346
8256
|
}
|
|
7347
|
-
var __dirname4,
|
|
8257
|
+
var __dirname4, TERMINALHIRE_DIR11, INDEX_CACHE_FILE5;
|
|
7348
8258
|
var init_jpi_save = __esm({
|
|
7349
8259
|
"bin/jpi-save.js"() {
|
|
7350
8260
|
"use strict";
|
|
7351
8261
|
__dirname4 = fileURLToPath5(new URL(".", import.meta.url));
|
|
7352
|
-
|
|
7353
|
-
INDEX_CACHE_FILE5 =
|
|
8262
|
+
TERMINALHIRE_DIR11 = join18(homedir16(), ".terminalhire");
|
|
8263
|
+
INDEX_CACHE_FILE5 = join18(TERMINALHIRE_DIR11, "index-cache.json");
|
|
7354
8264
|
}
|
|
7355
8265
|
});
|
|
7356
8266
|
|
|
7357
8267
|
// bin/jpi-dispatch.js
|
|
7358
8268
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
7359
|
-
import { join as
|
|
7360
|
-
import { existsSync as existsSync14, readFileSync as
|
|
8269
|
+
import { join as join19, dirname as dirname2 } from "path";
|
|
8270
|
+
import { existsSync as existsSync14, readFileSync as readFileSync18 } from "fs";
|
|
7361
8271
|
import { createRequire } from "module";
|
|
7362
8272
|
var __dirname5 = fileURLToPath6(new URL(".", import.meta.url));
|
|
7363
8273
|
function readPackageVersion() {
|
|
7364
8274
|
try {
|
|
7365
8275
|
const candidates = [
|
|
7366
|
-
|
|
7367
|
-
|
|
8276
|
+
join19(__dirname5, "..", "..", "package.json"),
|
|
8277
|
+
join19(__dirname5, "..", "package.json")
|
|
7368
8278
|
];
|
|
7369
8279
|
for (const p of candidates) {
|
|
7370
8280
|
if (existsSync14(p)) {
|
|
7371
|
-
const pkg = JSON.parse(
|
|
8281
|
+
const pkg = JSON.parse(readFileSync18(p, "utf8"));
|
|
7372
8282
|
if (pkg.version) return pkg.version;
|
|
7373
8283
|
}
|
|
7374
8284
|
}
|
|
@@ -7379,7 +8289,7 @@ function readPackageVersion() {
|
|
|
7379
8289
|
var firstArg = process.argv[2];
|
|
7380
8290
|
if (!firstArg && !process.stdin.isTTY) {
|
|
7381
8291
|
const { default: childProcess } = await import("child_process");
|
|
7382
|
-
const nudgeScript =
|
|
8292
|
+
const nudgeScript = join19(__dirname5, "jpi.js");
|
|
7383
8293
|
const child = childProcess.spawnSync(process.execPath, [nudgeScript], {
|
|
7384
8294
|
stdio: ["inherit", "inherit", "inherit"]
|
|
7385
8295
|
});
|
|
@@ -7396,6 +8306,10 @@ if (!firstArg || firstArg === "help" || firstArg === "--help" || firstArg === "-
|
|
|
7396
8306
|
console.log(" terminalhire jobs Fetch job index, match locally, browse roles");
|
|
7397
8307
|
console.log(" terminalhire jobs --limit N Show top N results (default: 10)");
|
|
7398
8308
|
console.log(" terminalhire jobs --remote-only Filter to remote roles only");
|
|
8309
|
+
console.log(" terminalhire devs Rank opted-in builders & projects locally (dev\u2194dev)");
|
|
8310
|
+
console.log(" terminalhire devs --as-project Rank builders against your declared project (founder side)");
|
|
8311
|
+
console.log(' terminalhire project "<title>: <skills>" Declare a project locally (never sent) to rank builders for');
|
|
8312
|
+
console.log(" terminalhire project --show Show your locally-declared project");
|
|
7399
8313
|
console.log(" terminalhire bounties Day-sized paid tasks you can knock out today");
|
|
7400
8314
|
console.log(" terminalhire bounties --priced Only bounties with a known $ amount");
|
|
7401
8315
|
console.log(" terminalhire claim record <id|issueUrl> Claim a bounty locally + print the executor brief");
|
|
@@ -7406,6 +8320,7 @@ if (!firstArg || firstArg === "help" || firstArg === "--help" || firstArg === "-
|
|
|
7406
8320
|
console.log(" terminalhire trajectory --inward Also show private rework/recovery (never exported)");
|
|
7407
8321
|
console.log(" terminalhire trajectory --push Opt-in: link your derived trajectory to your dashboard (typed-yes)");
|
|
7408
8322
|
console.log(" terminalhire trajectory --push --delete Unlink (revoke) your trajectory from the dashboard");
|
|
8323
|
+
console.log(" terminalhire intro <github-login> Request a consented intro to another developer (typed-yes)");
|
|
7409
8324
|
console.log(" terminalhire profile --show Display your encrypted local profile");
|
|
7410
8325
|
console.log(" terminalhire profile --edit Set displayName, contactEmail, prefs");
|
|
7411
8326
|
console.log(" terminalhire profile --delete Wipe profile and encryption key from disk");
|
|
@@ -7452,6 +8367,18 @@ if (firstArg === "jobs") {
|
|
|
7452
8367
|
await mod.run();
|
|
7453
8368
|
process.exit(0);
|
|
7454
8369
|
}
|
|
8370
|
+
if (firstArg === "devs") {
|
|
8371
|
+
process.argv.splice(2, 1);
|
|
8372
|
+
const mod = await Promise.resolve().then(() => (init_jpi_devs(), jpi_devs_exports));
|
|
8373
|
+
await mod.run();
|
|
8374
|
+
process.exit(0);
|
|
8375
|
+
}
|
|
8376
|
+
if (firstArg === "project") {
|
|
8377
|
+
process.argv.splice(2, 1);
|
|
8378
|
+
const mod = await Promise.resolve().then(() => (init_jpi_project(), jpi_project_exports));
|
|
8379
|
+
await mod.run();
|
|
8380
|
+
process.exit(0);
|
|
8381
|
+
}
|
|
7455
8382
|
if (firstArg === "bounties") {
|
|
7456
8383
|
process.argv.splice(2, 1);
|
|
7457
8384
|
const mod = await Promise.resolve().then(() => (init_jpi_bounties(), jpi_bounties_exports));
|
|
@@ -7470,6 +8397,12 @@ if (firstArg === "trajectory" || firstArg === "mirror") {
|
|
|
7470
8397
|
await mod.run();
|
|
7471
8398
|
process.exit(0);
|
|
7472
8399
|
}
|
|
8400
|
+
if (firstArg === "intro") {
|
|
8401
|
+
process.argv.splice(2, 1);
|
|
8402
|
+
const mod = await Promise.resolve().then(() => (init_jpi_intro(), jpi_intro_exports));
|
|
8403
|
+
await mod.run();
|
|
8404
|
+
process.exit(0);
|
|
8405
|
+
}
|
|
7473
8406
|
if (firstArg === "profile") {
|
|
7474
8407
|
const mod = await Promise.resolve().then(() => (init_jpi_profile(), jpi_profile_exports));
|
|
7475
8408
|
await mod.run();
|