careermate 0.1.0 → 0.1.2

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 (134) hide show
  1. package/README.md +22 -17
  2. package/THIRD_PARTY_NOTICES.md +15 -0
  3. package/apps/mcp/src/index.ts +4 -3
  4. package/apps/web/DESIGN_GUIDE.md +2 -2
  5. package/apps/web/demo/demo-shim.js +536 -0
  6. package/apps/web/demo/demo.css +75 -0
  7. package/apps/web/demo/seed.js +269 -0
  8. package/apps/web/public/app.js +34 -2
  9. package/apps/web/public/icon-180.png +0 -0
  10. package/apps/web/public/index.html +14 -3
  11. package/apps/web/public/lib.js +135 -23
  12. package/apps/web/public/pages/applications.js +18 -9
  13. package/apps/web/public/pages/documents.js +66 -50
  14. package/apps/web/public/pages/home.js +12 -11
  15. package/apps/web/public/pages/interview.js +9 -12
  16. package/apps/web/public/pages/jobs.js +39 -36
  17. package/apps/web/public/pages/profile.js +66 -86
  18. package/apps/web/public/pages/settings.js +21 -14
  19. package/apps/web/public/styles.css +105 -38
  20. package/apps/web/src/http.ts +31 -4
  21. package/apps/web/src/info.ts +9 -22
  22. package/apps/web/src/routes.ts +19 -14
  23. package/apps/web/src/server.ts +34 -29
  24. package/apps/web/src/settings.ts +35 -17
  25. package/bin/careermate.mjs +8 -5
  26. package/dist/doctor.mjs +78908 -0
  27. package/dist/install-page/.omc/state/agent-replay-55a58c0d-bb6e-43ea-8a05-4b6c93b80b34.jsonl +12 -0
  28. package/dist/install-page/.omc/state/idle-notif-cooldown.json +3 -0
  29. package/dist/install-page/.omc/state/sessions/55a58c0d-bb6e-43ea-8a05-4b6c93b80b34/mission-state.json +147 -0
  30. package/dist/install-page/.omc/state/sessions/55a58c0d-bb6e-43ea-8a05-4b6c93b80b34/pre-tool-advisory-throttle.json +22 -0
  31. package/dist/install-page/.omc/state/sessions/55a58c0d-bb6e-43ea-8a05-4b6c93b80b34/subagent-tracking-state.json +44 -0
  32. package/dist/install-page/favicon.svg +1 -0
  33. package/dist/install-page/icon-180.png +0 -0
  34. package/dist/install-page/icon-512.png +0 -0
  35. package/dist/install-page/index.html +328 -225
  36. package/dist/install-page/landing.js +54 -0
  37. package/dist/install-page/llms-install.txt +202 -0
  38. package/dist/install-page/og.png +0 -0
  39. package/dist/install-page/shots/applications-dark.png +0 -0
  40. package/dist/install-page/shots/applications-light.png +0 -0
  41. package/dist/install-page/shots/documents-dark.png +0 -0
  42. package/dist/install-page/shots/documents-light.png +0 -0
  43. package/dist/install-page/shots/home-dark.png +0 -0
  44. package/dist/install-page/shots/home-light.png +0 -0
  45. package/dist/install-page/shots/interview-dark.png +0 -0
  46. package/dist/install-page/shots/interview-light.png +0 -0
  47. package/dist/install-page/shots/jobs-dark.png +0 -0
  48. package/dist/install-page/shots/jobs-light.png +0 -0
  49. package/dist/install-page/shots/settings-dark.png +0 -0
  50. package/dist/install-page/shots/settings-light.png +0 -0
  51. package/dist/install-page/start.html +323 -0
  52. package/dist/install-page/style.css +128 -4
  53. package/dist/mcp.mjs +74225 -2007
  54. package/dist/{mcpb-stage/dist/web.mjs → migrate.mjs} +142 -2660
  55. package/dist/pdf.worker.min.mjs +28 -0
  56. package/dist/public/app.js +34 -2
  57. package/dist/public/icon-180.png +0 -0
  58. package/dist/public/index.html +14 -3
  59. package/dist/public/lib.js +135 -23
  60. package/dist/public/pages/applications.js +18 -9
  61. package/dist/public/pages/documents.js +66 -50
  62. package/dist/public/pages/home.js +12 -11
  63. package/dist/public/pages/interview.js +9 -12
  64. package/dist/public/pages/jobs.js +39 -36
  65. package/dist/public/pages/profile.js +66 -86
  66. package/dist/public/pages/settings.js +21 -14
  67. package/dist/public/styles.css +105 -38
  68. package/dist/web.mjs +39950 -369
  69. package/docs/ARCHITECTURE.md +5 -5
  70. package/docs/DECISIONS.md +1 -1
  71. package/docs/DEMO.md +6 -6
  72. package/docs/MCP_TOOLS.md +68 -9
  73. package/docs/ROADMAP.md +11 -10
  74. package/docs/RUNTIME_DISTRIBUTION_RESEARCH.md +135 -0
  75. package/docs/START_WORKFLOW.md +19 -7
  76. package/docs/SUPPORTED_AI_APPS.md +85 -33
  77. package/docs/TODO.md +1 -1
  78. package/docs/WORKFLOWS.md +8 -6
  79. package/docs/superpowers/specs/2026-06-16-demo-dashboard-deploy-design.md +64 -0
  80. package/package.json +8 -3
  81. package/packages/core/src/services.ts +42 -1
  82. package/packages/db/src/connection.ts +30 -0
  83. package/packages/db/src/index.ts +1 -0
  84. package/packages/db/src/paths.ts +11 -0
  85. package/packages/db/src/repositories.ts +109 -63
  86. package/packages/db/src/schema.ts +118 -0
  87. package/packages/exporters/src/html.ts +9 -12
  88. package/packages/mcp-tools/src/bridge.ts +55 -32
  89. package/packages/mcp-tools/src/index.ts +9 -0
  90. package/packages/mcp-tools/src/tools.ts +269 -65
  91. package/packages/mcp-tools/src/update.ts +330 -0
  92. package/packages/parsers/src/documents.ts +521 -0
  93. package/packages/parsers/src/index.ts +8 -5
  94. package/packages/prompts/src/onboarding.ts +2 -2
  95. package/packages/shared/src/build-info.ts +22 -0
  96. package/packages/shared/src/enums.ts +39 -0
  97. package/packages/shared/src/index.ts +1 -0
  98. package/packages/shared/src/schemas.ts +24 -3
  99. package/packages/workflows/src/definitions.ts +4 -2
  100. package/scripts/build-demo.mjs +130 -0
  101. package/scripts/build-dist.mjs +46 -8
  102. package/scripts/build-mcpb.mjs +60 -4
  103. package/scripts/doctor.ts +16 -3
  104. package/scripts/gen-og.mjs +86 -0
  105. package/scripts/init.ts +32 -3
  106. package/scripts/seed.ts +5 -5
  107. package/scripts/shots-seed.ts +192 -0
  108. package/scripts/shots.ts +89 -0
  109. package/tsconfig.json +3 -1
  110. package/dist/careermate.mcpb +0 -0
  111. package/dist/mcp-smoke.err +0 -3
  112. package/dist/mcpb-stage/README.md +0 -219
  113. package/dist/mcpb-stage/dist/install-page/index.html +0 -434
  114. package/dist/mcpb-stage/dist/install-page/style.css +0 -407
  115. package/dist/mcpb-stage/dist/install-page/vercel.json +0 -20
  116. package/dist/mcpb-stage/dist/mcp.mjs +0 -23704
  117. package/dist/mcpb-stage/dist/public/app.js +0 -118
  118. package/dist/mcpb-stage/dist/public/index.html +0 -41
  119. package/dist/mcpb-stage/dist/public/lib.js +0 -282
  120. package/dist/mcpb-stage/dist/public/pages/applications.js +0 -98
  121. package/dist/mcpb-stage/dist/public/pages/documents.js +0 -446
  122. package/dist/mcpb-stage/dist/public/pages/home.js +0 -263
  123. package/dist/mcpb-stage/dist/public/pages/interview.js +0 -230
  124. package/dist/mcpb-stage/dist/public/pages/jobs.js +0 -494
  125. package/dist/mcpb-stage/dist/public/pages/profile.js +0 -576
  126. package/dist/mcpb-stage/dist/public/pages/settings.js +0 -233
  127. package/dist/mcpb-stage/dist/public/styles.css +0 -420
  128. package/dist/mcpb-stage/manifest.json +0 -40
  129. package/docs/INSTALL.md +0 -148
  130. package/docs/INSTALL_AND_USAGE.md +0 -99
  131. package/install-page/index.html +0 -474
  132. package/install-page/style.css +0 -391
  133. package/install-page/vercel.json +0 -20
  134. /package/dist/{mcpb-stage/dist/public → install-page}/fonts/PretendardVariable.woff2 +0 -0
