hiregraph 0.1.4 → 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 +289 -121
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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(
|
|
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
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
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
|
-
|
|
2759
|
+
data = Buffer.concat(chunks).toString("utf-8");
|
|
2760
|
+
} else {
|
|
2761
|
+
data = readFileSync(filePath, "utf-8");
|
|
2601
2762
|
}
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
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
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
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);
|
|
2613
2781
|
}
|
|
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
2782
|
console.log();
|
|
2622
|
-
|
|
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);
|
|
2789
|
+
}
|
|
2623
2790
|
console.log();
|
|
2624
|
-
} catch (err) {
|
|
2625
|
-
error(err.message);
|
|
2626
|
-
process.exit(1);
|
|
2627
2791
|
}
|
|
2792
|
+
if (result.strong_matches.length === 0 && result.suggested_matches.length === 0) {
|
|
2793
|
+
warn(" No matches above threshold.");
|
|
2794
|
+
console.log();
|
|
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
|
-
|
|
3104
|
-
|
|
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
|
-
|
|
3114
|
-
tailoring =
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
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
|
|
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
|
|
3318
|
-
2. **NEVER use interactive mode.** Always pass
|
|
3319
|
-
3. **Ask the user** for their name, email, and role BEFORE running
|
|
3320
|
-
4. **
|
|
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
|
-
##
|
|
3512
|
+
## Setup
|
|
3323
3513
|
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
\`\`\`
|
|
3328
|
-
|
|
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
|
-
|
|
3518
|
+
## Workflow
|
|
3335
3519
|
|
|
3336
|
-
###
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
hiregraph
|
|
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
|
-
###
|
|
3343
|
-
|
|
3344
|
-
hiregraph
|
|
3345
|
-
|
|
3526
|
+
### Step 2: Scan projects
|
|
3527
|
+
\\\`\\\`\\\`bash
|
|
3528
|
+
hiregraph scan /path/to/project
|
|
3529
|
+
\\\`\\\`\\\`
|
|
3346
3530
|
|
|
3347
|
-
###
|
|
3348
|
-
|
|
3531
|
+
### Step 3: Fetch jobs
|
|
3532
|
+
\\\`\\\`\\\`bash
|
|
3349
3533
|
hiregraph jobs
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
hiregraph apply
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
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
|
-
|
|
3371
|
-
\`\`\`
|
|
3372
|
-
|
|
3373
|
-
## Workflow
|
|
3374
|
-
|
|
3375
|
-
When the user wants to use hiregraph, follow this exact sequence:
|
|
3376
|
-
|
|
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
|
|
3553
|
+
\\\`\\\`\\\`
|
|
3385
3554
|
|
|
3386
|
-
##
|
|
3555
|
+
## Key: Every command works without an API key. You (Claude Code) are the LLM.
|
|
3387
3556
|
|
|
3388
|
-
- NEVER
|
|
3389
|
-
- NEVER
|
|
3390
|
-
-
|
|
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,6 +3571,7 @@ 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.");
|
|
3407
3575
|
console.log();
|
|
3408
3576
|
}
|
|
3409
3577
|
|
|
@@ -3494,8 +3662,8 @@ program.command("init").description("Set up your builder profile (resume upload
|
|
|
3494
3662
|
program.command("scan").description("Scan a project and update your skill graph").argument("[path]", "Path to the project directory", ".").action(scanCommand);
|
|
3495
3663
|
program.command("status").description("Show your current skill graph summary").action(statusCommand);
|
|
3496
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);
|
|
3497
|
-
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);
|
|
3498
|
-
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);
|
|
3499
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);
|
|
3500
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);
|
|
3501
3669
|
program.command("install-skill").description("Install the HireGraph skill for Claude Code").action(installSkillCommand);
|