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 +22 -1
- package/dist/index.js +218 -78
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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
|
-
|
|
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.
|
|
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
|