careerclaw-js 0.11.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.
Files changed (81) hide show
  1. package/CHANGELOG.md +362 -0
  2. package/README.md +348 -0
  3. package/SECURITY.md +156 -0
  4. package/SKILL.md +463 -0
  5. package/dist/adapters/hackernews.d.ts +36 -0
  6. package/dist/adapters/hackernews.d.ts.map +1 -0
  7. package/dist/adapters/hackernews.js +164 -0
  8. package/dist/adapters/hackernews.js.map +1 -0
  9. package/dist/adapters/index.d.ts +10 -0
  10. package/dist/adapters/index.d.ts.map +1 -0
  11. package/dist/adapters/index.js +9 -0
  12. package/dist/adapters/index.js.map +1 -0
  13. package/dist/adapters/remoteok.d.ts +35 -0
  14. package/dist/adapters/remoteok.d.ts.map +1 -0
  15. package/dist/adapters/remoteok.js +212 -0
  16. package/dist/adapters/remoteok.js.map +1 -0
  17. package/dist/briefing.d.ts +81 -0
  18. package/dist/briefing.d.ts.map +1 -0
  19. package/dist/briefing.js +152 -0
  20. package/dist/briefing.js.map +1 -0
  21. package/dist/cli.d.ts +22 -0
  22. package/dist/cli.d.ts.map +1 -0
  23. package/dist/cli.js +235 -0
  24. package/dist/cli.js.map +1 -0
  25. package/dist/config.d.ts +91 -0
  26. package/dist/config.d.ts.map +1 -0
  27. package/dist/config.js +126 -0
  28. package/dist/config.js.map +1 -0
  29. package/dist/core/text-processing.d.ts +62 -0
  30. package/dist/core/text-processing.d.ts.map +1 -0
  31. package/dist/core/text-processing.js +187 -0
  32. package/dist/core/text-processing.js.map +1 -0
  33. package/dist/drafting.d.ts +28 -0
  34. package/dist/drafting.d.ts.map +1 -0
  35. package/dist/drafting.js +116 -0
  36. package/dist/drafting.js.map +1 -0
  37. package/dist/gap.d.ts +27 -0
  38. package/dist/gap.d.ts.map +1 -0
  39. package/dist/gap.js +90 -0
  40. package/dist/gap.js.map +1 -0
  41. package/dist/license.d.ts +40 -0
  42. package/dist/license.d.ts.map +1 -0
  43. package/dist/license.js +122 -0
  44. package/dist/license.js.map +1 -0
  45. package/dist/llm-enhance.d.ts +69 -0
  46. package/dist/llm-enhance.d.ts.map +1 -0
  47. package/dist/llm-enhance.js +376 -0
  48. package/dist/llm-enhance.js.map +1 -0
  49. package/dist/matching/engine.d.ts +31 -0
  50. package/dist/matching/engine.d.ts.map +1 -0
  51. package/dist/matching/engine.js +51 -0
  52. package/dist/matching/engine.js.map +1 -0
  53. package/dist/matching/index.d.ts +8 -0
  54. package/dist/matching/index.d.ts.map +1 -0
  55. package/dist/matching/index.js +8 -0
  56. package/dist/matching/index.js.map +1 -0
  57. package/dist/matching/scoring.d.ts +84 -0
  58. package/dist/matching/scoring.d.ts.map +1 -0
  59. package/dist/matching/scoring.js +184 -0
  60. package/dist/matching/scoring.js.map +1 -0
  61. package/dist/models.d.ts +221 -0
  62. package/dist/models.d.ts.map +1 -0
  63. package/dist/models.js +28 -0
  64. package/dist/models.js.map +1 -0
  65. package/dist/requirements.d.ts +22 -0
  66. package/dist/requirements.d.ts.map +1 -0
  67. package/dist/requirements.js +30 -0
  68. package/dist/requirements.js.map +1 -0
  69. package/dist/resume-intel.d.ts +40 -0
  70. package/dist/resume-intel.d.ts.map +1 -0
  71. package/dist/resume-intel.js +111 -0
  72. package/dist/resume-intel.js.map +1 -0
  73. package/dist/sources.d.ts +32 -0
  74. package/dist/sources.d.ts.map +1 -0
  75. package/dist/sources.js +72 -0
  76. package/dist/sources.js.map +1 -0
  77. package/dist/tracking.d.ts +68 -0
  78. package/dist/tracking.d.ts.map +1 -0
  79. package/dist/tracking.js +140 -0
  80. package/dist/tracking.js.map +1 -0
  81. package/package.json +58 -0
