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,815 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // ../../packages/core/src/types.ts
13
+ var init_types = __esm({
14
+ "../../packages/core/src/types.ts"() {
15
+ "use strict";
16
+ }
17
+ });
18
+
19
+ // ../../packages/core/src/vocabulary.ts
20
+ function normalize(tokens) {
21
+ const result = /* @__PURE__ */ new Set();
22
+ for (const raw of tokens) {
23
+ const lower = raw.toLowerCase().trim();
24
+ if (VOCAB_SET.has(lower)) {
25
+ result.add(lower);
26
+ continue;
27
+ }
28
+ const mapped = SYNONYMS[lower];
29
+ if (mapped && VOCAB_SET.has(mapped)) {
30
+ result.add(mapped);
31
+ }
32
+ }
33
+ return Array.from(result);
34
+ }
35
+ var VOCABULARY, SYNONYMS, VOCAB_SET;
36
+ var init_vocabulary = __esm({
37
+ "../../packages/core/src/vocabulary.ts"() {
38
+ "use strict";
39
+ VOCABULARY = [
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);
234
+ }
235
+ });
236
+
237
+ // ../../packages/core/src/matcher.ts
238
+ var init_matcher = __esm({
239
+ "../../packages/core/src/matcher.ts"() {
240
+ "use strict";
241
+ }
242
+ });
243
+
244
+ // ../../packages/core/src/feeds/greenhouse.ts
245
+ var init_greenhouse = __esm({
246
+ "../../packages/core/src/feeds/greenhouse.ts"() {
247
+ "use strict";
248
+ init_vocabulary();
249
+ }
250
+ });
251
+
252
+ // ../../packages/core/src/feeds/ashby.ts
253
+ var init_ashby = __esm({
254
+ "../../packages/core/src/feeds/ashby.ts"() {
255
+ "use strict";
256
+ init_vocabulary();
257
+ }
258
+ });
259
+
260
+ // ../../packages/core/src/feeds/himalayas.ts
261
+ var init_himalayas = __esm({
262
+ "../../packages/core/src/feeds/himalayas.ts"() {
263
+ "use strict";
264
+ init_vocabulary();
265
+ }
266
+ });
267
+
268
+ // ../../packages/core/src/feeds/wwr.ts
269
+ var init_wwr = __esm({
270
+ "../../packages/core/src/feeds/wwr.ts"() {
271
+ "use strict";
272
+ init_vocabulary();
273
+ }
274
+ });
275
+
276
+ // ../../packages/core/src/feeds/hn.ts
277
+ var init_hn = __esm({
278
+ "../../packages/core/src/feeds/hn.ts"() {
279
+ "use strict";
280
+ init_vocabulary();
281
+ }
282
+ });
283
+
284
+ // ../../packages/core/src/feeds/index.ts
285
+ var init_feeds = __esm({
286
+ "../../packages/core/src/feeds/index.ts"() {
287
+ "use strict";
288
+ init_greenhouse();
289
+ init_ashby();
290
+ init_himalayas();
291
+ init_wwr();
292
+ init_hn();
293
+ }
294
+ });
295
+
296
+ // ../../packages/core/src/coastal.ts
297
+ import { readFileSync } from "fs";
298
+ import { join } from "path";
299
+ import { fileURLToPath } from "url";
300
+ var init_coastal = __esm({
301
+ "../../packages/core/src/coastal.ts"() {
302
+ "use strict";
303
+ }
304
+ });
305
+
306
+ // ../../packages/core/src/indexer.ts
307
+ var init_indexer = __esm({
308
+ "../../packages/core/src/indexer.ts"() {
309
+ "use strict";
310
+ init_feeds();
311
+ init_coastal();
312
+ }
313
+ });
314
+
315
+ // ../../packages/core/src/github.ts
316
+ var init_github = __esm({
317
+ "../../packages/core/src/github.ts"() {
318
+ "use strict";
319
+ init_vocabulary();
320
+ }
321
+ });
322
+
323
+ // ../../packages/core/src/index.ts
324
+ var init_src = __esm({
325
+ "../../packages/core/src/index.ts"() {
326
+ "use strict";
327
+ init_types();
328
+ init_vocabulary();
329
+ init_matcher();
330
+ init_feeds();
331
+ init_indexer();
332
+ init_coastal();
333
+ init_github();
334
+ }
335
+ });
336
+
337
+ // src/signal.ts
338
+ var signal_exports = {};
339
+ __export(signal_exports, {
340
+ extractFingerprint: () => extractFingerprint
341
+ });
342
+ import { readFileSync as readFileSync2, readdirSync } from "fs";
343
+ import { execSync } from "child_process";
344
+ import { join as join2 } from "path";
345
+ function safeExec(cmd) {
346
+ try {
347
+ return execSync(cmd, { timeout: 2e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
348
+ } catch {
349
+ return "";
350
+ }
351
+ }
352
+ function isEmployerContext(cwd) {
353
+ const remote = safeExec('git -C "' + cwd + '" remote get-url origin 2>/dev/null');
354
+ if (remote) {
355
+ try {
356
+ const sshMatch = remote.match(/^git@([^:]+):/);
357
+ const httpsMatch = remote.match(/^https?:\/\/([^/]+)/);
358
+ const host = (sshMatch?.[1] ?? httpsMatch?.[1] ?? "").toLowerCase();
359
+ if (host && !PERSONAL_GIT_HOSTS.has(host)) {
360
+ return true;
361
+ }
362
+ } catch {
363
+ }
364
+ }
365
+ const email = safeExec('git -C "' + cwd + '" config user.email 2>/dev/null');
366
+ if (email) {
367
+ const domain = email.split("@")[1]?.toLowerCase() ?? "";
368
+ if (domain && !PERSONAL_EMAIL_DOMAINS.has(domain)) {
369
+ return true;
370
+ }
371
+ }
372
+ return false;
373
+ }
374
+ function readJsonSafe(path) {
375
+ try {
376
+ return JSON.parse(readFileSync2(path, "utf8"));
377
+ } catch {
378
+ return null;
379
+ }
380
+ }
381
+ function readFileSafe(path) {
382
+ try {
383
+ return readFileSync2(path, "utf8");
384
+ } catch {
385
+ return "";
386
+ }
387
+ }
388
+ function tokensFromPackageJson(cwd) {
389
+ const pkg = readJsonSafe(join2(cwd, "package.json"));
390
+ if (!pkg || typeof pkg !== "object") return [];
391
+ const p = pkg;
392
+ const deps = {
393
+ ...typeof p["dependencies"] === "object" ? p["dependencies"] : {},
394
+ ...typeof p["devDependencies"] === "object" ? p["devDependencies"] : {}
395
+ };
396
+ return Object.keys(deps);
397
+ }
398
+ function tokensFromRequirementsTxt(cwd) {
399
+ const content = readFileSafe(join2(cwd, "requirements.txt"));
400
+ if (!content) return [];
401
+ return content.split("\n").map((l) => l.trim().split(/[>=<!\[;]/)[0].trim().toLowerCase()).filter(Boolean);
402
+ }
403
+ function tokensFromGoMod(cwd) {
404
+ const content = readFileSafe(join2(cwd, "go.mod"));
405
+ if (!content) return ["go"];
406
+ const requires = Array.from(content.matchAll(/^\s+([^\s]+)\s+v/gm)).map((m) => m[1].split("/").pop() ?? "").filter(Boolean);
407
+ return ["go", ...requires];
408
+ }
409
+ function tokensFromCargoToml(cwd) {
410
+ const content = readFileSafe(join2(cwd, "Cargo.toml"));
411
+ if (!content) return [];
412
+ const deps = Array.from(content.matchAll(/^([a-zA-Z0-9_-]+)\s*=/gm)).map((m) => m[1].toLowerCase());
413
+ return ["rust", ...deps];
414
+ }
415
+ function tokensFromFileExtensions(cwd) {
416
+ const tokens = [];
417
+ const scanDirs = [cwd];
418
+ try {
419
+ const srcDir = join2(cwd, "src");
420
+ readdirSync(srcDir);
421
+ scanDirs.push(srcDir);
422
+ } catch {
423
+ }
424
+ for (const dir of scanDirs) {
425
+ try {
426
+ const entries = readdirSync(dir, { withFileTypes: true });
427
+ for (const e of entries) {
428
+ if (!e.isFile()) continue;
429
+ const dotIdx = e.name.lastIndexOf(".");
430
+ if (dotIdx === -1) continue;
431
+ const ext = e.name.slice(dotIdx).toLowerCase();
432
+ const tag = EXT_MAP[ext];
433
+ if (tag) tokens.push(tag);
434
+ }
435
+ } catch {
436
+ }
437
+ }
438
+ return tokens;
439
+ }
440
+ function inferSeniority(rawTokens) {
441
+ const seniorSignals = /* @__PURE__ */ new Set([
442
+ "kubernetes",
443
+ "terraform",
444
+ "pulumi",
445
+ "kafka",
446
+ "spark",
447
+ "airflow",
448
+ "dbt",
449
+ "opentelemetry",
450
+ "prometheus",
451
+ "grafana",
452
+ "microservices",
453
+ "api-design",
454
+ "security",
455
+ "oauth",
456
+ "payments"
457
+ ]);
458
+ const midSignals = /* @__PURE__ */ new Set([
459
+ "docker",
460
+ "ci-cd",
461
+ "github-actions",
462
+ "testing",
463
+ "postgresql",
464
+ "redis",
465
+ "graphql",
466
+ "trpc"
467
+ ]);
468
+ const normalized = new Set(normalize(rawTokens));
469
+ const seniorHits = [...normalized].filter((t) => seniorSignals.has(t)).length;
470
+ const midHits = [...normalized].filter((t) => midSignals.has(t)).length;
471
+ if (seniorHits >= 2) return "senior";
472
+ if (seniorHits >= 1 || midHits >= 2) return "mid";
473
+ return void 0;
474
+ }
475
+ function extractFingerprint(cwd) {
476
+ const employer = isEmployerContext(cwd);
477
+ const rawTokens = [
478
+ ...tokensFromPackageJson(cwd),
479
+ ...tokensFromRequirementsTxt(cwd),
480
+ ...tokensFromGoMod(cwd),
481
+ ...tokensFromCargoToml(cwd),
482
+ ...tokensFromFileExtensions(cwd)
483
+ ];
484
+ let skillTags = normalize(rawTokens);
485
+ if (employer) {
486
+ skillTags = skillTags.filter((t) => LANGUAGE_TAGS.has(t));
487
+ }
488
+ const seniorityBand = employer ? void 0 : inferSeniority(rawTokens);
489
+ return {
490
+ skillTags,
491
+ seniorityBand,
492
+ employerContext: employer
493
+ };
494
+ }
495
+ var LANGUAGE_TAGS, EXT_MAP, PERSONAL_GIT_HOSTS, PERSONAL_EMAIL_DOMAINS;
496
+ var init_signal = __esm({
497
+ "src/signal.ts"() {
498
+ "use strict";
499
+ init_src();
500
+ LANGUAGE_TAGS = /* @__PURE__ */ new Set([
501
+ "typescript",
502
+ "javascript",
503
+ "python",
504
+ "go",
505
+ "rust",
506
+ "java",
507
+ "ruby",
508
+ "elixir",
509
+ "scala",
510
+ "kotlin",
511
+ "swift",
512
+ "cpp",
513
+ "csharp",
514
+ "php",
515
+ "haskell",
516
+ "clojure",
517
+ "r"
518
+ ]);
519
+ EXT_MAP = {
520
+ ".ts": "typescript",
521
+ ".tsx": "typescript",
522
+ ".js": "javascript",
523
+ ".mjs": "javascript",
524
+ ".cjs": "javascript",
525
+ ".jsx": "javascript",
526
+ ".py": "python",
527
+ ".go": "go",
528
+ ".rs": "rust",
529
+ ".java": "java",
530
+ ".rb": "ruby",
531
+ ".ex": "elixir",
532
+ ".exs": "elixir",
533
+ ".scala": "scala",
534
+ ".kt": "kotlin",
535
+ ".swift": "swift",
536
+ ".cpp": "cpp",
537
+ ".cc": "cpp",
538
+ ".cxx": "cpp",
539
+ ".hpp": "cpp",
540
+ ".cs": "csharp",
541
+ ".php": "php",
542
+ ".hs": "haskell",
543
+ ".clj": "clojure",
544
+ ".cljs": "clojure",
545
+ ".r": "r",
546
+ ".vue": "vue",
547
+ ".svelte": "svelte"
548
+ };
549
+ PERSONAL_GIT_HOSTS = /* @__PURE__ */ new Set([
550
+ "github.com",
551
+ "gitlab.com",
552
+ "bitbucket.org",
553
+ "codeberg.org",
554
+ "sr.ht"
555
+ ]);
556
+ PERSONAL_EMAIL_DOMAINS = /* @__PURE__ */ new Set([
557
+ "gmail.com",
558
+ "googlemail.com",
559
+ "yahoo.com",
560
+ "outlook.com",
561
+ "hotmail.com",
562
+ "icloud.com",
563
+ "me.com",
564
+ "mac.com",
565
+ "proton.me",
566
+ "protonmail.com",
567
+ "fastmail.com",
568
+ "hey.com",
569
+ "duck.com"
570
+ ]);
571
+ }
572
+ });
573
+
574
+ // src/profile.ts
575
+ var profile_exports = {};
576
+ __export(profile_exports, {
577
+ accumulateGitHubTags: () => accumulateGitHubTags,
578
+ accumulateSession: () => accumulateSession,
579
+ accumulateTags: () => accumulateTags,
580
+ deleteProfile: () => deleteProfile,
581
+ profileToFingerprint: () => profileToFingerprint,
582
+ readProfile: () => readProfile,
583
+ writeProfile: () => writeProfile
584
+ });
585
+ import {
586
+ createCipheriv,
587
+ createDecipheriv,
588
+ randomBytes
589
+ } from "crypto";
590
+ import {
591
+ readFileSync as readFileSync3,
592
+ writeFileSync,
593
+ mkdirSync,
594
+ existsSync
595
+ } from "fs";
596
+ import { join as join3 } from "path";
597
+ import { homedir } from "os";
598
+ async function loadKey() {
599
+ try {
600
+ const kt = await import("keytar");
601
+ const stored = await kt.getPassword("terminalhire", "profile-key");
602
+ if (stored) {
603
+ return Buffer.from(stored, "hex");
604
+ }
605
+ const key2 = randomBytes(KEY_BYTES);
606
+ await kt.setPassword("terminalhire", "profile-key", key2.toString("hex"));
607
+ return key2;
608
+ } catch {
609
+ }
610
+ mkdirSync(TERMINALHIRE_DIR, { recursive: true });
611
+ if (existsSync(KEY_FILE)) {
612
+ return Buffer.from(readFileSync3(KEY_FILE, "utf8").trim(), "hex");
613
+ }
614
+ const key = randomBytes(KEY_BYTES);
615
+ writeFileSync(KEY_FILE, key.toString("hex"), { mode: 384, encoding: "utf8" });
616
+ return key;
617
+ }
618
+ function encrypt(plaintext, key) {
619
+ const iv = randomBytes(IV_BYTES);
620
+ const cipher = createCipheriv(ALGO, key, iv);
621
+ const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
622
+ const tag = cipher.getAuthTag();
623
+ return {
624
+ iv: iv.toString("hex"),
625
+ tag: tag.toString("hex"),
626
+ ciphertext: ct.toString("hex")
627
+ };
628
+ }
629
+ function decrypt(blob, key) {
630
+ const decipher = createDecipheriv(
631
+ ALGO,
632
+ key,
633
+ Buffer.from(blob.iv, "hex")
634
+ );
635
+ decipher.setAuthTag(Buffer.from(blob.tag, "hex"));
636
+ const plain = Buffer.concat([
637
+ decipher.update(Buffer.from(blob.ciphertext, "hex")),
638
+ decipher.final()
639
+ ]);
640
+ return plain.toString("utf8");
641
+ }
642
+ function blankProfile() {
643
+ return {
644
+ version: 3,
645
+ skillTags: [],
646
+ tagWeights: {},
647
+ hasEmployerSessions: false,
648
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
649
+ };
650
+ }
651
+ function recencyDecay(lastSeen) {
652
+ const ageMs = Date.now() - new Date(lastSeen).getTime();
653
+ return Math.pow(0.5, ageMs / DECAY_HALF_LIFE_MS);
654
+ }
655
+ function tagScore(w) {
656
+ return w.count * recencyDecay(w.lastSeen);
657
+ }
658
+ function deriveSkillTags(tagWeights) {
659
+ return Object.entries(tagWeights).filter(([, w]) => w.count >= 1).sort(([, a], [, b]) => tagScore(b) - tagScore(a)).map(([tag]) => tag);
660
+ }
661
+ function migrateTagWeights(profile) {
662
+ if (!profile.tagWeights) {
663
+ profile.tagWeights = {};
664
+ }
665
+ const now = (/* @__PURE__ */ new Date()).toISOString();
666
+ for (const tag of profile.skillTags) {
667
+ if (!profile.tagWeights[tag]) {
668
+ profile.tagWeights[tag] = { count: 1, firstSeen: now, lastSeen: now, sessions: 1 };
669
+ }
670
+ }
671
+ }
672
+ async function readProfile() {
673
+ if (!existsSync(PROFILE_FILE)) return blankProfile();
674
+ try {
675
+ const key = await loadKey();
676
+ const raw = readFileSync3(PROFILE_FILE, "utf8");
677
+ const blob = JSON.parse(raw);
678
+ const plaintext = decrypt(blob, key);
679
+ const parsed = JSON.parse(plaintext);
680
+ migrateTagWeights(parsed);
681
+ return parsed;
682
+ } catch {
683
+ return blankProfile();
684
+ }
685
+ }
686
+ async function writeProfile(profile) {
687
+ mkdirSync(TERMINALHIRE_DIR, { recursive: true });
688
+ const key = await loadKey();
689
+ profile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
690
+ profile.skillTags = deriveSkillTags(profile.tagWeights);
691
+ const blob = encrypt(JSON.stringify(profile), key);
692
+ writeFileSync(PROFILE_FILE, JSON.stringify(blob, null, 2), { encoding: "utf8" });
693
+ }
694
+ function accumulateSession(profile, tags, isEmployerContext2, inferredSeniority) {
695
+ const now = (/* @__PURE__ */ new Date()).toISOString();
696
+ let filtered = normalize(tags);
697
+ if (isEmployerContext2) {
698
+ filtered = filtered.filter((t) => LANGUAGE_TAGS2.has(t));
699
+ profile.hasEmployerSessions = true;
700
+ }
701
+ for (const tag of filtered) {
702
+ const existing = profile.tagWeights[tag];
703
+ if (existing) {
704
+ existing.count += 1;
705
+ existing.sessions += 1;
706
+ existing.lastSeen = now;
707
+ } else {
708
+ profile.tagWeights[tag] = { count: 1, firstSeen: now, lastSeen: now, sessions: 1 };
709
+ }
710
+ }
711
+ if (inferredSeniority && !isEmployerContext2) {
712
+ profile.seniority = inferredSeniority;
713
+ }
714
+ }
715
+ async function accumulateTags(rawTokens, isEmployerContext2, inferredSeniority) {
716
+ const profile = await readProfile();
717
+ accumulateSession(profile, rawTokens, isEmployerContext2, inferredSeniority);
718
+ await writeProfile(profile);
719
+ }
720
+ function accumulateGitHubTags(profile, tags) {
721
+ accumulateSession(
722
+ profile,
723
+ tags,
724
+ /* isEmployerContext */
725
+ false
726
+ );
727
+ }
728
+ async function deleteProfile() {
729
+ const { rmSync } = await import("fs");
730
+ try {
731
+ rmSync(PROFILE_FILE);
732
+ } catch {
733
+ }
734
+ try {
735
+ rmSync(KEY_FILE);
736
+ } catch {
737
+ }
738
+ }
739
+ function profileToFingerprint(profile) {
740
+ 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);
741
+ const skillTags = rankedTags.length > 0 ? rankedTags : profile.skillTags;
742
+ return {
743
+ skillTags,
744
+ seniorityBand: profile.seniority,
745
+ prefs: {
746
+ roleTypes: profile.roleTypes,
747
+ remoteOnly: profile.remoteOnly,
748
+ compFloorUsd: profile.compFloorUsd
749
+ }
750
+ };
751
+ }
752
+ var TERMINALHIRE_DIR, PROFILE_FILE, KEY_FILE, ALGO, KEY_BYTES, IV_BYTES, DECAY_HALF_LIFE_MS, LANGUAGE_TAGS2, MIN_FINGERPRINT_SCORE;
753
+ var init_profile = __esm({
754
+ "src/profile.ts"() {
755
+ "use strict";
756
+ init_src();
757
+ TERMINALHIRE_DIR = join3(homedir(), ".terminalhire");
758
+ PROFILE_FILE = join3(TERMINALHIRE_DIR, "profile.enc");
759
+ KEY_FILE = join3(TERMINALHIRE_DIR, "key");
760
+ ALGO = "aes-256-gcm";
761
+ KEY_BYTES = 32;
762
+ IV_BYTES = 12;
763
+ DECAY_HALF_LIFE_MS = 30 * 24 * 60 * 60 * 1e3;
764
+ LANGUAGE_TAGS2 = /* @__PURE__ */ new Set([
765
+ "typescript",
766
+ "javascript",
767
+ "python",
768
+ "go",
769
+ "rust",
770
+ "java",
771
+ "ruby",
772
+ "elixir",
773
+ "scala",
774
+ "kotlin",
775
+ "swift",
776
+ "cpp",
777
+ "csharp",
778
+ "php",
779
+ "haskell",
780
+ "clojure",
781
+ "r"
782
+ ]);
783
+ MIN_FINGERPRINT_SCORE = 0.05;
784
+ }
785
+ });
786
+
787
+ // bin/jpi-learn.js
788
+ async function run() {
789
+ try {
790
+ const args = process.argv.slice(2);
791
+ const cwdIdx = args.indexOf("--cwd");
792
+ const cwd = cwdIdx !== -1 && args[cwdIdx + 1] ? args[cwdIdx + 1] : process.cwd();
793
+ const { extractFingerprint: extractFingerprint2 } = await Promise.resolve().then(() => (init_signal(), signal_exports));
794
+ const { readProfile: readProfile2, writeProfile: writeProfile2, accumulateSession: accumulateSession2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
795
+ const fingerprint = extractFingerprint2(cwd);
796
+ const profile = await readProfile2();
797
+ accumulateSession2(
798
+ profile,
799
+ fingerprint.skillTags,
800
+ fingerprint.employerContext,
801
+ fingerprint.seniorityBand
802
+ );
803
+ await writeProfile2(profile);
804
+ process.exit(0);
805
+ } catch {
806
+ process.exit(0);
807
+ }
808
+ }
809
+ var isMain = process.argv[1]?.endsWith("jpi-learn.js") || process.argv[1]?.endsWith("jpi-learn");
810
+ if (isMain) {
811
+ run();
812
+ }
813
+ export {
814
+ run
815
+ };