@@ -1,60 +1,112 @@
1
- # 지원 AI 앱 (Supported AI Apps)
1
+ # 지원 AI 앱 & 설치 레퍼런스 (Supported AI Apps)
2
2
 
3
- CareerMate는 LLM이 없는 **로컬 MCP 도구**입니다. 그래서 *내 컴퓨터에서 로컬 프로그램을 직접 실행할 수 있는* AI 앱에서만 동작합니다. 1차 지원(first-class) 앱은 다음 셋입니다.
3
+ CareerMate는 LLM이 없는 **로컬 MCP 도구**입니다. *내 컴퓨터에서 로컬 프로그램을 직접 실행할 수 있는* AI 앱에서만 동작합니다.
4
4
 
5
- | 앱 | 영구 지침 | MCP 등록 위치 |
6
- | --- | --- | --- |
7
- | **Claude Desktop** | — | `.mcpb` 번들 (원클릭) |
8
- | **Claude Code** | 루트 `CLAUDE.md` | 프로젝트 `.mcp.json` |
9
- | **Codex** (OpenAI Codex CLI) | 루트 `AGENTS.md` | `~/.codex/config.toml` |
5
+ > **가장 쉬운 사람용 설치 안내는 https://careermate.life (랜딩)** 입니다. 이 문서는 **지원 앱 매트릭스 + 클라이언트별 수동 MCP 설정 + 알려진 제약**을 모은 레퍼런스입니다. AI 에이전트가 따라가는 단계별 런북은 루트 [`/INSTALL.md`](../INSTALL.md), 설치 후 사용 흐름은 [docs/START_WORKFLOW.md](./START_WORKFLOW.md)를 보세요.
10
6
 
11
7
  ---
12
8
 
13
- ## Claude Desktop
9
+ ## 설치는 크게 3가지 — "누가 설치하느냐"
14
10
 
