careermate 0.0.8 → 0.0.9

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.
Files changed (50) hide show
  1. package/AGENTS.md +2 -2
  2. package/LICENSE +1 -1
  3. package/README.md +78 -81
  4. package/bin/careermate.mjs +11 -2
  5. package/dist/career-os/eop/cover-letter.md +10 -4
  6. package/dist/career-os/eop/interview-prep.md +1 -0
  7. package/dist/career-os/knowledge/cover-letter.md +4 -0
  8. package/dist/career-os/knowledge/verifiers/human-voice.md +7 -2
  9. package/dist/doctor.mjs +888 -340
  10. package/dist/mcp.mjs +904 -350
  11. package/dist/migrate.mjs +208 -16
  12. package/dist/public/i18n/en/applications.js +19 -0
  13. package/dist/public/i18n/en/common.js +6 -0
  14. package/dist/public/i18n/en/jobs.js +17 -0
  15. package/dist/public/i18n/en/settings.js +5 -2
  16. package/dist/public/i18n/ko/applications.js +19 -0
  17. package/dist/public/i18n/ko/common.js +5 -0
  18. package/dist/public/i18n/ko/jobs.js +17 -0
  19. package/dist/public/i18n/ko/settings.js +5 -2
  20. package/dist/public/lib.js +29 -0
  21. package/dist/public/pages/applications.js +86 -10
  22. package/dist/public/pages/documents.js +16 -7
  23. package/dist/public/pages/home.js +12 -0
  24. package/dist/public/pages/interview.js +11 -4
  25. package/dist/public/pages/jobs.js +71 -1
  26. package/dist/public/pages/settings.js +2 -2
  27. package/dist/public/styles.css +13 -0
  28. package/dist/web.mjs +544 -199
  29. package/docs/FAQ.md +13 -3
  30. package/docs/MCP_TOOLS.md +22 -9
  31. package/docs/career-os/eop/cover-letter.md +10 -4
  32. package/docs/career-os/eop/interview-prep.md +1 -0
  33. package/docs/career-os/knowledge/cover-letter.md +4 -0
  34. package/docs/career-os/knowledge/verifiers/human-voice.md +7 -2
  35. package/package.json +3 -1
  36. package/packages/core/src/index.ts +1 -0
  37. package/packages/core/src/jobsearch.ts +6 -2
  38. package/packages/core/src/offers.ts +41 -0
  39. package/packages/core/src/services.ts +5 -1
  40. package/packages/core/src/summary.ts +245 -19
  41. package/packages/db/src/connection.ts +70 -2
  42. package/packages/db/src/index.ts +1 -0
  43. package/packages/db/src/repositories.ts +140 -8
  44. package/packages/db/src/schema.ts +140 -8
  45. package/packages/mcp-tools/src/result.ts +9 -0
  46. package/packages/mcp-tools/src/tools.ts +143 -20
  47. package/packages/parsers/src/documents.ts +7 -1
  48. package/packages/prompts/src/humanize.ts +4 -0
  49. package/packages/shared/src/schemas.ts +134 -0
  50. package/scripts/init.ts +2 -0
package/dist/web.mjs CHANGED
@@ -976,8 +976,8 @@ var init_tap = __esm({
976
976
  });
977
977
 
978
978
  // node_modules/underscore/modules/toPath.js
