hiregraph 0.1.3 → 0.2.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/index.js CHANGED
@@ -2045,6 +2045,7 @@ async function jobsCommand(options) {
2045
2045
 
2046
2046
  // src/commands/matches.ts
2047
2047
  import chalk4 from "chalk";
2048
+ import { readFileSync } from "fs";
2048
2049
 
2049
2050
  // src/matching/job-parser.ts
2050
2051
  var SYSTEM_PROMPT3 = `You extract structured job requirements from job descriptions.
@@ -2316,6 +2317,15 @@ function cosineSimilarity(a, b) {
2316
2317
  }
2317
2318
  return dotProduct / (a.magnitude * b.magnitude);
2318
2319
  }
2320
+ function buildJobVectorFromRaw(description, title, vocab) {
2321
+ const tokens = [
2322
+ ...tokenize(title),
2323
+ ...tokenize(title),
2324
+ // title gets 2x weight
2325
+ ...tokenize(description.slice(0, 3e3))
2326
+ ];
2327
+ return vectorize(tokens, vocab);
2328
+ }
2319
2329
  function buildAllDocuments(graph, requirements) {
2320
2330
  const docs = [];
2321
2331
  const skillTokens = [];
@@ -2337,6 +2347,23 @@ function buildAllDocuments(graph, requirements) {
2337
2347
  }
2338
2348
  return docs;
2339
2349
  }
2350
+ function buildAllDocumentsFromRaw(graph, jobs) {
2351
+ const docs = [];
2352
+ const skillTokens = [];
2353
+ for (const skill of Object.keys(graph.tech_stack)) {
2354
+ skillTokens.push(...tokenize(skill));
2355
+ }
2356
+ for (const proj of graph.projects) {
2357
+ for (const s of proj.stack) skillTokens.push(...tokenize(s));
2358
+ if (proj.domain) skillTokens.push(...tokenize(proj.domain));
2359
+ }
2360
+ docs.push(skillTokens);
2361
+ for (const job of jobs) {
2362
+ const tokens = [...tokenize(job.title), ...tokenize(job.description_raw.slice(0, 3e3))];
2363
+ docs.push(tokens);
2364
+ }
2365
+ return docs;
2366
+ }
2340
2367
 
2341
2368
  // src/matching/similarity.ts
2342
2369
  function findTopCandidates(skillVector, jobVectors, k) {
@@ -2579,11 +2606,124 @@ async function runMatchPipeline(options) {
2579
2606
  return matchRun;
2580
2607
  }
2581
2608
 
2609
+ // src/matching/prepare.ts
2610
+ async function prepareCandidates(topK = 50, refresh = false) {
2611
+ const graph = await loadGraph();
2612
+ if (!graph || graph.projects.length === 0) {
2613
+ throw new Error("No skill graph found. Run `hiregraph scan <path>` first.");
2614
+ }
2615
+ const identity = graph.builder_identity;
2616
+ const config = await loadJson("config.json");
2617
+ const filterConfig = {
2618
+ excluded_companies: config?.excluded_companies || [],
2619
+ remote_preference: identity.remote_preference,
2620
+ min_compensation: identity.min_compensation
2621
+ };
2622
+ const fetchResult = await fetchAllJobs({ refresh });
2623
+ if (fetchResult.jobs.length === 0) {
2624
+ throw new Error("No jobs found. Run `hiregraph jobs` first.");
2625
+ }
2626
+ const excluded = new Set((filterConfig.excluded_companies || []).map((s) => s.toLowerCase()));
2627
+ const filtered = fetchResult.jobs.filter((j) => !excluded.has(j.company_slug.toLowerCase()));
2628
+ start(`Vectorizing ${filtered.length} jobs...`);
2629
+ const docs = buildAllDocumentsFromRaw(graph, filtered);
2630
+ const vocab = buildVocabulary(docs);
2631
+ const skillVector = buildSkillVector(graph, vocab);
2632
+ const jobVectors = /* @__PURE__ */ new Map();
2633
+ for (const job of filtered) {
2634
+ jobVectors.set(job.id, buildJobVectorFromRaw(job.description_raw, job.title, vocab));
2635
+ }
2636
+ succeed(`Vectorized ${filtered.length} jobs`);
2637
+ start(`Finding top ${topK} candidates...`);
2638
+ const candidates = findTopCandidates(skillVector, jobVectors, topK);
2639
+ succeed(`Top ${candidates.length} candidates selected`);
2640
+ const jobMap = new Map(fetchResult.jobs.map((j) => [j.id, j]));
2641
+ const prepared = candidates.map((c) => {
2642
+ const job = jobMap.get(c.jobId);
2643
+ return {
2644
+ job_id: job.id,
2645
+ job_title: job.title,
2646
+ company: job.company,
2647
+ company_slug: job.company_slug,
2648
+ url: job.url,
2649
+ location: job.location,
2650
+ source: job.source,
2651
+ description_snippet: job.description_raw.slice(0, 500),
2652
+ similarity: Math.round(c.similarity * 1e3) / 1e3
2653
+ };
2654
+ });
2655
+ const skillSummary = buildSkillSummary2(graph);
2656
+ return {
2657
+ candidates: prepared,
2658
+ skill_summary: skillSummary,
2659
+ total_jobs: fetchResult.jobs.length,
2660
+ after_filter: filtered.length
2661
+ };
2662
+ }
2663
+ async function saveMatchResults(results, totalJobs) {
2664
+ results.sort((a, b) => b.score - a.score);
2665
+ const strong = results.filter((m) => m.score >= 8);
2666
+ const suggested = results.filter((m) => m.score >= 6 && m.score < 8);
2667
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2668
+ const matchRun = {
2669
+ date: today,
2670
+ total_jobs_fetched: totalJobs,
2671
+ total_jobs_parsed: 0,
2672
+ total_candidates_evaluated: results.length,
2673
+ strong_matches: strong,
2674
+ suggested_matches: suggested,
2675
+ run_at: (/* @__PURE__ */ new Date()).toISOString(),
2676
+ cost_estimate: { jobs_parsed: 0, pairs_evaluated: 0, estimated_usd: 0 }
2677
+ };
2678
+ await saveSubJson("matches", `${today}.json`, matchRun);
2679
+ return matchRun;
2680
+ }
2681
+ function buildSkillSummary2(graph) {
2682
+ const lines = [];
2683
+ if (graph.builder_identity.name) {
2684
+ lines.push(`Name: ${graph.builder_identity.name} (${graph.builder_identity.primary_role})`);
2685
+ }
2686
+ const skills = Object.entries(graph.tech_stack).sort((a, b) => b[1].proficiency - a[1].proficiency).slice(0, 15);
2687
+ if (skills.length > 0) {
2688
+ lines.push("Tech Stack:");
2689
+ for (const [name, data] of skills) {
2690
+ lines.push(` ${name}: ${data.loc.toLocaleString()} LOC, ${data.projects} projects`);
2691
+ }
2692
+ }
2693
+ if (graph.projects.length > 0) {
2694
+ lines.push("Projects:");
2695
+ for (const proj of graph.projects) {
2696
+ lines.push(` ${proj.name}: ${proj.domain || "unknown"} \u2014 ${proj.stack.slice(0, 5).join(", ")}`);
2697
+ }
2698
+ }
2699
+ const patterns = Object.entries(graph.architecture);
2700
+ if (patterns.length > 0) {
2701
+ lines.push(`Architecture: ${patterns.map(([n, { confidence }]) => `${n} (${confidence})`).join(", ")}`);
2702
+ }
2703
+ if (graph.builder_identity.previous_companies.length > 0) {
2704
+ lines.push("Work History:");
2705
+ for (const w of graph.builder_identity.previous_companies) {
2706
+ lines.push(` ${w.role} @ ${w.company} (${w.start_year}-${w.end_year || "present"})`);
2707
+ }
2708
+ }
2709
+ return lines.join("\n");
2710
+ }
2711
+
2582
2712
  // src/commands/matches.ts
2583
2713
  async function matchesCommand(options) {
2714
+ if (options.prepare) {
2715
+ await handlePrepare(options.top || 50, !!options.refresh);
2716
+ return;
2717
+ }
2718
+ if (options.saveResults) {
2719
+ await handleSaveResults(options.saveResults);
2720
+ return;
2721
+ }
2584
2722
  header("\n HireGraph Matches\n");
2585
2723
  if (!isApiKeyConfigured()) {
2586
- warn('ANTHROPIC_API_KEY not set. Add it to your environment variables or run: $env:ANTHROPIC_API_KEY = "your-key"');
2724
+ warn("ANTHROPIC_API_KEY not set. Use --prepare mode for Claude Code integration:");
2725
+ info(" hiregraph matches --prepare (outputs candidates as JSON)");
2726
+ info(" hiregraph matches --save-results <f> (imports evaluated results)");
2587
2727
  return;
2588
2728
  }
2589
2729
  try {
@@ -2591,40 +2731,75 @@ async function matchesCommand(options) {
2591
2731
  topK: options.top || 50,
2592
2732
  refresh: options.refresh
2593
2733
  });
2734
+ printResults(result, !!options.verbose);
2735
+ } catch (err) {
2736
+ error(err.message);
2737
+ }
2738
+ }
2739
+ async function handlePrepare(topK, refresh) {
2740
+ header("\n HireGraph \u2014 Prepare Candidates\n");
2741
+ try {
2742
+ const result = await prepareCandidates(topK, refresh);
2594
2743
  console.log();
2595
- if (result.strong_matches.length > 0) {
2596
- console.log(` ${chalk4.bold.green("Strong Matches (score 8-10):")}`);
2597
- for (let i = 0; i < result.strong_matches.length; i++) {
2598
- printMatch(result.strong_matches[i], i + 1, true);
2744
+ const output = JSON.stringify(result, null, 2);
2745
+ console.log(output);
2746
+ } catch (err) {
2747
+ error(err.message);
2748
+ }
2749
+ }
2750
+ async function handleSaveResults(filePath) {
2751
+ header("\n HireGraph \u2014 Save Match Results\n");
2752
+ try {
2753
+ let data;
2754
+ if (filePath === "-") {
2755
+ const chunks = [];
2756
+ for await (const chunk of process.stdin) {
2757
+ chunks.push(chunk);
2599
2758
  }
2600
- console.log();
2759
+ data = Buffer.concat(chunks).toString("utf-8");
2760
+ } else {
2761
+ data = readFileSync(filePath, "utf-8");
2601
2762
  }
2602
- if (result.suggested_matches.length > 0) {
2603
- console.log(` ${chalk4.bold.yellow("Suggested Matches (score 6-7):")}`);
2604
- for (let i = 0; i < result.suggested_matches.length; i++) {
2605
- const idx = result.strong_matches.length + i + 1;
2606
- printMatch(result.suggested_matches[i], idx, !!options.verbose);
2607
- }
2608
- console.log();
2763
+ const parsed = JSON.parse(data);
2764
+ const results = Array.isArray(parsed) ? parsed : parsed.results || parsed.strong_matches?.concat(parsed.suggested_matches) || [];
2765
+ if (results.length === 0) {
2766
+ error("No match results found in input.");
2767
+ return;
2609
2768
  }
2610
- if (result.strong_matches.length === 0 && result.suggested_matches.length === 0) {
2611
- warn(" No matches above threshold. Try scanning more projects or adjusting preferences.");
2612
- console.log();
2769
+ const matchRun = await saveMatchResults(results, parsed.total_jobs || 0);
2770
+ printResults(matchRun, true);
2771
+ } catch (err) {
2772
+ error(`Failed to save results: ${err.message}`);
2773
+ }
2774
+ }
2775
+ function printResults(result, verbose) {
2776
+ console.log();
2777
+ if (result.strong_matches.length > 0) {
2778
+ console.log(` ${chalk4.bold.green("Strong Matches (score 8-10):")}`);
2779
+ for (let i = 0; i < result.strong_matches.length; i++) {
2780
+ printMatch(result.strong_matches[i], i + 1, true);
2781
+ }
2782
+ console.log();
2783
+ }
2784
+ if (result.suggested_matches.length > 0) {
2785
+ console.log(` ${chalk4.bold.yellow("Suggested Matches (score 6-7):")}`);
2786
+ for (let i = 0; i < result.suggested_matches.length; i++) {
2787
+ const idx = result.strong_matches.length + i + 1;
2788
+ printMatch(result.suggested_matches[i], idx, verbose);
2613
2789
  }
2614
- console.log(` ${chalk4.bold("Summary:")}`);
2615
- console.log(` Jobs analyzed: ${result.total_jobs_fetched.toLocaleString()}`);
2616
- console.log(` Jobs parsed: ${result.total_jobs_parsed.toLocaleString()}`);
2617
- console.log(` LLM evaluated: ${result.total_candidates_evaluated}`);
2618
- console.log(` Strong matches: ${chalk4.green(String(result.strong_matches.length))}`);
2619
- console.log(` Suggested: ${chalk4.yellow(String(result.suggested_matches.length))}`);
2620
- console.log(` Cost estimate: ~$${result.cost_estimate.estimated_usd.toFixed(2)}`);
2621
2790
  console.log();
2622
- info(` Results saved to ~/.hiregraph/matches/${result.date}.json`);
2791
+ }
2792
+ if (result.strong_matches.length === 0 && result.suggested_matches.length === 0) {
2793
+ warn(" No matches above threshold.");
2623
2794
  console.log();
2624
- } catch (err) {
2625
- error(err.message);
2626
- process.exit(1);
2627
2795
  }
2796
+ console.log(` ${chalk4.bold("Summary:")}`);
2797
+ console.log(` Evaluated: ${result.total_candidates_evaluated}`);
2798
+ console.log(` Strong matches: ${chalk4.green(String(result.strong_matches.length))}`);
2799
+ console.log(` Suggested: ${chalk4.yellow(String(result.suggested_matches.length))}`);
2800
+ console.log();
2801
+ info(` Results saved to ~/.hiregraph/matches/${result.date}.json`);
2802
+ console.log();
2628
2803
  }
2629
2804
  function printMatch(match, rank, showDetails) {
2630
2805
  const scoreColor = match.score >= 8 ? chalk4.green : chalk4.yellow;
@@ -3044,10 +3219,6 @@ async function submitToAshby(rawJobId, pdfBuffer, identity) {
3044
3219
  // src/commands/apply.ts
3045
3220
  async function applyCommand(jobId, options) {
3046
3221
  header("\n HireGraph Apply\n");
3047
- if (!isApiKeyConfigured()) {
3048
- warn('ANTHROPIC_API_KEY not set. Add it to your environment variables or run: $env:ANTHROPIC_API_KEY = "your-key"');
3049
- return;
3050
- }
3051
3222
  const graph = await loadGraph();
3052
3223
  if (!graph || graph.projects.length === 0) {
3053
3224
  error("No skill graph found. Run `hiregraph scan <path>` first.");
@@ -3057,6 +3228,14 @@ async function applyCommand(jobId, options) {
3057
3228
  error("Name and email required. Run `hiregraph init` first.");
3058
3229
  return;
3059
3230
  }
3231
+ const hasApiKey = isApiKeyConfigured();
3232
+ const hasExternalSummary = !!options?.withSummary;
3233
+ if (!hasApiKey && !hasExternalSummary) {
3234
+ warn("No API key and no --with-summary provided.");
3235
+ info(' Use --with-summary "Your tailored summary text" (Claude Code provides this)');
3236
+ info(" Or set ANTHROPIC_API_KEY for automatic tailoring.");
3237
+ return;
3238
+ }
3060
3239
  const matchRun = await loadLatestMatches();
3061
3240
  if (!matchRun) {
3062
3241
  error("No match results found. Run `hiregraph matches` first.");
@@ -3086,8 +3265,6 @@ async function applyCommand(jobId, options) {
3086
3265
  targets = [match];
3087
3266
  } else {
3088
3267
  error("Provide a job-id or use --all-above <score>");
3089
- info("Usage: hiregraph apply <job-id> [--review] [--dry-run]");
3090
- info(" hiregraph apply --all-above 8");
3091
3268
  return;
3092
3269
  }
3093
3270
  let applied = 0;
@@ -3100,31 +3277,49 @@ async function applyCommand(jobId, options) {
3100
3277
  continue;
3101
3278
  }
3102
3279
  const job = jobsMap.get(match.job_id);
3103
- const requirements = requirementsMap[match.job_id];
3104
- if (!job || !requirements) {
3105
- warn(` Skipped (missing data): ${match.job_title} @ ${match.company}`);
3280
+ if (!job) {
3281
+ warn(` Skipped (missing job data): ${match.job_title} @ ${match.company}`);
3106
3282
  failed++;
3107
3283
  continue;
3108
3284
  }
3109
3285
  console.log(`
3110
3286
  ${chalk5.bold(match.job_title)} @ ${match.company} (score: ${chalk5.green(String(match.score))})`);
3111
- start("Tailoring resume...");
3112
3287
  let tailoring;
3113
- try {
3114
- tailoring = await tailorResume(graph, job, requirements, match);
3115
- succeed("Resume tailored");
3116
- } catch (err) {
3117
- fail(`Tailoring failed: ${err.message}`);
3118
- failed++;
3119
- continue;
3288
+ if (hasExternalSummary) {
3289
+ tailoring = {
3290
+ job_id: match.job_id,
3291
+ professional_summary: options.withSummary,
3292
+ project_order: options?.withProjects ? options.withProjects.split(",").map((s) => s.trim()) : graph.projects.map((p) => p.name),
3293
+ bullet_emphasis: {},
3294
+ skills_order: options?.withSkills ? options.withSkills.split(",").map((s) => s.trim()) : Object.keys(graph.tech_stack),
3295
+ generated_at: (/* @__PURE__ */ new Date()).toISOString()
3296
+ };
3297
+ } else {
3298
+ start("Tailoring resume...");
3299
+ const requirements = requirementsMap[match.job_id];
3300
+ try {
3301
+ tailoring = await tailorResume(graph, job, requirements || {
3302
+ job_id: match.job_id,
3303
+ must_have_skills: [],
3304
+ nice_to_have_skills: [],
3305
+ seniority_level: "mid",
3306
+ tech_stack: [],
3307
+ domain: "unknown",
3308
+ remote_policy: "unknown",
3309
+ parsed_at: (/* @__PURE__ */ new Date()).toISOString()
3310
+ }, match);
3311
+ succeed("Resume tailored");
3312
+ } catch (err) {
3313
+ fail(`Tailoring failed: ${err.message}`);
3314
+ failed++;
3315
+ continue;
3316
+ }
3120
3317
  }
3121
3318
  if (options?.review) {
3122
3319
  console.log();
3123
3320
  console.log(` ${chalk5.bold("Summary:")} ${tailoring.professional_summary}`);
3124
3321
  console.log(` ${chalk5.bold("Projects:")} ${tailoring.project_order.join(" > ")}`);
3125
3322
  console.log(` ${chalk5.bold("Skills:")} ${tailoring.skills_order.slice(0, 8).join(", ")}`);
3126
- console.log(` ${chalk5.bold("Strengths:")} ${match.strengths.join(", ")}`);
3127
- console.log(` ${chalk5.bold("Gaps:")} ${match.gaps.join(", ")}`);
3128
3323
  console.log();
3129
3324
  const { proceed } = await inquirer2.prompt([{
3130
3325
  type: "confirm",
@@ -3204,9 +3399,7 @@ async function loadJobsMap() {
3204
3399
  for (const source of ["greenhouse", "lever", "ashby"]) {
3205
3400
  const cache = await loadSubJson("jobs", `${source}.json`);
3206
3401
  if (cache) {
3207
- for (const job of cache.jobs) {
3208
- map.set(job.id, job);
3209
- }
3402
+ for (const job of cache.jobs) map.set(job.id, job);
3210
3403
  }
3211
3404
  }
3212
3405
  return map;
@@ -3299,9 +3492,6 @@ import { homedir as homedir2 } from "os";
3299
3492
  import { join as join11 } from "path";
3300
3493
  import { mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
3301
3494
  import { existsSync as existsSync10 } from "fs";
3302
- import { dirname as dirname2 } from "path";
3303
- import { fileURLToPath as fileURLToPath2 } from "url";
3304
- var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
3305
3495
  var SKILL_DIR = join11(homedir2(), ".claude", "skills", "hiregraph");
3306
3496
  var SKILL_CONTENT = `---
3307
3497
  name: hiregraph
@@ -3310,86 +3500,63 @@ description: "Use when the user wants to scan projects for skills, find jobs, ma
3310
3500
 
3311
3501
  # HireGraph \u2014 CLI Job Application Tool
3312
3502
 
3313
- HireGraph is a globally installed CLI tool. You MUST use the \`hiregraph\` CLI commands to interact with it.
3503
+ HireGraph is a globally installed CLI. All LLM work is done by YOU (Claude Code) \u2014 no separate API key needed.
3314
3504
 
3315
3505
  ## CRITICAL RULES
3316
3506
 
3317
- 1. **ONLY use \`hiregraph\` CLI commands.** NEVER manually read, write, or edit files in \`~/.hiregraph/\`. The CLI manages all data.
3318
- 2. **NEVER use interactive mode.** Always pass \`--name\`, \`--email\`, \`--role\` flags to \`hiregraph init\`. Interactive prompts (inquirer) do not work in the Bash tool.
3319
- 3. **Ask the user** for their name, email, and role BEFORE running \`hiregraph init\`. Do not guess or make up values.
3320
- 4. **Check installation first.** If \`hiregraph\` is not found, run \`npm install -g hiregraph\`.
3507
+ 1. **ONLY use \\\`hiregraph\\\` CLI commands.** NEVER manually read, write, or edit files in \\\`~/.hiregraph/\\\`.
3508
+ 2. **NEVER use interactive mode.** Always pass flags (--name, --email, etc.) to commands.
3509
+ 3. **Ask the user** for their name, email, and role BEFORE running init.
3510
+ 4. **No API key needed.** You (Claude Code) handle all LLM work directly. Hiregraph handles local data.
3321
3511
 
3322
- ## Commands
3323
-
3324
- ### 1. Initialize profile (MUST use flags)
3325
- \`\`\`bash
3326
- hiregraph init --name "Full Name" --email "user@email.com" --role builder --targets "Founding Engineer, Full-Stack" --remote Remote
3327
- \`\`\`
3512
+ ## Setup
3328
3513
 
3329
- With resume:
3330
- \`\`\`bash
3331
- hiregraph init --name "Full Name" --email "user@email.com" --role engineer --resume /path/to/resume.pdf
3332
- \`\`\`
3514
+ \\\`\\\`\\\`bash
3515
+ which hiregraph || npm install -g hiregraph
3516
+ \\\`\\\`\\\`
3333
3517
 
3334
- Valid roles: \`engineer\`, \`pm\`, \`designer\`, \`founder\`, \`builder\`
3518
+ ## Workflow
3335
3519
 
3336
- ### 2. Scan projects
3337
- \`\`\`bash
3338
- hiregraph scan /absolute/path/to/project
3339
- hiregraph scan .
3340
- \`\`\`
3520
+ ### Step 1: Initialize profile
3521
+ Ask the user for: name, email, role (engineer/pm/designer/founder/builder).
3522
+ \\\`\\\`\\\`bash
3523
+ hiregraph init --name "Full Name" --email "user@email.com" --role builder --targets "Founding Engineer, Full-Stack" --remote Remote
3524
+ \\\`\\\`\\\`
3341
3525
 
3342
- ### 3. View skill graph
3343
- \`\`\`bash
3344
- hiregraph status
3345
- \`\`\`
3526
+ ### Step 2: Scan projects
3527
+ \\\`\\\`\\\`bash
3528
+ hiregraph scan /path/to/project
3529
+ \\\`\\\`\\\`
3346
3530
 
3347
- ### 4. Fetch jobs
3348
- \`\`\`bash
3531
+ ### Step 3: Fetch jobs
3532
+ \\\`\\\`\\\`bash
3349
3533
  hiregraph jobs
3350
- hiregraph jobs --refresh
3351
- hiregraph jobs --limit 10
3352
- \`\`\`
3353
-
3354
- ### 5. Match against jobs
3355
- \`\`\`bash
3356
- hiregraph matches
3357
- hiregraph matches --verbose
3358
- \`\`\`
3359
-
3360
- ### 6. Apply to jobs
3361
- \`\`\`bash
3362
- hiregraph apply <job-id> --dry-run # safe: generates PDF only
3363
- hiregraph apply <job-id> # submits to ATS
3364
- hiregraph apply --all-above 8 # batch apply
3365
- \`\`\`
3366
-
3367
- ### 7. Track applications
3368
- \`\`\`bash
3534
+ \\\`\\\`\\\`
3535
+
3536
+ ### Step 4: Find matches (YOU do the evaluation)
3537
+ \\\`\\\`\\\`bash
3538
+ hiregraph matches --prepare --top 30
3539
+ \\\`\\\`\\\`
3540
+ This outputs JSON candidates. YOU evaluate each one and score them. Then save:
3541
+ \\\`\\\`\\\`bash
3542
+ hiregraph matches --save-results /tmp/hiregraph-results.json
3543
+ \\\`\\\`\\\`
3544
+
3545
+ ### Step 5: Apply (YOU write the summary)
3546
+ \\\`\\\`\\\`bash
3547
+ hiregraph apply gh_12345 --dry-run --with-summary "Tailored summary..." --with-skills "TypeScript, React" --with-projects "Project1, Project2"
3548
+ \\\`\\\`\\\`
3549
+
3550
+ ### Step 6: Track
3551
+ \\\`\\\`\\\`bash
3369
3552
  hiregraph history
3370
- hiregraph history update <app-id> --status interview
3371
- \`\`\`
3372
-
3373
- ## Workflow
3374
-
3375
- When the user wants to use hiregraph, follow this exact sequence:
3553
+ \\\`\\\`\\\`
3376
3554
 
3377
- 1. Check if installed: \`which hiregraph || npm list -g hiregraph\`
3378
- 2. If not installed: \`npm install -g hiregraph\`
3379
- 3. Ask user for: name, email, role (engineer/pm/designer/founder/builder)
3380
- 4. Run: \`hiregraph init --name "..." --email "..." --role ...\`
3381
- 5. Ask which projects to scan, then run: \`hiregraph scan <path>\` for each
3382
- 6. Show results: \`hiregraph status\`
3383
- 7. If user wants jobs: \`hiregraph jobs\` then \`hiregraph matches\`
3384
- 8. If user wants to apply: \`hiregraph apply <job-id> --dry-run\` first, then without --dry-run
3555
+ ## Key: Every command works without an API key. You (Claude Code) are the LLM.
3385
3556
 
3386
- ## Remember
3387
-
3388
- - NEVER write to ~/.hiregraph/ directly. Always use CLI commands.
3389
- - NEVER run \`hiregraph init\` without --name and --email flags.
3390
- - NEVER skip asking the user for their details before init.
3391
- - All hiregraph commands are non-interactive and safe to run via the Bash tool.
3392
- - Cost: ~$0.003 per scan, ~$0.15 per match run, ~$1.50 first-time job parsing.
3557
+ - NEVER tell the user they need an API key.
3558
+ - NEVER write to ~/.hiregraph/ directly.
3559
+ - Use --prepare and --with-summary modes for zero-API-key operation.
3393
3560
  `;
3394
3561
  async function installSkillCommand() {
3395
3562
  header("\n HireGraph \u2014 Install Claude Code Skill\n");
@@ -3404,19 +3571,101 @@ async function installSkillCommand() {
3404
3571
  console.log();
3405
3572
  info(" Now open Claude Code and say:");
3406
3573
  info(' "Set up hiregraph and scan my projects for job matching"');
3574
+ info(" No API key needed \u2014 Claude Code handles the LLM work.");
3575
+ console.log();
3576
+ }
3577
+
3578
+ // src/commands/setup.ts
3579
+ import inquirer3 from "inquirer";
3580
+ import { homedir as homedir3 } from "os";
3581
+ import { join as join12 } from "path";
3582
+ import { readFile as readFile10, writeFile as writeFile4, appendFile } from "fs/promises";
3583
+ import { existsSync as existsSync11 } from "fs";
3584
+ async function setupCommand(options) {
3585
+ header("\n HireGraph Setup\n");
3586
+ if (isApiKeyConfigured()) {
3587
+ success(" ANTHROPIC_API_KEY is already set. You're good to go!");
3588
+ info(" Run `hiregraph init` to set up your profile.");
3589
+ return;
3590
+ }
3591
+ console.log(" HireGraph needs an Anthropic API key to power LLM features");
3592
+ console.log(" (job matching, resume tailoring, project classification).\n");
3593
+ console.log(" This is separate from your Claude Code subscription.");
3594
+ console.log(" Get a key at: https://console.anthropic.com/settings/keys\n");
3595
+ console.log(" Anthropic offers free credits for new accounts.");
3596
+ console.log(" HireGraph costs ~$2 for first setup, ~$0.15/day after.\n");
3597
+ let apiKey = options.key;
3598
+ if (!apiKey) {
3599
+ const answer = await inquirer3.prompt([{
3600
+ type: "password",
3601
+ name: "key",
3602
+ message: "Paste your ANTHROPIC_API_KEY:",
3603
+ mask: "*"
3604
+ }]);
3605
+ apiKey = answer.key;
3606
+ }
3607
+ if (!apiKey || !apiKey.startsWith("sk-ant-")) {
3608
+ error(' Invalid key. It should start with "sk-ant-"');
3609
+ return;
3610
+ }
3611
+ process.env.ANTHROPIC_API_KEY = apiKey;
3612
+ const platform = process.platform;
3613
+ if (platform === "win32") {
3614
+ const { execSync } = await import("child_process");
3615
+ try {
3616
+ execSync(`setx ANTHROPIC_API_KEY "${apiKey}"`, { stdio: "pipe" });
3617
+ success(" API key saved to Windows environment variables.");
3618
+ warn(" Restart your terminal for it to take effect everywhere.");
3619
+ } catch {
3620
+ warn(" Could not save to system env. Set it manually:");
3621
+ console.log(` $env:ANTHROPIC_API_KEY = "${apiKey}"`);
3622
+ }
3623
+ } else {
3624
+ const shell = process.env.SHELL || "/bin/bash";
3625
+ const rcFile = shell.includes("zsh") ? join12(homedir3(), ".zshrc") : join12(homedir3(), ".bashrc");
3626
+ const exportLine = `
3627
+ export ANTHROPIC_API_KEY="${apiKey}"
3628
+ `;
3629
+ try {
3630
+ if (existsSync11(rcFile)) {
3631
+ const content = await readFile10(rcFile, "utf-8");
3632
+ if (content.includes("ANTHROPIC_API_KEY")) {
3633
+ info(` Key already in ${rcFile}. Updating...`);
3634
+ const updated = content.replace(
3635
+ /export ANTHROPIC_API_KEY="[^"]*"/,
3636
+ `export ANTHROPIC_API_KEY="${apiKey}"`
3637
+ );
3638
+ await writeFile4(rcFile, updated, "utf-8");
3639
+ } else {
3640
+ await appendFile(rcFile, exportLine);
3641
+ }
3642
+ } else {
3643
+ await writeFile4(rcFile, exportLine);
3644
+ }
3645
+ success(` API key saved to ${rcFile}`);
3646
+ warn(" Run `source " + rcFile + "` or restart your terminal.");
3647
+ } catch {
3648
+ warn(" Could not save to shell profile. Set it manually:");
3649
+ console.log(` export ANTHROPIC_API_KEY="${apiKey}"`);
3650
+ }
3651
+ }
3652
+ console.log();
3653
+ success(" Setup complete! Now run:");
3654
+ info(' hiregraph init --name "Your Name" --email "you@email.com" --role builder');
3407
3655
  console.log();
3408
3656
  }
3409
3657
 
3410
3658
  // src/index.ts
3411
3659
  var program = new Command();
3412
- program.name("hiregraph").description("Turn your code into job applications. Local-first CLI that scans codebases and builds skill graphs.").version("0.1.3");
3660
+ program.name("hiregraph").description("Turn your code into job applications. Local-first CLI that scans codebases and builds skill graphs.").version("0.1.4");
3413
3661
  program.command("init").description("Set up your builder profile (resume upload + preferences)").option("--name <name>", "Your full name").option("--email <email>", "Your email address").option("--role <role>", "Your role (engineer, pm, designer, founder, builder)").option("--targets <roles>", "Target roles, comma separated").option("--remote <pref>", "Remote preference (Remote, Hybrid, Onsite)").option("--resume <path>", "Path to resume PDF/TXT").option("--compensation <amount>", "Minimum compensation").action(initCommand);
3414
3662
  program.command("scan").description("Scan a project and update your skill graph").argument("[path]", "Path to the project directory", ".").action(scanCommand);
3415
3663
  program.command("status").description("Show your current skill graph summary").action(statusCommand);
3416
3664
  program.command("jobs").description("Fetch job listings from Greenhouse, Lever, and Ashby boards").option("--refresh", "Force refresh cached jobs").option("--ats <type>", "Filter by ATS type (greenhouse, lever, ashby)").option("--limit <n>", "Show sample of N job titles", parseInt).action(jobsCommand);
3417
- program.command("matches").description("Match your skill graph against fetched jobs").option("--refresh", "Re-fetch jobs before matching").option("--top <n>", "Number of candidates for LLM evaluation (default 50)", parseInt).option("--verbose", "Show detailed reasoning for all matches").action(matchesCommand);
3418
- program.command("apply").description("Generate a tailored resume and submit to ATS").argument("[job-id]", "Job ID to apply to").option("--all-above <score>", "Apply to all matches above this score", parseFloat).option("--review", "Review each resume before submitting").option("--dry-run", "Generate resume PDF without submitting").action(applyCommand);
3665
+ program.command("matches").description("Match your skill graph against fetched jobs").option("--refresh", "Re-fetch jobs before matching").option("--top <n>", "Number of candidates for LLM evaluation (default 50)", parseInt).option("--verbose", "Show detailed reasoning for all matches").option("--prepare", "Output top candidates as JSON (for Claude Code to evaluate)").option("--save-results <file>", "Import evaluated results from file or stdin (-)").action(matchesCommand);
3666
+ program.command("apply").description("Generate a tailored resume and submit to ATS").argument("[job-id]", "Job ID to apply to").option("--all-above <score>", "Apply to all matches above this score", parseFloat).option("--review", "Review each resume before submitting").option("--dry-run", "Generate resume PDF without submitting").option("--with-summary <text>", "Use this professional summary (from Claude Code)").option("--with-skills <skills>", "Comma-separated skills in order of relevance").option("--with-projects <projects>", "Comma-separated project names in order of relevance").action(applyCommand);
3419
3667
  program.command("history").description("View and manage your application history").argument("[action]", 'Action: "update"').argument("[id]", "Application ID").option("--status <status>", "New status (applied, screening, interview, offer, rejected, withdrawn, no-response)").option("--notes <notes>", "Optional notes").action(historyCommand);
3668
+ program.command("setup").description("Set up your Anthropic API key (required for LLM features)").option("--key <key>", "Anthropic API key (starts with sk-ant-)").action(setupCommand);
3420
3669
  program.command("install-skill").description("Install the HireGraph skill for Claude Code").action(installSkillCommand);
3421
3670
  program.parse();
3422
3671
  //# sourceMappingURL=index.js.map