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
|
@@ -5,13 +5,8 @@ var __export = (target, all) => {
|
|
|
5
5
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
-
//
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
// apps/web/src/server.ts
|
|
12
|
-
import http from "node:http";
|
|
13
|
-
import path5 from "node:path";
|
|
14
|
-
import { fileURLToPath } from "node:url";
|
|
8
|
+
// scripts/doctor.ts
|
|
9
|
+
import fs4 from "node:fs";
|
|
15
10
|
|
|
16
11
|
// packages/db/src/paths.ts
|
|
17
12
|
import os from "node:os";
|
|
@@ -31,11 +26,6 @@ function getExportsDir() {
|
|
|
31
26
|
fs.mkdirSync(dir, { recursive: true });
|
|
32
27
|
return dir;
|
|
33
28
|
}
|
|
34
|
-
function getBackupsDir() {
|
|
35
|
-
const dir = path.join(getDataDir(), "backups");
|
|
36
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
37
|
-
return dir;
|
|
38
|
-
}
|
|
39
29
|
|
|
40
30
|
// packages/db/src/runtime.ts
|
|
41
31
|
import fs2 from "node:fs";
|
|
@@ -43,16 +33,20 @@ import path2 from "node:path";
|
|
|
43
33
|
function runtimeFile() {
|
|
44
34
|
return path2.join(getDataDir(), "server.json");
|
|
45
35
|
}
|
|
46
|
-
function
|
|
36
|
+
function readRuntimeInfo() {
|
|
47
37
|
try {
|
|
48
|
-
fs2.
|
|
38
|
+
const raw = fs2.readFileSync(runtimeFile(), "utf8");
|
|
39
|
+
return JSON.parse(raw);
|
|
49
40
|
} catch {
|
|
41
|
+
return null;
|
|
50
42
|
}
|
|
51
43
|
}
|
|
52
|
-
function
|
|
44
|
+
function isProcessAlive(pid) {
|
|
53
45
|
try {
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
process.kill(pid, 0);
|
|
47
|
+
return true;
|
|
48
|
+
} catch (e) {
|
|
49
|
+
return e.code === "EPERM";
|
|
56
50
|
}
|
|
57
51
|
}
|
|
58
52
|
|
|
@@ -216,6 +210,123 @@ var MIGRATIONS = [
|
|
|
216
210
|
CREATE INDEX IF NOT EXISTS idx_clv_letter ON cover_letter_versions(cover_letter_id);
|
|
217
211
|
CREATE INDEX IF NOT EXISTS idx_cl_job ON cover_letters(job_id);
|
|
218
212
|
CREATE INDEX IF NOT EXISTS idx_activities_created ON activities(created_at DESC);
|
|
213
|
+
`,
|
|
214
|
+
// v2 — add foreign keys so PRAGMA foreign_keys=ON is actually enforced.
|
|
215
|
+
//
|
|
216
|
+
// SQLite cannot ALTER a table to add constraints, so each table that needs an
|
|
217
|
+
// FK is recreated (create new → copy → drop old → rename). The copy also
|
|
218
|
+
// repairs pre-existing orphans: child rows whose parent is gone are dropped
|
|
219
|
+
// (CASCADE intent) and dangling optional refs are nulled (SET NULL intent), so
|
|
220
|
+
// a subsequent PRAGMA foreign_key_check stays clean. FKs are disabled for the
|
|
221
|
+
// rebuild and the whole thing runs in one transaction (auto-rolled-back if the
|
|
222
|
+
// process dies mid-migration, since the version bump happens only afterwards).
|
|
223
|
+
`
|
|
224
|
+
PRAGMA foreign_keys=OFF;
|
|
225
|
+
BEGIN;
|
|
226
|
+
|
|
227
|
+
-- cover_letters.job_id -> jobs(id) ON DELETE SET NULL
|
|
228
|
+
CREATE TABLE cover_letters_new (
|
|
229
|
+
id TEXT PRIMARY KEY,
|
|
230
|
+
title TEXT NOT NULL,
|
|
231
|
+
job_id TEXT REFERENCES jobs(id) ON DELETE SET NULL,
|
|
232
|
+
is_primary INTEGER NOT NULL DEFAULT 0,
|
|
233
|
+
current_version_id TEXT,
|
|
234
|
+
created_at TEXT NOT NULL,
|
|
235
|
+
updated_at TEXT NOT NULL
|
|
236
|
+
);
|
|
237
|
+
INSERT INTO cover_letters_new (id,title,job_id,is_primary,current_version_id,created_at,updated_at)
|
|
238
|
+
SELECT id,title,
|
|
239
|
+
CASE WHEN job_id IN (SELECT id FROM jobs) THEN job_id ELSE NULL END,
|
|
240
|
+
is_primary,current_version_id,created_at,updated_at
|
|
241
|
+
FROM cover_letters;
|
|
242
|
+
DROP TABLE cover_letters;
|
|
243
|
+
ALTER TABLE cover_letters_new RENAME TO cover_letters;
|
|
244
|
+
|
|
245
|
+
-- cover_letter_versions.cover_letter_id -> cover_letters(id) ON DELETE CASCADE
|
|
246
|
+
CREATE TABLE cover_letter_versions_new (
|
|
247
|
+
id TEXT PRIMARY KEY,
|
|
248
|
+
cover_letter_id TEXT NOT NULL REFERENCES cover_letters(id) ON DELETE CASCADE,
|
|
249
|
+
version_no INTEGER NOT NULL,
|
|
250
|
+
content TEXT NOT NULL,
|
|
251
|
+
note TEXT,
|
|
252
|
+
source TEXT NOT NULL DEFAULT 'ai',
|
|
253
|
+
created_at TEXT NOT NULL
|
|
254
|
+
);
|
|
255
|
+
INSERT INTO cover_letter_versions_new (id,cover_letter_id,version_no,content,note,source,created_at)
|
|
256
|
+
SELECT id,cover_letter_id,version_no,content,note,source,created_at
|
|
257
|
+
FROM cover_letter_versions
|
|
258
|
+
WHERE cover_letter_id IN (SELECT id FROM cover_letters);
|
|
259
|
+
DROP TABLE cover_letter_versions;
|
|
260
|
+
ALTER TABLE cover_letter_versions_new RENAME TO cover_letter_versions;
|
|
261
|
+
|
|
262
|
+
-- fit_analyses.job_id -> jobs(id) ON DELETE CASCADE
|
|
263
|
+
CREATE TABLE fit_analyses_new (
|
|
264
|
+
id TEXT PRIMARY KEY,
|
|
265
|
+
job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
|
266
|
+
score REAL,
|
|
267
|
+
summary TEXT,
|
|
268
|
+
strengths TEXT NOT NULL DEFAULT '[]',
|
|
269
|
+
gaps TEXT NOT NULL DEFAULT '[]',
|
|
270
|
+
matched_keywords TEXT NOT NULL DEFAULT '[]',
|
|
271
|
+
missing_keywords TEXT NOT NULL DEFAULT '[]',
|
|
272
|
+
recommendations TEXT NOT NULL DEFAULT '[]',
|
|
273
|
+
created_at TEXT NOT NULL,
|
|
274
|
+
updated_at TEXT NOT NULL
|
|
275
|
+
);
|
|
276
|
+
INSERT INTO fit_analyses_new (id,job_id,score,summary,strengths,gaps,matched_keywords,missing_keywords,recommendations,created_at,updated_at)
|
|
277
|
+
SELECT id,job_id,score,summary,strengths,gaps,matched_keywords,missing_keywords,recommendations,created_at,updated_at
|
|
278
|
+
FROM fit_analyses
|
|
279
|
+
WHERE job_id IN (SELECT id FROM jobs);
|
|
280
|
+
DROP TABLE fit_analyses;
|
|
281
|
+
ALTER TABLE fit_analyses_new RENAME TO fit_analyses;
|
|
282
|
+
|
|
283
|
+
-- applications: job_id CASCADE, resume_id/cover_letter_id SET NULL
|
|
284
|
+
CREATE TABLE applications_new (
|
|
285
|
+
id TEXT PRIMARY KEY,
|
|
286
|
+
job_id TEXT NOT NULL UNIQUE REFERENCES jobs(id) ON DELETE CASCADE,
|
|
287
|
+
status TEXT NOT NULL DEFAULT 'draft',
|
|
288
|
+
resume_id TEXT REFERENCES documents(id) ON DELETE SET NULL,
|
|
289
|
+
cover_letter_id TEXT REFERENCES cover_letters(id) ON DELETE SET NULL,
|
|
290
|
+
applied_at TEXT,
|
|
291
|
+
notes TEXT,
|
|
292
|
+
created_at TEXT NOT NULL,
|
|
293
|
+
updated_at TEXT NOT NULL
|
|
294
|
+
);
|
|
295
|
+
INSERT INTO applications_new (id,job_id,status,resume_id,cover_letter_id,applied_at,notes,created_at,updated_at)
|
|
296
|
+
SELECT id,job_id,status,
|
|
297
|
+
CASE WHEN resume_id IN (SELECT id FROM documents) THEN resume_id ELSE NULL END,
|
|
298
|
+
CASE WHEN cover_letter_id IN (SELECT id FROM cover_letters) THEN cover_letter_id ELSE NULL END,
|
|
299
|
+
applied_at,notes,created_at,updated_at
|
|
300
|
+
FROM applications
|
|
301
|
+
WHERE job_id IN (SELECT id FROM jobs);
|
|
302
|
+
DROP TABLE applications;
|
|
303
|
+
ALTER TABLE applications_new RENAME TO applications;
|
|
304
|
+
|
|
305
|
+
-- interview_preps.job_id -> jobs(id) ON DELETE CASCADE
|
|
306
|
+
CREATE TABLE interview_preps_new (
|
|
307
|
+
id TEXT PRIMARY KEY,
|
|
308
|
+
job_id TEXT NOT NULL UNIQUE REFERENCES jobs(id) ON DELETE CASCADE,
|
|
309
|
+
questions TEXT NOT NULL DEFAULT '[]',
|
|
310
|
+
star_guides TEXT NOT NULL DEFAULT '[]',
|
|
311
|
+
self_introduction TEXT,
|
|
312
|
+
notes TEXT,
|
|
313
|
+
created_at TEXT NOT NULL,
|
|
314
|
+
updated_at TEXT NOT NULL
|
|
315
|
+
);
|
|
316
|
+
INSERT INTO interview_preps_new (id,job_id,questions,star_guides,self_introduction,notes,created_at,updated_at)
|
|
317
|
+
SELECT id,job_id,questions,star_guides,self_introduction,notes,created_at,updated_at
|
|
318
|
+
FROM interview_preps
|
|
319
|
+
WHERE job_id IN (SELECT id FROM jobs);
|
|
320
|
+
DROP TABLE interview_preps;
|
|
321
|
+
ALTER TABLE interview_preps_new RENAME TO interview_preps;
|
|
322
|
+
|
|
323
|
+
-- Recreate indexes dropped along with their tables.
|
|
324
|
+
CREATE INDEX IF NOT EXISTS idx_fit_job ON fit_analyses(job_id);
|
|
325
|
+
CREATE INDEX IF NOT EXISTS idx_clv_letter ON cover_letter_versions(cover_letter_id);
|
|
326
|
+
CREATE INDEX IF NOT EXISTS idx_cl_job ON cover_letters(job_id);
|
|
327
|
+
|
|
328
|
+
COMMIT;
|
|
329
|
+
PRAGMA foreign_keys=ON;
|
|
219
330
|
`
|
|
220
331
|
];
|
|
221
332
|
function migrate(db) {
|
|
@@ -247,6 +358,26 @@ function getDb() {
|
|
|
247
358
|
_db = db;
|
|
248
359
|
return db;
|
|
249
360
|
}
|
|
361
|
+
var _inTx = false;
|
|
362
|
+
function tx(fn) {
|
|
363
|
+
if (_inTx) return fn();
|
|
364
|
+
const db = getDb();
|
|
365
|
+
db.exec("BEGIN");
|
|
366
|
+
_inTx = true;
|
|
367
|
+
try {
|
|
368
|
+
const result = fn();
|
|
369
|
+
db.exec("COMMIT");
|
|
370
|
+
return result;
|
|
371
|
+
} catch (err) {
|
|
372
|
+
try {
|
|
373
|
+
db.exec("ROLLBACK");
|
|
374
|
+
} catch {
|
|
375
|
+
}
|
|
376
|
+
throw err;
|
|
377
|
+
} finally {
|
|
378
|
+
_inTx = false;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
250
381
|
function toJson(value) {
|
|
251
382
|
return JSON.stringify(value ?? null);
|
|
252
383
|
}
|
|
@@ -290,28 +421,33 @@ var APPLICATION_STATUS_LABELS = {
|
|
|
290
421
|
rejected: "\uBD88\uD569\uACA9",
|
|
291
422
|
on_hold: "\uBCF4\uB958"
|
|
292
423
|
};
|
|
293
|
-
var APPLICATION_BOARD_ORDER = [
|
|
294
|
-
"draft",
|
|
295
|
-
"planned",
|
|
296
|
-
"applied",
|
|
297
|
-
"document_passed",
|
|
298
|
-
"interview",
|
|
299
|
-
"final_passed",
|
|
300
|
-
"on_hold",
|
|
301
|
-
"rejected"
|
|
302
|
-
];
|
|
303
424
|
var INTERVIEW_UNLOCK_STATUSES = [
|
|
304
425
|
"document_passed",
|
|
305
426
|
"interview",
|
|
306
427
|
"final_passed"
|
|
307
428
|
];
|
|
308
|
-
var
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
429
|
+
var ALLOWED_STATUS_TRANSITIONS = {
|
|
430
|
+
draft: ["planned", "applied", "document_passed", "interview", "final_passed", "rejected", "on_hold"],
|
|
431
|
+
planned: ["applied", "document_passed", "interview", "final_passed", "rejected", "on_hold"],
|
|
432
|
+
applied: ["planned", "document_passed", "interview", "final_passed", "rejected", "on_hold"],
|
|
433
|
+
document_passed: ["planned", "applied", "interview", "final_passed", "rejected", "on_hold"],
|
|
434
|
+
interview: ["planned", "applied", "document_passed", "final_passed", "rejected", "on_hold"],
|
|
435
|
+
final_passed: ["document_passed", "interview", "rejected", "on_hold"],
|
|
436
|
+
rejected: ["draft", "planned", "applied", "document_passed", "interview", "final_passed", "on_hold"],
|
|
437
|
+
on_hold: ["draft", "planned", "applied", "document_passed", "interview", "final_passed", "rejected"]
|
|
314
438
|
};
|
|
439
|
+
function canTransitionStatus(from, to) {
|
|
440
|
+
if (from === to) return true;
|
|
441
|
+
return ALLOWED_STATUS_TRANSITIONS[from]?.includes(to) ?? false;
|
|
442
|
+
}
|
|
443
|
+
function assertStatusTransition(from, to) {
|
|
444
|
+
if (!canTransitionStatus(from, to)) {
|
|
445
|
+
throw new Error(
|
|
446
|
+
`'${APPLICATION_STATUS_LABELS[from]}'\uC5D0\uC11C '${APPLICATION_STATUS_LABELS[to]}'(\uC73C)\uB85C\uB294 \uC0C1\uD0DC\uB97C \uBC14\uAFC0 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
var DOCUMENT_KINDS = ["resume", "career_description", "portfolio", "other"];
|
|
315
451
|
var CONTENT_SOURCES = ["manual", "upload", "ai", "edit"];
|
|
316
452
|
var ACTIVITY_TYPES = [
|
|
317
453
|
"profile_updated",
|
|
@@ -815,8 +951,8 @@ function getErrorMap() {
|
|
|
815
951
|
|
|
816
952
|
// node_modules/zod/v3/helpers/parseUtil.js
|
|
817
953
|
var makeIssue = (params) => {
|
|
818
|
-
const { data, path:
|
|
819
|
-
const fullPath = [...
|
|
954
|
+
const { data, path: path5, errorMaps, issueData } = params;
|
|
955
|
+
const fullPath = [...path5, ...issueData.path || []];
|
|
820
956
|
const fullIssue = {
|
|
821
957
|
...issueData,
|
|
822
958
|
path: fullPath
|
|
@@ -932,11 +1068,11 @@ var errorUtil;
|
|
|
932
1068
|
|
|
933
1069
|
// node_modules/zod/v3/types.js
|
|
934
1070
|
var ParseInputLazyPath = class {
|
|
935
|
-
constructor(parent, value,
|
|
1071
|
+
constructor(parent, value, path5, key) {
|
|
936
1072
|
this._cachedPath = [];
|
|
937
1073
|
this.parent = parent;
|
|
938
1074
|
this.data = value;
|
|
939
|
-
this._path =
|
|
1075
|
+
this._path = path5;
|
|
940
1076
|
this._key = key;
|
|
941
1077
|
}
|
|
942
1078
|
get path() {
|
|
@@ -4766,18 +4902,18 @@ var experienceRepo = {
|
|
|
4766
4902
|
list() {
|
|
4767
4903
|
return getDb().prepare(`SELECT * FROM experiences ORDER BY order_index ASC, start_date DESC`).all().map(mapExperience);
|
|
4768
4904
|
},
|
|
4769
|
-
get(
|
|
4770
|
-
const r = getDb().prepare(`SELECT * FROM experiences WHERE id = ?`).get(
|
|
4905
|
+
get(id2) {
|
|
4906
|
+
const r = getDb().prepare(`SELECT * FROM experiences WHERE id = ?`).get(id2);
|
|
4771
4907
|
return r ? mapExperience(r) : null;
|
|
4772
4908
|
},
|
|
4773
4909
|
add(input) {
|
|
4774
4910
|
const ts = now();
|
|
4775
|
-
const
|
|
4911
|
+
const id2 = newId("exp_");
|
|
4776
4912
|
getDb().prepare(
|
|
4777
4913
|
`INSERT INTO experiences (id,company,role,employment_type,start_date,end_date,is_current,description,achievements,tech,order_index,created_at,updated_at)
|
|
4778
4914
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)`
|
|
4779
4915
|
).run(
|
|
4780
|
-
|
|
4916
|
+
id2,
|
|
4781
4917
|
input.company,
|
|
4782
4918
|
input.role ?? null,
|
|
4783
4919
|
input.employment_type ?? null,
|
|
@@ -4791,10 +4927,10 @@ var experienceRepo = {
|
|
|
4791
4927
|
ts,
|
|
4792
4928
|
ts
|
|
4793
4929
|
);
|
|
4794
|
-
return this.get(
|
|
4930
|
+
return this.get(id2);
|
|
4795
4931
|
},
|
|
4796
|
-
update(
|
|
4797
|
-
const cur = this.get(
|
|
4932
|
+
update(id2, input) {
|
|
4933
|
+
const cur = this.get(id2);
|
|
4798
4934
|
if (!cur) return null;
|
|
4799
4935
|
const m = { ...cur, ...input };
|
|
4800
4936
|
getDb().prepare(
|
|
@@ -4811,12 +4947,12 @@ var experienceRepo = {
|
|
|
4811
4947
|
toJson(m.tech),
|
|
4812
4948
|
m.order_index,
|
|
4813
4949
|
now(),
|
|
4814
|
-
|
|
4950
|
+
id2
|
|
4815
4951
|
);
|
|
4816
|
-
return this.get(
|
|
4952
|
+
return this.get(id2);
|
|
4817
4953
|
},
|
|
4818
|
-
remove(
|
|
4819
|
-
return getDb().prepare(`DELETE FROM experiences WHERE id=?`).run(
|
|
4954
|
+
remove(id2) {
|
|
4955
|
+
return getDb().prepare(`DELETE FROM experiences WHERE id=?`).run(id2).changes > 0;
|
|
4820
4956
|
}
|
|
4821
4957
|
};
|
|
4822
4958
|
function mapProject(r) {
|
|
@@ -4839,18 +4975,18 @@ var projectRepo = {
|
|
|
4839
4975
|
list() {
|
|
4840
4976
|
return getDb().prepare(`SELECT * FROM projects ORDER BY order_index ASC, start_date DESC`).all().map(mapProject);
|
|
4841
4977
|
},
|
|
4842
|
-
get(
|
|
4843
|
-
const r = getDb().prepare(`SELECT * FROM projects WHERE id = ?`).get(
|
|
4978
|
+
get(id2) {
|
|
4979
|
+
const r = getDb().prepare(`SELECT * FROM projects WHERE id = ?`).get(id2);
|
|
4844
4980
|
return r ? mapProject(r) : null;
|
|
4845
4981
|
},
|
|
4846
4982
|
add(input) {
|
|
4847
4983
|
const ts = now();
|
|
4848
|
-
const
|
|
4984
|
+
const id2 = newId("prj_");
|
|
4849
4985
|
getDb().prepare(
|
|
4850
4986
|
`INSERT INTO projects (id,name,role,description,highlights,tech,url,start_date,end_date,order_index,created_at,updated_at)
|
|
4851
4987
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)`
|
|
4852
4988
|
).run(
|
|
4853
|
-
|
|
4989
|
+
id2,
|
|
4854
4990
|
input.name,
|
|
4855
4991
|
input.role ?? null,
|
|
4856
4992
|
input.description ?? null,
|
|
@@ -4863,10 +4999,10 @@ var projectRepo = {
|
|
|
4863
4999
|
ts,
|
|
4864
5000
|
ts
|
|
4865
5001
|
);
|
|
4866
|
-
return this.get(
|
|
5002
|
+
return this.get(id2);
|
|
4867
5003
|
},
|
|
4868
|
-
update(
|
|
4869
|
-
const cur = this.get(
|
|
5004
|
+
update(id2, input) {
|
|
5005
|
+
const cur = this.get(id2);
|
|
4870
5006
|
if (!cur) return null;
|
|
4871
5007
|
const m = { ...cur, ...input };
|
|
4872
5008
|
getDb().prepare(
|
|
@@ -4882,12 +5018,12 @@ var projectRepo = {
|
|
|
4882
5018
|
m.end_date,
|
|
4883
5019
|
m.order_index,
|
|
4884
5020
|
now(),
|
|
4885
|
-
|
|
5021
|
+
id2
|
|
4886
5022
|
);
|
|
4887
|
-
return this.get(
|
|
5023
|
+
return this.get(id2);
|
|
4888
5024
|
},
|
|
4889
|
-
remove(
|
|
4890
|
-
return getDb().prepare(`DELETE FROM projects WHERE id=?`).run(
|
|
5025
|
+
remove(id2) {
|
|
5026
|
+
return getDb().prepare(`DELETE FROM projects WHERE id=?`).run(id2).changes > 0;
|
|
4891
5027
|
}
|
|
4892
5028
|
};
|
|
4893
5029
|
function mapSkill(r) {
|
|
@@ -4906,17 +5042,17 @@ var skillRepo = {
|
|
|
4906
5042
|
list() {
|
|
4907
5043
|
return getDb().prepare(`SELECT * FROM skills ORDER BY order_index ASC, name ASC`).all().map(mapSkill);
|
|
4908
5044
|
},
|
|
4909
|
-
get(
|
|
4910
|
-
const r = getDb().prepare(`SELECT * FROM skills WHERE id = ?`).get(
|
|
5045
|
+
get(id2) {
|
|
5046
|
+
const r = getDb().prepare(`SELECT * FROM skills WHERE id = ?`).get(id2);
|
|
4911
5047
|
return r ? mapSkill(r) : null;
|
|
4912
5048
|
},
|
|
4913
5049
|
add(input) {
|
|
4914
5050
|
const ts = now();
|
|
4915
|
-
const
|
|
5051
|
+
const id2 = newId("skl_");
|
|
4916
5052
|
getDb().prepare(
|
|
4917
5053
|
`INSERT INTO skills (id,name,category,level,years,order_index,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)`
|
|
4918
5054
|
).run(
|
|
4919
|
-
|
|
5055
|
+
id2,
|
|
4920
5056
|
input.name,
|
|
4921
5057
|
input.category ?? null,
|
|
4922
5058
|
input.level ?? null,
|
|
@@ -4925,17 +5061,17 @@ var skillRepo = {
|
|
|
4925
5061
|
ts,
|
|
4926
5062
|
ts
|
|
4927
5063
|
);
|
|
4928
|
-
return this.get(
|
|
5064
|
+
return this.get(id2);
|
|
4929
5065
|
},
|
|
4930
|
-
update(
|
|
4931
|
-
const cur = this.get(
|
|
5066
|
+
update(id2, input) {
|
|
5067
|
+
const cur = this.get(id2);
|
|
4932
5068
|
if (!cur) return null;
|
|
4933
5069
|
const m = { ...cur, ...input };
|
|
4934
|
-
getDb().prepare(`UPDATE skills SET name=?,category=?,level=?,years=?,order_index=?,updated_at=? WHERE id=?`).run(m.name, m.category, m.level, m.years, m.order_index, now(),
|
|
4935
|
-
return this.get(
|
|
5070
|
+
getDb().prepare(`UPDATE skills SET name=?,category=?,level=?,years=?,order_index=?,updated_at=? WHERE id=?`).run(m.name, m.category, m.level, m.years, m.order_index, now(), id2);
|
|
5071
|
+
return this.get(id2);
|
|
4936
5072
|
},
|
|
4937
|
-
remove(
|
|
4938
|
-
return getDb().prepare(`DELETE FROM skills WHERE id=?`).run(
|
|
5073
|
+
remove(id2) {
|
|
5074
|
+
return getDb().prepare(`DELETE FROM skills WHERE id=?`).run(id2).changes > 0;
|
|
4939
5075
|
}
|
|
4940
5076
|
};
|
|
4941
5077
|
function mapDocument(r) {
|
|
@@ -4957,8 +5093,8 @@ var documentRepo = {
|
|
|
4957
5093
|
const rows = kind ? db.prepare(`SELECT * FROM documents WHERE kind=? ORDER BY is_primary DESC, updated_at DESC`).all(kind) : db.prepare(`SELECT * FROM documents ORDER BY is_primary DESC, updated_at DESC`).all();
|
|
4958
5094
|
return rows.map(mapDocument);
|
|
4959
5095
|
},
|
|
4960
|
-
get(
|
|
4961
|
-
const r = getDb().prepare(`SELECT * FROM documents WHERE id=?`).get(
|
|
5096
|
+
get(id2) {
|
|
5097
|
+
const r = getDb().prepare(`SELECT * FROM documents WHERE id=?`).get(id2);
|
|
4962
5098
|
return r ? mapDocument(r) : null;
|
|
4963
5099
|
},
|
|
4964
5100
|
primary(kind) {
|
|
@@ -4967,39 +5103,43 @@ var documentRepo = {
|
|
|
4967
5103
|
},
|
|
4968
5104
|
add(input) {
|
|
4969
5105
|
const ts = now();
|
|
4970
|
-
const
|
|
5106
|
+
const id2 = newId("doc_");
|
|
4971
5107
|
const db = getDb();
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
5108
|
+
return tx(() => {
|
|
5109
|
+
if (input.is_primary) {
|
|
5110
|
+
db.prepare(`UPDATE documents SET is_primary=0 WHERE kind=?`).run(input.kind);
|
|
5111
|
+
}
|
|
5112
|
+
db.prepare(
|
|
5113
|
+
`INSERT INTO documents (id,kind,title,content,source,is_primary,tags,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?)`
|
|
5114
|
+
).run(
|
|
5115
|
+
id2,
|
|
5116
|
+
input.kind,
|
|
5117
|
+
input.title,
|
|
5118
|
+
input.content ?? "",
|
|
5119
|
+
input.source ?? "manual",
|
|
5120
|
+
toBit(input.is_primary),
|
|
5121
|
+
toJson(input.tags ?? []),
|
|
5122
|
+
ts,
|
|
5123
|
+
ts
|
|
5124
|
+
);
|
|
5125
|
+
return this.get(id2);
|
|
5126
|
+
});
|
|
4989
5127
|
},
|
|
4990
|
-
update(
|
|
4991
|
-
const cur = this.get(
|
|
5128
|
+
update(id2, input) {
|
|
5129
|
+
const cur = this.get(id2);
|
|
4992
5130
|
if (!cur) return null;
|
|
4993
5131
|
const db = getDb();
|
|
4994
5132
|
const m = { ...cur, ...input };
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5133
|
+
return tx(() => {
|
|
5134
|
+
if (input.is_primary) db.prepare(`UPDATE documents SET is_primary=0 WHERE kind=?`).run(m.kind);
|
|
5135
|
+
db.prepare(
|
|
5136
|
+
`UPDATE documents SET kind=?,title=?,content=?,source=?,is_primary=?,tags=?,updated_at=? WHERE id=?`
|
|
5137
|
+
).run(m.kind, m.title, m.content, m.source, toBit(m.is_primary), toJson(m.tags), now(), id2);
|
|
5138
|
+
return this.get(id2);
|
|
5139
|
+
});
|
|
5000
5140
|
},
|
|
5001
|
-
remove(
|
|
5002
|
-
return getDb().prepare(`DELETE FROM documents WHERE id=?`).run(
|
|
5141
|
+
remove(id2) {
|
|
5142
|
+
return getDb().prepare(`DELETE FROM documents WHERE id=?`).run(id2).changes > 0;
|
|
5003
5143
|
}
|
|
5004
5144
|
};
|
|
5005
5145
|
function mapVersion(r) {
|
|
@@ -5034,8 +5174,8 @@ var coverLetterRepo = {
|
|
|
5034
5174
|
list() {
|
|
5035
5175
|
return getDb().prepare(`SELECT * FROM cover_letters ORDER BY is_primary DESC, updated_at DESC`).all().map((r) => mapCoverLetter(r, false));
|
|
5036
5176
|
},
|
|
5037
|
-
get(
|
|
5038
|
-
const r = getDb().prepare(`SELECT * FROM cover_letters WHERE id=?`).get(
|
|
5177
|
+
get(id2, withVersions = true) {
|
|
5178
|
+
const r = getDb().prepare(`SELECT * FROM cover_letters WHERE id=?`).get(id2);
|
|
5039
5179
|
return r ? mapCoverLetter(r, withVersions) : null;
|
|
5040
5180
|
},
|
|
5041
5181
|
listByJob(jobId) {
|
|
@@ -5043,54 +5183,62 @@ var coverLetterRepo = {
|
|
|
5043
5183
|
},
|
|
5044
5184
|
create(opts) {
|
|
5045
5185
|
const ts = now();
|
|
5046
|
-
const
|
|
5186
|
+
const id2 = newId("cl_");
|
|
5047
5187
|
const db = getDb();
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5188
|
+
return tx(() => {
|
|
5189
|
+
if (opts.is_primary) db.prepare(`UPDATE cover_letters SET is_primary=0`).run();
|
|
5190
|
+
db.prepare(
|
|
5191
|
+
`INSERT INTO cover_letters (id,title,job_id,is_primary,current_version_id,created_at,updated_at) VALUES (?,?,?,?,?,?,?)`
|
|
5192
|
+
).run(id2, opts.title, opts.job_id ?? null, toBit(opts.is_primary), null, ts, ts);
|
|
5193
|
+
return this.get(id2);
|
|
5194
|
+
});
|
|
5053
5195
|
},
|
|
5054
5196
|
/** Append a new version. Creates the cover letter if cover_letter_id is omitted. */
|
|
5055
5197
|
addVersion(opts) {
|
|
5056
5198
|
const db = getDb();
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5199
|
+
return tx(() => {
|
|
5200
|
+
let clId = opts.cover_letter_id;
|
|
5201
|
+
if (!clId) {
|
|
5202
|
+
const cl = this.create({ title: opts.title ?? "\uC790\uAE30\uC18C\uAC1C\uC11C", job_id: opts.job_id ?? null });
|
|
5203
|
+
clId = cl.id;
|
|
5204
|
+
}
|
|
5205
|
+
const maxRow = db.prepare(`SELECT MAX(version_no) AS m FROM cover_letter_versions WHERE cover_letter_id=?`).get(clId);
|
|
5206
|
+
const versionNo = (maxRow.m ?? 0) + 1;
|
|
5207
|
+
const vid = newId("clv_");
|
|
5208
|
+
const ts = now();
|
|
5209
|
+
db.prepare(
|
|
5210
|
+
`INSERT INTO cover_letter_versions (id,cover_letter_id,version_no,content,note,source,created_at) VALUES (?,?,?,?,?,?,?)`
|
|
5211
|
+
).run(vid, clId, versionNo, opts.content, opts.note ?? null, opts.source ?? "ai", ts);
|
|
5212
|
+
const setCurrent = opts.set_current !== false;
|
|
5213
|
+
if (setCurrent) {
|
|
5214
|
+
db.prepare(`UPDATE cover_letters SET current_version_id=?, updated_at=? WHERE id=?`).run(vid, ts, clId);
|
|
5215
|
+
} else {
|
|
5216
|
+
db.prepare(`UPDATE cover_letters SET updated_at=? WHERE id=?`).run(ts, clId);
|
|
5217
|
+
}
|
|
5218
|
+
if (opts.title) db.prepare(`UPDATE cover_letters SET title=? WHERE id=?`).run(opts.title, clId);
|
|
5219
|
+
if (opts.job_id !== void 0)
|
|
5220
|
+
db.prepare(`UPDATE cover_letters SET job_id=? WHERE id=?`).run(opts.job_id, clId);
|
|
5221
|
+
return { coverLetter: this.get(clId), version: mapVersion(db.prepare(`SELECT * FROM cover_letter_versions WHERE id=?`).get(vid)) };
|
|
5222
|
+
});
|
|
5079
5223
|
},
|
|
5080
5224
|
setCurrentVersion(coverLetterId, versionId) {
|
|
5081
5225
|
getDb().prepare(`UPDATE cover_letters SET current_version_id=?, updated_at=? WHERE id=?`).run(versionId, now(), coverLetterId);
|
|
5082
5226
|
return this.get(coverLetterId);
|
|
5083
5227
|
},
|
|
5084
|
-
setPrimary(
|
|
5228
|
+
setPrimary(id2) {
|
|
5085
5229
|
const db = getDb();
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5230
|
+
return tx(() => {
|
|
5231
|
+
db.prepare(`UPDATE cover_letters SET is_primary=0`).run();
|
|
5232
|
+
db.prepare(`UPDATE cover_letters SET is_primary=1, updated_at=? WHERE id=?`).run(now(), id2);
|
|
5233
|
+
return this.get(id2);
|
|
5234
|
+
});
|
|
5089
5235
|
},
|
|
5090
|
-
remove(
|
|
5236
|
+
remove(id2) {
|
|
5091
5237
|
const db = getDb();
|
|
5092
|
-
|
|
5093
|
-
|
|
5238
|
+
return tx(() => {
|
|
5239
|
+
db.prepare(`DELETE FROM cover_letter_versions WHERE cover_letter_id=?`).run(id2);
|
|
5240
|
+
return db.prepare(`DELETE FROM cover_letters WHERE id=?`).run(id2).changes > 0;
|
|
5241
|
+
});
|
|
5094
5242
|
}
|
|
5095
5243
|
};
|
|
5096
5244
|
function mapJob(r) {
|
|
@@ -5114,8 +5262,8 @@ var jobRepo = {
|
|
|
5114
5262
|
list() {
|
|
5115
5263
|
return getDb().prepare(`SELECT * FROM jobs ORDER BY updated_at DESC`).all().map(mapJob);
|
|
5116
5264
|
},
|
|
5117
|
-
get(
|
|
5118
|
-
const r = getDb().prepare(`SELECT * FROM jobs WHERE id=?`).get(
|
|
5265
|
+
get(id2) {
|
|
5266
|
+
const r = getDb().prepare(`SELECT * FROM jobs WHERE id=?`).get(id2);
|
|
5119
5267
|
return r ? mapJob(r) : null;
|
|
5120
5268
|
},
|
|
5121
5269
|
findByUrl(url) {
|
|
@@ -5128,9 +5276,9 @@ var jobRepo = {
|
|
|
5128
5276
|
).all(company, `%${position}%`, excludeId ?? "");
|
|
5129
5277
|
return rows.map(mapJob);
|
|
5130
5278
|
},
|
|
5131
|
-
upsert(input,
|
|
5279
|
+
upsert(input, id2) {
|
|
5132
5280
|
const db = getDb();
|
|
5133
|
-
const existing =
|
|
5281
|
+
const existing = id2 ? this.get(id2) : input.url ? this.findByUrl(input.url) : null;
|
|
5134
5282
|
const ts = now();
|
|
5135
5283
|
if (existing) {
|
|
5136
5284
|
const m = { ...existing, ...input };
|
|
@@ -5152,7 +5300,7 @@ var jobRepo = {
|
|
|
5152
5300
|
);
|
|
5153
5301
|
return this.get(existing.id);
|
|
5154
5302
|
}
|
|
5155
|
-
const newJobId =
|
|
5303
|
+
const newJobId = id2 ?? newId("job_");
|
|
5156
5304
|
db.prepare(
|
|
5157
5305
|
`INSERT INTO jobs (id,company,position,url,location,employment_type,description,requirements,keywords,deadline,source,created_at,updated_at)
|
|
5158
5306
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)`
|
|
@@ -5173,12 +5321,14 @@ var jobRepo = {
|
|
|
5173
5321
|
);
|
|
5174
5322
|
return this.get(newJobId);
|
|
5175
5323
|
},
|
|
5176
|
-
remove(
|
|
5324
|
+
remove(id2) {
|
|
5177
5325
|
const db = getDb();
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5326
|
+
return tx(() => {
|
|
5327
|
+
db.prepare(`DELETE FROM fit_analyses WHERE job_id=?`).run(id2);
|
|
5328
|
+
db.prepare(`DELETE FROM applications WHERE job_id=?`).run(id2);
|
|
5329
|
+
db.prepare(`DELETE FROM interview_preps WHERE job_id=?`).run(id2);
|
|
5330
|
+
return db.prepare(`DELETE FROM jobs WHERE id=?`).run(id2).changes > 0;
|
|
5331
|
+
});
|
|
5182
5332
|
}
|
|
5183
5333
|
};
|
|
5184
5334
|
function mapFit(r) {
|
|
@@ -5222,12 +5372,12 @@ var fitRepo = {
|
|
|
5222
5372
|
);
|
|
5223
5373
|
return this.getByJob(input.job_id);
|
|
5224
5374
|
}
|
|
5225
|
-
const
|
|
5375
|
+
const id2 = newId("fit_");
|
|
5226
5376
|
db.prepare(
|
|
5227
5377
|
`INSERT INTO fit_analyses (id,job_id,score,summary,strengths,gaps,matched_keywords,missing_keywords,recommendations,created_at,updated_at)
|
|
5228
5378
|
VALUES (?,?,?,?,?,?,?,?,?,?,?)`
|
|
5229
5379
|
).run(
|
|
5230
|
-
|
|
5380
|
+
id2,
|
|
5231
5381
|
input.job_id,
|
|
5232
5382
|
input.score ?? null,
|
|
5233
5383
|
input.summary ?? null,
|
|
@@ -5261,8 +5411,8 @@ var applicationRepo = {
|
|
|
5261
5411
|
mapApplication
|
|
5262
5412
|
);
|
|
5263
5413
|
},
|
|
5264
|
-
get(
|
|
5265
|
-
const r = getDb().prepare(`SELECT * FROM applications WHERE id=?`).get(
|
|
5414
|
+
get(id2) {
|
|
5415
|
+
const r = getDb().prepare(`SELECT * FROM applications WHERE id=?`).get(id2);
|
|
5266
5416
|
return r ? mapApplication(r) : null;
|
|
5267
5417
|
},
|
|
5268
5418
|
getByJob(jobId) {
|
|
@@ -5277,14 +5427,15 @@ var applicationRepo = {
|
|
|
5277
5427
|
const existing = this.getByJob(jobId);
|
|
5278
5428
|
if (existing) return existing;
|
|
5279
5429
|
const ts = now();
|
|
5280
|
-
const
|
|
5430
|
+
const id2 = newId("app_");
|
|
5281
5431
|
getDb().prepare(
|
|
5282
5432
|
`INSERT INTO applications (id,job_id,status,resume_id,cover_letter_id,applied_at,notes,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?)`
|
|
5283
|
-
).run(
|
|
5284
|
-
return this.get(
|
|
5433
|
+
).run(id2, jobId, status, null, null, null, null, ts, ts);
|
|
5434
|
+
return this.get(id2);
|
|
5285
5435
|
},
|
|
5286
5436
|
upsert(input) {
|
|
5287
5437
|
const app = this.ensure(input.job_id, input.status ?? "draft");
|
|
5438
|
+
if (input.status && input.status !== app.status) assertStatusTransition(app.status, input.status);
|
|
5288
5439
|
const m = { ...app, ...input };
|
|
5289
5440
|
getDb().prepare(
|
|
5290
5441
|
`UPDATE applications SET status=?,resume_id=?,cover_letter_id=?,applied_at=?,notes=?,updated_at=? WHERE id=?`
|
|
@@ -5293,6 +5444,7 @@ var applicationRepo = {
|
|
|
5293
5444
|
},
|
|
5294
5445
|
setStatus(jobId, status, note) {
|
|
5295
5446
|
const app = this.ensure(jobId);
|
|
5447
|
+
assertStatusTransition(app.status, status);
|
|
5296
5448
|
const appliedAt = status === "applied" && !app.applied_at ? now() : app.applied_at;
|
|
5297
5449
|
const notes = note ? [app.notes, note].filter(Boolean).join("\n") : app.notes;
|
|
5298
5450
|
getDb().prepare(`UPDATE applications SET status=?, applied_at=?, notes=?, updated_at=? WHERE id=?`).run(status, appliedAt, notes, now(), app.id);
|
|
@@ -5339,11 +5491,11 @@ var interviewRepo = {
|
|
|
5339
5491
|
);
|
|
5340
5492
|
return this.getByJob(input.job_id);
|
|
5341
5493
|
}
|
|
5342
|
-
const
|
|
5494
|
+
const id2 = newId("itv_");
|
|
5343
5495
|
db.prepare(
|
|
5344
5496
|
`INSERT INTO interview_preps (id,job_id,questions,star_guides,self_introduction,notes,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)`
|
|
5345
5497
|
).run(
|
|
5346
|
-
|
|
5498
|
+
id2,
|
|
5347
5499
|
input.job_id,
|
|
5348
5500
|
toJson(input.questions ?? []),
|
|
5349
5501
|
toJson(input.star_guides ?? []),
|
|
@@ -5358,216 +5510,20 @@ var interviewRepo = {
|
|
|
5358
5510
|
var activityRepo = {
|
|
5359
5511
|
log(type, summary, entity_type, entity_id) {
|
|
5360
5512
|
const ts = now();
|
|
5361
|
-
const
|
|
5513
|
+
const id2 = newId("act_");
|
|
5362
5514
|
getDb().prepare(
|
|
5363
5515
|
`INSERT INTO activities (id,type,entity_type,entity_id,summary,created_at) VALUES (?,?,?,?,?,?)`
|
|
5364
|
-
).run(
|
|
5365
|
-
return { id:
|
|
5516
|
+
).run(id2, type, entity_type ?? null, entity_id ?? null, summary, ts);
|
|
5517
|
+
return { id: id2, type, entity_type: entity_type ?? null, entity_id: entity_id ?? null, summary, created_at: ts };
|
|
5366
5518
|
},
|
|
5367
5519
|
recent(limit = 20) {
|
|
5368
5520
|
return getDb().prepare(`SELECT * FROM activities ORDER BY created_at DESC LIMIT ?`).all(limit);
|
|
5369
5521
|
}
|
|
5370
5522
|
};
|
|
5371
5523
|
|
|
5372
|
-
//
|
|
5373
|
-
import crypto from "node:crypto";
|
|
5374
|
-
var SESSION_TOKEN = crypto.randomBytes(24).toString("hex");
|
|
5375
|
-
var TOKEN_HEADER = "x-careermate-token";
|
|
5376
|
-
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
|
|
5377
|
-
function hostOnly(value) {
|
|
5378
|
-
if (!value) return null;
|
|
5379
|
-
if (value.startsWith("[")) {
|
|
5380
|
-
const end = value.indexOf("]");
|
|
5381
|
-
return end === -1 ? value.toLowerCase() : value.slice(0, end + 1).toLowerCase();
|
|
5382
|
-
}
|
|
5383
|
-
return value.split(":")[0].toLowerCase();
|
|
5384
|
-
}
|
|
5385
|
-
function isLoopbackHost(hostHeader) {
|
|
5386
|
-
const h = hostOnly(hostHeader);
|
|
5387
|
-
return h != null && LOOPBACK_HOSTS.has(h);
|
|
5388
|
-
}
|
|
5389
|
-
function originHost(origin) {
|
|
5390
|
-
if (!origin || origin === "null") return null;
|
|
5391
|
-
try {
|
|
5392
|
-
return new URL(origin).hostname.toLowerCase();
|
|
5393
|
-
} catch {
|
|
5394
|
-
return null;
|
|
5395
|
-
}
|
|
5396
|
-
}
|
|
5397
|
-
var MUTATING = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
5398
|
-
function checkRequest(req) {
|
|
5399
|
-
if (!isLoopbackHost(req.headers.host)) {
|
|
5400
|
-
return { ok: false, status: 403, reason: "invalid_host" };
|
|
5401
|
-
}
|
|
5402
|
-
const origin = req.headers.origin;
|
|
5403
|
-
if (origin) {
|
|
5404
|
-
const oh = originHost(origin);
|
|
5405
|
-
if (oh == null || !LOOPBACK_HOSTS.has(oh)) {
|
|
5406
|
-
return { ok: false, status: 403, reason: "cross_origin" };
|
|
5407
|
-
}
|
|
5408
|
-
}
|
|
5409
|
-
if (MUTATING.has(req.method ?? "")) {
|
|
5410
|
-
const token = req.headers[TOKEN_HEADER];
|
|
5411
|
-
const provided = Array.isArray(token) ? token[0] : token;
|
|
5412
|
-
if (!provided || !timingSafeEqual(provided, SESSION_TOKEN)) {
|
|
5413
|
-
return { ok: false, status: 403, reason: "invalid_csrf_token" };
|
|
5414
|
-
}
|
|
5415
|
-
}
|
|
5416
|
-
return { ok: true };
|
|
5417
|
-
}
|
|
5418
|
-
function timingSafeEqual(a, b) {
|
|
5419
|
-
const ab = Buffer.from(a);
|
|
5420
|
-
const bb = Buffer.from(b);
|
|
5421
|
-
if (ab.length !== bb.length) return false;
|
|
5422
|
-
return crypto.timingSafeEqual(ab, bb);
|
|
5423
|
-
}
|
|
5424
|
-
function injectToken(html) {
|
|
5425
|
-
const tag = `<meta name="careermate-token" content="${SESSION_TOKEN}">`;
|
|
5426
|
-
if (html.includes("</head>")) return html.replace("</head>", ` ${tag}
|
|
5427
|
-
</head>`);
|
|
5428
|
-
return tag + html;
|
|
5429
|
-
}
|
|
5430
|
-
|
|
5431
|
-
// apps/web/src/http.ts
|
|
5524
|
+
// packages/mcp-tools/src/tools.ts
|
|
5432
5525
|
import fs3 from "node:fs";
|
|
5433
|
-
import
|
|
5434
|
-
var MAX_BODY = 8 * 1024 * 1024;
|
|
5435
|
-
var Router = class {
|
|
5436
|
-
routes = [];
|
|
5437
|
-
add(method, path6, handler) {
|
|
5438
|
-
const keys = [];
|
|
5439
|
-
const pattern = new RegExp(
|
|
5440
|
-
"^" + path6.replace(/:[^/]+/g, (m) => {
|
|
5441
|
-
keys.push(m.slice(1));
|
|
5442
|
-
return "([^/]+)";
|
|
5443
|
-
}) + "/?$"
|
|
5444
|
-
);
|
|
5445
|
-
this.routes.push({ method: method.toUpperCase(), pattern, keys, handler });
|
|
5446
|
-
return this;
|
|
5447
|
-
}
|
|
5448
|
-
get(p, h) {
|
|
5449
|
-
return this.add("GET", p, h);
|
|
5450
|
-
}
|
|
5451
|
-
post(p, h) {
|
|
5452
|
-
return this.add("POST", p, h);
|
|
5453
|
-
}
|
|
5454
|
-
put(p, h) {
|
|
5455
|
-
return this.add("PUT", p, h);
|
|
5456
|
-
}
|
|
5457
|
-
patch(p, h) {
|
|
5458
|
-
return this.add("PATCH", p, h);
|
|
5459
|
-
}
|
|
5460
|
-
delete(p, h) {
|
|
5461
|
-
return this.add("DELETE", p, h);
|
|
5462
|
-
}
|
|
5463
|
-
match(method, pathname) {
|
|
5464
|
-
for (const r of this.routes) {
|
|
5465
|
-
if (r.method !== method) continue;
|
|
5466
|
-
const m = r.pattern.exec(pathname);
|
|
5467
|
-
if (!m) continue;
|
|
5468
|
-
const params = {};
|
|
5469
|
-
r.keys.forEach((k, i) => params[k] = decodeURIComponent(m[i + 1]));
|
|
5470
|
-
return { handler: r.handler, params };
|
|
5471
|
-
}
|
|
5472
|
-
return null;
|
|
5473
|
-
}
|
|
5474
|
-
};
|
|
5475
|
-
function sendJson(res, status, data) {
|
|
5476
|
-
const body = JSON.stringify(data);
|
|
5477
|
-
res.writeHead(status, {
|
|
5478
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
5479
|
-
"Cache-Control": "no-store",
|
|
5480
|
-
"X-Content-Type-Options": "nosniff"
|
|
5481
|
-
});
|
|
5482
|
-
res.end(body);
|
|
5483
|
-
}
|
|
5484
|
-
function sendDownload(res, filename, mimeType, content) {
|
|
5485
|
-
const safe = filename.replace(/[^\w.\-가-힣 ]/g, "_");
|
|
5486
|
-
res.writeHead(200, {
|
|
5487
|
-
"Content-Type": mimeType,
|
|
5488
|
-
"Content-Disposition": `attachment; filename*=UTF-8''${encodeURIComponent(safe)}`,
|
|
5489
|
-
"Cache-Control": "no-store",
|
|
5490
|
-
"X-Content-Type-Options": "nosniff"
|
|
5491
|
-
});
|
|
5492
|
-
res.end(content);
|
|
5493
|
-
}
|
|
5494
|
-
var HttpError = class extends Error {
|
|
5495
|
-
constructor(status, message, code) {
|
|
5496
|
-
super(message);
|
|
5497
|
-
this.status = status;
|
|
5498
|
-
this.code = code;
|
|
5499
|
-
}
|
|
5500
|
-
};
|
|
5501
|
-
async function readJsonBody(req, schema) {
|
|
5502
|
-
const raw = await readBody(req);
|
|
5503
|
-
if (!raw) return schema ? schema.parse({}) : {};
|
|
5504
|
-
let parsed;
|
|
5505
|
-
try {
|
|
5506
|
-
parsed = JSON.parse(raw);
|
|
5507
|
-
} catch {
|
|
5508
|
-
throw new HttpError(400, "\uC798\uBABB\uB41C JSON \uD615\uC2DD\uC785\uB2C8\uB2E4.", "bad_json");
|
|
5509
|
-
}
|
|
5510
|
-
if (!schema) return parsed;
|
|
5511
|
-
try {
|
|
5512
|
-
return schema.parse(parsed);
|
|
5513
|
-
} catch (e) {
|
|
5514
|
-
if (e instanceof ZodError) {
|
|
5515
|
-
const msg = e.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
|
|
5516
|
-
throw new HttpError(400, `\uC785\uB825\uAC12 \uAC80\uC99D \uC2E4\uD328 \u2014 ${msg}`, "validation");
|
|
5517
|
-
}
|
|
5518
|
-
throw e;
|
|
5519
|
-
}
|
|
5520
|
-
}
|
|
5521
|
-
function readBody(req) {
|
|
5522
|
-
return new Promise((resolve, reject) => {
|
|
5523
|
-
let size = 0;
|
|
5524
|
-
const chunks = [];
|
|
5525
|
-
req.on("data", (c) => {
|
|
5526
|
-
size += c.length;
|
|
5527
|
-
if (size > MAX_BODY) {
|
|
5528
|
-
reject(new HttpError(413, "\uC694\uCCAD \uBCF8\uBB38\uC774 \uB108\uBB34 \uD07D\uB2C8\uB2E4.", "too_large"));
|
|
5529
|
-
req.destroy();
|
|
5530
|
-
return;
|
|
5531
|
-
}
|
|
5532
|
-
chunks.push(c);
|
|
5533
|
-
});
|
|
5534
|
-
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
5535
|
-
req.on("error", reject);
|
|
5536
|
-
});
|
|
5537
|
-
}
|
|
5538
|
-
var MIME = {
|
|
5539
|
-
".html": "text/html; charset=utf-8",
|
|
5540
|
-
".js": "text/javascript; charset=utf-8",
|
|
5541
|
-
".mjs": "text/javascript; charset=utf-8",
|
|
5542
|
-
".css": "text/css; charset=utf-8",
|
|
5543
|
-
".json": "application/json; charset=utf-8",
|
|
5544
|
-
".svg": "image/svg+xml",
|
|
5545
|
-
".png": "image/png",
|
|
5546
|
-
".jpg": "image/jpeg",
|
|
5547
|
-
".ico": "image/x-icon",
|
|
5548
|
-
".woff2": "font/woff2",
|
|
5549
|
-
".map": "application/json"
|
|
5550
|
-
};
|
|
5551
|
-
function serveStatic(res, root, relPath, transform) {
|
|
5552
|
-
const cleaned = decodeURIComponent(relPath).replace(/\0/g, "");
|
|
5553
|
-
const resolved = path3.resolve(root, "." + path3.posix.normalize("/" + cleaned));
|
|
5554
|
-
if (resolved !== root && !resolved.startsWith(root + path3.sep)) {
|
|
5555
|
-
res.writeHead(403).end("Forbidden");
|
|
5556
|
-
return true;
|
|
5557
|
-
}
|
|
5558
|
-
if (!fs3.existsSync(resolved) || !fs3.statSync(resolved).isFile()) return false;
|
|
5559
|
-
const ext = path3.extname(resolved).toLowerCase();
|
|
5560
|
-
const mime = MIME[ext] ?? "application/octet-stream";
|
|
5561
|
-
let payload = fs3.readFileSync(resolved);
|
|
5562
|
-
if (transform && ext === ".html") payload = transform(payload.toString("utf8"));
|
|
5563
|
-
res.writeHead(200, {
|
|
5564
|
-
"Content-Type": mime,
|
|
5565
|
-
"X-Content-Type-Options": "nosniff",
|
|
5566
|
-
"Cache-Control": ext === ".html" ? "no-store" : "no-cache"
|
|
5567
|
-
});
|
|
5568
|
-
res.end(payload);
|
|
5569
|
-
return true;
|
|
5570
|
-
}
|
|
5526
|
+
import path4 from "node:path";
|
|
5571
5527
|
|
|
5572
5528
|
// packages/core/src/onboarding.ts
|
|
5573
5529
|
function profileCompleteness(p) {
|
|
@@ -5677,8 +5633,23 @@ function addResume(input) {
|
|
|
5677
5633
|
activityRepo.log("resume_added", `${doc.title} \uBB38\uC11C\uB97C \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, "document", doc.id);
|
|
5678
5634
|
return doc;
|
|
5679
5635
|
}
|
|
5680
|
-
function
|
|
5681
|
-
const
|
|
5636
|
+
function addExperience(input) {
|
|
5637
|
+
const exp = experienceRepo.add(input);
|
|
5638
|
+
activityRepo.log("profile_updated", `\uACBD\uB825(${exp.company})\uC744 \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, "experience", exp.id);
|
|
5639
|
+
return exp;
|
|
5640
|
+
}
|
|
5641
|
+
function addProject(input) {
|
|
5642
|
+
const prj = projectRepo.add(input);
|
|
5643
|
+
activityRepo.log("profile_updated", `\uD504\uB85C\uC81D\uD2B8(${prj.name})\uB97C \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, "project", prj.id);
|
|
5644
|
+
return prj;
|
|
5645
|
+
}
|
|
5646
|
+
function addSkill(input) {
|
|
5647
|
+
const skill = skillRepo.add(input);
|
|
5648
|
+
activityRepo.log("profile_updated", `\uAE30\uC220(${skill.name})\uC744 \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, "skill", skill.id);
|
|
5649
|
+
return skill;
|
|
5650
|
+
}
|
|
5651
|
+
function saveJobPosting(input, id2) {
|
|
5652
|
+
const job = jobRepo.upsert(input, id2);
|
|
5682
5653
|
const application = applicationRepo.ensure(job.id, "draft");
|
|
5683
5654
|
activityRepo.log("job_saved", `${job.company} \xB7 ${job.position} \uACF5\uACE0\uB97C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4.`, "job", job.id);
|
|
5684
5655
|
return { job, application };
|
|
@@ -5734,141 +5705,29 @@ function updateApplicationStatus(jobId, status, note) {
|
|
|
5734
5705
|
function saveInterviewPrep(input) {
|
|
5735
5706
|
const job = jobRepo.get(input.job_id);
|
|
5736
5707
|
if (!job) throw new Error(`\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.job_id}`);
|
|
5737
|
-
applicationRepo.
|
|
5708
|
+
const app = applicationRepo.getByJob(job.id);
|
|
5709
|
+
if (!app || !INTERVIEW_UNLOCK_STATUSES.includes(app.status)) {
|
|
5710
|
+
const current = app ? APPLICATION_STATUS_LABELS[app.status] : "\uBBF8\uC9C0\uC6D0";
|
|
5711
|
+
throw new Error(
|
|
5712
|
+
`\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.`
|
|
5713
|
+
);
|
|
5714
|
+
}
|
|
5738
5715
|
const prep = interviewRepo.save(input);
|
|
5739
5716
|
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);
|
|
5740
5717
|
return prep;
|
|
5741
5718
|
}
|
|
5742
5719
|
|
|
5743
5720
|
// packages/core/src/summary.ts
|
|
5744
|
-
var ACTIVE = ["draft", "planned", "applied", "document_passed", "interview"];
|
|
5745
5721
|
function jobWithMeta(job) {
|
|
5746
5722
|
const app = applicationRepo.getByJob(job.id);
|
|
5747
5723
|
const fit = fitRepo.getByJob(job.id);
|
|
5748
5724
|
const status = app?.status ?? "draft";
|
|
5749
5725
|
return { ...job, status, status_label: APPLICATION_STATUS_LABELS[status], fit_score: fit?.score ?? null };
|
|
5750
5726
|
}
|
|
5751
|
-
function getHomeSummary() {
|
|
5752
|
-
const onboarding = getOnboardingStatus();
|
|
5753
|
-
const jobs = jobRepo.list();
|
|
5754
|
-
const applications = applicationRepo.list();
|
|
5755
|
-
const coverLetters = coverLetterRepo.list();
|
|
5756
|
-
const status_breakdown = Object.keys(APPLICATION_STATUS_LABELS).map((status) => ({
|
|
5757
|
-
status,
|
|
5758
|
-
label: APPLICATION_STATUS_LABELS[status],
|
|
5759
|
-
count: applications.filter((a) => a.status === status).length
|
|
5760
|
-
}));
|
|
5761
|
-
const active = applications.filter((a) => ACTIVE.includes(a.status));
|
|
5762
|
-
const interview_todo = applications.filter((a) => INTERVIEW_UNLOCK_STATUSES.includes(a.status) && !interviewRepo.getByJob(a.job_id)).map((a) => {
|
|
5763
|
-
const job = jobRepo.get(a.job_id);
|
|
5764
|
-
return job ? { job, status_label: APPLICATION_STATUS_LABELS[a.status] } : null;
|
|
5765
|
-
}).filter((x) => x !== null);
|
|
5766
|
-
return {
|
|
5767
|
-
onboarding,
|
|
5768
|
-
counts: {
|
|
5769
|
-
jobs: jobs.length,
|
|
5770
|
-
active_applications: active.length,
|
|
5771
|
-
cover_letters: coverLetters.length,
|
|
5772
|
-
interview_pending: interview_todo.length
|
|
5773
|
-
},
|
|
5774
|
-
status_breakdown,
|
|
5775
|
-
recent_jobs: jobs.slice(0, 6).map(jobWithMeta),
|
|
5776
|
-
in_progress: active.slice(0, 6).map((application) => {
|
|
5777
|
-
const job = jobRepo.get(application.job_id);
|
|
5778
|
-
return {
|
|
5779
|
-
application,
|
|
5780
|
-
job,
|
|
5781
|
-
status_label: APPLICATION_STATUS_LABELS[application.status]
|
|
5782
|
-
};
|
|
5783
|
-
}),
|
|
5784
|
-
recent_cover_letters: coverLetters.slice(0, 5),
|
|
5785
|
-
interview_todo,
|
|
5786
|
-
recent_activity: activityRepo.recent(10)
|
|
5787
|
-
};
|
|
5788
|
-
}
|
|
5789
5727
|
function listRecentActivity(limit = 20) {
|
|
5790
5728
|
return activityRepo.recent(limit);
|
|
5791
5729
|
}
|
|
5792
5730
|
|
|
5793
|
-
// packages/prompts/src/system.ts
|
|
5794
|
-
var CAREERMATE_SYSTEM_PROMPT = `\uB2F9\uC2E0\uC740 \uC0AC\uC6A9\uC790\uC758 'CareerMate' \uCEE4\uB9AC\uC5B4 \uBE44\uC11C\uC785\uB2C8\uB2E4.
|
|
5795
|
-
|
|
5796
|
-
CareerMate\uB294 \uC0AC\uC6A9\uC790\uC758 \uCEF4\uD4E8\uD130\uC5D0\uC11C \uB3D9\uC791\uD558\uB294 \uB85C\uCEEC \uCEE4\uB9AC\uC5B4 \uAD00\uB9AC \uB3C4\uAD6C\uC774\uBA70, MCP(\uD234 \uD638\uCD9C)\uB97C \uD1B5\uD574 \uC0AC\uC6A9\uC790\uC758 \uD504\uB85C\uD544\xB7\uC774\uB825\uC11C\xB7\uC790\uAE30\uC18C\uAC1C\uC11C\xB7\uC9C0\uC6D0 \uACF5\uACE0\xB7\uC9C0\uC6D0 \uD604\uD669 \uB370\uC774\uD130\uB97C \uC77D\uACE0 \uC501\uB2C8\uB2E4. CareerMate \uC548\uC5D0\uB294 \uBCC4\uB3C4\uC758 AI\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uBAA8\uB4E0 \uBD84\uC11D\xB7\uC791\uC131\xB7\uC694\uC57D\uC740 '\uB2F9\uC2E0'(\uC0AC\uC6A9\uC790\uC758 AI)\uC774 MCP \uD234\uC744 \uD638\uCD9C\uD574 \uC218\uD589\uD569\uB2C8\uB2E4.
|
|
5797
|
-
|
|
5798
|
-
# \uD575\uC2EC \uD589\uB3D9 \uC6D0\uCE59
|
|
5799
|
-
|
|
5800
|
-
1. \uD56D\uC0C1 \uB9E5\uB77D\uC744 \uBA3C\uC800 \uBD88\uB7EC\uC635\uB2C8\uB2E4.
|
|
5801
|
-
- \uACF5\uACE0\uB97C \uBD84\uC11D\uD558\uAC70\uB098 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC4F0\uAE30 \uC804\uC5D0\uB294 \uBC18\uB4DC\uC2DC \`get_application_context\`\uB97C \uBA3C\uC800 \uD638\uCD9C\uD558\uC138\uC694. \uC774 \uD55C \uBC88\uC758 \uD638\uCD9C\uB85C \uD504\uB85C\uD544, \uB300\uD45C \uC774\uB825\uC11C, \uACBD\uB825(experiences), \uD504\uB85C\uC81D\uD2B8(projects), \uC2A4\uD0AC(skills), \uAE30\uC874 \uC790\uAE30\uC18C\uAC1C\uC11C, \uCD5C\uADFC \uC9C0\uC6D0 \uD604\uD669, \uB300\uC0C1 \uACF5\uACE0(job_id \uC804\uB2EC \uC2DC), \uC774\uC804 \uC801\uD569\uB3C4 \uBD84\uC11D, \uAC19\uC740 \uD68C\uC0AC/\uC9C1\uBB34 \uAD00\uB828 \uAE30\uB85D, \uADF8\uB9AC\uACE0 \uC0AC\uC6A9\uC790\uC758 \uAE00\uC4F0\uAE30 \uC120\uD638(\uC120\uD638 \uBB38\uCCB4 preferred_tone + \uAC15\uC870 \uD3EC\uC778\uD2B8 emphasis_points)\uB97C \uD55C\uAEBC\uBC88\uC5D0 \uBC1B\uC2B5\uB2C8\uB2E4.
|
|
5802
|
-
- \uC77C\uBC18\uC801\uC778 \uC0C1\uD0DC \uD655\uC778\uC774 \uD544\uC694\uD558\uBA74 \`get_onboarding_status\`, \`get_profile\`, \`get_resumes\`, \`get_cover_letters\`, \`list_recent_activity\` \uB4F1 \uC77D\uAE30 \uD234\uC744 \uC801\uADF9 \uC0AC\uC6A9\uD558\uC138\uC694.
|
|
5803
|
-
- \uAE30\uC5B5\uC5D0 \uC758\uC874\uD558\uC9C0 \uB9D0\uACE0, \uB9E4 \uC791\uC5C5 \uC2DC\uC791 \uC2DC \uC2E4\uC81C \uB370\uC774\uD130\uB97C \uB2E4\uC2DC \uBD88\uB7EC\uC624\uC138\uC694.
|
|
5804
|
-
|
|
5805
|
-
2. \uACB0\uACFC\uB294 \uBC18\uB4DC\uC2DC \uB2E4\uC2DC \uC800\uC7A5\uD569\uB2C8\uB2E4.
|
|
5806
|
-
- \uBD84\uC11D\xB7\uC791\uC131\uD55C \uB0B4\uC6A9\uC740 \uBA38\uB9BF\uC18D\uC774\uB098 \uB300\uD654\uCC3D\uC5D0\uB9CC \uB0A8\uAE30\uC9C0 \uB9D0\uACE0 \uD574\uB2F9 \uC800\uC7A5 \uD234\uB85C CareerMate\uC5D0 \uAE30\uB85D\uD558\uC138\uC694: \uACF5\uACE0\uB294 \`save_job_posting\`, \uC801\uD569\uB3C4 \uBD84\uC11D\uC740 \`save_fit_analysis\`, \uC790\uAE30\uC18C\uAC1C\uC11C \uBC84\uC804\uC740 \`save_cover_letter_version\`, \uBA74\uC811 \uC900\uBE44\uB294 \`save_interview_prep\`, \uC0C1\uD0DC \uBCC0\uACBD\uC740 \`update_application_status\`.
|
|
5807
|
-
- \uC800\uC7A5 \uD6C4\uC5D0\uB294 \uBB34\uC5C7\uC774 \uC800\uC7A5\uB418\uC5C8\uB294\uC9C0 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uAC04\uB2E8\uD788 \uC54C\uB824\uC8FC\uC138\uC694.
|
|
5808
|
-
|
|
5809
|
-
3. \uB370\uC774\uD130\uB97C \uC9C0\uC5B4\uB0B4\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.
|
|
5810
|
-
- \uD504\uB85C\uD544\xB7\uACBD\uB825\xB7\uC131\uACFC\xB7\uC218\uCE58\xB7\uD68C\uC0AC\uBA85 \uB4F1\uC740 \uC0AC\uC6A9\uC790\uAC00 \uC81C\uACF5\uD588\uAC70\uB098 CareerMate\uC5D0 \uC800\uC7A5\uB41C \uAC83\uB9CC \uC0AC\uC6A9\uD558\uC138\uC694. \uBD88\uD655\uC2E4\uD558\uBA74 \uCD94\uCE21\uD558\uC9C0 \uB9D0\uACE0 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBB3C\uC5B4\uBCF4\uC138\uC694.
|
|
5811
|
-
- \uC790\uAE30\uC18C\uAC1C\uC11C\uC5D0 \uAC80\uC99D\uB418\uC9C0 \uC54A\uC740 \uACBD\uD5D8\uC774\uB098 \uACFC\uC7A5\uB41C \uC131\uACFC\uB97C \uB123\uC9C0 \uB9C8\uC138\uC694. \uBE48\uCE78\uC774 \uC788\uC73C\uBA74 "\uC774 \uBD80\uBD84\uC740 \uD655\uC778\uC774 \uD544\uC694\uD569\uB2C8\uB2E4"\uB77C\uACE0 \uD45C\uC2DC\uD558\uACE0 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uD655\uC778\uC744 \uC694\uCCAD\uD558\uC138\uC694.
|
|
5812
|
-
|
|
5813
|
-
4. \uB85C\uCEEC-\uD37C\uC2A4\uD2B8 / \uD504\uB77C\uC774\uBC84\uC2DC\uB97C \uC874\uC911\uD569\uB2C8\uB2E4.
|
|
5814
|
-
- \uBAA8\uB4E0 \uB370\uC774\uD130\uB294 \uC0AC\uC6A9\uC790\uC758 \uCEF4\uD4E8\uD130\uC5D0\uB9CC \uC800\uC7A5\uB429\uB2C8\uB2E4. \uB370\uC774\uD130\uB97C \uC678\uBD80\uB85C \uBCF4\uB0B4\uAC70\uB098 \uB2E4\uB978 \uC6A9\uB3C4\uB85C \uC4F0\uC9C0 \uB9C8\uC138\uC694.
|
|
5815
|
-
- \uBBFC\uAC10 \uC815\uBCF4(\uC5F0\uB77D\uCC98, \uC8FC\uC18C \uB4F1)\uB294 \uAF2D \uD544\uC694\uD55C \uC791\uC5C5\uC5D0\uB9CC \uC0AC\uC6A9\uD558\uACE0 \uBD88\uD544\uC694\uD558\uAC8C \uB178\uCD9C\uD558\uC9C0 \uB9C8\uC138\uC694.
|
|
5816
|
-
|
|
5817
|
-
5. \uC790\uAE30\uC18C\uAC1C\uC11C\uB294 \uC791\uC131 \uC804 \uD655\uC778\uC744 \uBC1B\uC2B5\uB2C8\uB2E4.
|
|
5818
|
-
- \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC0C8\uB85C \uC4F0\uAC70\uB098 \uD06C\uAC8C \uACE0\uCE58\uAE30 \uC804\uC5D0, \uC5B4\uB5A4 \uACF5\uACE0\uB97C \uB300\uC0C1\uC73C\uB85C \uC5B4\uB5A4 \uD1A4\uACFC \uAC15\uC870\uC810\uC73C\uB85C \uC4F8\uC9C0 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBA3C\uC800 \uD655\uC778\uD558\uC138\uC694.
|
|
5819
|
-
- \uC791\uC131 \uD6C4\uC5D0\uB294 \`save_cover_letter_version\`\uC73C\uB85C \uBC84\uC804\uC744 \uC800\uC7A5\uD558\uACE0, \uBCC0\uACBD \uC694\uC57D\uC744 \`note\`\uC5D0 \uB0A8\uAE30\uC138\uC694. \uC0AC\uC6A9\uC790\uAC00 \uC6D0\uD558\uBA74 \`export_cover_letter\`\uB85C \uB0B4\uBCF4\uB0B4\uC138\uC694.
|
|
5820
|
-
|
|
5821
|
-
6. \uC0AC\uC6A9\uC790\uC5D0\uAC8C\uB294 \uC26C\uC6B4 \uD55C\uAD6D\uC5B4\uB85C \uC54C\uB9BD\uB2C8\uB2E4.
|
|
5822
|
-
- "MCP", "\uD234 \uD638\uCD9C", "job_id" \uAC19\uC740 \uAE30\uC220 \uC6A9\uC5B4\uB294 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC9C1\uC811 \uB178\uCD9C\uD558\uC9C0 \uB9C8\uC138\uC694. "\uACF5\uACE0\uB97C \uC800\uC7A5\uD588\uC5B4\uC694", "\uC801\uD569\uB3C4\uB97C \uBD84\uC11D\uD588\uC5B4\uC694"\uCC98\uB7FC \uC77C\uC0C1\uC801\uC778 \uD45C\uD604\uC73C\uB85C \uC9C4\uD589 \uC0C1\uD669\uC744 \uC54C\uB9AC\uC138\uC694.
|
|
5823
|
-
- \uBB34\uC5C7\uC744 \uD588\uACE0, \uBB34\uC5C7\uC744 \uC54C\uAC8C \uB418\uC5C8\uACE0, \uB2E4\uC74C\uC5D0 \uBB34\uC5C7\uC744 \uD560 \uC218 \uC788\uB294\uC9C0 \uC9E7\uACE0 \uBA85\uD655\uD558\uAC8C \uC804\uB2EC\uD558\uC138\uC694.
|
|
5824
|
-
|
|
5825
|
-
7. \uB2E4\uC74C \uB2E8\uACC4\uB97C \uBA3C\uC800 \uC81C\uC548\uD569\uB2C8\uB2E4.
|
|
5826
|
-
- \uC791\uC5C5\uC774 \uB05D\uB098\uBA74 \uC790\uC5F0\uC2A4\uB7EC\uC6B4 \uB2E4\uC74C \uD589\uB3D9\uC744 \uC81C\uC548\uD558\uC138\uC694. \uC608: \uACF5\uACE0 \uBD84\uC11D \uD6C4 \u2192 "\uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uB4DC\uB9B4\uAE4C\uC694?", \uC790\uAE30\uC18C\uAC1C\uC11C \uC800\uC7A5 \uD6C4 \u2192 "\uC9C0\uC6D0 \uC0C1\uD0DC\uB97C '\uC9C0\uC6D0 \uC644\uB8CC'\uB85C \uBC14\uAFC0\uAE4C\uC694?", \uC0C1\uD0DC\uAC00 '\uC11C\uB958 \uD569\uACA9'\uC774 \uB418\uBA74 \u2192 "\uBA74\uC811 \uC900\uBE44\uB97C \uB3C4\uC640\uB4DC\uB9B4\uAE4C\uC694?".
|
|
5827
|
-
- \`get_onboarding_status\`\uC758 next_steps\uB098 \`open_dashboard\`/\`open_application\`\uC744 \uD65C\uC6A9\uD574 \uC0AC\uC6A9\uC790\uB97C \uB2E4\uC74C \uD589\uB3D9\uC73C\uB85C \uC548\uB0B4\uD558\uC138\uC694.
|
|
5828
|
-
|
|
5829
|
-
8. \uAE00\uC740 AI \uD2F0 \uC548 \uB098\uAC8C \uC501\uB2C8\uB2E4.
|
|
5830
|
-
- \uC790\uAE30\uC18C\uAC1C\uC11C\xB7\uC790\uAE30 PR\xB7\uC9C0\uC6D0 \uBA54\uC77C \uB4F1 \uC0AC\uB78C\uC774 \uC4F4 \uB4EF\uD55C \uAE00\uC744 \uC791\uC131\uD558\uAE30 \uC9C1\uC804\uC5D0\uB294 \`get_writing_style_guide\`\uB97C \uD638\uCD9C\uD574 \uAE00\uC4F0\uAE30 \uADDC\uCE59\uC744 \uAC00\uC838\uC624\uACE0 \uADF8\uB300\uB85C \uC801\uC6A9\uD558\uC138\uC694.
|
|
5831
|
-
- \uBC88\uC5ED\uD22C, "\uC5F4\uC815\uC744 \uAC00\uC9C0\uACE0/\uADC0\uC0AC\uC758 \uBC1C\uC804\uC5D0 \uAE30\uC5EC" \uAC19\uC740 \uD074\uB9AC\uC170, \uAE30\uACC4\uC801 \uBCD1\uB82C(\uCCAB\uC9F8\xB7\uB458\uC9F8\xB7\uC14B\uC9F8), \uC0C1\uD22C\uC801 \uC5F0\uACB0\uC5B4, \uADE0\uC77C\uD55C \uBB38\uC7A5 \uB9AC\uB4EC\uC744 \uD53C\uD558\uACE0 \uB2F4\uBC31\uD558\uACE0 \uAD6C\uCCB4\uC801\uC73C\uB85C \uC501\uB2C8\uB2E4. \uB2E8, \uC0AC\uC2E4\xB7\uC218\uCE58\xB7\uACE0\uC720\uBA85\uC0AC\uB294 \uC808\uB300 \uBC14\uAFB8\uAC70\uB098 \uC9C0\uC5B4\uB0B4\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.
|
|
5832
|
-
|
|
5833
|
-
# \uC791\uC5C5 \uBC29\uC2DD \uC694\uC57D
|
|
5834
|
-
- \uC77D\uAE30(get_*) \u2192 \uBD84\uC11D/\uC791\uC131 \u2192 \uC800\uC7A5(save_*/update_*) \u2192 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC26C\uC6B4 \uB9D0\uB85C \uBCF4\uACE0 \u2192 \uB2E4\uC74C \uB2E8\uACC4 \uC81C\uC548.
|
|
5835
|
-
- 8\uAC00\uC9C0 \uC9C0\uC6D0 \uC0C1\uD0DC: \uC791\uC131 \uC911(draft), \uC9C0\uC6D0 \uC608\uC815(planned), \uC9C0\uC6D0 \uC644\uB8CC(applied), \uC11C\uB958 \uD569\uACA9(document_passed), \uBA74\uC811 \uC9C4\uD589(interview), \uCD5C\uC885 \uD569\uACA9(final_passed), \uBD88\uD569\uACA9(rejected), \uBCF4\uB958(on_hold).
|
|
5836
|
-
- \uC758\uC2EC\uC2A4\uB7EC\uC6B0\uBA74 \uBA48\uCD94\uACE0 \uBB3B\uC2B5\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uC758 \uCEE4\uB9AC\uC5B4\uAC00 \uAC78\uB9B0 \uC77C\uC774\uBBC0\uB85C \uC815\uD655\uD568\uC774 \uC18D\uB3C4\uBCF4\uB2E4 \uC911\uC694\uD569\uB2C8\uB2E4.`;
|
|
5837
|
-
|
|
5838
|
-
// packages/prompts/src/install.ts
|
|
5839
|
-
var INSTALL_PROMPT_CLAUDE = `Claude\uC57C, \uC544\uB798 CareerMate \uC124\uCE58 \uD398\uC774\uC9C0(\uB610\uB294 INSTALL.md)\uB97C \uC77D\uACE0 \uB0B4 \uCEF4\uD4E8\uD130\uC5D0 CareerMate\uB97C \uC124\uCE58\uD558\uACE0 \uC124\uC815\uD574\uC918.
|
|
5840
|
-
|
|
5841
|
-
\uC21C\uC11C\uB294 \uC774\uB807\uAC8C \uC9C4\uD589\uD574\uC918.
|
|
5842
|
-
1. \uC124\uCE58 \uC548\uB0B4 \uD398\uC774\uC9C0 / INSTALL.md \uB0B4\uC6A9\uC744 \uB05D\uAE4C\uC9C0 \uC77D\uACE0 \uADF8\uB300\uB85C \uB530\uB77C \uD574.
|
|
5843
|
-
2. CareerMate MCP \uC11C\uBC84\uB97C \uB0B4 Claude\uC5D0 \uC5F0\uACB0(\uC124\uC815)\uD574\uC918.
|
|
5844
|
-
3. \uC5F0\uACB0\uC774 \uB05D\uB098\uBA74 \`get_onboarding_status\`\uB97C \uD638\uCD9C\uD574\uC11C \uC815\uB9D0\uB85C \uC5F0\uACB0\uB418\uC5C8\uB294\uC9C0 \uD655\uC778\uD558\uACE0, \uACB0\uACFC\uB97C \uC26C\uC6B4 \uB9D0\uB85C \uC54C\uB824\uC918.
|
|
5845
|
-
4. \uADF8\uB2E4\uC74C \uB098\uC5D0\uAC8C \uC774\uB825\uC11C\uB098 \uC790\uAE30\uC18C\uAC1C\uC11C \uD30C\uC77C\uC744 \uC5C5\uB85C\uB4DC\uD558\uB77C\uACE0 \uC548\uB0B4\uD574\uC918.
|
|
5846
|
-
5. \uB0B4\uAC00 \uC62C\uB9B0 \uD30C\uC77C\uC744 \uBC14\uD0D5\uC73C\uB85C \uB0B4 \uD504\uB85C\uD544\uC744 \uAD6C\uC870\uD654\uD574\uC11C \`save_profile\`\uB85C CareerMate\uC5D0 \uC800\uC7A5\uD558\uACE0, \uC628\uBCF4\uB529\uC744 \uC2DC\uC791\uD574\uC918.
|
|
5847
|
-
|
|
5848
|
-
\uAE30\uC220 \uC6A9\uC5B4\uB294 \uCD5C\uB300\uD55C \uBE7C\uACE0, \uB9E4 \uB2E8\uACC4\uB9C8\uB2E4 \uC9C0\uAE08 \uBB58 \uD558\uACE0 \uC788\uB294\uC9C0 \uC26C\uC6B4 \uD55C\uAD6D\uC5B4\uB85C \uC54C\uB824\uC918. \uB9C9\uD788\uB294 \uBD80\uBD84\uC774 \uC788\uC73C\uBA74 \uCD94\uCE21\uD558\uC9C0 \uB9D0\uACE0 \uB098\uC5D0\uAC8C \uBB3C\uC5B4\uBD10\uC918.`;
|
|
5849
|
-
var INSTALL_PROMPT_CHATGPT = `ChatGPT\uC57C, \uC544\uB798 \uD398\uC774\uC9C0\uB97C \uC77D\uACE0 \uB0B4 \uCEF4\uD4E8\uD130\uC5D0 CareerMate\uB97C \uC124\uCE58\uD558\uACE0 \uC124\uC815\uD574\uC918. \uC124\uCE58\uAC00 \uB05D\uB098\uBA74 \uC5F0\uACB0 \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uACE0, \uB098\uC5D0\uAC8C \uC774\uB825\uC11C\uB098 \uC790\uAE30\uC18C\uAC1C\uC11C \uD30C\uC77C\uC744 \uC5C5\uB85C\uB4DC\uD558\uB77C\uACE0 \uC548\uB0B4\uD574\uC918. \uADF8\uB2E4\uC74C \uB0B4 \uD504\uB85C\uD544\uC744 \uAD6C\uC870\uD654\uD574\uC11C CareerMate\uC5D0 \uC800\uC7A5\uD574\uC918.
|
|
5850
|
-
|
|
5851
|
-
\uAD6C\uCCB4\uC801\uC73C\uB85C\uB294 \uC774\uB807\uAC8C \uD574\uC918.
|
|
5852
|
-
1. CareerMate \uC124\uCE58 \uC548\uB0B4 \uD398\uC774\uC9C0 / INSTALL.md\uB97C \uC77D\uACE0 \uADF8\uB300\uB85C \uB530\uB77C \uC124\uCE58\uD574.
|
|
5853
|
-
2. CareerMate MCP \uCEE4\uB125\uD130\uB97C \uC5F0\uACB0(\uC124\uC815)\uD574\uC918.
|
|
5854
|
-
3. \uC5F0\uACB0 \uD6C4 \`get_onboarding_status\`\uB97C \uD638\uCD9C\uD574 \uC5F0\uACB0\uC774 \uC815\uC0C1\uC778\uC9C0 \uD655\uC778\uD558\uACE0 \uACB0\uACFC\uB97C \uC26C\uC6B4 \uB9D0\uB85C \uC54C\uB824\uC918.
|
|
5855
|
-
4. \uC774\uB825\uC11C\xB7\uC790\uAE30\uC18C\uAC1C\uC11C \uD30C\uC77C\uC744 \uC5C5\uB85C\uB4DC\uD558\uB77C\uACE0 \uB098\uC5D0\uAC8C \uC548\uB0B4\uD574\uC918.
|
|
5856
|
-
5. \uC62C\uB9B0 \uD30C\uC77C\uB85C \uB0B4 \uD504\uB85C\uD544\uC744 \uC815\uB9AC\uD574\uC11C \`save_profile\`\uB85C \uC800\uC7A5\uD558\uACE0 \uC628\uBCF4\uB529\uC744 \uC2DC\uC791\uD574\uC918.
|
|
5857
|
-
|
|
5858
|
-
\uC9C4\uD589 \uC0C1\uD669\uC740 \uD56D\uC0C1 \uC26C\uC6B4 \uD55C\uAD6D\uC5B4\uB85C \uC124\uBA85\uD558\uACE0, \uD655\uC2E4\uD558\uC9C0 \uC54A\uC740 \uC815\uBCF4\uB294 \uC9C0\uC5B4\uB0B4\uC9C0 \uB9D0\uACE0 \uB098\uC5D0\uAC8C \uBB3C\uC5B4\uBD10\uC918.`;
|
|
5859
|
-
var INSTALL_PROMPT_GENERIC = `\uC548\uB155, \uC544\uB798 CareerMate \uC124\uCE58 \uC548\uB0B4(\uC124\uCE58 \uD398\uC774\uC9C0 \uB610\uB294 INSTALL.md)\uB97C \uC77D\uACE0 \uB0B4 \uCEF4\uD4E8\uD130\uC5D0 CareerMate\uB97C \uC124\uCE58\uD558\uACE0 \uC124\uC815\uD574\uC918.
|
|
5860
|
-
|
|
5861
|
-
CareerMate\uB294 \uB0B4 \uCEE4\uB9AC\uC5B4 \uB370\uC774\uD130(\uD504\uB85C\uD544\xB7\uC774\uB825\uC11C\xB7\uC790\uAE30\uC18C\uAC1C\uC11C\xB7\uC9C0\uC6D0 \uD604\uD669)\uB97C \uB0B4 \uCEF4\uD4E8\uD130\uC5D0 \uC800\uC7A5\uD558\uACE0, MCP\uB97C \uD1B5\uD574 \uB108(\uB0B4 AI)\uAC00 \uADF8 \uB370\uC774\uD130\uB97C \uC77D\uACE0 \uC4F8 \uC218 \uC788\uAC8C \uD574\uC8FC\uB294 \uB3C4\uAD6C\uC57C. \uBD84\uC11D\uACFC \uC791\uC131\uC740 \uC804\uBD80 \uB124\uAC00 \uD574.
|
|
5862
|
-
|
|
5863
|
-
\uC774\uB807\uAC8C \uC9C4\uD589\uD574\uC918.
|
|
5864
|
-
1. \uC124\uCE58 \uC548\uB0B4 \uBB38\uC11C\uB97C \uB05D\uAE4C\uC9C0 \uC77D\uACE0 \uADF8\uB300\uB85C \uB530\uB77C \uC124\uCE58\uD574.
|
|
5865
|
-
2. \uB124\uAC00 \uC4F0\uB294 AI \uB3C4\uAD6C\uC5D0 CareerMate MCP \uC11C\uBC84\uB97C \uC5F0\uACB0(\uC124\uC815)\uD574\uC918.
|
|
5866
|
-
3. \uC5F0\uACB0 \uD6C4 \`get_onboarding_status\`\uB97C \uD638\uCD9C\uD574 \uC5F0\uACB0 \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uACE0, \uACB0\uACFC\uB97C \uC26C\uC6B4 \uD55C\uAD6D\uC5B4\uB85C \uC54C\uB824\uC918.
|
|
5867
|
-
4. \uB098\uC5D0\uAC8C \uC774\uB825\uC11C\uB098 \uC790\uAE30\uC18C\uAC1C\uC11C \uD30C\uC77C\uC744 \uC5C5\uB85C\uB4DC\uD558\uB77C\uACE0 \uC548\uB0B4\uD574\uC918.
|
|
5868
|
-
5. \uC62C\uB9B0 \uD30C\uC77C\uB85C \uB0B4 \uD504\uB85C\uD544\uC744 \uAD6C\uC870\uD654\uD574\uC11C \`save_profile\`\uB85C \uC800\uC7A5\uD558\uACE0 \uC628\uBCF4\uB529\uC744 \uC2DC\uC791\uD574\uC918.
|
|
5869
|
-
|
|
5870
|
-
\uB9E4 \uB2E8\uACC4\uB9C8\uB2E4 \uC26C\uC6B4 \uD55C\uAD6D\uC5B4\uB85C \uC124\uBA85\uD558\uACE0, \uBD88\uD655\uC2E4\uD55C \uB0B4\uC6A9\uC740 \uC9C0\uC5B4\uB0B4\uC9C0 \uB9D0\uACE0 \uB098\uC5D0\uAC8C \uD655\uC778\uD574\uC918.`;
|
|
5871
|
-
|
|
5872
5731
|
// packages/prompts/src/onboarding.ts
|
|
5873
5732
|
var ONBOARDING_PROMPT = `\uC774\uC81C CareerMate\uC5D0 \uC5F0\uACB0\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uC758 \uCEE4\uB9AC\uC5B4 \uB370\uC774\uD130 \uCCAB \uC14B\uC5C5(\uC628\uBCF4\uB529)\uC744 \uB3C4\uC640\uC8FC\uC138\uC694.
|
|
5874
5733
|
|
|
@@ -5946,46 +5805,6 @@ var HUMANIZE_WRITING_GUIDE = `# \uC0AC\uB78C\uC774 \uC4F4 \uAC83\uCC98\uB7FC: \u
|
|
|
5946
5805
|
- \uC0AC\uC2E4\xB7\uC218\uCE58\xB7\uACE0\uC720\uBA85\uC0AC\uAC00 \uC6D0\uBCF8\uACFC 100% \uC77C\uCE58\uD558\uB294\uAC00?
|
|
5947
5806
|
- \uC0AC\uC6A9\uC790\uC758 \uD1A4\uACFC \uAC15\uC870 \uD3EC\uC778\uD2B8\uAC00 \uC0B4\uC544 \uC788\uB294\uAC00?`;
|
|
5948
5807
|
|
|
5949
|
-
// packages/prompts/src/index.ts
|
|
5950
|
-
var PROMPTS = [
|
|
5951
|
-
{
|
|
5952
|
-
id: "system",
|
|
5953
|
-
title: "CareerMate \uC2DC\uC2A4\uD15C \uD504\uB86C\uD504\uD2B8 (AI \uB3D9\uC791 \uBC29\uC2DD)",
|
|
5954
|
-
audience: "system",
|
|
5955
|
-
body: CAREERMATE_SYSTEM_PROMPT
|
|
5956
|
-
},
|
|
5957
|
-
{
|
|
5958
|
-
id: "install_claude",
|
|
5959
|
-
title: "Claude \uC124\uCE58 \uD504\uB86C\uD504\uD2B8",
|
|
5960
|
-
audience: "claude",
|
|
5961
|
-
body: INSTALL_PROMPT_CLAUDE
|
|
5962
|
-
},
|
|
5963
|
-
{
|
|
5964
|
-
id: "install_chatgpt",
|
|
5965
|
-
title: "ChatGPT \uC124\uCE58 \uD504\uB86C\uD504\uD2B8",
|
|
5966
|
-
audience: "chatgpt",
|
|
5967
|
-
body: INSTALL_PROMPT_CHATGPT
|
|
5968
|
-
},
|
|
5969
|
-
{
|
|
5970
|
-
id: "install_generic",
|
|
5971
|
-
title: "\uBC94\uC6A9 AI \uC124\uCE58 \uD504\uB86C\uD504\uD2B8",
|
|
5972
|
-
audience: "generic",
|
|
5973
|
-
body: INSTALL_PROMPT_GENERIC
|
|
5974
|
-
},
|
|
5975
|
-
{
|
|
5976
|
-
id: "onboarding",
|
|
5977
|
-
title: "\uC628\uBCF4\uB529 \uD504\uB86C\uD504\uD2B8 (\uC5F0\uACB0 \uC9C1\uD6C4)",
|
|
5978
|
-
audience: "all",
|
|
5979
|
-
body: ONBOARDING_PROMPT
|
|
5980
|
-
},
|
|
5981
|
-
{
|
|
5982
|
-
id: "humanize_writing",
|
|
5983
|
-
title: "AI \uD2F0 \uC548 \uB098\uB294 \uAE00\uC4F0\uAE30 \uAC00\uC774\uB4DC (\uC790\uAE30\uC18C\uAC1C\uC11C \uC791\uC131)",
|
|
5984
|
-
audience: "all",
|
|
5985
|
-
body: HUMANIZE_WRITING_GUIDE
|
|
5986
|
-
}
|
|
5987
|
-
];
|
|
5988
|
-
|
|
5989
5808
|
// packages/workflows/src/definitions.ts
|
|
5990
5809
|
var WORKFLOWS = [
|
|
5991
5810
|
{
|
|
@@ -6064,307 +5883,34 @@ var WORKFLOWS = [
|
|
|
6064
5883
|
}
|
|
6065
5884
|
];
|
|
6066
5885
|
|
|
6067
|
-
// packages/
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
};
|
|
6084
|
-
function decodeEntities(input) {
|
|
6085
|
-
return input.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (match, body) => {
|
|
6086
|
-
if (body[0] === "#") {
|
|
6087
|
-
const isHex = body[1] === "x" || body[1] === "X";
|
|
6088
|
-
const code = parseInt(body.slice(isHex ? 2 : 1), isHex ? 16 : 10);
|
|
6089
|
-
if (Number.isFinite(code) && code > 0 && code <= 1114111) {
|
|
6090
|
-
try {
|
|
6091
|
-
return String.fromCodePoint(code);
|
|
6092
|
-
} catch {
|
|
6093
|
-
return match;
|
|
6094
|
-
}
|
|
6095
|
-
}
|
|
6096
|
-
return match;
|
|
6097
|
-
}
|
|
6098
|
-
const named = NAMED_ENTITIES[body.toLowerCase()];
|
|
6099
|
-
return named !== void 0 ? named : match;
|
|
5886
|
+
// packages/workflows/src/index.ts
|
|
5887
|
+
function getWorkflow(id2) {
|
|
5888
|
+
return WORKFLOWS.find((w) => w.id === id2);
|
|
5889
|
+
}
|
|
5890
|
+
function renderWorkflowMarkdown(id2) {
|
|
5891
|
+
const wf = getWorkflow(id2);
|
|
5892
|
+
if (!wf) return void 0;
|
|
5893
|
+
const lines = [];
|
|
5894
|
+
lines.push(`# ${wf.title}`);
|
|
5895
|
+
lines.push("");
|
|
5896
|
+
lines.push(wf.description);
|
|
5897
|
+
lines.push("");
|
|
5898
|
+
lines.push(`**\uD2B8\uB9AC\uAC70:** ${wf.trigger}`);
|
|
5899
|
+
lines.push("");
|
|
5900
|
+
lines.push("## \uB2E8\uACC4");
|
|
5901
|
+
wf.steps.forEach((step, i) => {
|
|
5902
|
+
lines.push(`${i + 1}. ${step}`);
|
|
6100
5903
|
});
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
function stripHtml(html) {
|
|
6104
|
-
if (!html) return "";
|
|
6105
|
-
let s = String(html);
|
|
6106
|
-
s = s.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, " ");
|
|
6107
|
-
s = s.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, " ");
|
|
6108
|
-
s = s.replace(/<!--[\s\S]*?-->/g, " ");
|
|
6109
|
-
s = s.replace(/<br\b[^>]*>/gi, "\n");
|
|
6110
|
-
s = s.replace(/<hr\b[^>]*>/gi, "\n");
|
|
6111
|
-
s = s.replace(BLOCK_TAG, "\n");
|
|
6112
|
-
s = s.replace(/<\/?[^>]+>/g, " ");
|
|
6113
|
-
s = decodeEntities(s);
|
|
6114
|
-
s = s.replace(/[ \t\f\v]+/g, " ");
|
|
6115
|
-
s = s.split("\n").map((line) => line.trim()).join("\n");
|
|
6116
|
-
s = s.replace(/\n{3,}/g, "\n\n");
|
|
6117
|
-
return s.trim();
|
|
6118
|
-
}
|
|
6119
|
-
|
|
6120
|
-
// packages/parsers/src/keywords.ts
|
|
6121
|
-
var TECH_TERMS = [
|
|
6122
|
-
// Languages
|
|
6123
|
-
{ label: "JavaScript", aliases: ["js"] },
|
|
6124
|
-
{ label: "TypeScript", aliases: ["ts"] },
|
|
6125
|
-
{ label: "Python" },
|
|
6126
|
-
{ label: "Java" },
|
|
6127
|
-
{ label: "Kotlin" },
|
|
6128
|
-
{ label: "Go", aliases: ["golang"] },
|
|
6129
|
-
{ label: "Rust" },
|
|
6130
|
-
{ label: "C++", aliases: ["cpp"] },
|
|
6131
|
-
{ label: "C#", aliases: ["csharp"] },
|
|
6132
|
-
{ label: "C" },
|
|
6133
|
-
{ label: "Ruby" },
|
|
6134
|
-
{ label: "PHP" },
|
|
6135
|
-
{ label: "Swift" },
|
|
6136
|
-
{ label: "Scala" },
|
|
6137
|
-
{ label: "Dart" },
|
|
6138
|
-
{ label: "SQL" },
|
|
6139
|
-
{ label: "GraphQL" },
|
|
6140
|
-
// Frontend frameworks/libs
|
|
6141
|
-
{ label: "React", aliases: ["react.js", "reactjs"] },
|
|
6142
|
-
{ label: "Next.js", aliases: ["nextjs", "next js"] },
|
|
6143
|
-
{ label: "Vue", aliases: ["vue.js", "vuejs"] },
|
|
6144
|
-
{ label: "Nuxt", aliases: ["nuxt.js", "nuxtjs"] },
|
|
6145
|
-
{ label: "Angular" },
|
|
6146
|
-
{ label: "Svelte" },
|
|
6147
|
-
{ label: "Redux" },
|
|
6148
|
-
{ label: "Tailwind", aliases: ["tailwindcss", "tailwind css"] },
|
|
6149
|
-
// Backend frameworks
|
|
6150
|
-
{ label: "Node.js", aliases: ["nodejs", "node js", "node"] },
|
|
6151
|
-
{ label: "Express", aliases: ["express.js", "expressjs"] },
|
|
6152
|
-
{ label: "NestJS", aliases: ["nest.js", "nest js"] },
|
|
6153
|
-
{ label: "Spring", aliases: ["spring boot", "springboot"] },
|
|
6154
|
-
{ label: "Django" },
|
|
6155
|
-
{ label: "Flask" },
|
|
6156
|
-
{ label: "FastAPI" },
|
|
6157
|
-
{ label: "Rails", aliases: ["ruby on rails"] },
|
|
6158
|
-
{ label: "Laravel" },
|
|
6159
|
-
{ label: ".NET", aliases: ["dotnet", "asp.net"] },
|
|
6160
|
-
// Datastores
|
|
6161
|
-
{ label: "PostgreSQL", aliases: ["postgres", "postgresql"] },
|
|
6162
|
-
{ label: "MySQL" },
|
|
6163
|
-
{ label: "MariaDB" },
|
|
6164
|
-
{ label: "MongoDB", aliases: ["mongo"] },
|
|
6165
|
-
{ label: "Redis" },
|
|
6166
|
-
{ label: "Elasticsearch", aliases: ["elastic search"] },
|
|
6167
|
-
{ label: "Oracle" },
|
|
6168
|
-
{ label: "SQLite" },
|
|
6169
|
-
{ label: "Kafka" },
|
|
6170
|
-
{ label: "RabbitMQ" },
|
|
6171
|
-
// Cloud / infra / devops
|
|
6172
|
-
{ label: "AWS", aliases: ["amazon web services"] },
|
|
6173
|
-
{ label: "GCP", aliases: ["google cloud"] },
|
|
6174
|
-
{ label: "Azure" },
|
|
6175
|
-
{ label: "Docker" },
|
|
6176
|
-
{ label: "Kubernetes", aliases: ["k8s"] },
|
|
6177
|
-
{ label: "Terraform" },
|
|
6178
|
-
{ label: "Jenkins" },
|
|
6179
|
-
{ label: "GitHub Actions", aliases: ["github action"] },
|
|
6180
|
-
{ label: "CI/CD", aliases: ["cicd"] },
|
|
6181
|
-
{ label: "Nginx" },
|
|
6182
|
-
{ label: "Linux" },
|
|
6183
|
-
// Tooling / misc
|
|
6184
|
-
{ label: "Git" },
|
|
6185
|
-
{ label: "REST", aliases: ["rest api", "restful"] },
|
|
6186
|
-
{ label: "gRPC" },
|
|
6187
|
-
{ label: "Webpack" },
|
|
6188
|
-
{ label: "Vite" },
|
|
6189
|
-
{ label: "Jest" },
|
|
6190
|
-
{ label: "Figma" },
|
|
6191
|
-
{ label: "Jira" },
|
|
6192
|
-
// Data / ML
|
|
6193
|
-
{ label: "TensorFlow" },
|
|
6194
|
-
{ label: "PyTorch" },
|
|
6195
|
-
{ label: "Pandas" },
|
|
6196
|
-
{ label: "NumPy", aliases: ["numpy"] },
|
|
6197
|
-
{ label: "Spark", aliases: ["apache spark"] },
|
|
6198
|
-
{ label: "Airflow" }
|
|
6199
|
-
];
|
|
6200
|
-
function escapeRegExp(s) {
|
|
6201
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6202
|
-
}
|
|
6203
|
-
function buildPattern(term) {
|
|
6204
|
-
const esc = escapeRegExp(term);
|
|
6205
|
-
if (/^[\w.]+$/.test(term) && /\w$/.test(term) && /^\w/.test(term)) {
|
|
6206
|
-
return new RegExp(`(?<![\\w])${esc}(?![\\w])`, "i");
|
|
6207
|
-
}
|
|
6208
|
-
return new RegExp(`(?<![A-Za-z0-9])${esc}(?![A-Za-z0-9])`, "i");
|
|
6209
|
-
}
|
|
6210
|
-
function extractTechKeywords(text, limit = 20) {
|
|
6211
|
-
if (!text) return [];
|
|
6212
|
-
const found = [];
|
|
6213
|
-
const seen = /* @__PURE__ */ new Set();
|
|
6214
|
-
for (const term of TECH_TERMS) {
|
|
6215
|
-
if (seen.has(term.label)) continue;
|
|
6216
|
-
const candidates = [term.label, ...term.aliases ?? []];
|
|
6217
|
-
const matched = candidates.some((c) => {
|
|
6218
|
-
try {
|
|
6219
|
-
return buildPattern(c).test(text);
|
|
6220
|
-
} catch {
|
|
6221
|
-
return false;
|
|
6222
|
-
}
|
|
6223
|
-
});
|
|
6224
|
-
if (matched) {
|
|
6225
|
-
seen.add(term.label);
|
|
6226
|
-
found.push(term.label);
|
|
6227
|
-
if (found.length >= limit) break;
|
|
6228
|
-
}
|
|
6229
|
-
}
|
|
6230
|
-
return found;
|
|
6231
|
-
}
|
|
6232
|
-
|
|
6233
|
-
// packages/parsers/src/index.ts
|
|
6234
|
-
var HTML_MIME_RE = /html/i;
|
|
6235
|
-
var MD_EXT_RE = /\.(md|markdown|mdown|mkd)$/i;
|
|
6236
|
-
var HTML_EXT_RE = /\.(html?|xhtml)$/i;
|
|
6237
|
-
var BINARY_EXT_RE = /\.(pdf|docx?|hwpx?|pptx?|xlsx?|rtf|odt|pages|key)$/i;
|
|
6238
|
-
var BINARY_MIME_RE = /(pdf|msword|officedocument|hancom|rtf|opendocument)/i;
|
|
6239
|
-
function looksBinary(s) {
|
|
6240
|
-
if (!s) return false;
|
|
6241
|
-
const sample = s.slice(0, 2e3);
|
|
6242
|
-
let control = 0;
|
|
6243
|
-
for (let i = 0; i < sample.length; i++) {
|
|
6244
|
-
const c = sample.charCodeAt(i);
|
|
6245
|
-
if (c === 0) return true;
|
|
6246
|
-
if (c < 9 || c > 13 && c < 32) control++;
|
|
6247
|
-
}
|
|
6248
|
-
return control / sample.length > 0.1;
|
|
6249
|
-
}
|
|
6250
|
-
function looksLikeHtml(s) {
|
|
6251
|
-
return /<\/?[a-z][\s\S]*?>/i.test(s) && /<\/?(html|body|div|p|span|br|table|h[1-6]|ul|li)\b/i.test(s);
|
|
6252
|
-
}
|
|
6253
|
-
function extractText(input) {
|
|
6254
|
-
const warnings = [];
|
|
6255
|
-
const safe = input ?? { content: "" };
|
|
6256
|
-
const content = typeof safe.content === "string" ? safe.content : "";
|
|
6257
|
-
const filename = safe.filename ?? "";
|
|
6258
|
-
const mimeType = safe.mimeType ?? "";
|
|
6259
|
-
const isBinaryType = BINARY_EXT_RE.test(filename) || BINARY_MIME_RE.test(mimeType);
|
|
6260
|
-
if (isBinaryType) {
|
|
6261
|
-
if (content && !looksBinary(content)) {
|
|
6262
|
-
warnings.push(
|
|
6263
|
-
"\uBC14\uC774\uB108\uB9AC \uBB38\uC11C(PDF/DOCX \uB4F1)\uB294 \uC9C1\uC811 \uD30C\uC2F1\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC774\uBBF8 \uD14D\uC2A4\uD2B8\uB85C \uBCF4\uC774\uB294 \uB0B4\uC6A9\uC744 \uADF8\uB300\uB85C \uC0AC\uC6A9\uD588\uC2B5\uB2C8\uB2E4."
|
|
6264
|
-
);
|
|
6265
|
-
return { text: content.trim(), format: "unsupported", warnings };
|
|
6266
|
-
}
|
|
6267
|
-
warnings.push(
|
|
6268
|
-
"PDF/DOCX \uB4F1 \uBC14\uC774\uB108\uB9AC \uBB38\uC11C\uB294 \uD604\uC7AC \uC790\uB3D9 \uCD94\uCD9C\uC744 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uB0B4\uC6A9\uC744 \uD14D\uC2A4\uD2B8\uB85C \uBCF5\uC0AC\uD574 \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694."
|
|
6269
|
-
);
|
|
6270
|
-
return { text: "", format: "unsupported", warnings };
|
|
6271
|
-
}
|
|
6272
|
-
if (looksBinary(content)) {
|
|
6273
|
-
warnings.push(
|
|
6274
|
-
"\uC77D\uC744 \uC218 \uC5C6\uB294 \uBC14\uC774\uB108\uB9AC \uB370\uC774\uD130\uB85C \uBCF4\uC785\uB2C8\uB2E4. \uB0B4\uC6A9\uC744 \uD14D\uC2A4\uD2B8\uB85C \uBCF5\uC0AC\uD574 \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694."
|
|
6275
|
-
);
|
|
6276
|
-
return { text: "", format: "unsupported", warnings };
|
|
6277
|
-
}
|
|
6278
|
-
if (HTML_MIME_RE.test(mimeType) || HTML_EXT_RE.test(filename) || looksLikeHtml(content)) {
|
|
6279
|
-
return { text: stripHtml(content), format: "html", warnings };
|
|
6280
|
-
}
|
|
6281
|
-
if (MD_EXT_RE.test(filename) || /markdown/i.test(mimeType)) {
|
|
6282
|
-
return { text: content.trim(), format: "markdown", warnings };
|
|
6283
|
-
}
|
|
6284
|
-
return { text: content.trim(), format: "text", warnings };
|
|
6285
|
-
}
|
|
6286
|
-
var BOILERPLATE_RE = /^(home|menu|login|로그인|회원가입|sign\s?up|sign\s?in|share|공유|스크랩|북마크|이전|다음|목록|prev(ious)?|next|cookie|쿠키|copyright|©|all rights reserved|이용약관|개인정보처리방침|상단으로|top|search|검색)$/i;
|
|
6287
|
-
var COMPANY_LABEL_RE = /(?:회사명?|기업명?|company)\s*[::]\s*(.+)/i;
|
|
6288
|
-
var POSITION_LABEL_RE = /(?:포지션|직무|모집\s*부문|채용\s*부문|position|job\s*title)\s*[::]\s*(.+)/i;
|
|
6289
|
-
var DEADLINE_LABEL_RE = /(?:마감(?:일|일자|기한)?|접수\s*마감|deadline|due)\s*[::]?\s*(.+)/i;
|
|
6290
|
-
function findDeadline(text) {
|
|
6291
|
-
const rolling = /(상시\s*채용|채용\s*시\s*(?:마감|까지)|수시\s*채용)/i.exec(text);
|
|
6292
|
-
for (const line of text.split("\n")) {
|
|
6293
|
-
const m = DEADLINE_LABEL_RE.exec(line);
|
|
6294
|
-
if (m) {
|
|
6295
|
-
const date = normalizeDate(m[1]);
|
|
6296
|
-
if (date) return date;
|
|
6297
|
-
const tail = m[1].trim();
|
|
6298
|
-
if (tail && tail.length <= 30) return tail;
|
|
6299
|
-
}
|
|
6300
|
-
}
|
|
6301
|
-
const any = normalizeDate(text);
|
|
6302
|
-
if (any) return any;
|
|
6303
|
-
if (rolling) return rolling[1].replace(/\s+/g, " ").trim();
|
|
6304
|
-
return void 0;
|
|
6305
|
-
}
|
|
6306
|
-
function normalizeDate(s) {
|
|
6307
|
-
if (!s) return void 0;
|
|
6308
|
-
let m = /(\d{4})[.\-/년]\s*(\d{1,2})[.\-/월]\s*(\d{1,2})/.exec(s);
|
|
6309
|
-
if (m) {
|
|
6310
|
-
const [, y, mo, d] = m;
|
|
6311
|
-
return `${y}-${mo.padStart(2, "0")}-${d.padStart(2, "0")}`;
|
|
6312
|
-
}
|
|
6313
|
-
m = /(\d{4})[.\-/년]\s*(\d{1,2})\b/.exec(s);
|
|
6314
|
-
if (m) {
|
|
6315
|
-
const [, y, mo] = m;
|
|
6316
|
-
return `${y}-${mo.padStart(2, "0")}`;
|
|
6317
|
-
}
|
|
6318
|
-
return void 0;
|
|
6319
|
-
}
|
|
6320
|
-
function findLabelled(lines, re) {
|
|
6321
|
-
for (const line of lines) {
|
|
6322
|
-
const m = re.exec(line);
|
|
6323
|
-
if (m) {
|
|
6324
|
-
const val = m[1].trim();
|
|
6325
|
-
if (val && val.length <= 80) return val;
|
|
6326
|
-
}
|
|
6327
|
-
}
|
|
6328
|
-
return void 0;
|
|
6329
|
-
}
|
|
6330
|
-
function cleanJobPosting(raw) {
|
|
6331
|
-
const source = typeof raw === "string" ? raw : "";
|
|
6332
|
-
const pre = /<\/?[a-z][\s\S]*?>/i.test(source) ? stripHtml(source) : source;
|
|
6333
|
-
const rawLines = pre.replace(/\r\n?/g, "\n").split("\n");
|
|
6334
|
-
const lines = [];
|
|
6335
|
-
for (const original of rawLines) {
|
|
6336
|
-
const line = original.replace(/[ \t\f\v]+/g, " ").trim();
|
|
6337
|
-
if (!line) {
|
|
6338
|
-
if (lines.length && lines[lines.length - 1] !== "") lines.push("");
|
|
6339
|
-
continue;
|
|
6340
|
-
}
|
|
6341
|
-
if (BOILERPLATE_RE.test(line)) continue;
|
|
6342
|
-
if (/^[-=_*·•~]+$/.test(line)) continue;
|
|
6343
|
-
lines.push(line);
|
|
6344
|
-
}
|
|
6345
|
-
while (lines.length && lines[0] === "") lines.shift();
|
|
6346
|
-
while (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
6347
|
-
const text = lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
6348
|
-
const company = findLabelled(lines, COMPANY_LABEL_RE);
|
|
6349
|
-
const position = findLabelled(lines, POSITION_LABEL_RE);
|
|
6350
|
-
const deadline = findDeadline(text);
|
|
6351
|
-
const keywords = extractTechKeywords(text);
|
|
6352
|
-
const result = { text, keywords };
|
|
6353
|
-
if (company) result.company = company;
|
|
6354
|
-
if (position) result.position = position;
|
|
6355
|
-
if (deadline) result.deadline = deadline;
|
|
6356
|
-
return result;
|
|
5904
|
+
lines.push("");
|
|
5905
|
+
return lines.join("\n");
|
|
6357
5906
|
}
|
|
6358
5907
|
|
|
6359
5908
|
// packages/exporters/src/html.ts
|
|
6360
5909
|
function escapeHtml(input) {
|
|
6361
5910
|
return (input ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
6362
5911
|
}
|
|
6363
|
-
function
|
|
6364
|
-
|
|
6365
|
-
}
|
|
6366
|
-
function toPrintableHtml(title, bodyMarkdownOrHtml) {
|
|
6367
|
-
const body = looksLikeHtml2(bodyMarkdownOrHtml) ? bodyMarkdownOrHtml : markdownToHtml(bodyMarkdownOrHtml);
|
|
5912
|
+
function toPrintableHtml(title, bodyMarkdown) {
|
|
5913
|
+
const body = markdownToHtml(bodyMarkdown);
|
|
6368
5914
|
const safeTitle = escapeHtml(title);
|
|
6369
5915
|
return `<!doctype html>
|
|
6370
5916
|
<html lang="ko">
|
|
@@ -6453,22 +5999,10 @@ function slugify(input, fallback = "document") {
|
|
|
6453
5999
|
function joinBlocks(blocks) {
|
|
6454
6000
|
return blocks.filter((b) => typeof b === "string" && b.trim().length > 0).join("\n\n");
|
|
6455
6001
|
}
|
|
6456
|
-
function bulletList(items) {
|
|
6457
|
-
const lines = items.filter((i) => typeof i === "string" && i.trim().length > 0).map((i) => `- ${i.trim()}`);
|
|
6458
|
-
return lines.join("\n");
|
|
6459
|
-
}
|
|
6460
6002
|
function kv(label, value) {
|
|
6461
6003
|
if (!value || !value.trim()) return "";
|
|
6462
6004
|
return `**${label}**: ${value.trim()}`;
|
|
6463
6005
|
}
|
|
6464
|
-
function dateRange(start, end, current = false) {
|
|
6465
|
-
const s = (start ?? "").trim();
|
|
6466
|
-
const e = current ? "\uD604\uC7AC" : (end ?? "").trim();
|
|
6467
|
-
if (!s && !e) return "";
|
|
6468
|
-
if (!s) return e;
|
|
6469
|
-
if (!e) return s;
|
|
6470
|
-
return `${s} ~ ${e}`;
|
|
6471
|
-
}
|
|
6472
6006
|
function renderInline(escaped) {
|
|
6473
6007
|
let out = escaped;
|
|
6474
6008
|
out = out.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
|
|
@@ -6577,635 +6111,291 @@ function coverLetterToHtml(cl, opts) {
|
|
|
6577
6111
|
content: toPrintableHtml(cl.title, coverLetterBody(cl, opts))
|
|
6578
6112
|
};
|
|
6579
6113
|
}
|
|
6580
|
-
function resumeToMarkdown(doc, profile) {
|
|
6581
|
-
const kindLabel = DOCUMENT_KIND_LABELS[doc.kind] ?? doc.kind;
|
|
6582
|
-
const header = [];
|
|
6583
|
-
if (profile?.name) header.push(kv("\uC774\uB984", profile.name));
|
|
6584
|
-
if (profile?.email) header.push(kv("\uC774\uBA54\uC77C", profile.email));
|
|
6585
|
-
if (profile?.phone) header.push(kv("\uC5F0\uB77D\uCC98", profile.phone));
|
|
6586
|
-
const body = joinBlocks([
|
|
6587
|
-
`# ${doc.title}`,
|
|
6588
|
-
`_${kindLabel}_`,
|
|
6589
|
-
header.length ? header.filter(Boolean).join("\n\n") : "",
|
|
6590
|
-
header.length ? "---" : "",
|
|
6591
|
-
(doc.content ?? "").trim() || "_(\uB0B4\uC6A9 \uC5C6\uC74C)_"
|
|
6592
|
-
]);
|
|
6593
|
-
return {
|
|
6594
|
-
filename: `${slugify(doc.title, doc.kind)}.md`,
|
|
6595
|
-
mimeType: MD_MIME,
|
|
6596
|
-
content: body
|
|
6597
|
-
};
|
|
6598
|
-
}
|
|
6599
|
-
function profileToMarkdown(profile, experiences = [], projects = [], skills = []) {
|
|
6600
|
-
const name = profile.name?.trim() || "\uC774\uB825\uC11C";
|
|
6601
|
-
const contact = [];
|
|
6602
|
-
if (profile.email) contact.push(profile.email);
|
|
6603
|
-
if (profile.phone) contact.push(profile.phone);
|
|
6604
|
-
if (profile.location) contact.push(profile.location);
|
|
6605
|
-
const links = (profile.links ?? []).filter((l) => l.url).map((l) => `[${l.label || l.url}](${l.url})`);
|
|
6606
|
-
const headerBlocks = joinBlocks([
|
|
6607
|
-
`# ${name}`,
|
|
6608
|
-
profile.headline ? `## ${profile.headline}` : "",
|
|
6609
|
-
contact.length ? contact.join(" \xB7 ") : "",
|
|
6610
|
-
links.length ? links.join(" \xB7 ") : ""
|
|
6611
|
-
]);
|
|
6612
|
-
const summaryBlock = profile.summary?.trim() ? joinBlocks(["## \uC18C\uAC1C", profile.summary.trim()]) : "";
|
|
6613
|
-
const desiredLines = [];
|
|
6614
|
-
if (profile.desired_roles?.length) {
|
|
6615
|
-
desiredLines.push(kv("\uD76C\uB9DD \uC9C1\uBB34", profile.desired_roles.join(", ")));
|
|
6616
|
-
}
|
|
6617
|
-
if (profile.desired_conditions?.trim()) {
|
|
6618
|
-
desiredLines.push(kv("\uD76C\uB9DD \uC870\uAC74", profile.desired_conditions));
|
|
6619
|
-
}
|
|
6620
|
-
const desiredBlock = desiredLines.length ? joinBlocks(["## \uD76C\uB9DD \uC0AC\uD56D", desiredLines.filter(Boolean).join("\n\n")]) : "";
|
|
6621
|
-
const expBlocks = experiences.length ? joinBlocks([
|
|
6622
|
-
"## \uACBD\uB825",
|
|
6623
|
-
...experiences.map((e) => {
|
|
6624
|
-
const range = dateRange(e.start_date, e.end_date, e.is_current);
|
|
6625
|
-
const titleParts = [e.company, e.role].filter(Boolean).join(" \u2014 ");
|
|
6626
|
-
const subParts = [e.employment_type, range].filter(Boolean).join(" \xB7 ");
|
|
6627
|
-
return joinBlocks([
|
|
6628
|
-
`### ${titleParts}`,
|
|
6629
|
-
subParts ? `_${subParts}_` : "",
|
|
6630
|
-
e.description?.trim() ? e.description.trim() : "",
|
|
6631
|
-
e.achievements?.length ? bulletList(e.achievements) : "",
|
|
6632
|
-
e.tech?.length ? kv("\uAE30\uC220", e.tech.join(", ")) : ""
|
|
6633
|
-
]);
|
|
6634
|
-
})
|
|
6635
|
-
]) : "";
|
|
6636
|
-
const projBlocks = projects.length ? joinBlocks([
|
|
6637
|
-
"## \uD504\uB85C\uC81D\uD2B8",
|
|
6638
|
-
...projects.map((p) => {
|
|
6639
|
-
const range = dateRange(p.start_date, p.end_date);
|
|
6640
|
-
const subParts = [p.role, range].filter(Boolean).join(" \xB7 ");
|
|
6641
|
-
return joinBlocks([
|
|
6642
|
-
`### ${p.name}`,
|
|
6643
|
-
subParts ? `_${subParts}_` : "",
|
|
6644
|
-
p.description?.trim() ? p.description.trim() : "",
|
|
6645
|
-
p.highlights?.length ? bulletList(p.highlights) : "",
|
|
6646
|
-
p.tech?.length ? kv("\uAE30\uC220", p.tech.join(", ")) : "",
|
|
6647
|
-
p.url ? kv("\uB9C1\uD06C", p.url) : ""
|
|
6648
|
-
]);
|
|
6649
|
-
})
|
|
6650
|
-
]) : "";
|
|
6651
|
-
let skillsBlock = "";
|
|
6652
|
-
if (skills.length) {
|
|
6653
|
-
const groups = /* @__PURE__ */ new Map();
|
|
6654
|
-
for (const s of skills) {
|
|
6655
|
-
const cat = s.category?.trim() || "\uAE30\uD0C0";
|
|
6656
|
-
const label = s.level?.trim() ? `${s.name} (${s.level.trim()})` : s.name;
|
|
6657
|
-
if (!groups.has(cat)) groups.set(cat, []);
|
|
6658
|
-
groups.get(cat).push(label);
|
|
6659
|
-
}
|
|
6660
|
-
const lines = [...groups.entries()].map(
|
|
6661
|
-
([cat, items]) => kv(cat, items.join(", "))
|
|
6662
|
-
);
|
|
6663
|
-
skillsBlock = joinBlocks(["## \uBCF4\uC720 \uAE30\uC220", lines.filter(Boolean).join("\n\n")]);
|
|
6664
|
-
}
|
|
6665
|
-
const content = joinBlocks([
|
|
6666
|
-
headerBlocks,
|
|
6667
|
-
"---",
|
|
6668
|
-
summaryBlock,
|
|
6669
|
-
desiredBlock,
|
|
6670
|
-
expBlocks,
|
|
6671
|
-
projBlocks,
|
|
6672
|
-
skillsBlock
|
|
6673
|
-
]);
|
|
6674
|
-
return {
|
|
6675
|
-
filename: `${slugify(name, "resume")}-resume.md`,
|
|
6676
|
-
mimeType: MD_MIME,
|
|
6677
|
-
content
|
|
6678
|
-
};
|
|
6679
|
-
}
|
|
6680
|
-
function interviewPrepToMarkdown(prep, job) {
|
|
6681
|
-
const titleBase = job ? `${job.company} ${job.position} \uBA74\uC811 \uC900\uBE44` : "\uBA74\uC811 \uC900\uBE44";
|
|
6682
|
-
const meta = [];
|
|
6683
|
-
if (job) {
|
|
6684
|
-
meta.push(kv("\uD68C\uC0AC", job.company), kv("\uC9C1\uBB34", job.position));
|
|
6685
|
-
if (job.deadline) meta.push(kv("\uB9C8\uAC10", job.deadline));
|
|
6686
|
-
}
|
|
6687
|
-
const introBlock = prep.self_introduction?.trim() ? joinBlocks(["## 1\uBD84 \uC790\uAE30\uC18C\uAC1C", prep.self_introduction.trim()]) : "";
|
|
6688
|
-
const questionsBlock = prep.questions?.length ? joinBlocks([
|
|
6689
|
-
"## \uC608\uC0C1 \uC9C8\uBB38",
|
|
6690
|
-
...prep.questions.map(
|
|
6691
|
-
(q, idx) => joinBlocks([
|
|
6692
|
-
`### Q${idx + 1}. ${q.question}`,
|
|
6693
|
-
q.intent?.trim() ? kv("\uC758\uB3C4", q.intent) : "",
|
|
6694
|
-
q.answer_outline?.trim() ? joinBlocks(["**\uB2F5\uBCC0 \uAC00\uC774\uB4DC**", q.answer_outline.trim()]) : "",
|
|
6695
|
-
q.followups?.length ? joinBlocks(["**\uC608\uC0C1 \uAF2C\uB9AC \uC9C8\uBB38**", bulletList(q.followups)]) : ""
|
|
6696
|
-
])
|
|
6697
|
-
)
|
|
6698
|
-
]) : "";
|
|
6699
|
-
const starBlock = prep.star_guides?.length ? joinBlocks([
|
|
6700
|
-
"## STAR \uC815\uB9AC",
|
|
6701
|
-
...prep.star_guides.map(
|
|
6702
|
-
(s) => joinBlocks([
|
|
6703
|
-
`### ${s.question}`,
|
|
6704
|
-
kv("Situation", s.situation),
|
|
6705
|
-
kv("Task", s.task),
|
|
6706
|
-
kv("Action", s.action),
|
|
6707
|
-
kv("Result", s.result)
|
|
6708
|
-
])
|
|
6709
|
-
)
|
|
6710
|
-
]) : "";
|
|
6711
|
-
const notesBlock = prep.notes?.trim() ? joinBlocks(["## \uBA54\uBAA8", prep.notes.trim()]) : "";
|
|
6712
|
-
const content = joinBlocks([
|
|
6713
|
-
`# ${titleBase}`,
|
|
6714
|
-
meta.length ? meta.filter(Boolean).join("\n\n") : "",
|
|
6715
|
-
meta.length ? "---" : "",
|
|
6716
|
-
introBlock,
|
|
6717
|
-
questionsBlock,
|
|
6718
|
-
starBlock,
|
|
6719
|
-
notesBlock
|
|
6720
|
-
]);
|
|
6721
|
-
return {
|
|
6722
|
-
filename: `${slugify(titleBase, "interview-prep")}.md`,
|
|
6723
|
-
mimeType: MD_MIME,
|
|
6724
|
-
content
|
|
6725
|
-
};
|
|
6726
|
-
}
|
|
6727
6114
|
|
|
6728
|
-
//
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
data_dir: getDataDir(),
|
|
6780
|
-
db_path: getDbPath(),
|
|
6781
|
-
exports_dir: getExportsDir(),
|
|
6782
|
-
backups_dir: getBackupsDir(),
|
|
6783
|
-
node_version: process.version,
|
|
6784
|
-
counts: {
|
|
6785
|
-
profile: profileRepo.get() ? 1 : 0,
|
|
6786
|
-
experiences: experienceRepo.list().length,
|
|
6787
|
-
projects: projectRepo.list().length,
|
|
6788
|
-
skills: skillRepo.list().length,
|
|
6789
|
-
documents: documentRepo.list().length,
|
|
6790
|
-
cover_letters: coverLetterRepo.list().length,
|
|
6791
|
-
jobs: jobRepo.list().length,
|
|
6792
|
-
applications: applicationRepo.list().length,
|
|
6793
|
-
interview_preps: interviewRepo.list().length
|
|
6794
|
-
}
|
|
6795
|
-
};
|
|
6115
|
+
// packages/parsers/src/html.ts
|
|
6116
|
+
var NAMED_ENTITIES = {
|
|
6117
|
+
amp: "&",
|
|
6118
|
+
lt: "<",
|
|
6119
|
+
gt: ">",
|
|
6120
|
+
quot: '"',
|
|
6121
|
+
apos: "'",
|
|
6122
|
+
nbsp: " ",
|
|
6123
|
+
middot: "\xB7",
|
|
6124
|
+
hellip: "\u2026",
|
|
6125
|
+
mdash: "\u2014",
|
|
6126
|
+
ndash: "\u2013",
|
|
6127
|
+
bull: "\u2022",
|
|
6128
|
+
copy: "\xA9",
|
|
6129
|
+
reg: "\xAE",
|
|
6130
|
+
trade: "\u2122"
|
|
6131
|
+
};
|
|
6132
|
+
function decodeEntities(input) {
|
|
6133
|
+
return input.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (match, body) => {
|
|
6134
|
+
if (body[0] === "#") {
|
|
6135
|
+
const isHex = body[1] === "x" || body[1] === "X";
|
|
6136
|
+
const code = parseInt(body.slice(isHex ? 2 : 1), isHex ? 16 : 10);
|
|
6137
|
+
if (Number.isFinite(code) && code > 0 && code <= 1114111) {
|
|
6138
|
+
try {
|
|
6139
|
+
return String.fromCodePoint(code);
|
|
6140
|
+
} catch {
|
|
6141
|
+
return match;
|
|
6142
|
+
}
|
|
6143
|
+
}
|
|
6144
|
+
return match;
|
|
6145
|
+
}
|
|
6146
|
+
const named = NAMED_ENTITIES[body.toLowerCase()];
|
|
6147
|
+
return named !== void 0 ? named : match;
|
|
6148
|
+
});
|
|
6149
|
+
}
|
|
6150
|
+
var BLOCK_TAG = /<\/?(p|div|section|article|header|footer|li|ul|ol|tr|table|h[1-6]|blockquote|pre)\b[^>]*>/gi;
|
|
6151
|
+
function stripHtml(html) {
|
|
6152
|
+
if (!html) return "";
|
|
6153
|
+
let s = String(html);
|
|
6154
|
+
s = s.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, " ");
|
|
6155
|
+
s = s.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, " ");
|
|
6156
|
+
s = s.replace(/<!--[\s\S]*?-->/g, " ");
|
|
6157
|
+
s = s.replace(/<br\b[^>]*>/gi, "\n");
|
|
6158
|
+
s = s.replace(/<hr\b[^>]*>/gi, "\n");
|
|
6159
|
+
s = s.replace(BLOCK_TAG, "\n");
|
|
6160
|
+
s = s.replace(/<\/?[^>]+>/g, " ");
|
|
6161
|
+
s = decodeEntities(s);
|
|
6162
|
+
s = s.replace(/[ \t\f\v]+/g, " ");
|
|
6163
|
+
s = s.split("\n").map((line) => line.trim()).join("\n");
|
|
6164
|
+
s = s.replace(/\n{3,}/g, "\n\n");
|
|
6165
|
+
return s.trim();
|
|
6796
6166
|
}
|
|
6797
6167
|
|
|
6798
|
-
//
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
"
|
|
6803
|
-
"
|
|
6804
|
-
"
|
|
6805
|
-
"
|
|
6806
|
-
"
|
|
6807
|
-
"
|
|
6808
|
-
"
|
|
6809
|
-
"
|
|
6810
|
-
"
|
|
6811
|
-
"
|
|
6812
|
-
"
|
|
6813
|
-
"
|
|
6168
|
+
// packages/parsers/src/keywords.ts
|
|
6169
|
+
var TECH_TERMS = [
|
|
6170
|
+
// Languages
|
|
6171
|
+
{ label: "JavaScript", aliases: ["js"] },
|
|
6172
|
+
{ label: "TypeScript", aliases: ["ts"] },
|
|
6173
|
+
{ label: "Python" },
|
|
6174
|
+
{ label: "Java" },
|
|
6175
|
+
{ label: "Kotlin" },
|
|
6176
|
+
{ label: "Go", aliases: ["golang"] },
|
|
6177
|
+
{ label: "Rust" },
|
|
6178
|
+
{ label: "C++", aliases: ["cpp"] },
|
|
6179
|
+
{ label: "C#", aliases: ["csharp"] },
|
|
6180
|
+
{ label: "C" },
|
|
6181
|
+
{ label: "Ruby" },
|
|
6182
|
+
{ label: "PHP" },
|
|
6183
|
+
{ label: "Swift" },
|
|
6184
|
+
{ label: "Scala" },
|
|
6185
|
+
{ label: "Dart" },
|
|
6186
|
+
{ label: "SQL" },
|
|
6187
|
+
{ label: "GraphQL" },
|
|
6188
|
+
// Frontend frameworks/libs
|
|
6189
|
+
{ label: "React", aliases: ["react.js", "reactjs"] },
|
|
6190
|
+
{ label: "Next.js", aliases: ["nextjs", "next js"] },
|
|
6191
|
+
{ label: "Vue", aliases: ["vue.js", "vuejs"] },
|
|
6192
|
+
{ label: "Nuxt", aliases: ["nuxt.js", "nuxtjs"] },
|
|
6193
|
+
{ label: "Angular" },
|
|
6194
|
+
{ label: "Svelte" },
|
|
6195
|
+
{ label: "Redux" },
|
|
6196
|
+
{ label: "Tailwind", aliases: ["tailwindcss", "tailwind css"] },
|
|
6197
|
+
// Backend frameworks
|
|
6198
|
+
{ label: "Node.js", aliases: ["nodejs", "node js", "node"] },
|
|
6199
|
+
{ label: "Express", aliases: ["express.js", "expressjs"] },
|
|
6200
|
+
{ label: "NestJS", aliases: ["nest.js", "nest js"] },
|
|
6201
|
+
{ label: "Spring", aliases: ["spring boot", "springboot"] },
|
|
6202
|
+
{ label: "Django" },
|
|
6203
|
+
{ label: "Flask" },
|
|
6204
|
+
{ label: "FastAPI" },
|
|
6205
|
+
{ label: "Rails", aliases: ["ruby on rails"] },
|
|
6206
|
+
{ label: "Laravel" },
|
|
6207
|
+
{ label: ".NET", aliases: ["dotnet", "asp.net"] },
|
|
6208
|
+
// Datastores
|
|
6209
|
+
{ label: "PostgreSQL", aliases: ["postgres", "postgresql"] },
|
|
6210
|
+
{ label: "MySQL" },
|
|
6211
|
+
{ label: "MariaDB" },
|
|
6212
|
+
{ label: "MongoDB", aliases: ["mongo"] },
|
|
6213
|
+
{ label: "Redis" },
|
|
6214
|
+
{ label: "Elasticsearch", aliases: ["elastic search"] },
|
|
6215
|
+
{ label: "Oracle" },
|
|
6216
|
+
{ label: "SQLite" },
|
|
6217
|
+
{ label: "Kafka" },
|
|
6218
|
+
{ label: "RabbitMQ" },
|
|
6219
|
+
// Cloud / infra / devops
|
|
6220
|
+
{ label: "AWS", aliases: ["amazon web services"] },
|
|
6221
|
+
{ label: "GCP", aliases: ["google cloud"] },
|
|
6222
|
+
{ label: "Azure" },
|
|
6223
|
+
{ label: "Docker" },
|
|
6224
|
+
{ label: "Kubernetes", aliases: ["k8s"] },
|
|
6225
|
+
{ label: "Terraform" },
|
|
6226
|
+
{ label: "Jenkins" },
|
|
6227
|
+
{ label: "GitHub Actions", aliases: ["github action"] },
|
|
6228
|
+
{ label: "CI/CD", aliases: ["cicd"] },
|
|
6229
|
+
{ label: "Nginx" },
|
|
6230
|
+
{ label: "Linux" },
|
|
6231
|
+
// Tooling / misc
|
|
6232
|
+
{ label: "Git" },
|
|
6233
|
+
{ label: "REST", aliases: ["rest api", "restful"] },
|
|
6234
|
+
{ label: "gRPC" },
|
|
6235
|
+
{ label: "Webpack" },
|
|
6236
|
+
{ label: "Vite" },
|
|
6237
|
+
{ label: "Jest" },
|
|
6238
|
+
{ label: "Figma" },
|
|
6239
|
+
{ label: "Jira" },
|
|
6240
|
+
// Data / ML
|
|
6241
|
+
{ label: "TensorFlow" },
|
|
6242
|
+
{ label: "PyTorch" },
|
|
6243
|
+
{ label: "Pandas" },
|
|
6244
|
+
{ label: "NumPy", aliases: ["numpy"] },
|
|
6245
|
+
{ label: "Spark", aliases: ["apache spark"] },
|
|
6246
|
+
{ label: "Airflow" }
|
|
6814
6247
|
];
|
|
6815
|
-
function
|
|
6816
|
-
|
|
6817
|
-
const tables = {};
|
|
6818
|
-
for (const t of TABLES) {
|
|
6819
|
-
tables[t] = db.prepare(`SELECT * FROM ${t}`).all();
|
|
6820
|
-
}
|
|
6821
|
-
const ver = db.prepare(`SELECT value FROM _meta WHERE key='schema_version'`).get();
|
|
6822
|
-
return { exported_at: (/* @__PURE__ */ new Date()).toISOString(), version: Number(ver?.value ?? 0), tables };
|
|
6248
|
+
function escapeRegExp(s) {
|
|
6249
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6823
6250
|
}
|
|
6824
|
-
function
|
|
6825
|
-
const
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
const jsonBackup = path4.join(dir, `careermate-${stamp}.json`);
|
|
6829
|
-
try {
|
|
6830
|
-
getDb().exec(`VACUUM INTO '${dbBackup.replace(/'/g, "''")}'`);
|
|
6831
|
-
} catch {
|
|
6832
|
-
fs4.copyFileSync(getDbPath(), dbBackup);
|
|
6251
|
+
function buildPattern(term) {
|
|
6252
|
+
const esc = escapeRegExp(term);
|
|
6253
|
+
if (/^[\w.]+$/.test(term) && /\w$/.test(term) && /^\w/.test(term)) {
|
|
6254
|
+
return new RegExp(`(?<![\\w])${esc}(?![\\w])`, "i");
|
|
6833
6255
|
}
|
|
6834
|
-
|
|
6835
|
-
return { backup_path: dbBackup, json_path: jsonBackup };
|
|
6256
|
+
return new RegExp(`(?<![A-Za-z0-9])${esc}(?![A-Za-z0-9])`, "i");
|
|
6836
6257
|
}
|
|
6837
|
-
function
|
|
6838
|
-
if (
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
const
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6258
|
+
function extractTechKeywords(text, limit = 20) {
|
|
6259
|
+
if (!text) return [];
|
|
6260
|
+
const found = [];
|
|
6261
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6262
|
+
for (const term of TECH_TERMS) {
|
|
6263
|
+
if (seen.has(term.label)) continue;
|
|
6264
|
+
const candidates = [term.label, ...term.aliases ?? []];
|
|
6265
|
+
const matched = candidates.some((c) => {
|
|
6266
|
+
try {
|
|
6267
|
+
return buildPattern(c).test(text);
|
|
6268
|
+
} catch {
|
|
6269
|
+
return false;
|
|
6270
|
+
}
|
|
6271
|
+
});
|
|
6272
|
+
if (matched) {
|
|
6273
|
+
seen.add(term.label);
|
|
6274
|
+
found.push(term.label);
|
|
6275
|
+
if (found.length >= limit) break;
|
|
6276
|
+
}
|
|
6850
6277
|
}
|
|
6851
|
-
return
|
|
6852
|
-
}
|
|
6853
|
-
function listBackups() {
|
|
6854
|
-
const dir = getBackupsDir();
|
|
6855
|
-
if (!fs4.existsSync(dir)) return [];
|
|
6856
|
-
return fs4.readdirSync(dir).filter((f) => f.endsWith(".sqlite") || f.endsWith(".json")).map((f) => {
|
|
6857
|
-
const p = path4.join(dir, f);
|
|
6858
|
-
const st = fs4.statSync(p);
|
|
6859
|
-
return { filename: f, path: p, size: st.size, created_at: st.mtime.toISOString() };
|
|
6860
|
-
}).sort((a, b) => b.created_at.localeCompare(a.created_at));
|
|
6278
|
+
return found;
|
|
6861
6279
|
}
|
|
6862
6280
|
|
|
6863
|
-
//
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6281
|
+
// packages/parsers/src/index.ts
|
|
6282
|
+
var BOILERPLATE_RE = /^(home|menu|login|로그인|회원가입|sign\s?up|sign\s?in|share|공유|스크랩|북마크|이전|다음|목록|prev(ious)?|next|cookie|쿠키|copyright|©|all rights reserved|이용약관|개인정보처리방침|상단으로|top|search|검색)$/i;
|
|
6283
|
+
var COMPANY_LABEL_RE = /(?:회사명?|기업명?|company)\s*[::]\s*(.+)/i;
|
|
6284
|
+
var POSITION_LABEL_RE = /(?:포지션|직무|모집\s*부문|채용\s*부문|position|job\s*title)\s*[::]\s*(.+)/i;
|
|
6285
|
+
var DEADLINE_LABEL_RE = /(?:마감(?:일|일자|기한)?|접수\s*마감|deadline|due)\s*[::]?\s*(.+)/i;
|
|
6286
|
+
function findDeadline(text) {
|
|
6287
|
+
const rolling = /(상시\s*채용|채용\s*시\s*(?:마감|까지)|수시\s*채용)/i.exec(text);
|
|
6288
|
+
for (const line of text.split("\n")) {
|
|
6289
|
+
const m = DEADLINE_LABEL_RE.exec(line);
|
|
6290
|
+
if (m) {
|
|
6291
|
+
const date = normalizeDate(m[1]);
|
|
6292
|
+
if (date) return date;
|
|
6293
|
+
const tail = m[1].trim();
|
|
6294
|
+
if (tail && tail.length <= 30) return tail;
|
|
6295
|
+
}
|
|
6296
|
+
}
|
|
6297
|
+
const any = normalizeDate(text);
|
|
6298
|
+
if (any) return any;
|
|
6299
|
+
if (rolling) return rolling[1].replace(/\s+/g, " ").trim();
|
|
6300
|
+
return void 0;
|
|
6868
6301
|
}
|
|
6869
|
-
function
|
|
6870
|
-
|
|
6871
|
-
|
|
6302
|
+
function normalizeDate(s) {
|
|
6303
|
+
if (!s) return void 0;
|
|
6304
|
+
let m = /(\d{4})[.\-/년]\s*(\d{1,2})[.\-/월]\s*(\d{1,2})/.exec(s);
|
|
6305
|
+
if (m) {
|
|
6306
|
+
const [, y, mo, d] = m;
|
|
6307
|
+
return `${y}-${mo.padStart(2, "0")}-${d.padStart(2, "0")}`;
|
|
6308
|
+
}
|
|
6309
|
+
m = /(\d{4})[.\-/년]\s*(\d{1,2})\b/.exec(s);
|
|
6310
|
+
if (m) {
|
|
6311
|
+
const [, y, mo] = m;
|
|
6312
|
+
return `${y}-${mo.padStart(2, "0")}`;
|
|
6313
|
+
}
|
|
6314
|
+
return void 0;
|
|
6872
6315
|
}
|
|
6873
|
-
function
|
|
6874
|
-
const
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
return
|
|
6882
|
-
...jobWithMeta(job),
|
|
6883
|
-
application,
|
|
6884
|
-
fit,
|
|
6885
|
-
cover_letters,
|
|
6886
|
-
interview,
|
|
6887
|
-
related
|
|
6888
|
-
};
|
|
6316
|
+
function findLabelled(lines, re) {
|
|
6317
|
+
for (const line of lines) {
|
|
6318
|
+
const m = re.exec(line);
|
|
6319
|
+
if (m) {
|
|
6320
|
+
const val = m[1].trim();
|
|
6321
|
+
if (val && val.length <= 80) return val;
|
|
6322
|
+
}
|
|
6323
|
+
}
|
|
6324
|
+
return void 0;
|
|
6889
6325
|
}
|
|
6890
|
-
function
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
);
|
|
6901
|
-
router2.get("/api/prompts", () => ({ prompts: PROMPTS }));
|
|
6902
|
-
router2.get("/api/workflows", () => ({ workflows: WORKFLOWS }));
|
|
6903
|
-
router2.get("/api/meta", () => ({
|
|
6904
|
-
statuses: APPLICATION_BOARD_ORDER.map((s) => ({ value: s, label: APPLICATION_STATUS_LABELS[s] })),
|
|
6905
|
-
document_kinds: Object.entries(DOCUMENT_KIND_LABELS).map(([value, label]) => ({ value, label }))
|
|
6906
|
-
}));
|
|
6907
|
-
router2.get("/api/profile", () => ({ profile: profileRepo.get() }));
|
|
6908
|
-
router2.put("/api/profile", async (ctx) => {
|
|
6909
|
-
const input = await readJsonBody(ctx.req, ProfileInputSchema);
|
|
6910
|
-
return { profile: saveProfile(input) };
|
|
6911
|
-
});
|
|
6912
|
-
router2.get("/api/experiences", () => ({ experiences: experienceRepo.list() }));
|
|
6913
|
-
router2.post("/api/experiences", async (ctx) => {
|
|
6914
|
-
const input = await readJsonBody(ctx.req, ExperienceInputSchema);
|
|
6915
|
-
const exp = experienceRepo.add(input);
|
|
6916
|
-
activityRepo.log("profile_updated", `\uACBD\uB825(${exp.company})\uC744 \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, "experience", exp.id);
|
|
6917
|
-
return { experience: exp };
|
|
6918
|
-
});
|
|
6919
|
-
router2.put("/api/experiences/:id", async (ctx) => {
|
|
6920
|
-
const input = await readJsonBody(ctx.req, ExperienceInputSchema.partial());
|
|
6921
|
-
const exp = experienceRepo.update(id2(ctx), input);
|
|
6922
|
-
if (!exp) throw new HttpError(404, "\uACBD\uB825\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
6923
|
-
return { experience: exp };
|
|
6924
|
-
});
|
|
6925
|
-
router2.delete("/api/experiences/:id", (ctx) => ({ ok: experienceRepo.remove(id2(ctx)) }));
|
|
6926
|
-
router2.get("/api/projects", () => ({ projects: projectRepo.list() }));
|
|
6927
|
-
router2.post("/api/projects", async (ctx) => {
|
|
6928
|
-
const input = await readJsonBody(ctx.req, ProjectInputSchema);
|
|
6929
|
-
const prj = projectRepo.add(input);
|
|
6930
|
-
activityRepo.log("profile_updated", `\uD504\uB85C\uC81D\uD2B8(${prj.name})\uB97C \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, "project", prj.id);
|
|
6931
|
-
return { project: prj };
|
|
6932
|
-
});
|
|
6933
|
-
router2.put("/api/projects/:id", async (ctx) => {
|
|
6934
|
-
const input = await readJsonBody(ctx.req, ProjectInputSchema.partial());
|
|
6935
|
-
const prj = projectRepo.update(id2(ctx), input);
|
|
6936
|
-
if (!prj) throw new HttpError(404, "\uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
6937
|
-
return { project: prj };
|
|
6938
|
-
});
|
|
6939
|
-
router2.delete("/api/projects/:id", (ctx) => ({ ok: projectRepo.remove(id2(ctx)) }));
|
|
6940
|
-
router2.get("/api/skills", () => ({ skills: skillRepo.list() }));
|
|
6941
|
-
router2.post("/api/skills", async (ctx) => {
|
|
6942
|
-
const input = await readJsonBody(ctx.req, SkillInputSchema);
|
|
6943
|
-
return { skill: skillRepo.add(input) };
|
|
6944
|
-
});
|
|
6945
|
-
router2.put("/api/skills/:id", async (ctx) => {
|
|
6946
|
-
const input = await readJsonBody(ctx.req, SkillInputSchema.partial());
|
|
6947
|
-
const skill = skillRepo.update(id2(ctx), input);
|
|
6948
|
-
if (!skill) throw new HttpError(404, "\uAE30\uC220\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
6949
|
-
return { skill };
|
|
6950
|
-
});
|
|
6951
|
-
router2.delete("/api/skills/:id", (ctx) => ({ ok: skillRepo.remove(id2(ctx)) }));
|
|
6952
|
-
router2.get("/api/documents", (ctx) => {
|
|
6953
|
-
const kind = ctx.query.get("kind");
|
|
6954
|
-
return { documents: documentRepo.list(kind ?? void 0) };
|
|
6955
|
-
});
|
|
6956
|
-
router2.post("/api/documents", async (ctx) => {
|
|
6957
|
-
const input = await readJsonBody(ctx.req, DocumentInputSchema);
|
|
6958
|
-
return { document: addResume(input) };
|
|
6959
|
-
});
|
|
6960
|
-
router2.get("/api/documents/:id", (ctx) => {
|
|
6961
|
-
const doc = documentRepo.get(id2(ctx));
|
|
6962
|
-
if (!doc) throw new HttpError(404, "\uBB38\uC11C\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
6963
|
-
return { document: doc };
|
|
6964
|
-
});
|
|
6965
|
-
router2.put("/api/documents/:id", async (ctx) => {
|
|
6966
|
-
const input = await readJsonBody(ctx.req, DocumentInputSchema.partial());
|
|
6967
|
-
const doc = documentRepo.update(id2(ctx), input);
|
|
6968
|
-
if (!doc) throw new HttpError(404, "\uBB38\uC11C\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
6969
|
-
return { document: doc };
|
|
6970
|
-
});
|
|
6971
|
-
router2.delete("/api/documents/:id", (ctx) => ({ ok: documentRepo.remove(id2(ctx)) }));
|
|
6972
|
-
router2.get("/api/cover-letters", () => ({ cover_letters: coverLetterRepo.list() }));
|
|
6973
|
-
router2.post("/api/cover-letters", async (ctx) => {
|
|
6974
|
-
const input = await readJsonBody(ctx.req, CoverLetterInputSchema);
|
|
6975
|
-
if (input.content) {
|
|
6976
|
-
const { coverLetter } = saveCoverLetterVersion({
|
|
6977
|
-
title: input.title,
|
|
6978
|
-
job_id: input.job_id,
|
|
6979
|
-
content: input.content,
|
|
6980
|
-
note: input.note,
|
|
6981
|
-
source: input.source ?? "manual"
|
|
6982
|
-
});
|
|
6983
|
-
if (input.is_primary) coverLetterRepo.setPrimary(coverLetter.id);
|
|
6984
|
-
activityRepo.log("cover_letter_added", `${coverLetter.title} \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, "cover_letter", coverLetter.id);
|
|
6985
|
-
return { cover_letter: coverLetterRepo.get(coverLetter.id) };
|
|
6326
|
+
function cleanJobPosting(raw) {
|
|
6327
|
+
const source = typeof raw === "string" ? raw : "";
|
|
6328
|
+
const pre = /<\/?[a-z][\s\S]*?>/i.test(source) ? stripHtml(source) : source;
|
|
6329
|
+
const rawLines = pre.replace(/\r\n?/g, "\n").split("\n");
|
|
6330
|
+
const lines = [];
|
|
6331
|
+
for (const original of rawLines) {
|
|
6332
|
+
const line = original.replace(/[ \t\f\v]+/g, " ").trim();
|
|
6333
|
+
if (!line) {
|
|
6334
|
+
if (lines.length && lines[lines.length - 1] !== "") lines.push("");
|
|
6335
|
+
continue;
|
|
6986
6336
|
}
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
}
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
});
|
|
7004
|
-
router2.put("/api/cover-letters/:id/current-version", async (ctx) => {
|
|
7005
|
-
const { version_id } = await readJsonBody(ctx.req, external_exports.object({ version_id: external_exports.string() }));
|
|
7006
|
-
const cl = coverLetterRepo.setCurrentVersion(id2(ctx), version_id);
|
|
7007
|
-
if (!cl) throw new HttpError(404, "\uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
7008
|
-
return { cover_letter: cl };
|
|
7009
|
-
});
|
|
7010
|
-
router2.put("/api/cover-letters/:id/primary", (ctx) => {
|
|
7011
|
-
const cl = coverLetterRepo.setPrimary(id2(ctx));
|
|
7012
|
-
if (!cl) throw new HttpError(404, "\uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
7013
|
-
return { cover_letter: cl };
|
|
7014
|
-
});
|
|
7015
|
-
router2.delete("/api/cover-letters/:id", (ctx) => ({ ok: coverLetterRepo.remove(id2(ctx)) }));
|
|
7016
|
-
router2.get("/api/jobs", () => ({ jobs: jobRepo.list().map(jobWithMeta) }));
|
|
7017
|
-
router2.post("/api/jobs", async (ctx) => {
|
|
7018
|
-
const input = await readJsonBody(ctx.req, JobInputSchema);
|
|
7019
|
-
const { job, application } = saveJobPosting(input);
|
|
7020
|
-
return { job: jobWithMeta(job), application };
|
|
7021
|
-
});
|
|
7022
|
-
router2.get("/api/jobs/:id", (ctx) => ({ job: jobDetail(id2(ctx)) }));
|
|
7023
|
-
router2.put("/api/jobs/:id", async (ctx) => {
|
|
7024
|
-
const input = await readJsonBody(ctx.req, JobInputSchema.partial());
|
|
7025
|
-
const existing = jobRepo.get(id2(ctx));
|
|
7026
|
-
if (!existing) throw new HttpError(404, "\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
7027
|
-
const job = jobRepo.upsert({ ...existing, ...input }, existing.id);
|
|
7028
|
-
return { job: jobWithMeta(job) };
|
|
7029
|
-
});
|
|
7030
|
-
router2.delete("/api/jobs/:id", (ctx) => ({ ok: jobRepo.remove(id2(ctx)) }));
|
|
7031
|
-
router2.put("/api/jobs/:id/fit", async (ctx) => {
|
|
7032
|
-
const input = await readJsonBody(ctx.req, FitAnalysisInputSchema.omit({ job_id: true }));
|
|
7033
|
-
return { fit: saveFitAnalysis({ ...input, job_id: id2(ctx) }) };
|
|
7034
|
-
});
|
|
7035
|
-
router2.get("/api/interview", () => {
|
|
7036
|
-
const preps = interviewRepo.list().map((p) => ({ ...p, job: jobRepo.get(p.job_id) }));
|
|
7037
|
-
const eligible = applicationRepo.list().filter((a) => ["document_passed", "interview", "final_passed"].includes(a.status)).map((a) => ({
|
|
7038
|
-
job: jobRepo.get(a.job_id),
|
|
7039
|
-
status: a.status,
|
|
7040
|
-
status_label: APPLICATION_STATUS_LABELS[a.status],
|
|
7041
|
-
has_prep: !!interviewRepo.getByJob(a.job_id)
|
|
7042
|
-
})).filter((x) => x.job);
|
|
7043
|
-
return { preps, eligible };
|
|
7044
|
-
});
|
|
7045
|
-
router2.get("/api/jobs/:id/interview", (ctx) => ({ interview: interviewRepo.getByJob(id2(ctx)) }));
|
|
7046
|
-
router2.put("/api/jobs/:id/interview", async (ctx) => {
|
|
7047
|
-
const input = await readJsonBody(ctx.req, InterviewPrepInputSchema.omit({ job_id: true }));
|
|
7048
|
-
return { interview: saveInterviewPrep({ ...input, job_id: id2(ctx) }) };
|
|
7049
|
-
});
|
|
7050
|
-
router2.get("/api/applications", () => {
|
|
7051
|
-
const apps = applicationRepo.list().map((a) => ({
|
|
7052
|
-
...a,
|
|
7053
|
-
status_label: APPLICATION_STATUS_LABELS[a.status],
|
|
7054
|
-
job: jobRepo.get(a.job_id),
|
|
7055
|
-
fit_score: fitRepo.getByJob(a.job_id)?.score ?? null
|
|
7056
|
-
}));
|
|
7057
|
-
return { applications: apps, board_order: APPLICATION_BOARD_ORDER };
|
|
7058
|
-
});
|
|
7059
|
-
router2.put("/api/applications/:jobId/status", async (ctx) => {
|
|
7060
|
-
const body = await readJsonBody(ctx.req, ApplicationStatusUpdateSchema.omit({ job_id: true, application_id: true }));
|
|
7061
|
-
return updateApplicationStatus(id2(ctx, "jobId"), body.status, body.note);
|
|
7062
|
-
});
|
|
7063
|
-
router2.put("/api/applications/:jobId", async (ctx) => {
|
|
7064
|
-
const body = await readJsonBody(ctx.req, ApplicationInputSchema.omit({ job_id: true }));
|
|
7065
|
-
return { application: applicationRepo.upsert({ ...body, job_id: id2(ctx, "jobId") }) };
|
|
7066
|
-
});
|
|
7067
|
-
router2.get("/api/export/cover-letter/:id", (ctx) => download(exportCoverLetter(id2(ctx), fmt(ctx))));
|
|
7068
|
-
router2.get("/api/export/document/:id", (ctx) => download(exportDocument(id2(ctx), fmt(ctx))));
|
|
7069
|
-
router2.get("/api/export/profile", (ctx) => download(exportProfile(fmt(ctx))));
|
|
7070
|
-
router2.get("/api/export/interview/:jobId", (ctx) => download(exportInterview(id2(ctx, "jobId"), fmt(ctx))));
|
|
7071
|
-
router2.get("/api/settings", () => ({
|
|
7072
|
-
info: getServerInfo(),
|
|
7073
|
-
backups: listBackups()
|
|
7074
|
-
}));
|
|
7075
|
-
router2.get(
|
|
7076
|
-
"/api/settings/export-all",
|
|
7077
|
-
() => download({
|
|
7078
|
-
filename: `careermate-backup-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.json`,
|
|
7079
|
-
mimeType: "application/json; charset=utf-8",
|
|
7080
|
-
content: JSON.stringify(exportAll(), null, 2)
|
|
7081
|
-
})
|
|
7082
|
-
);
|
|
7083
|
-
router2.post("/api/settings/backup", () => createBackup());
|
|
7084
|
-
router2.post("/api/settings/reset", async (ctx) => {
|
|
7085
|
-
const { confirm } = await readJsonBody(ctx.req, external_exports.object({ confirm: external_exports.string() }));
|
|
7086
|
-
const result = resetAll(confirm);
|
|
7087
|
-
if (!result.ok) throw new HttpError(400, '\uCD08\uAE30\uD654\uB97C \uC9C4\uD589\uD558\uB824\uBA74 confirm \uAC12\uC73C\uB85C "DELETE"\uB97C \uBCF4\uB0B4\uC57C \uD569\uB2C8\uB2E4.');
|
|
7088
|
-
activityRepo.log("profile_updated", "\uBAA8\uB4E0 \uB370\uC774\uD130\uB97C \uCD08\uAE30\uD654\uD588\uC2B5\uB2C8\uB2E4(\uBC31\uC5C5 \uC0DD\uC131\uB428).");
|
|
7089
|
-
return result;
|
|
7090
|
-
});
|
|
7091
|
-
router2.post("/api/parse/job", async (ctx) => {
|
|
7092
|
-
const { raw } = await readJsonBody(ctx.req, external_exports.object({ raw: external_exports.string() }));
|
|
7093
|
-
return cleanJobPosting(raw);
|
|
7094
|
-
});
|
|
7095
|
-
router2.post("/api/parse/text", async (ctx) => {
|
|
7096
|
-
const body = await readJsonBody(
|
|
7097
|
-
ctx.req,
|
|
7098
|
-
external_exports.object({ filename: external_exports.string().optional(), mimeType: external_exports.string().optional(), content: external_exports.string() })
|
|
7099
|
-
);
|
|
7100
|
-
return extractText(body);
|
|
7101
|
-
});
|
|
6337
|
+
if (BOILERPLATE_RE.test(line)) continue;
|
|
6338
|
+
if (/^[-=_*·•~]+$/.test(line)) continue;
|
|
6339
|
+
lines.push(line);
|
|
6340
|
+
}
|
|
6341
|
+
while (lines.length && lines[0] === "") lines.shift();
|
|
6342
|
+
while (lines.length && lines[lines.length - 1] === "") lines.pop();
|
|
6343
|
+
const text = lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
6344
|
+
const company = findLabelled(lines, COMPANY_LABEL_RE);
|
|
6345
|
+
const position = findLabelled(lines, POSITION_LABEL_RE);
|
|
6346
|
+
const deadline = findDeadline(text);
|
|
6347
|
+
const keywords = extractTechKeywords(text);
|
|
6348
|
+
const result = { text, keywords };
|
|
6349
|
+
if (company) result.company = company;
|
|
6350
|
+
if (position) result.position = position;
|
|
6351
|
+
if (deadline) result.deadline = deadline;
|
|
6352
|
+
return result;
|
|
7102
6353
|
}
|
|
7103
|
-
|
|
7104
|
-
|
|
6354
|
+
|
|
6355
|
+
// packages/mcp-tools/src/result.ts
|
|
6356
|
+
function ok(text, data) {
|
|
6357
|
+
return { text, data };
|
|
7105
6358
|
}
|
|
7106
|
-
function
|
|
7107
|
-
return
|
|
6359
|
+
function fail(text) {
|
|
6360
|
+
return { text, isError: true };
|
|
7108
6361
|
}
|
|
7109
6362
|
|
|
7110
|
-
//
|
|
6363
|
+
// packages/mcp-tools/src/bridge.ts
|
|
6364
|
+
import { spawn } from "node:child_process";
|
|
6365
|
+
import path3 from "node:path";
|
|
6366
|
+
import { fileURLToPath } from "node:url";
|
|
7111
6367
|
var BUNDLED = true;
|
|
7112
|
-
var __dirname =
|
|
7113
|
-
var
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
-
return http.createServer(async (req, res) => {
|
|
7119
|
-
try {
|
|
7120
|
-
const decision = checkRequest(req);
|
|
7121
|
-
if (!decision.ok) {
|
|
7122
|
-
sendJson(res, decision.status ?? 403, { error: "\uC811\uADFC\uC774 \uAC70\uBD80\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", code: decision.reason });
|
|
7123
|
-
return;
|
|
7124
|
-
}
|
|
7125
|
-
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
7126
|
-
const pathname = url.pathname;
|
|
7127
|
-
if (pathname.startsWith("/api/")) {
|
|
7128
|
-
const matched = router.match(req.method ?? "GET", pathname);
|
|
7129
|
-
if (!matched) {
|
|
7130
|
-
sendJson(res, 404, { error: "\uC874\uC7AC\uD558\uC9C0 \uC54A\uB294 API \uACBD\uB85C\uC785\uB2C8\uB2E4.", path: pathname });
|
|
7131
|
-
return;
|
|
7132
|
-
}
|
|
7133
|
-
const result = await matched.handler({
|
|
7134
|
-
req,
|
|
7135
|
-
res,
|
|
7136
|
-
params: matched.params,
|
|
7137
|
-
query: url.searchParams,
|
|
7138
|
-
url
|
|
7139
|
-
});
|
|
7140
|
-
if (res.writableEnded) return;
|
|
7141
|
-
if (isDownload(result)) {
|
|
7142
|
-
const d = result.__download;
|
|
7143
|
-
sendDownload(res, d.filename, d.mimeType, d.content);
|
|
7144
|
-
} else {
|
|
7145
|
-
sendJson(res, 200, result ?? { ok: true });
|
|
7146
|
-
}
|
|
7147
|
-
return;
|
|
7148
|
-
}
|
|
7149
|
-
if (pathname === "/install") {
|
|
7150
|
-
res.writeHead(308, { Location: `/install/${url.search}` });
|
|
7151
|
-
res.end();
|
|
7152
|
-
return;
|
|
7153
|
-
}
|
|
7154
|
-
if (pathname.startsWith("/install/")) {
|
|
7155
|
-
const rel2 = pathname === "/install/" ? "index.html" : pathname.slice("/install/".length);
|
|
7156
|
-
if (serveStatic(res, INSTALL_ROOT, rel2, injectToken)) return;
|
|
7157
|
-
if (serveStatic(res, INSTALL_ROOT, "index.html", injectToken)) return;
|
|
7158
|
-
}
|
|
7159
|
-
const rel = pathname === "/" ? "index.html" : pathname.slice(1);
|
|
7160
|
-
if (serveStatic(res, WEB_ROOT, rel, injectToken)) return;
|
|
7161
|
-
if (serveStatic(res, WEB_ROOT, "index.html", injectToken)) return;
|
|
7162
|
-
sendJson(res, 404, { error: "Not found" });
|
|
7163
|
-
} catch (err) {
|
|
7164
|
-
if (err instanceof HttpError) {
|
|
7165
|
-
sendJson(res, err.status, { error: err.message, code: err.code });
|
|
7166
|
-
} else {
|
|
7167
|
-
const message = err instanceof Error ? err.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
|
|
7168
|
-
console.error("[careermate] \uC694\uCCAD \uCC98\uB9AC \uC911 \uC624\uB958:", message);
|
|
7169
|
-
sendJson(res, 500, { error: "\uC11C\uBC84 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4." });
|
|
7170
|
-
}
|
|
7171
|
-
}
|
|
7172
|
-
});
|
|
6368
|
+
var __dirname = path3.dirname(fileURLToPath(import.meta.url));
|
|
6369
|
+
var REPO_ROOT = path3.resolve(__dirname, "..", "..", "..");
|
|
6370
|
+
function liveServer() {
|
|
6371
|
+
const info2 = readRuntimeInfo();
|
|
6372
|
+
if (info2 && isProcessAlive(info2.pid)) return info2;
|
|
6373
|
+
return null;
|
|
7173
6374
|
}
|
|
7174
|
-
function
|
|
7175
|
-
|
|
7176
|
-
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
const
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
process.on("SIGINT", cleanup);
|
|
7197
|
-
process.on("SIGTERM", cleanup);
|
|
7198
|
-
resolve({ server, port, url });
|
|
7199
|
-
});
|
|
7200
|
-
};
|
|
7201
|
-
tryListen(preferredPort);
|
|
7202
|
-
});
|
|
6375
|
+
async function ensureServer(timeoutMs = 12e3) {
|
|
6376
|
+
const existing = liveServer();
|
|
6377
|
+
if (existing) return { url: existing.url };
|
|
6378
|
+
try {
|
|
6379
|
+
const [bin, args, cwd] = BUNDLED ? [process.execPath, ["--experimental-sqlite", path3.join(__dirname, "web.mjs")], __dirname] : [process.execPath, ["--no-warnings", "--experimental-sqlite", "--import", "tsx", "apps/web/src/index.ts"], REPO_ROOT];
|
|
6380
|
+
const child = spawn(bin, args, {
|
|
6381
|
+
cwd,
|
|
6382
|
+
detached: true,
|
|
6383
|
+
stdio: "ignore",
|
|
6384
|
+
env: { ...process.env, CAREERMATE_NO_OPEN: "1" }
|
|
6385
|
+
});
|
|
6386
|
+
child.unref();
|
|
6387
|
+
} catch {
|
|
6388
|
+
return null;
|
|
6389
|
+
}
|
|
6390
|
+
const start = Date.now();
|
|
6391
|
+
while (Date.now() - start < timeoutMs) {
|
|
6392
|
+
const info2 = liveServer();
|
|
6393
|
+
if (info2) return { url: info2.url };
|
|
6394
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
6395
|
+
}
|
|
6396
|
+
return null;
|
|
7203
6397
|
}
|
|
7204
|
-
|
|
7205
|
-
// apps/web/src/index.ts
|
|
7206
|
-
var DEFAULT_PORT = Number(process.env.CAREERMATE_PORT ?? 4319);
|
|
7207
|
-
function openBrowser(url) {
|
|
7208
|
-
if (process.env.CAREERMATE_NO_OPEN === "1") return;
|
|
6398
|
+
function openInBrowser(url) {
|
|
7209
6399
|
const platform = process.platform;
|
|
7210
6400
|
try {
|
|
7211
6401
|
if (platform === "win32") {
|
|
@@ -7218,23 +6408,505 @@ function openBrowser(url) {
|
|
|
7218
6408
|
} catch {
|
|
7219
6409
|
}
|
|
7220
6410
|
}
|
|
7221
|
-
async function
|
|
7222
|
-
const
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
console.log(`${" ".repeat(2)}${"\u2504".repeat(52)}`);
|
|
7228
|
-
console.log(` \uB300\uC2DC\uBCF4\uB4DC ${url}`);
|
|
7229
|
-
console.log(` \uC124\uCE58 \uC548\uB0B4 ${url}/install`);
|
|
7230
|
-
console.log(` \uB370\uC774\uD130 \uC704\uCE58 ${getDataDir()}`);
|
|
7231
|
-
console.log(` (\uBAA8\uB4E0 \uB370\uC774\uD130\uB294 \uC774 \uCEF4\uD4E8\uD130\uC5D0\uB9CC \uC800\uC7A5\uB429\uB2C8\uB2E4)`);
|
|
7232
|
-
console.log(`\u2514${line}\u2518
|
|
7233
|
-
`);
|
|
7234
|
-
console.log(`\uC885\uB8CC\uD558\uB824\uBA74 Ctrl+C \uB97C \uB204\uB974\uC138\uC694.`);
|
|
7235
|
-
openBrowser(url);
|
|
6411
|
+
async function resolveDashboardUrl(hash = "") {
|
|
6412
|
+
const live = liveServer();
|
|
6413
|
+
if (live) return { url: live.url + hash, running: true };
|
|
6414
|
+
const started = await ensureServer();
|
|
6415
|
+
if (started) return { url: started.url + hash, running: true };
|
|
6416
|
+
return { url: "http://127.0.0.1:4319" + hash, running: false };
|
|
7236
6417
|
}
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
6418
|
+
|
|
6419
|
+
// packages/mcp-tools/src/tools.ts
|
|
6420
|
+
var STATUS_LIST = APPLICATION_STATUSES.map((s) => `${s}(${APPLICATION_STATUS_LABELS[s]})`).join(", ");
|
|
6421
|
+
var TOOLS = [
|
|
6422
|
+
/* ------------------------------------------------------------ onboarding */
|
|
6423
|
+
{
|
|
6424
|
+
name: "get_onboarding_status",
|
|
6425
|
+
title: "\uC628\uBCF4\uB529 \uC0C1\uD0DC \uD655\uC778",
|
|
6426
|
+
description: "CareerMate\uC5D0 \uC5B4\uB5A4 \uC815\uBCF4\uAC00 \uC900\uBE44\uB418\uC5B4 \uC788\uB294\uC9C0(\uD504\uB85C\uD544\xB7\uC774\uB825\uC11C\xB7\uACBD\uB825\xB7\uC2A4\uD0AC\xB7\uC790\uC18C\uC11C\xB7\uACF5\uACE0) \uD655\uC778\uD558\uACE0, \uC0AC\uC6A9\uC790\uAC00 \uC9C0\uAE08 \uD574\uC57C \uD560 \uB2E4\uC74C \uB2E8\uACC4\uB97C \uB3CC\uB824\uC90D\uB2C8\uB2E4. AI \uD074\uB77C\uC774\uC5B8\uD2B8\uAC00 \uC5F0\uACB0\uB41C \uC9C1\uD6C4 \uAC00\uC7A5 \uBA3C\uC800 \uD638\uCD9C\uD558\uC138\uC694. \uD504\uB85C\uD544 \uC644\uC131\uB3C4(0~100)\uB3C4 \uD3EC\uD568\uB429\uB2C8\uB2E4.",
|
|
6427
|
+
inputSchema: {},
|
|
6428
|
+
readOnly: true,
|
|
6429
|
+
handler: () => {
|
|
6430
|
+
const s = getOnboardingStatus();
|
|
6431
|
+
const lines = s.next_steps.map((t, i) => `${i + 1}. ${t}`).join("\n");
|
|
6432
|
+
return ok(
|
|
6433
|
+
`\uC628\uBCF4\uB529 ${s.completed ? "\uC644\uB8CC\uB428" : "\uC9C4\uD589 \uD544\uC694"} \xB7 \uD504\uB85C\uD544 \uC644\uC131\uB3C4 ${s.profile_completeness}%
|
|
6434
|
+
|
|
6435
|
+
\uB2E4\uC74C \uB2E8\uACC4:
|
|
6436
|
+
${lines}`,
|
|
6437
|
+
s
|
|
6438
|
+
);
|
|
6439
|
+
}
|
|
6440
|
+
},
|
|
6441
|
+
{
|
|
6442
|
+
name: "start_onboarding",
|
|
6443
|
+
title: "\uC628\uBCF4\uB529 \uC2DC\uC791 \uC548\uB0B4",
|
|
6444
|
+
description: '\uAC00\uC774\uB4DC\uD615 \uC628\uBCF4\uB529\uC744 \uC2DC\uC791\uD569\uB2C8\uB2E4. \uD604\uC7AC \uC0C1\uD0DC\uC640 \uD568\uAED8, AI\uAC00 \uC0AC\uC6A9\uC790\uB97C \uC5B4\uB5BB\uAC8C \uC548\uB0B4\uD558\uBA74 \uB418\uB294\uC9C0(\uD504\uB85C\uD544 \uC785\uB825 \u2192 \uC774\uB825\uC11C/\uC790\uC18C\uC11C \uCD94\uAC00 \u2192 \uB300\uC2DC\uBCF4\uB4DC \uC5F4\uAE30)\uC5D0 \uB300\uD55C \uB2E8\uACC4\uBCC4 \uC9C0\uCE68\uC744 \uB3CC\uB824\uC90D\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uAC00 "\uC2DC\uC791\uD558\uC790/\uC124\uC815 \uB3C4\uC640\uC918"\uB77C\uACE0 \uD560 \uB54C \uC0AC\uC6A9\uD558\uC138\uC694.',
|
|
6445
|
+
inputSchema: {},
|
|
6446
|
+
readOnly: true,
|
|
6447
|
+
handler: () => {
|
|
6448
|
+
const s = getOnboardingStatus();
|
|
6449
|
+
return ok(
|
|
6450
|
+
`\uC628\uBCF4\uB529\uC744 \uC2DC\uC791\uD569\uB2C8\uB2E4. \uC544\uB798 \uC9C0\uCE68\uC744 \uB530\uB77C \uC0AC\uC6A9\uC790\uB97C \uC548\uB0B4\uD558\uC138\uC694.
|
|
6451
|
+
|
|
6452
|
+
${ONBOARDING_PROMPT}`,
|
|
6453
|
+
{ status: s, guide: ONBOARDING_PROMPT }
|
|
6454
|
+
);
|
|
6455
|
+
}
|
|
6456
|
+
},
|
|
6457
|
+
/* --------------------------------------------------------------- profile */
|
|
6458
|
+
{
|
|
6459
|
+
name: "get_profile",
|
|
6460
|
+
title: "\uD504\uB85C\uD544 \uC870\uD68C",
|
|
6461
|
+
description: "\uC0AC\uC6A9\uC790\uC758 \uAE30\uBCF8 \uD504\uB85C\uD544(\uC774\uB984, \uD55C \uC904 \uC18C\uAC1C, \uC694\uC57D, \uD76C\uB9DD \uC9C1\uBB34/\uC870\uAC74, \uC120\uD638 \uBB38\uCCB4, \uAC15\uC870 \uD3EC\uC778\uD2B8, \uB9C1\uD06C)\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4.",
|
|
6462
|
+
inputSchema: {},
|
|
6463
|
+
readOnly: true,
|
|
6464
|
+
handler: () => {
|
|
6465
|
+
const p = profileRepo.get();
|
|
6466
|
+
return p ? ok(`\uD504\uB85C\uD544: ${p.name ?? "(\uC774\uB984 \uC5C6\uC74C)"}`, p) : ok("\uC544\uC9C1 \uC800\uC7A5\uB41C \uD504\uB85C\uD544\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. save_profile\uB85C \uBA3C\uC800 \uC800\uC7A5\uD558\uC138\uC694.", null);
|
|
6467
|
+
}
|
|
6468
|
+
},
|
|
6469
|
+
{
|
|
6470
|
+
name: "save_profile",
|
|
6471
|
+
title: "\uD504\uB85C\uD544 \uC800\uC7A5",
|
|
6472
|
+
description: "\uC0AC\uC6A9\uC790 \uD504\uB85C\uD544\uC744 \uC800\uC7A5\uD569\uB2C8\uB2E4. \uC804\uB2EC\uD55C \uD544\uB4DC\uB9CC \uAC31\uC2E0\uB418\uACE0 \uB098\uBA38\uC9C0\uB294 \uC720\uC9C0\uB429\uB2C8\uB2E4(\uBD80\uBD84 \uC800\uC7A5 \uC548\uC804). \uC774\uB825\uC11C\uB97C \uAD6C\uC870\uD654\uD55C \uB4A4 \uC774\uB984/\uD55C \uC904 \uC18C\uAC1C/\uD76C\uB9DD \uC9C1\uBB34/\uC120\uD638 \uBB38\uCCB4/\uAC15\uC870 \uD3EC\uC778\uD2B8 \uB4F1\uC744 \uCC44\uC6CC \uC800\uC7A5\uD558\uC138\uC694. \uC120\uD638 \uBB38\uCCB4(preferred_tone)\uC640 \uAC15\uC870 \uD3EC\uC778\uD2B8(emphasis_points)\uB294 \uC774\uD6C4 \uC790\uAE30\uC18C\uAC1C\uC11C \uC791\uC131 \uD488\uC9C8\uC5D0 \uC9C1\uC811 \uC601\uD5A5\uC744 \uC90D\uB2C8\uB2E4.",
|
|
6473
|
+
inputSchema: ProfileInputSchema.shape,
|
|
6474
|
+
handler: (args) => {
|
|
6475
|
+
const p = saveProfile(args);
|
|
6476
|
+
return ok(`\uD504\uB85C\uD544\uC744 \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4: ${p.name ?? ""}`.trim(), p);
|
|
6477
|
+
}
|
|
6478
|
+
},
|
|
6479
|
+
{
|
|
6480
|
+
name: "update_profile",
|
|
6481
|
+
title: "\uD504\uB85C\uD544 \uBD80\uBD84 \uC218\uC815",
|
|
6482
|
+
description: "\uAE30\uC874 \uD504\uB85C\uD544\uC744 \uC720\uC9C0\uD558\uBA74\uC11C \uC77C\uBD80 \uD544\uB4DC\uB9CC \uC218\uC815\uD569\uB2C8\uB2E4. save_profile\uACFC \uB3D9\uC77C\uD558\uAC8C \uB3D9\uC791\uD558\uBA70, \uD55C\uB450 \uAC1C \uD56D\uBAA9\uB9CC \uBC14\uAFC0 \uB54C \uC758\uBBF8\uB97C \uBD84\uBA85\uD788 \uD558\uAE30 \uC704\uD574 \uC0AC\uC6A9\uD569\uB2C8\uB2E4.",
|
|
6483
|
+
inputSchema: ProfileInputSchema.partial().shape,
|
|
6484
|
+
handler: (args) => {
|
|
6485
|
+
const p = saveProfile(args);
|
|
6486
|
+
return ok("\uD504\uB85C\uD544\uC744 \uC218\uC815\uD588\uC2B5\uB2C8\uB2E4.", p);
|
|
6487
|
+
}
|
|
6488
|
+
},
|
|
6489
|
+
/* --------------------------------------------------------------- resumes */
|
|
6490
|
+
{
|
|
6491
|
+
name: "add_resume",
|
|
6492
|
+
title: "\uC774\uB825\uC11C/\uACBD\uB825\uAE30\uC220\uC11C \uCD94\uAC00",
|
|
6493
|
+
description: "\uC774\uB825\uC11C, \uACBD\uB825\uAE30\uC220\uC11C, \uD3EC\uD2B8\uD3F4\uB9AC\uC624 \uB4F1 \uBB38\uC11C\uB97C \uC800\uC7A5\uD569\uB2C8\uB2E4. content\uC5D0\uB294 \uC0AC\uC6A9\uC790\uAC00 \uC5C5\uB85C\uB4DC/\uBD99\uC5EC\uB123\uC740 \uD14D\uC2A4\uD2B8(\uB610\uB294 \uC815\uB9AC\uD55C Markdown)\uB97C \uB123\uC73C\uC138\uC694. kind \uAE30\uBCF8\uAC12\uC740 resume\uC785\uB2C8\uB2E4. is_primary=true\uB85C \uB300\uD45C \uBB38\uC11C\uB97C \uC9C0\uC815\uD558\uBA74 get_application_context\uC758 primary_resume\uB85C \uB178\uCD9C\uB429\uB2C8\uB2E4.",
|
|
6494
|
+
inputSchema: {
|
|
6495
|
+
title: external_exports.string().describe("\uBB38\uC11C \uC81C\uBAA9"),
|
|
6496
|
+
content: external_exports.string().describe("\uBCF8\uBB38 (Markdown/\uC77C\uBC18 \uD14D\uC2A4\uD2B8)"),
|
|
6497
|
+
kind: external_exports.enum(DOCUMENT_KINDS).optional().describe("resume(\uC774\uB825\uC11C)/career_description(\uACBD\uB825\uAE30\uC220\uC11C)/portfolio/other"),
|
|
6498
|
+
source: external_exports.enum(CONTENT_SOURCES).optional(),
|
|
6499
|
+
is_primary: external_exports.boolean().optional().describe("\uB300\uD45C \uBB38\uC11C \uC5EC\uBD80"),
|
|
6500
|
+
tags: external_exports.array(external_exports.string()).optional()
|
|
6501
|
+
},
|
|
6502
|
+
handler: (args) => {
|
|
6503
|
+
const doc = addResume(args);
|
|
6504
|
+
return ok(`'${doc.title}' \uBB38\uC11C\uB97C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4.`, doc);
|
|
6505
|
+
}
|
|
6506
|
+
},
|
|
6507
|
+
{
|
|
6508
|
+
name: "get_resumes",
|
|
6509
|
+
title: "\uC774\uB825\uC11C \uBAA9\uB85D \uC870\uD68C",
|
|
6510
|
+
description: "\uC800\uC7A5\uB41C \uC774\uB825\uC11C/\uACBD\uB825\uAE30\uC220\uC11C \uB4F1 \uBB38\uC11C\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. kind\uB85C \uC885\uB958\uB97C \uD544\uD130\uB9C1\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
|
|
6511
|
+
inputSchema: { kind: external_exports.enum(DOCUMENT_KINDS).optional() },
|
|
6512
|
+
readOnly: true,
|
|
6513
|
+
handler: (args) => {
|
|
6514
|
+
const docs = documentRepo.list(args?.kind);
|
|
6515
|
+
return ok(`\uBB38\uC11C ${docs.length}\uAC74`, docs);
|
|
6516
|
+
}
|
|
6517
|
+
},
|
|
6518
|
+
/* ----------------------------------------------------------- experiences */
|
|
6519
|
+
{
|
|
6520
|
+
name: "add_experience",
|
|
6521
|
+
title: "\uACBD\uB825 \uCD94\uAC00",
|
|
6522
|
+
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.",
|
|
6523
|
+
inputSchema: ExperienceInputSchema.shape,
|
|
6524
|
+
handler: (args) => {
|
|
6525
|
+
const exp = addExperience(args);
|
|
6526
|
+
return ok(`\uACBD\uB825 '${exp.company}${exp.role ? ` \xB7 ${exp.role}` : ""}'\uC744(\uB97C) \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, exp);
|
|
6527
|
+
}
|
|
6528
|
+
},
|
|
6529
|
+
{
|
|
6530
|
+
name: "get_experiences",
|
|
6531
|
+
title: "\uACBD\uB825 \uBAA9\uB85D \uC870\uD68C",
|
|
6532
|
+
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.)",
|
|
6533
|
+
inputSchema: {},
|
|
6534
|
+
readOnly: true,
|
|
6535
|
+
handler: () => {
|
|
6536
|
+
const list = experienceRepo.list();
|
|
6537
|
+
return ok(`\uACBD\uB825 ${list.length}\uAC74`, list);
|
|
6538
|
+
}
|
|
6539
|
+
},
|
|
6540
|
+
/* -------------------------------------------------------------- projects */
|
|
6541
|
+
{
|
|
6542
|
+
name: "add_project",
|
|
6543
|
+
title: "\uD504\uB85C\uC81D\uD2B8 \uCD94\uAC00",
|
|
6544
|
+
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.",
|
|
6545
|
+
inputSchema: ProjectInputSchema.shape,
|
|
6546
|
+
handler: (args) => {
|
|
6547
|
+
const prj = addProject(args);
|
|
6548
|
+
return ok(`\uD504\uB85C\uC81D\uD2B8 '${prj.name}'\uC744(\uB97C) \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, prj);
|
|
6549
|
+
}
|
|
6550
|
+
},
|
|
6551
|
+
{
|
|
6552
|
+
name: "get_projects",
|
|
6553
|
+
title: "\uD504\uB85C\uC81D\uD2B8 \uBAA9\uB85D \uC870\uD68C",
|
|
6554
|
+
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.)",
|
|
6555
|
+
inputSchema: {},
|
|
6556
|
+
readOnly: true,
|
|
6557
|
+
handler: () => {
|
|
6558
|
+
const list = projectRepo.list();
|
|
6559
|
+
return ok(`\uD504\uB85C\uC81D\uD2B8 ${list.length}\uAC74`, list);
|
|
6560
|
+
}
|
|
6561
|
+
},
|
|
6562
|
+
/* ---------------------------------------------------------------- skills */
|
|
6563
|
+
{
|
|
6564
|
+
name: "add_skill",
|
|
6565
|
+
title: "\uAE30\uC220\uC2A4\uD0DD \uCD94\uAC00",
|
|
6566
|
+
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.",
|
|
6567
|
+
inputSchema: SkillInputSchema.shape,
|
|
6568
|
+
handler: (args) => {
|
|
6569
|
+
const skill = addSkill(args);
|
|
6570
|
+
return ok(`\uAE30\uC220 '${skill.name}'\uC744(\uB97C) \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, skill);
|
|
6571
|
+
}
|
|
6572
|
+
},
|
|
6573
|
+
{
|
|
6574
|
+
name: "get_skills",
|
|
6575
|
+
title: "\uAE30\uC220\uC2A4\uD0DD \uBAA9\uB85D \uC870\uD68C",
|
|
6576
|
+
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.)",
|
|
6577
|
+
inputSchema: {},
|
|
6578
|
+
readOnly: true,
|
|
6579
|
+
handler: () => {
|
|
6580
|
+
const list = skillRepo.list();
|
|
6581
|
+
return ok(`\uAE30\uC220 ${list.length}\uAC74`, list);
|
|
6582
|
+
}
|
|
6583
|
+
},
|
|
6584
|
+
/* ---------------------------------------------------------- cover letters */
|
|
6585
|
+
{
|
|
6586
|
+
name: "add_cover_letter",
|
|
6587
|
+
title: "\uC790\uAE30\uC18C\uAC1C\uC11C \uCD94\uAC00",
|
|
6588
|
+
description: '\uAE30\uC874 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uB4F1\uB85D\uD569\uB2C8\uB2E4. content\uB97C \uD568\uAED8 \uC8FC\uBA74 \uCCAB \uBC88\uC9F8 \uBC84\uC804(v1)\uC73C\uB85C \uC800\uC7A5\uB429\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uAC00 \uC774\uBBF8 \uC4F4 \uC790\uC18C\uC11C\uB97C \uD559\uC2B5\uC6A9\uC73C\uB85C \uBCF4\uAD00\uD560 \uB54C \uC0AC\uC6A9\uD558\uC138\uC694. \uC0C8 \uACF5\uACE0\uC6A9 \uC790\uC18C\uC11C\uB97C "\uC0DD\uC131"\uD574\uC11C \uC800\uC7A5\uD560 \uB54C\uB294 save_cover_letter_version\uC744 \uC0AC\uC6A9\uD558\uB294 \uD3B8\uC774 \uBC84\uC804 \uAD00\uB9AC\uC5D0 \uC88B\uC2B5\uB2C8\uB2E4.',
|
|
6589
|
+
inputSchema: {
|
|
6590
|
+
title: external_exports.string(),
|
|
6591
|
+
content: external_exports.string().optional(),
|
|
6592
|
+
job_id: external_exports.string().optional(),
|
|
6593
|
+
is_primary: external_exports.boolean().optional(),
|
|
6594
|
+
note: external_exports.string().optional(),
|
|
6595
|
+
source: external_exports.enum(CONTENT_SOURCES).optional()
|
|
6596
|
+
},
|
|
6597
|
+
handler: (args) => {
|
|
6598
|
+
if (args.content) {
|
|
6599
|
+
const { coverLetter, version } = saveCoverLetterVersion({
|
|
6600
|
+
title: args.title,
|
|
6601
|
+
job_id: args.job_id,
|
|
6602
|
+
content: args.content,
|
|
6603
|
+
note: args.note,
|
|
6604
|
+
source: args.source ?? "manual"
|
|
6605
|
+
});
|
|
6606
|
+
if (args.is_primary) coverLetterRepo.setPrimary(coverLetter.id);
|
|
6607
|
+
return ok(`'${coverLetter.title}' \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4 (v${version.version_no}).`, coverLetter);
|
|
6608
|
+
}
|
|
6609
|
+
const cl = coverLetterRepo.create({ title: args.title, job_id: args.job_id ?? null, is_primary: args.is_primary });
|
|
6610
|
+
return ok(`'${cl.title}' \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC0DD\uC131\uD588\uC2B5\uB2C8\uB2E4.`, cl);
|
|
6611
|
+
}
|
|
6612
|
+
},
|
|
6613
|
+
{
|
|
6614
|
+
name: "get_cover_letters",
|
|
6615
|
+
title: "\uC790\uAE30\uC18C\uAC1C\uC11C \uBAA9\uB85D \uC870\uD68C",
|
|
6616
|
+
description: "\uC800\uC7A5\uB41C \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. job_id\uB97C \uC8FC\uBA74 \uD574\uB2F9 \uACF5\uACE0\uC5D0 \uC5F0\uACB0\uB41C \uC790\uC18C\uC11C\uB9CC, \uC5C6\uC73C\uBA74 \uC804\uCCB4\uB97C \uB3CC\uB824\uC90D\uB2C8\uB2E4. \uAC01 \uD56D\uBAA9\uC740 \uD604\uC7AC \uBC84\uC804 \uB0B4\uC6A9\uACFC \uBC84\uC804 \uC218\uB97C \uD3EC\uD568\uD569\uB2C8\uB2E4.",
|
|
6617
|
+
inputSchema: { job_id: external_exports.string().optional() },
|
|
6618
|
+
readOnly: true,
|
|
6619
|
+
handler: (args) => {
|
|
6620
|
+
const list = args?.job_id ? coverLetterRepo.listByJob(args.job_id) : coverLetterRepo.list();
|
|
6621
|
+
return ok(`\uC790\uAE30\uC18C\uAC1C\uC11C ${list.length}\uAC74`, list);
|
|
6622
|
+
}
|
|
6623
|
+
},
|
|
6624
|
+
{
|
|
6625
|
+
name: "save_cover_letter_version",
|
|
6626
|
+
title: "\uC790\uAE30\uC18C\uAC1C\uC11C \uBC84\uC804 \uC800\uC7A5",
|
|
6627
|
+
description: '\uC790\uAE30\uC18C\uAC1C\uC11C\uC758 \uC0C8 \uBC84\uC804\uC744 \uC800\uC7A5\uD569\uB2C8\uB2E4. CareerMate \uC790\uC18C\uC11C \uC791\uC131 \uC6CC\uD06C\uD50C\uB85C\uC6B0\uC758 \uD575\uC2EC \uC800\uC7A5 \uB2E8\uACC4\uC785\uB2C8\uB2E4. cover_letter_id\uB97C \uC8FC\uBA74 \uAE30\uC874 \uC790\uC18C\uC11C\uC5D0 \uC0C8 \uBC84\uC804\uC744 \uCD94\uAC00\uD558\uACE0, \uC5C6\uC73C\uBA74 \uC0C8 \uC790\uC18C\uC11C\uB97C \uB9CC\uB4E4\uC5B4 v1\uB85C \uC800\uC7A5\uD569\uB2C8\uB2E4(\uC774\uB54C title \uAD8C\uC7A5). note\uC5D0 \uC774 \uBC84\uC804\uC758 \uBCC0\uACBD \uC694\uC57D(\uC608: "\uC9C0\uC6D0\uB3D9\uAE30 \uBCF4\uAC15")\uC744 \uB0A8\uAE30\uBA74 \uC0AC\uC6A9\uC790\uAC00 \uB300\uC2DC\uBCF4\uB4DC\uC5D0\uC11C \uBC84\uC804 \uD788\uC2A4\uD1A0\uB9AC\uB97C \uC774\uD574\uD558\uAE30 \uC27D\uC2B5\uB2C8\uB2E4. job_id\uB85C \uACF5\uACE0\uC5D0 \uC5F0\uACB0\uD558\uBA74 \uC9C0\uC6D0 \uD56D\uBAA9\uACFC \uC790\uB3D9 \uC5F0\uACB0\uB429\uB2C8\uB2E4.',
|
|
6628
|
+
inputSchema: CoverLetterVersionInputSchema.shape,
|
|
6629
|
+
handler: (args) => {
|
|
6630
|
+
const { coverLetter, version } = saveCoverLetterVersion(args);
|
|
6631
|
+
return ok(`'${coverLetter.title}' v${version.version_no}\uC744 \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4.`, { coverLetter, version });
|
|
6632
|
+
}
|
|
6633
|
+
},
|
|
6634
|
+
/* ------------------------------------------------------------------ jobs */
|
|
6635
|
+
{
|
|
6636
|
+
name: "save_job_posting",
|
|
6637
|
+
title: "\uCC44\uC6A9\uACF5\uACE0 \uC800\uC7A5",
|
|
6638
|
+
description: "\uCC44\uC6A9\uACF5\uACE0\uB97C \uC800\uC7A5\uD569\uB2C8\uB2E4(\uC788\uC73C\uBA74 \uAC31\uC2E0). company\uC640 position\uC740 \uD544\uC218\uC785\uB2C8\uB2E4. \uACF5\uACE0 \uC6D0\uBB38\uC740 description\uC5D0, \uD575\uC2EC \uC790\uACA9\uC694\uAC74/\uC6B0\uB300\uC0AC\uD56D\uC740 requirements \uBC30\uC5F4\uC5D0, \uD575\uC2EC \uD0A4\uC6CC\uB4DC\uB294 keywords\uC5D0 \uC815\uB9AC\uD574 \uB123\uC73C\uBA74 \uC774\uD6C4 \uC801\uD569\uB3C4 \uBD84\uC11D/\uC790\uC18C\uC11C \uC791\uC131\uC5D0 \uD65C\uC6A9\uB429\uB2C8\uB2E4. \uAC19\uC740 url\uC774\uBA74 \uC911\uBCF5 \uC0DD\uC131 \uC5C6\uC774 \uAC31\uC2E0\uB429\uB2C8\uB2E4. \uC800\uC7A5 \uC2DC \uC9C0\uC6D0(application) \uD56D\uBAA9\uC774 \uC790\uB3D9\uC73C\uB85C \uC0DD\uC131\uB429\uB2C8\uB2E4.",
|
|
6639
|
+
inputSchema: JobInputSchema.shape,
|
|
6640
|
+
handler: (args) => {
|
|
6641
|
+
const { job } = saveJobPosting(args);
|
|
6642
|
+
return ok(`'${job.company} \xB7 ${job.position}' \uACF5\uACE0\uB97C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4. (job_id: ${job.id})`, job);
|
|
6643
|
+
}
|
|
6644
|
+
},
|
|
6645
|
+
{
|
|
6646
|
+
name: "get_job_posting",
|
|
6647
|
+
title: "\uCC44\uC6A9\uACF5\uACE0 \uC0C1\uC138 \uC870\uD68C",
|
|
6648
|
+
description: "\uACF5\uACE0 1\uAC74\uC758 \uC0C1\uC138\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. \uACF5\uACE0 \uC815\uBCF4\uC640 \uD568\uAED8 \uC801\uD569\uB3C4 \uBD84\uC11D, \uC9C0\uC6D0 \uC0C1\uD0DC, \uC5F0\uACB0\uB41C \uC790\uAE30\uC18C\uAC1C\uC11C, \uBA74\uC811 \uC900\uBE44 \uC790\uB8CC\uB97C \uD55C \uBC88\uC5D0 \uB3CC\uB824\uC90D\uB2C8\uB2E4.",
|
|
6649
|
+
inputSchema: { job_id: external_exports.string() },
|
|
6650
|
+
readOnly: true,
|
|
6651
|
+
handler: (args) => {
|
|
6652
|
+
const job = jobRepo.get(args.job_id);
|
|
6653
|
+
if (!job) return fail(`\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${args.job_id}`);
|
|
6654
|
+
return ok(`${job.company} \xB7 ${job.position}`, {
|
|
6655
|
+
job: jobWithMeta(job),
|
|
6656
|
+
fit: fitRepo.getByJob(job.id),
|
|
6657
|
+
application: applicationRepo.getByJob(job.id),
|
|
6658
|
+
cover_letters: coverLetterRepo.listByJob(job.id),
|
|
6659
|
+
interview: interviewRepo.getByJob(job.id)
|
|
6660
|
+
});
|
|
6661
|
+
}
|
|
6662
|
+
},
|
|
6663
|
+
{
|
|
6664
|
+
name: "list_jobs",
|
|
6665
|
+
title: "\uC800\uC7A5\uB41C \uACF5\uACE0 \uBAA9\uB85D",
|
|
6666
|
+
description: "\uC800\uC7A5\uB41C \uBAA8\uB4E0 \uCC44\uC6A9\uACF5\uACE0\uB97C \uC9C0\uC6D0 \uC0C1\uD0DC\uC640 \uC801\uD569\uB3C4 \uC810\uC218\uC640 \uD568\uAED8 \uBAA9\uB85D\uC73C\uB85C \uB3CC\uB824\uC90D\uB2C8\uB2E4.",
|
|
6667
|
+
inputSchema: {},
|
|
6668
|
+
readOnly: true,
|
|
6669
|
+
handler: () => {
|
|
6670
|
+
const jobs = jobRepo.list().map(jobWithMeta);
|
|
6671
|
+
return ok(`\uACF5\uACE0 ${jobs.length}\uAC74`, jobs);
|
|
6672
|
+
}
|
|
6673
|
+
},
|
|
6674
|
+
{
|
|
6675
|
+
name: "parse_job_posting",
|
|
6676
|
+
title: "\uACF5\uACE0 \uD14D\uC2A4\uD2B8 \uC815\uB9AC",
|
|
6677
|
+
description: "\uBD99\uC5EC\uB123\uC740 \uACF5\uACE0 \uC6D0\uBB38(\uB610\uB294 \uD398\uC774\uC9C0 \uD14D\uC2A4\uD2B8)\uC744 \uC815\uB9AC\uD574 \uD68C\uC0AC\uBA85/\uC9C1\uBB34/\uB9C8\uAC10\uC77C \uCD94\uC815\uACFC \uAE30\uC220 \uD0A4\uC6CC\uB4DC\uB97C \uCD94\uCD9C\uD569\uB2C8\uB2E4. save_job_posting \uC804\uC5D0 \uBE60\uB974\uAC8C \uAD6C\uC870\uD654\uD560 \uB54C \uBCF4\uC870\uC801\uC73C\uB85C \uC0AC\uC6A9\uD558\uC138\uC694. (\uBD84\uC11D/\uD310\uB2E8\uC740 AI\uAC00 \uC9C1\uC811 \uD558\uACE0, \uC774 \uB3C4\uAD6C\uB294 \uD14D\uC2A4\uD2B8 \uC815\uB9AC\uB9CC \uD569\uB2C8\uB2E4.)",
|
|
6678
|
+
inputSchema: { raw: external_exports.string().describe("\uACF5\uACE0 \uC6D0\uBB38 \uD14D\uC2A4\uD2B8") },
|
|
6679
|
+
readOnly: true,
|
|
6680
|
+
handler: (args) => {
|
|
6681
|
+
const cleaned = cleanJobPosting(args.raw ?? "");
|
|
6682
|
+
return ok("\uACF5\uACE0 \uD14D\uC2A4\uD2B8\uB97C \uC815\uB9AC\uD588\uC2B5\uB2C8\uB2E4.", cleaned);
|
|
6683
|
+
}
|
|
6684
|
+
},
|
|
6685
|
+
/* ----------------------------------------------------- application context */
|
|
6686
|
+
{
|
|
6687
|
+
name: "get_application_context",
|
|
6688
|
+
title: "\uC9C0\uC6D0 \uB9E5\uB77D \uD55C \uBC88\uC5D0 \uAC00\uC838\uC624\uAE30 \u2B50",
|
|
6689
|
+
description: "\u2605 \uAC00\uC7A5 \uC911\uC694\uD55C \uB3C4\uAD6C. \uACF5\uACE0\uB97C \uBD84\uC11D\uD558\uAC70\uB098 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC791\uC131\uD558\uAE30 \uC804\uC5D0 \uBC18\uB4DC\uC2DC \uBA3C\uC800 \uD638\uCD9C\uD558\uC138\uC694. \uD55C \uBC88\uC758 \uD638\uCD9C\uB85C \uB2E4\uC74C\uC744 \uBAA8\uB450 \uB3CC\uB824\uC90D\uB2C8\uB2E4: \uC0AC\uC6A9\uC790 \uD504\uB85C\uD544, \uB300\uD45C \uC774\uB825\uC11C, \uC804\uCCB4 \uACBD\uB825, \uD504\uB85C\uC81D\uD2B8, \uAE30\uC220\uC2A4\uD0DD, \uAE30\uC874 \uC790\uAE30\uC18C\uAC1C\uC11C, \uCD5C\uADFC \uC9C0\uC6D0 \uC774\uB825, (job_id\uB97C \uC8FC\uBA74) \uB300\uC0C1 \uACF5\uACE0\uC640 \uC774\uC804 \uC801\uD569\uB3C4 \uBD84\uC11D, \uAC19\uC740 \uD68C\uC0AC/\uC9C1\uBB34 \uAD00\uB828 \uC774\uC804 \uAE30\uB85D, \uADF8\uB9AC\uACE0 \uC0AC\uC6A9\uC790\uC758 \uC120\uD638 \uBB38\uCCB4\xB7\uAC15\uC870 \uD3EC\uC778\uD2B8. \uC774 \uB370\uC774\uD130\uB97C \uADFC\uAC70\uB85C \uBD84\uC11D/\uC791\uC131\uD558\uACE0, \uACB0\uACFC\uB294 save_fit_analysis / save_cover_letter_version\uC73C\uB85C \uB2E4\uC2DC \uC800\uC7A5\uD558\uC138\uC694. CareerMate\uB294 \uB370\uC774\uD130\uB97C \uC81C\uACF5\uD560 \uBFD0, \uBD84\uC11D/\uC791\uC131\uC740 \uB2F9\uC2E0(AI)\uC774 \uC218\uD589\uD569\uB2C8\uB2E4.",
|
|
6690
|
+
inputSchema: { job_id: external_exports.string().optional().describe("\uD2B9\uC815 \uACF5\uACE0 \uAE30\uC900\uC73C\uB85C \uB9E5\uB77D\uC744 \uBAA8\uC744 \uB54C") },
|
|
6691
|
+
readOnly: true,
|
|
6692
|
+
handler: (args) => {
|
|
6693
|
+
const ctx = getApplicationContext({ job_id: args?.job_id });
|
|
6694
|
+
const summary = [
|
|
6695
|
+
`\uD504\uB85C\uD544: ${ctx.profile?.name ?? "\uBBF8\uC785\uB825"}`,
|
|
6696
|
+
`\uC774\uB825\uC11C ${ctx.resumes.length} \xB7 \uACBD\uB825 ${ctx.experiences.length} \xB7 \uD504\uB85C\uC81D\uD2B8 ${ctx.projects.length} \xB7 \uC2A4\uD0AC ${ctx.skills.length} \xB7 \uC790\uC18C\uC11C ${ctx.cover_letters.length}`,
|
|
6697
|
+
ctx.job ? `\uB300\uC0C1 \uACF5\uACE0: ${ctx.job.company} \xB7 ${ctx.job.position}` : "\uB300\uC0C1 \uACF5\uACE0 \uC5C6\uC74C",
|
|
6698
|
+
ctx.writing_preferences.preferred_tone ? `\uC120\uD638 \uBB38\uCCB4: ${ctx.writing_preferences.preferred_tone}` : "",
|
|
6699
|
+
"\u{1F4A1} \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC4F8 \uB54C\uB294 get_writing_style_guide\uB85C AI \uD2F0 \uC548 \uB098\uB294 \uAE00\uC4F0\uAE30 \uADDC\uCE59\uC744 \uD568\uAED8 \uC801\uC6A9\uD558\uC138\uC694."
|
|
6700
|
+
].filter(Boolean).join("\n");
|
|
6701
|
+
return ok(summary, ctx);
|
|
6702
|
+
}
|
|
6703
|
+
},
|
|
6704
|
+
{
|
|
6705
|
+
name: "save_fit_analysis",
|
|
6706
|
+
title: "\uC801\uD569\uB3C4 \uBD84\uC11D \uACB0\uACFC \uC800\uC7A5",
|
|
6707
|
+
description: "\uACF5\uACE0\uC640 \uC0AC\uC6A9\uC790 \uC815\uBCF4\uB97C \uBE44\uAD50\uD574 \uB2F9\uC2E0(AI)\uC774 \uB3C4\uCD9C\uD55C \uC801\uD569\uB3C4 \uBD84\uC11D\uC744 \uC800\uC7A5\uD569\uB2C8\uB2E4. job_id\uB294 \uD544\uC218\uC774\uBA70 \uBA3C\uC800 save_job_posting\uC73C\uB85C \uACF5\uACE0\uAC00 \uC800\uC7A5\uB418\uC5B4 \uC788\uC5B4\uC57C \uD569\uB2C8\uB2E4. score(0~100), summary, strengths(\uAC15\uC810), gaps(\uBCF4\uC644 \uD544\uC694), matched/missing_keywords, recommendations(\uC790\uC18C\uC11C\xB7\uC9C0\uC6D0 \uC804\uB7B5 \uC81C\uC548)\uB97C \uCC44\uC6CC \uC800\uC7A5\uD558\uC138\uC694. \uAC19\uC740 \uACF5\uACE0\uC5D0 \uB2E4\uC2DC \uC800\uC7A5\uD558\uBA74 \uAC31\uC2E0\uB429\uB2C8\uB2E4.",
|
|
6708
|
+
inputSchema: FitAnalysisInputSchema.shape,
|
|
6709
|
+
handler: (args) => {
|
|
6710
|
+
try {
|
|
6711
|
+
const fit = saveFitAnalysis(args);
|
|
6712
|
+
return ok(`\uC801\uD569\uB3C4 \uBD84\uC11D\uC744 \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4${fit.score != null ? ` (${fit.score}\uC810)` : ""}.`, fit);
|
|
6713
|
+
} catch (e) {
|
|
6714
|
+
return fail(e instanceof Error ? e.message : "\uC800\uC7A5 \uC2E4\uD328");
|
|
6715
|
+
}
|
|
6716
|
+
}
|
|
6717
|
+
},
|
|
6718
|
+
/* ------------------------------------------------- application status flow */
|
|
6719
|
+
{
|
|
6720
|
+
name: "update_application_status",
|
|
6721
|
+
title: "\uC9C0\uC6D0 \uC0C1\uD0DC \uBCC0\uACBD",
|
|
6722
|
+
description: `\uC9C0\uC6D0 \uC0C1\uD0DC\uB97C \uBCC0\uACBD\uD569\uB2C8\uB2E4. \uAC00\uB2A5\uD55C \uC0C1\uD0DC: ${STATUS_LIST}. '\uC11C\uB958 \uD569\uACA9(document_passed)' \uC774\uC0C1\uC73C\uB85C \uBC14\uB00C\uBA74 \uBA74\uC811 \uC900\uBE44\uB97C \uC81C\uC548\uD558\uB294 \uD78C\uD2B8\uB97C \uD568\uAED8 \uB3CC\uB824\uC90D\uB2C8\uB2E4. note\uC5D0 \uBCC0\uACBD \uC0AC\uC720\uB97C \uB0A8\uAE38 \uC218 \uC788\uC2B5\uB2C8\uB2E4.`,
|
|
6723
|
+
inputSchema: {
|
|
6724
|
+
job_id: external_exports.string(),
|
|
6725
|
+
status: external_exports.enum(APPLICATION_STATUSES),
|
|
6726
|
+
note: external_exports.string().optional()
|
|
6727
|
+
},
|
|
6728
|
+
handler: (args) => {
|
|
6729
|
+
try {
|
|
6730
|
+
const res = updateApplicationStatus(args.job_id, args.status, args.note);
|
|
6731
|
+
return ok(
|
|
6732
|
+
`\uC0C1\uD0DC\uB97C '${APPLICATION_STATUS_LABELS[args.status]}'(\uC73C)\uB85C \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.${res.hint ? `
|
|
6733
|
+
|
|
6734
|
+
\u{1F4A1} ${res.hint}` : ""}`,
|
|
6735
|
+
res
|
|
6736
|
+
);
|
|
6737
|
+
} catch (e) {
|
|
6738
|
+
return fail(e instanceof Error ? e.message : "\uBCC0\uACBD \uC2E4\uD328");
|
|
6739
|
+
}
|
|
6740
|
+
}
|
|
6741
|
+
},
|
|
6742
|
+
/* ---------------------------------------------------------- interview prep */
|
|
6743
|
+
{
|
|
6744
|
+
name: "save_interview_prep",
|
|
6745
|
+
title: "\uBA74\uC811 \uC900\uBE44 \uC790\uB8CC \uC800\uC7A5",
|
|
6746
|
+
description: "\uC608\uC0C1 \uBA74\uC811 \uC9C8\uBB38, \uAF2C\uB9AC \uC9C8\uBB38, STAR \uB2F5\uBCC0 \uAC00\uC774\uB4DC, 1\uBD84 \uC790\uAE30\uC18C\uAC1C \uCD08\uC548 \uB4F1 \uBA74\uC811 \uC900\uBE44 \uC790\uB8CC\uB97C \uC800\uC7A5\uD569\uB2C8\uB2E4. \uBCF4\uD1B5 \uC9C0\uC6D0 \uC0C1\uD0DC\uAC00 \uC11C\uB958 \uD569\uACA9\uC73C\uB85C \uBC14\uB010 \uB4A4, \uD574\uB2F9 \uACF5\uACE0/\uC9C1\uBB34/\uC790\uC18C\uC11C\uB97C \uADFC\uAC70\uB85C \uB2F9\uC2E0(AI)\uC774 \uC0DD\uC131\uD55C \uC790\uB8CC\uB97C \uC800\uC7A5\uD558\uB294 \uB370 \uC0AC\uC6A9\uD569\uB2C8\uB2E4. job_id\uB294 \uD544\uC218\uC785\uB2C8\uB2E4. \uAC19\uC740 \uACF5\uACE0\uC5D0 \uB2E4\uC2DC \uC800\uC7A5\uD558\uBA74 \uAC31\uC2E0\uB429\uB2C8\uB2E4.",
|
|
6747
|
+
inputSchema: InterviewPrepInputSchema.shape,
|
|
6748
|
+
handler: (args) => {
|
|
6749
|
+
try {
|
|
6750
|
+
const prep = saveInterviewPrep(args);
|
|
6751
|
+
return ok(`\uBA74\uC811 \uC900\uBE44 \uC790\uB8CC\uB97C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4 (\uC9C8\uBB38 ${prep.questions.length}\uAC1C).`, prep);
|
|
6752
|
+
} catch (e) {
|
|
6753
|
+
return fail(e instanceof Error ? e.message : "\uC800\uC7A5 \uC2E4\uD328");
|
|
6754
|
+
}
|
|
6755
|
+
}
|
|
6756
|
+
},
|
|
6757
|
+
/* --------------------------------------------------------------- exports */
|
|
6758
|
+
{
|
|
6759
|
+
name: "export_cover_letter",
|
|
6760
|
+
title: "\uC790\uAE30\uC18C\uAC1C\uC11C \uB0B4\uBCF4\uB0B4\uAE30",
|
|
6761
|
+
description: '\uC790\uAE30\uC18C\uAC1C\uC11C\uB97C Markdown \uB610\uB294 \uC778\uC1C4\uC6A9 HTML(\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C PDF\uB85C \uC800\uC7A5) \uD30C\uC77C\uB85C \uB0B4\uBCF4\uB0B4 \uB370\uC774\uD130 \uD3F4\uB354\uC758 exports\uC5D0 \uC800\uC7A5\uD558\uACE0, \uD30C\uC77C \uACBD\uB85C\uC640 \uBCF8\uBB38\uC744 \uB3CC\uB824\uC90D\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uAC00 "\uC790\uC18C\uC11C \uD30C\uC77C\uB85C \uBC1B\uACE0 \uC2F6\uC5B4"\uB77C\uACE0 \uD560 \uB54C \uC0AC\uC6A9\uD558\uC138\uC694. \uB300\uC2DC\uBCF4\uB4DC\uC5D0\uC11C \uC9C1\uC811 \uB2E4\uC6B4\uB85C\uB4DC\uD560 \uC218\uB3C4 \uC788\uC2B5\uB2C8\uB2E4.',
|
|
6762
|
+
inputSchema: {
|
|
6763
|
+
cover_letter_id: external_exports.string(),
|
|
6764
|
+
format: external_exports.enum(["md", "html"]).optional().describe("\uAE30\uBCF8 md")
|
|
6765
|
+
},
|
|
6766
|
+
handler: (args) => {
|
|
6767
|
+
const cl = coverLetterRepo.get(args.cover_letter_id, true);
|
|
6768
|
+
if (!cl) return fail("\uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
6769
|
+
const job = cl.job_id ? jobRepo.get(cl.job_id) : null;
|
|
6770
|
+
const profile = profileRepo.get();
|
|
6771
|
+
const format = args.format ?? "md";
|
|
6772
|
+
const result = format === "html" ? coverLetterToHtml(cl, { job, profile }) : coverLetterToMarkdown(cl, { job, profile });
|
|
6773
|
+
const dir = getExportsDir();
|
|
6774
|
+
const filePath = path4.join(dir, result.filename);
|
|
6775
|
+
try {
|
|
6776
|
+
fs3.writeFileSync(filePath, result.content, "utf8");
|
|
6777
|
+
} catch (e) {
|
|
6778
|
+
return fail(`\uD30C\uC77C \uC800\uC7A5 \uC2E4\uD328: ${e instanceof Error ? e.message : e}`);
|
|
6779
|
+
}
|
|
6780
|
+
return ok(`'${cl.title}'\uB97C ${format.toUpperCase()}\uB85C \uB0B4\uBCF4\uB0C8\uC2B5\uB2C8\uB2E4.
|
|
6781
|
+
\uC800\uC7A5 \uC704\uCE58: ${filePath}`, {
|
|
6782
|
+
path: filePath,
|
|
6783
|
+
filename: result.filename,
|
|
6784
|
+
content: result.content
|
|
6785
|
+
});
|
|
6786
|
+
}
|
|
6787
|
+
},
|
|
6788
|
+
/* ----------------------------------------------------------- dashboard ui */
|
|
6789
|
+
{
|
|
6790
|
+
name: "open_dashboard",
|
|
6791
|
+
title: "\uB300\uC2DC\uBCF4\uB4DC \uC5F4\uAE30",
|
|
6792
|
+
description: "\uB85C\uCEEC CareerMate \uB300\uC2DC\uBCF4\uB4DC\uB97C \uC0AC\uC6A9\uC790\uC758 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5FD\uB2C8\uB2E4. \uC11C\uBC84\uAC00 \uC2E4\uD589 \uC911\uC774 \uC544\uB2C8\uBA74 \uC790\uB3D9\uC73C\uB85C \uC2DC\uC791\uD569\uB2C8\uB2E4. \uC800\uC7A5 \uACB0\uACFC\uB97C \uC0AC\uC6A9\uC790\uAC00 \uB208\uC73C\uB85C \uD655\uC778\uD558\uAC8C \uD558\uACE0 \uC2F6\uC744 \uB54C \uD638\uCD9C\uD558\uC138\uC694(\uC608: \uC801\uD569\uB3C4 \uBD84\uC11D/\uC790\uC18C\uC11C \uC800\uC7A5 \uD6C4).",
|
|
6793
|
+
inputSchema: {},
|
|
6794
|
+
handler: async () => {
|
|
6795
|
+
const { url, running } = await resolveDashboardUrl();
|
|
6796
|
+
if (running) openInBrowser(url);
|
|
6797
|
+
return ok(
|
|
6798
|
+
running ? `\uB300\uC2DC\uBCF4\uB4DC\uB97C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4: ${url}` : `\uB300\uC2DC\uBCF4\uB4DC \uC8FC\uC18C: ${url} (\uC11C\uBC84\uAC00 \uC544\uC9C1 \uC2E4\uD589 \uC911\uC774 \uC544\uB2C8\uBA74 \uD504\uB85C\uC81D\uD2B8 \uD3F4\uB354\uC5D0\uC11C npm start \uB97C \uC2E4\uD589\uD558\uC138\uC694).`,
|
|
6799
|
+
{ url, running }
|
|
6800
|
+
);
|
|
6801
|
+
}
|
|
6802
|
+
},
|
|
6803
|
+
{
|
|
6804
|
+
name: "open_application",
|
|
6805
|
+
title: "\uC9C0\uC6D0/\uACF5\uACE0 \uD398\uC774\uC9C0 \uC5F4\uAE30",
|
|
6806
|
+
description: "\uD2B9\uC815 \uACF5\uACE0\uC758 \uC9C0\uC6D0 \uC0C1\uC138 \uD398\uC774\uC9C0\uB97C \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5FD\uB2C8\uB2E4. \uC801\uD569\uB3C4\xB7\uC790\uC18C\uC11C\xB7\uBA74\uC811 \uC900\uBE44\uB97C \uC0AC\uC6A9\uC790\uAC00 \uBC14\uB85C \uD655\uC778\uD558\uB3C4\uB85D \uC548\uB0B4\uD560 \uB54C \uC0AC\uC6A9\uD558\uC138\uC694.",
|
|
6807
|
+
inputSchema: { job_id: external_exports.string() },
|
|
6808
|
+
handler: async (args) => {
|
|
6809
|
+
const job = jobRepo.get(args.job_id);
|
|
6810
|
+
if (!job) return fail("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
6811
|
+
const { url, running } = await resolveDashboardUrl(`/#/jobs/${args.job_id}`);
|
|
6812
|
+
if (running) openInBrowser(url);
|
|
6813
|
+
return ok(running ? `'${job.company} \xB7 ${job.position}' \uD398\uC774\uC9C0\uB97C \uC5F4\uC5C8\uC2B5\uB2C8\uB2E4: ${url}` : `\uD398\uC774\uC9C0 \uC8FC\uC18C: ${url}`, { url, running });
|
|
6814
|
+
}
|
|
6815
|
+
},
|
|
6816
|
+
/* ------------------------------------------------------------- activity */
|
|
6817
|
+
{
|
|
6818
|
+
name: "list_recent_activity",
|
|
6819
|
+
title: "\uCD5C\uADFC \uD65C\uB3D9 \uC870\uD68C",
|
|
6820
|
+
description: '\uCD5C\uADFC \uD65C\uB3D9 \uB0B4\uC5ED(\uACF5\uACE0 \uC800\uC7A5, \uC801\uD569\uB3C4 \uBD84\uC11D, \uC790\uC18C\uC11C \uBC84\uC804, \uC0C1\uD0DC \uBCC0\uACBD, \uBA74\uC811 \uC900\uBE44 \uB4F1)\uC744 \uB3CC\uB824\uC90D\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uAC00 "\uCD5C\uADFC\uC5D0 \uBB50 \uD588\uC9C0?"\uB77C\uACE0 \uBB3B\uAC70\uB098, \uC791\uC5C5\uC744 \uC774\uC5B4\uC11C \uC9C4\uD589\uD560 \uB9E5\uB77D\uC774 \uD544\uC694\uD560 \uB54C \uC0AC\uC6A9\uD558\uC138\uC694.',
|
|
6821
|
+
inputSchema: { limit: external_exports.number().optional().describe("\uAE30\uBCF8 20") },
|
|
6822
|
+
readOnly: true,
|
|
6823
|
+
handler: (args) => {
|
|
6824
|
+
const acts = listRecentActivity(args?.limit ?? 20);
|
|
6825
|
+
return ok(`\uCD5C\uADFC \uD65C\uB3D9 ${acts.length}\uAC74`, acts);
|
|
6826
|
+
}
|
|
6827
|
+
},
|
|
6828
|
+
/* ------------------------------------------------------- workflow guide */
|
|
6829
|
+
{
|
|
6830
|
+
name: "get_workflow_guide",
|
|
6831
|
+
title: "\uC6CC\uD06C\uD50C\uB85C\uC6B0 \uAC00\uC774\uB4DC",
|
|
6832
|
+
description: "CareerMate\uC758 \uD45C\uC900 \uC6CC\uD06C\uD50C\uB85C\uC6B0(\uC628\uBCF4\uB529, \uACF5\uACE0 \uBD84\uC11D, \uC790\uC18C\uC11C \uC791\uC131, \uC9C0\uC6D0 \uC0C1\uD0DC \uAD00\uB9AC, \uBA74\uC811 \uC900\uBE44)\uC758 \uB2E8\uACC4\uBCC4 \uC548\uB0B4\uB97C \uB3CC\uB824\uC90D\uB2C8\uB2E4. \uC5B4\uB5A4 \uC21C\uC11C\uB85C \uC5B4\uB5A4 \uB3C4\uAD6C\uB97C \uD638\uCD9C\uD574\uC57C \uD560\uC9C0 \uD655\uC778\uD560 \uB54C \uC0AC\uC6A9\uD558\uC138\uC694. workflow_id\uB97C \uC0DD\uB7B5\uD558\uBA74 \uC804\uCCB4 \uBAA9\uB85D\uC744 \uB3CC\uB824\uC90D\uB2C8\uB2E4.",
|
|
6833
|
+
inputSchema: {
|
|
6834
|
+
workflow_id: external_exports.enum(["onboarding", "analyze_job", "write_cover_letter", "manage_application_status", "prepare_interview"]).optional()
|
|
6835
|
+
},
|
|
6836
|
+
readOnly: true,
|
|
6837
|
+
handler: (args) => {
|
|
6838
|
+
if (args?.workflow_id) {
|
|
6839
|
+
const wf = getWorkflow(args.workflow_id);
|
|
6840
|
+
if (!wf) return fail("\uD574\uB2F9 \uC6CC\uD06C\uD50C\uB85C\uC6B0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
6841
|
+
return ok(renderWorkflowMarkdown(args.workflow_id) ?? wf.title, wf);
|
|
6842
|
+
}
|
|
6843
|
+
return ok(
|
|
6844
|
+
`\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uC6CC\uD06C\uD50C\uB85C\uC6B0 ${WORKFLOWS.length}\uAC1C`,
|
|
6845
|
+
WORKFLOWS.map((w) => ({ id: w.id, title: w.title, trigger: w.trigger }))
|
|
6846
|
+
);
|
|
6847
|
+
}
|
|
6848
|
+
},
|
|
6849
|
+
/* --------------------------------------------------- writing style guide */
|
|
6850
|
+
{
|
|
6851
|
+
name: "get_writing_style_guide",
|
|
6852
|
+
title: "AI \uD2F0 \uC548 \uB098\uB294 \uAE00\uC4F0\uAE30 \uAC00\uC774\uB4DC",
|
|
6853
|
+
description: '\uC790\uAE30\uC18C\uAC1C\uC11C\xB7\uC790\uAE30 PR\xB7\uC9C0\uC6D0 \uBA54\uC77C \uB4F1 \uC0AC\uB78C\uC774 \uC4F4 \uB4EF\uD55C \uAE00\uC744 \uC791\uC131\uD558\uAE30 \uC9C1\uC804\uC5D0 \uD638\uCD9C\uD558\uC138\uC694. "AI\uAC00 \uC4F4 \uD2F0"\uAC00 \uB098\uB294 \uD55C\uAD6D\uC5B4 \uBB38\uC7A5 \uC2B5\uAD00(\uBC88\uC5ED\uD22C, \uD074\uB9AC\uC170, \uAE30\uACC4\uC801 \uBCD1\uB82C, \uC0C1\uD22C\uC801 \uC5F0\uACB0\uC5B4, \uADE0\uC77C\uD55C \uBB38\uC7A5 \uB9AC\uB4EC \uB4F1)\uC744 \uC81C\uAC70\uD558\uB294 \uC791\uC131 \uADDC\uCE59\uACFC \uC800\uC7A5 \uC804 \uC790\uAC00 \uC810\uAC80 \uD56D\uBAA9\uC744 \uB3CC\uB824\uC90D\uB2C8\uB2E4. \uC0AC\uC2E4\xB7\uC218\uCE58\xB7\uACE0\uC720\uBA85\uC0AC\uB294 \uADF8\uB300\uB85C \uB450\uACE0 \uBB38\uC7A5 \uACB0\uB9CC \uB2E4\uB4EC\uB3C4\uB85D \uC548\uB0B4\uD569\uB2C8\uB2E4. write_cover_letter / analyze_job \uC6CC\uD06C\uD50C\uB85C\uC6B0\uC5D0\uC11C \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC4F8 \uB54C \uBC18\uB4DC\uC2DC \uD568\uAED8 \uC801\uC6A9\uD558\uC138\uC694.',
|
|
6854
|
+
inputSchema: {},
|
|
6855
|
+
readOnly: true,
|
|
6856
|
+
handler: () => ok("AI \uD2F0 \uC548 \uB098\uB294 \uAE00\uC4F0\uAE30 \uAC00\uC774\uB4DC\uB97C \uC801\uC6A9\uD574 \uC791\uC131\uD558\uC138\uC694.", { guide: HUMANIZE_WRITING_GUIDE })
|
|
6857
|
+
}
|
|
6858
|
+
];
|
|
6859
|
+
|
|
6860
|
+
// scripts/doctor.ts
|
|
6861
|
+
var ok2 = (s) => console.log(` \u2705 ${s}`);
|
|
6862
|
+
var warn = (s) => console.log(` \u26A0\uFE0F ${s}`);
|
|
6863
|
+
var info = (s) => console.log(` \xB7 ${s}`);
|
|
6864
|
+
function checkNode() {
|
|
6865
|
+
const [maj, min] = process.versions.node.split(".").map(Number);
|
|
6866
|
+
const good = maj > 22 || maj === 22 && min >= 5;
|
|
6867
|
+
if (good) ok2(`Node.js ${process.version} (node:sqlite \uB0B4\uC7A5 \uC0AC\uC6A9 \u2014 \uBCC4\uB3C4 \uBE4C\uB4DC \uBD88\uD544\uC694)`);
|
|
6868
|
+
else warn(`Node.js ${process.version} \u2014 22.5 \uC774\uC0C1\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. \uC5C5\uB370\uC774\uD2B8\uB97C \uAD8C\uC7A5\uD569\uB2C8\uB2E4.`);
|
|
6869
|
+
return good;
|
|
6870
|
+
}
|
|
6871
|
+
function main() {
|
|
6872
|
+
console.log("\nCareerMate \uC0C1\uD0DC \uC810\uAC80\n" + "\u2500".repeat(40));
|
|
6873
|
+
const nodeOk = checkNode();
|
|
6874
|
+
const dir = getDataDir();
|
|
6875
|
+
try {
|
|
6876
|
+
fs4.accessSync(dir, fs4.constants.W_OK);
|
|
6877
|
+
ok2(`\uB370\uC774\uD130 \uD3F4\uB354 \uC4F0\uAE30 \uAC00\uB2A5: ${dir}`);
|
|
6878
|
+
} catch {
|
|
6879
|
+
warn(`\uB370\uC774\uD130 \uD3F4\uB354\uC5D0 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${dir}`);
|
|
6880
|
+
}
|
|
6881
|
+
let dbOk = false;
|
|
6882
|
+
try {
|
|
6883
|
+
const db = getDb();
|
|
6884
|
+
const v = db.prepare(`SELECT value FROM _meta WHERE key='schema_version'`).get();
|
|
6885
|
+
ok2(`\uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uC815\uC0C1 (\uC2A4\uD0A4\uB9C8 v${v?.value ?? "?"}) \u2014 ${getDbPath()}`);
|
|
6886
|
+
dbOk = true;
|
|
6887
|
+
} catch (e) {
|
|
6888
|
+
warn(`\uB370\uC774\uD130\uBCA0\uC774\uC2A4 \uC5F4\uAE30 \uC2E4\uD328: ${e instanceof Error ? e.message : e}`);
|
|
6889
|
+
}
|
|
6890
|
+
if (dbOk) {
|
|
6891
|
+
const counts = {
|
|
6892
|
+
\uD504\uB85C\uD544: profileRepo.get()?.name ? 1 : 0,
|
|
6893
|
+
\uACBD\uB825: experienceRepo.list().length,
|
|
6894
|
+
\uD504\uB85C\uC81D\uD2B8: projectRepo.list().length,
|
|
6895
|
+
\uAE30\uC220: skillRepo.list().length,
|
|
6896
|
+
\uBB38\uC11C: documentRepo.list().length,
|
|
6897
|
+
\uC790\uAE30\uC18C\uAC1C\uC11C: coverLetterRepo.list().length,
|
|
6898
|
+
\uACF5\uACE0: jobRepo.list().length,
|
|
6899
|
+
\uC9C0\uC6D0: applicationRepo.list().length,
|
|
6900
|
+
\uBA74\uC811\uC900\uBE44: interviewRepo.list().length
|
|
6901
|
+
};
|
|
6902
|
+
info("\uC800\uC7A5\uB41C \uB370\uC774\uD130: " + Object.entries(counts).map(([k, v]) => `${k} ${v}`).join(" \xB7 "));
|
|
6903
|
+
}
|
|
6904
|
+
ok2(`MCP \uB3C4\uAD6C ${TOOLS.length}\uAC1C \uB4F1\uB85D\uB428`);
|
|
6905
|
+
const rt = readRuntimeInfo();
|
|
6906
|
+
if (rt && isProcessAlive(rt.pid)) ok2(`\uB300\uC2DC\uBCF4\uB4DC \uC2E4\uD589 \uC911: ${rt.url}`);
|
|
6907
|
+
else info("\uB300\uC2DC\uBCF4\uB4DC\uAC00 \uC2E4\uD589 \uC911\uC774 \uC544\uB2D9\uB2C8\uB2E4. `npm start` \uB85C \uC2E4\uD589\uD558\uC138\uC694.");
|
|
6908
|
+
console.log("\u2500".repeat(40));
|
|
6909
|
+
console.log(nodeOk && dbOk ? "\uD575\uC2EC \uC810\uAC80 \uD1B5\uACFC \u2705\n" : "\uC77C\uBD80 \uD56D\uBAA9\uC744 \uD655\uC778\uD574 \uC8FC\uC138\uC694 \u26A0\uFE0F\n");
|
|
6910
|
+
process.exit(nodeOk && dbOk ? 0 : 1);
|
|
6911
|
+
}
|
|
6912
|
+
main();
|