terminalhire 0.7.0 → 0.8.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.
@@ -2800,6 +2800,15 @@ var init_intro = __esm({
2800
2800
  }
2801
2801
  });
2802
2802
 
2803
+ // ../../packages/core/src/directoryThreshold.ts
2804
+ var STRONG_MATCH_THRESHOLD;
2805
+ var init_directoryThreshold = __esm({
2806
+ "../../packages/core/src/directoryThreshold.ts"() {
2807
+ "use strict";
2808
+ STRONG_MATCH_THRESHOLD = 0.58;
2809
+ }
2810
+ });
2811
+
2803
2812
  // ../../packages/core/src/index.ts
2804
2813
  var src_exports = {};
2805
2814
  __export(src_exports, {
@@ -2819,6 +2828,7 @@ __export(src_exports, {
2819
2828
  INTRO_ALLOWED_FIELDS: () => INTRO_ALLOWED_FIELDS,
2820
2829
  INTRO_PENDING_TTL_MS: () => INTRO_PENDING_TTL_MS,
2821
2830
  LEVER_SLUGS_BY_TIER: () => LEVER_SLUGS_BY_TIER,
2831
+ STRONG_MATCH_THRESHOLD: () => STRONG_MATCH_THRESHOLD,
2822
2832
  SYNONYMS: () => SYNONYMS,
2823
2833
  VOCABULARY: () => VOCABULARY,
2824
2834
  VOCAB_NODES: () => VOCAB_NODES,
@@ -2887,6 +2897,7 @@ var init_src = __esm({
2887
2897
  init_partners();
2888
2898
  init_github();
2889
2899
  init_intro();
2900
+ init_directoryThreshold();
2890
2901
  }
2891
2902
  });
2892
2903
 
@@ -1,25 +1,40 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // bin/jpi-config.js
4
+ import { join as join2 } from "path";
5
+ import { homedir as homedir2 } from "os";
6
+
7
+ // src/config.ts
4
8
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
5
9
  import { join } from "path";
6
10
  import { homedir } from "os";
7
11
  var TERMINALHIRE_DIR = join(homedir(), ".terminalhire");
8
12
  var CONFIG_FILE = join(TERMINALHIRE_DIR, "config.json");
9
- var DEFAULT_CONFIG = { nudge: "session" };
13
+ var DEFAULT_CONFIG = {
14
+ nudge: "session",
15
+ peerConnect: false,
16
+ peerConnectPrompted: false
17
+ };
10
18
  function readConfig() {
11
19
  try {
12
20
  if (!existsSync(CONFIG_FILE)) return { ...DEFAULT_CONFIG };
13
- return { ...DEFAULT_CONFIG, ...JSON.parse(readFileSync(CONFIG_FILE, "utf8")) };
21
+ const raw = readFileSync(CONFIG_FILE, "utf8");
22
+ const parsed = JSON.parse(raw);
23
+ return { ...DEFAULT_CONFIG, ...parsed };
14
24
  } catch {
15
25
  return { ...DEFAULT_CONFIG };
16
26
  }
17
27
  }
18
- function writeConfig(patch) {
28
+ function writeConfig(config) {
19
29
  mkdirSync(TERMINALHIRE_DIR, { recursive: true });
20
- const merged = { ...readConfig(), ...patch };
30
+ const current = readConfig();
31
+ const merged = { ...current, ...config };
21
32
  writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n", "utf8");
22
33
  }
34
+
35
+ // bin/jpi-config.js
36
+ var TERMINALHIRE_DIR2 = join2(homedir2(), ".terminalhire");
37
+ var CONFIG_FILE2 = join2(TERMINALHIRE_DIR2, "config.json");
23
38
  function parseNudgeMode(raw) {
24
39
  if (raw === "session" || raw === "always") return raw;
25
40
  const m = /^every:(\d+)$/.exec(raw);
@@ -39,13 +54,18 @@ async function run() {
39
54
  if (envOverride) {
40
55
  console.log(` (overridden by TERMINALHIRE_NUDGE=${envOverride} at runtime)`);
41
56
  }
42
- console.log(` config file: ${CONFIG_FILE}`);
57
+ console.log(` peer-connect: ${cfg.peerConnect ? "on" : "off"} (ambient peer & founder surfacing; default off)`);
58
+ console.log(` config file: ${CONFIG_FILE2}`);
43
59
  console.log("");
44
60
  console.log(" Valid nudge values:");
45
61
  console.log(" session \u2014 print at most once per Claude Code session (default)");
46
62
  console.log(" always \u2014 print every statusLine render when matches exist");
47
63
  console.log(" every:N \u2014 print every Nth render (e.g. every:3)");
48
64
  console.log("");
65
+ console.log(" Peer-connect (--connect on|off):");
66
+ console.log(" on \u2014 surface peers & founders in the spinner + send an anonymous matched signal");
67
+ console.log(" off \u2014 no peer matching, no directory fetch, no signal (default)");
68
+ console.log("");
49
69
  return;
50
70
  }
51
71
  const nudgeIdx = filtered.indexOf("--nudge");
@@ -62,10 +82,23 @@ async function run() {
62
82
  }
63
83
  writeConfig({ nudge: parsed });
64
84
  console.log(` nudge set to: ${parsed}`);
65
- console.log(` (saved to ${CONFIG_FILE})`);
85
+ console.log(` (saved to ${CONFIG_FILE2})`);
86
+ return;
87
+ }
88
+ const connectIdx = filtered.indexOf("--connect");
89
+ if (connectIdx !== -1) {
90
+ const value = filtered[connectIdx + 1];
91
+ if (value !== "on" && value !== "off") {
92
+ console.error("Error: --connect requires a value: on | off");
93
+ process.exit(1);
94
+ }
95
+ writeConfig({ peerConnect: value === "on", peerConnectPrompted: true });
96
+ console.log(` peer-connect set to: ${value}`);
97
+ console.log(` (saved to ${CONFIG_FILE2})`);
66
98
  return;
67
99
  }
68
100
  console.error("Usage: terminalhire config --nudge <session|always|every:N>");
101
+ console.error(" terminalhire config --connect <on|off>");
69
102
  console.error(" terminalhire config --show");
70
103
  process.exit(1);
71
104
  }
@@ -2543,21 +2543,21 @@ var init_feeds = __esm({
2543
2543
  });
2544
2544
 
2545
2545
  // ../../packages/core/src/partners.ts
2546
- import { readFileSync } from "fs";
2547
- import { join } from "path";
2546
+ import { readFileSync as readFileSync2 } from "fs";
2547
+ import { join as join2 } from "path";
2548
2548
  import { fileURLToPath } from "url";
2549
2549
  function resolveDataPath() {
2550
2550
  try {
2551
2551
  const dir = fileURLToPath(new URL("../../../data", import.meta.url));
2552
- return join(dir, "partner-roles.json");
2552
+ return join2(dir, "partner-roles.json");
2553
2553
  } catch {
2554
- return join(process.cwd(), "data", "partner-roles.json");
2554
+ return join2(process.cwd(), "data", "partner-roles.json");
2555
2555
  }
2556
2556
  }
2557
2557
  function loadPartnerRoles() {
2558
2558
  const filePath = resolveDataPath();
2559
2559
  try {
2560
- const raw = readFileSync(filePath, "utf-8");
2560
+ const raw = readFileSync2(filePath, "utf-8");
2561
2561
  const parsed = JSON.parse(raw);
2562
2562
  if (!Array.isArray(parsed)) {
2563
2563
  console.warn("[partners] partner-roles.json is not an array \u2014 skipping");
@@ -2800,6 +2800,15 @@ var init_intro = __esm({
2800
2800
  }
2801
2801
  });
2802
2802
 
2803
+ // ../../packages/core/src/directoryThreshold.ts
2804
+ var STRONG_MATCH_THRESHOLD;
2805
+ var init_directoryThreshold = __esm({
2806
+ "../../packages/core/src/directoryThreshold.ts"() {
2807
+ "use strict";
2808
+ STRONG_MATCH_THRESHOLD = 0.58;
2809
+ }
2810
+ });
2811
+
2803
2812
  // ../../packages/core/src/index.ts
2804
2813
  var src_exports = {};
2805
2814
  __export(src_exports, {
@@ -2819,6 +2828,7 @@ __export(src_exports, {
2819
2828
  INTRO_ALLOWED_FIELDS: () => INTRO_ALLOWED_FIELDS,
2820
2829
  INTRO_PENDING_TTL_MS: () => INTRO_PENDING_TTL_MS,
2821
2830
  LEVER_SLUGS_BY_TIER: () => LEVER_SLUGS_BY_TIER,
2831
+ STRONG_MATCH_THRESHOLD: () => STRONG_MATCH_THRESHOLD,
2822
2832
  SYNONYMS: () => SYNONYMS,
2823
2833
  VOCABULARY: () => VOCABULARY,
2824
2834
  VOCAB_NODES: () => VOCAB_NODES,
@@ -2887,6 +2897,7 @@ var init_src = __esm({
2887
2897
  init_partners();
2888
2898
  init_github();
2889
2899
  init_intro();
2900
+ init_directoryThreshold();
2890
2901
  }
2891
2902
  });
2892
2903
 
@@ -2910,13 +2921,13 @@ import {
2910
2921
  randomBytes
2911
2922
  } from "crypto";
2912
2923
  import {
2913
- readFileSync as readFileSync2,
2914
- writeFileSync,
2915
- mkdirSync,
2924
+ readFileSync as readFileSync3,
2925
+ writeFileSync as writeFileSync2,
2926
+ mkdirSync as mkdirSync2,
2916
2927
  existsSync
2917
2928
  } from "fs";
2918
- import { join as join2 } from "path";
2919
- import { homedir } from "os";
2929
+ import { join as join3 } from "path";
2930
+ import { homedir as homedir2 } from "os";
2920
2931
  async function loadKey() {
2921
2932
  try {
2922
2933
  const kt = await import("keytar");
@@ -2929,12 +2940,12 @@ async function loadKey() {
2929
2940
  return key2;
2930
2941
  } catch {
2931
2942
  }
2932
- mkdirSync(TERMINALHIRE_DIR, { recursive: true });
2943
+ mkdirSync2(TERMINALHIRE_DIR2, { recursive: true });
2933
2944
  if (existsSync(KEY_FILE)) {
2934
- return Buffer.from(readFileSync2(KEY_FILE, "utf8").trim(), "hex");
2945
+ return Buffer.from(readFileSync3(KEY_FILE, "utf8").trim(), "hex");
2935
2946
  }
2936
2947
  const key = randomBytes(KEY_BYTES);
2937
- writeFileSync(KEY_FILE, key.toString("hex"), { mode: 384, encoding: "utf8" });
2948
+ writeFileSync2(KEY_FILE, key.toString("hex"), { mode: 384, encoding: "utf8" });
2938
2949
  return key;
2939
2950
  }
2940
2951
  function encrypt(plaintext, key) {
@@ -2995,7 +3006,7 @@ async function readProfile() {
2995
3006
  if (!existsSync(PROFILE_FILE)) return blankProfile();
2996
3007
  try {
2997
3008
  const key = await loadKey();
2998
- const raw = readFileSync2(PROFILE_FILE, "utf8");
3009
+ const raw = readFileSync3(PROFILE_FILE, "utf8");
2999
3010
  const blob = JSON.parse(raw);
3000
3011
  const plaintext = decrypt(blob, key);
3001
3012
  const parsed = JSON.parse(plaintext);
@@ -3006,12 +3017,12 @@ async function readProfile() {
3006
3017
  }
3007
3018
  }
3008
3019
  async function writeProfile(profile) {
3009
- mkdirSync(TERMINALHIRE_DIR, { recursive: true });
3020
+ mkdirSync2(TERMINALHIRE_DIR2, { recursive: true });
3010
3021
  const key = await loadKey();
3011
3022
  profile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3012
3023
  profile.skillTags = deriveSkillTags(profile.tagWeights);
3013
3024
  const blob = encrypt(JSON.stringify(profile), key);
3014
- writeFileSync(PROFILE_FILE, JSON.stringify(blob, null, 2), { encoding: "utf8" });
3025
+ writeFileSync2(PROFILE_FILE, JSON.stringify(blob, null, 2), { encoding: "utf8" });
3015
3026
  }
3016
3027
  function accumulateSession(profile, tags, isEmployerContext, inferredSeniority, seniorityIsAuthoritative = false) {
3017
3028
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -3095,14 +3106,14 @@ function profileToFingerprint(profile) {
3095
3106
  }
3096
3107
  };
3097
3108
  }
3098
- var TERMINALHIRE_DIR, PROFILE_FILE, KEY_FILE, ALGO, KEY_BYTES, IV_BYTES, DECAY_HALF_LIFE_MS, LANGUAGE_TAGS, MIN_FINGERPRINT_SCORE;
3109
+ var TERMINALHIRE_DIR2, PROFILE_FILE, KEY_FILE, ALGO, KEY_BYTES, IV_BYTES, DECAY_HALF_LIFE_MS, LANGUAGE_TAGS, MIN_FINGERPRINT_SCORE;
3099
3110
  var init_profile = __esm({
3100
3111
  "src/profile.ts"() {
3101
3112
  "use strict";
3102
3113
  init_src();
3103
- TERMINALHIRE_DIR = join2(homedir(), ".terminalhire");
3104
- PROFILE_FILE = join2(TERMINALHIRE_DIR, "profile.enc");
3105
- KEY_FILE = join2(TERMINALHIRE_DIR, "key");
3114
+ TERMINALHIRE_DIR2 = join3(homedir2(), ".terminalhire");
3115
+ PROFILE_FILE = join3(TERMINALHIRE_DIR2, "profile.enc");
3116
+ KEY_FILE = join3(TERMINALHIRE_DIR2, "key");
3106
3117
  ALGO = "aes-256-gcm";
3107
3118
  KEY_BYTES = 32;
3108
3119
  IV_BYTES = 12;
@@ -3131,24 +3142,20 @@ var init_profile = __esm({
3131
3142
  });
3132
3143
 
3133
3144
  // bin/jpi-devs.js
3134
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
3135
- import { join as join3 } from "path";
3136
- import { homedir as homedir2 } from "os";
3137
3145
  import { createInterface } from "readline";
3138
- var TERMINALHIRE_DIR2 = join3(homedir2(), ".terminalhire");
3139
- var DIRECTORY_CACHE_FILE = join3(TERMINALHIRE_DIR2, "directory-cache.json");
3140
- var PROJECT_FILE = join3(TERMINALHIRE_DIR2, "project.json");
3146
+
3147
+ // bin/directory.js
3148
+ import { readFileSync, writeFileSync, mkdirSync } from "fs";
3149
+ import { join } from "path";
3150
+ import { homedir } from "os";
3151
+ var TERMINALHIRE_DIR = join(homedir(), ".terminalhire");
3152
+ var DIRECTORY_CACHE_FILE = join(TERMINALHIRE_DIR, "directory-cache.json");
3153
+ var PROJECT_FILE = join(TERMINALHIRE_DIR, "project.json");
3141
3154
  var INDEX_TTL_MS = 15 * 60 * 1e3;
3142
3155
  var API_URL = process.env["TERMINALHIRE_API_URL"] ?? process.env["JPI_API_URL"] ?? "https://terminalhire.com";
3143
- var DEFAULT_LIMIT = 10;
3144
- var args = process.argv.slice(2);
3145
- var limitArg = args.indexOf("--limit");
3146
- var LIMIT = limitArg !== -1 ? parseInt(args[limitArg + 1] ?? "10", 10) : DEFAULT_LIMIT;
3147
- var SHOW_ALL = args.includes("--all");
3148
- var AS_PROJECT = args.includes("--as-project");
3149
3156
  function readDirectoryCache() {
3150
3157
  try {
3151
- const entry = JSON.parse(readFileSync3(DIRECTORY_CACHE_FILE, "utf8"));
3158
+ const entry = JSON.parse(readFileSync(DIRECTORY_CACHE_FILE, "utf8"));
3152
3159
  if (typeof entry.ts === "number" && Number.isFinite(entry.ts) && Date.now() - entry.ts < INDEX_TTL_MS) {
3153
3160
  return { index: entry.index, ts: entry.ts };
3154
3161
  }
@@ -3158,12 +3165,12 @@ function readDirectoryCache() {
3158
3165
  }
3159
3166
  }
3160
3167
  function writeDirectoryCache(index) {
3161
- mkdirSync2(TERMINALHIRE_DIR2, { recursive: true });
3162
- writeFileSync2(DIRECTORY_CACHE_FILE, JSON.stringify({ ts: Date.now(), index }), "utf8");
3168
+ mkdirSync(TERMINALHIRE_DIR, { recursive: true });
3169
+ writeFileSync(DIRECTORY_CACHE_FILE, JSON.stringify({ ts: Date.now(), index }), "utf8");
3163
3170
  }
3164
3171
  function readProject() {
3165
3172
  try {
3166
- return JSON.parse(readFileSync3(PROJECT_FILE, "utf8"));
3173
+ return JSON.parse(readFileSync(PROJECT_FILE, "utf8"));
3167
3174
  } catch {
3168
3175
  return null;
3169
3176
  }
@@ -3174,13 +3181,13 @@ function relativeTime(ts) {
3174
3181
  const mins = Math.round(secs / 60);
3175
3182
  return mins < 60 ? `${mins}m ago` : `${Math.round(mins / 60)}h ago`;
3176
3183
  }
3177
- async function fetchDirectory() {
3184
+ async function fetchDirectory({ quiet = false } = {}) {
3178
3185
  const cached = readDirectoryCache();
3179
3186
  if (cached) {
3180
- console.log(`\u2713 Using cached directory (updated ${relativeTime(cached.ts)})`);
3187
+ if (!quiet) console.log(`\u2713 Using cached directory (updated ${relativeTime(cached.ts)})`);
3181
3188
  return cached.index;
3182
3189
  }
3183
- console.log(`\u21BB Refreshing builder directory from ${API_URL}/api/directory...`);
3190
+ if (!quiet) console.log(`\u21BB Refreshing builder directory from ${API_URL}/api/directory...`);
3184
3191
  const res = await fetch(`${API_URL}/api/directory`, { signal: AbortSignal.timeout(1e4) });
3185
3192
  if (!res.ok) throw new Error(`/api/directory returned ${res.status}`);
3186
3193
  const index = await res.json();
@@ -3195,7 +3202,7 @@ function reportMatched(results, fetchImpl = fetch) {
3195
3202
  )
3196
3203
  ];
3197
3204
  if (logins.length === 0) return;
3198
- Promise.resolve(
3205
+ return Promise.resolve(
3199
3206
  fetchImpl(`${API_URL}/api/directory/matched`, {
3200
3207
  method: "POST",
3201
3208
  headers: { "Content-Type": "application/json" },
@@ -3207,6 +3214,15 @@ function reportMatched(results, fetchImpl = fetch) {
3207
3214
  } catch {
3208
3215
  }
3209
3216
  }
3217
+
3218
+ // bin/jpi-devs.js
3219
+ var API_URL2 = process.env["TERMINALHIRE_API_URL"] ?? process.env["JPI_API_URL"] ?? "https://terminalhire.com";
3220
+ var DEFAULT_LIMIT = 10;
3221
+ var args = process.argv.slice(2);
3222
+ var limitArg = args.indexOf("--limit");
3223
+ var LIMIT = limitArg !== -1 ? parseInt(args[limitArg + 1] ?? "10", 10) : DEFAULT_LIMIT;
3224
+ var SHOW_ALL = args.includes("--all");
3225
+ var AS_PROJECT = args.includes("--as-project");
3210
3226
  function prompt(question) {
3211
3227
  const rl = createInterface({ input: process.stdin, output: process.stdout });
3212
3228
  return new Promise((resolve) => {
@@ -3218,7 +3234,7 @@ function prompt(question) {
3218
3234
  }
3219
3235
  function credentialUrl(job) {
3220
3236
  const u = job.url ?? "";
3221
- return u.startsWith("http") ? u : `${API_URL}${u}`;
3237
+ return u.startsWith("http") ? u : `${API_URL2}${u}`;
3222
3238
  }
3223
3239
  function linkTitle(title, url) {
3224
3240
  const isTTY = process.stdout.isTTY;