careermate 0.1.2 → 0.1.4

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 (77) hide show
  1. package/AGENTS.md +33 -0
  2. package/LICENSE +21 -0
  3. package/README.md +3 -1
  4. package/apps/web/public/pages/documents.js +7 -2
  5. package/apps/web/public/pages/home.js +4 -7
  6. package/apps/web/public/pages/jobs.js +1 -5
  7. package/apps/web/public/pages/profile.js +3 -3
  8. package/apps/web/public/styles.css +7 -2
  9. package/dist/doctor.mjs +112 -90
  10. package/dist/install-page/analytics.js +97 -0
  11. package/dist/install-page/index.html +77 -56
  12. package/dist/install-page/og.png +0 -0
  13. package/dist/install-page/robots.txt +4 -0
  14. package/dist/install-page/sitemap.xml +21 -0
  15. package/dist/install-page/start.html +13 -10
  16. package/dist/install-page/style.css +75 -40
  17. package/dist/mcp.mjs +112 -90
  18. package/dist/migrate.mjs +95 -82
  19. package/dist/public/pages/documents.js +7 -2
  20. package/dist/public/pages/home.js +4 -7
  21. package/dist/public/pages/jobs.js +1 -5
  22. package/dist/public/pages/profile.js +3 -3
  23. package/dist/public/styles.css +7 -2
  24. package/dist/web.mjs +113 -86
  25. package/docs/FAQ.md +101 -0
  26. package/docs/career-os/README.md +76 -0
  27. package/docs/career-os/eop/cover-letter.md +46 -0
  28. package/docs/career-os/eop/fit-analysis.md +48 -0
  29. package/docs/career-os/eop/interview-prep.md +39 -0
  30. package/docs/career-os/eop/job-analysis.md +42 -0
  31. package/docs/career-os/eop/profile-extraction.md +37 -0
  32. package/docs/career-os/knowledge/ats.md +131 -0
  33. package/docs/career-os/knowledge/company-research.md +137 -0
  34. package/docs/career-os/knowledge/cover-letter.md +135 -0
  35. package/docs/career-os/knowledge/fit-matching.md +121 -0
  36. package/docs/career-os/knowledge/human-writing.md +118 -0
  37. package/docs/career-os/knowledge/interview-behavioral.md +132 -0
  38. package/docs/career-os/knowledge/interview-technical.md +126 -0
  39. package/docs/career-os/knowledge/linkedin-profile.md +152 -0
  40. package/docs/career-os/knowledge/networking-referrals.md +166 -0
  41. package/docs/career-os/knowledge/offer-evaluation-decision.md +121 -0
  42. package/docs/career-os/knowledge/onboarding-first-90-days.md +126 -0
  43. package/docs/career-os/knowledge/portfolio.md +121 -0
  44. package/docs/career-os/knowledge/recruiter-screen.md +105 -0
  45. package/docs/career-os/knowledge/rejection-triage-iteration.md +115 -0
  46. package/docs/career-os/knowledge/resume.md +123 -0
  47. package/docs/career-os/knowledge/salary-negotiation.md +123 -0
  48. package/docs/career-os/knowledge/verifiers/ats-compat.md +64 -0
  49. package/docs/career-os/knowledge/verifiers/consistency.md +61 -0
  50. package/docs/career-os/knowledge/verifiers/human-voice.md +82 -0
  51. package/docs/career-os/knowledge/verifiers/recency-staleness.md +72 -0
  52. package/docs/career-os/knowledge/verifiers/responsiveness-on-target.md +82 -0
  53. package/docs/career-os/knowledge/verifiers/truthfulness.md +67 -0
  54. package/package.json +11 -3
  55. package/packages/db/src/repositories.ts +18 -1
  56. package/packages/mcp-tools/src/tools.ts +5 -5
  57. package/packages/parsers/src/documents.ts +13 -1
  58. package/packages/shared/src/schemas.ts +115 -81
  59. package/scripts/build-dist.mjs +7 -1
  60. package/scripts/gen-og.mjs +1 -1
  61. package/scripts/test.ts +25 -0
  62. package/scripts/ui-smoke.ts +32 -4
  63. package/dist/install-page/.omc/state/agent-replay-55a58c0d-bb6e-43ea-8a05-4b6c93b80b34.jsonl +0 -12
  64. package/dist/install-page/.omc/state/idle-notif-cooldown.json +0 -3
  65. package/dist/install-page/.omc/state/sessions/55a58c0d-bb6e-43ea-8a05-4b6c93b80b34/mission-state.json +0 -147
  66. package/dist/install-page/.omc/state/sessions/55a58c0d-bb6e-43ea-8a05-4b6c93b80b34/pre-tool-advisory-throttle.json +0 -22
  67. package/dist/install-page/.omc/state/sessions/55a58c0d-bb6e-43ea-8a05-4b6c93b80b34/subagent-tracking-state.json +0 -44
  68. package/docs/ARCHITECTURE.md +0 -208
  69. package/docs/CHANGES_V1.md +0 -103
  70. package/docs/DATA_MODEL.md +0 -460
  71. package/docs/DECISIONS.md +0 -277
  72. package/docs/DEMO.md +0 -242
  73. package/docs/ROADMAP.md +0 -135
  74. package/docs/RUNTIME_DISTRIBUTION_RESEARCH.md +0 -135
  75. package/docs/TODO.md +0 -57
  76. package/docs/UX_NOTES.md +0 -247
  77. package/docs/superpowers/specs/2026-06-16-demo-dashboard-deploy-design.md +0 -64
