terminalhire 0.1.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.
@@ -0,0 +1,447 @@
1
+ // src/signal.ts
2
+ import { readFileSync as readFileSync2, readdirSync } from "fs";
3
+ import { execSync } from "child_process";
4
+ import { join as join2 } from "path";
5
+
6
+ // ../../packages/core/src/vocabulary.ts
7
+ var VOCABULARY = [
8
+ // Languages
9
+ "typescript",
10
+ "javascript",
11
+ "python",
12
+ "go",
13
+ "rust",
14
+ "java",
15
+ "ruby",
16
+ "elixir",
17
+ "scala",
18
+ "kotlin",
19
+ "swift",
20
+ "cpp",
21
+ "csharp",
22
+ "php",
23
+ "haskell",
24
+ "clojure",
25
+ "r",
26
+ // Frontend frameworks / libs
27
+ "react",
28
+ "nextjs",
29
+ "vue",
30
+ "nuxt",
31
+ "svelte",
32
+ "angular",
33
+ "solidjs",
34
+ "tailwind",
35
+ "css",
36
+ "html",
37
+ "graphql",
38
+ "trpc",
39
+ // Backend frameworks
40
+ "nodejs",
41
+ "express",
42
+ "fastify",
43
+ "nestjs",
44
+ "django",
45
+ "fastapi",
46
+ "flask",
47
+ "rails",
48
+ "spring",
49
+ "actix",
50
+ "gin",
51
+ "phoenix",
52
+ "laravel",
53
+ "dotnet",
54
+ // Infrastructure & DevOps
55
+ "kubernetes",
56
+ "docker",
57
+ "terraform",
58
+ "aws",
59
+ "gcp",
60
+ "azure",
61
+ "ci-cd",
62
+ "github-actions",
63
+ "linux",
64
+ "nginx",
65
+ "pulumi",
66
+ "ansible",
67
+ "prometheus",
68
+ "grafana",
69
+ "datadog",
70
+ "opentelemetry",
71
+ // Data & ML
72
+ "postgresql",
73
+ "mysql",
74
+ "sqlite",
75
+ "mongodb",
76
+ "redis",
77
+ "elasticsearch",
78
+ "kafka",
79
+ "rabbitmq",
80
+ "data-engineering",
81
+ "spark",
82
+ "airflow",
83
+ "dbt",
84
+ "ml",
85
+ "llm",
86
+ "pytorch",
87
+ "tensorflow",
88
+ "pandas",
89
+ "numpy",
90
+ // Domains / capabilities
91
+ "oauth",
92
+ "authentication",
93
+ "security",
94
+ "payments",
95
+ "billing",
96
+ "frontend",
97
+ "backend",
98
+ "devops",
99
+ "mobile",
100
+ "ios",
101
+ "android",
102
+ "api-design",
103
+ "microservices",
104
+ "websockets",
105
+ "testing",
106
+ "accessibility",
107
+ "seo",
108
+ "performance",
109
+ "observability",
110
+ "search",
111
+ "realtime"
112
+ ];
113
+ var SYNONYMS = {
114
+ // Kubernetes aliases
115
+ "k8s": "kubernetes",
116
+ "kube": "kubernetes",
117
+ // Auth / identity
118
+ "passport": "authentication",
119
+ "oauth2": "oauth",
120
+ "oidc": "oauth",
121
+ "jwt": "authentication",
122
+ "saml": "authentication",
123
+ "auth0": "authentication",
124
+ "clerk": "authentication",
125
+ "nextauth": "authentication",
126
+ // Payments
127
+ "@stripe/stripe-js": "payments",
128
+ "stripe": "payments",
129
+ "braintree": "payments",
130
+ "paddle": "payments",
131
+ "lemonsqueezy": "payments",
132
+ "recurly": "billing",
133
+ "chargebee": "billing",
134
+ // Framework / lib aliases
135
+ "next": "nextjs",
136
+ "next.js": "nextjs",
137
+ "nuxt.js": "nuxt",
138
+ "vue.js": "vue",
139
+ "angular.js": "angular",
140
+ "angularjs": "angular",
141
+ "express.js": "express",
142
+ "expressjs": "express",
143
+ "fastapi": "fastapi",
144
+ "nest": "nestjs",
145
+ "nest.js": "nestjs",
146
+ "sveltekit": "svelte",
147
+ // Language aliases
148
+ "ts": "typescript",
149
+ "js": "javascript",
150
+ "py": "python",
151
+ "golang": "go",
152
+ "c++": "cpp",
153
+ "c#": "csharp",
154
+ ".net": "dotnet",
155
+ "asp.net": "dotnet",
156
+ // DB aliases
157
+ "postgres": "postgresql",
158
+ "pg": "postgresql",
159
+ "mongo": "mongodb",
160
+ "elastic": "elasticsearch",
161
+ // Cloud aliases
162
+ "amazon web services": "aws",
163
+ "google cloud": "gcp",
164
+ "google cloud platform": "gcp",
165
+ "microsoft azure": "azure",
166
+ // CI/CD aliases
167
+ "github actions": "github-actions",
168
+ "circle ci": "ci-cd",
169
+ "circleci": "ci-cd",
170
+ "jenkins": "ci-cd",
171
+ "gitlab ci": "ci-cd",
172
+ "travis": "ci-cd",
173
+ // Mobile
174
+ "react native": "mobile",
175
+ "flutter": "mobile",
176
+ "expo": "mobile",
177
+ // AI / ML
178
+ "openai": "llm",
179
+ "anthropic": "llm",
180
+ "langchain": "llm",
181
+ "llamaindex": "llm",
182
+ "hugging face": "ml",
183
+ "huggingface": "ml",
184
+ "scikit-learn": "ml",
185
+ "sklearn": "ml",
186
+ // Data pipeline
187
+ "apache kafka": "kafka",
188
+ "apache spark": "spark",
189
+ "apache airflow": "airflow",
190
+ // Misc
191
+ "tailwindcss": "tailwind",
192
+ "tw": "tailwind",
193
+ "gql": "graphql",
194
+ "ws": "websockets",
195
+ "socket.io": "websockets",
196
+ "jest": "testing",
197
+ "vitest": "testing",
198
+ "playwright": "testing",
199
+ "cypress": "testing"
200
+ };
201
+ var VOCAB_SET = new Set(VOCABULARY);
202
+ function normalize(tokens) {
203
+ const result = /* @__PURE__ */ new Set();
204
+ for (const raw of tokens) {
205
+ const lower = raw.toLowerCase().trim();
206
+ if (VOCAB_SET.has(lower)) {
207
+ result.add(lower);
208
+ continue;
209
+ }
210
+ const mapped = SYNONYMS[lower];
211
+ if (mapped && VOCAB_SET.has(mapped)) {
212
+ result.add(mapped);
213
+ }
214
+ }
215
+ return Array.from(result);
216
+ }
217
+
218
+ // ../../packages/core/src/coastal.ts
219
+ import { readFileSync } from "fs";
220
+ import { join } from "path";
221
+ import { fileURLToPath } from "url";
222
+
223
+ // src/signal.ts
224
+ var LANGUAGE_TAGS = /* @__PURE__ */ new Set([
225
+ "typescript",
226
+ "javascript",
227
+ "python",
228
+ "go",
229
+ "rust",
230
+ "java",
231
+ "ruby",
232
+ "elixir",
233
+ "scala",
234
+ "kotlin",
235
+ "swift",
236
+ "cpp",
237
+ "csharp",
238
+ "php",
239
+ "haskell",
240
+ "clojure",
241
+ "r"
242
+ ]);
243
+ var EXT_MAP = {
244
+ ".ts": "typescript",
245
+ ".tsx": "typescript",
246
+ ".js": "javascript",
247
+ ".mjs": "javascript",
248
+ ".cjs": "javascript",
249
+ ".jsx": "javascript",
250
+ ".py": "python",
251
+ ".go": "go",
252
+ ".rs": "rust",
253
+ ".java": "java",
254
+ ".rb": "ruby",
255
+ ".ex": "elixir",
256
+ ".exs": "elixir",
257
+ ".scala": "scala",
258
+ ".kt": "kotlin",
259
+ ".swift": "swift",
260
+ ".cpp": "cpp",
261
+ ".cc": "cpp",
262
+ ".cxx": "cpp",
263
+ ".hpp": "cpp",
264
+ ".cs": "csharp",
265
+ ".php": "php",
266
+ ".hs": "haskell",
267
+ ".clj": "clojure",
268
+ ".cljs": "clojure",
269
+ ".r": "r",
270
+ ".vue": "vue",
271
+ ".svelte": "svelte"
272
+ };
273
+ var PERSONAL_GIT_HOSTS = /* @__PURE__ */ new Set([
274
+ "github.com",
275
+ "gitlab.com",
276
+ "bitbucket.org",
277
+ "codeberg.org",
278
+ "sr.ht"
279
+ ]);
280
+ var PERSONAL_EMAIL_DOMAINS = /* @__PURE__ */ new Set([
281
+ "gmail.com",
282
+ "googlemail.com",
283
+ "yahoo.com",
284
+ "outlook.com",
285
+ "hotmail.com",
286
+ "icloud.com",
287
+ "me.com",
288
+ "mac.com",
289
+ "proton.me",
290
+ "protonmail.com",
291
+ "fastmail.com",
292
+ "hey.com",
293
+ "duck.com"
294
+ ]);
295
+ function safeExec(cmd) {
296
+ try {
297
+ return execSync(cmd, { timeout: 2e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
298
+ } catch {
299
+ return "";
300
+ }
301
+ }
302
+ function isEmployerContext(cwd) {
303
+ const remote = safeExec('git -C "' + cwd + '" remote get-url origin 2>/dev/null');
304
+ if (remote) {
305
+ try {
306
+ const sshMatch = remote.match(/^git@([^:]+):/);
307
+ const httpsMatch = remote.match(/^https?:\/\/([^/]+)/);
308
+ const host = (sshMatch?.[1] ?? httpsMatch?.[1] ?? "").toLowerCase();
309
+ if (host && !PERSONAL_GIT_HOSTS.has(host)) {
310
+ return true;
311
+ }
312
+ } catch {
313
+ }
314
+ }
315
+ const email = safeExec('git -C "' + cwd + '" config user.email 2>/dev/null');
316
+ if (email) {
317
+ const domain = email.split("@")[1]?.toLowerCase() ?? "";
318
+ if (domain && !PERSONAL_EMAIL_DOMAINS.has(domain)) {
319
+ return true;
320
+ }
321
+ }
322
+ return false;
323
+ }
324
+ function readJsonSafe(path) {
325
+ try {
326
+ return JSON.parse(readFileSync2(path, "utf8"));
327
+ } catch {
328
+ return null;
329
+ }
330
+ }
331
+ function readFileSafe(path) {
332
+ try {
333
+ return readFileSync2(path, "utf8");
334
+ } catch {
335
+ return "";
336
+ }
337
+ }
338
+ function tokensFromPackageJson(cwd) {
339
+ const pkg = readJsonSafe(join2(cwd, "package.json"));
340
+ if (!pkg || typeof pkg !== "object") return [];
341
+ const p = pkg;
342
+ const deps = {
343
+ ...typeof p["dependencies"] === "object" ? p["dependencies"] : {},
344
+ ...typeof p["devDependencies"] === "object" ? p["devDependencies"] : {}
345
+ };
346
+ return Object.keys(deps);
347
+ }
348
+ function tokensFromRequirementsTxt(cwd) {
349
+ const content = readFileSafe(join2(cwd, "requirements.txt"));
350
+ if (!content) return [];
351
+ return content.split("\n").map((l) => l.trim().split(/[>=<!\[;]/)[0].trim().toLowerCase()).filter(Boolean);
352
+ }
353
+ function tokensFromGoMod(cwd) {
354
+ const content = readFileSafe(join2(cwd, "go.mod"));
355
+ if (!content) return ["go"];
356
+ const requires = Array.from(content.matchAll(/^\s+([^\s]+)\s+v/gm)).map((m) => m[1].split("/").pop() ?? "").filter(Boolean);
357
+ return ["go", ...requires];
358
+ }
359
+ function tokensFromCargoToml(cwd) {
360
+ const content = readFileSafe(join2(cwd, "Cargo.toml"));
361
+ if (!content) return [];
362
+ const deps = Array.from(content.matchAll(/^([a-zA-Z0-9_-]+)\s*=/gm)).map((m) => m[1].toLowerCase());
363
+ return ["rust", ...deps];
364
+ }
365
+ function tokensFromFileExtensions(cwd) {
366
+ const tokens = [];
367
+ const scanDirs = [cwd];
368
+ try {
369
+ const srcDir = join2(cwd, "src");
370
+ readdirSync(srcDir);
371
+ scanDirs.push(srcDir);
372
+ } catch {
373
+ }
374
+ for (const dir of scanDirs) {
375
+ try {
376
+ const entries = readdirSync(dir, { withFileTypes: true });
377
+ for (const e of entries) {
378
+ if (!e.isFile()) continue;
379
+ const dotIdx = e.name.lastIndexOf(".");
380
+ if (dotIdx === -1) continue;
381
+ const ext = e.name.slice(dotIdx).toLowerCase();
382
+ const tag = EXT_MAP[ext];
383
+ if (tag) tokens.push(tag);
384
+ }
385
+ } catch {
386
+ }
387
+ }
388
+ return tokens;
389
+ }
390
+ function inferSeniority(rawTokens) {
391
+ const seniorSignals = /* @__PURE__ */ new Set([
392
+ "kubernetes",
393
+ "terraform",
394
+ "pulumi",
395
+ "kafka",
396
+ "spark",
397
+ "airflow",
398
+ "dbt",
399
+ "opentelemetry",
400
+ "prometheus",
401
+ "grafana",
402
+ "microservices",
403
+ "api-design",
404
+ "security",
405
+ "oauth",
406
+ "payments"
407
+ ]);
408
+ const midSignals = /* @__PURE__ */ new Set([
409
+ "docker",
410
+ "ci-cd",
411
+ "github-actions",
412
+ "testing",
413
+ "postgresql",
414
+ "redis",
415
+ "graphql",
416
+ "trpc"
417
+ ]);
418
+ const normalized = new Set(normalize(rawTokens));
419
+ const seniorHits = [...normalized].filter((t) => seniorSignals.has(t)).length;
420
+ const midHits = [...normalized].filter((t) => midSignals.has(t)).length;
421
+ if (seniorHits >= 2) return "senior";
422
+ if (seniorHits >= 1 || midHits >= 2) return "mid";
423
+ return void 0;
424
+ }
425
+ function extractFingerprint(cwd) {
426
+ const employer = isEmployerContext(cwd);
427
+ const rawTokens = [
428
+ ...tokensFromPackageJson(cwd),
429
+ ...tokensFromRequirementsTxt(cwd),
430
+ ...tokensFromGoMod(cwd),
431
+ ...tokensFromCargoToml(cwd),
432
+ ...tokensFromFileExtensions(cwd)
433
+ ];
434
+ let skillTags = normalize(rawTokens);
435
+ if (employer) {
436
+ skillTags = skillTags.filter((t) => LANGUAGE_TAGS.has(t));
437
+ }
438
+ const seniorityBand = employer ? void 0 : inferSeniority(rawTokens);
439
+ return {
440
+ skillTags,
441
+ seniorityBand,
442
+ employerContext: employer
443
+ };
444
+ }
445
+ export {
446
+ extractFingerprint
447
+ };
@@ -0,0 +1,33 @@
1
+ {
2
+ "login": "janedev",
3
+ "name": "Jane Developer",
4
+ "publicEmail": "jane@example.com",
5
+ "avatarUrl": "https://avatars.githubusercontent.com/u/1234567?v=4",
6
+ "accountCreatedAt": "2016-03-14T09:22:00Z",
7
+ "publicRepos": 52,
8
+ "followers": 314,
9
+ "topLanguages": [
10
+ "typescript",
11
+ "python",
12
+ "go",
13
+ "rust",
14
+ "javascript"
15
+ ],
16
+ "topics": [
17
+ "react",
18
+ "nextjs",
19
+ "postgresql",
20
+ "docker",
21
+ "kubernetes",
22
+ "graphql",
23
+ "testing",
24
+ "llm",
25
+ "oauth",
26
+ "redis"
27
+ ],
28
+ "recentPRorgs": [
29
+ "vercel",
30
+ "prisma",
31
+ "trpc"
32
+ ]
33
+ }