terminalhire 0.2.4 → 0.3.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 +2031 -0
- package/dist/bin/jpi-dispatch.js +1174 -473
- package/dist/bin/jpi-jobs.js +705 -276
- package/dist/bin/jpi-learn.js +442 -249
- package/dist/bin/jpi-login.js +700 -282
- package/dist/bin/jpi-profile.js +381 -213
- package/dist/bin/jpi-refresh.js +757 -311
- package/dist/bin/jpi-save.js +381 -213
- package/dist/bin/jpi-sync.js +502 -230
- package/dist/src/github-auth.js +3 -3
- package/dist/src/profile.js +313 -207
- package/dist/src/signal.js +364 -237
- package/package.json +3 -3
package/dist/bin/jpi-login.js
CHANGED
|
@@ -101,7 +101,7 @@ async function runDeviceFlow() {
|
|
|
101
101
|
await writeGitHubToken(MOCK_TOKEN);
|
|
102
102
|
return MOCK_LOGIN;
|
|
103
103
|
}
|
|
104
|
-
const clientId = process.env["GITHUB_DEVICE_CLIENT_ID"] ?? process.env["GITHUB_CLIENT_ID"] ??
|
|
104
|
+
const clientId = process.env["GITHUB_DEVICE_CLIENT_ID"] ?? process.env["GITHUB_CLIENT_ID"] ?? BAKED_IN_CLIENT_ID;
|
|
105
105
|
if (clientId === "Iv1.PLACEHOLDER_REGISTER_YOUR_APP") {
|
|
106
106
|
console.warn("\nWarning: GITHUB_CLIENT_ID env var looks like a placeholder.");
|
|
107
107
|
console.warn("Remove it to use the baked-in client ID, or set it to your own OAuth App Client ID.\n");
|
|
@@ -124,7 +124,7 @@ async function runDeviceFlow() {
|
|
|
124
124
|
console.log(" \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");
|
|
125
125
|
console.log(` 1. Open: ${deviceData.verification_uri}`);
|
|
126
126
|
console.log(` 2. Enter code: ${deviceData.user_code}`);
|
|
127
|
-
console.log(' 3. Authorize "
|
|
127
|
+
console.log(' 3. Authorize "Terminalhire" (scope: read:user \u2014 public data only)');
|
|
128
128
|
console.log(" \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");
|
|
129
129
|
console.log(" Waiting for authorization...");
|
|
130
130
|
console.log("");
|
|
@@ -204,7 +204,7 @@ async function resolveStoredLogin() {
|
|
|
204
204
|
function sleep(ms) {
|
|
205
205
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
206
206
|
}
|
|
207
|
-
var TERMINALHIRE_DIR, TOKEN_FILE, KEY_FILE, ALGO, KEY_BYTES, IV_BYTES, GITHUB_SCOPE, DEVICE_CODE_URL, ACCESS_TOKEN_URL,
|
|
207
|
+
var TERMINALHIRE_DIR, TOKEN_FILE, KEY_FILE, ALGO, KEY_BYTES, IV_BYTES, GITHUB_SCOPE, DEVICE_CODE_URL, ACCESS_TOKEN_URL, BAKED_IN_CLIENT_ID, MOCK_TOKEN, MOCK_LOGIN;
|
|
208
208
|
var init_github_auth = __esm({
|
|
209
209
|
"src/github-auth.ts"() {
|
|
210
210
|
"use strict";
|
|
@@ -217,234 +217,376 @@ var init_github_auth = __esm({
|
|
|
217
217
|
GITHUB_SCOPE = "read:user";
|
|
218
218
|
DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
219
219
|
ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
220
|
-
|
|
220
|
+
BAKED_IN_CLIENT_ID = "Ov23lignE2ZSBe0J3a6B";
|
|
221
221
|
MOCK_TOKEN = "mock-github-token-jpi-dev";
|
|
222
222
|
MOCK_LOGIN = "janedev";
|
|
223
223
|
}
|
|
224
224
|
});
|
|
225
225
|
|
|
226
226
|
// ../../packages/core/src/types.ts
|
|
227
|
+
function isBounty(job) {
|
|
228
|
+
return job.source === "bounty" && job.bounty != null;
|
|
229
|
+
}
|
|
227
230
|
var init_types = __esm({
|
|
228
231
|
"../../packages/core/src/types.ts"() {
|
|
229
232
|
"use strict";
|
|
230
233
|
}
|
|
231
234
|
});
|
|
232
235
|
|
|
233
|
-
// ../../packages/core/src/
|
|
236
|
+
// ../../packages/core/src/vocab/graph.data.ts
|
|
237
|
+
var VOCAB_NODES;
|
|
238
|
+
var init_graph_data = __esm({
|
|
239
|
+
"../../packages/core/src/vocab/graph.data.ts"() {
|
|
240
|
+
"use strict";
|
|
241
|
+
VOCAB_NODES = [
|
|
242
|
+
// ── Languages ─────────────────────────────────────────────────────────────
|
|
243
|
+
{ id: "javascript", synonyms: ["js"], related: [{ to: "typescript", w: 0.6 }] },
|
|
244
|
+
{ id: "typescript", parents: ["javascript"], synonyms: ["ts"] },
|
|
245
|
+
{ id: "python", synonyms: ["py"] },
|
|
246
|
+
{ id: "go", synonyms: ["golang"] },
|
|
247
|
+
{ id: "rust" },
|
|
248
|
+
{ id: "java", related: [{ to: "kotlin", w: 0.45 }, { to: "scala", w: 0.4 }] },
|
|
249
|
+
{ id: "ruby" },
|
|
250
|
+
{ id: "elixir" },
|
|
251
|
+
{ id: "scala", related: [{ to: "java", w: 0.4 }] },
|
|
252
|
+
{ id: "kotlin", related: [{ to: "java", w: 0.45 }] },
|
|
253
|
+
{ id: "swift" },
|
|
254
|
+
{ id: "cpp", synonyms: ["c++"] },
|
|
255
|
+
{ id: "csharp", synonyms: ["c#"] },
|
|
256
|
+
{ id: "php" },
|
|
257
|
+
{ id: "haskell" },
|
|
258
|
+
{ id: "clojure" },
|
|
259
|
+
{ id: "r" },
|
|
260
|
+
{ id: "dart" },
|
|
261
|
+
// ── Frontend ──────────────────────────────────────────────────────────────
|
|
262
|
+
{
|
|
263
|
+
id: "react",
|
|
264
|
+
parents: ["javascript"],
|
|
265
|
+
synonyms: ["reactjs"],
|
|
266
|
+
related: [{ to: "nextjs", w: 0.55 }, { to: "vue", w: 0.4 }, { to: "svelte", w: 0.4 }, { to: "solidjs", w: 0.5 }, { to: "angular", w: 0.35 }]
|
|
267
|
+
},
|
|
268
|
+
{ id: "nextjs", parents: ["react"], synonyms: ["next", "next.js"], related: [{ to: "remix", w: 0.5 }] },
|
|
269
|
+
{ id: "vue", parents: ["javascript"], synonyms: ["vue.js"], related: [{ to: "nuxt", w: 0.6 }] },
|
|
270
|
+
{ id: "nuxt", parents: ["vue"], synonyms: ["nuxt.js"] },
|
|
271
|
+
{ id: "svelte", parents: ["javascript"], related: [{ to: "sveltekit", w: 0.65 }] },
|
|
272
|
+
{ id: "sveltekit", parents: ["svelte"] },
|
|
273
|
+
{ id: "angular", parents: ["typescript"], synonyms: ["angular.js", "angularjs"] },
|
|
274
|
+
{ id: "solidjs", parents: ["javascript"] },
|
|
275
|
+
{ id: "remix", parents: ["react"], synonyms: ["remix.run"] },
|
|
276
|
+
{ id: "astro", parents: ["javascript"], related: [{ to: "nextjs", w: 0.4 }] },
|
|
277
|
+
{ id: "qwik", parents: ["javascript"] },
|
|
278
|
+
{ id: "tailwind", parents: ["css"], synonyms: ["tailwindcss", "tw"] },
|
|
279
|
+
{ id: "css" },
|
|
280
|
+
{ id: "html" },
|
|
281
|
+
{ id: "redux", parents: ["react"] },
|
|
282
|
+
{ id: "vite", parents: ["frontend"] },
|
|
283
|
+
{ id: "webpack", parents: ["frontend"] },
|
|
284
|
+
{ id: "storybook", parents: ["frontend"] },
|
|
285
|
+
// ── Backend frameworks / runtimes ───────────────────────────────────────────
|
|
286
|
+
{
|
|
287
|
+
id: "nodejs",
|
|
288
|
+
parents: ["javascript"],
|
|
289
|
+
synonyms: ["node", "node.js"],
|
|
290
|
+
related: [{ to: "express", w: 0.5 }, { to: "fastify", w: 0.45 }, { to: "nestjs", w: 0.45 }]
|
|
291
|
+
},
|
|
292
|
+
{ id: "express", parents: ["nodejs"], synonyms: ["express.js", "expressjs"], related: [{ to: "fastify", w: 0.5 }] },
|
|
293
|
+
{ id: "fastify", parents: ["nodejs"] },
|
|
294
|
+
{ id: "nestjs", parents: ["nodejs"], synonyms: ["nest", "nest.js"] },
|
|
295
|
+
{ id: "hono", parents: ["nodejs"] },
|
|
296
|
+
{ id: "deno", parents: ["javascript"], related: [{ to: "nodejs", w: 0.5 }, { to: "bun", w: 0.5 }] },
|
|
297
|
+
{ id: "bun", parents: ["javascript"], related: [{ to: "nodejs", w: 0.5 }] },
|
|
298
|
+
{ id: "django", parents: ["python"], related: [{ to: "flask", w: 0.5 }, { to: "fastapi", w: 0.45 }] },
|
|
299
|
+
{ id: "fastapi", parents: ["python"], related: [{ to: "flask", w: 0.55 }, { to: "django", w: 0.45 }] },
|
|
300
|
+
{ id: "flask", parents: ["python"] },
|
|
301
|
+
{ id: "rails", parents: ["ruby"], synonyms: ["ruby-on-rails", "ror"] },
|
|
302
|
+
{ id: "spring", parents: ["java"], synonyms: ["spring-boot", "springboot"] },
|
|
303
|
+
{ id: "actix", parents: ["rust"] },
|
|
304
|
+
{ id: "gin", parents: ["go"] },
|
|
305
|
+
{ id: "phoenix", parents: ["elixir"] },
|
|
306
|
+
{ id: "laravel", parents: ["php"] },
|
|
307
|
+
{ id: "dotnet", parents: ["csharp"], synonyms: [".net", "asp.net", "dotnet-core"] },
|
|
308
|
+
// ── Infrastructure & DevOps ─────────────────────────────────────────────────
|
|
309
|
+
{ id: "kubernetes", synonyms: ["k8s", "kube"], related: [{ to: "docker", w: 0.5 }, { to: "helm", w: 0.55 }, { to: "terraform", w: 0.4 }, { to: "argocd", w: 0.45 }] },
|
|
310
|
+
{ id: "docker", parents: ["devops"], related: [{ to: "kubernetes", w: 0.5 }] },
|
|
311
|
+
{ id: "terraform", synonyms: ["tf"], related: [{ to: "pulumi", w: 0.55 }, { to: "ansible", w: 0.4 }, { to: "aws", w: 0.4 }] },
|
|
312
|
+
{ id: "pulumi", related: [{ to: "terraform", w: 0.55 }] },
|
|
313
|
+
{ id: "ansible" },
|
|
314
|
+
{ id: "aws", synonyms: ["amazon-web-services"], related: [{ to: "gcp", w: 0.4 }, { to: "azure", w: 0.4 }] },
|
|
315
|
+
{ id: "gcp", synonyms: ["google-cloud", "google-cloud-platform"], related: [{ to: "aws", w: 0.4 }, { to: "azure", w: 0.4 }] },
|
|
316
|
+
{ id: "azure", synonyms: ["microsoft-azure"], related: [{ to: "aws", w: 0.4 }] },
|
|
317
|
+
{ id: "ci-cd", synonyms: ["cicd", "jenkins", "circleci", "circle-ci", "travis"], related: [{ to: "github-actions", w: 0.6 }, { to: "gitlab-ci", w: 0.6 }] },
|
|
318
|
+
{ id: "github-actions", parents: ["ci-cd"], synonyms: ["github-action"] },
|
|
319
|
+
{ id: "gitlab-ci", parents: ["ci-cd"], synonyms: ["gitlab"] },
|
|
320
|
+
{ id: "linux" },
|
|
321
|
+
{ id: "nginx" },
|
|
322
|
+
{ id: "prometheus", parents: ["observability"], related: [{ to: "grafana", w: 0.6 }] },
|
|
323
|
+
{ id: "grafana", parents: ["observability"] },
|
|
324
|
+
{ id: "datadog", parents: ["observability"] },
|
|
325
|
+
{ id: "opentelemetry", parents: ["observability"], synonyms: ["otel"] },
|
|
326
|
+
{ id: "vercel", related: [{ to: "netlify", w: 0.5 }, { to: "nextjs", w: 0.4 }] },
|
|
327
|
+
{ id: "netlify" },
|
|
328
|
+
{ id: "fly", synonyms: ["fly.io"], related: [{ to: "railway", w: 0.5 }, { to: "render", w: 0.5 }] },
|
|
329
|
+
{ id: "railway", related: [{ to: "render", w: 0.5 }] },
|
|
330
|
+
{ id: "render" },
|
|
331
|
+
{ id: "cloudflare", synonyms: ["cloudflare-workers"] },
|
|
332
|
+
{ id: "helm", parents: ["kubernetes"] },
|
|
333
|
+
{ id: "argocd", parents: ["kubernetes"] },
|
|
334
|
+
{ id: "serverless", parents: ["devops"] },
|
|
335
|
+
// ── Databases & storage ─────────────────────────────────────────────────────
|
|
336
|
+
{ id: "postgresql", synonyms: ["postgres", "pg"], related: [{ to: "mysql", w: 0.45 }, { to: "sqlite", w: 0.4 }] },
|
|
337
|
+
{ id: "mysql", related: [{ to: "postgresql", w: 0.45 }] },
|
|
338
|
+
{ id: "sqlite" },
|
|
339
|
+
{ id: "mongodb", synonyms: ["mongo"] },
|
|
340
|
+
{ id: "redis", related: [{ to: "caching", w: 0.5 }] },
|
|
341
|
+
{ id: "elasticsearch", synonyms: ["elastic"], related: [{ to: "search", w: 0.55 }] },
|
|
342
|
+
{ id: "kafka", synonyms: ["apache-kafka"], related: [{ to: "rabbitmq", w: 0.5 }, { to: "message-queue", w: 0.55 }] },
|
|
343
|
+
{ id: "rabbitmq", related: [{ to: "message-queue", w: 0.55 }] },
|
|
344
|
+
{ id: "cassandra" },
|
|
345
|
+
{ id: "dynamodb", parents: ["aws"] },
|
|
346
|
+
{ id: "snowflake", parents: ["data-engineering"], related: [{ to: "clickhouse", w: 0.4 }] },
|
|
347
|
+
{ id: "clickhouse", parents: ["data-engineering"], related: [{ to: "duckdb", w: 0.35 }] },
|
|
348
|
+
{ id: "duckdb", parents: ["data-engineering"] },
|
|
349
|
+
{ id: "supabase", related: [{ to: "postgresql", w: 0.5 }, { to: "neon", w: 0.4 }] },
|
|
350
|
+
{ id: "planetscale", related: [{ to: "mysql", w: 0.5 }] },
|
|
351
|
+
{ id: "neon", related: [{ to: "postgresql", w: 0.5 }] },
|
|
352
|
+
{ id: "turso", related: [{ to: "sqlite", w: 0.5 }] },
|
|
353
|
+
{ id: "cockroachdb", related: [{ to: "postgresql", w: 0.45 }] },
|
|
354
|
+
{ id: "prisma", parents: ["backend"], synonyms: ["@prisma/client"], related: [{ to: "drizzle", w: 0.5 }, { to: "typeorm", w: 0.45 }, { to: "sequelize", w: 0.4 }] },
|
|
355
|
+
{ id: "drizzle", synonyms: ["drizzle-orm"], related: [{ to: "prisma", w: 0.5 }] },
|
|
356
|
+
{ id: "sequelize", related: [{ to: "typeorm", w: 0.4 }] },
|
|
357
|
+
{ id: "typeorm", related: [{ to: "prisma", w: 0.45 }] },
|
|
358
|
+
{ id: "sqlalchemy", parents: ["python"] },
|
|
359
|
+
// ── Data engineering & ML ───────────────────────────────────────────────────
|
|
360
|
+
{ id: "data-engineering", synonyms: ["data-eng"], related: [{ to: "spark", w: 0.5 }, { to: "airflow", w: 0.5 }, { to: "dbt", w: 0.45 }] },
|
|
361
|
+
{ id: "spark", parents: ["data-engineering"], synonyms: ["apache-spark"] },
|
|
362
|
+
{ id: "airflow", parents: ["data-engineering"], synonyms: ["apache-airflow"] },
|
|
363
|
+
{ id: "dbt", parents: ["data-engineering"] },
|
|
364
|
+
{ id: "ml", synonyms: ["machine-learning"], related: [{ to: "pytorch", w: 0.5 }, { to: "tensorflow", w: 0.5 }, { to: "scikit-learn", w: 0.5 }] },
|
|
365
|
+
{ id: "llm", parents: ["ml"], synonyms: ["llms", "genai", "generative-ai"], related: [{ to: "langchain", w: 0.5 }, { to: "rag", w: 0.55 }, { to: "openai", w: 0.45 }, { to: "anthropic", w: 0.45 }] },
|
|
366
|
+
{ id: "pytorch", parents: ["ml"], synonyms: ["torch"], related: [{ to: "tensorflow", w: 0.5 }] },
|
|
367
|
+
{ id: "tensorflow", parents: ["ml"], synonyms: ["keras", "tf-keras"] },
|
|
368
|
+
{ id: "pandas", parents: ["python"], related: [{ to: "numpy", w: 0.6 }] },
|
|
369
|
+
{ id: "numpy", parents: ["python"] },
|
|
370
|
+
{ id: "scikit-learn", parents: ["ml"], synonyms: ["sklearn"] },
|
|
371
|
+
{ id: "jupyter", parents: ["python"] },
|
|
372
|
+
{ id: "langchain", parents: ["llm"], synonyms: ["llamaindex"] },
|
|
373
|
+
{ id: "huggingface", parents: ["ml"], synonyms: ["hugging-face"] },
|
|
374
|
+
{ id: "openai", parents: ["llm"] },
|
|
375
|
+
{ id: "anthropic", parents: ["llm"], synonyms: ["claude"] },
|
|
376
|
+
{ id: "rag", parents: ["llm"], synonyms: ["retrieval-augmented-generation"] },
|
|
377
|
+
{ id: "mlops", parents: ["ml"], related: [{ to: "devops", w: 0.4 }] },
|
|
378
|
+
// ── Mobile ──────────────────────────────────────────────────────────────────
|
|
379
|
+
{ id: "mobile", related: [{ to: "ios", w: 0.5 }, { to: "android", w: 0.5 }] },
|
|
380
|
+
{ id: "ios", parents: ["mobile", "swift"], related: [{ to: "android", w: 0.4 }] },
|
|
381
|
+
{ id: "android", parents: ["mobile"], related: [{ to: "kotlin", w: 0.4 }] },
|
|
382
|
+
{ id: "swiftui", parents: ["ios", "swift"] },
|
|
383
|
+
{ id: "react-native", parents: ["mobile", "react"], synonyms: ["reactnative"], related: [{ to: "flutter", w: 0.4 }, { to: "expo", w: 0.6 }] },
|
|
384
|
+
{ id: "flutter", parents: ["mobile", "dart"] },
|
|
385
|
+
{ id: "expo", parents: ["react-native"] },
|
|
386
|
+
{ id: "kotlin-multiplatform", parents: ["mobile", "kotlin"], synonyms: ["kmp"] },
|
|
387
|
+
// ── Domains / capabilities ──────────────────────────────────────────────────
|
|
388
|
+
{ id: "frontend", related: [{ to: "react", w: 0.4 }, { to: "css", w: 0.3 }] },
|
|
389
|
+
{ id: "backend", related: [{ to: "api-design", w: 0.4 }, { to: "microservices", w: 0.4 }] },
|
|
390
|
+
{ id: "devops", related: [{ to: "kubernetes", w: 0.4 }, { to: "ci-cd", w: 0.4 }, { to: "docker", w: 0.4 }] },
|
|
391
|
+
{ id: "authentication", synonyms: ["auth", "jwt", "saml", "passport", "auth0", "clerk", "nextauth"], related: [{ to: "oauth", w: 0.6 }, { to: "security", w: 0.5 }] },
|
|
392
|
+
{ id: "oauth", parents: ["authentication"], synonyms: ["oauth2", "oidc"], related: [{ to: "security", w: 0.4 }] },
|
|
393
|
+
{ id: "security", related: [{ to: "authentication", w: 0.5 }] },
|
|
394
|
+
{ id: "payments", synonyms: ["stripe", "braintree", "paddle", "lemonsqueezy", "@stripe/stripe-js"], related: [{ to: "billing", w: 0.6 }] },
|
|
395
|
+
{ id: "billing", synonyms: ["recurly", "chargebee"] },
|
|
396
|
+
{ id: "api-design", synonyms: ["rest", "restful", "rest-api"], related: [{ to: "graphql", w: 0.4 }, { to: "grpc", w: 0.4 }, { to: "backend", w: 0.4 }] },
|
|
397
|
+
{ id: "graphql", synonyms: ["gql"], related: [{ to: "trpc", w: 0.4 }] },
|
|
398
|
+
{ id: "trpc", related: [{ to: "graphql", w: 0.4 }] },
|
|
399
|
+
{ id: "grpc", synonyms: ["grpc-web"], related: [{ to: "microservices", w: 0.3 }] },
|
|
400
|
+
{ id: "microservices" },
|
|
401
|
+
{ id: "websockets", synonyms: ["ws", "socket.io"], related: [{ to: "realtime", w: 0.6 }] },
|
|
402
|
+
{ id: "realtime", synonyms: ["real-time"] },
|
|
403
|
+
{ id: "message-queue", synonyms: ["mq"] },
|
|
404
|
+
{ id: "caching", synonyms: ["cache"] },
|
|
405
|
+
{ id: "search", synonyms: ["full-text-search"] },
|
|
406
|
+
{ id: "observability", synonyms: ["o11y"], related: [{ to: "monitoring", w: 0.6 }] },
|
|
407
|
+
{ id: "monitoring", related: [{ to: "prometheus", w: 0.4 }] },
|
|
408
|
+
{ id: "testing", related: [{ to: "unit-testing", w: 0.5 }, { to: "e2e-testing", w: 0.5 }] },
|
|
409
|
+
{ id: "unit-testing", parents: ["testing"] },
|
|
410
|
+
{ id: "e2e-testing", parents: ["testing"], synonyms: ["e2e", "end-to-end-testing"] },
|
|
411
|
+
{ id: "jest", parents: ["testing"], related: [{ to: "vitest", w: 0.6 }, { to: "mocha", w: 0.5 }] },
|
|
412
|
+
{ id: "vitest", parents: ["testing"], related: [{ to: "jest", w: 0.6 }] },
|
|
413
|
+
{ id: "playwright", parents: ["e2e-testing"], related: [{ to: "cypress", w: 0.6 }] },
|
|
414
|
+
{ id: "cypress", parents: ["e2e-testing"] },
|
|
415
|
+
{ id: "mocha", parents: ["testing"] },
|
|
416
|
+
{ id: "pytest", parents: ["testing", "python"] },
|
|
417
|
+
{ id: "accessibility", synonyms: ["a11y"] },
|
|
418
|
+
{ id: "seo" },
|
|
419
|
+
{ id: "performance", synonyms: ["perf", "web-performance"] }
|
|
420
|
+
];
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// ../../packages/core/src/vocab/closure.ts
|
|
425
|
+
function round3(n) {
|
|
426
|
+
return Math.round(n * 1e3) / 1e3;
|
|
427
|
+
}
|
|
428
|
+
function validateGraph(nodes) {
|
|
429
|
+
const ids = /* @__PURE__ */ new Set();
|
|
430
|
+
for (const n of nodes) {
|
|
431
|
+
if (ids.has(n.id)) throw new Error(`vocab: duplicate id "${n.id}"`);
|
|
432
|
+
ids.add(n.id);
|
|
433
|
+
}
|
|
434
|
+
const seenAlias = /* @__PURE__ */ new Map();
|
|
435
|
+
for (const n of nodes) {
|
|
436
|
+
for (const p of n.parents ?? []) {
|
|
437
|
+
if (p === n.id) throw new Error(`vocab: "${n.id}" lists itself as a parent`);
|
|
438
|
+
if (!ids.has(p)) throw new Error(`vocab: "${n.id}" parent "${p}" is not a defined id`);
|
|
439
|
+
}
|
|
440
|
+
for (const e of n.related ?? []) {
|
|
441
|
+
if (e.to === n.id) throw new Error(`vocab: "${n.id}" relates to itself`);
|
|
442
|
+
if (!ids.has(e.to)) throw new Error(`vocab: "${n.id}" related "${e.to}" is not a defined id`);
|
|
443
|
+
if (!(e.w > 0 && e.w <= 1)) throw new Error(`vocab: "${n.id}"\u2192"${e.to}" weight ${e.w} out of (0,1]`);
|
|
444
|
+
}
|
|
445
|
+
for (const s of n.synonyms ?? []) {
|
|
446
|
+
const alias = s.toLowerCase();
|
|
447
|
+
if (ids.has(alias)) throw new Error(`vocab: synonym "${alias}" collides with a canonical id`);
|
|
448
|
+
const prev = seenAlias.get(alias);
|
|
449
|
+
if (prev && prev !== n.id) throw new Error(`vocab: synonym "${alias}" maps to both "${prev}" and "${n.id}"`);
|
|
450
|
+
seenAlias.set(alias, n.id);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
454
|
+
const done = /* @__PURE__ */ new Set();
|
|
455
|
+
const parentMap = new Map(nodes.map((n) => [n.id, n.parents ?? []]));
|
|
456
|
+
const walk = (id, path) => {
|
|
457
|
+
if (done.has(id)) return;
|
|
458
|
+
if (visiting.has(id)) throw new Error(`vocab: parent cycle ${[...path, id].join(" \u2192 ")}`);
|
|
459
|
+
visiting.add(id);
|
|
460
|
+
for (const p of parentMap.get(id) ?? []) walk(p, [...path, id]);
|
|
461
|
+
visiting.delete(id);
|
|
462
|
+
done.add(id);
|
|
463
|
+
};
|
|
464
|
+
for (const n of nodes) walk(n.id, []);
|
|
465
|
+
}
|
|
466
|
+
function buildAdjacency(nodes) {
|
|
467
|
+
const adj = /* @__PURE__ */ new Map();
|
|
468
|
+
const add = (from, to, w) => {
|
|
469
|
+
let m = adj.get(from);
|
|
470
|
+
if (!m) adj.set(from, m = /* @__PURE__ */ new Map());
|
|
471
|
+
if (w > (m.get(to) ?? 0)) m.set(to, w);
|
|
472
|
+
};
|
|
473
|
+
for (const n of nodes) {
|
|
474
|
+
for (const p of n.parents ?? []) {
|
|
475
|
+
add(n.id, p, PARENT_UP);
|
|
476
|
+
add(p, n.id, PARENT_DOWN);
|
|
477
|
+
}
|
|
478
|
+
for (const e of n.related ?? []) {
|
|
479
|
+
add(n.id, e.to, e.w);
|
|
480
|
+
add(e.to, n.id, e.w);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return adj;
|
|
484
|
+
}
|
|
485
|
+
function closureFrom(source, adj) {
|
|
486
|
+
const best = /* @__PURE__ */ new Map();
|
|
487
|
+
for (const [t, w] of adj.get(source) ?? []) {
|
|
488
|
+
if (w >= DECAY_FLOOR) best.set(t, { w: round3(w), via: t });
|
|
489
|
+
}
|
|
490
|
+
const settled = /* @__PURE__ */ new Set([source]);
|
|
491
|
+
while (true) {
|
|
492
|
+
let u;
|
|
493
|
+
let uw = 0;
|
|
494
|
+
for (const [t, e] of best) {
|
|
495
|
+
if (!settled.has(t) && e.w > uw) {
|
|
496
|
+
u = t;
|
|
497
|
+
uw = e.w;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (!u) break;
|
|
501
|
+
settled.add(u);
|
|
502
|
+
const via = best.get(u).via;
|
|
503
|
+
for (const [t, we] of adj.get(u) ?? []) {
|
|
504
|
+
if (settled.has(t)) continue;
|
|
505
|
+
const cand = round3(uw * we);
|
|
506
|
+
if (cand >= DECAY_FLOOR && cand > (best.get(t)?.w ?? 0)) {
|
|
507
|
+
best.set(t, { w: cand, via });
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
best.delete(source);
|
|
512
|
+
return best;
|
|
513
|
+
}
|
|
514
|
+
function buildGraph(nodes) {
|
|
515
|
+
validateGraph(nodes);
|
|
516
|
+
const ids = new Set(nodes.map((n) => n.id));
|
|
517
|
+
const synonyms = /* @__PURE__ */ new Map();
|
|
518
|
+
for (const n of nodes) {
|
|
519
|
+
for (const s of n.synonyms ?? []) synonyms.set(s.toLowerCase(), n.id);
|
|
520
|
+
}
|
|
521
|
+
const adj = buildAdjacency(nodes);
|
|
522
|
+
const closure = /* @__PURE__ */ new Map();
|
|
523
|
+
for (const n of nodes) closure.set(n.id, closureFrom(n.id, adj));
|
|
524
|
+
return { ids, synonyms, closure };
|
|
525
|
+
}
|
|
526
|
+
var PARENT_UP, PARENT_DOWN, DECAY_FLOOR;
|
|
527
|
+
var init_closure = __esm({
|
|
528
|
+
"../../packages/core/src/vocab/closure.ts"() {
|
|
529
|
+
"use strict";
|
|
530
|
+
PARENT_UP = 0.6;
|
|
531
|
+
PARENT_DOWN = 0.35;
|
|
532
|
+
DECAY_FLOOR = 0.25;
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
// ../../packages/core/src/vocab/types.ts
|
|
537
|
+
var init_types2 = __esm({
|
|
538
|
+
"../../packages/core/src/vocab/types.ts"() {
|
|
539
|
+
"use strict";
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
// ../../packages/core/src/vocab/index.ts
|
|
234
544
|
function normalize(tokens) {
|
|
235
545
|
const result = /* @__PURE__ */ new Set();
|
|
236
546
|
for (const raw of tokens) {
|
|
237
547
|
const lower = raw.toLowerCase().trim();
|
|
238
|
-
if (
|
|
548
|
+
if (GRAPH.ids.has(lower)) {
|
|
239
549
|
result.add(lower);
|
|
240
550
|
continue;
|
|
241
551
|
}
|
|
242
|
-
const mapped =
|
|
243
|
-
if (mapped
|
|
244
|
-
result.add(mapped);
|
|
245
|
-
}
|
|
552
|
+
const mapped = GRAPH.synonyms.get(lower);
|
|
553
|
+
if (mapped) result.add(mapped);
|
|
246
554
|
}
|
|
247
555
|
return Array.from(result);
|
|
248
556
|
}
|
|
249
|
-
|
|
557
|
+
function expandWeighted(tags, graph = GRAPH) {
|
|
558
|
+
const out = /* @__PURE__ */ new Map();
|
|
559
|
+
const put = (tag, weight, via) => {
|
|
560
|
+
const ex = out.get(tag);
|
|
561
|
+
if (!ex || weight > ex.weight) out.set(tag, { tag, weight, via });
|
|
562
|
+
};
|
|
563
|
+
for (const t of tags) {
|
|
564
|
+
put(t, 1, t);
|
|
565
|
+
const near = graph.closure.get(t);
|
|
566
|
+
if (near) for (const [n, edge] of near) put(n, edge.w, t);
|
|
567
|
+
}
|
|
568
|
+
return out;
|
|
569
|
+
}
|
|
570
|
+
var GRAPH, VOCABULARY, SYNONYMS;
|
|
571
|
+
var init_vocab = __esm({
|
|
572
|
+
"../../packages/core/src/vocab/index.ts"() {
|
|
573
|
+
"use strict";
|
|
574
|
+
init_graph_data();
|
|
575
|
+
init_closure();
|
|
576
|
+
init_types2();
|
|
577
|
+
init_closure();
|
|
578
|
+
init_graph_data();
|
|
579
|
+
GRAPH = buildGraph(VOCAB_NODES);
|
|
580
|
+
VOCABULARY = [...GRAPH.ids];
|
|
581
|
+
SYNONYMS = Object.fromEntries(GRAPH.synonyms);
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
// ../../packages/core/src/vocabulary.ts
|
|
250
586
|
var init_vocabulary = __esm({
|
|
251
587
|
"../../packages/core/src/vocabulary.ts"() {
|
|
252
588
|
"use strict";
|
|
253
|
-
|
|
254
|
-
// Languages
|
|
255
|
-
"typescript",
|
|
256
|
-
"javascript",
|
|
257
|
-
"python",
|
|
258
|
-
"go",
|
|
259
|
-
"rust",
|
|
260
|
-
"java",
|
|
261
|
-
"ruby",
|
|
262
|
-
"elixir",
|
|
263
|
-
"scala",
|
|
264
|
-
"kotlin",
|
|
265
|
-
"swift",
|
|
266
|
-
"cpp",
|
|
267
|
-
"csharp",
|
|
268
|
-
"php",
|
|
269
|
-
"haskell",
|
|
270
|
-
"clojure",
|
|
271
|
-
"r",
|
|
272
|
-
// Frontend frameworks / libs
|
|
273
|
-
"react",
|
|
274
|
-
"nextjs",
|
|
275
|
-
"vue",
|
|
276
|
-
"nuxt",
|
|
277
|
-
"svelte",
|
|
278
|
-
"angular",
|
|
279
|
-
"solidjs",
|
|
280
|
-
"tailwind",
|
|
281
|
-
"css",
|
|
282
|
-
"html",
|
|
283
|
-
"graphql",
|
|
284
|
-
"trpc",
|
|
285
|
-
// Backend frameworks
|
|
286
|
-
"nodejs",
|
|
287
|
-
"express",
|
|
288
|
-
"fastify",
|
|
289
|
-
"nestjs",
|
|
290
|
-
"django",
|
|
291
|
-
"fastapi",
|
|
292
|
-
"flask",
|
|
293
|
-
"rails",
|
|
294
|
-
"spring",
|
|
295
|
-
"actix",
|
|
296
|
-
"gin",
|
|
297
|
-
"phoenix",
|
|
298
|
-
"laravel",
|
|
299
|
-
"dotnet",
|
|
300
|
-
// Infrastructure & DevOps
|
|
301
|
-
"kubernetes",
|
|
302
|
-
"docker",
|
|
303
|
-
"terraform",
|
|
304
|
-
"aws",
|
|
305
|
-
"gcp",
|
|
306
|
-
"azure",
|
|
307
|
-
"ci-cd",
|
|
308
|
-
"github-actions",
|
|
309
|
-
"linux",
|
|
310
|
-
"nginx",
|
|
311
|
-
"pulumi",
|
|
312
|
-
"ansible",
|
|
313
|
-
"prometheus",
|
|
314
|
-
"grafana",
|
|
315
|
-
"datadog",
|
|
316
|
-
"opentelemetry",
|
|
317
|
-
// Data & ML
|
|
318
|
-
"postgresql",
|
|
319
|
-
"mysql",
|
|
320
|
-
"sqlite",
|
|
321
|
-
"mongodb",
|
|
322
|
-
"redis",
|
|
323
|
-
"elasticsearch",
|
|
324
|
-
"kafka",
|
|
325
|
-
"rabbitmq",
|
|
326
|
-
"data-engineering",
|
|
327
|
-
"spark",
|
|
328
|
-
"airflow",
|
|
329
|
-
"dbt",
|
|
330
|
-
"ml",
|
|
331
|
-
"llm",
|
|
332
|
-
"pytorch",
|
|
333
|
-
"tensorflow",
|
|
334
|
-
"pandas",
|
|
335
|
-
"numpy",
|
|
336
|
-
// Domains / capabilities
|
|
337
|
-
"oauth",
|
|
338
|
-
"authentication",
|
|
339
|
-
"security",
|
|
340
|
-
"payments",
|
|
341
|
-
"billing",
|
|
342
|
-
"frontend",
|
|
343
|
-
"backend",
|
|
344
|
-
"devops",
|
|
345
|
-
"mobile",
|
|
346
|
-
"ios",
|
|
347
|
-
"android",
|
|
348
|
-
"api-design",
|
|
349
|
-
"microservices",
|
|
350
|
-
"websockets",
|
|
351
|
-
"testing",
|
|
352
|
-
"accessibility",
|
|
353
|
-
"seo",
|
|
354
|
-
"performance",
|
|
355
|
-
"observability",
|
|
356
|
-
"search",
|
|
357
|
-
"realtime"
|
|
358
|
-
];
|
|
359
|
-
SYNONYMS = {
|
|
360
|
-
// Kubernetes aliases
|
|
361
|
-
"k8s": "kubernetes",
|
|
362
|
-
"kube": "kubernetes",
|
|
363
|
-
// Auth / identity
|
|
364
|
-
"passport": "authentication",
|
|
365
|
-
"oauth2": "oauth",
|
|
366
|
-
"oidc": "oauth",
|
|
367
|
-
"jwt": "authentication",
|
|
368
|
-
"saml": "authentication",
|
|
369
|
-
"auth0": "authentication",
|
|
370
|
-
"clerk": "authentication",
|
|
371
|
-
"nextauth": "authentication",
|
|
372
|
-
// Payments
|
|
373
|
-
"@stripe/stripe-js": "payments",
|
|
374
|
-
"stripe": "payments",
|
|
375
|
-
"braintree": "payments",
|
|
376
|
-
"paddle": "payments",
|
|
377
|
-
"lemonsqueezy": "payments",
|
|
378
|
-
"recurly": "billing",
|
|
379
|
-
"chargebee": "billing",
|
|
380
|
-
// Framework / lib aliases
|
|
381
|
-
"next": "nextjs",
|
|
382
|
-
"next.js": "nextjs",
|
|
383
|
-
"nuxt.js": "nuxt",
|
|
384
|
-
"vue.js": "vue",
|
|
385
|
-
"angular.js": "angular",
|
|
386
|
-
"angularjs": "angular",
|
|
387
|
-
"express.js": "express",
|
|
388
|
-
"expressjs": "express",
|
|
389
|
-
"fastapi": "fastapi",
|
|
390
|
-
"nest": "nestjs",
|
|
391
|
-
"nest.js": "nestjs",
|
|
392
|
-
"sveltekit": "svelte",
|
|
393
|
-
// Language aliases
|
|
394
|
-
"ts": "typescript",
|
|
395
|
-
"js": "javascript",
|
|
396
|
-
"py": "python",
|
|
397
|
-
"golang": "go",
|
|
398
|
-
"c++": "cpp",
|
|
399
|
-
"c#": "csharp",
|
|
400
|
-
".net": "dotnet",
|
|
401
|
-
"asp.net": "dotnet",
|
|
402
|
-
// DB aliases
|
|
403
|
-
"postgres": "postgresql",
|
|
404
|
-
"pg": "postgresql",
|
|
405
|
-
"mongo": "mongodb",
|
|
406
|
-
"elastic": "elasticsearch",
|
|
407
|
-
// Cloud aliases
|
|
408
|
-
"amazon web services": "aws",
|
|
409
|
-
"google cloud": "gcp",
|
|
410
|
-
"google cloud platform": "gcp",
|
|
411
|
-
"microsoft azure": "azure",
|
|
412
|
-
// CI/CD aliases
|
|
413
|
-
"github actions": "github-actions",
|
|
414
|
-
"circle ci": "ci-cd",
|
|
415
|
-
"circleci": "ci-cd",
|
|
416
|
-
"jenkins": "ci-cd",
|
|
417
|
-
"gitlab ci": "ci-cd",
|
|
418
|
-
"travis": "ci-cd",
|
|
419
|
-
// Mobile
|
|
420
|
-
"react native": "mobile",
|
|
421
|
-
"flutter": "mobile",
|
|
422
|
-
"expo": "mobile",
|
|
423
|
-
// AI / ML
|
|
424
|
-
"openai": "llm",
|
|
425
|
-
"anthropic": "llm",
|
|
426
|
-
"langchain": "llm",
|
|
427
|
-
"llamaindex": "llm",
|
|
428
|
-
"hugging face": "ml",
|
|
429
|
-
"huggingface": "ml",
|
|
430
|
-
"scikit-learn": "ml",
|
|
431
|
-
"sklearn": "ml",
|
|
432
|
-
// Data pipeline
|
|
433
|
-
"apache kafka": "kafka",
|
|
434
|
-
"apache spark": "spark",
|
|
435
|
-
"apache airflow": "airflow",
|
|
436
|
-
// Misc
|
|
437
|
-
"tailwindcss": "tailwind",
|
|
438
|
-
"tw": "tailwind",
|
|
439
|
-
"gql": "graphql",
|
|
440
|
-
"ws": "websockets",
|
|
441
|
-
"socket.io": "websockets",
|
|
442
|
-
"jest": "testing",
|
|
443
|
-
"vitest": "testing",
|
|
444
|
-
"playwright": "testing",
|
|
445
|
-
"cypress": "testing"
|
|
446
|
-
};
|
|
447
|
-
VOCAB_SET = new Set(VOCABULARY);
|
|
589
|
+
init_vocab();
|
|
448
590
|
}
|
|
449
591
|
});
|
|
450
592
|
|
|
@@ -465,6 +607,7 @@ function computeIdf(jobs) {
|
|
|
465
607
|
return idf;
|
|
466
608
|
}
|
|
467
609
|
function inferSeniority(title) {
|
|
610
|
+
if (!ENG_TITLE.test(title)) return void 0;
|
|
468
611
|
for (const [re, level] of SENIORITY_PATTERNS) {
|
|
469
612
|
if (re.test(title)) return level;
|
|
470
613
|
}
|
|
@@ -478,15 +621,15 @@ function seniorityScore(fp, job) {
|
|
|
478
621
|
const got = SENIORITY_RANK[jobLevel] ?? 1;
|
|
479
622
|
const delta = Math.abs(wanted - got);
|
|
480
623
|
if (delta === 0) return 1;
|
|
481
|
-
if (delta === 1) return 0.
|
|
482
|
-
return 0.
|
|
624
|
+
if (delta === 1) return 0.7;
|
|
625
|
+
return 0.4;
|
|
483
626
|
}
|
|
484
|
-
function recencyScore(postedAt) {
|
|
627
|
+
function recencyScore(postedAt, now) {
|
|
485
628
|
if (!postedAt) return 0.75;
|
|
486
|
-
const
|
|
487
|
-
if (
|
|
488
|
-
if (
|
|
489
|
-
if (
|
|
629
|
+
const ageDays2 = (now - new Date(postedAt).getTime()) / 864e5;
|
|
630
|
+
if (ageDays2 < 7) return 1;
|
|
631
|
+
if (ageDays2 < 30) return 0.9;
|
|
632
|
+
if (ageDays2 < 90) return 0.75;
|
|
490
633
|
return 0.6;
|
|
491
634
|
}
|
|
492
635
|
function passesFilters(fp, job) {
|
|
@@ -501,52 +644,73 @@ function passesFilters(fp, job) {
|
|
|
501
644
|
}
|
|
502
645
|
return true;
|
|
503
646
|
}
|
|
504
|
-
function buildReason(
|
|
505
|
-
if (
|
|
506
|
-
const
|
|
507
|
-
const
|
|
647
|
+
function buildReason(details) {
|
|
648
|
+
if (details.length === 0) return "No direct skill overlap found.";
|
|
649
|
+
const render = (d) => !d.via || d.via === d.tag ? d.tag : `${d.via}\u2192${d.tag} (${d.weight})`;
|
|
650
|
+
const top = details.slice(0, 3).map(render);
|
|
651
|
+
const rest = details.length - top.length;
|
|
508
652
|
const listed = top.join(", ");
|
|
509
653
|
if (rest === 0) return `Matched on ${listed}.`;
|
|
510
654
|
return `Matched on ${listed} + ${rest} more skill${rest > 1 ? "s" : ""}.`;
|
|
511
655
|
}
|
|
512
|
-
function
|
|
656
|
+
function harmonicMean(a, b) {
|
|
657
|
+
if (a <= 0 || b <= 0) return 0;
|
|
658
|
+
return 2 * a * b / (a + b);
|
|
659
|
+
}
|
|
660
|
+
function match(fp, jobs, limit = 5, now = Date.now()) {
|
|
513
661
|
const idf = computeIdf(jobs);
|
|
514
|
-
const
|
|
515
|
-
const
|
|
662
|
+
const idfOf = (t) => idf.get(t) ?? 0;
|
|
663
|
+
const expanded = expandWeighted(fp.skillTags);
|
|
664
|
+
const maxDevScore = fp.skillTags.reduce((acc, t) => acc + idfOf(t), 0);
|
|
516
665
|
const candidates = jobs.filter((j) => passesFilters(fp, j));
|
|
517
666
|
const scored = candidates.map((job) => {
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
let
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
667
|
+
const details = [];
|
|
668
|
+
let jobMatchScore = 0;
|
|
669
|
+
let jobMaxScore = 0;
|
|
670
|
+
const devCovByTag = /* @__PURE__ */ new Map();
|
|
671
|
+
for (const tag of job.tags) {
|
|
672
|
+
const w = idfOf(tag);
|
|
673
|
+
jobMaxScore += w;
|
|
674
|
+
const hit = expanded.get(tag);
|
|
675
|
+
if (hit) {
|
|
676
|
+
const credit = Math.pow(hit.weight, SHARPEN);
|
|
677
|
+
jobMatchScore += w * credit;
|
|
678
|
+
details.push({ tag, weight: hit.weight, via: hit.via });
|
|
679
|
+
if (credit > (devCovByTag.get(hit.via) ?? 0)) devCovByTag.set(hit.via, credit);
|
|
526
680
|
}
|
|
527
681
|
}
|
|
528
|
-
|
|
529
|
-
|
|
682
|
+
let devScore = 0;
|
|
683
|
+
for (const t of fp.skillTags) devScore += idfOf(t) * (devCovByTag.get(t) ?? 0);
|
|
684
|
+
const devCov = maxDevScore > 0 ? Math.min(1, devScore / maxDevScore) : 0;
|
|
685
|
+
const jobCov = jobMaxScore > 0 ? Math.min(1, jobMatchScore / jobMaxScore) : 0;
|
|
686
|
+
const tagComponent = harmonicMean(devCov, jobCov);
|
|
687
|
+
if (tagComponent === 0) return null;
|
|
688
|
+
details.sort((a, b) => idfOf(b.tag) * b.weight - idfOf(a.tag) * a.weight);
|
|
530
689
|
const sScore = seniorityScore(fp, job);
|
|
531
|
-
const rScore = recencyScore(job.postedAt);
|
|
532
|
-
const score =
|
|
690
|
+
const rScore = recencyScore(job.postedAt, now);
|
|
691
|
+
const score = tagComponent * 0.6 + sScore * 0.25 + rScore * 0.15;
|
|
692
|
+
const matchedTags = [...new Set(details.map((d) => d.via ?? d.tag))];
|
|
533
693
|
return {
|
|
534
694
|
job,
|
|
535
695
|
score: Math.round(score * 1e3) / 1e3,
|
|
536
|
-
matchedTags
|
|
537
|
-
|
|
696
|
+
matchedTags,
|
|
697
|
+
matchDetails: details,
|
|
698
|
+
reason: buildReason(details)
|
|
538
699
|
};
|
|
539
700
|
});
|
|
540
|
-
return scored.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
701
|
+
return scored.filter((r) => r !== null && r.score >= MIN_SCORE).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
541
702
|
}
|
|
542
703
|
function matchOne(fp, job) {
|
|
543
704
|
const results = match(fp, [job], 1);
|
|
544
705
|
return results.length > 0 ? results[0] : null;
|
|
545
706
|
}
|
|
546
|
-
var SENIORITY_RANK, SENIORITY_PATTERNS;
|
|
707
|
+
var MIN_SCORE, SHARPEN, SENIORITY_RANK, SENIORITY_PATTERNS, ENG_TITLE;
|
|
547
708
|
var init_matcher = __esm({
|
|
548
709
|
"../../packages/core/src/matcher.ts"() {
|
|
549
710
|
"use strict";
|
|
711
|
+
init_vocabulary();
|
|
712
|
+
MIN_SCORE = 0.15;
|
|
713
|
+
SHARPEN = 1.6;
|
|
550
714
|
SENIORITY_RANK = {
|
|
551
715
|
junior: 0,
|
|
552
716
|
mid: 1,
|
|
@@ -559,6 +723,7 @@ var init_matcher = __esm({
|
|
|
559
723
|
[/\bjunior\b|\bjr\.?\b|\bentry[\s-]?level\b/i, "junior"],
|
|
560
724
|
[/\bmid[\s-]?level\b|\bmid\b/i, "mid"]
|
|
561
725
|
];
|
|
726
|
+
ENG_TITLE = /\b(engineer|engineering|developer|dev|swe|sde|programmer|architect)\b/i;
|
|
562
727
|
}
|
|
563
728
|
});
|
|
564
729
|
|
|
@@ -899,6 +1064,33 @@ var init_himalayas = __esm({
|
|
|
899
1064
|
}
|
|
900
1065
|
});
|
|
901
1066
|
|
|
1067
|
+
// ../../packages/core/src/feeds/entities.ts
|
|
1068
|
+
function fromCodePoint(cp) {
|
|
1069
|
+
if (!Number.isFinite(cp) || cp < 0 || cp > 1114111) return "";
|
|
1070
|
+
try {
|
|
1071
|
+
return String.fromCodePoint(cp);
|
|
1072
|
+
} catch {
|
|
1073
|
+
return "";
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
function decodeEntities(input) {
|
|
1077
|
+
if (!input || !input.includes("&")) return input;
|
|
1078
|
+
return input.replace(/&#(\d+);/g, (_, n) => fromCodePoint(parseInt(n, 10))).replace(/&#[xX]([0-9a-fA-F]+);/g, (_, h) => fromCodePoint(parseInt(h, 16))).replace(/&(lt|gt|quot|apos|nbsp);/g, (_, name) => NAMED[name] ?? `&${name};`).replace(/&/g, "&");
|
|
1079
|
+
}
|
|
1080
|
+
var NAMED;
|
|
1081
|
+
var init_entities = __esm({
|
|
1082
|
+
"../../packages/core/src/feeds/entities.ts"() {
|
|
1083
|
+
"use strict";
|
|
1084
|
+
NAMED = {
|
|
1085
|
+
lt: "<",
|
|
1086
|
+
gt: ">",
|
|
1087
|
+
quot: '"',
|
|
1088
|
+
apos: "'",
|
|
1089
|
+
nbsp: " "
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
});
|
|
1093
|
+
|
|
902
1094
|
// ../../packages/core/src/feeds/wwr.ts
|
|
903
1095
|
function tokenize5(text) {
|
|
904
1096
|
return text.toLowerCase().replace(/[^a-z0-9.\-+#]/g, " ").split(/\s+/).filter(Boolean);
|
|
@@ -922,9 +1114,9 @@ function parseRss(xml) {
|
|
|
922
1114
|
for (const block of itemBlocks) {
|
|
923
1115
|
const get = (tag) => {
|
|
924
1116
|
const cdataMatch = block.match(new RegExp(`<${tag}[^>]*><!\\[CDATA\\[([\\s\\S]*?)\\]\\]><\\/${tag}>`, "i"));
|
|
925
|
-
if (cdataMatch) return cdataMatch[1].trim();
|
|
1117
|
+
if (cdataMatch) return decodeEntities(cdataMatch[1].trim());
|
|
926
1118
|
const plainMatch = block.match(new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, "i"));
|
|
927
|
-
return plainMatch?.[1].trim() ?? "";
|
|
1119
|
+
return decodeEntities(plainMatch?.[1].trim() ?? "");
|
|
928
1120
|
};
|
|
929
1121
|
const rawTitle = get("title");
|
|
930
1122
|
const colonIdx = rawTitle.indexOf(":");
|
|
@@ -951,6 +1143,7 @@ var init_wwr = __esm({
|
|
|
951
1143
|
"../../packages/core/src/feeds/wwr.ts"() {
|
|
952
1144
|
"use strict";
|
|
953
1145
|
init_vocabulary();
|
|
1146
|
+
init_entities();
|
|
954
1147
|
WWR_RSS_URL = "https://weworkremotely.com/remote-jobs.rss";
|
|
955
1148
|
wwr = {
|
|
956
1149
|
source: "wwr",
|
|
@@ -989,7 +1182,7 @@ function tokenize6(text) {
|
|
|
989
1182
|
return text.toLowerCase().replace(/[^a-z0-9.\-+#]/g, " ").split(/\s+/).filter(Boolean);
|
|
990
1183
|
}
|
|
991
1184
|
function stripHtml2(html) {
|
|
992
|
-
return html.replace(/<p>/gi, " ").replace(/<[^>]*>/g, "")
|
|
1185
|
+
return decodeEntities(html.replace(/<p>/gi, " ").replace(/<[^>]*>/g, "")).replace(/\s+/g, " ").trim();
|
|
993
1186
|
}
|
|
994
1187
|
function extractUrl(text) {
|
|
995
1188
|
const match2 = text.match(/https?:\/\/[^\s<>"']+/);
|
|
@@ -1043,6 +1236,7 @@ var init_hn = __esm({
|
|
|
1043
1236
|
"../../packages/core/src/feeds/hn.ts"() {
|
|
1044
1237
|
"use strict";
|
|
1045
1238
|
init_vocabulary();
|
|
1239
|
+
init_entities();
|
|
1046
1240
|
ALGOLIA_SEARCH = "https://hn.algolia.com/api/v1/search?query=Ask+HN%3A+Who+is+Hiring%3F&tags=story,ask_hn&hitsPerPage=1";
|
|
1047
1241
|
ALGOLIA_ITEMS = "https://hn.algolia.com/api/v1/items/";
|
|
1048
1242
|
hn = {
|
|
@@ -1079,7 +1273,198 @@ var init_hn = __esm({
|
|
|
1079
1273
|
}
|
|
1080
1274
|
});
|
|
1081
1275
|
|
|
1276
|
+
// ../../packages/core/src/feeds/bounty-gate.ts
|
|
1277
|
+
function ageDays(createdAtIso) {
|
|
1278
|
+
const created = Date.parse(createdAtIso);
|
|
1279
|
+
if (!Number.isFinite(created)) return 0;
|
|
1280
|
+
return (Date.now() - created) / (1e3 * 60 * 60 * 24);
|
|
1281
|
+
}
|
|
1282
|
+
function passesMaturityGate(repo) {
|
|
1283
|
+
if (repo.archived || repo.disabled) return false;
|
|
1284
|
+
if (repo.stargazers < MIN_REPO_STARS) return false;
|
|
1285
|
+
if (ageDays(repo.createdAt) < MIN_REPO_AGE_DAYS) return false;
|
|
1286
|
+
return true;
|
|
1287
|
+
}
|
|
1288
|
+
var DEFAULT_BOUNTY_REPOS, MAX_BOUNTIES_PER_REPO, MIN_REPO_STARS, MIN_REPO_AGE_DAYS;
|
|
1289
|
+
var init_bounty_gate = __esm({
|
|
1290
|
+
"../../packages/core/src/feeds/bounty-gate.ts"() {
|
|
1291
|
+
"use strict";
|
|
1292
|
+
DEFAULT_BOUNTY_REPOS = [
|
|
1293
|
+
"tenstorrent/tt-metal",
|
|
1294
|
+
"sequelize/sequelize",
|
|
1295
|
+
"commaai/opendbc",
|
|
1296
|
+
"aragon/hack",
|
|
1297
|
+
"spacemeshos/app",
|
|
1298
|
+
"archestra-ai/archestra",
|
|
1299
|
+
"boundlessfi/boundless",
|
|
1300
|
+
"ucfopen/Obojobo",
|
|
1301
|
+
"widgetti/ipyvolume",
|
|
1302
|
+
"moorcheh-ai/memanto",
|
|
1303
|
+
"PrismarineJS/mineflayer"
|
|
1304
|
+
];
|
|
1305
|
+
MAX_BOUNTIES_PER_REPO = 10;
|
|
1306
|
+
MIN_REPO_STARS = 5;
|
|
1307
|
+
MIN_REPO_AGE_DAYS = 30;
|
|
1308
|
+
}
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
// ../../packages/core/src/feeds/github-bounties.ts
|
|
1312
|
+
function authHeaders() {
|
|
1313
|
+
const token = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"];
|
|
1314
|
+
const h = {
|
|
1315
|
+
Accept: "application/vnd.github+json",
|
|
1316
|
+
"User-Agent": "terminalhire",
|
|
1317
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
1318
|
+
};
|
|
1319
|
+
if (token) h["Authorization"] = `Bearer ${token}`;
|
|
1320
|
+
return h;
|
|
1321
|
+
}
|
|
1322
|
+
function tokenize7(text) {
|
|
1323
|
+
return text.toLowerCase().replace(/[^a-z0-9.\-+#]/g, " ").split(/\s+/).filter(Boolean);
|
|
1324
|
+
}
|
|
1325
|
+
function parseAmountUSD(text) {
|
|
1326
|
+
const m = text.match(/\$\s?([0-9][0-9,]*(?:\.[0-9]+)?)\s?([kK])?/);
|
|
1327
|
+
if (!m) return void 0;
|
|
1328
|
+
let n = parseFloat(m[1].replace(/,/g, ""));
|
|
1329
|
+
if (m[2]) n *= 1e3;
|
|
1330
|
+
if (!Number.isFinite(n) || n <= 0 || n > 1e6) return void 0;
|
|
1331
|
+
return Math.round(n);
|
|
1332
|
+
}
|
|
1333
|
+
function effortFromAmount(amount) {
|
|
1334
|
+
if (amount == null) return void 0;
|
|
1335
|
+
if (amount <= 500) return "small";
|
|
1336
|
+
if (amount <= 2e3) return "medium";
|
|
1337
|
+
return "large";
|
|
1338
|
+
}
|
|
1339
|
+
function labelNames(issue) {
|
|
1340
|
+
return (issue.labels ?? []).map((l) => typeof l === "string" ? l : l.name ?? "").filter(Boolean);
|
|
1341
|
+
}
|
|
1342
|
+
function isBountyIssue(issue) {
|
|
1343
|
+
if (issue.pull_request) return false;
|
|
1344
|
+
const labels = labelNames(issue);
|
|
1345
|
+
if (labels.some((n) => BOUNTY_LABEL_RE.test(n))) return true;
|
|
1346
|
+
return /bounty/i.test(issue.title) && parseAmountUSD(issue.title) != null;
|
|
1347
|
+
}
|
|
1348
|
+
async function ghJson(path) {
|
|
1349
|
+
let res;
|
|
1350
|
+
try {
|
|
1351
|
+
res = await fetch(`${GITHUB_API}${path}`, { headers: authHeaders() });
|
|
1352
|
+
} catch (err) {
|
|
1353
|
+
console.warn(`[github-bounties] network error ${path} \u2014`, err);
|
|
1354
|
+
return null;
|
|
1355
|
+
}
|
|
1356
|
+
if (res.status === 403 && res.headers.get("x-ratelimit-remaining") === "0") {
|
|
1357
|
+
console.warn("[github-bounties] rate-limited (set GITHUB_TOKEN for 5000/hr)");
|
|
1358
|
+
return null;
|
|
1359
|
+
}
|
|
1360
|
+
if (!res.ok) {
|
|
1361
|
+
console.warn(`[github-bounties] HTTP ${res.status} ${path}`);
|
|
1362
|
+
return null;
|
|
1363
|
+
}
|
|
1364
|
+
try {
|
|
1365
|
+
return await res.json();
|
|
1366
|
+
} catch {
|
|
1367
|
+
return null;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
async function fetchCommentAmount(repoFullName, issueNumber) {
|
|
1371
|
+
const comments = await ghJson(
|
|
1372
|
+
`/repos/${repoFullName}/issues/${issueNumber}/comments?per_page=30`
|
|
1373
|
+
);
|
|
1374
|
+
if (!comments) return void 0;
|
|
1375
|
+
for (const c of comments) {
|
|
1376
|
+
const body = c.body ?? "";
|
|
1377
|
+
if (BOUNTY_LABEL_RE.test(body)) {
|
|
1378
|
+
const amt = parseAmountUSD(body);
|
|
1379
|
+
if (amt != null) return amt;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
return void 0;
|
|
1383
|
+
}
|
|
1384
|
+
async function fetchRepoBounties(repoFullName) {
|
|
1385
|
+
const repo = await ghJson(`/repos/${repoFullName}`);
|
|
1386
|
+
if (!repo) return [];
|
|
1387
|
+
const meta = {
|
|
1388
|
+
fullName: repo.full_name,
|
|
1389
|
+
stargazers: repo.stargazers_count,
|
|
1390
|
+
createdAt: repo.created_at,
|
|
1391
|
+
archived: repo.archived,
|
|
1392
|
+
disabled: repo.disabled
|
|
1393
|
+
};
|
|
1394
|
+
if (!passesMaturityGate(meta)) {
|
|
1395
|
+
console.info(`[github-bounties] ${repoFullName}: failed maturity gate, skipping`);
|
|
1396
|
+
return [];
|
|
1397
|
+
}
|
|
1398
|
+
const issues = await ghJson(`/repos/${repoFullName}/issues?state=open&per_page=100`);
|
|
1399
|
+
if (!issues) return [];
|
|
1400
|
+
const bounties = issues.filter(isBountyIssue).slice(0, MAX_BOUNTIES_PER_REPO);
|
|
1401
|
+
const owner = repo.owner.login;
|
|
1402
|
+
return Promise.all(bounties.map(async (issue) => {
|
|
1403
|
+
const title = decodeEntities(issue.title).trim();
|
|
1404
|
+
const body = issue.body ? decodeEntities(issue.body) : "";
|
|
1405
|
+
const amountUSD = parseAmountUSD(title) ?? parseAmountUSD(body) ?? await fetchCommentAmount(repoFullName, issue.number);
|
|
1406
|
+
const labels = labelNames(issue);
|
|
1407
|
+
const tags = normalize(tokenize7([title, labels.join(" "), body.slice(0, 2e3)].join(" ")));
|
|
1408
|
+
return {
|
|
1409
|
+
id: `bounty:${repoFullName}#${issue.number}`,
|
|
1410
|
+
source: "bounty",
|
|
1411
|
+
title,
|
|
1412
|
+
company: owner,
|
|
1413
|
+
url: issue.html_url,
|
|
1414
|
+
remote: true,
|
|
1415
|
+
location: "Remote",
|
|
1416
|
+
tags,
|
|
1417
|
+
roleType: "freelance",
|
|
1418
|
+
postedAt: issue.created_at,
|
|
1419
|
+
applyMode: "direct",
|
|
1420
|
+
bounty: {
|
|
1421
|
+
amountUSD,
|
|
1422
|
+
estimatedEffort: effortFromAmount(amountUSD),
|
|
1423
|
+
bountySource: "github",
|
|
1424
|
+
claimUrl: issue.html_url,
|
|
1425
|
+
repoFullName,
|
|
1426
|
+
repoStars: repo.stargazers_count,
|
|
1427
|
+
issueBody: body.slice(0, 1e3) || void 0
|
|
1428
|
+
},
|
|
1429
|
+
raw: issue
|
|
1430
|
+
};
|
|
1431
|
+
}));
|
|
1432
|
+
}
|
|
1433
|
+
var GITHUB_API, BOUNTY_LABEL_RE, githubBounties;
|
|
1434
|
+
var init_github_bounties = __esm({
|
|
1435
|
+
"../../packages/core/src/feeds/github-bounties.ts"() {
|
|
1436
|
+
"use strict";
|
|
1437
|
+
init_vocabulary();
|
|
1438
|
+
init_entities();
|
|
1439
|
+
init_bounty_gate();
|
|
1440
|
+
GITHUB_API = "https://api.github.com";
|
|
1441
|
+
BOUNTY_LABEL_RE = /bounty|reward|funded|💎|💰/i;
|
|
1442
|
+
githubBounties = {
|
|
1443
|
+
source: "bounty",
|
|
1444
|
+
async fetch(opts) {
|
|
1445
|
+
const repos = opts?.slugs && opts.slugs.length > 0 ? opts.slugs : DEFAULT_BOUNTY_REPOS;
|
|
1446
|
+
console.info(`[github-bounties] scanning ${repos.length} repos`);
|
|
1447
|
+
const settled = await Promise.allSettled(repos.map(fetchRepoBounties));
|
|
1448
|
+
const jobs = [];
|
|
1449
|
+
let failures = 0;
|
|
1450
|
+
for (const r of settled) {
|
|
1451
|
+
if (r.status === "fulfilled") jobs.push(...r.value);
|
|
1452
|
+
else {
|
|
1453
|
+
failures++;
|
|
1454
|
+
console.warn("[github-bounties] repo fetch rejected:", r.reason);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
console.info(`[github-bounties] total: ${jobs.length} bounties, ${failures} repo failures`);
|
|
1458
|
+
return jobs;
|
|
1459
|
+
}
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
});
|
|
1463
|
+
|
|
1082
1464
|
// ../../packages/core/src/feeds/index.ts
|
|
1465
|
+
async function aggregateBounties(opts) {
|
|
1466
|
+
return githubBounties.fetch({ slugs: opts?.repos });
|
|
1467
|
+
}
|
|
1083
1468
|
function flattenTiers(t) {
|
|
1084
1469
|
return [.../* @__PURE__ */ new Set([...t.bigco, ...t.scaleup, ...t.startup])];
|
|
1085
1470
|
}
|
|
@@ -1112,6 +1497,19 @@ async function aggregate(opts) {
|
|
|
1112
1497
|
}
|
|
1113
1498
|
}
|
|
1114
1499
|
}
|
|
1500
|
+
if (opts?.includeBounties !== false) {
|
|
1501
|
+
try {
|
|
1502
|
+
const bounties = await githubBounties.fetch({ slugs: opts?.slugs?.["bounty"], limit });
|
|
1503
|
+
for (const b of bounties) {
|
|
1504
|
+
if (!seen.has(b.id)) {
|
|
1505
|
+
seen.add(b.id);
|
|
1506
|
+
jobs.push(b);
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
} catch (err) {
|
|
1510
|
+
console.warn("[feeds] bounties failed:", err);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1115
1513
|
return jobs;
|
|
1116
1514
|
}
|
|
1117
1515
|
var FEEDS, GREENHOUSE_SLUGS_BY_TIER, ASHBY_SLUGS_BY_TIER, LEVER_SLUGS_BY_TIER, DEFAULT_GREENHOUSE_SLUGS, DEFAULT_ASHBY_SLUGS, DEFAULT_LEVER_SLUGS;
|
|
@@ -1124,6 +1522,8 @@ var init_feeds = __esm({
|
|
|
1124
1522
|
init_himalayas();
|
|
1125
1523
|
init_wwr();
|
|
1126
1524
|
init_hn();
|
|
1525
|
+
init_github_bounties();
|
|
1526
|
+
init_bounty_gate();
|
|
1127
1527
|
FEEDS = [greenhouse, ashby, lever, himalayas, wwr, hn];
|
|
1128
1528
|
GREENHOUSE_SLUGS_BY_TIER = {
|
|
1129
1529
|
bigco: [
|
|
@@ -1233,72 +1633,78 @@ var init_feeds = __esm({
|
|
|
1233
1633
|
}
|
|
1234
1634
|
});
|
|
1235
1635
|
|
|
1236
|
-
// ../../packages/core/src/
|
|
1636
|
+
// ../../packages/core/src/partners.ts
|
|
1237
1637
|
import { readFileSync as readFileSync2 } from "fs";
|
|
1238
1638
|
import { join as join2 } from "path";
|
|
1239
1639
|
import { fileURLToPath } from "url";
|
|
1240
1640
|
function resolveDataPath() {
|
|
1241
1641
|
try {
|
|
1242
1642
|
const dir = fileURLToPath(new URL("../../../data", import.meta.url));
|
|
1243
|
-
return join2(dir, "
|
|
1643
|
+
return join2(dir, "partner-roles.json");
|
|
1244
1644
|
} catch {
|
|
1245
|
-
return join2(process.cwd(), "data", "
|
|
1645
|
+
return join2(process.cwd(), "data", "partner-roles.json");
|
|
1246
1646
|
}
|
|
1247
1647
|
}
|
|
1248
|
-
function
|
|
1648
|
+
function loadPartnerRoles() {
|
|
1249
1649
|
const filePath = resolveDataPath();
|
|
1250
1650
|
try {
|
|
1251
1651
|
const raw = readFileSync2(filePath, "utf-8");
|
|
1252
1652
|
const parsed = JSON.parse(raw);
|
|
1253
1653
|
if (!Array.isArray(parsed)) {
|
|
1254
|
-
console.warn("[
|
|
1654
|
+
console.warn("[partners] partner-roles.json is not an array \u2014 skipping");
|
|
1255
1655
|
return [];
|
|
1256
1656
|
}
|
|
1257
1657
|
const valid = [];
|
|
1258
1658
|
for (const entry of parsed) {
|
|
1259
|
-
|
|
1659
|
+
const e = entry;
|
|
1660
|
+
if (typeof entry === "object" && entry !== null && typeof e.id === "string" && e.applyMode === "buyer-lead" && typeof e.buyer === "string" && e.buyer.length > 0) {
|
|
1260
1661
|
valid.push(entry);
|
|
1261
1662
|
} else {
|
|
1262
|
-
console.warn("[
|
|
1663
|
+
console.warn("[partners] Skipping malformed role entry:", entry);
|
|
1263
1664
|
}
|
|
1264
1665
|
}
|
|
1265
1666
|
return valid;
|
|
1266
1667
|
} catch (err) {
|
|
1267
1668
|
if (err.code === "ENOENT") {
|
|
1268
|
-
console.warn(`[
|
|
1669
|
+
console.warn(`[partners] data/partner-roles.json not found at ${filePath} \u2014 no partner roles loaded`);
|
|
1269
1670
|
} else {
|
|
1270
|
-
console.warn("[
|
|
1671
|
+
console.warn("[partners] Failed to load partner-roles.json:", err);
|
|
1271
1672
|
}
|
|
1272
1673
|
return [];
|
|
1273
1674
|
}
|
|
1274
1675
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1676
|
+
function getBuyer(id) {
|
|
1677
|
+
return BUYER_REGISTRY[id];
|
|
1678
|
+
}
|
|
1679
|
+
var EXAMPLE_BUYER, BUYER_REGISTRY;
|
|
1680
|
+
var init_partners = __esm({
|
|
1681
|
+
"../../packages/core/src/partners.ts"() {
|
|
1278
1682
|
"use strict";
|
|
1279
|
-
|
|
1280
|
-
id: "
|
|
1281
|
-
legalName: "
|
|
1282
|
-
matchCriteria: {
|
|
1283
|
-
|
|
1284
|
-
|
|
1683
|
+
EXAMPLE_BUYER = {
|
|
1684
|
+
id: "northstar",
|
|
1685
|
+
legalName: "Northstar Talent Partners",
|
|
1686
|
+
matchCriteria: { roleTypes: ["full_time"] }
|
|
1687
|
+
};
|
|
1688
|
+
BUYER_REGISTRY = {
|
|
1689
|
+
[EXAMPLE_BUYER.id]: EXAMPLE_BUYER
|
|
1285
1690
|
};
|
|
1286
1691
|
}
|
|
1287
1692
|
});
|
|
1288
1693
|
|
|
1289
1694
|
// ../../packages/core/src/indexer.ts
|
|
1290
1695
|
async function buildIndex(opts) {
|
|
1291
|
-
const
|
|
1696
|
+
const includePartners = opts?.includePartners ?? true;
|
|
1292
1697
|
const publicJobs = await aggregate(opts);
|
|
1293
1698
|
const allJobs = [...publicJobs];
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1699
|
+
const seen = new Set(publicJobs.map((j) => j.id));
|
|
1700
|
+
const partnerJobs = [
|
|
1701
|
+
...includePartners ? loadPartnerRoles() : [],
|
|
1702
|
+
...opts?.partnerRoles ?? []
|
|
1703
|
+
];
|
|
1704
|
+
for (const job of partnerJobs) {
|
|
1705
|
+
if (!seen.has(job.id)) {
|
|
1706
|
+
seen.add(job.id);
|
|
1707
|
+
allJobs.push(job);
|
|
1302
1708
|
}
|
|
1303
1709
|
}
|
|
1304
1710
|
const jobs = allJobs.map(({ raw: _raw, ...rest }) => rest);
|
|
@@ -1311,7 +1717,7 @@ var init_indexer = __esm({
|
|
|
1311
1717
|
"../../packages/core/src/indexer.ts"() {
|
|
1312
1718
|
"use strict";
|
|
1313
1719
|
init_feeds();
|
|
1314
|
-
|
|
1720
|
+
init_partners();
|
|
1315
1721
|
}
|
|
1316
1722
|
});
|
|
1317
1723
|
|
|
@@ -1393,8 +1799,7 @@ function inferSeniority2(p) {
|
|
|
1393
1799
|
if (ageYears >= 9 && (p.publicRepos >= 40 || p.followers >= 500)) return "staff";
|
|
1394
1800
|
if (ageYears >= 5 && (p.publicRepos >= 20 || p.followers >= 100)) return "senior";
|
|
1395
1801
|
if (ageYears >= 2 && p.publicRepos >= 5) return "mid";
|
|
1396
|
-
|
|
1397
|
-
return void 0;
|
|
1802
|
+
return "junior";
|
|
1398
1803
|
}
|
|
1399
1804
|
function githubToFingerprint(p) {
|
|
1400
1805
|
const rawTokens = [
|
|
@@ -1417,30 +1822,42 @@ var init_github = __esm({
|
|
|
1417
1822
|
var src_exports = {};
|
|
1418
1823
|
__export(src_exports, {
|
|
1419
1824
|
ASHBY_SLUGS_BY_TIER: () => ASHBY_SLUGS_BY_TIER,
|
|
1420
|
-
|
|
1825
|
+
DECAY_FLOOR: () => DECAY_FLOOR,
|
|
1421
1826
|
DEFAULT_ASHBY_SLUGS: () => DEFAULT_ASHBY_SLUGS,
|
|
1827
|
+
DEFAULT_BOUNTY_REPOS: () => DEFAULT_BOUNTY_REPOS,
|
|
1422
1828
|
DEFAULT_GREENHOUSE_SLUGS: () => DEFAULT_GREENHOUSE_SLUGS,
|
|
1423
1829
|
DEFAULT_LEVER_SLUGS: () => DEFAULT_LEVER_SLUGS,
|
|
1830
|
+
EXAMPLE_BUYER: () => EXAMPLE_BUYER,
|
|
1424
1831
|
FEEDS: () => FEEDS,
|
|
1832
|
+
GRAPH: () => GRAPH,
|
|
1425
1833
|
GREENHOUSE_SLUGS_BY_TIER: () => GREENHOUSE_SLUGS_BY_TIER,
|
|
1426
1834
|
LEVER_SLUGS_BY_TIER: () => LEVER_SLUGS_BY_TIER,
|
|
1427
1835
|
SYNONYMS: () => SYNONYMS,
|
|
1428
1836
|
VOCABULARY: () => VOCABULARY,
|
|
1837
|
+
VOCAB_NODES: () => VOCAB_NODES,
|
|
1429
1838
|
aggregate: () => aggregate,
|
|
1839
|
+
aggregateBounties: () => aggregateBounties,
|
|
1430
1840
|
ashby: () => ashby,
|
|
1841
|
+
buildGraph: () => buildGraph,
|
|
1431
1842
|
buildIndex: () => buildIndex,
|
|
1432
1843
|
buildReason: () => buildReason,
|
|
1844
|
+
expandWeighted: () => expandWeighted,
|
|
1433
1845
|
fetchGitHubProfile: () => fetchGitHubProfile,
|
|
1434
1846
|
flattenTiers: () => flattenTiers,
|
|
1847
|
+
getBuyer: () => getBuyer,
|
|
1848
|
+
githubBounties: () => githubBounties,
|
|
1435
1849
|
githubToFingerprint: () => githubToFingerprint,
|
|
1436
1850
|
greenhouse: () => greenhouse,
|
|
1437
1851
|
himalayas: () => himalayas,
|
|
1438
1852
|
hn: () => hn,
|
|
1853
|
+
isBounty: () => isBounty,
|
|
1439
1854
|
lever: () => lever,
|
|
1440
|
-
|
|
1855
|
+
loadPartnerRoles: () => loadPartnerRoles,
|
|
1441
1856
|
match: () => match,
|
|
1442
1857
|
matchOne: () => matchOne,
|
|
1443
1858
|
normalize: () => normalize,
|
|
1859
|
+
passesMaturityGate: () => passesMaturityGate,
|
|
1860
|
+
validateGraph: () => validateGraph,
|
|
1444
1861
|
wwr: () => wwr
|
|
1445
1862
|
});
|
|
1446
1863
|
var init_src = __esm({
|
|
@@ -1451,7 +1868,7 @@ var init_src = __esm({
|
|
|
1451
1868
|
init_matcher();
|
|
1452
1869
|
init_feeds();
|
|
1453
1870
|
init_indexer();
|
|
1454
|
-
|
|
1871
|
+
init_partners();
|
|
1455
1872
|
init_github();
|
|
1456
1873
|
}
|
|
1457
1874
|
});
|
|
@@ -1550,10 +1967,10 @@ function migrateTagWeights(profile) {
|
|
|
1550
1967
|
if (!profile.tagWeights) {
|
|
1551
1968
|
profile.tagWeights = {};
|
|
1552
1969
|
}
|
|
1553
|
-
const
|
|
1970
|
+
const seed = profile.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1554
1971
|
for (const tag of profile.skillTags) {
|
|
1555
1972
|
if (!profile.tagWeights[tag]) {
|
|
1556
|
-
profile.tagWeights[tag] = { count: 1, firstSeen:
|
|
1973
|
+
profile.tagWeights[tag] = { count: 1, firstSeen: seed, lastSeen: seed, sessions: 1 };
|
|
1557
1974
|
}
|
|
1558
1975
|
}
|
|
1559
1976
|
}
|
|
@@ -1579,7 +1996,7 @@ async function writeProfile(profile) {
|
|
|
1579
1996
|
const blob = encrypt2(JSON.stringify(profile), key);
|
|
1580
1997
|
writeFileSync2(PROFILE_FILE, JSON.stringify(blob, null, 2), { encoding: "utf8" });
|
|
1581
1998
|
}
|
|
1582
|
-
function accumulateSession(profile, tags, isEmployerContext, inferredSeniority) {
|
|
1999
|
+
function accumulateSession(profile, tags, isEmployerContext, inferredSeniority, seniorityIsAuthoritative = false) {
|
|
1583
2000
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1584
2001
|
let filtered = normalize(tags);
|
|
1585
2002
|
if (isEmployerContext) {
|
|
@@ -1597,7 +2014,9 @@ function accumulateSession(profile, tags, isEmployerContext, inferredSeniority)
|
|
|
1597
2014
|
}
|
|
1598
2015
|
}
|
|
1599
2016
|
if (inferredSeniority && !isEmployerContext) {
|
|
1600
|
-
profile.
|
|
2017
|
+
if (seniorityIsAuthoritative || !profile.github) {
|
|
2018
|
+
profile.seniority = inferredSeniority;
|
|
2019
|
+
}
|
|
1601
2020
|
}
|
|
1602
2021
|
}
|
|
1603
2022
|
async function accumulateTags(rawTokens, isEmployerContext, inferredSeniority) {
|
|
@@ -1605,12 +2024,14 @@ async function accumulateTags(rawTokens, isEmployerContext, inferredSeniority) {
|
|
|
1605
2024
|
accumulateSession(profile, rawTokens, isEmployerContext, inferredSeniority);
|
|
1606
2025
|
await writeProfile(profile);
|
|
1607
2026
|
}
|
|
1608
|
-
function accumulateGitHubTags(profile, tags) {
|
|
2027
|
+
function accumulateGitHubTags(profile, tags, inferredSeniority) {
|
|
1609
2028
|
accumulateSession(
|
|
1610
2029
|
profile,
|
|
1611
2030
|
tags,
|
|
1612
2031
|
/* isEmployerContext */
|
|
1613
|
-
false
|
|
2032
|
+
false,
|
|
2033
|
+
inferredSeniority,
|
|
2034
|
+
true
|
|
1614
2035
|
);
|
|
1615
2036
|
}
|
|
1616
2037
|
async function listSavedJobs() {
|
|
@@ -1732,10 +2153,7 @@ async function runLogin() {
|
|
|
1732
2153
|
}
|
|
1733
2154
|
const fragment = githubToFingerprint2(ghProfile);
|
|
1734
2155
|
const profile = await readProfile2();
|
|
1735
|
-
accumulateGitHubTags2(profile, fragment.skillTags);
|
|
1736
|
-
if (fragment.seniorityBand && !profile.seniority) {
|
|
1737
|
-
profile.seniority = fragment.seniorityBand;
|
|
1738
|
-
}
|
|
2156
|
+
accumulateGitHubTags2(profile, fragment.skillTags, fragment.seniorityBand);
|
|
1739
2157
|
if (!profile.displayName && ghProfile.name) {
|
|
1740
2158
|
profile.displayName = ghProfile.name;
|
|
1741
2159
|
}
|