hiregraph 0.1.0 → 0.1.2

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
@@ -122,7 +122,7 @@ async function parseResume(filePath) {
122
122
  throw new Error(`Unsupported resume format: ${ext}. Supported: .pdf, .txt, .md`);
123
123
  }
124
124
  if (!isApiKeyConfigured()) {
125
- throw new Error("No API key detected. Run hiregraph inside Claude Code or Cursor for resume parsing.");
125
+ throw new Error("ANTHROPIC_API_KEY not set. Set it in your environment variables.");
126
126
  }
127
127
  const response = await callHaiku(SYSTEM_PROMPT, `Extract structured data from this resume:
128
128
 
@@ -355,10 +355,12 @@ var ROLE_CHOICES = [
355
355
  { name: "Founder", value: "founder" },
356
356
  { name: "Builder", value: "builder" }
357
357
  ];
358
- async function initCommand() {
358
+ var VALID_ROLES = ROLE_CHOICES.map((c) => c.value);
359
+ async function initCommand(options) {
359
360
  header("\n HireGraph Init\n");
361
+ const isNonInteractive = !!(options.name && options.email);
360
362
  const existing = await loadJson("identity.json");
361
- if (existing) {
363
+ if (existing && !isNonInteractive) {
362
364
  const { overwrite } = await inquirer.prompt([{
363
365
  type: "confirm",
364
366
  name: "overwrite",
@@ -370,87 +372,111 @@ async function initCommand() {
370
372
  return;
371
373
  }
372
374
  }
373
- const { name, email } = await inquirer.prompt([
374
- { type: "input", name: "name", message: "Name:" },
375
- { type: "input", name: "email", message: "Email:" }
376
- ]);
375
+ let name;
376
+ let email;
377
+ let role;
378
+ let targetRoles;
379
+ let remotePref;
380
+ let minComp;
381
+ if (isNonInteractive) {
382
+ name = options.name;
383
+ email = options.email;
384
+ role = VALID_ROLES.includes(options.role || "") ? options.role : "engineer";
385
+ targetRoles = options.targets || "Founding Engineer, Full-Stack Engineer";
386
+ remotePref = options.remote || "Remote";
387
+ minComp = options.compensation || "";
388
+ } else {
389
+ ({ name, email } = await inquirer.prompt([
390
+ { type: "input", name: "name", message: "Name:" },
391
+ { type: "input", name: "email", message: "Email:" }
392
+ ]));
393
+ ({ role } = await inquirer.prompt([{
394
+ type: "list",
395
+ name: "role",
396
+ message: "What describes you best?",
397
+ choices: ROLE_CHOICES
398
+ }]));
399
+ ({ targetRoles } = await inquirer.prompt([{
400
+ type: "input",
401
+ name: "targetRoles",
402
+ message: "Target roles (comma separated):",
403
+ default: "Founding Engineer, Full-Stack Engineer"
404
+ }]));
405
+ ({ remotePref } = await inquirer.prompt([{
406
+ type: "list",
407
+ name: "remotePref",
408
+ message: "Remote preference?",
409
+ choices: ["Remote", "Hybrid", "Onsite", "No preference"]
410
+ }]));
411
+ ({ minComp } = await inquirer.prompt([{
412
+ type: "input",
413
+ name: "minComp",
414
+ message: "Min compensation (optional):",
415
+ default: ""
416
+ }]));
417
+ }
377
418
  let resumeData = null;
378
- const { hasResume } = await inquirer.prompt([{
379
- type: "confirm",
380
- name: "hasResume",
381
- message: "Do you have an existing resume? (PDF/TXT)",
382
- default: true
383
- }]);
384
- if (hasResume) {
385
- if (!isApiKeyConfigured()) {
386
- warn("No API key detected. Run inside Claude Code for resume parsing. Using manual flow.");
387
- } else {
388
- const { resumePath } = await inquirer.prompt([{
389
- type: "input",
390
- name: "resumePath",
391
- message: "Path to resume:"
392
- }]);
393
- const resolved = resolve(resumePath.trim().replace(/^["']|["']$/g, ""));
394
- if (existsSync2(resolved)) {
395
- start("Parsing resume...");
396
- try {
397
- resumeData = await parseResume(resolved);
398
- succeed("Resume parsed");
399
- info(` Name: ${resumeData.name}`);
400
- info(` Email: ${resumeData.email}`);
401
- if (resumeData.work_history.length > 0) {
402
- info(" Work History:");
403
- for (const w of resumeData.work_history) {
404
- info(` ${w.role} @ ${w.company} (${w.start_year}-${w.end_year || "present"})`);
405
- }
406
- }
407
- if (resumeData.skills.length > 0) {
408
- info(` Skills: ${resumeData.skills.join(", ")}`);
409
- }
410
- const { looksRight } = await inquirer.prompt([{
411
- type: "confirm",
412
- name: "looksRight",
413
- message: "Look right?",
414
- default: true
415
- }]);
416
- if (!looksRight) {
417
- info("Resume data discarded. Using manual input.");
418
- resumeData = null;
419
+ const resumePath = options.resume;
420
+ if (resumePath && isApiKeyConfigured()) {
421
+ const resolved = resolve(resumePath.trim().replace(/^["']|["']$/g, ""));
422
+ if (existsSync2(resolved)) {
423
+ start("Parsing resume...");
424
+ try {
425
+ resumeData = await parseResume(resolved);
426
+ succeed("Resume parsed");
427
+ info(` Name: ${resumeData.name}`);
428
+ info(` Email: ${resumeData.email}`);
429
+ if (resumeData.work_history.length > 0) {
430
+ info(" Work History:");
431
+ for (const w of resumeData.work_history) {
432
+ info(` ${w.role} @ ${w.company} (${w.start_year}-${w.end_year || "present"})`);
419
433
  }
420
- } catch (err) {
421
- fail("Failed to parse resume");
422
- error(err.message);
423
- resumeData = null;
424
434
  }
435
+ } catch (err) {
436
+ fail("Failed to parse resume");
437
+ error(err.message);
438
+ resumeData = null;
439
+ }
440
+ } else {
441
+ warn(`Resume file not found: ${resolved}`);
442
+ }
443
+ } else if (!isNonInteractive && !resumePath) {
444
+ const { hasResume } = await inquirer.prompt([{
445
+ type: "confirm",
446
+ name: "hasResume",
447
+ message: "Do you have an existing resume? (PDF/TXT)",
448
+ default: true
449
+ }]);
450
+ if (hasResume) {
451
+ if (!isApiKeyConfigured()) {
452
+ warn("ANTHROPIC_API_KEY not set \u2014 skipping resume parsing. Set it in your environment for full features.");
425
453
  } else {
426
- warn("File not found. Continuing without resume.");
454
+ const { rPath } = await inquirer.prompt([{
455
+ type: "input",
456
+ name: "rPath",
457
+ message: "Path to resume:"
458
+ }]);
459
+ const resolved = resolve(rPath.trim().replace(/^["']|["']$/g, ""));
460
+ if (existsSync2(resolved)) {
461
+ start("Parsing resume...");
462
+ try {
463
+ resumeData = await parseResume(resolved);
464
+ succeed("Resume parsed");
465
+ const { looksRight } = await inquirer.prompt([{
466
+ type: "confirm",
467
+ name: "looksRight",
468
+ message: "Look right?",
469
+ default: true
470
+ }]);
471
+ if (!looksRight) resumeData = null;
472
+ } catch (err) {
473
+ fail("Failed to parse resume");
474
+ error(err.message);
475
+ }
476
+ }
427
477
  }
428
478
  }
429
479
  }
430
- const { role } = await inquirer.prompt([{
431
- type: "list",
432
- name: "role",
433
- message: "What describes you best?",
434
- choices: ROLE_CHOICES
435
- }]);
436
- const { targetRoles } = await inquirer.prompt([{
437
- type: "input",
438
- name: "targetRoles",
439
- message: "Target roles (comma separated):",
440
- default: "Founding Engineer, Full-Stack Engineer"
441
- }]);
442
- const { remotePref } = await inquirer.prompt([{
443
- type: "list",
444
- name: "remotePref",
445
- message: "Remote preference?",
446
- choices: ["Remote", "Hybrid", "Onsite", "No preference"]
447
- }]);
448
- const { minComp } = await inquirer.prompt([{
449
- type: "input",
450
- name: "minComp",
451
- message: "Min compensation (optional):",
452
- default: ""
453
- }]);
454
480
  const targets = targetRoles.split(",").map((r) => r.trim()).filter(Boolean);
455
481
  const identity = resumeData ? resumeToIdentity(resumeData, role, targets, remotePref, minComp) : {
456
482
  name,
@@ -1591,7 +1617,7 @@ async function scanCommand(path) {
1591
1617
  fail("Layer 7: LLM classification failed");
1592
1618
  }
1593
1619
  } else {
1594
- dim(" Layer 7: Skipped (no API key \u2014 run inside Claude Code for full analysis)");
1620
+ dim(" Layer 7: Skipped (set ANTHROPIC_API_KEY for LLM classification)");
1595
1621
  }
1596
1622
  const scanResult = {
1597
1623
  project_name: projectName,
@@ -2036,7 +2062,7 @@ var BATCH_SIZE2 = 10;
2036
2062
  var BATCH_DELAY_MS2 = 1e3;
2037
2063
  async function parseJobsBatch(jobs) {
2038
2064
  if (!isApiKeyConfigured()) {
2039
- throw new Error("No API key detected. Run hiregraph inside Claude Code or Cursor.");
2065
+ throw new Error("ANTHROPIC_API_KEY not set. Set it in your environment variables.");
2040
2066
  }
2041
2067
  const cached = await loadSubJson("jobs", "parsed.json");
2042
2068
  const requirements = cached?.requirements || {};
@@ -2372,7 +2398,7 @@ var BATCH_SIZE3 = 5;
2372
2398
  var BATCH_DELAY_MS3 = 1500;
2373
2399
  async function evaluateMatchesBatch(candidates, graph) {
2374
2400
  if (!isApiKeyConfigured()) {
2375
- throw new Error("No API key detected. Run hiregraph inside Claude Code or Cursor.");
2401
+ throw new Error("ANTHROPIC_API_KEY not set. Set it in your environment variables.");
2376
2402
  }
2377
2403
  const skillSummary = buildSkillSummary(graph);
2378
2404
  const results = [];
@@ -2557,7 +2583,7 @@ async function runMatchPipeline(options) {
2557
2583
  async function matchesCommand(options) {
2558
2584
  header("\n HireGraph Matches\n");
2559
2585
  if (!isApiKeyConfigured()) {
2560
- warn("No API key detected. Run hiregraph inside Claude Code or Cursor for automatic access.");
2586
+ warn('ANTHROPIC_API_KEY not set. Add it to your environment variables or run: $env:ANTHROPIC_API_KEY = "your-key"');
2561
2587
  return;
2562
2588
  }
2563
2589
  try {
@@ -3019,7 +3045,7 @@ async function submitToAshby(rawJobId, pdfBuffer, identity) {
3019
3045
  async function applyCommand(jobId, options) {
3020
3046
  header("\n HireGraph Apply\n");
3021
3047
  if (!isApiKeyConfigured()) {
3022
- warn("No API key detected. Run hiregraph inside Claude Code or Cursor.");
3048
+ warn('ANTHROPIC_API_KEY not set. Add it to your environment variables or run: $env:ANTHROPIC_API_KEY = "your-key"');
3023
3049
  return;
3024
3050
  }
3025
3051
  const graph = await loadGraph();
@@ -3270,8 +3296,8 @@ function getTimeAgo2(isoDate) {
3270
3296
 
3271
3297
  // src/index.ts
3272
3298
  var program = new Command();
3273
- program.name("hiregraph").description("Turn your code into job applications. Local-first CLI that scans codebases and builds skill graphs.").version("0.1.0");
3274
- program.command("init").description("Set up your builder profile (resume upload + preferences)").action(initCommand);
3299
+ program.name("hiregraph").description("Turn your code into job applications. Local-first CLI that scans codebases and builds skill graphs.").version("0.1.2");
3300
+ 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);
3275
3301
  program.command("scan").description("Scan a project and update your skill graph").argument("[path]", "Path to the project directory", ".").action(scanCommand);
3276
3302
  program.command("status").description("Show your current skill graph summary").action(statusCommand);
3277
3303
  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);