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/doctor.mjs CHANGED
@@ -6477,9 +6477,9 @@ var require_timers = __commonJS({
6477
6477
  clearTimeout(this.handle);
6478
6478
  };
6479
6479
  var afterValue = function(value) {
6480
- return delay(+this).thenReturn(value);
6480
+ return delay2(+this).thenReturn(value);
6481
6481
  };
6482
- var delay = Promise2.delay = function(ms, value) {
6482
+ var delay2 = Promise2.delay = function(ms, value) {
6483
6483
  var ret2;
6484
6484
  var handle;
6485
6485
  if (value !== void 0) {
@@ -6501,7 +6501,7 @@ var require_timers = __commonJS({
6501
6501
  return ret2;
6502
6502
  };
6503
6503
  Promise2.prototype.delay = function(ms) {
6504
- return delay(ms, this);
6504
+ return delay2(ms, this);
6505
6505
  };
6506
6506
  var afterTimeout = function(promise, message, parent) {
6507
6507
  var err2;
@@ -28297,7 +28297,7 @@ var require_path_is_absolute = __commonJS({
28297
28297
  var require_files = __commonJS({
28298
28298
  "node_modules/mammoth/lib/docx/files.js"(exports2) {
28299
28299
  "use strict";
28300
- var fs8 = __require("fs");
28300
+ var fs9 = __require("fs");
28301
28301
  var url = __require("url");
28302
28302
  var os3 = __require("os");
28303
28303
  var dirname = __require("path").dirname;
@@ -28338,7 +28338,7 @@ var require_files = __commonJS({
28338
28338
  read
28339
28339
  };
28340
28340
  }
28341
- var readFile = promises.promisify(fs8.readFile.bind(fs8));
28341
+ var readFile = promises.promisify(fs9.readFile.bind(fs9));
28342
28342
  function uriToPath(uriString, platform) {
28343
28343
  if (!platform) {
28344
28344
  platform = os3.platform();
@@ -31080,11 +31080,11 @@ var require_options_reader = __commonJS({
31080
31080
  var require_unzip = __commonJS({
31081
31081
  "node_modules/mammoth/lib/unzip.js"(exports2) {
31082
31082
  "use strict";
31083
- var fs8 = __require("fs");
31083
+ var fs9 = __require("fs");
31084
31084
  var promises = require_promises();
31085
31085
  var zipfile = require_zipfile();
31086
31086
  exports2.openZip = openZip;
31087
- var readFile = promises.promisify(fs8.readFile);
31087
+ var readFile = promises.promisify(fs9.readFile);
31088
31088
  function openZip(options) {
31089
31089
  if (options.path) {
31090
31090
  return readFile(options.path).then(zipfile.openArrayBuffer);
@@ -31809,9 +31809,9 @@ var require_cjs = __commonJS({
31809
31809
  }
31810
31810
  return o;
31811
31811
  }
31812
- var fs8;
31812
+ var fs9;
31813
31813
  function get_fs() {
31814
- return fs8 || (fs8 = require$$0__default["default"]);
31814
+ return fs9 || (fs9 = require$$0__default["default"]);
31815
31815
  }
31816
31816
  function parse2(file, options) {
31817
31817
  if (file[0] == 80 && file[1] == 75) return parse_zip(file, options);
@@ -32096,7 +32096,7 @@ var require_cjs = __commonJS({
32096
32096
  }
32097
32097
  function read_file(filename2, options) {
32098
32098
  get_fs();
32099
- return parse2(fs8.readFileSync(filename2), options);
32099
+ return parse2(fs9.readFileSync(filename2), options);
32100
32100
  }
32101
32101
  function read(blob, options) {
32102
32102
  switch (options && options.type || "base64") {
@@ -32411,7 +32411,7 @@ var require_cjs = __commonJS({
32411
32411
  function write_file(cfb2, filename2, options) {
32412
32412
  get_fs();
32413
32413
  var o = _write(cfb2, options);
32414
- fs8.writeFileSync(filename2, o);
32414
+ fs9.writeFileSync(filename2, o);
32415
32415
  }
32416
32416
  function a2s(o) {
32417
32417
  var out = new Array(o.length);
@@ -32423,7 +32423,7 @@ var require_cjs = __commonJS({
32423
32423
  switch (options && options.type || "buffer") {
32424
32424
  case "file":
32425
32425
  get_fs();
32426
- fs8.writeFileSync(options.filename, o);
32426
+ fs9.writeFileSync(options.filename, o);
32427
32427
  return o;
32428
32428
  case "binary":
32429
32429
  return typeof o == "string" ? o : a2s(o);
@@ -40116,8 +40116,8 @@ function blend(fg, bg, alpha) {
40116
40116
  return Math.round(alpha * fg + (1 - alpha) * bg);
40117
40117
  }
40118
40118
  async function node_utils_fetchData(url) {
40119
- const fs8 = process.getBuiltinModule("fs/promises");
40120
- const data = await fs8.readFile(url);
40119
+ const fs9 = process.getBuiltinModule("fs/promises");
40120
+ const data = await fs9.readFile(url);
40121
40121
  return new Uint8Array(data);
40122
40122
  }
40123
40123
  function initGPU() {
@@ -40746,11 +40746,11 @@ function network_getArrayBuffer(val) {
40746
40746
  return typeof val !== "string" ? val : stringToBytes(val).buffer;
40747
40747
  }
40748
40748
  function getReadableStream(url, opts = null) {
40749
- const fs8 = process.getBuiltinModule("fs");
40749
+ const fs9 = process.getBuiltinModule("fs");
40750
40750
  const {
40751
40751
  Readable
40752
40752
  } = process.getBuiltinModule("stream");
40753
- const readStream = fs8.createReadStream(url, opts);
40753
+ const readStream = fs9.createReadStream(url, opts);
40754
40754
  return Readable.toWeb(readStream);
40755
40755
  }
40756
40756
  function getNetworkStream(url) {
@@ -57747,8 +57747,8 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
57747
57747
  url
57748
57748
  } = stream._source;
57749
57749
  this._isStreamingSupported = !disableStream;
57750
- const fs8 = process.getBuiltinModule("fs/promises");
57751
- fs8.lstat(url).then((stat) => {
57750
+ const fs9 = process.getBuiltinModule("fs/promises");
57751
+ fs9.lstat(url).then((stat) => {
57752
57752
  const readableStream = getReadableStream(url);
57753
57753
  this._reader = readableStream.getReader();
57754
57754
  const {
@@ -59394,9 +59394,9 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
59394
59394
  return;
59395
59395
  }
59396
59396
  if (reason instanceof RenderingCancelledException) {
59397
- let delay = RENDERING_CANCELLED_TIMEOUT;
59397
+ let delay2 = RENDERING_CANCELLED_TIMEOUT;
59398
59398
  if (reason.extraDelay > 0 && reason.extraDelay < 1e3) {
59399
- delay += reason.extraDelay;
59399
+ delay2 += reason.extraDelay;
59400
59400
  }
59401
59401
  intentState.streamReaderCancelTimeout = setTimeout(() => {
59402
59402
  intentState.streamReaderCancelTimeout = null;
@@ -59405,7 +59405,7 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
59405
59405
  reason,
59406
59406
  force: true
59407
59407
  });
59408
- }, delay);
59408
+ }, delay2);
59409
59409
  return;
59410
59410
  }
59411
59411
  }
@@ -68172,7 +68172,7 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
68172
68172
  const ij = i2 * width + j;
68173
68173
  const center = buf[ij];
68174
68174
  let sum = 0;
68175
- let norm = 0;
68175
+ let norm2 = 0;
68176
68176
  for (let k = 0; k < kernelSize; k++) {
68177
68177
  const y = i2 + k - halfSize;
68178
68178
  if (y < 0 || y >= height) {
@@ -68186,10 +68186,10 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
68186
68186
  const neighbour = buf[y * width + x2];
68187
68187
  const w = kernel[k * kernelSize + l2] * rangeValues[Math.abs(neighbour - center)];
68188
68188
  sum += neighbour * w;
68189
- norm += w;
68189
+ norm2 += w;
68190
68190
  }
68191
68191
  }
68192
- const pix = out[ij] = Math.round(sum / norm);
68192
+ const pix = out[ij] = Math.round(sum / norm2);
68193
68193
  histogram[pix]++;
68194
68194
  }
68195
68195
  }
@@ -71040,7 +71040,7 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
71040
71040
  });
71041
71041
 
71042
71042
  // scripts/doctor.ts
71043
- import fs7 from "node:fs";
71043
+ import fs8 from "node:fs";
71044
71044
 
71045
71045
  // packages/db/src/paths.ts
71046
71046
  import os from "node:os";
@@ -71451,6 +71451,24 @@ function setVerifyStrict(on) {
71451
71451
  return on;
71452
71452
  }
71453
71453
 
71454
+ // packages/shared/src/errors.ts
71455
+ var CareerMateError = class extends Error {
71456
+ constructor(message, httpStatus = 400, code) {
71457
+ super(message);
71458
+ this.httpStatus = httpStatus;
71459
+ this.code = code;
71460
+ this.name = "CareerMateError";
71461
+ }
71462
+ httpStatus;
71463
+ code;
71464
+ };
71465
+ function notFound(message) {
71466
+ return new CareerMateError(message, 404, "not_found");
71467
+ }
71468
+ function conflict(message) {
71469
+ return new CareerMateError(message, 409, "conflict");
71470
+ }
71471
+
71454
71472
  // packages/shared/src/enums.ts
71455
71473
  var APPLICATION_STATUSES = [
71456
71474
  "draft",
@@ -71501,8 +71519,9 @@ function canTransitionStatus(from, to) {
71501
71519
  }
71502
71520
  function assertStatusTransition(from, to) {
71503
71521
  if (!canTransitionStatus(from, to)) {
71504
- throw new Error(
71505
- `'${APPLICATION_STATUS_LABELS[from]}'\uC5D0\uC11C '${APPLICATION_STATUS_LABELS[to]}'(\uC73C)\uB85C\uB294 \uC0C1\uD0DC\uB97C \uBC14\uAFC0 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`
71522
+ 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.)` : "";
71523
+ throw conflict(
71524
+ `'${APPLICATION_STATUS_LABELS[from]}'\uC5D0\uC11C '${APPLICATION_STATUS_LABELS[to]}'(\uC73C)\uB85C\uB294 \uC0C1\uD0DC\uB97C \uBC14\uAFC0 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.${suffix}`
71506
71525
  );
71507
71526
  }
71508
71527
  }
@@ -75595,7 +75614,20 @@ var optLine = external_exports.string().max(MAX_LINE).optional();
75595
75614
  var optNote = external_exports.string().max(MAX_NOTE).optional();
75596
75615
  var optBody = external_exports.string().max(MAX_BODY).optional();
75597
75616
  var reqBody = external_exports.string().max(MAX_BODY).refine((s) => s.trim().length > 0, { message: EMPTY_MSG });
75598
- var strList = external_exports.array(external_exports.string().max(MAX_ITEM)).max(MAX_ITEMS);
75617
+ function toStrList(v) {
75618
+ if (typeof v !== "string") return v;
75619
+ const s = v.trim();
75620
+ if (!s) return [];
75621
+ if (s.startsWith("[")) {
75622
+ try {
75623
+ const parsed = JSON.parse(s);
75624
+ if (Array.isArray(parsed)) return parsed;
75625
+ } catch {
75626
+ }
75627
+ }
75628
+ return s.split(/[\n,]/).map((x2) => x2.trim()).filter(Boolean);
75629
+ }
75630
+ var strList = external_exports.preprocess(toStrList, external_exports.array(external_exports.string().max(MAX_ITEM)).max(MAX_ITEMS));
75599
75631
  var baseRecord = {
75600
75632
  id,
75601
75633
  created_at: isoDateTime,
@@ -75657,6 +75689,9 @@ var ExperienceRecordSchema = external_exports.object({
75657
75689
  tech: external_exports.array(external_exports.string()),
75658
75690
  order_index: external_exports.number()
75659
75691
  });
75692
+ var ExperienceBatchInputSchema = external_exports.object({
75693
+ 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.")
75694
+ });
75660
75695
  var ProjectInputSchema = external_exports.object({
75661
75696
  name: reqLine,
75662
75697
  role: optLine,
@@ -75680,6 +75715,9 @@ var ProjectRecordSchema = external_exports.object({
75680
75715
  end_date: external_exports.string().nullable(),
75681
75716
  order_index: external_exports.number()
75682
75717
  });
75718
+ var ProjectBatchInputSchema = external_exports.object({
75719
+ 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.")
75720
+ });
75683
75721
  var SkillInputSchema = external_exports.object({
75684
75722
  name: reqLine,
75685
75723
  category: external_exports.string().max(MAX_LINE).optional().describe("\uC5B8\uC5B4/\uD504\uB808\uC784\uC6CC\uD06C/\uD234/\uC18C\uD504\uD2B8\uC2A4\uD0AC \uB4F1"),
@@ -75695,6 +75733,9 @@ var SkillRecordSchema = external_exports.object({
75695
75733
  years: external_exports.number().nullable(),
75696
75734
  order_index: external_exports.number()
75697
75735
  });
75736
+ var SkillBatchInputSchema = external_exports.object({
75737
+ 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.")
75738
+ });
75698
75739
  var DocumentInputSchema = external_exports.object({
75699
75740
  kind: external_exports.enum(DOCUMENT_KINDS),
75700
75741
  title: reqLine,
@@ -75891,7 +75932,7 @@ var ApplicationContextSchema = external_exports.object({
75891
75932
  });
75892
75933
 
75893
75934
  // packages/shared/src/build-info.ts
75894
- var APP_VERSION = true ? "0.0.3" : "0.0.0-dev";
75935
+ var APP_VERSION = true ? "0.0.5" : "0.0.0-dev";
75895
75936
  var BUNDLED = true;
75896
75937
 
75897
75938
  // packages/shared/src/index.ts
@@ -75906,6 +75947,7 @@ function now() {
75906
75947
 
75907
75948
  // packages/db/src/repositories.ts
75908
75949
  var PROFILE_ID = "profile-singleton";
75950
+ var norm = (s) => (s ?? "").trim().toLowerCase();
75909
75951
  function mapProfile(r) {
75910
75952
  return {
75911
75953
  id: r.id,
@@ -75983,6 +76025,9 @@ function mapExperience(r) {
75983
76025
  updated_at: r.updated_at
75984
76026
  };
75985
76027
  }
76028
+ function expKey(company, role, start) {
76029
+ return [norm(company), norm(role), norm(start)].join("\0");
76030
+ }
75986
76031
  var experienceRepo = {
75987
76032
  list() {
75988
76033
  return getDb().prepare(`SELECT * FROM experiences ORDER BY order_index ASC, start_date DESC`).all().map(mapExperience);
@@ -75991,6 +76036,32 @@ var experienceRepo = {
75991
76036
  const r = getDb().prepare(`SELECT * FROM experiences WHERE id = ?`).get(id2);
75992
76037
  return r ? mapExperience(r) : null;
75993
76038
  },
76039
+ /** Existing experience with the same company+role+start_date, or null (for idempotent batch upserts). */
76040
+ findMatch(input) {
76041
+ const key = expKey(input.company, input.role ?? null, input.start_date ?? null);
76042
+ return this.list().find((e) => expKey(e.company, e.role, e.start_date) === key) ?? null;
76043
+ },
76044
+ /** Insert, or merge into the matching experience (omitted fields keep their stored value). */
76045
+ upsert(input) {
76046
+ const existing = this.findMatch(input);
76047
+ if (existing) return { record: this.update(existing.id, input), created: false };
76048
+ return { record: this.add(input), created: true };
76049
+ },
76050
+ /** Batch insert/merge in one transaction; within-batch duplicates collapse onto one row. */
76051
+ addMany(inputs) {
76052
+ return tx(() => {
76053
+ const records = [];
76054
+ let created = 0;
76055
+ let updated = 0;
76056
+ for (const input of inputs) {
76057
+ const { record, created: isNew } = this.upsert(input);
76058
+ records.push(record);
76059
+ if (isNew) created += 1;
76060
+ else updated += 1;
76061
+ }
76062
+ return { records, created, updated };
76063
+ });
76064
+ },
75994
76065
  add(input) {
75995
76066
  const ts = now();
75996
76067
  const id2 = newId("exp_");
@@ -76064,6 +76135,32 @@ var projectRepo = {
76064
76135
  const r = getDb().prepare(`SELECT * FROM projects WHERE id = ?`).get(id2);
76065
76136
  return r ? mapProject(r) : null;
76066
76137
  },
76138
+ /** Existing project with the same name (case-insensitive), or null (for idempotent batch upserts). */
76139
+ findByName(name) {
76140
+ const key = norm(name);
76141
+ return this.list().find((p) => norm(p.name) === key) ?? null;
76142
+ },
76143
+ /** Insert, or merge into the same-named project (omitted fields keep their stored value). */
76144
+ upsert(input) {
76145
+ const existing = this.findByName(input.name);
76146
+ if (existing) return { record: this.update(existing.id, input), created: false };
76147
+ return { record: this.add(input), created: true };
76148
+ },
76149
+ /** Batch insert/merge in one transaction; within-batch duplicates collapse onto one row. */
76150
+ addMany(inputs) {
76151
+ return tx(() => {
76152
+ const records = [];
76153
+ let created = 0;
76154
+ let updated = 0;
76155
+ for (const input of inputs) {
76156
+ const { record, created: isNew } = this.upsert(input);
76157
+ records.push(record);
76158
+ if (isNew) created += 1;
76159
+ else updated += 1;
76160
+ }
76161
+ return { records, created, updated };
76162
+ });
76163
+ },
76067
76164
  add(input) {
76068
76165
  const ts = now();
76069
76166
  const id2 = newId("prj_");
@@ -76131,6 +76228,32 @@ var skillRepo = {
76131
76228
  const r = getDb().prepare(`SELECT * FROM skills WHERE id = ?`).get(id2);
76132
76229
  return r ? mapSkill(r) : null;
76133
76230
  },
76231
+ /** Existing skill with the same name (case-insensitive), or null (for idempotent batch upserts). */
76232
+ findByName(name) {
76233
+ const key = norm(name);
76234
+ return this.list().find((s) => norm(s.name) === key) ?? null;
76235
+ },
76236
+ /** Insert, or merge into the same-named skill (omitted fields keep their stored value). */
76237
+ upsert(input) {
76238
+ const existing = this.findByName(input.name);
76239
+ if (existing) return { record: this.update(existing.id, input), created: false };
76240
+ return { record: this.add(input), created: true };
76241
+ },
76242
+ /** Batch insert/merge in one transaction; within-batch duplicates collapse onto one row. */
76243
+ addMany(inputs) {
76244
+ return tx(() => {
76245
+ const records = [];
76246
+ let created = 0;
76247
+ let updated = 0;
76248
+ for (const input of inputs) {
76249
+ const { record, created: isNew } = this.upsert(input);
76250
+ records.push(record);
76251
+ if (isNew) created += 1;
76252
+ else updated += 1;
76253
+ }
76254
+ return { records, created, updated };
76255
+ });
76256
+ },
76134
76257
  add(input) {
76135
76258
  const ts = now();
76136
76259
  const id2 = newId("skl_");
@@ -76321,6 +76444,7 @@ var coverLetterRepo = {
76321
76444
  remove(id2) {
76322
76445
  const db = getDb();
76323
76446
  return tx(() => {
76447
+ db.prepare(`UPDATE applications SET cover_letter_id=NULL, updated_at=? WHERE cover_letter_id=?`).run(now(), id2);
76324
76448
  db.prepare(`DELETE FROM cover_letter_versions WHERE cover_letter_id=?`).run(id2);
76325
76449
  return db.prepare(`DELETE FROM cover_letters WHERE id=?`).run(id2).changes > 0;
76326
76450
  });
@@ -76409,6 +76533,7 @@ var jobRepo = {
76409
76533
  remove(id2) {
76410
76534
  const db = getDb();
76411
76535
  return tx(() => {
76536
+ db.prepare(`UPDATE cover_letters SET job_id=NULL, updated_at=? WHERE job_id=?`).run(now(), id2);
76412
76537
  db.prepare(`DELETE FROM fit_analyses WHERE job_id=?`).run(id2);
76413
76538
  db.prepare(`DELETE FROM applications WHERE job_id=?`).run(id2);
76414
76539
  db.prepare(`DELETE FROM interview_preps WHERE job_id=?`).run(id2);
@@ -76607,7 +76732,7 @@ var activityRepo = {
76607
76732
  };
76608
76733
 
76609
76734
  // packages/mcp-tools/src/tools.ts
76610
- import fs6 from "node:fs";
76735
+ import fs7 from "node:fs";
76611
76736
  import path7 from "node:path";
76612
76737
 
76613
76738
  // packages/core/src/onboarding.ts
@@ -76960,7 +77085,7 @@ function styleSignals(text) {
76960
77085
  }
76961
77086
  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.";
76962
77087
  function lintArtifact(kind, text, corpus, opts = {}) {
76963
- const norm = normalizeText(text);
77088
+ const norm2 = normalizeText(text);
76964
77089
  const corpusAvailable = (corpus.documents + corpus.structured).trim().length > 0;
76965
77090
  const provenance = analyzeProvenance(text, corpus);
76966
77091
  const signals = styleSignals(text);
@@ -76984,7 +77109,7 @@ function lintArtifact(kind, text, corpus, opts = {}) {
76984
77109
  }
76985
77110
  return {
76986
77111
  kind,
76987
- charCount: [...norm].length,
77112
+ charCount: [...norm2].length,
76988
77113
  signals,
76989
77114
  provenance,
76990
77115
  blocking,
@@ -77044,20 +77169,40 @@ function addResume(input) {
77044
77169
  activityRepo.log("resume_added", `${doc.title} \uBB38\uC11C\uB97C \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, "document", doc.id);
77045
77170
  return doc;
77046
77171
  }
77047
- function addExperience(input) {
77048
- const exp = experienceRepo.add(input);
77049
- activityRepo.log("profile_updated", `\uACBD\uB825(${exp.company})\uC744 \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, "experience", exp.id);
77050
- return exp;
77172
+ function batchNames(names) {
77173
+ const shown = names.filter(Boolean).slice(0, 8).join(", ");
77174
+ if (!shown) return "";
77175
+ return `: ${shown}${names.length > 8 ? " \uC678" : ""}`;
77051
77176
  }
77052
- function addProject(input) {
77053
- const prj = projectRepo.add(input);
77054
- activityRepo.log("profile_updated", `\uD504\uB85C\uC81D\uD2B8(${prj.name})\uB97C \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, "project", prj.id);
77055
- return prj;
77177
+ function addExperiences(inputs) {
77178
+ const res = experienceRepo.addMany(inputs);
77179
+ activityRepo.log(
77180
+ "profile_updated",
77181
+ `\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))}.`,
77182
+ "experience",
77183
+ res.records[0]?.id
77184
+ );
77185
+ return res;
77056
77186
  }
77057
- function addSkill(input) {
77058
- const skill = skillRepo.add(input);
77059
- activityRepo.log("profile_updated", `\uAE30\uC220(${skill.name})\uC744 \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, "skill", skill.id);
77060
- return skill;
77187
+ function addProjects(inputs) {
77188
+ const res = projectRepo.addMany(inputs);
77189
+ activityRepo.log(
77190
+ "profile_updated",
77191
+ `\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))}.`,
77192
+ "project",
77193
+ res.records[0]?.id
77194
+ );
77195
+ return res;
77196
+ }
77197
+ function addSkills(inputs) {
77198
+ const res = skillRepo.addMany(inputs);
77199
+ activityRepo.log(
77200
+ "profile_updated",
77201
+ `\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))}.`,
77202
+ "skill",
77203
+ res.records[0]?.id
77204
+ );
77205
+ return res;
77061
77206
  }
77062
77207
  function saveJobPosting(input, id2) {
77063
77208
  const job = jobRepo.upsert(input, id2);
@@ -77067,7 +77212,7 @@ function saveJobPosting(input, id2) {
77067
77212
  }
77068
77213
  function saveFitAnalysis(input) {
77069
77214
  const job = jobRepo.get(input.job_id);
77070
- 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.`);
77215
+ 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.");
77071
77216
  applicationRepo.ensure(job.id, "draft");
77072
77217
  const fit = fitRepo.save(input);
77073
77218
  const scoreText = fit.score != null ? ` (\uC801\uD569\uB3C4 ${fit.score}\uC810)` : "";
@@ -77082,7 +77227,7 @@ function saveCoverLetterVersion(input) {
77082
77227
  const detail = verification.blocking.map((b) => `${b.label}: ${b.detail}`).join("; ");
77083
77228
  const strictHit = verification.blocking.some((b) => b.id === "unverified_numbers_strict");
77084
77229
  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).`;
77085
- throw new Error(`\uC800\uC7A5 \uC804 \uC790\uB3D9 \uC810\uAC80\uC5D0\uC11C \uB9C9\uD614\uC2B5\uB2C8\uB2E4 \u2014 ${detail}. ${tail}`);
77230
+ throw new CareerMateError(`\uC800\uC7A5 \uC804 \uC790\uB3D9 \uC810\uAC80\uC5D0\uC11C \uB9C9\uD614\uC2B5\uB2C8\uB2E4 \u2014 ${detail}. ${tail}`, 400, "save_gate");
77086
77231
  }
77087
77232
  const { coverLetter, version: version2 } = coverLetterRepo.addVersion({
77088
77233
  cover_letter_id: input.cover_letter_id,
@@ -77109,7 +77254,7 @@ function saveCoverLetterVersion(input) {
77109
77254
  }
77110
77255
  function updateApplicationStatus(jobId, status, note) {
77111
77256
  const job = jobRepo.get(jobId);
77112
- if (!job) throw new Error(`\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${jobId}`);
77257
+ if (!job) throw notFound("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
77113
77258
  const application = applicationRepo.setStatus(jobId, status, note);
77114
77259
  activityRepo.log(
77115
77260
  "application_status_changed",
@@ -77119,17 +77264,17 @@ function updateApplicationStatus(jobId, status, note) {
77119
77264
  );
77120
77265
  const interview_unlocked = INTERVIEW_UNLOCK_STATUSES.includes(status);
77121
77266
  const hasPrep = !!interviewRepo.getByJob(jobId);
77122
- 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;
77267
+ 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;
77123
77268
  return { application, job, interview_unlocked, hint };
77124
77269
  }
77125
77270
  function saveInterviewPrep(input) {
77126
77271
  const job = jobRepo.get(input.job_id);
77127
- if (!job) throw new Error(`\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.job_id}`);
77272
+ if (!job) throw notFound("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
77128
77273
  const app = applicationRepo.getByJob(job.id);
77129
77274
  if (!app || !INTERVIEW_UNLOCK_STATUSES.includes(app.status)) {
77130
77275
  const current = app ? APPLICATION_STATUS_LABELS[app.status] : "\uBBF8\uC9C0\uC6D0";
77131
- throw new Error(
77132
- `\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.`
77276
+ throw conflict(
77277
+ `\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.`
77133
77278
  );
77134
77279
  }
77135
77280
  const prep = interviewRepo.save(input);
@@ -77209,7 +77354,7 @@ var WORKFLOWS = [
77209
77354
  '\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.)',
77210
77355
  "\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.",
77211
77356
  "\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).",
77212
- "\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).",
77357
+ "\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).",
77213
77358
  "\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).",
77214
77359
  "`open_dashboard`\uB97C \uD638\uCD9C\uD574 \uC0AC\uC6A9\uC790\uAC00 \uC790\uAE30 \uB370\uC774\uD130\uB97C \uD655\uC778\uD558\uAC8C \uD55C\uB2E4.",
77215
77360
  "\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."
@@ -77227,8 +77372,8 @@ var WORKFLOWS = [
77227
77372
  "\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.",
77228
77373
  "\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).",
77229
77374
  "\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.",
77230
- '"\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.',
77231
- '\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.',
77375
+ '\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".',
77376
+ '\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.',
77232
77377
  "\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).",
77233
77378
  "`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."
77234
77379
  ]
@@ -78496,14 +78641,94 @@ function fail(text) {
78496
78641
  }
78497
78642
 
78498
78643
  // packages/mcp-tools/src/bridge.ts
78499
- import { spawn } from "node:child_process";
78644
+ import { spawn, spawnSync } from "node:child_process";
78645
+ import fs5 from "node:fs";
78500
78646
  import path5 from "node:path";
78501
78647
  import { fileURLToPath as fileURLToPath2 } from "node:url";
78502
78648
  var __dirname = path5.dirname(fileURLToPath2(import.meta.url));
78503
78649
  var REPO_ROOT = path5.resolve(__dirname, "..", "..", "..");
78504
- function liveServer() {
78650
+ var HEALTH_TIMEOUT_MS = 800;
78651
+ var pendingStart = null;
78652
+ function dashboardLogPath() {
78653
+ return path5.join(getDataDir(), "server.log");
78654
+ }
78655
+ function launchdPlistPath() {
78656
+ return path5.join(getDataDir(), "dashboard.launchd.plist");
78657
+ }
78658
+ function launchdLabel() {
78659
+ let hash = 5381;
78660
+ for (const ch of getDataDir()) hash = (hash * 33 ^ ch.charCodeAt(0)) >>> 0;
78661
+ return `life.careermate.dashboard.${hash.toString(16)}`;
78662
+ }
78663
+ function xmlEscape(value) {
78664
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
78665
+ }
78666
+ function plistString(key, value) {
78667
+ return ` <key>${xmlEscape(key)}</key>
78668
+ <string>${xmlEscape(value)}</string>`;
78669
+ }
78670
+ function renderLaunchdPlist(bin, args, cwd, log) {
78671
+ const env2 = {
78672
+ CAREERMATE_DATA_DIR: getDataDir(),
78673
+ CAREERMATE_NO_OPEN: "1"
78674
+ };
78675
+ if (process.env.CAREERMATE_PORT) env2.CAREERMATE_PORT = process.env.CAREERMATE_PORT;
78676
+ if (process.env.PATH) env2.PATH = process.env.PATH;
78677
+ const programArgs = [bin, ...args].map((arg) => ` <string>${xmlEscape(arg)}</string>`).join("\n");
78678
+ const envEntries = Object.entries(env2).map(([key, value]) => `${plistString(key, value)}`).join("\n");
78679
+ return `<?xml version="1.0" encoding="UTF-8"?>
78680
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
78681
+ <plist version="1.0">
78682
+ <dict>
78683
+ <key>Label</key>
78684
+ <string>${xmlEscape(launchdLabel())}</string>
78685
+ <key>ProgramArguments</key>
78686
+ <array>
78687
+ ${programArgs}
78688
+ </array>
78689
+ <key>WorkingDirectory</key>
78690
+ <string>${xmlEscape(cwd)}</string>
78691
+ <key>EnvironmentVariables</key>
78692
+ <dict>
78693
+ ${envEntries}
78694
+ </dict>
78695
+ <key>RunAtLoad</key>
78696
+ <true/>
78697
+ <key>KeepAlive</key>
78698
+ <false/>
78699
+ <key>StandardOutPath</key>
78700
+ <string>${xmlEscape(log)}</string>
78701
+ <key>StandardErrorPath</key>
78702
+ <string>${xmlEscape(log)}</string>
78703
+ </dict>
78704
+ </plist>
78705
+ `;
78706
+ }
78707
+ function delay(ms) {
78708
+ return new Promise((resolve) => setTimeout(resolve, ms));
78709
+ }
78710
+ async function pingDashboard(url, timeoutMs = HEALTH_TIMEOUT_MS) {
78711
+ const controller = new AbortController();
78712
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
78713
+ timer.unref?.();
78714
+ try {
78715
+ const healthUrl = new URL("/api/health", url);
78716
+ const res = await fetch(healthUrl, {
78717
+ signal: controller.signal,
78718
+ headers: { accept: "application/json" }
78719
+ });
78720
+ if (!res.ok) return false;
78721
+ const body = await res.json().catch(() => null);
78722
+ return body?.ok === true;
78723
+ } catch {
78724
+ return false;
78725
+ } finally {
78726
+ clearTimeout(timer);
78727
+ }
78728
+ }
78729
+ async function liveServer() {
78505
78730
  const info3 = readRuntimeInfo();
78506
- if (info3 && isProcessAlive(info3.pid)) return info3;
78731
+ if (info3 && isProcessAlive(info3.pid) && await pingDashboard(info3.url)) return info3;
78507
78732
  return null;
78508
78733
  }
78509
78734
  function spawnDetached(cmd, args, opts = {}) {
@@ -78512,19 +78737,101 @@ function spawnDetached(cmd, args, opts = {}) {
78512
78737
  child.on("error", () => {
78513
78738
  });
78514
78739
  child.unref();
78740
+ return child.pid ?? null;
78515
78741
  } catch {
78742
+ return null;
78516
78743
  }
78517
78744
  }
78745
+ function spawnViaLaunchd(bin, args, cwd, log) {
78746
+ if (process.platform !== "darwin" || process.env.CAREERMATE_LAUNCHER === "detached") return false;
78747
+ if (typeof process.getuid !== "function") return false;
78748
+ const domain = `gui/${process.getuid()}`;
78749
+ const label = launchdLabel();
78750
+ const plist = launchdPlistPath();
78751
+ try {
78752
+ fs5.writeFileSync(plist, renderLaunchdPlist(bin, args, cwd, log), "utf8");
78753
+ spawnSync("launchctl", ["bootout", `${domain}/${label}`], { stdio: "ignore" });
78754
+ const res = spawnSync("launchctl", ["bootstrap", domain, plist], {
78755
+ encoding: "utf8",
78756
+ timeout: 5e3
78757
+ });
78758
+ if (res.status === 0) return true;
78759
+ fs5.appendFileSync(log, `[${(/* @__PURE__ */ new Date()).toISOString()}] launchctl bootstrap failed: ${res.stderr || res.stdout || res.status}
78760
+ `);
78761
+ } catch (e) {
78762
+ try {
78763
+ fs5.appendFileSync(log, `[${(/* @__PURE__ */ new Date()).toISOString()}] launchctl launch failed: ${e instanceof Error ? e.message : String(e)}
78764
+ `);
78765
+ } catch {
78766
+ }
78767
+ }
78768
+ return false;
78769
+ }
78770
+ function spawnDashboardDetached(bin, args, cwd, logFd) {
78771
+ spawnDetached(bin, args, {
78772
+ cwd,
78773
+ env: { ...process.env, CAREERMATE_NO_OPEN: "1" },
78774
+ stdio: logFd === null ? "ignore" : ["ignore", logFd, logFd]
78775
+ });
78776
+ }
78518
78777
  async function ensureServer(timeoutMs = 12e3) {
78519
- const existing = liveServer();
78778
+ const existing = await liveServer();
78520
78779
  if (existing) return { url: existing.url };
78521
- 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];
78522
- spawnDetached(bin, args, { cwd, env: { ...process.env, CAREERMATE_NO_OPEN: "1" } });
78780
+ if (pendingStart) return pendingStart;
78781
+ pendingStart = startServerBackground(timeoutMs);
78782
+ try {
78783
+ return await pendingStart;
78784
+ } finally {
78785
+ pendingStart = null;
78786
+ }
78787
+ }
78788
+ async function startServerBackground(timeoutMs) {
78789
+ const [bin, args, cwd] = BUNDLED ? [process.execPath, ["--experimental-sqlite", path5.join(__dirname, "web.mjs")], __dirname] : [
78790
+ process.execPath,
78791
+ ["--no-warnings", "--experimental-sqlite", "--import", "tsx", path5.join(REPO_ROOT, "apps/web/src/index.ts")],
78792
+ REPO_ROOT
78793
+ ];
78794
+ const log = dashboardLogPath();
78795
+ let logFd = null;
78796
+ try {
78797
+ logFd = fs5.openSync(log, "a");
78798
+ fs5.writeSync(logFd, `
78799
+ [${(/* @__PURE__ */ new Date()).toISOString()}] CareerMate dashboard background launch
78800
+ `);
78801
+ } catch {
78802
+ logFd = null;
78803
+ }
78804
+ let launchedViaLaunchd = false;
78805
+ try {
78806
+ launchedViaLaunchd = spawnViaLaunchd(bin, args, cwd, log);
78807
+ if (!launchedViaLaunchd) spawnDashboardDetached(bin, args, cwd, logFd);
78808
+ } finally {
78809
+ if (logFd !== null) {
78810
+ try {
78811
+ fs5.closeSync(logFd);
78812
+ } catch {
78813
+ }
78814
+ }
78815
+ }
78523
78816
  const start = Date.now();
78524
78817
  while (Date.now() - start < timeoutMs) {
78525
- const info3 = liveServer();
78818
+ const info3 = await liveServer();
78526
78819
  if (info3) return { url: info3.url };
78527
- await new Promise((r) => setTimeout(r, 300));
78820
+ await delay(300);
78821
+ }
78822
+ if (launchedViaLaunchd) {
78823
+ try {
78824
+ fs5.appendFileSync(log, `[${(/* @__PURE__ */ new Date()).toISOString()}] launchd did not produce a healthy server; falling back to detached spawn
78825
+ `);
78826
+ } catch {
78827
+ }
78828
+ spawnDashboardDetached(bin, args, cwd, null);
78829
+ const fallbackStart = Date.now();
78830
+ while (Date.now() - fallbackStart < Math.min(timeoutMs, 8e3)) {
78831
+ const info3 = await liveServer();
78832
+ if (info3) return { url: info3.url };
78833
+ await delay(300);
78834
+ }
78528
78835
  }
78529
78836
  return null;
78530
78837
  }
@@ -78548,7 +78855,7 @@ function openInFileManager(dir) {
78548
78855
  else openOsTarget(dir);
78549
78856
  }
78550
78857
  async function resolveDashboardUrl(hash = "") {
78551
- const live = liveServer();
78858
+ const live = await liveServer();
78552
78859
  if (live) return { url: live.url + hash, running: true };
78553
78860
  const started = await ensureServer();
78554
78861
  if (started) return { url: started.url + hash, running: true };
@@ -78556,7 +78863,7 @@ async function resolveDashboardUrl(hash = "") {
78556
78863
  }
78557
78864
 
78558
78865
  // packages/mcp-tools/src/update.ts
78559
- import fs5 from "node:fs";
78866
+ import fs6 from "node:fs";
78560
78867
  import path6 from "node:path";
78561
78868
  import { fileURLToPath as fileURLToPath3 } from "node:url";
78562
78869
  import { spawn as spawn2 } from "node:child_process";
@@ -78613,14 +78920,14 @@ function comparePrerelease(a, b) {
78613
78920
  }
78614
78921
  function readCache() {
78615
78922
  try {
78616
- return JSON.parse(fs5.readFileSync(cacheFile(), "utf8"));
78923
+ return JSON.parse(fs6.readFileSync(cacheFile(), "utf8"));
78617
78924
  } catch {
78618
78925
  return null;
78619
78926
  }
78620
78927
  }
78621
78928
  function writeCache(c) {
78622
78929
  try {
78623
- fs5.writeFileSync(cacheFile(), JSON.stringify(c), "utf8");
78930
+ fs6.writeFileSync(cacheFile(), JSON.stringify(c), "utf8");
78624
78931
  } catch {
78625
78932
  }
78626
78933
  }
@@ -78777,16 +79084,18 @@ async function runSelfUpdate() {
78777
79084
  ok: true,
78778
79085
  from,
78779
79086
  to: latest,
78780
- 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.`
79087
+ 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.`
78781
79088
  };
78782
79089
  }
78783
79090
 
78784
79091
  // packages/mcp-tools/src/tools.ts
78785
79092
  var STATUS_LIST = APPLICATION_STATUSES.map((s) => `${s}(${APPLICATION_STATUS_LABELS[s]})`).join(", ");
79093
+ var DELETE_CONFIRMATION = "DELETE";
79094
+ var DASHBOARD_URL = "http://127.0.0.1:4319";
78786
79095
  function listInboxFiles(dir) {
78787
79096
  let names;
78788
79097
  try {
78789
- names = fs6.readdirSync(dir);
79098
+ names = fs7.readdirSync(dir);
78790
79099
  } catch {
78791
79100
  return [];
78792
79101
  }
@@ -78796,7 +79105,7 @@ function listInboxFiles(dir) {
78796
79105
  if (SKIP.test(name)) continue;
78797
79106
  const p = path7.join(dir, name);
78798
79107
  try {
78799
- if (fs6.statSync(p).isFile()) out.push({ name, path: p });
79108
+ if (fs7.statSync(p).isFile()) out.push({ name, path: p });
78800
79109
  } catch {
78801
79110
  }
78802
79111
  }
@@ -78959,9 +79268,17 @@ ${lines}`;
78959
79268
  if (upd.update_available) {
78960
79269
  text += `
78961
79270
 
78962
- \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.`;
78963
- }
78964
- return ok(text, { ...s, verify_strict: strict, update: upd });
79271
+ \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.`;
79272
+ }
79273
+ return ok(text, {
79274
+ ...s,
79275
+ verify_strict: strict,
79276
+ update: upd,
79277
+ suggested_next_action: upd.update_available ? {
79278
+ prompt: `\uC0C8 \uBC84\uC804 v${upd.latest}\uC774 \uB098\uC654\uC2B5\uB2C8\uB2E4. \uC9C0\uAE08 \uC5C5\uB370\uC774\uD2B8\uD560\uAE4C\uC694?`,
79279
+ next_tool: "update_careermate"
79280
+ } : null
79281
+ });
78965
79282
  }
78966
79283
  },
78967
79284
  /* --------------------------------------------------------------- profile */
@@ -79018,11 +79335,12 @@ ${lines}`;
79018
79335
  {
79019
79336
  name: "add_experience",
79020
79337
  title: "\uACBD\uB825 \uCD94\uAC00",
79021
- 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.",
79022
- inputSchema: ExperienceInputSchema.shape,
79338
+ 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.",
79339
+ inputSchema: ExperienceBatchInputSchema.shape,
79023
79340
  handler: (args) => {
79024
- const exp = addExperience(args);
79025
- return ok(`\uACBD\uB825 '${exp.company}${exp.role ? ` \xB7 ${exp.role}` : ""}'\uC744(\uB97C) \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, exp);
79341
+ const { records, created, updated } = addExperiences(args.experiences);
79342
+ const names = records.map((e) => `${e.company}${e.role ? ` \xB7 ${e.role}` : ""}`).join(", ");
79343
+ return ok(`\uACBD\uB825 ${records.length}\uAC74\uC744 \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4 (\uC2E0\uADDC ${created} \xB7 \uAC31\uC2E0 ${updated}): ${names}`, records);
79026
79344
  }
79027
79345
  },
79028
79346
  {
@@ -79040,11 +79358,12 @@ ${lines}`;
79040
79358
  {
79041
79359
  name: "add_project",
79042
79360
  title: "\uD504\uB85C\uC81D\uD2B8 \uCD94\uAC00",
79043
- 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.",
79044
- inputSchema: ProjectInputSchema.shape,
79361
+ 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.",
79362
+ inputSchema: ProjectBatchInputSchema.shape,
79045
79363
  handler: (args) => {
79046
- const prj = addProject(args);
79047
- return ok(`\uD504\uB85C\uC81D\uD2B8 '${prj.name}'\uC744(\uB97C) \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, prj);
79364
+ const { records, created, updated } = addProjects(args.projects);
79365
+ const names = records.map((p) => p.name).join(", ");
79366
+ 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);
79048
79367
  }
79049
79368
  },
79050
79369
  {
@@ -79062,11 +79381,12 @@ ${lines}`;
79062
79381
  {
79063
79382
  name: "add_skill",
79064
79383
  title: "\uAE30\uC220\uC2A4\uD0DD \uCD94\uAC00",
79065
- 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.",
79066
- inputSchema: SkillInputSchema.shape,
79384
+ 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.",
79385
+ inputSchema: SkillBatchInputSchema.shape,
79067
79386
  handler: (args) => {
79068
- const skill = addSkill(args);
79069
- return ok(`\uAE30\uC220 '${skill.name}'\uC744(\uB97C) \uCD94\uAC00\uD588\uC2B5\uB2C8\uB2E4.`, skill);
79387
+ const { records, created, updated } = addSkills(args.skills);
79388
+ const names = records.map((s) => s.name).join(", ");
79389
+ return ok(`\uAE30\uC220 ${records.length}\uAC1C\uB97C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4 (\uC2E0\uADDC ${created} \xB7 \uAC31\uC2E0 ${updated}): ${names}`, records);
79070
79390
  }
79071
79391
  },
79072
79392
  {
@@ -79111,6 +79431,31 @@ ${lines}`;
79111
79431
  }
79112
79432
  }
79113
79433
  },
79434
+ {
79435
+ name: "delete_cover_letter",
79436
+ title: "\uC790\uAE30\uC18C\uAC1C\uC11C \uC0AD\uC81C",
79437
+ 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.",
79438
+ inputSchema: {
79439
+ cover_letter_id: external_exports.string(),
79440
+ confirm: external_exports.string().describe("\uC0AD\uC81C \uD655\uC778\uAC12. \uC815\uD655\uD788 DELETE\uB97C \uB123\uC5B4\uC57C \uC0AD\uC81C\uB429\uB2C8\uB2E4.")
79441
+ },
79442
+ handler: (args) => {
79443
+ if (args?.confirm !== DELETE_CONFIRMATION) {
79444
+ return fail("\uC0AD\uC81C\uD558\uB824\uBA74 confirm\uC5D0 DELETE\uB97C \uB123\uC5B4 \uB2E4\uC2DC \uD638\uCD9C\uD558\uC138\uC694.");
79445
+ }
79446
+ const cl = coverLetterRepo.get(args.cover_letter_id, false);
79447
+ if (!cl) return fail("\uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
79448
+ const removed = coverLetterRepo.remove(cl.id);
79449
+ if (!removed) return fail("\uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC0AD\uC81C\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
79450
+ 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.`;
79451
+ return ok(user_message, {
79452
+ deleted: true,
79453
+ cover_letter_id: cl.id,
79454
+ title: cl.title,
79455
+ user_message
79456
+ });
79457
+ }
79458
+ },
79114
79459
  /* ------------------------------------------------------------------ jobs */
79115
79460
  {
79116
79461
  name: "save_job_posting",
@@ -79119,7 +79464,15 @@ ${lines}`;
79119
79464
  inputSchema: JobInputSchema.shape,
79120
79465
  handler: (args) => {
79121
79466
  const { job } = saveJobPosting(args);
79122
- return ok(`'${job.company} \xB7 ${job.position}' \uACF5\uACE0\uB97C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4. (job_id: ${job.id})`, job);
79467
+ const user_message = `'${job.company} \xB7 ${job.position}' \uACF5\uACE0\uB97C \uC800\uC7A5\uD588\uC5B4\uC694.`;
79468
+ return ok(user_message, {
79469
+ ...job,
79470
+ user_message,
79471
+ suggested_next_action: {
79472
+ prompt: "\uC774 \uACF5\uACE0\uC640 \uB0B4 \uD504\uB85C\uD544\uC744 \uBE44\uAD50\uD574 \uC801\uD569\uB3C4\uB97C \uBD84\uC11D\uD560\uAE4C\uC694?",
79473
+ next_workflow: "analyze_job"
79474
+ }
79475
+ });
79123
79476
  }
79124
79477
  },
79125
79478
  {
@@ -79130,7 +79483,7 @@ ${lines}`;
79130
79483
  readOnly: true,
79131
79484
  handler: (args) => {
79132
79485
  const job = jobRepo.get(args.job_id);
79133
- if (!job) return fail(`\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${args.job_id}`);
79486
+ if (!job) return fail("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
79134
79487
  return ok(`${job.company} \xB7 ${job.position}`, {
79135
79488
  job: jobWithMeta(job),
79136
79489
  fit: fitRepo.getByJob(job.id),
@@ -79151,6 +79504,35 @@ ${lines}`;
79151
79504
  return ok(`\uACF5\uACE0 ${jobs.length}\uAC74`, jobs);
79152
79505
  }
79153
79506
  },
79507
+ {
79508
+ name: "delete_job_posting",
79509
+ title: "\uCC44\uC6A9\uACF5\uACE0 \uC0AD\uC81C",
79510
+ 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.",
79511
+ inputSchema: {
79512
+ job_id: external_exports.string(),
79513
+ confirm: external_exports.string().describe("\uC0AD\uC81C \uD655\uC778\uAC12. \uC815\uD655\uD788 DELETE\uB97C \uB123\uC5B4\uC57C \uC0AD\uC81C\uB429\uB2C8\uB2E4.")
79514
+ },
79515
+ handler: (args) => {
79516
+ if (args?.confirm !== DELETE_CONFIRMATION) {
79517
+ return fail("\uC0AD\uC81C\uD558\uB824\uBA74 confirm\uC5D0 DELETE\uB97C \uB123\uC5B4 \uB2E4\uC2DC \uD638\uCD9C\uD558\uC138\uC694.");
79518
+ }
79519
+ const job = jobRepo.get(args.job_id);
79520
+ if (!job) return fail("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
79521
+ const detachedCoverLetters = coverLetterRepo.listByJob(job.id).length;
79522
+ const removed = jobRepo.remove(job.id);
79523
+ if (!removed) return fail("\uACF5\uACE0\uB97C \uC0AD\uC81C\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.");
79524
+ 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.` : "";
79525
+ 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}`;
79526
+ return ok(user_message, {
79527
+ deleted: true,
79528
+ job_id: job.id,
79529
+ company: job.company,
79530
+ position: job.position,
79531
+ detached_cover_letters: detachedCoverLetters,
79532
+ user_message
79533
+ });
79534
+ }
79535
+ },
79154
79536
  /* ----------------------------------------------------- application context */
79155
79537
  {
79156
79538
  name: "get_application_context",
@@ -79173,7 +79555,7 @@ ${lines}`;
79173
79555
  `\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}`,
79174
79556
  ctx.job ? `\uB300\uC0C1 \uACF5\uACE0: ${ctx.job.company} \xB7 ${ctx.job.position}` : "\uB300\uC0C1 \uACF5\uACE0 \uC5C6\uC74C",
79175
79557
  ctx.writing_preferences.preferred_tone ? `\uC120\uD638 \uBB38\uCCB4: ${ctx.writing_preferences.preferred_tone}` : "",
79176
- 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."
79558
+ 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."
79177
79559
  ].filter(Boolean).join("\n");
79178
79560
  return ok(summary, enriched);
79179
79561
  }
@@ -79186,7 +79568,34 @@ ${lines}`;
79186
79568
  handler: (args) => {
79187
79569
  try {
79188
79570
  const fit = saveFitAnalysis(args);
79189
- return ok(`\uC801\uD569\uB3C4 \uBD84\uC11D\uC744 \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4${fit.score != null ? ` (${fit.score}\uC810)` : ""}.`, fit);
79571
+ const suggested_next_action = {
79572
+ prompt: "\uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uB4DC\uB9B4\uAE4C\uC694?",
79573
+ options: [
79574
+ {
79575
+ label: "\uB124, \uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uC918",
79576
+ next_workflow: "write_cover_letter"
79577
+ },
79578
+ {
79579
+ label: "\uC544\uB2C8\uC694, \uBD84\uC11D\uB9CC \uC800\uC7A5\uD574\uC918",
79580
+ next_workflow: null
79581
+ }
79582
+ ]
79583
+ };
79584
+ 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}`;
79585
+ return ok(
79586
+ [
79587
+ user_message,
79588
+ "",
79589
+ "\uC0AC\uC6A9\uC790\uC5D0\uAC8C \uB2E4\uC74C \uC120\uD0DD\uC9C0\uB97C \uBCF4\uC5EC\uC8FC\uC138\uC694:",
79590
+ "1. \uB124, \uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uC918",
79591
+ "2. \uC544\uB2C8\uC694, \uBD84\uC11D\uB9CC \uC800\uC7A5\uD574\uC918"
79592
+ ].join("\n"),
79593
+ {
79594
+ ...fit,
79595
+ user_message,
79596
+ suggested_next_action
79597
+ }
79598
+ );
79190
79599
  } catch (e) {
79191
79600
  return fail(e instanceof Error ? e.message : "\uC800\uC7A5 \uC2E4\uD328");
79192
79601
  }
@@ -79205,11 +79614,19 @@ ${lines}`;
79205
79614
  handler: (args) => {
79206
79615
  try {
79207
79616
  const res = updateApplicationStatus(args.job_id, args.status, args.note);
79208
- return ok(
79209
- `\uC0C1\uD0DC\uB97C '${APPLICATION_STATUS_LABELS[args.status]}'(\uC73C)\uB85C \uBCC0\uACBD\uD588\uC2B5\uB2C8\uB2E4.${res.hint ? `
79617
+ const user_message = `\uC9C0\uC6D0 \uC0C1\uD0DC\uB97C '${APPLICATION_STATUS_LABELS[args.status]}'(\uC73C)\uB85C \uBC14\uAFE8\uC5B4\uC694.` + (res.hint ? `
79210
79618
 
79211
- \u{1F4A1} ${res.hint}` : ""}`,
79212
- res
79619
+ ${res.hint}` : "");
79620
+ return ok(
79621
+ user_message,
79622
+ {
79623
+ ...res,
79624
+ user_message,
79625
+ suggested_next_action: res.hint ? {
79626
+ prompt: res.hint,
79627
+ next_workflow: "prepare_interview"
79628
+ } : null
79629
+ }
79213
79630
  );
79214
79631
  } catch (e) {
79215
79632
  return fail(e instanceof Error ? e.message : "\uBCC0\uACBD \uC2E4\uD328");
@@ -79225,7 +79642,8 @@ ${lines}`;
79225
79642
  handler: (args) => {
79226
79643
  try {
79227
79644
  const prep = saveInterviewPrep(args);
79228
- return ok(`\uBA74\uC811 \uC900\uBE44 \uC790\uB8CC\uB97C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4 (\uC9C8\uBB38 ${prep.questions.length}\uAC1C).`, prep);
79645
+ 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.`;
79646
+ return ok(user_message, { ...prep, user_message });
79229
79647
  } catch (e) {
79230
79648
  return fail(e instanceof Error ? e.message : "\uC800\uC7A5 \uC2E4\uD328");
79231
79649
  }
@@ -79250,7 +79668,7 @@ ${lines}`;
79250
79668
  const dir = getExportsDir();
79251
79669
  const filePath = path7.join(dir, result2.filename);
79252
79670
  try {
79253
- fs6.writeFileSync(filePath, result2.content, "utf8");
79671
+ fs7.writeFileSync(filePath, result2.content, "utf8");
79254
79672
  } catch (e) {
79255
79673
  return fail(`\uD30C\uC77C \uC800\uC7A5 \uC2E4\uD328: ${e instanceof Error ? e.message : e}`);
79256
79674
  }
@@ -79412,29 +79830,37 @@ ${report.disclaimer}`, report);
79412
79830
  {
79413
79831
  name: "check_for_update",
79414
79832
  title: "\uC5C5\uB370\uC774\uD2B8 \uD655\uC778",
79415
- 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.',
79833
+ 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.',
79416
79834
  inputSchema: {},
79417
79835
  readOnly: true,
79418
79836
  handler: async () => {
79419
79837
  const u = await getUpdateStatusAsync();
79420
79838
  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);
79839
+ 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}).`;
79421
79840
  return ok(
79422
- 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}).`,
79423
- u
79841
+ user_message,
79842
+ {
79843
+ ...u,
79844
+ user_message,
79845
+ suggested_next_action: u.update_available ? {
79846
+ prompt: "\uC9C0\uAE08 CareerMate\uB97C \uC5C5\uB370\uC774\uD2B8\uD560\uAE4C\uC694?",
79847
+ next_tool: "update_careermate"
79848
+ } : null
79849
+ }
79424
79850
  );
79425
79851
  }
79426
79852
  },
79427
79853
  {
79428
79854
  name: "update_careermate",
79429
79855
  title: "CareerMate \uC5C5\uB370\uC774\uD2B8",
79430
- 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.',
79856
+ 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.',
79431
79857
  inputSchema: {},
79432
79858
  handler: async () => {
79433
79859
  const r = await runSelfUpdate();
79434
79860
  if (!r.ok) return fail(`${r.message}${r.manual ? `
79435
79861
 
79436
79862
  \uC9C1\uC811 \uC2E4\uD589: ${r.manual}` : ""}`);
79437
- return ok(r.message, r);
79863
+ return ok(r.message, { ...r, user_message: r.message });
79438
79864
  }
79439
79865
  }
79440
79866
  ];
@@ -79455,7 +79881,7 @@ async function main() {
79455
79881
  const nodeOk = checkNode();
79456
79882
  const dir = getDataDir();
79457
79883
  try {
79458
- fs7.accessSync(dir, fs7.constants.W_OK);
79884
+ fs8.accessSync(dir, fs8.constants.W_OK);
79459
79885
  ok2(`\uB370\uC774\uD130 \uD3F4\uB354 \uC4F0\uAE30 \uAC00\uB2A5: ${dir}`);
79460
79886
  } catch {
79461
79887
  warn2(`\uB370\uC774\uD130 \uD3F4\uB354\uC5D0 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${dir}`);