terminalhire 0.2.3 → 0.2.5

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.
@@ -257,6 +257,14 @@ var init_ashby = __esm({
257
257
  }
258
258
  });
259
259
 
260
+ // ../../packages/core/src/feeds/lever.ts
261
+ var init_lever = __esm({
262
+ "../../packages/core/src/feeds/lever.ts"() {
263
+ "use strict";
264
+ init_vocabulary();
265
+ }
266
+ });
267
+
260
268
  // ../../packages/core/src/feeds/himalayas.ts
261
269
  var init_himalayas = __esm({
262
270
  "../../packages/core/src/feeds/himalayas.ts"() {
@@ -282,14 +290,124 @@ var init_hn = __esm({
282
290
  });
283
291
 
284
292
  // ../../packages/core/src/feeds/index.ts
293
+ function flattenTiers(t) {
294
+ return [.../* @__PURE__ */ new Set([...t.bigco, ...t.scaleup, ...t.startup])];
295
+ }
296
+ var GREENHOUSE_SLUGS_BY_TIER, ASHBY_SLUGS_BY_TIER, LEVER_SLUGS_BY_TIER, DEFAULT_GREENHOUSE_SLUGS, DEFAULT_ASHBY_SLUGS, DEFAULT_LEVER_SLUGS;
285
297
  var init_feeds = __esm({
286
298
  "../../packages/core/src/feeds/index.ts"() {
287
299
  "use strict";
288
300
  init_greenhouse();
289
301
  init_ashby();
302
+ init_lever();
290
303
  init_himalayas();
291
304
  init_wwr();
292
305
  init_hn();
306
+ GREENHOUSE_SLUGS_BY_TIER = {
307
+ bigco: [
308
+ "stripe",
309
+ "anthropic",
310
+ "figma",
311
+ "discord",
312
+ "brex",
313
+ "mercury",
314
+ "plaid",
315
+ "gusto",
316
+ "scale",
317
+ "databricks",
318
+ "coinbase",
319
+ "robinhood",
320
+ "doordash",
321
+ "airbnb",
322
+ "dropbox",
323
+ "datadog",
324
+ "cloudflare",
325
+ "reddit",
326
+ "lyft",
327
+ "instacart"
328
+ ],
329
+ scaleup: [
330
+ "samsara",
331
+ "verkada",
332
+ "affirm",
333
+ "gitlab",
334
+ "asana",
335
+ "flexport",
336
+ "faire",
337
+ "twitch",
338
+ "airtable",
339
+ "retool"
340
+ ],
341
+ startup: [
342
+ "watershed"
343
+ ]
344
+ };
345
+ ASHBY_SLUGS_BY_TIER = {
346
+ bigco: [
347
+ "openai"
348
+ ],
349
+ scaleup: [
350
+ "harvey",
351
+ "elevenlabs",
352
+ "notion",
353
+ "sierra",
354
+ "cohere",
355
+ "ramp",
356
+ "vanta",
357
+ "decagon",
358
+ "cursor",
359
+ "replit",
360
+ "perplexity",
361
+ "baseten",
362
+ "drata",
363
+ "writer",
364
+ "temporal",
365
+ "supabase"
366
+ ],
367
+ startup: [
368
+ "suno",
369
+ "attio",
370
+ "modal",
371
+ "workos",
372
+ "linear",
373
+ "render",
374
+ "warp",
375
+ "plain",
376
+ "posthog",
377
+ "pylon",
378
+ "resend",
379
+ "langfuse",
380
+ "railway",
381
+ "mintlify",
382
+ "neon",
383
+ "browserbase",
384
+ "knock",
385
+ "speakeasy",
386
+ "stytch",
387
+ "runway",
388
+ "doppler",
389
+ "inngest",
390
+ "hightouch",
391
+ "zed"
392
+ ]
393
+ };
394
+ LEVER_SLUGS_BY_TIER = {
395
+ bigco: [
396
+ "palantir",
397
+ "spotify"
398
+ ],
399
+ scaleup: [
400
+ "mistral",
401
+ "ro",
402
+ "secureframe"
403
+ ],
404
+ startup: [
405
+ "anyscale"
406
+ ]
407
+ };
408
+ DEFAULT_GREENHOUSE_SLUGS = flattenTiers(GREENHOUSE_SLUGS_BY_TIER);
409
+ DEFAULT_ASHBY_SLUGS = flattenTiers(ASHBY_SLUGS_BY_TIER);
410
+ DEFAULT_LEVER_SLUGS = flattenTiers(LEVER_SLUGS_BY_TIER);
293
411
  }
294
412
  });
295
413
 
@@ -573,11 +691,15 @@ var init_profile = __esm({
573
691
  // bin/jpi-sync.js
574
692
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2, rmSync } from "fs";
575
693
  import { join as join3 } from "path";
576
- import { homedir as homedir2 } from "os";
694
+ import { homedir as homedir2, hostname as osHostname } from "os";
577
695
  import { createInterface } from "readline";
696
+ import { spawn } from "child_process";
578
697
  var TH_DIR = process.env["TERMINALHIRE_DIR"] || join3(homedir2(), ".terminalhire");
579
698
  var TIER1_MARKER = join3(TH_DIR, "tier1.json");
580
699
  var API_URL = process.env["TERMINALHIRE_API_URL"] || process.env["JPI_API_URL"] || "https://terminalhire.com";
700
+ var SYNC_BASE = "https://www.terminalhire.com";
701
+ var POLL_INTERVAL_MS = 2e3;
702
+ var POLL_TIMEOUT_MS = 10 * 60 * 1e3;
581
703
  var CONSENT_VERSION = 1;
582
704
  function ask(question) {
583
705
  const rl = createInterface({ input: process.stdin, output: process.stdout });
@@ -621,14 +743,14 @@ function buildConsentFields(profile) {
621
743
  }
622
744
  return fields;
623
745
  }
624
- function renderConsentCard(fields) {
746
+ function renderPreview(fields) {
625
747
  console.log("");
626
748
  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");
627
749
  console.log("\u2502 terminalhire \u2014 sync your profile (Tier-1, opt-in) \u2502");
628
750
  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");
629
751
  console.log("");
630
- console.log(" You are about to send the following data to");
631
- console.log(" staqs (terminalhire.com):");
752
+ console.log(" The following data will be shared with staqs (terminalhire.com)");
753
+ console.log(" AFTER you authorize + consent in the browser:");
632
754
  console.log("");
633
755
  for (const f of fields) {
634
756
  const shown = Array.isArray(f.value) ? JSON.stringify(f.value) : String(f.value ?? "(not set)");
@@ -647,6 +769,30 @@ function renderConsentCard(fields) {
647
769
  console.log(" This is NOT required to use terminalhire.");
648
770
  console.log("");
649
771
  }
772
+ function openInBrowser(url) {
773
+ let cmd;
774
+ let args;
775
+ if (process.platform === "darwin") {
776
+ cmd = "open";
777
+ args = [url];
778
+ } else if (process.platform === "win32") {
779
+ cmd = "cmd";
780
+ args = ["/c", "start", "", url];
781
+ } else {
782
+ cmd = "xdg-open";
783
+ args = [url];
784
+ }
785
+ try {
786
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
787
+ child.on("error", () => {
788
+ });
789
+ child.unref();
790
+ } catch {
791
+ }
792
+ }
793
+ function sleep(ms) {
794
+ return new Promise((res) => setTimeout(res, ms));
795
+ }
650
796
  async function runPush() {
651
797
  const { readProfile: readProfile2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
652
798
  const profile = await readProfile2();
@@ -658,15 +804,91 @@ async function runPush() {
658
804
  process.exit(1);
659
805
  }
660
806
  const fields = buildConsentFields(profile);
661
- renderConsentCard(fields);
662
- let consentConfirmed = false;
663
- const answer = await ask(' Sync your profile to staqs (terminalhire.com)? Type "yes" to continue: ');
664
- if (answer === "yes") {
665
- consentConfirmed = true;
666
- }
667
- if (!consentConfirmed) {
668
- console.log("\n Aborted \u2014 nothing was sent.\n");
669
- process.exit(0);
807
+ renderPreview(fields);
808
+ await new Promise((resolve) => {
809
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
810
+ rl.question(
811
+ " Press Enter to open your browser to authorize + consent (or Ctrl-C to cancel)... ",
812
+ () => {
813
+ rl.close();
814
+ resolve();
815
+ }
816
+ );
817
+ });
818
+ console.log("");
819
+ console.log(" Starting browser verification...");
820
+ let begin;
821
+ try {
822
+ const r = await fetch(`${SYNC_BASE}/api/profile-sync/begin`, {
823
+ method: "POST",
824
+ headers: { "Content-Type": "application/json" },
825
+ body: JSON.stringify({ hostname: osHostname() }),
826
+ signal: AbortSignal.timeout(1e4)
827
+ });
828
+ if (!r.ok) {
829
+ let detail = "";
830
+ try {
831
+ detail = (await r.json())?.message || "";
832
+ } catch {
833
+ }
834
+ console.error(`
835
+ Could not start sync: /api/profile-sync/begin returned ${r.status}. ${detail}`);
836
+ if (r.status === 503) console.error(" (Tier-1 sync is not enabled on the server yet.)");
837
+ process.exit(1);
838
+ }
839
+ begin = await r.json();
840
+ } catch (err) {
841
+ console.error(`
842
+ Could not start sync: ${err instanceof Error ? err.message : String(err)}`);
843
+ process.exit(1);
844
+ }
845
+ const { challenge, verifyUrl } = begin || {};
846
+ if (!challenge || !verifyUrl) {
847
+ console.error("\n Could not start sync: malformed begin response.");
848
+ process.exit(1);
849
+ }
850
+ console.log("");
851
+ console.log(" Open this URL in your browser to authorize + consent:");
852
+ console.log(` ${verifyUrl}`);
853
+ console.log("");
854
+ console.log(" (Attempting to open it automatically...)");
855
+ openInBrowser(verifyUrl);
856
+ console.log(" Waiting for browser verification...");
857
+ const deadline = Date.now() + POLL_TIMEOUT_MS;
858
+ let proofToken = null;
859
+ while (Date.now() < deadline) {
860
+ await sleep(POLL_INTERVAL_MS);
861
+ let statusRes;
862
+ try {
863
+ statusRes = await fetch(
864
+ `${SYNC_BASE}/api/profile-sync/status?challenge=${encodeURIComponent(challenge)}`,
865
+ { signal: AbortSignal.timeout(1e4) }
866
+ );
867
+ } catch {
868
+ continue;
869
+ }
870
+ if (!statusRes.ok) {
871
+ if (statusRes.status === 503) {
872
+ console.error("\n Tier-1 sync is not enabled on the server yet.\n");
873
+ process.exit(1);
874
+ }
875
+ continue;
876
+ }
877
+ let body;
878
+ try {
879
+ body = await statusRes.json();
880
+ } catch {
881
+ continue;
882
+ }
883
+ if (body && body.status === "verified" && body.proofToken) {
884
+ proofToken = body.proofToken;
885
+ break;
886
+ }
887
+ }
888
+ if (!proofToken) {
889
+ console.error("\n Timed out waiting for browser verification (10 min).");
890
+ console.error(" Re-run `terminalhire sync --push` to try again.\n");
891
+ process.exit(1);
670
892
  }
671
893
  const consentedAt = (/* @__PURE__ */ new Date()).toISOString();
672
894
  const consentToken = {
@@ -685,14 +907,14 @@ async function runPush() {
685
907
  };
686
908
  const priorMarker = readMarker();
687
909
  const rowToken = priorMarker && priorMarker.deleteToken ? priorMarker.deleteToken : null;
688
- const requestBody = { consentToken, profile: payloadProfile };
910
+ const requestBody = { consentToken, profile: payloadProfile, proofToken };
689
911
  if (rowToken) {
690
912
  requestBody.rowToken = rowToken;
691
913
  }
692
- console.log("\n Sending one-time snapshot...");
914
+ console.log("\n Verified. Sending one-time snapshot...");
693
915
  let res;
694
916
  try {
695
- res = await fetch(`${API_URL}/api/profile-sync`, {
917
+ res = await fetch(`${SYNC_BASE}/api/profile-sync`, {
696
918
  method: "POST",
697
919
  headers: { "Content-Type": "application/json" },
698
920
  body: JSON.stringify(requestBody),
@@ -715,7 +937,7 @@ async function runPush() {
715
937
  console.error(" (Tier-1 sync is not enabled on the server yet.)");
716
938
  }
717
939
  if (res.status === 403) {
718
- console.error(" (This GitHub login was already claimed by a different push.)");
940
+ console.error(" (Ownership proof rejected, expired, or already used \u2014 re-run sync --push.)");
719
941
  }
720
942
  process.exit(1);
721
943
  }
@@ -157,13 +157,40 @@ function clearSpinnerVerbs() {
157
157
  }
158
158
  return { cleared: true, keptUserVerbs };
159
159
  }
160
+ function interleaveBySource(topMatches) {
161
+ if (!Array.isArray(topMatches) || topMatches.length === 0) return topMatches;
162
+ const buckets = /* @__PURE__ */ new Map();
163
+ const order = [];
164
+ for (const m of topMatches) {
165
+ const id = m && m.id ? String(m.id) : "";
166
+ const idx = id.indexOf(":");
167
+ const source = idx > 0 ? id.slice(0, idx) : "_";
168
+ if (!buckets.has(source)) {
169
+ buckets.set(source, []);
170
+ order.push(source);
171
+ }
172
+ buckets.get(source).push(m);
173
+ }
174
+ const out = [];
175
+ let remaining = topMatches.length;
176
+ while (remaining > 0) {
177
+ for (const source of order) {
178
+ const b = buckets.get(source);
179
+ if (b && b.length) {
180
+ out.push(b.shift());
181
+ remaining--;
182
+ }
183
+ }
184
+ }
185
+ return out;
186
+ }
160
187
  function buildTips(topMatches, baseUrl, max = 8) {
161
188
  const base = String(baseUrl || "https://terminalhire.com").replace(/\/+$/, "");
162
189
  const out = [];
163
190
  const seenRole = /* @__PURE__ */ new Set();
164
191
  const perCompany = /* @__PURE__ */ new Map();
165
192
  const COMPANY_CAP = 2;
166
- for (const m of Array.isArray(topMatches) ? topMatches : []) {
193
+ for (const m of interleaveBySource(Array.isArray(topMatches) ? topMatches : [])) {
167
194
  if (!m || !m.title || !m.company || !m.id) continue;
168
195
  const idx = String(m.id).indexOf(":");
169
196
  if (idx <= 0) continue;
@@ -237,6 +264,7 @@ export {
237
264
  clearSpinnerVerbs,
238
265
  ctaVerb,
239
266
  formatVerbs,
267
+ interleaveBySource,
240
268
  rankBySessionTags,
241
269
  readSpinnerConfig
242
270
  };
@@ -225,6 +225,116 @@ function normalize(tokens) {
225
225
  return Array.from(result);
226
226
  }
227
227
 
228
+ // ../../packages/core/src/feeds/index.ts
229
+ var GREENHOUSE_SLUGS_BY_TIER = {
230
+ bigco: [
231
+ "stripe",
232
+ "anthropic",
233
+ "figma",
234
+ "discord",
235
+ "brex",
236
+ "mercury",
237
+ "plaid",
238
+ "gusto",
239
+ "scale",
240
+ "databricks",
241
+ "coinbase",
242
+ "robinhood",
243
+ "doordash",
244
+ "airbnb",
245
+ "dropbox",
246
+ "datadog",
247
+ "cloudflare",
248
+ "reddit",
249
+ "lyft",
250
+ "instacart"
251
+ ],
252
+ scaleup: [
253
+ "samsara",
254
+ "verkada",
255
+ "affirm",
256
+ "gitlab",
257
+ "asana",
258
+ "flexport",
259
+ "faire",
260
+ "twitch",
261
+ "airtable",
262
+ "retool"
263
+ ],
264
+ startup: [
265
+ "watershed"
266
+ ]
267
+ };
268
+ var ASHBY_SLUGS_BY_TIER = {
269
+ bigco: [
270
+ "openai"
271
+ ],
272
+ scaleup: [
273
+ "harvey",
274
+ "elevenlabs",
275
+ "notion",
276
+ "sierra",
277
+ "cohere",
278
+ "ramp",
279
+ "vanta",
280
+ "decagon",
281
+ "cursor",
282
+ "replit",
283
+ "perplexity",
284
+ "baseten",
285
+ "drata",
286
+ "writer",
287
+ "temporal",
288
+ "supabase"
289
+ ],
290
+ startup: [
291
+ "suno",
292
+ "attio",
293
+ "modal",
294
+ "workos",
295
+ "linear",
296
+ "render",
297
+ "warp",
298
+ "plain",
299
+ "posthog",
300
+ "pylon",
301
+ "resend",
302
+ "langfuse",
303
+ "railway",
304
+ "mintlify",
305
+ "neon",
306
+ "browserbase",
307
+ "knock",
308
+ "speakeasy",
309
+ "stytch",
310
+ "runway",
311
+ "doppler",
312
+ "inngest",
313
+ "hightouch",
314
+ "zed"
315
+ ]
316
+ };
317
+ var LEVER_SLUGS_BY_TIER = {
318
+ bigco: [
319
+ "palantir",
320
+ "spotify"
321
+ ],
322
+ scaleup: [
323
+ "mistral",
324
+ "ro",
325
+ "secureframe"
326
+ ],
327
+ startup: [
328
+ "anyscale"
329
+ ]
330
+ };
331
+ function flattenTiers(t) {
332
+ return [.../* @__PURE__ */ new Set([...t.bigco, ...t.scaleup, ...t.startup])];
333
+ }
334
+ var DEFAULT_GREENHOUSE_SLUGS = flattenTiers(GREENHOUSE_SLUGS_BY_TIER);
335
+ var DEFAULT_ASHBY_SLUGS = flattenTiers(ASHBY_SLUGS_BY_TIER);
336
+ var DEFAULT_LEVER_SLUGS = flattenTiers(LEVER_SLUGS_BY_TIER);
337
+
228
338
  // ../../packages/core/src/coastal.ts
229
339
  import { readFileSync } from "fs";
230
340
  import { join } from "path";
@@ -215,6 +215,116 @@ function normalize(tokens) {
215
215
  return Array.from(result);
216
216
  }
217
217
 
218
+ // ../../packages/core/src/feeds/index.ts
219
+ var GREENHOUSE_SLUGS_BY_TIER = {
220
+ bigco: [
221
+ "stripe",
222
+ "anthropic",
223
+ "figma",
224
+ "discord",
225
+ "brex",
226
+ "mercury",
227
+ "plaid",
228
+ "gusto",
229
+ "scale",
230
+ "databricks",
231
+ "coinbase",
232
+ "robinhood",
233
+ "doordash",
234
+ "airbnb",
235
+ "dropbox",
236
+ "datadog",
237
+ "cloudflare",
238
+ "reddit",
239
+ "lyft",
240
+ "instacart"
241
+ ],
242
+ scaleup: [
243
+ "samsara",
244
+ "verkada",
245
+ "affirm",
246
+ "gitlab",
247
+ "asana",
248
+ "flexport",
249
+ "faire",
250
+ "twitch",
251
+ "airtable",
252
+ "retool"
253
+ ],
254
+ startup: [
255
+ "watershed"
256
+ ]
257
+ };
258
+ var ASHBY_SLUGS_BY_TIER = {
259
+ bigco: [
260
+ "openai"
261
+ ],
262
+ scaleup: [
263
+ "harvey",
264
+ "elevenlabs",
265
+ "notion",
266
+ "sierra",
267
+ "cohere",
268
+ "ramp",
269
+ "vanta",
270
+ "decagon",
271
+ "cursor",
272
+ "replit",
273
+ "perplexity",
274
+ "baseten",
275
+ "drata",
276
+ "writer",
277
+ "temporal",
278
+ "supabase"
279
+ ],
280
+ startup: [
281
+ "suno",
282
+ "attio",
283
+ "modal",
284
+ "workos",
285
+ "linear",
286
+ "render",
287
+ "warp",
288
+ "plain",
289
+ "posthog",
290
+ "pylon",
291
+ "resend",
292
+ "langfuse",
293
+ "railway",
294
+ "mintlify",
295
+ "neon",
296
+ "browserbase",
297
+ "knock",
298
+ "speakeasy",
299
+ "stytch",
300
+ "runway",
301
+ "doppler",
302
+ "inngest",
303
+ "hightouch",
304
+ "zed"
305
+ ]
306
+ };
307
+ var LEVER_SLUGS_BY_TIER = {
308
+ bigco: [
309
+ "palantir",
310
+ "spotify"
311
+ ],
312
+ scaleup: [
313
+ "mistral",
314
+ "ro",
315
+ "secureframe"
316
+ ],
317
+ startup: [
318
+ "anyscale"
319
+ ]
320
+ };
321
+ function flattenTiers(t) {
322
+ return [.../* @__PURE__ */ new Set([...t.bigco, ...t.scaleup, ...t.startup])];
323
+ }
324
+ var DEFAULT_GREENHOUSE_SLUGS = flattenTiers(GREENHOUSE_SLUGS_BY_TIER);
325
+ var DEFAULT_ASHBY_SLUGS = flattenTiers(ASHBY_SLUGS_BY_TIER);
326
+ var DEFAULT_LEVER_SLUGS = flattenTiers(LEVER_SLUGS_BY_TIER);
327
+
218
328
  // ../../packages/core/src/coastal.ts
219
329
  import { readFileSync } from "fs";
220
330
  import { join } from "path";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "terminalhire",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Local-first job matching for developers — ambient job matches in the Claude Code spinner. Matching runs on your machine; your profile never leaves it.",
5
5
  "repository": {
6
6
  "type": "git",