careermate 0.0.4 → 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/web.mjs CHANGED
@@ -6746,15 +6746,15 @@ var require_generators = __commonJS({
6746
6746
  var stack = new Error().stack;
6747
6747
  return function() {
6748
6748
  var generator = generatorFunction.apply(this, arguments);
6749
- var spawn2 = new PromiseSpawn$(
6749
+ var spawn3 = new PromiseSpawn$(
6750
6750
  void 0,
6751
6751
  void 0,
6752
6752
  yieldHandler,
6753
6753
  stack
6754
6754
  );
6755
- var ret2 = spawn2.promise();
6756
- spawn2._generator = generator;
6757
- spawn2._promiseFulfilled(void 0);
6755
+ var ret2 = spawn3.promise();
6756
+ spawn3._generator = generator;
6757
+ spawn3._promiseFulfilled(void 0);
6758
6758
  return ret2;
6759
6759
  };
6760
6760
  };
@@ -6769,9 +6769,9 @@ var require_generators = __commonJS({
6769
6769
  if (typeof generatorFunction !== "function") {
6770
6770
  return apiRejection("generatorFunction must be a function\n\n See http://goo.gl/MqrFmX\n");
6771
6771
  }
6772
- var spawn2 = new PromiseSpawn(generatorFunction, this);
6773
- var ret2 = spawn2.promise();
6774
- spawn2._run(Promise2.spawn);
6772
+ var spawn3 = new PromiseSpawn(generatorFunction, this);
6773
+ var ret2 = spawn3.promise();
6774
+ spawn3._run(Promise2.spawn);
6775
6775
  return ret2;
6776
6776
  };
6777
6777
  };
@@ -28299,7 +28299,7 @@ var require_files = __commonJS({
28299
28299
  "use strict";
28300
28300
  var fs5 = __require("fs");
28301
28301
  var url = __require("url");
28302
- var os2 = __require("os");
28302
+ var os3 = __require("os");
28303
28303
  var dirname = __require("path").dirname;
28304
28304
  var resolvePath = __require("path").resolve;
28305
28305
  var isAbsolutePath = require_path_is_absolute();
@@ -28341,7 +28341,7 @@ var require_files = __commonJS({
28341
28341
  var readFile = promises.promisify(fs5.readFile.bind(fs5));
28342
28342
  function uriToPath(uriString, platform) {
28343
28343
  if (!platform) {
28344
- platform = os2.platform();
28344
+ platform = os3.platform();
28345
28345
  }
28346
28346
  var uri = url.parse(uriString);
28347
28347
  if (isLocalFileUri(uri) || isRelativeUri(uri)) {
@@ -39354,12 +39354,12 @@ var require_cjs = __commonJS({
39354
39354
  });
39355
39355
 
39356
39356
  // apps/web/src/index.ts
39357
- import { spawn } from "node:child_process";
39357
+ import { spawn as spawn2 } from "node:child_process";
39358
39358
 
39359
39359
  // apps/web/src/server.ts
39360
39360
  import http from "node:http";
39361
39361
  import path5 from "node:path";
39362
- import { fileURLToPath } from "node:url";
39362
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
39363
39363
 
39364
39364
  // packages/db/src/paths.ts
39365
39365
  import os from "node:os";
@@ -44263,7 +44263,7 @@ var ApplicationContextSchema = external_exports.object({
44263
44263
  });
44264
44264
 
44265
44265
  // packages/shared/src/build-info.ts
44266
- var APP_VERSION = true ? "0.0.4" : "0.0.0-dev";
44266
+ var APP_VERSION = true ? "0.0.5" : "0.0.0-dev";
44267
44267
  var BUNDLED = true;
44268
44268
 
44269
44269
  // packages/shared/src/index.ts
@@ -44775,6 +44775,7 @@ var coverLetterRepo = {
44775
44775
  remove(id3) {
44776
44776
  const db = getDb();
44777
44777
  return tx(() => {
44778
+ db.prepare(`UPDATE applications SET cover_letter_id=NULL, updated_at=? WHERE cover_letter_id=?`).run(now(), id3);
44778
44779
  db.prepare(`DELETE FROM cover_letter_versions WHERE cover_letter_id=?`).run(id3);
44779
44780
  return db.prepare(`DELETE FROM cover_letters WHERE id=?`).run(id3).changes > 0;
44780
44781
  });
@@ -44863,6 +44864,7 @@ var jobRepo = {
44863
44864
  remove(id3) {
44864
44865
  const db = getDb();
44865
44866
  return tx(() => {
44867
+ db.prepare(`UPDATE cover_letters SET job_id=NULL, updated_at=? WHERE job_id=?`).run(now(), id3);
44866
44868
  db.prepare(`DELETE FROM fit_analyses WHERE job_id=?`).run(id3);
44867
44869
  db.prepare(`DELETE FROM applications WHERE job_id=?`).run(id3);
44868
44870
  db.prepare(`DELETE FROM interview_preps WHERE job_id=?`).run(id3);
@@ -45151,7 +45153,7 @@ function injectToken(html) {
45151
45153
  // apps/web/src/http.ts
45152
45154
  import fs3 from "node:fs";
45153
45155
  import path3 from "node:path";
45154
- var MAX_BODY2 = 8 * 1024 * 1024;
45156
+ var MAX_BODY2 = 32 * 1024 * 1024;
45155
45157
  var Router = class {
45156
45158
  routes = [];
45157
45159
  add(method, path6, handler) {
@@ -45746,7 +45748,7 @@ function saveJobPosting(input, id3) {
45746
45748
  }
45747
45749
  function saveFitAnalysis(input) {
45748
45750
  const job = jobRepo.get(input.job_id);
45749
- if (!job) throw notFound(`\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.`);
45751
+ if (!job) throw notFound("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 \uACF5\uACE0\uB97C \uC800\uC7A5\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.");
45750
45752
  applicationRepo.ensure(job.id, "draft");
45751
45753
  const fit = fitRepo.save(input);
45752
45754
  const scoreText = fit.score != null ? ` (\uC801\uD569\uB3C4 ${fit.score}\uC810)` : "";
@@ -45788,7 +45790,7 @@ function saveCoverLetterVersion(input) {
45788
45790
  }
45789
45791
  function updateApplicationStatus(jobId, status, note) {
45790
45792
  const job = jobRepo.get(jobId);
45791
- if (!job) throw notFound(`\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${jobId}`);
45793
+ if (!job) throw notFound("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
45792
45794
  const application = applicationRepo.setStatus(jobId, status, note);
45793
45795
  activityRepo.log(
45794
45796
  "application_status_changed",
@@ -45798,17 +45800,17 @@ function updateApplicationStatus(jobId, status, note) {
45798
45800
  );
45799
45801
  const interview_unlocked = INTERVIEW_UNLOCK_STATUSES.includes(status);
45800
45802
  const hasPrep = !!interviewRepo.getByJob(jobId);
45801
- 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;
45803
+ const hint = interview_unlocked && !hasPrep ? "\uC11C\uB958 \uB2E8\uACC4\uB97C \uD1B5\uACFC\uD588\uC5B4\uC694. \uC774 \uACF5\uACE0 \uAE30\uC900\uC73C\uB85C \uC608\uC0C1 \uBA74\uC811 \uC9C8\uBB38\uACFC 1\uBD84 \uC790\uAE30\uC18C\uAC1C\uB97C \uC900\uBE44\uD560\uAE4C\uC694?" : null;
45802
45804
  return { application, job, interview_unlocked, hint };
45803
45805
  }
45804
45806
  function saveInterviewPrep(input) {
45805
45807
  const job = jobRepo.get(input.job_id);
45806
- if (!job) throw notFound(`\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${input.job_id}`);
45808
+ if (!job) throw notFound("\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
45807
45809
  const app = applicationRepo.getByJob(job.id);
45808
45810
  if (!app || !INTERVIEW_UNLOCK_STATUSES.includes(app.status)) {
45809
45811
  const current = app ? APPLICATION_STATUS_LABELS[app.status] : "\uBBF8\uC9C0\uC6D0";
45810
45812
  throw conflict(
45811
- `\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.`
45813
+ `\uBA74\uC811 \uC900\uBE44\uB294 '\uC11C\uB958 \uD569\uACA9' \uC774\uC0C1 \uC0C1\uD0DC\uC5D0\uC11C\uB9CC \uC800\uC7A5\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uD604\uC7AC \uC0C1\uD0DC: '${current}'. \uBA3C\uC800 \uC9C0\uC6D0 \uC0C1\uD0DC\uB97C '\uC11C\uB958 \uD569\uACA9' \uC774\uC0C1\uC73C\uB85C \uBC14\uAFBC \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.`
45812
45814
  );
45813
45815
  }
45814
45816
  const prep = interviewRepo.save(input);
@@ -45896,17 +45898,25 @@ CareerMate\uB294 \uC0AC\uC6A9\uC790\uC758 \uCEF4\uD4E8\uD130\uC5D0\uC11C \uB3D9\
45896
45898
 
45897
45899
  6. \uC0AC\uC6A9\uC790\uC5D0\uAC8C\uB294 \uC26C\uC6B4 \uD55C\uAD6D\uC5B4\uB85C \uC54C\uB9BD\uB2C8\uB2E4.
45898
45900
  - "MCP", "\uD234 \uD638\uCD9C", "job_id" \uAC19\uC740 \uAE30\uC220 \uC6A9\uC5B4\uB294 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC9C1\uC811 \uB178\uCD9C\uD558\uC9C0 \uB9C8\uC138\uC694. "\uACF5\uACE0\uB97C \uC800\uC7A5\uD588\uC5B4\uC694", "\uC801\uD569\uB3C4\uB97C \uBD84\uC11D\uD588\uC5B4\uC694"\uCC98\uB7FC \uC77C\uC0C1\uC801\uC778 \uD45C\uD604\uC73C\uB85C \uC9C4\uD589 \uC0C1\uD669\uC744 \uC54C\uB9AC\uC138\uC694.
45901
+ - \uB3C4\uAD6C \uC751\uB2F5\uC5D0 \`user_message\`\uAC00 \uC788\uC73C\uBA74 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBCF4\uACE0\uD560 \uB54C \uADF8 \uBB38\uC7A5\uC744 \uC6B0\uC120 \uD65C\uC6A9\uD558\uC138\uC694. \`id\`, \`job_id\`, \`cover_letter_id\`, \`workflow_id\`, JSON \uC6D0\uBB38, \uB3C4\uAD6C \uC774\uB984\uC740 \uB0B4\uBD80 \uC791\uC5C5\uC6A9\uC73C\uB85C\uB9CC \uC4F0\uACE0 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uADF8\uB300\uB85C \uBCF5\uC0AC\uD558\uC9C0 \uB9C8\uC138\uC694.
45902
+ - \uB3C4\uAD6C \uC751\uB2F5\uC5D0 \`suggested_next_action\`\uC774 \uC788\uC73C\uBA74 \uB2E4\uC74C \uD589\uB3D9\uC744 \uAC15\uC694\uD558\uC9C0 \uB9D0\uACE0 \uC120\uD0DD\uC9C0\uB85C \uC81C\uC548\uD558\uC138\uC694.
45899
45903
  - \uBB34\uC5C7\uC744 \uD588\uACE0, \uBB34\uC5C7\uC744 \uC54C\uAC8C \uB418\uC5C8\uACE0, \uB2E4\uC74C\uC5D0 \uBB34\uC5C7\uC744 \uD560 \uC218 \uC788\uB294\uC9C0 \uC9E7\uACE0 \uBA85\uD655\uD558\uAC8C \uC804\uB2EC\uD558\uC138\uC694.
45900
45904
 
45901
45905
  7. \uB2E4\uC74C \uB2E8\uACC4\uB97C \uBA3C\uC800 \uC81C\uC548\uD569\uB2C8\uB2E4.
45902
45906
  - \uC791\uC5C5\uC774 \uB05D\uB098\uBA74 \uC790\uC5F0\uC2A4\uB7EC\uC6B4 \uB2E4\uC74C \uD589\uB3D9\uC744 \uC81C\uC548\uD558\uC138\uC694. \uC608: \uACF5\uACE0 \uBD84\uC11D \uD6C4 \u2192 "\uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uB4DC\uB9B4\uAE4C\uC694?", \uC790\uAE30\uC18C\uAC1C\uC11C \uC800\uC7A5 \uD6C4 \u2192 "\uC9C0\uC6D0 \uC0C1\uD0DC\uB97C '\uC9C0\uC6D0 \uC644\uB8CC'\uB85C \uBC14\uAFC0\uAE4C\uC694?", \uC0C1\uD0DC\uAC00 '\uC11C\uB958 \uD569\uACA9'\uC774 \uB418\uBA74 \u2192 "\uBA74\uC811 \uC900\uBE44\uB97C \uB3C4\uC640\uB4DC\uB9B4\uAE4C\uC694?".
45907
+ - \uACF5\uACE0 \uBD84\uC11D \uD6C4\uC5D0\uB294 \uACE7\uC7A5 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC4F0\uC9C0 \uB9D0\uACE0, \uBC18\uB4DC\uC2DC \uC120\uD0DD\uC9C0\uB97C \uBCF4\uC5EC\uC8FC\uC138\uC694: "1. \uB124, \uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uC918" / "2. \uC544\uB2C8\uC694, \uBD84\uC11D\uB9CC \uC800\uC7A5\uD574\uC918". \uC0AC\uC6A9\uC790\uAC00 1\uBC88\uC744 \uACE0\uB978 \uACBD\uC6B0\uC5D0\uB9CC \uC790\uAE30\uC18C\uAC1C\uC11C \uC791\uC131 \uD750\uB984\uC73C\uB85C \uC774\uC5B4\uAC11\uB2C8\uB2E4.
45903
45908
  - \`get_onboarding_status\`\uC758 next_steps\uB098 \`open_dashboard\`/\`open_application\`\uC744 \uD65C\uC6A9\uD574 \uC0AC\uC6A9\uC790\uB97C \uB2E4\uC74C \uD589\uB3D9\uC73C\uB85C \uC548\uB0B4\uD558\uC138\uC694.
45904
45909
 
45905
- 8. \uAE00\uC740 AI \uD2F0 \uC548 \uB098\uAC8C \uC501\uB2C8\uB2E4.
45910
+ 8. \uC5C5\uB370\uC774\uD2B8 \uC694\uCCAD\uC740 \uBA85\uD655\uD558\uAC8C \uCC98\uB9AC\uD569\uB2C8\uB2E4.
45911
+ - \uC0AC\uC6A9\uC790\uAC00 "\uCD5C\uC2E0\uC774\uC57C?", "\uC5C5\uB370\uC774\uD2B8 \uC788\uC5B4?"\uCC98\uB7FC \uD655\uC778\uB9CC \uC694\uCCAD\uD558\uBA74 \`check_for_update\`\uB97C \uD638\uCD9C\uD558\uACE0 \uACB0\uACFC\uB97C \uC26C\uC6B4 \uB9D0\uB85C \uC54C\uB824\uC8FC\uC138\uC694.
45912
+ - \uC0AC\uC6A9\uC790\uAC00 "\uC5C5\uB370\uC774\uD2B8\uD574\uC918", "CareerMate \uC5C5\uB370\uC774\uD2B8\uD574\uC918", "\uCD5C\uC2E0\uC73C\uB85C \uC62C\uB824\uC918"\uCC98\uB7FC \uC801\uC6A9\uC744 \uBA85\uC2DC\uD558\uBA74 \`update_careermate\`\uB97C \uBC14\uB85C \uD638\uCD9C\uD558\uC138\uC694. \uC774 \uB3C4\uAD6C\uAC00 \uCD5C\uC2E0 \uBC84\uC804 \uD655\uC778\uB3C4 \uD568\uAED8 \uD569\uB2C8\uB2E4.
45913
+ - \uC5C5\uB370\uC774\uD2B8 \uC131\uACF5 \uD6C4\uC5D0\uB294 \uC801\uC6A9\uD558\uB824\uBA74 AI \uC571 \uB610\uB294 MCP \uC5F0\uACB0\uC744 \uC7AC\uC2DC\uC791\uD574\uC57C \uD55C\uB2E4\uACE0 \uC548\uB0B4\uD558\uC138\uC694. \uC0AC\uC6A9\uC790 \uCEE4\uB9AC\uC5B4 \uB370\uC774\uD130\uB294 \uB85C\uCEEC\uC5D0 \uBCF4\uC874\uB418\uACE0, npm\uC5D0\uB294 \uACF5\uAC1C \uBC84\uC804 \uC815\uBCF4 \uD655\uC778/\uC124\uCE58 \uC694\uCCAD\uB9CC \uB098\uAC11\uB2C8\uB2E4.
45914
+
45915
+ 9. \uAE00\uC740 AI \uD2F0 \uC548 \uB098\uAC8C \uC501\uB2C8\uB2E4.
45906
45916
  - \uC790\uAE30\uC18C\uAC1C\uC11C\xB7\uC790\uAE30 PR\xB7\uC9C0\uC6D0 \uBA54\uC77C \uB4F1 \uC0AC\uB78C\uC774 \uC4F4 \uB4EF\uD55C \uAE00\uC744 \uC791\uC131\uD558\uAE30 \uC9C1\uC804\uC5D0\uB294 \`get_writing_style_guide\`\uB97C \uD638\uCD9C\uD574 \uAE00\uC4F0\uAE30 \uADDC\uCE59\uC744 \uAC00\uC838\uC624\uACE0 \uADF8\uB300\uB85C \uC801\uC6A9\uD558\uC138\uC694.
45907
45917
  - \uBC88\uC5ED\uD22C, "\uC5F4\uC815\uC744 \uAC00\uC9C0\uACE0/\uADC0\uC0AC\uC758 \uBC1C\uC804\uC5D0 \uAE30\uC5EC" \uAC19\uC740 \uD074\uB9AC\uC170, \uAE30\uACC4\uC801 \uBCD1\uB82C(\uCCAB\uC9F8\xB7\uB458\uC9F8\xB7\uC14B\uC9F8), \uC0C1\uD22C\uC801 \uC5F0\uACB0\uC5B4, \uADE0\uC77C\uD55C \uBB38\uC7A5 \uB9AC\uB4EC\uC744 \uD53C\uD558\uACE0 \uB2F4\uBC31\uD558\uACE0 \uAD6C\uCCB4\uC801\uC73C\uB85C \uC501\uB2C8\uB2E4. \uB2E8, \uC0AC\uC2E4\xB7\uC218\uCE58\xB7\uACE0\uC720\uBA85\uC0AC\uB294 \uC808\uB300 \uBC14\uAFB8\uAC70\uB098 \uC9C0\uC5B4\uB0B4\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.
45908
45918
 
45909
- 9. \uC804\uBB38\uAC00 \uC808\uCC28\uB85C \uB9CC\uB4E4\uACE0, \uC800\uC7A5 \uC804 \uB2F9\uC2E0\uC774 \uC790\uAC00\uAC80\uC99D\uD569\uB2C8\uB2E4.
45919
+ 10. \uC804\uBB38\uAC00 \uC808\uCC28\uB85C \uB9CC\uB4E4\uACE0, \uC800\uC7A5 \uC804 \uB2F9\uC2E0\uC774 \uC790\uAC00\uAC80\uC99D\uD569\uB2C8\uB2E4.
45910
45920
  - \uACF5\uACE0 \uBD84\uC11D\xB7\uC790\uC18C\uC11C\xB7\uBA74\uC811 \uC790\uB8CC\uCC98\uB7FC \uC0AC\uC6A9\uC790 \uB300\uBA74 \uACB0\uACFC\uB97C \uB9CC\uB4E4\uAE30 \uC804\uC5D0\uB294, \uC989\uB2F5\uD558\uC9C0 \uB9D0\uACE0 \`get_workflow_guide\`(workflow_id \uC9C0\uC815)\uB97C \uBA3C\uC800 \uD638\uCD9C\uD574 \uADF8 \uC791\uC5C5\uC758 \uC804\uBB38\uAC00 \uC2E4\uD589 \uC808\uCC28(EOP)\uC640 \uC801\uC6A9\uD560 \uD50C\uB808\uC774\uBD81\xB7\uAC80\uC99D\uAE30 \uC21C\uC11C\uB97C \uBC1B\uC73C\uC138\uC694. \uC774\uC5B4 \`get_playbook\`\uC73C\uB85C \uB3C4\uBA54\uC778 \uC9C0\uC2DD\uC744 \uBC1B\uC544 \uC791\uC131\uD558\uACE0, save\uB85C \uC800\uC7A5\uD558\uAE30 \uC9C1\uC804 \`get_verifier\`\uB85C \uAC80\uC99D \uB8E8\uBE0C\uB9AD\uC744 \uBC1B\uC544 **\uB2F9\uC2E0\uC774 \uC9C1\uC811** \uC810\uAC80\xB7\uC218\uC815\uD55C \uB4A4 \uC800\uC7A5\uD569\uB2C8\uB2E4.
45911
45921
  - CareerMate\uB294 \uC2AC\uB86D \uBC00\uB3C4\xB7\uD0A4\uC6CC\uB4DC \uB9E4\uCE6D\uCC98\uB7FC \uC140 \uC218 \uC788\uB294 \uD56D\uBAA9\uB9CC \uBCF4\uC870\uB85C \uB3D5\uACE0, \uC758\uBBF8 \uD310\uB2E8(\uC774 \uAE00\uC774 \uC9C8\uBB38\uC5D0 \uB2F5\uD558\uB294\uAC00\xB7\uD1A4\uC774 \uB9DE\uB294\uAC00)\uC740 \uB2F9\uC2E0\uC774 \uD569\uB2C8\uB2E4. \uC0AC\uC2E4\xB7\uC218\uCE58\xB7\uACE0\uC720\uBA85\uC0AC\uB294 \uADF8\uB300\uB85C \uBCF4\uC874\uD569\uB2C8\uB2E4.
45912
45922
 
@@ -46097,8 +46107,8 @@ var WORKFLOWS = [
46097
46107
  "\uC544\uC9C1 \uC800\uC7A5\uB418\uC9C0 \uC54A\uC740 \uACF5\uACE0\uB77C\uBA74 `save_job_posting`\uC73C\uB85C \uACF5\uACE0\uB97C \uC800\uC7A5\uD558\uACE0 job_id\uB97C \uD655\uBCF4\uD55C\uB2E4.",
46098
46108
  "\uB3C4\uCD9C\uD55C \uBD84\uC11D \uACB0\uACFC\uB97C `save_fit_analysis`\uB85C \uC800\uC7A5\uD55C\uB2E4(job_id, score, summary, strengths, gaps, matched_keywords, missing_keywords, recommendations).",
46099
46109
  "\uBD84\uC11D \uACB0\uACFC\uB97C \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC26C\uC6B4 \uD55C\uAD6D\uC5B4\uB85C \uC124\uBA85\uD55C\uB2E4(\uC798 \uB9DE\uB294 \uC810, \uBCF4\uC644\uD560 \uC810, \uCD94\uCC9C \uC804\uB7B5). \uAE30\uC220 \uC6A9\uC5B4\uB294 \uBE7C\uACE0 \uC804\uB2EC\uD55C\uB2E4.",
46100
- '"\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.',
46101
- '\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.',
46110
+ '\uBD84\uC11D \uB4A4 \uACE7\uC7A5 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC4F0\uC9C0 \uB9D0\uACE0, "\uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uB4DC\uB9B4\uAE4C\uC694?"\uB77C\uACE0 \uBB3B\uACE0 \uC120\uD0DD\uC9C0\uB97C \uC81C\uC2DC\uD55C\uB2E4: \u2460 "\uB124, \uC774 \uACF5\uACE0\uC5D0 \uB9DE\uCD98 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC368\uC918" \u2461 "\uC544\uB2C8\uC694, \uBD84\uC11D\uB9CC \uC800\uC7A5\uD574\uC918".',
46111
+ '\uC0AC\uC6A9\uC790\uAC00 \u2460\uC744 \uC120\uD0DD\uD558\uBA74 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC791\uC131\uD558\uAE30 \uC9C1\uC804\uC5D0 `get_writing_style_guide`\uB97C \uD638\uCD9C\uD574 "AI \uD2F0 \uC548 \uB098\uB294 \uAE00\uC4F0\uAE30 \uADDC\uCE59"\uC744 \uAC00\uC838\uC628\uB2E4. \u2461\uB97C \uC120\uD0DD\uD558\uBA74 \uC790\uC18C\uC11C \uC791\uC131 \uC5C6\uC774 \uBA48\uCD94\uACE0 \uC800\uC7A5 \uACB0\uACFC\uC640 \uB300\uC2DC\uBCF4\uB4DC \uD655\uC778\uB9CC \uC548\uB0B4\uD55C\uB2E4.',
46102
46112
  "\uC801\uD569\uB3C4 \uBD84\uC11D\xB7\uAE00\uC4F0\uAE30 \uC120\uD638\uC640 \uAE00\uC4F0\uAE30 \uADDC\uCE59\uC744 \uD568\uAED8 \uBC18\uC601\uD574 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uC791\uC131\uD558\uACE0(\uBC88\uC5ED\uD22C\xB7\uD074\uB9AC\uC170\xB7\uAE30\uACC4\uC801 \uBCD1\uB82C \uC81C\uAC70, \uC0AC\uC2E4\xB7\uC218\uCE58\uB294 \uADF8\uB300\uB85C), `save_cover_letter_version`\uC73C\uB85C \uC800\uC7A5\uD55C\uB2E4(job_id \uC5F0\uACB0, note\uC5D0 \uC791\uC131 \uC758\uB3C4 \uC694\uC57D).",
46103
46113
  "`open_application`\uC73C\uB85C \uD574\uB2F9 \uC9C0\uC6D0 \uAC74\uC744 \uC5F4\uC5B4 \uC0AC\uC6A9\uC790\uAC00 \uACB0\uACFC\uB97C \uD655\uC778\uD558\uAC8C \uD558\uACE0, \uC9C0\uC6D0 \uC0C1\uD0DC \uBCC0\uACBD(\uC608: \uC9C0\uC6D0 \uC608\uC815/\uC9C0\uC6D0 \uC644\uB8CC)\uC744 \uC81C\uC548\uD55C\uB2E4."
46104
46114
  ]
@@ -46874,20 +46884,286 @@ function getServerInfo() {
46874
46884
 
46875
46885
  // apps/web/src/settings.ts
46876
46886
  import fs4 from "node:fs";
46887
+ import os2 from "node:os";
46877
46888
  import path4 from "node:path";
46889
+ import { spawn } from "node:child_process";
46890
+ import { fileURLToPath } from "node:url";
46891
+ var __dirname = path4.dirname(fileURLToPath(import.meta.url));
46878
46892
  function listTables(db) {
46879
46893
  return db.prepare(
46880
46894
  `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name != '_meta' ORDER BY name`
46881
46895
  ).all().map((r) => r.name);
46882
46896
  }
46897
+ var RESTORE_ORDER = [
46898
+ "profile",
46899
+ "experiences",
46900
+ "projects",
46901
+ "skills",
46902
+ "documents",
46903
+ "jobs",
46904
+ "cover_letters",
46905
+ "cover_letter_versions",
46906
+ "fit_analyses",
46907
+ "applications",
46908
+ "interview_preps",
46909
+ "activities"
46910
+ ];
46911
+ function quoteIdent(name) {
46912
+ return `"${name.replace(/"/g, '""')}"`;
46913
+ }
46914
+ function shQuote(value) {
46915
+ return `'${value.replace(/'/g, `'\\''`)}'`;
46916
+ }
46917
+ function cmdQuote(value) {
46918
+ return value.replace(/"/g, '""');
46919
+ }
46920
+ function schemaVersion(db) {
46921
+ const ver = db.prepare(`SELECT value FROM _meta WHERE key='schema_version'`).get();
46922
+ return Number(ver?.value ?? 0);
46923
+ }
46924
+ function tableColumns(db, table) {
46925
+ return db.prepare(`PRAGMA table_info(${quoteIdent(table)})`).all().map((r) => r.name);
46926
+ }
46927
+ function isRecord(v) {
46928
+ return typeof v === "object" && v !== null && !Array.isArray(v);
46929
+ }
46930
+ function appRoot() {
46931
+ return BUNDLED ? __dirname : path4.resolve(__dirname, "..", "..", "..");
46932
+ }
46933
+ function shortcutFolder() {
46934
+ const override = process.env.CAREERMATE_SHORTCUT_DIR?.trim();
46935
+ if (override) return override;
46936
+ const desktop = path4.join(os2.homedir(), "Desktop");
46937
+ const parent = fs4.existsSync(desktop) ? desktop : getExportsDir();
46938
+ return path4.join(parent, "CareerMate \uB300\uC2DC\uBCF4\uB4DC");
46939
+ }
46940
+ function openFolder(dir) {
46941
+ try {
46942
+ if (process.platform === "win32") spawn("explorer", [dir], { stdio: "ignore", detached: true }).unref();
46943
+ else if (process.platform === "darwin") spawn("open", [dir], { stdio: "ignore", detached: true }).unref();
46944
+ else spawn("xdg-open", [dir], { stdio: "ignore", detached: true }).unref();
46945
+ } catch {
46946
+ }
46947
+ }
46948
+ function parseBackup(raw) {
46949
+ if (!isRecord(raw) || !isRecord(raw.tables)) {
46950
+ throw new Error("CareerMate \uBC31\uC5C5 JSON \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4.");
46951
+ }
46952
+ const rawTables = raw.tables;
46953
+ const db = getDb();
46954
+ const currentVersion = schemaVersion(db);
46955
+ const version = Number(raw.version);
46956
+ if (!Number.isInteger(version) || version < 0) {
46957
+ throw new Error("\uBC31\uC5C5 \uD30C\uC77C\uC758 \uC2A4\uD0A4\uB9C8 \uBC84\uC804\uC744 \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
46958
+ }
46959
+ if (version > currentVersion) {
46960
+ throw new Error("\uC774 \uBC31\uC5C5\uC740 \uB354 \uCD5C\uC2E0 CareerMate\uC5D0\uC11C \uB9CC\uB4E4\uC5B4\uC84C\uC2B5\uB2C8\uB2E4. \uC571\uC744 \uC5C5\uB370\uC774\uD2B8\uD55C \uB4A4 \uB2E4\uC2DC \uAC00\uC838\uC640 \uC8FC\uC138\uC694.");
46961
+ }
46962
+ const allowedTables = new Set(listTables(db));
46963
+ const unknownTables = Object.keys(rawTables).filter((name) => !allowedTables.has(name));
46964
+ if (unknownTables.length > 0) {
46965
+ throw new Error(`\uD604\uC7AC \uC571\uC5D0\uC11C \uC54C \uC218 \uC5C6\uB294 \uB370\uC774\uD130\uAC00 \uD3EC\uD568\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4: ${unknownTables.join(", ")}`);
46966
+ }
46967
+ const columnsByTable = /* @__PURE__ */ new Map();
46968
+ for (const table of allowedTables) columnsByTable.set(table, new Set(tableColumns(db, table)));
46969
+ const tables = {};
46970
+ const counts = {};
46971
+ let totalRows = 0;
46972
+ for (const table of allowedTables) {
46973
+ const rows = rawTables[table] ?? [];
46974
+ if (!Array.isArray(rows)) throw new Error(`${table} \uB370\uC774\uD130\uAC00 \uBC30\uC5F4 \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4.`);
46975
+ const columns = columnsByTable.get(table);
46976
+ tables[table] = rows.map((row, rowIndex) => {
46977
+ if (!isRecord(row)) throw new Error(`${table} ${rowIndex + 1}\uBC88\uC9F8 \uD589\uC774 \uC62C\uBC14\uB978 \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4.`);
46978
+ const clean = {};
46979
+ for (const [key, value] of Object.entries(row)) {
46980
+ if (!columns.has(key)) throw new Error(`${table} \uB370\uC774\uD130\uC5D0 \uD604\uC7AC \uC571\uC5D0\uC11C \uC4F0\uC9C0 \uC54A\uB294 \uD56D\uBAA9(${key})\uC774 \uC788\uC2B5\uB2C8\uB2E4.`);
46981
+ if (value !== null && !["string", "number", "boolean"].includes(typeof value)) {
46982
+ throw new Error(`${table} \uB370\uC774\uD130\uC758 ${key} \uAC12 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`);
46983
+ }
46984
+ clean[key] = value;
46985
+ }
46986
+ if (Object.keys(clean).length === 0) throw new Error(`${table} ${rowIndex + 1}\uBC88\uC9F8 \uD589\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.`);
46987
+ return clean;
46988
+ });
46989
+ counts[table] = tables[table].length;
46990
+ totalRows += tables[table].length;
46991
+ }
46992
+ const warnings = [...allowedTables].filter((table) => !(table in rawTables)).map((table) => `${table} \uB370\uC774\uD130\uAC00 \uBC31\uC5C5 \uD30C\uC77C\uC5D0 \uC5C6\uC5B4 \uBE48 \uAC12\uC73C\uB85C \uAC00\uC838\uC635\uB2C8\uB2E4.`);
46993
+ return {
46994
+ exported_at: typeof raw.exported_at === "string" ? raw.exported_at : null,
46995
+ version,
46996
+ current_version: currentVersion,
46997
+ tables,
46998
+ counts,
46999
+ total_rows: totalRows,
47000
+ warnings
47001
+ };
47002
+ }
47003
+ function macLauncherScript() {
47004
+ const root2 = appRoot();
47005
+ const dataDir = getDataDir();
47006
+ const nodePath = process.execPath;
47007
+ const binPath = path4.join(root2, "bin", "careermate.mjs");
47008
+ const bundledWeb = BUNDLED ? path4.join(root2, "web.mjs") : path4.join(root2, "dist", "web.mjs");
47009
+ return `#!/bin/zsh
47010
+ set -e
47011
+
47012
+ export CAREERMATE_DATA_DIR=${shQuote(dataDir)}
47013
+ NODE=${shQuote(nodePath)}
47014
+ ROOT=${shQuote(root2)}
47015
+ BIN=${shQuote(binPath)}
47016
+ BUNDLED_WEB=${shQuote(bundledWeb)}
47017
+ INFO="$CAREERMATE_DATA_DIR/server.json"
47018
+
47019
+ if [ ! -x "$NODE" ]; then
47020
+ NODE="$(command -v node || true)"
47021
+ fi
47022
+ if [ -z "$NODE" ]; then
47023
+ echo "Node.js\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. CareerMate\uB97C \uC124\uCE58\uD55C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC138\uC694."
47024
+ exit 1
47025
+ fi
47026
+
47027
+ URL=$("$NODE" -e 'const fs=require("fs"); try { const info=JSON.parse(fs.readFileSync(process.argv[1], "utf8")); process.stdout.write(info.url || ""); } catch {}' "$INFO")
47028
+ if [ -n "$URL" ] && command -v curl >/dev/null 2>&1 && curl -fsS --max-time 1 "$URL/api/health" >/dev/null 2>&1; then
47029
+ open "$URL"
47030
+ exit 0
47031
+ fi
47032
+
47033
+ echo "CareerMate \uB300\uC2DC\uBCF4\uB4DC\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4."
47034
+ if [ -f "$BIN" ]; then
47035
+ cd "$ROOT"
47036
+ exec "$NODE" "$BIN" start
47037
+ fi
47038
+
47039
+ if [ -f "$BUNDLED_WEB" ]; then
47040
+ cd "$(dirname "$BUNDLED_WEB")"
47041
+ exec "$NODE" --no-warnings --experimental-sqlite "$BUNDLED_WEB"
47042
+ fi
47043
+
47044
+ if command -v careermate >/dev/null 2>&1; then
47045
+ exec careermate start
47046
+ fi
47047
+
47048
+ exec npx -y careermate start
47049
+ `;
47050
+ }
47051
+ function windowsLauncherScript() {
47052
+ const root2 = appRoot();
47053
+ const dataDir = getDataDir();
47054
+ const nodePath = process.execPath;
47055
+ const binPath = path4.join(root2, "bin", "careermate.mjs");
47056
+ const bundledWeb = BUNDLED ? path4.join(root2, "web.mjs") : path4.join(root2, "dist", "web.mjs");
47057
+ return `@echo off
47058
+ setlocal
47059
+ set "CAREERMATE_DATA_DIR=${cmdQuote(dataDir)}"
47060
+ set "NODE=${cmdQuote(nodePath)}"
47061
+ set "ROOT=${cmdQuote(root2)}"
47062
+ set "BIN=${cmdQuote(binPath)}"
47063
+ set "BUNDLED_WEB=${cmdQuote(bundledWeb)}"
47064
+ set "INFO=%CAREERMATE_DATA_DIR%\\server.json"
47065
+ set "URL="
47066
+
47067
+ if not exist "%NODE%" (
47068
+ for /f "usebackq delims=" %%N in (\`where node 2^>nul\`) do (
47069
+ set "NODE=%%N"
47070
+ goto :node_found
47071
+ )
47072
+ )
47073
+ :node_found
47074
+ if not exist "%NODE%" (
47075
+ echo Node.js\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. CareerMate\uB97C \uC124\uCE58\uD55C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.
47076
+ pause
47077
+ exit /b 1
47078
+ )
47079
+
47080
+ for /f "usebackq delims=" %%U in (\`"%NODE%" -e "const fs=require('fs'); try { const info=JSON.parse(fs.readFileSync(process.argv[1], 'utf8')); process.stdout.write(info.url || ''); } catch {}" "%INFO%"\`) do set "URL=%%U"
47081
+ if defined URL (
47082
+ powershell -NoProfile -ExecutionPolicy Bypass -Command "try { Invoke-WebRequest -UseBasicParsing -TimeoutSec 1 '%URL%/api/health' | Out-Null; Start-Process '%URL%'; exit 0 } catch { exit 1 }"
47083
+ if not errorlevel 1 exit /b 0
47084
+ )
47085
+
47086
+ echo CareerMate \uB300\uC2DC\uBCF4\uB4DC\uB97C \uC2DC\uC791\uD569\uB2C8\uB2E4.
47087
+ if exist "%BIN%" (
47088
+ cd /d "%ROOT%"
47089
+ "%NODE%" "%BIN%" start
47090
+ exit /b %ERRORLEVEL%
47091
+ )
47092
+
47093
+ if exist "%BUNDLED_WEB%" (
47094
+ cd /d "%ROOT%"
47095
+ "%NODE%" --no-warnings --experimental-sqlite "%BUNDLED_WEB%"
47096
+ exit /b %ERRORLEVEL%
47097
+ )
47098
+
47099
+ where careermate >nul 2>nul
47100
+ if not errorlevel 1 (
47101
+ careermate start
47102
+ exit /b %ERRORLEVEL%
47103
+ )
47104
+
47105
+ npx -y careermate start
47106
+ `;
47107
+ }
47108
+ function linuxLauncherScript() {
47109
+ return macLauncherScript().replace("#!/bin/zsh", "#!/usr/bin/env sh").replace('\n open "$URL"', '\n xdg-open "$URL"');
47110
+ }
46883
47111
  function exportAll() {
46884
47112
  const db = getDb();
46885
47113
  const tables = {};
46886
47114
  for (const t of listTables(db)) {
46887
- tables[t] = db.prepare(`SELECT * FROM ${t}`).all();
47115
+ tables[t] = db.prepare(`SELECT * FROM ${quoteIdent(t)}`).all();
46888
47116
  }
46889
- const ver = db.prepare(`SELECT value FROM _meta WHERE key='schema_version'`).get();
46890
- return { exported_at: (/* @__PURE__ */ new Date()).toISOString(), version: Number(ver?.value ?? 0), tables };
47117
+ return { exported_at: (/* @__PURE__ */ new Date()).toISOString(), version: schemaVersion(db), tables };
47118
+ }
47119
+ function previewBackupImport(raw) {
47120
+ const parsed = parseBackup(raw);
47121
+ return {
47122
+ exported_at: parsed.exported_at,
47123
+ version: parsed.version,
47124
+ current_version: parsed.current_version,
47125
+ counts: parsed.counts,
47126
+ total_rows: parsed.total_rows,
47127
+ warnings: parsed.warnings
47128
+ };
47129
+ }
47130
+ function restoreBackup(raw) {
47131
+ const parsed = parseBackup(raw);
47132
+ const safety = createBackup();
47133
+ const db = getDb();
47134
+ const tables = listTables(db);
47135
+ const restoreOrder = [
47136
+ ...RESTORE_ORDER.filter((table) => tables.includes(table)),
47137
+ ...tables.filter((table) => !RESTORE_ORDER.includes(table))
47138
+ ];
47139
+ tx(() => {
47140
+ for (const table of [...restoreOrder].reverse()) {
47141
+ db.prepare(`DELETE FROM ${quoteIdent(table)}`).run();
47142
+ }
47143
+ for (const table of restoreOrder) {
47144
+ for (const row of parsed.tables[table] ?? []) {
47145
+ const columns = Object.keys(row);
47146
+ const placeholders = columns.map(() => "?").join(",");
47147
+ const values2 = columns.map((column) => {
47148
+ const value = row[column];
47149
+ return typeof value === "boolean" ? Number(value) : value;
47150
+ });
47151
+ db.prepare(
47152
+ `INSERT INTO ${quoteIdent(table)} (${columns.map(quoteIdent).join(",")}) VALUES (${placeholders})`
47153
+ ).run(...values2);
47154
+ }
47155
+ }
47156
+ const fkProblems = db.prepare(`PRAGMA foreign_key_check`).all();
47157
+ if (fkProblems.length > 0) {
47158
+ throw new Error("\uBC31\uC5C5 \uD30C\uC77C\uC758 \uB370\uC774\uD130 \uAD00\uACC4\uAC00 \uB9DE\uC9C0 \uC54A\uC544 \uAC00\uC838\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
47159
+ }
47160
+ });
47161
+ return {
47162
+ ok: true,
47163
+ safety_backup_path: safety.backup_path,
47164
+ safety_json_path: safety.json_path,
47165
+ restored: previewBackupImport(raw)
47166
+ };
46891
47167
  }
46892
47168
  function createBackup() {
46893
47169
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -46908,6 +47184,38 @@ function createBackup() {
46908
47184
  fs4.writeFileSync(jsonBackup, JSON.stringify(exportAll(), null, 2), "utf8");
46909
47185
  return { backup_path: dbBackup, json_path: jsonBackup };
46910
47186
  }
47187
+ function createDashboardShortcut(open = false) {
47188
+ const dir = shortcutFolder();
47189
+ fs4.mkdirSync(dir, { recursive: true });
47190
+ const readme = `CareerMate \uB300\uC2DC\uBCF4\uB4DC \uBC14\uB85C\uAC00\uAE30
47191
+
47192
+ - \uB300\uC2DC\uBCF4\uB4DC\uAC00 \uC774\uBBF8 \uC2E4\uD589 \uC911\uC774\uBA74 \uBE0C\uB77C\uC6B0\uC800\uB9CC \uC5FD\uB2C8\uB2E4.
47193
+ - \uB300\uC2DC\uBCF4\uB4DC\uAC00 \uAEBC\uC838 \uC788\uC73C\uBA74 \uC11C\uBC84\uB97C \uC2DC\uC791\uD558\uACE0 \uBE0C\uB77C\uC6B0\uC800\uB97C \uC5FD\uB2C8\uB2E4.
47194
+ - \uC774 \uD3F4\uB354\uB294 \uC774 \uCEF4\uD4E8\uD130\uC758 CareerMate \uC124\uCE58 \uACBD\uB85C\uC640 \uB370\uC774\uD130 \uD3F4\uB354\uB97C \uAE30\uC900\uC73C\uB85C \uB9CC\uB4E4\uC5B4\uC84C\uC2B5\uB2C8\uB2E4.
47195
+ `;
47196
+ const readmePath = path4.join(dir, "README.txt");
47197
+ fs4.writeFileSync(readmePath, readme, "utf8");
47198
+ let launcherPath;
47199
+ if (process.platform === "win32") {
47200
+ launcherPath = path4.join(dir, "CareerMate \uB300\uC2DC\uBCF4\uB4DC.cmd");
47201
+ fs4.writeFileSync(launcherPath, windowsLauncherScript(), "utf8");
47202
+ } else if (process.platform === "darwin") {
47203
+ launcherPath = path4.join(dir, "CareerMate \uB300\uC2DC\uBCF4\uB4DC.command");
47204
+ fs4.writeFileSync(launcherPath, macLauncherScript(), { encoding: "utf8", mode: 493 });
47205
+ fs4.chmodSync(launcherPath, 493);
47206
+ } else {
47207
+ launcherPath = path4.join(dir, "careermate-dashboard.sh");
47208
+ fs4.writeFileSync(launcherPath, linuxLauncherScript(), { encoding: "utf8", mode: 493 });
47209
+ fs4.chmodSync(launcherPath, 493);
47210
+ }
47211
+ if (open) openFolder(dir);
47212
+ return {
47213
+ shortcut_dir: dir,
47214
+ launcher_path: launcherPath,
47215
+ platform: process.platform,
47216
+ files: [launcherPath, readmePath]
47217
+ };
47218
+ }
46911
47219
  function resetAll(confirm) {
46912
47220
  if (confirm !== "DELETE") {
46913
47221
  return { ok: false };
@@ -46916,7 +47224,7 @@ function resetAll(confirm) {
46916
47224
  const db = getDb();
46917
47225
  db.exec("BEGIN");
46918
47226
  try {
46919
- for (const t of listTables(db)) db.exec(`DELETE FROM ${t}`);
47227
+ for (const t of listTables(db)) db.exec(`DELETE FROM ${quoteIdent(t)}`);
46920
47228
  db.exec("COMMIT");
46921
47229
  } catch (e) {
46922
47230
  db.exec("ROLLBACK");
@@ -46944,6 +47252,9 @@ function fmt(ctx) {
46944
47252
  const f = (ctx.query.get("format") ?? "md").toLowerCase();
46945
47253
  return ["md", "html", "txt"].includes(f) ? f : "md";
46946
47254
  }
47255
+ function backupError(e) {
47256
+ return new HttpError(400, e instanceof Error ? e.message : "\uBC31\uC5C5 \uD30C\uC77C\uC744 \uCC98\uB9AC\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.", "backup_import");
47257
+ }
46947
47258
  function jobDetail(jobId) {
46948
47259
  const job = jobRepo.get(jobId);
46949
47260
  if (!job) throw new HttpError(404, "\uACF5\uACE0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
@@ -47160,6 +47471,32 @@ function registerApiRoutes(router2) {
47160
47471
  return { verify_strict: on };
47161
47472
  });
47162
47473
  router2.post("/api/settings/backup", () => createBackup());
47474
+ router2.post("/api/settings/dashboard-shortcut", async (ctx) => {
47475
+ const { open } = await readJsonBody(ctx.req, external_exports.object({ open: external_exports.boolean().optional() }));
47476
+ return { shortcut: createDashboardShortcut(open === true) };
47477
+ });
47478
+ router2.post("/api/settings/import-preview", async (ctx) => {
47479
+ const { backup } = await readJsonBody(ctx.req, external_exports.object({ backup: external_exports.unknown() }));
47480
+ try {
47481
+ return { preview: previewBackupImport(backup) };
47482
+ } catch (e) {
47483
+ throw backupError(e);
47484
+ }
47485
+ });
47486
+ router2.post("/api/settings/restore", async (ctx) => {
47487
+ const { backup, confirm } = await readJsonBody(
47488
+ ctx.req,
47489
+ external_exports.object({ backup: external_exports.unknown(), confirm: external_exports.string() })
47490
+ );
47491
+ if (confirm !== "RESTORE") {
47492
+ throw new HttpError(400, "\uBCF5\uC6D0\uC744 \uC9C4\uD589\uD558\uB824\uBA74 \uD655\uC778 \uAC12\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.", "restore_confirm_required");
47493
+ }
47494
+ try {
47495
+ return restoreBackup(backup);
47496
+ } catch (e) {
47497
+ throw backupError(e);
47498
+ }
47499
+ });
47163
47500
  router2.post("/api/settings/reset", async (ctx) => {
47164
47501
  const { confirm } = await readJsonBody(ctx.req, external_exports.object({ confirm: external_exports.string() }));
47165
47502
  const result2 = resetAll(confirm);
@@ -47187,9 +47524,9 @@ function isDownload(v) {
47187
47524
  }
47188
47525
 
47189
47526
  // apps/web/src/server.ts
47190
- var __dirname = path5.dirname(fileURLToPath(import.meta.url));
47191
- var WEB_ROOT = BUNDLED ? path5.resolve(__dirname, "public") : path5.resolve(__dirname, "..", "public");
47192
- var INSTALL_ROOT = BUNDLED ? path5.resolve(__dirname, "site") : path5.resolve(__dirname, "..", "..", "..", "site");
47527
+ var __dirname2 = path5.dirname(fileURLToPath2(import.meta.url));
47528
+ var WEB_ROOT = BUNDLED ? path5.resolve(__dirname2, "public") : path5.resolve(__dirname2, "..", "public");
47529
+ var INSTALL_ROOT = BUNDLED ? path5.resolve(__dirname2, "site") : path5.resolve(__dirname2, "..", "..", "..", "site");
47193
47530
  var router = new Router();
47194
47531
  registerApiRoutes(router);
47195
47532
  function createServer() {
@@ -47291,11 +47628,11 @@ function openBrowser(url) {
47291
47628
  const platform = process.platform;
47292
47629
  try {
47293
47630
  if (platform === "win32") {
47294
- spawn("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
47631
+ spawn2("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
47295
47632
  } else if (platform === "darwin") {
47296
- spawn("open", [url], { stdio: "ignore", detached: true }).unref();
47633
+ spawn2("open", [url], { stdio: "ignore", detached: true }).unref();
47297
47634
  } else {
47298
- spawn("xdg-open", [url], { stdio: "ignore", detached: true }).unref();
47635
+ spawn2("xdg-open", [url], { stdio: "ignore", detached: true }).unref();
47299
47636
  }
47300
47637
  } catch {
47301
47638
  }
package/docs/FAQ.md CHANGED
@@ -32,10 +32,11 @@ CareerMate는 **Node.js 22.5.0 이상**이 필요합니다(`node:sqlite` 사용)
32
32
  ## 대시보드
33
33
 
34
34
  ### "대시보드가 안 열려요" (`http://127.0.0.1:4319`)
35
- - 그 주소는 **CareerMate가 실행 중일 때만** 열립니다. AI에게 무언가를 저장해 달라고 해
36
- 서버가 떠 있는 상태인지 확인하세요.
35
+ - 그 주소는 **CareerMate가 실행 중일 때만** 열립니다. AI에게 **"대시보드 열어줘"** 라고 하면
36
+ 서버가 꺼져 있을 때도 백그라운드로 자동 시작한 뒤 브라우저를 엽니다.
37
37
  - **포트가 이미 사용 중**이면 자동으로 다른 포트로 폴백합니다. 환경변수
38
38
  `CAREERMATE_PORT`로 고정할 수 있습니다.
39
+ - 자동 시작이 실패하면 `~/.careermate/server.log`를 확인하세요.
39
40
  - 대시보드는 보안상 **`127.0.0.1`(이 컴퓨터)** 에만 열립니다. 다른 기기에서는 접속되지
40
41
  않습니다(의도된 동작).
41
42