careermate 0.1.0 → 0.1.1
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 +10 -7
- package/apps/mcp/src/index.ts +3 -1
- package/apps/web/DESIGN_GUIDE.md +2 -2
- package/apps/web/public/icon-180.png +0 -0
- package/apps/web/public/index.html +4 -1
- package/apps/web/public/lib.js +1 -1
- package/apps/web/public/pages/applications.js +17 -1
- package/apps/web/public/pages/settings.js +6 -4
- package/apps/web/public/styles.css +49 -36
- package/apps/web/src/info.ts +9 -22
- package/apps/web/src/routes.ts +17 -13
- package/apps/web/src/server.ts +33 -25
- package/apps/web/src/settings.ts +35 -17
- package/bin/careermate.mjs +8 -5
- package/dist/{mcpb-stage/dist/web.mjs → doctor.mjs} +1122 -1450
- package/dist/install-page/favicon.svg +1 -0
- package/dist/install-page/icon-180.png +0 -0
- package/dist/install-page/icon-512.png +0 -0
- package/dist/install-page/index.html +309 -167
- package/dist/install-page/llms-install.txt +202 -0
- package/dist/install-page/og.png +0 -0
- package/dist/install-page/shots/applications-dark.png +0 -0
- package/dist/install-page/shots/applications-light.png +0 -0
- package/dist/install-page/shots/documents-dark.png +0 -0
- package/dist/install-page/shots/documents-light.png +0 -0
- package/dist/install-page/shots/home-dark.png +0 -0
- package/dist/install-page/shots/home-light.png +0 -0
- package/dist/install-page/shots/interview-dark.png +0 -0
- package/dist/install-page/shots/interview-light.png +0 -0
- package/dist/install-page/shots/jobs-dark.png +0 -0
- package/dist/install-page/shots/jobs-light.png +0 -0
- package/dist/install-page/shots/settings-dark.png +0 -0
- package/dist/install-page/shots/settings-light.png +0 -0
- package/dist/install-page/start.html +346 -0
- package/dist/install-page/style.css +94 -3
- package/dist/mcp.mjs +326 -68
- package/dist/migrate.mjs +4715 -0
- package/dist/public/icon-180.png +0 -0
- package/dist/public/index.html +4 -1
- package/dist/public/lib.js +1 -1
- package/dist/public/pages/applications.js +17 -1
- package/dist/public/pages/settings.js +6 -4
- package/dist/public/styles.css +49 -36
- package/dist/web.mjs +323 -129
- package/docs/DEMO.md +3 -3
- package/docs/MCP_TOOLS.md +21 -2
- package/docs/ROADMAP.md +3 -3
- package/docs/RUNTIME_DISTRIBUTION_RESEARCH.md +135 -0
- package/docs/START_WORKFLOW.md +6 -0
- package/docs/SUPPORTED_AI_APPS.md +85 -33
- package/docs/WORKFLOWS.md +1 -1
- package/package.json +3 -2
- package/packages/core/src/services.ts +42 -1
- package/packages/db/src/connection.ts +30 -0
- package/packages/db/src/index.ts +1 -0
- package/packages/db/src/repositories.ts +109 -63
- package/packages/db/src/schema.ts +118 -0
- package/packages/exporters/src/html.ts +9 -12
- package/packages/mcp-tools/src/tools.ts +81 -0
- package/packages/shared/src/enums.ts +39 -0
- package/scripts/build-dist.mjs +27 -8
- package/scripts/build-mcpb.mjs +60 -4
- package/scripts/gen-og.mjs +86 -0
- package/scripts/init.ts +32 -3
- package/scripts/seed.ts +5 -5
- package/scripts/shots-seed.ts +192 -0
- package/scripts/shots.ts +89 -0
- package/tsconfig.json +3 -1
- package/dist/careermate.mcpb +0 -0
- package/dist/mcp-smoke.err +0 -3
- package/dist/mcpb-stage/README.md +0 -219
- package/dist/mcpb-stage/dist/install-page/index.html +0 -434
- package/dist/mcpb-stage/dist/install-page/style.css +0 -407
- package/dist/mcpb-stage/dist/install-page/vercel.json +0 -20
- package/dist/mcpb-stage/dist/mcp.mjs +0 -23704
- package/dist/mcpb-stage/dist/public/app.js +0 -118
- package/dist/mcpb-stage/dist/public/index.html +0 -41
- package/dist/mcpb-stage/dist/public/lib.js +0 -282
- package/dist/mcpb-stage/dist/public/pages/applications.js +0 -98
- package/dist/mcpb-stage/dist/public/pages/documents.js +0 -446
- package/dist/mcpb-stage/dist/public/pages/home.js +0 -263
- package/dist/mcpb-stage/dist/public/pages/interview.js +0 -230
- package/dist/mcpb-stage/dist/public/pages/jobs.js +0 -494
- package/dist/mcpb-stage/dist/public/pages/profile.js +0 -576
- package/dist/mcpb-stage/dist/public/pages/settings.js +0 -233
- package/dist/mcpb-stage/dist/public/styles.css +0 -420
- package/dist/mcpb-stage/manifest.json +0 -40
- package/docs/INSTALL.md +0 -148
- package/docs/INSTALL_AND_USAGE.md +0 -99
- package/install-page/index.html +0 -474
- package/install-page/style.css +0 -391
- package/install-page/vercel.json +0 -20
- /package/dist/{mcpb-stage/dist/public → install-page}/fonts/PretendardVariable.woff2 +0 -0
|
@@ -12,6 +12,21 @@
|
|
|
12
12
|
Light + dark (system preference or manual toggle).
|
|
13
13
|
========================================================================== */
|
|
14
14
|
|
|
15
|
+
/* Self-hosted brand font — the SAME face the dashboard ships (apps/web), so the
|
|
16
|
+
landing prose and the embedded dashboard screenshots read as one product.
|
|
17
|
+
Without this the page silently falls back to Segoe UI / 맑은 고딕 on machines
|
|
18
|
+
that don't have Pretendard installed. */
|
|
19
|
+
@font-face {
|
|
20
|
+
font-family: 'Pretendard Variable';
|
|
21
|
+
font-weight: 45 920;
|
|
22
|
+
font-style: normal;
|
|
23
|
+
font-display: swap;
|
|
24
|
+
/* relative URL: resolves under the site root (Vercel) AND under /install/ when
|
|
25
|
+
the local dashboard serves this same page. */
|
|
26
|
+
src: local('Pretendard Variable'),
|
|
27
|
+
url('fonts/PretendardVariable.woff2') format('woff2');
|
|
28
|
+
}
|
|
29
|
+
|
|
15
30
|
:root {
|
|
16
31
|
/* warm paper neutrals (light) */
|
|
17
32
|
--bg: #faf8f4;
|
|
@@ -47,7 +62,7 @@
|
|
|
47
62
|
--radius-lg: 12px;
|
|
48
63
|
--maxw: 1000px;
|
|
49
64
|
|
|
50
|
-
--font: 'Pretendard', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Malgun Gothic',
|
|
65
|
+
--font: 'Pretendard Variable', Pretendard, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Malgun Gothic',
|
|
51
66
|
'Apple SD Gothic Neo', 'Noto Sans KR', Roboto, sans-serif;
|
|
52
67
|
--mono: 'Cascadia Code', 'SFMono-Regular', 'JetBrains Mono', Consolas, 'D2Coding', ui-monospace, monospace;
|
|
53
68
|
}
|
|
@@ -96,6 +111,7 @@ code { font-family: var(--mono); }
|
|
|
96
111
|
::selection { background: color-mix(in srgb, var(--accent) 22%, transparent); }
|
|
97
112
|
|
|
98
113
|
.accent { color: var(--accent); }
|
|
114
|
+
.nowrap { white-space: nowrap; }
|
|
99
115
|
|
|
100
116
|
/* ----------------------------------------------------------------- nav */
|
|
101
117
|
.nav {
|
|
@@ -226,6 +242,74 @@ code { font-family: var(--mono); }
|
|
|
226
242
|
.check strong { display: block; font-size: 14.5px; font-weight: 650; }
|
|
227
243
|
.check span { display: block; color: var(--text-secondary); font-size: 13px; margin-top: 2px; }
|
|
228
244
|
|
|
245
|
+
/* ----------------------------------------------------------- dashboard preview */
|
|
246
|
+
/* A carousel of real dashboard screenshots, one browser-framed slide at a time.
|
|
247
|
+
Prev/next arrows + dots; light/dark variants both in the DOM, CSS shows the one
|
|
248
|
+
matching the page theme. Degrades to the first slide if JS is off. */
|
|
249
|
+
/* side padding reserves space for the arrows so they sit OUTSIDE the frame
|
|
250
|
+
(never covering the screenshot). The frame shrinks to fit; on small screens
|
|
251
|
+
the padding collapses and arrows overlap the edges instead. */
|
|
252
|
+
.carousel { position: relative; max-width: 936px; margin: 0 auto; padding: 0 54px; }
|
|
253
|
+
.carousel__viewport { overflow: hidden; border-radius: var(--radius-lg); }
|
|
254
|
+
.carousel__track { display: flex; transition: transform .42s cubic-bezier(.22,.61,.36,1); }
|
|
255
|
+
@media (prefers-reduced-motion: reduce) { .carousel__track { transition: none; } }
|
|
256
|
+
.slide { flex: 0 0 100%; min-width: 0; margin: 0; } /* reset <figure> default margin so each slide is exactly 100% (translateX math) */
|
|
257
|
+
.slide figcaption { margin-top: 14px; text-align: center; font-size: 13.5px; color: var(--text-secondary); }
|
|
258
|
+
.slide figcaption strong { color: var(--text); font-weight: 650; }
|
|
259
|
+
|
|
260
|
+
.browser {
|
|
261
|
+
border: 1px solid var(--border-strong); border-radius: var(--radius-lg); overflow: hidden;
|
|
262
|
+
background: var(--surface); box-shadow: var(--shadow-md);
|
|
263
|
+
}
|
|
264
|
+
.browser__bar {
|
|
265
|
+
display: flex; align-items: center; gap: 10px; height: 38px; padding: 0 14px;
|
|
266
|
+
background: var(--bg-subtle); border-bottom: 1px solid var(--border);
|
|
267
|
+
}
|
|
268
|
+
.browser__dots { display: inline-flex; gap: 6px; flex: none; }
|
|
269
|
+
.browser__dots i { width: 10px; height: 10px; border-radius: 999px; background: var(--border-strong); }
|
|
270
|
+
.browser__url {
|
|
271
|
+
flex: 1; max-width: 320px; margin: 0 auto;
|
|
272
|
+
font-family: var(--mono); font-size: 12px; color: var(--text-tertiary); text-align: center;
|
|
273
|
+
background: var(--surface); border: 1px solid var(--border); border-radius: 999px; padding: 3px 12px;
|
|
274
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
275
|
+
}
|
|
276
|
+
.browser__body { display: block; background: var(--bg); }
|
|
277
|
+
.browser__body img { width: 100%; height: auto; }
|
|
278
|
+
|
|
279
|
+
/* show the screenshot that matches the current theme (default page theme = dark) */
|
|
280
|
+
.shot__img { display: none; }
|
|
281
|
+
:root[data-theme='dark'] .shot__img--dark { display: block; }
|
|
282
|
+
:root:not([data-theme='dark']) .shot__img--light { display: block; }
|
|
283
|
+
|
|
284
|
+
/* prev / next arrows — overlap the frame edges, vertically centred on the frame */
|
|
285
|
+
.carousel__arrow {
|
|
286
|
+
position: absolute; top: 44%; transform: translateY(-50%);
|
|
287
|
+
width: 44px; height: 44px; border-radius: 999px; display: grid; place-items: center;
|
|
288
|
+
background: var(--surface); border: 1px solid var(--border-strong); color: var(--text);
|
|
289
|
+
box-shadow: var(--shadow-md); cursor: pointer; z-index: 2;
|
|
290
|
+
transition: background .15s, opacity .15s;
|
|
291
|
+
}
|
|
292
|
+
.carousel__arrow:hover { background: var(--surface-hover); }
|
|
293
|
+
.carousel__arrow:disabled { opacity: .3; cursor: default; box-shadow: none; }
|
|
294
|
+
.carousel__arrow svg { width: 20px; height: 20px; }
|
|
295
|
+
.carousel__arrow--prev { left: 0; }
|
|
296
|
+
.carousel__arrow--next { right: 0; }
|
|
297
|
+
|
|
298
|
+
/* dots */
|
|
299
|
+
.carousel__dots { display: flex; justify-content: center; gap: 8px; margin-top: 18px; }
|
|
300
|
+
.carousel__dot {
|
|
301
|
+
width: 8px; height: 8px; padding: 0; border: 0; border-radius: 999px;
|
|
302
|
+
background: var(--border-strong); cursor: pointer; transition: background .15s, width .15s;
|
|
303
|
+
}
|
|
304
|
+
.carousel__dot:hover { background: var(--text-tertiary); }
|
|
305
|
+
.carousel__dot.is-active { width: 22px; background: var(--accent); }
|
|
306
|
+
|
|
307
|
+
@media (max-width: 640px) {
|
|
308
|
+
.carousel { padding: 0; }
|
|
309
|
+
.carousel__arrow--prev { left: 6px; }
|
|
310
|
+
.carousel__arrow--next { right: 6px; }
|
|
311
|
+
}
|
|
312
|
+
|
|
229
313
|
/* ----------------------------------------------------------------- install */
|
|
230
314
|
/* install track (A: .mcpb / B: terminal) */
|
|
231
315
|
.track {
|
|
@@ -287,8 +371,9 @@ kbd {
|
|
|
287
371
|
color: var(--text-tertiary); padding: 9px 13px 0;
|
|
288
372
|
}
|
|
289
373
|
.code pre {
|
|
290
|
-
margin: 0; padding: 11px 13px 13px; overflow-x: auto;
|
|
291
|
-
font-family: var(--mono); font-size: 12.5px; line-height: 1.6; color: var(--code-text);
|
|
374
|
+
margin: 0; padding: 11px 64px 13px 13px; overflow-x: auto;
|
|
375
|
+
font-family: var(--mono); font-size: 12.5px; line-height: 1.6; color: var(--code-text);
|
|
376
|
+
white-space: pre-wrap; overflow-wrap: anywhere;
|
|
292
377
|
}
|
|
293
378
|
.copy-btn {
|
|
294
379
|
position: absolute; top: 7px; right: 7px; font-family: inherit; font-size: 12px; font-weight: 600;
|
|
@@ -327,6 +412,12 @@ kbd {
|
|
|
327
412
|
.acc > summary:hover { background: var(--surface-hover); }
|
|
328
413
|
.acc__hint { font-weight: 500; font-size: 12.5px; color: var(--text-tertiary); }
|
|
329
414
|
.acc__body { padding: 0 17px 16px; }
|
|
415
|
+
.acc + .acc { margin-top: 10px; }
|
|
416
|
+
.track + .acc { margin-top: 16px; }
|
|
417
|
+
/* track-as-accordion: pill in summary + intro paragraph in body */
|
|
418
|
+
.acc > summary .track__tag { margin: 0; flex: none; }
|
|
419
|
+
.acc > summary { flex-wrap: wrap; }
|
|
420
|
+
.track-desc { color: var(--text-secondary); font-size: 14px; line-height: 1.6; margin: 0 0 12px; }
|
|
330
421
|
.trouble { display: grid; grid-template-columns: repeat(2, 1fr); gap: 13px; }
|
|
331
422
|
.trouble__item h4 { font-size: 14px; margin-bottom: 4px; }
|
|
332
423
|
.trouble__item p { color: var(--text-secondary); font-size: 13px; }
|
package/dist/mcp.mjs
CHANGED
|
@@ -13916,10 +13916,10 @@ function _endsWith(suffix, params) {
|
|
|
13916
13916
|
suffix
|
|
13917
13917
|
});
|
|
13918
13918
|
}
|
|
13919
|
-
function _overwrite(
|
|
13919
|
+
function _overwrite(tx2) {
|
|
13920
13920
|
return new $ZodCheckOverwrite({
|
|
13921
13921
|
check: "overwrite",
|
|
13922
|
-
tx
|
|
13922
|
+
tx: tx2
|
|
13923
13923
|
});
|
|
13924
13924
|
}
|
|
13925
13925
|
function _normalize(form) {
|
|
@@ -15041,7 +15041,7 @@ var ZodType2 = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
|
|
|
15041
15041
|
inst.array = () => array(inst);
|
|
15042
15042
|
inst.or = (arg) => union([inst, arg]);
|
|
15043
15043
|
inst.and = (arg) => intersection(inst, arg);
|
|
15044
|
-
inst.transform = (
|
|
15044
|
+
inst.transform = (tx2) => pipe(inst, transform(tx2));
|
|
15045
15045
|
inst.default = (def2) => _default(inst, def2);
|
|
15046
15046
|
inst.prefault = (def2) => prefault(inst, def2);
|
|
15047
15047
|
inst.catch = (params) => _catch(inst, params);
|
|
@@ -21300,6 +21300,123 @@ var MIGRATIONS = [
|
|
|
21300
21300
|
CREATE INDEX IF NOT EXISTS idx_clv_letter ON cover_letter_versions(cover_letter_id);
|
|
21301
21301
|
CREATE INDEX IF NOT EXISTS idx_cl_job ON cover_letters(job_id);
|
|
21302
21302
|
CREATE INDEX IF NOT EXISTS idx_activities_created ON activities(created_at DESC);
|
|
21303
|
+
`,
|
|
21304
|
+
// v2 — add foreign keys so PRAGMA foreign_keys=ON is actually enforced.
|
|
21305
|
+
//
|
|
21306
|
+
// SQLite cannot ALTER a table to add constraints, so each table that needs an
|
|
21307
|
+
// FK is recreated (create new → copy → drop old → rename). The copy also
|
|
21308
|
+
// repairs pre-existing orphans: child rows whose parent is gone are dropped
|
|
21309
|
+
// (CASCADE intent) and dangling optional refs are nulled (SET NULL intent), so
|
|
21310
|
+
// a subsequent PRAGMA foreign_key_check stays clean. FKs are disabled for the
|
|
21311
|
+
// rebuild and the whole thing runs in one transaction (auto-rolled-back if the
|
|
21312
|
+
// process dies mid-migration, since the version bump happens only afterwards).
|
|
21313
|
+
`
|
|
21314
|
+
PRAGMA foreign_keys=OFF;
|
|
21315
|
+
BEGIN;
|
|
21316
|
+
|
|
21317
|
+
-- cover_letters.job_id -> jobs(id) ON DELETE SET NULL
|
|
21318
|
+
CREATE TABLE cover_letters_new (
|
|
21319
|
+
id TEXT PRIMARY KEY,
|
|
21320
|
+
title TEXT NOT NULL,
|
|
21321
|
+
job_id TEXT REFERENCES jobs(id) ON DELETE SET NULL,
|
|
21322
|
+
is_primary INTEGER NOT NULL DEFAULT 0,
|
|
21323
|
+
current_version_id TEXT,
|
|
21324
|
+
created_at TEXT NOT NULL,
|
|
21325
|
+
updated_at TEXT NOT NULL
|
|
21326
|
+
);
|
|
21327
|
+
INSERT INTO cover_letters_new (id,title,job_id,is_primary,current_version_id,created_at,updated_at)
|
|
21328
|
+
SELECT id,title,
|
|
21329
|
+
CASE WHEN job_id IN (SELECT id FROM jobs) THEN job_id ELSE NULL END,
|
|
21330
|
+
is_primary,current_version_id,created_at,updated_at
|
|
21331
|
+
FROM cover_letters;
|
|
21332
|
+
DROP TABLE cover_letters;
|
|
21333
|
+
ALTER TABLE cover_letters_new RENAME TO cover_letters;
|
|
21334
|
+
|
|
21335
|
+
-- cover_letter_versions.cover_letter_id -> cover_letters(id) ON DELETE CASCADE
|
|
21336
|
+
CREATE TABLE cover_letter_versions_new (
|
|
21337
|
+
id TEXT PRIMARY KEY,
|
|
21338
|
+
cover_letter_id TEXT NOT NULL REFERENCES cover_letters(id) ON DELETE CASCADE,
|
|
21339
|
+
version_no INTEGER NOT NULL,
|
|
21340
|
+
content TEXT NOT NULL,
|
|
21341
|
+
note TEXT,
|
|
21342
|
+
source TEXT NOT NULL DEFAULT 'ai',
|
|
21343
|
+
created_at TEXT NOT NULL
|
|
21344
|
+
);
|
|
21345
|
+
INSERT INTO cover_letter_versions_new (id,cover_letter_id,version_no,content,note,source,created_at)
|
|
21346
|
+
SELECT id,cover_letter_id,version_no,content,note,source,created_at
|
|
21347
|
+
FROM cover_letter_versions
|
|
21348
|
+
WHERE cover_letter_id IN (SELECT id FROM cover_letters);
|
|
21349
|
+
DROP TABLE cover_letter_versions;
|
|
21350
|
+
ALTER TABLE cover_letter_versions_new RENAME TO cover_letter_versions;
|
|
21351
|
+
|
|
21352
|
+
-- fit_analyses.job_id -> jobs(id) ON DELETE CASCADE
|
|
21353
|
+
CREATE TABLE fit_analyses_new (
|
|
21354
|
+
id TEXT PRIMARY KEY,
|
|
21355
|
+
job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
|
21356
|
+
score REAL,
|
|
21357
|
+
summary TEXT,
|
|
21358
|
+
strengths TEXT NOT NULL DEFAULT '[]',
|
|
21359
|
+
gaps TEXT NOT NULL DEFAULT '[]',
|
|
21360
|
+
matched_keywords TEXT NOT NULL DEFAULT '[]',
|
|
21361
|
+
missing_keywords TEXT NOT NULL DEFAULT '[]',
|
|
21362
|
+
recommendations TEXT NOT NULL DEFAULT '[]',
|
|
21363
|
+
created_at TEXT NOT NULL,
|
|
21364
|
+
updated_at TEXT NOT NULL
|
|
21365
|
+
);
|
|
21366
|
+
INSERT INTO fit_analyses_new (id,job_id,score,summary,strengths,gaps,matched_keywords,missing_keywords,recommendations,created_at,updated_at)
|
|
21367
|
+
SELECT id,job_id,score,summary,strengths,gaps,matched_keywords,missing_keywords,recommendations,created_at,updated_at
|
|
21368
|
+
FROM fit_analyses
|
|
21369
|
+
WHERE job_id IN (SELECT id FROM jobs);
|
|
21370
|
+
DROP TABLE fit_analyses;
|
|
21371
|
+
ALTER TABLE fit_analyses_new RENAME TO fit_analyses;
|
|
21372
|
+
|
|
21373
|
+
-- applications: job_id CASCADE, resume_id/cover_letter_id SET NULL
|
|
21374
|
+
CREATE TABLE applications_new (
|
|
21375
|
+
id TEXT PRIMARY KEY,
|
|
21376
|
+
job_id TEXT NOT NULL UNIQUE REFERENCES jobs(id) ON DELETE CASCADE,
|
|
21377
|
+
status TEXT NOT NULL DEFAULT 'draft',
|
|
21378
|
+
resume_id TEXT REFERENCES documents(id) ON DELETE SET NULL,
|
|
21379
|
+
cover_letter_id TEXT REFERENCES cover_letters(id) ON DELETE SET NULL,
|
|
21380
|
+
applied_at TEXT,
|
|
21381
|
+
notes TEXT,
|
|
21382
|
+
created_at TEXT NOT NULL,
|
|
21383
|
+
updated_at TEXT NOT NULL
|
|
21384
|
+
);
|
|
21385
|
+
INSERT INTO applications_new (id,job_id,status,resume_id,cover_letter_id,applied_at,notes,created_at,updated_at)
|
|
21386
|
+
SELECT id,job_id,status,
|
|
21387
|
+
CASE WHEN resume_id IN (SELECT id FROM documents) THEN resume_id ELSE NULL END,
|
|
21388
|
+
CASE WHEN cover_letter_id IN (SELECT id FROM cover_letters) THEN cover_letter_id ELSE NULL END,
|
|
21389
|
+
applied_at,notes,created_at,updated_at
|
|
21390
|
+
FROM applications
|
|
21391
|
+
WHERE job_id IN (SELECT id FROM jobs);
|
|
21392
|
+
DROP TABLE applications;
|
|
21393
|
+
ALTER TABLE applications_new RENAME TO applications;
|
|
21394
|
+
|
|
21395
|
+
-- interview_preps.job_id -> jobs(id) ON DELETE CASCADE
|
|
21396
|
+
CREATE TABLE interview_preps_new (
|
|
21397
|
+
id TEXT PRIMARY KEY,
|
|
21398
|
+
job_id TEXT NOT NULL UNIQUE REFERENCES jobs(id) ON DELETE CASCADE,
|
|
21399
|
+
questions TEXT NOT NULL DEFAULT '[]',
|
|
21400
|
+
star_guides TEXT NOT NULL DEFAULT '[]',
|
|
21401
|
+
self_introduction TEXT,
|
|
21402
|
+
notes TEXT,
|
|
21403
|
+
created_at TEXT NOT NULL,
|
|
21404
|
+
updated_at TEXT NOT NULL
|
|
21405
|
+
);
|
|
21406
|
+
INSERT INTO interview_preps_new (id,job_id,questions,star_guides,self_introduction,notes,created_at,updated_at)
|
|
21407
|
+
SELECT id,job_id,questions,star_guides,self_introduction,notes,created_at,updated_at
|
|
21408
|
+
FROM interview_preps
|
|
21409
|
+
WHERE job_id IN (SELECT id FROM jobs);
|
|
21410
|
+
DROP TABLE interview_preps;
|
|
21411
|
+
ALTER TABLE interview_preps_new RENAME TO interview_preps;
|
|
21412
|
+
|
|
21413
|
+
-- Recreate indexes dropped along with their tables.
|
|
21414
|
+
CREATE INDEX IF NOT EXISTS idx_fit_job ON fit_analyses(job_id);
|
|
21415
|
+
CREATE INDEX IF NOT EXISTS idx_clv_letter ON cover_letter_versions(cover_letter_id);
|
|
21416
|
+
CREATE INDEX IF NOT EXISTS idx_cl_job ON cover_letters(job_id);
|
|
21417
|
+
|
|
21418
|
+
COMMIT;
|
|
21419
|
+
PRAGMA foreign_keys=ON;
|
|
21303
21420
|
`
|
|
21304
21421
|
];
|
|
21305
21422
|
function migrate(db) {
|
|
@@ -21331,6 +21448,26 @@ function getDb() {
|
|
|
21331
21448
|
_db = db;
|
|
21332
21449
|
return db;
|
|
21333
21450
|
}
|
|
21451
|
+
var _inTx = false;
|
|
21452
|
+
function tx(fn) {
|
|
21453
|
+
if (_inTx) return fn();
|
|
21454
|
+
const db = getDb();
|
|
21455
|
+
db.exec("BEGIN");
|
|
21456
|
+
_inTx = true;
|
|
21457
|
+
try {
|
|
21458
|
+
const result = fn();
|
|
21459
|
+
db.exec("COMMIT");
|
|
21460
|
+
return result;
|
|
21461
|
+
} catch (err) {
|
|
21462
|
+
try {
|
|
21463
|
+
db.exec("ROLLBACK");
|
|
21464
|
+
} catch {
|
|
21465
|
+
}
|
|
21466
|
+
throw err;
|
|
21467
|
+
} finally {
|
|
21468
|
+
_inTx = false;
|
|
21469
|
+
}
|
|
21470
|
+
}
|
|
21334
21471
|
function toJson(value) {
|
|
21335
21472
|
return JSON.stringify(value ?? null);
|
|
21336
21473
|
}
|
|
@@ -21379,6 +21516,27 @@ var INTERVIEW_UNLOCK_STATUSES = [
|
|
|
21379
21516
|
"interview",
|
|
21380
21517
|
"final_passed"
|
|
21381
21518
|
];
|
|
21519
|
+
var ALLOWED_STATUS_TRANSITIONS = {
|
|
21520
|
+
draft: ["planned", "applied", "document_passed", "interview", "final_passed", "rejected", "on_hold"],
|
|
21521
|
+
planned: ["applied", "document_passed", "interview", "final_passed", "rejected", "on_hold"],
|
|
21522
|
+
applied: ["planned", "document_passed", "interview", "final_passed", "rejected", "on_hold"],
|
|
21523
|
+
document_passed: ["planned", "applied", "interview", "final_passed", "rejected", "on_hold"],
|
|
21524
|
+
interview: ["planned", "applied", "document_passed", "final_passed", "rejected", "on_hold"],
|
|
21525
|
+
final_passed: ["document_passed", "interview", "rejected", "on_hold"],
|
|
21526
|
+
rejected: ["draft", "planned", "applied", "document_passed", "interview", "final_passed", "on_hold"],
|
|
21527
|
+
on_hold: ["draft", "planned", "applied", "document_passed", "interview", "final_passed", "rejected"]
|
|
21528
|
+
};
|
|
21529
|
+
function canTransitionStatus(from, to) {
|
|
21530
|
+
if (from === to) return true;
|
|
21531
|
+
return ALLOWED_STATUS_TRANSITIONS[from]?.includes(to) ?? false;
|
|
21532
|
+
}
|
|
21533
|
+
function assertStatusTransition(from, to) {
|
|
21534
|
+
if (!canTransitionStatus(from, to)) {
|
|
21535
|
+
throw new Error(
|
|
21536
|
+
`'${APPLICATION_STATUS_LABELS[from]}'\uC5D0\uC11C '${APPLICATION_STATUS_LABELS[to]}'(\uC73C)\uB85C\uB294 \uC0C1\uD0DC\uB97C \uBC14\uAFC0 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
|
|
21537
|
+
);
|
|
21538
|
+
}
|
|
21539
|
+
}
|
|
21382
21540
|
var DOCUMENT_KINDS = ["resume", "career_description", "portfolio", "other"];
|
|
21383
21541
|
var CONTENT_SOURCES = ["manual", "upload", "ai", "edit"];
|
|
21384
21542
|
var ACTIVITY_TYPES = [
|
|
@@ -21996,34 +22154,38 @@ var documentRepo = {
|
|
|
21996
22154
|
const ts = now();
|
|
21997
22155
|
const id2 = newId("doc_");
|
|
21998
22156
|
const db = getDb();
|
|
21999
|
-
|
|
22000
|
-
|
|
22001
|
-
|
|
22002
|
-
|
|
22003
|
-
|
|
22004
|
-
|
|
22005
|
-
|
|
22006
|
-
|
|
22007
|
-
|
|
22008
|
-
|
|
22009
|
-
|
|
22010
|
-
|
|
22011
|
-
|
|
22012
|
-
|
|
22013
|
-
|
|
22014
|
-
|
|
22015
|
-
|
|
22157
|
+
return tx(() => {
|
|
22158
|
+
if (input.is_primary) {
|
|
22159
|
+
db.prepare(`UPDATE documents SET is_primary=0 WHERE kind=?`).run(input.kind);
|
|
22160
|
+
}
|
|
22161
|
+
db.prepare(
|
|
22162
|
+
`INSERT INTO documents (id,kind,title,content,source,is_primary,tags,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?)`
|
|
22163
|
+
).run(
|
|
22164
|
+
id2,
|
|
22165
|
+
input.kind,
|
|
22166
|
+
input.title,
|
|
22167
|
+
input.content ?? "",
|
|
22168
|
+
input.source ?? "manual",
|
|
22169
|
+
toBit(input.is_primary),
|
|
22170
|
+
toJson(input.tags ?? []),
|
|
22171
|
+
ts,
|
|
22172
|
+
ts
|
|
22173
|
+
);
|
|
22174
|
+
return this.get(id2);
|
|
22175
|
+
});
|
|
22016
22176
|
},
|
|
22017
22177
|
update(id2, input) {
|
|
22018
22178
|
const cur = this.get(id2);
|
|
22019
22179
|
if (!cur) return null;
|
|
22020
22180
|
const db = getDb();
|
|
22021
22181
|
const m = { ...cur, ...input };
|
|
22022
|
-
|
|
22023
|
-
|
|
22024
|
-
|
|
22025
|
-
|
|
22026
|
-
|
|
22182
|
+
return tx(() => {
|
|
22183
|
+
if (input.is_primary) db.prepare(`UPDATE documents SET is_primary=0 WHERE kind=?`).run(m.kind);
|
|
22184
|
+
db.prepare(
|
|
22185
|
+
`UPDATE documents SET kind=?,title=?,content=?,source=?,is_primary=?,tags=?,updated_at=? WHERE id=?`
|
|
22186
|
+
).run(m.kind, m.title, m.content, m.source, toBit(m.is_primary), toJson(m.tags), now(), id2);
|
|
22187
|
+
return this.get(id2);
|
|
22188
|
+
});
|
|
22027
22189
|
},
|
|
22028
22190
|
remove(id2) {
|
|
22029
22191
|
return getDb().prepare(`DELETE FROM documents WHERE id=?`).run(id2).changes > 0;
|
|
@@ -22072,37 +22234,41 @@ var coverLetterRepo = {
|
|
|
22072
22234
|
const ts = now();
|
|
22073
22235
|
const id2 = newId("cl_");
|
|
22074
22236
|
const db = getDb();
|
|
22075
|
-
|
|
22076
|
-
|
|
22077
|
-
|
|
22078
|
-
|
|
22079
|
-
|
|
22237
|
+
return tx(() => {
|
|
22238
|
+
if (opts.is_primary) db.prepare(`UPDATE cover_letters SET is_primary=0`).run();
|
|
22239
|
+
db.prepare(
|
|
22240
|
+
`INSERT INTO cover_letters (id,title,job_id,is_primary,current_version_id,created_at,updated_at) VALUES (?,?,?,?,?,?,?)`
|
|
22241
|
+
).run(id2, opts.title, opts.job_id ?? null, toBit(opts.is_primary), null, ts, ts);
|
|
22242
|
+
return this.get(id2);
|
|
22243
|
+
});
|
|
22080
22244
|
},
|
|
22081
22245
|
/** Append a new version. Creates the cover letter if cover_letter_id is omitted. */
|
|
22082
22246
|
addVersion(opts) {
|
|
22083
22247
|
const db = getDb();
|
|
22084
|
-
|
|
22085
|
-
|
|
22086
|
-
|
|
22087
|
-
|
|
22088
|
-
|
|
22089
|
-
|
|
22090
|
-
|
|
22091
|
-
|
|
22092
|
-
|
|
22093
|
-
|
|
22094
|
-
|
|
22095
|
-
|
|
22096
|
-
|
|
22097
|
-
|
|
22098
|
-
|
|
22099
|
-
|
|
22100
|
-
|
|
22101
|
-
|
|
22102
|
-
|
|
22103
|
-
|
|
22104
|
-
|
|
22105
|
-
|
|
22248
|
+
return tx(() => {
|
|
22249
|
+
let clId = opts.cover_letter_id;
|
|
22250
|
+
if (!clId) {
|
|
22251
|
+
const cl = this.create({ title: opts.title ?? "\uC790\uAE30\uC18C\uAC1C\uC11C", job_id: opts.job_id ?? null });
|
|
22252
|
+
clId = cl.id;
|
|
22253
|
+
}
|
|
22254
|
+
const maxRow = db.prepare(`SELECT MAX(version_no) AS m FROM cover_letter_versions WHERE cover_letter_id=?`).get(clId);
|
|
22255
|
+
const versionNo = (maxRow.m ?? 0) + 1;
|
|
22256
|
+
const vid = newId("clv_");
|
|
22257
|
+
const ts = now();
|
|
22258
|
+
db.prepare(
|
|
22259
|
+
`INSERT INTO cover_letter_versions (id,cover_letter_id,version_no,content,note,source,created_at) VALUES (?,?,?,?,?,?,?)`
|
|
22260
|
+
).run(vid, clId, versionNo, opts.content, opts.note ?? null, opts.source ?? "ai", ts);
|
|
22261
|
+
const setCurrent = opts.set_current !== false;
|
|
22262
|
+
if (setCurrent) {
|
|
22263
|
+
db.prepare(`UPDATE cover_letters SET current_version_id=?, updated_at=? WHERE id=?`).run(vid, ts, clId);
|
|
22264
|
+
} else {
|
|
22265
|
+
db.prepare(`UPDATE cover_letters SET updated_at=? WHERE id=?`).run(ts, clId);
|
|
22266
|
+
}
|
|
22267
|
+
if (opts.title) db.prepare(`UPDATE cover_letters SET title=? WHERE id=?`).run(opts.title, clId);
|
|
22268
|
+
if (opts.job_id !== void 0)
|
|
22269
|
+
db.prepare(`UPDATE cover_letters SET job_id=? WHERE id=?`).run(opts.job_id, clId);
|
|
22270
|
+
return { coverLetter: this.get(clId), version: mapVersion(db.prepare(`SELECT * FROM cover_letter_versions WHERE id=?`).get(vid)) };
|
|
22271
|
+
});
|
|
22106
22272
|
},
|
|
22107
22273
|
setCurrentVersion(coverLetterId, versionId) {
|
|
22108
22274
|
getDb().prepare(`UPDATE cover_letters SET current_version_id=?, updated_at=? WHERE id=?`).run(versionId, now(), coverLetterId);
|
|
@@ -22110,14 +22276,18 @@ var coverLetterRepo = {
|
|
|
22110
22276
|
},
|
|
22111
22277
|
setPrimary(id2) {
|
|
22112
22278
|
const db = getDb();
|
|
22113
|
-
|
|
22114
|
-
|
|
22115
|
-
|
|
22279
|
+
return tx(() => {
|
|
22280
|
+
db.prepare(`UPDATE cover_letters SET is_primary=0`).run();
|
|
22281
|
+
db.prepare(`UPDATE cover_letters SET is_primary=1, updated_at=? WHERE id=?`).run(now(), id2);
|
|
22282
|
+
return this.get(id2);
|
|
22283
|
+
});
|
|
22116
22284
|
},
|
|
22117
22285
|
remove(id2) {
|
|
22118
22286
|
const db = getDb();
|
|
22119
|
-
|
|
22120
|
-
|
|
22287
|
+
return tx(() => {
|
|
22288
|
+
db.prepare(`DELETE FROM cover_letter_versions WHERE cover_letter_id=?`).run(id2);
|
|
22289
|
+
return db.prepare(`DELETE FROM cover_letters WHERE id=?`).run(id2).changes > 0;
|
|
22290
|
+
});
|
|
22121
22291
|
}
|
|
22122
22292
|
};
|
|
22123
22293
|
function mapJob(r) {
|
|
@@ -22202,10 +22372,12 @@ var jobRepo = {
|
|
|
22202
22372
|
},
|
|
22203
22373
|
remove(id2) {
|
|
22204
22374
|
const db = getDb();
|
|
22205
|
-
|
|
22206
|
-
|
|
22207
|
-
|
|
22208
|
-
|
|
22375
|
+
return tx(() => {
|
|
22376
|
+
db.prepare(`DELETE FROM fit_analyses WHERE job_id=?`).run(id2);
|
|
22377
|
+
db.prepare(`DELETE FROM applications WHERE job_id=?`).run(id2);
|
|
22378
|
+
db.prepare(`DELETE FROM interview_preps WHERE job_id=?`).run(id2);
|
|
22379
|
+
return db.prepare(`DELETE FROM jobs WHERE id=?`).run(id2).changes > 0;
|
|
22380
|
+
});
|
|
22209
22381
|
}
|
|
22210
22382
|
};
|
|
22211
22383
|
function mapFit(r) {
|
|
@@ -22312,6 +22484,7 @@ var applicationRepo = {
|
|
|
22312
22484
|
},
|
|
22313
22485
|
upsert(input) {
|
|
22314
22486
|
const app = this.ensure(input.job_id, input.status ?? "draft");
|
|
22487
|
+
if (input.status && input.status !== app.status) assertStatusTransition(app.status, input.status);
|
|
22315
22488
|
const m = { ...app, ...input };
|
|
22316
22489
|
getDb().prepare(
|
|
22317
22490
|
`UPDATE applications SET status=?,resume_id=?,cover_letter_id=?,applied_at=?,notes=?,updated_at=? WHERE id=?`
|
|
@@ -22320,6 +22493,7 @@ var applicationRepo = {
|
|
|
22320
22493
|
},
|
|
22321
22494
|
setStatus(jobId, status, note) {
|
|
22322
22495
|
const app = this.ensure(jobId);
|
|
22496
|
+
assertStatusTransition(app.status, status);
|
|
22323
22497
|
const appliedAt = status === "applied" && !app.applied_at ? now() : app.applied_at;
|
|
22324
22498
|
const notes = note ? [app.notes, note].filter(Boolean).join("\n") : app.notes;
|
|
22325
22499
|
getDb().prepare(`UPDATE applications SET status=?, applied_at=?, notes=?, updated_at=? WHERE id=?`).run(status, appliedAt, notes, now(), app.id);
|
|
@@ -22508,6 +22682,21 @@ function addResume(input) {
|
|
|
22508
22682
|
activityRepo.log("resume_added", `${doc.title} \uBB38\uC11C\uB97C \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, "document", doc.id);
|
|
22509
22683
|
return doc;
|
|
22510
22684
|
}
|
|
22685
|
+
function addExperience(input) {
|
|
22686
|
+
const exp = experienceRepo.add(input);
|
|
22687
|
+
activityRepo.log("profile_updated", `\uACBD\uB825(${exp.company})\uC744 \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, "experience", exp.id);
|
|
22688
|
+
return exp;
|
|
22689
|
+
}
|
|
22690
|
+
function addProject(input) {
|
|
22691
|
+
const prj = projectRepo.add(input);
|
|
22692
|
+
activityRepo.log("profile_updated", `\uD504\uB85C\uC81D\uD2B8(${prj.name})\uB97C \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, "project", prj.id);
|
|
22693
|
+
return prj;
|
|
22694
|
+
}
|
|
22695
|
+
function addSkill(input) {
|
|
22696
|
+
const skill = skillRepo.add(input);
|
|
22697
|
+
activityRepo.log("profile_updated", `\uAE30\uC220(${skill.name})\uC744 \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, "skill", skill.id);
|
|
22698
|
+
return skill;
|
|
22699
|
+
}
|
|
22511
22700
|
function saveJobPosting(input, id2) {
|
|
22512
22701
|
const job = jobRepo.upsert(input, id2);
|
|
22513
22702
|
const application = applicationRepo.ensure(job.id, "draft");
|
|
@@ -22565,7 +22754,13 @@ function updateApplicationStatus(jobId, status, note) {
|
|
|
22565
22754
|
function saveInterviewPrep(input) {
|
|
22566
22755
|
const job = jobRepo.get(input.job_id);
|
|
22567
22756
|
if (!job) throw new Error(`\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.job_id}`);
|
|
22568
|
-
applicationRepo.
|
|
22757
|
+
const app = applicationRepo.getByJob(job.id);
|
|
22758
|
+
if (!app || !INTERVIEW_UNLOCK_STATUSES.includes(app.status)) {
|
|
22759
|
+
const current = app ? APPLICATION_STATUS_LABELS[app.status] : "\uBBF8\uC9C0\uC6D0";
|
|
22760
|
+
throw new Error(
|
|
22761
|
+
`\uBA74\uC811 \uC900\uBE44\uB294 '\uC11C\uB958 \uD569\uACA9' \uC774\uC0C1 \uC0C1\uD0DC\uC5D0\uC11C\uB9CC \uC800\uC7A5\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uD604\uC7AC \uC0C1\uD0DC: '${current}'. \uBA3C\uC800 update_application_status\uB85C \uC0C1\uD0DC\uB97C \uC62C\uB9B0 \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.`
|
|
22762
|
+
);
|
|
22763
|
+
}
|
|
22569
22764
|
const prep = interviewRepo.save(input);
|
|
22570
22765
|
activityRepo.log("interview_prep_saved", `${job.company} \xB7 ${job.position} \uBA74\uC811 \uC900\uBE44 \uC790\uB8CC\uB97C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4.`, "interview_prep", prep.id);
|
|
22571
22766
|
return prep;
|
|
@@ -22763,11 +22958,8 @@ function renderWorkflowMarkdown(id2) {
|
|
|
22763
22958
|
function escapeHtml(input) {
|
|
22764
22959
|
return (input ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
22765
22960
|
}
|
|
22766
|
-
function
|
|
22767
|
-
|
|
22768
|
-
}
|
|
22769
|
-
function toPrintableHtml(title, bodyMarkdownOrHtml) {
|
|
22770
|
-
const body = looksLikeHtml(bodyMarkdownOrHtml) ? bodyMarkdownOrHtml : markdownToHtml(bodyMarkdownOrHtml);
|
|
22961
|
+
function toPrintableHtml(title, bodyMarkdown) {
|
|
22962
|
+
const body = markdownToHtml(bodyMarkdown);
|
|
22771
22963
|
const safeTitle = escapeHtml(title);
|
|
22772
22964
|
return `<!doctype html>
|
|
22773
22965
|
<html lang="ko">
|
|
@@ -23384,6 +23576,72 @@ ${ONBOARDING_PROMPT}`,
|
|
|
23384
23576
|
return ok(`\uBB38\uC11C ${docs.length}\uAC74`, docs);
|
|
23385
23577
|
}
|
|
23386
23578
|
},
|
|
23579
|
+
/* ----------------------------------------------------------- experiences */
|
|
23580
|
+
{
|
|
23581
|
+
name: "add_experience",
|
|
23582
|
+
title: "\uACBD\uB825 \uCD94\uAC00",
|
|
23583
|
+
description: "\uAD6C\uC870\uD654\uB41C \uACBD\uB825(\uC9C1\uC7A5 \uB2E8\uC704)\uC744 \uBCC4\uB3C4 \uD56D\uBAA9\uC73C\uB85C \uC800\uC7A5\uD569\uB2C8\uB2E4. \uC774\uB825\uC11C \uBCF8\uBB38(add_resume)\uACFC \uB2EC\uB9AC, \uD68C\uC0AC\xB7\uC9C1\uBB34\xB7\uAE30\uAC04\xB7\uC131\uACFC\uB97C \uD544\uB4DC\uB85C \uB098\uB220 \uC800\uC7A5\uD574 \uC801\uD569\uB3C4 \uBD84\uC11D/\uC790\uC18C\uC11C \uC791\uC131 \uC2DC \uC815\uBC00\uD558\uAC8C \uD65C\uC6A9\uB429\uB2C8\uB2E4. company\uB294 \uD544\uC218\uC774\uBA70, achievements\uC5D0\uB294 \uC815\uB7C9 \uC9C0\uD45C\uAC00 \uB2F4\uAE34 \uC131\uACFC\uB97C, tech\uC5D0\uB294 \uC0AC\uC6A9 \uAE30\uC220\uC744 \uBC30\uC5F4\uB85C \uB123\uC73C\uC138\uC694. \uC628\uBCF4\uB529 \uC644\uC131\uB3C4(get_onboarding_status)\uC758 has_experience\uB97C \uCC44\uC6B0\uB824\uBA74 \uC774 \uB3C4\uAD6C\uB85C 1\uAC74 \uC774\uC0C1 \uCD94\uAC00\uD574\uC57C \uD569\uB2C8\uB2E4.",
|
|
23584
|
+
inputSchema: ExperienceInputSchema.shape,
|
|
23585
|
+
handler: (args) => {
|
|
23586
|
+
const exp = addExperience(args);
|
|
23587
|
+
return ok(`\uACBD\uB825 '${exp.company}${exp.role ? ` \xB7 ${exp.role}` : ""}'\uC744(\uB97C) \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, exp);
|
|
23588
|
+
}
|
|
23589
|
+
},
|
|
23590
|
+
{
|
|
23591
|
+
name: "get_experiences",
|
|
23592
|
+
title: "\uACBD\uB825 \uBAA9\uB85D \uC870\uD68C",
|
|
23593
|
+
description: "\uC800\uC7A5\uB41C \uACBD\uB825\uC744 \uC2DC\uAC04 \uC5ED\uC21C\uC73C\uB85C \uC870\uD68C\uD569\uB2C8\uB2E4. (\uC804\uCCB4 \uB9E5\uB77D\uC774 \uD544\uC694\uD558\uBA74 get_application_context\uB97C \uC4F0\uC138\uC694.)",
|
|
23594
|
+
inputSchema: {},
|
|
23595
|
+
readOnly: true,
|
|
23596
|
+
handler: () => {
|
|
23597
|
+
const list = experienceRepo.list();
|
|
23598
|
+
return ok(`\uACBD\uB825 ${list.length}\uAC74`, list);
|
|
23599
|
+
}
|
|
23600
|
+
},
|
|
23601
|
+
/* -------------------------------------------------------------- projects */
|
|
23602
|
+
{
|
|
23603
|
+
name: "add_project",
|
|
23604
|
+
title: "\uD504\uB85C\uC81D\uD2B8 \uCD94\uAC00",
|
|
23605
|
+
description: "\uB300\uD45C \uD504\uB85C\uC81D\uD2B8\uB97C \uBCC4\uB3C4 \uD56D\uBAA9\uC73C\uB85C \uC800\uC7A5\uD569\uB2C8\uB2E4. name\uC740 \uD544\uC218\uC774\uBA70, highlights(\uD575\uC2EC \uC131\uACFC)\uC640 tech(\uC0AC\uC6A9 \uAE30\uC220)\uB97C \uBC30\uC5F4\uB85C \uB123\uC73C\uBA74 \uC790\uC18C\uC11C\xB7\uBA74\uC811 \uC900\uBE44\uC5D0\uC11C \uADFC\uAC70\uB85C \uD65C\uC6A9\uB429\uB2C8\uB2E4. url\uC5D0 \uACB0\uACFC\uBB3C \uB9C1\uD06C\uB97C \uB123\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
|
|
23606
|
+
inputSchema: ProjectInputSchema.shape,
|
|
23607
|
+
handler: (args) => {
|
|
23608
|
+
const prj = addProject(args);
|
|
23609
|
+
return ok(`\uD504\uB85C\uC81D\uD2B8 '${prj.name}'\uC744(\uB97C) \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, prj);
|
|
23610
|
+
}
|
|
23611
|
+
},
|
|
23612
|
+
{
|
|
23613
|
+
name: "get_projects",
|
|
23614
|
+
title: "\uD504\uB85C\uC81D\uD2B8 \uBAA9\uB85D \uC870\uD68C",
|
|
23615
|
+
description: "\uC800\uC7A5\uB41C \uD504\uB85C\uC81D\uD2B8\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. (\uC804\uCCB4 \uB9E5\uB77D\uC774 \uD544\uC694\uD558\uBA74 get_application_context\uB97C \uC4F0\uC138\uC694.)",
|
|
23616
|
+
inputSchema: {},
|
|
23617
|
+
readOnly: true,
|
|
23618
|
+
handler: () => {
|
|
23619
|
+
const list = projectRepo.list();
|
|
23620
|
+
return ok(`\uD504\uB85C\uC81D\uD2B8 ${list.length}\uAC74`, list);
|
|
23621
|
+
}
|
|
23622
|
+
},
|
|
23623
|
+
/* ---------------------------------------------------------------- skills */
|
|
23624
|
+
{
|
|
23625
|
+
name: "add_skill",
|
|
23626
|
+
title: "\uAE30\uC220\uC2A4\uD0DD \uCD94\uAC00",
|
|
23627
|
+
description: "\uBCF4\uC720 \uAE30\uC220\uC2A4\uD0DD\uC744 \uBCC4\uB3C4 \uD56D\uBAA9\uC73C\uB85C \uC800\uC7A5\uD569\uB2C8\uB2E4. name\uC740 \uD544\uC218\uC774\uBA70, category(\uC5B8\uC5B4/\uD504\uB808\uC784\uC6CC\uD06C/\uD234/\uC18C\uD504\uD2B8\uC2A4\uD0AC \uB4F1)\xB7level\xB7years\uB85C \uC219\uB828\uB3C4\uB97C \uD568\uAED8 \uAE30\uB85D\uD558\uBA74 \uACF5\uACE0 \uD0A4\uC6CC\uB4DC \uB9E4\uCE6D\uC774 \uC815\uD655\uD574\uC9D1\uB2C8\uB2E4. \uC628\uBCF4\uB529 \uC644\uC131\uB3C4\uC758 has_skills\uB97C \uCC44\uC6B0\uB824\uBA74 \uC774 \uB3C4\uAD6C\uB85C 1\uAC74 \uC774\uC0C1 \uCD94\uAC00\uD574\uC57C \uD569\uB2C8\uB2E4. \uC5EC\uB7EC \uAC1C\uBA74 \uB3C4\uAD6C\uB97C \uBC18\uBCF5 \uD638\uCD9C\uD558\uC138\uC694.",
|
|
23628
|
+
inputSchema: SkillInputSchema.shape,
|
|
23629
|
+
handler: (args) => {
|
|
23630
|
+
const skill = addSkill(args);
|
|
23631
|
+
return ok(`\uAE30\uC220 '${skill.name}'\uC744(\uB97C) \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, skill);
|
|
23632
|
+
}
|
|
23633
|
+
},
|
|
23634
|
+
{
|
|
23635
|
+
name: "get_skills",
|
|
23636
|
+
title: "\uAE30\uC220\uC2A4\uD0DD \uBAA9\uB85D \uC870\uD68C",
|
|
23637
|
+
description: "\uC800\uC7A5\uB41C \uAE30\uC220\uC2A4\uD0DD\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. (\uC804\uCCB4 \uB9E5\uB77D\uC774 \uD544\uC694\uD558\uBA74 get_application_context\uB97C \uC4F0\uC138\uC694.)",
|
|
23638
|
+
inputSchema: {},
|
|
23639
|
+
readOnly: true,
|
|
23640
|
+
handler: () => {
|
|
23641
|
+
const list = skillRepo.list();
|
|
23642
|
+
return ok(`\uAE30\uC220 ${list.length}\uAC74`, list);
|
|
23643
|
+
}
|
|
23644
|
+
},
|
|
23387
23645
|
/* ---------------------------------------------------------- cover letters */
|
|
23388
23646
|
{
|
|
23389
23647
|
name: "add_cover_letter",
|
|
@@ -23661,7 +23919,7 @@ ${ONBOARDING_PROMPT}`,
|
|
|
23661
23919
|
];
|
|
23662
23920
|
|
|
23663
23921
|
// apps/mcp/src/index.ts
|
|
23664
|
-
var APP_VERSION = "0.1.0";
|
|
23922
|
+
var APP_VERSION = true ? "0.1.1" : "0.0.0-dev";
|
|
23665
23923
|
async function main() {
|
|
23666
23924
|
getDb();
|
|
23667
23925
|
const server = new McpServer(
|