package/AGENTS.md ADDED
@@ -0,0 +1,33 @@
1
+ # AGENTS.md — CareerMate 상시 지침 (모든 AI 에이전트 공통)
2
+
3
+ 이 파일은 CareerMate를 사용하는 AI 에이전트의 **상시 지침 단일 원본**입니다. Codex CLI는 이 파일을 자동으로 읽고, **Claude(`CLAUDE.md`)와 Gemini(`GEMINI.md`)는 이 파일을 그대로 가져다 씁니다.** 따라서 공통 규칙은 항상 여기 한 곳만 고치면 됩니다. 당신(AI 에이전트)이 **사용자의 커리어를 함께 관리**하기 위한 규칙입니다.
4
+
5
+ ## CareerMate란
6
+
7
+ CareerMate는 사용자 PC에만 데이터를 두는 **로컬 커리어 작업 공간**입니다. 내부에 LLM이 없는 MCP-우선 도구이며, **AI 서비스가 아니라 "커리어 서랍장"** 입니다. 분석·글쓰기 같은 사고는 모두 **당신이** 하고, CareerMate는 구조화된 커리어 데이터를 저장·제공할 뿐입니다.
8
+
9
+ > 프라임 디렉티브: **두뇌는 당신, 저장은 CareerMate.** 적합도 분석·자기소개서·면접 자료는 당신이 작성하고, 그 입력과 결과는 CareerMate 도구로 가져오고 저장합니다.
10
+
11
+ ## 규칙
12
+
13
+ - **기존 데이터를 항상 먼저 활용하라.** 사용자에게 묻거나 무언가를 새로 만들기 전에, CareerMate를 MCP로 조회한다: `get_onboarding_status`(연결 직후 최우선), `get_application_context`, `list_jobs`, `get_profile`, `get_resumes`, `get_cover_letters`, `list_recent_activity`. 이미 채워진 항목은 다시 묻지 마라.
14
+ - **사용자가 파일을 주면 `read_document`로 읽어라.** 이력서·경력기술서·자소서·채용공고가 PDF·Word(`.docx`)·한컴(`.hwp`/`.hwpx`)·텍스트(`.txt`/`.md`) 파일로 컴퓨터에 있고 사용자가 경로로 가리키면, `read_document({ path })`로 본문 텍스트를 추출한 뒤 그 내용으로 작업하고 결과는 아래 저장 도구로 보관한다(파일 내용을 새 파일로 다시 쓰지 마라). 경로는 가능하면 절대 경로로 준다. 이미지·스캔 PDF는 텍스트 레이어가 없어 추출되지 않을 수 있는데, 그때는 이미지를 직접 볼 수 있는 클라이언트로 읽거나 사용자에게 내용을 텍스트로 붙여넣어 달라고 안내한다.
15
+ - **공고 분석·자소서 작성 전에는 반드시 `get_application_context`를 먼저 호출하라.** 한 번의 호출로 프로필·대표 이력서·경력·스킬·기존 자소서·대상 공고·선호 문체·강조 포인트를 모두 돌려준다. 맥락 없이 작성하지 마라.
16
+ - **결과는 다시 CareerMate에 저장하라.** 분석 → `save_fit_analysis`, 자소서 → `save_cover_letter_version`, 공고 → `save_job_posting`, 면접 자료 → `save_interview_prep`. 커리어 데이터는 흩어진 파일이 아니라 CareerMate 안에 둔다. 임의의 로컬 파일 생성은 최소화하라(사용자가 파일을 원하면 `export_cover_letter`로 `~/.careermate/exports`에 내보낸다).
17
+ - **5개 표준 워크플로우를 따르라**: onboarding, analyze_job, write_cover_letter, manage_application_status, prepare_interview. 순서가 헷갈리면 `get_workflow_guide`를 호출한다.
18
+ - **사람이 쓴 듯한 글**이 필요할 때(자소서·자기 PR·지원 메일)는 작성 직전 `get_writing_style_guide`를 적용한다. 사실·수치·고유명사는 그대로 보존한다.
19
+ - **8단계 지원 상태 게이팅을 존중하라**: `draft`(작성 중) → `planned`(지원 예정) → `applied`(지원 완료) → `document_passed`(서류 합격) → `interview`(면접 진행) → `final_passed`(최종 합격) / `rejected`(불합격) / `on_hold`(보류). 면접 준비는 **`document_passed` 이상**에서만 해금된다. 상태 변경은 `update_application_status`.
20
+ - **결과를 낼 때마다 대시보드로 안내하라.** 공고 저장·적합도 분석·자소서 버전·상태 변경·면접 준비 등 무언가를 저장·완료할 때마다 대시보드(`http://127.0.0.1:4319`) 주소를 알려준다. 브라우저를 직접 여는 건 **첫 온보딩 때 한 번만**(`open_dashboard`) — 이후에는 자동으로 열지 말고 링크만 보여주고, 사용자가 "열어줘"라고 하면 그때 `open_application`(단건)·`open_dashboard`(전체)로 연다.
21
+ - **절대 사용자 데이터를 외부로 업로드하지 마라.** 모든 데이터는 `~/.careermate`(Windows: `%USERPROFILE%\.careermate`)에만 있고, 대시보드는 `127.0.0.1`에만 바인딩된다. CareerMate는 (선택적 버전 확인 외) 네트워크로 사용자 데이터를 보내지 않는다.
22
+ - 기술 용어(MCP, `job_id` 등)는 사용자에게 노출하지 말고, 매 단계 무엇을 하는지 쉬운 한국어로 설명하라. 확실하지 않으면 지어내지 말고 사용자에게 확인하라.
23
+
24
+ ## 아직 연결되지 않았다면
25
+
26
+ CareerMate MCP 도구가 보이지 않으면(예: `get_onboarding_status` 호출 불가) **먼저 설치·등록부터** 한다. **클라이언트별 등록 방법은 [`./INSTALL.md`](INSTALL.md)** 에 있다(Claude Code/Desktop, Codex, Gemini 등). 요지: 해당 클라이언트의 MCP 설정에 stdio 서버로 등록 → AI 클라이언트를 **완전히 재시작** → `get_onboarding_status`를 한 번 호출해 응답이 오는지 확인한다.
27
+
28
+ ## 더 보기
29
+
30
+ - [`./INSTALL.md`](INSTALL.md) — 설치·등록 런북(클라이언트별 등록 방법)
31
+ - [`docs/START_WORKFLOW.md`](docs/START_WORKFLOW.md) — 연결 직후 사용자를 빠르게 생산적으로 만드는 단계별 런북
32
+ - [`docs/MCP_TOOLS.md`](docs/MCP_TOOLS.md) — MCP 도구 레퍼런스(입력·반환·호출 시점)
33
+ - [`docs/WORKFLOWS.md`](docs/WORKFLOWS.md) — 5개 표준 워크플로우 상세
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CareerMate
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -240,7 +240,9 @@ CareerMate/
240
240
  - [`AGENTS.md`](AGENTS.md) — Codex용 영속 지침. / [`CLAUDE.md`](CLAUDE.md) — Claude Code용 영속 지침.
