terminalhire 0.19.2 → 0.21.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 +409 -0
- package/dist/bin/jpi-bounties.js +124 -15
- package/dist/bin/jpi-chat-read.js +19 -2
- package/dist/bin/jpi-chat.js +19 -2
- package/dist/bin/jpi-claim.js +331 -136
- package/dist/bin/jpi-contribute.js +19 -2
- package/dist/bin/jpi-devs.js +125 -16
- package/dist/bin/jpi-dispatch.js +659 -265
- package/dist/bin/jpi-inbox.js +19 -2
- package/dist/bin/jpi-init.js +6921 -28
- package/dist/bin/jpi-intro.js +19 -2
- package/dist/bin/jpi-jobs.js +124 -15
- package/dist/bin/jpi-learn.js +19 -2
- package/dist/bin/jpi-login.js +124 -15
- package/dist/bin/jpi-profile.js +19 -2
- package/dist/bin/jpi-project.js +166 -30
- package/dist/bin/jpi-refresh.js +516 -65
- package/dist/bin/jpi-save.js +19 -2
- package/dist/bin/jpi-spinner.js +18 -0
- package/dist/bin/jpi-sync.js +19 -2
- package/dist/bin/jpi-trajectory.js +19 -2
- package/dist/bin/spinner.js +20 -22
- package/dist/src/chat-client.js +20 -2
- package/dist/src/chat-keystore.js +20 -2
- package/dist/src/claims.js +11 -1
- package/dist/src/github-auth.js +2 -2
- package/dist/src/intro.js +19 -2
- package/dist/src/profile.js +20 -2
- package/dist/src/signal.js +18 -0
- package/dist/src/trajectory.js +19 -2
- package/package.json +1 -1
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
9
|
+
}) : x)(function(x) {
|
|
10
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
11
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
|
+
});
|
|
13
|
+
var __esm = (fn, res) => function __init() {
|
|
14
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
15
|
+
};
|
|
16
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
17
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
18
|
+
};
|
|
19
|
+
var __export = (target, all) => {
|
|
20
|
+
for (var name in all)
|
|
21
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
22
|
+
};
|
|
23
|
+
var __copyProps = (to, from, except, desc) => {
|
|
24
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
25
|
+
for (let key of __getOwnPropNames(from))
|
|
26
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
27
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
28
|
+
}
|
|
29
|
+
return to;
|
|
30
|
+
};
|
|
31
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
32
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
33
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
34
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
35
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
36
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
37
|
+
mod
|
|
38
|
+
));
|
|
39
|
+
|
|
40
|
+
// ../../node_modules/keytar/build/Release/keytar.node
|
|
41
|
+
var keytar_default;
|
|
42
|
+
var init_keytar = __esm({
|
|
43
|
+
"../../node_modules/keytar/build/Release/keytar.node"() {
|
|
44
|
+
keytar_default = "../keytar-KOAAH267.node";
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
|
|
49
|
+
var require_keytar = __commonJS({
|
|
50
|
+
"node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
|
|
51
|
+
"use strict";
|
|
52
|
+
init_keytar();
|
|
53
|
+
try {
|
|
54
|
+
module.exports = __require(keytar_default);
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// ../../node_modules/keytar/lib/keytar.js
|
|
61
|
+
var require_keytar2 = __commonJS({
|
|
62
|
+
"../../node_modules/keytar/lib/keytar.js"(exports, module) {
|
|
63
|
+
"use strict";
|
|
64
|
+
var keytar = require_keytar();
|
|
65
|
+
function checkRequired(val, name) {
|
|
66
|
+
if (!val || val.length <= 0) {
|
|
67
|
+
throw new Error(name + " is required.");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
module.exports = {
|
|
71
|
+
getPassword: function(service, account) {
|
|
72
|
+
checkRequired(service, "Service");
|
|
73
|
+
checkRequired(account, "Account");
|
|
74
|
+
return keytar.getPassword(service, account);
|
|
75
|
+
},
|
|
76
|
+
setPassword: function(service, account, password) {
|
|
77
|
+
checkRequired(service, "Service");
|
|
78
|
+
checkRequired(account, "Account");
|
|
79
|
+
checkRequired(password, "Password");
|
|
80
|
+
return keytar.setPassword(service, account, password);
|
|
81
|
+
},
|
|
82
|
+
deletePassword: function(service, account) {
|
|
83
|
+
checkRequired(service, "Service");
|
|
84
|
+
checkRequired(account, "Account");
|
|
85
|
+
return keytar.deletePassword(service, account);
|
|
86
|
+
},
|
|
87
|
+
findPassword: function(service) {
|
|
88
|
+
checkRequired(service, "Service");
|
|
89
|
+
return keytar.findPassword(service);
|
|
90
|
+
},
|
|
91
|
+
findCredentials: function(service) {
|
|
92
|
+
checkRequired(service, "Service");
|
|
93
|
+
return keytar.findCredentials(service);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// src/claims.ts
|
|
100
|
+
var claims_exports = {};
|
|
101
|
+
__export(claims_exports, {
|
|
102
|
+
PUSHED_CLAIM_FIELDS: () => PUSHED_CLAIM_FIELDS,
|
|
103
|
+
acceptedPRRate: () => acceptedPRRate,
|
|
104
|
+
findClaim: () => findClaim,
|
|
105
|
+
listClaims: () => listClaims,
|
|
106
|
+
readClaims: () => readClaims,
|
|
107
|
+
recordClaim: () => recordClaim,
|
|
108
|
+
removeClaim: () => removeClaim,
|
|
109
|
+
toPushedClaim: () => toPushedClaim,
|
|
110
|
+
updateClaim: () => updateClaim
|
|
111
|
+
});
|
|
112
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, renameSync, existsSync as existsSync2 } from "fs";
|
|
113
|
+
import { join as join2 } from "path";
|
|
114
|
+
import { homedir as homedir2 } from "os";
|
|
115
|
+
function toPushedClaim(claim) {
|
|
116
|
+
return {
|
|
117
|
+
kind: claim.kind,
|
|
118
|
+
repoFullName: claim.repoFullName,
|
|
119
|
+
state: claim.state,
|
|
120
|
+
prUrl: claim.prUrl,
|
|
121
|
+
merged: claim.state === "merged",
|
|
122
|
+
claimedAt: claim.claimedAt,
|
|
123
|
+
updatedAt: claim.updatedAt
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function nowISO() {
|
|
127
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
128
|
+
}
|
|
129
|
+
function normalizeClaim(c) {
|
|
130
|
+
return { ...c, kind: c.kind ?? "bounty", policy: c.policy ?? null };
|
|
131
|
+
}
|
|
132
|
+
function readClaims() {
|
|
133
|
+
try {
|
|
134
|
+
if (!existsSync2(CLAIMS_FILE)) return [];
|
|
135
|
+
const data = JSON.parse(readFileSync2(CLAIMS_FILE, "utf8"));
|
|
136
|
+
const claims = Array.isArray(data?.claims) ? data.claims : [];
|
|
137
|
+
return claims.map(normalizeClaim);
|
|
138
|
+
} catch {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function writeClaims(claims) {
|
|
143
|
+
mkdirSync2(TERMINALHIRE_DIR2, { recursive: true });
|
|
144
|
+
const tmp = `${CLAIMS_FILE}.tmp`;
|
|
145
|
+
const payload = { claims };
|
|
146
|
+
writeFileSync2(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
147
|
+
renameSync(tmp, CLAIMS_FILE);
|
|
148
|
+
}
|
|
149
|
+
function findClaim(id) {
|
|
150
|
+
return readClaims().find((c) => c.id === id) ?? null;
|
|
151
|
+
}
|
|
152
|
+
function listClaims(opts = {}) {
|
|
153
|
+
const claims = readClaims();
|
|
154
|
+
if (!opts.active) return claims;
|
|
155
|
+
return claims.filter((c) => !TERMINAL_STATES.has(c.state));
|
|
156
|
+
}
|
|
157
|
+
function recordClaim(rec) {
|
|
158
|
+
const claims = readClaims();
|
|
159
|
+
if (claims.some((c) => c.id === rec.id)) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`claim already exists for '${rec.id}' \u2014 run 'terminalhire claim status ${rec.id}' or 'terminalhire claim release ${rec.id}'`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
const ts = nowISO();
|
|
165
|
+
const claim = {
|
|
166
|
+
...rec,
|
|
167
|
+
// Defensive default (mirrors normalizeClaim's `kind ?? 'bounty'` pattern):
|
|
168
|
+
// a caller written before `policy` existed, or a plain-JS caller that skips
|
|
169
|
+
// it, still produces a valid record instead of `policy: undefined`.
|
|
170
|
+
policy: rec.policy ?? null,
|
|
171
|
+
state: "claimed",
|
|
172
|
+
worktreePath: null,
|
|
173
|
+
branch: null,
|
|
174
|
+
prUrl: null,
|
|
175
|
+
review: null,
|
|
176
|
+
claimedAt: ts,
|
|
177
|
+
updatedAt: ts
|
|
178
|
+
};
|
|
179
|
+
claims.push(claim);
|
|
180
|
+
writeClaims(claims);
|
|
181
|
+
return claim;
|
|
182
|
+
}
|
|
183
|
+
function updateClaim(id, patch) {
|
|
184
|
+
const claims = readClaims();
|
|
185
|
+
const idx = claims.findIndex((c) => c.id === id);
|
|
186
|
+
if (idx === -1) return null;
|
|
187
|
+
claims[idx] = { ...claims[idx], ...patch, updatedAt: nowISO() };
|
|
188
|
+
writeClaims(claims);
|
|
189
|
+
return claims[idx];
|
|
190
|
+
}
|
|
191
|
+
function removeClaim(id) {
|
|
192
|
+
const claims = readClaims();
|
|
193
|
+
const next = claims.filter((c) => c.id !== id);
|
|
194
|
+
if (next.length === claims.length) return false;
|
|
195
|
+
writeClaims(next);
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
function acceptedPRRate(claims = readClaims()) {
|
|
199
|
+
const total = claims.length;
|
|
200
|
+
const merged = claims.filter((c) => c.state === "merged").length;
|
|
201
|
+
return { merged, total, rate: total === 0 ? 0 : merged / total };
|
|
202
|
+
}
|
|
203
|
+
var TERMINALHIRE_DIR2, CLAIMS_FILE, PUSHED_CLAIM_FIELDS, TERMINAL_STATES;
|
|
204
|
+
var init_claims = __esm({
|
|
205
|
+
"src/claims.ts"() {
|
|
206
|
+
"use strict";
|
|
207
|
+
TERMINALHIRE_DIR2 = process.env.TERMINALHIRE_DIR || join2(homedir2(), ".terminalhire");
|
|
208
|
+
CLAIMS_FILE = join2(TERMINALHIRE_DIR2, "claims.json");
|
|
209
|
+
PUSHED_CLAIM_FIELDS = [
|
|
210
|
+
"kind",
|
|
211
|
+
"repoFullName",
|
|
212
|
+
"state",
|
|
213
|
+
"prUrl",
|
|
214
|
+
"merged",
|
|
215
|
+
"claimedAt",
|
|
216
|
+
"updatedAt"
|
|
217
|
+
];
|
|
218
|
+
TERMINAL_STATES = /* @__PURE__ */ new Set(["merged", "abandoned"]);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// bin/claim-push-bg.js
|
|
223
|
+
import { createHash } from "crypto";
|
|
224
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync3, rmSync as rmSync2 } from "fs";
|
|
225
|
+
import { join as join3 } from "path";
|
|
226
|
+
import { homedir as homedir3 } from "os";
|
|
227
|
+
|
|
228
|
+
// src/github-auth.ts
|
|
229
|
+
import {
|
|
230
|
+
createCipheriv,
|
|
231
|
+
createDecipheriv,
|
|
232
|
+
randomBytes
|
|
233
|
+
} from "crypto";
|
|
234
|
+
import {
|
|
235
|
+
readFileSync,
|
|
236
|
+
writeFileSync,
|
|
237
|
+
mkdirSync,
|
|
238
|
+
existsSync,
|
|
239
|
+
rmSync
|
|
240
|
+
} from "fs";
|
|
241
|
+
import { join } from "path";
|
|
242
|
+
import { homedir } from "os";
|
|
243
|
+
var TERMINALHIRE_DIR = join(homedir(), ".terminalhire");
|
|
244
|
+
var TOKEN_FILE = join(TERMINALHIRE_DIR, "github-token.enc");
|
|
245
|
+
var KEY_FILE = join(TERMINALHIRE_DIR, "key");
|
|
246
|
+
var ALGO = "aes-256-gcm";
|
|
247
|
+
var KEY_BYTES = 32;
|
|
248
|
+
var IV_BYTES = 12;
|
|
249
|
+
async function loadKey() {
|
|
250
|
+
try {
|
|
251
|
+
const kt = await Promise.resolve().then(() => __toESM(require_keytar2(), 1));
|
|
252
|
+
const stored = await kt.getPassword("terminalhire", "profile-key");
|
|
253
|
+
if (stored) return Buffer.from(stored, "hex");
|
|
254
|
+
const key2 = randomBytes(KEY_BYTES);
|
|
255
|
+
await kt.setPassword("terminalhire", "profile-key", key2.toString("hex"));
|
|
256
|
+
return key2;
|
|
257
|
+
} catch {
|
|
258
|
+
}
|
|
259
|
+
mkdirSync(TERMINALHIRE_DIR, { recursive: true });
|
|
260
|
+
if (existsSync(KEY_FILE)) {
|
|
261
|
+
return Buffer.from(readFileSync(KEY_FILE, "utf8").trim(), "hex");
|
|
262
|
+
}
|
|
263
|
+
const key = randomBytes(KEY_BYTES);
|
|
264
|
+
writeFileSync(KEY_FILE, key.toString("hex"), { mode: 384, encoding: "utf8" });
|
|
265
|
+
return key;
|
|
266
|
+
}
|
|
267
|
+
function encrypt(plaintext, key) {
|
|
268
|
+
const iv = randomBytes(IV_BYTES);
|
|
269
|
+
const cipher = createCipheriv(ALGO, key, iv);
|
|
270
|
+
const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
271
|
+
const tag = cipher.getAuthTag();
|
|
272
|
+
return { iv: iv.toString("hex"), tag: tag.toString("hex"), ciphertext: ct.toString("hex") };
|
|
273
|
+
}
|
|
274
|
+
function decrypt(blob, key) {
|
|
275
|
+
const decipher = createDecipheriv(ALGO, key, Buffer.from(blob.iv, "hex"));
|
|
276
|
+
decipher.setAuthTag(Buffer.from(blob.tag, "hex"));
|
|
277
|
+
const plain = Buffer.concat([
|
|
278
|
+
decipher.update(Buffer.from(blob.ciphertext, "hex")),
|
|
279
|
+
decipher.final()
|
|
280
|
+
]);
|
|
281
|
+
return plain.toString("utf8");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// bin/claim-push-bg.js
|
|
285
|
+
var TERMINALHIRE_DIR3 = process.env.TERMINALHIRE_DIR || join3(homedir3(), ".terminalhire");
|
|
286
|
+
var CLAIM_PUSH_AUTO_MARKER = join3(TERMINALHIRE_DIR3, "claim-push-auto.json");
|
|
287
|
+
var CLAIM_PUSH_TOKEN_FILE = join3(TERMINALHIRE_DIR3, "claim-push-token.enc");
|
|
288
|
+
var CLAIM_SYNC_BASE = "https://terminalhire.com";
|
|
289
|
+
var AUTO_CONSENT_VERSION = 2;
|
|
290
|
+
var AUTO_PUSH_THROTTLE_MS = 24 * 60 * 60 * 1e3;
|
|
291
|
+
async function writePushTokenEnc(rawToken) {
|
|
292
|
+
mkdirSync3(TERMINALHIRE_DIR3, { recursive: true });
|
|
293
|
+
const key = await loadKey();
|
|
294
|
+
const blob = encrypt(rawToken, key);
|
|
295
|
+
writeFileSync3(CLAIM_PUSH_TOKEN_FILE, JSON.stringify(blob, null, 2), { encoding: "utf8" });
|
|
296
|
+
}
|
|
297
|
+
async function readPushTokenEnc() {
|
|
298
|
+
if (!existsSync3(CLAIM_PUSH_TOKEN_FILE)) return void 0;
|
|
299
|
+
try {
|
|
300
|
+
const key = await loadKey();
|
|
301
|
+
const blob = JSON.parse(readFileSync3(CLAIM_PUSH_TOKEN_FILE, "utf8"));
|
|
302
|
+
return decrypt(blob, key);
|
|
303
|
+
} catch {
|
|
304
|
+
return void 0;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function clearPushTokenEnc() {
|
|
308
|
+
try {
|
|
309
|
+
rmSync2(CLAIM_PUSH_TOKEN_FILE);
|
|
310
|
+
} catch {
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function readAutoMarker() {
|
|
314
|
+
try {
|
|
315
|
+
return existsSync3(CLAIM_PUSH_AUTO_MARKER) ? JSON.parse(readFileSync3(CLAIM_PUSH_AUTO_MARKER, "utf8")) : null;
|
|
316
|
+
} catch {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function writeAutoMarker(marker) {
|
|
321
|
+
mkdirSync3(TERMINALHIRE_DIR3, { recursive: true });
|
|
322
|
+
writeFileSync3(CLAIM_PUSH_AUTO_MARKER, JSON.stringify(marker, null, 2) + "\n", "utf8");
|
|
323
|
+
}
|
|
324
|
+
function clearAutoMarker() {
|
|
325
|
+
try {
|
|
326
|
+
rmSync2(CLAIM_PUSH_AUTO_MARKER);
|
|
327
|
+
} catch {
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function computeSnapshotHash(pushed) {
|
|
331
|
+
return createHash("sha256").update(JSON.stringify(pushed)).digest("hex");
|
|
332
|
+
}
|
|
333
|
+
function backgroundPushGate(params) {
|
|
334
|
+
const {
|
|
335
|
+
autoMarkerExists,
|
|
336
|
+
tokenFileExists,
|
|
337
|
+
lastPushedAt,
|
|
338
|
+
now,
|
|
339
|
+
throttleMs,
|
|
340
|
+
currentHash,
|
|
341
|
+
lastSnapshotHash
|
|
342
|
+
} = params;
|
|
343
|
+
if (!autoMarkerExists || !tokenFileExists) {
|
|
344
|
+
return { push: false, reason: "not-opted-in" };
|
|
345
|
+
}
|
|
346
|
+
const last = lastPushedAt ? Date.parse(lastPushedAt) : NaN;
|
|
347
|
+
if (!Number.isNaN(last) && now - last < throttleMs) {
|
|
348
|
+
return { push: false, reason: "throttled" };
|
|
349
|
+
}
|
|
350
|
+
if (lastSnapshotHash && lastSnapshotHash === currentHash) {
|
|
351
|
+
return { push: false, reason: "unchanged" };
|
|
352
|
+
}
|
|
353
|
+
return { push: true, reason: "ok" };
|
|
354
|
+
}
|
|
355
|
+
async function runBackgroundClaimPush({ now = Date.now() } = {}) {
|
|
356
|
+
try {
|
|
357
|
+
if (!existsSync3(CLAIM_PUSH_AUTO_MARKER) || !existsSync3(CLAIM_PUSH_TOKEN_FILE)) return;
|
|
358
|
+
const marker = readAutoMarker();
|
|
359
|
+
if (!marker || !marker.autoConsentedAt) return;
|
|
360
|
+
const { listClaims: listClaims2, toPushedClaim: toPushedClaim2, PUSHED_CLAIM_FIELDS: PUSHED_CLAIM_FIELDS2 } = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
361
|
+
const pushed = listClaims2().map((c) => toPushedClaim2(c));
|
|
362
|
+
const currentHash = computeSnapshotHash(pushed);
|
|
363
|
+
const gate = backgroundPushGate({
|
|
364
|
+
autoMarkerExists: true,
|
|
365
|
+
tokenFileExists: true,
|
|
366
|
+
lastPushedAt: marker.lastPushedAt ?? null,
|
|
367
|
+
now,
|
|
368
|
+
throttleMs: AUTO_PUSH_THROTTLE_MS,
|
|
369
|
+
currentHash,
|
|
370
|
+
lastSnapshotHash: marker.lastSnapshotHash ?? null
|
|
371
|
+
});
|
|
372
|
+
if (!gate.push) return;
|
|
373
|
+
const token = await readPushTokenEnc();
|
|
374
|
+
if (!token) return;
|
|
375
|
+
const consentReceipt = {
|
|
376
|
+
consentedAt: marker.autoConsentedAt,
|
|
377
|
+
version: AUTO_CONSENT_VERSION,
|
|
378
|
+
fields: PUSHED_CLAIM_FIELDS2
|
|
379
|
+
};
|
|
380
|
+
const res = await fetch(`${CLAIM_SYNC_BASE}/api/claim-sync`, {
|
|
381
|
+
method: "POST",
|
|
382
|
+
headers: { "Content-Type": "application/json" },
|
|
383
|
+
body: JSON.stringify({ consentToken: consentReceipt, claims: pushed, pushToken: token }),
|
|
384
|
+
signal: AbortSignal.timeout(1e4)
|
|
385
|
+
});
|
|
386
|
+
if (!res.ok) return;
|
|
387
|
+
writeAutoMarker({
|
|
388
|
+
...marker,
|
|
389
|
+
lastPushedAt: new Date(now).toISOString(),
|
|
390
|
+
lastSnapshotHash: currentHash
|
|
391
|
+
});
|
|
392
|
+
} catch {
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
export {
|
|
396
|
+
AUTO_CONSENT_VERSION,
|
|
397
|
+
AUTO_PUSH_THROTTLE_MS,
|
|
398
|
+
CLAIM_PUSH_AUTO_MARKER,
|
|
399
|
+
CLAIM_PUSH_TOKEN_FILE,
|
|
400
|
+
backgroundPushGate,
|
|
401
|
+
clearAutoMarker,
|
|
402
|
+
clearPushTokenEnc,
|
|
403
|
+
computeSnapshotHash,
|
|
404
|
+
readAutoMarker,
|
|
405
|
+
readPushTokenEnc,
|
|
406
|
+
runBackgroundClaimPush,
|
|
407
|
+
writeAutoMarker,
|
|
408
|
+
writePushTokenEnc
|
|
409
|
+
};
|
package/dist/bin/jpi-bounties.js
CHANGED
|
@@ -924,7 +924,7 @@ async function repoContributorCount(owner, name, token) {
|
|
|
924
924
|
return void 0;
|
|
925
925
|
}
|
|
926
926
|
}
|
|
927
|
-
async function fetchRepoMeta(owner, name, token, cache) {
|
|
927
|
+
async function fetchRepoMeta(owner, name, token, cache, stats) {
|
|
928
928
|
const key = `${owner}/${name}`.toLowerCase();
|
|
929
929
|
const cached = cache.get(key);
|
|
930
930
|
if (cached !== void 0) return cached;
|
|
@@ -941,8 +941,10 @@ async function fetchRepoMeta(owner, name, token, cache) {
|
|
|
941
941
|
topics: r.topics ?? [],
|
|
942
942
|
contributors
|
|
943
943
|
};
|
|
944
|
-
} catch {
|
|
944
|
+
} catch (err) {
|
|
945
945
|
meta = null;
|
|
946
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
947
|
+
if (stats && TRANSIENT_META_ERROR.test(msg)) stats.transient += 1;
|
|
946
948
|
}
|
|
947
949
|
cache.set(key, meta);
|
|
948
950
|
return meta;
|
|
@@ -981,8 +983,10 @@ async function computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates
|
|
|
981
983
|
return emptyCredential(/HTTP 403|HTTP 429|rate limit/i.test(msg) ? "rate-limited" : "failed");
|
|
982
984
|
}
|
|
983
985
|
const byDomain = {};
|
|
986
|
+
const distinctOrgSet = /* @__PURE__ */ new Set();
|
|
984
987
|
let qualifyingTotal = 0;
|
|
985
988
|
const qualifyingPRs = [];
|
|
989
|
+
const metaStats = { transient: 0 };
|
|
986
990
|
for (const item of items) {
|
|
987
991
|
const repo = parseRepoUrl(item.repository_url);
|
|
988
992
|
if (!repo) continue;
|
|
@@ -990,13 +994,20 @@ async function computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates
|
|
|
990
994
|
if (ownerLc === loginLc) continue;
|
|
991
995
|
if (ownedOrgs.has(ownerLc)) continue;
|
|
992
996
|
if (isTrivialPRTitle(item.title)) continue;
|
|
993
|
-
const meta = await fetchRepoMeta(repo.owner, repo.name, token, cache);
|
|
997
|
+
const meta = await fetchRepoMeta(repo.owner, repo.name, token, cache, metaStats);
|
|
998
|
+
if (metaStats.transient > 0) {
|
|
999
|
+
console.warn(
|
|
1000
|
+
`[acceptance] ${login}: per-repo metadata transient failure (${metaStats.transient}) \u2014 degrading to 'rate-limited' rather than a fabricated count`
|
|
1001
|
+
);
|
|
1002
|
+
return emptyCredential("rate-limited");
|
|
1003
|
+
}
|
|
994
1004
|
if (!meta) continue;
|
|
995
1005
|
if (meta.private) continue;
|
|
996
1006
|
if (meta.archived || meta.fork) continue;
|
|
997
1007
|
if (meta.stars < gates.minStars) continue;
|
|
998
1008
|
if (meta.contributors !== void 0 && meta.contributors < gates.minContributors) continue;
|
|
999
1009
|
qualifyingTotal += 1;
|
|
1010
|
+
distinctOrgSet.add(ownerLc);
|
|
1000
1011
|
const mergedAt = item.pull_request?.merged_at ?? item.closed_at ?? item.created_at;
|
|
1001
1012
|
const rawDomains = [meta.language ?? "", ...meta.topics].filter(Boolean);
|
|
1002
1013
|
const domainTags = [...new Set(normalize(rawDomains))];
|
|
@@ -1022,7 +1033,14 @@ async function computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates
|
|
|
1022
1033
|
lastMergedAt: b.lastMergedAt
|
|
1023
1034
|
};
|
|
1024
1035
|
}
|
|
1025
|
-
return {
|
|
1036
|
+
return {
|
|
1037
|
+
status: "ok",
|
|
1038
|
+
byDomain: finalDomains,
|
|
1039
|
+
qualifyingTotal,
|
|
1040
|
+
qualifyingPRs,
|
|
1041
|
+
distinctOrgs: distinctOrgSet.size,
|
|
1042
|
+
computedAt
|
|
1043
|
+
};
|
|
1026
1044
|
}
|
|
1027
1045
|
async function computeAcceptanceCredential(login, token, cache = /* @__PURE__ */ new Map()) {
|
|
1028
1046
|
if (!token) return emptyCredential("no-token");
|
|
@@ -1036,6 +1054,61 @@ async function computeAcceptanceCredentialPublic(login, token, cache = /* @__PUR
|
|
|
1036
1054
|
const gates = opts?.relaxGates ? { minStars: 0, minContributors: 0 } : void 0;
|
|
1037
1055
|
return computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates);
|
|
1038
1056
|
}
|
|
1057
|
+
async function fetchOpenExternalPRs(login, token, cache = /* @__PURE__ */ new Map(), gates = {
|
|
1058
|
+
minStars: MIN_STARS,
|
|
1059
|
+
minContributors: MIN_CONTRIBUTORS
|
|
1060
|
+
}) {
|
|
1061
|
+
if (!token) return [];
|
|
1062
|
+
const loginLc = login.toLowerCase();
|
|
1063
|
+
let ownedOrgs;
|
|
1064
|
+
try {
|
|
1065
|
+
ownedOrgs = await fetchPublicOrgs(login, token);
|
|
1066
|
+
} catch {
|
|
1067
|
+
return null;
|
|
1068
|
+
}
|
|
1069
|
+
let items;
|
|
1070
|
+
try {
|
|
1071
|
+
const q = encodeURIComponent(`type:pr is:open is:public author:${login} -user:${login} sort:updated`);
|
|
1072
|
+
const res = await ghFetch(
|
|
1073
|
+
`/search/issues?q=${q}&per_page=${OPEN_PR_PAGE}`,
|
|
1074
|
+
token
|
|
1075
|
+
);
|
|
1076
|
+
items = res.items ?? [];
|
|
1077
|
+
} catch (err) {
|
|
1078
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1079
|
+
console.warn("[open-prs] search failed:", msg);
|
|
1080
|
+
return null;
|
|
1081
|
+
}
|
|
1082
|
+
const metaStats = { transient: 0 };
|
|
1083
|
+
const out = [];
|
|
1084
|
+
for (const item of items) {
|
|
1085
|
+
const repo = parseRepoUrl(item.repository_url);
|
|
1086
|
+
if (!repo) continue;
|
|
1087
|
+
const ownerLc = repo.owner.toLowerCase();
|
|
1088
|
+
if (ownerLc === loginLc) continue;
|
|
1089
|
+
if (ownedOrgs.has(ownerLc)) continue;
|
|
1090
|
+
if (isTrivialPRTitle(item.title)) continue;
|
|
1091
|
+
const meta = await fetchRepoMeta(repo.owner, repo.name, token, cache, metaStats);
|
|
1092
|
+
if (metaStats.transient > 0) {
|
|
1093
|
+
console.warn(
|
|
1094
|
+
`[open-prs] ${login}: per-repo metadata transient failure (${metaStats.transient}) \u2014 returning null (keep prior strip)`
|
|
1095
|
+
);
|
|
1096
|
+
return null;
|
|
1097
|
+
}
|
|
1098
|
+
if (!meta) continue;
|
|
1099
|
+
if (meta.private) continue;
|
|
1100
|
+
if (meta.archived || meta.fork) continue;
|
|
1101
|
+
if (meta.stars < gates.minStars) continue;
|
|
1102
|
+
if (meta.contributors !== void 0 && meta.contributors < gates.minContributors) continue;
|
|
1103
|
+
out.push({
|
|
1104
|
+
title: item.title,
|
|
1105
|
+
url: item.html_url,
|
|
1106
|
+
repoFullName: `${repo.owner}/${repo.name}`,
|
|
1107
|
+
openedAt: item.created_at
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
return out;
|
|
1111
|
+
}
|
|
1039
1112
|
function acceptanceCountForDomains(cred, domains) {
|
|
1040
1113
|
if (cred.status !== "ok") return 0;
|
|
1041
1114
|
let max = 0;
|
|
@@ -1107,7 +1180,7 @@ function deriveResumeTrend(cred, repoRecency, now = Date.now()) {
|
|
|
1107
1180
|
}
|
|
1108
1181
|
return scored.sort((a, b) => b.weight - a.weight).slice(0, 12).map((s) => s.t);
|
|
1109
1182
|
}
|
|
1110
|
-
var TRACTION_TOP_N, CANDIDATE_PR_PAGE, RESUME_DECAY_HALF_LIFE_MS, RESUME_MIN_SCORE;
|
|
1183
|
+
var TRACTION_TOP_N, CANDIDATE_PR_PAGE, OPEN_PR_PAGE, TRANSIENT_META_ERROR, RESUME_DECAY_HALF_LIFE_MS, RESUME_MIN_SCORE;
|
|
1111
1184
|
var init_github = __esm({
|
|
1112
1185
|
"../../packages/core/src/github.ts"() {
|
|
1113
1186
|
"use strict";
|
|
@@ -1115,6 +1188,8 @@ var init_github = __esm({
|
|
|
1115
1188
|
init_contribution_gate();
|
|
1116
1189
|
TRACTION_TOP_N = 6;
|
|
1117
1190
|
CANDIDATE_PR_PAGE = 50;
|
|
1191
|
+
OPEN_PR_PAGE = 20;
|
|
1192
|
+
TRANSIENT_META_ERROR = /HTTP 403|HTTP 429|rate limit|HTTP 5\d\d|timeout|network|fetch failed/i;
|
|
1118
1193
|
RESUME_DECAY_HALF_LIFE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
1119
1194
|
RESUME_MIN_SCORE = 0.05;
|
|
1120
1195
|
}
|
|
@@ -2860,6 +2935,10 @@ ${pr.body ?? ""}`.matchAll(/#(\d+)\b/g)) {
|
|
|
2860
2935
|
}
|
|
2861
2936
|
return refs;
|
|
2862
2937
|
}
|
|
2938
|
+
async function fetchRateLimit(client) {
|
|
2939
|
+
const r = await client.json("/rate_limit");
|
|
2940
|
+
return r?.resources ?? null;
|
|
2941
|
+
}
|
|
2863
2942
|
async function searchContribIssues(client, queries) {
|
|
2864
2943
|
const byUrl = /* @__PURE__ */ new Map();
|
|
2865
2944
|
for (const q of queries) {
|
|
@@ -2904,6 +2983,9 @@ async function aggregateContributions(opts = {}) {
|
|
|
2904
2983
|
const jobs = [];
|
|
2905
2984
|
const seen = /* @__PURE__ */ new Set();
|
|
2906
2985
|
const perRepo = /* @__PURE__ */ new Map();
|
|
2986
|
+
let metaNull = 0;
|
|
2987
|
+
let contribUndefined = 0;
|
|
2988
|
+
let prRefsNull = 0;
|
|
2907
2989
|
for (const issue of issues) {
|
|
2908
2990
|
if (jobs.length >= MAX_CONTRIB_ITEMS) break;
|
|
2909
2991
|
const fullName = repoFullNameFromApiUrl2(issue.repository_url);
|
|
@@ -2914,9 +2996,13 @@ async function aggregateContributions(opts = {}) {
|
|
|
2914
2996
|
if (isAssigned(issue)) continue;
|
|
2915
2997
|
if ((perRepo.get(fullName) ?? 0) >= MAX_BOUNTIES_PER_DISCOVERED_REPO) continue;
|
|
2916
2998
|
const repo = await repoMeta(fullName);
|
|
2917
|
-
if (!repo)
|
|
2999
|
+
if (!repo) {
|
|
3000
|
+
metaNull++;
|
|
3001
|
+
continue;
|
|
3002
|
+
}
|
|
2918
3003
|
const title = decodeEntities(issue.title).trim();
|
|
2919
3004
|
const contributors = await repoContribCount(fullName);
|
|
3005
|
+
if (contributors === void 0) contribUndefined++;
|
|
2920
3006
|
if (!passesContributionGate({
|
|
2921
3007
|
fullName,
|
|
2922
3008
|
stars: repo.stargazers_count,
|
|
@@ -2932,6 +3018,7 @@ async function aggregateContributions(opts = {}) {
|
|
|
2932
3018
|
const labels = labelNames2(issue.labels);
|
|
2933
3019
|
if (looksLikeContentTask({ title, body, labels })) continue;
|
|
2934
3020
|
const prRefs = await repoPRRefs(fullName);
|
|
3021
|
+
if (prRefs === null) prRefsNull++;
|
|
2935
3022
|
if (prRefs && prRefs.has(issue.number)) continue;
|
|
2936
3023
|
const openPRsAtDiscovery = prRefs ? 0 : void 0;
|
|
2937
3024
|
const tags = normalize(
|
|
@@ -2971,9 +3058,18 @@ async function aggregateContributions(opts = {}) {
|
|
|
2971
3058
|
raw: issue
|
|
2972
3059
|
});
|
|
2973
3060
|
}
|
|
3061
|
+
if (!opts.fetchImpl) {
|
|
3062
|
+
const rl = await fetchRateLimit(client);
|
|
3063
|
+
const core = rl?.core ? `${rl.core.remaining}/${rl.core.limit}` : "n/a";
|
|
3064
|
+
const search = rl?.search ? `${rl.search.remaining}/${rl.search.limit}` : "n/a";
|
|
3065
|
+
const noToken = !(process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"]);
|
|
3066
|
+
console.info(
|
|
3067
|
+
`[contribute] build metrics \u2014 scanned=${issues.length} reposDistinct=${repoCache.size} emitted=${jobs.length} metaNull=${metaNull} contribUndefined=${contribUndefined} prRefsNull=${prRefsNull} core=${core} search=${search}` + (noToken ? " (NO TOKEN \u2192 60/hr)" : "")
|
|
3068
|
+
);
|
|
3069
|
+
}
|
|
2974
3070
|
return jobs;
|
|
2975
3071
|
}
|
|
2976
|
-
var GITHUB_API2, CONTRIB_SEARCH_QUERIES, SEARCH_PER_PAGE2, MAX_CONTRIB_ITEMS, MAX_CONTRIB_ISSUES_SCANNED;
|
|
3072
|
+
var GITHUB_API2, CONTRIB_LABEL_QUERIES, CONTRIB_LANGUAGE_QUERIES, CONTRIB_SEARCH_QUERIES, SEARCH_PER_PAGE2, MAX_CONTRIB_ITEMS, MAX_CONTRIB_ISSUES_SCANNED;
|
|
2977
3073
|
var init_contributions = __esm({
|
|
2978
3074
|
"../../packages/core/src/feeds/contributions.ts"() {
|
|
2979
3075
|
"use strict";
|
|
@@ -2985,15 +3081,25 @@ var init_contributions = __esm({
|
|
|
2985
3081
|
init_github_bounties();
|
|
2986
3082
|
init_http();
|
|
2987
3083
|
GITHUB_API2 = "https://api.github.com";
|
|
2988
|
-
|
|
3084
|
+
CONTRIB_LABEL_QUERIES = [
|
|
2989
3085
|
'label:"good first issue" type:issue state:open',
|
|
2990
|
-
'label:"help wanted" type:issue state:open',
|
|
2991
3086
|
'label:"good-first-issue" type:issue state:open',
|
|
2992
|
-
'label:"help
|
|
3087
|
+
'label:"help wanted" type:issue state:open',
|
|
3088
|
+
'label:"help-wanted" type:issue state:open',
|
|
3089
|
+
'label:"up-for-grabs" type:issue state:open'
|
|
3090
|
+
];
|
|
3091
|
+
CONTRIB_LANGUAGE_QUERIES = [
|
|
3092
|
+
...["rust", "go", "python", "c++", "ruby"].map(
|
|
3093
|
+
(lang) => `label:"help wanted" language:${lang} type:issue state:open`
|
|
3094
|
+
),
|
|
3095
|
+
...["rust", "go"].map(
|
|
3096
|
+
(lang) => `label:"good first issue" language:${lang} type:issue state:open`
|
|
3097
|
+
)
|
|
2993
3098
|
];
|
|
3099
|
+
CONTRIB_SEARCH_QUERIES = [...CONTRIB_LABEL_QUERIES, ...CONTRIB_LANGUAGE_QUERIES];
|
|
2994
3100
|
SEARCH_PER_PAGE2 = 100;
|
|
2995
3101
|
MAX_CONTRIB_ITEMS = 150;
|
|
2996
|
-
MAX_CONTRIB_ISSUES_SCANNED =
|
|
3102
|
+
MAX_CONTRIB_ISSUES_SCANNED = 600;
|
|
2997
3103
|
}
|
|
2998
3104
|
});
|
|
2999
3105
|
|
|
@@ -6436,13 +6542,15 @@ function deriveLegibleProfile(credential, recency, traction, seniorityBand) {
|
|
|
6436
6542
|
daysAgo = Math.max(0, Math.round(ageDays2));
|
|
6437
6543
|
recencyBadge = { lastMergedAt: mostRecent, state: ageDays2 <= thresholdDays ? "live" : "dormant" };
|
|
6438
6544
|
}
|
|
6439
|
-
const
|
|
6545
|
+
const exactOrgCount = typeof credential.distinctOrgs === "number" && credential.distinctOrgs > 0;
|
|
6546
|
+
const orgCount = exactOrgCount ? credential.distinctOrgs : Object.values(domains).reduce((m, d) => Math.max(m, d.distinctOrgs), 0);
|
|
6440
6547
|
let proofSentence;
|
|
6441
6548
|
if (!ok) {
|
|
6442
6549
|
proofSentence = "Contribution credential unavailable \u2014 could not verify.";
|
|
6443
6550
|
} else {
|
|
6444
6551
|
const prs = credential.qualifyingTotal;
|
|
6445
|
-
|
|
6552
|
+
const orgPhrase = exactOrgCount ? `${orgCount}` : `at least ${orgCount}`;
|
|
6553
|
+
let s = `${prs} substantive PR${prs === 1 ? "" : "s"} merged into ${orgPhrase} external org${orgCount === 1 ? "" : "s"} (\u2265${MIN_STARS}\u2605, \u2265${MIN_CONTRIBUTORS} contributors)`;
|
|
6446
6554
|
if (daysAgo !== void 0) s += ` \u2014 most recent ${daysAgo}d ago`;
|
|
6447
6555
|
proofSentence = `${s}.`;
|
|
6448
6556
|
}
|
|
@@ -6680,6 +6788,7 @@ __export(src_exports, {
|
|
|
6680
6788
|
expandWeighted: () => expandWeighted,
|
|
6681
6789
|
extractSkillTags: () => extractSkillTags,
|
|
6682
6790
|
fetchGitHubProfile: () => fetchGitHubProfile,
|
|
6791
|
+
fetchOpenExternalPRs: () => fetchOpenExternalPRs,
|
|
6683
6792
|
fetchOwnedRepoTraction: () => fetchOwnedRepoTraction,
|
|
6684
6793
|
fetchRepoRecency: () => fetchRepoRecency,
|
|
6685
6794
|
flattenTiers: () => flattenTiers,
|
|
@@ -6759,9 +6868,9 @@ var init_keytar = __esm({
|
|
|
6759
6868
|
}
|
|
6760
6869
|
});
|
|
6761
6870
|
|
|
6762
|
-
// node-file:/Users/ericgang/job-placement-inline
|
|
6871
|
+
// node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
|
|
6763
6872
|
var require_keytar = __commonJS({
|
|
6764
|
-
"node-file:/Users/ericgang/job-placement-inline
|
|
6873
|
+
"node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
|
|
6765
6874
|
"use strict";
|
|
6766
6875
|
init_keytar();
|
|
6767
6876
|
try {
|