terminalhire 0.35.3 → 0.36.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/claim-push-bg.js +1 -1
- package/dist/bin/jpi-bounties.js +120 -19
- package/dist/bin/jpi-chat-read.js +28 -8
- package/dist/bin/jpi-chat.js +30 -10
- package/dist/bin/jpi-claim.js +121 -20
- package/dist/bin/jpi-config.js +8 -3
- package/dist/bin/jpi-contribute.js +25 -36
- package/dist/bin/jpi-devs.js +121 -19
- package/dist/bin/jpi-dispatch.js +6496 -501
- package/dist/bin/jpi-hub.js +127 -26
- package/dist/bin/jpi-inbox.js +30 -10
- package/dist/bin/jpi-init.js +118 -17
- package/dist/bin/jpi-intro.js +19 -4
- package/dist/bin/jpi-jobs.js +129 -23
- package/dist/bin/jpi-learn.js +18 -3
- package/dist/bin/jpi-link.js +9 -4
- package/dist/bin/jpi-login.js +129 -23
- package/dist/bin/jpi-mcp-chat.js +27177 -0
- package/dist/bin/jpi-mcp.js +5838 -141
- package/dist/bin/jpi-profile.js +18 -3
- package/dist/bin/jpi-project.js +118 -17
- package/dist/bin/jpi-refresh.js +159 -61
- package/dist/bin/jpi-repo.js +18 -3
- package/dist/bin/jpi-save.js +18 -3
- package/dist/bin/jpi-spinner.js +16 -13
- package/dist/bin/jpi-sync.js +18 -3
- package/dist/bin/jpi-trajectory.js +20 -5
- package/dist/bin/peer-connect-prompt.js +8 -3
- package/dist/bin/pulse-prompt.js +9 -4
- package/dist/bin/spinner.js +16 -15
- package/dist/src/chat-client.js +20 -5
- package/dist/src/chat-keystore.js +18 -3
- package/dist/src/config.js +10 -8
- package/dist/src/crypto-store.js +1 -1
- package/dist/src/github-auth.js +1 -1
- package/dist/src/intro.js +19 -4
- package/dist/src/link.js +9 -4
- package/dist/src/profile.js +18 -3
- package/dist/src/repo-experience.js +18 -3
- package/dist/src/reputation/fetch.js +16 -1
- package/dist/src/signal.js +16 -1
- package/dist/src/trajectory.js +20 -5
- package/dist/src/web-session.js +1 -1
- package/package.json +2 -2
package/dist/bin/jpi-claim.js
CHANGED
|
@@ -1748,6 +1748,32 @@ async function openPRClosingRefs(owner, name, token, signal, governor) {
|
|
|
1748
1748
|
return null;
|
|
1749
1749
|
}
|
|
1750
1750
|
}
|
|
1751
|
+
async function issueCrossRefPRAttempts(owner, name, issueNumber, token, signal, governor) {
|
|
1752
|
+
const url = `https://api.github.com/repos/${owner}/${name}/issues/${issueNumber}/timeline?per_page=100`;
|
|
1753
|
+
try {
|
|
1754
|
+
const res = governor ? await governor.get(url, { headers: ghHeaders(token), signal }) : await fetch(url, { headers: ghHeaders(token), signal });
|
|
1755
|
+
if (!res || !res.ok) return null;
|
|
1756
|
+
const link = res.headers?.get("link") ?? null;
|
|
1757
|
+
const hasNextPage = link != null && /\brel="next"/.test(link);
|
|
1758
|
+
const events = await res.json();
|
|
1759
|
+
if (!Array.isArray(events)) return null;
|
|
1760
|
+
if (hasNextPage || events.length >= 100) return null;
|
|
1761
|
+
let hasOpenPR = false;
|
|
1762
|
+
let hasClosedPR = false;
|
|
1763
|
+
const prNumbers = [];
|
|
1764
|
+
for (const ev of events) {
|
|
1765
|
+
if (ev?.event !== "cross-referenced") continue;
|
|
1766
|
+
const src = ev.source?.issue;
|
|
1767
|
+
if (!src || src.pull_request == null) continue;
|
|
1768
|
+
if (typeof src.number === "number") prNumbers.push(src.number);
|
|
1769
|
+
if (src.state === "open") hasOpenPR = true;
|
|
1770
|
+
else if (src.state === "closed") hasClosedPR = true;
|
|
1771
|
+
}
|
|
1772
|
+
return { hasOpenPR, hasClosedPR, prNumbers };
|
|
1773
|
+
} catch {
|
|
1774
|
+
return null;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1751
1777
|
function makeScoringGovernor(governor) {
|
|
1752
1778
|
return governor ?? makeGitHubGovernor(
|
|
1753
1779
|
((url, init) => fetch(url, init)),
|
|
@@ -3925,6 +3951,30 @@ var init_feeds = __esm({
|
|
|
3925
3951
|
});
|
|
3926
3952
|
|
|
3927
3953
|
// ../../packages/core/src/feeds/contributions.ts
|
|
3954
|
+
function readSearchMaxPages() {
|
|
3955
|
+
const raw = process.env["CONTRIB_SEARCH_MAX_PAGES"];
|
|
3956
|
+
if (raw == null) return DEFAULT_SEARCH_MAX_PAGES;
|
|
3957
|
+
const n = Number.parseInt(raw, 10);
|
|
3958
|
+
if (Number.isNaN(n)) return DEFAULT_SEARCH_MAX_PAGES;
|
|
3959
|
+
return Math.min(Math.max(n, MIN_SEARCH_MAX_PAGES), MAX_SEARCH_MAX_PAGES);
|
|
3960
|
+
}
|
|
3961
|
+
function readMaxContribItems() {
|
|
3962
|
+
const raw = process.env["CONTRIB_MAX_ITEMS"];
|
|
3963
|
+
if (raw == null) return DEFAULT_MAX_CONTRIB_ITEMS;
|
|
3964
|
+
const n = Number.parseInt(raw, 10);
|
|
3965
|
+
if (Number.isNaN(n)) return DEFAULT_MAX_CONTRIB_ITEMS;
|
|
3966
|
+
return Math.min(Math.max(n, MIN_MAX_CONTRIB_ITEMS), MAX_MAX_CONTRIB_ITEMS);
|
|
3967
|
+
}
|
|
3968
|
+
function readMaxContribIssuesScanned() {
|
|
3969
|
+
const raw = process.env["CONTRIB_MAX_ISSUES_SCANNED"];
|
|
3970
|
+
if (raw == null) return DEFAULT_MAX_CONTRIB_ISSUES_SCANNED;
|
|
3971
|
+
const n = Number.parseInt(raw, 10);
|
|
3972
|
+
if (Number.isNaN(n)) return DEFAULT_MAX_CONTRIB_ISSUES_SCANNED;
|
|
3973
|
+
return Math.min(
|
|
3974
|
+
Math.max(n, MIN_MAX_CONTRIB_ISSUES_SCANNED),
|
|
3975
|
+
MAX_MAX_CONTRIB_ISSUES_SCANNED
|
|
3976
|
+
);
|
|
3977
|
+
}
|
|
3928
3978
|
function authHeaders2() {
|
|
3929
3979
|
const token = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"];
|
|
3930
3980
|
const h = {
|
|
@@ -4012,13 +4062,21 @@ async function fetchRateLimit(client) {
|
|
|
4012
4062
|
}
|
|
4013
4063
|
async function searchContribIssues(client, queries) {
|
|
4014
4064
|
const byUrl = /* @__PURE__ */ new Map();
|
|
4065
|
+
const maxPages = readSearchMaxPages();
|
|
4015
4066
|
for (const q of queries) {
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
if (
|
|
4067
|
+
for (let page = 1; page <= maxPages; page++) {
|
|
4068
|
+
const res = await client.json(
|
|
4069
|
+
`/search/issues?q=${encodeURIComponent(q)}&sort=created&order=desc&per_page=${SEARCH_PER_PAGE2}&page=${page}`
|
|
4070
|
+
);
|
|
4071
|
+
const items = res?.items;
|
|
4072
|
+
if (items == null) break;
|
|
4073
|
+
for (const it of items) {
|
|
4074
|
+
if (it.pull_request) continue;
|
|
4075
|
+
if (!byUrl.has(it.html_url)) byUrl.set(it.html_url, it);
|
|
4076
|
+
}
|
|
4077
|
+
if (items.length < SEARCH_PER_PAGE2) break;
|
|
4078
|
+
const stats = client.getStats();
|
|
4079
|
+
if (stats.budgetAborted || stats.secondaryAborted) break;
|
|
4022
4080
|
}
|
|
4023
4081
|
}
|
|
4024
4082
|
return [...byUrl.values()].sort(
|
|
@@ -4054,7 +4112,12 @@ function buildContributionJob(a) {
|
|
|
4054
4112
|
// TERM-27: persist the repo's primary language so project curation can
|
|
4055
4113
|
// exclude the repo's OWN language id (folded into every issue's tags) from
|
|
4056
4114
|
// the distinct-skill signal. Same `repo` used by the tokenize() tag build.
|
|
4057
|
-
language: a.repo.language ?? null
|
|
4115
|
+
language: a.repo.language ?? null,
|
|
4116
|
+
// TERM-35: stamp the search item's comment count (already in the response —
|
|
4117
|
+
// zero extra egress). A NEUTRAL volume signal only: set solely when the
|
|
4118
|
+
// search item carries a finite count >= 0; a failed/absent value leaves it
|
|
4119
|
+
// undefined (never a fabricated 0), so a render's chip falls through cleanly.
|
|
4120
|
+
commentsAtDiscovery: typeof a.issue.comments === "number" && Number.isFinite(a.issue.comments) && a.issue.comments >= 0 ? a.issue.comments : void 0
|
|
4058
4121
|
},
|
|
4059
4122
|
// Provenance: repo-first discovered items only (label-first omits the field).
|
|
4060
4123
|
...a.discovered ? { discovered: true } : {},
|
|
@@ -4074,10 +4137,14 @@ async function aggregateContributions(opts = {}) {
|
|
|
4074
4137
|
probeTimeoutMs: opts.fetchImpl ? null : PROBE_TIMEOUT_MS
|
|
4075
4138
|
});
|
|
4076
4139
|
const queries = opts.queries ?? CONTRIB_SEARCH_QUERIES;
|
|
4140
|
+
const maxContribItems = readMaxContribItems();
|
|
4077
4141
|
const startRl = await fetchRateLimit(client);
|
|
4078
4142
|
const coreHealthyAtStart = (startRl?.core?.remaining ?? 0) >= 500;
|
|
4079
4143
|
client.setSecondaryHint(coreHealthyAtStart);
|
|
4080
|
-
const issues = (await searchContribIssues(client, queries)).slice(
|
|
4144
|
+
const issues = (await searchContribIssues(client, queries)).slice(
|
|
4145
|
+
0,
|
|
4146
|
+
readMaxContribIssuesScanned()
|
|
4147
|
+
);
|
|
4081
4148
|
const repoCache = /* @__PURE__ */ new Map();
|
|
4082
4149
|
const contribCache = /* @__PURE__ */ new Map();
|
|
4083
4150
|
const prRefsCache = /* @__PURE__ */ new Map();
|
|
@@ -4167,7 +4234,7 @@ async function aggregateContributions(opts = {}) {
|
|
|
4167
4234
|
let contribUndefined = 0;
|
|
4168
4235
|
let prRefsNull = 0;
|
|
4169
4236
|
for (const issue of issues) {
|
|
4170
|
-
if (jobs.length >=
|
|
4237
|
+
if (jobs.length >= maxContribItems) break;
|
|
4171
4238
|
const fullName = repoFullNameFromApiUrl2(issue.repository_url);
|
|
4172
4239
|
if (!fullName) continue;
|
|
4173
4240
|
const id = `contribute:${repoKey(fullName)}#${issue.number}`;
|
|
@@ -4225,7 +4292,7 @@ async function aggregateContributions(opts = {}) {
|
|
|
4225
4292
|
const doDiscovery = opts.discoverRepos ?? (opts.trendingSlugs != null || opts.vocabTerms != null);
|
|
4226
4293
|
let discoveredEmitted = 0;
|
|
4227
4294
|
let discoveryBudgetStopped = false;
|
|
4228
|
-
if (doDiscovery && jobs.length <
|
|
4295
|
+
if (doDiscovery && jobs.length < maxContribItems) {
|
|
4229
4296
|
const maxRepos = Math.min(
|
|
4230
4297
|
Math.max(0, opts.maxDiscoveredRepos ?? MAX_DISCOVERED_REPOS),
|
|
4231
4298
|
MAX_DISCOVERED_REPOS
|
|
@@ -4278,7 +4345,7 @@ async function aggregateContributions(opts = {}) {
|
|
|
4278
4345
|
}
|
|
4279
4346
|
const scanned = candidates.slice(0, maxRepos);
|
|
4280
4347
|
for (const fullName of scanned) {
|
|
4281
|
-
if (jobs.length >=
|
|
4348
|
+
if (jobs.length >= maxContribItems) break;
|
|
4282
4349
|
if (client.getStats().budgetAborted || client.getStats().secondaryAborted) {
|
|
4283
4350
|
discoveryBudgetStopped = true;
|
|
4284
4351
|
break;
|
|
@@ -4303,7 +4370,7 @@ async function aggregateContributions(opts = {}) {
|
|
|
4303
4370
|
const repoIssues = (searchRes?.items ?? []).filter((it) => !it.pull_request);
|
|
4304
4371
|
let perRepoDiscovered = 0;
|
|
4305
4372
|
for (const issue of repoIssues) {
|
|
4306
|
-
if (jobs.length >=
|
|
4373
|
+
if (jobs.length >= maxContribItems) break;
|
|
4307
4374
|
if (perRepoDiscovered >= MAX_ISSUES_PER_DISCOVERED_REPO) break;
|
|
4308
4375
|
if ((perRepo.get(repoKey(fullName)) ?? 0) >= MAX_BOUNTIES_PER_DISCOVERED_REPO) break;
|
|
4309
4376
|
const id = `contribute:${repoKey(fullName)}#${issue.number}`;
|
|
@@ -4368,7 +4435,7 @@ async function aggregateContributions(opts = {}) {
|
|
|
4368
4435
|
}
|
|
4369
4436
|
return jobs;
|
|
4370
4437
|
}
|
|
4371
|
-
var GITHUB_API2, CONTRIB_LABEL_QUERIES, CONTRIB_LANGUAGE_QUERIES, CONTRIB_SEARCH_QUERIES, SEARCH_PER_PAGE2,
|
|
4438
|
+
var GITHUB_API2, CONTRIB_LABEL_QUERIES, CONTRIB_LANGUAGE_QUERIES, CONTRIB_SEARCH_QUERIES, SEARCH_PER_PAGE2, DEFAULT_SEARCH_MAX_PAGES, MIN_SEARCH_MAX_PAGES, MAX_SEARCH_MAX_PAGES, DEFAULT_MAX_CONTRIB_ITEMS, MIN_MAX_CONTRIB_ITEMS, MAX_MAX_CONTRIB_ITEMS, DEFAULT_MAX_CONTRIB_ISSUES_SCANNED, MIN_MAX_CONTRIB_ISSUES_SCANNED, MAX_MAX_CONTRIB_ISSUES_SCANNED, MAX_DISCOVERED_REPOS, MAX_ISSUES_PER_DISCOVERED_REPO, DISCOVERY_REPOS_PER_TERM, DISCOVERY_VOCAB_TERMS, DISCOVERY_ISSUE_LABELS, repoKey;
|
|
4372
4439
|
var init_contributions = __esm({
|
|
4373
4440
|
"../../packages/core/src/feeds/contributions.ts"() {
|
|
4374
4441
|
"use strict";
|
|
@@ -4387,7 +4454,11 @@ var init_contributions = __esm({
|
|
|
4387
4454
|
'label:"good-first-issue" type:issue state:open',
|
|
4388
4455
|
'label:"help wanted" type:issue state:open',
|
|
4389
4456
|
'label:"help-wanted" type:issue state:open',
|
|
4390
|
-
'label:"up-for-grabs" type:issue state:open'
|
|
4457
|
+
'label:"up-for-grabs" type:issue state:open',
|
|
4458
|
+
// supply-expansion D: two more first-contribution label families widen the
|
|
4459
|
+
// global newest-first slice WITHOUT relaxing the credential gate.
|
|
4460
|
+
'label:"beginner-friendly" type:issue state:open',
|
|
4461
|
+
'label:"first-timers-only" type:issue state:open'
|
|
4391
4462
|
];
|
|
4392
4463
|
CONTRIB_LANGUAGE_QUERIES = [
|
|
4393
4464
|
...["rust", "go", "python", "c++", "ruby"].map(
|
|
@@ -4395,12 +4466,30 @@ var init_contributions = __esm({
|
|
|
4395
4466
|
),
|
|
4396
4467
|
...["rust", "go"].map(
|
|
4397
4468
|
(lang) => `label:"good first issue" language:${lang} type:issue state:open`
|
|
4469
|
+
),
|
|
4470
|
+
// supply-expansion D: cover the high-volume web/enterprise ecosystems the
|
|
4471
|
+
// original set omitted. TS/JS were previously left out of "good first issue"
|
|
4472
|
+
// (the global slice over-represented them) but a LANGUAGE-scoped page surfaces
|
|
4473
|
+
// DIFFERENT repos than the global newest-first slice, so re-including them widens
|
|
4474
|
+
// distinct-repo coverage rather than duplicating it.
|
|
4475
|
+
...["typescript", "javascript", "java", "python"].map(
|
|
4476
|
+
(lang) => `label:"good first issue" language:${lang} type:issue state:open`
|
|
4477
|
+
),
|
|
4478
|
+
...["typescript", "javascript", "c#", "php"].map(
|
|
4479
|
+
(lang) => `label:"help wanted" language:${lang} type:issue state:open`
|
|
4398
4480
|
)
|
|
4399
4481
|
];
|
|
4400
4482
|
CONTRIB_SEARCH_QUERIES = [...CONTRIB_LABEL_QUERIES, ...CONTRIB_LANGUAGE_QUERIES];
|
|
4401
4483
|
SEARCH_PER_PAGE2 = 100;
|
|
4402
|
-
|
|
4403
|
-
|
|
4484
|
+
DEFAULT_SEARCH_MAX_PAGES = 1;
|
|
4485
|
+
MIN_SEARCH_MAX_PAGES = 1;
|
|
4486
|
+
MAX_SEARCH_MAX_PAGES = 5;
|
|
4487
|
+
DEFAULT_MAX_CONTRIB_ITEMS = 400;
|
|
4488
|
+
MIN_MAX_CONTRIB_ITEMS = 50;
|
|
4489
|
+
MAX_MAX_CONTRIB_ITEMS = 1e3;
|
|
4490
|
+
DEFAULT_MAX_CONTRIB_ISSUES_SCANNED = 1500;
|
|
4491
|
+
MIN_MAX_CONTRIB_ISSUES_SCANNED = 100;
|
|
4492
|
+
MAX_MAX_CONTRIB_ISSUES_SCANNED = 5e3;
|
|
4404
4493
|
MAX_DISCOVERED_REPOS = 15;
|
|
4405
4494
|
MAX_ISSUES_PER_DISCOVERED_REPO = 3;
|
|
4406
4495
|
DISCOVERY_REPOS_PER_TERM = 20;
|
|
@@ -4570,6 +4659,15 @@ async function enrichWinnability(jobs, contribute, w) {
|
|
|
4570
4659
|
);
|
|
4571
4660
|
}
|
|
4572
4661
|
}
|
|
4662
|
+
function hasClickableUrl(url) {
|
|
4663
|
+
if (!url) return false;
|
|
4664
|
+
try {
|
|
4665
|
+
const parsed = new URL(url);
|
|
4666
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
4667
|
+
} catch {
|
|
4668
|
+
return false;
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4573
4671
|
async function buildIndex(opts) {
|
|
4574
4672
|
const includePartners = opts?.includePartners ?? true;
|
|
4575
4673
|
const publicJobs = await aggregate(opts);
|
|
@@ -4580,6 +4678,7 @@ async function buildIndex(opts) {
|
|
|
4580
4678
|
...opts?.partnerRoles ?? []
|
|
4581
4679
|
];
|
|
4582
4680
|
for (const job of partnerJobs) {
|
|
4681
|
+
if (!hasClickableUrl(job.url)) continue;
|
|
4583
4682
|
if (!seen.has(job.id)) {
|
|
4584
4683
|
seen.add(job.id);
|
|
4585
4684
|
allJobs.push(job);
|
|
@@ -4661,7 +4760,8 @@ async function fetchIssueStatus(fullName, issueNumber, opts = {}) {
|
|
|
4661
4760
|
for (const a of body.assignees ?? []) {
|
|
4662
4761
|
if (a && typeof a.login === "string") assignees.add(a.login);
|
|
4663
4762
|
}
|
|
4664
|
-
|
|
4763
|
+
const comments = typeof body.comments === "number" && Number.isFinite(body.comments) && body.comments >= 0 ? body.comments : null;
|
|
4764
|
+
return { state, assignees: [...assignees], comments };
|
|
4665
4765
|
}
|
|
4666
4766
|
var GITHUB_API3, DEFAULT_ISSUE_STATUS_TIMEOUT_MS;
|
|
4667
4767
|
var init_github_issue_status = __esm({
|
|
@@ -8371,6 +8471,7 @@ __export(src_exports, {
|
|
|
8371
8471
|
isOverIntroLimit: () => isOverIntroLimit,
|
|
8372
8472
|
isTrivialPRTitle: () => isTrivialPRTitle,
|
|
8373
8473
|
isWinnableIssue: () => isWinnableIssue,
|
|
8474
|
+
issueCrossRefPRAttempts: () => issueCrossRefPRAttempts,
|
|
8374
8475
|
jobShortToken: () => jobShortToken,
|
|
8375
8476
|
joinLabels: () => joinLabels,
|
|
8376
8477
|
labelFor: () => labelFor,
|
|
@@ -8937,7 +9038,7 @@ var TERMINALHIRE_DIR4, KEY_FILE2, KEYTAR_SERVICE, KEYTAR_ACCOUNT, ALGO2, KEY_BYT
|
|
|
8937
9038
|
var init_crypto_store = __esm({
|
|
8938
9039
|
"src/crypto-store.ts"() {
|
|
8939
9040
|
"use strict";
|
|
8940
|
-
TERMINALHIRE_DIR4 = join5(homedir4(), ".terminalhire");
|
|
9041
|
+
TERMINALHIRE_DIR4 = process.env.TERMINALHIRE_DIR || join5(homedir4(), ".terminalhire");
|
|
8941
9042
|
KEY_FILE2 = join5(TERMINALHIRE_DIR4, "key");
|
|
8942
9043
|
KEYTAR_SERVICE = "terminalhire";
|
|
8943
9044
|
KEYTAR_ACCOUNT = "profile-key";
|
|
@@ -9087,7 +9188,7 @@ var init_profile = __esm({
|
|
|
9087
9188
|
"use strict";
|
|
9088
9189
|
init_src();
|
|
9089
9190
|
init_crypto_store();
|
|
9090
|
-
TERMINALHIRE_DIR5 = join6(homedir5(), ".terminalhire");
|
|
9191
|
+
TERMINALHIRE_DIR5 = process.env.TERMINALHIRE_DIR || join6(homedir5(), ".terminalhire");
|
|
9091
9192
|
PROFILE_FILE = join6(TERMINALHIRE_DIR5, "profile.enc");
|
|
9092
9193
|
profileStore = createEncryptedStore(PROFILE_FILE, {
|
|
9093
9194
|
blank: blankProfile,
|
|
@@ -9726,7 +9827,7 @@ import {
|
|
|
9726
9827
|
} from "fs";
|
|
9727
9828
|
import { join as join3 } from "path";
|
|
9728
9829
|
import { homedir as homedir2 } from "os";
|
|
9729
|
-
var TERMINALHIRE_DIR2 = join3(homedir2(), ".terminalhire");
|
|
9830
|
+
var TERMINALHIRE_DIR2 = process.env.TERMINALHIRE_DIR || join3(homedir2(), ".terminalhire");
|
|
9730
9831
|
var TOKEN_FILE = join3(TERMINALHIRE_DIR2, "github-token.enc");
|
|
9731
9832
|
var KEY_FILE = join3(TERMINALHIRE_DIR2, "key");
|
|
9732
9833
|
var ALGO = "aes-256-gcm";
|
package/dist/bin/jpi-config.js
CHANGED
|
@@ -8,7 +8,7 @@ import { homedir as homedir2 } from "os";
|
|
|
8
8
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
9
9
|
import { join } from "path";
|
|
10
10
|
import { homedir } from "os";
|
|
11
|
-
var TERMINALHIRE_DIR = join(homedir(), ".terminalhire");
|
|
11
|
+
var TERMINALHIRE_DIR = process.env.TERMINALHIRE_DIR || join(homedir(), ".terminalhire");
|
|
12
12
|
var CONFIG_FILE = join(TERMINALHIRE_DIR, "config.json");
|
|
13
13
|
var DEFAULT_CONFIG = {
|
|
14
14
|
nudge: "session",
|
|
@@ -19,8 +19,7 @@ var DEFAULT_CONFIG = {
|
|
|
19
19
|
chatShareActivity: false,
|
|
20
20
|
inboundNudgeMuted: false,
|
|
21
21
|
inboundNudgeDisclosed: false,
|
|
22
|
-
contributeEnabled:
|
|
23
|
-
contributePrompted: false,
|
|
22
|
+
contributeEnabled: true,
|
|
24
23
|
betaOptIn: false,
|
|
25
24
|
lastFullFeedbackAt: null,
|
|
26
25
|
lastPulseAskAt: null,
|
|
@@ -41,6 +40,12 @@ function writeConfig(config) {
|
|
|
41
40
|
mkdirSync(TERMINALHIRE_DIR, { recursive: true });
|
|
42
41
|
const current = readConfig();
|
|
43
42
|
const merged = { ...current, ...config };
|
|
43
|
+
if ("contributePrompted" in merged) {
|
|
44
|
+
if (merged.contributeEnabled === false && !("contributeEnabled" in config)) {
|
|
45
|
+
delete merged.contributeEnabled;
|
|
46
|
+
}
|
|
47
|
+
delete merged.contributePrompted;
|
|
48
|
+
}
|
|
44
49
|
writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
45
50
|
}
|
|
46
51
|
function parseSurfaceMix(raw) {
|
|
@@ -1280,7 +1280,11 @@ var init_contributions = __esm({
|
|
|
1280
1280
|
'label:"good-first-issue" type:issue state:open',
|
|
1281
1281
|
'label:"help wanted" type:issue state:open',
|
|
1282
1282
|
'label:"help-wanted" type:issue state:open',
|
|
1283
|
-
'label:"up-for-grabs" type:issue state:open'
|
|
1283
|
+
'label:"up-for-grabs" type:issue state:open',
|
|
1284
|
+
// supply-expansion D: two more first-contribution label families widen the
|
|
1285
|
+
// global newest-first slice WITHOUT relaxing the credential gate.
|
|
1286
|
+
'label:"beginner-friendly" type:issue state:open',
|
|
1287
|
+
'label:"first-timers-only" type:issue state:open'
|
|
1284
1288
|
];
|
|
1285
1289
|
CONTRIB_LANGUAGE_QUERIES = [
|
|
1286
1290
|
...["rust", "go", "python", "c++", "ruby"].map(
|
|
@@ -1288,6 +1292,17 @@ var init_contributions = __esm({
|
|
|
1288
1292
|
),
|
|
1289
1293
|
...["rust", "go"].map(
|
|
1290
1294
|
(lang) => `label:"good first issue" language:${lang} type:issue state:open`
|
|
1295
|
+
),
|
|
1296
|
+
// supply-expansion D: cover the high-volume web/enterprise ecosystems the
|
|
1297
|
+
// original set omitted. TS/JS were previously left out of "good first issue"
|
|
1298
|
+
// (the global slice over-represented them) but a LANGUAGE-scoped page surfaces
|
|
1299
|
+
// DIFFERENT repos than the global newest-first slice, so re-including them widens
|
|
1300
|
+
// distinct-repo coverage rather than duplicating it.
|
|
1301
|
+
...["typescript", "javascript", "java", "python"].map(
|
|
1302
|
+
(lang) => `label:"good first issue" language:${lang} type:issue state:open`
|
|
1303
|
+
),
|
|
1304
|
+
...["typescript", "javascript", "c#", "php"].map(
|
|
1305
|
+
(lang) => `label:"help wanted" language:${lang} type:issue state:open`
|
|
1291
1306
|
)
|
|
1292
1307
|
];
|
|
1293
1308
|
CONTRIB_SEARCH_QUERIES = [...CONTRIB_LABEL_QUERIES, ...CONTRIB_LANGUAGE_QUERIES];
|
|
@@ -1633,7 +1648,7 @@ var TERMINALHIRE_DIR3, KEY_FILE, KEYTAR_SERVICE, KEYTAR_ACCOUNT, ALGO, KEY_BYTES
|
|
|
1633
1648
|
var init_crypto_store = __esm({
|
|
1634
1649
|
"src/crypto-store.ts"() {
|
|
1635
1650
|
"use strict";
|
|
1636
|
-
TERMINALHIRE_DIR3 = join4(homedir3(), ".terminalhire");
|
|
1651
|
+
TERMINALHIRE_DIR3 = process.env.TERMINALHIRE_DIR || join4(homedir3(), ".terminalhire");
|
|
1637
1652
|
KEY_FILE = join4(TERMINALHIRE_DIR3, "key");
|
|
1638
1653
|
KEYTAR_SERVICE = "terminalhire";
|
|
1639
1654
|
KEYTAR_ACCOUNT = "profile-key";
|
|
@@ -1783,7 +1798,7 @@ var init_profile = __esm({
|
|
|
1783
1798
|
"use strict";
|
|
1784
1799
|
init_src();
|
|
1785
1800
|
init_crypto_store();
|
|
1786
|
-
TERMINALHIRE_DIR4 = join5(homedir4(), ".terminalhire");
|
|
1801
|
+
TERMINALHIRE_DIR4 = process.env.TERMINALHIRE_DIR || join5(homedir4(), ".terminalhire");
|
|
1787
1802
|
PROFILE_FILE = join5(TERMINALHIRE_DIR4, "profile.enc");
|
|
1788
1803
|
profileStore = createEncryptedStore(PROFILE_FILE, {
|
|
1789
1804
|
blank: blankProfile,
|
|
@@ -2239,7 +2254,6 @@ init_src();
|
|
|
2239
2254
|
import { readFileSync as readFileSync6 } from "fs";
|
|
2240
2255
|
import { join as join8 } from "path";
|
|
2241
2256
|
import { homedir as homedir7 } from "os";
|
|
2242
|
-
import { createInterface } from "readline";
|
|
2243
2257
|
|
|
2244
2258
|
// bin/cache-store.js
|
|
2245
2259
|
import { readFileSync as readFileSync2, writeFileSync, mkdirSync, renameSync } from "fs";
|
|
@@ -2275,7 +2289,7 @@ function updateIndexCache(patch) {
|
|
|
2275
2289
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync } from "fs";
|
|
2276
2290
|
import { join as join3 } from "path";
|
|
2277
2291
|
import { homedir as homedir2 } from "os";
|
|
2278
|
-
var TERMINALHIRE_DIR2 = join3(homedir2(), ".terminalhire");
|
|
2292
|
+
var TERMINALHIRE_DIR2 = process.env.TERMINALHIRE_DIR || join3(homedir2(), ".terminalhire");
|
|
2279
2293
|
var CONFIG_FILE = join3(TERMINALHIRE_DIR2, "config.json");
|
|
2280
2294
|
var DEFAULT_CONFIG = {
|
|
2281
2295
|
nudge: "session",
|
|
@@ -2286,8 +2300,7 @@ var DEFAULT_CONFIG = {
|
|
|
2286
2300
|
chatShareActivity: false,
|
|
2287
2301
|
inboundNudgeMuted: false,
|
|
2288
2302
|
inboundNudgeDisclosed: false,
|
|
2289
|
-
contributeEnabled:
|
|
2290
|
-
contributePrompted: false,
|
|
2303
|
+
contributeEnabled: true,
|
|
2291
2304
|
betaOptIn: false,
|
|
2292
2305
|
lastFullFeedbackAt: null,
|
|
2293
2306
|
lastPulseAskAt: null,
|
|
@@ -2304,14 +2317,9 @@ function readConfig() {
|
|
|
2304
2317
|
return { ...DEFAULT_CONFIG };
|
|
2305
2318
|
}
|
|
2306
2319
|
}
|
|
2307
|
-
function writeConfig(config) {
|
|
2308
|
-
mkdirSync2(TERMINALHIRE_DIR2, { recursive: true });
|
|
2309
|
-
const current = readConfig();
|
|
2310
|
-
const merged = { ...current, ...config };
|
|
2311
|
-
writeFileSync2(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
2312
|
-
}
|
|
2313
2320
|
function isContributeEnabled() {
|
|
2314
|
-
|
|
2321
|
+
const cfg = readConfig();
|
|
2322
|
+
return !(cfg.contributeEnabled === false && !("contributePrompted" in cfg));
|
|
2315
2323
|
}
|
|
2316
2324
|
|
|
2317
2325
|
// bin/sanitize.js
|
|
@@ -2352,7 +2360,7 @@ var INDEX_TTL_MS = 15 * 60 * 1e3;
|
|
|
2352
2360
|
var API_URL = process.env["TERMINALHIRE_API_URL"] ?? process.env["JPI_API_URL"] ?? "https://terminalhire.com";
|
|
2353
2361
|
var CONTINUITY_RANK_DISABLED = process.env["TERMINALHIRE_NO_CONTINUITY_RANK"] === "1";
|
|
2354
2362
|
var HEADER = "Contribution opportunities \u2014 open issues where a merged PR actually counts toward your r\xE9sum\xE9.\nRepos \u226550\u2605 / \u226510 contributors \xB7 unassigned \xB7 merit-merge \xB7 matched to your stack.";
|
|
2355
|
-
var
|
|
2363
|
+
var CONTRIBUTE_OFF = "Contribute is off \u2014 you set contributeEnabled: false in ~/.terminalhire/config.json.\nRemove that line (or set it to true) to see open issues where a merged PR would count toward your r\xE9sum\xE9.";
|
|
2356
2364
|
var EMPTY_STATE = "Nothing clears the bar right now. We only list issues where a merged PR actually counts toward\nyour r\xE9sum\xE9 \u2014 so the list stays honest. Try again after the next refresh.";
|
|
2357
2365
|
function readIndexCache() {
|
|
2358
2366
|
try {
|
|
@@ -2377,15 +2385,6 @@ async function fetchIndex(fetchImpl, useCache = true) {
|
|
|
2377
2385
|
if (useCache) writeIndexCache(index);
|
|
2378
2386
|
return index;
|
|
2379
2387
|
}
|
|
2380
|
-
function defaultPrompt(question) {
|
|
2381
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
2382
|
-
return new Promise((resolve) => {
|
|
2383
|
-
rl.question(question, (answer) => {
|
|
2384
|
-
rl.close();
|
|
2385
|
-
resolve(answer.trim().toLowerCase());
|
|
2386
|
-
});
|
|
2387
|
-
});
|
|
2388
|
-
}
|
|
2389
2388
|
var STRONG_THRESHOLD = 0.5;
|
|
2390
2389
|
if (STRONG_THRESHOLD <= 0) {
|
|
2391
2390
|
throw new Error("STRONG_THRESHOLD must be > 0 or the contested-issue fail-safe breaks");
|
|
@@ -2496,22 +2495,12 @@ ${line3}`;
|
|
|
2496
2495
|
}
|
|
2497
2496
|
async function run(opts = {}) {
|
|
2498
2497
|
const log = opts.log ?? console.log;
|
|
2499
|
-
const isTTY = opts.isTTY ?? Boolean(process.stdin.isTTY);
|
|
2500
|
-
const promptImpl = opts.prompt ?? defaultPrompt;
|
|
2501
2498
|
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
2502
2499
|
const useCache = opts.fetchImpl == null;
|
|
2503
2500
|
try {
|
|
2504
2501
|
if (!isContributeEnabled()) {
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
const yes = answer === "y" || answer === "yes";
|
|
2508
|
-
writeConfig(yes ? { contributeEnabled: true, contributePrompted: true } : { contributePrompted: true });
|
|
2509
|
-
if (!yes) return;
|
|
2510
|
-
} else {
|
|
2511
|
-
log(OPT_IN_PROMPT);
|
|
2512
|
-
writeConfig({ contributePrompted: true });
|
|
2513
|
-
return;
|
|
2514
|
-
}
|
|
2502
|
+
log(CONTRIBUTE_OFF);
|
|
2503
|
+
return;
|
|
2515
2504
|
}
|
|
2516
2505
|
const index = await fetchIndex(fetchImpl, useCache);
|
|
2517
2506
|
const items = Array.isArray(index?.contribute) ? index.contribute : [];
|