career-compass-mcp 2.1.0 → 2.3.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/README.md +25 -1
- package/build/bin/cli.d.ts.map +1 -1
- package/build/bin/cli.js +15 -2
- package/build/bin/cli.js.map +1 -1
- package/build/src/dashboard-lite/render.d.ts.map +1 -1
- package/build/src/dashboard-lite/render.js +100 -27
- package/build/src/dashboard-lite/render.js.map +1 -1
- package/build/src/dashboard-lite/server.d.ts +18 -1
- package/build/src/dashboard-lite/server.d.ts.map +1 -1
- package/build/src/dashboard-lite/server.js +31 -3
- package/build/src/dashboard-lite/server.js.map +1 -1
- package/build/src/empty-state.d.ts +16 -0
- package/build/src/empty-state.d.ts.map +1 -0
- package/build/src/empty-state.js +31 -0
- package/build/src/empty-state.js.map +1 -0
- package/build/src/storage/file-store.d.ts +15 -0
- package/build/src/storage/file-store.d.ts.map +1 -1
- package/build/src/storage/file-store.js +19 -1
- package/build/src/storage/file-store.js.map +1 -1
- package/build/src/tools/career-kb.d.ts.map +1 -1
- package/build/src/tools/career-kb.js +115 -10
- package/build/src/tools/career-kb.js.map +1 -1
- package/build/src/tools/interview.d.ts.map +1 -1
- package/build/src/tools/interview.js +18 -3
- package/build/src/tools/interview.js.map +1 -1
- package/build/src/tools/opportunity.d.ts.map +1 -1
- package/build/src/tools/opportunity.js +16 -1
- package/build/src/tools/opportunity.js.map +1 -1
- package/build/src/tools/pipeline.d.ts.map +1 -1
- package/build/src/tools/pipeline.js +102 -57
- package/build/src/tools/pipeline.js.map +1 -1
- package/build/src/tools/resume.d.ts.map +1 -1
- package/build/src/tools/resume.js +25 -3
- package/build/src/tools/resume.js.map +1 -1
- package/build/src/untrusted.d.ts +1 -1
- package/build/src/untrusted.js +1 -1
- package/data/example/pipeline/applications.yaml +66 -66
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -240,6 +240,27 @@ same board is available as a live artifact that dispatches the prompt straight i
|
|
|
240
240
|
|
|
241
241
|
---
|
|
242
242
|
|
|
243
|
+
## Privacy Policy
|
|
244
|
+
|
|
245
|
+
Career Compass runs entirely on your own computer. There is no account, no cloud sync, and
|
|
246
|
+
no telemetry — the software makes no outbound network requests of its own.
|
|
247
|
+
|
|
248
|
+
- **What it handles:** the career history, job pipeline, and pasted documents you give it.
|
|
249
|
+
- **Where it's stored:** plain YAML in the directory you set via `CAREER_DATA_PATH`
|
|
250
|
+
(default `~/.career-compass/`), plus timestamped `.bak` copies of previous versions.
|
|
251
|
+
- **Who else sees it:** only the MCP client you connect (e.g. Claude Desktop), and through
|
|
252
|
+
it your model provider, under *their* policy — and only for the requests you make.
|
|
253
|
+
Career Compass sends nothing anywhere on its own.
|
|
254
|
+
- **Third-party sharing:** none. We never receive your data, so there is nothing to share.
|
|
255
|
+
- **Retention:** your files stay until you delete them. Remove the `CAREER_DATA_PATH`
|
|
256
|
+
directory to erase everything.
|
|
257
|
+
- **Contact:** https://github.com/benskamps/career-compass-mcp/issues
|
|
258
|
+
|
|
259
|
+
Full policy: **[PRIVACY.md](PRIVACY.md)** ·
|
|
260
|
+
published at https://benskamps.github.io/career-compass-mcp/privacy
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
243
264
|
## Tools
|
|
244
265
|
|
|
245
266
|
| Tool | What it does |
|
|
@@ -249,13 +270,16 @@ same board is available as a live artifact that dispatches the prompt straight i
|
|
|
249
270
|
| `tailor_resume` | Generates an ATS-optimized, tailored resume from your KB — standard, federal, academic, or functional formats |
|
|
250
271
|
| `generate_cover_letter` | Writes a personalized cover letter with your actual achievements woven in |
|
|
251
272
|
| `format_for_ats` | Reformats resume content for specific ATS systems: Workday, Greenhouse, Lever, LinkedIn, iCIMS, Taleo |
|
|
252
|
-
| `
|
|
273
|
+
| `pipeline_view` | Reads the pipeline — list, funnel stats, what needs attention, or one application by id. Read-only, so it runs without a permission prompt |
|
|
274
|
+
| `pipeline_add` | Adds one job application to the pipeline |
|
|
275
|
+
| `pipeline_update` | Updates one application — status, notes, follow-up date, a contact, or an interview round |
|
|
253
276
|
| `classify_email` | Classifies a job-search email and extracts contacts, dates, and suggested pipeline updates |
|
|
254
277
|
| `prepare_interview` | Full interview prep: opening pitch, STAR stories, likely questions, company alignment, questions to ask |
|
|
255
278
|
| `evaluate_offer` | Breaks down total comp, compares to market, builds negotiation strategy, drafts counter scripts |
|
|
256
279
|
| `ingest_document` | Extracts achievements from any document: performance review, award email, LinkedIn recommendation, project summary |
|
|
257
280
|
| `generate_rejection_response` | Drafts a graceful response that keeps the door open and maintains the relationship |
|
|
258
281
|
| `capture_insight` | Appends a dated signal to your career journal — fit signals, interview insights, offer reflections, rejection patterns, skill evidence, wins — which later resume, interview, and fit prompts read back |
|
|
282
|
+
| `save_career_section` | Writes one section of your Career KB to disk as plain YAML — this is how the KB gets populated. Replaces the whole section; the previous version is kept as a `.bak` |
|
|
259
283
|
|
|
260
284
|
## Resources
|
|
261
285
|
|
package/build/bin/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../bin/cli.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../bin/cli.ts"],"names":[],"mappings":";AAkBA;;;;GAIG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,CAAC"}
|
package/build/bin/cli.js
CHANGED
|
@@ -5,6 +5,10 @@ import { existsSync, mkdirSync } from "fs";
|
|
|
5
5
|
import { createServer as createNetServer } from "net";
|
|
6
6
|
import { spawn } from "child_process";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
|
+
/** The dashboard binds loopback and nothing else. Declared here because this
|
|
9
|
+
* file executes top-level: findPort() runs during module evaluation, so a
|
|
10
|
+
* const declared further down is still in its temporal dead zone. */
|
|
11
|
+
const LOOPBACK = "127.0.0.1";
|
|
8
12
|
const args = process.argv.slice(2);
|
|
9
13
|
// ─── Version ─────────────────────────────────────────────────────────────────
|
|
10
14
|
const __cliDir = fileURLToPath(new URL(".", import.meta.url));
|
|
@@ -119,13 +123,22 @@ else {
|
|
|
119
123
|
process.on("SIGTERM", shutdown);
|
|
120
124
|
}
|
|
121
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Find a free port, probing the exact address the dashboard will bind.
|
|
128
|
+
*
|
|
129
|
+
* `listen(port)` with no host binds every interface, which is the wrong test in
|
|
130
|
+
* two ways: it can call a port free when something already holds it on
|
|
131
|
+
* 127.0.0.1, and for the moment it is open it accepts connections from the
|
|
132
|
+
* network — on a tool whose entire promise is that nothing leaves the machine.
|
|
133
|
+
* Probe loopback, bind loopback.
|
|
134
|
+
*/
|
|
122
135
|
function findPort(preferred) {
|
|
123
136
|
return new Promise((resolve) => {
|
|
124
137
|
const server = createNetServer();
|
|
125
|
-
server.listen(preferred, () => { server.close(() => resolve(preferred)); });
|
|
138
|
+
server.listen(preferred, LOOPBACK, () => { server.close(() => resolve(preferred)); });
|
|
126
139
|
server.on("error", () => {
|
|
127
140
|
const fallback = createNetServer();
|
|
128
|
-
fallback.listen(0, () => {
|
|
141
|
+
fallback.listen(0, LOOPBACK, () => {
|
|
129
142
|
const addr = fallback.address();
|
|
130
143
|
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
131
144
|
fallback.close(() => resolve(port));
|
package/build/bin/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../bin/cli.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../bin/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/dashboard-lite/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAqB,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/dashboard-lite/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAqB,MAAM,6BAA6B,CAAC;AA0D5F,UAAU,UAAU;IAAG,GAAG,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;CAAE;AAE/F,0FAA0F;AAC1F,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,KAAK,OAAa,GAAG,UAAU,EAAE,CAkCvF;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IACtE,YAAY,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;CACzC;AACD,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,aAAa,CAa/D;AAkBD,oDAAoD;AACpD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAkL9D"}
|
|
@@ -19,10 +19,33 @@ const STAGE_ORDER = [
|
|
|
19
19
|
const ACTIVE = [
|
|
20
20
|
"discovered", "applied", "screening", "interviewing", "offer", "negotiating",
|
|
21
21
|
];
|
|
22
|
+
/**
|
|
23
|
+
* Stage colours as a temperature ramp, not a rainbow.
|
|
24
|
+
*
|
|
25
|
+
* These were the stock Tailwind hues — slate, sky, violet, amber, emerald, red.
|
|
26
|
+
* Six fully-saturated colours from three different families, dropped into a page
|
|
27
|
+
* built on warm neutrals and one clay accent: the board and the chart read as a
|
|
28
|
+
* different product than the frame around them, and in dark mode the violet and
|
|
29
|
+
* sky went neon.
|
|
30
|
+
*
|
|
31
|
+
* The replacement encodes the funnel instead of decorating it. Cool ash at the
|
|
32
|
+
* top where nothing has happened yet, warming through brass and clay as a
|
|
33
|
+
* conversation heats up, resolving to green when it lands. Everything is
|
|
34
|
+
* desaturated enough to sit inside a warm neutral system and to hold contrast
|
|
35
|
+
* on both the cream and the bark background. Terminal states are muted on
|
|
36
|
+
* purpose — a rejection should not be the brightest thing on the page.
|
|
37
|
+
*/
|
|
22
38
|
const STAGE_COLOR = {
|
|
23
|
-
discovered: "#
|
|
24
|
-
|
|
25
|
-
|
|
39
|
+
discovered: "#8e887d", // ash — noticed, not acted on
|
|
40
|
+
applied: "#7c8fa1", // cool steel — sent, waiting
|
|
41
|
+
screening: "#a2894f", // brass — someone replied
|
|
42
|
+
interviewing: "#c2603c", // clay — the product's accent, the hottest live stage
|
|
43
|
+
offer: "#4f8a6d", // sage — it landed
|
|
44
|
+
negotiating: "#3f7f74", // deep teal — landed, still moving
|
|
45
|
+
accepted: "#3d7a52", // forest — done, good
|
|
46
|
+
rejected: "#a85a4a", // muted brick — done, not good. Deliberately not red.
|
|
47
|
+
withdrawn: "#8a8278", // faded ash — done, your call
|
|
48
|
+
ghosted: "#6f6a63", // dim — done, no answer
|
|
26
49
|
};
|
|
27
50
|
function esc(s) {
|
|
28
51
|
return String(s ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
|
|
@@ -30,8 +53,20 @@ function esc(s) {
|
|
|
30
53
|
/** Derive next actions from follow-up dates, upcoming interviews, and expiring offers. */
|
|
31
54
|
export function deriveNextActions(apps, today = new Date()) {
|
|
32
55
|
const out = [];
|
|
33
|
-
|
|
34
|
-
|
|
56
|
+
// Compare CALENDAR DAYS in the user's own timezone, not timestamps.
|
|
57
|
+
// `new Date("2026-07-24")` is parsed as UTC midnight, while `Date.now()` is
|
|
58
|
+
// local — so west of UTC a follow-up due today came out negative and rendered
|
|
59
|
+
// as "overdue by 1d", and east of UTC tomorrow's reminder fired a day early.
|
|
60
|
+
// A date-only field has no time in it; treating it as one is the bug.
|
|
61
|
+
const midnight = new Date(today.getFullYear(), today.getMonth(), today.getDate()).getTime();
|
|
62
|
+
const days = (iso) => {
|
|
63
|
+
if (!iso)
|
|
64
|
+
return NaN;
|
|
65
|
+
const [y, m, d] = iso.slice(0, 10).split("-").map(Number);
|
|
66
|
+
if (!y || !m || !d)
|
|
67
|
+
return NaN;
|
|
68
|
+
return Math.round((new Date(y, m - 1, d).getTime() - midnight) / 86400000);
|
|
69
|
+
};
|
|
35
70
|
for (const app of apps) {
|
|
36
71
|
if (["accepted", "rejected", "withdrawn", "ghosted"].includes(app.status))
|
|
37
72
|
continue;
|
|
@@ -98,14 +133,37 @@ export function renderLiteDashboard(pipeline) {
|
|
|
98
133
|
kpi(`${s.responseRate}%`, "Response rate"),
|
|
99
134
|
kpi(`${s.ghostRate}%`, "Ghost rate"),
|
|
100
135
|
].join("");
|
|
101
|
-
|
|
102
|
-
|
|
136
|
+
// Only show a stage that has something in it, plus the handful of early
|
|
137
|
+
// stages that read as "nothing here yet" rather than as clutter. Rendering
|
|
138
|
+
// every active stage regardless meant a small pipeline still produced six
|
|
139
|
+
// columns, several of them just "0 —".
|
|
140
|
+
// The live funnel is the thing you scan every day; closed applications are
|
|
141
|
+
// reference. Giving them equal column weight pushed the board onto a second
|
|
142
|
+
// ragged row and made "Rejected" as loud as "Interviewing". Terminal stages
|
|
143
|
+
// now collapse behind one disclosure.
|
|
144
|
+
const CLOSED = ["accepted", "rejected", "withdrawn", "ghosted"];
|
|
145
|
+
const ALWAYS_SHOW = ["applied", "screening", "interviewing"];
|
|
146
|
+
const stageCol = (st) => {
|
|
103
147
|
const items = apps.filter((a) => a.status === st);
|
|
104
148
|
return `<div class="col">
|
|
105
149
|
<div class="h"><span class="sw" style="background:${STAGE_COLOR[st]}"></span> ${st} <span class="count">${items.length}</span></div>
|
|
106
|
-
<div class="stack">${items.map(jobCard).join("") || '<div class="none"
|
|
150
|
+
<div class="stack">${items.map(jobCard).join("") || '<div class="none">Nothing here yet</div>'}</div>
|
|
107
151
|
</div>`;
|
|
108
|
-
}
|
|
152
|
+
};
|
|
153
|
+
const liveStages = STAGE_ORDER.filter((st) => !CLOSED.includes(st) && (apps.some((a) => a.status === st) || ALWAYS_SHOW.includes(st)));
|
|
154
|
+
const closedStages = STAGE_ORDER.filter((st) => CLOSED.includes(st) && apps.some((a) => a.status === st));
|
|
155
|
+
const closedCount = apps.filter((a) => CLOSED.includes(a.status)).length;
|
|
156
|
+
const board = liveStages.map(stageCol).join("");
|
|
157
|
+
const closedBoard = closedCount
|
|
158
|
+
? `<details class="closed">
|
|
159
|
+
<summary>Closed <span class="count">${closedCount}</span>
|
|
160
|
+
<span class="dots">${closedStages
|
|
161
|
+
.map((st) => `<span class="sw" style="background:${STAGE_COLOR[st]}" title="${st}"></span>`)
|
|
162
|
+
.join("")}</span>
|
|
163
|
+
</summary>
|
|
164
|
+
<div class="board">${closedStages.map(stageCol).join("")}</div>
|
|
165
|
+
</details>`
|
|
166
|
+
: "";
|
|
109
167
|
const actionsHtml = actions.length
|
|
110
168
|
? actions.map((x) => `<div class="action ${x.urgency}"><span class="dot"></span><div class="t"><b>${esc(x.label)}</b><span>${esc(x.app.role)}</span></div></div>`).join("")
|
|
111
169
|
: `<div class="none-lg">✅ Nothing overdue. Follow-ups, upcoming interviews, and expiring offers surface here.</div>`;
|
|
@@ -123,8 +181,13 @@ export function renderLiteDashboard(pipeline) {
|
|
|
123
181
|
<html lang="en"><head>
|
|
124
182
|
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
|
125
183
|
<title>Career Compass — Pipeline</title>
|
|
184
|
+
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='7' fill='%231f1e1d'/%3E%3Ccircle cx='16' cy='16' r='10' fill='%23c2603c'/%3E%3Cpath d='M20.5 10.5 L14.5 17.5 L11.5 21.5 L17.5 14.5 Z' fill='%231f1e1d'/%3E%3C/svg%3E">
|
|
126
185
|
<style>
|
|
127
|
-
|
|
186
|
+
/* Warm neutrals + clay accent, so the page sits inside Claude rather than
|
|
187
|
+
next to it, and follows the host's light/dark preference instead of
|
|
188
|
+
blinding anyone running Claude Desktop in dark mode. */
|
|
189
|
+
:root{color-scheme:light dark;--bg:#faf9f5;--card:#fff;--ink:#1f1e1d;--muted:#6b675f;--line:#e8e5dd;--accent:#c2603c;--accent-soft:#f6ece7;--sunk:#f5f3ed;--shadow:0 1px 2px rgba(31,30,29,.05),0 4px 14px rgba(31,30,29,.04)}
|
|
190
|
+
@media(prefers-color-scheme:dark){:root{--bg:#1f1e1d;--card:#262624;--ink:#f2efe8;--muted:#a5a096;--line:#37352f;--accent:#e08560;--accent-soft:#33241d;--sunk:#221f1d;--shadow:0 1px 2px rgba(0,0,0,.3),0 4px 14px rgba(0,0,0,.22)}}
|
|
128
191
|
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased}
|
|
129
192
|
.wrap{max-width:1180px;margin:0 auto;padding:20px 20px 64px}
|
|
130
193
|
header.top{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-bottom:18px}
|
|
@@ -133,35 +196,47 @@ h1{font-size:20px;margin:0 0 2px;letter-spacing:-.01em}.sub{color:var(--muted);f
|
|
|
133
196
|
.kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:12px;margin-bottom:20px}
|
|
134
197
|
.kpi{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:14px 16px;box-shadow:var(--shadow)}
|
|
135
198
|
.kpi .n{font-size:26px;font-weight:700;letter-spacing:-.02em}.kpi .l{color:var(--muted);font-size:12px;margin-top:2px}.kpi .foot{font-size:11.5px;margin-top:6px;color:var(--muted)}
|
|
136
|
-
.grid2{display:grid;grid-template-columns:
|
|
199
|
+
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:start}@media(max-width:900px){.grid2{grid-template-columns:1fr}}
|
|
137
200
|
.panel{background:var(--card);border:1px solid var(--line);border-radius:14px;box-shadow:var(--shadow)}
|
|
138
201
|
.panel h2{font-size:13px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);margin:0;padding:14px 16px;border-bottom:1px solid var(--line)}
|
|
139
|
-
.panel .body{padding:14px 16px}.chart-box{position:relative;
|
|
202
|
+
.panel .body{padding:14px 16px}.chart-box{position:relative;display:flex;gap:10px}
|
|
140
203
|
.bar-row{display:flex;flex-direction:column;gap:9px;width:100%}
|
|
141
204
|
.bar{display:flex;align-items:center;gap:10px}.bar .lab{width:96px;font-size:12px;color:var(--muted);text-align:right;flex:0 0 auto}
|
|
142
|
-
.bar .track{flex:1;background
|
|
205
|
+
.bar .track{flex:1;background:var(--sunk);border-radius:6px;height:22px;overflow:hidden}
|
|
206
|
+
/* display:block is load-bearing. These are <span>s, and width has no effect on
|
|
207
|
+
an inline box — so every bar in this chart rendered as an empty track. */
|
|
208
|
+
.bar .fill{display:block;height:100%;border-radius:6px;min-width:3px;transition:width .3s ease}
|
|
143
209
|
.bar .val{font-size:12px;font-weight:600;width:22px}
|
|
144
210
|
.action{display:flex;gap:10px;padding:11px 0;border-bottom:1px dashed var(--line)}.action:last-child{border-bottom:0}
|
|
145
211
|
.action .dot{width:8px;height:8px;border-radius:50%;margin-top:6px;flex:0 0 auto;background:var(--accent)}
|
|
146
212
|
.action.overdue .dot{background:#ef4444}.action.soon .dot{background:#f59e0b}
|
|
147
213
|
.action .t{flex:1}.action .t b{display:block;font-size:13.5px}.action .t span{color:var(--muted);font-size:12px}
|
|
148
|
-
.board{display:grid;grid-
|
|
149
|
-
.col{background
|
|
214
|
+
.board{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:12px;align-items:start}
|
|
215
|
+
.col{background:var(--sunk);border:1px solid var(--line);border-radius:12px;min-height:80px}
|
|
150
216
|
.col .h{display:flex;align-items:center;gap:8px;padding:10px 12px;border-bottom:1px solid var(--line);font-weight:600;font-size:12.5px;text-transform:capitalize}
|
|
151
|
-
.col .h .sw{width:9px;height:9px;border-radius:50%}.col .h .count{margin-left:auto;background
|
|
217
|
+
.col .h .sw{width:9px;height:9px;border-radius:50%}.col .h .count{margin-left:auto;background:var(--sunk);color:var(--muted);border:1px solid var(--line);border-radius:999px;font-size:11px;padding:1px 8px}
|
|
152
218
|
.col .stack{padding:10px;display:flex;flex-direction:column;gap:9px}.none{color:var(--muted);font-size:11.5px;padding:4px 2px}.none-lg{color:var(--muted);font-size:13px}
|
|
153
219
|
.jc{background:var(--card);border:1px solid var(--line);border-left:3px solid var(--stage,#ccc);border-radius:10px;padding:10px 11px;cursor:pointer;transition:.12s}
|
|
154
220
|
.jc:hover{box-shadow:var(--shadow);transform:translateY(-1px)}.jc .co{font-weight:650;font-size:13px}.jc .ro{color:var(--muted);font-size:12px;margin-top:1px}
|
|
155
221
|
.jc .meta{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px;align-items:center}
|
|
156
|
-
.pill{font-size:10.5px;padding:2px 7px;border-radius:999px;font-weight:600}.p-high{background
|
|
222
|
+
.pill{font-size:10.5px;padding:2px 7px;border-radius:999px;font-weight:600}.p-high{background:color-mix(in srgb,#ef4444 16%,transparent);color:#c04a3a}.p-medium{background:color-mix(in srgb,#f59e0b 18%,transparent);color:#a06a1b}.p-low{background:color-mix(in srgb,var(--muted) 16%,transparent);color:var(--muted)}
|
|
157
223
|
.exc{font-size:11px;color:var(--muted);margin-left:auto}
|
|
158
224
|
.btn{border:1px solid var(--line);background:var(--card);color:var(--ink);border-radius:9px;padding:7px 12px;font-size:12.5px;font-weight:600;cursor:pointer;transition:.12s}
|
|
159
|
-
.btn:hover{border-color:var(--accent);color:var(--accent)}.btn.primary{background:var(--accent);border-color:var(--accent);color:#fff}.btn.primary:hover{
|
|
225
|
+
.btn:hover{border-color:var(--accent);color:var(--accent)}.btn.primary{background:var(--accent);border-color:var(--accent);color:#fff}.btn.primary:hover{filter:brightness(.93)}
|
|
160
226
|
.toolbar{display:flex;gap:8px;flex-wrap:wrap;margin:0 0 14px}
|
|
161
227
|
.state{text-align:center;padding:48px 20px;color:var(--muted)}.state h3{color:var(--ink);font-size:16px;margin:0 0 6px}.state .btns{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-top:16px}
|
|
162
228
|
.foot-note{color:var(--muted);font-size:11.5px;margin-top:22px;text-align:center}
|
|
163
|
-
#toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%) translateY(20px);background:var(--ink);color
|
|
229
|
+
#toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%) translateY(20px);background:var(--ink);color:var(--card);padding:10px 16px;border-radius:10px;font-size:13px;opacity:0;transition:.2s;pointer-events:none}
|
|
164
230
|
#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
|
|
231
|
+
.closed{margin-top:12px;border-top:1px solid var(--line);padding-top:10px}
|
|
232
|
+
.closed summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:8px;font-size:12.5px;font-weight:600;color:var(--muted);padding:4px 2px;border-radius:8px}
|
|
233
|
+
.closed summary::-webkit-details-marker{display:none}
|
|
234
|
+
.closed summary::before{content:"▸";display:inline-block;transition:transform .15s;color:var(--muted);font-size:11px}
|
|
235
|
+
.closed[open] summary::before{transform:rotate(90deg)}
|
|
236
|
+
.closed summary:hover{color:var(--ink)}
|
|
237
|
+
.closed .dots{display:inline-flex;gap:4px;margin-left:2px}
|
|
238
|
+
.closed .dots .sw{width:8px;height:8px;border-radius:50%}
|
|
239
|
+
.closed .board{margin-top:10px}
|
|
165
240
|
.hidden{display:none!important}
|
|
166
241
|
</style></head>
|
|
167
242
|
<body><div class="wrap">
|
|
@@ -176,15 +251,13 @@ h1{font-size:20px;margin:0 0 2px;letter-spacing:-.01em}.sub{color:var(--muted);f
|
|
|
176
251
|
</div>
|
|
177
252
|
<div class="kpis">${kpis}</div>
|
|
178
253
|
${apps.length === 0 ? emptyState : `
|
|
254
|
+
<div class="panel" style="margin-bottom:16px">
|
|
255
|
+
<h2>Pipeline by stage</h2>
|
|
256
|
+
<div class="body"><div class="board">${board}</div>${closedBoard}</div>
|
|
257
|
+
</div>
|
|
179
258
|
<div class="grid2">
|
|
180
|
-
<div class="panel">
|
|
181
|
-
|
|
182
|
-
<div class="body"><div class="board">${board}</div></div>
|
|
183
|
-
</div>
|
|
184
|
-
<div style="display:flex;flex-direction:column;gap:16px">
|
|
185
|
-
<div class="panel"><h2>Next actions</h2><div class="body">${actionsHtml}</div></div>
|
|
186
|
-
<div class="panel"><h2>Stage distribution</h2><div class="body"><div id="chart" class="chart-box"></div></div></div>
|
|
187
|
-
</div>
|
|
259
|
+
<div class="panel"><h2>Next actions</h2><div class="body">${actionsHtml}</div></div>
|
|
260
|
+
<div class="panel"><h2>Stage distribution</h2><div class="body"><div id="chart" class="chart-box"></div></div></div>
|
|
188
261
|
</div>`}
|
|
189
262
|
<div class="foot-note">Data stays local (<code>~/.career-compass</code>). Click any card to copy a prompt for Claude — that's where the work happens. Refresh the page to re-read from disk.</div>
|
|
190
263
|
</div>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/dashboard-lite/render.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,GAAwB;IACvC,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc;IACpD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS;CACvE,CAAC;AACF,MAAM,MAAM,GAAwB;IAClC,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,OAAO,EAAE,aAAa;CAC7E,CAAC;AACF,MAAM,WAAW,GAAsC;IACrD,UAAU,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/dashboard-lite/render.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,23 @@ import { type Server } from "http";
|
|
|
9
9
|
* dashboard. Ships in the npm package (pure JS, no framework, no build).
|
|
10
10
|
*/
|
|
11
11
|
export declare function createLiteDashboardServer(): Server;
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Start the lite dashboard on `port`, resolving once it's listening.
|
|
14
|
+
*
|
|
15
|
+
* Binds the literal IPv4 loopback rather than the name "localhost". Passing a
|
|
16
|
+
* name lets Node resolve it, and on Windows that returns `::1` first — so the
|
|
17
|
+
* server bound IPv6-only, and every client that tried `127.0.0.1` got
|
|
18
|
+
* ECONNREFUSED while the CLI cheerfully printed a `http://localhost` URL that
|
|
19
|
+
* happened to work on the author's machine. Which of the two a given browser
|
|
20
|
+
* picks is not something we get to decide, so binding the name is a coin flip
|
|
21
|
+
* on someone else's computer.
|
|
22
|
+
*
|
|
23
|
+
* `127.0.0.1` is the address every client tries, so it is the one that must
|
|
24
|
+
* work. A second listener on `::1` is attempted for hosts where `localhost`
|
|
25
|
+
* resolves IPv6-first; failure there is ignored, because IPv4 alone is a
|
|
26
|
+
* working dashboard and a machine with no IPv6 loopback is not an error.
|
|
27
|
+
*
|
|
28
|
+
* Both are loopback: the dashboard is never reachable from the network.
|
|
29
|
+
*/
|
|
13
30
|
export declare function startLiteDashboard(port: number, hostname?: string): Promise<Server>;
|
|
14
31
|
//# sourceMappingURL=server.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/dashboard-lite/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,MAAM,CAAC;AAIjD;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CA2BlD;AAED
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/dashboard-lite/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,MAAM,CAAC;AAIjD;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CA2BlD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBxF"}
|
|
@@ -39,12 +39,40 @@ export function createLiteDashboardServer() {
|
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
|
-
/**
|
|
43
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Start the lite dashboard on `port`, resolving once it's listening.
|
|
44
|
+
*
|
|
45
|
+
* Binds the literal IPv4 loopback rather than the name "localhost". Passing a
|
|
46
|
+
* name lets Node resolve it, and on Windows that returns `::1` first — so the
|
|
47
|
+
* server bound IPv6-only, and every client that tried `127.0.0.1` got
|
|
48
|
+
* ECONNREFUSED while the CLI cheerfully printed a `http://localhost` URL that
|
|
49
|
+
* happened to work on the author's machine. Which of the two a given browser
|
|
50
|
+
* picks is not something we get to decide, so binding the name is a coin flip
|
|
51
|
+
* on someone else's computer.
|
|
52
|
+
*
|
|
53
|
+
* `127.0.0.1` is the address every client tries, so it is the one that must
|
|
54
|
+
* work. A second listener on `::1` is attempted for hosts where `localhost`
|
|
55
|
+
* resolves IPv6-first; failure there is ignored, because IPv4 alone is a
|
|
56
|
+
* working dashboard and a machine with no IPv6 loopback is not an error.
|
|
57
|
+
*
|
|
58
|
+
* Both are loopback: the dashboard is never reachable from the network.
|
|
59
|
+
*/
|
|
60
|
+
export function startLiteDashboard(port, hostname = "127.0.0.1") {
|
|
44
61
|
const server = createLiteDashboardServer();
|
|
45
62
|
return new Promise((resolve, reject) => {
|
|
46
63
|
server.once("error", reject);
|
|
47
|
-
server.listen(port, hostname, () =>
|
|
64
|
+
server.listen(port, hostname, () => {
|
|
65
|
+
if (hostname !== "127.0.0.1")
|
|
66
|
+
return resolve(server);
|
|
67
|
+
// Best-effort dual-stack. Its lifetime is tied to the primary server so
|
|
68
|
+
// `close()` on the returned handle tears down both.
|
|
69
|
+
const v6 = createLiteDashboardServer();
|
|
70
|
+
v6.once("error", () => resolve(server)); // no IPv6 loopback: IPv4 is enough
|
|
71
|
+
v6.listen(port, "::1", () => {
|
|
72
|
+
server.once("close", () => v6.close());
|
|
73
|
+
resolve(server);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
48
76
|
});
|
|
49
77
|
}
|
|
50
78
|
//# sourceMappingURL=server.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/dashboard-lite/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAe,MAAM,MAAM,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD;;;;;;;;GAQG;AACH,MAAM,UAAU,yBAAyB;IACvC,OAAO,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACrC,yEAAyE;QACzE,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5C,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,aAAa,EAAE,CAAC;YAC3C,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,CAAC;YACrD,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;YAC3C,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;gBACjB,cAAc,EAAE,0BAA0B;gBAC1C,6DAA6D;gBAC7D,eAAe,EAAE,2BAA2B;aAC7C,CAAC,CAAC;YACH,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;YACxC,MAAM,GAAG,GAAG,OAAO;gBACjB,CAAC,CAAC,uGAAwG,GAAa,CAAC,OAAO,EAAE;gBACjI,CAAC,CAAC,+BAAgC,GAAa,EAAE,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5E,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,2BAA2B,EAAE,CAAC,CAAC;YACpF,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACf,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/dashboard-lite/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAe,MAAM,MAAM,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD;;;;;;;;GAQG;AACH,MAAM,UAAU,yBAAyB;IACvC,OAAO,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACrC,yEAAyE;QACzE,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5C,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,aAAa,EAAE,CAAC;YAC3C,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,CAAC;YACrD,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;YAC3C,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;gBACjB,cAAc,EAAE,0BAA0B;gBAC1C,6DAA6D;gBAC7D,eAAe,EAAE,2BAA2B;aAC7C,CAAC,CAAC;YACH,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;YACxC,MAAM,GAAG,GAAG,OAAO;gBACjB,CAAC,CAAC,uGAAwG,GAAa,CAAC,OAAO,EAAE;gBACjI,CAAC,CAAC,+BAAgC,GAAa,EAAE,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5E,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,2BAA2B,EAAE,CAAC,CAAC;YACpF,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACf,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,QAAQ,GAAG,WAAW;IACrE,MAAM,MAAM,GAAG,yBAAyB,EAAE,CAAC;IAC3C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC7B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE;YACjC,IAAI,QAAQ,KAAK,WAAW;gBAAE,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC;YACrD,wEAAwE;YACxE,oDAAoD;YACpD,MAAM,EAAE,GAAG,yBAAyB,EAAE,CAAC;YACvC,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,mCAAmC;YAC5E,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE;gBAC1B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC;gBACvC,OAAO,CAAC,MAAM,CAAC,CAAC;YAClB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one message a user sees before they have any career data.
|
|
3
|
+
*
|
|
4
|
+
* There used to be four different ones, and the only one carrying an
|
|
5
|
+
* instruction gave two wrong facts in twenty words: it named `data/career/`,
|
|
6
|
+
* a repo-relative path that exists nowhere on an installed user's machine, and
|
|
7
|
+
* it told them to run `ingest_document`, which by design never writes anything.
|
|
8
|
+
* So the first thing a new user saw was a dead end that also lied about where
|
|
9
|
+
* their files live.
|
|
10
|
+
*
|
|
11
|
+
* This names the actual resolved directory — the server knows it — and offers
|
|
12
|
+
* a first move that works: `save_career_section`, which is the only tool that
|
|
13
|
+
* writes the Career KB.
|
|
14
|
+
*/
|
|
15
|
+
export declare function noCareerDataMessage(): string;
|
|
16
|
+
//# sourceMappingURL=empty-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"empty-state.d.ts","sourceRoot":"","sources":["../../src/empty-state.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAa5C"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { getDataDir } from "./storage/file-store.js";
|
|
3
|
+
/**
|
|
4
|
+
* The one message a user sees before they have any career data.
|
|
5
|
+
*
|
|
6
|
+
* There used to be four different ones, and the only one carrying an
|
|
7
|
+
* instruction gave two wrong facts in twenty words: it named `data/career/`,
|
|
8
|
+
* a repo-relative path that exists nowhere on an installed user's machine, and
|
|
9
|
+
* it told them to run `ingest_document`, which by design never writes anything.
|
|
10
|
+
* So the first thing a new user saw was a dead end that also lied about where
|
|
11
|
+
* their files live.
|
|
12
|
+
*
|
|
13
|
+
* This names the actual resolved directory — the server knows it — and offers
|
|
14
|
+
* a first move that works: `save_career_section`, which is the only tool that
|
|
15
|
+
* writes the Career KB.
|
|
16
|
+
*/
|
|
17
|
+
export function noCareerDataMessage() {
|
|
18
|
+
const careerDir = join(getDataDir(), "career");
|
|
19
|
+
return [
|
|
20
|
+
"**No career data yet.** Career Compass reads plain YAML from:",
|
|
21
|
+
"",
|
|
22
|
+
` ${careerDir}`,
|
|
23
|
+
"",
|
|
24
|
+
"Nothing is there so far, which is why this tool has nothing to work with.",
|
|
25
|
+
"The fastest way to start: paste in a resume and ask me to save it — I'll",
|
|
26
|
+
"extract the structure and write it with `save_career_section`. You can also",
|
|
27
|
+
"drop your own YAML into that folder directly; it's yours, and it never",
|
|
28
|
+
"leaves this machine.",
|
|
29
|
+
].join("\n");
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=empty-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"empty-state.js","sourceRoot":"","sources":["../../src/empty-state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC/C,OAAO;QACL,+DAA+D;QAC/D,EAAE;QACF,OAAO,SAAS,EAAE;QAClB,EAAE;QACF,2EAA2E;QAC3E,0EAA0E;QAC1E,6EAA6E;QAC7E,wEAAwE;QACxE,sBAAsB;KACvB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -16,7 +16,22 @@ export declare class CorruptDataError extends Error {
|
|
|
16
16
|
}
|
|
17
17
|
export declare function isCorruptDataError(e: unknown): e is CorruptDataError;
|
|
18
18
|
export declare function withDataLock<T>(key: string, fn: () => Promise<T>): Promise<T>;
|
|
19
|
+
/** Absolute path of the directory the Career KB is read from and written to.
|
|
20
|
+
* Exported so empty-state messages can name the user's real folder instead of
|
|
21
|
+
* a repo-relative path that exists nowhere on their machine. */
|
|
22
|
+
export declare function getDataDir(): string;
|
|
19
23
|
export declare function loadCareerData(): Promise<CareerData | null>;
|
|
24
|
+
/** The only section names that may become a filename. */
|
|
25
|
+
export declare const CAREER_SECTIONS: readonly ["profile", "experience", "skills", "education", "projects", "testimonials"];
|
|
26
|
+
export type CareerSection = (typeof CAREER_SECTIONS)[number];
|
|
27
|
+
/**
|
|
28
|
+
* Write one section of the Career KB.
|
|
29
|
+
*
|
|
30
|
+
* `section` becomes a path segment, so it is checked against an allowlist rather
|
|
31
|
+
* than trusted. Before this was reachable from a tool it was only ever called
|
|
32
|
+
* with literals; now that a model can supply the value, `../../.ssh/id_rsa` has
|
|
33
|
+
* to be impossible rather than merely unlikely.
|
|
34
|
+
*/
|
|
20
35
|
export declare function saveCareerSection(section: string, data: unknown): Promise<void>;
|
|
21
36
|
/**
|
|
22
37
|
* Load the career journal.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../../../src/storage/file-store.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAkB,MAAM,6BAA6B,CAAC;AACnF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAKhE;;;;;;;;GAQG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;gBACb,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAU9C;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,gBAAgB,CAEpE;AA8BD,wBAAgB,YAAY,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAc7E;
|
|
1
|
+
{"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../../../src/storage/file-store.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAkB,MAAM,6BAA6B,CAAC;AACnF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAKhE;;;;;;;;GAQG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;gBACb,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAU9C;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,gBAAgB,CAEpE;AA8BD,wBAAgB,YAAY,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAc7E;AAID;;iEAEiE;AACjE,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AA2ED,wBAAsB,cAAc,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CA8CjE;AAED,yDAAyD;AACzD,eAAO,MAAM,eAAe,uFAElB,CAAC;AACX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAQrF;AAQD;;;;;;;;;GASG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC,CAG3D;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAWrF;AAID,wBAAsB,YAAY,IAAI,OAAO,CAAC,QAAQ,CAAC,CAgBtD;AAED,wBAAsB,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAGpE;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,cAAc,CAAC,CAAC,EACpC,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAC9C,OAAO,CAAC,CAAC,CAAC,CAaZ;AAID,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAGpD"}
|
|
@@ -66,7 +66,10 @@ export function withDataLock(key, fn) {
|
|
|
66
66
|
return run;
|
|
67
67
|
}
|
|
68
68
|
// ─── Path resolution ──────────────────────────────────────────────────────────
|
|
69
|
-
|
|
69
|
+
/** Absolute path of the directory the Career KB is read from and written to.
|
|
70
|
+
* Exported so empty-state messages can name the user's real folder instead of
|
|
71
|
+
* a repo-relative path that exists nowhere on their machine. */
|
|
72
|
+
export function getDataDir() {
|
|
70
73
|
return process.env.CAREER_DATA_PATH ?? join(homedir(), ".career-compass");
|
|
71
74
|
}
|
|
72
75
|
function careerDir() { return join(getDataDir(), "career"); }
|
|
@@ -187,7 +190,22 @@ export async function loadCareerData() {
|
|
|
187
190
|
throw new CorruptDataError(profilePath, error);
|
|
188
191
|
}
|
|
189
192
|
}
|
|
193
|
+
/** The only section names that may become a filename. */
|
|
194
|
+
export const CAREER_SECTIONS = [
|
|
195
|
+
"profile", "experience", "skills", "education", "projects", "testimonials",
|
|
196
|
+
];
|
|
197
|
+
/**
|
|
198
|
+
* Write one section of the Career KB.
|
|
199
|
+
*
|
|
200
|
+
* `section` becomes a path segment, so it is checked against an allowlist rather
|
|
201
|
+
* than trusted. Before this was reachable from a tool it was only ever called
|
|
202
|
+
* with literals; now that a model can supply the value, `../../.ssh/id_rsa` has
|
|
203
|
+
* to be impossible rather than merely unlikely.
|
|
204
|
+
*/
|
|
190
205
|
export async function saveCareerSection(section, data) {
|
|
206
|
+
if (!CAREER_SECTIONS.includes(section)) {
|
|
207
|
+
throw new Error(`Unknown career section "${section}". Expected one of: ${CAREER_SECTIONS.join(", ")}.`);
|
|
208
|
+
}
|
|
191
209
|
const path = join(careerDir(), `${section}.yaml`);
|
|
192
210
|
await withDataLock(path, () => atomicWriteYaml(path, data));
|
|
193
211
|
}
|