15
- Claude Desktop이 Node를 내장 제공하므로 미리 깔 것이 없습니다.
11
+ | | 무엇 | Node | 대상 |
12
+ |---|---|---|---|
13
+ | **1. 에이전트가 대신 설치** | 작업 폴더에서 **한 문장** → AI가 `careermate.life/llms-install.txt`를 읽고 npm 설치 + MCP 등록 | 필요(에이전트가 winget/brew 또는 nodejs.org로 도와줌) | Claude Code(데스크톱 **"Code" 탭** / CLI)·Codex CLI·Gemini CLI·Cursor 등 *명령 실행 가능한 에이전트* |
14
+ | **2. npm 명령 직접** | 터미널에 `npx -y careermate init` 한 줄(감지된 앱 자동 등록) | 필요 | Claude Desktop·Codex·Cursor 등(그 외는 `--print`로 수동) |
15
+ | **3. `.mcpb`** | 파일 하나를 설정에서 추가 | **불필요**(Claude Desktop 내장) | **Claude Desktop "채팅" 앱 전용** |
16
16
 
17
- 1. **Claude Desktop 설치**
18
- 2. **`careermate.mcpb` 받기** — Release `v0.1.0`에 첨부돼 있으나 저장소가 **비공개**라 접근 권한이 있어야 받을 수 있습니다. 그 외에는 소스에서 `npm run build:mcpb`로 직접 빌드하세요.
19
- 3. **더블클릭** (또는 Settings → Extensions 로 끌어다 놓기)
20
- 4. **Claude Desktop 재시작**
17
+ **1·2는 같은 메커니즘**(Node + npm/npx + MCP 등록) — 에이전트가 치느냐 / 내가 치느냐의 차이일 뿐입니다. 그래서 1·2의 "대상" 제품군이 서로 **겹칩니다**(같은 Claude Code·Codex·Cursor 등). 어느 앱이 어느 방식을 쓰는지는 아래 **앱 × 방식** 매트릭스로 보세요. **3(`.mcpb`)만 Claude Desktop "채팅" 앱 전용**이라 성격이 다릅니다(내장 Node 사용, 단 버전<22.5면 불안정).
21
18
 
22
- ---
23
-
24
- ## Claude Code
19
+ ### 앱 × 방식
25
20
 
26
- CareerMate 폴더 안에서 실행하는 것이 핵심입니다.
21
+ | 앱 | 1 에이전트 | 2 npm직접 | 3 .mcpb | 영구 지침 | MCP 등록 위치 |
22
+ |---|---|---|---|---|---|
23
+ | **Claude Desktop "채팅"** | — | ✅(config 등록) | ✅ | — | `.mcpb` 또는 `claude_desktop_config.json` |
24
+ | **Claude Code (Code 탭 / CLI)** | ✅ | ✅ | — | 루트 `CLAUDE.md` | 프로젝트 `.mcp.json` |
25
+ | **Codex CLI** (⚠️ 데스크톱 앱 ✗) | ✅ | ✅ | — | 루트 `AGENTS.md` | `~/.codex/config.toml` |
26
+ | **Gemini CLI / Antigravity** | ✅ | ✅ | — | — | Gemini CLI `~/.gemini/settings.json` · Antigravity `~/.gemini/.../mcp_config.json` |
27
+ | **Cursor · Cline · Windsurf** | ✅ | ✅ | — | — | `~/.cursor/mcp.json` 등 |
27
28
 
28
- 1. **Claude Code 실행** (CareerMate 폴더에서)
29
- 2. 프롬프트 입력:
30
- > "CareerMate를 설치하고 설정해줘. INSTALL.md를 따라 진행해줘."
29
+ > **Gemini CLI → Antigravity 전환(2026-06):** Google이 소비자(무료/Pro/Ultra)용 Gemini CLI를 **2026-06-18부터 Antigravity CLI로 전환**합니다(Gemini CLI repo는 오픈소스로 유지·엔터프라이즈 계속). MCP 스키마는 동일(`mcpServers` + `command`/`args`/`env`)합니다. `careermate init`은 **Gemini CLI(`~/.gemini/settings.json`)를 자동 등록**하고, **Antigravity는 설정 파일 경로가 유동적이라 후보 파일이 이미 있을 때만** 등록합니다(없으면 위 mcpServers 블록을 해당 파일에 수동 추가).
31
30
 
32
- 프로젝트 스코프 `.mcp.json`을 CareerMate 폴더에 등록하고, 첫 실행 때 **1회 승인**을 묻습니다. 승인하면 끝입니다.
31
+ ### 동작하지 않는 것
32
+ **ChatGPT · Gemini · Claude의 웹·모바일 앱은 안 됩니다.** 클라우드에서 실행되어 **원격 URL 기반 MCP만** 지원하므로, 내 컴퓨터의 로컬 stdio 서버에 직접 닿을 수 없습니다.
33
33
 
34
34
  ---
35
35
 
36
- ## Codex
36
+ ## 구독·준비물 (2026)
37
37
 
38
- OpenAI Codex CLI(로컬 코딩 에이전트)에서 동작합니다.
38
+ - **Claude Code**(Code 탭/CLI): **Pro 이상**(무료 플랜 불가) 또는 `ANTHROPIC_API_KEY` 종량제.
39
+ - **Codex**: **ChatGPT Plus 이상** 로그인으로 충분(API 키 불필요).
40
+ - **`.mcpb`**(Claude Desktop 채팅 확장): **무료 플랜에서도 설치** 가능(안정성이 변수).
41
+ - **터미널/npx**: 무료(Node만). 연결할 앱의 구독 정책은 별개.
39
42
 
