terminalhire 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/jpi-bounties.js +2031 -0
- package/dist/bin/jpi-dispatch.js +1174 -473
- package/dist/bin/jpi-jobs.js +705 -276
- package/dist/bin/jpi-learn.js +442 -249
- package/dist/bin/jpi-login.js +700 -282
- package/dist/bin/jpi-profile.js +381 -213
- package/dist/bin/jpi-refresh.js +757 -311
- package/dist/bin/jpi-save.js +381 -213
- package/dist/bin/jpi-sync.js +502 -230
- package/dist/src/github-auth.js +3 -3
- package/dist/src/profile.js +313 -207
- package/dist/src/signal.js +364 -237
- package/package.json +3 -3
package/dist/bin/jpi-sync.js
CHANGED
|
@@ -16,221 +16,347 @@ var init_types = __esm({
|
|
|
16
16
|
}
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
// ../../packages/core/src/
|
|
19
|
+
// ../../packages/core/src/vocab/graph.data.ts
|
|
20
|
+
var VOCAB_NODES;
|
|
21
|
+
var init_graph_data = __esm({
|
|
22
|
+
"../../packages/core/src/vocab/graph.data.ts"() {
|
|
23
|
+
"use strict";
|
|
24
|
+
VOCAB_NODES = [
|
|
25
|
+
// ── Languages ─────────────────────────────────────────────────────────────
|
|
26
|
+
{ id: "javascript", synonyms: ["js"], related: [{ to: "typescript", w: 0.6 }] },
|
|
27
|
+
{ id: "typescript", parents: ["javascript"], synonyms: ["ts"] },
|
|
28
|
+
{ id: "python", synonyms: ["py"] },
|
|
29
|
+
{ id: "go", synonyms: ["golang"] },
|
|
30
|
+
{ id: "rust" },
|
|
31
|
+
{ id: "java", related: [{ to: "kotlin", w: 0.45 }, { to: "scala", w: 0.4 }] },
|
|
32
|
+
{ id: "ruby" },
|
|
33
|
+
{ id: "elixir" },
|
|
34
|
+
{ id: "scala", related: [{ to: "java", w: 0.4 }] },
|
|
35
|
+
{ id: "kotlin", related: [{ to: "java", w: 0.45 }] },
|
|
36
|
+
{ id: "swift" },
|
|
37
|
+
{ id: "cpp", synonyms: ["c++"] },
|
|
38
|
+
{ id: "csharp", synonyms: ["c#"] },
|
|
39
|
+
{ id: "php" },
|
|
40
|
+
{ id: "haskell" },
|
|
41
|
+
{ id: "clojure" },
|
|
42
|
+
{ id: "r" },
|
|
43
|
+
{ id: "dart" },
|
|
44
|
+
// ── Frontend ──────────────────────────────────────────────────────────────
|
|
45
|
+
{
|
|
46
|
+
id: "react",
|
|
47
|
+
parents: ["javascript"],
|
|
48
|
+
synonyms: ["reactjs"],
|
|
49
|
+
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 }]
|
|
50
|
+
},
|
|
51
|
+
{ id: "nextjs", parents: ["react"], synonyms: ["next", "next.js"], related: [{ to: "remix", w: 0.5 }] },
|
|
52
|
+
{ id: "vue", parents: ["javascript"], synonyms: ["vue.js"], related: [{ to: "nuxt", w: 0.6 }] },
|
|
53
|
+
{ id: "nuxt", parents: ["vue"], synonyms: ["nuxt.js"] },
|
|
54
|
+
{ id: "svelte", parents: ["javascript"], related: [{ to: "sveltekit", w: 0.65 }] },
|
|
55
|
+
{ id: "sveltekit", parents: ["svelte"] },
|
|
56
|
+
{ id: "angular", parents: ["typescript"], synonyms: ["angular.js", "angularjs"] },
|
|
57
|
+
{ id: "solidjs", parents: ["javascript"] },
|
|
58
|
+
{ id: "remix", parents: ["react"], synonyms: ["remix.run"] },
|
|
59
|
+
{ id: "astro", parents: ["javascript"], related: [{ to: "nextjs", w: 0.4 }] },
|
|
60
|
+
{ id: "qwik", parents: ["javascript"] },
|
|
61
|
+
{ id: "tailwind", parents: ["css"], synonyms: ["tailwindcss", "tw"] },
|
|
62
|
+
{ id: "css" },
|
|
63
|
+
{ id: "html" },
|
|
64
|
+
{ id: "redux", parents: ["react"] },
|
|
65
|
+
{ id: "vite", parents: ["frontend"] },
|
|
66
|
+
{ id: "webpack", parents: ["frontend"] },
|
|
67
|
+
{ id: "storybook", parents: ["frontend"] },
|
|
68
|
+
// ── Backend frameworks / runtimes ───────────────────────────────────────────
|
|
69
|
+
{
|
|
70
|
+
id: "nodejs",
|
|
71
|
+
parents: ["javascript"],
|
|
72
|
+
synonyms: ["node", "node.js"],
|
|
73
|
+
related: [{ to: "express", w: 0.5 }, { to: "fastify", w: 0.45 }, { to: "nestjs", w: 0.45 }]
|
|
74
|
+
},
|
|
75
|
+
{ id: "express", parents: ["nodejs"], synonyms: ["express.js", "expressjs"], related: [{ to: "fastify", w: 0.5 }] },
|
|
76
|
+
{ id: "fastify", parents: ["nodejs"] },
|
|
77
|
+
{ id: "nestjs", parents: ["nodejs"], synonyms: ["nest", "nest.js"] },
|
|
78
|
+
{ id: "hono", parents: ["nodejs"] },
|
|
79
|
+
{ id: "deno", parents: ["javascript"], related: [{ to: "nodejs", w: 0.5 }, { to: "bun", w: 0.5 }] },
|
|
80
|
+
{ id: "bun", parents: ["javascript"], related: [{ to: "nodejs", w: 0.5 }] },
|
|
81
|
+
{ id: "django", parents: ["python"], related: [{ to: "flask", w: 0.5 }, { to: "fastapi", w: 0.45 }] },
|
|
82
|
+
{ id: "fastapi", parents: ["python"], related: [{ to: "flask", w: 0.55 }, { to: "django", w: 0.45 }] },
|
|
83
|
+
{ id: "flask", parents: ["python"] },
|
|
84
|
+
{ id: "rails", parents: ["ruby"], synonyms: ["ruby-on-rails", "ror"] },
|
|
85
|
+
{ id: "spring", parents: ["java"], synonyms: ["spring-boot", "springboot"] },
|
|
86
|
+
{ id: "actix", parents: ["rust"] },
|
|
87
|
+
{ id: "gin", parents: ["go"] },
|
|
88
|
+
{ id: "phoenix", parents: ["elixir"] },
|
|
89
|
+
{ id: "laravel", parents: ["php"] },
|
|
90
|
+
{ id: "dotnet", parents: ["csharp"], synonyms: [".net", "asp.net", "dotnet-core"] },
|
|
91
|
+
// ── Infrastructure & DevOps ─────────────────────────────────────────────────
|
|
92
|
+
{ 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 }] },
|
|
93
|
+
{ id: "docker", parents: ["devops"], related: [{ to: "kubernetes", w: 0.5 }] },
|
|
94
|
+
{ id: "terraform", synonyms: ["tf"], related: [{ to: "pulumi", w: 0.55 }, { to: "ansible", w: 0.4 }, { to: "aws", w: 0.4 }] },
|
|
95
|
+
{ id: "pulumi", related: [{ to: "terraform", w: 0.55 }] },
|
|
96
|
+
{ id: "ansible" },
|
|
97
|
+
{ id: "aws", synonyms: ["amazon-web-services"], related: [{ to: "gcp", w: 0.4 }, { to: "azure", w: 0.4 }] },
|
|
98
|
+
{ id: "gcp", synonyms: ["google-cloud", "google-cloud-platform"], related: [{ to: "aws", w: 0.4 }, { to: "azure", w: 0.4 }] },
|
|
99
|
+
{ id: "azure", synonyms: ["microsoft-azure"], related: [{ to: "aws", w: 0.4 }] },
|
|
100
|
+
{ id: "ci-cd", synonyms: ["cicd", "jenkins", "circleci", "circle-ci", "travis"], related: [{ to: "github-actions", w: 0.6 }, { to: "gitlab-ci", w: 0.6 }] },
|
|
101
|
+
{ id: "github-actions", parents: ["ci-cd"], synonyms: ["github-action"] },
|
|
102
|
+
{ id: "gitlab-ci", parents: ["ci-cd"], synonyms: ["gitlab"] },
|
|
103
|
+
{ id: "linux" },
|
|
104
|
+
{ id: "nginx" },
|
|
105
|
+
{ id: "prometheus", parents: ["observability"], related: [{ to: "grafana", w: 0.6 }] },
|
|
106
|
+
{ id: "grafana", parents: ["observability"] },
|
|
107
|
+
{ id: "datadog", parents: ["observability"] },
|
|
108
|
+
{ id: "opentelemetry", parents: ["observability"], synonyms: ["otel"] },
|
|
109
|
+
{ id: "vercel", related: [{ to: "netlify", w: 0.5 }, { to: "nextjs", w: 0.4 }] },
|
|
110
|
+
{ id: "netlify" },
|
|
111
|
+
{ id: "fly", synonyms: ["fly.io"], related: [{ to: "railway", w: 0.5 }, { to: "render", w: 0.5 }] },
|
|
112
|
+
{ id: "railway", related: [{ to: "render", w: 0.5 }] },
|
|
113
|
+
{ id: "render" },
|
|
114
|
+
{ id: "cloudflare", synonyms: ["cloudflare-workers"] },
|
|
115
|
+
{ id: "helm", parents: ["kubernetes"] },
|
|
116
|
+
{ id: "argocd", parents: ["kubernetes"] },
|
|
117
|
+
{ id: "serverless", parents: ["devops"] },
|
|
118
|
+
// ── Databases & storage ─────────────────────────────────────────────────────
|
|
119
|
+
{ id: "postgresql", synonyms: ["postgres", "pg"], related: [{ to: "mysql", w: 0.45 }, { to: "sqlite", w: 0.4 }] },
|
|
120
|
+
{ id: "mysql", related: [{ to: "postgresql", w: 0.45 }] },
|
|
121
|
+
{ id: "sqlite" },
|
|
122
|
+
{ id: "mongodb", synonyms: ["mongo"] },
|
|
123
|
+
{ id: "redis", related: [{ to: "caching", w: 0.5 }] },
|
|
124
|
+
{ id: "elasticsearch", synonyms: ["elastic"], related: [{ to: "search", w: 0.55 }] },
|
|
125
|
+
{ id: "kafka", synonyms: ["apache-kafka"], related: [{ to: "rabbitmq", w: 0.5 }, { to: "message-queue", w: 0.55 }] },
|
|
126
|
+
{ id: "rabbitmq", related: [{ to: "message-queue", w: 0.55 }] },
|
|
127
|
+
{ id: "cassandra" },
|
|
128
|
+
{ id: "dynamodb", parents: ["aws"] },
|
|
129
|
+
{ id: "snowflake", parents: ["data-engineering"], related: [{ to: "clickhouse", w: 0.4 }] },
|
|
130
|
+
{ id: "clickhouse", parents: ["data-engineering"], related: [{ to: "duckdb", w: 0.35 }] },
|
|
131
|
+
{ id: "duckdb", parents: ["data-engineering"] },
|
|
132
|
+
{ id: "supabase", related: [{ to: "postgresql", w: 0.5 }, { to: "neon", w: 0.4 }] },
|
|
133
|
+
{ id: "planetscale", related: [{ to: "mysql", w: 0.5 }] },
|
|
134
|
+
{ id: "neon", related: [{ to: "postgresql", w: 0.5 }] },
|
|
135
|
+
{ id: "turso", related: [{ to: "sqlite", w: 0.5 }] },
|
|
136
|
+
{ id: "cockroachdb", related: [{ to: "postgresql", w: 0.45 }] },
|
|
137
|
+
{ id: "prisma", parents: ["backend"], synonyms: ["@prisma/client"], related: [{ to: "drizzle", w: 0.5 }, { to: "typeorm", w: 0.45 }, { to: "sequelize", w: 0.4 }] },
|
|
138
|
+
{ id: "drizzle", synonyms: ["drizzle-orm"], related: [{ to: "prisma", w: 0.5 }] },
|
|
139
|
+
{ id: "sequelize", related: [{ to: "typeorm", w: 0.4 }] },
|
|
140
|
+
{ id: "typeorm", related: [{ to: "prisma", w: 0.45 }] },
|
|
141
|
+
{ id: "sqlalchemy", parents: ["python"] },
|
|
142
|
+
// ── Data engineering & ML ───────────────────────────────────────────────────
|
|
143
|
+
{ id: "data-engineering", synonyms: ["data-eng"], related: [{ to: "spark", w: 0.5 }, { to: "airflow", w: 0.5 }, { to: "dbt", w: 0.45 }] },
|
|
144
|
+
{ id: "spark", parents: ["data-engineering"], synonyms: ["apache-spark"] },
|
|
145
|
+
{ id: "airflow", parents: ["data-engineering"], synonyms: ["apache-airflow"] },
|
|
146
|
+
{ id: "dbt", parents: ["data-engineering"] },
|
|
147
|
+
{ id: "ml", synonyms: ["machine-learning"], related: [{ to: "pytorch", w: 0.5 }, { to: "tensorflow", w: 0.5 }, { to: "scikit-learn", w: 0.5 }] },
|
|
148
|
+
{ id: "llm", parents: ["ml"], synonyms: ["llms", "genai", "generative-ai"], related: [{ to: "langchain", w: 0.5 }, { to: "rag", w: 0.55 }, { to: "openai", w: 0.45 }, { to: "anthropic", w: 0.45 }] },
|
|
149
|
+
{ id: "pytorch", parents: ["ml"], synonyms: ["torch"], related: [{ to: "tensorflow", w: 0.5 }] },
|
|
150
|
+
{ id: "tensorflow", parents: ["ml"], synonyms: ["keras", "tf-keras"] },
|
|
151
|
+
{ id: "pandas", parents: ["python"], related: [{ to: "numpy", w: 0.6 }] },
|
|
152
|
+
{ id: "numpy", parents: ["python"] },
|
|
153
|
+
{ id: "scikit-learn", parents: ["ml"], synonyms: ["sklearn"] },
|
|
154
|
+
{ id: "jupyter", parents: ["python"] },
|
|
155
|
+
{ id: "langchain", parents: ["llm"], synonyms: ["llamaindex"] },
|
|
156
|
+
{ id: "huggingface", parents: ["ml"], synonyms: ["hugging-face"] },
|
|
157
|
+
{ id: "openai", parents: ["llm"] },
|
|
158
|
+
{ id: "anthropic", parents: ["llm"], synonyms: ["claude"] },
|
|
159
|
+
{ id: "rag", parents: ["llm"], synonyms: ["retrieval-augmented-generation"] },
|
|
160
|
+
{ id: "mlops", parents: ["ml"], related: [{ to: "devops", w: 0.4 }] },
|
|
161
|
+
// ── Mobile ──────────────────────────────────────────────────────────────────
|
|
162
|
+
{ id: "mobile", related: [{ to: "ios", w: 0.5 }, { to: "android", w: 0.5 }] },
|
|
163
|
+
{ id: "ios", parents: ["mobile", "swift"], related: [{ to: "android", w: 0.4 }] },
|
|
164
|
+
{ id: "android", parents: ["mobile"], related: [{ to: "kotlin", w: 0.4 }] },
|
|
165
|
+
{ id: "swiftui", parents: ["ios", "swift"] },
|
|
166
|
+
{ id: "react-native", parents: ["mobile", "react"], synonyms: ["reactnative"], related: [{ to: "flutter", w: 0.4 }, { to: "expo", w: 0.6 }] },
|
|
167
|
+
{ id: "flutter", parents: ["mobile", "dart"] },
|
|
168
|
+
{ id: "expo", parents: ["react-native"] },
|
|
169
|
+
{ id: "kotlin-multiplatform", parents: ["mobile", "kotlin"], synonyms: ["kmp"] },
|
|
170
|
+
// ── Domains / capabilities ──────────────────────────────────────────────────
|
|
171
|
+
{ id: "frontend", related: [{ to: "react", w: 0.4 }, { to: "css", w: 0.3 }] },
|
|
172
|
+
{ id: "backend", related: [{ to: "api-design", w: 0.4 }, { to: "microservices", w: 0.4 }] },
|
|
173
|
+
{ id: "devops", related: [{ to: "kubernetes", w: 0.4 }, { to: "ci-cd", w: 0.4 }, { to: "docker", w: 0.4 }] },
|
|
174
|
+
{ id: "authentication", synonyms: ["auth", "jwt", "saml", "passport", "auth0", "clerk", "nextauth"], related: [{ to: "oauth", w: 0.6 }, { to: "security", w: 0.5 }] },
|
|
175
|
+
{ id: "oauth", parents: ["authentication"], synonyms: ["oauth2", "oidc"], related: [{ to: "security", w: 0.4 }] },
|
|
176
|
+
{ id: "security", related: [{ to: "authentication", w: 0.5 }] },
|
|
177
|
+
{ id: "payments", synonyms: ["stripe", "braintree", "paddle", "lemonsqueezy", "@stripe/stripe-js"], related: [{ to: "billing", w: 0.6 }] },
|
|
178
|
+
{ id: "billing", synonyms: ["recurly", "chargebee"] },
|
|
179
|
+
{ id: "api-design", synonyms: ["rest", "restful", "rest-api"], related: [{ to: "graphql", w: 0.4 }, { to: "grpc", w: 0.4 }, { to: "backend", w: 0.4 }] },
|
|
180
|
+
{ id: "graphql", synonyms: ["gql"], related: [{ to: "trpc", w: 0.4 }] },
|
|
181
|
+
{ id: "trpc", related: [{ to: "graphql", w: 0.4 }] },
|
|
182
|
+
{ id: "grpc", synonyms: ["grpc-web"], related: [{ to: "microservices", w: 0.3 }] },
|
|
183
|
+
{ id: "microservices" },
|
|
184
|
+
{ id: "websockets", synonyms: ["ws", "socket.io"], related: [{ to: "realtime", w: 0.6 }] },
|
|
185
|
+
{ id: "realtime", synonyms: ["real-time"] },
|
|
186
|
+
{ id: "message-queue", synonyms: ["mq"] },
|
|
187
|
+
{ id: "caching", synonyms: ["cache"] },
|
|
188
|
+
{ id: "search", synonyms: ["full-text-search"] },
|
|
189
|
+
{ id: "observability", synonyms: ["o11y"], related: [{ to: "monitoring", w: 0.6 }] },
|
|
190
|
+
{ id: "monitoring", related: [{ to: "prometheus", w: 0.4 }] },
|
|
191
|
+
{ id: "testing", related: [{ to: "unit-testing", w: 0.5 }, { to: "e2e-testing", w: 0.5 }] },
|
|
192
|
+
{ id: "unit-testing", parents: ["testing"] },
|
|
193
|
+
{ id: "e2e-testing", parents: ["testing"], synonyms: ["e2e", "end-to-end-testing"] },
|
|
194
|
+
{ id: "jest", parents: ["testing"], related: [{ to: "vitest", w: 0.6 }, { to: "mocha", w: 0.5 }] },
|
|
195
|
+
{ id: "vitest", parents: ["testing"], related: [{ to: "jest", w: 0.6 }] },
|
|
196
|
+
{ id: "playwright", parents: ["e2e-testing"], related: [{ to: "cypress", w: 0.6 }] },
|
|
197
|
+
{ id: "cypress", parents: ["e2e-testing"] },
|
|
198
|
+
{ id: "mocha", parents: ["testing"] },
|
|
199
|
+
{ id: "pytest", parents: ["testing", "python"] },
|
|
200
|
+
{ id: "accessibility", synonyms: ["a11y"] },
|
|
201
|
+
{ id: "seo" },
|
|
202
|
+
{ id: "performance", synonyms: ["perf", "web-performance"] }
|
|
203
|
+
];
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// ../../packages/core/src/vocab/closure.ts
|
|
208
|
+
function round3(n) {
|
|
209
|
+
return Math.round(n * 1e3) / 1e3;
|
|
210
|
+
}
|
|
211
|
+
function validateGraph(nodes) {
|
|
212
|
+
const ids = /* @__PURE__ */ new Set();
|
|
213
|
+
for (const n of nodes) {
|
|
214
|
+
if (ids.has(n.id)) throw new Error(`vocab: duplicate id "${n.id}"`);
|
|
215
|
+
ids.add(n.id);
|
|
216
|
+
}
|
|
217
|
+
const seenAlias = /* @__PURE__ */ new Map();
|
|
218
|
+
for (const n of nodes) {
|
|
219
|
+
for (const p of n.parents ?? []) {
|
|
220
|
+
if (p === n.id) throw new Error(`vocab: "${n.id}" lists itself as a parent`);
|
|
221
|
+
if (!ids.has(p)) throw new Error(`vocab: "${n.id}" parent "${p}" is not a defined id`);
|
|
222
|
+
}
|
|
223
|
+
for (const e of n.related ?? []) {
|
|
224
|
+
if (e.to === n.id) throw new Error(`vocab: "${n.id}" relates to itself`);
|
|
225
|
+
if (!ids.has(e.to)) throw new Error(`vocab: "${n.id}" related "${e.to}" is not a defined id`);
|
|
226
|
+
if (!(e.w > 0 && e.w <= 1)) throw new Error(`vocab: "${n.id}"\u2192"${e.to}" weight ${e.w} out of (0,1]`);
|
|
227
|
+
}
|
|
228
|
+
for (const s of n.synonyms ?? []) {
|
|
229
|
+
const alias = s.toLowerCase();
|
|
230
|
+
if (ids.has(alias)) throw new Error(`vocab: synonym "${alias}" collides with a canonical id`);
|
|
231
|
+
const prev = seenAlias.get(alias);
|
|
232
|
+
if (prev && prev !== n.id) throw new Error(`vocab: synonym "${alias}" maps to both "${prev}" and "${n.id}"`);
|
|
233
|
+
seenAlias.set(alias, n.id);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
237
|
+
const done = /* @__PURE__ */ new Set();
|
|
238
|
+
const parentMap = new Map(nodes.map((n) => [n.id, n.parents ?? []]));
|
|
239
|
+
const walk = (id, path) => {
|
|
240
|
+
if (done.has(id)) return;
|
|
241
|
+
if (visiting.has(id)) throw new Error(`vocab: parent cycle ${[...path, id].join(" \u2192 ")}`);
|
|
242
|
+
visiting.add(id);
|
|
243
|
+
for (const p of parentMap.get(id) ?? []) walk(p, [...path, id]);
|
|
244
|
+
visiting.delete(id);
|
|
245
|
+
done.add(id);
|
|
246
|
+
};
|
|
247
|
+
for (const n of nodes) walk(n.id, []);
|
|
248
|
+
}
|
|
249
|
+
function buildAdjacency(nodes) {
|
|
250
|
+
const adj = /* @__PURE__ */ new Map();
|
|
251
|
+
const add = (from, to, w) => {
|
|
252
|
+
let m = adj.get(from);
|
|
253
|
+
if (!m) adj.set(from, m = /* @__PURE__ */ new Map());
|
|
254
|
+
if (w > (m.get(to) ?? 0)) m.set(to, w);
|
|
255
|
+
};
|
|
256
|
+
for (const n of nodes) {
|
|
257
|
+
for (const p of n.parents ?? []) {
|
|
258
|
+
add(n.id, p, PARENT_UP);
|
|
259
|
+
add(p, n.id, PARENT_DOWN);
|
|
260
|
+
}
|
|
261
|
+
for (const e of n.related ?? []) {
|
|
262
|
+
add(n.id, e.to, e.w);
|
|
263
|
+
add(e.to, n.id, e.w);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return adj;
|
|
267
|
+
}
|
|
268
|
+
function closureFrom(source, adj) {
|
|
269
|
+
const best = /* @__PURE__ */ new Map();
|
|
270
|
+
for (const [t, w] of adj.get(source) ?? []) {
|
|
271
|
+
if (w >= DECAY_FLOOR) best.set(t, { w: round3(w), via: t });
|
|
272
|
+
}
|
|
273
|
+
const settled = /* @__PURE__ */ new Set([source]);
|
|
274
|
+
while (true) {
|
|
275
|
+
let u;
|
|
276
|
+
let uw = 0;
|
|
277
|
+
for (const [t, e] of best) {
|
|
278
|
+
if (!settled.has(t) && e.w > uw) {
|
|
279
|
+
u = t;
|
|
280
|
+
uw = e.w;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (!u) break;
|
|
284
|
+
settled.add(u);
|
|
285
|
+
const via = best.get(u).via;
|
|
286
|
+
for (const [t, we] of adj.get(u) ?? []) {
|
|
287
|
+
if (settled.has(t)) continue;
|
|
288
|
+
const cand = round3(uw * we);
|
|
289
|
+
if (cand >= DECAY_FLOOR && cand > (best.get(t)?.w ?? 0)) {
|
|
290
|
+
best.set(t, { w: cand, via });
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
best.delete(source);
|
|
295
|
+
return best;
|
|
296
|
+
}
|
|
297
|
+
function buildGraph(nodes) {
|
|
298
|
+
validateGraph(nodes);
|
|
299
|
+
const ids = new Set(nodes.map((n) => n.id));
|
|
300
|
+
const synonyms = /* @__PURE__ */ new Map();
|
|
301
|
+
for (const n of nodes) {
|
|
302
|
+
for (const s of n.synonyms ?? []) synonyms.set(s.toLowerCase(), n.id);
|
|
303
|
+
}
|
|
304
|
+
const adj = buildAdjacency(nodes);
|
|
305
|
+
const closure = /* @__PURE__ */ new Map();
|
|
306
|
+
for (const n of nodes) closure.set(n.id, closureFrom(n.id, adj));
|
|
307
|
+
return { ids, synonyms, closure };
|
|
308
|
+
}
|
|
309
|
+
var PARENT_UP, PARENT_DOWN, DECAY_FLOOR;
|
|
310
|
+
var init_closure = __esm({
|
|
311
|
+
"../../packages/core/src/vocab/closure.ts"() {
|
|
312
|
+
"use strict";
|
|
313
|
+
PARENT_UP = 0.6;
|
|
314
|
+
PARENT_DOWN = 0.35;
|
|
315
|
+
DECAY_FLOOR = 0.25;
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// ../../packages/core/src/vocab/types.ts
|
|
320
|
+
var init_types2 = __esm({
|
|
321
|
+
"../../packages/core/src/vocab/types.ts"() {
|
|
322
|
+
"use strict";
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// ../../packages/core/src/vocab/index.ts
|
|
20
327
|
function normalize(tokens) {
|
|
21
328
|
const result = /* @__PURE__ */ new Set();
|
|
22
329
|
for (const raw of tokens) {
|
|
23
330
|
const lower = raw.toLowerCase().trim();
|
|
24
|
-
if (
|
|
331
|
+
if (GRAPH.ids.has(lower)) {
|
|
25
332
|
result.add(lower);
|
|
26
333
|
continue;
|
|
27
334
|
}
|
|
28
|
-
const mapped =
|
|
29
|
-
if (mapped
|
|
30
|
-
result.add(mapped);
|
|
31
|
-
}
|
|
335
|
+
const mapped = GRAPH.synonyms.get(lower);
|
|
336
|
+
if (mapped) result.add(mapped);
|
|
32
337
|
}
|
|
33
338
|
return Array.from(result);
|
|
34
339
|
}
|
|
35
|
-
var VOCABULARY, SYNONYMS
|
|
340
|
+
var GRAPH, VOCABULARY, SYNONYMS;
|
|
341
|
+
var init_vocab = __esm({
|
|
342
|
+
"../../packages/core/src/vocab/index.ts"() {
|
|
343
|
+
"use strict";
|
|
344
|
+
init_graph_data();
|
|
345
|
+
init_closure();
|
|
346
|
+
init_types2();
|
|
347
|
+
init_closure();
|
|
348
|
+
init_graph_data();
|
|
349
|
+
GRAPH = buildGraph(VOCAB_NODES);
|
|
350
|
+
VOCABULARY = [...GRAPH.ids];
|
|
351
|
+
SYNONYMS = Object.fromEntries(GRAPH.synonyms);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// ../../packages/core/src/vocabulary.ts
|
|
36
356
|
var init_vocabulary = __esm({
|
|
37
357
|
"../../packages/core/src/vocabulary.ts"() {
|
|
38
358
|
"use strict";
|
|
39
|
-
|
|
40
|
-
// Languages
|
|
41
|
-
"typescript",
|
|
42
|
-
"javascript",
|
|
43
|
-
"python",
|
|
44
|
-
"go",
|
|
45
|
-
"rust",
|
|
46
|
-
"java",
|
|
47
|
-
"ruby",
|
|
48
|
-
"elixir",
|
|
49
|
-
"scala",
|
|
50
|
-
"kotlin",
|
|
51
|
-
"swift",
|
|
52
|
-
"cpp",
|
|
53
|
-
"csharp",
|
|
54
|
-
"php",
|
|
55
|
-
"haskell",
|
|
56
|
-
"clojure",
|
|
57
|
-
"r",
|
|
58
|
-
// Frontend frameworks / libs
|
|
59
|
-
"react",
|
|
60
|
-
"nextjs",
|
|
61
|
-
"vue",
|
|
62
|
-
"nuxt",
|
|
63
|
-
"svelte",
|
|
64
|
-
"angular",
|
|
65
|
-
"solidjs",
|
|
66
|
-
"tailwind",
|
|
67
|
-
"css",
|
|
68
|
-
"html",
|
|
69
|
-
"graphql",
|
|
70
|
-
"trpc",
|
|
71
|
-
// Backend frameworks
|
|
72
|
-
"nodejs",
|
|
73
|
-
"express",
|
|
74
|
-
"fastify",
|
|
75
|
-
"nestjs",
|
|
76
|
-
"django",
|
|
77
|
-
"fastapi",
|
|
78
|
-
"flask",
|
|
79
|
-
"rails",
|
|
80
|
-
"spring",
|
|
81
|
-
"actix",
|
|
82
|
-
"gin",
|
|
83
|
-
"phoenix",
|
|
84
|
-
"laravel",
|
|
85
|
-
"dotnet",
|
|
86
|
-
// Infrastructure & DevOps
|
|
87
|
-
"kubernetes",
|
|
88
|
-
"docker",
|
|
89
|
-
"terraform",
|
|
90
|
-
"aws",
|
|
91
|
-
"gcp",
|
|
92
|
-
"azure",
|
|
93
|
-
"ci-cd",
|
|
94
|
-
"github-actions",
|
|
95
|
-
"linux",
|
|
96
|
-
"nginx",
|
|
97
|
-
"pulumi",
|
|
98
|
-
"ansible",
|
|
99
|
-
"prometheus",
|
|
100
|
-
"grafana",
|
|
101
|
-
"datadog",
|
|
102
|
-
"opentelemetry",
|
|
103
|
-
// Data & ML
|
|
104
|
-
"postgresql",
|
|
105
|
-
"mysql",
|
|
106
|
-
"sqlite",
|
|
107
|
-
"mongodb",
|
|
108
|
-
"redis",
|
|
109
|
-
"elasticsearch",
|
|
110
|
-
"kafka",
|
|
111
|
-
"rabbitmq",
|
|
112
|
-
"data-engineering",
|
|
113
|
-
"spark",
|
|
114
|
-
"airflow",
|
|
115
|
-
"dbt",
|
|
116
|
-
"ml",
|
|
117
|
-
"llm",
|
|
118
|
-
"pytorch",
|
|
119
|
-
"tensorflow",
|
|
120
|
-
"pandas",
|
|
121
|
-
"numpy",
|
|
122
|
-
// Domains / capabilities
|
|
123
|
-
"oauth",
|
|
124
|
-
"authentication",
|
|
125
|
-
"security",
|
|
126
|
-
"payments",
|
|
127
|
-
"billing",
|
|
128
|
-
"frontend",
|
|
129
|
-
"backend",
|
|
130
|
-
"devops",
|
|
131
|
-
"mobile",
|
|
132
|
-
"ios",
|
|
133
|
-
"android",
|
|
134
|
-
"api-design",
|
|
135
|
-
"microservices",
|
|
136
|
-
"websockets",
|
|
137
|
-
"testing",
|
|
138
|
-
"accessibility",
|
|
139
|
-
"seo",
|
|
140
|
-
"performance",
|
|
141
|
-
"observability",
|
|
142
|
-
"search",
|
|
143
|
-
"realtime"
|
|
144
|
-
];
|
|
145
|
-
SYNONYMS = {
|
|
146
|
-
// Kubernetes aliases
|
|
147
|
-
"k8s": "kubernetes",
|
|
148
|
-
"kube": "kubernetes",
|
|
149
|
-
// Auth / identity
|
|
150
|
-
"passport": "authentication",
|
|
151
|
-
"oauth2": "oauth",
|
|
152
|
-
"oidc": "oauth",
|
|
153
|
-
"jwt": "authentication",
|
|
154
|
-
"saml": "authentication",
|
|
155
|
-
"auth0": "authentication",
|
|
156
|
-
"clerk": "authentication",
|
|
157
|
-
"nextauth": "authentication",
|
|
158
|
-
// Payments
|
|
159
|
-
"@stripe/stripe-js": "payments",
|
|
160
|
-
"stripe": "payments",
|
|
161
|
-
"braintree": "payments",
|
|
162
|
-
"paddle": "payments",
|
|
163
|
-
"lemonsqueezy": "payments",
|
|
164
|
-
"recurly": "billing",
|
|
165
|
-
"chargebee": "billing",
|
|
166
|
-
// Framework / lib aliases
|
|
167
|
-
"next": "nextjs",
|
|
168
|
-
"next.js": "nextjs",
|
|
169
|
-
"nuxt.js": "nuxt",
|
|
170
|
-
"vue.js": "vue",
|
|
171
|
-
"angular.js": "angular",
|
|
172
|
-
"angularjs": "angular",
|
|
173
|
-
"express.js": "express",
|
|
174
|
-
"expressjs": "express",
|
|
175
|
-
"fastapi": "fastapi",
|
|
176
|
-
"nest": "nestjs",
|
|
177
|
-
"nest.js": "nestjs",
|
|
178
|
-
"sveltekit": "svelte",
|
|
179
|
-
// Language aliases
|
|
180
|
-
"ts": "typescript",
|
|
181
|
-
"js": "javascript",
|
|
182
|
-
"py": "python",
|
|
183
|
-
"golang": "go",
|
|
184
|
-
"c++": "cpp",
|
|
185
|
-
"c#": "csharp",
|
|
186
|
-
".net": "dotnet",
|
|
187
|
-
"asp.net": "dotnet",
|
|
188
|
-
// DB aliases
|
|
189
|
-
"postgres": "postgresql",
|
|
190
|
-
"pg": "postgresql",
|
|
191
|
-
"mongo": "mongodb",
|
|
192
|
-
"elastic": "elasticsearch",
|
|
193
|
-
// Cloud aliases
|
|
194
|
-
"amazon web services": "aws",
|
|
195
|
-
"google cloud": "gcp",
|
|
196
|
-
"google cloud platform": "gcp",
|
|
197
|
-
"microsoft azure": "azure",
|
|
198
|
-
// CI/CD aliases
|
|
199
|
-
"github actions": "github-actions",
|
|
200
|
-
"circle ci": "ci-cd",
|
|
201
|
-
"circleci": "ci-cd",
|
|
202
|
-
"jenkins": "ci-cd",
|
|
203
|
-
"gitlab ci": "ci-cd",
|
|
204
|
-
"travis": "ci-cd",
|
|
205
|
-
// Mobile
|
|
206
|
-
"react native": "mobile",
|
|
207
|
-
"flutter": "mobile",
|
|
208
|
-
"expo": "mobile",
|
|
209
|
-
// AI / ML
|
|
210
|
-
"openai": "llm",
|
|
211
|
-
"anthropic": "llm",
|
|
212
|
-
"langchain": "llm",
|
|
213
|
-
"llamaindex": "llm",
|
|
214
|
-
"hugging face": "ml",
|
|
215
|
-
"huggingface": "ml",
|
|
216
|
-
"scikit-learn": "ml",
|
|
217
|
-
"sklearn": "ml",
|
|
218
|
-
// Data pipeline
|
|
219
|
-
"apache kafka": "kafka",
|
|
220
|
-
"apache spark": "spark",
|
|
221
|
-
"apache airflow": "airflow",
|
|
222
|
-
// Misc
|
|
223
|
-
"tailwindcss": "tailwind",
|
|
224
|
-
"tw": "tailwind",
|
|
225
|
-
"gql": "graphql",
|
|
226
|
-
"ws": "websockets",
|
|
227
|
-
"socket.io": "websockets",
|
|
228
|
-
"jest": "testing",
|
|
229
|
-
"vitest": "testing",
|
|
230
|
-
"playwright": "testing",
|
|
231
|
-
"cypress": "testing"
|
|
232
|
-
};
|
|
233
|
-
VOCAB_SET = new Set(VOCABULARY);
|
|
359
|
+
init_vocab();
|
|
234
360
|
}
|
|
235
361
|
});
|
|
236
362
|
|
|
@@ -238,6 +364,7 @@ var init_vocabulary = __esm({
|
|
|
238
364
|
var init_matcher = __esm({
|
|
239
365
|
"../../packages/core/src/matcher.ts"() {
|
|
240
366
|
"use strict";
|
|
367
|
+
init_vocabulary();
|
|
241
368
|
}
|
|
242
369
|
});
|
|
243
370
|
|
|
@@ -273,11 +400,19 @@ var init_himalayas = __esm({
|
|
|
273
400
|
}
|
|
274
401
|
});
|
|
275
402
|
|
|
403
|
+
// ../../packages/core/src/feeds/entities.ts
|
|
404
|
+
var init_entities = __esm({
|
|
405
|
+
"../../packages/core/src/feeds/entities.ts"() {
|
|
406
|
+
"use strict";
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
276
410
|
// ../../packages/core/src/feeds/wwr.ts
|
|
277
411
|
var init_wwr = __esm({
|
|
278
412
|
"../../packages/core/src/feeds/wwr.ts"() {
|
|
279
413
|
"use strict";
|
|
280
414
|
init_vocabulary();
|
|
415
|
+
init_entities();
|
|
281
416
|
}
|
|
282
417
|
});
|
|
283
418
|
|
|
@@ -286,6 +421,24 @@ var init_hn = __esm({
|
|
|
286
421
|
"../../packages/core/src/feeds/hn.ts"() {
|
|
287
422
|
"use strict";
|
|
288
423
|
init_vocabulary();
|
|
424
|
+
init_entities();
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// ../../packages/core/src/feeds/bounty-gate.ts
|
|
429
|
+
var init_bounty_gate = __esm({
|
|
430
|
+
"../../packages/core/src/feeds/bounty-gate.ts"() {
|
|
431
|
+
"use strict";
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// ../../packages/core/src/feeds/github-bounties.ts
|
|
436
|
+
var init_github_bounties = __esm({
|
|
437
|
+
"../../packages/core/src/feeds/github-bounties.ts"() {
|
|
438
|
+
"use strict";
|
|
439
|
+
init_vocabulary();
|
|
440
|
+
init_entities();
|
|
441
|
+
init_bounty_gate();
|
|
289
442
|
}
|
|
290
443
|
});
|
|
291
444
|
|
|
@@ -303,6 +456,8 @@ var init_feeds = __esm({
|
|
|
303
456
|
init_himalayas();
|
|
304
457
|
init_wwr();
|
|
305
458
|
init_hn();
|
|
459
|
+
init_github_bounties();
|
|
460
|
+
init_bounty_gate();
|
|
306
461
|
GREENHOUSE_SLUGS_BY_TIER = {
|
|
307
462
|
bigco: [
|
|
308
463
|
"stripe",
|
|
@@ -411,13 +566,22 @@ var init_feeds = __esm({
|
|
|
411
566
|
}
|
|
412
567
|
});
|
|
413
568
|
|
|
414
|
-
// ../../packages/core/src/
|
|
569
|
+
// ../../packages/core/src/partners.ts
|
|
415
570
|
import { readFileSync } from "fs";
|
|
416
571
|
import { join } from "path";
|
|
417
572
|
import { fileURLToPath } from "url";
|
|
418
|
-
var
|
|
419
|
-
|
|
573
|
+
var EXAMPLE_BUYER, BUYER_REGISTRY;
|
|
574
|
+
var init_partners = __esm({
|
|
575
|
+
"../../packages/core/src/partners.ts"() {
|
|
420
576
|
"use strict";
|
|
577
|
+
EXAMPLE_BUYER = {
|
|
578
|
+
id: "northstar",
|
|
579
|
+
legalName: "Northstar Talent Partners",
|
|
580
|
+
matchCriteria: { roleTypes: ["full_time"] }
|
|
581
|
+
};
|
|
582
|
+
BUYER_REGISTRY = {
|
|
583
|
+
[EXAMPLE_BUYER.id]: EXAMPLE_BUYER
|
|
584
|
+
};
|
|
421
585
|
}
|
|
422
586
|
});
|
|
423
587
|
|
|
@@ -426,7 +590,7 @@ var init_indexer = __esm({
|
|
|
426
590
|
"../../packages/core/src/indexer.ts"() {
|
|
427
591
|
"use strict";
|
|
428
592
|
init_feeds();
|
|
429
|
-
|
|
593
|
+
init_partners();
|
|
430
594
|
}
|
|
431
595
|
});
|
|
432
596
|
|
|
@@ -447,7 +611,7 @@ var init_src = __esm({
|
|
|
447
611
|
init_matcher();
|
|
448
612
|
init_feeds();
|
|
449
613
|
init_indexer();
|
|
450
|
-
|
|
614
|
+
init_partners();
|
|
451
615
|
init_github();
|
|
452
616
|
}
|
|
453
617
|
});
|
|
@@ -546,10 +710,10 @@ function migrateTagWeights(profile) {
|
|
|
546
710
|
if (!profile.tagWeights) {
|
|
547
711
|
profile.tagWeights = {};
|
|
548
712
|
}
|
|
549
|
-
const
|
|
713
|
+
const seed = profile.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
550
714
|
for (const tag of profile.skillTags) {
|
|
551
715
|
if (!profile.tagWeights[tag]) {
|
|
552
|
-
profile.tagWeights[tag] = { count: 1, firstSeen:
|
|
716
|
+
profile.tagWeights[tag] = { count: 1, firstSeen: seed, lastSeen: seed, sessions: 1 };
|
|
553
717
|
}
|
|
554
718
|
}
|
|
555
719
|
}
|
|
@@ -575,7 +739,7 @@ async function writeProfile(profile) {
|
|
|
575
739
|
const blob = encrypt(JSON.stringify(profile), key);
|
|
576
740
|
writeFileSync(PROFILE_FILE, JSON.stringify(blob, null, 2), { encoding: "utf8" });
|
|
577
741
|
}
|
|
578
|
-
function accumulateSession(profile, tags, isEmployerContext, inferredSeniority) {
|
|
742
|
+
function accumulateSession(profile, tags, isEmployerContext, inferredSeniority, seniorityIsAuthoritative = false) {
|
|
579
743
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
580
744
|
let filtered = normalize(tags);
|
|
581
745
|
if (isEmployerContext) {
|
|
@@ -593,7 +757,9 @@ function accumulateSession(profile, tags, isEmployerContext, inferredSeniority)
|
|
|
593
757
|
}
|
|
594
758
|
}
|
|
595
759
|
if (inferredSeniority && !isEmployerContext) {
|
|
596
|
-
profile.
|
|
760
|
+
if (seniorityIsAuthoritative || !profile.github) {
|
|
761
|
+
profile.seniority = inferredSeniority;
|
|
762
|
+
}
|
|
597
763
|
}
|
|
598
764
|
}
|
|
599
765
|
async function accumulateTags(rawTokens, isEmployerContext, inferredSeniority) {
|
|
@@ -601,12 +767,14 @@ async function accumulateTags(rawTokens, isEmployerContext, inferredSeniority) {
|
|
|
601
767
|
accumulateSession(profile, rawTokens, isEmployerContext, inferredSeniority);
|
|
602
768
|
await writeProfile(profile);
|
|
603
769
|
}
|
|
604
|
-
function accumulateGitHubTags(profile, tags) {
|
|
770
|
+
function accumulateGitHubTags(profile, tags, inferredSeniority) {
|
|
605
771
|
accumulateSession(
|
|
606
772
|
profile,
|
|
607
773
|
tags,
|
|
608
774
|
/* isEmployerContext */
|
|
609
|
-
false
|
|
775
|
+
false,
|
|
776
|
+
inferredSeniority,
|
|
777
|
+
true
|
|
610
778
|
);
|
|
611
779
|
}
|
|
612
780
|
async function listSavedJobs() {
|
|
@@ -691,11 +859,15 @@ var init_profile = __esm({
|
|
|
691
859
|
// bin/jpi-sync.js
|
|
692
860
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2, rmSync } from "fs";
|
|
693
861
|
import { join as join3 } from "path";
|
|
694
|
-
import { homedir as homedir2 } from "os";
|
|
862
|
+
import { homedir as homedir2, hostname as osHostname } from "os";
|
|
695
863
|
import { createInterface } from "readline";
|
|
864
|
+
import { spawn } from "child_process";
|
|
696
865
|
var TH_DIR = process.env["TERMINALHIRE_DIR"] || join3(homedir2(), ".terminalhire");
|
|
697
866
|
var TIER1_MARKER = join3(TH_DIR, "tier1.json");
|
|
698
867
|
var API_URL = process.env["TERMINALHIRE_API_URL"] || process.env["JPI_API_URL"] || "https://terminalhire.com";
|
|
868
|
+
var SYNC_BASE = "https://www.terminalhire.com";
|
|
869
|
+
var POLL_INTERVAL_MS = 2e3;
|
|
870
|
+
var POLL_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
699
871
|
var CONSENT_VERSION = 1;
|
|
700
872
|
function ask(question) {
|
|
701
873
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -739,14 +911,14 @@ function buildConsentFields(profile) {
|
|
|
739
911
|
}
|
|
740
912
|
return fields;
|
|
741
913
|
}
|
|
742
|
-
function
|
|
914
|
+
function renderPreview(fields) {
|
|
743
915
|
console.log("");
|
|
744
916
|
console.log("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
745
917
|
console.log("\u2502 terminalhire \u2014 sync your profile (Tier-1, opt-in) \u2502");
|
|
746
918
|
console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
747
919
|
console.log("");
|
|
748
|
-
console.log("
|
|
749
|
-
console.log("
|
|
920
|
+
console.log(" The following data will be shared with staqs (terminalhire.com)");
|
|
921
|
+
console.log(" AFTER you authorize + consent in the browser:");
|
|
750
922
|
console.log("");
|
|
751
923
|
for (const f of fields) {
|
|
752
924
|
const shown = Array.isArray(f.value) ? JSON.stringify(f.value) : String(f.value ?? "(not set)");
|
|
@@ -765,6 +937,30 @@ function renderConsentCard(fields) {
|
|
|
765
937
|
console.log(" This is NOT required to use terminalhire.");
|
|
766
938
|
console.log("");
|
|
767
939
|
}
|
|
940
|
+
function openInBrowser(url) {
|
|
941
|
+
let cmd;
|
|
942
|
+
let args;
|
|
943
|
+
if (process.platform === "darwin") {
|
|
944
|
+
cmd = "open";
|
|
945
|
+
args = [url];
|
|
946
|
+
} else if (process.platform === "win32") {
|
|
947
|
+
cmd = "cmd";
|
|
948
|
+
args = ["/c", "start", "", url];
|
|
949
|
+
} else {
|
|
950
|
+
cmd = "xdg-open";
|
|
951
|
+
args = [url];
|
|
952
|
+
}
|
|
953
|
+
try {
|
|
954
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
955
|
+
child.on("error", () => {
|
|
956
|
+
});
|
|
957
|
+
child.unref();
|
|
958
|
+
} catch {
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
function sleep(ms) {
|
|
962
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
963
|
+
}
|
|
768
964
|
async function runPush() {
|
|
769
965
|
const { readProfile: readProfile2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
|
|
770
966
|
const profile = await readProfile2();
|
|
@@ -776,15 +972,91 @@ async function runPush() {
|
|
|
776
972
|
process.exit(1);
|
|
777
973
|
}
|
|
778
974
|
const fields = buildConsentFields(profile);
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
975
|
+
renderPreview(fields);
|
|
976
|
+
await new Promise((resolve) => {
|
|
977
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
978
|
+
rl.question(
|
|
979
|
+
" Press Enter to open your browser to authorize + consent (or Ctrl-C to cancel)... ",
|
|
980
|
+
() => {
|
|
981
|
+
rl.close();
|
|
982
|
+
resolve();
|
|
983
|
+
}
|
|
984
|
+
);
|
|
985
|
+
});
|
|
986
|
+
console.log("");
|
|
987
|
+
console.log(" Starting browser verification...");
|
|
988
|
+
let begin;
|
|
989
|
+
try {
|
|
990
|
+
const r = await fetch(`${SYNC_BASE}/api/profile-sync/begin`, {
|
|
991
|
+
method: "POST",
|
|
992
|
+
headers: { "Content-Type": "application/json" },
|
|
993
|
+
body: JSON.stringify({ hostname: osHostname() }),
|
|
994
|
+
signal: AbortSignal.timeout(1e4)
|
|
995
|
+
});
|
|
996
|
+
if (!r.ok) {
|
|
997
|
+
let detail = "";
|
|
998
|
+
try {
|
|
999
|
+
detail = (await r.json())?.message || "";
|
|
1000
|
+
} catch {
|
|
1001
|
+
}
|
|
1002
|
+
console.error(`
|
|
1003
|
+
Could not start sync: /api/profile-sync/begin returned ${r.status}. ${detail}`);
|
|
1004
|
+
if (r.status === 503) console.error(" (Tier-1 sync is not enabled on the server yet.)");
|
|
1005
|
+
process.exit(1);
|
|
1006
|
+
}
|
|
1007
|
+
begin = await r.json();
|
|
1008
|
+
} catch (err) {
|
|
1009
|
+
console.error(`
|
|
1010
|
+
Could not start sync: ${err instanceof Error ? err.message : String(err)}`);
|
|
1011
|
+
process.exit(1);
|
|
1012
|
+
}
|
|
1013
|
+
const { challenge, verifyUrl } = begin || {};
|
|
1014
|
+
if (!challenge || !verifyUrl) {
|
|
1015
|
+
console.error("\n Could not start sync: malformed begin response.");
|
|
1016
|
+
process.exit(1);
|
|
1017
|
+
}
|
|
1018
|
+
console.log("");
|
|
1019
|
+
console.log(" Open this URL in your browser to authorize + consent:");
|
|
1020
|
+
console.log(` ${verifyUrl}`);
|
|
1021
|
+
console.log("");
|
|
1022
|
+
console.log(" (Attempting to open it automatically...)");
|
|
1023
|
+
openInBrowser(verifyUrl);
|
|
1024
|
+
console.log(" Waiting for browser verification...");
|
|
1025
|
+
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
1026
|
+
let proofToken = null;
|
|
1027
|
+
while (Date.now() < deadline) {
|
|
1028
|
+
await sleep(POLL_INTERVAL_MS);
|
|
1029
|
+
let statusRes;
|
|
1030
|
+
try {
|
|
1031
|
+
statusRes = await fetch(
|
|
1032
|
+
`${SYNC_BASE}/api/profile-sync/status?challenge=${encodeURIComponent(challenge)}`,
|
|
1033
|
+
{ signal: AbortSignal.timeout(1e4) }
|
|
1034
|
+
);
|
|
1035
|
+
} catch {
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
if (!statusRes.ok) {
|
|
1039
|
+
if (statusRes.status === 503) {
|
|
1040
|
+
console.error("\n Tier-1 sync is not enabled on the server yet.\n");
|
|
1041
|
+
process.exit(1);
|
|
1042
|
+
}
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
let body;
|
|
1046
|
+
try {
|
|
1047
|
+
body = await statusRes.json();
|
|
1048
|
+
} catch {
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
if (body && body.status === "verified" && body.proofToken) {
|
|
1052
|
+
proofToken = body.proofToken;
|
|
1053
|
+
break;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
if (!proofToken) {
|
|
1057
|
+
console.error("\n Timed out waiting for browser verification (10 min).");
|
|
1058
|
+
console.error(" Re-run `terminalhire sync --push` to try again.\n");
|
|
1059
|
+
process.exit(1);
|
|
788
1060
|
}
|
|
789
1061
|
const consentedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
790
1062
|
const consentToken = {
|
|
@@ -803,14 +1075,14 @@ async function runPush() {
|
|
|
803
1075
|
};
|
|
804
1076
|
const priorMarker = readMarker();
|
|
805
1077
|
const rowToken = priorMarker && priorMarker.deleteToken ? priorMarker.deleteToken : null;
|
|
806
|
-
const requestBody = { consentToken, profile: payloadProfile };
|
|
1078
|
+
const requestBody = { consentToken, profile: payloadProfile, proofToken };
|
|
807
1079
|
if (rowToken) {
|
|
808
1080
|
requestBody.rowToken = rowToken;
|
|
809
1081
|
}
|
|
810
|
-
console.log("\n Sending one-time snapshot...");
|
|
1082
|
+
console.log("\n Verified. Sending one-time snapshot...");
|
|
811
1083
|
let res;
|
|
812
1084
|
try {
|
|
813
|
-
res = await fetch(`${
|
|
1085
|
+
res = await fetch(`${SYNC_BASE}/api/profile-sync`, {
|
|
814
1086
|
method: "POST",
|
|
815
1087
|
headers: { "Content-Type": "application/json" },
|
|
816
1088
|
body: JSON.stringify(requestBody),
|
|
@@ -833,7 +1105,7 @@ async function runPush() {
|
|
|
833
1105
|
console.error(" (Tier-1 sync is not enabled on the server yet.)");
|
|
834
1106
|
}
|
|
835
1107
|
if (res.status === 403) {
|
|
836
|
-
console.error(" (
|
|
1108
|
+
console.error(" (Ownership proof rejected, expired, or already used \u2014 re-run sync --push.)");
|
|
837
1109
|
}
|
|
838
1110
|
process.exit(1);
|
|
839
1111
|
}
|