careermate 0.0.3 → 0.0.5
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/AGENTS.md +3 -0
- package/INSTALL.md +2 -1
- package/README.md +8 -6
- package/dist/doctor.mjs +526 -100
- package/dist/mcp.mjs +528 -102
- package/dist/migrate.mjs +23 -1
- package/dist/public/lib.js +6 -1
- package/dist/public/pages/applications.js +7 -0
- package/dist/public/pages/jobs.js +22 -15
- package/dist/public/pages/settings.js +160 -14
- package/dist/public/styles.css +19 -2
- package/dist/site/index.html +88 -228
- package/dist/site/landing.js +109 -0
- package/dist/site/start.html +154 -131
- package/dist/site/style.css +169 -63
- package/dist/web.mjs +503 -41
- package/docs/FAQ.md +3 -2
- package/docs/MCP_TOOLS.md +16 -8
- package/docs/START_WORKFLOW.md +2 -1
- package/docs/WORKFLOWS.md +6 -6
- package/package.json +2 -2
- package/packages/core/src/services.ts +69 -7
- package/packages/db/src/repositories.ts +0 -0
- package/packages/mcp-tools/src/bridge.ts +194 -13
- package/packages/mcp-tools/src/tools.ts +166 -35
- package/packages/mcp-tools/src/update.ts +1 -1
- package/packages/prompts/src/onboarding.ts +1 -1
- package/packages/prompts/src/system.ts +10 -2
- package/packages/shared/src/enums.ts +7 -2
- package/packages/shared/src/errors.ts +33 -0
- package/packages/shared/src/index.ts +1 -0
- package/packages/shared/src/schemas.ts +56 -2
- package/packages/workflows/src/definitions.ts +3 -3
package/dist/web.mjs
CHANGED
|
@@ -6746,15 +6746,15 @@ var require_generators = __commonJS({
|
|
|
6746
6746
|
var stack = new Error().stack;
|
|
6747
6747
|
return function() {
|
|
6748
6748
|
var generator = generatorFunction.apply(this, arguments);
|
|
6749
|
-
var
|
|
6749
|
+
var spawn3 = new PromiseSpawn$(
|
|
6750
6750
|
void 0,
|
|
6751
6751
|
void 0,
|
|
6752
6752
|
yieldHandler,
|
|
6753
6753
|
stack
|
|
6754
6754
|
);
|
|
6755
|
-
var ret2 =
|
|
6756
|
-
|
|
6757
|
-
|
|
6755
|
+
var ret2 = spawn3.promise();
|
|
6756
|
+
spawn3._generator = generator;
|
|
6757
|
+
spawn3._promiseFulfilled(void 0);
|
|
6758
6758
|
return ret2;
|
|
6759
6759
|
};
|
|
6760
6760
|
};
|
|
@@ -6769,9 +6769,9 @@ var require_generators = __commonJS({
|
|
|
6769
6769
|
if (typeof generatorFunction !== "function") {
|
|
6770
6770
|
return apiRejection("generatorFunction must be a function\n\n See http://goo.gl/MqrFmX\n");
|
|
6771
6771
|
}
|
|
6772
|
-
var
|
|
6773
|
-
var ret2 =
|
|
6774
|
-
|
|
6772
|
+
var spawn3 = new PromiseSpawn(generatorFunction, this);
|
|
6773
|
+
var ret2 = spawn3.promise();
|
|
6774
|
+
spawn3._run(Promise2.spawn);
|
|
6775
6775
|
return ret2;
|
|
6776
6776
|
};
|
|
6777
6777
|
};
|
|
@@ -28299,7 +28299,7 @@ var require_files = __commonJS({
|
|
|
28299
28299
|
"use strict";
|
|
28300
28300
|
var fs5 = __require("fs");
|
|
28301
28301
|
var url = __require("url");
|
|
28302
|
-
var
|
|
28302
|
+
var os3 = __require("os");
|
|
28303
28303
|
var dirname = __require("path").dirname;
|
|
28304
28304
|
var resolvePath = __require("path").resolve;
|
|
28305
28305
|
var isAbsolutePath = require_path_is_absolute();
|
|
@@ -28341,7 +28341,7 @@ var require_files = __commonJS({
|
|
|
28341
28341
|
var readFile = promises.promisify(fs5.readFile.bind(fs5));
|
|
28342
28342
|
function uriToPath(uriString, platform) {
|
|
28343
28343
|
if (!platform) {
|
|
28344
|
-
platform =
|
|
28344
|
+
platform = os3.platform();
|
|
28345
28345
|
}
|
|
28346
28346
|
var uri = url.parse(uriString);
|
|
28347
28347
|
if (isLocalFileUri(uri) || isRelativeUri(uri)) {
|
|
@@ -39354,12 +39354,12 @@ var require_cjs = __commonJS({
|
|
|
39354
39354
|
});
|
|
39355
39355
|
|
|
39356
39356
|
// apps/web/src/index.ts
|
|
39357
|
-
import { spawn } from "node:child_process";
|
|
39357
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
39358
39358
|
|
|
39359
39359
|
// apps/web/src/server.ts
|
|
39360
39360
|
import http from "node:http";
|
|
39361
39361
|
import path5 from "node:path";
|
|
39362
|
-
import { fileURLToPath } from "node:url";
|
|
39362
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
39363
39363
|
|
|
39364
39364
|
// packages/db/src/paths.ts
|
|
39365
39365
|
import os from "node:os";
|
|
@@ -39766,6 +39766,24 @@ function setVerifyStrict(on) {
|
|
|
39766
39766
|
return on;
|
|
39767
39767
|
}
|
|
39768
39768
|
|
|
39769
|
+
// packages/shared/src/errors.ts
|
|
39770
|
+
var CareerMateError = class extends Error {
|
|
39771
|
+
constructor(message, httpStatus = 400, code) {
|
|
39772
|
+
super(message);
|
|
39773
|
+
this.httpStatus = httpStatus;
|
|
39774
|
+
this.code = code;
|
|
39775
|
+
this.name = "CareerMateError";
|
|
39776
|
+
}
|
|
39777
|
+
httpStatus;
|
|
39778
|
+
code;
|
|
39779
|
+
};
|
|
39780
|
+
function notFound(message) {
|
|
39781
|
+
return new CareerMateError(message, 404, "not_found");
|
|
39782
|
+
}
|
|
39783
|
+
function conflict(message) {
|
|
39784
|
+
return new CareerMateError(message, 409, "conflict");
|
|
39785
|
+
}
|
|
39786
|
+
|
|
39769
39787
|
// packages/shared/src/enums.ts
|
|
39770
39788
|
var APPLICATION_STATUSES = [
|
|
39771
39789
|
"draft",
|
|
@@ -39826,8 +39844,9 @@ function canTransitionStatus(from, to) {
|
|
|
39826
39844
|
}
|
|
39827
39845
|
function assertStatusTransition(from, to) {
|
|
39828
39846
|
if (!canTransitionStatus(from, to)) {
|
|
39829
|
-
|
|
39830
|
-
|
|
39847
|
+
const suffix = to === "draft" ? ` ('${APPLICATION_STATUS_LABELS[from]}' \uB2E8\uACC4\uC758 \uC9C0\uC6D0\uC740 \uC791\uC131 \uC911(\uBBF8\uCD94\uC801)\uC73C\uB85C \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uCD08\uAE30 \uB2E8\uACC4\uB85C \uC62E\uAE30\uB824\uBA74 '\uC9C0\uC6D0 \uC608\uC815'\uC744 \uC120\uD0DD\uD558\uC138\uC694.)` : "";
|
|
39848
|
+
throw conflict(
|
|
39849
|
+
`'${APPLICATION_STATUS_LABELS[from]}'\uC5D0\uC11C '${APPLICATION_STATUS_LABELS[to]}'(\uC73C)\uB85C\uB294 \uC0C1\uD0DC\uB97C \uBC14\uAFC0 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.${suffix}`
|
|
39831
39850
|
);
|
|
39832
39851
|
}
|
|
39833
39852
|
}
|
|
@@ -43926,7 +43945,20 @@ var optLine = external_exports.string().max(MAX_LINE).optional();
|
|
|
43926
43945
|
var optNote = external_exports.string().max(MAX_NOTE).optional();
|
|
43927
43946
|
var optBody = external_exports.string().max(MAX_BODY).optional();
|
|
43928
43947
|
var reqBody = external_exports.string().max(MAX_BODY).refine((s) => s.trim().length > 0, { message: EMPTY_MSG });
|
|
43929
|
-
|
|
43948
|
+
function toStrList(v) {
|
|
43949
|
+
if (typeof v !== "string") return v;
|
|
43950
|
+
const s = v.trim();
|
|
43951
|
+
if (!s) return [];
|
|
43952
|
+
if (s.startsWith("[")) {
|
|
43953
|
+
try {
|
|
43954
|
+
const parsed = JSON.parse(s);
|
|
43955
|
+
if (Array.isArray(parsed)) return parsed;
|
|
43956
|
+
} catch {
|
|
43957
|
+
}
|
|
43958
|
+
}
|
|
43959
|
+
return s.split(/[\n,]/).map((x) => x.trim()).filter(Boolean);
|
|
43960
|
+
}
|
|
43961
|
+
var strList = external_exports.preprocess(toStrList, external_exports.array(external_exports.string().max(MAX_ITEM)).max(MAX_ITEMS));
|
|
43930
43962
|
var baseRecord = {
|
|
43931
43963
|
id,
|
|
43932
43964
|
created_at: isoDateTime,
|
|
@@ -43988,6 +44020,9 @@ var ExperienceRecordSchema = external_exports.object({
|
|
|
43988
44020
|
tech: external_exports.array(external_exports.string()),
|
|
43989
44021
|
order_index: external_exports.number()
|
|
43990
44022
|
});
|
|
44023
|
+
var ExperienceBatchInputSchema = external_exports.object({
|
|
44024
|
+
experiences: external_exports.array(ExperienceInputSchema).min(1).max(MAX_ITEMS).describe("\uCD94\uAC00\uD560 \uACBD\uB825 \uBAA9\uB85D(\uD55C \uAC74\uC774\uC5B4\uB3C4 \uBC30\uC5F4\uB85C). \uAC19\uC740 \uD68C\uC0AC\xB7\uC9C1\uBB34\xB7\uC785\uC0AC\uC77C\uC740 \uAC31\uC2E0\uB418\uACE0 \uC911\uBCF5 \uC0DD\uC131\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.")
|
|
44025
|
+
});
|
|
43991
44026
|
var ProjectInputSchema = external_exports.object({
|
|
43992
44027
|
name: reqLine,
|
|
43993
44028
|
role: optLine,
|
|
@@ -44011,6 +44046,9 @@ var ProjectRecordSchema = external_exports.object({
|
|
|
44011
44046
|
end_date: external_exports.string().nullable(),
|
|
44012
44047
|
order_index: external_exports.number()
|
|
44013
44048
|
});
|
|
44049
|
+
var ProjectBatchInputSchema = external_exports.object({
|
|
44050
|
+
projects: external_exports.array(ProjectInputSchema).min(1).max(MAX_ITEMS).describe("\uCD94\uAC00\uD560 \uD504\uB85C\uC81D\uD2B8 \uBAA9\uB85D(\uD55C \uAC1C\uC5EC\uB3C4 \uBC30\uC5F4\uB85C). \uAC19\uC740 \uC774\uB984\uC758 \uD504\uB85C\uC81D\uD2B8\uB294 \uAC31\uC2E0\uB418\uACE0 \uC911\uBCF5 \uC0DD\uC131\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.")
|
|
44051
|
+
});
|
|
44014
44052
|
var SkillInputSchema = external_exports.object({
|
|
44015
44053
|
name: reqLine,
|
|
44016
44054
|
category: external_exports.string().max(MAX_LINE).optional().describe("\uC5B8\uC5B4/\uD504\uB808\uC784\uC6CC\uD06C/\uD234/\uC18C\uD504\uD2B8\uC2A4\uD0AC \uB4F1"),
|
|
@@ -44026,6 +44064,9 @@ var SkillRecordSchema = external_exports.object({
|
|
|
44026
44064
|
years: external_exports.number().nullable(),
|
|
44027
44065
|
order_index: external_exports.number()
|
|
44028
44066
|
});
|
|
44067
|
+
var SkillBatchInputSchema = external_exports.object({
|
|
44068
|
+
skills: external_exports.array(SkillInputSchema).min(1).max(MAX_ITEMS).describe("\uCD94\uAC00\uD560 \uAE30\uC220\uC2A4\uD0DD \uBAA9\uB85D(\uD55C \uAC1C\uC5EC\uB3C4 \uBC30\uC5F4\uB85C). \uAC19\uC740 \uC774\uB984\uC758 \uAE30\uC220\uC740 \uAC31\uC2E0\uB418\uACE0 \uC911\uBCF5 \uC0DD\uC131\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.")
|
|
44069
|
+
});
|
|
44029
44070
|
var DocumentInputSchema = external_exports.object({
|
|
44030
44071
|
kind: external_exports.enum(DOCUMENT_KINDS),
|
|
44031
44072
|
title: reqLine,
|
|
@@ -44222,7 +44263,7 @@ var ApplicationContextSchema = external_exports.object({
|
|
|
44222
44263
|
});
|
|
44223
44264
|
|
|
44224
44265
|
// packages/shared/src/build-info.ts
|
|
44225
|
-
var APP_VERSION = true ? "0.0.
|
|
44266
|
+
var APP_VERSION = true ? "0.0.5" : "0.0.0-dev";
|
|
44226
44267
|
var BUNDLED = true;
|
|
44227
44268
|
|
|
44228
44269
|
// packages/shared/src/index.ts
|
|
@@ -44237,6 +44278,7 @@ function now() {
|
|
|
44237
44278
|
|
|
44238
44279
|
// packages/db/src/repositories.ts
|
|
44239
44280
|
var PROFILE_ID = "profile-singleton";
|
|
44281
|
+
var norm = (s) => (s ?? "").trim().toLowerCase();
|
|
44240
44282
|
function mapProfile(r) {
|
|
44241
44283
|
return {
|
|
44242
44284
|
id: r.id,
|
|
@@ -44314,6 +44356,9 @@ function mapExperience(r) {
|
|
|
44314
44356
|
updated_at: r.updated_at
|
|
44315
44357
|
};
|
|
44316
44358
|
}
|
|
44359
|
+
function expKey(company, role, start) {
|
|
44360
|
+
return [norm(company), norm(role), norm(start)].join("\0");
|
|
44361
|
+
}
|
|
44317
44362
|
var experienceRepo = {
|
|
44318
44363
|
list() {
|
|
44319
44364
|
return getDb().prepare(`SELECT * FROM experiences ORDER BY order_index ASC, start_date DESC`).all().map(mapExperience);
|
|
@@ -44322,6 +44367,32 @@ var experienceRepo = {
|
|
|
44322
44367
|
const r = getDb().prepare(`SELECT * FROM experiences WHERE id = ?`).get(id3);
|
|
44323
44368
|
return r ? mapExperience(r) : null;
|
|
44324
44369
|
},
|
|
44370
|
+
/** Existing experience with the same company+role+start_date, or null (for idempotent batch upserts). */
|
|
44371
|
+
findMatch(input) {
|
|
44372
|
+
const key = expKey(input.company, input.role ?? null, input.start_date ?? null);
|
|
44373
|
+
return this.list().find((e) => expKey(e.company, e.role, e.start_date) === key) ?? null;
|
|
44374
|
+
},
|
|
44375
|
+
/** Insert, or merge into the matching experience (omitted fields keep their stored value). */
|
|
44376
|
+
upsert(input) {
|
|
44377
|
+
const existing = this.findMatch(input);
|
|
44378
|
+
if (existing) return { record: this.update(existing.id, input), created: false };
|
|
44379
|
+
return { record: this.add(input), created: true };
|
|
44380
|
+
},
|
|
44381
|
+
/** Batch insert/merge in one transaction; within-batch duplicates collapse onto one row. */
|
|
44382
|
+
addMany(inputs) {
|
|
44383
|
+
return tx(() => {
|
|
44384
|
+
const records = [];
|
|
44385
|
+
let created = 0;
|
|
44386
|
+
let updated = 0;
|
|
44387
|
+
for (const input of inputs) {
|
|
44388
|
+
const { record, created: isNew } = this.upsert(input);
|
|
44389
|
+
records.push(record);
|
|
44390
|
+
if (isNew) created += 1;
|
|
44391
|
+
else updated += 1;
|
|
44392
|
+
}
|
|
44393
|
+
return { records, created, updated };
|
|
44394
|
+
});
|
|
44395
|
+
},
|
|
44325
44396
|
add(input) {
|
|
44326
44397
|
const ts = now();
|
|
44327
44398
|
const id3 = newId("exp_");
|
|
@@ -44395,6 +44466,32 @@ var projectRepo = {
|
|
|
44395
44466
|
const r = getDb().prepare(`SELECT * FROM projects WHERE id = ?`).get(id3);
|
|
44396
44467
|
return r ? mapProject(r) : null;
|
|
44397
44468
|
},
|
|
44469
|
+
/** Existing project with the same name (case-insensitive), or null (for idempotent batch upserts). */
|
|
44470
|
+
findByName(name) {
|
|
44471
|
+
const key = norm(name);
|
|
44472
|
+
return this.list().find((p) => norm(p.name) === key) ?? null;
|
|
44473
|
+
},
|
|
44474
|
+
/** Insert, or merge into the same-named project (omitted fields keep their stored value). */
|
|
44475
|
+
upsert(input) {
|
|
44476
|
+
const existing = this.findByName(input.name);
|
|
44477
|
+
if (existing) return { record: this.update(existing.id, input), created: false };
|
|
44478
|
+
return { record: this.add(input), created: true };
|
|
44479
|
+
},
|
|
44480
|
+
/** Batch insert/merge in one transaction; within-batch duplicates collapse onto one row. */
|
|
44481
|
+
addMany(inputs) {
|
|
44482
|
+
return tx(() => {
|
|
44483
|
+
const records = [];
|
|
44484
|
+
let created = 0;
|
|
44485
|
+
let updated = 0;
|
|
44486
|
+
for (const input of inputs) {
|
|
44487
|
+
const { record, created: isNew } = this.upsert(input);
|
|
44488
|
+
records.push(record);
|
|
44489
|
+
if (isNew) created += 1;
|
|
44490
|
+
else updated += 1;
|
|
44491
|
+
}
|
|
44492
|
+
return { records, created, updated };
|
|
44493
|
+
});
|
|
44494
|
+
},
|
|
44398
44495
|
add(input) {
|
|
44399
44496
|
const ts = now();
|
|
44400
44497
|
const id3 = newId("prj_");
|
|
@@ -44462,6 +44559,32 @@ var skillRepo = {
|
|
|
44462
44559
|
const r = getDb().prepare(`SELECT * FROM skills WHERE id = ?`).get(id3);
|
|
44463
44560
|
return r ? mapSkill(r) : null;
|
|
44464
44561
|
},
|
|
44562
|
+
/** Existing skill with the same name (case-insensitive), or null (for idempotent batch upserts). */
|
|
44563
|
+
findByName(name) {
|
|
44564
|
+
const key = norm(name);
|
|
44565
|
+
return this.list().find((s) => norm(s.name) === key) ?? null;
|
|
44566
|
+
},
|
|
44567
|
+
/** Insert, or merge into the same-named skill (omitted fields keep their stored value). */
|
|
44568
|
+
upsert(input) {
|
|
44569
|
+
const existing = this.findByName(input.name);
|
|
44570
|
+
if (existing) return { record: this.update(existing.id, input), created: false };
|
|
44571
|
+
return { record: this.add(input), created: true };
|
|
44572
|
+
},
|
|
44573
|
+
/** Batch insert/merge in one transaction; within-batch duplicates collapse onto one row. */
|
|
44574
|
+
addMany(inputs) {
|
|
44575
|
+
return tx(() => {
|
|
44576
|
+
const records = [];
|
|
44577
|
+
let created = 0;
|
|
44578
|
+
let updated = 0;
|
|
44579
|
+
for (const input of inputs) {
|
|
44580
|
+
const { record, created: isNew } = this.upsert(input);
|
|
44581
|
+
records.push(record);
|
|
44582
|
+
if (isNew) created += 1;
|
|
44583
|
+
else updated += 1;
|
|
44584
|
+
}
|
|
44585
|
+
return { records, created, updated };
|
|
44586
|
+
});
|
|
44587
|
+
},
|
|
44465
44588
|
add(input) {
|
|
44466
44589
|
const ts = now();
|
|
44467
44590
|
const id3 = newId("skl_");
|
|
@@ -44652,6 +44775,7 @@ var coverLetterRepo = {
|
|
|
44652
44775
|
remove(id3) {
|
|
44653
44776
|
const db = getDb();
|
|
44654
44777
|
return tx(() => {
|
|
44778
|
+
db.prepare(`UPDATE applications SET cover_letter_id=NULL, updated_at=? WHERE cover_letter_id=?`).run(now(), id3);
|
|
44655
44779
|
db.prepare(`DELETE FROM cover_letter_versions WHERE cover_letter_id=?`).run(id3);
|
|
44656
44780
|
return db.prepare(`DELETE FROM cover_letters WHERE id=?`).run(id3).changes > 0;
|
|
44657
44781
|
});
|
|
@@ -44740,6 +44864,7 @@ var jobRepo = {
|
|
|
44740
44864
|
remove(id3) {
|
|
44741
44865
|
const db = getDb();
|
|
44742
44866
|
return tx(() => {
|
|
44867
|
+
db.prepare(`UPDATE cover_letters SET job_id=NULL, updated_at=? WHERE job_id=?`).run(now(), id3);
|
|
44743
44868
|
db.prepare(`DELETE FROM fit_analyses WHERE job_id=?`).run(id3);
|
|
44744
44869
|
db.prepare(`DELETE FROM applications WHERE job_id=?`).run(id3);
|
|
44745
44870
|
db.prepare(`DELETE FROM interview_preps WHERE job_id=?`).run(id3);
|
|
@@ -45028,7 +45153,7 @@ function injectToken(html) {
|
|
|
45028
45153
|
// apps/web/src/http.ts
|
|
45029
45154
|
import fs3 from "node:fs";
|
|
45030
45155
|
import path3 from "node:path";
|
|
45031
|
-
var MAX_BODY2 =
|
|
45156
|
+
var MAX_BODY2 = 32 * 1024 * 1024;
|
|
45032
45157
|
var Router = class {
|
|
45033
45158
|
routes = [];
|
|
45034
45159
|
add(method, path6, handler) {
|
|
@@ -45536,7 +45661,7 @@ function styleSignals(text) {
|
|
|
45536
45661
|
}
|
|
45537
45662
|
var DISCLAIMER = "\uC774 \uC810\uAC80\uC740 LLM \uC5C6\uC774 \uC140 \uC218 \uC788\uB294 \uAC83\uB9CC \uBD05\uB2C8\uB2E4(\uC22B\uC790 \uCD9C\uCC98\xB7\uBB38\uCCB4 \uC2E0\uD638). \uD1B5\uACFC=\uD569\uACA9\uC774 \uC544\uB2C8\uBA70, \uC0AC\uC2E4\xB7\uB3D9\uBB38\uC11C\uB2F5\xB7\uC758\uBBF8 \uD310\uB2E8\uC740 \uB2F9\uC2E0(AI)\uC774 \uC9C1\uC811 \uD574\uC57C \uD569\uB2C8\uB2E4.";
|
|
45538
45663
|
function lintArtifact(kind, text, corpus, opts = {}) {
|
|
45539
|
-
const
|
|
45664
|
+
const norm2 = normalizeText(text);
|
|
45540
45665
|
const corpusAvailable = (corpus.documents + corpus.structured).trim().length > 0;
|
|
45541
45666
|
const provenance = analyzeProvenance(text, corpus);
|
|
45542
45667
|
const signals = styleSignals(text);
|
|
@@ -45560,7 +45685,7 @@ function lintArtifact(kind, text, corpus, opts = {}) {
|
|
|
45560
45685
|
}
|
|
45561
45686
|
return {
|
|
45562
45687
|
kind,
|
|
45563
|
-
charCount: [...
|
|
45688
|
+
charCount: [...norm2].length,
|
|
45564
45689
|
signals,
|
|
45565
45690
|
provenance,
|
|
45566
45691
|
blocking,
|
|
@@ -45623,7 +45748,7 @@ function saveJobPosting(input, id3) {
|
|
|
45623
45748
|
}
|
|
45624
45749
|
function saveFitAnalysis(input) {
|
|
45625
45750
|
const job = jobRepo.get(input.job_id);
|
|
45626
|
-
if (!job) throw
|
|
45751
|
+
if (!job) throw notFound("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 \uACF5\uACE0\uB97C \uC800\uC7A5\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.");
|
|
45627
45752
|
applicationRepo.ensure(job.id, "draft");
|
|
45628
45753
|
const fit = fitRepo.save(input);
|
|
45629
45754
|
const scoreText = fit.score != null ? ` (\uC801\uD569\uB3C4 ${fit.score}\uC810)` : "";
|
|
@@ -45638,7 +45763,7 @@ function saveCoverLetterVersion(input) {
|
|
|
45638
45763
|
const detail = verification.blocking.map((b) => `${b.label}: ${b.detail}`).join("; ");
|
|
45639
45764
|
const strictHit = verification.blocking.some((b) => b.id === "unverified_numbers_strict");
|
|
45640
45765
|
const tail = strictHit ? `\uC5C4\uACA9 \uBAA8\uB4DC\uAC00 \uCF1C\uC838 \uC788\uC5B4, \uC62C\uB9B0 \uC774\uB825\uC11C \uBCF8\uBB38\uC5D0 \uC5C6\uB294 \uC218\uCE58\uB294 \uB9C9\uD799\uB2C8\uB2E4. \uC774\uB825\uC11C\uB97C \uC62C\uB9AC\uAC70\uB098 \uC5C4\uACA9 \uBAA8\uB4DC\uB97C \uB044\uAC70\uB098, \uC758\uB3C4\uD55C \uAC12\uC774\uBA74 force: true\uB85C \uC800\uC7A5\uD558\uC138\uC694.` : `\uC774 \uC218\uCE58\uB294 \uC800\uC7A5\uB41C \uACBD\uB825\xB7\uC774\uB825\uC11C\xB7\uD504\uB85C\uC81D\uD2B8\uC5D0 \uC5C6\uC2B5\uB2C8\uB2E4. \uC6D0\uBCF8 \uB370\uC774\uD130\uC758 \uC2E4\uC81C \uC218\uCE58\uB85C \uACE0\uCE58\uAC70\uB098, \uC758\uB3C4\uD55C \uAC12\uC774 \uB9DE\uB2E4\uBA74 \uBA3C\uC800 \uACBD\uB825/\uD504\uB85C\uC81D\uD2B8\uC5D0 \uADF8 \uC218\uCE58\uB97C \uCD94\uAC00\uD55C \uB4A4 \uB2E4\uC2DC \uC800\uC7A5\uD558\uC138\uC694(\uADF8\uB300\uB85C \uC800\uC7A5\uD558\uB824\uBA74 force: true).`;
|
|
45641
|
-
throw new
|
|
45766
|
+
throw new CareerMateError(`\uC800\uC7A5 \uC804 \uC790\uB3D9 \uC810\uAC80\uC5D0\uC11C \uB9C9\uD614\uC2B5\uB2C8\uB2E4 \u2014 ${detail}. ${tail}`, 400, "save_gate");
|
|
45642
45767
|
}
|
|
45643
45768
|
const { coverLetter, version } = coverLetterRepo.addVersion({
|
|
45644
45769
|
cover_letter_id: input.cover_letter_id,
|
|
@@ -45665,7 +45790,7 @@ function saveCoverLetterVersion(input) {
|
|
|
45665
45790
|
}
|
|
45666
45791
|
function updateApplicationStatus(jobId, status, note) {
|
|
45667
45792
|
const job = jobRepo.get(jobId);
|
|
45668
|
-
if (!job) throw
|
|
45793
|
+
if (!job) throw notFound("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
45669
45794
|
const application = applicationRepo.setStatus(jobId, status, note);
|
|
45670
45795
|
activityRepo.log(
|
|
45671
45796
|
"application_status_changed",
|
|
@@ -45675,17 +45800,17 @@ function updateApplicationStatus(jobId, status, note) {
|
|
|
45675
45800
|
);
|
|
45676
45801
|
const interview_unlocked = INTERVIEW_UNLOCK_STATUSES.includes(status);
|
|
45677
45802
|
const hasPrep = !!interviewRepo.getByJob(jobId);
|
|
45678
|
-
const hint = interview_unlocked && !hasPrep ? "\uC11C\uB958 \uB2E8\uACC4\uB97C \uD1B5\uACFC\uD588\uC5B4\uC694. \uC774 \uACF5\uACE0 \uAE30\uC900\uC73C\uB85C \uC608\uC0C1 \uBA74\uC811 \uC9C8\uBB38\uACFC 1\uBD84 \uC790\uAE30\uC18C\uAC1C\uB97C \uC900\uBE44\uD560\uAE4C\uC694?
|
|
45803
|
+
const hint = interview_unlocked && !hasPrep ? "\uC11C\uB958 \uB2E8\uACC4\uB97C \uD1B5\uACFC\uD588\uC5B4\uC694. \uC774 \uACF5\uACE0 \uAE30\uC900\uC73C\uB85C \uC608\uC0C1 \uBA74\uC811 \uC9C8\uBB38\uACFC 1\uBD84 \uC790\uAE30\uC18C\uAC1C\uB97C \uC900\uBE44\uD560\uAE4C\uC694?" : null;
|
|
45679
45804
|
return { application, job, interview_unlocked, hint };
|
|
45680
45805
|
}
|
|
45681
45806
|
function saveInterviewPrep(input) {
|
|
45682
45807
|
const job = jobRepo.get(input.job_id);
|
|
45683
|
-
if (!job) throw
|
|
45808
|
+
if (!job) throw notFound("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
45684
45809
|
const app = applicationRepo.getByJob(job.id);
|
|
45685
45810
|
if (!app || !INTERVIEW_UNLOCK_STATUSES.includes(app.status)) {
|
|
45686
45811
|
const current = app ? APPLICATION_STATUS_LABELS[app.status] : "\uBBF8\uC9C0\uC6D0";
|
|
45687
|
-
throw
|
|
45688
|
-
`\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
|
|
45812
|
+
throw conflict(
|
|
45813
|
+
`\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 \uC9C0\uC6D0 \uC0C1\uD0DC\uB97C '\uC11C\uB958 \uD569\uACA9' \uC774\uC0C1\uC73C\uB85C \uBC14\uAFBC \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.`
|
|
45689
45814
|
);
|
|
45690
45815
|
}
|
|
45691
45816
|
const prep = interviewRepo.save(input);
|
|
@@ -45773,17 +45898,25 @@ CareerMate\uB294 \uC0AC\uC6A9\uC790\uC758 \uCEF4\uD4E8\uD130\uC5D0\uC11C \uB3D9\
|
|
|
45773
45898
|
|
|
45774
45899
|
6. \uC0AC\uC6A9\uC790\uC5D0\uAC8C\uB294 \uC26C\uC6B4 \uD55C\uAD6D\uC5B4\uB85C \uC54C\uB9BD\uB2C8\uB2E4.
|
|
45775
45900
|
- "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.
|
|
45901
|
+
- \uB3C4\uAD6C \uC751\uB2F5\uC5D0 \`user_message\`\uAC00 \uC788\uC73C\uBA74 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBCF4\uACE0\uD560 \uB54C \uADF8 \uBB38\uC7A5\uC744 \uC6B0\uC120 \uD65C\uC6A9\uD558\uC138\uC694. \`id\`, \`job_id\`, \`cover_letter_id\`, \`workflow_id\`, JSON \uC6D0\uBB38, \uB3C4\uAD6C \uC774\uB984\uC740 \uB0B4\uBD80 \uC791\uC5C5\uC6A9\uC73C\uB85C\uB9CC \uC4F0\uACE0 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uADF8\uB300\uB85C \uBCF5\uC0AC\uD558\uC9C0 \uB9C8\uC138\uC694.
|
|
45902
|
+
- \uB3C4\uAD6C \uC751\uB2F5\uC5D0 \`suggested_next_action\`\uC774 \uC788\uC73C\uBA74 \uB2E4\uC74C \uD589\uB3D9\uC744 \uAC15\uC694\uD558\uC9C0 \uB9D0\uACE0 \uC120\uD0DD\uC9C0\uB85C \uC81C\uC548\uD558\uC138\uC694.
|
|
45776
45903
|
- \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.
|
|
45777
45904
|
|
|
45778
45905
|
7. \uB2E4\uC74C \uB2E8\uACC4\uB97C \uBA3C\uC800 \uC81C\uC548\uD569\uB2C8\uB2E4.
|
|
45779
45906
|
- \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?".
|
|
45907
|
+
- \uACF5\uACE0 \uBD84\uC11D \uD6C4\uC5D0\uB294 \uACE7\uC7A5 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC4F0\uC9C0 \uB9D0\uACE0, \uBC18\uB4DC\uC2DC \uC120\uD0DD\uC9C0\uB97C \uBCF4\uC5EC\uC8FC\uC138\uC694: "1. \uB124, \uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uC918" / "2. \uC544\uB2C8\uC694, \uBD84\uC11D\uB9CC \uC800\uC7A5\uD574\uC918". \uC0AC\uC6A9\uC790\uAC00 1\uBC88\uC744 \uACE0\uB978 \uACBD\uC6B0\uC5D0\uB9CC \uC790\uAE30\uC18C\uAC1C\uC11C \uC791\uC131 \uD750\uB984\uC73C\uB85C \uC774\uC5B4\uAC11\uB2C8\uB2E4.
|
|
45780
45908
|
- \`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.
|
|
45781
45909
|
|
|
45782
|
-
8. \
|
|
45910
|
+
8. \uC5C5\uB370\uC774\uD2B8 \uC694\uCCAD\uC740 \uBA85\uD655\uD558\uAC8C \uCC98\uB9AC\uD569\uB2C8\uB2E4.
|
|
45911
|
+
- \uC0AC\uC6A9\uC790\uAC00 "\uCD5C\uC2E0\uC774\uC57C?", "\uC5C5\uB370\uC774\uD2B8 \uC788\uC5B4?"\uCC98\uB7FC \uD655\uC778\uB9CC \uC694\uCCAD\uD558\uBA74 \`check_for_update\`\uB97C \uD638\uCD9C\uD558\uACE0 \uACB0\uACFC\uB97C \uC26C\uC6B4 \uB9D0\uB85C \uC54C\uB824\uC8FC\uC138\uC694.
|
|
45912
|
+
- \uC0AC\uC6A9\uC790\uAC00 "\uC5C5\uB370\uC774\uD2B8\uD574\uC918", "CareerMate \uC5C5\uB370\uC774\uD2B8\uD574\uC918", "\uCD5C\uC2E0\uC73C\uB85C \uC62C\uB824\uC918"\uCC98\uB7FC \uC801\uC6A9\uC744 \uBA85\uC2DC\uD558\uBA74 \`update_careermate\`\uB97C \uBC14\uB85C \uD638\uCD9C\uD558\uC138\uC694. \uC774 \uB3C4\uAD6C\uAC00 \uCD5C\uC2E0 \uBC84\uC804 \uD655\uC778\uB3C4 \uD568\uAED8 \uD569\uB2C8\uB2E4.
|
|
45913
|
+
- \uC5C5\uB370\uC774\uD2B8 \uC131\uACF5 \uD6C4\uC5D0\uB294 \uC801\uC6A9\uD558\uB824\uBA74 AI \uC571 \uB610\uB294 MCP \uC5F0\uACB0\uC744 \uC7AC\uC2DC\uC791\uD574\uC57C \uD55C\uB2E4\uACE0 \uC548\uB0B4\uD558\uC138\uC694. \uC0AC\uC6A9\uC790 \uCEE4\uB9AC\uC5B4 \uB370\uC774\uD130\uB294 \uB85C\uCEEC\uC5D0 \uBCF4\uC874\uB418\uACE0, npm\uC5D0\uB294 \uACF5\uAC1C \uBC84\uC804 \uC815\uBCF4 \uD655\uC778/\uC124\uCE58 \uC694\uCCAD\uB9CC \uB098\uAC11\uB2C8\uB2E4.
|
|
45914
|
+
|
|
45915
|
+
9. \uAE00\uC740 AI \uD2F0 \uC548 \uB098\uAC8C \uC501\uB2C8\uB2E4.
|
|
45783
45916
|
- \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.
|
|
45784
45917
|
- \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.
|
|
45785
45918
|
|
|
45786
|
-
|
|
45919
|
+
10. \uC804\uBB38\uAC00 \uC808\uCC28\uB85C \uB9CC\uB4E4\uACE0, \uC800\uC7A5 \uC804 \uB2F9\uC2E0\uC774 \uC790\uAC00\uAC80\uC99D\uD569\uB2C8\uB2E4.
|
|
45787
45920
|
- \uACF5\uACE0 \uBD84\uC11D\xB7\uC790\uC18C\uC11C\xB7\uBA74\uC811 \uC790\uB8CC\uCC98\uB7FC \uC0AC\uC6A9\uC790 \uB300\uBA74 \uACB0\uACFC\uB97C \uB9CC\uB4E4\uAE30 \uC804\uC5D0\uB294, \uC989\uB2F5\uD558\uC9C0 \uB9D0\uACE0 \`get_workflow_guide\`(workflow_id \uC9C0\uC815)\uB97C \uBA3C\uC800 \uD638\uCD9C\uD574 \uADF8 \uC791\uC5C5\uC758 \uC804\uBB38\uAC00 \uC2E4\uD589 \uC808\uCC28(EOP)\uC640 \uC801\uC6A9\uD560 \uD50C\uB808\uC774\uBD81\xB7\uAC80\uC99D\uAE30 \uC21C\uC11C\uB97C \uBC1B\uC73C\uC138\uC694. \uC774\uC5B4 \`get_playbook\`\uC73C\uB85C \uB3C4\uBA54\uC778 \uC9C0\uC2DD\uC744 \uBC1B\uC544 \uC791\uC131\uD558\uACE0, save\uB85C \uC800\uC7A5\uD558\uAE30 \uC9C1\uC804 \`get_verifier\`\uB85C \uAC80\uC99D \uB8E8\uBE0C\uB9AD\uC744 \uBC1B\uC544 **\uB2F9\uC2E0\uC774 \uC9C1\uC811** \uC810\uAC80\xB7\uC218\uC815\uD55C \uB4A4 \uC800\uC7A5\uD569\uB2C8\uB2E4.
|
|
45788
45921
|
- CareerMate\uB294 \uC2AC\uB86D \uBC00\uB3C4\xB7\uD0A4\uC6CC\uB4DC \uB9E4\uCE6D\uCC98\uB7FC \uC140 \uC218 \uC788\uB294 \uD56D\uBAA9\uB9CC \uBCF4\uC870\uB85C \uB3D5\uACE0, \uC758\uBBF8 \uD310\uB2E8(\uC774 \uAE00\uC774 \uC9C8\uBB38\uC5D0 \uB2F5\uD558\uB294\uAC00\xB7\uD1A4\uC774 \uB9DE\uB294\uAC00)\uC740 \uB2F9\uC2E0\uC774 \uD569\uB2C8\uB2E4. \uC0AC\uC2E4\xB7\uC218\uCE58\xB7\uACE0\uC720\uBA85\uC0AC\uB294 \uADF8\uB300\uB85C \uBCF4\uC874\uD569\uB2C8\uB2E4.
|
|
45789
45922
|
|
|
@@ -45838,7 +45971,7 @@ var ONBOARDING_PROMPT = `\uC774\uC81C CareerMate\uC5D0 \uC5F0\uACB0\uB418\uC5C8\
|
|
|
45838
45971
|
- \uC774\uB984, \uC5F0\uB77D\uCC98, \uD55C \uC904 \uC18C\uAC1C(headline), \uC790\uAE30\uC18C\uAC1C \uC694\uC57D(summary), \uD76C\uB9DD \uC9C1\uBB34(desired_roles), \uD76C\uB9DD \uADFC\uBB34 \uC870\uAC74(desired_conditions)\uC744 \uC815\uB9AC\uD569\uB2C8\uB2E4.
|
|
45839
45972
|
- \uAE00\uC4F0\uAE30 \uC120\uD638\uB3C4 \uD568\uAED8 \uBB3C\uC5B4\uBCF4\uC138\uC694: \uC120\uD638 \uBB38\uCCB4(preferred_tone, \uC608: "\uB2F4\uBC31\uD558\uACE0 \uAD6C\uCCB4\uC801")\uC640 \uAC15\uC870\uD558\uACE0 \uC2F6\uC740 \uD575\uC2EC \uD3EC\uC778\uD2B8(emphasis_points). \uC774 \uC815\uBCF4\uB294 \uC774\uD6C4 \uC790\uAE30\uC18C\uAC1C\uC11C \uC791\uC131\uC5D0 \uADF8\uB300\uB85C \uD65C\uC6A9\uB429\uB2C8\uB2E4.
|
|
45840
45973
|
- \uC815\uB9AC\uD55C \uB0B4\uC6A9\uC744 \`save_profile\`\uB85C \uC800\uC7A5\uD558\uC138\uC694. \uD30C\uC77C\uC5D0\uC11C \uC77D\uC5B4\uC628 \uC815\uBCF4\uB294 source\uB97C 'upload', \uC0AC\uC6A9\uC790\uAC00 \uB9D0\uB85C \uC54C\uB824\uC900 \uC815\uBCF4\uB294 'manual'\uB85C \uD45C\uC2DC\uD558\uC138\uC694.
|
|
45841
|
-
- \uACBD\uB825\xB7\uD504\uB85C\uC81D\uD2B8\xB7\uC2A4\uD0AC \uC815\uBCF4\uAC00 \uC788\uC73C\uBA74 \`add_experience\`\xB7\`add_project\`\xB7\`add_skill\`\uB85C \
|
|
45974
|
+
- \uACBD\uB825\xB7\uD504\uB85C\uC81D\uD2B8\xB7\uC2A4\uD0AC \uC815\uBCF4\uAC00 \uC788\uC73C\uBA74 \`add_experience\`\xB7\`add_project\`\xB7\`add_skill\`\uB85C \uC800\uC7A5\uD558\uC138\uC694. \uAC01 \uB3C4\uAD6C\uB294 \uBC30\uC5F4\uC744 \uBC1B\uC73C\uB2C8 \uD30C\uC545\uD55C \uD56D\uBAA9\uC744 \uD55C \uBC88\uC5D0 \uBAA8\uC544 \uB118\uAE30\uC138\uC694(\uD56D\uBAA9\uB9C8\uB2E4 \uBC18\uBCF5 \uD638\uCD9C\uD558\uC9C0 \uB9C8\uC138\uC694). \uAC19\uC740 \uD56D\uBAA9\uC744 \uB2E4\uC2DC \uB123\uC5B4\uB3C4 \uC911\uBCF5 \uC5C6\uC774 \uAC31\uC2E0\uB429\uB2C8\uB2E4(\uACBD\uB825 achievements\uB294 \uAC00\uB2A5\uD558\uBA74 \uC815\uB7C9 \uC9C0\uD45C \uD3EC\uD568). \uC774 \uD56D\uBAA9\uB4E4\uC740 \uC628\uBCF4\uB529 \uC644\uC131\uB3C4(has_experience/has_skills)\uC5D0 \uBC18\uC601\uB429\uB2C8\uB2E4.
|
|
45842
45975
|
- \uD655\uC2E4\uD558\uC9C0 \uC54A\uC740 \uC815\uBCF4\uB294 \uC9C0\uC5B4\uB0B4\uC9C0 \uB9D0\uACE0 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uD655\uC778\uD558\uC138\uC694.
|
|
45843
45976
|
|
|
45844
45977
|
# 3\uB2E8\uACC4 \u2014 \uC774\uB825\uC11C \uB4F1\uB85D
|
|
@@ -45956,7 +46089,7 @@ var WORKFLOWS = [
|
|
|
45956
46089
|
'\uD30C\uC77C \uC785\uB825 \uBD84\uAE30: \u2460 \uC0AC\uC6A9\uC790\uAC00 "\uD30C\uC77C\uC774 \uC788\uB2E4"\uACE0 \uD558\uBA74 `open_inbox`\uB85C \uC778\uC785 \uD3F4\uB354\uB97C \uC5F4\uC5B4 \uD30C\uC77C\uC744 \uB123\uAC8C \uD558\uACE0, "\uB2E4 \uB123\uC5C8\uB2E4"\uACE0 \uD558\uBA74 `read_inbox`\uB85C \uBCF8\uBB38\uC744 \uC77D\uB294\uB2E4(pdf\xB7\uC774\uBBF8\uC9C0\uB294 read_inbox\uAC00 \uACBD\uB85C\uB9CC \uC8FC\uBBC0\uB85C \uD074\uB77C\uC774\uC5B8\uD2B8\uC758 \uD30C\uC77C \uC77D\uAE30\uB85C \uC9C1\uC811 \uC77D\uB294\uB2E4). \u2461 \uC0AC\uC6A9\uC790\uAC00 \uD30C\uC77C \uACBD\uB85C\uB97C \uC9C1\uC811 \uC54C\uB824\uC8FC\uBA74 `read_document`\uB85C \uC77D\uB294\uB2E4. \u2462 "\uC5C6\uB2E4/\uC9C1\uC811 \uC785\uB825/\uD14D\uC2A4\uD2B8\uB85C \uBD99\uC5EC\uB123\uAE30"\uBA74 \uD3F4\uB354\uB97C \uC5F4\uC9C0 \uB9D0\uACE0 \uBC1B\uC740 \uD14D\uC2A4\uD2B8\uB97C \uADF8\uB300\uB85C \uC4F4\uB2E4. (\uBB3B\uC9C0\uB3C4 \uC54A\uACE0 open_inbox\uB85C \uD3F4\uB354\uB97C \uC790\uB3D9\uC73C\uB85C \uC5F4\uC9C0 \uC54A\uB294\uB2E4.)',
|
|
45957
46090
|
"\uC218\uC9D1\uD55C \uC815\uBCF4\uB97C \uAD6C\uC870\uD654\uD574 `save_profile`\uB85C \uC800\uC7A5\uD55C\uB2E4(\uC774\uB984\xB7\uC5F0\uB77D\uCC98\xB7headline\xB7summary\xB7desired_roles\xB7desired_conditions, \uADF8\uB9AC\uACE0 \uAE00\uC4F0\uAE30 \uC120\uD638\uC778 preferred_tone\uACFC emphasis_points \uD3EC\uD568). \uD30C\uC77C \uCD9C\uCC98\uB294 source=upload, \uC9C1\uC811 \uC785\uB825\uC740 manual.",
|
|
45958
46091
|
"\uC5C5\uB85C\uB4DC\uD55C \uC774\uB825\uC11C/\uACBD\uB825\uAE30\uC220\uC11C/\uD3EC\uD2B8\uD3F4\uB9AC\uC624 \uBCF8\uBB38\uC744 `add_resume`\uB85C \uC800\uC7A5\uD55C\uB2E4(kind\xB7title \uC9C0\uC815, \uB300\uD45C \uBB38\uC11C\uB294 is_primary=true).",
|
|
45959
|
-
"\uACBD\uB825\xB7\uD504\uB85C\uC81D\uD2B8\xB7\uC2A4\uD0AC\uC774 \uD30C\uC545\uB418\uBA74 `add_experience`\xB7`add_project`\xB7`add_skill`\uB85C \
|
|
46092
|
+
"\uACBD\uB825\xB7\uD504\uB85C\uC81D\uD2B8\xB7\uC2A4\uD0AC\uC774 \uD30C\uC545\uB418\uBA74 `add_experience`\xB7`add_project`\xB7`add_skill`\uB85C \uC800\uC7A5\uD55C\uB2E4 \u2014 \uAC01 \uB3C4\uAD6C\uB294 \uBC30\uC5F4\uC744 \uBC1B\uC73C\uBBC0\uB85C \uD30C\uC545\uD55C \uD56D\uBAA9\uC744 \uD55C \uBC88\uC5D0 \uBAA8\uC544 \uB118\uAE34\uB2E4(\uD56D\uBAA9\uB9C8\uB2E4 \uBC18\uBCF5 \uD638\uCD9C\uD558\uC9C0 \uC54A\uB294\uB2E4). \uAC19\uC740 \uD56D\uBAA9\uC744 \uB2E4\uC2DC \uB123\uC5B4\uB3C4 \uC911\uBCF5 \uC5C6\uC774 \uAC31\uC2E0\uB41C\uB2E4(\uC644\uC131\uB3C4\uC758 has_experience/has_skills\uC5D0 \uBC18\uC601).",
|
|
45960
46093
|
"\uAE30\uC874 \uC790\uAE30\uC18C\uAC1C\uC11C\uAC00 \uC788\uC73C\uBA74 `save_cover_letter_version`\uC73C\uB85C \uC800\uC7A5\uD55C\uB2E4(title+content\uBA74 v1\uB85C \uC800\uC7A5, \uC5C6\uC73C\uBA74 \uB098\uC911\uC5D0 \uC791\uC131 \uAC00\uB2A5\uD568\uC744 \uC548\uB0B4).",
|
|
45961
46094
|
"`open_dashboard`\uB97C \uD638\uCD9C\uD574 \uC0AC\uC6A9\uC790\uAC00 \uC790\uAE30 \uB370\uC774\uD130\uB97C \uD655\uC778\uD558\uAC8C \uD55C\uB2E4.",
|
|
45962
46095
|
"\uB2E4\uC2DC `get_onboarding_status`\uB85C \uC644\uC131\uB3C4\uB97C \uD655\uC778\uD558\uACE0, \uB2E4\uC74C \uB2E8\uACC4(\uACF5\uACE0 \uBD84\uC11D \uB4F1)\uB97C \uC81C\uC548\uD55C\uB2E4."
|
|
@@ -45974,8 +46107,8 @@ var WORKFLOWS = [
|
|
|
45974
46107
|
"\uC544\uC9C1 \uC800\uC7A5\uB418\uC9C0 \uC54A\uC740 \uACF5\uACE0\uB77C\uBA74 `save_job_posting`\uC73C\uB85C \uACF5\uACE0\uB97C \uC800\uC7A5\uD558\uACE0 job_id\uB97C \uD655\uBCF4\uD55C\uB2E4.",
|
|
45975
46108
|
"\uB3C4\uCD9C\uD55C \uBD84\uC11D \uACB0\uACFC\uB97C `save_fit_analysis`\uB85C \uC800\uC7A5\uD55C\uB2E4(job_id, score, summary, strengths, gaps, matched_keywords, missing_keywords, recommendations).",
|
|
45976
46109
|
"\uBD84\uC11D \uACB0\uACFC\uB97C \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC26C\uC6B4 \uD55C\uAD6D\uC5B4\uB85C \uC124\uBA85\uD55C\uB2E4(\uC798 \uB9DE\uB294 \uC810, \uBCF4\uC644\uD560 \uC810, \uCD94\uCC9C \uC804\uB7B5). \uAE30\uC220 \uC6A9\uC5B4\uB294 \uBE7C\uACE0 \uC804\uB2EC\uD55C\uB2E4.",
|
|
45977
|
-
'"\uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uB4DC\uB9B4\uAE4C\uC694?"\uB77C\uACE0 \uC790\uAE30\uC18C\uAC1C\uC11C \
|
|
45978
|
-
'\uC0AC\uC6A9\uC790\uAC00 \
|
|
46110
|
+
'\uBD84\uC11D \uB4A4 \uACE7\uC7A5 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC4F0\uC9C0 \uB9D0\uACE0, "\uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uB4DC\uB9B4\uAE4C\uC694?"\uB77C\uACE0 \uBB3B\uACE0 \uC120\uD0DD\uC9C0\uB97C \uC81C\uC2DC\uD55C\uB2E4: \u2460 "\uB124, \uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uC918" \u2461 "\uC544\uB2C8\uC694, \uBD84\uC11D\uB9CC \uC800\uC7A5\uD574\uC918".',
|
|
46111
|
+
'\uC0AC\uC6A9\uC790\uAC00 \u2460\uC744 \uC120\uD0DD\uD558\uBA74 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC791\uC131\uD558\uAE30 \uC9C1\uC804\uC5D0 `get_writing_style_guide`\uB97C \uD638\uCD9C\uD574 "AI \uD2F0 \uC548 \uB098\uB294 \uAE00\uC4F0\uAE30 \uADDC\uCE59"\uC744 \uAC00\uC838\uC628\uB2E4. \u2461\uB97C \uC120\uD0DD\uD558\uBA74 \uC790\uC18C\uC11C \uC791\uC131 \uC5C6\uC774 \uBA48\uCD94\uACE0 \uC800\uC7A5 \uACB0\uACFC\uC640 \uB300\uC2DC\uBCF4\uB4DC \uD655\uC778\uB9CC \uC548\uB0B4\uD55C\uB2E4.',
|
|
45979
46112
|
"\uC801\uD569\uB3C4 \uBD84\uC11D\xB7\uAE00\uC4F0\uAE30 \uC120\uD638\uC640 \uAE00\uC4F0\uAE30 \uADDC\uCE59\uC744 \uD568\uAED8 \uBC18\uC601\uD574 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC791\uC131\uD558\uACE0(\uBC88\uC5ED\uD22C\xB7\uD074\uB9AC\uC170\xB7\uAE30\uACC4\uC801 \uBCD1\uB82C \uC81C\uAC70, \uC0AC\uC2E4\xB7\uC218\uCE58\uB294 \uADF8\uB300\uB85C), `save_cover_letter_version`\uC73C\uB85C \uC800\uC7A5\uD55C\uB2E4(job_id \uC5F0\uACB0, note\uC5D0 \uC791\uC131 \uC758\uB3C4 \uC694\uC57D).",
|
|
45980
46113
|
"`open_application`\uC73C\uB85C \uD574\uB2F9 \uC9C0\uC6D0 \uAC74\uC744 \uC5F4\uC5B4 \uC0AC\uC6A9\uC790\uAC00 \uACB0\uACFC\uB97C \uD655\uC778\uD558\uAC8C \uD558\uACE0, \uC9C0\uC6D0 \uC0C1\uD0DC \uBCC0\uACBD(\uC608: \uC9C0\uC6D0 \uC608\uC815/\uC9C0\uC6D0 \uC644\uB8CC)\uC744 \uC81C\uC548\uD55C\uB2E4."
|
|
45981
46114
|
]
|
|
@@ -46751,20 +46884,286 @@ function getServerInfo() {
|
|
|
46751
46884
|
|
|
46752
46885
|
// apps/web/src/settings.ts
|
|
46753
46886
|
import fs4 from "node:fs";
|
|
46887
|
+
import os2 from "node:os";
|
|
46754
46888
|
import path4 from "node:path";
|
|
46889
|
+
import { spawn } from "node:child_process";
|
|
46890
|
+
import { fileURLToPath } from "node:url";
|
|
46891
|
+
var __dirname = path4.dirname(fileURLToPath(import.meta.url));
|
|
46755
46892
|
function listTables(db) {
|
|
46756
46893
|
return db.prepare(
|
|
46757
46894
|
`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name != '_meta' ORDER BY name`
|
|
46758
46895
|
).all().map((r) => r.name);
|
|
46759
46896
|
}
|
|
46897
|
+
var RESTORE_ORDER = [
|
|
46898
|
+
"profile",
|
|
46899
|
+
"experiences",
|
|
46900
|
+
"projects",
|
|
46901
|
+
"skills",
|
|
46902
|
+
"documents",
|
|
46903
|
+
"jobs",
|
|
46904
|
+
"cover_letters",
|
|
46905
|
+
"cover_letter_versions",
|
|
46906
|
+
"fit_analyses",
|
|
46907
|
+
"applications",
|
|
46908
|
+
"interview_preps",
|
|
46909
|
+
"activities"
|
|
46910
|
+
];
|
|
46911
|
+
function quoteIdent(name) {
|
|
46912
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
46913
|
+
}
|
|
46914
|
+
function shQuote(value) {
|
|
46915
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
46916
|
+
}
|
|
46917
|
+
function cmdQuote(value) {
|
|
46918
|
+
return value.replace(/"/g, '""');
|
|
46919
|
+
}
|
|
46920
|
+
function schemaVersion(db) {
|
|
46921
|
+
const ver = db.prepare(`SELECT value FROM _meta WHERE key='schema_version'`).get();
|
|
46922
|
+
return Number(ver?.value ?? 0);
|
|
46923
|
+
}
|
|
46924
|
+
function tableColumns(db, table) {
|
|
46925
|
+
return db.prepare(`PRAGMA table_info(${quoteIdent(table)})`).all().map((r) => r.name);
|
|
46926
|
+
}
|
|
46927
|
+
function isRecord(v) {
|
|
46928
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
46929
|
+
}
|
|
46930
|
+
function appRoot() {
|
|
46931
|
+
return BUNDLED ? __dirname : path4.resolve(__dirname, "..", "..", "..");
|
|
46932
|
+
}
|
|
46933
|
+
function shortcutFolder() {
|
|
46934
|
+
const override = process.env.CAREERMATE_SHORTCUT_DIR?.trim();
|
|
46935
|
+
if (override) return override;
|
|
46936
|
+
const desktop = path4.join(os2.homedir(), "Desktop");
|
|
46937
|
+
const parent = fs4.existsSync(desktop) ? desktop : getExportsDir();
|
|
46938
|
+
return path4.join(parent, "CareerMate \uB300\uC2DC\uBCF4\uB4DC");
|
|
46939
|
+
}
|
|
46940
|
+
function openFolder(dir) {
|
|
46941
|
+
try {
|
|
46942
|
+
if (process.platform === "win32") spawn("explorer", [dir], { stdio: "ignore", detached: true }).unref();
|
|
46943
|
+
else if (process.platform === "darwin") spawn("open", [dir], { stdio: "ignore", detached: true }).unref();
|
|
46944
|
+
else spawn("xdg-open", [dir], { stdio: "ignore", detached: true }).unref();
|
|
46945
|
+
} catch {
|
|
46946
|
+
}
|
|
46947
|
+
}
|
|
46948
|
+
function parseBackup(raw) {
|
|
46949
|
+
if (!isRecord(raw) || !isRecord(raw.tables)) {
|
|
46950
|
+
throw new Error("CareerMate \uBC31\uC5C5 JSON \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4.");
|
|
46951
|
+
}
|
|
46952
|
+
const rawTables = raw.tables;
|
|
46953
|
+
const db = getDb();
|
|
46954
|
+
const currentVersion = schemaVersion(db);
|
|
46955
|
+
const version = Number(raw.version);
|
|
46956
|
+
if (!Number.isInteger(version) || version < 0) {
|
|
46957
|
+
throw new Error("\uBC31\uC5C5 \uD30C\uC77C\uC758 \uC2A4\uD0A4\uB9C8 \uBC84\uC804\uC744 \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
46958
|
+
}
|
|
46959
|
+
if (version > currentVersion) {
|
|
46960
|
+
throw new Error("\uC774 \uBC31\uC5C5\uC740 \uB354 \uCD5C\uC2E0 CareerMate\uC5D0\uC11C \uB9CC\uB4E4\uC5B4\uC84C\uC2B5\uB2C8\uB2E4. \uC571\uC744 \uC5C5\uB370\uC774\uD2B8\uD55C \uB4A4 \uB2E4\uC2DC \uAC00\uC838\uC640 \uC8FC\uC138\uC694.");
|
|
46961
|
+
}
|
|
46962
|
+
const allowedTables = new Set(listTables(db));
|
|
46963
|
+
const unknownTables = Object.keys(rawTables).filter((name) => !allowedTables.has(name));
|
|
46964
|
+
if (unknownTables.length > 0) {
|
|
46965
|
+
throw new Error(`\uD604\uC7AC \uC571\uC5D0\uC11C \uC54C \uC218 \uC5C6\uB294 \uB370\uC774\uD130\uAC00 \uD3EC\uD568\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4: ${unknownTables.join(", ")}`);
|
|
46966
|
+
}
|
|
46967
|
+
const columnsByTable = /* @__PURE__ */ new Map();
|
|
46968
|
+
for (const table of allowedTables) columnsByTable.set(table, new Set(tableColumns(db, table)));
|
|
46969
|
+
const tables = {};
|
|
46970
|
+
const counts = {};
|
|
46971
|
+
let totalRows = 0;
|
|
46972
|
+
for (const table of allowedTables) {
|
|
46973
|
+
const rows = rawTables[table] ?? [];
|
|
46974
|
+
if (!Array.isArray(rows)) throw new Error(`${table} \uB370\uC774\uD130\uAC00 \uBC30\uC5F4 \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4.`);
|
|
46975
|
+
const columns = columnsByTable.get(table);
|
|
46976
|
+
tables[table] = rows.map((row, rowIndex) => {
|
|
46977
|
+
if (!isRecord(row)) throw new Error(`${table} ${rowIndex + 1}\uBC88\uC9F8 \uD589\uC774 \uC62C\uBC14\uB978 \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4.`);
|
|
46978
|
+
const clean = {};
|
|
46979
|
+
for (const [key, value] of Object.entries(row)) {
|
|
46980
|
+
if (!columns.has(key)) throw new Error(`${table} \uB370\uC774\uD130\uC5D0 \uD604\uC7AC \uC571\uC5D0\uC11C \uC4F0\uC9C0 \uC54A\uB294 \uD56D\uBAA9(${key})\uC774 \uC788\uC2B5\uB2C8\uB2E4.`);
|
|
46981
|
+
if (value !== null && !["string", "number", "boolean"].includes(typeof value)) {
|
|
46982
|
+
throw new Error(`${table} \uB370\uC774\uD130\uC758 ${key} \uAC12 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`);
|
|
46983
|
+
}
|
|
46984
|
+
clean[key] = value;
|
|
46985
|
+
}
|
|
46986
|
+
if (Object.keys(clean).length === 0) throw new Error(`${table} ${rowIndex + 1}\uBC88\uC9F8 \uD589\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.`);
|
|
46987
|
+
return clean;
|
|
46988
|
+
});
|
|
46989
|
+
counts[table] = tables[table].length;
|
|
46990
|
+
totalRows += tables[table].length;
|
|
46991
|
+
}
|
|
46992
|
+
const warnings = [...allowedTables].filter((table) => !(table in rawTables)).map((table) => `${table} \uB370\uC774\uD130\uAC00 \uBC31\uC5C5 \uD30C\uC77C\uC5D0 \uC5C6\uC5B4 \uBE48 \uAC12\uC73C\uB85C \uAC00\uC838\uC635\uB2C8\uB2E4.`);
|
|
46993
|
+
return {
|
|
46994
|
+
exported_at: typeof raw.exported_at === "string" ? raw.exported_at : null,
|
|
46995
|
+
version,
|
|
46996
|
+
current_version: currentVersion,
|
|
46997
|
+
tables,
|
|
46998
|
+
counts,
|
|
46999
|
+
total_rows: totalRows,
|
|
47000
|
+
warnings
|
|
47001
|
+
};
|
|
47002
|
+
}
|
|
47003
|
+
function macLauncherScript() {
|
|
47004
|
+
const root2 = appRoot();
|
|
47005
|
+
const dataDir = getDataDir();
|
|
47006
|
+
const nodePath = process.execPath;
|
|
47007
|
+
const binPath = path4.join(root2, "bin", "careermate.mjs");
|
|
47008
|
+
const bundledWeb = BUNDLED ? path4.join(root2, "web.mjs") : path4.join(root2, "dist", "web.mjs");
|
|
47009
|
+
return `#!/bin/zsh
|
|
47010
|
+
set -e
|
|
47011
|
+
|
|
47012
|
+
export CAREERMATE_DATA_DIR=${shQuote(dataDir)}
|
|
47013
|
+
NODE=${shQuote(nodePath)}
|
|
47014
|
+
ROOT=${shQuote(root2)}
|
|
47015
|
+
BIN=${shQuote(binPath)}
|
|
47016
|
+
BUNDLED_WEB=${shQuote(bundledWeb)}
|
|
47017
|
+
INFO="$CAREERMATE_DATA_DIR/server.json"
|
|
47018
|
+
|
|
47019
|
+
if [ ! -x "$NODE" ]; then
|
|
47020
|
+
NODE="$(command -v node || true)"
|
|
47021
|
+
fi
|
|
47022
|
+
if [ -z "$NODE" ]; then
|
|
47023
|
+
echo "Node.js\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. CareerMate\uB97C \uC124\uCE58\uD55C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC138\uC694."
|
|
47024
|
+
exit 1
|
|
47025
|
+
fi
|
|
47026
|
+
|
|
47027
|
+
URL=$("$NODE" -e 'const fs=require("fs"); try { const info=JSON.parse(fs.readFileSync(process.argv[1], "utf8")); process.stdout.write(info.url || ""); } catch {}' "$INFO")
|
|
47028
|
+
if [ -n "$URL" ] && command -v curl >/dev/null 2>&1 && curl -fsS --max-time 1 "$URL/api/health" >/dev/null 2>&1; then
|
|
47029
|
+
open "$URL"
|
|
47030
|
+
exit 0
|
|
47031
|
+
fi
|
|
47032
|
+
|
|
47033
|
+
echo "CareerMate \uB300\uC2DC\uBCF4\uB4DC\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4."
|
|
47034
|
+
if [ -f "$BIN" ]; then
|
|
47035
|
+
cd "$ROOT"
|
|
47036
|
+
exec "$NODE" "$BIN" start
|
|
47037
|
+
fi
|
|
47038
|
+
|
|
47039
|
+
if [ -f "$BUNDLED_WEB" ]; then
|
|
47040
|
+
cd "$(dirname "$BUNDLED_WEB")"
|
|
47041
|
+
exec "$NODE" --no-warnings --experimental-sqlite "$BUNDLED_WEB"
|
|
47042
|
+
fi
|
|
47043
|
+
|
|
47044
|
+
if command -v careermate >/dev/null 2>&1; then
|
|
47045
|
+
exec careermate start
|
|
47046
|
+
fi
|
|
47047
|
+
|
|
47048
|
+
exec npx -y careermate start
|
|
47049
|
+
`;
|
|
47050
|
+
}
|
|
47051
|
+
function windowsLauncherScript() {
|
|
47052
|
+
const root2 = appRoot();
|
|
47053
|
+
const dataDir = getDataDir();
|
|
47054
|
+
const nodePath = process.execPath;
|
|
47055
|
+
const binPath = path4.join(root2, "bin", "careermate.mjs");
|
|
47056
|
+
const bundledWeb = BUNDLED ? path4.join(root2, "web.mjs") : path4.join(root2, "dist", "web.mjs");
|
|
47057
|
+
return `@echo off
|
|
47058
|
+
setlocal
|
|
47059
|
+
set "CAREERMATE_DATA_DIR=${cmdQuote(dataDir)}"
|
|
47060
|
+
set "NODE=${cmdQuote(nodePath)}"
|
|
47061
|
+
set "ROOT=${cmdQuote(root2)}"
|
|
47062
|
+
set "BIN=${cmdQuote(binPath)}"
|
|
47063
|
+
set "BUNDLED_WEB=${cmdQuote(bundledWeb)}"
|
|
47064
|
+
set "INFO=%CAREERMATE_DATA_DIR%\\server.json"
|
|
47065
|
+
set "URL="
|
|
47066
|
+
|
|
47067
|
+
if not exist "%NODE%" (
|
|
47068
|
+
for /f "usebackq delims=" %%N in (\`where node 2^>nul\`) do (
|
|
47069
|
+
set "NODE=%%N"
|
|
47070
|
+
goto :node_found
|
|
47071
|
+
)
|
|
47072
|
+
)
|
|
47073
|
+
:node_found
|
|
47074
|
+
if not exist "%NODE%" (
|
|
47075
|
+
echo Node.js\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. CareerMate\uB97C \uC124\uCE58\uD55C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.
|
|
47076
|
+
pause
|
|
47077
|
+
exit /b 1
|
|
47078
|
+
)
|
|
47079
|
+
|
|
47080
|
+
for /f "usebackq delims=" %%U in (\`"%NODE%" -e "const fs=require('fs'); try { const info=JSON.parse(fs.readFileSync(process.argv[1], 'utf8')); process.stdout.write(info.url || ''); } catch {}" "%INFO%"\`) do set "URL=%%U"
|
|
47081
|
+
if defined URL (
|
|
47082
|
+
powershell -NoProfile -ExecutionPolicy Bypass -Command "try { Invoke-WebRequest -UseBasicParsing -TimeoutSec 1 '%URL%/api/health' | Out-Null; Start-Process '%URL%'; exit 0 } catch { exit 1 }"
|
|
47083
|
+
if not errorlevel 1 exit /b 0
|
|
47084
|
+
)
|
|
47085
|
+
|
|
47086
|
+
echo CareerMate \uB300\uC2DC\uBCF4\uB4DC\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4.
|
|
47087
|
+
if exist "%BIN%" (
|
|
47088
|
+
cd /d "%ROOT%"
|
|
47089
|
+
"%NODE%" "%BIN%" start
|
|
47090
|
+
exit /b %ERRORLEVEL%
|
|
47091
|
+
)
|
|
47092
|
+
|
|
47093
|
+
if exist "%BUNDLED_WEB%" (
|
|
47094
|
+
cd /d "%ROOT%"
|
|
47095
|
+
"%NODE%" --no-warnings --experimental-sqlite "%BUNDLED_WEB%"
|
|
47096
|
+
exit /b %ERRORLEVEL%
|
|
47097
|
+
)
|
|
47098
|
+
|
|
47099
|
+
where careermate >nul 2>nul
|
|
47100
|
+
if not errorlevel 1 (
|
|
47101
|
+
careermate start
|
|
47102
|
+
exit /b %ERRORLEVEL%
|
|
47103
|
+
)
|
|
47104
|
+
|
|
47105
|
+
npx -y careermate start
|
|
47106
|
+
`;
|
|
47107
|
+
}
|
|
47108
|
+
function linuxLauncherScript() {
|
|
47109
|
+
return macLauncherScript().replace("#!/bin/zsh", "#!/usr/bin/env sh").replace('\n open "$URL"', '\n xdg-open "$URL"');
|
|
47110
|
+
}
|
|
46760
47111
|
function exportAll() {
|
|
46761
47112
|
const db = getDb();
|
|
46762
47113
|
const tables = {};
|
|
46763
47114
|
for (const t of listTables(db)) {
|
|
46764
|
-
tables[t] = db.prepare(`SELECT * FROM ${t}`).all();
|
|
47115
|
+
tables[t] = db.prepare(`SELECT * FROM ${quoteIdent(t)}`).all();
|
|
46765
47116
|
}
|
|
46766
|
-
|
|
46767
|
-
|
|
47117
|
+
return { exported_at: (/* @__PURE__ */ new Date()).toISOString(), version: schemaVersion(db), tables };
|
|
47118
|
+
}
|
|
47119
|
+
function previewBackupImport(raw) {
|
|
47120
|
+
const parsed = parseBackup(raw);
|
|
47121
|
+
return {
|
|
47122
|
+
exported_at: parsed.exported_at,
|
|
47123
|
+
version: parsed.version,
|
|
47124
|
+
current_version: parsed.current_version,
|
|
47125
|
+
counts: parsed.counts,
|
|
47126
|
+
total_rows: parsed.total_rows,
|
|
47127
|
+
warnings: parsed.warnings
|
|
47128
|
+
};
|
|
47129
|
+
}
|
|
47130
|
+
function restoreBackup(raw) {
|
|
47131
|
+
const parsed = parseBackup(raw);
|
|
47132
|
+
const safety = createBackup();
|
|
47133
|
+
const db = getDb();
|
|
47134
|
+
const tables = listTables(db);
|
|
47135
|
+
const restoreOrder = [
|
|
47136
|
+
...RESTORE_ORDER.filter((table) => tables.includes(table)),
|
|
47137
|
+
...tables.filter((table) => !RESTORE_ORDER.includes(table))
|
|
47138
|
+
];
|
|
47139
|
+
tx(() => {
|
|
47140
|
+
for (const table of [...restoreOrder].reverse()) {
|
|
47141
|
+
db.prepare(`DELETE FROM ${quoteIdent(table)}`).run();
|
|
47142
|
+
}
|
|
47143
|
+
for (const table of restoreOrder) {
|
|
47144
|
+
for (const row of parsed.tables[table] ?? []) {
|
|
47145
|
+
const columns = Object.keys(row);
|
|
47146
|
+
const placeholders = columns.map(() => "?").join(",");
|
|
47147
|
+
const values2 = columns.map((column) => {
|
|
47148
|
+
const value = row[column];
|
|
47149
|
+
return typeof value === "boolean" ? Number(value) : value;
|
|
47150
|
+
});
|
|
47151
|
+
db.prepare(
|
|
47152
|
+
`INSERT INTO ${quoteIdent(table)} (${columns.map(quoteIdent).join(",")}) VALUES (${placeholders})`
|
|
47153
|
+
).run(...values2);
|
|
47154
|
+
}
|
|
47155
|
+
}
|
|
47156
|
+
const fkProblems = db.prepare(`PRAGMA foreign_key_check`).all();
|
|
47157
|
+
if (fkProblems.length > 0) {
|
|
47158
|
+
throw new Error("\uBC31\uC5C5 \uD30C\uC77C\uC758 \uB370\uC774\uD130 \uAD00\uACC4\uAC00 \uB9DE\uC9C0 \uC54A\uC544 \uAC00\uC838\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
47159
|
+
}
|
|
47160
|
+
});
|
|
47161
|
+
return {
|
|
47162
|
+
ok: true,
|
|
47163
|
+
safety_backup_path: safety.backup_path,
|
|
47164
|
+
safety_json_path: safety.json_path,
|
|
47165
|
+
restored: previewBackupImport(raw)
|
|
47166
|
+
};
|
|
46768
47167
|
}
|
|
46769
47168
|
function createBackup() {
|
|
46770
47169
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -46785,6 +47184,38 @@ function createBackup() {
|
|
|
46785
47184
|
fs4.writeFileSync(jsonBackup, JSON.stringify(exportAll(), null, 2), "utf8");
|
|
46786
47185
|
return { backup_path: dbBackup, json_path: jsonBackup };
|
|
46787
47186
|
}
|
|
47187
|
+
function createDashboardShortcut(open = false) {
|
|
47188
|
+
const dir = shortcutFolder();
|
|
47189
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
47190
|
+
const readme = `CareerMate \uB300\uC2DC\uBCF4\uB4DC \uBC14\uB85C\uAC00\uAE30
|
|
47191
|
+
|
|
47192
|
+
- \uB300\uC2DC\uBCF4\uB4DC\uAC00 \uC774\uBBF8 \uC2E4\uD589 \uC911\uC774\uBA74 \uBE0C\uB77C\uC6B0\uC800\uB9CC \uC5FD\uB2C8\uB2E4.
|
|
47193
|
+
- \uB300\uC2DC\uBCF4\uB4DC\uAC00 \uAEBC\uC838 \uC788\uC73C\uBA74 \uC11C\uBC84\uB97C \uC2DC\uC791\uD558\uACE0 \uBE0C\uB77C\uC6B0\uC800\uB97C \uC5FD\uB2C8\uB2E4.
|
|
47194
|
+
- \uC774 \uD3F4\uB354\uB294 \uC774 \uCEF4\uD4E8\uD130\uC758 CareerMate \uC124\uCE58 \uACBD\uB85C\uC640 \uB370\uC774\uD130 \uD3F4\uB354\uB97C \uAE30\uC900\uC73C\uB85C \uB9CC\uB4E4\uC5B4\uC84C\uC2B5\uB2C8\uB2E4.
|
|
47195
|
+
`;
|
|
47196
|
+
const readmePath = path4.join(dir, "README.txt");
|
|
47197
|
+
fs4.writeFileSync(readmePath, readme, "utf8");
|
|
47198
|
+
let launcherPath;
|
|
47199
|
+
if (process.platform === "win32") {
|
|
47200
|
+
launcherPath = path4.join(dir, "CareerMate \uB300\uC2DC\uBCF4\uB4DC.cmd");
|
|
47201
|
+
fs4.writeFileSync(launcherPath, windowsLauncherScript(), "utf8");
|
|
47202
|
+
} else if (process.platform === "darwin") {
|
|
47203
|
+
launcherPath = path4.join(dir, "CareerMate \uB300\uC2DC\uBCF4\uB4DC.command");
|
|
47204
|
+
fs4.writeFileSync(launcherPath, macLauncherScript(), { encoding: "utf8", mode: 493 });
|
|
47205
|
+
fs4.chmodSync(launcherPath, 493);
|
|
47206
|
+
} else {
|
|
47207
|
+
launcherPath = path4.join(dir, "careermate-dashboard.sh");
|
|
47208
|
+
fs4.writeFileSync(launcherPath, linuxLauncherScript(), { encoding: "utf8", mode: 493 });
|
|
47209
|
+
fs4.chmodSync(launcherPath, 493);
|
|
47210
|
+
}
|
|
47211
|
+
if (open) openFolder(dir);
|
|
47212
|
+
return {
|
|
47213
|
+
shortcut_dir: dir,
|
|
47214
|
+
launcher_path: launcherPath,
|
|
47215
|
+
platform: process.platform,
|
|
47216
|
+
files: [launcherPath, readmePath]
|
|
47217
|
+
};
|
|
47218
|
+
}
|
|
46788
47219
|
function resetAll(confirm) {
|
|
46789
47220
|
if (confirm !== "DELETE") {
|
|
46790
47221
|
return { ok: false };
|
|
@@ -46793,7 +47224,7 @@ function resetAll(confirm) {
|
|
|
46793
47224
|
const db = getDb();
|
|
46794
47225
|
db.exec("BEGIN");
|
|
46795
47226
|
try {
|
|
46796
|
-
for (const t of listTables(db)) db.exec(`DELETE FROM ${t}`);
|
|
47227
|
+
for (const t of listTables(db)) db.exec(`DELETE FROM ${quoteIdent(t)}`);
|
|
46797
47228
|
db.exec("COMMIT");
|
|
46798
47229
|
} catch (e) {
|
|
46799
47230
|
db.exec("ROLLBACK");
|
|
@@ -46821,6 +47252,9 @@ function fmt(ctx) {
|
|
|
46821
47252
|
const f = (ctx.query.get("format") ?? "md").toLowerCase();
|
|
46822
47253
|
return ["md", "html", "txt"].includes(f) ? f : "md";
|
|
46823
47254
|
}
|
|
47255
|
+
function backupError(e) {
|
|
47256
|
+
return new HttpError(400, e instanceof Error ? e.message : "\uBC31\uC5C5 \uD30C\uC77C\uC744 \uCC98\uB9AC\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.", "backup_import");
|
|
47257
|
+
}
|
|
46824
47258
|
function jobDetail(jobId) {
|
|
46825
47259
|
const job = jobRepo.get(jobId);
|
|
46826
47260
|
if (!job) throw new HttpError(404, "\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
@@ -47037,6 +47471,32 @@ function registerApiRoutes(router2) {
|
|
|
47037
47471
|
return { verify_strict: on };
|
|
47038
47472
|
});
|
|
47039
47473
|
router2.post("/api/settings/backup", () => createBackup());
|
|
47474
|
+
router2.post("/api/settings/dashboard-shortcut", async (ctx) => {
|
|
47475
|
+
const { open } = await readJsonBody(ctx.req, external_exports.object({ open: external_exports.boolean().optional() }));
|
|
47476
|
+
return { shortcut: createDashboardShortcut(open === true) };
|
|
47477
|
+
});
|
|
47478
|
+
router2.post("/api/settings/import-preview", async (ctx) => {
|
|
47479
|
+
const { backup } = await readJsonBody(ctx.req, external_exports.object({ backup: external_exports.unknown() }));
|
|
47480
|
+
try {
|
|
47481
|
+
return { preview: previewBackupImport(backup) };
|
|
47482
|
+
} catch (e) {
|
|
47483
|
+
throw backupError(e);
|
|
47484
|
+
}
|
|
47485
|
+
});
|
|
47486
|
+
router2.post("/api/settings/restore", async (ctx) => {
|
|
47487
|
+
const { backup, confirm } = await readJsonBody(
|
|
47488
|
+
ctx.req,
|
|
47489
|
+
external_exports.object({ backup: external_exports.unknown(), confirm: external_exports.string() })
|
|
47490
|
+
);
|
|
47491
|
+
if (confirm !== "RESTORE") {
|
|
47492
|
+
throw new HttpError(400, "\uBCF5\uC6D0\uC744 \uC9C4\uD589\uD558\uB824\uBA74 \uD655\uC778 \uAC12\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.", "restore_confirm_required");
|
|
47493
|
+
}
|
|
47494
|
+
try {
|
|
47495
|
+
return restoreBackup(backup);
|
|
47496
|
+
} catch (e) {
|
|
47497
|
+
throw backupError(e);
|
|
47498
|
+
}
|
|
47499
|
+
});
|
|
47040
47500
|
router2.post("/api/settings/reset", async (ctx) => {
|
|
47041
47501
|
const { confirm } = await readJsonBody(ctx.req, external_exports.object({ confirm: external_exports.string() }));
|
|
47042
47502
|
const result2 = resetAll(confirm);
|
|
@@ -47064,9 +47524,9 @@ function isDownload(v) {
|
|
|
47064
47524
|
}
|
|
47065
47525
|
|
|
47066
47526
|
// apps/web/src/server.ts
|
|
47067
|
-
var
|
|
47068
|
-
var WEB_ROOT = BUNDLED ? path5.resolve(
|
|
47069
|
-
var INSTALL_ROOT = BUNDLED ? path5.resolve(
|
|
47527
|
+
var __dirname2 = path5.dirname(fileURLToPath2(import.meta.url));
|
|
47528
|
+
var WEB_ROOT = BUNDLED ? path5.resolve(__dirname2, "public") : path5.resolve(__dirname2, "..", "public");
|
|
47529
|
+
var INSTALL_ROOT = BUNDLED ? path5.resolve(__dirname2, "site") : path5.resolve(__dirname2, "..", "..", "..", "site");
|
|
47070
47530
|
var router = new Router();
|
|
47071
47531
|
registerApiRoutes(router);
|
|
47072
47532
|
function createServer() {
|
|
@@ -47118,6 +47578,8 @@ function createServer() {
|
|
|
47118
47578
|
} catch (err) {
|
|
47119
47579
|
if (err instanceof HttpError) {
|
|
47120
47580
|
sendJson(res, err.status, { error: err.message, code: err.code });
|
|
47581
|
+
} else if (err instanceof CareerMateError) {
|
|
47582
|
+
sendJson(res, err.httpStatus, { error: err.message, code: err.code });
|
|
47121
47583
|
} else {
|
|
47122
47584
|
const message = err instanceof Error ? err.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
|
|
47123
47585
|
console.error("[careermate] \uC694\uCCAD \uCC98\uB9AC \uC911 \uC624\uB958:", message);
|
|
@@ -47166,11 +47628,11 @@ function openBrowser(url) {
|
|
|
47166
47628
|
const platform = process.platform;
|
|
47167
47629
|
try {
|
|
47168
47630
|
if (platform === "win32") {
|
|
47169
|
-
|
|
47631
|
+
spawn2("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
|
|
47170
47632
|
} else if (platform === "darwin") {
|
|
47171
|
-
|
|
47633
|
+
spawn2("open", [url], { stdio: "ignore", detached: true }).unref();
|
|
47172
47634
|
} else {
|
|
47173
|
-
|
|
47635
|
+
spawn2("xdg-open", [url], { stdio: "ignore", detached: true }).unref();
|
|
47174
47636
|
}
|
|
47175
47637
|
} catch {
|
|
47176
47638
|
}
|