hiregraph 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -12,7 +12,28 @@ Open Source | Local-First | Bring Your Own Key
12
12
  npm install -g hiregraph
13
13
  ```
14
14
 
15
- That's it. No account. No sign up. HireGraph reads `ANTHROPIC_API_KEY` from your environment — the same key Claude Code or Cursor already uses.
15
+ ### For Claude Code users (recommended)
16
+
17
+ ```bash
18
+ npm install -g hiregraph
19
+ hiregraph install-skill
20
+ ```
21
+
22
+ Then open Claude Code and say: *"Set up hiregraph and scan my projects"*. Claude Code handles everything — no manual commands needed.
23
+
24
+ ### Or install the skill directly
25
+
26
+ ```bash
27
+ npx skills add https://github.com/c0ncepT23/hiregraph --skill hiregraph --yes --global
28
+ ```
29
+
30
+ ### API Key
31
+
32
+ HireGraph reads `ANTHROPIC_API_KEY` from your environment. Set it once:
33
+
34
+ - **Mac/Linux:** `export ANTHROPIC_API_KEY="sk-ant-..."` (add to `~/.bashrc` or `~/.zshrc`)
35
+ - **Windows:** Add `ANTHROPIC_API_KEY` to System Environment Variables
36
+ - **Claude Code:** Already set automatically
16
37
 
17
38
  ## Quick Start
18
39
 
package/dist/index.js CHANGED
@@ -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("ANTHROPIC_API_KEY not set \u2014 skipping resume parsing. Set it in your environment for full features.");
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,
@@ -3268,15 +3294,129 @@ function getTimeAgo2(isoDate) {
3268
3294
  return `${days}d ago`;
3269
3295
  }
3270
3296
 
3297
+ // src/commands/install-skill.ts
3298
+ import { homedir as homedir2 } from "os";
3299
+ import { join as join11 } from "path";
3300
+ import { mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
3301
+ 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
+ var SKILL_DIR = join11(homedir2(), ".claude", "skills", "hiregraph");
3306
+ var SKILL_CONTENT = `---
3307
+ name: hiregraph
3308
+ description: "Use when the user wants to scan projects for skills, find jobs, match against job listings, generate resumes, apply to jobs, or track applications. Triggered by: job search, resume, skill graph, apply to jobs, find matches, hiregraph, scan project skills."
3309
+ ---
3310
+
3311
+ # HireGraph \u2014 CLI Job Application Tool
3312
+
3313
+ HireGraph is a globally installed CLI tool. You MUST use the \`hiregraph\` CLI commands to interact with it.
3314
+
3315
+ ## CRITICAL RULES
3316
+
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\`.
3321
+
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
+ \`\`\`
3328
+
3329
+ With resume:
3330
+ \`\`\`bash
3331
+ hiregraph init --name "Full Name" --email "user@email.com" --role engineer --resume /path/to/resume.pdf
3332
+ \`\`\`
3333
+
3334
+ Valid roles: \`engineer\`, \`pm\`, \`designer\`, \`founder\`, \`builder\`
3335
+
3336
+ ### 2. Scan projects
3337
+ \`\`\`bash
3338
+ hiregraph scan /absolute/path/to/project
3339
+ hiregraph scan .
3340
+ \`\`\`
3341
+
3342
+ ### 3. View skill graph
3343
+ \`\`\`bash
3344
+ hiregraph status
3345
+ \`\`\`
3346
+
3347
+ ### 4. Fetch jobs
3348
+ \`\`\`bash
3349
+ 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
3369
+ 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:
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
3385
+
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.
3393
+ `;
3394
+ async function installSkillCommand() {
3395
+ header("\n HireGraph \u2014 Install Claude Code Skill\n");
3396
+ if (!existsSync10(join11(homedir2(), ".claude"))) {
3397
+ warn(" Claude Code not detected (~/.claude/ not found).");
3398
+ info(" Install Claude Code first: https://claude.ai/code");
3399
+ return;
3400
+ }
3401
+ await mkdir2(SKILL_DIR, { recursive: true });
3402
+ await writeFile3(join11(SKILL_DIR, "SKILL.md"), SKILL_CONTENT, "utf-8");
3403
+ success(" Skill installed to ~/.claude/skills/hiregraph/SKILL.md");
3404
+ console.log();
3405
+ info(" Now open Claude Code and say:");
3406
+ info(' "Set up hiregraph and scan my projects for job matching"');
3407
+ console.log();
3408
+ }
3409
+
3271
3410
  // src/index.ts
3272
3411
  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);
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");
3413
+ 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
3414
  program.command("scan").description("Scan a project and update your skill graph").argument("[path]", "Path to the project directory", ".").action(scanCommand);
3276
3415
  program.command("status").description("Show your current skill graph summary").action(statusCommand);
3277
3416
  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);
3278
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);
3279
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);
3280
3419
  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);
3420
+ program.command("install-skill").description("Install the HireGraph skill for Claude Code").action(installSkillCommand);
3281
3421
  program.parse();
3282
3422
  //# sourceMappingURL=index.js.map