package/dist/cli.js ADDED
@@ -0,0 +1,235 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cli.ts — CareerClaw command-line entry point.
4
+ *
5
+ * Usage:
6
+ * npx careerclaw-js [options]
7
+ * node --env-file=.env dist/cli.js [options]
8
+ *
9
+ * Options:
10
+ * --profile PATH Path to profile.json (default: ~/.careerclaw/profile.json)
11
+ * --resume-txt PATH Plain-text resume file to enhance matching
12
+ * --top-k INT Number of top matches to return (default: 3)
13
+ * --dry-run Run without writing tracking or run log
14
+ * --json Print JSON output only (machine-readable; no colour)
15
+ * --help Print this help message and exit
16
+ *
17
+ * Exit codes:
18
+ * 0 — briefing completed (even if 0 matches — not an error)
19
+ * 1 — fatal error (profile not found, unreadable, or invalid JSON)
20
+ */
21
+ import { parseArgs } from "node:util";
22
+ import { readFileSync, existsSync } from "node:fs";
23
+ import { runBriefing } from "./briefing.js";
24
+ import { PROFILE_PATH, RESUME_TXT_PATH, DEFAULT_TOP_K, PRO_KEY, } from "./config.js";
25
+ import { buildResumeIntelligence } from "./resume-intel.js";
26
+ // ---------------------------------------------------------------------------
27
+ // Arg parsing
28
+ // ---------------------------------------------------------------------------
29
+ const { values: args } = parseArgs({
30
+ options: {
31
+ profile: { type: "string", short: "p" },
32
+ "resume-txt": { type: "string" },
33
+ "top-k": { type: "string", short: "k" },
34
+ "dry-run": { type: "boolean", short: "d", default: false },
35
+ json: { type: "boolean", short: "j", default: false },
36
+ help: { type: "boolean", short: "h", default: false },
37
+ },
38
+ strict: true,
39
+ allowPositionals: false,
40
+ });
41
+ if (args.help) {
42
+ printHelp();
43
+ process.exit(0);
44
+ }
45
+ const profilePath = args["profile"] ?? PROFILE_PATH;
46
+ const resumeTxtPath = args["resume-txt"] ?? null;
47
+ const topK = parseInt(args["top-k"] ?? String(DEFAULT_TOP_K), 10);
48
+ const dryRun = args["dry-run"];
49
+ const jsonMode = args["json"];
50
+ if (isNaN(topK) || topK < 1) {
51
+ fatal(`--top-k must be a positive integer, got: ${args["top-k"]}`);
52
+ }
53
+ // ---------------------------------------------------------------------------
54
+ // Load profile
55
+ // ---------------------------------------------------------------------------
56
+ function loadProfile(path) {
57
+ if (!existsSync(path)) {
58
+ fatal(`Profile not found: ${path}\n` +
59
+ ` Create one at ${path} or pass --profile PATH.\n` +
60
+ ` See README for the profile schema.`);
61
+ }
62
+ let raw;
63
+ try {
64
+ raw = readFileSync(path, "utf8");
65
+ }
66
+ catch (err) {
67
+ fatal(`Could not read profile: ${path}\n ${String(err)}`);
68
+ }
69
+ try {
70
+ return JSON.parse(raw);
71
+ }
72
+ catch (err) {
73
+ fatal(`Profile is not valid JSON: ${path}\n ${String(err)}`);
74
+ }
75
+ }
76
+ // ---------------------------------------------------------------------------
77
+ // Load resume (optional)
78
+ // ---------------------------------------------------------------------------
79
+ function loadResumeTxt(path) {
80
+ if (!existsSync(path)) {
81
+ warn(`Resume file not found: ${path} — running without resume intelligence`);
82
+ return null;
83
+ }
84
+ try {
85
+ return readFileSync(path, "utf8");
86
+ }
87
+ catch {
88
+ warn(`Could not read resume: ${path} — running without resume intelligence`);
89
+ return null;
90
+ }
91
+ }
92
+ // ---------------------------------------------------------------------------
93
+ // Console formatter — mirrors Python output format
94
+ // ---------------------------------------------------------------------------
95
+ function printBriefing(result, profile) {
96
+ const { run, matches, drafts, tracking, dry_run } = result;
97
+ const sources = Object.entries(run.sources)
98
+ .map(([s, n]) => `${s}: ${n}`)
99
+ .join(" | ");
100
+ console.log(`\n=== CareerClaw Daily Briefing ===`);
101
+ console.log(`Fetched jobs: ${run.jobs_fetched} | Sources: ${sources}`);
102
+ console.log(`Duration: ${run.timings.fetch_ms ?? 0}ms fetch + ${run.timings.rank_ms ?? 0}ms rank`);
103
+ if (dry_run)
104
+ console.log(`(dry-run — no files written)`);
105
+ console.log();
106
+ // ---- Top matches ----
107
+ if (matches.length === 0) {
108
+ console.log(`No matches found for your profile.`);
109
+ console.log(`Try broadening your skills list or checking source health with:`);
110
+ console.log(` npm run smoke\n`);
111
+ }
112
+ else {
113
+ console.log(`Top Matches:\n`);
114
+ }
115
+ for (let i = 0; i < matches.length; i++) {
116
+ const m = matches[i];
117
+ const fitPct = Math.round(m.breakdown.keyword * 100);
118
+ const matchStr = m.matched_keywords.slice(0, 5).join(", ") || "(none)";
119
+ const gapStr = m.gap_keywords.slice(0, 5).join(", ") || "(none)";
120
+ console.log(`${i + 1}) ${m.job.title} @ ${m.job.company} [${m.job.source}]`);
121
+ console.log(` score: ${m.score.toFixed(4)} | fit: ${fitPct}%`);
122
+ console.log(` matches: ${matchStr}`);
123
+ console.log(` gaps: ${gapStr}`);
124
+ console.log(` location: ${m.job.location || "(not specified)"}`);
125
+ console.log(` url: ${m.job.url}`);
126
+ console.log();
127
+ }
128
+ // ---- Drafts ----
129
+ console.log(`Drafts:\n`);
130
+ for (let i = 0; i < drafts.length; i++) {
131
+ const d = drafts[i];
132
+ console.log(`--- Draft #${i + 1} ---`);
133
+ console.log(`Subject: ${d.subject}`);
134
+ console.log();
135
+ console.log(d.body);
136
+ console.log();
137
+ }
138
+ // ---- Tracking summary ----
139
+ console.log(`Tracking:`);
140
+ console.log(` ${tracking.created} new job(s) saved`);
141
+ if (tracking.already_present > 0) {
142
+ console.log(` ${tracking.already_present} already in your tracker (last_seen_at updated)`);
143
+ }
144
+ console.log();
145
+ }
146
+ // ---------------------------------------------------------------------------
147
+ // Main
148
+ // ---------------------------------------------------------------------------
149
+ async function main() {
150
+ const profile = loadProfile(profilePath);
151
+ // Resume text is loaded for future LLM enhancement (Phase 10).
152
+ // It is NOT merged into profile.skills — that would inflate the token
153
+ // corpus with generic words and collapse Jaccard scores.
154
+ // profile.skills + profile.resume_summary already provide the correct
155
+ // matching corpus. Resume intelligence will be wired as a weighted
156
+ // signal layer when the LLM enhancement module ships.
157
+ // Resume text is loaded and converted to keyword signals for LLM enhancement.
158
+ // Raw text is NEVER forwarded to the LLM — only extracted impact_signals.
159
+ const resumePath = resumeTxtPath ?? (existsSync(RESUME_TXT_PATH) ? RESUME_TXT_PATH : null);
160
+ let resumeText = null;
161
+ if (resumePath) {
162
+ resumeText = loadResumeTxt(resumePath);
163
+ if (resumeText && !jsonMode) {
164
+ console.log(`Resume loaded: ${resumePath} (ready for Pro enhancement)`);
165
+ }
166
+ }
167
+ const intelParams = {
168
+ resume_summary: profile.resume_summary ?? "",
169
+ skills: profile.skills,
170
+ target_roles: profile.target_roles,
171
+ };
172
+ if (resumeText)
173
+ intelParams.resume_text = resumeText;
174
+ const resumeIntel = buildResumeIntelligence(intelParams);
175
+ const briefingOptions = { topK, dryRun, resumeIntel };
176
+ if (PRO_KEY)
177
+ briefingOptions.proKey = PRO_KEY;
178
+ const result = await runBriefing(profile, briefingOptions);
179
+ if (jsonMode) {
180
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
181
+ process.exit(0);
182
+ }
183
+ printBriefing(result, profile);
184
+ process.exit(0);
185
+ }
186
+ // ---------------------------------------------------------------------------
187
+ // Helpers
188
+ // ---------------------------------------------------------------------------
189
+ function fatal(message) {
190
+ console.error(`\nError: ${message}\n`);
191
+ process.exit(1);
192
+ }
193
+ function warn(message) {
194
+ console.warn(`Warning: ${message}`);
195
+ }
196
+ function printHelp() {
197
+ console.log(`
198
+ careerclaw-js — AI-powered job search briefing
199
+
200
+ Usage:
201
+ careerclaw-js [options]
202
+ node --env-file=.env dist/cli.js [options]
203
+
204
+ Options:
205
+ -p, --profile PATH Path to profile.json
206
+ (default: ~/.careerclaw/profile.json)
207
+ --resume-txt PATH Plain-text resume to enhance keyword matching
208
+ (default: ~/.careerclaw/resume.txt if present)
209
+ -k, --top-k INT Number of top matches to return (default: 3)
210
+ -d, --dry-run Run without writing tracking or run log
211
+ -j, --json Machine-readable JSON output (no colour, no headers)
212
+ -h, --help Show this help message
213
+
214
+ Examples:
215
+ # First run — dry-run to preview without writing files
216
+ careerclaw-js --dry-run
217
+
218
+ # With your resume for better match quality
219
+ careerclaw-js --resume-txt ~/.careerclaw/resume.txt --dry-run
220
+
221
+ # JSON output for agent/script consumption
222
+ careerclaw-js --json --dry-run
223
+
224
+ # Custom profile and top 5 results
225
+ careerclaw-js --profile ./my-profile.json --top-k 5
226
+
227
+ Environment:
228
+ node --env-file=.env dist/cli.js Load credentials from .env (Node 20+)
229
+ Copy .env.example to .env and fill in CAREERCLAW_PRO_KEY and LLM keys.
230
+ `);
231
+ }
232
+ main().catch((err) => {
233
+ fatal(String(err));
234
+ });
235
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * config.ts — Environment and source configuration for CareerClaw.
3
+ *
4
+ * All tuneable values live here. Env-vars are read once at import time so
5
+ * that callers can treat `config` as a plain object. No third-party dotenv
6
+ * library is required — Node 20+ reads .env automatically when launched with
7
+ * --env-file, and the OpenClaw gateway passes secrets as real env vars.
8
+ */
9
+ export declare const CAREERCLAW_DIR: string;
10
+ export declare const PROFILE_PATH: string;
11
+ export declare const TRACKING_PATH: string;
12
+ export declare const RUNS_PATH: string;
13
+ export declare const RESUME_TXT_PATH: string;
14
+ export declare const RESUME_PDF_PATH: string;
15
+ export declare const LICENSE_CACHE_PATH: string;
16
+ /** Milliseconds before an outbound HTTP request is aborted. */
17
+ export declare const HTTP_TIMEOUT_MS = 15000;
18
+ /**
19
+ * User-Agent sent with all outbound requests.
20
+ * Identifies the tool and provides a contact point per robots.txt convention.
21
+ */
22
+ export declare const USER_AGENT = "careerclaw-js/0.11.0 (https://github.com/orestes-garcia-martinez/careerclaw-js)";
23
+ /** RemoteOK RSS feed — public, no auth required. */
24
+ export declare const REMOTEOK_RSS_URL = "https://remoteok.com/remote-jobs.rss";
25
+ /**
26
+ * Hacker News "Who is Hiring?" thread ID.
27
+ *
28
+ * HN posts a new thread on the first weekday of each month. This value
29
+ * must be updated manually each month. The adapter degrades gracefully
30
+ * (returns []) when the thread cannot be fetched.
31
+ *
32
+ * Override via env var HN_WHO_IS_HIRING_ID for one-off testing.
33
+ *
34
+ * To find the current thread ID: search HN for "Ask HN: Who is hiring?"
35
+ * and copy the numeric ID from the URL (e.g. https://news.ycombinator.com/item?id=43354977).
36
+ */
37
+ export declare const HN_WHO_IS_HIRING_ID: number;
38
+ /** HN Firebase API base URL — public, no auth required. */
39
+ export declare const HN_API_BASE = "https://hacker-news.firebaseio.com/v0";
40
+ /** Maximum number of HN comment IDs to fetch per briefing run. */
41
+ export declare const HN_MAX_COMMENTS = 200;
42
+ /** Default number of top matches to return. */
43
+ export declare const DEFAULT_TOP_K = 3;
44
+ /**
45
+ * Provider-specific LLM keys for draft enhancement (recommended).
46
+ * These take precedence over the legacy CAREERCLAW_LLM_KEY.
47
+ * Never written to disk or logged.
48
+ */
49
+ export declare const LLM_ANTHROPIC_KEY: string | undefined;
50
+ export declare const LLM_OPENAI_KEY: string | undefined;
51
+ /**
52
+ * Legacy single-key override. Used when the provider-specific key above
53
+ * is absent. Not recommended for mixed failover chains.
54
+ */
55
+ export declare const LLM_API_KEY: string | undefined;
56
+ /** LLM provider: "anthropic" | "openai". Defaults to "anthropic". */
57
+ export declare const LLM_PROVIDER: string;
58
+ /** Model to use for draft enhancement. Default: fast, low-cost Haiku. */
59
+ export declare const LLM_MODEL: string;
60
+ /**
61
+ * Comma-separated provider/model failover chain.
62
+ * Tried left to right on failure.
63
+ */
64
+ export declare const LLM_CHAIN: string;
65
+ /** Max retries per chain candidate before trying the next. */
66
+ export declare const LLM_MAX_RETRIES: number;
67
+ /** Consecutive failures before the circuit breaker opens. */
68
+ export declare const LLM_CIRCUIT_BREAKER_FAILS: number;
69
+ /** CareerClaw Pro license key. Never written to disk raw — only a SHA-256 hash is cached. */
70
+ export declare const PRO_KEY: string | undefined;
71
+ /**
72
+ * Gumroad product ID for license key verification.
73
+ *
74
+ * Find it in your Gumroad dashboard — product — Content — License key module.
75
+ * Required for Pro license validation. If unset, checkLicense() returns
76
+ * { valid: false, source: "none" } immediately — Free tier still works.
77
+ */
78
+ export declare const GUMROAD_PRODUCT_ID: string | undefined;
79
+ /** Gumroad license verification API base URL. */
80
+ export declare const GUMROAD_API_BASE = "https://api.gumroad.com";
81
+ /**
82
+ * How long a cached license validation remains valid without re-checking
83
+ * the Gumroad API. After this window the cache is stale and a live API
84
+ * call is required.
85
+ */
86
+ export declare const LICENSE_CACHE_TTL_MS: number;
87
+ /** @deprecated Polar is not yet the active processor. Use GUMROAD_* constants. */
88
+ export declare const POLAR_PRODUCT_SLUG: string;
89
+ /** @deprecated Polar is not yet the active processor. Use GUMROAD_API_BASE. */
90
+ export declare const POLAR_API_BASE = "https://api.polar.sh";
91
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAeH,eAAO,MAAM,cAAc,EAAE,MAGS,CAAC;AAEvC,eAAO,MAAM,YAAY,EAAE,MAA6C,CAAC;AACzE,eAAO,MAAM,aAAa,EAAE,MAA8C,CAAC;AAC3E,eAAO,MAAM,SAAS,EAAE,MAA2C,CAAC;AACpE,eAAO,MAAM,eAAe,EAAE,MAA2C,CAAC;AAC1E,eAAO,MAAM,eAAe,EAAE,MAA2C,CAAC;AAC1E,eAAO,MAAM,kBAAkB,EAAE,MAGhC,CAAC;AAMF,+DAA+D;AAC/D,eAAO,MAAM,eAAe,QAAS,CAAC;AAEtC;;;GAGG;AACH,eAAO,MAAM,UAAU,oFAC2D,CAAC;AAMnF,oDAAoD;AACpD,eAAO,MAAM,gBAAgB,yCAAyC,CAAC;AAEvE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAGjC,CAAC;AAEF,2DAA2D;AAC3D,eAAO,MAAM,WAAW,0CAA0C,CAAC;AAEnE,kEAAkE;AAClE,eAAO,MAAM,eAAe,MAAM,CAAC;AAMnC,+CAA+C;AAC/C,eAAO,MAAM,aAAa,IAAI,CAAC;AAM/B;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,GAAG,SACD,CAAC;AAEzC,eAAO,MAAM,cAAc,EAAE,MAAM,GAAG,SACD,CAAC;AAEtC;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,GAAG,SAA6C,CAAC;AAEjF,qEAAqE;AACrE,eAAO,MAAM,YAAY,EAAE,MAC2B,CAAC;AAEvD,yEAAyE;AACzE,eAAO,MAAM,SAAS,EAAE,MAC2C,CAAC;AAEpE;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,MAEiC,CAAC;AAE1D,8DAA8D;AAC9D,eAAO,MAAM,eAAe,EAAE,MAG7B,CAAC;AAEF,6DAA6D;AAC7D,eAAO,MAAM,yBAAyB,EAAE,MAGvC,CAAC;AAMF,6FAA6F;AAC7F,eAAO,MAAM,OAAO,EAAE,MAAM,GAAG,SAA6C,CAAC;AAM7E;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,GAAG,SACG,CAAC;AAE9C,iDAAiD;AACjD,eAAO,MAAM,gBAAgB,4BAA4B,CAAC;AAE1D;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,QAA0B,CAAC;AAO5D,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,QAC0B,CAAC;AAE1D,+EAA+E;AAC/E,eAAO,MAAM,cAAc,yBAAyB,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1,126 @@
1
+ /**
2
+ * config.ts — Environment and source configuration for CareerClaw.
3
+ *
4
+ * All tuneable values live here. Env-vars are read once at import time so
5
+ * that callers can treat `config` as a plain object. No third-party dotenv
6
+ * library is required — Node 20+ reads .env automatically when launched with
7
+ * --env-file, and the OpenClaw gateway passes secrets as real env vars.
8
+ */
9
+ import { join } from "node:path";
10
+ // ---------------------------------------------------------------------------
11
+ // Runtime directory
12
+ // ---------------------------------------------------------------------------
13
+ /**
14
+ * Root directory for all CareerClaw runtime state.
15
+ * Mirrors Python's `.careerclaw/` convention.
16
+ * Can be overridden via CAREERCLAW_DIR for testing.
17
+ */
18
+ const envCareerClawDir = process.env["CAREERCLAW_DIR"]?.trim();
19
+ export const CAREERCLAW_DIR = envCareerClawDir && envCareerClawDir.length > 0
20
+ ? envCareerClawDir
21
+ : join(process.cwd(), ".careerclaw");
22
+ export const PROFILE_PATH = join(CAREERCLAW_DIR, "profile.json");
23
+ export const TRACKING_PATH = join(CAREERCLAW_DIR, "tracking.json");
24
+ export const RUNS_PATH = join(CAREERCLAW_DIR, "runs.jsonl");
25
+ export const RESUME_TXT_PATH = join(CAREERCLAW_DIR, "resume.txt");
26
+ export const RESUME_PDF_PATH = join(CAREERCLAW_DIR, "resume.pdf");
27
+ export const LICENSE_CACHE_PATH = join(CAREERCLAW_DIR, ".license_cache");
28
+ // ---------------------------------------------------------------------------
29
+ // HTTP client defaults
30
+ // ---------------------------------------------------------------------------
31
+ /** Milliseconds before an outbound HTTP request is aborted. */
32
+ export const HTTP_TIMEOUT_MS = 15_000;
33
+ /**
34
+ * User-Agent sent with all outbound requests.
35
+ * Identifies the tool and provides a contact point per robots.txt convention.
36
+ */
37
+ export const USER_AGENT = "careerclaw-js/0.11.0 (https://github.com/orestes-garcia-martinez/careerclaw-js)";
38
+ // ---------------------------------------------------------------------------
39
+ // Job sources
40
+ // ---------------------------------------------------------------------------
41
+ /** RemoteOK RSS feed — public, no auth required. */
42
+ export const REMOTEOK_RSS_URL = "https://remoteok.com/remote-jobs.rss";
43
+ /**
44
+ * Hacker News "Who is Hiring?" thread ID.
45
+ *
46
+ * HN posts a new thread on the first weekday of each month. This value
47
+ * must be updated manually each month. The adapter degrades gracefully
48
+ * (returns []) when the thread cannot be fetched.
49
+ *
50
+ * Override via env var HN_WHO_IS_HIRING_ID for one-off testing.
51
+ *
52
+ * To find the current thread ID: search HN for "Ask HN: Who is hiring?"
53
+ * and copy the numeric ID from the URL (e.g. https://news.ycombinator.com/item?id=43354977).
54
+ */
55
+ export const HN_WHO_IS_HIRING_ID = parseInt(process.env["HN_WHO_IS_HIRING_ID"] ?? "47219668", 10);
56
+ /** HN Firebase API base URL — public, no auth required. */
57
+ export const HN_API_BASE = "https://hacker-news.firebaseio.com/v0";
58
+ /** Maximum number of HN comment IDs to fetch per briefing run. */
59
+ export const HN_MAX_COMMENTS = 200;
60
+ // ---------------------------------------------------------------------------
61
+ // Briefing defaults
62
+ // ---------------------------------------------------------------------------
63
+ /** Default number of top matches to return. */
64
+ export const DEFAULT_TOP_K = 3;
65
+ // ---------------------------------------------------------------------------
66
+ // LLM (Pro tier)
67
+ // ---------------------------------------------------------------------------
68
+ /**
69
+ * Provider-specific LLM keys for draft enhancement (recommended).
70
+ * These take precedence over the legacy CAREERCLAW_LLM_KEY.
71
+ * Never written to disk or logged.
72
+ */
73
+ export const LLM_ANTHROPIC_KEY = process.env["CAREERCLAW_ANTHROPIC_KEY"];
74
+ export const LLM_OPENAI_KEY = process.env["CAREERCLAW_OPENAI_KEY"];
75
+ /**
76
+ * Legacy single-key override. Used when the provider-specific key above
77
+ * is absent. Not recommended for mixed failover chains.
78
+ */
79
+ export const LLM_API_KEY = process.env["CAREERCLAW_LLM_KEY"];
80
+ /** LLM provider: "anthropic" | "openai". Defaults to "anthropic". */
81
+ export const LLM_PROVIDER = process.env["CAREERCLAW_LLM_PROVIDER"] ?? "anthropic";
82
+ /** Model to use for draft enhancement. Default: fast, low-cost Haiku. */
83
+ export const LLM_MODEL = process.env["CAREERCLAW_LLM_MODEL"] ?? "claude-haiku-4-5-20251001";
84
+ /**
85
+ * Comma-separated provider/model failover chain.
86
+ * Tried left to right on failure.
87
+ */
88
+ export const LLM_CHAIN = process.env["CAREERCLAW_LLM_CHAIN"] ??
89
+ "anthropic/claude-haiku-4-5-20251001,openai/gpt-4o-mini";
90
+ /** Max retries per chain candidate before trying the next. */
91
+ export const LLM_MAX_RETRIES = parseInt(process.env["CAREERCLAW_LLM_MAX_RETRIES"] ?? "2", 10);
92
+ /** Consecutive failures before the circuit breaker opens. */
93
+ export const LLM_CIRCUIT_BREAKER_FAILS = parseInt(process.env["CAREERCLAW_LLM_CIRCUIT_BREAKER_FAILS"] ?? "2", 10);
94
+ // ---------------------------------------------------------------------------
95
+ // Licensing (Pro tier)
96
+ // ---------------------------------------------------------------------------
97
+ /** CareerClaw Pro license key. Never written to disk raw — only a SHA-256 hash is cached. */
98
+ export const PRO_KEY = process.env["CAREERCLAW_PRO_KEY"];
99
+ // ---------------------------------------------------------------------------
100
+ // Gumroad license validation (active payment processor)
101
+ // ---------------------------------------------------------------------------
102
+ /**
103
+ * Gumroad product ID for license key verification.
104
+ *
105
+ * Find it in your Gumroad dashboard — product — Content — License key module.
106
+ * Required for Pro license validation. If unset, checkLicense() returns
107
+ * { valid: false, source: "none" } immediately — Free tier still works.
108
+ */
109
+ export const GUMROAD_PRODUCT_ID = process.env["CAREERCLAW_GUMROAD_PRODUCT_ID"];
110
+ /** Gumroad license verification API base URL. */
111
+ export const GUMROAD_API_BASE = "https://api.gumroad.com";
112
+ /**
113
+ * How long a cached license validation remains valid without re-checking
114
+ * the Gumroad API. After this window the cache is stale and a live API
115
+ * call is required.
116
+ */
117
+ export const LICENSE_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
118
+ // ---------------------------------------------------------------------------
119
+ // Polar.sh (future migration — not yet active)
120
+ // TODO: Phase 11-Polar — swap GUMROAD_* for POLAR_* once Polar is configured
121
+ // ---------------------------------------------------------------------------
122
+ /** @deprecated Polar is not yet the active processor. Use GUMROAD_* constants. */
123
+ export const POLAR_PRODUCT_SLUG = process.env["CAREERCLAW_POLAR_SLUG"] ?? "careerclaw-pro";
124
+ /** @deprecated Polar is not yet the active processor. Use GUMROAD_API_BASE. */
125
+ export const POLAR_API_BASE = "https://api.polar.sh";
126
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * core/text-processing.ts — Shared text processing utilities.
3
+ *
4
+ * Used by the matching engine (Phase 4), resume intelligence (Phase 5),
5
+ * and gap analysis (Phase 5). All functions are pure and stateless.
6
+ *
7
+ * Design notes:
8
+ * - No external NLP dependencies — stopword logic is ported directly
9
+ * from the Python careerclaw implementation.
10
+ * - STOPWORDS is the single source of truth. No parallel lists.
11
+ * - Phrases are sliding-window bigrams and trigrams over the token stream.
12
+ * - SECTION_WEIGHTS drives keyword importance in resume intelligence (Phase 5).
13
+ */
14
+ /**
15
+ * Combined stopword set: common English function words + recruitment
16
+ * boilerplate terms that carry zero signal value for job matching.
17
+ *
18
+ * Recruitment boilerplate sourced from PR-E signal hygiene analysis
19
+ * (February 2026): terms that appeared consistently in gap keyword lists
20
+ * despite having no technical meaning.
21
+ */
22
+ export declare const STOPWORDS: ReadonlySet<string>;
23
+ /**
24
+ * Relative importance weights for resume sections in keyword extraction.
25
+ * Used by resume intelligence (Phase 5) to weight keyword scores.
26
+ * Skills = 1.0 is the maximum weight.
27
+ */
28
+ export declare const SECTION_WEIGHTS: Readonly<Record<string, number>>;
29
+ export declare function tokenize(text: string): string[];
30
+ /**
31
+ * Tokenize and return only unique tokens (set semantics, insertion order).
32
+ * Useful for keyword overlap scoring where duplicates add no information.
33
+ */
34
+ export declare function tokenizeUnique(text: string): string[];
35
+ /**
36
+ * Extract bigrams and trigrams from a token stream.
37
+ *
38
+ * Tokens are joined with a space: ["senior", "engineer"] → "senior engineer".
39
+ * Phrases from short token streams (< 2 tokens) are returned as empty array.
40
+ */
41
+ export declare function extractPhrases(tokens: string[]): string[];
42
+ /**
43
+ * Tokenize text and extract bigram + trigram phrases in one pass.
44
+ * Returns unique phrases (insertion order).
45
+ */
46
+ export declare function extractPhrasesFromText(text: string): string[];
47
+ /**
48
+ * Compute the Jaccard-like overlap between two token sets.
49
+ * Returns a value in [0, 1]: |intersection| / |union|.
50
+ */
51
+ export declare function tokenOverlap(a: string[], b: string[]): number;
52
+ /**
53
+ * Return tokens in `query` that are present in `corpus`.
54
+ * Used to produce "matched_keywords" in ScoredJob.
55
+ */
56
+ export declare function matchedTokens(query: string[], corpus: string[]): string[];
57
+ /**
58
+ * Return tokens in `query` that are absent from `corpus`.
59
+ * Used to produce "gap_keywords" in ScoredJob.
60
+ */
61
+ export declare function gapTokens(query: string[], corpus: string[]): string[];
62
+ //# sourceMappingURL=text-processing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-processing.d.ts","sourceRoot":"","sources":["../../src/core/text-processing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,EAAE,WAAW,CAAC,MAAM,CA4DxC,CAAC;AAMH;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAM5D,CAAC;AAkBF,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAM/C;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAErD;AAMD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAczD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAE7D;AAMD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAO7D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAGzE;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAGrE"}