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/dist/mcp.mjs CHANGED
@@ -6891,12 +6891,12 @@ var require_dist = __commonJS({
6891
6891
  throw new Error(`Unknown format "${name}"`);
6892
6892
  return f;
6893
6893
  };
6894
- function addFormats(ajv, list, fs7, exportName) {
6894
+ function addFormats(ajv, list, fs8, exportName) {
6895
6895
  var _a2;
6896
6896
  var _b2;
6897
6897
  (_a2 = (_b2 = ajv.opts.code).formats) !== null && _a2 !== void 0 ? _a2 : _b2.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
6898
6898
  for (const f of list)
6899
- ajv.addFormat(f, fs7[f]);
6899
+ ajv.addFormat(f, fs8[f]);
6900
6900
  }
6901
6901
  module2.exports = exports2 = formatsPlugin;
6902
6902
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -13333,9 +13333,9 @@ var require_timers = __commonJS({
13333
13333
  clearTimeout(this.handle);
13334
13334
  };
13335
13335
  var afterValue = function(value) {
13336
- return delay(+this).thenReturn(value);
13336
+ return delay2(+this).thenReturn(value);
13337
13337
  };
13338
- var delay = Promise2.delay = function(ms, value) {
13338
+ var delay2 = Promise2.delay = function(ms, value) {
13339
13339
  var ret2;
13340
13340
  var handle;
13341
13341
  if (value !== void 0) {
@@ -13357,7 +13357,7 @@ var require_timers = __commonJS({
13357
13357
  return ret2;
13358
13358
  };
13359
13359
  Promise2.prototype.delay = function(ms) {
13360
- return delay(ms, this);
13360
+ return delay2(ms, this);
13361
13361
  };
13362
13362
  var afterTimeout = function(promise, message, parent) {
13363
13363
  var err2;
@@ -35153,7 +35153,7 @@ var require_path_is_absolute = __commonJS({
35153
35153
  var require_files = __commonJS({
35154
35154
  "node_modules/mammoth/lib/docx/files.js"(exports2) {
35155
35155
  "use strict";
35156
- var fs7 = __require("fs");
35156
+ var fs8 = __require("fs");
35157
35157
  var url = __require("url");
35158
35158
  var os3 = __require("os");
35159
35159
  var dirname = __require("path").dirname;
@@ -35194,7 +35194,7 @@ var require_files = __commonJS({
35194
35194
  read
35195
35195
  };
35196
35196
  }
35197
- var readFile = promises.promisify(fs7.readFile.bind(fs7));
35197
+ var readFile = promises.promisify(fs8.readFile.bind(fs8));
35198
35198
  function uriToPath(uriString, platform) {
35199
35199
  if (!platform) {
35200
35200
  platform = os3.platform();
@@ -37936,11 +37936,11 @@ var require_options_reader = __commonJS({
37936
37936
  var require_unzip = __commonJS({
37937
37937
  "node_modules/mammoth/lib/unzip.js"(exports2) {
37938
37938
  "use strict";
37939
- var fs7 = __require("fs");
37939
+ var fs8 = __require("fs");
37940
37940
  var promises = require_promises();
37941
37941
  var zipfile = require_zipfile();
37942
37942
  exports2.openZip = openZip;
37943
- var readFile = promises.promisify(fs7.readFile);
37943
+ var readFile = promises.promisify(fs8.readFile);
37944
37944
  function openZip(options) {
37945
37945
  if (options.path) {
37946
37946
  return readFile(options.path).then(zipfile.openArrayBuffer);
@@ -38665,9 +38665,9 @@ var require_cjs = __commonJS({
38665
38665
  }
38666
38666
  return o;
38667
38667
  }
38668
- var fs7;
38668
+ var fs8;
38669
38669
  function get_fs() {
38670
- return fs7 || (fs7 = require$$0__default["default"]);
38670
+ return fs8 || (fs8 = require$$0__default["default"]);
38671
38671
  }
38672
38672
  function parse4(file, options) {
38673
38673
  if (file[0] == 80 && file[1] == 75) return parse_zip(file, options);
@@ -38952,7 +38952,7 @@ var require_cjs = __commonJS({
38952
38952
  }
38953
38953
  function read_file(filename2, options) {
38954
38954
  get_fs();
38955
- return parse4(fs7.readFileSync(filename2), options);
38955
+ return parse4(fs8.readFileSync(filename2), options);
38956
38956
  }
38957
38957
  function read(blob, options) {
38958
38958
  switch (options && options.type || "base64") {
@@ -39267,7 +39267,7 @@ var require_cjs = __commonJS({
39267
39267
  function write_file(cfb2, filename2, options) {
39268
39268
  get_fs();
39269
39269
  var o = _write(cfb2, options);
39270
- fs7.writeFileSync(filename2, o);
39270
+ fs8.writeFileSync(filename2, o);
39271
39271
  }
39272
39272
  function a2s(o) {
39273
39273
  var out = new Array(o.length);
@@ -39279,7 +39279,7 @@ var require_cjs = __commonJS({
39279
39279
  switch (options && options.type || "buffer") {
39280
39280
  case "file":
39281
39281
  get_fs();
39282
- fs7.writeFileSync(options.filename, o);
39282
+ fs8.writeFileSync(options.filename, o);
39283
39283
  return o;
39284
39284
  case "binary":
39285
39285
  return typeof o == "string" ? o : a2s(o);
@@ -46972,8 +46972,8 @@ function blend(fg, bg, alpha) {
46972
46972
  return Math.round(alpha * fg + (1 - alpha) * bg);
46973
46973
  }
46974
46974
  async function node_utils_fetchData(url) {
46975
- const fs7 = process.getBuiltinModule("fs/promises");
46976
- const data = await fs7.readFile(url);
46975
+ const fs8 = process.getBuiltinModule("fs/promises");
46976
+ const data = await fs8.readFile(url);
46977
46977
  return new Uint8Array(data);
46978
46978
  }
46979
46979
  function initGPU() {
@@ -47602,11 +47602,11 @@ function network_getArrayBuffer(val) {
47602
47602
  return typeof val !== "string" ? val : stringToBytes(val).buffer;
47603
47603
  }
47604
47604
  function getReadableStream(url, opts = null) {
47605
- const fs7 = process.getBuiltinModule("fs");
47605
+ const fs8 = process.getBuiltinModule("fs");
47606
47606
  const {
47607
47607
  Readable
47608
47608
  } = process.getBuiltinModule("stream");
47609
- const readStream = fs7.createReadStream(url, opts);
47609
+ const readStream = fs8.createReadStream(url, opts);
47610
47610
  return Readable.toWeb(readStream);
47611
47611
  }
47612
47612
  function getNetworkStream(url) {
@@ -64603,8 +64603,8 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
64603
64603
  url
64604
64604
  } = stream._source;
64605
64605
  this._isStreamingSupported = !disableStream;
64606
- const fs7 = process.getBuiltinModule("fs/promises");
64607
- fs7.lstat(url).then((stat) => {
64606
+ const fs8 = process.getBuiltinModule("fs/promises");
64607
+ fs8.lstat(url).then((stat) => {
64608
64608
  const readableStream = getReadableStream(url);
64609
64609
  this._reader = readableStream.getReader();
64610
64610
  const {
@@ -66250,9 +66250,9 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
66250
66250
  return;
66251
66251
  }
66252
66252
  if (reason instanceof RenderingCancelledException) {
66253
- let delay = RENDERING_CANCELLED_TIMEOUT;
66253
+ let delay2 = RENDERING_CANCELLED_TIMEOUT;
66254
66254
  if (reason.extraDelay > 0 && reason.extraDelay < 1e3) {
66255
- delay += reason.extraDelay;
66255
+ delay2 += reason.extraDelay;
66256
66256
  }
66257
66257
  intentState.streamReaderCancelTimeout = setTimeout(() => {
66258
66258
  intentState.streamReaderCancelTimeout = null;
@@ -66261,7 +66261,7 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
66261
66261
  reason,
66262
66262
  force: true
66263
66263
  });
66264
- }, delay);
66264
+ }, delay2);
66265
66265
  return;
66266
66266
  }
66267
66267
  }
@@ -75028,7 +75028,7 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
75028
75028
  const ij = i2 * width + j;
75029
75029
  const center = buf[ij];
75030
75030
  let sum = 0;
75031
- let norm = 0;
75031
+ let norm2 = 0;
75032
75032
  for (let k = 0; k < kernelSize; k++) {
75033
75033
  const y = i2 + k - halfSize;
75034
75034
  if (y < 0 || y >= height) {
@@ -75042,10 +75042,10 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
75042
75042
  const neighbour = buf[y * width + x2];
75043
75043
  const w = kernel[k * kernelSize + l2] * rangeValues[Math.abs(neighbour - center)];
75044
75044
  sum += neighbour * w;
75045
- norm += w;
75045
+ norm2 += w;
75046
75046
  }
75047
75047
  }
75048
- const pix = out[ij] = Math.round(sum / norm);
75048
+ const pix = out[ij] = Math.round(sum / norm2);
75049
75049
  histogram[pix]++;
75050
75050
  }
75051
75051
  }
@@ -84952,13 +84952,13 @@ function _array(Class2, element, params) {
84952
84952
  });
84953
84953
  }
84954
84954
  function _custom(Class2, fn, _params) {
84955
- const norm = normalizeParams(_params);
84956
- norm.abort ?? (norm.abort = true);
84955
+ const norm2 = normalizeParams(_params);
84956
+ norm2.abort ?? (norm2.abort = true);
84957
84957
  const schema = new Class2({
84958
84958
  type: "custom",
84959
84959
  check: "custom",
84960
84960
  fn,
84961
- ...norm
84961
+ ...norm2
84962
84962
  });
84963
84963
  return schema;
84964
84964
  }
@@ -92516,6 +92516,24 @@ function setVerifyStrict(on) {
92516
92516
  return on;
92517
92517
  }
92518
92518
 
92519
+ // packages/shared/src/errors.ts
92520
+ var CareerMateError = class extends Error {
92521
+ constructor(message, httpStatus = 400, code) {
92522
+ super(message);
92523
+ this.httpStatus = httpStatus;
92524
+ this.code = code;
92525
+ this.name = "CareerMateError";
92526
+ }
92527
+ httpStatus;
92528
+ code;
92529
+ };
92530
+ function notFound(message) {
92531
+ return new CareerMateError(message, 404, "not_found");
92532
+ }
92533
+ function conflict(message) {
92534
+ return new CareerMateError(message, 409, "conflict");
92535
+ }
92536
+
92519
92537
  // packages/shared/src/enums.ts
92520
92538
  var APPLICATION_STATUSES = [
92521
92539
  "draft",
@@ -92566,8 +92584,9 @@ function canTransitionStatus(from, to) {
92566
92584
  }
92567
92585
  function assertStatusTransition(from, to) {
92568
92586
  if (!canTransitionStatus(from, to)) {
92569
- throw new Error(
92570
- `'${APPLICATION_STATUS_LABELS[from]}'\uC5D0\uC11C '${APPLICATION_STATUS_LABELS[to]}'(\uC73C)\uB85C\uB294 \uC0C1\uD0DC\uB97C \uBC14\uAFC0 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
92587
+ 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.)` : "";
92588
+ throw conflict(
92589
+ `'${APPLICATION_STATUS_LABELS[from]}'\uC5D0\uC11C '${APPLICATION_STATUS_LABELS[to]}'(\uC73C)\uB85C\uB294 \uC0C1\uD0DC\uB97C \uBC14\uAFC0 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.${suffix}`
92571
92590
  );
92572
92591
  }
92573
92592
  }
@@ -92619,7 +92638,20 @@ var optLine = external_exports.string().max(MAX_LINE).optional();
92619
92638
  var optNote = external_exports.string().max(MAX_NOTE).optional();
92620
92639
  var optBody = external_exports.string().max(MAX_BODY).optional();
92621
92640
  var reqBody = external_exports.string().max(MAX_BODY).refine((s) => s.trim().length > 0, { message: EMPTY_MSG });
92622
- var strList = external_exports.array(external_exports.string().max(MAX_ITEM)).max(MAX_ITEMS);
92641
+ function toStrList(v) {
92642
+ if (typeof v !== "string") return v;
92643
+ const s = v.trim();
92644
+ if (!s) return [];
92645
+ if (s.startsWith("[")) {
92646
+ try {
92647
+ const parsed = JSON.parse(s);
92648
+ if (Array.isArray(parsed)) return parsed;
92649
+ } catch {
92650
+ }
92651
+ }
92652
+ return s.split(/[\n,]/).map((x2) => x2.trim()).filter(Boolean);
92653
+ }
92654
+ var strList = external_exports.preprocess(toStrList, external_exports.array(external_exports.string().max(MAX_ITEM)).max(MAX_ITEMS));
92623
92655
  var baseRecord = {
92624
92656
  id,
92625
92657
  created_at: isoDateTime,
@@ -92681,6 +92713,9 @@ var ExperienceRecordSchema = external_exports.object({
92681
92713
  tech: external_exports.array(external_exports.string()),
92682
92714
  order_index: external_exports.number()
92683
92715
  });
92716
+ var ExperienceBatchInputSchema = external_exports.object({
92717
+ 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.")
92718
+ });
92684
92719
  var ProjectInputSchema = external_exports.object({
92685
92720
  name: reqLine,
92686
92721
  role: optLine,
@@ -92704,6 +92739,9 @@ var ProjectRecordSchema = external_exports.object({
92704
92739
  end_date: external_exports.string().nullable(),
92705
92740
  order_index: external_exports.number()
92706
92741
  });
92742
+ var ProjectBatchInputSchema = external_exports.object({
92743
+ 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.")
92744
+ });
92707
92745
  var SkillInputSchema = external_exports.object({
92708
92746
  name: reqLine,
92709
92747
  category: external_exports.string().max(MAX_LINE).optional().describe("\uC5B8\uC5B4/\uD504\uB808\uC784\uC6CC\uD06C/\uD234/\uC18C\uD504\uD2B8\uC2A4\uD0AC \uB4F1"),
@@ -92719,6 +92757,9 @@ var SkillRecordSchema = external_exports.object({
92719
92757
  years: external_exports.number().nullable(),
92720
92758
  order_index: external_exports.number()
92721
92759
  });
92760
+ var SkillBatchInputSchema = external_exports.object({
92761
+ 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.")
92762
+ });
92722
92763
  var DocumentInputSchema = external_exports.object({
92723
92764
  kind: external_exports.enum(DOCUMENT_KINDS),
92724
92765
  title: reqLine,
@@ -92915,7 +92956,7 @@ var ApplicationContextSchema = external_exports.object({
92915
92956
  });
92916
92957
 
92917
92958
  // packages/shared/src/build-info.ts
92918
- var APP_VERSION = true ? "0.0.3" : "0.0.0-dev";
92959
+ var APP_VERSION = true ? "0.0.5" : "0.0.0-dev";
92919
92960
  var BUNDLED = true;
92920
92961
 
92921
92962
  // packages/shared/src/index.ts
@@ -92930,6 +92971,7 @@ function now() {
92930
92971
 
92931
92972
  // packages/db/src/repositories.ts
92932
92973
  var PROFILE_ID = "profile-singleton";
92974
+ var norm = (s) => (s ?? "").trim().toLowerCase();
92933
92975
  function mapProfile(r) {
92934
92976
  return {
92935
92977
  id: r.id,
@@ -93007,6 +93049,9 @@ function mapExperience(r) {
93007
93049
  updated_at: r.updated_at
93008
93050
  };
93009
93051
  }
93052
+ function expKey(company, role, start) {
93053
+ return [norm(company), norm(role), norm(start)].join("\0");
93054
+ }
93010
93055
  var experienceRepo = {
93011
93056
  list() {
93012
93057
  return getDb().prepare(`SELECT * FROM experiences ORDER BY order_index ASC, start_date DESC`).all().map(mapExperience);
@@ -93015,6 +93060,32 @@ var experienceRepo = {
93015
93060
  const r = getDb().prepare(`SELECT * FROM experiences WHERE id = ?`).get(id2);
93016
93061
  return r ? mapExperience(r) : null;
93017
93062
  },
93063
+ /** Existing experience with the same company+role+start_date, or null (for idempotent batch upserts). */
93064
+ findMatch(input) {
93065
+ const key = expKey(input.company, input.role ?? null, input.start_date ?? null);
93066
+ return this.list().find((e) => expKey(e.company, e.role, e.start_date) === key) ?? null;
93067
+ },
93068
+ /** Insert, or merge into the matching experience (omitted fields keep their stored value). */
93069
+ upsert(input) {
93070
+ const existing = this.findMatch(input);
93071
+ if (existing) return { record: this.update(existing.id, input), created: false };
93072
+ return { record: this.add(input), created: true };
93073
+ },
93074
+ /** Batch insert/merge in one transaction; within-batch duplicates collapse onto one row. */
93075
+ addMany(inputs) {
93076
+ return tx(() => {
93077
+ const records = [];
93078
+ let created = 0;
93079
+ let updated = 0;
93080
+ for (const input of inputs) {
93081
+ const { record: record2, created: isNew } = this.upsert(input);
93082
+ records.push(record2);
93083
+ if (isNew) created += 1;
93084
+ else updated += 1;
93085
+ }
93086
+ return { records, created, updated };
93087
+ });
93088
+ },
93018
93089
  add(input) {
93019
93090
  const ts = now();
93020
93091
  const id2 = newId("exp_");
@@ -93088,6 +93159,32 @@ var projectRepo = {
93088
93159
  const r = getDb().prepare(`SELECT * FROM projects WHERE id = ?`).get(id2);
93089
93160
  return r ? mapProject(r) : null;
93090
93161
  },
93162
+ /** Existing project with the same name (case-insensitive), or null (for idempotent batch upserts). */
93163
+ findByName(name) {
93164
+ const key = norm(name);
93165
+ return this.list().find((p) => norm(p.name) === key) ?? null;
93166
+ },
93167
+ /** Insert, or merge into the same-named project (omitted fields keep their stored value). */
93168
+ upsert(input) {
93169
+ const existing = this.findByName(input.name);
93170
+ if (existing) return { record: this.update(existing.id, input), created: false };
93171
+ return { record: this.add(input), created: true };
93172
+ },
93173
+ /** Batch insert/merge in one transaction; within-batch duplicates collapse onto one row. */
93174
+ addMany(inputs) {
93175
+ return tx(() => {
93176
+ const records = [];
93177
+ let created = 0;
93178
+ let updated = 0;
93179
+ for (const input of inputs) {
93180
+ const { record: record2, created: isNew } = this.upsert(input);
93181
+ records.push(record2);
93182
+ if (isNew) created += 1;
93183
+ else updated += 1;
93184
+ }
93185
+ return { records, created, updated };
93186
+ });
93187
+ },
93091
93188
  add(input) {
93092
93189
  const ts = now();
93093
93190
  const id2 = newId("prj_");
@@ -93155,6 +93252,32 @@ var skillRepo = {
93155
93252
  const r = getDb().prepare(`SELECT * FROM skills WHERE id = ?`).get(id2);
93156
93253
  return r ? mapSkill(r) : null;
93157
93254
  },
93255
+ /** Existing skill with the same name (case-insensitive), or null (for idempotent batch upserts). */
93256
+ findByName(name) {
93257
+ const key = norm(name);
93258
+ return this.list().find((s) => norm(s.name) === key) ?? null;
93259
+ },
93260
+ /** Insert, or merge into the same-named skill (omitted fields keep their stored value). */
93261
+ upsert(input) {
93262
+ const existing = this.findByName(input.name);
93263
+ if (existing) return { record: this.update(existing.id, input), created: false };
93264
+ return { record: this.add(input), created: true };
93265
+ },
93266
+ /** Batch insert/merge in one transaction; within-batch duplicates collapse onto one row. */
93267
+ addMany(inputs) {
93268
+ return tx(() => {
93269
+ const records = [];
93270
+ let created = 0;
93271
+ let updated = 0;
93272
+ for (const input of inputs) {
93273
+ const { record: record2, created: isNew } = this.upsert(input);
93274
+ records.push(record2);
93275
+ if (isNew) created += 1;
93276
+ else updated += 1;
93277
+ }
93278
+ return { records, created, updated };
93279
+ });
93280
+ },
93158
93281
  add(input) {
93159
93282
  const ts = now();
93160
93283
  const id2 = newId("skl_");
@@ -93345,6 +93468,7 @@ var coverLetterRepo = {
93345
93468
  remove(id2) {
93346
93469
  const db = getDb();
93347
93470
  return tx(() => {
93471
+ db.prepare(`UPDATE applications SET cover_letter_id=NULL, updated_at=? WHERE cover_letter_id=?`).run(now(), id2);
93348
93472
  db.prepare(`DELETE FROM cover_letter_versions WHERE cover_letter_id=?`).run(id2);
93349
93473
  return db.prepare(`DELETE FROM cover_letters WHERE id=?`).run(id2).changes > 0;
93350
93474
  });
@@ -93433,6 +93557,7 @@ var jobRepo = {
93433
93557
  remove(id2) {
93434
93558
  const db = getDb();
93435
93559
  return tx(() => {
93560
+ db.prepare(`UPDATE cover_letters SET job_id=NULL, updated_at=? WHERE job_id=?`).run(now(), id2);
93436
93561
  db.prepare(`DELETE FROM fit_analyses WHERE job_id=?`).run(id2);
93437
93562
  db.prepare(`DELETE FROM applications WHERE job_id=?`).run(id2);
93438
93563
  db.prepare(`DELETE FROM interview_preps WHERE job_id=?`).run(id2);
@@ -93631,7 +93756,7 @@ var activityRepo = {
93631
93756
  };
93632
93757
 
93633
93758
  // packages/mcp-tools/src/tools.ts
93634
- import fs6 from "node:fs";
93759
+ import fs7 from "node:fs";
93635
93760
  import path7 from "node:path";
93636
93761
 
93637
93762
  // packages/core/src/onboarding.ts
@@ -93984,7 +94109,7 @@ function styleSignals(text) {
93984
94109
  }
93985
94110
  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.";
93986
94111
  function lintArtifact(kind, text, corpus, opts = {}) {
93987
- const norm = normalizeText(text);
94112
+ const norm2 = normalizeText(text);
93988
94113
  const corpusAvailable = (corpus.documents + corpus.structured).trim().length > 0;
93989
94114
  const provenance = analyzeProvenance(text, corpus);
93990
94115
  const signals = styleSignals(text);
@@ -94008,7 +94133,7 @@ function lintArtifact(kind, text, corpus, opts = {}) {
94008
94133
  }
94009
94134
  return {
94010
94135
  kind,
94011
- charCount: [...norm].length,
94136
+ charCount: [...norm2].length,
94012
94137
  signals,
94013
94138
  provenance,
94014
94139
  blocking,
@@ -94068,20 +94193,40 @@ function addResume(input) {
94068
94193
  activityRepo.log("resume_added", `${doc.title} \uBB38\uC11C\uB97C \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, "document", doc.id);
94069
94194
  return doc;
94070
94195
  }
94071
- function addExperience(input) {
94072
- const exp = experienceRepo.add(input);
94073
- activityRepo.log("profile_updated", `\uACBD\uB825(${exp.company})\uC744 \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, "experience", exp.id);
94074
- return exp;
94196
+ function batchNames(names) {
94197
+ const shown = names.filter(Boolean).slice(0, 8).join(", ");
94198
+ if (!shown) return "";
94199
+ return `: ${shown}${names.length > 8 ? " \uC678" : ""}`;
94075
94200
  }
94076
- function addProject(input) {
94077
- const prj = projectRepo.add(input);
94078
- activityRepo.log("profile_updated", `\uD504\uB85C\uC81D\uD2B8(${prj.name})\uB97C \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, "project", prj.id);
94079
- return prj;
94201
+ function addExperiences(inputs) {
94202
+ const res = experienceRepo.addMany(inputs);
94203
+ activityRepo.log(
94204
+ "profile_updated",
94205
+ `\uACBD\uB825 ${res.records.length}\uAC74\uC744 \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4(\uC2E0\uADDC ${res.created} \xB7 \uAC31\uC2E0 ${res.updated})${batchNames(res.records.map((e) => e.company))}.`,
94206
+ "experience",
94207
+ res.records[0]?.id
94208
+ );
94209
+ return res;
94080
94210
  }
94081
- function addSkill(input) {
94082
- const skill = skillRepo.add(input);
94083
- activityRepo.log("profile_updated", `\uAE30\uC220(${skill.name})\uC744 \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, "skill", skill.id);
94084
- return skill;
94211
+ function addProjects(inputs) {
94212
+ const res = projectRepo.addMany(inputs);
94213
+ activityRepo.log(
94214
+ "profile_updated",
94215
+ `\uD504\uB85C\uC81D\uD2B8 ${res.records.length}\uAC74\uC744 \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4(\uC2E0\uADDC ${res.created} \xB7 \uAC31\uC2E0 ${res.updated})${batchNames(res.records.map((p) => p.name))}.`,
94216
+ "project",
94217
+ res.records[0]?.id
94218
+ );
94219
+ return res;
94220
+ }
94221
+ function addSkills(inputs) {
94222
+ const res = skillRepo.addMany(inputs);
94223
+ activityRepo.log(
94224
+ "profile_updated",
94225
+ `\uAE30\uC220 ${res.records.length}\uAC1C\uB97C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4(\uC2E0\uADDC ${res.created} \xB7 \uAC31\uC2E0 ${res.updated})${batchNames(res.records.map((s) => s.name))}.`,
94226
+ "skill",
94227
+ res.records[0]?.id
94228
+ );
94229
+ return res;
94085
94230
  }
94086
94231
  function saveJobPosting(input, id2) {
94087
94232
  const job = jobRepo.upsert(input, id2);
@@ -94091,7 +94236,7 @@ function saveJobPosting(input, id2) {
94091
94236
  }
94092
94237
  function saveFitAnalysis(input) {
94093
94238
  const job = jobRepo.get(input.job_id);
94094
- if (!job) throw new Error(`\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.job_id}. \uBA3C\uC800 save_job_posting\uC73C\uB85C \uACF5\uACE0\uB97C \uC800\uC7A5\uD558\uC138\uC694.`);
94239
+ 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.");
94095
94240
  applicationRepo.ensure(job.id, "draft");
94096
94241
  const fit = fitRepo.save(input);
94097
94242
  const scoreText = fit.score != null ? ` (\uC801\uD569\uB3C4 ${fit.score}\uC810)` : "";
@@ -94106,7 +94251,7 @@ function saveCoverLetterVersion(input) {
94106
94251
  const detail = verification.blocking.map((b) => `${b.label}: ${b.detail}`).join("; ");
94107
94252
  const strictHit = verification.blocking.some((b) => b.id === "unverified_numbers_strict");
94108
94253
  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).`;
94109
- throw new Error(`\uC800\uC7A5 \uC804 \uC790\uB3D9 \uC810\uAC80\uC5D0\uC11C \uB9C9\uD614\uC2B5\uB2C8\uB2E4 \u2014 ${detail}. ${tail}`);
94254
+ throw new CareerMateError(`\uC800\uC7A5 \uC804 \uC790\uB3D9 \uC810\uAC80\uC5D0\uC11C \uB9C9\uD614\uC2B5\uB2C8\uB2E4 \u2014 ${detail}. ${tail}`, 400, "save_gate");
94110
94255
  }
94111
94256
  const { coverLetter, version: version3 } = coverLetterRepo.addVersion({
94112
94257
  cover_letter_id: input.cover_letter_id,
@@ -94133,7 +94278,7 @@ function saveCoverLetterVersion(input) {
94133
94278
  }
94134
94279
  function updateApplicationStatus(jobId, status, note) {
94135
94280
  const job = jobRepo.get(jobId);
94136
- if (!job) throw new Error(`\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${jobId}`);
94281
+ if (!job) throw notFound("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
94137
94282
  const application = applicationRepo.setStatus(jobId, status, note);
94138
94283
  activityRepo.log(
94139
94284
  "application_status_changed",
@@ -94143,17 +94288,17 @@ function updateApplicationStatus(jobId, status, note) {
94143
94288
  );
94144
94289
  const interview_unlocked = INTERVIEW_UNLOCK_STATUSES.includes(status);
94145
94290
  const hasPrep = !!interviewRepo.getByJob(jobId);
94146
- 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? (save_interview_prep)" : null;
94291
+ 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;
94147
94292
  return { application, job, interview_unlocked, hint };
94148
94293
  }
94149
94294
  function saveInterviewPrep(input) {
94150
94295
  const job = jobRepo.get(input.job_id);
94151
- if (!job) throw new Error(`\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.job_id}`);
94296
+ if (!job) throw notFound("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
94152
94297
  const app = applicationRepo.getByJob(job.id);
94153
94298
  if (!app || !INTERVIEW_UNLOCK_STATUSES.includes(app.status)) {
94154
94299
  const current = app ? APPLICATION_STATUS_LABELS[app.status] : "\uBBF8\uC9C0\uC6D0";
94155
- throw new Error(
94156
- `\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.`
94300
+ throw conflict(
94301
+ `\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.`
94157
94302
  );
94158
94303
  }
94159
94304
  const prep = interviewRepo.save(input);
@@ -94233,7 +94378,7 @@ var WORKFLOWS = [
94233
94378
  '\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.)',
94234
94379
  "\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.",
94235
94380
  "\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).",
94236
- "\uACBD\uB825\xB7\uD504\uB85C\uC81D\uD2B8\xB7\uC2A4\uD0AC\uC774 \uD30C\uC545\uB418\uBA74 `add_experience`\xB7`add_project`\xB7`add_skill`\uB85C \uAC01\uAC01 \uC800\uC7A5\uD55C\uB2E4(\uC644\uC131\uB3C4\uC758 has_experience/has_skills\uC5D0 \uBC18\uC601).",
94381
+ "\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).",
94237
94382
  "\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).",
94238
94383
  "`open_dashboard`\uB97C \uD638\uCD9C\uD574 \uC0AC\uC6A9\uC790\uAC00 \uC790\uAE30 \uB370\uC774\uD130\uB97C \uD655\uC778\uD558\uAC8C \uD55C\uB2E4.",
94239
94384
  "\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."
@@ -94251,8 +94396,8 @@ var WORKFLOWS = [
94251
94396
  "\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.",
94252
94397
  "\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).",
94253
94398
  "\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.",
94254
- '"\uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uB4DC\uB9B4\uAE4C\uC694?"\uB77C\uACE0 \uC790\uAE30\uC18C\uAC1C\uC11C \uC791\uC131\uC744 \uC81C\uC548\uD55C\uB2E4.',
94255
- '\uC0AC\uC6A9\uC790\uAC00 \uB3D9\uC758\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.',
94399
+ '\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".',
94400
+ '\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.',
94256
94401
  "\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).",
94257
94402
  "`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."
94258
94403
  ]
@@ -95532,14 +95677,94 @@ function toCallToolResult(r) {
95532
95677
  }
95533
95678
 
95534
95679
  // packages/mcp-tools/src/bridge.ts
95535
- import { spawn } from "node:child_process";
95680
+ import { spawn, spawnSync } from "node:child_process";
95681
+ import fs5 from "node:fs";
95536
95682
  import path5 from "node:path";
95537
95683
  import { fileURLToPath as fileURLToPath2 } from "node:url";
95538
95684
  var __dirname = path5.dirname(fileURLToPath2(import.meta.url));
95539
95685
  var REPO_ROOT = path5.resolve(__dirname, "..", "..", "..");
95540
- function liveServer() {
95686
+ var HEALTH_TIMEOUT_MS = 800;
95687
+ var pendingStart = null;
95688
+ function dashboardLogPath() {
95689
+ return path5.join(getDataDir(), "server.log");
95690
+ }
95691
+ function launchdPlistPath() {
95692
+ return path5.join(getDataDir(), "dashboard.launchd.plist");
95693
+ }
95694
+ function launchdLabel() {
95695
+ let hash = 5381;
95696
+ for (const ch of getDataDir()) hash = (hash * 33 ^ ch.charCodeAt(0)) >>> 0;
95697
+ return `life.careermate.dashboard.${hash.toString(16)}`;
95698
+ }
95699
+ function xmlEscape(value) {
95700
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
95701
+ }
95702
+ function plistString(key, value) {
95703
+ return ` <key>${xmlEscape(key)}</key>
95704
+ <string>${xmlEscape(value)}</string>`;
95705
+ }
95706
+ function renderLaunchdPlist(bin, args, cwd, log) {
95707
+ const env2 = {
95708
+ CAREERMATE_DATA_DIR: getDataDir(),
95709
+ CAREERMATE_NO_OPEN: "1"
95710
+ };
95711
+ if (process.env.CAREERMATE_PORT) env2.CAREERMATE_PORT = process.env.CAREERMATE_PORT;
95712
+ if (process.env.PATH) env2.PATH = process.env.PATH;
95713
+ const programArgs = [bin, ...args].map((arg) => ` <string>${xmlEscape(arg)}</string>`).join("\n");
95714
+ const envEntries = Object.entries(env2).map(([key, value]) => `${plistString(key, value)}`).join("\n");
95715
+ return `<?xml version="1.0" encoding="UTF-8"?>
95716
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
95717
+ <plist version="1.0">
95718
+ <dict>
95719
+ <key>Label</key>
95720
+ <string>${xmlEscape(launchdLabel())}</string>
95721
+ <key>ProgramArguments</key>
95722
+ <array>
95723
+ ${programArgs}
95724
+ </array>
95725
+ <key>WorkingDirectory</key>
95726
+ <string>${xmlEscape(cwd)}</string>
95727
+ <key>EnvironmentVariables</key>
95728
+ <dict>
95729
+ ${envEntries}
95730
+ </dict>
95731
+ <key>RunAtLoad</key>
95732
+ <true/>
95733
+ <key>KeepAlive</key>
95734
+ <false/>
95735
+ <key>StandardOutPath</key>
95736
+ <string>${xmlEscape(log)}</string>
95737
+ <key>StandardErrorPath</key>
95738
+ <string>${xmlEscape(log)}</string>
95739
+ </dict>
95740
+ </plist>
95741
+ `;
95742
+ }
95743
+ function delay(ms) {
95744
+ return new Promise((resolve) => setTimeout(resolve, ms));
95745
+ }
95746
+ async function pingDashboard(url, timeoutMs = HEALTH_TIMEOUT_MS) {
95747
+ const controller = new AbortController();
95748
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
95749
+ timer.unref?.();
95750
+ try {
95751
+ const healthUrl = new URL("/api/health", url);
95752
+ const res = await fetch(healthUrl, {
95753
+ signal: controller.signal,
95754
+ headers: { accept: "application/json" }
95755
+ });
95756
+ if (!res.ok) return false;
95757
+ const body = await res.json().catch(() => null);
95758
+ return body?.ok === true;
95759
+ } catch {
95760
+ return false;
95761
+ } finally {
95762
+ clearTimeout(timer);
95763
+ }
95764
+ }
95765
+ async function liveServer() {
95541
95766
  const info2 = readRuntimeInfo();
95542
- if (info2 && isProcessAlive(info2.pid)) return info2;
95767
+ if (info2 && isProcessAlive(info2.pid) && await pingDashboard(info2.url)) return info2;
95543
95768
  return null;
95544
95769
  }
95545
95770
  function spawnDetached(cmd, args, opts = {}) {
@@ -95548,19 +95773,101 @@ function spawnDetached(cmd, args, opts = {}) {
95548
95773
  child.on("error", () => {
95549
95774
  });
95550
95775
  child.unref();
95776
+ return child.pid ?? null;
95551
95777
  } catch {
95778
+ return null;
95779
+ }
95780
+ }
95781
+ function spawnViaLaunchd(bin, args, cwd, log) {
95782
+ if (process.platform !== "darwin" || process.env.CAREERMATE_LAUNCHER === "detached") return false;
95783
+ if (typeof process.getuid !== "function") return false;
95784
+ const domain = `gui/${process.getuid()}`;
95785
+ const label = launchdLabel();
95786
+ const plist = launchdPlistPath();
95787
+ try {
95788
+ fs5.writeFileSync(plist, renderLaunchdPlist(bin, args, cwd, log), "utf8");
95789
+ spawnSync("launchctl", ["bootout", `${domain}/${label}`], { stdio: "ignore" });
95790
+ const res = spawnSync("launchctl", ["bootstrap", domain, plist], {
95791
+ encoding: "utf8",
95792
+ timeout: 5e3
95793
+ });
95794
+ if (res.status === 0) return true;
95795
+ fs5.appendFileSync(log, `[${(/* @__PURE__ */ new Date()).toISOString()}] launchctl bootstrap failed: ${res.stderr || res.stdout || res.status}
95796
+ `);
95797
+ } catch (e) {
95798
+ try {
95799
+ fs5.appendFileSync(log, `[${(/* @__PURE__ */ new Date()).toISOString()}] launchctl launch failed: ${e instanceof Error ? e.message : String(e)}
95800
+ `);
95801
+ } catch {
95802
+ }
95552
95803
  }
95804
+ return false;
95805
+ }
95806
+ function spawnDashboardDetached(bin, args, cwd, logFd) {
95807
+ spawnDetached(bin, args, {
95808
+ cwd,
95809
+ env: { ...process.env, CAREERMATE_NO_OPEN: "1" },
95810
+ stdio: logFd === null ? "ignore" : ["ignore", logFd, logFd]
95811
+ });
95553
95812
  }
95554
95813
  async function ensureServer(timeoutMs = 12e3) {
95555
- const existing = liveServer();
95814
+ const existing = await liveServer();
95556
95815
  if (existing) return { url: existing.url };
95557
- const [bin, args, cwd] = BUNDLED ? [process.execPath, ["--experimental-sqlite", path5.join(__dirname, "web.mjs")], __dirname] : [process.execPath, ["--no-warnings", "--experimental-sqlite", "--import", "tsx", "apps/web/src/index.ts"], REPO_ROOT];
95558
- spawnDetached(bin, args, { cwd, env: { ...process.env, CAREERMATE_NO_OPEN: "1" } });
95816
+ if (pendingStart) return pendingStart;
95817
+ pendingStart = startServerBackground(timeoutMs);
95818
+ try {
95819
+ return await pendingStart;
95820
+ } finally {
95821
+ pendingStart = null;
95822
+ }
95823
+ }
95824
+ async function startServerBackground(timeoutMs) {
95825
+ const [bin, args, cwd] = BUNDLED ? [process.execPath, ["--experimental-sqlite", path5.join(__dirname, "web.mjs")], __dirname] : [
95826
+ process.execPath,
95827
+ ["--no-warnings", "--experimental-sqlite", "--import", "tsx", path5.join(REPO_ROOT, "apps/web/src/index.ts")],
95828
+ REPO_ROOT
95829
+ ];
95830
+ const log = dashboardLogPath();
95831
+ let logFd = null;
95832
+ try {
95833
+ logFd = fs5.openSync(log, "a");
95834
+ fs5.writeSync(logFd, `
95835
+ [${(/* @__PURE__ */ new Date()).toISOString()}] CareerMate dashboard background launch
95836
+ `);
95837
+ } catch {
95838
+ logFd = null;
95839
+ }
95840
+ let launchedViaLaunchd = false;
95841
+ try {
95842
+ launchedViaLaunchd = spawnViaLaunchd(bin, args, cwd, log);
95843
+ if (!launchedViaLaunchd) spawnDashboardDetached(bin, args, cwd, logFd);
95844
+ } finally {
95845
+ if (logFd !== null) {
95846
+ try {
95847
+ fs5.closeSync(logFd);
95848
+ } catch {
95849
+ }
95850
+ }
95851
+ }
95559
95852
  const start = Date.now();
95560
95853
  while (Date.now() - start < timeoutMs) {
95561
- const info2 = liveServer();
95854
+ const info2 = await liveServer();
95562
95855
  if (info2) return { url: info2.url };
95563
- await new Promise((r) => setTimeout(r, 300));
95856
+ await delay(300);
95857
+ }
95858
+ if (launchedViaLaunchd) {
95859
+ try {
95860
+ fs5.appendFileSync(log, `[${(/* @__PURE__ */ new Date()).toISOString()}] launchd did not produce a healthy server; falling back to detached spawn
95861
+ `);
95862
+ } catch {
95863
+ }
95864
+ spawnDashboardDetached(bin, args, cwd, null);
95865
+ const fallbackStart = Date.now();
95866
+ while (Date.now() - fallbackStart < Math.min(timeoutMs, 8e3)) {
95867
+ const info2 = await liveServer();
95868
+ if (info2) return { url: info2.url };
95869
+ await delay(300);
95870
+ }
95564
95871
  }
95565
95872
  return null;
95566
95873
  }
@@ -95584,7 +95891,7 @@ function openInFileManager(dir) {
95584
95891
  else openOsTarget(dir);
95585
95892
  }
95586
95893
  async function resolveDashboardUrl(hash = "") {
95587
- const live = liveServer();
95894
+ const live = await liveServer();
95588
95895
  if (live) return { url: live.url + hash, running: true };
95589
95896
  const started = await ensureServer();
95590
95897
  if (started) return { url: started.url + hash, running: true };
@@ -95592,7 +95899,7 @@ async function resolveDashboardUrl(hash = "") {
95592
95899
  }
95593
95900
 
95594
95901
  // packages/mcp-tools/src/update.ts
95595
- import fs5 from "node:fs";
95902
+ import fs6 from "node:fs";
95596
95903
  import path6 from "node:path";
95597
95904
  import { fileURLToPath as fileURLToPath3 } from "node:url";
95598
95905
  import { spawn as spawn2 } from "node:child_process";
@@ -95649,14 +95956,14 @@ function comparePrerelease(a, b) {
95649
95956
  }
95650
95957
  function readCache() {
95651
95958
  try {
95652
- return JSON.parse(fs5.readFileSync(cacheFile(), "utf8"));
95959
+ return JSON.parse(fs6.readFileSync(cacheFile(), "utf8"));
95653
95960
  } catch {
95654
95961
  return null;
95655
95962
  }
95656
95963
  }
95657
95964
  function writeCache(c) {
95658
95965
  try {
95659
- fs5.writeFileSync(cacheFile(), JSON.stringify(c), "utf8");
95966
+ fs6.writeFileSync(cacheFile(), JSON.stringify(c), "utf8");
95660
95967
  } catch {
95661
95968
  }
95662
95969
  }
@@ -95813,16 +96120,18 @@ async function runSelfUpdate() {
95813
96120
  ok: true,
95814
96121
  from,
95815
96122
  to: latest,
95816
- message: `careermate\uB97C ${latest ? `v${latest}` : "\uCD5C\uC2E0 \uBC84\uC804"}(\uC73C)\uB85C \uC5C5\uB370\uC774\uD2B8\uD588\uC2B5\uB2C8\uB2E4. \uC801\uC6A9\uD558\uB824\uBA74 AI \uC571(\uB610\uB294 MCP \uC5F0\uACB0)\uC744 \uC7AC\uC2DC\uC791\uD558\uC138\uC694. \uC7AC\uC2DC\uC791 \uC2DC \uB370\uC774\uD130\uBCA0\uC774\uC2A4\uB294 \uC790\uB3D9\uC73C\uB85C \uB9C8\uC774\uADF8\uB808\uC774\uC158\uB418\uBA70 \uAE30\uC874 \uB370\uC774\uD130\uB294 \uBCF4\uC874\uB429\uB2C8\uB2E4.`
96123
+ message: `CareerMate\uB97C ${latest ? `v${latest}` : "\uCD5C\uC2E0 \uBC84\uC804"}(\uC73C)\uB85C \uC5C5\uB370\uC774\uD2B8\uD588\uC2B5\uB2C8\uB2E4. \uC801\uC6A9\uD558\uB824\uBA74 AI \uC571(\uB610\uB294 MCP \uC5F0\uACB0)\uC744 \uC7AC\uC2DC\uC791\uD558\uC138\uC694. \uC7AC\uC2DC\uC791 \uC2DC \uB370\uC774\uD130\uBCA0\uC774\uC2A4\uB294 \uC790\uB3D9\uC73C\uB85C \uB9C8\uC774\uADF8\uB808\uC774\uC158\uB418\uBA70 \uAE30\uC874 \uB370\uC774\uD130\uB294 \uBCF4\uC874\uB429\uB2C8\uB2E4.`
95817
96124
  };
95818
96125
  }
95819
96126
 
95820
96127
  // packages/mcp-tools/src/tools.ts
95821
96128
  var STATUS_LIST = APPLICATION_STATUSES.map((s) => `${s}(${APPLICATION_STATUS_LABELS[s]})`).join(", ");
96129
+ var DELETE_CONFIRMATION = "DELETE";
96130
+ var DASHBOARD_URL = "http://127.0.0.1:4319";
95822
96131
  function listInboxFiles(dir) {
95823
96132
  let names;
95824
96133
  try {
95825
- names = fs6.readdirSync(dir);
96134
+ names = fs7.readdirSync(dir);
95826
96135
  } catch {
95827
96136
  return [];
95828
96137
  }
@@ -95832,7 +96141,7 @@ function listInboxFiles(dir) {
95832
96141
  if (SKIP.test(name)) continue;
95833
96142
  const p = path7.join(dir, name);
95834
96143
  try {
95835
- if (fs6.statSync(p).isFile()) out.push({ name, path: p });
96144
+ if (fs7.statSync(p).isFile()) out.push({ name, path: p });
95836
96145
  } catch {
95837
96146
  }
95838
96147
  }
@@ -95995,9 +96304,17 @@ ${lines}`;
95995
96304
  if (upd.update_available) {
95996
96305
  text += `
95997
96306
 
95998
- \u{1F195} \uC0C8 \uBC84\uC804 v${upd.latest} \uC0AC\uC6A9 \uAC00\uB2A5 (\uD604\uC7AC v${upd.current}). \uC0AC\uC6A9\uC790\uAC00 \uC6D0\uD558\uBA74 update_careermate \uB3C4\uAD6C\uB85C \uC5C5\uB370\uC774\uD2B8\uD558\uC138\uC694.`;
96307
+ \uC0C8 \uBC84\uC804 v${upd.latest}\uC774 \uB098\uC654\uC2B5\uB2C8\uB2E4(\uD604\uC7AC v${upd.current}). \uC0AC\uC6A9\uC790\uAC00 \uC6D0\uD558\uBA74 \uBC14\uB85C \uC5C5\uB370\uC774\uD2B8\uB97C \uC9C4\uD589\uD558\uC138\uC694. \uC5C5\uB370\uC774\uD2B8 \uD6C4\uC5D0\uB294 AI \uC571 \uB610\uB294 MCP \uC5F0\uACB0\uC744 \uC7AC\uC2DC\uC791\uD574\uC57C \uC801\uC6A9\uB429\uB2C8\uB2E4.`;
95999
96308
  }
96000
- return ok(text, { ...s, verify_strict: strict, update: upd });
96309
+ return ok(text, {
96310
+ ...s,
96311
+ verify_strict: strict,
96312
+ update: upd,
96313
+ suggested_next_action: upd.update_available ? {
96314
+ prompt: `\uC0C8 \uBC84\uC804 v${upd.latest}\uC774 \uB098\uC654\uC2B5\uB2C8\uB2E4. \uC9C0\uAE08 \uC5C5\uB370\uC774\uD2B8\uD560\uAE4C\uC694?`,
96315
+ next_tool: "update_careermate"
96316
+ } : null
96317
+ });
96001
96318
  }
96002
96319
  },
96003
96320
  /* --------------------------------------------------------------- profile */
@@ -96054,11 +96371,12 @@ ${lines}`;
96054
96371
  {
96055
96372
  name: "add_experience",
96056
96373
  title: "\uACBD\uB825 \uCD94\uAC00",
96057
- 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.",
96058
- inputSchema: ExperienceInputSchema.shape,
96374
+ description: "\uAD6C\uC870\uD654\uB41C \uACBD\uB825(\uC9C1\uC7A5 \uB2E8\uC704)\uC744 \uC800\uC7A5\uD569\uB2C8\uB2E4. \uC774\uB825\uC11C\uC5D0\uC11C \uD30C\uC545\uD55C \uACBD\uB825\uC744 experiences \uBC30\uC5F4\uC5D0 \uD55C \uBC88\uC5D0 \uB2F4\uC544 \uD638\uCD9C\uD558\uC138\uC694(\uD55C \uAC74\uC774\uC5B4\uB3C4 \uBC30\uC5F4\uB85C \u2014 \uD56D\uBAA9\uB9C8\uB2E4 \uB3C4\uAD6C\uB97C \uBC18\uBCF5 \uD638\uCD9C\uD558\uC9C0 \uB9C8\uC138\uC694). \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. \uAC01 \uD56D\uBAA9\uC740 company\uAC00 \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. \uAC19\uC740 \uD68C\uC0AC\xB7\uC9C1\uBB34\xB7\uC785\uC0AC\uC77C(start_date)\uC774 \uC77C\uCE58\uD558\uBA74 \uAC31\uC2E0\uB418\uACE0 \uC911\uBCF5 \uC0DD\uC131\uB418\uC9C0 \uC54A\uC73C\uB2C8 \uC5EC\uB7EC \uBC88 \uC800\uC7A5\uD574\uB3C4 \uC548\uC804\uD569\uB2C8\uB2E4. \uC628\uBCF4\uB529 \uC644\uC131\uB3C4(get_onboarding_status)\uC758 has_experience\uB97C \uCC44\uC6B0\uB824\uBA74 1\uAC74 \uC774\uC0C1 \uCD94\uAC00\uD558\uC138\uC694.",
96375
+ inputSchema: ExperienceBatchInputSchema.shape,
96059
96376
  handler: (args) => {
96060
- const exp = addExperience(args);
96061
- return ok(`\uACBD\uB825 '${exp.company}${exp.role ? ` \xB7 ${exp.role}` : ""}'\uC744(\uB97C) \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, exp);
96377
+ const { records, created, updated } = addExperiences(args.experiences);
96378
+ const names = records.map((e) => `${e.company}${e.role ? ` \xB7 ${e.role}` : ""}`).join(", ");
96379
+ return ok(`\uACBD\uB825 ${records.length}\uAC74\uC744 \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4 (\uC2E0\uADDC ${created} \xB7 \uAC31\uC2E0 ${updated}): ${names}`, records);
96062
96380
  }
96063
96381
  },
96064
96382
  {
@@ -96076,11 +96394,12 @@ ${lines}`;
96076
96394
  {
96077
96395
  name: "add_project",
96078
96396
  title: "\uD504\uB85C\uC81D\uD2B8 \uCD94\uAC00",
96079
- 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.",
96080
- inputSchema: ProjectInputSchema.shape,
96397
+ description: "\uB300\uD45C \uD504\uB85C\uC81D\uD2B8\uB97C \uC800\uC7A5\uD569\uB2C8\uB2E4. projects \uBC30\uC5F4\uC5D0 \uC5EC\uB7EC \uAC1C\uB97C \uD55C \uBC88\uC5D0 \uB2F4\uC544 \uD638\uCD9C\uD558\uC138\uC694(\uD55C \uAC1C\uC5EC\uB3C4 \uBC30\uC5F4\uB85C \u2014 \uD56D\uBAA9\uB9C8\uB2E4 \uBC18\uBCF5 \uD638\uCD9C\uD558\uC9C0 \uB9C8\uC138\uC694). \uAC01 \uD56D\uBAA9\uC740 name\uC774 \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. \uAC19\uC740 \uC774\uB984\uC758 \uD504\uB85C\uC81D\uD2B8\uB294 \uAC31\uC2E0\uB418\uACE0 \uC911\uBCF5 \uC0DD\uC131\uB418\uC9C0 \uC54A\uC73C\uB2C8 \uC5EC\uB7EC \uBC88 \uC800\uC7A5\uD574\uB3C4 \uC548\uC804\uD569\uB2C8\uB2E4.",
96398
+ inputSchema: ProjectBatchInputSchema.shape,
96081
96399
  handler: (args) => {
96082
- const prj = addProject(args);
96083
- return ok(`\uD504\uB85C\uC81D\uD2B8 '${prj.name}'\uC744(\uB97C) \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, prj);
96400
+ const { records, created, updated } = addProjects(args.projects);
96401
+ const names = records.map((p) => p.name).join(", ");
96402
+ return ok(`\uD504\uB85C\uC81D\uD2B8 ${records.length}\uAC74\uC744 \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4 (\uC2E0\uADDC ${created} \xB7 \uAC31\uC2E0 ${updated}): ${names}`, records);
96084
96403
  }
96085
96404
  },
96086
96405
  {
@@ -96098,11 +96417,12 @@ ${lines}`;
96098
96417
  {
96099
96418
  name: "add_skill",
96100
96419
  title: "\uAE30\uC220\uC2A4\uD0DD \uCD94\uAC00",
96101
- 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.",
96102
- inputSchema: SkillInputSchema.shape,
96420
+ description: "\uBCF4\uC720 \uAE30\uC220\uC2A4\uD0DD\uC744 \uC800\uC7A5\uD569\uB2C8\uB2E4. \uC5EC\uB7EC \uAC1C\uB97C skills \uBC30\uC5F4\uC5D0 \uD55C \uBC88\uC5D0 \uB2F4\uC544 \uD638\uCD9C\uD558\uC138\uC694(\uD55C \uAC1C\uC5EC\uB3C4 \uBC30\uC5F4\uB85C \u2014 \uAE30\uC220\uB9C8\uB2E4 \uB3C4\uAD6C\uB97C \uBC18\uBCF5 \uD638\uCD9C\uD558\uC9C0 \uB9C8\uC138\uC694). \uAC01 \uD56D\uBAA9\uC740 name\uC774 \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. \uAC19\uC740 \uC774\uB984\uC758 \uAE30\uC220\uC740 \uAC31\uC2E0\uB418\uACE0 \uC911\uBCF5 \uC0DD\uC131\uB418\uC9C0 \uC54A\uC73C\uB2C8 \uC5EC\uB7EC \uBC88 \uC800\uC7A5\uD574\uB3C4 \uC548\uC804\uD569\uB2C8\uB2E4. \uC628\uBCF4\uB529 \uC644\uC131\uB3C4\uC758 has_skills\uB97C \uCC44\uC6B0\uB824\uBA74 1\uAC1C \uC774\uC0C1 \uCD94\uAC00\uD558\uC138\uC694.",
96421
+ inputSchema: SkillBatchInputSchema.shape,
96103
96422
  handler: (args) => {
96104
- const skill = addSkill(args);
96105
- return ok(`\uAE30\uC220 '${skill.name}'\uC744(\uB97C) \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, skill);
96423
+ const { records, created, updated } = addSkills(args.skills);
96424
+ const names = records.map((s) => s.name).join(", ");
96425
+ return ok(`\uAE30\uC220 ${records.length}\uAC1C\uB97C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4 (\uC2E0\uADDC ${created} \xB7 \uAC31\uC2E0 ${updated}): ${names}`, records);
96106
96426
  }
96107
96427
  },
96108
96428
  {
@@ -96147,6 +96467,31 @@ ${lines}`;
96147
96467
  }
96148
96468
  }
96149
96469
  },
96470
+ {
96471
+ name: "delete_cover_letter",
96472
+ title: "\uC790\uAE30\uC18C\uAC1C\uC11C \uC0AD\uC81C",
96473
+ description: "\uC0AC\uC6A9\uC790\uAC00 \uC800\uC7A5\uB41C \uC790\uAE30\uC18C\uAC1C\uC11C \uC0AD\uC81C\uB97C \uBA85\uD655\uD788 \uC694\uCCAD\uD588\uC744 \uB54C\uB9CC \uC0AC\uC6A9\uD569\uB2C8\uB2E4. \uBA3C\uC800 get_cover_letters\uB85C \uC804\uCCB4 \uBAA9\uB85D \uB610\uB294 \uACF5\uACE0\uBCC4 \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uACE0, \uC0AD\uC81C\uD560 \uD56D\uBAA9\uC744 \uC0AC\uC6A9\uC790\uAC00 \uD655\uC778\uD55C \uB4A4 \uD638\uCD9C\uD558\uC138\uC694. \uC0AD\uC81C\uD558\uBA74 \uD574\uB2F9 \uC790\uAE30\uC18C\uAC1C\uC11C\uC758 \uBAA8\uB4E0 \uBC84\uC804\uC774 \uD568\uAED8 \uC0AD\uC81C\uB418\uBA70 \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. confirm\uC5D0\uB294 DELETE\uB97C \uB123\uC73C\uC138\uC694.",
96474
+ inputSchema: {
96475
+ cover_letter_id: external_exports.string(),
96476
+ confirm: external_exports.string().describe("\uC0AD\uC81C \uD655\uC778\uAC12. \uC815\uD655\uD788 DELETE\uB97C \uB123\uC5B4\uC57C \uC0AD\uC81C\uB429\uB2C8\uB2E4.")
96477
+ },
96478
+ handler: (args) => {
96479
+ if (args?.confirm !== DELETE_CONFIRMATION) {
96480
+ return fail("\uC0AD\uC81C\uD558\uB824\uBA74 confirm\uC5D0 DELETE\uB97C \uB123\uC5B4 \uB2E4\uC2DC \uD638\uCD9C\uD558\uC138\uC694.");
96481
+ }
96482
+ const cl = coverLetterRepo.get(args.cover_letter_id, false);
96483
+ if (!cl) return fail("\uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
96484
+ const removed = coverLetterRepo.remove(cl.id);
96485
+ if (!removed) return fail("\uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC0AD\uC81C\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
96486
+ const user_message = `'${cl.title}' \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC0AD\uC81C\uD588\uC5B4\uC694. \uC5F0\uACB0\uB41C \uC9C0\uC6D0 \uAE30\uB85D\uC5D0\uC11C\uB294 \uC790\uAE30\uC18C\uAC1C\uC11C \uC5F0\uACB0\uB9CC \uBE44\uC6CC\uB480\uC2B5\uB2C8\uB2E4.`;
96487
+ return ok(user_message, {
96488
+ deleted: true,
96489
+ cover_letter_id: cl.id,
96490
+ title: cl.title,
96491
+ user_message
96492
+ });
96493
+ }
96494
+ },
96150
96495
  /* ------------------------------------------------------------------ jobs */
96151
96496
  {
96152
96497
  name: "save_job_posting",
@@ -96155,7 +96500,15 @@ ${lines}`;
96155
96500
  inputSchema: JobInputSchema.shape,
96156
96501
  handler: (args) => {
96157
96502
  const { job } = saveJobPosting(args);
96158
- return ok(`'${job.company} \xB7 ${job.position}' \uACF5\uACE0\uB97C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4. (job_id: ${job.id})`, job);
96503
+ const user_message = `'${job.company} \xB7 ${job.position}' \uACF5\uACE0\uB97C \uC800\uC7A5\uD588\uC5B4\uC694.`;
96504
+ return ok(user_message, {
96505
+ ...job,
96506
+ user_message,
96507
+ suggested_next_action: {
96508
+ prompt: "\uC774 \uACF5\uACE0\uC640 \uB0B4 \uD504\uB85C\uD544\uC744 \uBE44\uAD50\uD574 \uC801\uD569\uB3C4\uB97C \uBD84\uC11D\uD560\uAE4C\uC694?",
96509
+ next_workflow: "analyze_job"
96510
+ }
96511
+ });
96159
96512
  }
96160
96513
  },
96161
96514
  {
@@ -96166,7 +96519,7 @@ ${lines}`;
96166
96519
  readOnly: true,
96167
96520
  handler: (args) => {
96168
96521
  const job = jobRepo.get(args.job_id);
96169
- if (!job) return fail(`\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${args.job_id}`);
96522
+ if (!job) return fail("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
96170
96523
  return ok(`${job.company} \xB7 ${job.position}`, {
96171
96524
  job: jobWithMeta(job),
96172
96525
  fit: fitRepo.getByJob(job.id),
@@ -96187,6 +96540,35 @@ ${lines}`;
96187
96540
  return ok(`\uACF5\uACE0 ${jobs.length}\uAC74`, jobs);
96188
96541
  }
96189
96542
  },
96543
+ {
96544
+ name: "delete_job_posting",
96545
+ title: "\uCC44\uC6A9\uACF5\uACE0 \uC0AD\uC81C",
96546
+ description: "\uC0AC\uC6A9\uC790\uAC00 \uC800\uC7A5\uB41C \uCC44\uC6A9\uACF5\uACE0 \uC0AD\uC81C\uB97C \uBA85\uD655\uD788 \uC694\uCCAD\uD588\uC744 \uB54C\uB9CC \uC0AC\uC6A9\uD569\uB2C8\uB2E4. \uBA3C\uC800 list_jobs\uB85C \uC804\uCCB4 \uACF5\uACE0 \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uACE0, \uC0AD\uC81C\uD560 \uACF5\uACE0\uB97C \uC0AC\uC6A9\uC790\uAC00 \uD655\uC778\uD55C \uB4A4 \uD638\uCD9C\uD558\uC138\uC694. \uC0AD\uC81C\uD558\uBA74 \uC5F0\uACB0\uB41C \uC9C0\uC6D0 \uC0C1\uD0DC\xB7\uC801\uD569\uB3C4 \uBD84\uC11D\xB7\uBA74\uC811 \uC900\uBE44\uAC00 \uD568\uAED8 \uC0AD\uC81C\uB429\uB2C8\uB2E4. \uC5F0\uACB0\uB41C \uC790\uAE30\uC18C\uAC1C\uC11C\uB294 \uC0AD\uC81C\uD558\uC9C0 \uC54A\uACE0 \uACF5\uACE0 \uC5F0\uACB0\uB9CC \uD574\uC81C\uD569\uB2C8\uB2E4. confirm\uC5D0\uB294 DELETE\uB97C \uB123\uC73C\uC138\uC694.",
96547
+ inputSchema: {
96548
+ job_id: external_exports.string(),
96549
+ confirm: external_exports.string().describe("\uC0AD\uC81C \uD655\uC778\uAC12. \uC815\uD655\uD788 DELETE\uB97C \uB123\uC5B4\uC57C \uC0AD\uC81C\uB429\uB2C8\uB2E4.")
96550
+ },
96551
+ handler: (args) => {
96552
+ if (args?.confirm !== DELETE_CONFIRMATION) {
96553
+ return fail("\uC0AD\uC81C\uD558\uB824\uBA74 confirm\uC5D0 DELETE\uB97C \uB123\uC5B4 \uB2E4\uC2DC \uD638\uCD9C\uD558\uC138\uC694.");
96554
+ }
96555
+ const job = jobRepo.get(args.job_id);
96556
+ if (!job) return fail("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
96557
+ const detachedCoverLetters = coverLetterRepo.listByJob(job.id).length;
96558
+ const removed = jobRepo.remove(job.id);
96559
+ if (!removed) return fail("\uACF5\uACE0\uB97C \uC0AD\uC81C\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
96560
+ const detachedNote = detachedCoverLetters ? ` \uC5F0\uACB0\uB41C \uC790\uAE30\uC18C\uAC1C\uC11C ${detachedCoverLetters}\uAC74\uC740 \uC0AD\uC81C\uD558\uC9C0 \uC54A\uACE0 \uACF5\uACE0 \uC5F0\uACB0\uB9CC \uD574\uC81C\uD588\uC2B5\uB2C8\uB2E4.` : "";
96561
+ const user_message = `'${job.company} \xB7 ${job.position}' \uACF5\uACE0\uB97C \uC0AD\uC81C\uD588\uC5B4\uC694. \uC5F0\uACB0\uB41C \uC9C0\uC6D0 \uC0C1\uD0DC, \uC801\uD569\uB3C4 \uBD84\uC11D, \uBA74\uC811 \uC900\uBE44\uB3C4 \uD568\uAED8 \uC815\uB9AC\uD588\uC2B5\uB2C8\uB2E4.${detachedNote}`;
96562
+ return ok(user_message, {
96563
+ deleted: true,
96564
+ job_id: job.id,
96565
+ company: job.company,
96566
+ position: job.position,
96567
+ detached_cover_letters: detachedCoverLetters,
96568
+ user_message
96569
+ });
96570
+ }
96571
+ },
96190
96572
  /* ----------------------------------------------------- application context */
96191
96573
  {
96192
96574
  name: "get_application_context",
@@ -96209,7 +96591,7 @@ ${lines}`;
96209
96591
  `\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}`,
96210
96592
  ctx.job ? `\uB300\uC0C1 \uACF5\uACE0: ${ctx.job.company} \xB7 ${ctx.job.position}` : "\uB300\uC0C1 \uACF5\uACE0 \uC5C6\uC74C",
96211
96593
  ctx.writing_preferences.preferred_tone ? `\uC120\uD638 \uBB38\uCCB4: ${ctx.writing_preferences.preferred_tone}` : "",
96212
- recommended_route ? `\u{1F4A1} \uB2E4\uC74C \uB2E8\uACC4: get_workflow_guide({ workflow_id: "${recommended_route}" })\uB85C \uC804\uBB38\uAC00 \uC2E4\uD589 \uC808\uCC28(EOP)\uC640 \uC801\uC6A9\uD560 \uD50C\uB808\uC774\uBD81\xB7\uAC80\uC99D \uC21C\uC11C\uB97C \uBC1B\uC544 \uC801\uC6A9\uD558\uC138\uC694(\uC989\uB2F5 \uAE08\uC9C0).` : "\u{1F4A1} \uCEE4\uB9AC\uC5B4 \uC791\uC5C5\uC744 \uC2DC\uC791\uD558\uB824\uBA74 get_workflow_guide\uB85C \uC804\uBB38\uAC00 \uC2E4\uD589 \uC808\uCC28\uC640 \uAC80\uC99D \uC21C\uC11C\uB97C \uBA3C\uC800 \uBC1B\uC73C\uC138\uC694."
96594
+ recommended_route ? `\uB0B4\uBD80 \uC548\uB0B4: \uC0AC\uC6A9\uC790\uC5D0\uAC8C\uB294 \uB3C4\uAD6C\uBA85\uC774\uB098 ID\uB97C \uB9D0\uD558\uC9C0 \uB9D0\uACE0, \uBA3C\uC800 get_workflow_guide({ workflow_id: "${recommended_route}" })\uB85C \uC2E4\uD589 \uC808\uCC28\uC640 \uAC80\uC99D \uC21C\uC11C\uB97C \uBC1B\uC740 \uB4A4 \uC26C\uC6B4 \uB9D0\uB85C \uC548\uB0B4\uD558\uC138\uC694.` : "\uB0B4\uBD80 \uC548\uB0B4: \uC0AC\uC6A9\uC790\uC5D0\uAC8C\uB294 \uB3C4\uAD6C\uBA85\uC744 \uB9D0\uD558\uC9C0 \uB9D0\uACE0, \uBA3C\uC800 get_workflow_guide\uB85C \uC2E4\uD589 \uC808\uCC28\uC640 \uAC80\uC99D \uC21C\uC11C\uB97C \uBC1B\uC740 \uB4A4 \uC26C\uC6B4 \uB9D0\uB85C \uC548\uB0B4\uD558\uC138\uC694."
96213
96595
  ].filter(Boolean).join("\n");
96214
96596
  return ok(summary, enriched);
96215
96597
  }
@@ -96222,7 +96604,34 @@ ${lines}`;
96222
96604
  handler: (args) => {
96223
96605
  try {
96224
96606
  const fit = saveFitAnalysis(args);
96225
- return ok(`\uC801\uD569\uB3C4 \uBD84\uC11D\uC744 \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4${fit.score != null ? ` (${fit.score}\uC810)` : ""}.`, fit);
96607
+ const suggested_next_action = {
96608
+ prompt: "\uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uB4DC\uB9B4\uAE4C\uC694?",
96609
+ options: [
96610
+ {
96611
+ label: "\uB124, \uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uC918",
96612
+ next_workflow: "write_cover_letter"
96613
+ },
96614
+ {
96615
+ label: "\uC544\uB2C8\uC694, \uBD84\uC11D\uB9CC \uC800\uC7A5\uD574\uC918",
96616
+ next_workflow: null
96617
+ }
96618
+ ]
96619
+ };
96620
+ const user_message = `\uC801\uD569\uB3C4 \uBD84\uC11D\uC744 \uC800\uC7A5\uD588\uC5B4\uC694${fit.score != null ? ` (${fit.score}\uC810)` : ""}. \uC790\uC138\uD55C \uACB0\uACFC\uB294 \uB300\uC2DC\uBCF4\uB4DC\uC5D0\uC11C\uB3C4 \uD655\uC778\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4: ${DASHBOARD_URL}`;
96621
+ return ok(
96622
+ [
96623
+ user_message,
96624
+ "",
96625
+ "\uC0AC\uC6A9\uC790\uC5D0\uAC8C \uB2E4\uC74C \uC120\uD0DD\uC9C0\uB97C \uBCF4\uC5EC\uC8FC\uC138\uC694:",
96626
+ "1. \uB124, \uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uC918",
96627
+ "2. \uC544\uB2C8\uC694, \uBD84\uC11D\uB9CC \uC800\uC7A5\uD574\uC918"
96628
+ ].join("\n"),
96629
+ {
96630
+ ...fit,
96631
+ user_message,
96632
+ suggested_next_action
96633
+ }
96634
+ );
96226
96635
  } catch (e) {
96227
96636
  return fail(e instanceof Error ? e.message : "\uC800\uC7A5 \uC2E4\uD328");
96228
96637
  }
@@ -96241,11 +96650,19 @@ ${lines}`;
96241
96650
  handler: (args) => {
96242
96651
  try {
96243
96652
  const res = updateApplicationStatus(args.job_id, args.status, args.note);
96244
- return ok(
96245
- `\uC0C1\uD0DC\uB97C '${APPLICATION_STATUS_LABELS[args.status]}'(\uC73C)\uB85C \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.${res.hint ? `
96653
+ const user_message = `\uC9C0\uC6D0 \uC0C1\uD0DC\uB97C '${APPLICATION_STATUS_LABELS[args.status]}'(\uC73C)\uB85C \uBC14\uAFE8\uC5B4\uC694.` + (res.hint ? `
96246
96654
 
96247
- \u{1F4A1} ${res.hint}` : ""}`,
96248
- res
96655
+ ${res.hint}` : "");
96656
+ return ok(
96657
+ user_message,
96658
+ {
96659
+ ...res,
96660
+ user_message,
96661
+ suggested_next_action: res.hint ? {
96662
+ prompt: res.hint,
96663
+ next_workflow: "prepare_interview"
96664
+ } : null
96665
+ }
96249
96666
  );
96250
96667
  } catch (e) {
96251
96668
  return fail(e instanceof Error ? e.message : "\uBCC0\uACBD \uC2E4\uD328");
@@ -96261,7 +96678,8 @@ ${lines}`;
96261
96678
  handler: (args) => {
96262
96679
  try {
96263
96680
  const prep = saveInterviewPrep(args);
96264
- return ok(`\uBA74\uC811 \uC900\uBE44 \uC790\uB8CC\uB97C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4 (\uC9C8\uBB38 ${prep.questions.length}\uAC1C).`, prep);
96681
+ const user_message = `\uBA74\uC811 \uC900\uBE44 \uC790\uB8CC\uB97C \uC800\uC7A5\uD588\uC5B4\uC694. \uC608\uC0C1 \uC9C8\uBB38 ${prep.questions.length}\uAC1C\uAC00 \uC900\uBE44\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.`;
96682
+ return ok(user_message, { ...prep, user_message });
96265
96683
  } catch (e) {
96266
96684
  return fail(e instanceof Error ? e.message : "\uC800\uC7A5 \uC2E4\uD328");
96267
96685
  }
@@ -96286,7 +96704,7 @@ ${lines}`;
96286
96704
  const dir = getExportsDir();
96287
96705
  const filePath = path7.join(dir, result2.filename);
96288
96706
  try {
96289
- fs6.writeFileSync(filePath, result2.content, "utf8");
96707
+ fs7.writeFileSync(filePath, result2.content, "utf8");
96290
96708
  } catch (e) {
96291
96709
  return fail(`\uD30C\uC77C \uC800\uC7A5 \uC2E4\uD328: ${e instanceof Error ? e.message : e}`);
96292
96710
  }
@@ -96448,29 +96866,37 @@ ${report.disclaimer}`, report);
96448
96866
  {
96449
96867
  name: "check_for_update",
96450
96868
  title: "\uC5C5\uB370\uC774\uD2B8 \uD655\uC778",
96451
- description: 'CareerMate\uC5D0 \uC0C8 \uBC84\uC804\uC774 \uC788\uB294\uC9C0 npm \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uC5D0\uC11C \uC9C0\uAE08 \uD655\uC778\uD569\uB2C8\uB2E4(\uACF5\uAC1C \uBC84\uC804 \uC815\uBCF4\uB9CC GET\uD558\uBA70 \uC0AC\uC6A9\uC790 \uB370\uC774\uD130\uB294 \uC804\uC1A1\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4). \uD604\uC7AC \uBC84\uC804\xB7\uCD5C\uC2E0 \uBC84\uC804\xB7\uC5C5\uB370\uC774\uD2B8 \uAC00\uB2A5 \uC5EC\uBD80\uB97C \uB3CC\uB824\uC90D\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uAC00 "\uCD5C\uC2E0\uC774\uC57C?/\uC5C5\uB370\uC774\uD2B8 \uC788\uC5B4?"\uB77C\uACE0 \uBB3B\uAC70\uB098, get_onboarding_status\uAC00 \uC0C8 \uBC84\uC804\uC744 \uC54C\uB9B4 \uB54C \uD638\uCD9C\uD558\uC138\uC694. \uC2E4\uC81C \uC801\uC6A9\uC740 update_careermate\uB85C \uD569\uB2C8\uB2E4.',
96869
+ description: 'CareerMate\uC5D0 \uC0C8 \uBC84\uC804\uC774 \uC788\uB294\uC9C0 npm \uB808\uC9C0\uC2A4\uD2B8\uB9AC\uC5D0\uC11C \uC9C0\uAE08 \uD655\uC778\uD569\uB2C8\uB2E4(\uACF5\uAC1C \uBC84\uC804 \uC815\uBCF4\uB9CC GET\uD558\uBA70 \uC0AC\uC6A9\uC790 \uB370\uC774\uD130\uB294 \uC804\uC1A1\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4). \uD604\uC7AC \uBC84\uC804\xB7\uCD5C\uC2E0 \uBC84\uC804\xB7\uC5C5\uB370\uC774\uD2B8 \uAC00\uB2A5 \uC5EC\uBD80\uB97C \uB3CC\uB824\uC90D\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uAC00 "\uCD5C\uC2E0\uC774\uC57C?/\uC5C5\uB370\uC774\uD2B8 \uC788\uC5B4?"\uB77C\uACE0 \uBB3B\uAC70\uB098, get_onboarding_status\uAC00 \uC0C8 \uBC84\uC804\uC744 \uC54C\uB9B4 \uB54C \uD638\uCD9C\uD558\uC138\uC694. \uD655\uC778\uB9CC \uD558\uACE0 \uC124\uCE58\uB294 \uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uAC00 "\uC5C5\uB370\uC774\uD2B8\uD574\uC918/\uCD5C\uC2E0\uC73C\uB85C \uC62C\uB824\uC918"\uCC98\uB7FC \uBA85\uC2DC\uC801\uC73C\uB85C \uC694\uCCAD\uD558\uBA74 update_careermate\uB97C \uD638\uCD9C\uD558\uC138\uC694.',
96452
96870
  inputSchema: {},
96453
96871
  readOnly: true,
96454
96872
  handler: async () => {
96455
96873
  const u = await getUpdateStatusAsync();
96456
96874
  if (u.latest === null) return ok("\uC5C5\uB370\uC774\uD2B8 \uD655\uC778\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4(\uC624\uD504\uB77C\uC778\uC774\uAC70\uB098 \uC77C\uC2DC\uC801 \uC624\uB958). \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.", u);
96875
+ const user_message = u.update_available ? `\uC0C8 \uBC84\uC804 v${u.latest}\uC774 \uB098\uC654\uC5B4\uC694. \uD604\uC7AC \uBC84\uC804\uC740 v${u.current}\uC785\uB2C8\uB2E4. \uC6D0\uD558\uC2DC\uBA74 \uC9C0\uAE08 \uC5C5\uB370\uC774\uD2B8\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.` : `\uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4 (v${u.current}).`;
96457
96876
  return ok(
96458
- u.update_available ? `\uC0C8 \uBC84\uC804 v${u.latest} \uC0AC\uC6A9 \uAC00\uB2A5 (\uD604\uC7AC v${u.current}). update_careermate\uB85C \uC5C5\uB370\uC774\uD2B8\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.` : `\uCD5C\uC2E0 \uBC84\uC804\uC785\uB2C8\uB2E4 (v${u.current}).`,
96459
- u
96877
+ user_message,
96878
+ {
96879
+ ...u,
96880
+ user_message,
96881
+ suggested_next_action: u.update_available ? {
96882
+ prompt: "\uC9C0\uAE08 CareerMate\uB97C \uC5C5\uB370\uC774\uD2B8\uD560\uAE4C\uC694?",
96883
+ next_tool: "update_careermate"
96884
+ } : null
96885
+ }
96460
96886
  );
96461
96887
  }
96462
96888
  },
96463
96889
  {
96464
96890
  name: "update_careermate",
96465
96891
  title: "CareerMate \uC5C5\uB370\uC774\uD2B8",
96466
- description: 'CareerMate\uB97C \uCD5C\uC2E0 \uBC84\uC804\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD569\uB2C8\uB2E4(\uC124\uCE58 \uD3F4\uB354\uC5D0 npm\uC73C\uB85C careermate@latest \uC124\uCE58). \uC0AC\uC6A9\uC790\uAC00 \uBA85\uC2DC\uC801\uC73C\uB85C "\uC5C5\uB370\uC774\uD2B8\uD574\uC918"\uB77C\uACE0 \uD560 \uB54C\uB9CC \uD638\uCD9C\uD558\uC138\uC694. \uC131\uACF5\uD558\uBA74 \uC801\uC6A9\uC744 \uC704\uD574 AI \uC571(\uB610\uB294 MCP \uC5F0\uACB0) \uC7AC\uC2DC\uC791\uC774 \uD544\uC694\uD558\uBA70, \uC7AC\uC2DC\uC791 \uC2DC \uB370\uC774\uD130\uBCA0\uC774\uC2A4\uB294 \uC790\uB3D9 \uB9C8\uC774\uADF8\uB808\uC774\uC158\uB418\uC5B4 \uAE30\uC874 \uB370\uC774\uD130\uAC00 \uBCF4\uC874\uB429\uB2C8\uB2E4. \uC790\uB3D9 \uC124\uCE58\uAC00 \uB9C9\uD788\uBA74(\uAC1C\uBC1C \uBAA8\uB4DC\xB7\uBE44\uD45C\uC900 \uC124\uCE58) \uC9C1\uC811 \uCE60 \uBA85\uB839\uC744 \uC548\uB0B4\uD569\uB2C8\uB2E4.',
96892
+ description: 'CareerMate\uB97C \uCD5C\uC2E0 \uBC84\uC804\uC73C\uB85C \uC5C5\uB370\uC774\uD2B8\uD569\uB2C8\uB2E4(\uC124\uCE58 \uD3F4\uB354\uC5D0 npm\uC73C\uB85C careermate@latest \uC124\uCE58). \uC0AC\uC6A9\uC790\uAC00 \uBA85\uC2DC\uC801\uC73C\uB85C "\uC5C5\uB370\uC774\uD2B8\uD574\uC918", "CareerMate \uC5C5\uB370\uC774\uD2B8\uD574\uC918", "\uCD5C\uC2E0\uC73C\uB85C \uC62C\uB824\uC918"\uB77C\uACE0 \uD560 \uB54C \uD638\uCD9C\uD558\uC138\uC694. \uC774 \uB3C4\uAD6C\uAC00 \uCD5C\uC2E0 \uBC84\uC804 \uD655\uC778\uB3C4 \uD568\uAED8 \uD558\uBBC0\uB85C \uBCC4\uB3C4 \uD655\uC778 \uC9C8\uBB38 \uC5C6\uC774 \uC2E4\uD589\uD574\uB3C4 \uB429\uB2C8\uB2E4. \uC131\uACF5\uD558\uBA74 \uC801\uC6A9\uC744 \uC704\uD574 AI \uC571(\uB610\uB294 MCP \uC5F0\uACB0) \uC7AC\uC2DC\uC791\uC774 \uD544\uC694\uD558\uBA70, \uC7AC\uC2DC\uC791 \uC2DC \uB370\uC774\uD130\uBCA0\uC774\uC2A4\uB294 \uC790\uB3D9 \uB9C8\uC774\uADF8\uB808\uC774\uC158\uB418\uC5B4 \uAE30\uC874 \uB370\uC774\uD130\uB294 \uBCF4\uC874\uB429\uB2C8\uB2E4. \uC790\uB3D9 \uC124\uCE58\uAC00 \uB9C9\uD788\uBA74(\uAC1C\uBC1C \uBAA8\uB4DC\xB7\uBE44\uD45C\uC900 \uC124\uCE58) \uC9C1\uC811 \uCE60 \uBA85\uB839\uC744 \uC548\uB0B4\uD569\uB2C8\uB2E4.',
96467
96893
  inputSchema: {},
96468
96894
  handler: async () => {
96469
96895
  const r = await runSelfUpdate();
96470
96896
  if (!r.ok) return fail(`${r.message}${r.manual ? `
96471
96897
 
96472
96898
  \uC9C1\uC811 \uC2E4\uD589: ${r.manual}` : ""}`);
96473
- return ok(r.message, r);
96899
+ return ok(r.message, { ...r, user_message: r.message });
96474
96900
  }
96475
96901
  }
96476
96902
  ];