terminalhire 0.18.0 → 0.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/jpi-bounties.js +83 -28
- package/dist/bin/jpi-chat-read.js +11 -2
- package/dist/bin/jpi-chat.js +11 -2
- package/dist/bin/jpi-contribute.js +61 -12
- package/dist/bin/jpi-devs.js +82 -27
- package/dist/bin/jpi-dispatch.js +136 -61
- package/dist/bin/jpi-inbox.js +11 -2
- package/dist/bin/jpi-intro.js +11 -2
- package/dist/bin/jpi-jobs.js +84 -31
- package/dist/bin/jpi-learn.js +11 -2
- package/dist/bin/jpi-login.js +49 -19
- package/dist/bin/jpi-profile.js +11 -2
- package/dist/bin/jpi-project.js +47 -17
- package/dist/bin/jpi-refresh.js +245 -79
- package/dist/bin/jpi-save.js +11 -2
- package/dist/bin/jpi-sync.js +11 -2
- package/dist/bin/jpi-trajectory.js +11 -2
- package/dist/bin/sanitize.js +35 -0
- package/dist/src/chat-client.js +2 -2
- package/dist/src/chat-keystore.js +2 -2
- package/dist/src/github-auth.js +2 -2
- package/dist/src/intro.js +11 -2
- package/dist/src/profile.js +2 -2
- package/dist/src/trajectory.js +11 -2
- package/package.json +1 -1
package/dist/bin/jpi-devs.js
CHANGED
|
@@ -391,6 +391,11 @@ ${body}`;
|
|
|
391
391
|
for (const tok of tokens) {
|
|
392
392
|
const r = resolveToken(tok);
|
|
393
393
|
if (!r) continue;
|
|
394
|
+
const synGate = AMBIGUOUS_SYNONYM[tok] ?? AMBIGUOUS_SYNONYM[tok.replace(/^[.\-+#]+|[.\-+#]+$/g, "")];
|
|
395
|
+
if (synGate && r.id === synGate.id) {
|
|
396
|
+
if (synGate.cue.test(text)) ids.add(r.id);
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
394
399
|
if (NON_EXTRACTABLE.has(r.id)) continue;
|
|
395
400
|
if (SYNONYM_ONLY.has(r.id) && !r.viaSynonym) continue;
|
|
396
401
|
const cue = AMBIGUOUS[r.id];
|
|
@@ -408,7 +413,7 @@ ${body}`;
|
|
|
408
413
|
function coreTagsFromTitle(title) {
|
|
409
414
|
return extractSkillTags(title, "").filter((t) => !SOFT_DOMAIN.has(t));
|
|
410
415
|
}
|
|
411
|
-
var SOFT_DOMAIN, SYNONYM_ONLY, NON_EXTRACTABLE, AMBIGUOUS, ENG_INTENT, NON_ENG_TITLE;
|
|
416
|
+
var SOFT_DOMAIN, SYNONYM_ONLY, NON_EXTRACTABLE, AMBIGUOUS, AMBIGUOUS_SYNONYM, ENG_INTENT, NON_ENG_TITLE;
|
|
412
417
|
var init_extract = __esm({
|
|
413
418
|
"../../packages/core/src/vocab/extract.ts"() {
|
|
414
419
|
"use strict";
|
|
@@ -445,6 +450,16 @@ var init_extract = __esm({
|
|
|
445
450
|
r: /\b(rstudio|tidyverse|ggplot|shiny|dplyr|cran|r-lang|rlang)\b/i,
|
|
446
451
|
ml: /\b(machine[\s-]?learning|pytorch|tensorflow|scikit|sklearn|keras|neural|model training|deep[\s-]?learning|numpy|pandas|ml\s+(?:engineer|platform|researcher|infrastructure)|(?:ml|ai)\s+research)\b/i
|
|
447
452
|
};
|
|
453
|
+
AMBIGUOUS_SYNONYM = {
|
|
454
|
+
// "prompt" is the LLM skill only in AI context. In raw JD prose it is "prompt
|
|
455
|
+
// delivery / response / payment / communication / attention". Accept only with
|
|
456
|
+
// the explicit "prompt engineering" phrase OR an LLM/AI ecosystem cue; the cue
|
|
457
|
+
// deliberately excludes the bare word "prompt(s)" itself so it can't self-satisfy.
|
|
458
|
+
prompt: {
|
|
459
|
+
id: "prompt-engineering",
|
|
460
|
+
cue: /\bprompt[\s-]?engineer(?:ing|s)?\b|\b(llms?|gpt-?[0-9o]*|claude|gemini|llama|mistral|openai|anthropic|langchain|llama[\s-]?index|rag|retrieval[\s-]?augmented|embeddings?|fine[\s-]?tun(?:e|ed|ing)|vector[\s-]?(?:db|database|store)|agentic|ai agents?|chatbots?|generative ai|gen[\s-]?ai|genai|few[\s-]?shot|zero[\s-]?shot)\b/i
|
|
461
|
+
}
|
|
462
|
+
};
|
|
448
463
|
ENG_INTENT = /\b(engineer|engineering|developer|dev\b|swe|sde|programmer|architect|full[\s-]?stack|front[\s-]?end|back[\s-]?end|devops|sre|software|coding|codebase|technical staff|tech(?:nical)? lead)\b/i;
|
|
449
464
|
NON_ENG_TITLE = /\b(account executive|account manager|sales (?:rep|representative|development|manager|lead)|sdr|bdr|recruiter|recruiting|talent|marketing|administrative|business partner|billing coordinator|operations (?:administrator|coordinator)|customer success|project finance|controller|bookkeeper|graphic|brand)\b/i;
|
|
450
465
|
}
|
|
@@ -921,6 +936,7 @@ async function fetchRepoMeta(owner, name, token, cache) {
|
|
|
921
936
|
stars: r.stargazers_count ?? 0,
|
|
922
937
|
archived: !!r.archived,
|
|
923
938
|
fork: !!r.fork,
|
|
939
|
+
private: !!r.private,
|
|
924
940
|
language: r.language ?? null,
|
|
925
941
|
topics: r.topics ?? [],
|
|
926
942
|
contributors
|
|
@@ -953,7 +969,7 @@ async function computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates
|
|
|
953
969
|
const loginLc = login.toLowerCase();
|
|
954
970
|
let items;
|
|
955
971
|
try {
|
|
956
|
-
const q = encodeURIComponent(`type:pr is:merged author:${login} -user:${login} sort:updated`);
|
|
972
|
+
const q = encodeURIComponent(`type:pr is:merged is:public author:${login} -user:${login} sort:updated`);
|
|
957
973
|
const res = await ghFetch(
|
|
958
974
|
`/search/issues?q=${q}&per_page=${CANDIDATE_PR_PAGE}`,
|
|
959
975
|
token
|
|
@@ -966,6 +982,7 @@ async function computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates
|
|
|
966
982
|
}
|
|
967
983
|
const byDomain = {};
|
|
968
984
|
let qualifyingTotal = 0;
|
|
985
|
+
const qualifyingPRs = [];
|
|
969
986
|
for (const item of items) {
|
|
970
987
|
const repo = parseRepoUrl(item.repository_url);
|
|
971
988
|
if (!repo) continue;
|
|
@@ -975,13 +992,22 @@ async function computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates
|
|
|
975
992
|
if (isTrivialPRTitle(item.title)) continue;
|
|
976
993
|
const meta = await fetchRepoMeta(repo.owner, repo.name, token, cache);
|
|
977
994
|
if (!meta) continue;
|
|
995
|
+
if (meta.private) continue;
|
|
978
996
|
if (meta.archived || meta.fork) continue;
|
|
979
997
|
if (meta.stars < gates.minStars) continue;
|
|
980
998
|
if (meta.contributors !== void 0 && meta.contributors < gates.minContributors) continue;
|
|
981
999
|
qualifyingTotal += 1;
|
|
982
1000
|
const mergedAt = item.pull_request?.merged_at ?? item.closed_at ?? item.created_at;
|
|
983
1001
|
const rawDomains = [meta.language ?? "", ...meta.topics].filter(Boolean);
|
|
984
|
-
|
|
1002
|
+
const domainTags = [...new Set(normalize(rawDomains))];
|
|
1003
|
+
qualifyingPRs.push({
|
|
1004
|
+
url: item.html_url,
|
|
1005
|
+
title: item.title,
|
|
1006
|
+
repo: `${repo.owner}/${repo.name}`,
|
|
1007
|
+
domains: domainTags,
|
|
1008
|
+
mergedAt
|
|
1009
|
+
});
|
|
1010
|
+
for (const d of domainTags) {
|
|
985
1011
|
const b = byDomain[d] ?? (byDomain[d] = { mergedPRs: 0, distinctOrgs: 0, lastMergedAt: mergedAt, orgs: /* @__PURE__ */ new Set() });
|
|
986
1012
|
b.mergedPRs += 1;
|
|
987
1013
|
b.orgs.add(ownerLc);
|
|
@@ -996,7 +1022,7 @@ async function computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates
|
|
|
996
1022
|
lastMergedAt: b.lastMergedAt
|
|
997
1023
|
};
|
|
998
1024
|
}
|
|
999
|
-
return { status: "ok", byDomain: finalDomains, qualifyingTotal, computedAt };
|
|
1025
|
+
return { status: "ok", byDomain: finalDomains, qualifyingTotal, qualifyingPRs, computedAt };
|
|
1000
1026
|
}
|
|
1001
1027
|
async function computeAcceptanceCredential(login, token, cache = /* @__PURE__ */ new Map()) {
|
|
1002
1028
|
if (!token) return emptyCredential("no-token");
|
|
@@ -1807,6 +1833,19 @@ var init_concurrency = __esm({
|
|
|
1807
1833
|
}
|
|
1808
1834
|
});
|
|
1809
1835
|
|
|
1836
|
+
// ../../packages/core/src/feeds/effort.ts
|
|
1837
|
+
function effortFromAmount(amount) {
|
|
1838
|
+
if (amount == null) return void 0;
|
|
1839
|
+
if (amount <= 500) return "small";
|
|
1840
|
+
if (amount <= 2e3) return "medium";
|
|
1841
|
+
return "large";
|
|
1842
|
+
}
|
|
1843
|
+
var init_effort = __esm({
|
|
1844
|
+
"../../packages/core/src/feeds/effort.ts"() {
|
|
1845
|
+
"use strict";
|
|
1846
|
+
}
|
|
1847
|
+
});
|
|
1848
|
+
|
|
1810
1849
|
// ../../packages/core/src/feeds/github-bounties.ts
|
|
1811
1850
|
function authHeaders() {
|
|
1812
1851
|
const token = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"];
|
|
@@ -1829,12 +1868,6 @@ function parseAmountUSD(text) {
|
|
|
1829
1868
|
if (!Number.isFinite(n) || n <= 0 || n > 1e6) return void 0;
|
|
1830
1869
|
return Math.round(n);
|
|
1831
1870
|
}
|
|
1832
|
-
function effortFromAmount(amount) {
|
|
1833
|
-
if (amount == null) return void 0;
|
|
1834
|
-
if (amount <= 500) return "small";
|
|
1835
|
-
if (amount <= 2e3) return "medium";
|
|
1836
|
-
return "large";
|
|
1837
|
-
}
|
|
1838
1871
|
function labelNames(issue) {
|
|
1839
1872
|
return (issue.labels ?? []).map((l) => typeof l === "string" ? l : l.name ?? "").filter(Boolean);
|
|
1840
1873
|
}
|
|
@@ -2082,6 +2115,7 @@ var init_github_bounties = __esm({
|
|
|
2082
2115
|
init_bounty_gate();
|
|
2083
2116
|
init_http();
|
|
2084
2117
|
init_concurrency();
|
|
2118
|
+
init_effort();
|
|
2085
2119
|
GITHUB_API = "https://api.github.com";
|
|
2086
2120
|
BOUNTY_LABEL_RE = /bounty|reward|funded|💎|💰/i;
|
|
2087
2121
|
SEARCH_QUERIES = [
|
|
@@ -2133,12 +2167,6 @@ var init_github_bounties = __esm({
|
|
|
2133
2167
|
function tokenize3(text) {
|
|
2134
2168
|
return text.toLowerCase().replace(/[^a-z0-9.\-+#]/g, " ").split(/\s+/).filter((w) => w.length > 1);
|
|
2135
2169
|
}
|
|
2136
|
-
function effortFromAmount2(usd) {
|
|
2137
|
-
if (usd == null) return void 0;
|
|
2138
|
-
if (usd < 150) return "small";
|
|
2139
|
-
if (usd < 750) return "medium";
|
|
2140
|
-
return "large";
|
|
2141
|
-
}
|
|
2142
2170
|
function priceToUSD(p) {
|
|
2143
2171
|
if (!p || typeof p.value !== "number") return void 0;
|
|
2144
2172
|
if (p.unit === "USD_CENT") return Math.round(p.value) / 100;
|
|
@@ -2161,6 +2189,7 @@ var init_opire = __esm({
|
|
|
2161
2189
|
init_bounty_gate();
|
|
2162
2190
|
init_github_bounties();
|
|
2163
2191
|
init_http();
|
|
2192
|
+
init_effort();
|
|
2164
2193
|
OPIRE_REWARDS_URL = "https://api.opire.dev/rewards";
|
|
2165
2194
|
MIN_USD = 25;
|
|
2166
2195
|
MAX_USD = 25e3;
|
|
@@ -2197,7 +2226,7 @@ var init_opire = __esm({
|
|
|
2197
2226
|
const tags = normalize([...r.programmingLanguages ?? [], ...tokenize3(title)]);
|
|
2198
2227
|
const bounty = {
|
|
2199
2228
|
amountUSD,
|
|
2200
|
-
estimatedEffort:
|
|
2229
|
+
estimatedEffort: effortFromAmount(amountUSD),
|
|
2201
2230
|
bountySource: "opire",
|
|
2202
2231
|
claimUrl: r.url,
|
|
2203
2232
|
repoFullName
|
|
@@ -6544,6 +6573,7 @@ __export(src_exports, {
|
|
|
6544
6573
|
isBounty: () => isBounty,
|
|
6545
6574
|
isContribution: () => isContribution,
|
|
6546
6575
|
isOverIntroLimit: () => isOverIntroLimit,
|
|
6576
|
+
isTrivialPRTitle: () => isTrivialPRTitle,
|
|
6547
6577
|
joinLabels: () => joinLabels,
|
|
6548
6578
|
labelFor: () => labelFor,
|
|
6549
6579
|
lever: () => lever,
|
|
@@ -6600,9 +6630,9 @@ var init_keytar = __esm({
|
|
|
6600
6630
|
}
|
|
6601
6631
|
});
|
|
6602
6632
|
|
|
6603
|
-
// node-file:/Users/ericgang/job-placement-inline
|
|
6633
|
+
// node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
|
|
6604
6634
|
var require_keytar = __commonJS({
|
|
6605
|
-
"node-file:/Users/ericgang/job-placement-inline
|
|
6635
|
+
"node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
|
|
6606
6636
|
"use strict";
|
|
6607
6637
|
init_keytar();
|
|
6608
6638
|
try {
|
|
@@ -6974,6 +7004,37 @@ function excludeOwnCard(results, ownLogin) {
|
|
|
6974
7004
|
});
|
|
6975
7005
|
}
|
|
6976
7006
|
|
|
7007
|
+
// bin/sanitize.js
|
|
7008
|
+
var CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
|
|
7009
|
+
function sanitizeText(s) {
|
|
7010
|
+
if (s == null) return "";
|
|
7011
|
+
return String(s).replace(CONTROL_CHARS, "");
|
|
7012
|
+
}
|
|
7013
|
+
function safeHttpUrl(url) {
|
|
7014
|
+
if (url == null) return null;
|
|
7015
|
+
const raw = String(url);
|
|
7016
|
+
CONTROL_CHARS.lastIndex = 0;
|
|
7017
|
+
if (CONTROL_CHARS.test(raw)) return null;
|
|
7018
|
+
let parsed;
|
|
7019
|
+
try {
|
|
7020
|
+
parsed = new URL(raw);
|
|
7021
|
+
} catch {
|
|
7022
|
+
return null;
|
|
7023
|
+
}
|
|
7024
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
|
|
7025
|
+
return parsed.href;
|
|
7026
|
+
}
|
|
7027
|
+
function linkTitle(title, url) {
|
|
7028
|
+
const safeTitle = sanitizeText(title);
|
|
7029
|
+
const href = safeHttpUrl(url);
|
|
7030
|
+
const isTTY = process.stdout.isTTY;
|
|
7031
|
+
const noColor = process.env["NO_COLOR"] !== void 0;
|
|
7032
|
+
if (isTTY && !noColor && href) {
|
|
7033
|
+
return `\x1B]8;;${href}\x1B\\${safeTitle}\x1B]8;;\x1B\\`;
|
|
7034
|
+
}
|
|
7035
|
+
return href ? `${safeTitle} (${href})` : safeTitle;
|
|
7036
|
+
}
|
|
7037
|
+
|
|
6977
7038
|
// bin/jpi-devs.js
|
|
6978
7039
|
var API_URL2 = process.env["TERMINALHIRE_API_URL"] ?? process.env["JPI_API_URL"] ?? "https://terminalhire.com";
|
|
6979
7040
|
var DEFAULT_LIMIT = 10;
|
|
@@ -6995,24 +7056,18 @@ function credentialUrl(job) {
|
|
|
6995
7056
|
const u = job.url ?? "";
|
|
6996
7057
|
return u.startsWith("http") ? u : `${API_URL2}${u}`;
|
|
6997
7058
|
}
|
|
6998
|
-
function linkTitle(title, url) {
|
|
6999
|
-
const isTTY = process.stdout.isTTY;
|
|
7000
|
-
const noColor = process.env["NO_COLOR"] !== void 0;
|
|
7001
|
-
if (isTTY && !noColor && url) return `\x1B]8;;${url}\x1B\\${title}\x1B]8;;\x1B\\`;
|
|
7002
|
-
return url ? `${title} (${url})` : title;
|
|
7003
|
-
}
|
|
7004
7059
|
function printCard(i, result) {
|
|
7005
7060
|
const { job, score, matchedTags, reason } = result;
|
|
7006
7061
|
const url = credentialUrl(job);
|
|
7007
7062
|
const kind = job.source === "project" ? "project" : "developer";
|
|
7008
|
-
const byline = job.source === "project" ? ` \xB7 by @${job.company}` : "";
|
|
7063
|
+
const byline = job.source === "project" ? ` \xB7 by @${sanitizeText(job.company)}` : "";
|
|
7009
7064
|
const scoreStr = score > 0 ? ` \xB7 match ${Math.round(score * 100)}%` : "";
|
|
7010
7065
|
console.log(`
|
|
7011
7066
|
${i + 1}. ${linkTitle(job.title, url)} \u2014 ${kind}${byline}${scoreStr}`);
|
|
7012
7067
|
console.log(` id: ${job.id}`);
|
|
7013
7068
|
if (reason) console.log(` ${reason}`);
|
|
7014
7069
|
if (matchedTags && matchedTags.length) console.log(` Tags matched: ${matchedTags.slice(0, 5).join(", ")}`);
|
|
7015
|
-
console.log(` Profile: ${url}`);
|
|
7070
|
+
console.log(` Profile: ${sanitizeText(url)}`);
|
|
7016
7071
|
}
|
|
7017
7072
|
async function run() {
|
|
7018
7073
|
try {
|