@stithy/jobtracker 0.1.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/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Business Source License 1.1
2
+
3
+ Parameters
4
+
5
+ Licensor: Stithy
6
+ Licensed Work: @stithy/memory
7
+ Copyright (c) 2026 Stithy
8
+ Additional Use Grant: You may use the Licensed Work for non-production
9
+ use, internal evaluation, and personal projects
10
+ free of charge. Production use by organizations
11
+ with annual revenue over $1M USD requires a
12
+ commercial license purchased through the MCP
13
+ Marketplace or directly from Stithy.
14
+
15
+ Change Date: 2028-04-12
16
+ Change License: Apache License, Version 2.0
17
+
18
+ For full BSL 1.1 terms see: https://mariadb.com/bsl11/
19
+
20
+ ---
21
+
22
+ Notice
23
+
24
+ The Business Source License (this document, or the "License") is not an
25
+ Open Source license. However, the Licensed Work will eventually be made
26
+ available under an Open Source License, as stated in this License.
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # @stithy/jobtracker
2
+
3
+ > Your AI-native job hunt copilot. Tailor resumes per role, track every application, never miss a follow-up — all in your AI client, all local.
4
+
5
+ Stithy Jobtracker is a local-first MCP server for managing a real job search. Every application, interaction, and follow-up lives in a SQLite database on your machine. Your resume, salary expectations, and recruiter notes never leave your laptop.
6
+
7
+ ## Outcomes
8
+
9
+ - **Stop losing applications in your inbox.** Track every role with status, salary range, JD, and notes — pulled up in one prompt.
10
+ - **Stop missing follow-ups.** Set reminders. Run `due_followups` at the start of every session.
11
+ - **Stop pasting your resume into ChatGPT 30 times a week.** Save your base resume once. Tailor it per role with a single tool call.
12
+ - **Stop guessing at salary.** Triangulate from BLS, Levels.fyi, and pay-transparency listings via a structured prompt.
13
+
14
+ ## Install
15
+
16
+ ```
17
+ npx -y @stithy/jobtracker install
18
+ ```
19
+
20
+ This wires the server into Claude Desktop, Cursor, Windsurf, and Claude Code (whichever it detects). Then restart your AI client.
21
+
22
+ ## Tools
23
+
24
+ | Tool | Purpose |
25
+ |------|---------|
26
+ | `add_application` | Record a new role |
27
+ | `update_application` | Change status, salary, notes |
28
+ | `list_applications` | List with optional status filter |
29
+ | `get_application` | Full detail including interactions and follow-ups |
30
+ | `delete_application` | Remove + cascade |
31
+ | `log_interaction` | Recruiter calls, interviews, emails |
32
+ | `set_follow_up` | Schedule a reminder |
33
+ | `complete_follow_up` | Mark a reminder done |
34
+ | `due_followups` | What's due (and overdue) — run this first thing each session |
35
+ | `pipeline_view` | Kanban-style snapshot |
36
+ | `application_stats` | Response, interview, offer rates |
37
+ | `save_resume` | Store base/variant resumes |
38
+ | `tailor_resume` | Returns tailoring prompt for the host LLM |
39
+ | `cover_letter_prompt` | Returns a structured cover-letter prompt |
40
+ | `interview_prep` | Returns a prep prompt scoped to this app |
41
+ | `salary_research` | Returns a triangulation prompt with public sources |
42
+
43
+ ## Privacy
44
+
45
+ Everything is local. The server writes to `~/.stithy/jobtracker.db` (override with `STITHY_JOBTRACKER_DB`). Nothing is sent to Stithy or any third party. No telemetry. The LLM-calling tools (`tailor_resume`, `cover_letter_prompt`, etc.) return *prompts* — generation happens in your AI client, not on a server we run.
46
+
47
+ ## License
48
+
49
+ [BUSL-1.1](./LICENSE). Free for personal and commercial use under $1M annual revenue. Commercial license via mcp-marketplace.io for revenue above that threshold.
50
+
51
+ ## Maintained
52
+
53
+ Active maintenance, 7-day bug-fix turnaround, 30-day refund policy. Issues: https://github.com/resolceo-ai/stithy-mcp/issues
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ const cmd = process.argv[2];
3
+ if (cmd === "install" || cmd === "uninstall" || cmd === "list") {
4
+ await import("./install.js");
5
+ }
6
+ else if (cmd === "license") {
7
+ const { verifyLicense } = await import("@mcp_marketplace/license");
8
+ const r = await verifyLicense({ slug: "stithy-jobtracker" });
9
+ if (r.valid) {
10
+ console.log(`Licensed (server_id: ${r.server_id ?? "n/a"})`);
11
+ if (r.expires_at)
12
+ console.log(`Expires: ${r.expires_at}`);
13
+ }
14
+ else {
15
+ console.log(`Not licensed: ${r.reason ?? "unknown"}`);
16
+ console.log(`Set MCP_LICENSE_KEY in your environment.`);
17
+ console.log(`Get a key: https://mcp-marketplace.io/stithy-jobtracker`);
18
+ }
19
+ }
20
+ else if (cmd === "--help" || cmd === "-h" || cmd === "help") {
21
+ console.log(`stithy-jobtracker — local-first job application tracker MCP server
22
+
23
+ Usage:
24
+ npx @stithy/jobtracker Run the MCP server (stdio transport)
25
+ npx @stithy/jobtracker install Install into all detected AI clients
26
+ npx @stithy/jobtracker install --force Overwrite existing config
27
+ npx @stithy/jobtracker list Show install status for each client
28
+ npx @stithy/jobtracker uninstall Remove from all detected clients
29
+ npx @stithy/jobtracker license Check license status
30
+
31
+ Environment:
32
+ MCP_LICENSE_KEY Marketplace license key (required to run server)
33
+ STITHY_JOBTRACKER_DB Path to SQLite db (default: ~/.stithy/jobtracker.db)
34
+ STITHY_SKIP_LICENSE Set to 1 to bypass license check (dev/test only)
35
+
36
+ Docs: https://github.com/resolceo-ai/stithy-mcp
37
+ `);
38
+ }
39
+ else {
40
+ await import("./server.js");
41
+ }
42
+ export {};
43
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAE5B,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,WAAW,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;IAC/D,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;AAC/B,CAAC;KAAM,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;IAC7B,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,0BAA0B,CAAC,CAAC;IACnE,MAAM,CAAC,GAAG,MAAM,aAAa,CAAC,EAAE,IAAI,EAAE,mBAAmB,EAAE,CAAC,CAAC;IAC7D,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;QACZ,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,SAAS,IAAI,KAAK,GAAG,CAAC,CAAC;QAC7D,IAAI,CAAC,CAAC,UAAU;YAAE,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC;IAC5D,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC,CAAC;QACtD,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;QACxD,OAAO,CAAC,GAAG,CAAC,yDAAyD,CAAC,CAAC;IACzE,CAAC;AACH,CAAC;KAAM,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;IAC9D,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;CAgBb,CAAC,CAAC;AACH,CAAC;KAAM,CAAC;IACN,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;AAC9B,CAAC"}
package/dist/db.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import Database from "better-sqlite3";
2
+ export declare function openDb(path: string): Database.Database;
3
+ export declare const VALID_STATUSES: readonly ["saved", "applied", "phone_screen", "interview", "offer", "accepted", "rejected", "withdrawn", "ghosted"];
4
+ export type Status = (typeof VALID_STATUSES)[number];
5
+ export declare const VALID_INTERACTIONS: readonly ["recruiter_call", "phone_screen", "interview", "email", "follow_up", "offer_call", "rejection", "note"];
6
+ export type InteractionKind = (typeof VALID_INTERACTIONS)[number];
package/dist/db.js ADDED
@@ -0,0 +1,87 @@
1
+ import Database from "better-sqlite3";
2
+ import { mkdirSync } from "node:fs";
3
+ import { dirname } from "node:path";
4
+ export function openDb(path) {
5
+ mkdirSync(dirname(path), { recursive: true });
6
+ const db = new Database(path);
7
+ db.pragma("journal_mode = WAL");
8
+ db.pragma("synchronous = NORMAL");
9
+ db.pragma("foreign_keys = ON");
10
+ migrate(db);
11
+ return db;
12
+ }
13
+ function migrate(db) {
14
+ db.exec(`
15
+ CREATE TABLE IF NOT EXISTS applications (
16
+ id TEXT PRIMARY KEY,
17
+ company TEXT NOT NULL,
18
+ role TEXT NOT NULL,
19
+ url TEXT,
20
+ job_description TEXT,
21
+ status TEXT NOT NULL DEFAULT 'applied',
22
+ salary_min INTEGER,
23
+ salary_max INTEGER,
24
+ salary_currency TEXT NOT NULL DEFAULT 'USD',
25
+ location TEXT,
26
+ remote INTEGER NOT NULL DEFAULT 0,
27
+ source TEXT,
28
+ notes TEXT,
29
+ applied_at INTEGER NOT NULL,
30
+ updated_at INTEGER NOT NULL
31
+ );
32
+ CREATE INDEX IF NOT EXISTS idx_apps_status ON applications(status);
33
+ CREATE INDEX IF NOT EXISTS idx_apps_updated ON applications(updated_at DESC);
34
+
35
+ CREATE TABLE IF NOT EXISTS interactions (
36
+ id TEXT PRIMARY KEY,
37
+ application_id TEXT NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
38
+ kind TEXT NOT NULL,
39
+ summary TEXT NOT NULL,
40
+ occurred_at INTEGER NOT NULL,
41
+ created_at INTEGER NOT NULL
42
+ );
43
+ CREATE INDEX IF NOT EXISTS idx_int_app ON interactions(application_id, occurred_at DESC);
44
+
45
+ CREATE TABLE IF NOT EXISTS follow_ups (
46
+ id TEXT PRIMARY KEY,
47
+ application_id TEXT NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
48
+ due_at INTEGER NOT NULL,
49
+ reminder TEXT NOT NULL,
50
+ done INTEGER NOT NULL DEFAULT 0,
51
+ done_at INTEGER,
52
+ created_at INTEGER NOT NULL
53
+ );
54
+ CREATE INDEX IF NOT EXISTS idx_fu_due ON follow_ups(done, due_at);
55
+
56
+ CREATE TABLE IF NOT EXISTS resumes (
57
+ id TEXT PRIMARY KEY,
58
+ label TEXT NOT NULL,
59
+ content TEXT NOT NULL,
60
+ is_base INTEGER NOT NULL DEFAULT 0,
61
+ created_at INTEGER NOT NULL,
62
+ updated_at INTEGER NOT NULL
63
+ );
64
+ `);
65
+ }
66
+ export const VALID_STATUSES = [
67
+ "saved",
68
+ "applied",
69
+ "phone_screen",
70
+ "interview",
71
+ "offer",
72
+ "accepted",
73
+ "rejected",
74
+ "withdrawn",
75
+ "ghosted",
76
+ ];
77
+ export const VALID_INTERACTIONS = [
78
+ "recruiter_call",
79
+ "phone_screen",
80
+ "interview",
81
+ "email",
82
+ "follow_up",
83
+ "offer_call",
84
+ "rejection",
85
+ "note",
86
+ ];
87
+ //# sourceMappingURL=db.js.map
package/dist/db.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,UAAU,MAAM,CAAC,IAAY;IACjC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC9B,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC;IAClC,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,CAAC;IACZ,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,OAAO,CAAC,EAAqB;IACpC,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDP,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,OAAO;IACP,SAAS;IACT,cAAc;IACd,WAAW;IACX,OAAO;IACP,UAAU;IACV,UAAU;IACV,WAAW;IACX,SAAS;CACD,CAAC;AAGX,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,gBAAgB;IAChB,cAAc;IACd,WAAW;IACX,OAAO;IACP,WAAW;IACX,YAAY;IACZ,WAAW;IACX,MAAM;CACE,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync, renameSync } from "node:fs";
3
+ import { homedir, platform } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ function targets() {
6
+ const home = homedir();
7
+ const p = platform();
8
+ const list = [];
9
+ if (p === "darwin") {
10
+ list.push({
11
+ name: "Claude Desktop",
12
+ configPath: join(home, "Library/Application Support/Claude/claude_desktop_config.json"),
13
+ });
14
+ }
15
+ else if (p === "win32") {
16
+ const appdata = process.env.APPDATA ?? join(home, "AppData/Roaming");
17
+ list.push({
18
+ name: "Claude Desktop",
19
+ configPath: join(appdata, "Claude/claude_desktop_config.json"),
20
+ });
21
+ }
22
+ else {
23
+ list.push({
24
+ name: "Claude Desktop",
25
+ configPath: join(home, ".config/Claude/claude_desktop_config.json"),
26
+ });
27
+ }
28
+ list.push({ name: "Cursor", configPath: join(home, ".cursor/mcp.json") });
29
+ list.push({
30
+ name: "Windsurf",
31
+ configPath: join(home, ".codeium/windsurf/mcp_config.json"),
32
+ });
33
+ list.push({ name: "Claude Code", configPath: join(home, ".claude.json") });
34
+ return list;
35
+ }
36
+ function serverEntry() {
37
+ return { command: "npx", args: ["-y", "@stithy/jobtracker"] };
38
+ }
39
+ function readJson(path) {
40
+ if (!existsSync(path))
41
+ return { ok: true, data: {} };
42
+ try {
43
+ const raw = readFileSync(path, "utf8");
44
+ if (raw.trim().length === 0)
45
+ return { ok: true, data: {} };
46
+ const parsed = JSON.parse(raw);
47
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
48
+ return { ok: false, reason: "Existing config is not a JSON object — refusing to overwrite" };
49
+ }
50
+ return { ok: true, data: parsed };
51
+ }
52
+ catch (e) {
53
+ return {
54
+ ok: false,
55
+ reason: `Existing config is not valid JSON (${e instanceof Error ? e.message : String(e)}) — refusing to overwrite`,
56
+ };
57
+ }
58
+ }
59
+ function writeJsonAtomic(path, data) {
60
+ const tmp = `${path}.stithy.tmp`;
61
+ const json = JSON.stringify(data, null, 2);
62
+ writeFileSync(tmp, json);
63
+ JSON.parse(readFileSync(tmp, "utf8"));
64
+ renameSync(tmp, path);
65
+ }
66
+ function backup(path) {
67
+ if (!existsSync(path))
68
+ return;
69
+ const bak = `${path}.stithy.bak.${Date.now()}`;
70
+ copyFileSync(path, bak);
71
+ console.log(` backup: ${bak}`);
72
+ }
73
+ function installFor(t, name, force) {
74
+ try {
75
+ const result = readJson(t.configPath);
76
+ if (!result.ok) {
77
+ console.error(`✗ ${t.name}: ${result.reason}`);
78
+ return "error";
79
+ }
80
+ const cfg = result.data;
81
+ const servers = cfg["mcpServers"] ?? {};
82
+ if (servers[name] && !force) {
83
+ console.log(`✓ ${t.name}: already configured (use --force to overwrite)`);
84
+ return "skipped";
85
+ }
86
+ servers[name] = serverEntry();
87
+ cfg["mcpServers"] = servers;
88
+ if (existsSync(t.configPath))
89
+ backup(t.configPath);
90
+ mkdirSync(dirname(t.configPath), { recursive: true });
91
+ writeJsonAtomic(t.configPath, cfg);
92
+ console.log(`✓ ${t.name}: installed at ${t.configPath}`);
93
+ return "installed";
94
+ }
95
+ catch (e) {
96
+ console.error(`✗ ${t.name}: ${e instanceof Error ? e.message : String(e)}`);
97
+ return "error";
98
+ }
99
+ }
100
+ function uninstallFor(t, name) {
101
+ try {
102
+ if (!existsSync(t.configPath))
103
+ return false;
104
+ const result = readJson(t.configPath);
105
+ if (!result.ok) {
106
+ console.error(`✗ ${t.name}: ${result.reason}`);
107
+ return false;
108
+ }
109
+ const cfg = result.data;
110
+ const servers = cfg["mcpServers"] ?? {};
111
+ if (!servers[name])
112
+ return false;
113
+ delete servers[name];
114
+ cfg["mcpServers"] = servers;
115
+ backup(t.configPath);
116
+ writeJsonAtomic(t.configPath, cfg);
117
+ console.log(`✓ ${t.name}: removed`);
118
+ return true;
119
+ }
120
+ catch (e) {
121
+ console.error(`✗ ${t.name}: ${e instanceof Error ? e.message : String(e)}`);
122
+ return false;
123
+ }
124
+ }
125
+ function main() {
126
+ const args = process.argv.slice(2);
127
+ const cmd = args[0] ?? "install";
128
+ const force = args.includes("--force");
129
+ const name = "stithy-jobtracker";
130
+ if (cmd === "uninstall") {
131
+ console.log("Removing stithy-jobtracker from detected clients...");
132
+ for (const t of targets())
133
+ uninstallFor(t, name);
134
+ console.log("\nDone. Restart your AI client to pick up the change.");
135
+ return;
136
+ }
137
+ if (cmd === "list") {
138
+ for (const t of targets()) {
139
+ const present = existsSync(t.configPath);
140
+ let has = false;
141
+ let bad = false;
142
+ if (present) {
143
+ const r = readJson(t.configPath);
144
+ if (!r.ok) {
145
+ bad = true;
146
+ }
147
+ else {
148
+ const servers = r.data["mcpServers"] ?? {};
149
+ has = Boolean(servers[name]);
150
+ }
151
+ }
152
+ const marker = bad ? "?" : has ? "✓" : present ? "·" : "—";
153
+ console.log(`${marker} ${t.name.padEnd(16)} ${t.configPath}${bad ? " (config unparseable)" : ""}`);
154
+ }
155
+ return;
156
+ }
157
+ console.log("Installing stithy-jobtracker across detected AI clients...\n");
158
+ let installed = 0;
159
+ for (const t of targets()) {
160
+ const result = installFor(t, name, force);
161
+ if (result === "installed")
162
+ installed++;
163
+ }
164
+ console.log(`\nInstalled to ${installed} client${installed === 1 ? "" : "s"}.`);
165
+ console.log("Restart your AI client(s) to load stithy-jobtracker.");
166
+ }
167
+ main();
168
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/server.js ADDED
@@ -0,0 +1,397 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { verifyLicense } from "@mcp_marketplace/license";
5
+ import { z } from "zod";
6
+ import { homedir } from "node:os";
7
+ import { join } from "node:path";
8
+ import { openDb, VALID_STATUSES, VALID_INTERACTIONS } from "./db.js";
9
+ import { Store } from "./store.js";
10
+ const SLUG = "stithy-jobtracker";
11
+ const SKIP_LICENSE = process.env.STITHY_SKIP_LICENSE === "1";
12
+ if (!SKIP_LICENSE) {
13
+ const result = await verifyLicense({ slug: SLUG });
14
+ if (!result.valid) {
15
+ console.error(`Stithy Jobtracker: license check failed (${result.reason ?? "unknown"}).\n` +
16
+ `Set MCP_LICENSE_KEY in your environment.\n` +
17
+ `Get a key: https://mcp-marketplace.io/${SLUG}`);
18
+ process.exit(1);
19
+ }
20
+ }
21
+ const dbPath = process.env.STITHY_JOBTRACKER_DB ?? join(homedir(), ".stithy", "jobtracker.db");
22
+ const db = openDb(dbPath);
23
+ const store = new Store(db);
24
+ const server = new McpServer({ name: "stithy-jobtracker", version: "0.1.0" }, {
25
+ instructions: "Local-first job application tracker. Use add_application when the user mentions a new role, log_interaction after recruiter calls or interviews, set_follow_up to remember next steps, due_followups at session start to surface overdue items.",
26
+ });
27
+ function err(e) {
28
+ return e instanceof Error ? e.message : String(e);
29
+ }
30
+ function ok(text) {
31
+ return { content: [{ type: "text", text }] };
32
+ }
33
+ function fail(text) {
34
+ return { content: [{ type: "text", text }], isError: true };
35
+ }
36
+ server.registerTool("add_application", {
37
+ title: "Add Job Application",
38
+ description: "Record a new job application with company, role, and optional salary/JD/notes.",
39
+ inputSchema: {
40
+ company: z.string().min(1),
41
+ role: z.string().min(1),
42
+ url: z.string().url().optional(),
43
+ job_description: z.string().optional(),
44
+ status: z.enum(VALID_STATUSES).optional(),
45
+ salary_min: z.number().int().optional(),
46
+ salary_max: z.number().int().optional(),
47
+ salary_currency: z.string().length(3).optional(),
48
+ location: z.string().optional(),
49
+ remote: z.boolean().optional(),
50
+ source: z.string().optional(),
51
+ notes: z.string().optional(),
52
+ },
53
+ }, async (args) => {
54
+ try {
55
+ const a = store.addApplication(args);
56
+ return ok(`Added ${a.company} — ${a.role} (id: ${a.id}, status: ${a.status})`);
57
+ }
58
+ catch (e) {
59
+ return fail(err(e));
60
+ }
61
+ });
62
+ server.registerTool("update_application", {
63
+ title: "Update Application",
64
+ description: "Patch fields on an existing application (commonly status, notes, salary).",
65
+ inputSchema: {
66
+ id: z.string(),
67
+ company: z.string().optional(),
68
+ role: z.string().optional(),
69
+ url: z.string().url().nullable().optional(),
70
+ job_description: z.string().nullable().optional(),
71
+ status: z.enum(VALID_STATUSES).optional(),
72
+ salary_min: z.number().int().nullable().optional(),
73
+ salary_max: z.number().int().nullable().optional(),
74
+ salary_currency: z.string().length(3).optional(),
75
+ location: z.string().nullable().optional(),
76
+ remote: z.boolean().optional(),
77
+ source: z.string().nullable().optional(),
78
+ notes: z.string().nullable().optional(),
79
+ },
80
+ }, async ({ id, ...patch }) => {
81
+ try {
82
+ const a = store.updateApplication(id, patch);
83
+ if (!a)
84
+ return fail("Not found.");
85
+ return ok(`Updated ${a.id} (status: ${a.status})`);
86
+ }
87
+ catch (e) {
88
+ return fail(err(e));
89
+ }
90
+ });
91
+ server.registerTool("list_applications", {
92
+ title: "List Applications",
93
+ description: "List applications, newest first, optionally filtered by status.",
94
+ inputSchema: {
95
+ status: z.enum(VALID_STATUSES).optional(),
96
+ limit: z.number().int().min(1).max(500).optional(),
97
+ },
98
+ }, async ({ status, limit }) => {
99
+ try {
100
+ const apps = store.listApplications({ status, limit });
101
+ if (apps.length === 0)
102
+ return ok("No applications.");
103
+ const text = apps
104
+ .map((a) => `- [${a.status}] ${a.company} — ${a.role}` +
105
+ (a.location ? ` (${a.location}${a.remote ? ", remote" : ""})` : "") +
106
+ `\n id: ${a.id} | updated: ${new Date(a.updated_at).toISOString()}`)
107
+ .join("\n");
108
+ return ok(text);
109
+ }
110
+ catch (e) {
111
+ return fail(err(e));
112
+ }
113
+ });
114
+ server.registerTool("get_application", {
115
+ title: "Get Application",
116
+ description: "Fetch full application details plus interactions and pending follow-ups.",
117
+ inputSchema: { id: z.string() },
118
+ }, async ({ id }) => {
119
+ try {
120
+ const app = store.getApplication(id);
121
+ if (!app)
122
+ return fail("Not found.");
123
+ const interactions = store.listInteractions(id);
124
+ const followups = store
125
+ .dueFollowUps({ horizon_hours: 24 * 365 })
126
+ .filter((f) => f.application_id === id);
127
+ return ok(JSON.stringify({ application: app, interactions, followups }, null, 2));
128
+ }
129
+ catch (e) {
130
+ return fail(err(e));
131
+ }
132
+ });
133
+ server.registerTool("delete_application", {
134
+ title: "Delete Application",
135
+ description: "Remove an application and all its interactions and follow-ups.",
136
+ inputSchema: { id: z.string() },
137
+ }, async ({ id }) => {
138
+ try {
139
+ const okDel = store.deleteApplication(id);
140
+ return okDel ? ok(`Deleted ${id}.`) : fail("Not found.");
141
+ }
142
+ catch (e) {
143
+ return fail(err(e));
144
+ }
145
+ });
146
+ server.registerTool("log_interaction", {
147
+ title: "Log Interaction",
148
+ description: "Log a recruiter call, interview, email, or note for an application.",
149
+ inputSchema: {
150
+ application_id: z.string(),
151
+ kind: z.enum(VALID_INTERACTIONS),
152
+ summary: z.string().min(1),
153
+ occurred_at: z.number().int().optional(),
154
+ },
155
+ }, async (args) => {
156
+ try {
157
+ const i = store.addInteraction(args);
158
+ return ok(`Logged ${i.kind} for ${i.application_id} at ${new Date(i.occurred_at).toISOString()}`);
159
+ }
160
+ catch (e) {
161
+ return fail(err(e));
162
+ }
163
+ });
164
+ server.registerTool("set_follow_up", {
165
+ title: "Set Follow-Up",
166
+ description: "Schedule a follow-up reminder. Provide due_at as Unix ms.",
167
+ inputSchema: {
168
+ application_id: z.string(),
169
+ due_at: z.number().int(),
170
+ reminder: z.string().min(1),
171
+ },
172
+ }, async (args) => {
173
+ try {
174
+ const f = store.setFollowUp(args);
175
+ return ok(`Follow-up set for ${new Date(f.due_at).toISOString()} (id: ${f.id})`);
176
+ }
177
+ catch (e) {
178
+ return fail(err(e));
179
+ }
180
+ });
181
+ server.registerTool("complete_follow_up", {
182
+ title: "Complete Follow-Up",
183
+ description: "Mark a follow-up as done.",
184
+ inputSchema: { id: z.string() },
185
+ }, async ({ id }) => {
186
+ try {
187
+ const f = store.completeFollowUp(id);
188
+ return f ? ok(`Completed ${id}.`) : fail("Not found.");
189
+ }
190
+ catch (e) {
191
+ return fail(err(e));
192
+ }
193
+ });
194
+ server.registerTool("due_followups", {
195
+ title: "Due Follow-Ups",
196
+ description: "Show follow-ups due within horizon_hours (default 24) and any overdue. Use at session start.",
197
+ inputSchema: {
198
+ include_overdue: z.boolean().optional(),
199
+ horizon_hours: z.number().int().min(1).max(24 * 365).optional(),
200
+ },
201
+ }, async (args) => {
202
+ try {
203
+ const items = store.dueFollowUps(args);
204
+ if (items.length === 0)
205
+ return ok("No follow-ups due.");
206
+ const now = Date.now();
207
+ const text = items
208
+ .map((f) => {
209
+ const app = store.getApplication(f.application_id);
210
+ const overdue = f.due_at < now ? " OVERDUE" : "";
211
+ return `- ${new Date(f.due_at).toISOString()}${overdue} | ${app?.company ?? "?"} (${app?.role ?? "?"}): ${f.reminder}\n id: ${f.id}`;
212
+ })
213
+ .join("\n");
214
+ return ok(text);
215
+ }
216
+ catch (e) {
217
+ return fail(err(e));
218
+ }
219
+ });
220
+ server.registerTool("pipeline_view", {
221
+ title: "Pipeline View",
222
+ description: "Group all applications by status (kanban-style).",
223
+ inputSchema: {},
224
+ }, async () => {
225
+ try {
226
+ const p = store.pipeline();
227
+ const text = Object.entries(p)
228
+ .filter(([, list]) => list.length > 0)
229
+ .map(([status, list]) => `## ${status} (${list.length})\n` +
230
+ list.map((a) => ` - ${a.company} — ${a.role}`).join("\n"))
231
+ .join("\n\n");
232
+ return ok(text || "Empty pipeline.");
233
+ }
234
+ catch (e) {
235
+ return fail(err(e));
236
+ }
237
+ });
238
+ server.registerTool("application_stats", {
239
+ title: "Application Stats",
240
+ description: "Totals, response rate, interview rate, offer rate, follow-up health.",
241
+ inputSchema: {},
242
+ }, async () => {
243
+ try {
244
+ return ok(JSON.stringify(store.stats(), null, 2));
245
+ }
246
+ catch (e) {
247
+ return fail(err(e));
248
+ }
249
+ });
250
+ server.registerTool("save_resume", {
251
+ title: "Save Resume",
252
+ description: "Store a resume version (paste full text). Mark is_base=true for the canonical version used by tailoring tools.",
253
+ inputSchema: {
254
+ label: z.string().min(1),
255
+ content: z.string().min(1),
256
+ is_base: z.boolean().optional(),
257
+ },
258
+ }, async (args) => {
259
+ try {
260
+ const r = store.saveResume(args);
261
+ return ok(`Saved resume '${r.label}' (id: ${r.id}${r.is_base ? ", BASE" : ""})`);
262
+ }
263
+ catch (e) {
264
+ return fail(err(e));
265
+ }
266
+ });
267
+ server.registerTool("tailor_resume", {
268
+ title: "Tailor Resume",
269
+ description: "Returns base resume + JD with structured tailoring instructions for the host LLM. Workflow: call this, then ask the LLM to produce the tailored version. (LLM-native; the diff happens in your AI client.)",
270
+ inputSchema: {
271
+ application_id: z.string(),
272
+ focus_areas: z.array(z.string()).optional(),
273
+ },
274
+ }, async ({ application_id, focus_areas }) => {
275
+ try {
276
+ const app = store.getApplication(application_id);
277
+ if (!app)
278
+ return fail("Application not found.");
279
+ const base = store.getBaseResume();
280
+ if (!base)
281
+ return fail("No base resume saved. Use save_resume(is_base=true) first.");
282
+ if (!app.job_description) {
283
+ return fail("Application has no job_description. Update with the JD first.");
284
+ }
285
+ const focus = focus_areas?.length ? focus_areas.join(", ") : "skills + outcomes most cited in the JD";
286
+ const text = `Tailor this resume for the role below.\n\n` +
287
+ `# Job\n${app.company} — ${app.role}\n\n` +
288
+ `# Job Description\n${app.job_description}\n\n` +
289
+ `# Base Resume (label: ${base.label})\n${base.content}\n\n` +
290
+ `# Instructions\n` +
291
+ `1. Reorder bullet points so the strongest matches for ${focus} are first.\n` +
292
+ `2. Rewrite each bullet to mirror the JD's exact phrasing where truthful.\n` +
293
+ `3. Cut bullets that don't speak to the JD; keep total to 1 page.\n` +
294
+ `4. Output the tailored resume in plain markdown.\n` +
295
+ `5. Output a CHANGES section listing what you reordered/rewrote/cut and why.`;
296
+ return ok(text);
297
+ }
298
+ catch (e) {
299
+ return fail(err(e));
300
+ }
301
+ });
302
+ server.registerTool("cover_letter_prompt", {
303
+ title: "Cover Letter Prompt",
304
+ description: "Returns a structured cover-letter prompt with company + role + JD context for the host LLM.",
305
+ inputSchema: {
306
+ application_id: z.string(),
307
+ tone: z.enum(["professional", "warm", "concise", "enthusiastic"]).optional(),
308
+ },
309
+ }, async ({ application_id, tone }) => {
310
+ try {
311
+ const app = store.getApplication(application_id);
312
+ if (!app)
313
+ return fail("Application not found.");
314
+ const base = store.getBaseResume();
315
+ const text = `Write a ${tone ?? "professional"} cover letter (≤ 250 words, 3 short paragraphs).\n\n` +
316
+ `# Role\n${app.company} — ${app.role}${app.location ? ` (${app.location})` : ""}\n\n` +
317
+ (app.job_description ? `# Job Description\n${app.job_description}\n\n` : "") +
318
+ (base ? `# Candidate Background\n${base.content}\n\n` : "") +
319
+ `# Instructions\n` +
320
+ `- Open with a concrete reason this candidate fits THIS role at THIS company.\n` +
321
+ `- Middle paragraph: 2-3 outcomes with numbers, not duties.\n` +
322
+ `- Close with a clear call to next step.\n` +
323
+ `- No "I am writing to apply" filler.\n` +
324
+ `- No bullet lists.\n`;
325
+ return ok(text);
326
+ }
327
+ catch (e) {
328
+ return fail(err(e));
329
+ }
330
+ });
331
+ server.registerTool("interview_prep", {
332
+ title: "Interview Prep",
333
+ description: "Returns a prep prompt scoped to this application: company, role, JD, prior interactions, and likely questions to anticipate.",
334
+ inputSchema: { application_id: z.string() },
335
+ }, async ({ application_id }) => {
336
+ try {
337
+ const app = store.getApplication(application_id);
338
+ if (!app)
339
+ return fail("Application not found.");
340
+ const ix = store.listInteractions(application_id);
341
+ const text = `Prepare me for an interview at ${app.company} for ${app.role}.\n\n` +
342
+ (app.job_description ? `# Job Description\n${app.job_description}\n\n` : "") +
343
+ `# Prior Interactions\n` +
344
+ (ix.length === 0 ? "(none)" : ix.map((i) => `- ${i.kind}: ${i.summary}`).join("\n")) +
345
+ `\n\n# Instructions\n` +
346
+ `1. Likely behavioral questions for this role (top 6).\n` +
347
+ `2. Likely technical/role-specific questions (top 6).\n` +
348
+ `3. 5 questions I should ask THEM that signal seriousness without sounding rehearsed.\n` +
349
+ `4. Two anecdotes from my background (you'll need to ask for them) framed for STAR.\n` +
350
+ `5. One concrete fact about ${app.company} I should reference.`;
351
+ return ok(text);
352
+ }
353
+ catch (e) {
354
+ return fail(err(e));
355
+ }
356
+ });
357
+ server.registerTool("salary_research", {
358
+ title: "Salary Research",
359
+ description: "Returns a structured prompt for the host LLM to triangulate market salary using public data sources for this role/location.",
360
+ inputSchema: { application_id: z.string() },
361
+ }, async ({ application_id }) => {
362
+ try {
363
+ const app = store.getApplication(application_id);
364
+ if (!app)
365
+ return fail("Application not found.");
366
+ const text = `Research market salary for ${app.role} at ${app.company}` +
367
+ (app.location ? ` in ${app.location}` : "") +
368
+ `${app.remote ? " (remote)" : ""}.\n\n` +
369
+ `# Sources to triangulate\n` +
370
+ `- BLS OEWS data for the closest SOC code\n` +
371
+ `- Levels.fyi if applicable\n` +
372
+ `- Glassdoor / Indeed salary ranges\n` +
373
+ `- State pay-transparency listings (CO, NY, WA, CA)\n\n` +
374
+ `# Output\n` +
375
+ `1. Estimated 25/50/75 percentile total comp\n` +
376
+ `2. Where this offer (if any) sits in that range\n` +
377
+ `3. Negotiation levers most likely to move comp at this stage`;
378
+ return ok(text);
379
+ }
380
+ catch (e) {
381
+ return fail(err(e));
382
+ }
383
+ });
384
+ const shutdown = (sig) => {
385
+ console.error(`stithy-jobtracker: ${sig}, closing db`);
386
+ try {
387
+ db.close();
388
+ }
389
+ catch { }
390
+ process.exit(0);
391
+ };
392
+ process.on("SIGINT", () => shutdown("SIGINT"));
393
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
394
+ const transport = new StdioServerTransport();
395
+ await server.connect(transport);
396
+ console.error(`stithy-jobtracker v0.1.0 ready (db: ${dbPath})`);
397
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,117 @@
1
+ import type Database from "better-sqlite3";
2
+ import { type Status, type InteractionKind } from "./db.js";
3
+ export interface Application {
4
+ id: string;
5
+ company: string;
6
+ role: string;
7
+ url: string | null;
8
+ job_description: string | null;
9
+ status: Status;
10
+ salary_min: number | null;
11
+ salary_max: number | null;
12
+ salary_currency: string;
13
+ location: string | null;
14
+ remote: boolean;
15
+ source: string | null;
16
+ notes: string | null;
17
+ applied_at: number;
18
+ updated_at: number;
19
+ }
20
+ export interface Interaction {
21
+ id: string;
22
+ application_id: string;
23
+ kind: InteractionKind;
24
+ summary: string;
25
+ occurred_at: number;
26
+ created_at: number;
27
+ }
28
+ export interface FollowUp {
29
+ id: string;
30
+ application_id: string;
31
+ due_at: number;
32
+ reminder: string;
33
+ done: boolean;
34
+ done_at: number | null;
35
+ created_at: number;
36
+ }
37
+ export interface Resume {
38
+ id: string;
39
+ label: string;
40
+ content: string;
41
+ is_base: boolean;
42
+ created_at: number;
43
+ updated_at: number;
44
+ }
45
+ export declare class Store {
46
+ private readonly db;
47
+ constructor(db: Database.Database);
48
+ addApplication(input: {
49
+ company: string;
50
+ role: string;
51
+ url?: string;
52
+ job_description?: string;
53
+ status?: Status;
54
+ salary_min?: number;
55
+ salary_max?: number;
56
+ salary_currency?: string;
57
+ location?: string;
58
+ remote?: boolean;
59
+ source?: string;
60
+ notes?: string;
61
+ }): Application;
62
+ updateApplication(id: string, patch: Partial<{
63
+ company: string;
64
+ role: string;
65
+ url: string | null;
66
+ job_description: string | null;
67
+ status: Status;
68
+ salary_min: number | null;
69
+ salary_max: number | null;
70
+ salary_currency: string;
71
+ location: string | null;
72
+ remote: boolean;
73
+ source: string | null;
74
+ notes: string | null;
75
+ }>): Application | null;
76
+ getApplication(id: string): Application | null;
77
+ listApplications(filter?: {
78
+ status?: Status;
79
+ limit?: number;
80
+ }): Application[];
81
+ deleteApplication(id: string): boolean;
82
+ addInteraction(input: {
83
+ application_id: string;
84
+ kind: InteractionKind;
85
+ summary: string;
86
+ occurred_at?: number;
87
+ }): Interaction;
88
+ listInteractions(application_id: string): Interaction[];
89
+ setFollowUp(input: {
90
+ application_id: string;
91
+ due_at: number;
92
+ reminder: string;
93
+ }): FollowUp;
94
+ completeFollowUp(id: string): FollowUp | null;
95
+ getFollowUp(id: string): FollowUp | null;
96
+ dueFollowUps(opts?: {
97
+ include_overdue?: boolean;
98
+ horizon_hours?: number;
99
+ }): FollowUp[];
100
+ pipeline(): Record<Status, Application[]>;
101
+ stats(): {
102
+ total: number;
103
+ by_status: Record<string, number>;
104
+ response_rate: number;
105
+ interview_rate: number;
106
+ offer_rate: number;
107
+ open_followups: number;
108
+ overdue_followups: number;
109
+ };
110
+ saveResume(input: {
111
+ label: string;
112
+ content: string;
113
+ is_base?: boolean;
114
+ }): Resume;
115
+ getBaseResume(): Resume | null;
116
+ listResumes(): Resume[];
117
+ }
package/dist/store.js ADDED
@@ -0,0 +1,224 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { VALID_STATUSES, VALID_INTERACTIONS, } from "./db.js";
3
+ function rowToApp(r) {
4
+ return {
5
+ id: r.id,
6
+ company: r.company,
7
+ role: r.role,
8
+ url: r.url,
9
+ job_description: r.job_description,
10
+ status: r.status,
11
+ salary_min: r.salary_min,
12
+ salary_max: r.salary_max,
13
+ salary_currency: r.salary_currency,
14
+ location: r.location,
15
+ remote: !!r.remote,
16
+ source: r.source,
17
+ notes: r.notes,
18
+ applied_at: r.applied_at,
19
+ updated_at: r.updated_at,
20
+ };
21
+ }
22
+ export class Store {
23
+ db;
24
+ constructor(db) {
25
+ this.db = db;
26
+ }
27
+ addApplication(input) {
28
+ const status = input.status ?? "applied";
29
+ if (!VALID_STATUSES.includes(status)) {
30
+ throw new Error(`invalid status '${status}'. Valid: ${VALID_STATUSES.join(", ")}`);
31
+ }
32
+ const id = randomUUID();
33
+ const now = Date.now();
34
+ this.db
35
+ .prepare(`INSERT INTO applications (id, company, role, url, job_description, status, salary_min, salary_max, salary_currency, location, remote, source, notes, applied_at, updated_at)
36
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
37
+ .run(id, input.company, input.role, input.url ?? null, input.job_description ?? null, status, input.salary_min ?? null, input.salary_max ?? null, input.salary_currency ?? "USD", input.location ?? null, input.remote ? 1 : 0, input.source ?? null, input.notes ?? null, now, now);
38
+ return this.getApplication(id);
39
+ }
40
+ updateApplication(id, patch) {
41
+ const existing = this.getApplication(id);
42
+ if (!existing)
43
+ return null;
44
+ if (patch.status !== undefined && !VALID_STATUSES.includes(patch.status)) {
45
+ throw new Error(`invalid status '${patch.status}'. Valid: ${VALID_STATUSES.join(", ")}`);
46
+ }
47
+ const next = {
48
+ ...existing,
49
+ ...patch,
50
+ remote: patch.remote !== undefined ? patch.remote : existing.remote,
51
+ updated_at: Date.now(),
52
+ };
53
+ this.db
54
+ .prepare(`UPDATE applications SET company=?, role=?, url=?, job_description=?, status=?, salary_min=?, salary_max=?, salary_currency=?, location=?, remote=?, source=?, notes=?, updated_at=? WHERE id=?`)
55
+ .run(next.company, next.role, next.url, next.job_description, next.status, next.salary_min, next.salary_max, next.salary_currency, next.location, next.remote ? 1 : 0, next.source, next.notes, next.updated_at, id);
56
+ return next;
57
+ }
58
+ getApplication(id) {
59
+ const r = this.db
60
+ .prepare(`SELECT * FROM applications WHERE id = ?`)
61
+ .get(id);
62
+ return r ? rowToApp(r) : null;
63
+ }
64
+ listApplications(filter) {
65
+ const limit = filter?.limit ?? 100;
66
+ if (filter?.status) {
67
+ const rows = this.db
68
+ .prepare(`SELECT * FROM applications WHERE status = ? ORDER BY updated_at DESC LIMIT ?`)
69
+ .all(filter.status, limit);
70
+ return rows.map(rowToApp);
71
+ }
72
+ const rows = this.db
73
+ .prepare(`SELECT * FROM applications ORDER BY updated_at DESC LIMIT ?`)
74
+ .all(limit);
75
+ return rows.map(rowToApp);
76
+ }
77
+ deleteApplication(id) {
78
+ const r = this.db.prepare(`DELETE FROM applications WHERE id = ?`).run(id);
79
+ return r.changes > 0;
80
+ }
81
+ addInteraction(input) {
82
+ if (!VALID_INTERACTIONS.includes(input.kind)) {
83
+ throw new Error(`invalid kind '${input.kind}'. Valid: ${VALID_INTERACTIONS.join(", ")}`);
84
+ }
85
+ if (!this.getApplication(input.application_id)) {
86
+ throw new Error(`application ${input.application_id} not found`);
87
+ }
88
+ const id = randomUUID();
89
+ const now = Date.now();
90
+ const occurred = input.occurred_at ?? now;
91
+ this.db
92
+ .prepare(`INSERT INTO interactions (id, application_id, kind, summary, occurred_at, created_at) VALUES (?, ?, ?, ?, ?, ?)`)
93
+ .run(id, input.application_id, input.kind, input.summary, occurred, now);
94
+ this.db
95
+ .prepare(`UPDATE applications SET updated_at = ? WHERE id = ?`)
96
+ .run(now, input.application_id);
97
+ return {
98
+ id,
99
+ application_id: input.application_id,
100
+ kind: input.kind,
101
+ summary: input.summary,
102
+ occurred_at: occurred,
103
+ created_at: now,
104
+ };
105
+ }
106
+ listInteractions(application_id) {
107
+ const rows = this.db
108
+ .prepare(`SELECT * FROM interactions WHERE application_id = ? ORDER BY occurred_at DESC`)
109
+ .all(application_id);
110
+ return rows.map((r) => ({ ...r, kind: r.kind }));
111
+ }
112
+ setFollowUp(input) {
113
+ if (!this.getApplication(input.application_id)) {
114
+ throw new Error(`application ${input.application_id} not found`);
115
+ }
116
+ const id = randomUUID();
117
+ const now = Date.now();
118
+ this.db
119
+ .prepare(`INSERT INTO follow_ups (id, application_id, due_at, reminder, done, created_at) VALUES (?, ?, ?, ?, 0, ?)`)
120
+ .run(id, input.application_id, input.due_at, input.reminder, now);
121
+ return {
122
+ id,
123
+ application_id: input.application_id,
124
+ due_at: input.due_at,
125
+ reminder: input.reminder,
126
+ done: false,
127
+ done_at: null,
128
+ created_at: now,
129
+ };
130
+ }
131
+ completeFollowUp(id) {
132
+ const now = Date.now();
133
+ const r = this.db
134
+ .prepare(`UPDATE follow_ups SET done = 1, done_at = ? WHERE id = ?`)
135
+ .run(now, id);
136
+ if (r.changes === 0)
137
+ return null;
138
+ return this.getFollowUp(id);
139
+ }
140
+ getFollowUp(id) {
141
+ const r = this.db
142
+ .prepare(`SELECT * FROM follow_ups WHERE id = ?`)
143
+ .get(id);
144
+ if (!r)
145
+ return null;
146
+ return { ...r, done: !!r.done };
147
+ }
148
+ dueFollowUps(opts) {
149
+ const horizonMs = (opts?.horizon_hours ?? 24) * 3600 * 1000;
150
+ const max = Date.now() + horizonMs;
151
+ const min = opts?.include_overdue === false ? Date.now() : 0;
152
+ const rows = this.db
153
+ .prepare(`SELECT * FROM follow_ups WHERE done = 0 AND due_at >= ? AND due_at <= ? ORDER BY due_at ASC`)
154
+ .all(min, max);
155
+ return rows.map((r) => ({ ...r, done: !!r.done }));
156
+ }
157
+ pipeline() {
158
+ const out = {};
159
+ for (const s of VALID_STATUSES)
160
+ out[s] = [];
161
+ for (const app of this.listApplications({ limit: 1000 })) {
162
+ out[app.status].push(app);
163
+ }
164
+ return out;
165
+ }
166
+ stats() {
167
+ const apps = this.listApplications({ limit: 10000 });
168
+ const by_status = {};
169
+ for (const s of VALID_STATUSES)
170
+ by_status[s] = 0;
171
+ for (const a of apps)
172
+ by_status[a.status]++;
173
+ const applied = apps.length === 0 ? 0 : apps.filter((a) => a.status !== "saved").length;
174
+ const responded = apps.filter((a) => ["phone_screen", "interview", "offer", "accepted", "rejected"].includes(a.status)).length;
175
+ const interviewed = apps.filter((a) => ["interview", "offer", "accepted"].includes(a.status)).length;
176
+ const offers = apps.filter((a) => ["offer", "accepted"].includes(a.status)).length;
177
+ const open = this.db
178
+ .prepare(`SELECT COUNT(*) as c FROM follow_ups WHERE done = 0`)
179
+ .get();
180
+ const overdue = this.db
181
+ .prepare(`SELECT COUNT(*) as c FROM follow_ups WHERE done = 0 AND due_at < ?`)
182
+ .get(Date.now());
183
+ return {
184
+ total: apps.length,
185
+ by_status,
186
+ response_rate: applied === 0 ? 0 : +(responded / applied).toFixed(3),
187
+ interview_rate: applied === 0 ? 0 : +(interviewed / applied).toFixed(3),
188
+ offer_rate: applied === 0 ? 0 : +(offers / applied).toFixed(3),
189
+ open_followups: open?.c ?? 0,
190
+ overdue_followups: overdue?.c ?? 0,
191
+ };
192
+ }
193
+ saveResume(input) {
194
+ const id = randomUUID();
195
+ const now = Date.now();
196
+ if (input.is_base) {
197
+ this.db.prepare(`UPDATE resumes SET is_base = 0`).run();
198
+ }
199
+ this.db
200
+ .prepare(`INSERT INTO resumes (id, label, content, is_base, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`)
201
+ .run(id, input.label, input.content, input.is_base ? 1 : 0, now, now);
202
+ return {
203
+ id,
204
+ label: input.label,
205
+ content: input.content,
206
+ is_base: !!input.is_base,
207
+ created_at: now,
208
+ updated_at: now,
209
+ };
210
+ }
211
+ getBaseResume() {
212
+ const r = this.db
213
+ .prepare(`SELECT * FROM resumes WHERE is_base = 1 LIMIT 1`)
214
+ .get();
215
+ return r ? { ...r, is_base: !!r.is_base } : null;
216
+ }
217
+ listResumes() {
218
+ const rows = this.db
219
+ .prepare(`SELECT * FROM resumes ORDER BY updated_at DESC`)
220
+ .all();
221
+ return rows.map((r) => ({ ...r, is_base: !!r.is_base }));
222
+ }
223
+ }
224
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@stithy/jobtracker",
3
+ "version": "0.1.0",
4
+ "description": "Local-first AI-native job application tracker MCP server — track applications, tailor resumes, manage follow-ups",
5
+ "type": "module",
6
+ "bin": {
7
+ "stithy-jobtracker": "dist/cli.js"
8
+ },
9
+ "main": "./dist/server.js",
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/resolceo-ai/stithy-mcp.git",
18
+ "directory": "servers/jobtracker"
19
+ },
20
+ "homepage": "https://github.com/resolceo-ai/stithy-mcp/tree/main/servers/jobtracker",
21
+ "bugs": "https://github.com/resolceo-ai/stithy-mcp/issues",
22
+ "scripts": {
23
+ "build": "tsc",
24
+ "dev": "tsx src/server.ts",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest",
27
+ "typecheck": "tsc --noEmit"
28
+ },
29
+ "keywords": [
30
+ "mcp",
31
+ "model-context-protocol",
32
+ "job-search",
33
+ "resume",
34
+ "claude",
35
+ "cursor"
36
+ ],
37
+ "author": "Stithy",
38
+ "license": "BUSL-1.1",
39
+ "engines": {
40
+ "node": ">=20"
41
+ },
42
+ "dependencies": {
43
+ "@mcp_marketplace/license": "^1.1.0",
44
+ "@modelcontextprotocol/sdk": "^1.0.4",
45
+ "better-sqlite3": "^11.5.0",
46
+ "zod": "^3.23.8"
47
+ },
48
+ "devDependencies": {
49
+ "@types/better-sqlite3": "^7.6.11",
50
+ "@types/node": "^22.10.0",
51
+ "tsx": "^4.19.2",
52
+ "typescript": "^5.7.2",
53
+ "vitest": "^2.1.8"
54
+ }
55
+ }