979
- function toPath(path6) {
980
- return isArray_default(path6) ? path6 : [path6];
979
+ function toPath(path7) {
980
+ return isArray_default(path7) ? path7 : [path7];
981
981
  }
982
982
  var init_toPath = __esm({
983
983
  "node_modules/underscore/modules/toPath.js"() {
@@ -989,8 +989,8 @@ var init_toPath = __esm({
989
989
  });
990
990
 
991
991
  // node_modules/underscore/modules/_toPath.js
992
- function toPath2(path6) {
993
- return _.toPath(path6);
992
+ function toPath2(path7) {
993
+ return _.toPath(path7);
994
994
  }
995
995
  var init_toPath2 = __esm({
996
996
  "node_modules/underscore/modules/_toPath.js"() {
@@ -1001,11 +1001,11 @@ var init_toPath2 = __esm({
1001
1001
  });
1002
1002
 
1003
1003
  // node_modules/underscore/modules/_deepGet.js
1004
- function deepGet(obj2, path6) {
1005
- var length = path6.length;
1004
+ function deepGet(obj2, path7) {
1005
+ var length = path7.length;
1006
1006
  for (var i = 0; i < length; i++) {
1007
1007
  if (obj2 == null) return void 0;
1008
- obj2 = obj2[path6[i]];
1008
+ obj2 = obj2[path7[i]];
1009
1009
  }
1010
1010
  return length ? obj2 : void 0;
1011
1011
  }
@@ -1016,8 +1016,8 @@ var init_deepGet = __esm({
1016
1016
  });
1017
1017
 
1018
1018
  // node_modules/underscore/modules/get.js
1019
- function get(object2, path6, defaultValue) {
1020
- var value = deepGet(object2, toPath2(path6));
1019
+ function get(object2, path7, defaultValue) {
1020
+ var value = deepGet(object2, toPath2(path7));
1021
1021
  return isUndefined(value) ? defaultValue : value;
1022
1022
  }
1023
1023
  var init_get = __esm({
@@ -1030,11 +1030,11 @@ var init_get = __esm({
1030
1030
  });
1031
1031
 
1032
1032
  // node_modules/underscore/modules/has.js
1033
- function has2(obj2, path6) {
1034
- path6 = toPath2(path6);
1035
- var length = path6.length;
1033
+ function has2(obj2, path7) {
1034
+ path7 = toPath2(path7);
1035
+ var length = path7.length;
1036
1036
  for (var i = 0; i < length; i++) {
1037
- var key = path6[i];
1037
+ var key = path7[i];
1038
1038
  if (!has(obj2, key)) return false;
1039
1039
  obj2 = obj2[key];
1040
1040
  }
@@ -1074,10 +1074,10 @@ var init_matcher = __esm({
1074
1074
  });
1075
1075
 
1076
1076
  // node_modules/underscore/modules/property.js
1077
- function property(path6) {
1078
- path6 = toPath2(path6);
1077
+ function property(path7) {
1078
+ path7 = toPath2(path7);
1079
1079
  return function(obj2) {
1080
- return deepGet(obj2, path6);
1080
+ return deepGet(obj2, path7);
1081
1081
  };
1082
1082
  }
1083
1083
  var init_property = __esm({
@@ -1193,8 +1193,8 @@ var init_noop = __esm({
1193
1193
  // node_modules/underscore/modules/propertyOf.js
1194
1194
  function propertyOf(obj2) {
1195
1195
  if (obj2 == null) return noop;
1196
- return function(path6) {
1197
- return get(obj2, path6);
1196
+ return function(path7) {
1197
+ return get(obj2, path7);
1198
1198
  };
1199
1199
  }
1200
1200
  var init_propertyOf = __esm({
@@ -1399,14 +1399,14 @@ var init_template = __esm({
1399
1399
  });
1400
1400
 
1401
1401
  // node_modules/underscore/modules/result.js
1402
- function result(obj2, path6, fallback) {
1403
- path6 = toPath2(path6);
1404
- var length = path6.length;
1402
+ function result(obj2, path7, fallback) {
1403
+ path7 = toPath2(path7);
1404
+ var length = path7.length;
1405
1405
  if (!length) {
1406
1406
  return isFunction_default(fallback) ? fallback.call(obj2) : fallback;
1407
1407
  }
1408
1408
  for (var i = 0; i < length; i++) {
1409
- var prop = obj2 == null ? void 0 : obj2[path6[i]];
1409
+ var prop = obj2 == null ? void 0 : obj2[path7[i]];
1410
1410
  if (prop === void 0) {
1411
1411
  prop = fallback;
1412
1412
  i = length;
@@ -2130,14 +2130,14 @@ var init_invoke = __esm({
2130
2130
  init_map();
2131
2131
  init_deepGet();
2132
2132
  init_toPath2();
2133
- invoke_default = restArguments(function(obj2, path6, args) {
2133
+ invoke_default = restArguments(function(obj2, path7, args) {
2134
2134
  var contextPath, func;
2135
- if (isFunction_default(path6)) {
2136
- func = path6;
2135
+ if (isFunction_default(path7)) {
2136
+ func = path7;
2137
2137
  } else {
2138
- path6 = toPath2(path6);
2139
- contextPath = path6.slice(0, -1);
2140
- path6 = path6[path6.length - 1];
2138
+ path7 = toPath2(path7);
2139
+ contextPath = path7.slice(0, -1);
2140
+ path7 = path7[path7.length - 1];
2141
2141
  }
2142
2142
  return map(obj2, function(context) {
2143
2143
  var method = func;
@@ -2146,7 +2146,7 @@ var init_invoke = __esm({
2146
2146
  context = deepGet(context, contextPath);
2147
2147
  }
2148
2148
  if (context == null) return void 0;
2149
- method = context[path6];
2149
+ method = context[path7];
2150
2150
  }
2151
2151
  return method == null ? method : method.apply(context, args);
2152
2152
  });
@@ -11664,8 +11664,8 @@ var require_utils = __commonJS({
11664
11664
  var result2 = transform[inputType][outputType](input);
11665
11665
  return result2;
11666
11666
  };
11667
- exports2.resolve = function(path6) {
11668
- var parts = path6.split("/");
11667
+ exports2.resolve = function(path7) {
11668
+ var parts = path7.split("/");
11669
11669
  var result2 = [];
11670
11670
  for (var index = 0; index < parts.length; index++) {
11671
11671
  var part = parts[index];
@@ -17518,18 +17518,18 @@ var require_object = __commonJS({
17518
17518
  var object2 = new ZipObject(name, zipObjectContent, o);
17519
17519
  this.files[name] = object2;
17520
17520
  };
17521
- var parentFolder = function(path6) {
17522
- if (path6.slice(-1) === "/") {
17523
- path6 = path6.substring(0, path6.length - 1);
17521
+ var parentFolder = function(path7) {
17522
+ if (path7.slice(-1) === "/") {
17523
+ path7 = path7.substring(0, path7.length - 1);
17524
17524
  }
17525
- var lastSlash = path6.lastIndexOf("/");
17526
- return lastSlash > 0 ? path6.substring(0, lastSlash) : "";
17525
+ var lastSlash = path7.lastIndexOf("/");
17526
+ return lastSlash > 0 ? path7.substring(0, lastSlash) : "";
17527
17527
  };
17528
- var forceTrailingSlash = function(path6) {
17529
- if (path6.slice(-1) !== "/") {
17530
- path6 += "/";
17528
+ var forceTrailingSlash = function(path7) {
17529
+ if (path7.slice(-1) !== "/") {
17530
+ path7 += "/";
17531
17531
  }
17532
- return path6;
17532
+ return path7;
17533
17533
  };
17534
17534
  var folderAdd = function(name, createFolders) {
17535
17535
  createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
@@ -18568,27 +18568,27 @@ var require_zipfile = __commonJS({
18568
18568
  };
18569
18569
  });
18570
18570
  }
18571
- function splitPath(path6) {
18572
- var lastIndex = path6.lastIndexOf("/");
18571
+ function splitPath(path7) {
18572
+ var lastIndex = path7.lastIndexOf("/");
18573
18573
  if (lastIndex === -1) {
18574
- return { dirname: "", basename: path6 };
18574
+ return { dirname: "", basename: path7 };
18575
18575
  } else {
18576
18576
  return {
18577
- dirname: path6.substring(0, lastIndex),
18578
- basename: path6.substring(lastIndex + 1)
18577
+ dirname: path7.substring(0, lastIndex),
18578
+ basename: path7.substring(lastIndex + 1)
18579
18579
  };
18580
18580
  }
18581
18581
  }
18582
18582
  function joinPath() {
18583
- var nonEmptyPaths = Array.prototype.filter.call(arguments, function(path6) {
18584
- return path6;
18583
+ var nonEmptyPaths = Array.prototype.filter.call(arguments, function(path7) {
18584
+ return path7;
18585
18585
  });
18586
18586
  var relevantPaths = [];
18587
- nonEmptyPaths.forEach(function(path6) {
18588
- if (/^\//.test(path6)) {
18589
- relevantPaths = [path6];
18587
+ nonEmptyPaths.forEach(function(path7) {
18588
+ if (/^\//.test(path7)) {
18589
+ relevantPaths = [path7];
18590
18590
  } else {
18591
- relevantPaths.push(path6);
18591
+ relevantPaths.push(path7);
18592
18592
  }
18593
18593
  });
18594
18594
  return relevantPaths.join("/");
@@ -26013,9 +26013,9 @@ var require_office_xml_reader = __commonJS({
26013
26013
  return collapseAlternateContent(document2)[0];
26014
26014
  });
26015
26015
  }
26016
- function readXmlFromZipFile(docxFile, path6) {
26017
- if (docxFile.exists(path6)) {
26018
- return docxFile.read(path6, "utf-8").then(stripUtf8Bom).then(read);
26016
+ function readXmlFromZipFile(docxFile, path7) {
26017
+ if (docxFile.exists(path7)) {
26018
+ return docxFile.read(path7, "utf-8").then(stripUtf8Bom).then(read);
26019
26019
  } else {
26020
26020
  return promises.resolve(null);
26021
26021
  }
@@ -27782,10 +27782,10 @@ var require_body_reader = __commonJS({
27782
27782
  }
27783
27783
  }
27784
27784
  function findEmbeddedImageFile(relationshipId) {
27785
- var path6 = uris.uriToZipEntryName("word", relationships.findTargetByRelationshipId(relationshipId));
27785
+ var path7 = uris.uriToZipEntryName("word", relationships.findTargetByRelationshipId(relationshipId));
27786
27786
  return {
27787
- path: path6,
27788
- read: docxFile.read.bind(docxFile, path6)
27787
+ path: path7,
27788
+ read: docxFile.read.bind(docxFile, path7)
27789
27789
  };
27790
27790
  }
27791
27791
  function readImage(imageFile, altText) {
@@ -28025,12 +28025,12 @@ var require_content_types_reader = __commonJS({
28025
28025
  }
28026
28026
  function contentTypes(overrides, extensionDefaults) {
28027
28027
  return {
28028
- findContentType: function(path6) {
28029
- var overrideContentType = overrides[path6];
28028
+ findContentType: function(path7) {
28029
+ var overrideContentType = overrides[path7];
28030
28030
  if (overrideContentType) {
28031
28031
  return overrideContentType;
28032
28032
  } else {
28033
- var pathParts = path6.split(".");
28033
+ var pathParts = path7.split(".");
28034
28034
  var extension = pathParts[pathParts.length - 1];
28035
28035
  if (extensionDefaults.hasOwnProperty(extension)) {
28036
28036
  return extensionDefaults[extension];
@@ -28277,12 +28277,12 @@ var require_comments_reader = __commonJS({
28277
28277
  var require_path_is_absolute = __commonJS({
28278
28278
  "node_modules/path-is-absolute/index.js"(exports2, module2) {
28279
28279
  "use strict";
28280
- function posix(path6) {
28281
- return path6.charAt(0) === "/";
28280
+ function posix(path7) {
28281
+ return path7.charAt(0) === "/";
28282
28282
  }
28283
- function win32(path6) {
28283
+ function win32(path7) {
28284
28284
  var splitDeviceRe = /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/;
28285
- var result2 = splitDeviceRe.exec(path6);
28285
+ var result2 = splitDeviceRe.exec(path7);
28286
28286
  var device = result2[1] || "";
28287
28287
  var isUnc = Boolean(device && device.charAt(1) !== ":");
28288
28288
  return Boolean(result2[2] || isUnc);
@@ -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 fs5 = __require("fs");
28300
+ var fs6 = __require("fs");
28301
28301
  var url = __require("url");
28302
28302
  var os3 = __require("os");
28303
28303
  var dirname = __require("path").dirname;
@@ -28317,19 +28317,19 @@ var require_files = __commonJS({
28317
28317
  }
28318
28318
  var base = options.relativeToFile ? dirname(options.relativeToFile) : null;
28319
28319
  function read(uri, encoding) {
28320
- return resolveUri(uri).then(function(path6) {
28321
- return readFile(path6, encoding).caught(function(error) {
28320
+ return resolveUri(uri).then(function(path7) {
28321
+ return readFile(path7, encoding).caught(function(error) {
28322
28322
  var message = "could not open external image: '" + uri + "' (document directory: '" + base + "')\n" + error.message;
28323
28323
  return promises.reject(new Error(message));
28324
28324
  });
28325
28325
  });
28326
28326
  }
28327
28327
  function resolveUri(uri) {
28328
- var path6 = uriToPath(uri);
28329
- if (isAbsolutePath(path6)) {
28330
- return promises.resolve(path6);
28328
+ var path7 = uriToPath(uri);
28329
+ if (isAbsolutePath(path7)) {
28330
+ return promises.resolve(path7);
28331
28331
  } else if (base) {
28332
- return promises.resolve(resolvePath(base, path6));
28332
+ return promises.resolve(resolvePath(base, path7));
28333
28333
  } else {
28334
28334
  return promises.reject(new Error("could not find external image '" + uri + "', path of input document is unknown"));
28335
28335
  }
@@ -28338,18 +28338,18 @@ var require_files = __commonJS({
28338
28338
  read
28339
28339
  };
28340
28340
  }
28341
- var readFile = promises.promisify(fs5.readFile.bind(fs5));
28341
+ var readFile = promises.promisify(fs6.readFile.bind(fs6));
28342
28342
  function uriToPath(uriString, platform) {
28343
28343
  if (!platform) {
28344
28344
  platform = os3.platform();
28345
28345
  }
28346
28346
  var uri = url.parse(uriString);
28347
28347
  if (isLocalFileUri(uri) || isRelativeUri(uri)) {
28348
- var path6 = decodeURIComponent(uri.path);
28349
- if (platform === "win32" && /^\/[a-z]:/i.test(path6)) {
28350
- return path6.slice(1);
28348
+ var path7 = decodeURIComponent(uri.path);
28349
+ if (platform === "win32" && /^\/[a-z]:/i.test(path7)) {
28350
+ return path7.slice(1);
28351
28351
  } else {
28352
- return path6;
28352
+ return path7;
28353
28353
  }
28354
28354
  } else {
28355
28355
  throw new Error("Could not convert URI to path: " + uriString);
@@ -28550,18 +28550,18 @@ var require_docx_reader = __commonJS({
28550
28550
  readElement: contentTypesReader.readContentTypesFromXml,
28551
28551
  defaultValue: contentTypesReader.defaultContentTypes
28552
28552
  });
28553
- function readNumberingFromZipFile(zipFile, path6, styles) {
28553
+ function readNumberingFromZipFile(zipFile, path7, styles) {
28554
28554
  return xmlFileReader({
28555
- filename: path6,
28555
+ filename: path7,
28556
28556
  readElement: function(element) {
28557
28557
  return numberingXml.readNumberingXml(element, { styles });
28558
28558
  },
28559
28559
  defaultValue: numberingXml.defaultNumbering
28560
28560
  })(zipFile);
28561
28561
  }
28562
- function readStylesFromZipFile(zipFile, path6) {
28562
+ function readStylesFromZipFile(zipFile, path7) {
28563
28563
  return xmlFileReader({
28564
- filename: path6,
28564
+ filename: path7,
28565
28565
  readElement: stylesReader.readStylesXml,
28566
28566
  defaultValue: stylesReader.defaultStyles
28567
28567
  })(zipFile);
@@ -28593,10 +28593,10 @@ var require_style_map = __commonJS({
28593
28593
  });
28594
28594
  }
28595
28595
  function updateRelationships(docxFile) {
28596
- var path6 = "word/_rels/document.xml.rels";
28596
+ var path7 = "word/_rels/document.xml.rels";
28597
28597
  var relationshipsUri = "http://schemas.openxmlformats.org/package/2006/relationships";
28598
28598
  var relationshipElementName = "{" + relationshipsUri + "}Relationship";
28599
- return docxFile.read(path6, "utf8").then(xml.readString).then(function(relationshipsContainer) {
28599
+ return docxFile.read(path7, "utf8").then(xml.readString).then(function(relationshipsContainer) {
28600
28600
  var relationships = relationshipsContainer.children;
28601
28601
  addOrUpdateElement(relationships, relationshipElementName, "Id", {
28602
28602
  "Id": "rMammothStyleMap",
@@ -28604,21 +28604,21 @@ var require_style_map = __commonJS({
28604
28604
  "Target": styleMapAbsolutePath
28605
28605
  });
28606
28606
  var namespaces = { "": relationshipsUri };
28607
- return docxFile.write(path6, xml.writeString(relationshipsContainer, namespaces));
28607
+ return docxFile.write(path7, xml.writeString(relationshipsContainer, namespaces));
28608
28608
  });
28609
28609
  }
28610
28610
  function updateContentTypes(docxFile) {
28611
- var path6 = "[Content_Types].xml";
28611
+ var path7 = "[Content_Types].xml";
28612
28612
  var contentTypesUri = "http://schemas.openxmlformats.org/package/2006/content-types";
28613
28613
  var overrideName = "{" + contentTypesUri + "}Override";
28614
- return docxFile.read(path6, "utf8").then(xml.readString).then(function(typesElement) {
28614
+ return docxFile.read(path7, "utf8").then(xml.readString).then(function(typesElement) {
28615
28615
  var children = typesElement.children;
28616
28616
  addOrUpdateElement(children, overrideName, "PartName", {
28617
28617
  "PartName": styleMapAbsolutePath,
28618
28618
  "ContentType": "text/prs.mammoth.style-map"
28619
28619
  });
28620
28620
  var namespaces = { "": contentTypesUri };
28621
- return docxFile.write(path6, xml.writeString(typesElement, namespaces));
28621
+ return docxFile.write(path7, xml.writeString(typesElement, namespaces));
28622
28622
  });
28623
28623
  }
28624
28624
  function addOrUpdateElement(elements, name, identifyingAttribute, attributes) {
@@ -29315,9 +29315,9 @@ var require_document_to_html = __commonJS({
29315
29315
  };
29316
29316
  var paths = [];
29317
29317
  if (run.highlight !== null) {
29318
- var path6 = findHtmlPath({ type: "highlight", color: run.highlight });
29319
- if (path6) {
29320
- paths.push(path6);
29318
+ var path7 = findHtmlPath({ type: "highlight", color: run.highlight });
29319
+ if (path7) {
29320
+ paths.push(path7);
29321
29321
  }
29322
29322
  }
29323
29323
  if (run.isSmallCaps) {
@@ -29352,15 +29352,15 @@ var require_document_to_html = __commonJS({
29352
29352
  messages.push(unrecognisedStyleWarning("run", run));
29353
29353
  }
29354
29354
  paths.push(stylePath);
29355
- paths.forEach(function(path7) {
29356
- nodes = path7.wrap.bind(path7, nodes);
29355
+ paths.forEach(function(path8) {
29356
+ nodes = path8.wrap.bind(path8, nodes);
29357
29357
  });
29358
29358
  return nodes();
29359
29359
  }
29360
29360
  function findHtmlPathForRunProperty(elementType, defaultTagName) {
29361
- var path6 = findHtmlPath({ type: elementType });
29362
- if (path6) {
29363
- return path6;
29361
+ var path7 = findHtmlPath({ type: elementType });
29362
+ if (path7) {
29363
+ return path7;
29364
29364
  } else if (defaultTagName) {
29365
29365
  return htmlPaths.element(defaultTagName, {}, { fresh: false });
29366
29366
  } else {
@@ -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 fs5 = __require("fs");
31083
+ var fs6 = __require("fs");
31084
31084
  var promises = require_promises();
31085
31085
  var zipfile = require_zipfile();
31086
31086
  exports2.openZip = openZip;
31087
- var readFile = promises.promisify(fs5.readFile);
31087
+ var readFile = promises.promisify(fs6.readFile);
31088
31088
  function openZip(options) {
31089
31089
  if (options.path) {
31090
31090
  return readFile(options.path).then(zipfile.openArrayBuffer);
@@ -31401,8 +31401,8 @@ var require_cjs = __commonJS({
31401
31401
  return module3 = {
31402
31402
  path: basedir,
31403
31403
  exports: {},
31404
- require: function(path6, base) {
31405
- return commonjsRequire(path6, base === void 0 || base === null ? module3.path : base);
31404
+ require: function(path7, base) {
31405
+ return commonjsRequire(path7, base === void 0 || base === null ? module3.path : base);
31406
31406
  }
31407
31407
  }, fn(module3, module3.exports), module3.exports;
31408
31408
  }
@@ -31809,9 +31809,9 @@ var require_cjs = __commonJS({
31809
31809
  }
31810
31810
  return o;
31811
31811
  }
31812
- var fs5;
31812
+ var fs6;
31813
31813
  function get_fs() {
31814
- return fs5 || (fs5 = require$$0__default["default"]);
31814
+ return fs6 || (fs6 = 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(fs5.readFileSync(filename2), options);
32099
+ return parse2(fs6.readFileSync(filename2), options);
32100
32100
  }
32101
32101
  function read(blob, options) {
32102
32102
  switch (options && options.type || "base64") {
@@ -32362,7 +32362,7 @@ var require_cjs = __commonJS({
32362
32362
  while (o.l < o.length) o.write_shift(1, 0);
32363
32363
  return o;
32364
32364
  }
32365
- function find2(cfb2, path6) {
32365
+ function find2(cfb2, path7) {
32366
32366
  var UCFullPaths = cfb2.FullPaths.map(function(x) {
32367
32367
  return x.toUpperCase();
32368
32368
  });
@@ -32371,11 +32371,11 @@ var require_cjs = __commonJS({
32371
32371
  return y[y.length - (x.slice(-1) == "/" ? 2 : 1)];
32372
32372
  });
32373
32373
  var k = false;
32374
- if (path6.charCodeAt(0) === 47) {
32374
+ if (path7.charCodeAt(0) === 47) {
32375
32375
  k = true;
32376
- path6 = UCFullPaths[0].slice(0, -1) + path6;
32377
- } else k = path6.indexOf("/") !== -1;
32378
- var UCPath = path6.toUpperCase();
32376
+ path7 = UCFullPaths[0].slice(0, -1) + path7;
32377
+ } else k = path7.indexOf("/") !== -1;
32378
+ var UCPath = path7.toUpperCase();
32379
32379
  var w = k === true ? UCFullPaths.indexOf(UCPath) : UCPaths.indexOf(UCPath);
32380
32380
  if (w !== -1) return cfb2.FileIndex[w];
32381
32381
  var m = !UCPath.match(chr1);
@@ -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
- fs5.writeFileSync(filename2, o);
32414
+ fs6.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
- fs5.writeFileSync(options.filename, o);
32426
+ fs6.writeFileSync(options.filename, o);
32427
32427
  return o;
32428
32428
  case "binary":
32429
32429
  return typeof o == "string" ? o : a2s(o);
@@ -37848,8 +37848,8 @@ var require_cjs = __commonJS({
37848
37848
  reader.readUInt16();
37849
37849
  var id3 = reader.readUInt16();
37850
37850
  var extension = reader.readString();
37851
- var path6 = "Root Entry/BinData/BIN".concat("".concat(id3.toString(16).toUpperCase()).padStart(4, "0"), ".").concat(extension);
37852
- var payload = cfb.find(this.container, path6).content;
37851
+ var path7 = "Root Entry/BinData/BIN".concat("".concat(id3.toString(16).toUpperCase()).padStart(4, "0"), ".").concat(extension);
37852
+ var payload = cfb.find(this.container, path7).content;
37853
37853
  this.result.binData.push(new BinData(extension, pako_1.inflate(payload, {
37854
37854
  windowBits: -15
37855
37855
  })));
@@ -39358,7 +39358,7 @@ import { spawn as spawn2 } from "node:child_process";
39358
39358
 
39359
39359
  // apps/web/src/server.ts
39360
39360
  import http from "node:http";
39361
- import path5 from "node:path";
39361
+ import path6 from "node:path";
39362
39362
  import { fileURLToPath as fileURLToPath2 } from "node:url";
39363
39363
 
39364
39364
  // packages/db/src/paths.ts
@@ -39405,6 +39405,8 @@ function clearRuntimeInfo() {
39405
39405
  }
39406
39406
 
39407
39407
  // packages/db/src/connection.ts
39408
+ import fs3 from "node:fs";
39409
+ import path3 from "node:path";
39408
39410
  import { DatabaseSync } from "node:sqlite";
39409
39411
 
39410
39412
  // packages/db/src/schema.ts
@@ -39723,33 +39725,155 @@ var MIGRATIONS = [
39723
39725
  // idempotent. Feeds next-round prep and consistency (lessons carry into later interviews).
39724
39726
  `
39725
39727
  ALTER TABLE interview_preps ADD COLUMN debrief TEXT NOT NULL DEFAULT '{}';
39728
+ `,
39729
+ // v7 — offer comparison (한국 총보상 필드) + company reputation research field on jobs.
39730
+ // offers: one per job (UNIQUE job_id, mirrors interview_preps). Cash pieces are numeric (만원) so
39731
+ // the server can deterministically sum an annual estimate; equity/other stay free-text (comp is
39732
+ // messy). ai_score/verdict are written by the connected AI at save time (no LLM here) and rendered
39733
+ // by the dashboard, so it never needs to call an LLM. jobs.reputation: AI-researched company
39734
+ // reputation summary (same pattern as company_overview) — feeds offer verdicts + offer-evaluation.
39735
+ `
39736
+ CREATE TABLE IF NOT EXISTS offers (
39737
+ id TEXT PRIMARY KEY,
39738
+ job_id TEXT NOT NULL UNIQUE REFERENCES jobs(id) ON DELETE CASCADE,
39739
+ base_salary INTEGER,
39740
+ bonus_amount INTEGER,
39741
+ welfare_amount INTEGER,
39742
+ signing_amount INTEGER,
39743
+ equity_note TEXT,
39744
+ comp_note TEXT,
39745
+ work_arrangement TEXT,
39746
+ contract_type TEXT,
39747
+ accept_deadline TEXT,
39748
+ ai_score INTEGER,
39749
+ verdict TEXT,
39750
+ notes TEXT,
39751
+ created_at TEXT NOT NULL,
39752
+ updated_at TEXT NOT NULL
39753
+ );
39754
+ CREATE INDEX IF NOT EXISTS idx_offers_job ON offers(job_id);
39755
+ ALTER TABLE jobs ADD COLUMN reputation TEXT;
39756
+ `,
39757
+ // v8 — offer lifecycle status. 지원 상태(전형 깔때기 = 회사의 프로세스)와 직교하는
39758
+ // "내 응답" 축: open(진행)/accepted(수락)/declined(거절). declined여도 행을 지우지
39759
+ // 않는다 — 조건(연봉·처우) 히스토리는 다음 협상의 자산이라, 대시보드가 접힌
39760
+ // '지난 오퍼'로 강등해 보여줄 뿐이다. Additive ALTER (runner가 멱등 처리).
39761
+ `
39762
+ ALTER TABLE offers ADD COLUMN offer_status TEXT NOT NULL DEFAULT 'open';
39763
+ `,
39764
+ // v9 — 한국 문항형 자소서: 회사가 지정한 문항·글자수를 공고에 구조화 보관. 한국 자소서는
39765
+ // 회사별 문항형(항목·글자수 제한)이 지배적이라, AI가 문항별로 답변을 쓰고 글자수를 점검할 때
39766
+ // 참조할 원본을 공고 옆에 둔다(별도 엔티티가 아니라 job 컨텍스트에 함께 실린다). JSON 배열을
39767
+ // TEXT에 저장(다른 JSON 필드와 동일). Additive ALTER (runner가 멱등 처리).
39768
+ `
39769
+ ALTER TABLE jobs ADD COLUMN questions TEXT NOT NULL DEFAULT '[]';
39770
+ `,
39771
+ // v10 — 탈락 회고: 어느 전형 단계에서 떨어졌는지 자가 기록. 탈락 사유를 고지하는 기업은 6%뿐이고
39772
+ // 지원자 83%가 알고 싶어하므로, 이 자가 기록이 "서류는 붙는데 면접에서 떨어진다" 같은 패턴을 로컬
39773
+ // 데이터로 드러내는 유일한 신호다. 탈락이 아니거나 미기록이면 NULL. Additive ALTER (runner가 멱등 처리).
39774
+ `
39775
+ ALTER TABLE applications ADD COLUMN rejected_stage TEXT;
39776
+ `,
39777
+ // v11 — 전형 일정: 서류마감→인적성·AI역검→1·2차 면접→발표로 이어지는 단계별 날짜를 공고에
39778
+ // 구조화 보관. 일정을 jobs에 두는 이유: 서류마감(deadline)이 이미 jobs에 있고, save_job_posting
39779
+ // 업서트를 그대로 재사용해 도구 신설 없이 갱신할 수 있다. 면접초대 메일·발표 공지를 사용자가
39780
+ // 붙여넣으면 AI가 날짜를 추출해 저장하고, 서버가 D-day·같은 날짜 충돌을 결정론으로 계산한다.
39781
+ // JSON 배열을 TEXT에 저장(다른 JSON 필드와 동일). Additive ALTER (runner가 멱등 처리).
39782
+ `
39783
+ ALTER TABLE jobs ADD COLUMN schedule TEXT NOT NULL DEFAULT '[]';
39726
39784
  `
39727
39785
  ];
39728
39786
  function migrate(db) {
39729
39787
  db.exec(`CREATE TABLE IF NOT EXISTS _meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);`);
39730
39788
  const row = db.prepare(`SELECT value FROM _meta WHERE key = 'schema_version'`).get();
39731
39789
  const from = row ? Number(row.value) : 0;
39790
+ if (from >= MIGRATIONS.length) return { from, to: from };
39732
39791
  for (let v = from; v < MIGRATIONS.length; v++) {
39733
- db.exec(MIGRATIONS[v]);
39792
+ applyMigration(db, v);
39734
39793
  }
39735
- const to = MIGRATIONS.length;
39736
- if (to !== from) {
39737
- db.prepare(
39738
- `INSERT INTO _meta (key, value) VALUES ('schema_version', ?)
39739
- ON CONFLICT(key) DO UPDATE SET value = excluded.value`
39740
- ).run(String(to));
39794
+ return { from, to: MIGRATIONS.length };
39795
+ }
39796
+ function stampVersion(db, version) {
39797
+ db.prepare(
39798
+ `INSERT INTO _meta (key, value) VALUES ('schema_version', ?)
39799
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`
39800
+ ).run(String(version));
39801
+ }
39802
+ function columnExists(db, table, column) {
39803
+ const rows = db.prepare(`PRAGMA table_info("${table.replaceAll('"', '""')}")`).all();
39804
+ return rows.some((r) => r.name === column);
39805
+ }
39806
+ var SELF_TRANSACTIONAL_MIGRATIONS = /* @__PURE__ */ new Set([1]);
39807
+ var ALTER_ADD_COLUMN_RE = /^[ \t]*ALTER TABLE\s+(\w+)\s+ADD COLUMN\s+(\w+)[^;]*;[ \t]*$/gm;
39808
+ function skipExistingColumns(db, sql) {
39809
+ return sql.replace(
39810
+ ALTER_ADD_COLUMN_RE,
39811
+ (stmt, table, column) => columnExists(db, table, column) ? "" : stmt
39812
+ );
39813
+ }
39814
+ function applyMigration(db, v) {
39815
+ const sql = MIGRATIONS[v];
39816
+ if (SELF_TRANSACTIONAL_MIGRATIONS.has(v)) {
39817
+ db.exec(sql);
39818
+ stampVersion(db, v + 1);
39819
+ return;
39820
+ }
39821
+ db.exec("BEGIN");
39822
+ try {
39823
+ db.exec(skipExistingColumns(db, sql));
39824
+ stampVersion(db, v + 1);
39825
+ db.exec("COMMIT");
39826
+ } catch (err) {
39827
+ try {
39828
+ db.exec("ROLLBACK");
39829
+ } catch {
39830
+ }
39831
+ throw err;
39741
39832
  }
39742
- return { from, to };
39743
39833
  }
39744
39834
 
39745
39835
  // packages/db/src/connection.ts
39746
39836
  var _db = null;
39837
+ var PRE_MIGRATE_KEEP = 5;
39838
+ function vacuumInto(db, destPath) {
39839
+ if (/[\0\n\r]/.test(destPath)) throw new Error("backup path not safe for SQL literal");
39840
+ db.exec(`VACUUM INTO '${destPath.replace(/'/g, "''")}'`);
39841
+ }
39842
+ function preMigrationBackup(db) {
39843
+ try {
39844
+ const hasMeta = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='_meta'`).get();
39845
+ if (!hasMeta) return;
39846
+ const row = db.prepare(`SELECT value FROM _meta WHERE key = 'schema_version'`).get();
39847
+ const from = row ? Number(row.value) : 0;
39848
+ if (from === 0 || from >= MIGRATIONS.length) return;
39849
+ const dir = getBackupsDir();
39850
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
39851
+ const dest = path3.join(dir, `pre-migrate-v${from}-${stamp}.sqlite`);
39852
+ try {
39853
+ vacuumInto(db, dest);
39854
+ } catch {
39855
+ fs3.copyFileSync(getDbPath(), dest);
39856
+ }
39857
+ const stale = fs3.readdirSync(dir).filter((f) => f.startsWith("pre-migrate-") && f.endsWith(".sqlite")).sort().reverse().slice(PRE_MIGRATE_KEEP);
39858
+ for (const f of stale) {
39859
+ try {
39860
+ fs3.unlinkSync(path3.join(dir, f));
39861
+ } catch {
39862
+ }
39863
+ }
39864
+ } catch (err) {
39865
+ console.error(
39866
+ `[careermate] \uC5C5\uADF8\uB808\uC774\uB4DC \uC804 \uC790\uB3D9 \uBC31\uC5C5 \uC2E4\uD328(\uB9C8\uC774\uADF8\uB808\uC774\uC158\uC740 \uACC4\uC18D \uC9C4\uD589): ${err instanceof Error ? err.message : String(err)}`
39867
+ );
39868
+ }
39869
+ }
39747
39870
  function getDb() {
39748
39871
  if (_db) return _db;
39749
39872
  const db = new DatabaseSync(getDbPath());
39750
39873
  db.exec("PRAGMA journal_mode = WAL;");
39751
39874
  db.exec("PRAGMA foreign_keys = ON;");
39752
39875
  db.exec("PRAGMA busy_timeout = 5000;");
39876
+ preMigrationBackup(db);
39753
39877
  migrate(db);
39754
39878
  _db = db;
39755
39879
  return db;
@@ -40409,8 +40533,8 @@ function getErrorMap() {
40409
40533
 
40410
40534
  // node_modules/zod/v3/helpers/parseUtil.js
40411
40535
  var makeIssue = (params) => {
40412
- const { data, path: path6, errorMaps, issueData } = params;
40413
- const fullPath = [...path6, ...issueData.path || []];
40536
+ const { data, path: path7, errorMaps, issueData } = params;
40537
+ const fullPath = [...path7, ...issueData.path || []];
40414
40538
  const fullIssue = {
40415
40539
  ...issueData,
40416
40540
  path: fullPath
@@ -40526,11 +40650,11 @@ var errorUtil;
40526
40650
 
40527
40651
  // node_modules/zod/v3/types.js
40528
40652
  var ParseInputLazyPath = class {
40529
- constructor(parent, value, path6, key) {
40653
+ constructor(parent, value, path7, key) {
40530
40654
  this._cachedPath = [];
40531
40655
  this.parent = parent;
40532
40656
  this.data = value;
40533
- this._path = path6;
40657
+ this._path = path7;
40534
40658
  this._key = key;
40535
40659
  }
40536
40660
  get path() {
@@ -44206,6 +44330,22 @@ var CoverLetterRecordSchema = external_exports.object({
44206
44330
  current_content: external_exports.string().nullable(),
44207
44331
  versions: external_exports.array(CoverLetterVersionRecordSchema).optional()
44208
44332
  });
44333
+ var JobQuestionSchema = external_exports.object({
44334
+ no: external_exports.number().int().positive().optional().describe("\uBB38\uD56D \uBC88\uD638(\uC788\uC73C\uBA74)"),
44335
+ question: external_exports.string().max(MAX_BODY).describe("\uBB38\uD56D \uD14D\uC2A4\uD2B8(\uD68C\uC0AC\uAC00 \uC81C\uC2DC\uD55C \uC790\uC18C\uC11C \uD56D\uBAA9)"),
44336
+ min_chars: external_exports.number().int().positive().optional().describe("\uAE00\uC790\uC218 \uD558\uD55C(\uC788\uC73C\uBA74)"),
44337
+ max_chars: external_exports.number().int().positive().optional().describe("\uAE00\uC790\uC218 \uC0C1\uD55C(\uC788\uC73C\uBA74)"),
44338
+ count_mode: external_exports.enum(["with_space", "no_space", "byte"]).optional().describe("\uAE00\uC790\uC218 \uAE30\uC900: with_space=\uACF5\uBC31\uD3EC\uD568(\uAE30\uBCF8)\xB7no_space=\uACF5\uBC31\uC81C\uC678\xB7byte=UTF-8 \uBC14\uC774\uD2B8"),
44339
+ note: optLine.describe('\uBD80\uAC00 \uBA54\uBAA8(\uC608: "\uD544\uC218", "\uACBD\uD5D8 \uC911\uC2EC")')
44340
+ });
44341
+ var SCHEDULE_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
44342
+ var SCHEDULE_TIME_RE = /^([01]\d|2[0-3]):[0-5]\d$/;
44343
+ var JobScheduleItemSchema = external_exports.object({
44344
+ label: reqLine.describe("\uC77C\uC815 \uC774\uB984 (\uC608: 1\uCC28 \uBA74\uC811\xB7AI\uC5ED\uAC80\xB7\uC11C\uB958 \uBC1C\uD45C)"),
44345
+ date: external_exports.string().regex(SCHEDULE_DATE_RE, "YYYY-MM-DD \uD615\uC2DD\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4").describe("\uC77C\uC815 \uB0A0\uC9DC YYYY-MM-DD"),
44346
+ time: external_exports.string().regex(SCHEDULE_TIME_RE, "HH:MM(24\uC2DC\uAC04) \uD615\uC2DD\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4").optional().describe("\uC2DC\uAC01 HH:MM(24\uC2DC\uAC04, \uC120\uD0DD)"),
44347
+ note: optLine.describe("\uBD80\uAC00 \uBA54\uBAA8(\uC608: \uC7A5\uC18C\xB7\uC900\uBE44\uBB3C, \uC120\uD0DD)")
44348
+ });
44209
44349
  var JobInputSchema = external_exports.object({
44210
44350
  company: reqLine,
44211
44351
  position: reqLine.describe("\uC9C1\uBB34/\uD3EC\uC9C0\uC158\uBA85"),
@@ -44219,7 +44359,10 @@ var JobInputSchema = external_exports.object({
44219
44359
  source: external_exports.string().max(MAX_LINE).optional().describe("\uCD9C\uCC98 (\uC0AC\uB78C\uC778/\uC6D0\uD2F0\uB4DC/\uC9C1\uC811 \uC785\uB825 \uB4F1)"),
44220
44360
  company_overview: optBody.describe("\uD68C\uC0AC \uC0AC\uC5C5\uAC1C\uC694\xB7\uBBF8\uC158\xB7\uC8FC\uC694 \uC81C\uD488\xB7\uCD5C\uADFC \uD65C\uB3D9 (AI\uAC00 \uB9AC\uC11C\uCE58\uD574 \uCC44\uC6C0)"),
44221
44361
  talent_profile: optBody.describe("\uD68C\uC0AC\uAC00 \uAC15\uC870\uD558\uB294 \uC778\uC7AC\uC0C1 (\uD55C\uAD6D \uC790\uC18C\uC11C\xB7\uBA74\uC811\uC5D0\uC11C \uC815\uB82C \uAE30\uC900)"),
44222
- core_values: strList.optional().describe("\uD68C\uC0AC \uD575\uC2EC\uAC00\uCE58 (\uD0A4\uC6CC\uB4DC \uBC30\uC5F4)")
44362
+ core_values: strList.optional().describe("\uD68C\uC0AC \uD575\uC2EC\uAC00\uCE58 (\uD0A4\uC6CC\uB4DC \uBC30\uC5F4)"),
44363
+ reputation: optBody.describe("\uD68C\uC0AC \uD3C9\uD310 \uC694\uC57D \u2014 \uD3C9\uC810\xB7\uC6CC\uB77C\uBC38\xB7\uBB38\uD654 \uB9AC\uC2A4\uD06C \uB4F1 (AI\uAC00 company-research \uD50C\uB808\uC774\uBD81\uC73C\uB85C \uB9AC\uC11C\uCE58\uD574 \uCC44\uC6C0; \uCD9C\uCC98\xB7\uCD5C\uADFC\uC131 \uD45C\uAE30)"),
44364
+ questions: external_exports.array(JobQuestionSchema).max(MAX_ITEMS).optional().describe("\uD68C\uC0AC \uC9C0\uC815 \uC790\uC18C\uC11C \uBB38\uD56D\xB7\uAE00\uC790\uC218 \u2014 \uC0AC\uC6A9\uC790\uAC00 \uBB38\uD56D\uC744 \uC8FC\uBA74 \uAD6C\uC870\uD654 \uC800\uC7A5"),
44365
+ schedule: external_exports.array(JobScheduleItemSchema).max(MAX_ITEMS).optional().describe("\uC804\uD615 \uC77C\uC815 \u2014 \uBA74\uC811\uCD08\uB300 \uBA54\uC77C\xB7\uBC1C\uD45C \uACF5\uC9C0\uB97C \uBC1B\uC73C\uBA74 \uAD6C\uC870\uD654 \uC800\uC7A5(label\xB7date \uD544\uC218, time \uC120\uD0DD). \uC11C\uB958 \uB9C8\uAC10\uC740 deadline \uD544\uB4DC\uB97C \uC4F0\uC138\uC694.")
44223
44366
  });
44224
44367
  var JobRecordSchema = external_exports.object({
44225
44368
  ...baseRecord,
@@ -44235,7 +44378,46 @@ var JobRecordSchema = external_exports.object({
44235
44378
  source: external_exports.string().nullable(),
44236
44379
  company_overview: external_exports.string().nullable(),
44237
44380
  talent_profile: external_exports.string().nullable(),
44238
- core_values: external_exports.array(external_exports.string())
44381
+ core_values: external_exports.array(external_exports.string()),
44382
+ reputation: external_exports.string().nullable(),
44383
+ questions: external_exports.array(JobQuestionSchema),
44384
+ schedule: external_exports.array(JobScheduleItemSchema)
44385
+ });
44386
+ var OFFER_STATUSES = ["open", "accepted", "declined"];
44387
+ var OfferInputSchema = external_exports.object({
44388
+ job_id: reqLine,
44389
+ offer_status: external_exports.enum(OFFER_STATUSES).optional().describe("\uC624\uD37C \uC0C1\uD0DC \u2014 open(\uC9C4\uD589 \uC911)/accepted(\uC218\uB77D)/declined(\uAC70\uC808). \uC0DD\uB7B5 \uC2DC \uAE30\uC874 \uAC12 \uC720\uC9C0(\uC2E0\uADDC \uC800\uC7A5\uC740 open)"),
44390
+ base_salary: external_exports.number().nonnegative().optional().describe("\uAE30\uBCF8\uAE09/\uC5F0\uBD09 (\uB9CC\uC6D0)"),
44391
+ bonus_amount: external_exports.number().nonnegative().optional().describe("\uC5F0 \uC131\uACFC\uAE09\xB7\uC0C1\uC5EC \uAE30\uB300\uC561 (\uB9CC\uC6D0)"),
44392
+ welfare_amount: external_exports.number().nonnegative().optional().describe("\uBCF5\uC9C0\uD3EC\uC778\uD2B8 \uB4F1 \uC5F0\uD658\uC0B0 (\uB9CC\uC6D0)"),
44393
+ signing_amount: external_exports.number().nonnegative().optional().describe("\uC0AC\uC774\uB2DD\uBCF4\uB108\uC2A4 \uC77C\uC2DC\uAE08 (\uB9CC\uC6D0)"),
44394
+ equity_note: optNote.describe("\uC8FC\uC2DD/\uC2A4\uD1A1\uC635\uC158 (\uC815\uC131 \uC11C\uC220 \u2014 \uAC00\uCE58\uD3C9\uAC00\uAC00 \uBAA8\uD638\uD558\uBBC0\uB85C \uD14D\uC2A4\uD2B8)"),
44395
+ comp_note: optBody.describe("\uAE30\uD0C0 \uBCF4\uC0C1 \uC790\uC720 \uC11C\uC220"),
44396
+ work_arrangement: optLine.describe("\uADFC\uBB34\uD615\uD0DC (\uC7AC\uD0DD/\uD558\uC774\uBE0C\uB9AC\uB4DC/\uCD9C\uADFC)"),
44397
+ contract_type: optLine.describe("\uACC4\uC57D\uD615\uD0DC (\uC815\uADDC/\uACC4\uC57D \uB4F1)"),
44398
+ accept_deadline: optLine.describe("\uC218\uB77D \uB9C8\uAC10\uC77C YYYY-MM-DD"),
44399
+ ai_score: external_exports.number().min(0).max(100).optional().describe("AI\uAC00 \uB9E4\uAE34 \uC885\uD569 \uC810\uC218 0~100 (\uC800\uC7A5 \uC2DC AI\uAC00 \uCC44\uC6C0)"),
44400
+ verdict: optBody.describe("\uCD5C\uC885\uD3C9 \u2014 \uC5B4\uB5A4 \uC0C1\uD669\uC774\uBA74 \uC5B4\uB514\uAC00 \uC88B\uC740\uC9C0 \uB4F1 (AI\uAC00 \uC800\uC7A5 \uC2DC \uC791\uC131)"),
44401
+ notes: optNote
44402
+ });
44403
+ var OfferRecordSchema = external_exports.object({
44404
+ ...baseRecord,
44405
+ job_id: external_exports.string(),
44406
+ offer_status: external_exports.enum(OFFER_STATUSES),
44407
+ base_salary: external_exports.number().nullable(),
44408
+ bonus_amount: external_exports.number().nullable(),
44409
+ welfare_amount: external_exports.number().nullable(),
44410
+ signing_amount: external_exports.number().nullable(),
44411
+ equity_note: external_exports.string().nullable(),
44412
+ comp_note: external_exports.string().nullable(),
44413
+ work_arrangement: external_exports.string().nullable(),
44414
+ contract_type: external_exports.string().nullable(),
44415
+ accept_deadline: external_exports.string().nullable(),
44416
+ ai_score: external_exports.number().nullable(),
44417
+ verdict: external_exports.string().nullable(),
44418
+ notes: external_exports.string().nullable(),
44419
+ /** Server-computed annual cash estimate = base + bonus + welfare (signing is one-time, excluded). Null if no numeric pieces. */
44420
+ total_comp_annual_est: external_exports.number().nullable()
44239
44421
  });
44240
44422
  var FitAnalysisInputSchema = external_exports.object({
44241
44423
  job_id: reqLine.describe("\uBD84\uC11D \uB300\uC0C1 \uACF5\uACE0 ID"),
@@ -44258,6 +44440,7 @@ var FitAnalysisRecordSchema = external_exports.object({
44258
44440
  missing_keywords: external_exports.array(external_exports.string()),
44259
44441
  recommendations: external_exports.array(external_exports.string())
44260
44442
  });
44443
+ var REJECTED_STAGES = ["document", "assessment", "interview", "final", "other"];
44261
44444
  var ApplicationInputSchema = external_exports.object({
44262
44445
  job_id: reqLine,
44263
44446
  status: external_exports.enum(APPLICATION_STATUSES).optional(),
@@ -44278,7 +44461,10 @@ var ApplicationStatusUpdateSchema = external_exports.object({
44278
44461
  application_id: optLine,
44279
44462
  status: external_exports.enum(APPLICATION_STATUSES),
44280
44463
  note: optNote.describe("\uC0C1\uD0DC \uBCC0\uACBD \uC0AC\uC720/\uBA54\uBAA8"),
44281
- submission: ApplicationSubmissionSchema.optional().describe("\uC9C0\uC6D0 \uC644\uB8CC \uAE30\uB85D\uC5D0 \uB0A8\uAE38 \uC81C\uCD9C \uC790\uB8CC \uC2A4\uB0C5\uC0F7")
44464
+ submission: ApplicationSubmissionSchema.optional().describe("\uC9C0\uC6D0 \uC644\uB8CC \uAE30\uB85D\uC5D0 \uB0A8\uAE38 \uC81C\uCD9C \uC790\uB8CC \uC2A4\uB0C5\uC0F7"),
44465
+ rejected_stage: external_exports.enum(REJECTED_STAGES).optional().describe(
44466
+ "\uD0C8\uB77D \uD68C\uACE0 \u2014 'rejected'\uB85C \uBC14\uAFC0 \uB54C \uC5B4\uB290 \uB2E8\uACC4\uC5D0\uC11C \uB5A8\uC5B4\uC84C\uB294\uC9C0: \uC11C\uB958=document, \uC778\uC801\uC131\xB7AI\uC5ED\uAC80=assessment, \uBA74\uC811=interview, \uCD5C\uC885\xB7\uCC98\uC6B0=final, \uAE30\uD0C0=other. rejected\uAC00 \uC544\uB2CC \uC0C1\uD0DC\uC5D0\uB294 \uC800\uC7A5\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4."
44467
+ )
44282
44468
  });
44283
44469
  var ApplicationRecordSchema = external_exports.object({
44284
44470
  ...baseRecord,
@@ -44287,7 +44473,8 @@ var ApplicationRecordSchema = external_exports.object({
44287
44473
  resume_id: external_exports.string().nullable(),
44288
44474
  cover_letter_id: external_exports.string().nullable(),
44289
44475
  applied_at: external_exports.string().nullable(),
44290
- notes: external_exports.string().nullable()
44476
+ notes: external_exports.string().nullable(),
44477
+ rejected_stage: external_exports.enum(REJECTED_STAGES).nullable()
44291
44478
  });
44292
44479
  var StarGuideSchema = external_exports.object({
44293
44480
  question: external_exports.string().max(MAX_NOTE),
@@ -44391,7 +44578,7 @@ var ApplicationContextSchema = external_exports.object({
44391
44578
  });
44392
44579
 
44393
44580
  // packages/shared/src/build-info.ts
44394
- var APP_VERSION = true ? "0.0.8" : "0.0.0-dev";
44581
+ var APP_VERSION = true ? "0.0.9" : "0.0.0-dev";
44395
44582
  var BUNDLED = true;
44396
44583
 
44397
44584
  // packages/shared/src/index.ts
@@ -44939,6 +45126,9 @@ function mapJob(r) {
44939
45126
  company_overview: r.company_overview ?? null,
44940
45127
  talent_profile: r.talent_profile ?? null,
44941
45128
  core_values: fromJson(r.core_values, []),
45129
+ reputation: r.reputation ?? null,
45130
+ questions: fromJson(r.questions, []),
45131
+ schedule: fromJson(r.schedule, []),
44942
45132
  created_at: r.created_at,
44943
45133
  updated_at: r.updated_at
44944
45134
  };
@@ -44968,7 +45158,7 @@ var jobRepo = {
44968
45158
  if (existing) {
44969
45159
  const m = { ...existing, ...input };
44970
45160
  db.prepare(
44971
- `UPDATE jobs SET company=?,position=?,url=?,location=?,employment_type=?,description=?,requirements=?,keywords=?,deadline=?,source=?,company_overview=?,talent_profile=?,core_values=?,updated_at=? WHERE id=?`
45161
+ `UPDATE jobs SET company=?,position=?,url=?,location=?,employment_type=?,description=?,requirements=?,keywords=?,deadline=?,source=?,company_overview=?,talent_profile=?,core_values=?,reputation=?,questions=?,schedule=?,updated_at=? WHERE id=?`
44972
45162
  ).run(
44973
45163
  m.company,
44974
45164
  m.position,
@@ -44983,6 +45173,12 @@ var jobRepo = {
44983
45173
  m.company_overview ?? null,
44984
45174
  m.talent_profile ?? null,
44985
45175
  toJson(m.core_values ?? []),
45176
+ m.reputation ?? null,
45177
+ // questions omitted from input → m keeps existing.questions (preserved);
45178
+ // provided → replaced. Same merge semantics as the other JSON fields.
45179
+ toJson(m.questions ?? []),
45180
+ // schedule follows the same preserve-on-omit merge (v11 전형 일정).
45181
+ toJson(m.schedule ?? []),
44986
45182
  ts,
44987
45183
  existing.id
44988
45184
  );
@@ -44990,8 +45186,8 @@ var jobRepo = {
44990
45186
  }
44991
45187
  const newJobId = id3 ?? newId("job_");
44992
45188
  db.prepare(
44993
- `INSERT INTO jobs (id,company,position,url,location,employment_type,description,requirements,keywords,deadline,source,company_overview,talent_profile,core_values,created_at,updated_at)
44994
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`
45189
+ `INSERT INTO jobs (id,company,position,url,location,employment_type,description,requirements,keywords,deadline,source,company_overview,talent_profile,core_values,reputation,questions,schedule,created_at,updated_at)
45190
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`
44995
45191
  ).run(
44996
45192
  newJobId,
44997
45193
  input.company,
@@ -45007,6 +45203,9 @@ var jobRepo = {
45007
45203
  input.company_overview ?? null,
45008
45204
  input.talent_profile ?? null,
45009
45205
  toJson(input.core_values ?? []),
45206
+ input.reputation ?? null,
45207
+ toJson(input.questions ?? []),
45208
+ toJson(input.schedule ?? []),
45010
45209
  ts,
45011
45210
  ts
45012
45211
  );
@@ -45020,6 +45219,7 @@ var jobRepo = {
45020
45219
  db.prepare(`DELETE FROM applications WHERE job_id=?`).run(id3);
45021
45220
  db.prepare(`DELETE FROM interview_preps WHERE job_id=?`).run(id3);
45022
45221
  db.prepare(`DELETE FROM application_timeline_events WHERE job_id=?`).run(id3);
45222
+ db.prepare(`DELETE FROM offers WHERE job_id=?`).run(id3);
45023
45223
  return db.prepare(`DELETE FROM jobs WHERE id=?`).run(id3).changes > 0;
45024
45224
  });
45025
45225
  }
@@ -45094,6 +45294,8 @@ function mapApplication(r) {
45094
45294
  cover_letter_id: r.cover_letter_id,
45095
45295
  applied_at: r.applied_at,
45096
45296
  notes: r.notes,
45297
+ // 탈락 회고: 탈락이 아니거나 미기록이면 NULL(v10 이전 DB는 컬럼이 없어 undefined → null 정규화).
45298
+ rejected_stage: r.rejected_stage ?? null,
45097
45299
  created_at: r.created_at,
45098
45300
  updated_at: r.updated_at
45099
45301
  };
@@ -45130,17 +45332,19 @@ var applicationRepo = {
45130
45332
  const app = this.ensure(input.job_id, input.status ?? "draft");
45131
45333
  if (input.status && input.status !== app.status) assertStatusTransition(app.status, input.status);
45132
45334
  const m = { ...app, ...input };
45335
+ const rejectedStageValue = m.status === "rejected" ? m.rejected_stage ?? null : null;
45133
45336
  getDb().prepare(
45134
- `UPDATE applications SET status=?,resume_id=?,cover_letter_id=?,applied_at=?,notes=?,updated_at=? WHERE id=?`
45135
- ).run(m.status, m.resume_id ?? null, m.cover_letter_id ?? null, m.applied_at ?? null, m.notes ?? null, now(), app.id);
45337
+ `UPDATE applications SET status=?,resume_id=?,cover_letter_id=?,applied_at=?,notes=?,rejected_stage=?,updated_at=? WHERE id=?`
45338
+ ).run(m.status, m.resume_id ?? null, m.cover_letter_id ?? null, m.applied_at ?? null, m.notes ?? null, rejectedStageValue, now(), app.id);
45136
45339
  return this.get(app.id);
45137
45340
  },
45138
- setStatus(jobId, status, note) {
45341
+ setStatus(jobId, status, note, rejectedStage) {
45139
45342
  const app = this.ensure(jobId);
45140
45343
  assertStatusTransition(app.status, status);
45141
45344
  const appliedAt = status === "applied" && !app.applied_at ? now() : app.applied_at;
45142
45345
  const notes = note ? [app.notes, note].filter(Boolean).join("\n") : app.notes;
45143
- getDb().prepare(`UPDATE applications SET status=?, applied_at=?, notes=?, updated_at=? WHERE id=?`).run(status, appliedAt, notes, now(), app.id);
45346
+ const rejectedStageValue = status === "rejected" ? rejectedStage ?? app.rejected_stage ?? null : null;
45347
+ getDb().prepare(`UPDATE applications SET status=?, applied_at=?, notes=?, rejected_stage=?, updated_at=? WHERE id=?`).run(status, appliedAt, notes, rejectedStageValue, now(), app.id);
45144
45348
  return this.get(app.id);
45145
45349
  }
45146
45350
  };
@@ -45260,6 +45464,96 @@ var timelineRepo = {
45260
45464
  return getDb().prepare(`DELETE FROM application_timeline_events WHERE id=?`).run(id3).changes > 0;
45261
45465
  }
45262
45466
  };
45467
+ function mapOffer(r) {
45468
+ const base = r.base_salary ?? null;
45469
+ const bonus = r.bonus_amount ?? null;
45470
+ const welfare = r.welfare_amount ?? null;
45471
+ const hasAny = base != null || bonus != null || welfare != null;
45472
+ const total = hasAny ? (base ?? 0) + (bonus ?? 0) + (welfare ?? 0) : null;
45473
+ return {
45474
+ id: r.id,
45475
+ job_id: r.job_id,
45476
+ offer_status: r.offer_status ?? "open",
45477
+ base_salary: base,
45478
+ bonus_amount: bonus,
45479
+ welfare_amount: welfare,
45480
+ signing_amount: r.signing_amount ?? null,
45481
+ equity_note: r.equity_note ?? null,
45482
+ comp_note: r.comp_note ?? null,
45483
+ work_arrangement: r.work_arrangement ?? null,
45484
+ contract_type: r.contract_type ?? null,
45485
+ accept_deadline: r.accept_deadline ?? null,
45486
+ ai_score: r.ai_score ?? null,
45487
+ verdict: r.verdict ?? null,
45488
+ notes: r.notes ?? null,
45489
+ total_comp_annual_est: total,
45490
+ created_at: r.created_at,
45491
+ updated_at: r.updated_at
45492
+ };
45493
+ }
45494
+ var offerRepo = {
45495
+ getByJob(jobId) {
45496
+ const r = getDb().prepare(`SELECT * FROM offers WHERE job_id=?`).get(jobId);
45497
+ return r ? mapOffer(r) : null;
45498
+ },
45499
+ list() {
45500
+ return getDb().prepare(`SELECT * FROM offers ORDER BY ai_score IS NULL, ai_score DESC, updated_at DESC`).all().map(mapOffer);
45501
+ },
45502
+ /** Upsert by job_id (one offer per job). Missing fields are preserved on update (partial-safe). */
45503
+ save(input) {
45504
+ const db = getDb();
45505
+ const existing = this.getByJob(input.job_id);
45506
+ const ts = now();
45507
+ const n = (v, prev) => v ?? prev ?? null;
45508
+ const s = (v, prev) => v ?? prev ?? null;
45509
+ if (existing) {
45510
+ db.prepare(
45511
+ `UPDATE offers SET offer_status=?,base_salary=?,bonus_amount=?,welfare_amount=?,signing_amount=?,equity_note=?,comp_note=?,work_arrangement=?,contract_type=?,accept_deadline=?,ai_score=?,verdict=?,notes=?,updated_at=? WHERE id=?`
45512
+ ).run(
45513
+ input.offer_status ?? existing.offer_status,
45514
+ n(input.base_salary, existing.base_salary),
45515
+ n(input.bonus_amount, existing.bonus_amount),
45516
+ n(input.welfare_amount, existing.welfare_amount),
45517
+ n(input.signing_amount, existing.signing_amount),
45518
+ s(input.equity_note, existing.equity_note),
45519
+ s(input.comp_note, existing.comp_note),
45520
+ s(input.work_arrangement, existing.work_arrangement),
45521
+ s(input.contract_type, existing.contract_type),
45522
+ s(input.accept_deadline, existing.accept_deadline),
45523
+ n(input.ai_score, existing.ai_score),
45524
+ s(input.verdict, existing.verdict),
45525
+ s(input.notes, existing.notes),
45526
+ ts,
45527
+ existing.id
45528
+ );
45529
+ return this.getByJob(input.job_id);
45530
+ }
45531
+ const id3 = newId("ofr_");
45532
+ db.prepare(
45533
+ `INSERT INTO offers (id,job_id,offer_status,base_salary,bonus_amount,welfare_amount,signing_amount,equity_note,comp_note,work_arrangement,contract_type,accept_deadline,ai_score,verdict,notes,created_at,updated_at)
45534
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`
45535
+ ).run(
45536
+ id3,
45537
+ input.job_id,
45538
+ input.offer_status ?? "open",
45539
+ input.base_salary ?? null,
45540
+ input.bonus_amount ?? null,
45541
+ input.welfare_amount ?? null,
45542
+ input.signing_amount ?? null,
45543
+ input.equity_note ?? null,
45544
+ input.comp_note ?? null,
45545
+ input.work_arrangement ?? null,
45546
+ input.contract_type ?? null,
45547
+ input.accept_deadline ?? null,
45548
+ input.ai_score ?? null,
45549
+ input.verdict ?? null,
45550
+ input.notes ?? null,
45551
+ ts,
45552
+ ts
45553
+ );
45554
+ return this.getByJob(input.job_id);
45555
+ }
45556
+ };
45263
45557
  function getEntityCounts() {
45264
45558
  const db = getDb();
45265
45559
  const ALLOWED_TABLES = /* @__PURE__ */ new Set([
@@ -45272,7 +45566,8 @@ function getEntityCounts() {
45272
45566
  "jobs",
45273
45567
  "applications",
45274
45568
  "interview_preps",
45275
- "application_timeline_events"
45569
+ "application_timeline_events",
45570
+ "offers"
45276
45571
  ]);
45277
45572
  const c = (t) => {
45278
45573
  if (!ALLOWED_TABLES.has(t)) throw new Error(`getEntityCounts: \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uC740 \uD14C\uC774\uBE14 \uC774\uB984: ${t}`);
@@ -45288,7 +45583,8 @@ function getEntityCounts() {
45288
45583
  jobs: c("jobs"),
45289
45584
  applications: c("applications"),
45290
45585
  interview_preps: c("interview_preps"),
45291
- application_timeline_events: c("application_timeline_events")
45586
+ application_timeline_events: c("application_timeline_events"),
45587
+ offers: c("offers")
45292
45588
  };
45293
45589
  }
45294
45590
 
@@ -45352,15 +45648,15 @@ function injectToken(html) {
45352
45648
  }
45353
45649
 
45354
45650
  // apps/web/src/http.ts
45355
- import fs3 from "node:fs";
45356
- import path3 from "node:path";
45651
+ import fs4 from "node:fs";
45652
+ import path4 from "node:path";
45357
45653
  var MAX_BODY2 = 32 * 1024 * 1024;
45358
45654
  var Router = class {
45359
45655
  routes = [];
45360
- add(method, path6, handler) {
45656
+ add(method, path7, handler) {
45361
45657
  const keys2 = [];
45362
45658
  const pattern = new RegExp(
45363
- "^" + path6.replace(/:[^/]+/g, (m) => {
45659
+ "^" + path7.replace(/:[^/]+/g, (m) => {
45364
45660
  keys2.push(m.slice(1));
45365
45661
  return "([^/]+)";
45366
45662
  }) + "/?$"
@@ -45475,15 +45771,15 @@ var MIME = {
45475
45771
  };
45476
45772
  function serveStatic(res, root2, relPath, transform) {
45477
45773
  const cleaned = decodeURIComponent(relPath).replace(/\0/g, "");
45478
- const resolved = path3.resolve(root2, "." + path3.posix.normalize("/" + cleaned));
45479
- if (resolved !== root2 && !resolved.startsWith(root2 + path3.sep)) {
45774
+ const resolved = path4.resolve(root2, "." + path4.posix.normalize("/" + cleaned));
45775
+ if (resolved !== root2 && !resolved.startsWith(root2 + path4.sep)) {
45480
45776
  res.writeHead(403).end("Forbidden");
45481
45777
  return true;
45482
45778
  }
45483
- if (!fs3.existsSync(resolved) || !fs3.statSync(resolved).isFile()) return false;
45484
- const ext = path3.extname(resolved).toLowerCase();
45779
+ if (!fs4.existsSync(resolved) || !fs4.statSync(resolved).isFile()) return false;
45780
+ const ext = path4.extname(resolved).toLowerCase();
45485
45781
  const mime = MIME[ext] ?? "application/octet-stream";
45486
- let payload = fs3.readFileSync(resolved);
45782
+ let payload = fs4.readFileSync(resolved);
45487
45783
  if (transform && ext === ".html") payload = transform(payload.toString("utf8"));
45488
45784
  const headers = {
45489
45785
  "Content-Type": mime,
@@ -46075,12 +46371,12 @@ function saveCoverLetterVersion(input) {
46075
46371
  }
46076
46372
  return { coverLetter, version, verification, forced };
46077
46373
  }
46078
- function updateApplicationStatus(jobId, status, note, submission) {
46374
+ function updateApplicationStatus(jobId, status, note, submission, rejectedStage) {
46079
46375
  const job = jobRepo.get(jobId);
46080
46376
  if (!job) throw notFound("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
46081
46377
  const before2 = applicationRepo.getByJob(jobId);
46082
46378
  const application = tx(() => {
46083
- let application2 = applicationRepo.setStatus(jobId, status, note);
46379
+ let application2 = applicationRepo.setStatus(jobId, status, note, rejectedStage);
46084
46380
  const submittedDocs = (submission?.document_ids ?? []).map((docId) => documentRepo.get(docId)).filter((doc) => !!doc);
46085
46381
  const submittedCoverLetter = submission?.cover_letter_id ? coverLetterRepo.get(submission.cover_letter_id, true) : null;
46086
46382
  if (status === "applied" && submission) {
@@ -46116,7 +46412,9 @@ function updateApplicationStatus(jobId, status, note, submission) {
46116
46412
  status,
46117
46413
  status_label: APPLICATION_STATUS_LABELS[status],
46118
46414
  note: note ?? null,
46119
- submission: submissionPayload
46415
+ submission: submissionPayload,
46416
+ // 탈락 회고: rejected로 넘어갈 때 실제 저장된 단계(setStatus가 정합성 처리한 값)를 타임라인에도 남긴다.
46417
+ rejected_stage: application2.rejected_stage
46120
46418
  },
46121
46419
  occurred_at: submittedAtToOccurrence(submittedAt)
46122
46420
  });
@@ -46150,8 +46448,21 @@ function saveInterviewPrep(input) {
46150
46448
  var ACTIVE = ["draft", "planned", "applied", "document_passed", "interview"];
46151
46449
  var DEADLINE_ACTIONABLE = ["draft", "planned"];
46152
46450
  var DEADLINE_SOON_DAYS = 14;
46153
- var FOLLOWUP_STALE_DAYS = 10;
46451
+ var SCHEDULE_SOON_DAYS = 7;
46154
46452
  var DAY_MS = 864e5;
46453
+ function collectFutureSchedule(jobs, now2) {
46454
+ const items = [];
46455
+ for (const job of jobs) {
46456
+ for (const s of job.schedule ?? []) {
46457
+ const d_day = daysUntilDate(s.date, now2);
46458
+ if (d_day == null || d_day < 0) continue;
46459
+ items.push({ job, label: s.label, date: s.date, time: s.time, d_day });
46460
+ }
46461
+ }
46462
+ return items.sort(
46463
+ (a, b) => a.date < b.date ? -1 : a.date > b.date ? 1 : (a.time || "99:99").localeCompare(b.time || "99:99")
46464
+ );
46465
+ }
46155
46466
  function daysUntilDate(dateStr, now2) {
46156
46467
  if (!dateStr) return null;
46157
46468
  const d = /* @__PURE__ */ new Date(`${dateStr}T00:00:00`);
@@ -46166,6 +46477,24 @@ function daysSinceTimestamp(iso, now2) {
46166
46477
  if (isNaN(d.getTime())) return null;
46167
46478
  return Math.floor((now2.getTime() - d.getTime()) / DAY_MS);
46168
46479
  }
46480
+ function businessDaysSince(iso, now2) {
46481
+ if (!iso) return null;
46482
+ const start = new Date(iso);
46483
+ if (isNaN(start.getTime())) return null;
46484
+ const s = new Date(start);
46485
+ s.setHours(0, 0, 0, 0);
46486
+ const e = new Date(now2);
46487
+ e.setHours(0, 0, 0, 0);
46488
+ if (e.getTime() <= s.getTime()) return 0;
46489
+ let count = 0;
46490
+ const cursor = new Date(s);
46491
+ while (cursor.getTime() < e.getTime()) {
46492
+ cursor.setDate(cursor.getDate() + 1);
46493
+ const dow = cursor.getDay();
46494
+ if (dow !== 0 && dow !== 6) count++;
46495
+ }
46496
+ return count;
46497
+ }
46169
46498
  function jobWithMeta(job) {
46170
46499
  const app = applicationRepo.getByJob(job.id);
46171
46500
  const fit = fitRepo.getByJob(job.id);
@@ -46178,14 +46507,16 @@ function getHomeSummary(now2 = /* @__PURE__ */ new Date()) {
46178
46507
  const applications = applicationRepo.list();
46179
46508
  const coverLetters = coverLetterRepo.list();
46180
46509
  const statusByJob = new Map(applications.map((a) => [a.job_id, a]));
46510
+ const jobById = new Map(jobs.map((j) => [j.id, j]));
46511
+ const preppedJobIds = new Set(interviewRepo.list().map((p) => p.job_id));
46181
46512
  const status_breakdown = Object.keys(APPLICATION_STATUS_LABELS).map((status) => ({
46182
46513
  status,
46183
46514
  label: APPLICATION_STATUS_LABELS[status],
46184
46515
  count: applications.filter((a) => a.status === status).length
46185
46516
  }));
46186
46517
  const active = applications.filter((a) => ACTIVE.includes(a.status));
46187
- const interview_todo = applications.filter((a) => INTERVIEW_UNLOCK_STATUSES.includes(a.status) && !interviewRepo.getByJob(a.job_id)).map((a) => {
46188
- const job = jobRepo.get(a.job_id);
46518
+ const interview_todo = applications.filter((a) => INTERVIEW_UNLOCK_STATUSES.includes(a.status) && !preppedJobIds.has(a.job_id)).map((a) => {
46519
+ const job = jobById.get(a.job_id);
46189
46520
  return job ? { job, status_label: APPLICATION_STATUS_LABELS[a.status] } : null;
46190
46521
  }).filter((x) => x !== null);
46191
46522
  const deadlines = jobs.map((job) => {
@@ -46196,11 +46527,13 @@ function getHomeSummary(now2 = /* @__PURE__ */ new Date()) {
46196
46527
  return { job, status, status_label: APPLICATION_STATUS_LABELS[status], days_left };
46197
46528
  }).filter((x) => x !== null).sort((a, b) => a.days_left - b.days_left).slice(0, 5);
46198
46529
  const followups = applications.filter((a) => a.status === "applied").map((a) => {
46199
- const days_since = daysSinceTimestamp(a.applied_at, now2);
46200
- if (days_since == null || days_since < FOLLOWUP_STALE_DAYS) return null;
46201
- const job = jobRepo.get(a.job_id);
46530
+ const biz = businessDaysSince(a.applied_at, now2);
46531
+ if (biz == null || biz < FOLLOWUP_DUE_BIZ_DAYS) return null;
46532
+ const days_since = daysSinceTimestamp(a.applied_at, now2) ?? 0;
46533
+ const job = jobById.get(a.job_id);
46202
46534
  return job ? { job, days_since } : null;
46203
46535
  }).filter((x) => x !== null).sort((a, b) => b.days_since - a.days_since).slice(0, 5);
46536
+ const upcoming_schedule = collectFutureSchedule(jobs, now2).filter((x) => x.d_day <= SCHEDULE_SOON_DAYS).map((x) => ({ job: x.job, label: x.label, date: x.date, ...x.time ? { time: x.time } : {}, d_day: x.d_day })).slice(0, 6);
46204
46537
  return {
46205
46538
  onboarding,
46206
46539
  counts: {
@@ -46212,7 +46545,7 @@ function getHomeSummary(now2 = /* @__PURE__ */ new Date()) {
46212
46545
  status_breakdown,
46213
46546
  recent_jobs: jobs.slice(0, 6).map(jobWithMeta),
46214
46547
  in_progress: active.slice(0, 6).map((application) => {
46215
- const job = jobRepo.get(application.job_id);
46548
+ const job = jobById.get(application.job_id) ?? null;
46216
46549
  return {
46217
46550
  application,
46218
46551
  job,
@@ -46223,15 +46556,17 @@ function getHomeSummary(now2 = /* @__PURE__ */ new Date()) {
46223
46556
  interview_todo,
46224
46557
  deadlines,
46225
46558
  followups,
46559
+ upcoming_schedule,
46226
46560
  recent_activity: activityRepo.recent(10)
46227
46561
  };
46228
46562
  }
46229
46563
  function listRecentActivity(limit = 20) {
46230
46564
  return activityRepo.recent(limit);
46231
46565
  }
46566
+ var FOLLOWUP_DUE_BIZ_DAYS = 5;
46232
46567
 
46233
46568
  // packages/core/src/jobsearch.ts
46234
- var UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36";
46569
+ var UA = `careermate/${APP_VERSION} (+https://careermate.life)`;
46235
46570
  function toYmd(v) {
46236
46571
  if (typeof v !== "string" || v.length < 10) return void 0;
46237
46572
  const m = v.match(/^(\d{4}-\d{2}-\d{2})/);
@@ -46296,6 +46631,14 @@ var jumpit = {
46296
46631
  var PROVIDERS = [wanted, jumpit];
46297
46632
  var JOB_SEARCH_SOURCES = PROVIDERS.map((p) => p.id);
46298
46633
 
46634
+ // packages/core/src/offers.ts
46635
+ function withJob(offer, job) {
46636
+ return { ...offer, company: job?.company ?? "\u2014", position: job?.position ?? "\u2014" };
46637
+ }
46638
+ function compareOffers() {
46639
+ return offerRepo.list().map((o) => withJob(o, jobRepo.get(o.job_id)));
46640
+ }
46641
+
46299
46642
  // packages/prompts/src/system.ts
46300
46643
  var CAREERMATE_SYSTEM_PROMPT = `\uB2F9\uC2E0\uC740 \uC0AC\uC6A9\uC790\uC758 'CareerMate' \uCEE4\uB9AC\uC5B4 \uBE44\uC11C\uC785\uB2C8\uB2E4.
46301
46644
 
@@ -46446,12 +46789,15 @@ var HUMANIZE_WRITING_GUIDE = `# \uC0AC\uB78C\uC774 \uC4F4 \uAC83\uCC98\uB7FC: \u
46446
46789
  3. \uAE30\uACC4\uC801 \uBCD1\uB82C\uAD6C\uC870 \u2014 \uC138 \uAC1C\uC529 \uB098\uC5F4(rule of three), \uBAA8\uB4E0 \uBB38\uB2E8\uC774 \uB611\uAC19\uC740 \uACE8\uACA9.
46447
46790
  - "\uCCAB\uC9F8\u2026\uB458\uC9F8\u2026\uC14B\uC9F8", "A\uD558\uACE0 B\uD558\uBA70 C\uD558\uB294" \uAC19\uC740 \uADE0\uC77C\uD55C \uB098\uC5F4\uC744 \uBC18\uBCF5\uD558\uC9C0 \uC54A\uB294\uB2E4.
46448
46791
  - \uBB38\uB2E8\uB9C8\uB2E4 \uAE38\uC774\uC640 \uC2DC\uC791 \uBC29\uC2DD\uC744 \uC77C\uBD80\uB7EC \uB2E4\uB974\uAC8C \uD55C\uB2E4.
46792
+ - \uAC19\uC740 \uCE90\uCE58\uD504\uB808\uC774\uC988\xB7\uBAA8\uD2F0\uD504(\uC608: "1\uC6D0\uB3C4 \uD2C0\uB9AC\uC9C0 \uC54A\uB294")\uB97C \uBB38\uC11C \uC804\uCCB4\uC5D0\uC11C **\uCD5C\uB300 2\uD68C**\uAE4C\uC9C0\uB9CC \uC4F4\uB2E4. 3\uD68C\uBD80\uD130\uB294 \uC778\uC704\uC801 \uC7A5\uCE58\uB85C \uC77D\uD600 \uC624\uD788\uB824 \uC0AC\uB78C \uB0C4\uC0C8\uB97C \uC9C0\uC6B4\uB2E4 \u2014 \uBC18\uBCF5\uD558\uACE0 \uC2F6\uC73C\uBA74 \uB9E4\uBC88 \uB2E4\uB978 \uB9D0\uB85C \uBC14\uAFD4 \uD45C\uD604\uD55C\uB2E4.
46449
46793
 
46450
46794
  4. \uC0C1\uD22C\uC801 \uC5F0\uACB0\uC5B4 \uB0A8\uBC1C \u2014 "\uB610\uD55C", "\uBFD0\uB9CC \uC544\uB2C8\uB77C", "\uB354 \uB098\uC544\uAC00", "\uC774\uB97C \uD1B5\uD574", "\uACB0\uB860\uC801\uC73C\uB85C".
46451
46795
  - \uC811\uC18D\uC5B4 \uC5C6\uC774 \uBB38\uC7A5\uC744 \uC787\uAC70\uB098, \uC0AC\uAC74\uC758 \uD750\uB984 \uC790\uCCB4\uB85C \uC5F0\uACB0\uD55C\uB2E4.
46452
46796
 
46453
46797
  5. \uADE0\uC77C\uD55C \uBB38\uC7A5 \uB9AC\uB4EC \u2014 \uBAA8\uB4E0 \uBB38\uC7A5\uC774 \uBE44\uC2B7\uD55C \uAE38\uC774.
46454
46798
  - \uC9E7\uC740 \uBB38\uC7A5\uACFC \uAE34 \uBB38\uC7A5\uC744 \uC11E\uB294\uB2E4. \uAC00\uB054 \uC544\uC8FC \uC9E7\uAC8C \uB04A\uB294\uB2E4. \uC774\uB807\uAC8C.
46799
+ - \uBE44\uC2B7\uD55C \uAE38\uC774\uC758 \uBB38\uC7A5\uC744 \uC138 \uAC1C \uC5F0\uC18D\uC73C\uB85C \uC4F0\uC9C0 \uC54A\uB294\uB2E4. \uD558\uB098\uB294 \uAE38\uAC8C \uD480\uACE0 \uB2E4\uC74C\uC740 \uD22D \uB04A\uB294 \uC2DD\uC73C\uB85C \uD638\uD761\uC744 \uBC14\uAFBC\uB2E4.
46800
+ - \uBB38\uB2E8\uB9C8\uB2E4 \uD55C \uD638\uD761\uC5D0 \uC77D\uD788\uB294 \uC9E7\uC740 \uB2E8\uD0C0 \uBB38\uC7A5(8\uB2E8\uC5B4 \uC548\uD30E \uC774\uD558)\uC744 \uCD5C\uC18C \uD558\uB098 \uB123\uB294\uB2E4.
46455
46801
 
46456
46802
  6. \uACFC\uC7A5\uB41C \uC218\uC2DD\xB7\uC218\uB3D9\uD0DC \u2014 "\uB9E4\uC6B0", "\uC815\uB9D0\uB85C", "\uD0C1\uC6D4\uD55C", "\uC131\uACF5\uC801\uC73C\uB85C", "~\uB418\uC5B4\uC84C\uC2B5\uB2C8\uB2E4".
46457
46803
  - \uBD80\uC0AC\uB97C \uBE7C\uACE0 \uB3D9\uC0AC\xB7\uBA85\uC0AC\uB97C \uAD6C\uCCB4\uD654\uD55C\uB2E4. \uB2A5\uB3D9\uD0DC\uB97C \uAE30\uBCF8\uC73C\uB85C \uD55C\uB2E4.
@@ -64434,16 +64780,16 @@ var replacer = ({ json, patch, patchText, context, keepOriginalStyles = true })
64434
64780
  didFindOccurrence: true
64435
64781
  };
64436
64782
  };
64437
- var goToElementFromPath = (json, path6) => {
64783
+ var goToElementFromPath = (json, path7) => {
64438
64784
  let element = json;
64439
- for (let i = 1; i < path6.length; i++) {
64440
- const index = path6[i];
64785
+ for (let i = 1; i < path7.length; i++) {
64786
+ const index = path7[i];
64441
64787
  element = element.elements[index];
64442
64788
  }
64443
64789
  return element;
64444
64790
  };
64445
- var goToParentElementFromPath = (json, path6) => goToElementFromPath(json, path6.slice(0, path6.length - 1));
64446
- var getLastElementIndexFromPath = (path6) => path6[path6.length - 1];
64791
+ var goToParentElementFromPath = (json, path7) => goToElementFromPath(json, path7.slice(0, path7.length - 1));
64792
+ var getLastElementIndexFromPath = (path7) => path7[path7.length - 1];
64447
64793
  var PatchType = {
64448
64794
  /** Replace entire file-level elements (e.g., whole paragraphs) */
64449
64795
  DOCUMENT: "file",
@@ -65046,12 +65392,12 @@ function getServerInfo() {
65046
65392
  }
65047
65393
 
65048
65394
  // apps/web/src/settings.ts
65049
- import fs4 from "node:fs";
65395
+ import fs5 from "node:fs";
65050
65396
  import os2 from "node:os";
65051
- import path4 from "node:path";
65397
+ import path5 from "node:path";
65052
65398
  import { spawn } from "node:child_process";
65053
65399
  import { fileURLToPath } from "node:url";
65054
- var __dirname = path4.dirname(fileURLToPath(import.meta.url));
65400
+ var __dirname = path5.dirname(fileURLToPath(import.meta.url));
65055
65401
  function listTables(db) {
65056
65402
  return db.prepare(
65057
65403
  `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name != '_meta' ORDER BY name`
@@ -65092,14 +65438,14 @@ function isRecord(v) {
65092
65438
  return typeof v === "object" && v !== null && !Array.isArray(v);
65093
65439
  }
65094
65440
  function appRoot() {
65095
- return BUNDLED ? __dirname : path4.resolve(__dirname, "..", "..", "..");
65441
+ return BUNDLED ? __dirname : path5.resolve(__dirname, "..", "..", "..");
65096
65442
  }
65097
65443
  function shortcutFolder() {
65098
65444
  const override = process.env.CAREERMATE_SHORTCUT_DIR?.trim();
65099
65445
  if (override) return override;
65100
- const desktop = path4.join(os2.homedir(), "Desktop");
65101
- const parent = fs4.existsSync(desktop) ? desktop : getExportsDir();
65102
- return path4.join(parent, "CareerMate \uB300\uC2DC\uBCF4\uB4DC");
65446
+ const desktop = path5.join(os2.homedir(), "Desktop");
65447
+ const parent = fs5.existsSync(desktop) ? desktop : getExportsDir();
65448
+ return path5.join(parent, "CareerMate \uB300\uC2DC\uBCF4\uB4DC");
65103
65449
  }
65104
65450
  function openFolder(dir) {
65105
65451
  try {
@@ -65168,8 +65514,8 @@ function macLauncherScript() {
65168
65514
  const root2 = appRoot();
65169
65515
  const dataDir = getDataDir();
65170
65516
  const nodePath = process.execPath;
65171
- const binPath = path4.join(root2, "bin", "careermate.mjs");
65172
- const bundledWeb = BUNDLED ? path4.join(root2, "web.mjs") : path4.join(root2, "dist", "web.mjs");
65517
+ const binPath = path5.join(root2, "bin", "careermate.mjs");
65518
+ const bundledWeb = BUNDLED ? path5.join(root2, "web.mjs") : path5.join(root2, "dist", "web.mjs");
65173
65519
  return `#!/bin/zsh
65174
65520
  set -e
65175
65521
 
@@ -65216,8 +65562,8 @@ function windowsLauncherScript() {
65216
65562
  const root2 = appRoot();
65217
65563
  const dataDir = getDataDir();
65218
65564
  const nodePath = process.execPath;
65219
- const binPath = path4.join(root2, "bin", "careermate.mjs");
65220
- const bundledWeb = BUNDLED ? path4.join(root2, "web.mjs") : path4.join(root2, "dist", "web.mjs");
65565
+ const binPath = path5.join(root2, "bin", "careermate.mjs");
65566
+ const bundledWeb = BUNDLED ? path5.join(root2, "web.mjs") : path5.join(root2, "dist", "web.mjs");
65221
65567
  return `@echo off
65222
65568
  setlocal
65223
65569
  set "CAREERMATE_DATA_DIR=${cmdQuote(dataDir)}"
@@ -65332,45 +65678,43 @@ function restoreBackup(raw) {
65332
65678
  function createBackup() {
65333
65679
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
65334
65680
  const dir = getBackupsDir();
65335
- const dbBackup = path4.join(dir, `careermate-${stamp}.sqlite`);
65336
- const jsonBackup = path4.join(dir, `careermate-${stamp}.json`);
65337
- const resolvedDir = path4.resolve(dir);
65338
- if (!path4.resolve(dbBackup).startsWith(resolvedDir + path4.sep)) {
65681
+ const dbBackup = path5.join(dir, `careermate-${stamp}.sqlite`);
65682
+ const jsonBackup = path5.join(dir, `careermate-${stamp}.json`);
65683
+ const resolvedDir = path5.resolve(dir);
65684
+ if (!path5.resolve(dbBackup).startsWith(resolvedDir + path5.sep)) {
65339
65685
  throw new Error("\uBC31\uC5C5 \uACBD\uB85C\uAC00 \uBC31\uC5C5 \uD3F4\uB354\uB97C \uBC97\uC5B4\uB0AC\uC2B5\uB2C8\uB2E4.");
65340
65686
  }
65341
- const safeForLiteral = !/[\0\n\r]/.test(dbBackup);
65342
65687
  try {
65343
- if (!safeForLiteral) throw new Error("backup path not safe for SQL literal");
65344
- getDb().exec(`VACUUM INTO '${dbBackup.replace(/'/g, "''")}'`);
65688
+ vacuumInto(getDb(), dbBackup);
65345
65689
  } catch {
65346
- fs4.copyFileSync(getDbPath(), dbBackup);
65690
+ fs5.copyFileSync(getDbPath(), dbBackup);
65347
65691
  }
65348
- fs4.writeFileSync(jsonBackup, JSON.stringify(exportAll(), null, 2), "utf8");
65692
+ fs5.writeFileSync(jsonBackup, JSON.stringify(exportAll(), null, 2), "utf8");
65349
65693
  return { backup_path: dbBackup, json_path: jsonBackup };
65350
65694
  }
65351
65695
  function createDashboardShortcut(open = false) {
65352
65696
  const dir = shortcutFolder();
65353
- fs4.mkdirSync(dir, { recursive: true });
65697
+ fs5.mkdirSync(dir, { recursive: true });
65354
65698
  const readme = `CareerMate \uB300\uC2DC\uBCF4\uB4DC \uBC14\uB85C\uAC00\uAE30
65355
65699
 
65356
65700
  - \uB300\uC2DC\uBCF4\uB4DC\uAC00 \uC774\uBBF8 \uC2E4\uD589 \uC911\uC774\uBA74 \uBE0C\uB77C\uC6B0\uC800\uB9CC \uC5FD\uB2C8\uB2E4.
65357
65701
  - \uB300\uC2DC\uBCF4\uB4DC\uAC00 \uAEBC\uC838 \uC788\uC73C\uBA74 \uC11C\uBC84\uB97C \uC2DC\uC791\uD558\uACE0 \uBE0C\uB77C\uC6B0\uC800\uB97C \uC5FD\uB2C8\uB2E4.
65358
65702
  - \uC774 \uD3F4\uB354\uB294 \uC774 \uCEF4\uD4E8\uD130\uC758 CareerMate \uC124\uCE58 \uACBD\uB85C\uC640 \uB370\uC774\uD130 \uD3F4\uB354\uB97C \uAE30\uC900\uC73C\uB85C \uB9CC\uB4E4\uC5B4\uC84C\uC2B5\uB2C8\uB2E4.
65359
65703
  `;
65360
- const readmePath = path4.join(dir, "README.txt");
65361
- fs4.writeFileSync(readmePath, readme, "utf8");
65704
+ const readmePath = path5.join(dir, "README.txt");
65705
+ fs5.writeFileSync(readmePath, readme, "utf8");
65362
65706
  let launcherPath;
65363
65707
  if (process.platform === "win32") {
65364
- launcherPath = path4.join(dir, "CareerMate \uB300\uC2DC\uBCF4\uB4DC.cmd");
65365
- fs4.writeFileSync(launcherPath, windowsLauncherScript(), "utf8");
65708
+ launcherPath = path5.join(dir, "CareerMate \uB300\uC2DC\uBCF4\uB4DC.cmd");
65709
+ fs5.writeFileSync(launcherPath, windowsLauncherScript(), "utf8");
65366
65710
  } else if (process.platform === "darwin") {
65367
- launcherPath = path4.join(dir, "CareerMate \uB300\uC2DC\uBCF4\uB4DC.command");
65368
- fs4.writeFileSync(launcherPath, macLauncherScript(), { encoding: "utf8", mode: 493 });
65369
- fs4.chmodSync(launcherPath, 493);
65711
+ launcherPath = path5.join(dir, "CareerMate \uB300\uC2DC\uBCF4\uB4DC.command");
65712
+ fs5.writeFileSync(launcherPath, macLauncherScript(), { encoding: "utf8", mode: 493 });
65713
+ fs5.chmodSync(launcherPath, 493);
65370
65714
  } else {
65371
- launcherPath = path4.join(dir, "careermate-dashboard.sh");
65372
- fs4.writeFileSync(launcherPath, linuxLauncherScript(), { encoding: "utf8", mode: 493 });
65373
- fs4.chmodSync(launcherPath, 493);
65715
+ launcherPath = path5.join(dir, "careermate-dashboard.sh");
65716
+ fs5.writeFileSync(launcherPath, linuxLauncherScript(), { encoding: "utf8", mode: 493 });
65717
+ fs5.chmodSync(launcherPath, 493);
65374
65718
  }
65375
65719
  if (open) openFolder(dir);
65376
65720
  return {
@@ -65398,10 +65742,10 @@ function resetAll(confirm) {
65398
65742
  }
65399
65743
  function listBackups() {
65400
65744
  const dir = getBackupsDir();
65401
- if (!fs4.existsSync(dir)) return [];
65402
- return fs4.readdirSync(dir).filter((f) => f.endsWith(".sqlite") || f.endsWith(".json")).map((f) => {
65403
- const p = path4.join(dir, f);
65404
- const st = fs4.statSync(p);
65745
+ if (!fs5.existsSync(dir)) return [];
65746
+ return fs5.readdirSync(dir).filter((f) => f.endsWith(".sqlite") || f.endsWith(".json")).map((f) => {
65747
+ const p = path5.join(dir, f);
65748
+ const st = fs5.statSync(p);
65405
65749
  return { filename: f, path: p, size: st.size, created_at: st.mtime.toISOString() };
65406
65750
  }).sort((a, b) => b.created_at.localeCompare(a.created_at));
65407
65751
  }
@@ -65583,6 +65927,7 @@ function registerApiRoutes(router2) {
65583
65927
  "/api/context",
65584
65928
  (ctx) => getApplicationContext({ job_id: ctx.query.get("job_id") ?? void 0 })
65585
65929
  );
65930
+ router2.get("/api/offers", () => ({ offers: compareOffers() }));
65586
65931
  router2.get("/api/prompts", () => ({ prompts: PROMPTS }));
65587
65932
  router2.get("/api/workflows", () => ({ workflows: WORKFLOWS }));
65588
65933
  router2.get("/api/meta", () => ({
@@ -65822,8 +66167,8 @@ function isDownload(v) {
65822
66167
  }
65823
66168
 
65824
66169
  // apps/web/src/server.ts
65825
- var __dirname2 = path5.dirname(fileURLToPath2(import.meta.url));
65826
- var WEB_ROOT = BUNDLED ? path5.resolve(__dirname2, "public") : path5.resolve(__dirname2, "..", "public");
66170
+ var __dirname2 = path6.dirname(fileURLToPath2(import.meta.url));
66171
+ var WEB_ROOT = BUNDLED ? path6.resolve(__dirname2, "public") : path6.resolve(__dirname2, "..", "public");
65827
66172
  var LANDING_URL = "https://careermate.life/";
65828
66173
  var router = new Router();
65829
66174
  registerApiRoutes(router);