terminalhire 0.18.2 → 0.19.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/README.md +8 -2
- package/dist/bin/jpi-bounties.js +144 -15
- package/dist/bin/jpi-chat-read.js +18 -1
- package/dist/bin/jpi-chat.js +18 -1
- package/dist/bin/jpi-claim.js +1743 -514
- package/dist/bin/jpi-config.js +2 -1
- package/dist/bin/jpi-contribute.js +44 -8
- package/dist/bin/jpi-devs.js +144 -15
- package/dist/bin/jpi-dispatch.js +1116 -214
- package/dist/bin/jpi-inbox.js +18 -1
- package/dist/bin/jpi-intro.js +16 -0
- package/dist/bin/jpi-jobs.js +145 -16
- package/dist/bin/jpi-learn.js +16 -0
- package/dist/bin/jpi-link.js +2 -1
- package/dist/bin/jpi-login.js +146 -16
- package/dist/bin/jpi-mcp.js +6 -1
- package/dist/bin/jpi-profile.js +16 -0
- package/dist/bin/jpi-project.js +144 -15
- package/dist/bin/jpi-refresh.js +407 -79
- package/dist/bin/jpi-save.js +16 -0
- package/dist/bin/jpi-sync.js +16 -0
- package/dist/bin/jpi-trajectory.js +16 -0
- package/dist/bin/peer-connect-prompt.js +2 -1
- package/dist/bin/spinner.js +4 -2
- package/dist/bin/version-nudge.js +46 -4
- package/dist/src/claims.js +12 -0
- package/dist/src/config.js +6 -1
- package/dist/src/intro.js +16 -0
- package/dist/src/link.js +2 -1
- package/dist/src/trajectory.js +16 -0
- package/package.json +1 -1
package/dist/bin/jpi-claim.js
CHANGED
|
@@ -1,13 +1,954 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
9
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
10
|
+
}) : x)(function(x) {
|
|
11
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
12
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
13
|
+
});
|
|
4
14
|
var __esm = (fn, res) => function __init() {
|
|
5
15
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
16
|
};
|
|
17
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
18
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
19
|
+
};
|
|
7
20
|
var __export = (target, all) => {
|
|
8
21
|
for (var name in all)
|
|
9
22
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
23
|
};
|
|
24
|
+
var __copyProps = (to, from, except, desc) => {
|
|
25
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
26
|
+
for (let key of __getOwnPropNames(from))
|
|
27
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
28
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
29
|
+
}
|
|
30
|
+
return to;
|
|
31
|
+
};
|
|
32
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
33
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
34
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
35
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
36
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
37
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
38
|
+
mod
|
|
39
|
+
));
|
|
40
|
+
|
|
41
|
+
// ../../packages/core/src/types.ts
|
|
42
|
+
var init_types = __esm({
|
|
43
|
+
"../../packages/core/src/types.ts"() {
|
|
44
|
+
"use strict";
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// ../../packages/core/src/vocab/graph.data.ts
|
|
49
|
+
var VOCAB_NODES;
|
|
50
|
+
var init_graph_data = __esm({
|
|
51
|
+
"../../packages/core/src/vocab/graph.data.ts"() {
|
|
52
|
+
"use strict";
|
|
53
|
+
VOCAB_NODES = [
|
|
54
|
+
// ── Languages ─────────────────────────────────────────────────────────────
|
|
55
|
+
{ id: "javascript", synonyms: ["js"], related: [{ to: "typescript", w: 0.6 }] },
|
|
56
|
+
{ id: "typescript", parents: ["javascript"], synonyms: ["ts"] },
|
|
57
|
+
{ id: "python", synonyms: ["py"] },
|
|
58
|
+
{ id: "go", synonyms: ["golang"] },
|
|
59
|
+
{ id: "rust" },
|
|
60
|
+
{ id: "java", related: [{ to: "kotlin", w: 0.45 }, { to: "scala", w: 0.4 }] },
|
|
61
|
+
{ id: "ruby" },
|
|
62
|
+
{ id: "elixir" },
|
|
63
|
+
{ id: "scala", related: [{ to: "java", w: 0.4 }] },
|
|
64
|
+
{ id: "kotlin", related: [{ to: "java", w: 0.45 }] },
|
|
65
|
+
{ id: "swift" },
|
|
66
|
+
{ id: "cpp", synonyms: ["c++"] },
|
|
67
|
+
{ id: "csharp", synonyms: ["c#"] },
|
|
68
|
+
{ id: "php" },
|
|
69
|
+
{ id: "haskell" },
|
|
70
|
+
{ id: "clojure" },
|
|
71
|
+
{ id: "r" },
|
|
72
|
+
{ id: "dart" },
|
|
73
|
+
// ── Frontend ──────────────────────────────────────────────────────────────
|
|
74
|
+
{
|
|
75
|
+
id: "react",
|
|
76
|
+
parents: ["javascript"],
|
|
77
|
+
synonyms: ["reactjs"],
|
|
78
|
+
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 }]
|
|
79
|
+
},
|
|
80
|
+
{ id: "nextjs", parents: ["react"], synonyms: ["next", "next.js"], related: [{ to: "remix", w: 0.5 }] },
|
|
81
|
+
{ id: "vue", parents: ["javascript"], synonyms: ["vue.js"], related: [{ to: "nuxt", w: 0.6 }] },
|
|
82
|
+
{ id: "nuxt", parents: ["vue"], synonyms: ["nuxt.js"] },
|
|
83
|
+
{ id: "svelte", parents: ["javascript"], related: [{ to: "sveltekit", w: 0.65 }] },
|
|
84
|
+
{ id: "sveltekit", parents: ["svelte"] },
|
|
85
|
+
{ id: "angular", parents: ["typescript"], synonyms: ["angular.js", "angularjs"] },
|
|
86
|
+
{ id: "solidjs", parents: ["javascript"] },
|
|
87
|
+
{ id: "remix", parents: ["react"], synonyms: ["remix.run"] },
|
|
88
|
+
{ id: "astro", parents: ["javascript"], related: [{ to: "nextjs", w: 0.4 }] },
|
|
89
|
+
{ id: "qwik", parents: ["javascript"] },
|
|
90
|
+
{ id: "tailwind", parents: ["css"], synonyms: ["tailwindcss", "tw"] },
|
|
91
|
+
{ id: "css" },
|
|
92
|
+
{ id: "html" },
|
|
93
|
+
{ id: "redux", parents: ["react"] },
|
|
94
|
+
{ id: "vite", parents: ["frontend"] },
|
|
95
|
+
{ id: "webpack", parents: ["frontend"] },
|
|
96
|
+
{ id: "storybook", parents: ["frontend"] },
|
|
97
|
+
// ── Backend frameworks / runtimes ───────────────────────────────────────────
|
|
98
|
+
{
|
|
99
|
+
id: "nodejs",
|
|
100
|
+
parents: ["javascript"],
|
|
101
|
+
synonyms: ["node", "node.js"],
|
|
102
|
+
related: [{ to: "express", w: 0.5 }, { to: "fastify", w: 0.45 }, { to: "nestjs", w: 0.45 }]
|
|
103
|
+
},
|
|
104
|
+
{ id: "express", parents: ["nodejs"], synonyms: ["express.js", "expressjs"], related: [{ to: "fastify", w: 0.5 }] },
|
|
105
|
+
{ id: "fastify", parents: ["nodejs"] },
|
|
106
|
+
{ id: "nestjs", parents: ["nodejs"], synonyms: ["nest", "nest.js"] },
|
|
107
|
+
{ id: "hono", parents: ["nodejs"] },
|
|
108
|
+
{ id: "deno", parents: ["javascript"], related: [{ to: "nodejs", w: 0.5 }, { to: "bun", w: 0.5 }] },
|
|
109
|
+
{ id: "bun", parents: ["javascript"], related: [{ to: "nodejs", w: 0.5 }] },
|
|
110
|
+
{ id: "django", parents: ["python"], related: [{ to: "flask", w: 0.5 }, { to: "fastapi", w: 0.45 }] },
|
|
111
|
+
{ id: "fastapi", parents: ["python"], related: [{ to: "flask", w: 0.55 }, { to: "django", w: 0.45 }] },
|
|
112
|
+
{ id: "flask", parents: ["python"] },
|
|
113
|
+
{ id: "rails", parents: ["ruby"], synonyms: ["ruby-on-rails", "ror"] },
|
|
114
|
+
{ id: "spring", parents: ["java"], synonyms: ["spring-boot", "springboot"] },
|
|
115
|
+
{ id: "actix", parents: ["rust"] },
|
|
116
|
+
{ id: "gin", parents: ["go"] },
|
|
117
|
+
{ id: "phoenix", parents: ["elixir"] },
|
|
118
|
+
{ id: "laravel", parents: ["php"] },
|
|
119
|
+
{ id: "dotnet", parents: ["csharp"], synonyms: [".net", "asp.net", "dotnet-core"] },
|
|
120
|
+
// ── Infrastructure & DevOps ─────────────────────────────────────────────────
|
|
121
|
+
{ 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 }] },
|
|
122
|
+
{ id: "docker", parents: ["devops"], related: [{ to: "kubernetes", w: 0.5 }] },
|
|
123
|
+
{ id: "terraform", synonyms: ["tf"], related: [{ to: "pulumi", w: 0.55 }, { to: "ansible", w: 0.4 }, { to: "aws", w: 0.4 }] },
|
|
124
|
+
{ id: "pulumi", related: [{ to: "terraform", w: 0.55 }] },
|
|
125
|
+
{ id: "ansible" },
|
|
126
|
+
{ id: "aws", synonyms: ["amazon-web-services"], related: [{ to: "gcp", w: 0.4 }, { to: "azure", w: 0.4 }] },
|
|
127
|
+
{ id: "gcp", synonyms: ["google-cloud", "google-cloud-platform"], related: [{ to: "aws", w: 0.4 }, { to: "azure", w: 0.4 }] },
|
|
128
|
+
{ id: "azure", synonyms: ["microsoft-azure"], related: [{ to: "aws", w: 0.4 }] },
|
|
129
|
+
{ id: "ci-cd", synonyms: ["cicd", "jenkins", "circleci", "circle-ci", "travis"], related: [{ to: "github-actions", w: 0.6 }, { to: "gitlab-ci", w: 0.6 }] },
|
|
130
|
+
{ id: "github-actions", parents: ["ci-cd"], synonyms: ["github-action"] },
|
|
131
|
+
{ id: "gitlab-ci", parents: ["ci-cd"], synonyms: ["gitlab"] },
|
|
132
|
+
{ id: "linux" },
|
|
133
|
+
{ id: "nginx" },
|
|
134
|
+
{ id: "prometheus", parents: ["observability"], related: [{ to: "grafana", w: 0.6 }] },
|
|
135
|
+
{ id: "grafana", parents: ["observability"] },
|
|
136
|
+
{ id: "datadog", parents: ["observability"] },
|
|
137
|
+
{ id: "opentelemetry", parents: ["observability"], synonyms: ["otel"] },
|
|
138
|
+
{ id: "vercel", related: [{ to: "netlify", w: 0.5 }, { to: "nextjs", w: 0.4 }] },
|
|
139
|
+
{ id: "netlify" },
|
|
140
|
+
{ id: "fly", synonyms: ["fly.io"], related: [{ to: "railway", w: 0.5 }, { to: "render", w: 0.5 }] },
|
|
141
|
+
{ id: "railway", related: [{ to: "render", w: 0.5 }] },
|
|
142
|
+
{ id: "render" },
|
|
143
|
+
{ id: "cloudflare", synonyms: ["cloudflare-workers"] },
|
|
144
|
+
{ id: "helm", parents: ["kubernetes"] },
|
|
145
|
+
{ id: "argocd", parents: ["kubernetes"] },
|
|
146
|
+
{ id: "serverless", parents: ["devops"] },
|
|
147
|
+
// ── Databases & storage ─────────────────────────────────────────────────────
|
|
148
|
+
{ id: "postgresql", synonyms: ["postgres", "pg"], related: [{ to: "mysql", w: 0.45 }, { to: "sqlite", w: 0.4 }] },
|
|
149
|
+
{ id: "mysql", related: [{ to: "postgresql", w: 0.45 }] },
|
|
150
|
+
{ id: "sqlite" },
|
|
151
|
+
{ id: "mongodb", synonyms: ["mongo"] },
|
|
152
|
+
{ id: "redis", related: [{ to: "caching", w: 0.5 }] },
|
|
153
|
+
{ id: "elasticsearch", synonyms: ["elastic"], related: [{ to: "search", w: 0.55 }] },
|
|
154
|
+
{ id: "kafka", synonyms: ["apache-kafka"], related: [{ to: "rabbitmq", w: 0.5 }, { to: "message-queue", w: 0.55 }] },
|
|
155
|
+
{ id: "rabbitmq", related: [{ to: "message-queue", w: 0.55 }] },
|
|
156
|
+
{ id: "cassandra" },
|
|
157
|
+
{ id: "dynamodb", parents: ["aws"] },
|
|
158
|
+
{ id: "snowflake", parents: ["data-engineering"], related: [{ to: "clickhouse", w: 0.4 }] },
|
|
159
|
+
{ id: "clickhouse", parents: ["data-engineering"], related: [{ to: "duckdb", w: 0.35 }] },
|
|
160
|
+
{ id: "duckdb", parents: ["data-engineering"] },
|
|
161
|
+
{ id: "supabase", related: [{ to: "postgresql", w: 0.5 }, { to: "neon", w: 0.4 }] },
|
|
162
|
+
{ id: "planetscale", related: [{ to: "mysql", w: 0.5 }] },
|
|
163
|
+
{ id: "neon", related: [{ to: "postgresql", w: 0.5 }] },
|
|
164
|
+
{ id: "turso", related: [{ to: "sqlite", w: 0.5 }] },
|
|
165
|
+
{ id: "cockroachdb", related: [{ to: "postgresql", w: 0.45 }] },
|
|
166
|
+
{ id: "prisma", parents: ["backend"], synonyms: ["@prisma/client"], related: [{ to: "drizzle", w: 0.5 }, { to: "typeorm", w: 0.45 }, { to: "sequelize", w: 0.4 }] },
|
|
167
|
+
{ id: "drizzle", synonyms: ["drizzle-orm"], related: [{ to: "prisma", w: 0.5 }] },
|
|
168
|
+
{ id: "sequelize", related: [{ to: "typeorm", w: 0.4 }] },
|
|
169
|
+
{ id: "typeorm", related: [{ to: "prisma", w: 0.45 }] },
|
|
170
|
+
{ id: "sqlalchemy", parents: ["python"] },
|
|
171
|
+
// ── Data engineering & ML ───────────────────────────────────────────────────
|
|
172
|
+
{ id: "data-engineering", synonyms: ["data-eng"], related: [{ to: "spark", w: 0.5 }, { to: "airflow", w: 0.5 }, { to: "dbt", w: 0.45 }] },
|
|
173
|
+
{ id: "spark", parents: ["data-engineering"], synonyms: ["apache-spark"] },
|
|
174
|
+
{ id: "airflow", parents: ["data-engineering"], synonyms: ["apache-airflow"] },
|
|
175
|
+
{ id: "dbt", parents: ["data-engineering"] },
|
|
176
|
+
{ id: "ml", synonyms: ["machine-learning"], related: [{ to: "pytorch", w: 0.5 }, { to: "tensorflow", w: 0.5 }, { to: "scikit-learn", w: 0.5 }, { to: "data-engineering", w: 0.4 }] },
|
|
177
|
+
{ id: "llm", parents: ["ml"], synonyms: ["llms", "genai", "generative-ai", "gpt"], related: [{ to: "langchain", w: 0.5 }, { to: "rag", w: 0.55 }, { to: "openai", w: 0.45 }, { to: "anthropic", w: 0.45 }] },
|
|
178
|
+
{ id: "pytorch", parents: ["ml"], synonyms: ["torch"], related: [{ to: "tensorflow", w: 0.5 }] },
|
|
179
|
+
{ id: "tensorflow", parents: ["ml"], synonyms: ["keras", "tf-keras"] },
|
|
180
|
+
{ id: "pandas", parents: ["python"], related: [{ to: "numpy", w: 0.6 }, { to: "data-engineering", w: 0.45 }, { to: "spark", w: 0.4 }] },
|
|
181
|
+
{ id: "numpy", parents: ["python"] },
|
|
182
|
+
{ id: "scikit-learn", parents: ["ml"], synonyms: ["sklearn"] },
|
|
183
|
+
{ id: "jupyter", parents: ["python"] },
|
|
184
|
+
{ id: "langchain", parents: ["llm"], synonyms: ["llamaindex"] },
|
|
185
|
+
{ id: "huggingface", parents: ["ml"], synonyms: ["hugging-face"] },
|
|
186
|
+
{ id: "openai", parents: ["llm"] },
|
|
187
|
+
{ id: "anthropic", parents: ["llm"], synonyms: ["claude"] },
|
|
188
|
+
{ id: "rag", parents: ["llm"], synonyms: ["retrieval-augmented-generation"] },
|
|
189
|
+
{ id: "mlops", parents: ["ml"], related: [{ to: "devops", w: 0.4 }] },
|
|
190
|
+
{ id: "agents", parents: ["llm"], synonyms: ["agentic", "ai-agents", "multi-agent"], related: [{ to: "rag", w: 0.4 }] },
|
|
191
|
+
{ id: "mcp", parents: ["agents"], synonyms: ["model-context-protocol"], related: [{ to: "llm", w: 0.45 }] },
|
|
192
|
+
{ id: "inference", parents: ["ml"], synonyms: ["model-inference", "llm-inference", "model-serving"], related: [{ to: "mlops", w: 0.5 }, { to: "llm", w: 0.4 }] },
|
|
193
|
+
{ id: "embeddings", parents: ["ml"], synonyms: ["embedding", "vector-embeddings"], related: [{ to: "rag", w: 0.55 }, { to: "llm", w: 0.45 }] },
|
|
194
|
+
{ id: "prompt-engineering", parents: ["llm"], synonyms: ["prompting", "prompt"] },
|
|
195
|
+
{ id: "fine-tuning", parents: ["ml"], synonyms: ["finetuning", "fine-tune", "rlhf"], related: [{ to: "llm", w: 0.5 }] },
|
|
196
|
+
{ id: "computer-vision", parents: ["ml"], synonyms: ["image-recognition", "object-detection"] },
|
|
197
|
+
{ id: "recsys", parents: ["ml"], synonyms: ["recommender-systems", "recommendation-systems", "recommendation"] },
|
|
198
|
+
// ── Mobile ──────────────────────────────────────────────────────────────────
|
|
199
|
+
{ id: "mobile", related: [{ to: "ios", w: 0.5 }, { to: "android", w: 0.5 }] },
|
|
200
|
+
{ id: "ios", parents: ["mobile", "swift"], related: [{ to: "android", w: 0.4 }] },
|
|
201
|
+
{ id: "android", parents: ["mobile"], related: [{ to: "kotlin", w: 0.4 }] },
|
|
202
|
+
{ id: "swiftui", parents: ["ios", "swift"] },
|
|
203
|
+
{ id: "react-native", parents: ["mobile", "react"], synonyms: ["reactnative"], related: [{ to: "flutter", w: 0.4 }, { to: "expo", w: 0.6 }] },
|
|
204
|
+
{ id: "flutter", parents: ["mobile", "dart"] },
|
|
205
|
+
{ id: "expo", parents: ["react-native"] },
|
|
206
|
+
{ id: "kotlin-multiplatform", parents: ["mobile", "kotlin"], synonyms: ["kmp"] },
|
|
207
|
+
// ── Domains / capabilities ──────────────────────────────────────────────────
|
|
208
|
+
{ id: "frontend", related: [{ to: "react", w: 0.4 }, { to: "css", w: 0.3 }] },
|
|
209
|
+
{ id: "backend", related: [{ to: "api-design", w: 0.4 }, { to: "microservices", w: 0.4 }] },
|
|
210
|
+
{ id: "devops", related: [{ to: "kubernetes", w: 0.4 }, { to: "ci-cd", w: 0.4 }, { to: "docker", w: 0.4 }] },
|
|
211
|
+
{ id: "authentication", synonyms: ["auth", "jwt", "saml", "passport", "auth0", "clerk", "nextauth"], related: [{ to: "oauth", w: 0.6 }, { to: "security", w: 0.5 }] },
|
|
212
|
+
{ id: "oauth", parents: ["authentication"], synonyms: ["oauth2", "oidc"], related: [{ to: "security", w: 0.4 }] },
|
|
213
|
+
{ id: "security", related: [{ to: "authentication", w: 0.5 }] },
|
|
214
|
+
{ id: "payments", synonyms: ["stripe", "braintree", "paddle", "lemonsqueezy", "@stripe/stripe-js"], related: [{ to: "billing", w: 0.6 }] },
|
|
215
|
+
{ id: "billing", synonyms: ["recurly", "chargebee"] },
|
|
216
|
+
{ id: "api-design", synonyms: ["rest", "restful", "rest-api"], related: [{ to: "graphql", w: 0.4 }, { to: "grpc", w: 0.4 }, { to: "backend", w: 0.4 }] },
|
|
217
|
+
{ id: "graphql", synonyms: ["gql"], related: [{ to: "trpc", w: 0.4 }] },
|
|
218
|
+
{ id: "trpc", related: [{ to: "graphql", w: 0.4 }] },
|
|
219
|
+
{ id: "grpc", synonyms: ["grpc-web"], related: [{ to: "microservices", w: 0.3 }] },
|
|
220
|
+
{ id: "microservices" },
|
|
221
|
+
{ id: "websockets", synonyms: ["ws", "socket.io"], related: [{ to: "realtime", w: 0.6 }] },
|
|
222
|
+
{ id: "realtime", synonyms: ["real-time"] },
|
|
223
|
+
{ id: "message-queue", synonyms: ["mq"] },
|
|
224
|
+
{ id: "caching", synonyms: ["cache"] },
|
|
225
|
+
{ id: "search", synonyms: ["full-text-search"] },
|
|
226
|
+
{ id: "observability", synonyms: ["o11y"], related: [{ to: "monitoring", w: 0.6 }] },
|
|
227
|
+
{ id: "monitoring", related: [{ to: "prometheus", w: 0.4 }] },
|
|
228
|
+
{ id: "testing", related: [{ to: "unit-testing", w: 0.5 }, { to: "e2e-testing", w: 0.5 }] },
|
|
229
|
+
{ id: "unit-testing", parents: ["testing"] },
|
|
230
|
+
{ id: "e2e-testing", parents: ["testing"], synonyms: ["e2e", "end-to-end-testing"] },
|
|
231
|
+
{ id: "jest", parents: ["testing"], related: [{ to: "vitest", w: 0.6 }, { to: "mocha", w: 0.5 }] },
|
|
232
|
+
{ id: "vitest", parents: ["testing"], related: [{ to: "jest", w: 0.6 }] },
|
|
233
|
+
{ id: "playwright", parents: ["e2e-testing"], related: [{ to: "cypress", w: 0.6 }] },
|
|
234
|
+
{ id: "cypress", parents: ["e2e-testing"] },
|
|
235
|
+
{ id: "mocha", parents: ["testing"] },
|
|
236
|
+
{ id: "pytest", parents: ["testing", "python"] },
|
|
237
|
+
{ id: "accessibility", synonyms: ["a11y"] },
|
|
238
|
+
{ id: "seo" },
|
|
239
|
+
{ id: "performance", synonyms: ["perf", "web-performance"] }
|
|
240
|
+
];
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// ../../packages/core/src/vocab/closure.ts
|
|
245
|
+
function round3(n) {
|
|
246
|
+
return Math.round(n * 1e3) / 1e3;
|
|
247
|
+
}
|
|
248
|
+
function validateGraph(nodes) {
|
|
249
|
+
const ids = /* @__PURE__ */ new Set();
|
|
250
|
+
for (const n of nodes) {
|
|
251
|
+
if (ids.has(n.id)) throw new Error(`vocab: duplicate id "${n.id}"`);
|
|
252
|
+
ids.add(n.id);
|
|
253
|
+
}
|
|
254
|
+
const seenAlias = /* @__PURE__ */ new Map();
|
|
255
|
+
for (const n of nodes) {
|
|
256
|
+
for (const p of n.parents ?? []) {
|
|
257
|
+
if (p === n.id) throw new Error(`vocab: "${n.id}" lists itself as a parent`);
|
|
258
|
+
if (!ids.has(p)) throw new Error(`vocab: "${n.id}" parent "${p}" is not a defined id`);
|
|
259
|
+
}
|
|
260
|
+
for (const e of n.related ?? []) {
|
|
261
|
+
if (e.to === n.id) throw new Error(`vocab: "${n.id}" relates to itself`);
|
|
262
|
+
if (!ids.has(e.to)) throw new Error(`vocab: "${n.id}" related "${e.to}" is not a defined id`);
|
|
263
|
+
if (!(e.w > 0 && e.w <= 1)) throw new Error(`vocab: "${n.id}"\u2192"${e.to}" weight ${e.w} out of (0,1]`);
|
|
264
|
+
}
|
|
265
|
+
for (const s of n.synonyms ?? []) {
|
|
266
|
+
const alias = s.toLowerCase();
|
|
267
|
+
if (ids.has(alias)) throw new Error(`vocab: synonym "${alias}" collides with a canonical id`);
|
|
268
|
+
const prev = seenAlias.get(alias);
|
|
269
|
+
if (prev && prev !== n.id) throw new Error(`vocab: synonym "${alias}" maps to both "${prev}" and "${n.id}"`);
|
|
270
|
+
seenAlias.set(alias, n.id);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
274
|
+
const done = /* @__PURE__ */ new Set();
|
|
275
|
+
const parentMap = new Map(nodes.map((n) => [n.id, n.parents ?? []]));
|
|
276
|
+
const walk = (id, path) => {
|
|
277
|
+
if (done.has(id)) return;
|
|
278
|
+
if (visiting.has(id)) throw new Error(`vocab: parent cycle ${[...path, id].join(" \u2192 ")}`);
|
|
279
|
+
visiting.add(id);
|
|
280
|
+
for (const p of parentMap.get(id) ?? []) walk(p, [...path, id]);
|
|
281
|
+
visiting.delete(id);
|
|
282
|
+
done.add(id);
|
|
283
|
+
};
|
|
284
|
+
for (const n of nodes) walk(n.id, []);
|
|
285
|
+
}
|
|
286
|
+
function buildAdjacency(nodes) {
|
|
287
|
+
const adj = /* @__PURE__ */ new Map();
|
|
288
|
+
const add = (from, to, w) => {
|
|
289
|
+
let m = adj.get(from);
|
|
290
|
+
if (!m) adj.set(from, m = /* @__PURE__ */ new Map());
|
|
291
|
+
if (w > (m.get(to) ?? 0)) m.set(to, w);
|
|
292
|
+
};
|
|
293
|
+
for (const n of nodes) {
|
|
294
|
+
for (const p of n.parents ?? []) {
|
|
295
|
+
add(n.id, p, PARENT_UP);
|
|
296
|
+
add(p, n.id, PARENT_DOWN);
|
|
297
|
+
}
|
|
298
|
+
for (const e of n.related ?? []) {
|
|
299
|
+
add(n.id, e.to, e.w);
|
|
300
|
+
add(e.to, n.id, e.w);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return adj;
|
|
304
|
+
}
|
|
305
|
+
function closureFrom(source, adj) {
|
|
306
|
+
const best = /* @__PURE__ */ new Map();
|
|
307
|
+
for (const [t, w] of adj.get(source) ?? []) {
|
|
308
|
+
if (w >= DECAY_FLOOR) best.set(t, { w: round3(w), via: t });
|
|
309
|
+
}
|
|
310
|
+
const settled = /* @__PURE__ */ new Set([source]);
|
|
311
|
+
while (true) {
|
|
312
|
+
let u;
|
|
313
|
+
let uw = 0;
|
|
314
|
+
for (const [t, e] of best) {
|
|
315
|
+
if (!settled.has(t) && e.w > uw) {
|
|
316
|
+
u = t;
|
|
317
|
+
uw = e.w;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (!u) break;
|
|
321
|
+
settled.add(u);
|
|
322
|
+
const via = best.get(u).via;
|
|
323
|
+
for (const [t, we] of adj.get(u) ?? []) {
|
|
324
|
+
if (settled.has(t)) continue;
|
|
325
|
+
const cand = round3(uw * we);
|
|
326
|
+
if (cand >= DECAY_FLOOR && cand > (best.get(t)?.w ?? 0)) {
|
|
327
|
+
best.set(t, { w: cand, via });
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
best.delete(source);
|
|
332
|
+
return best;
|
|
333
|
+
}
|
|
334
|
+
function buildGraph(nodes) {
|
|
335
|
+
validateGraph(nodes);
|
|
336
|
+
const ids = new Set(nodes.map((n) => n.id));
|
|
337
|
+
const synonyms = /* @__PURE__ */ new Map();
|
|
338
|
+
for (const n of nodes) {
|
|
339
|
+
for (const s of n.synonyms ?? []) synonyms.set(s.toLowerCase(), n.id);
|
|
340
|
+
}
|
|
341
|
+
const adj = buildAdjacency(nodes);
|
|
342
|
+
const closure = /* @__PURE__ */ new Map();
|
|
343
|
+
for (const n of nodes) closure.set(n.id, closureFrom(n.id, adj));
|
|
344
|
+
return { ids, synonyms, closure };
|
|
345
|
+
}
|
|
346
|
+
var PARENT_UP, PARENT_DOWN, DECAY_FLOOR;
|
|
347
|
+
var init_closure = __esm({
|
|
348
|
+
"../../packages/core/src/vocab/closure.ts"() {
|
|
349
|
+
"use strict";
|
|
350
|
+
PARENT_UP = 0.6;
|
|
351
|
+
PARENT_DOWN = 0.35;
|
|
352
|
+
DECAY_FLOOR = 0.25;
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// ../../packages/core/src/vocab/types.ts
|
|
357
|
+
var init_types2 = __esm({
|
|
358
|
+
"../../packages/core/src/vocab/types.ts"() {
|
|
359
|
+
"use strict";
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// ../../packages/core/src/vocab/extract.ts
|
|
364
|
+
var SOFT_DOMAIN, SYNONYM_ONLY;
|
|
365
|
+
var init_extract = __esm({
|
|
366
|
+
"../../packages/core/src/vocab/extract.ts"() {
|
|
367
|
+
"use strict";
|
|
368
|
+
init_vocab();
|
|
369
|
+
SOFT_DOMAIN = /* @__PURE__ */ new Set([
|
|
370
|
+
"frontend",
|
|
371
|
+
"backend",
|
|
372
|
+
"devops",
|
|
373
|
+
"security",
|
|
374
|
+
"payments",
|
|
375
|
+
"billing",
|
|
376
|
+
"microservices",
|
|
377
|
+
"caching",
|
|
378
|
+
"search",
|
|
379
|
+
"observability",
|
|
380
|
+
"monitoring",
|
|
381
|
+
"testing",
|
|
382
|
+
"accessibility",
|
|
383
|
+
"seo",
|
|
384
|
+
"performance",
|
|
385
|
+
"realtime",
|
|
386
|
+
"authentication",
|
|
387
|
+
"api-design"
|
|
388
|
+
]);
|
|
389
|
+
SYNONYM_ONLY = /* @__PURE__ */ new Set(["performance", "security", "seo"]);
|
|
390
|
+
for (const id of SYNONYM_ONLY) {
|
|
391
|
+
if (!SOFT_DOMAIN.has(id)) throw new Error(`extract: SYNONYM_ONLY "${id}" not in SOFT_DOMAIN`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// ../../packages/core/src/vocab/idf-background.ts
|
|
397
|
+
var init_idf_background = __esm({
|
|
398
|
+
"../../packages/core/src/vocab/idf-background.ts"() {
|
|
399
|
+
"use strict";
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// ../../packages/core/src/vocab/index.ts
|
|
404
|
+
function normalize(tokens) {
|
|
405
|
+
const result = /* @__PURE__ */ new Set();
|
|
406
|
+
for (const raw of tokens) {
|
|
407
|
+
const lower = raw.toLowerCase().trim();
|
|
408
|
+
if (GRAPH.ids.has(lower)) {
|
|
409
|
+
result.add(lower);
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
const mapped = GRAPH.synonyms.get(lower);
|
|
413
|
+
if (mapped) result.add(mapped);
|
|
414
|
+
}
|
|
415
|
+
return Array.from(result);
|
|
416
|
+
}
|
|
417
|
+
var GRAPH, VOCABULARY, SYNONYMS;
|
|
418
|
+
var init_vocab = __esm({
|
|
419
|
+
"../../packages/core/src/vocab/index.ts"() {
|
|
420
|
+
"use strict";
|
|
421
|
+
init_graph_data();
|
|
422
|
+
init_closure();
|
|
423
|
+
init_types2();
|
|
424
|
+
init_closure();
|
|
425
|
+
init_graph_data();
|
|
426
|
+
init_extract();
|
|
427
|
+
init_idf_background();
|
|
428
|
+
GRAPH = buildGraph(VOCAB_NODES);
|
|
429
|
+
VOCABULARY = [...GRAPH.ids];
|
|
430
|
+
SYNONYMS = Object.fromEntries(GRAPH.synonyms);
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
// ../../packages/core/src/vocabulary.ts
|
|
435
|
+
var init_vocabulary = __esm({
|
|
436
|
+
"../../packages/core/src/vocabulary.ts"() {
|
|
437
|
+
"use strict";
|
|
438
|
+
init_vocab();
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// ../../packages/core/src/feeds/bounty-gate.ts
|
|
443
|
+
var BOUNTY_REPO_DENYLIST, DENYLIST_LC, AI_BAN_DENYLIST, AI_BAN_LC;
|
|
444
|
+
var init_bounty_gate = __esm({
|
|
445
|
+
"../../packages/core/src/feeds/bounty-gate.ts"() {
|
|
446
|
+
"use strict";
|
|
447
|
+
BOUNTY_REPO_DENYLIST = [
|
|
448
|
+
"SecureBananaLabs/bug-bounty",
|
|
449
|
+
// Meta-farm: a bounty PLATFORM whose own issues are an assignment-gated
|
|
450
|
+
// contributor queue ("please assign me, my chief") — an unsolicited PR won't
|
|
451
|
+
// merge, so it's not a real claimable bounty. Not structurally derivable from
|
|
452
|
+
// any fetched field, so it's a manual entry (also dropped from the allowlist).
|
|
453
|
+
"boundlessfi/boundless"
|
|
454
|
+
];
|
|
455
|
+
DENYLIST_LC = new Set(BOUNTY_REPO_DENYLIST.map((r) => r.toLowerCase()));
|
|
456
|
+
AI_BAN_DENYLIST = [
|
|
457
|
+
// Gentoo Council voted 6-0 (2024-04-14) to ban AI/ML-generated contributions
|
|
458
|
+
// project-wide. https://wiki.gentoo.org/wiki/Project:Council/AI_policy
|
|
459
|
+
"gentoo",
|
|
460
|
+
// NetBSD Commit Guidelines: code generated by an LLM/similar technology is
|
|
461
|
+
// "presumed to be tainted code, and must not be committed without prior
|
|
462
|
+
// written approval by core". https://www.netbsd.org/developers/commit-guidelines.html
|
|
463
|
+
"NetBSD"
|
|
464
|
+
// NOT listed (checked, deliberately excluded): QEMU's blanket AI-contribution
|
|
465
|
+
// ban is IN FLUX as of 2026-05 — a patch replacing it with a disclosure-based
|
|
466
|
+
// policy ("AI-used-for:" tag) was posted to qemu-devel and appears to have
|
|
467
|
+
// developer buy-in, so it is no longer a clean/current ban to key a hard
|
|
468
|
+
// supply-side drop on. https://www.theregister.com/ai-and-ml/2026/05/29/qemu-mulls-relaxing-ai-contribution-ban/
|
|
469
|
+
];
|
|
470
|
+
AI_BAN_LC = new Set(AI_BAN_DENYLIST.map((g) => g.toLowerCase()));
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// ../../packages/core/src/feeds/contribution-gate.ts
|
|
475
|
+
var init_contribution_gate = __esm({
|
|
476
|
+
"../../packages/core/src/feeds/contribution-gate.ts"() {
|
|
477
|
+
"use strict";
|
|
478
|
+
init_bounty_gate();
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
// ../../packages/core/src/github.ts
|
|
483
|
+
var RESUME_DECAY_HALF_LIFE_MS;
|
|
484
|
+
var init_github = __esm({
|
|
485
|
+
"../../packages/core/src/github.ts"() {
|
|
486
|
+
"use strict";
|
|
487
|
+
init_vocabulary();
|
|
488
|
+
init_contribution_gate();
|
|
489
|
+
RESUME_DECAY_HALF_LIFE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
// ../../packages/core/src/matcher.ts
|
|
494
|
+
var init_matcher = __esm({
|
|
495
|
+
"../../packages/core/src/matcher.ts"() {
|
|
496
|
+
"use strict";
|
|
497
|
+
init_vocabulary();
|
|
498
|
+
init_github();
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
// ../../packages/core/src/rerank.ts
|
|
503
|
+
var init_rerank = __esm({
|
|
504
|
+
"../../packages/core/src/rerank.ts"() {
|
|
505
|
+
"use strict";
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
// ../../packages/core/src/feeds/http.ts
|
|
510
|
+
var init_http = __esm({
|
|
511
|
+
"../../packages/core/src/feeds/http.ts"() {
|
|
512
|
+
"use strict";
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// ../../packages/core/src/feeds/greenhouse.ts
|
|
517
|
+
var init_greenhouse = __esm({
|
|
518
|
+
"../../packages/core/src/feeds/greenhouse.ts"() {
|
|
519
|
+
"use strict";
|
|
520
|
+
init_vocabulary();
|
|
521
|
+
init_http();
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
// ../../packages/core/src/feeds/ashby.ts
|
|
526
|
+
var init_ashby = __esm({
|
|
527
|
+
"../../packages/core/src/feeds/ashby.ts"() {
|
|
528
|
+
"use strict";
|
|
529
|
+
init_vocabulary();
|
|
530
|
+
init_http();
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
// ../../packages/core/src/feeds/lever.ts
|
|
535
|
+
var init_lever = __esm({
|
|
536
|
+
"../../packages/core/src/feeds/lever.ts"() {
|
|
537
|
+
"use strict";
|
|
538
|
+
init_vocabulary();
|
|
539
|
+
init_http();
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
// ../../packages/core/src/feeds/himalayas.ts
|
|
544
|
+
var init_himalayas = __esm({
|
|
545
|
+
"../../packages/core/src/feeds/himalayas.ts"() {
|
|
546
|
+
"use strict";
|
|
547
|
+
init_vocabulary();
|
|
548
|
+
init_http();
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
// ../../packages/core/src/feeds/entities.ts
|
|
553
|
+
var init_entities = __esm({
|
|
554
|
+
"../../packages/core/src/feeds/entities.ts"() {
|
|
555
|
+
"use strict";
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
// ../../packages/core/src/feeds/wwr.ts
|
|
560
|
+
var init_wwr = __esm({
|
|
561
|
+
"../../packages/core/src/feeds/wwr.ts"() {
|
|
562
|
+
"use strict";
|
|
563
|
+
init_vocabulary();
|
|
564
|
+
init_entities();
|
|
565
|
+
init_http();
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
// ../../packages/core/src/feeds/hn.ts
|
|
570
|
+
var init_hn = __esm({
|
|
571
|
+
"../../packages/core/src/feeds/hn.ts"() {
|
|
572
|
+
"use strict";
|
|
573
|
+
init_vocabulary();
|
|
574
|
+
init_entities();
|
|
575
|
+
init_http();
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// ../../packages/core/src/concurrency.ts
|
|
580
|
+
var init_concurrency = __esm({
|
|
581
|
+
"../../packages/core/src/concurrency.ts"() {
|
|
582
|
+
"use strict";
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
// ../../packages/core/src/feeds/effort.ts
|
|
587
|
+
var init_effort = __esm({
|
|
588
|
+
"../../packages/core/src/feeds/effort.ts"() {
|
|
589
|
+
"use strict";
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
// ../../packages/core/src/feeds/github-bounties.ts
|
|
594
|
+
var init_github_bounties = __esm({
|
|
595
|
+
"../../packages/core/src/feeds/github-bounties.ts"() {
|
|
596
|
+
"use strict";
|
|
597
|
+
init_vocabulary();
|
|
598
|
+
init_entities();
|
|
599
|
+
init_bounty_gate();
|
|
600
|
+
init_http();
|
|
601
|
+
init_concurrency();
|
|
602
|
+
init_effort();
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
// ../../packages/core/src/feeds/opire.ts
|
|
607
|
+
var init_opire = __esm({
|
|
608
|
+
"../../packages/core/src/feeds/opire.ts"() {
|
|
609
|
+
"use strict";
|
|
610
|
+
init_vocabulary();
|
|
611
|
+
init_bounty_gate();
|
|
612
|
+
init_github_bounties();
|
|
613
|
+
init_http();
|
|
614
|
+
init_effort();
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
// ../../packages/core/src/feeds/workable.ts
|
|
619
|
+
var init_workable = __esm({
|
|
620
|
+
"../../packages/core/src/feeds/workable.ts"() {
|
|
621
|
+
"use strict";
|
|
622
|
+
init_vocabulary();
|
|
623
|
+
init_http();
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
// ../../packages/core/src/feeds/directory.ts
|
|
628
|
+
var init_directory = __esm({
|
|
629
|
+
"../../packages/core/src/feeds/directory.ts"() {
|
|
630
|
+
"use strict";
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
// ../../packages/core/src/feeds/index.ts
|
|
635
|
+
function flattenTiers(t) {
|
|
636
|
+
return [.../* @__PURE__ */ new Set([...t.bigco, ...t.scaleup, ...t.startup])];
|
|
637
|
+
}
|
|
638
|
+
var GREENHOUSE_SLUGS_BY_TIER, ASHBY_SLUGS_BY_TIER, LEVER_SLUGS_BY_TIER, DEFAULT_GREENHOUSE_SLUGS, DEFAULT_ASHBY_SLUGS, DEFAULT_LEVER_SLUGS;
|
|
639
|
+
var init_feeds = __esm({
|
|
640
|
+
"../../packages/core/src/feeds/index.ts"() {
|
|
641
|
+
"use strict";
|
|
642
|
+
init_greenhouse();
|
|
643
|
+
init_ashby();
|
|
644
|
+
init_lever();
|
|
645
|
+
init_himalayas();
|
|
646
|
+
init_wwr();
|
|
647
|
+
init_hn();
|
|
648
|
+
init_github_bounties();
|
|
649
|
+
init_opire();
|
|
650
|
+
init_workable();
|
|
651
|
+
init_directory();
|
|
652
|
+
init_bounty_gate();
|
|
653
|
+
init_bounty_gate();
|
|
654
|
+
init_contribution_gate();
|
|
655
|
+
GREENHOUSE_SLUGS_BY_TIER = {
|
|
656
|
+
bigco: [
|
|
657
|
+
"stripe",
|
|
658
|
+
"anthropic",
|
|
659
|
+
"figma",
|
|
660
|
+
"discord",
|
|
661
|
+
"brex",
|
|
662
|
+
"mercury",
|
|
663
|
+
"plaid",
|
|
664
|
+
"gusto",
|
|
665
|
+
"scale",
|
|
666
|
+
"databricks",
|
|
667
|
+
"coinbase",
|
|
668
|
+
"robinhood",
|
|
669
|
+
"doordash",
|
|
670
|
+
"airbnb",
|
|
671
|
+
"dropbox",
|
|
672
|
+
"datadog",
|
|
673
|
+
"cloudflare",
|
|
674
|
+
"reddit",
|
|
675
|
+
"lyft",
|
|
676
|
+
"instacart"
|
|
677
|
+
],
|
|
678
|
+
scaleup: [
|
|
679
|
+
"samsara",
|
|
680
|
+
"verkada",
|
|
681
|
+
"affirm",
|
|
682
|
+
"gitlab",
|
|
683
|
+
"asana",
|
|
684
|
+
"flexport",
|
|
685
|
+
"faire",
|
|
686
|
+
"twitch",
|
|
687
|
+
"airtable",
|
|
688
|
+
"retool"
|
|
689
|
+
],
|
|
690
|
+
startup: [
|
|
691
|
+
"watershed"
|
|
692
|
+
]
|
|
693
|
+
};
|
|
694
|
+
ASHBY_SLUGS_BY_TIER = {
|
|
695
|
+
bigco: [
|
|
696
|
+
"openai"
|
|
697
|
+
],
|
|
698
|
+
scaleup: [
|
|
699
|
+
"harvey",
|
|
700
|
+
"elevenlabs",
|
|
701
|
+
"notion",
|
|
702
|
+
"sierra",
|
|
703
|
+
"cohere",
|
|
704
|
+
"ramp",
|
|
705
|
+
"vanta",
|
|
706
|
+
"decagon",
|
|
707
|
+
"cursor",
|
|
708
|
+
"replit",
|
|
709
|
+
"perplexity",
|
|
710
|
+
"baseten",
|
|
711
|
+
"drata",
|
|
712
|
+
"writer",
|
|
713
|
+
"temporal",
|
|
714
|
+
"supabase"
|
|
715
|
+
],
|
|
716
|
+
startup: [
|
|
717
|
+
"suno",
|
|
718
|
+
"attio",
|
|
719
|
+
"modal",
|
|
720
|
+
"workos",
|
|
721
|
+
"linear",
|
|
722
|
+
"render",
|
|
723
|
+
"warp",
|
|
724
|
+
"plain",
|
|
725
|
+
"posthog",
|
|
726
|
+
"pylon",
|
|
727
|
+
"resend",
|
|
728
|
+
"langfuse",
|
|
729
|
+
"railway",
|
|
730
|
+
"mintlify",
|
|
731
|
+
"neon",
|
|
732
|
+
"browserbase",
|
|
733
|
+
"knock",
|
|
734
|
+
"speakeasy",
|
|
735
|
+
"stytch",
|
|
736
|
+
"runway",
|
|
737
|
+
"doppler",
|
|
738
|
+
"inngest",
|
|
739
|
+
"hightouch",
|
|
740
|
+
"zed"
|
|
741
|
+
]
|
|
742
|
+
};
|
|
743
|
+
LEVER_SLUGS_BY_TIER = {
|
|
744
|
+
bigco: [
|
|
745
|
+
"palantir",
|
|
746
|
+
"spotify"
|
|
747
|
+
],
|
|
748
|
+
scaleup: [
|
|
749
|
+
"mistral",
|
|
750
|
+
"ro",
|
|
751
|
+
"secureframe"
|
|
752
|
+
],
|
|
753
|
+
startup: [
|
|
754
|
+
"anyscale"
|
|
755
|
+
]
|
|
756
|
+
};
|
|
757
|
+
DEFAULT_GREENHOUSE_SLUGS = flattenTiers(GREENHOUSE_SLUGS_BY_TIER);
|
|
758
|
+
DEFAULT_ASHBY_SLUGS = flattenTiers(ASHBY_SLUGS_BY_TIER);
|
|
759
|
+
DEFAULT_LEVER_SLUGS = flattenTiers(LEVER_SLUGS_BY_TIER);
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
// ../../packages/core/src/feeds/contribution-classify.ts
|
|
764
|
+
var init_contribution_classify = __esm({
|
|
765
|
+
"../../packages/core/src/feeds/contribution-classify.ts"() {
|
|
766
|
+
"use strict";
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
// ../../packages/core/src/feeds/contributions.ts
|
|
771
|
+
var init_contributions = __esm({
|
|
772
|
+
"../../packages/core/src/feeds/contributions.ts"() {
|
|
773
|
+
"use strict";
|
|
774
|
+
init_vocabulary();
|
|
775
|
+
init_entities();
|
|
776
|
+
init_bounty_gate();
|
|
777
|
+
init_contribution_gate();
|
|
778
|
+
init_contribution_classify();
|
|
779
|
+
init_github_bounties();
|
|
780
|
+
init_http();
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
// ../../packages/core/src/partners.ts
|
|
785
|
+
import { readFileSync } from "fs";
|
|
786
|
+
import { join } from "path";
|
|
787
|
+
import { fileURLToPath } from "url";
|
|
788
|
+
var EXAMPLE_BUYER, BUYER_REGISTRY;
|
|
789
|
+
var init_partners = __esm({
|
|
790
|
+
"../../packages/core/src/partners.ts"() {
|
|
791
|
+
"use strict";
|
|
792
|
+
EXAMPLE_BUYER = {
|
|
793
|
+
id: "northstar",
|
|
794
|
+
legalName: "Northstar Talent Partners",
|
|
795
|
+
matchCriteria: { roleTypes: ["full_time"] }
|
|
796
|
+
};
|
|
797
|
+
BUYER_REGISTRY = {
|
|
798
|
+
[EXAMPLE_BUYER.id]: EXAMPLE_BUYER
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
// ../../packages/core/src/indexer.ts
|
|
804
|
+
var init_indexer = __esm({
|
|
805
|
+
"../../packages/core/src/indexer.ts"() {
|
|
806
|
+
"use strict";
|
|
807
|
+
init_feeds();
|
|
808
|
+
init_contributions();
|
|
809
|
+
init_partners();
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
// ../../packages/core/src/intro.ts
|
|
814
|
+
var INTRO_ALLOWED_FIELDS, INTRO_ALLOWED_SET, INTRO_PENDING_TTL_MS, INTRO_ACCEPTED_TTL_MS;
|
|
815
|
+
var init_intro = __esm({
|
|
816
|
+
"../../packages/core/src/intro.ts"() {
|
|
817
|
+
"use strict";
|
|
818
|
+
INTRO_ALLOWED_FIELDS = [
|
|
819
|
+
"requesterLogin",
|
|
820
|
+
"requesterDisplayName",
|
|
821
|
+
"requesterContact",
|
|
822
|
+
"note",
|
|
823
|
+
"targetLogin"
|
|
824
|
+
];
|
|
825
|
+
INTRO_ALLOWED_SET = new Set(INTRO_ALLOWED_FIELDS);
|
|
826
|
+
INTRO_PENDING_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
827
|
+
INTRO_ACCEPTED_TTL_MS = 365 * 24 * 60 * 60 * 1e3;
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
// ../../packages/core/src/directoryThreshold.ts
|
|
832
|
+
var init_directoryThreshold = __esm({
|
|
833
|
+
"../../packages/core/src/directoryThreshold.ts"() {
|
|
834
|
+
"use strict";
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
// ../../packages/core/src/chatCrypto.ts
|
|
839
|
+
import { hkdfSync, createHash, randomBytes } from "crypto";
|
|
840
|
+
var KDF_INFO;
|
|
841
|
+
var init_chatCrypto = __esm({
|
|
842
|
+
"../../packages/core/src/chatCrypto.ts"() {
|
|
843
|
+
"use strict";
|
|
844
|
+
KDF_INFO = Buffer.from("terminalhire-chat-v1");
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
// ../../packages/core/src/job-status.ts
|
|
849
|
+
function recordClick(map, id) {
|
|
850
|
+
const prev = map[id];
|
|
851
|
+
if (prev?.clicked === true) return map;
|
|
852
|
+
return { ...map, [id]: { ...prev, clicked: true } };
|
|
853
|
+
}
|
|
854
|
+
function setStatus(map, id, s, markedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
855
|
+
const prev = map[id];
|
|
856
|
+
return { ...map, [id]: { ...prev, status: s, markedAt } };
|
|
857
|
+
}
|
|
858
|
+
var init_job_status = __esm({
|
|
859
|
+
"../../packages/core/src/job-status.ts"() {
|
|
860
|
+
"use strict";
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
// ../../packages/core/src/episodes/schema.ts
|
|
865
|
+
var init_schema = __esm({
|
|
866
|
+
"../../packages/core/src/episodes/schema.ts"() {
|
|
867
|
+
"use strict";
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
// ../../packages/core/src/episodes/doors.ts
|
|
872
|
+
var init_doors = __esm({
|
|
873
|
+
"../../packages/core/src/episodes/doors.ts"() {
|
|
874
|
+
"use strict";
|
|
875
|
+
init_schema();
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
// ../../packages/core/src/episodes/node-model.ts
|
|
880
|
+
var init_node_model = __esm({
|
|
881
|
+
"../../packages/core/src/episodes/node-model.ts"() {
|
|
882
|
+
"use strict";
|
|
883
|
+
}
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
// ../../packages/core/src/episodes/derivers/signals.ts
|
|
887
|
+
var init_signals = __esm({
|
|
888
|
+
"../../packages/core/src/episodes/derivers/signals.ts"() {
|
|
889
|
+
"use strict";
|
|
890
|
+
init_node_model();
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
// ../../packages/core/src/episodes/derivers/recency-split.ts
|
|
895
|
+
var init_recency_split = __esm({
|
|
896
|
+
"../../packages/core/src/episodes/derivers/recency-split.ts"() {
|
|
897
|
+
"use strict";
|
|
898
|
+
init_doors();
|
|
899
|
+
init_signals();
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
// ../../packages/core/src/credential/legible.ts
|
|
904
|
+
var init_legible = __esm({
|
|
905
|
+
"../../packages/core/src/credential/legible.ts"() {
|
|
906
|
+
"use strict";
|
|
907
|
+
init_contribution_gate();
|
|
908
|
+
init_vocabulary();
|
|
909
|
+
init_recency_split();
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
// ../../packages/core/src/credential/legible-trajectory.ts
|
|
914
|
+
var init_legible_trajectory = __esm({
|
|
915
|
+
"../../packages/core/src/credential/legible-trajectory.ts"() {
|
|
916
|
+
"use strict";
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
// ../../packages/core/src/short-token.ts
|
|
921
|
+
import { createHash as createHash2 } from "crypto";
|
|
922
|
+
function opportunityShortToken(id) {
|
|
923
|
+
return createHash2("sha256").update(id, "utf8").digest("base64url").slice(0, 8);
|
|
924
|
+
}
|
|
925
|
+
var init_short_token = __esm({
|
|
926
|
+
"../../packages/core/src/short-token.ts"() {
|
|
927
|
+
"use strict";
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
// ../../packages/core/src/index.ts
|
|
932
|
+
var init_src = __esm({
|
|
933
|
+
"../../packages/core/src/index.ts"() {
|
|
934
|
+
"use strict";
|
|
935
|
+
init_types();
|
|
936
|
+
init_vocabulary();
|
|
937
|
+
init_matcher();
|
|
938
|
+
init_rerank();
|
|
939
|
+
init_feeds();
|
|
940
|
+
init_indexer();
|
|
941
|
+
init_partners();
|
|
942
|
+
init_github();
|
|
943
|
+
init_intro();
|
|
944
|
+
init_directoryThreshold();
|
|
945
|
+
init_chatCrypto();
|
|
946
|
+
init_job_status();
|
|
947
|
+
init_legible();
|
|
948
|
+
init_legible_trajectory();
|
|
949
|
+
init_short_token();
|
|
950
|
+
}
|
|
951
|
+
});
|
|
11
952
|
|
|
12
953
|
// src/claims.ts
|
|
13
954
|
var claims_exports = {};
|
|
@@ -18,11 +959,23 @@ __export(claims_exports, {
|
|
|
18
959
|
readClaims: () => readClaims,
|
|
19
960
|
recordClaim: () => recordClaim,
|
|
20
961
|
removeClaim: () => removeClaim,
|
|
962
|
+
toPushedClaim: () => toPushedClaim,
|
|
21
963
|
updateClaim: () => updateClaim
|
|
22
964
|
});
|
|
23
965
|
import { readFileSync as readFileSync2, writeFileSync, mkdirSync, renameSync, existsSync } from "fs";
|
|
24
966
|
import { join as join2 } from "path";
|
|
25
967
|
import { homedir } from "os";
|
|
968
|
+
function toPushedClaim(claim) {
|
|
969
|
+
return {
|
|
970
|
+
kind: claim.kind,
|
|
971
|
+
repoFullName: claim.repoFullName,
|
|
972
|
+
state: claim.state,
|
|
973
|
+
prUrl: claim.prUrl,
|
|
974
|
+
merged: claim.state === "merged",
|
|
975
|
+
claimedAt: claim.claimedAt,
|
|
976
|
+
updatedAt: claim.updatedAt
|
|
977
|
+
};
|
|
978
|
+
}
|
|
26
979
|
function nowISO() {
|
|
27
980
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
28
981
|
}
|
|
@@ -195,518 +1148,469 @@ var init_repo_policy = __esm({
|
|
|
195
1148
|
}
|
|
196
1149
|
});
|
|
197
1150
|
|
|
198
|
-
// bin/
|
|
199
|
-
|
|
200
|
-
|
|
1151
|
+
// bin/job-status-store.js
|
|
1152
|
+
var job_status_store_exports = {};
|
|
1153
|
+
__export(job_status_store_exports, {
|
|
1154
|
+
markClicked: () => markClicked,
|
|
1155
|
+
markStatus: () => markStatus,
|
|
1156
|
+
readStatusMap: () => readStatusMap,
|
|
1157
|
+
statusFilePath: () => statusFilePath
|
|
1158
|
+
});
|
|
1159
|
+
import {
|
|
1160
|
+
readFileSync as readFileSync3,
|
|
1161
|
+
writeFileSync as writeFileSync2,
|
|
1162
|
+
renameSync as renameSync2,
|
|
1163
|
+
mkdirSync as mkdirSync2,
|
|
1164
|
+
existsSync as existsSync2,
|
|
1165
|
+
copyFileSync,
|
|
1166
|
+
openSync,
|
|
1167
|
+
closeSync,
|
|
1168
|
+
unlinkSync
|
|
1169
|
+
} from "fs";
|
|
1170
|
+
import { join as join3, dirname } from "path";
|
|
201
1171
|
import { homedir as homedir2 } from "os";
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
import { createInterface } from "readline";
|
|
205
|
-
|
|
206
|
-
// ../../packages/core/src/vocab/graph.data.ts
|
|
207
|
-
var VOCAB_NODES = [
|
|
208
|
-
// ── Languages ─────────────────────────────────────────────────────────────
|
|
209
|
-
{ id: "javascript", synonyms: ["js"], related: [{ to: "typescript", w: 0.6 }] },
|
|
210
|
-
{ id: "typescript", parents: ["javascript"], synonyms: ["ts"] },
|
|
211
|
-
{ id: "python", synonyms: ["py"] },
|
|
212
|
-
{ id: "go", synonyms: ["golang"] },
|
|
213
|
-
{ id: "rust" },
|
|
214
|
-
{ id: "java", related: [{ to: "kotlin", w: 0.45 }, { to: "scala", w: 0.4 }] },
|
|
215
|
-
{ id: "ruby" },
|
|
216
|
-
{ id: "elixir" },
|
|
217
|
-
{ id: "scala", related: [{ to: "java", w: 0.4 }] },
|
|
218
|
-
{ id: "kotlin", related: [{ to: "java", w: 0.45 }] },
|
|
219
|
-
{ id: "swift" },
|
|
220
|
-
{ id: "cpp", synonyms: ["c++"] },
|
|
221
|
-
{ id: "csharp", synonyms: ["c#"] },
|
|
222
|
-
{ id: "php" },
|
|
223
|
-
{ id: "haskell" },
|
|
224
|
-
{ id: "clojure" },
|
|
225
|
-
{ id: "r" },
|
|
226
|
-
{ id: "dart" },
|
|
227
|
-
// ── Frontend ──────────────────────────────────────────────────────────────
|
|
228
|
-
{
|
|
229
|
-
id: "react",
|
|
230
|
-
parents: ["javascript"],
|
|
231
|
-
synonyms: ["reactjs"],
|
|
232
|
-
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 }]
|
|
233
|
-
},
|
|
234
|
-
{ id: "nextjs", parents: ["react"], synonyms: ["next", "next.js"], related: [{ to: "remix", w: 0.5 }] },
|
|
235
|
-
{ id: "vue", parents: ["javascript"], synonyms: ["vue.js"], related: [{ to: "nuxt", w: 0.6 }] },
|
|
236
|
-
{ id: "nuxt", parents: ["vue"], synonyms: ["nuxt.js"] },
|
|
237
|
-
{ id: "svelte", parents: ["javascript"], related: [{ to: "sveltekit", w: 0.65 }] },
|
|
238
|
-
{ id: "sveltekit", parents: ["svelte"] },
|
|
239
|
-
{ id: "angular", parents: ["typescript"], synonyms: ["angular.js", "angularjs"] },
|
|
240
|
-
{ id: "solidjs", parents: ["javascript"] },
|
|
241
|
-
{ id: "remix", parents: ["react"], synonyms: ["remix.run"] },
|
|
242
|
-
{ id: "astro", parents: ["javascript"], related: [{ to: "nextjs", w: 0.4 }] },
|
|
243
|
-
{ id: "qwik", parents: ["javascript"] },
|
|
244
|
-
{ id: "tailwind", parents: ["css"], synonyms: ["tailwindcss", "tw"] },
|
|
245
|
-
{ id: "css" },
|
|
246
|
-
{ id: "html" },
|
|
247
|
-
{ id: "redux", parents: ["react"] },
|
|
248
|
-
{ id: "vite", parents: ["frontend"] },
|
|
249
|
-
{ id: "webpack", parents: ["frontend"] },
|
|
250
|
-
{ id: "storybook", parents: ["frontend"] },
|
|
251
|
-
// ── Backend frameworks / runtimes ───────────────────────────────────────────
|
|
252
|
-
{
|
|
253
|
-
id: "nodejs",
|
|
254
|
-
parents: ["javascript"],
|
|
255
|
-
synonyms: ["node", "node.js"],
|
|
256
|
-
related: [{ to: "express", w: 0.5 }, { to: "fastify", w: 0.45 }, { to: "nestjs", w: 0.45 }]
|
|
257
|
-
},
|
|
258
|
-
{ id: "express", parents: ["nodejs"], synonyms: ["express.js", "expressjs"], related: [{ to: "fastify", w: 0.5 }] },
|
|
259
|
-
{ id: "fastify", parents: ["nodejs"] },
|
|
260
|
-
{ id: "nestjs", parents: ["nodejs"], synonyms: ["nest", "nest.js"] },
|
|
261
|
-
{ id: "hono", parents: ["nodejs"] },
|
|
262
|
-
{ id: "deno", parents: ["javascript"], related: [{ to: "nodejs", w: 0.5 }, { to: "bun", w: 0.5 }] },
|
|
263
|
-
{ id: "bun", parents: ["javascript"], related: [{ to: "nodejs", w: 0.5 }] },
|
|
264
|
-
{ id: "django", parents: ["python"], related: [{ to: "flask", w: 0.5 }, { to: "fastapi", w: 0.45 }] },
|
|
265
|
-
{ id: "fastapi", parents: ["python"], related: [{ to: "flask", w: 0.55 }, { to: "django", w: 0.45 }] },
|
|
266
|
-
{ id: "flask", parents: ["python"] },
|
|
267
|
-
{ id: "rails", parents: ["ruby"], synonyms: ["ruby-on-rails", "ror"] },
|
|
268
|
-
{ id: "spring", parents: ["java"], synonyms: ["spring-boot", "springboot"] },
|
|
269
|
-
{ id: "actix", parents: ["rust"] },
|
|
270
|
-
{ id: "gin", parents: ["go"] },
|
|
271
|
-
{ id: "phoenix", parents: ["elixir"] },
|
|
272
|
-
{ id: "laravel", parents: ["php"] },
|
|
273
|
-
{ id: "dotnet", parents: ["csharp"], synonyms: [".net", "asp.net", "dotnet-core"] },
|
|
274
|
-
// ── Infrastructure & DevOps ─────────────────────────────────────────────────
|
|
275
|
-
{ 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 }] },
|
|
276
|
-
{ id: "docker", parents: ["devops"], related: [{ to: "kubernetes", w: 0.5 }] },
|
|
277
|
-
{ id: "terraform", synonyms: ["tf"], related: [{ to: "pulumi", w: 0.55 }, { to: "ansible", w: 0.4 }, { to: "aws", w: 0.4 }] },
|
|
278
|
-
{ id: "pulumi", related: [{ to: "terraform", w: 0.55 }] },
|
|
279
|
-
{ id: "ansible" },
|
|
280
|
-
{ id: "aws", synonyms: ["amazon-web-services"], related: [{ to: "gcp", w: 0.4 }, { to: "azure", w: 0.4 }] },
|
|
281
|
-
{ id: "gcp", synonyms: ["google-cloud", "google-cloud-platform"], related: [{ to: "aws", w: 0.4 }, { to: "azure", w: 0.4 }] },
|
|
282
|
-
{ id: "azure", synonyms: ["microsoft-azure"], related: [{ to: "aws", w: 0.4 }] },
|
|
283
|
-
{ id: "ci-cd", synonyms: ["cicd", "jenkins", "circleci", "circle-ci", "travis"], related: [{ to: "github-actions", w: 0.6 }, { to: "gitlab-ci", w: 0.6 }] },
|
|
284
|
-
{ id: "github-actions", parents: ["ci-cd"], synonyms: ["github-action"] },
|
|
285
|
-
{ id: "gitlab-ci", parents: ["ci-cd"], synonyms: ["gitlab"] },
|
|
286
|
-
{ id: "linux" },
|
|
287
|
-
{ id: "nginx" },
|
|
288
|
-
{ id: "prometheus", parents: ["observability"], related: [{ to: "grafana", w: 0.6 }] },
|
|
289
|
-
{ id: "grafana", parents: ["observability"] },
|
|
290
|
-
{ id: "datadog", parents: ["observability"] },
|
|
291
|
-
{ id: "opentelemetry", parents: ["observability"], synonyms: ["otel"] },
|
|
292
|
-
{ id: "vercel", related: [{ to: "netlify", w: 0.5 }, { to: "nextjs", w: 0.4 }] },
|
|
293
|
-
{ id: "netlify" },
|
|
294
|
-
{ id: "fly", synonyms: ["fly.io"], related: [{ to: "railway", w: 0.5 }, { to: "render", w: 0.5 }] },
|
|
295
|
-
{ id: "railway", related: [{ to: "render", w: 0.5 }] },
|
|
296
|
-
{ id: "render" },
|
|
297
|
-
{ id: "cloudflare", synonyms: ["cloudflare-workers"] },
|
|
298
|
-
{ id: "helm", parents: ["kubernetes"] },
|
|
299
|
-
{ id: "argocd", parents: ["kubernetes"] },
|
|
300
|
-
{ id: "serverless", parents: ["devops"] },
|
|
301
|
-
// ── Databases & storage ─────────────────────────────────────────────────────
|
|
302
|
-
{ id: "postgresql", synonyms: ["postgres", "pg"], related: [{ to: "mysql", w: 0.45 }, { to: "sqlite", w: 0.4 }] },
|
|
303
|
-
{ id: "mysql", related: [{ to: "postgresql", w: 0.45 }] },
|
|
304
|
-
{ id: "sqlite" },
|
|
305
|
-
{ id: "mongodb", synonyms: ["mongo"] },
|
|
306
|
-
{ id: "redis", related: [{ to: "caching", w: 0.5 }] },
|
|
307
|
-
{ id: "elasticsearch", synonyms: ["elastic"], related: [{ to: "search", w: 0.55 }] },
|
|
308
|
-
{ id: "kafka", synonyms: ["apache-kafka"], related: [{ to: "rabbitmq", w: 0.5 }, { to: "message-queue", w: 0.55 }] },
|
|
309
|
-
{ id: "rabbitmq", related: [{ to: "message-queue", w: 0.55 }] },
|
|
310
|
-
{ id: "cassandra" },
|
|
311
|
-
{ id: "dynamodb", parents: ["aws"] },
|
|
312
|
-
{ id: "snowflake", parents: ["data-engineering"], related: [{ to: "clickhouse", w: 0.4 }] },
|
|
313
|
-
{ id: "clickhouse", parents: ["data-engineering"], related: [{ to: "duckdb", w: 0.35 }] },
|
|
314
|
-
{ id: "duckdb", parents: ["data-engineering"] },
|
|
315
|
-
{ id: "supabase", related: [{ to: "postgresql", w: 0.5 }, { to: "neon", w: 0.4 }] },
|
|
316
|
-
{ id: "planetscale", related: [{ to: "mysql", w: 0.5 }] },
|
|
317
|
-
{ id: "neon", related: [{ to: "postgresql", w: 0.5 }] },
|
|
318
|
-
{ id: "turso", related: [{ to: "sqlite", w: 0.5 }] },
|
|
319
|
-
{ id: "cockroachdb", related: [{ to: "postgresql", w: 0.45 }] },
|
|
320
|
-
{ id: "prisma", parents: ["backend"], synonyms: ["@prisma/client"], related: [{ to: "drizzle", w: 0.5 }, { to: "typeorm", w: 0.45 }, { to: "sequelize", w: 0.4 }] },
|
|
321
|
-
{ id: "drizzle", synonyms: ["drizzle-orm"], related: [{ to: "prisma", w: 0.5 }] },
|
|
322
|
-
{ id: "sequelize", related: [{ to: "typeorm", w: 0.4 }] },
|
|
323
|
-
{ id: "typeorm", related: [{ to: "prisma", w: 0.45 }] },
|
|
324
|
-
{ id: "sqlalchemy", parents: ["python"] },
|
|
325
|
-
// ── Data engineering & ML ───────────────────────────────────────────────────
|
|
326
|
-
{ id: "data-engineering", synonyms: ["data-eng"], related: [{ to: "spark", w: 0.5 }, { to: "airflow", w: 0.5 }, { to: "dbt", w: 0.45 }] },
|
|
327
|
-
{ id: "spark", parents: ["data-engineering"], synonyms: ["apache-spark"] },
|
|
328
|
-
{ id: "airflow", parents: ["data-engineering"], synonyms: ["apache-airflow"] },
|
|
329
|
-
{ id: "dbt", parents: ["data-engineering"] },
|
|
330
|
-
{ id: "ml", synonyms: ["machine-learning"], related: [{ to: "pytorch", w: 0.5 }, { to: "tensorflow", w: 0.5 }, { to: "scikit-learn", w: 0.5 }, { to: "data-engineering", w: 0.4 }] },
|
|
331
|
-
{ id: "llm", parents: ["ml"], synonyms: ["llms", "genai", "generative-ai", "gpt"], related: [{ to: "langchain", w: 0.5 }, { to: "rag", w: 0.55 }, { to: "openai", w: 0.45 }, { to: "anthropic", w: 0.45 }] },
|
|
332
|
-
{ id: "pytorch", parents: ["ml"], synonyms: ["torch"], related: [{ to: "tensorflow", w: 0.5 }] },
|
|
333
|
-
{ id: "tensorflow", parents: ["ml"], synonyms: ["keras", "tf-keras"] },
|
|
334
|
-
{ id: "pandas", parents: ["python"], related: [{ to: "numpy", w: 0.6 }, { to: "data-engineering", w: 0.45 }, { to: "spark", w: 0.4 }] },
|
|
335
|
-
{ id: "numpy", parents: ["python"] },
|
|
336
|
-
{ id: "scikit-learn", parents: ["ml"], synonyms: ["sklearn"] },
|
|
337
|
-
{ id: "jupyter", parents: ["python"] },
|
|
338
|
-
{ id: "langchain", parents: ["llm"], synonyms: ["llamaindex"] },
|
|
339
|
-
{ id: "huggingface", parents: ["ml"], synonyms: ["hugging-face"] },
|
|
340
|
-
{ id: "openai", parents: ["llm"] },
|
|
341
|
-
{ id: "anthropic", parents: ["llm"], synonyms: ["claude"] },
|
|
342
|
-
{ id: "rag", parents: ["llm"], synonyms: ["retrieval-augmented-generation"] },
|
|
343
|
-
{ id: "mlops", parents: ["ml"], related: [{ to: "devops", w: 0.4 }] },
|
|
344
|
-
{ id: "agents", parents: ["llm"], synonyms: ["agentic", "ai-agents", "multi-agent"], related: [{ to: "rag", w: 0.4 }] },
|
|
345
|
-
{ id: "mcp", parents: ["agents"], synonyms: ["model-context-protocol"], related: [{ to: "llm", w: 0.45 }] },
|
|
346
|
-
{ id: "inference", parents: ["ml"], synonyms: ["model-inference", "llm-inference", "model-serving"], related: [{ to: "mlops", w: 0.5 }, { to: "llm", w: 0.4 }] },
|
|
347
|
-
{ id: "embeddings", parents: ["ml"], synonyms: ["embedding", "vector-embeddings"], related: [{ to: "rag", w: 0.55 }, { to: "llm", w: 0.45 }] },
|
|
348
|
-
{ id: "prompt-engineering", parents: ["llm"], synonyms: ["prompting", "prompt"] },
|
|
349
|
-
{ id: "fine-tuning", parents: ["ml"], synonyms: ["finetuning", "fine-tune", "rlhf"], related: [{ to: "llm", w: 0.5 }] },
|
|
350
|
-
{ id: "computer-vision", parents: ["ml"], synonyms: ["image-recognition", "object-detection"] },
|
|
351
|
-
{ id: "recsys", parents: ["ml"], synonyms: ["recommender-systems", "recommendation-systems", "recommendation"] },
|
|
352
|
-
// ── Mobile ──────────────────────────────────────────────────────────────────
|
|
353
|
-
{ id: "mobile", related: [{ to: "ios", w: 0.5 }, { to: "android", w: 0.5 }] },
|
|
354
|
-
{ id: "ios", parents: ["mobile", "swift"], related: [{ to: "android", w: 0.4 }] },
|
|
355
|
-
{ id: "android", parents: ["mobile"], related: [{ to: "kotlin", w: 0.4 }] },
|
|
356
|
-
{ id: "swiftui", parents: ["ios", "swift"] },
|
|
357
|
-
{ id: "react-native", parents: ["mobile", "react"], synonyms: ["reactnative"], related: [{ to: "flutter", w: 0.4 }, { to: "expo", w: 0.6 }] },
|
|
358
|
-
{ id: "flutter", parents: ["mobile", "dart"] },
|
|
359
|
-
{ id: "expo", parents: ["react-native"] },
|
|
360
|
-
{ id: "kotlin-multiplatform", parents: ["mobile", "kotlin"], synonyms: ["kmp"] },
|
|
361
|
-
// ── Domains / capabilities ──────────────────────────────────────────────────
|
|
362
|
-
{ id: "frontend", related: [{ to: "react", w: 0.4 }, { to: "css", w: 0.3 }] },
|
|
363
|
-
{ id: "backend", related: [{ to: "api-design", w: 0.4 }, { to: "microservices", w: 0.4 }] },
|
|
364
|
-
{ id: "devops", related: [{ to: "kubernetes", w: 0.4 }, { to: "ci-cd", w: 0.4 }, { to: "docker", w: 0.4 }] },
|
|
365
|
-
{ id: "authentication", synonyms: ["auth", "jwt", "saml", "passport", "auth0", "clerk", "nextauth"], related: [{ to: "oauth", w: 0.6 }, { to: "security", w: 0.5 }] },
|
|
366
|
-
{ id: "oauth", parents: ["authentication"], synonyms: ["oauth2", "oidc"], related: [{ to: "security", w: 0.4 }] },
|
|
367
|
-
{ id: "security", related: [{ to: "authentication", w: 0.5 }] },
|
|
368
|
-
{ id: "payments", synonyms: ["stripe", "braintree", "paddle", "lemonsqueezy", "@stripe/stripe-js"], related: [{ to: "billing", w: 0.6 }] },
|
|
369
|
-
{ id: "billing", synonyms: ["recurly", "chargebee"] },
|
|
370
|
-
{ id: "api-design", synonyms: ["rest", "restful", "rest-api"], related: [{ to: "graphql", w: 0.4 }, { to: "grpc", w: 0.4 }, { to: "backend", w: 0.4 }] },
|
|
371
|
-
{ id: "graphql", synonyms: ["gql"], related: [{ to: "trpc", w: 0.4 }] },
|
|
372
|
-
{ id: "trpc", related: [{ to: "graphql", w: 0.4 }] },
|
|
373
|
-
{ id: "grpc", synonyms: ["grpc-web"], related: [{ to: "microservices", w: 0.3 }] },
|
|
374
|
-
{ id: "microservices" },
|
|
375
|
-
{ id: "websockets", synonyms: ["ws", "socket.io"], related: [{ to: "realtime", w: 0.6 }] },
|
|
376
|
-
{ id: "realtime", synonyms: ["real-time"] },
|
|
377
|
-
{ id: "message-queue", synonyms: ["mq"] },
|
|
378
|
-
{ id: "caching", synonyms: ["cache"] },
|
|
379
|
-
{ id: "search", synonyms: ["full-text-search"] },
|
|
380
|
-
{ id: "observability", synonyms: ["o11y"], related: [{ to: "monitoring", w: 0.6 }] },
|
|
381
|
-
{ id: "monitoring", related: [{ to: "prometheus", w: 0.4 }] },
|
|
382
|
-
{ id: "testing", related: [{ to: "unit-testing", w: 0.5 }, { to: "e2e-testing", w: 0.5 }] },
|
|
383
|
-
{ id: "unit-testing", parents: ["testing"] },
|
|
384
|
-
{ id: "e2e-testing", parents: ["testing"], synonyms: ["e2e", "end-to-end-testing"] },
|
|
385
|
-
{ id: "jest", parents: ["testing"], related: [{ to: "vitest", w: 0.6 }, { to: "mocha", w: 0.5 }] },
|
|
386
|
-
{ id: "vitest", parents: ["testing"], related: [{ to: "jest", w: 0.6 }] },
|
|
387
|
-
{ id: "playwright", parents: ["e2e-testing"], related: [{ to: "cypress", w: 0.6 }] },
|
|
388
|
-
{ id: "cypress", parents: ["e2e-testing"] },
|
|
389
|
-
{ id: "mocha", parents: ["testing"] },
|
|
390
|
-
{ id: "pytest", parents: ["testing", "python"] },
|
|
391
|
-
{ id: "accessibility", synonyms: ["a11y"] },
|
|
392
|
-
{ id: "seo" },
|
|
393
|
-
{ id: "performance", synonyms: ["perf", "web-performance"] }
|
|
394
|
-
];
|
|
395
|
-
|
|
396
|
-
// ../../packages/core/src/vocab/closure.ts
|
|
397
|
-
var PARENT_UP = 0.6;
|
|
398
|
-
var PARENT_DOWN = 0.35;
|
|
399
|
-
var DECAY_FLOOR = 0.25;
|
|
400
|
-
function round3(n) {
|
|
401
|
-
return Math.round(n * 1e3) / 1e3;
|
|
1172
|
+
function statusFilePath() {
|
|
1173
|
+
return STATUS_FILE;
|
|
402
1174
|
}
|
|
403
|
-
function
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
1175
|
+
function atomicWriteJson(path, obj) {
|
|
1176
|
+
mkdirSync2(dirname(path), { recursive: true });
|
|
1177
|
+
const tmp = `${path}.tmp-${process.pid}`;
|
|
1178
|
+
writeFileSync2(tmp, JSON.stringify(obj, null, 2) + "\n", "utf8");
|
|
1179
|
+
renameSync2(tmp, path);
|
|
1180
|
+
}
|
|
1181
|
+
function sleepMs(ms) {
|
|
1182
|
+
try {
|
|
1183
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
1184
|
+
} catch {
|
|
1185
|
+
const end = Date.now() + ms;
|
|
1186
|
+
while (Date.now() < end) {
|
|
1187
|
+
}
|
|
408
1188
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
1189
|
+
}
|
|
1190
|
+
function withLock(fn) {
|
|
1191
|
+
const deadline = Date.now() + 2e3;
|
|
1192
|
+
for (; ; ) {
|
|
1193
|
+
let fd;
|
|
1194
|
+
try {
|
|
1195
|
+
mkdirSync2(dirname(LOCK_FILE), { recursive: true });
|
|
1196
|
+
fd = openSync(LOCK_FILE, "wx");
|
|
1197
|
+
} catch (err) {
|
|
1198
|
+
if (err && err.code === "EEXIST") {
|
|
1199
|
+
if (Date.now() > deadline) {
|
|
1200
|
+
try {
|
|
1201
|
+
unlinkSync(LOCK_FILE);
|
|
1202
|
+
} catch {
|
|
1203
|
+
}
|
|
1204
|
+
continue;
|
|
1205
|
+
}
|
|
1206
|
+
sleepMs(5);
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1209
|
+
throw err;
|
|
414
1210
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
1211
|
+
try {
|
|
1212
|
+
return fn();
|
|
1213
|
+
} finally {
|
|
1214
|
+
try {
|
|
1215
|
+
closeSync(fd);
|
|
1216
|
+
} catch {
|
|
1217
|
+
}
|
|
1218
|
+
try {
|
|
1219
|
+
unlinkSync(LOCK_FILE);
|
|
1220
|
+
} catch {
|
|
1221
|
+
}
|
|
419
1222
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
function readStatusMap() {
|
|
1226
|
+
if (!existsSync2(STATUS_FILE)) return {};
|
|
1227
|
+
let raw;
|
|
1228
|
+
try {
|
|
1229
|
+
raw = readFileSync3(STATUS_FILE, "utf8");
|
|
1230
|
+
} catch {
|
|
1231
|
+
return {};
|
|
1232
|
+
}
|
|
1233
|
+
try {
|
|
1234
|
+
const parsed = JSON.parse(raw);
|
|
1235
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
1236
|
+
throw new Error("status store is not an object");
|
|
1237
|
+
} catch {
|
|
1238
|
+
try {
|
|
1239
|
+
copyFileSync(STATUS_FILE, BAK_FILE);
|
|
1240
|
+
} catch {
|
|
1241
|
+
}
|
|
1242
|
+
return {};
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
function markStatus(id, status) {
|
|
1246
|
+
return withLock(() => {
|
|
1247
|
+
const current = readStatusMap();
|
|
1248
|
+
const next = status === "claimed" ? { ...current, [id]: { ...current[id], status: "claimed", markedAt: (/* @__PURE__ */ new Date()).toISOString() } } : setStatus(current, id, status);
|
|
1249
|
+
atomicWriteJson(STATUS_FILE, next);
|
|
1250
|
+
return next[id];
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
function markClicked(id) {
|
|
1254
|
+
return withLock(() => {
|
|
1255
|
+
const current = readStatusMap();
|
|
1256
|
+
const next = recordClick(current, id);
|
|
1257
|
+
atomicWriteJson(STATUS_FILE, next);
|
|
1258
|
+
return next[id];
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
var TERMINALHIRE_DIR2, STATUS_FILE, LOCK_FILE, BAK_FILE;
|
|
1262
|
+
var init_job_status_store = __esm({
|
|
1263
|
+
"bin/job-status-store.js"() {
|
|
1264
|
+
"use strict";
|
|
1265
|
+
init_src();
|
|
1266
|
+
TERMINALHIRE_DIR2 = process.env.TERMINALHIRE_DIR || join3(homedir2(), ".terminalhire");
|
|
1267
|
+
STATUS_FILE = join3(TERMINALHIRE_DIR2, "job-status.json");
|
|
1268
|
+
LOCK_FILE = `${STATUS_FILE}.lock`;
|
|
1269
|
+
BAK_FILE = `${STATUS_FILE}.bak`;
|
|
1270
|
+
}
|
|
1271
|
+
});
|
|
1272
|
+
|
|
1273
|
+
// ../../node_modules/keytar/build/Release/keytar.node
|
|
1274
|
+
var keytar_default;
|
|
1275
|
+
var init_keytar = __esm({
|
|
1276
|
+
"../../node_modules/keytar/build/Release/keytar.node"() {
|
|
1277
|
+
keytar_default = "../keytar-KOAAH267.node";
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
// node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
|
|
1282
|
+
var require_keytar = __commonJS({
|
|
1283
|
+
"node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
|
|
1284
|
+
"use strict";
|
|
1285
|
+
init_keytar();
|
|
1286
|
+
try {
|
|
1287
|
+
module.exports = __require(keytar_default);
|
|
1288
|
+
} catch {
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
});
|
|
1292
|
+
|
|
1293
|
+
// ../../node_modules/keytar/lib/keytar.js
|
|
1294
|
+
var require_keytar2 = __commonJS({
|
|
1295
|
+
"../../node_modules/keytar/lib/keytar.js"(exports, module) {
|
|
1296
|
+
"use strict";
|
|
1297
|
+
var keytar = require_keytar();
|
|
1298
|
+
function checkRequired(val, name) {
|
|
1299
|
+
if (!val || val.length <= 0) {
|
|
1300
|
+
throw new Error(name + " is required.");
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
module.exports = {
|
|
1304
|
+
getPassword: function(service, account) {
|
|
1305
|
+
checkRequired(service, "Service");
|
|
1306
|
+
checkRequired(account, "Account");
|
|
1307
|
+
return keytar.getPassword(service, account);
|
|
1308
|
+
},
|
|
1309
|
+
setPassword: function(service, account, password) {
|
|
1310
|
+
checkRequired(service, "Service");
|
|
1311
|
+
checkRequired(account, "Account");
|
|
1312
|
+
checkRequired(password, "Password");
|
|
1313
|
+
return keytar.setPassword(service, account, password);
|
|
1314
|
+
},
|
|
1315
|
+
deletePassword: function(service, account) {
|
|
1316
|
+
checkRequired(service, "Service");
|
|
1317
|
+
checkRequired(account, "Account");
|
|
1318
|
+
return keytar.deletePassword(service, account);
|
|
1319
|
+
},
|
|
1320
|
+
findPassword: function(service) {
|
|
1321
|
+
checkRequired(service, "Service");
|
|
1322
|
+
return keytar.findPassword(service);
|
|
1323
|
+
},
|
|
1324
|
+
findCredentials: function(service) {
|
|
1325
|
+
checkRequired(service, "Service");
|
|
1326
|
+
return keytar.findCredentials(service);
|
|
1327
|
+
}
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
// src/profile.ts
|
|
1333
|
+
var profile_exports = {};
|
|
1334
|
+
__export(profile_exports, {
|
|
1335
|
+
accumulateGitHubTags: () => accumulateGitHubTags,
|
|
1336
|
+
accumulateSession: () => accumulateSession,
|
|
1337
|
+
accumulateTags: () => accumulateTags,
|
|
1338
|
+
addSavedJob: () => addSavedJob,
|
|
1339
|
+
deleteProfile: () => deleteProfile,
|
|
1340
|
+
listSavedJobs: () => listSavedJobs,
|
|
1341
|
+
profileToFingerprint: () => profileToFingerprint,
|
|
1342
|
+
readProfile: () => readProfile,
|
|
1343
|
+
removeSavedJob: () => removeSavedJob,
|
|
1344
|
+
writeProfile: () => writeProfile
|
|
1345
|
+
});
|
|
1346
|
+
import {
|
|
1347
|
+
createCipheriv,
|
|
1348
|
+
createDecipheriv,
|
|
1349
|
+
randomBytes as randomBytes2
|
|
1350
|
+
} from "crypto";
|
|
1351
|
+
import {
|
|
1352
|
+
readFileSync as readFileSync4,
|
|
1353
|
+
writeFileSync as writeFileSync3,
|
|
1354
|
+
mkdirSync as mkdirSync3,
|
|
1355
|
+
existsSync as existsSync3
|
|
1356
|
+
} from "fs";
|
|
1357
|
+
import { join as join4 } from "path";
|
|
1358
|
+
import { homedir as homedir3 } from "os";
|
|
1359
|
+
async function loadKey() {
|
|
1360
|
+
try {
|
|
1361
|
+
const kt = await Promise.resolve().then(() => __toESM(require_keytar2(), 1));
|
|
1362
|
+
const stored = await kt.getPassword("terminalhire", "profile-key");
|
|
1363
|
+
if (stored) {
|
|
1364
|
+
return Buffer.from(stored, "hex");
|
|
426
1365
|
}
|
|
1366
|
+
const key2 = randomBytes2(KEY_BYTES);
|
|
1367
|
+
await kt.setPassword("terminalhire", "profile-key", key2.toString("hex"));
|
|
1368
|
+
return key2;
|
|
1369
|
+
} catch {
|
|
427
1370
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
1371
|
+
mkdirSync3(TERMINALHIRE_DIR3, { recursive: true });
|
|
1372
|
+
if (existsSync3(KEY_FILE)) {
|
|
1373
|
+
return Buffer.from(readFileSync4(KEY_FILE, "utf8").trim(), "hex");
|
|
1374
|
+
}
|
|
1375
|
+
const key = randomBytes2(KEY_BYTES);
|
|
1376
|
+
writeFileSync3(KEY_FILE, key.toString("hex"), { mode: 384, encoding: "utf8" });
|
|
1377
|
+
return key;
|
|
1378
|
+
}
|
|
1379
|
+
function encrypt(plaintext, key) {
|
|
1380
|
+
const iv = randomBytes2(IV_BYTES);
|
|
1381
|
+
const cipher = createCipheriv(ALGO, key, iv);
|
|
1382
|
+
const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
1383
|
+
const tag = cipher.getAuthTag();
|
|
1384
|
+
return {
|
|
1385
|
+
iv: iv.toString("hex"),
|
|
1386
|
+
tag: tag.toString("hex"),
|
|
1387
|
+
ciphertext: ct.toString("hex")
|
|
438
1388
|
};
|
|
439
|
-
for (const n of nodes) walk(n.id, []);
|
|
440
1389
|
}
|
|
441
|
-
function
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
1390
|
+
function decrypt(blob, key) {
|
|
1391
|
+
const decipher = createDecipheriv(
|
|
1392
|
+
ALGO,
|
|
1393
|
+
key,
|
|
1394
|
+
Buffer.from(blob.iv, "hex")
|
|
1395
|
+
);
|
|
1396
|
+
decipher.setAuthTag(Buffer.from(blob.tag, "hex"));
|
|
1397
|
+
const plain = Buffer.concat([
|
|
1398
|
+
decipher.update(Buffer.from(blob.ciphertext, "hex")),
|
|
1399
|
+
decipher.final()
|
|
1400
|
+
]);
|
|
1401
|
+
return plain.toString("utf8");
|
|
1402
|
+
}
|
|
1403
|
+
function blankProfile() {
|
|
1404
|
+
return {
|
|
1405
|
+
version: 3,
|
|
1406
|
+
skillTags: [],
|
|
1407
|
+
tagWeights: {},
|
|
1408
|
+
hasEmployerSessions: false,
|
|
1409
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
447
1410
|
};
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
1411
|
+
}
|
|
1412
|
+
function recencyDecay(lastSeen) {
|
|
1413
|
+
const ageMs = Date.now() - new Date(lastSeen).getTime();
|
|
1414
|
+
return Math.pow(0.5, ageMs / DECAY_HALF_LIFE_MS);
|
|
1415
|
+
}
|
|
1416
|
+
function tagScore(w) {
|
|
1417
|
+
return w.count * recencyDecay(w.lastSeen);
|
|
1418
|
+
}
|
|
1419
|
+
function deriveSkillTags(tagWeights) {
|
|
1420
|
+
return Object.entries(tagWeights).filter(([, w]) => w.count >= 1).sort(([, a], [, b]) => tagScore(b) - tagScore(a)).map(([tag]) => tag);
|
|
1421
|
+
}
|
|
1422
|
+
function migrateTagWeights(profile) {
|
|
1423
|
+
if (!profile.tagWeights) {
|
|
1424
|
+
profile.tagWeights = {};
|
|
1425
|
+
}
|
|
1426
|
+
const seed = profile.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1427
|
+
for (const tag of profile.skillTags) {
|
|
1428
|
+
if (!profile.tagWeights[tag]) {
|
|
1429
|
+
profile.tagWeights[tag] = { count: 1, firstSeen: seed, lastSeen: seed, sessions: 1 };
|
|
456
1430
|
}
|
|
457
1431
|
}
|
|
458
|
-
return adj;
|
|
459
1432
|
}
|
|
460
|
-
function
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
1433
|
+
async function readProfile() {
|
|
1434
|
+
if (!existsSync3(PROFILE_FILE)) return blankProfile();
|
|
1435
|
+
try {
|
|
1436
|
+
const key = await loadKey();
|
|
1437
|
+
const raw = readFileSync4(PROFILE_FILE, "utf8");
|
|
1438
|
+
const blob = JSON.parse(raw);
|
|
1439
|
+
const plaintext = decrypt(blob, key);
|
|
1440
|
+
const parsed = JSON.parse(plaintext);
|
|
1441
|
+
migrateTagWeights(parsed);
|
|
1442
|
+
return parsed;
|
|
1443
|
+
} catch {
|
|
1444
|
+
return blankProfile();
|
|
464
1445
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
1446
|
+
}
|
|
1447
|
+
async function writeProfile(profile) {
|
|
1448
|
+
mkdirSync3(TERMINALHIRE_DIR3, { recursive: true });
|
|
1449
|
+
const key = await loadKey();
|
|
1450
|
+
profile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1451
|
+
profile.skillTags = deriveSkillTags(profile.tagWeights);
|
|
1452
|
+
const blob = encrypt(JSON.stringify(profile), key);
|
|
1453
|
+
writeFileSync3(PROFILE_FILE, JSON.stringify(blob, null, 2), { encoding: "utf8" });
|
|
1454
|
+
}
|
|
1455
|
+
function accumulateSession(profile, tags, isEmployerContext, inferredSeniority, seniorityIsAuthoritative = false) {
|
|
1456
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1457
|
+
let filtered = normalize(tags);
|
|
1458
|
+
if (isEmployerContext) {
|
|
1459
|
+
filtered = filtered.filter((t) => LANGUAGE_TAGS.has(t));
|
|
1460
|
+
profile.hasEmployerSessions = true;
|
|
1461
|
+
}
|
|
1462
|
+
for (const tag of filtered) {
|
|
1463
|
+
const existing = profile.tagWeights[tag];
|
|
1464
|
+
if (existing) {
|
|
1465
|
+
existing.count += 1;
|
|
1466
|
+
existing.sessions += 1;
|
|
1467
|
+
existing.lastSeen = now;
|
|
1468
|
+
} else {
|
|
1469
|
+
profile.tagWeights[tag] = { count: 1, firstSeen: now, lastSeen: now, sessions: 1 };
|
|
474
1470
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
if (settled.has(t)) continue;
|
|
480
|
-
const cand = round3(uw * we);
|
|
481
|
-
if (cand >= DECAY_FLOOR && cand > (best.get(t)?.w ?? 0)) {
|
|
482
|
-
best.set(t, { w: cand, via });
|
|
483
|
-
}
|
|
1471
|
+
}
|
|
1472
|
+
if (inferredSeniority && !isEmployerContext) {
|
|
1473
|
+
if (seniorityIsAuthoritative || !profile.github) {
|
|
1474
|
+
profile.seniority = inferredSeniority;
|
|
484
1475
|
}
|
|
485
1476
|
}
|
|
486
|
-
best.delete(source);
|
|
487
|
-
return best;
|
|
488
1477
|
}
|
|
489
|
-
function
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
for (const n of nodes) {
|
|
494
|
-
for (const s of n.synonyms ?? []) synonyms.set(s.toLowerCase(), n.id);
|
|
495
|
-
}
|
|
496
|
-
const adj = buildAdjacency(nodes);
|
|
497
|
-
const closure = /* @__PURE__ */ new Map();
|
|
498
|
-
for (const n of nodes) closure.set(n.id, closureFrom(n.id, adj));
|
|
499
|
-
return { ids, synonyms, closure };
|
|
1478
|
+
async function accumulateTags(rawTokens, isEmployerContext, inferredSeniority) {
|
|
1479
|
+
const profile = await readProfile();
|
|
1480
|
+
accumulateSession(profile, rawTokens, isEmployerContext, inferredSeniority);
|
|
1481
|
+
await writeProfile(profile);
|
|
500
1482
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
"microservices",
|
|
511
|
-
"caching",
|
|
512
|
-
"search",
|
|
513
|
-
"observability",
|
|
514
|
-
"monitoring",
|
|
515
|
-
"testing",
|
|
516
|
-
"accessibility",
|
|
517
|
-
"seo",
|
|
518
|
-
"performance",
|
|
519
|
-
"realtime",
|
|
520
|
-
"authentication",
|
|
521
|
-
"api-design"
|
|
522
|
-
]);
|
|
523
|
-
var SYNONYM_ONLY = /* @__PURE__ */ new Set(["performance", "security", "seo"]);
|
|
524
|
-
for (const id of SYNONYM_ONLY) {
|
|
525
|
-
if (!SOFT_DOMAIN.has(id)) throw new Error(`extract: SYNONYM_ONLY "${id}" not in SOFT_DOMAIN`);
|
|
1483
|
+
function accumulateGitHubTags(profile, tags, inferredSeniority) {
|
|
1484
|
+
accumulateSession(
|
|
1485
|
+
profile,
|
|
1486
|
+
tags,
|
|
1487
|
+
/* isEmployerContext */
|
|
1488
|
+
false,
|
|
1489
|
+
inferredSeniority,
|
|
1490
|
+
true
|
|
1491
|
+
);
|
|
526
1492
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
var VOCABULARY = [...GRAPH.ids];
|
|
531
|
-
var SYNONYMS = Object.fromEntries(GRAPH.synonyms);
|
|
532
|
-
|
|
533
|
-
// ../../packages/core/src/feeds/bounty-gate.ts
|
|
534
|
-
var BOUNTY_REPO_DENYLIST = [
|
|
535
|
-
"SecureBananaLabs/bug-bounty",
|
|
536
|
-
// Meta-farm: a bounty PLATFORM whose own issues are an assignment-gated
|
|
537
|
-
// contributor queue ("please assign me, my chief") — an unsolicited PR won't
|
|
538
|
-
// merge, so it's not a real claimable bounty. Not structurally derivable from
|
|
539
|
-
// any fetched field, so it's a manual entry (also dropped from the allowlist).
|
|
540
|
-
"boundlessfi/boundless"
|
|
541
|
-
];
|
|
542
|
-
var DENYLIST_LC = new Set(BOUNTY_REPO_DENYLIST.map((r) => r.toLowerCase()));
|
|
543
|
-
var AI_BAN_DENYLIST = [
|
|
544
|
-
// Gentoo Council voted 6-0 (2024-04-14) to ban AI/ML-generated contributions
|
|
545
|
-
// project-wide. https://wiki.gentoo.org/wiki/Project:Council/AI_policy
|
|
546
|
-
"gentoo",
|
|
547
|
-
// NetBSD Commit Guidelines: code generated by an LLM/similar technology is
|
|
548
|
-
// "presumed to be tainted code, and must not be committed without prior
|
|
549
|
-
// written approval by core". https://www.netbsd.org/developers/commit-guidelines.html
|
|
550
|
-
"NetBSD"
|
|
551
|
-
// NOT listed (checked, deliberately excluded): QEMU's blanket AI-contribution
|
|
552
|
-
// ban is IN FLUX as of 2026-05 — a patch replacing it with a disclosure-based
|
|
553
|
-
// policy ("AI-used-for:" tag) was posted to qemu-devel and appears to have
|
|
554
|
-
// developer buy-in, so it is no longer a clean/current ban to key a hard
|
|
555
|
-
// supply-side drop on. https://www.theregister.com/ai-and-ml/2026/05/29/qemu-mulls-relaxing-ai-contribution-ban/
|
|
556
|
-
];
|
|
557
|
-
var AI_BAN_LC = new Set(AI_BAN_DENYLIST.map((g) => g.toLowerCase()));
|
|
558
|
-
|
|
559
|
-
// ../../packages/core/src/github.ts
|
|
560
|
-
var RESUME_DECAY_HALF_LIFE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
561
|
-
|
|
562
|
-
// ../../packages/core/src/feeds/index.ts
|
|
563
|
-
var GREENHOUSE_SLUGS_BY_TIER = {
|
|
564
|
-
bigco: [
|
|
565
|
-
"stripe",
|
|
566
|
-
"anthropic",
|
|
567
|
-
"figma",
|
|
568
|
-
"discord",
|
|
569
|
-
"brex",
|
|
570
|
-
"mercury",
|
|
571
|
-
"plaid",
|
|
572
|
-
"gusto",
|
|
573
|
-
"scale",
|
|
574
|
-
"databricks",
|
|
575
|
-
"coinbase",
|
|
576
|
-
"robinhood",
|
|
577
|
-
"doordash",
|
|
578
|
-
"airbnb",
|
|
579
|
-
"dropbox",
|
|
580
|
-
"datadog",
|
|
581
|
-
"cloudflare",
|
|
582
|
-
"reddit",
|
|
583
|
-
"lyft",
|
|
584
|
-
"instacart"
|
|
585
|
-
],
|
|
586
|
-
scaleup: [
|
|
587
|
-
"samsara",
|
|
588
|
-
"verkada",
|
|
589
|
-
"affirm",
|
|
590
|
-
"gitlab",
|
|
591
|
-
"asana",
|
|
592
|
-
"flexport",
|
|
593
|
-
"faire",
|
|
594
|
-
"twitch",
|
|
595
|
-
"airtable",
|
|
596
|
-
"retool"
|
|
597
|
-
],
|
|
598
|
-
startup: [
|
|
599
|
-
"watershed"
|
|
600
|
-
]
|
|
601
|
-
};
|
|
602
|
-
var ASHBY_SLUGS_BY_TIER = {
|
|
603
|
-
bigco: [
|
|
604
|
-
"openai"
|
|
605
|
-
],
|
|
606
|
-
scaleup: [
|
|
607
|
-
"harvey",
|
|
608
|
-
"elevenlabs",
|
|
609
|
-
"notion",
|
|
610
|
-
"sierra",
|
|
611
|
-
"cohere",
|
|
612
|
-
"ramp",
|
|
613
|
-
"vanta",
|
|
614
|
-
"decagon",
|
|
615
|
-
"cursor",
|
|
616
|
-
"replit",
|
|
617
|
-
"perplexity",
|
|
618
|
-
"baseten",
|
|
619
|
-
"drata",
|
|
620
|
-
"writer",
|
|
621
|
-
"temporal",
|
|
622
|
-
"supabase"
|
|
623
|
-
],
|
|
624
|
-
startup: [
|
|
625
|
-
"suno",
|
|
626
|
-
"attio",
|
|
627
|
-
"modal",
|
|
628
|
-
"workos",
|
|
629
|
-
"linear",
|
|
630
|
-
"render",
|
|
631
|
-
"warp",
|
|
632
|
-
"plain",
|
|
633
|
-
"posthog",
|
|
634
|
-
"pylon",
|
|
635
|
-
"resend",
|
|
636
|
-
"langfuse",
|
|
637
|
-
"railway",
|
|
638
|
-
"mintlify",
|
|
639
|
-
"neon",
|
|
640
|
-
"browserbase",
|
|
641
|
-
"knock",
|
|
642
|
-
"speakeasy",
|
|
643
|
-
"stytch",
|
|
644
|
-
"runway",
|
|
645
|
-
"doppler",
|
|
646
|
-
"inngest",
|
|
647
|
-
"hightouch",
|
|
648
|
-
"zed"
|
|
649
|
-
]
|
|
650
|
-
};
|
|
651
|
-
var LEVER_SLUGS_BY_TIER = {
|
|
652
|
-
bigco: [
|
|
653
|
-
"palantir",
|
|
654
|
-
"spotify"
|
|
655
|
-
],
|
|
656
|
-
scaleup: [
|
|
657
|
-
"mistral",
|
|
658
|
-
"ro",
|
|
659
|
-
"secureframe"
|
|
660
|
-
],
|
|
661
|
-
startup: [
|
|
662
|
-
"anyscale"
|
|
663
|
-
]
|
|
664
|
-
};
|
|
665
|
-
function flattenTiers(t) {
|
|
666
|
-
return [.../* @__PURE__ */ new Set([...t.bigco, ...t.scaleup, ...t.startup])];
|
|
1493
|
+
async function listSavedJobs() {
|
|
1494
|
+
const profile = await readProfile();
|
|
1495
|
+
return profile.savedJobs ?? [];
|
|
667
1496
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
1497
|
+
async function addSavedJob(job) {
|
|
1498
|
+
const profile = await readProfile();
|
|
1499
|
+
const existing = profile.savedJobs ?? [];
|
|
1500
|
+
const filtered = existing.filter((j) => j.id !== job.id);
|
|
1501
|
+
profile.savedJobs = [...filtered, { ...job, savedAt: (/* @__PURE__ */ new Date()).toISOString() }];
|
|
1502
|
+
await writeProfile(profile);
|
|
1503
|
+
}
|
|
1504
|
+
async function removeSavedJob(id) {
|
|
1505
|
+
const profile = await readProfile();
|
|
1506
|
+
const existing = profile.savedJobs ?? [];
|
|
1507
|
+
const filtered = existing.filter((j) => j.id !== id);
|
|
1508
|
+
if (filtered.length === existing.length) return false;
|
|
1509
|
+
profile.savedJobs = filtered;
|
|
1510
|
+
await writeProfile(profile);
|
|
1511
|
+
return true;
|
|
1512
|
+
}
|
|
1513
|
+
async function deleteProfile() {
|
|
1514
|
+
const { rmSync: rmSync2 } = await import("fs");
|
|
1515
|
+
try {
|
|
1516
|
+
rmSync2(PROFILE_FILE);
|
|
1517
|
+
} catch {
|
|
1518
|
+
}
|
|
1519
|
+
try {
|
|
1520
|
+
rmSync2(KEY_FILE);
|
|
1521
|
+
} catch {
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
function profileToFingerprint(profile) {
|
|
1525
|
+
const rankedTags = Object.entries(profile.tagWeights).map(([tag, w]) => ({ tag, score: tagScore(w) })).filter(({ score }) => score >= MIN_FINGERPRINT_SCORE).sort((a, b) => b.score - a.score).map(({ tag }) => tag);
|
|
1526
|
+
const skillTags = rankedTags.length > 0 ? rankedTags : profile.skillTags;
|
|
1527
|
+
return {
|
|
1528
|
+
skillTags,
|
|
1529
|
+
seniorityBand: profile.seniority,
|
|
1530
|
+
prefs: {
|
|
1531
|
+
roleTypes: profile.roleTypes,
|
|
1532
|
+
remoteOnly: profile.remoteOnly,
|
|
1533
|
+
compFloorUsd: profile.compFloorUsd
|
|
1534
|
+
}
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
var TERMINALHIRE_DIR3, PROFILE_FILE, KEY_FILE, ALGO, KEY_BYTES, IV_BYTES, DECAY_HALF_LIFE_MS, LANGUAGE_TAGS, MIN_FINGERPRINT_SCORE;
|
|
1538
|
+
var init_profile = __esm({
|
|
1539
|
+
"src/profile.ts"() {
|
|
1540
|
+
"use strict";
|
|
1541
|
+
init_src();
|
|
1542
|
+
TERMINALHIRE_DIR3 = join4(homedir3(), ".terminalhire");
|
|
1543
|
+
PROFILE_FILE = join4(TERMINALHIRE_DIR3, "profile.enc");
|
|
1544
|
+
KEY_FILE = join4(TERMINALHIRE_DIR3, "key");
|
|
1545
|
+
ALGO = "aes-256-gcm";
|
|
1546
|
+
KEY_BYTES = 32;
|
|
1547
|
+
IV_BYTES = 12;
|
|
1548
|
+
DECAY_HALF_LIFE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
1549
|
+
LANGUAGE_TAGS = /* @__PURE__ */ new Set([
|
|
1550
|
+
"typescript",
|
|
1551
|
+
"javascript",
|
|
1552
|
+
"python",
|
|
1553
|
+
"go",
|
|
1554
|
+
"rust",
|
|
1555
|
+
"java",
|
|
1556
|
+
"ruby",
|
|
1557
|
+
"elixir",
|
|
1558
|
+
"scala",
|
|
1559
|
+
"kotlin",
|
|
1560
|
+
"swift",
|
|
1561
|
+
"cpp",
|
|
1562
|
+
"csharp",
|
|
1563
|
+
"php",
|
|
1564
|
+
"haskell",
|
|
1565
|
+
"clojure",
|
|
1566
|
+
"r"
|
|
1567
|
+
]);
|
|
1568
|
+
MIN_FINGERPRINT_SCORE = 0.05;
|
|
1569
|
+
}
|
|
1570
|
+
});
|
|
696
1571
|
|
|
697
|
-
//
|
|
698
|
-
|
|
699
|
-
|
|
1572
|
+
// bin/jpi-claim.js
|
|
1573
|
+
init_src();
|
|
1574
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync4, rmSync } from "fs";
|
|
1575
|
+
import { join as join5 } from "path";
|
|
1576
|
+
import { homedir as homedir4, hostname as osHostname } from "os";
|
|
1577
|
+
import { execFile } from "child_process";
|
|
1578
|
+
import { promisify } from "util";
|
|
1579
|
+
import { createInterface } from "readline";
|
|
700
1580
|
|
|
701
|
-
//
|
|
702
|
-
import {
|
|
703
|
-
function
|
|
704
|
-
|
|
1581
|
+
// src/open-url.js
|
|
1582
|
+
import { spawn } from "child_process";
|
|
1583
|
+
function openInBrowser(url) {
|
|
1584
|
+
let cmd;
|
|
1585
|
+
let args;
|
|
1586
|
+
if (process.platform === "darwin") {
|
|
1587
|
+
cmd = "open";
|
|
1588
|
+
args = [url];
|
|
1589
|
+
} else if (process.platform === "win32") {
|
|
1590
|
+
cmd = "cmd";
|
|
1591
|
+
args = ["/c", "start", "", url];
|
|
1592
|
+
} else {
|
|
1593
|
+
cmd = "xdg-open";
|
|
1594
|
+
args = [url];
|
|
1595
|
+
}
|
|
1596
|
+
try {
|
|
1597
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
1598
|
+
child.on("error", () => {
|
|
1599
|
+
});
|
|
1600
|
+
child.unref();
|
|
1601
|
+
} catch {
|
|
1602
|
+
}
|
|
705
1603
|
}
|
|
706
1604
|
|
|
707
1605
|
// bin/jpi-claim.js
|
|
708
|
-
var
|
|
709
|
-
var INDEX_CACHE_FILE =
|
|
1606
|
+
var TERMINALHIRE_DIR4 = process.env.TERMINALHIRE_DIR || join5(homedir4(), ".terminalhire");
|
|
1607
|
+
var INDEX_CACHE_FILE = join5(TERMINALHIRE_DIR4, "index-cache.json");
|
|
1608
|
+
var CLAIM_PUSH_MARKER = join5(TERMINALHIRE_DIR4, "claim-push.json");
|
|
1609
|
+
var API_URL = process.env["TERMINALHIRE_API_URL"] ?? process.env["JPI_API_URL"] ?? "https://terminalhire.com";
|
|
1610
|
+
var CLAIM_SYNC_BASE = "https://terminalhire.com";
|
|
1611
|
+
var CLAIM_CONSENT_VERSION = 1;
|
|
1612
|
+
var CLAIM_POLL_INTERVAL_MS = 2e3;
|
|
1613
|
+
var CLAIM_POLL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
710
1614
|
var GH_API2 = "https://api.github.com";
|
|
711
1615
|
var GH_HEADERS2 = { "User-Agent": "terminalhire-claim", Accept: "application/vnd.github+json" };
|
|
712
1616
|
var AI_DISCLOSURE_NOTE = "---\nThis contribution was developed with AI assistance via [terminalhire](https://terminalhire.com). The author has reviewed the change and takes responsibility for its content.";
|
|
@@ -773,8 +1677,8 @@ function parseRepoFromRemote(url) {
|
|
|
773
1677
|
return m ? `${m[1]}/${m[2]}` : null;
|
|
774
1678
|
}
|
|
775
1679
|
function readClaimablePool() {
|
|
776
|
-
if (!
|
|
777
|
-
const entry = JSON.parse(
|
|
1680
|
+
if (!existsSync4(INDEX_CACHE_FILE)) return [];
|
|
1681
|
+
const entry = JSON.parse(readFileSync5(INDEX_CACHE_FILE, "utf8"));
|
|
778
1682
|
const bounties = (entry?.index?.jobs ?? []).filter((j) => j.source === "bounty");
|
|
779
1683
|
const contributions = (entry?.index?.contribute ?? []).filter((j) => j.source === "contribute");
|
|
780
1684
|
return [...bounties, ...contributions];
|
|
@@ -791,11 +1695,33 @@ function looksLikeShortRef(arg) {
|
|
|
791
1695
|
}
|
|
792
1696
|
function findClaimableByShortRef(ref) {
|
|
793
1697
|
try {
|
|
794
|
-
return readClaimablePool()
|
|
1698
|
+
return findByShortRefInPool(readClaimablePool(), ref);
|
|
795
1699
|
} catch {
|
|
796
1700
|
return null;
|
|
797
1701
|
}
|
|
798
1702
|
}
|
|
1703
|
+
function findByShortRefInPool(pool, ref) {
|
|
1704
|
+
return pool.find((j) => opportunityShortToken(j.id) === ref) ?? null;
|
|
1705
|
+
}
|
|
1706
|
+
async function fetchFreshClaimablePool() {
|
|
1707
|
+
let index;
|
|
1708
|
+
for (let attempt = 1; ; attempt++) {
|
|
1709
|
+
try {
|
|
1710
|
+
const res = await fetch(`${API_URL}/api/index`, {
|
|
1711
|
+
signal: AbortSignal.timeout(15e3),
|
|
1712
|
+
headers: { Accept: "application/json" }
|
|
1713
|
+
});
|
|
1714
|
+
if (!res.ok) return null;
|
|
1715
|
+
index = await res.json();
|
|
1716
|
+
break;
|
|
1717
|
+
} catch {
|
|
1718
|
+
if (attempt >= 2) return null;
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
const bounties = (index?.jobs ?? []).filter((j) => j.source === "bounty");
|
|
1722
|
+
const contributions = (index?.contribute ?? []).filter((j) => j.source === "contribute");
|
|
1723
|
+
return [...bounties, ...contributions];
|
|
1724
|
+
}
|
|
799
1725
|
function extractClaimableFields(job) {
|
|
800
1726
|
if (job.source === "contribute") {
|
|
801
1727
|
const c = job.contribution ?? {};
|
|
@@ -805,7 +1731,11 @@ function extractClaimableFields(job) {
|
|
|
805
1731
|
repoFullName: c.repoFullName ?? job.company ?? "",
|
|
806
1732
|
issueUrl: c.issueUrl ?? job.url ?? "",
|
|
807
1733
|
amountUSD: null,
|
|
808
|
-
source: "contribute"
|
|
1734
|
+
source: "contribute",
|
|
1735
|
+
// Fix 2: the aggregator proved 0 open PRs at discovery (it only admits an
|
|
1736
|
+
// uncontested issue). Carry it through so resolveBounty can prefer it over
|
|
1737
|
+
// a live re-count that degrades to null under a rate limit / >100-PR page.
|
|
1738
|
+
openPRsAtDiscovery: c.openPRsAtDiscovery
|
|
809
1739
|
};
|
|
810
1740
|
}
|
|
811
1741
|
const b = job.bounty ?? {};
|
|
@@ -815,7 +1745,9 @@ function extractClaimableFields(job) {
|
|
|
815
1745
|
repoFullName: b.repoFullName ?? job.company ?? "",
|
|
816
1746
|
issueUrl: b.claimUrl ?? job.url ?? "",
|
|
817
1747
|
amountUSD: b.amountUSD ?? null,
|
|
818
|
-
source: "bounty"
|
|
1748
|
+
source: "bounty",
|
|
1749
|
+
// Bounties carry no discovery-time PR proof — always live-count (unchanged).
|
|
1750
|
+
openPRsAtDiscovery: void 0
|
|
819
1751
|
};
|
|
820
1752
|
}
|
|
821
1753
|
function parseGitHubUrl(url) {
|
|
@@ -849,7 +1781,18 @@ async function fetchIssue(repoFullName, issueNumber) {
|
|
|
849
1781
|
if (!res.ok) return null;
|
|
850
1782
|
const issue = await res.json();
|
|
851
1783
|
const state = issue.state === "open" ? "open" : issue.state === "closed" ? "closed" : null;
|
|
852
|
-
|
|
1784
|
+
const logins = /* @__PURE__ */ new Set();
|
|
1785
|
+
if (issue.assignee && typeof issue.assignee.login === "string") logins.add(issue.assignee.login);
|
|
1786
|
+
if (Array.isArray(issue.assignees)) {
|
|
1787
|
+
for (const a of issue.assignees) {
|
|
1788
|
+
if (a && typeof a.login === "string") logins.add(a.login);
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
return {
|
|
1792
|
+
state,
|
|
1793
|
+
title: typeof issue.title === "string" ? issue.title : null,
|
|
1794
|
+
assignees: [...logins]
|
|
1795
|
+
};
|
|
853
1796
|
} catch {
|
|
854
1797
|
return null;
|
|
855
1798
|
}
|
|
@@ -881,10 +1824,16 @@ function printMetric(rate) {
|
|
|
881
1824
|
\u{1F4CA} Accepted-PR rate: ${rate.merged}/${rate.total} claims merged (${pct}%)`);
|
|
882
1825
|
}
|
|
883
1826
|
async function resolveBounty(arg) {
|
|
884
|
-
let bountyId, title, repoFullName, issueUrl, amountUSD, source;
|
|
885
|
-
|
|
1827
|
+
let bountyId, title, repoFullName, issueUrl, amountUSD, source, openPRsAtDiscovery, indexNativeId;
|
|
1828
|
+
let job = findClaimableInCache(arg) ?? (looksLikeShortRef(arg) ? findClaimableByShortRef(arg) : null);
|
|
1829
|
+
let freshPool;
|
|
1830
|
+
if (!job && looksLikeShortRef(arg)) {
|
|
1831
|
+
freshPool = await fetchFreshClaimablePool();
|
|
1832
|
+
if (freshPool) job = findByShortRefInPool(freshPool, arg);
|
|
1833
|
+
}
|
|
886
1834
|
if (job) {
|
|
887
|
-
({ bountyId, title, repoFullName, issueUrl, amountUSD, source } = extractClaimableFields(job));
|
|
1835
|
+
({ bountyId, title, repoFullName, issueUrl, amountUSD, source, openPRsAtDiscovery } = extractClaimableFields(job));
|
|
1836
|
+
indexNativeId = bountyId;
|
|
888
1837
|
} else {
|
|
889
1838
|
const parsed = parseGitHubUrl(arg);
|
|
890
1839
|
if (!parsed) return null;
|
|
@@ -894,30 +1843,84 @@ async function resolveBounty(arg) {
|
|
|
894
1843
|
issueUrl = arg;
|
|
895
1844
|
amountUSD = null;
|
|
896
1845
|
source = "bounty";
|
|
1846
|
+
indexNativeId = `bounty:${parsed.repoFullName}#${parsed.number}`;
|
|
1847
|
+
if (freshPool === void 0) freshPool = await fetchFreshClaimablePool();
|
|
1848
|
+
const pooled = (freshPool ?? []).find((j) => {
|
|
1849
|
+
const p = parseGitHubUrl(extractClaimableFields(j).issueUrl);
|
|
1850
|
+
return p && p.repoFullName.toLowerCase() === parsed.repoFullName.toLowerCase() && p.number === parsed.number;
|
|
1851
|
+
});
|
|
1852
|
+
if (pooled) {
|
|
1853
|
+
const f = extractClaimableFields(pooled);
|
|
1854
|
+
indexNativeId = f.bountyId;
|
|
1855
|
+
source = f.source;
|
|
1856
|
+
}
|
|
897
1857
|
}
|
|
898
1858
|
const ghIssue = parseGitHubUrl(issueUrl);
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
1859
|
+
const issue = ghIssue ? await fetchIssue(repoFullName, ghIssue.number) : null;
|
|
1860
|
+
let openPRs;
|
|
1861
|
+
let openPRsFromDiscovery = false;
|
|
1862
|
+
if (openPRsAtDiscovery != null) {
|
|
1863
|
+
openPRs = openPRsAtDiscovery;
|
|
1864
|
+
openPRsFromDiscovery = true;
|
|
1865
|
+
} else {
|
|
1866
|
+
openPRs = ghIssue ? await countOpenPRsReferencingIssue(repoFullName, ghIssue.number) : null;
|
|
1867
|
+
}
|
|
1868
|
+
let openPRsLive;
|
|
1869
|
+
if (openPRsFromDiscovery) {
|
|
1870
|
+
openPRsLive = ghIssue ? await countOpenPRsReferencingIssue(repoFullName, ghIssue.number) : null;
|
|
1871
|
+
} else {
|
|
1872
|
+
openPRsLive = openPRs;
|
|
1873
|
+
}
|
|
903
1874
|
const issueState = issue ? issue.state : null;
|
|
1875
|
+
const assignees = issue ? issue.assignees : null;
|
|
904
1876
|
if (!job && issue && issue.title) title = issue.title;
|
|
905
1877
|
return {
|
|
906
1878
|
bountyId,
|
|
1879
|
+
indexNativeId,
|
|
907
1880
|
title,
|
|
908
1881
|
repoFullName,
|
|
909
1882
|
issueUrl,
|
|
910
1883
|
amountUSD,
|
|
911
1884
|
source,
|
|
912
1885
|
issueState,
|
|
1886
|
+
assignees,
|
|
913
1887
|
openPRs,
|
|
1888
|
+
openPRsFromDiscovery,
|
|
1889
|
+
// Live open-PR count for the contention decision only (Fix 2). Distinct from
|
|
1890
|
+
// `openPRs`, which may be the discovery-time snapshot used for display.
|
|
1891
|
+
openPRsLive,
|
|
914
1892
|
issueNumber: ghIssue ? ghIssue.number : null
|
|
915
1893
|
};
|
|
916
1894
|
}
|
|
1895
|
+
function fmtOpenPRsLine(b) {
|
|
1896
|
+
const atDiscovery = b.openPRsFromDiscovery ? " (at discovery)" : "";
|
|
1897
|
+
if (b.openPRs == null) {
|
|
1898
|
+
return " open PRs: unknown (GitHub read unavailable \u2014 check the issue manually before working)";
|
|
1899
|
+
} else if (b.openPRs > 0) {
|
|
1900
|
+
return ` \u26A0 open PRs referencing this issue${atDiscovery}: ${b.openPRs} \u2014 someone may already be on it. Check before working.`;
|
|
1901
|
+
}
|
|
1902
|
+
return ` open PRs referencing this issue${atDiscovery}: 0`;
|
|
1903
|
+
}
|
|
1904
|
+
function isContested(b) {
|
|
1905
|
+
const assignedToSomeoneElse = Array.isArray(b.assignees) && b.assignees.length > 0;
|
|
1906
|
+
const hasOpenPR = b.openPRsLive != null && b.openPRsLive > 0;
|
|
1907
|
+
return assignedToSomeoneElse || hasOpenPR;
|
|
1908
|
+
}
|
|
1909
|
+
function fmtContestedWarning(b) {
|
|
1910
|
+
if (!isContested(b)) return null;
|
|
1911
|
+
const parts = [];
|
|
1912
|
+
if (Array.isArray(b.assignees) && b.assignees.length > 0) {
|
|
1913
|
+
parts.push(`assigned to ${b.assignees.map((l) => `@${l}`).join(", ")}`);
|
|
1914
|
+
}
|
|
1915
|
+
if (b.openPRsLive != null && b.openPRsLive > 0) {
|
|
1916
|
+
parts.push(`${b.openPRsLive} open PR${b.openPRsLive === 1 ? "" : "s"} referencing it`);
|
|
1917
|
+
}
|
|
1918
|
+
return ` \u26A0 This issue looks taken: ${parts.join(" / ")}. A merged PR here is unlikely.`;
|
|
1919
|
+
}
|
|
917
1920
|
async function cmdRecord(arg, flags = {}) {
|
|
918
1921
|
const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
919
1922
|
if (!arg) {
|
|
920
|
-
console.error("Usage: terminalhire claim record <bountyId|issueUrl> [--ack-policy]");
|
|
1923
|
+
console.error("Usage: terminalhire claim record <bountyId|issueUrl> [--ack-policy] [--ack-contested]");
|
|
921
1924
|
console.error(" Run `terminalhire bounties` first to populate the local index cache,");
|
|
922
1925
|
console.error(" then pass the id shown in its output \u2014 or pass a GitHub issue URL directly.");
|
|
923
1926
|
process.exit(1);
|
|
@@ -936,6 +1939,21 @@ async function cmdRecord(arg, flags = {}) {
|
|
|
936
1939
|
);
|
|
937
1940
|
process.exit(1);
|
|
938
1941
|
}
|
|
1942
|
+
const contestedWarning = fmtContestedWarning(b);
|
|
1943
|
+
if (contestedWarning) {
|
|
1944
|
+
console.log(`
|
|
1945
|
+
${contestedWarning}`);
|
|
1946
|
+
let acked = Boolean(flags["ack-contested"]);
|
|
1947
|
+
if (!acked && process.stdin.isTTY) {
|
|
1948
|
+
acked = await confirm("\n Claim it anyway? (y/N) ");
|
|
1949
|
+
}
|
|
1950
|
+
if (!acked) {
|
|
1951
|
+
console.error(
|
|
1952
|
+
"\nterminalhire claim: refusing to record \u2014 this issue looks taken.\n Re-run with --ack-contested to claim it anyway (or confirm interactively)."
|
|
1953
|
+
);
|
|
1954
|
+
process.exit(1);
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
939
1957
|
const { checkRepoPolicy: checkRepoPolicy2 } = await Promise.resolve().then(() => (init_repo_policy(), repo_policy_exports));
|
|
940
1958
|
const policy = await checkRepoPolicy2(b.repoFullName);
|
|
941
1959
|
printPolicySection(policy);
|
|
@@ -977,19 +1995,18 @@ terminalhire claim: refusing to record \u2014 read ${b.repoFullName}'s contribut
|
|
|
977
1995
|
console.error(`terminalhire claim: ${err.message ?? err}`);
|
|
978
1996
|
process.exit(1);
|
|
979
1997
|
}
|
|
1998
|
+
try {
|
|
1999
|
+
const { markStatus: markStatus2 } = await Promise.resolve().then(() => (init_job_status_store(), job_status_store_exports));
|
|
2000
|
+
markStatus2(b.indexNativeId, "claimed");
|
|
2001
|
+
} catch {
|
|
2002
|
+
}
|
|
980
2003
|
console.log(`
|
|
981
2004
|
\u2713 Claimed: ${claim.title}`);
|
|
982
2005
|
console.log(` id: ${claim.id}`);
|
|
983
2006
|
console.log(` repo: ${claim.repoFullName}`);
|
|
984
2007
|
console.log(` amount: ${fmtAmount(claim.amountUSD)}`);
|
|
985
2008
|
console.log(` issue: ${claim.issueUrl}`);
|
|
986
|
-
|
|
987
|
-
console.log(" open PRs: unknown (GitHub read unavailable \u2014 check the issue manually before working)");
|
|
988
|
-
} else if (b.openPRs > 0) {
|
|
989
|
-
console.log(` \u26A0 open PRs referencing this issue: ${b.openPRs} \u2014 someone may already be on it. Check before working.`);
|
|
990
|
-
} else {
|
|
991
|
-
console.log(" open PRs referencing this issue: 0");
|
|
992
|
-
}
|
|
2009
|
+
console.log(fmtOpenPRsLine(b));
|
|
993
2010
|
console.log("\n Executor constraints (enforce when spawning the background agent):");
|
|
994
2011
|
console.log(" \u2022 work in an ISOLATED git worktree; scrub the subprocess env (no token/profile inheritance)");
|
|
995
2012
|
console.log(" \u2022 MUST NOT `git push` or `gh pr` \u2014 pushing happens only via `terminalhire submit`");
|
|
@@ -1023,6 +2040,8 @@ async function cmdPreview(arg, { json } = {}) {
|
|
|
1023
2040
|
issueUrl: b.issueUrl,
|
|
1024
2041
|
issueState: b.issueState,
|
|
1025
2042
|
openPRs: b.openPRs,
|
|
2043
|
+
assignees: b.assignees,
|
|
2044
|
+
contested: isContested(b),
|
|
1026
2045
|
policy: { status: policy.status, hits: policy.hits }
|
|
1027
2046
|
}) + "\n"
|
|
1028
2047
|
);
|
|
@@ -1037,13 +2056,9 @@ async function cmdPreview(arg, { json } = {}) {
|
|
|
1037
2056
|
if (b.issueState === "closed") {
|
|
1038
2057
|
console.log(" \u2717 CLOSED \u2014 not claimable (the pool drops closed issues; likely a stale cache entry)");
|
|
1039
2058
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
console.log(` \u26A0 open PRs referencing this issue: ${b.openPRs} \u2014 someone may already be on it. Check before working.`);
|
|
1044
|
-
} else {
|
|
1045
|
-
console.log(" open PRs referencing this issue: 0");
|
|
1046
|
-
}
|
|
2059
|
+
console.log(fmtOpenPRsLine(b));
|
|
2060
|
+
const previewContested = fmtContestedWarning(b);
|
|
2061
|
+
if (previewContested) console.log(previewContested);
|
|
1047
2062
|
printPolicySection(policy);
|
|
1048
2063
|
console.log("\n Preview only \u2014 NOT claimed. Run `terminalhire claim record " + arg + "` to claim it.");
|
|
1049
2064
|
}
|
|
@@ -1312,11 +2327,223 @@ async function cmdSubmit(id, worktreeOverride) {
|
|
|
1312
2327
|
\u2713 Submitted ${id} \u2192 ${prUrl}`);
|
|
1313
2328
|
console.log(` Run 'terminalhire claim status ${id}' after the maintainer acts to fold the merge into your accepted-PR rate.`);
|
|
1314
2329
|
}
|
|
2330
|
+
var CLAIM_PUSH_FIELDS = ["kind", "repoFullName", "state", "prUrl", "merged", "claimedAt", "updatedAt"];
|
|
2331
|
+
function askYes(question) {
|
|
2332
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
2333
|
+
return new Promise((res) => rl.question(question, (a) => {
|
|
2334
|
+
rl.close();
|
|
2335
|
+
res(String(a).trim().toLowerCase());
|
|
2336
|
+
}));
|
|
2337
|
+
}
|
|
2338
|
+
function claimSleep(ms) {
|
|
2339
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
2340
|
+
}
|
|
2341
|
+
function readClaimPushMarker() {
|
|
2342
|
+
try {
|
|
2343
|
+
return existsSync4(CLAIM_PUSH_MARKER) ? JSON.parse(readFileSync5(CLAIM_PUSH_MARKER, "utf8")) : null;
|
|
2344
|
+
} catch {
|
|
2345
|
+
return null;
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
function writeClaimPushMarker(marker) {
|
|
2349
|
+
mkdirSync4(TERMINALHIRE_DIR4, { recursive: true });
|
|
2350
|
+
writeFileSync4(CLAIM_PUSH_MARKER, JSON.stringify(marker, null, 2) + "\n", "utf8");
|
|
2351
|
+
}
|
|
2352
|
+
function clearClaimPushMarker() {
|
|
2353
|
+
try {
|
|
2354
|
+
rmSync(CLAIM_PUSH_MARKER);
|
|
2355
|
+
} catch {
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
function renderClaimConsent(pushed, login) {
|
|
2359
|
+
console.log("");
|
|
2360
|
+
console.log(" terminalhire \u2014 show your claims on your dashboard (opt-in)");
|
|
2361
|
+
console.log("");
|
|
2362
|
+
console.log(` As @${login}, the following SCORE-FREE fields of your ${pushed.length} claim${pushed.length === 1 ? "" : "s"}`);
|
|
2363
|
+
console.log(" will be shared with staqs (terminalhire.com) after you confirm in the browser:");
|
|
2364
|
+
console.log("");
|
|
2365
|
+
for (const f of CLAIM_PUSH_FIELDS) console.log(` - ${f}`);
|
|
2366
|
+
console.log("");
|
|
2367
|
+
console.log(" What is NEVER sent: your diff-acceptance score, review blockers,");
|
|
2368
|
+
console.log(" branch names, worktree paths, repo policy, amounts, or any private data.");
|
|
2369
|
+
console.log("");
|
|
2370
|
+
console.log(" Revoke any time: terminalhire claim --push --revoke");
|
|
2371
|
+
console.log(" This is NOT required to use terminalhire.");
|
|
2372
|
+
console.log("");
|
|
2373
|
+
}
|
|
2374
|
+
async function cmdPush() {
|
|
2375
|
+
const claimsMod = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
2376
|
+
const { readProfile: readProfile2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
|
|
2377
|
+
const profile = await readProfile2();
|
|
2378
|
+
const login = profile.github && profile.github.login ? profile.github.login : null;
|
|
2379
|
+
if (!login) {
|
|
2380
|
+
console.log("\n No GitHub data in your local profile yet.");
|
|
2381
|
+
console.log(" Run `terminalhire login` first, then re-run `terminalhire claim --push`.\n");
|
|
2382
|
+
process.exit(1);
|
|
2383
|
+
}
|
|
2384
|
+
const all = claimsMod.listClaims();
|
|
2385
|
+
if (all.length === 0) {
|
|
2386
|
+
console.log("\n No claims recorded yet \u2014 nothing to push.");
|
|
2387
|
+
console.log(" Record one first: terminalhire claim record <bountyId|issueUrl>\n");
|
|
2388
|
+
process.exit(0);
|
|
2389
|
+
}
|
|
2390
|
+
const pushed = all.map((c) => claimsMod.toPushedClaim(c));
|
|
2391
|
+
renderClaimConsent(pushed, login);
|
|
2392
|
+
const answer = await askYes(' Share these with your dashboard? Type "yes" to confirm: ');
|
|
2393
|
+
if (answer !== "yes") {
|
|
2394
|
+
console.log("\n Aborted \u2014 nothing was shared.\n");
|
|
2395
|
+
process.exit(0);
|
|
2396
|
+
}
|
|
2397
|
+
const consentedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2398
|
+
console.log("\n Starting browser verification...");
|
|
2399
|
+
let begin;
|
|
2400
|
+
try {
|
|
2401
|
+
const r = await fetch(`${CLAIM_SYNC_BASE}/api/claim-sync/begin`, {
|
|
2402
|
+
method: "POST",
|
|
2403
|
+
headers: { "Content-Type": "application/json" },
|
|
2404
|
+
body: JSON.stringify({ hostname: osHostname() }),
|
|
2405
|
+
signal: AbortSignal.timeout(1e4)
|
|
2406
|
+
});
|
|
2407
|
+
if (!r.ok) {
|
|
2408
|
+
let detail = "";
|
|
2409
|
+
try {
|
|
2410
|
+
detail = (await r.json())?.message || "";
|
|
2411
|
+
} catch {
|
|
2412
|
+
}
|
|
2413
|
+
console.error(`
|
|
2414
|
+
Could not start claim sync: /api/claim-sync/begin returned ${r.status}. ${detail}`);
|
|
2415
|
+
process.exit(1);
|
|
2416
|
+
}
|
|
2417
|
+
begin = await r.json();
|
|
2418
|
+
} catch (err) {
|
|
2419
|
+
console.error(`
|
|
2420
|
+
Could not start claim sync: ${err instanceof Error ? err.message : String(err)}`);
|
|
2421
|
+
process.exit(1);
|
|
2422
|
+
}
|
|
2423
|
+
const { challenge, verifyUrl } = begin || {};
|
|
2424
|
+
if (!challenge || !verifyUrl) {
|
|
2425
|
+
console.error("\n Could not start claim sync: malformed begin response.");
|
|
2426
|
+
process.exit(1);
|
|
2427
|
+
}
|
|
2428
|
+
console.log("\n Open this URL to authorize (sign in with GitHub, then Confirm):");
|
|
2429
|
+
console.log(` ${verifyUrl}`);
|
|
2430
|
+
console.log("\n (Attempting to open it automatically...)");
|
|
2431
|
+
openInBrowser(verifyUrl);
|
|
2432
|
+
console.log(" Waiting for browser verification...");
|
|
2433
|
+
const deadline = Date.now() + CLAIM_POLL_TIMEOUT_MS;
|
|
2434
|
+
let proofToken = null;
|
|
2435
|
+
while (Date.now() < deadline) {
|
|
2436
|
+
await claimSleep(CLAIM_POLL_INTERVAL_MS);
|
|
2437
|
+
let statusRes;
|
|
2438
|
+
try {
|
|
2439
|
+
statusRes = await fetch(
|
|
2440
|
+
`${CLAIM_SYNC_BASE}/api/claim-sync/status?challenge=${encodeURIComponent(challenge)}`,
|
|
2441
|
+
{ signal: AbortSignal.timeout(1e4) }
|
|
2442
|
+
);
|
|
2443
|
+
} catch {
|
|
2444
|
+
continue;
|
|
2445
|
+
}
|
|
2446
|
+
if (!statusRes.ok) continue;
|
|
2447
|
+
let body;
|
|
2448
|
+
try {
|
|
2449
|
+
body = await statusRes.json();
|
|
2450
|
+
} catch {
|
|
2451
|
+
continue;
|
|
2452
|
+
}
|
|
2453
|
+
if (body && body.status === "verified" && body.proofToken) {
|
|
2454
|
+
proofToken = body.proofToken;
|
|
2455
|
+
break;
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
if (!proofToken) {
|
|
2459
|
+
console.error("\n Timed out waiting for browser verification (10 min).");
|
|
2460
|
+
console.error(" Re-run `terminalhire claim --push` to try again.\n");
|
|
2461
|
+
process.exit(1);
|
|
2462
|
+
}
|
|
2463
|
+
const consentToken = { consentedAt, version: CLAIM_CONSENT_VERSION, fields: CLAIM_PUSH_FIELDS };
|
|
2464
|
+
console.log("\n Verified. Sharing your claims...");
|
|
2465
|
+
let res;
|
|
2466
|
+
try {
|
|
2467
|
+
res = await fetch(`${CLAIM_SYNC_BASE}/api/claim-sync`, {
|
|
2468
|
+
method: "POST",
|
|
2469
|
+
headers: { "Content-Type": "application/json" },
|
|
2470
|
+
body: JSON.stringify({ consentToken, claims: pushed, proofToken }),
|
|
2471
|
+
signal: AbortSignal.timeout(1e4)
|
|
2472
|
+
});
|
|
2473
|
+
} catch (err) {
|
|
2474
|
+
console.error(`
|
|
2475
|
+
Claim sync failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2476
|
+
process.exit(1);
|
|
2477
|
+
}
|
|
2478
|
+
if (!res.ok) {
|
|
2479
|
+
let detail = "";
|
|
2480
|
+
try {
|
|
2481
|
+
detail = (await res.json())?.message || "";
|
|
2482
|
+
} catch {
|
|
2483
|
+
}
|
|
2484
|
+
console.error(`
|
|
2485
|
+
Claim sync failed: /api/claim-sync returned ${res.status}. ${detail}`);
|
|
2486
|
+
process.exit(1);
|
|
2487
|
+
}
|
|
2488
|
+
let deleteToken = null;
|
|
2489
|
+
try {
|
|
2490
|
+
deleteToken = (await res.json())?.deleteToken || null;
|
|
2491
|
+
} catch {
|
|
2492
|
+
}
|
|
2493
|
+
writeClaimPushMarker({ consentedAt, login, deleteToken });
|
|
2494
|
+
console.log("\n \u2713 Your claims now show on your dashboard: https://terminalhire.com/dashboard");
|
|
2495
|
+
console.log(" Delete them any time: terminalhire claim --push --revoke\n");
|
|
2496
|
+
}
|
|
2497
|
+
async function cmdRevoke() {
|
|
2498
|
+
const marker = readClaimPushMarker();
|
|
2499
|
+
console.log("\n This hard-deletes your pushed claims from staqs (terminalhire.com)");
|
|
2500
|
+
console.log(" and removes the local marker. There is no soft-delete.\n");
|
|
2501
|
+
const answer = await askYes(' Delete your pushed claims? Type "yes" to confirm: ');
|
|
2502
|
+
if (answer !== "yes") {
|
|
2503
|
+
console.log("\n Aborted \u2014 nothing was deleted.\n");
|
|
2504
|
+
process.exit(0);
|
|
2505
|
+
}
|
|
2506
|
+
const login = marker && marker.login ? marker.login : null;
|
|
2507
|
+
const deleteToken = marker && marker.deleteToken ? marker.deleteToken : null;
|
|
2508
|
+
if (!login || !deleteToken) {
|
|
2509
|
+
console.log("\n No claim-push marker found on this machine.");
|
|
2510
|
+
console.log(" Deletion must run from the machine that pushed (the delete token is stored there),");
|
|
2511
|
+
console.log(' or use the "delete my pushed claims" button on your dashboard.\n');
|
|
2512
|
+
process.exit(1);
|
|
2513
|
+
}
|
|
2514
|
+
console.log("\n Requesting deletion...");
|
|
2515
|
+
let res;
|
|
2516
|
+
try {
|
|
2517
|
+
res = await fetch(`${CLAIM_SYNC_BASE}/api/claim-sync`, {
|
|
2518
|
+
method: "DELETE",
|
|
2519
|
+
headers: { "Content-Type": "application/json" },
|
|
2520
|
+
body: JSON.stringify({ login, deleteToken }),
|
|
2521
|
+
signal: AbortSignal.timeout(1e4)
|
|
2522
|
+
});
|
|
2523
|
+
} catch (err) {
|
|
2524
|
+
console.error(`
|
|
2525
|
+
Delete failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2526
|
+
console.error(" Local marker NOT cleared (server state unknown). Re-run to retry.\n");
|
|
2527
|
+
process.exit(1);
|
|
2528
|
+
}
|
|
2529
|
+
if (!res.ok) {
|
|
2530
|
+
console.error(`
|
|
2531
|
+
Delete failed: /api/claim-sync returned ${res.status}.`);
|
|
2532
|
+
process.exit(1);
|
|
2533
|
+
}
|
|
2534
|
+
clearClaimPushMarker();
|
|
2535
|
+
console.log("\n \u2713 Pushed claims deleted and local marker cleared.\n");
|
|
2536
|
+
}
|
|
1315
2537
|
async function run() {
|
|
1316
2538
|
const verb = process.argv[2];
|
|
1317
2539
|
const { flags, positional } = parseArgs(process.argv.slice(3));
|
|
1318
2540
|
const active = Boolean(flags.active);
|
|
1319
2541
|
const json = Boolean(flags.json);
|
|
2542
|
+
if (verb === "--push") {
|
|
2543
|
+
if (flags.revoke) await cmdRevoke();
|
|
2544
|
+
else await cmdPush();
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
1320
2547
|
try {
|
|
1321
2548
|
switch (verb) {
|
|
1322
2549
|
case "preview":
|
|
@@ -1358,6 +2585,8 @@ export {
|
|
|
1358
2585
|
cmdRecord,
|
|
1359
2586
|
findClaimableByShortRef,
|
|
1360
2587
|
findClaimableInCache,
|
|
2588
|
+
fmtContestedWarning,
|
|
2589
|
+
isContested,
|
|
1361
2590
|
resolveBounty,
|
|
1362
2591
|
run
|
|
1363
2592
|
};
|