40
- 1. **Codex 실행** (CareerMate 폴더에서)
41
- 2. 프롬프트 입력:
42
- > "CareerMate를 설치하고 설정해줘. INSTALL.md를 따라 진행해줘."
43
+ > **런타임.** CareerMate 서버는 Node 프로그램이라 **Node.js 22.5+ 가 필요**합니다 — 위 1·2 경로 공통(없으면 에이전트가 winget/brew, 안 되면 [nodejs.org](https://nodejs.org) LTS 설치 파일). **Node가 아예 필요 없는 길은 `.mcpb` 하나뿐**인데 그건 내장 Node 버전에 의존해 불안정할 수 있습니다 — **"완전 무설치 + 안정"을 동시에 주는 길은 아직 없습니다.**
44
+ >
45
+ > **Windows + Claude Code(Code 탭)**: [Git for Windows](https://git-scm.com/download/win) 사전 설치가 필요합니다(앱이 없으면 "Git is required" 안내). Claude Code가 **세션 격리(git worktree)와 내부 POSIX 셸 도구**에 Git을 쓰기 때문이며 CareerMate와는 무관합니다. macOS는 Git 기본 포함이라 불필요.
46
+
47
+ ---
43
48
 
44
- `~/.codex/config.toml`의 `[mcp_servers.careermate]` 항목을 등록합니다. Codex 안에서 `/mcp` 명령으로 연결을 확인하세요.
49
+ ## 작업 폴더 안에 설치 (권장 — GUI 앱·샌드박스 친화)
50
+
51
+ Claude "Code" 탭이나 Codex처럼 **작업 폴더(cwd)를 잡고** 쓰는 경우, 설치물과 데이터를 그 폴더 **안에** 두면 (1) 폴더만 백업·이동하면 데이터가 따라오고 (2) 샌드박스가 cwd 하위 쓰기를 허용해 마찰이 줄어듭니다. `<WS>`를 작업 폴더 절대경로라 할 때:
52
+
53
+ ```bash
54
+ npm install --prefix "<WS>/.cm/app" careermate
55
+ ```
56
+ 데이터는 `CAREERMATE_DATA_DIR` 환경변수로 작업 폴더 안에 고정합니다(기본값 `~/.careermate` 대신). 아래 MCP 등록의 `env`에 넣으면 됩니다:
57
+ ```
58
+ CAREERMATE_DATA_DIR = <WS>/.cm/data
59
+ ```
60
+ - `<BIN>` = `<WS>/.cm/app/node_modules/careermate/bin/careermate.mjs` (이 셸이 `--experimental-sqlite`를 알아서 붙임).
61
+ - 데이터 폴더 이름을 `.git`·`.codex`·`.agents`로 두지 마세요(앱이 읽기 전용으로 보호).
62
+ - **포터블 Node(고급):** 시스템에 Node 22.5+가 없으면, 작업 폴더 안에 포터블 Node를 두고 `command`를 그 `node` 절대경로로 가리켜도 동작합니다. 단 다운로드 용량·OS 보안 경고·폴더 이동 시 경로 갱신 부담이 있어, 지금은 "시스템 Node 1회 설치"가 더 단순합니다.
45
63
 
46
64
  ---
47
65
 
48
- ## 그 외 — 기타 MCP 클라이언트
66
+ ## 클라이언트별 수동 MCP 설정 (방식 2의 수동판)
67
+
68
+ 대부분 `npx -y careermate init`(또는 소스에서 `npm run init`)이 자동 등록하므로 **수동 설정은 보통 불필요**합니다. 직접 붙여넣어야 할 때만 참고하세요. `npm run init -- --print`로 아래 스니펫을 출력할 수도 있습니다.
49
69
 
50
- Cursor · Cline · Windsurf 등 로컬 stdio MCP를 지원하는 클라이언트도 동작합니다. CareerMate 폴더에서 `npm run init`(또는 설정만 출력하는 `npm run init -- --print`)으로 등록하세요.
70
+ **경로 규칙** — 아래 `<BIN>`은 **실제 절대경로**로 바꾸세요(`~`·`$HOME`은 설정 파일 안에서 확장 안 됨).
71
+ - **Windows**: JSON에서 역슬래시는 `\\`로 두 번(`C:\\...\\careermate.mjs`). TOML은 **작은따옴표 리터럴**(`'C:\...\careermate.mjs'`)을 쓰면 한 번만.
72
+ - **macOS/Linux**: `/` 그대로.
51
73
 
52
- ## 동작하지 않는 것
74
+ ### Claude Desktop (채팅앱 수동)
75
+ 설정 파일: Windows `%APPDATA%\Claude\claude_desktop_config.json` · macOS `~/Library/Application Support/Claude/claude_desktop_config.json` (Settings → Developer → Edit Config로도 열림)
76
+ ```json
77
+ { "mcpServers": { "careermate": { "command": "node", "args": ["<BIN>", "mcp"] } } }
78
+ ```
53
79
 
54
- **ChatGPT / Gemini 웹·모바일은 동작하지 않습니다.** 이들은 클라우드에서 실행되므로 내 컴퓨터의 **로컬 stdio MCP 서버에 닿을 수 없습니다.** 로컬에서 도는 Codex(CLI) · Claude Code · Claude Desktop만 CareerMate를 쓸 수 있습니다.
80
+ ### Claude Code (프로젝트 `.mcp.json`, 작업 폴더 최상단)
81
+ ```json
82
+ { "mcpServers": { "careermate": { "type": "stdio", "command": "node", "args": ["<BIN>", "mcp"], "env": { "CAREERMATE_DATA_DIR": "<WS>/.cm/data" } } } }
83
+ ```
84
+ 또는 CLI: `claude mcp add --scope project --transport stdio careermate -- node <BIN> mcp` · 첫 실행 시 **1회 승인**.
85
+
86
+ ### Codex CLI (`~/.codex/config.toml`)
87
+ ```toml
88
+ [mcp_servers.careermate]
89
+ command = "node"
90
+ args = ["<BIN>", "mcp"]
91
+ # 작업 폴더 안에 데이터: env = { CAREERMATE_DATA_DIR = "<WS>/.cm/data" }
92
+ ```
93
+ 또는 CLI: `codex mcp add careermate -- node <BIN> mcp` · 등록 후 `/mcp`로 확인.
94
+
95
+ ### Cursor / Cline / Windsurf
96
+ `~/.cursor/mcp.json`(전역) 또는 `.cursor/mcp.json`(프로젝트) 등에 위 Claude Code와 같은 `mcpServers` 블록. 추가 후 클라이언트 재시작.
97
+
98
+ > **소스를 클론한 개발자**는 `<BIN>` 대신 TS 소스를 직접 가리킬 수 있습니다(빌드 불필요):
99
+ > `node --no-warnings --experimental-sqlite --import tsx <careermate>/apps/mcp/src/index.ts mcp`
55
100
 
56
101
  ---
57
102
 
58
- 설치 절차 전체는 [/INSTALL.md](../INSTALL.md)(AI용 런북)를 따르세요. 클라이언트별 **수동 MCP 설정 스니펫**은 [docs/INSTALL.md](./INSTALL.md), 사람용 두 갈래 설치는 [docs/INSTALL_AND_USAGE.md](./INSTALL_AND_USAGE.md)를 참고하세요.
103
+ ## 알려진 제약 · 트러블슈팅
104
+
105
+ - **Codex 데스크톱 앱(⚠️ CLI 권장):** [#24718](https://github.com/openai/codex/issues/24718)(OPEN) 시작 시 전역 `config.toml`을 재작성해 등록이 사라질 수 있음 + [#13025](https://github.com/openai/codex/issues/13025)(OPEN) 프로젝트 `.codex/config.toml` 무시. 그래서 **데스크톱 앱은 비권장, CLI를 쓰세요.** 설치 단계 `npm install`은 기본 샌드박스에서 네트워크가 꺼져 있어 **1회 허용**(또는 `[sandbox_workspace_write] network_access = true`)이 필요. (단 **MCP 서버 자체는 샌드박스 밖에서 돌아** 연결 후 데이터 읽기/쓰기는 막히지 않습니다.)
106
+ - **Claude Desktop에서 등록이 사라짐:** 팀/클라우드 동기화가 로컬 MCP를 덮어쓴 이슈([#59368](https://github.com/anthropics/claude-code/issues/59368), 수정됨). 동기화를 끄거나 **CLI + 프로젝트 `.mcp.json`** 사용.
107
+ - **연결 검증:** `get_onboarding_status` 호출(Codex는 `/mcp`). 빠른 점검: `npx -y careermate doctor`(Node·데이터 폴더·DB 상태).
108
+ - **자세한 복구**(포트 충돌·DB 마이그레이션·완전 초기화): 루트 [`/INSTALL.md`](../INSTALL.md) 9단계.
109
+
110
+ ---
59
111
 
60
- 각 모드의 **영구 지침 파일**: Claude Code → [/CLAUDE.md](../CLAUDE.md), Codex → [/AGENTS.md](../AGENTS.md).
112
+ 각 모드의 **영구 지침 파일**: Claude Code → [/CLAUDE.md](../CLAUDE.md), Codex → [/AGENTS.md](../AGENTS.md). 사람용 설치 → https://careermate.life · 사용 흐름 → [docs/START_WORKFLOW.md](./START_WORKFLOW.md).
package/docs/TODO.md CHANGED
@@ -54,4 +54,4 @@
54
54
  - **.mcpb GitHub Release 생성됨(접근 제한)** — Release `v0.1.0`에 `careermate.mcpb` 첨부. 단 저장소가 **비공개**라 공개 다운로드 링크는 아직 없음. → 저장소를 public으로 전환하면 공개 다운로드 활성화.
55
55
  - **공개 repo URL 확정** — git remote 기준 정본은 `osntak/CareerMate`(현재 **비공개**). 공개 전환 여부는 별도 결정 사항.
56
56
  - **THIRD_PARTY_NOTICES — `im-not-ai`(MIT)** 항목의 저작권자/연도가 비어 있다. 실제 저작권자·연도로 채우기.
57
- - **THIRD_PARTY_NOTICES — `im-not-ai`(MIT)** 항목의 저작권자/연도가 비어 있다. 실제 저작권자·연도로 채우기.
57
+ - **`install-page/` 폴더명 변경** — 이제 랜딩+시작마법사+데모(`/demo`)+설치안내를 모두 담는 "공개 웹사이트" 폴더라 `install-page`는 이름이 안 맞는다. 후보: `site` / `www` / `landing`. 주의: 이 폴더는 **Vercel 프로젝트의 Root Directory 설정**이므로, 폴더명을 바꾸면 Vercel 대시보드의 Root Directory 값도 같이 바꿔야 배포가 안 깨진다. 레포 내 코드 참조(고쳐야 할 곳): `scripts/build-dist.mjs`·`scripts/build-mcpb.mjs`·`scripts/shots.ts`·`scripts/gen-og.mjs`·`apps/web/src/server.ts`. 데모 완성 후 진행하기로 합의(2026-06-16).
package/docs/WORKFLOWS.md CHANGED
@@ -7,8 +7,8 @@ CareerMate는 내부에 LLM을 두지 않는다. 분석과 작성은 사용자
7
7
  관련 문서:
8
8
 
9
9
  - 연결 직후 AI가 따라가는 시작 런북은 [시작 런북](./START_WORKFLOW.md)을 참고한다.
10
- - 설치·연결 절차는 [설치 가이드](./INSTALL.md)를 참고한다.
11
- - MCP 도구 24종의 입출력 명세는 코드(`packages/mcp-tools/src/tools.ts`)에 정의되어 있으며, AI는 각 도구의 `inputSchema`로 직접 확인할 수 있다.
10
+ - 설치·연결 절차는 [설치 런북](../INSTALL.md)(또는 [지원 앱·설치 레퍼런스](./SUPPORTED_AI_APPS.md))을 참고한다.
11
+ - MCP 도구 31종의 입출력 명세는 코드(`packages/mcp-tools/src/tools.ts`)에 정의되어 있으며, AI는 각 도구의 `inputSchema`로 직접 확인할 수 있다.
12
12
 
13
13
  ## 워크플로우 5종 개요
14
14
 
@@ -41,9 +41,10 @@ CareerMate는 내부에 LLM을 두지 않는다. 분석과 작성은 사용자
41
41
  2. 비어 있는 항목을 사용자에게 쉬운 한국어로 안내하고, 이력서/자기소개서 파일 업로드 또는 정보 입력을 요청한다.
42
42
  3. 수집한 정보를 구조화해 `save_profile`로 저장한다(이름·연락처·`headline`·`summary`·`desired_roles`·`desired_conditions`, 그리고 글쓰기 선호인 `preferred_tone`과 `emphasis_points` 포함). 파일 출처는 `source=upload`, 직접 입력은 `manual`.
43
43
  4. 업로드한 이력서/경력기술서/포트폴리오 본문을 `add_resume`로 저장한다(`kind`·`title` 지정, 대표 문서는 `is_primary=true`).
44
- 5. 기존 자기소개서가 있으면 `add_cover_letter`로 저장한다(없으면 나중에 작성 가능함을 안내).
45
- 6. `open_dashboard`를 호출해 사용자가 자기 데이터를 확인하게 한다.
46
- 7. 다시 `get_onboarding_status`로 완성도를 확인하고, 다음 단계(공고 분석 등)를 제안한다.
44
+ 5. 경력·프로젝트·스킬이 파악되면 `add_experience`·`add_project`·`add_skill`로 각각 저장한다(완성도의 `has_experience`/`has_skills`에 반영).
45
+ 6. 기존 자기소개서가 있으면 `save_cover_letter_version`으로 저장한다(`title`+`content`면 v1로 저장, 없으면 나중에 작성 가능함을 안내).
46
+ 7. `open_dashboard`를 호출해 사용자가 자기 데이터를 확인하게 한다.
47
+ 8. 다시 `get_onboarding_status`로 완성도를 확인하고, 다음 단계(공고 분석 등)를 제안한다.
47
48
 
48
49
  **도구 호출 순서 요약:**
49
50
 
@@ -51,7 +52,8 @@ CareerMate는 내부에 LLM을 두지 않는다. 분석과 작성은 사용자
51
52
  get_onboarding_status
52
53
  → save_profile
53
54
  → add_resume
54
- → add_cover_letter (선택)
55
+ → add_experience / add_project / add_skill (있으면)
56
+ → save_cover_letter_version (기존 자소서가 있으면)
55
57
  → open_dashboard
56
58
  → get_onboarding_status (재확인)
57
59
  ```
@@ -0,0 +1,64 @@
1
+ # 데모 대시보드 배포 설계 (careermate.life/demo)
2
+
3
+ 작성일: 2026-06-16
4
+
5
+ ## 목표
6
+
7
+ 랜딩 방문자가 **로그인·설치 없이** CareerMate 대시보드를 둘러볼 수 있는 데모 페이지를
8
+ `careermate.life/demo`에 배포한다. 사용자(개발자)가 원격에서 로컬 대시보드(127.0.0.1)에
9
+ 접근할 수 없는 문제도 함께 해소한다.
10
+
11
+ ## 결정 사항 (사용자 확인 완료)
12
+
13
+ - **상호작용**: 편집 가능. 단 메모리 전용 — 디스크/서버 저장 없음, 새로고침 시 샘플로 리셋.
14
+ - **위치**: 기존 Vercel 랜딩(`install-page/`) 하위 `/demo` 서브경로. Vercel 설정·빌드 무변경.
15
+
16
+ ## 핵심 구조
17
+
18
+ 대시보드는 정적 SPA(`apps/web/public`)가 로컬 Node 서버의 `/api/*` JSON을 호출하는 구조다.
19
+ 서버 없이 배포하려면 **클라이언트에서 `fetch('/api/*')`를 가로채 메모리 DB로 응답하는 shim**을 끼운다.
20
+
21
+ 대부분의 GET 응답(`/api/summary`, `/api/applications`, `/api/interview`, `/api/jobs/:id` 등)은
22
+ 몇 개 기본 컬렉션(jobs, applications, coverLetters, profile, experiences, projects, skills,
23
+ documents, interviews, fits, activities)에서 **파생**된다. shim이 그 파생 로직을 재현하므로
24
+ "한 곳을 수정하면 홈 카운트·목록이 자동 갱신"되는 동작이 공짜로 따라온다.
25
+
26
+ ## 구성 요소
27
+
28
+ 1. **데모 오버레이 소스** (`apps/web/demo/`, 원본 대시보드와 분리)
29
+ - `seed.js` — 풍부한 샘플 데이터(공고 6+, 지원 단계 다양, 프로필/경력/스킬/문서/자소서/면접).
30
+ - `demo-shim.js` — 메모리 가변 DB + `window.fetch` 가로채기. GET=읽기, POST/PUT/DELETE=메모리 변경.
31
+ 서버 라우트(`apps/web/src/routes.ts`)와 동일한 응답 형태/파생 로직을 미러링.
32
+ - `demo.css` + 하단 고정 배너(데모 안내 + "CareerMate 소개로 →" 링크).
33
+
34
+ 2. **빌드 스크립트** (`scripts/build-demo.mjs`)
35
+ - `apps/web/public/*` → `install-page/demo/`로 복사.
36
+ - 복사본의 루트 절대경로(`/lib.js`, `/app.js`, `/pages/`, `/styles.css`, `/fonts/`,
37
+ `/icon-180.png`)를 `/demo/...`로 재작성. `/api/`는 그대로(shim이 처리).
38
+ - `index.html` `<head>`에 데모 shim(classic script, fetch 선패치) + `demo.css` 주입,
39
+ `<body>` 끝에 데모 배너 주입.
40
+ - Node 25의 `fs.rmSync(recursive)` 크래시 회피 → 수동 재귀 삭제 사용.
41
+
42
+ 3. **진입 동선**
43
+ - 랜딩(`install-page/index.html`)에 "데모 둘러보기" 링크.
44
+ - 데모 하단 배너의 "CareerMate 소개로 →" 버튼이 랜딩(`/`)으로 복귀.
45
+
46
+ 4. **배포**
47
+ - 생성물 `install-page/demo/`를 git 커밋 → 기존 Vercel이 자동 서빙.
48
+ - 갱신: `node scripts/build-demo.mjs`.
49
+
50
+ ## 후속 작업 (TODO)
51
+
52
+ - [ ] **`install-page/` 폴더명 변경** — 이제 랜딩+시작마법사+데모+설치안내를 모두 담는
53
+ "공개 웹사이트"라 `install-page`는 이름이 안 맞음. 후보: `site` / `www` / `landing`.
54
+ 주의: 이 폴더는 Vercel 프로젝트의 **Root Directory 설정**이라, 폴더명을 바꾸면
55
+ Vercel 대시보드의 Root Directory 값도 같이 바꿔야 배포가 안 깨짐(레포 내 참조는 코드 5곳
56
+ +문서: `scripts/build-dist.mjs`·`build-mcpb.mjs`·`shots.ts`·`gen-og.mjs`·`apps/web/src/server.ts`).
57
+ → **데모 완성 후 진행하기로 사용자와 합의(2026-06-16).** 그때까지는 `install-page/` 유지.
58
+
59
+ ## 비고 / 한계
60
+
61
+ - 쓰기 동작 중 AI가 생성하는 것(적합도 분석·자소서 본문·면접 질문)은 시드에 미리 채워 보여주고,
62
+ 대시보드의 수동 쓰기 경로(공고/프로필/상태/문서 CRUD)만 메모리에 반영한다.
63
+ - 데이터는 전부 브라우저 메모리에만 존재. 외부 전송 없음(프라이버시 약속 유지).
64
+ - 검증: 로컬에서 `install-page`를 정적 서빙하고 Playwright로 `/demo` 전 페이지 스크린샷 확인.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "careermate",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "CareerMate — an MCP-first, local-first career workspace. Connect it to a local AI agent (Claude Desktop, Claude Code, Codex); CareerMate stores your career data locally on your machine and exposes it over MCP.",
6
6
  "license": "MIT",
@@ -29,7 +29,6 @@
29
29
  "apps",
30
30
  "packages",
31
31
  "scripts",
32
- "install-page",
33
32
  "docs",
34
33
  "tsconfig.json",
35
34
  "README.md",
@@ -48,6 +47,8 @@
48
47
  "mcp": "node --no-warnings --experimental-sqlite --import tsx apps/mcp/src/index.ts",
49
48
  "migrate": "node --no-warnings --experimental-sqlite --import tsx scripts/migrate.ts",
50
49
  "seed": "node --no-warnings --experimental-sqlite --import tsx scripts/seed.ts",
50
+ "shots:seed": "node --no-warnings --experimental-sqlite --import tsx scripts/shots-seed.ts",
51
+ "shots": "node --no-warnings --experimental-sqlite --import tsx scripts/shots.ts",
51
52
  "doctor": "node --no-warnings --experimental-sqlite --import tsx scripts/doctor.ts",
52
53
  "test": "node --no-warnings scripts/run.mjs scripts/test.ts \"TEST_VERDICT PASS\"",
53
54
  "test:ui": "node --no-warnings scripts/run.mjs scripts/ui-smoke.ts \"UI_VERDICT PASS\"",
@@ -55,13 +56,17 @@
55
56
  },
56
57
  "dependencies": {
57
58
  "@modelcontextprotocol/sdk": "^1.12.0",
59
+ "fflate": "^0.8.3",
60
+ "hwp.js": "^0.0.3",
61
+ "mammoth": "^1.12.0",
62
+ "pdfjs-dist": "^6.0.0",
58
63
  "tsx": "^4.19.2",
59
64
  "zod": "^3.24.1"
60
65
  },
61
66
  "devDependencies": {
62
67
  "@playwright/test": "^1.60.0",
63
68
  "@types/node": "^22.10.0",
64
- "esbuild": "^0.24.0",
69
+ "esbuild": "^0.28.1",
65
70
  "playwright": "^1.60.0",
66
71
  "typescript": "^5.7.2"
67
72
  }
@@ -20,6 +20,12 @@ import {
20
20
  type DocumentRecord,
21
21
  type ProfileInput,
22
22
  type ProfileRecord,
23
+ type ExperienceInput,
24
+ type ExperienceRecord,
25
+ type ProjectInput,
26
+ type ProjectRecord,
27
+ type SkillInput,
28
+ type SkillRecord,
23
29
  APPLICATION_STATUS_LABELS,
24
30
  INTERVIEW_UNLOCK_STATUSES,
25
31
  } from '@careermate/shared';
@@ -31,6 +37,9 @@ import {
31
37
  interviewRepo,
32
38
  documentRepo,
33
39
  profileRepo,
40
+ experienceRepo,
41
+ projectRepo,
42
+ skillRepo,
34
43
  activityRepo,
35
44
  } from '@careermate/db';
36
45
 
@@ -50,6 +59,30 @@ export function addResume(input: Omit<DocumentInput, 'kind'> & { kind?: Document
50
59
  return doc;
51
60
  }
52
61
 
62
+ /* ----------------------------------------------- Experiences / Projects / Skills */
63
+
64
+ // These structured-profile entities have no dedicated activity type; the dashboard
65
+ // logs them under 'profile_updated' (see apps/web routes), so the MCP path matches.
66
+ // Summaries carry only company/project/skill names — never résumé body text.
67
+
68
+ export function addExperience(input: ExperienceInput): ExperienceRecord {
69
+ const exp = experienceRepo.add(input);
70
+ activityRepo.log('profile_updated', `경력(${exp.company})을 추가했습니다.`, 'experience', exp.id);
71
+ return exp;
72
+ }
73
+
74
+ export function addProject(input: ProjectInput): ProjectRecord {
75
+ const prj = projectRepo.add(input);
76
+ activityRepo.log('profile_updated', `프로젝트(${prj.name})를 추가했습니다.`, 'project', prj.id);
77
+ return prj;
78
+ }
79
+
80
+ export function addSkill(input: SkillInput): SkillRecord {
81
+ const skill = skillRepo.add(input);
82
+ activityRepo.log('profile_updated', `기술(${skill.name})을 추가했습니다.`, 'skill', skill.id);
83
+ return skill;
84
+ }
85
+
53
86
  /* -------------------------------------------------------------------- Jobs */
54
87
 
55
88
  /** Save (or update) a posting and make sure an application row tracks it. */
@@ -139,7 +172,15 @@ export function updateApplicationStatus(
139
172
  export function saveInterviewPrep(input: InterviewPrepInput): InterviewPrepRecord {
140
173
  const job = jobRepo.get(input.job_id);
141
174
  if (!job) throw new Error(`공고를 찾을 수 없습니다: ${input.job_id}`);
142
- applicationRepo.ensure(job.id);
175
+ // Spec gate: interview prep is only allowed once the application has reached
176
+ // 서류 합격(document_passed) or beyond. Enforce it server-side, not just in prompts.
177
+ const app = applicationRepo.getByJob(job.id);
178
+ if (!app || !INTERVIEW_UNLOCK_STATUSES.includes(app.status)) {
179
+ const current = app ? APPLICATION_STATUS_LABELS[app.status] : '미지원';
180
+ throw new Error(
181
+ `면접 준비는 '서류 합격' 이상 상태에서만 저장할 수 있습니다. 현재 상태: '${current}'. 먼저 update_application_status로 상태를 올린 뒤 다시 시도하세요.`,
182
+ );
183
+ }
143
184
  const prep = interviewRepo.save(input);
144
185
  activityRepo.log('interview_prep_saved', `${job.company} · ${job.position} 면접 준비 자료를 저장했습니다.`, 'interview_prep', prep.id);
145
186
  return prep;
@@ -28,6 +28,36 @@ export function closeDb(): void {
28
28
  }
29
29
  }
30
30
 
31
+ let _inTx = false;
32
+
33
+ /**
34
+ * Run `fn` inside a single transaction so multi-statement writes are atomic:
35
+ * any thrown error rolls back every statement instead of leaving a half-written
36
+ * row. node:sqlite has no transaction() helper, so we drive BEGIN/COMMIT/ROLLBACK
37
+ * directly. Re-entrant: a nested tx() call joins the outer transaction (SQLite
38
+ * has no nested transactions), so only the outermost call commits or rolls back.
39
+ */
40
+ export function tx<T>(fn: () => T): T {
41
+ if (_inTx) return fn();
42
+ const db = getDb();
43
+ db.exec('BEGIN');
44
+ _inTx = true;
45
+ try {
46
+ const result = fn();
47
+ db.exec('COMMIT');
48
+ return result;
49
+ } catch (err) {
50
+ try {
51
+ db.exec('ROLLBACK');
52
+ } catch {
53
+ /* ignore rollback failure; surface the original error */
54
+ }
55
+ throw err;
56
+ } finally {
57
+ _inTx = false;
58
+ }
59
+ }
60
+
31
61
  /** Helpers for the JSON-text columns. */
32
62
  export function toJson(value: unknown): string {
33
63
  return JSON.stringify(value ?? null);
@@ -19,4 +19,5 @@ export {
19
19
  applicationRepo,
20
20
  interviewRepo,
21
21
  activityRepo,
22
+ getEntityCounts,
22
23
  } from './repositories.ts';
@@ -34,6 +34,17 @@ export function getUploadsDir(): string {
34
34
  return dir;
35
35
  }
36
36
 
37
+ /**
38
+ * Drop folder where the user places existing résumé/career files for the AI to
39
+ * ingest — a workaround for CLI clients that can't attach files (only paths).
40
+ * Lives under the data dir (not the user's project) so it never pollutes a repo.
41
+ */
42
+ export function getInboxDir(): string {
43
+ const dir = path.join(getDataDir(), 'inbox');
44
+ fs.mkdirSync(dir, { recursive: true });
45
+ return dir;
46
+ }
47
+
37
48
  export function getBackupsDir(): string {
38
49
  const dir = path.join(getDataDir(), 'backups');
39
50
  fs.mkdirSync(dir, { recursive: true });