241
241
  - [`docs/SUPPORTED_AI_APPS.md`](docs/SUPPORTED_AI_APPS.md) — **설치 레퍼런스 허브**: 지원 앱 매트릭스 + 설치 3방식(에이전트/npx/.mcpb) + 클라이언트별 수동 MCP 설정 + 알려진 제약. (사람용 안내는 https://careermate.life)
242
242
  - [`docs/START_WORKFLOW.md`](docs/START_WORKFLOW.md) — 등록부터 면접 준비까지 단계별 작업 런북.
243
+ - [`docs/FAQ.md`](docs/FAQ.md) — **자주 묻는 질문 / 문제 해결**: "PDF가 안 읽혀요", "분석이 안 돼요", "공고 URL은 어떻게 넣어요" 등.
243
244
  - [`docs/TODO.md`](docs/TODO.md) — 남은 작업과 v1 범위 밖 항목.
245
+ - [`CHANGELOG.md`](CHANGELOG.md) — 버전별 변경 이력. / [`CONTRIBUTING.md`](CONTRIBUTING.md) · [`SECURITY.md`](SECURITY.md) · [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) — 기여·보안·행동 강령.
244
246
 
245
247
  ---
246
248
 
@@ -258,4 +260,4 @@ npm run typecheck
258
260
 
259
261
  ## 라이선스
260
262
 
261
- MIT 라이선스 — npm으로 배포되는 패키지 코드에 적용됩니다. (개발용 GitHub 저장소는 현재 비공개입니다.)
263
+ MIT 라이선스 — 전문은 [`LICENSE`](LICENSE) 파일을 참고하세요. (개발용 GitHub 저장소는 현재 비공개입니다.)
@@ -23,6 +23,7 @@ export async function render(ctx) {
23
23
  root.append(el('div', { class: 'tabs' }, tabCover, tabDocs), panel);
24
24
 
25
25
  let query = '';
26
+ let searchInput = null;
26
27
  function selectTab(next) {
27
28
  tab = next;
28
29
  query = '';
@@ -43,9 +44,12 @@ export async function render(ctx) {
43
44
  if (!hide) shown += 1;
44
45
  }
45
46
  const existing = panel.querySelector('.search-empty');
47
+ if (existing) existing.remove();
46
48
  if (q && cards.length && shown === 0) {
47
- if (!existing) panel.append(el('p', { class: 'search-empty muted', style: { textAlign: 'center', margin: '8px 0' } }, '검색 결과가 없어요.'));
48
- } else if (existing) existing.remove();
49
+ panel.append(el('div', { class: 'search-empty empty' },
50
+ el('p', { class: 'muted', style: { margin: '0 0 12px' } }, `‘${query.trim()}’에 맞는 항목이 없어요.`),
51
+ Btn('필터 초기화', { sm: true, variant: 'ghost', onClick: () => { query = ''; if (searchInput) searchInput.value = ''; applyFilter(); } })));
52
+ }
49
53
  }
50
54
 
51
55
  async function reload() {
@@ -70,6 +74,7 @@ export async function render(ctx) {
70
74
  function setActions() {
71
75
  const search = Input({ type: 'search', placeholder: tab === 'cover' ? '자기소개서 검색' : '문서 검색', value: query, attrs: { 'aria-label': '검색' } });
72
76
  search.classList.add('input--inline');
77
+ searchInput = search;
73
78
  search.addEventListener('input', () => { query = search.value; applyFilter(); });
74
79
  const createBtn = tab === 'cover'
75
80
  ? Btn('새 자기소개서', { icon: 'plus', variant: 'primary', onClick: () => openCoverCreate(reload) })
@@ -4,7 +4,7 @@
4
4
  // the 시작하기 card. Every empty state is ONE screen-specific sentence; never repeat
5
5
  // the same "AI에게 보내면 저장됩니다" instruction across surfaces.
6
6
  import {
7
- el, get, icon, navigate, Card, Badge, Btn, EmptyState, ListRow, CheckRow,
7
+ el, get, icon, navigate, Card, Badge, Btn, ListRow, CheckRow,
8
8
  Stat, PageHead, fmtDate, fmtRelative, scoreClass, STATUS_COLOR, statusColor, mount,
9
9
  } from '/lib.js';
10
10
 
@@ -59,7 +59,7 @@ function Greeting(name, s, firstRun) {
59
59
  let focus;
60
60
  if (firstRun) focus = '시작해 볼까요 — 먼저 기본 정보를 채워 주세요.';
61
61
  else if (s.interview_todo.length) focus = `면접 준비가 필요한 지원 ${s.interview_todo.length}건이 있어요.`;
62
- else if (s.in_progress.length) focus = `진행 중인 지원 ${s.in_progress.length}건을 확인하세요.`;
62
+ else if (s.in_progress.length) focus = `진행 중인 지원 ${s.in_progress.length}건이 있어요.`;
63
63
  else if (s.counts.jobs) focus = `저장된 공고 ${s.counts.jobs}건 · 새 적합도 분석을 받아보세요.`;
64
64
  else focus = '오늘도 한 걸음 나아가 볼까요.';
65
65
  return PageHead({ title: name ? `${name}님, 안녕하세요` : '환영합니다', desc: focus });
@@ -87,7 +87,7 @@ function GettingStarted(o) {
87
87
  el('div', { class: 'callout__body' },
88
88
  '공고 분석·자기소개서·면접 준비는 평소 쓰던 AI에게 말로 시키면, 결과가 이 대시보드에 쌓여요.'))),
89
89
  el('div', { class: 'progress' }, el('div', { class: 'progress__bar', style: { width: `${o.profile_completeness}%` } })),
90
- el('p', { class: 'text-secondary text-sm', style: { margin: '12px 0 8px' } },
90
+ el('p', { class: 'text-secondary text-sm mt-3 mb-2' },
91
91
  '완성할수록 AI의 적합도 분석과 자기소개서 품질이 좋아져요.'),
92
92
  el('div', {}, ...steps.map((it) => CheckRow({ done: it.done, label: it.label, onClick: () => navigate(it.to) }))),
93
93
  ],
@@ -201,10 +201,7 @@ function RecentJobs(jobs) {
201
201
  if (!jobs.length) {
202
202
  return Card({
203
203
  title: '최근 공고',
204
- body: EmptyState({
205
- iconName: 'briefcase', title: '저장된 공고가 아직 없어요',
206
- action: Btn('공고 추가', { icon: 'plus', variant: 'ghost', onClick: () => navigate('/jobs') }),
207
- }),
204
+ body: el('p', { class: 'muted', style: { margin: 0 } }, '저장된 공고가 아직 없어요. 새 공고를 추가해 보세요.'),
208
205
  });
209
206
  }
210
207
  const rows = jobs.map((j) => ListRow({
@@ -365,11 +365,7 @@ function FitCard(fit) {
365
365
  if (!fit) {
366
366
  return Card({
367
367
  title: '적합도 분석',
368
- body: EmptyState({
369
- iconName: 'target',
370
- title: '적합도 분석 전이에요',
371
- body: '이 공고와 내 프로필을 비교한 분석 결과가 여기에 표시됩니다.',
372
- }),
368
+ body: el('p', { class: 'muted', style: { margin: 0 } }, '아직 분석 전이에요. 이 공고와 내 프로필을 비교한 결과가 여기에 표시됩니다.'),
373
369
  });
374
370
  }
375
371
 
@@ -264,7 +264,7 @@ function editWriting(p, reload) {
264
264
  /* ---------------------------------------------------- 4. 경력 (Experiences) */
265
265
 
266
266
  function ExperiencesCard(items, reload) {
267
- const add = Btn('추가', { icon: 'plus', sm: true, variant: 'ghost', onClick: () => editExperience(null, reload) });
267
+ const add = Btn('경력 추가', { icon: 'plus', sm: true, variant: 'ghost', onClick: () => editExperience(null, reload) });
268
268
 
269
269
  if (!items.length) {
270
270
  return Card({
@@ -368,7 +368,7 @@ function editExperience(x, reload) {
368
368
  /* ----------------------------------------------------- 5. 프로젝트 (Projects) */
369
369
 
370
370
  function ProjectsCard(items, reload) {
371
- const add = Btn('추가', { icon: 'plus', sm: true, variant: 'ghost', onClick: () => editProject(null, reload) });
371
+ const add = Btn('프로젝트 추가', { icon: 'plus', sm: true, variant: 'ghost', onClick: () => editProject(null, reload) });
372
372
 
373
373
  if (!items.length) {
374
374
  return Card({
@@ -469,7 +469,7 @@ function editProject(x, reload) {
469
469
  /* -------------------------------------------------------- 6. 기술 (Skills) */
470
470
 
471
471
  function SkillsCard(items, reload) {
472
- const add = Btn('추가', { icon: 'plus', sm: true, variant: 'ghost', onClick: () => editSkill(null, reload) });
472
+ const add = Btn('기술 추가', { icon: 'plus', sm: true, variant: 'ghost', onClick: () => editSkill(null, reload) });
473
473
 
474
474
  if (!items.length) {
475
475
  return Card({
@@ -403,7 +403,9 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
403
403
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 22px 26px; max-height: 60vh; overflow-y: auto; }
404
404
  .kv { display: grid; grid-template-columns: 132px 1fr; gap: 8px 16px; }
405
405
  .kv dt { color: var(--text-tertiary); font-size: 13px; }
406
- .kv dd { margin: 0; }
406
+ /* min-width:0 overrides a grid item's default min-width:auto so the 1fr column
407
+ can shrink below its content — otherwise a long path/value overflows the grid. */
408
+ .kv dd { margin: 0; min-width: 0; }
407
409
 
408
410
  /* ----------------------------------------------------------------- modal */
409
411
  .modal-root { position: fixed; inset: 0; z-index: 60; display: none; }
@@ -450,7 +452,10 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
450
452
  .between { justify-content: space-between; } .center { align-items: center; } .wrap { flex-wrap: wrap; }
451
453
  .ml-auto { margin-left: auto; } .mt-2{margin-top:8px;} .mt-3{margin-top:12px;} .mt-4{margin-top:16px;} .mb-2{margin-bottom:8px;} .mb-3{margin-bottom:12px;} .mb-4{margin-bottom:16px;}
452
454
  .tnum { font-variant-numeric: tabular-nums; }
453
- .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
455
+ /* min-width:0 lets a truncating element actually shrink inside a flex row;
456
+ without it a long unbreakable value (e.g. a data-folder path) forces the
457
+ flex item to its content width and overflows the viewport on narrow screens. */
458
+ .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; min-width: 0; }
454
459
  .divider { height: 1px; background: var(--border); margin: 16px 0; }
455
460
  .skeleton { background: linear-gradient(90deg, var(--bg-sunken) 25%, var(--surface-hover) 37%, var(--bg-sunken) 63%); background-size: 400% 100%; animation: shimmer 1.4s infinite; border-radius: 6px; }
456
461
  @keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
package/dist/doctor.mjs CHANGED
@@ -75561,29 +75561,42 @@ function isSafeUrl(u) {
75561
75561
  return !/^[a-z][a-z0-9+.-]*:/i.test(s) || /^(https?:|mailto:)/i.test(s);
75562
75562
  }
75563
75563
  var UNSAFE_URL_MSG = "javascript: \uAC19\uC740 \uC704\uD5D8\uD55C \uB9C1\uD06C\uB294 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. http(s) \uB9C1\uD06C\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.";
75564
- var SafeUrl = external_exports.string().min(1).refine(isSafeUrl, { message: UNSAFE_URL_MSG });
75565
- var SafeUrlOptional = external_exports.string().optional().refine((u) => u == null || u === "" || isSafeUrl(u), { message: UNSAFE_URL_MSG });
75564
+ var MAX_URL = 2e3;
75565
+ var SafeUrl = external_exports.string().min(1).max(MAX_URL).refine(isSafeUrl, { message: UNSAFE_URL_MSG });
75566
+ var SafeUrlOptional = external_exports.string().max(MAX_URL).optional().refine((u) => u == null || u === "" || isSafeUrl(u), { message: UNSAFE_URL_MSG });
75567
+ var MAX_LINE = 500;
75568
+ var MAX_NOTE = 1e4;
75569
+ var MAX_BODY = 2e5;
75570
+ var MAX_ITEM = 2e3;
75571
+ var MAX_ITEMS = 500;
75572
+ var EMPTY_MSG = "\uB0B4\uC6A9\uC774 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4(\uACF5\uBC31\uB9CC\uC73C\uB85C\uB294 \uC800\uC7A5\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4).";
75573
+ var reqLine = external_exports.string().trim().min(1).max(MAX_LINE);
75574
+ var optLine = external_exports.string().max(MAX_LINE).optional();
75575
+ var optNote = external_exports.string().max(MAX_NOTE).optional();
75576
+ var optBody = external_exports.string().max(MAX_BODY).optional();
75577
+ var reqBody = external_exports.string().max(MAX_BODY).refine((s) => s.trim().length > 0, { message: EMPTY_MSG });
75578
+ var strList = external_exports.array(external_exports.string().max(MAX_ITEM)).max(MAX_ITEMS);
75566
75579
  var baseRecord = {
75567
75580
  id,
75568
75581
  created_at: isoDateTime,
75569
75582
  updated_at: isoDateTime
75570
75583
  };
75571
75584
  var LinkSchema = external_exports.object({
75572
- label: external_exports.string().min(1),
75585
+ label: reqLine,
75573
75586
  url: SafeUrl
75574
75587
  });
75575
75588
  var ProfileInputSchema = external_exports.object({
75576
- name: external_exports.string().min(1).optional(),
75577
- email: external_exports.string().optional(),
75578
- phone: external_exports.string().optional(),
75579
- location: external_exports.string().optional(),
75580
- headline: external_exports.string().optional().describe("\uD55C \uC904 \uC18C\uAC1C / \uC9C1\uBB34 \uD0C0\uC774\uD2C0"),
75581
- summary: external_exports.string().optional().describe("\uC790\uAE30\uC18C\uAC1C \uC694\uC57D"),
75582
- desired_roles: external_exports.array(external_exports.string()).optional().describe("\uD76C\uB9DD \uC9C1\uBB34"),
75583
- desired_conditions: external_exports.string().optional().describe("\uD76C\uB9DD \uADFC\uBB34 \uC870\uAC74 (\uC5F0\uBD09, \uC9C0\uC5ED, \uADFC\uBB34\uD615\uD0DC \uB4F1)"),
75584
- preferred_tone: external_exports.string().optional().describe("\uC790\uAE30\uC18C\uAC1C\uC11C \uC120\uD638 \uBB38\uCCB4 (\uC608: \uB2F4\uBC31\uD558\uACE0 \uAD6C\uCCB4\uC801)"),
75585
- emphasis_points: external_exports.array(external_exports.string()).optional().describe("\uAC15\uC870\uD558\uACE0 \uC2F6\uC740 \uD575\uC2EC \uD3EC\uC778\uD2B8"),
75586
- links: external_exports.array(LinkSchema).optional().describe("\uD3EC\uD2B8\uD3F4\uB9AC\uC624/\uAE43\uD5C8\uBE0C/\uB9C1\uD06C\uB4DC\uC778 \uB4F1")
75589
+ name: reqLine.optional(),
75590
+ email: optLine,
75591
+ phone: optLine,
75592
+ location: optLine,
75593
+ headline: external_exports.string().max(MAX_LINE).optional().describe("\uD55C \uC904 \uC18C\uAC1C / \uC9C1\uBB34 \uD0C0\uC774\uD2C0"),
75594
+ summary: external_exports.string().max(MAX_NOTE).optional().describe("\uC790\uAE30\uC18C\uAC1C \uC694\uC57D"),
75595
+ desired_roles: strList.optional().describe("\uD76C\uB9DD \uC9C1\uBB34"),
75596
+ desired_conditions: external_exports.string().max(MAX_NOTE).optional().describe("\uD76C\uB9DD \uADFC\uBB34 \uC870\uAC74 (\uC5F0\uBD09, \uC9C0\uC5ED, \uADFC\uBB34\uD615\uD0DC \uB4F1)"),
75597
+ preferred_tone: external_exports.string().max(MAX_LINE).optional().describe("\uC790\uAE30\uC18C\uAC1C\uC11C \uC120\uD638 \uBB38\uCCB4 (\uC608: \uB2F4\uBC31\uD558\uACE0 \uAD6C\uCCB4\uC801)"),
75598
+ emphasis_points: strList.optional().describe("\uAC15\uC870\uD558\uACE0 \uC2F6\uC740 \uD575\uC2EC \uD3EC\uC778\uD2B8"),
75599
+ links: external_exports.array(LinkSchema).max(MAX_ITEMS).optional().describe("\uD3EC\uD2B8\uD3F4\uB9AC\uC624/\uAE43\uD5C8\uBE0C/\uB9C1\uD06C\uB4DC\uC778 \uB4F1")
75587
75600
  });
75588
75601
  var ProfileRecordSchema = external_exports.object({
75589
75602
  ...baseRecord,
@@ -75600,15 +75613,15 @@ var ProfileRecordSchema = external_exports.object({
75600
75613
  links: external_exports.array(LinkSchema)
75601
75614
  });
75602
75615
  var ExperienceInputSchema = external_exports.object({
75603
- company: external_exports.string().min(1),
75604
- role: external_exports.string().optional(),
75605
- employment_type: external_exports.string().optional().describe("\uC815\uADDC\uC9C1/\uACC4\uC57D\uC9C1/\uC778\uD134/\uD504\uB9AC\uB79C\uC11C \uB4F1"),
75606
- start_date: external_exports.string().optional().describe("YYYY-MM \uB610\uB294 YYYY-MM-DD"),
75607
- end_date: external_exports.string().optional().describe("\uC7AC\uC9C1 \uC911\uC774\uBA74 \uBE44\uC6CC\uB450\uAE30"),
75616
+ company: reqLine,
75617
+ role: optLine,
75618
+ employment_type: external_exports.string().max(MAX_LINE).optional().describe("\uC815\uADDC\uC9C1/\uACC4\uC57D\uC9C1/\uC778\uD134/\uD504\uB9AC\uB79C\uC11C \uB4F1"),
75619
+ start_date: external_exports.string().max(MAX_LINE).optional().describe("YYYY-MM \uB610\uB294 YYYY-MM-DD"),
75620
+ end_date: external_exports.string().max(MAX_LINE).optional().describe("\uC7AC\uC9C1 \uC911\uC774\uBA74 \uBE44\uC6CC\uB450\uAE30"),
75608
75621
  is_current: external_exports.boolean().optional(),
75609
- description: external_exports.string().optional(),
75610
- achievements: external_exports.array(external_exports.string()).optional().describe("\uC131\uACFC/\uC5C5\uC801 (\uC815\uB7C9 \uC9C0\uD45C \uAD8C\uC7A5)"),
75611
- tech: external_exports.array(external_exports.string()).optional().describe("\uC0AC\uC6A9 \uAE30\uC220/\uB3C4\uAD6C"),
75622
+ description: optBody,
75623
+ achievements: strList.optional().describe("\uC131\uACFC/\uC5C5\uC801 (\uC815\uB7C9 \uC9C0\uD45C \uAD8C\uC7A5)"),
75624
+ tech: strList.optional().describe("\uC0AC\uC6A9 \uAE30\uC220/\uB3C4\uAD6C"),
75612
75625
  order_index: external_exports.number().optional()
75613
75626
  });
75614
75627
  var ExperienceRecordSchema = external_exports.object({
@@ -75625,14 +75638,14 @@ var ExperienceRecordSchema = external_exports.object({
75625
75638
  order_index: external_exports.number()
75626
75639
  });
75627
75640
  var ProjectInputSchema = external_exports.object({
75628
- name: external_exports.string().min(1),
75629
- role: external_exports.string().optional(),
75630
- description: external_exports.string().optional(),
75631
- highlights: external_exports.array(external_exports.string()).optional(),
75632
- tech: external_exports.array(external_exports.string()).optional(),
75641
+ name: reqLine,
75642
+ role: optLine,
75643
+ description: optBody,
75644
+ highlights: strList.optional(),
75645
+ tech: strList.optional(),
75633
75646
  url: SafeUrlOptional,
75634
- start_date: external_exports.string().optional(),
75635
- end_date: external_exports.string().optional(),
75647
+ start_date: optLine,
75648
+ end_date: optLine,
75636
75649
  order_index: external_exports.number().optional()
75637
75650
  });
75638
75651
  var ProjectRecordSchema = external_exports.object({
@@ -75648,9 +75661,9 @@ var ProjectRecordSchema = external_exports.object({
75648
75661
  order_index: external_exports.number()
75649
75662
  });
75650
75663
  var SkillInputSchema = external_exports.object({
75651
- name: external_exports.string().min(1),
75652
- category: external_exports.string().optional().describe("\uC5B8\uC5B4/\uD504\uB808\uC784\uC6CC\uD06C/\uD234/\uC18C\uD504\uD2B8\uC2A4\uD0AC \uB4F1"),
75653
- level: external_exports.string().optional().describe("\uC0C1/\uC911/\uD558 \uB610\uB294 \uC790\uC720 \uC11C\uC220"),
75664
+ name: reqLine,
75665
+ category: external_exports.string().max(MAX_LINE).optional().describe("\uC5B8\uC5B4/\uD504\uB808\uC784\uC6CC\uD06C/\uD234/\uC18C\uD504\uD2B8\uC2A4\uD0AC \uB4F1"),
75666
+ level: external_exports.string().max(MAX_LINE).optional().describe("\uC0C1/\uC911/\uD558 \uB610\uB294 \uC790\uC720 \uC11C\uC220"),
75654
75667
  years: external_exports.number().optional(),
75655
75668
  order_index: external_exports.number().optional()
75656
75669
  });
@@ -75664,11 +75677,11 @@ var SkillRecordSchema = external_exports.object({
75664
75677
  });
75665
75678
  var DocumentInputSchema = external_exports.object({
75666
75679
  kind: external_exports.enum(DOCUMENT_KINDS),
75667
- title: external_exports.string().min(1),
75668
- content: external_exports.string().describe("Markdown \uB610\uB294 \uC77C\uBC18 \uD14D\uC2A4\uD2B8 \uBCF8\uBB38"),
75680
+ title: reqLine,
75681
+ content: reqBody.describe("Markdown \uB610\uB294 \uC77C\uBC18 \uD14D\uC2A4\uD2B8 \uBCF8\uBB38"),
75669
75682
  source: external_exports.enum(CONTENT_SOURCES).optional(),
75670
75683
  is_primary: external_exports.boolean().optional().describe("\uB300\uD45C \uBB38\uC11C \uC5EC\uBD80"),
75671
- tags: external_exports.array(external_exports.string()).optional()
75684
+ tags: strList.optional()
75672
75685
  });
75673
75686
  var DocumentRecordSchema = external_exports.object({
75674
75687
  ...baseRecord,
@@ -75680,19 +75693,19 @@ var DocumentRecordSchema = external_exports.object({
75680
75693
  tags: external_exports.array(external_exports.string())
75681
75694
  });
75682
75695
  var CoverLetterInputSchema = external_exports.object({
75683
- title: external_exports.string().min(1),
75684
- job_id: external_exports.string().optional().describe("\uD2B9\uC815 \uACF5\uACE0\uC5D0 \uC5F0\uACB0\uD560 \uACBD\uC6B0"),
75696
+ title: reqLine,
75697
+ job_id: optLine.describe("\uD2B9\uC815 \uACF5\uACE0\uC5D0 \uC5F0\uACB0\uD560 \uACBD\uC6B0"),
75685
75698
  is_primary: external_exports.boolean().optional(),
75686
- content: external_exports.string().optional().describe("\uCD08\uAE30 \uBC84\uC804 \uBCF8\uBB38 (\uC788\uC73C\uBA74 v1\uB85C \uC800\uC7A5)"),
75699
+ content: optBody.describe("\uCD08\uAE30 \uBC84\uC804 \uBCF8\uBB38 (\uC788\uC73C\uBA74 v1\uB85C \uC800\uC7A5)"),
75687
75700
  source: external_exports.enum(CONTENT_SOURCES).optional(),
75688
- note: external_exports.string().optional().describe("\uCD08\uAE30 \uBC84\uC804 \uBA54\uBAA8")
75701
+ note: optNote.describe("\uCD08\uAE30 \uBC84\uC804 \uBA54\uBAA8")
75689
75702
  });
75690
75703
  var CoverLetterVersionInputSchema = external_exports.object({
75691
- cover_letter_id: external_exports.string().optional().describe("\uAE30\uC874 \uC790\uAE30\uC18C\uAC1C\uC11C\uC5D0 \uC0C8 \uBC84\uC804 \uCD94\uAC00 \uC2DC"),
75692
- title: external_exports.string().optional().describe("\uC0C8 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uB9CC\uB4E4 \uB54C \uC81C\uBAA9"),
75693
- job_id: external_exports.string().optional(),
75694
- content: external_exports.string().min(1),
75695
- note: external_exports.string().optional().describe("\uC774 \uBC84\uC804\uC758 \uBCC0\uACBD \uC694\uC57D (\uC608: \uC9C0\uC6D0\uB3D9\uAE30 \uBCF4\uAC15)"),
75704
+ cover_letter_id: optLine.describe("\uAE30\uC874 \uC790\uAE30\uC18C\uAC1C\uC11C\uC5D0 \uC0C8 \uBC84\uC804 \uCD94\uAC00 \uC2DC"),
75705
+ title: external_exports.string().max(MAX_LINE).optional().describe("\uC0C8 \uC790\uAE30\uC18C\uAC1C\uC11C\uB97C \uB9CC\uB4E4 \uB54C \uC81C\uBAA9"),
75706
+ job_id: optLine,
75707
+ content: reqBody,
75708
+ note: optNote.describe("\uC774 \uBC84\uC804\uC758 \uBCC0\uACBD \uC694\uC57D (\uC608: \uC9C0\uC6D0\uB3D9\uAE30 \uBCF4\uAC15)"),
75696
75709
  source: external_exports.enum(CONTENT_SOURCES).optional(),
75697
75710
  set_current: external_exports.boolean().optional().describe("\uC774 \uBC84\uC804\uC744 \uD604\uC7AC \uBC84\uC804\uC73C\uB85C \uC9C0\uC815 (\uAE30\uBCF8 true)")
75698
75711
  });
@@ -75716,16 +75729,16 @@ var CoverLetterRecordSchema = external_exports.object({
75716
75729
  versions: external_exports.array(CoverLetterVersionRecordSchema).optional()
75717
75730
  });
75718
75731
  var JobInputSchema = external_exports.object({
75719
- company: external_exports.string().min(1),
75720
- position: external_exports.string().min(1).describe("\uC9C1\uBB34/\uD3EC\uC9C0\uC158\uBA85"),
75732
+ company: reqLine,
75733
+ position: reqLine.describe("\uC9C1\uBB34/\uD3EC\uC9C0\uC158\uBA85"),
75721
75734
  url: SafeUrlOptional,
75722
- location: external_exports.string().optional(),
75723
- employment_type: external_exports.string().optional(),
75724
- description: external_exports.string().optional().describe("\uACF5\uACE0 \uC6D0\uBB38 \uB610\uB294 \uC815\uB9AC\uB41C \uD14D\uC2A4\uD2B8"),
75725
- requirements: external_exports.array(external_exports.string()).optional().describe("\uC790\uACA9\uC694\uAC74/\uC6B0\uB300\uC0AC\uD56D \uD575\uC2EC"),
75726
- keywords: external_exports.array(external_exports.string()).optional().describe("\uD575\uC2EC \uD0A4\uC6CC\uB4DC"),
75727
- deadline: external_exports.string().optional().describe("\uB9C8\uAC10\uC77C YYYY-MM-DD"),
75728
- source: external_exports.string().optional().describe("\uCD9C\uCC98 (\uC0AC\uB78C\uC778/\uC6D0\uD2F0\uB4DC/\uC9C1\uC811 \uC785\uB825 \uB4F1)")
75735
+ location: optLine,
75736
+ employment_type: optLine,
75737
+ description: optBody.describe("\uACF5\uACE0 \uC6D0\uBB38 \uB610\uB294 \uC815\uB9AC\uB41C \uD14D\uC2A4\uD2B8"),
75738
+ requirements: strList.optional().describe("\uC790\uACA9\uC694\uAC74/\uC6B0\uB300\uC0AC\uD56D \uD575\uC2EC"),
75739
+ keywords: strList.optional().describe("\uD575\uC2EC \uD0A4\uC6CC\uB4DC"),
75740
+ deadline: external_exports.string().max(MAX_LINE).optional().describe("\uB9C8\uAC10\uC77C YYYY-MM-DD"),
75741
+ source: external_exports.string().max(MAX_LINE).optional().describe("\uCD9C\uCC98 (\uC0AC\uB78C\uC778/\uC6D0\uD2F0\uB4DC/\uC9C1\uC811 \uC785\uB825 \uB4F1)")
75729
75742
  });
75730
75743
  var JobRecordSchema = external_exports.object({
75731
75744
  ...baseRecord,
@@ -75741,14 +75754,14 @@ var JobRecordSchema = external_exports.object({
75741
75754
  source: external_exports.string().nullable()
75742
75755
  });
75743
75756
  var FitAnalysisInputSchema = external_exports.object({
75744
- job_id: external_exports.string().describe("\uBD84\uC11D \uB300\uC0C1 \uACF5\uACE0 ID"),
75757
+ job_id: reqLine.describe("\uBD84\uC11D \uB300\uC0C1 \uACF5\uACE0 ID"),
75745
75758
  score: external_exports.number().min(0).max(100).optional().describe("\uC885\uD569 \uC801\uD569\uB3C4 0~100"),
75746
- summary: external_exports.string().optional().describe("\uD55C\uB450 \uBB38\uB2E8 \uC694\uC57D"),
75747
- strengths: external_exports.array(external_exports.string()).optional().describe("\uAC15\uC810 / \uC798 \uB9DE\uB294 \uBD80\uBD84"),
75748
- gaps: external_exports.array(external_exports.string()).optional().describe("\uBD80\uC871\uD55C \uBD80\uBD84 / \uBCF4\uC644 \uD544\uC694"),
75749
- matched_keywords: external_exports.array(external_exports.string()).optional(),
75750
- missing_keywords: external_exports.array(external_exports.string()).optional(),
75751
- recommendations: external_exports.array(external_exports.string()).optional().describe("\uC790\uAE30\uC18C\uAC1C\uC11C/\uC9C0\uC6D0 \uC804\uB7B5 \uC81C\uC548")
75759
+ summary: external_exports.string().max(MAX_NOTE).optional().describe("\uD55C\uB450 \uBB38\uB2E8 \uC694\uC57D"),
75760
+ strengths: strList.optional().describe("\uAC15\uC810 / \uC798 \uB9DE\uB294 \uBD80\uBD84"),
75761
+ gaps: strList.optional().describe("\uBD80\uC871\uD55C \uBD80\uBD84 / \uBCF4\uC644 \uD544\uC694"),
75762
+ matched_keywords: strList.optional(),
75763
+ missing_keywords: strList.optional(),
75764
+ recommendations: strList.optional().describe("\uC790\uAE30\uC18C\uAC1C\uC11C/\uC9C0\uC6D0 \uC804\uB7B5 \uC81C\uC548")
75752
75765
  });
75753
75766
  var FitAnalysisRecordSchema = external_exports.object({
75754
75767
  ...baseRecord,
@@ -75762,18 +75775,18 @@ var FitAnalysisRecordSchema = external_exports.object({
75762
75775
  recommendations: external_exports.array(external_exports.string())
75763
75776
  });
75764
75777
  var ApplicationInputSchema = external_exports.object({
75765
- job_id: external_exports.string(),
75778
+ job_id: reqLine,
75766
75779
  status: external_exports.enum(APPLICATION_STATUSES).optional(),
75767
- resume_id: external_exports.string().optional(),
75768
- cover_letter_id: external_exports.string().optional(),
75769
- applied_at: external_exports.string().optional(),
75770
- notes: external_exports.string().optional()
75780
+ resume_id: optLine,
75781
+ cover_letter_id: optLine,
75782
+ applied_at: optLine,
75783
+ notes: optNote
75771
75784
  });
75772
75785
  var ApplicationStatusUpdateSchema = external_exports.object({
75773
- job_id: external_exports.string().optional(),
75774
- application_id: external_exports.string().optional(),
75786
+ job_id: optLine,
75787
+ application_id: optLine,
75775
75788
  status: external_exports.enum(APPLICATION_STATUSES),
75776
- note: external_exports.string().optional().describe("\uC0C1\uD0DC \uBCC0\uACBD \uC0AC\uC720/\uBA54\uBAA8")
75789
+ note: optNote.describe("\uC0C1\uD0DC \uBCC0\uACBD \uC0AC\uC720/\uBA54\uBAA8")
75777
75790
  });
75778
75791
  var ApplicationRecordSchema = external_exports.object({
75779
75792
  ...baseRecord,
@@ -75785,24 +75798,24 @@ var ApplicationRecordSchema = external_exports.object({
75785
75798
  notes: external_exports.string().nullable()
75786
75799
  });
75787
75800
  var StarGuideSchema = external_exports.object({
75788
- question: external_exports.string(),
75789
- situation: external_exports.string().optional(),
75790
- task: external_exports.string().optional(),
75791
- action: external_exports.string().optional(),
75792
- result: external_exports.string().optional()
75801
+ question: external_exports.string().max(MAX_NOTE),
75802
+ situation: optNote,
75803
+ task: optNote,
75804
+ action: optNote,
75805
+ result: optNote
75793
75806
  });
75794
75807
  var InterviewQuestionSchema = external_exports.object({
75795
- question: external_exports.string(),
75796
- intent: external_exports.string().optional().describe("\uC9C8\uBB38 \uC758\uB3C4"),
75797
- followups: external_exports.array(external_exports.string()).optional().describe("\uC608\uC0C1 \uAF2C\uB9AC \uC9C8\uBB38"),
75798
- answer_outline: external_exports.string().optional().describe("\uB2F5\uBCC0 \uAC00\uC774\uB4DC/\uD575\uC2EC \uD3EC\uC778\uD2B8")
75808
+ question: external_exports.string().max(MAX_NOTE),
75809
+ intent: external_exports.string().max(MAX_NOTE).optional().describe("\uC9C8\uBB38 \uC758\uB3C4"),
75810
+ followups: strList.optional().describe("\uC608\uC0C1 \uAF2C\uB9AC \uC9C8\uBB38"),
75811
+ answer_outline: optBody.describe("\uB2F5\uBCC0 \uAC00\uC774\uB4DC/\uD575\uC2EC \uD3EC\uC778\uD2B8")
75799
75812
  });
75800
75813
  var InterviewPrepInputSchema = external_exports.object({
75801
- job_id: external_exports.string(),
75802
- questions: external_exports.array(InterviewQuestionSchema).optional(),
75803
- star_guides: external_exports.array(StarGuideSchema).optional(),
75804
- self_introduction: external_exports.string().optional().describe("1\uBD84 \uC790\uAE30\uC18C\uAC1C \uCD08\uC548"),
75805
- notes: external_exports.string().optional().describe("\uBA74\uC811 \uD6C4\uAE30/\uBA54\uBAA8")
75814
+ job_id: reqLine,
75815
+ questions: external_exports.array(InterviewQuestionSchema).max(MAX_ITEMS).optional(),
75816
+ star_guides: external_exports.array(StarGuideSchema).max(MAX_ITEMS).optional(),
75817
+ self_introduction: optBody.describe("1\uBD84 \uC790\uAE30\uC18C\uAC1C \uCD08\uC548"),
75818
+ notes: optNote.describe("\uBA74\uC811 \uD6C4\uAE30/\uBA54\uBAA8")
75806
75819
  });
75807
75820
  var InterviewPrepRecordSchema = external_exports.object({
75808
75821
  ...baseRecord,
@@ -75856,7 +75869,7 @@ var ApplicationContextSchema = external_exports.object({
75856
75869
  });
75857
75870
 
75858
75871
  // packages/shared/src/build-info.ts
75859
- var APP_VERSION = true ? "0.1.2" : "0.0.0-dev";
75872
+ var APP_VERSION = true ? "0.1.4" : "0.0.0-dev";
75860
75873
  var BUNDLED = true;
75861
75874
 
75862
75875
  // packages/shared/src/index.ts
@@ -77931,9 +77944,19 @@ async function finishPdf(buf, base) {
77931
77944
  });
77932
77945
  return base("pdf", { text, bytes: buf.length, warnings: pageWarnings });
77933
77946
  } catch (e) {
77947
+ const name = e && typeof e === "object" && "name" in e ? String(e.name) : "";
77948
+ if (name === "PasswordException") {
77949
+ return base("unsupported", {
77950
+ bytes: buf.length,
77951
+ warnings: [
77952
+ "\uC774 PDF\uB294 \uC554\uD638(\uBE44\uBC00\uBC88\uD638)\uB85C \uBCF4\uD638\uB418\uC5B4 \uC788\uC5B4 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. PDF \uBDF0\uC5B4\uC5D0\uC11C \uC554\uD638\uB97C \uD574\uC81C\uD574 \uB2E4\uC2DC \uC800\uC7A5\uD558\uAC70\uB098, \uB0B4\uC6A9\uC744 \uD14D\uC2A4\uD2B8\uB85C \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694."
77953
+ ],
77954
+ unsupported: true
77955
+ });
77956
+ }
77934
77957
  return base("unsupported", {
77935
77958
  bytes: buf.length,
77936
- warnings: [`PDF\uB97C \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4(${errMsg(e)}). \uC554\uD638\uD654\uB418\uC5C8\uAC70\uB098 \uC190\uC0C1\uB41C PDF\uC77C \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uD14D\uC2A4\uD2B8\uB85C \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694.`],
77959
+ warnings: [`PDF\uB97C \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4(${errMsg(e)}). \uC190\uC0C1\uB418\uC5C8\uAC70\uB098 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 PDF\uC77C \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uD14D\uC2A4\uD2B8\uB85C \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694.`],
77937
77960
  unsupported: true
77938
77961
  });
77939
77962
  }
@@ -78487,13 +78510,12 @@ ${lines}`;
78487
78510
  name: "add_resume",
78488
78511
  title: "\uC774\uB825\uC11C/\uACBD\uB825\uAE30\uC220\uC11C \uCD94\uAC00",
78489
78512
  description: "\uC774\uB825\uC11C, \uACBD\uB825\uAE30\uC220\uC11C, \uD3EC\uD2B8\uD3F4\uB9AC\uC624 \uB4F1 \uBB38\uC11C\uB97C \uC800\uC7A5\uD569\uB2C8\uB2E4. content\uC5D0\uB294 \uC0AC\uC6A9\uC790\uAC00 \uC5C5\uB85C\uB4DC/\uBD99\uC5EC\uB123\uC740 \uD14D\uC2A4\uD2B8(\uB610\uB294 \uC815\uB9AC\uD55C Markdown)\uB97C \uB123\uC73C\uC138\uC694. kind \uAE30\uBCF8\uAC12\uC740 resume\uC785\uB2C8\uB2E4. is_primary=true\uB85C \uB300\uD45C \uBB38\uC11C\uB97C \uC9C0\uC815\uD558\uBA74 get_application_context\uC758 primary_resume\uB85C \uB178\uCD9C\uB429\uB2C8\uB2E4.",
78513
+ // Reuse the shared (length-bounded, whitespace-rejecting) Document schema so
78514
+ // the MCP ingestion path — the primary route for résumé text — inherits the
78515
+ // same caps as the web API. kind stays optional here (defaults to resume).
78490
78516
  inputSchema: {
78491
- title: external_exports.string().describe("\uBB38\uC11C \uC81C\uBAA9"),
78492
- content: external_exports.string().describe("\uBCF8\uBB38 (Markdown/\uC77C\uBC18 \uD14D\uC2A4\uD2B8)"),
78493
- kind: external_exports.enum(DOCUMENT_KINDS).optional().describe("resume(\uC774\uB825\uC11C)/career_description(\uACBD\uB825\uAE30\uC220\uC11C)/portfolio/other"),
78494
- source: external_exports.enum(CONTENT_SOURCES).optional(),
78495
- is_primary: external_exports.boolean().optional().describe("\uB300\uD45C \uBB38\uC11C \uC5EC\uBD80"),
78496
- tags: external_exports.array(external_exports.string()).optional()
78517
+ ...DocumentInputSchema.omit({ kind: true }).shape,
78518
+ kind: external_exports.enum(DOCUMENT_KINDS).optional().describe("resume(\uC774\uB825\uC11C)/career_description(\uACBD\uB825\uAE30\uC220\uC11C)/portfolio/other")
78497
78519
  },
78498
78520
  handler: (args) => {
78499
78521
  const doc = addResume(args);