careermate 0.1.1 → 0.1.3

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 (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +14 -10
  3. package/THIRD_PARTY_NOTICES.md +15 -0
  4. package/apps/mcp/src/index.ts +4 -5
  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/index.html +10 -2
  10. package/apps/web/public/lib.js +134 -22
  11. package/apps/web/public/pages/applications.js +2 -9
  12. package/apps/web/public/pages/documents.js +73 -52
  13. package/apps/web/public/pages/home.js +16 -18
  14. package/apps/web/public/pages/interview.js +9 -12
  15. package/apps/web/public/pages/jobs.js +40 -41
  16. package/apps/web/public/pages/profile.js +69 -89
  17. package/apps/web/public/pages/settings.js +15 -10
  18. package/apps/web/public/styles.css +63 -4
  19. package/apps/web/src/http.ts +31 -4
  20. package/apps/web/src/info.ts +3 -3
  21. package/apps/web/src/routes.ts +2 -1
  22. package/apps/web/src/server.ts +1 -4
  23. package/dist/doctor.mjs +72715 -697
  24. package/dist/install-page/analytics.js +97 -0
  25. package/dist/install-page/index.html +94 -112
  26. package/dist/install-page/landing.js +54 -0
  27. package/dist/install-page/og.png +0 -0
  28. package/dist/install-page/robots.txt +4 -0
  29. package/dist/install-page/sitemap.xml +21 -0
  30. package/dist/install-page/start.html +26 -46
  31. package/dist/install-page/style.css +105 -37
  32. package/dist/mcp.mjs +74044 -2062
  33. package/dist/migrate.mjs +110 -90
  34. package/dist/pdf.worker.min.mjs +28 -0
  35. package/dist/public/app.js +34 -2
  36. package/dist/public/index.html +10 -2
  37. package/dist/public/lib.js +134 -22
  38. package/dist/public/pages/applications.js +2 -9
  39. package/dist/public/pages/documents.js +73 -52
  40. package/dist/public/pages/home.js +16 -18
  41. package/dist/public/pages/interview.js +9 -12
  42. package/dist/public/pages/jobs.js +40 -41
  43. package/dist/public/pages/profile.js +69 -89
  44. package/dist/public/pages/settings.js +15 -10
  45. package/dist/public/styles.css +63 -4
  46. package/dist/web.mjs +39740 -326
  47. package/package.json +6 -3
  48. package/packages/db/src/paths.ts +11 -0
  49. package/packages/db/src/repositories.ts +18 -1
  50. package/packages/mcp-tools/src/bridge.ts +55 -32
  51. package/packages/mcp-tools/src/index.ts +9 -0
  52. package/packages/mcp-tools/src/tools.ts +198 -75
  53. package/packages/mcp-tools/src/update.ts +330 -0
  54. package/packages/parsers/src/documents.ts +533 -0
  55. package/packages/parsers/src/index.ts +8 -5
  56. package/packages/prompts/src/onboarding.ts +2 -2
  57. package/packages/shared/src/build-info.ts +22 -0
  58. package/packages/shared/src/index.ts +1 -0
  59. package/packages/shared/src/schemas.ts +138 -83
  60. package/packages/workflows/src/definitions.ts +4 -2
  61. package/scripts/build-demo.mjs +130 -0
  62. package/scripts/build-dist.mjs +26 -1
  63. package/scripts/doctor.ts +16 -3
  64. package/scripts/gen-og.mjs +1 -1
  65. package/scripts/test.ts +25 -0
  66. package/scripts/ui-smoke.ts +32 -4
  67. package/docs/ARCHITECTURE.md +0 -208
  68. package/docs/CHANGES_V1.md +0 -103
  69. package/docs/DATA_MODEL.md +0 -460
  70. package/docs/DECISIONS.md +0 -277
  71. package/docs/DEMO.md +0 -242
  72. package/docs/MCP_TOOLS.md +0 -252
  73. package/docs/ROADMAP.md +0 -134
  74. package/docs/RUNTIME_DISTRIBUTION_RESEARCH.md +0 -135
  75. package/docs/START_WORKFLOW.md +0 -131
  76. package/docs/SUPPORTED_AI_APPS.md +0 -112
  77. package/docs/TODO.md +0 -57
  78. package/docs/UX_NOTES.md +0 -247
  79. package/docs/WORKFLOWS.md +0 -200
@@ -0,0 +1,97 @@
1
+ // =============================================================================
2
+ // CareerMate marketing-site analytics — careermate.life ONLY.
3
+ //
4
+ // SCOPE: this file loads on the PUBLIC marketing/demo site (install-page/) that
5
+ // Vercel serves. It must NEVER be loaded by the local dashboard (apps/web, bound
6
+ // to 127.0.0.1) — that would break the core promise in AGENTS.md:
7
+ // "절대 사용자 데이터를 외부로 업로드하지 마라."
8
+ // There is no user career data on this site; only anonymous page/traffic signals.
9
+ //
10
+ // What runs here:
11
+ // 1) Vercel Web Analytics — cookieless, no PII, no consent banner required.
12
+ // 2) Vercel Speed Insights — Core Web Vitals, also cookieless.
13
+ // 3) Google Analytics 4 — OPTIONAL. Off until GA_MEASUREMENT_ID is filled in.
14
+ // Runs under Google Consent Mode v2 with analytics_storage DENIED by default,
15
+ // i.e. cookieless pings, so it needs no consent banner and stays consistent
16
+ // with the local-first / privacy-first brand. (Flip consent to 'granted'
17
+ // later only if you add a real consent UI.)
18
+ //
19
+ // The marketing pages (index.html, start.html) are classic multi-page navigations,
20
+ // so a single pageview on load is enough. The demo (/demo/) is a hash-router SPA,
21
+ // so we emit a virtual pageview on every hashchange — otherwise every sub-route
22
+ // (#/jobs, #/applications, …) would collapse into one "/demo" view.
23
+ // =============================================================================
24
+ (function () {
25
+ 'use strict';
26
+
27
+ // ── 1. Vercel Web Analytics (cookieless) ───────────────────────────────────
28
+ // Queue shim per Vercel's HTML snippet; the injected script drains window.vaq.
29
+ window.va = window.va || function () { (window.vaq = window.vaq || []).push(arguments); };
30
+ loadScript('/_vercel/insights/script.js');
31
+
32
+ // ── 2. Vercel Speed Insights (cookieless) — OFF by default ──────────────────
33
+ // Vercel's Hobby plan allows Speed Insights on only ONE project at a time, and it's
34
+ // more valuable on the real app than on this rarely-changing static site (use the free
35
+ // PageSpeed Insights for ad-hoc checks here). Flip to true to load it (needs Enable in
36
+ // the Vercel dashboard too); leaving it false avoids a dead 404 script request.
37
+ var ENABLE_SPEED_INSIGHTS = false;
38
+ if (ENABLE_SPEED_INSIGHTS) {
39
+ window.si = window.si || function () { (window.siq = window.siq || []).push(arguments); };
40
+ loadScript('/_vercel/speed-insights/script.js');
41
+ }
42
+
43
+ // ── 3. Google Analytics 4 (optional, cookieless via Consent Mode v2) ────────
44
+ // Paste your GA4 measurement ID (looks like "G-XXXXXXXXXX") here to turn it on.
45
+ // Leaving it blank keeps GA off entirely — Vercel analytics above still runs.
46
+ var GA_MEASUREMENT_ID = 'G-HK9KWTYCBD';
47
+ if (/^G-[A-Z0-9]+$/.test(GA_MEASUREMENT_ID)) initGoogleAnalytics(GA_MEASUREMENT_ID);
48
+
49
+ // ── Demo SPA: report hash route changes to Vercel as a custom event ─────────
50
+ // Vercel's script auto-tracks History API navigations but not hashchange.
51
+ if (location.pathname.indexOf('/demo') === 0) {
52
+ window.addEventListener('hashchange', function () {
53
+ window.va('event', { name: 'demo_route', data: { route: currentRoute() } });
54
+ });
55
+ }
56
+
57
+ // ── helpers ─────────────────────────────────────────────────────────────────
58
+ function loadScript(src) {
59
+ var s = document.createElement('script');
60
+ s.src = src;
61
+ s.defer = true;
62
+ document.head.appendChild(s);
63
+ }
64
+
65
+ function currentRoute() {
66
+ return (location.hash || '').replace(/^#\/?/, '').split('/')[0] || 'home';
67
+ }
68
+
69
+ function initGoogleAnalytics(id) {
70
+ window.dataLayer = window.dataLayer || [];
71
+ function gtag() { window.dataLayer.push(arguments); }
72
+ window.gtag = gtag;
73
+ // Deny storage by default → GA runs cookieless. No banner required.
74
+ gtag('consent', 'default', {
75
+ ad_storage: 'denied',
76
+ ad_user_data: 'denied',
77
+ ad_personalization: 'denied',
78
+ analytics_storage: 'denied',
79
+ });
80
+ gtag('js', new Date());
81
+ // send_page_view:false so we control pageviews (incl. the demo's hash routes).
82
+ gtag('config', id, { send_page_view: false, anonymize_ip: true });
83
+ loadScript('https://www.googletagmanager.com/gtag/js?id=' + id);
84
+
85
+ sendGaPageview();
86
+ window.addEventListener('hashchange', sendGaPageview);
87
+ }
88
+
89
+ function sendGaPageview() {
90
+ if (!window.gtag) return;
91
+ window.gtag('event', 'page_view', {
92
+ page_path: location.pathname + location.search + location.hash,
93
+ page_location: location.href,
94
+ page_title: document.title,
95
+ });
96
+ }
97
+ })();
@@ -7,7 +7,7 @@
7
7
  <script>
8
8
  (function () {
9
9
  try {
10
- var t = window.localStorage.getItem('cf-theme');
10
+ var t = window.localStorage.getItem('careermate-theme') || window.localStorage.getItem('cf-theme');
11
11
  document.documentElement.setAttribute('data-theme', t === 'light' || t === 'dark' ? t : 'dark');
12
12
  } catch (e) { document.documentElement.setAttribute('data-theme', 'dark'); }
13
13
  })();
@@ -15,6 +15,7 @@
15
15
  <title>CareerMate — 내 AI를 커리어 비서로</title>
16
16
  <meta name="description" content="Claude Desktop·Claude Code·Codex 같은 로컬 AI 앱에 연결하세요. CareerMate가 이력서·자기소개서·지원 현황을 이 컴퓨터에만 저장하고 MCP로 당신의 AI에 연결합니다." />
17
17
  <link rel="canonical" href="https://careermate.life/" />
18
+ <meta name="google-site-verification" content="MfU5uwl1RNXKklsJhXz2hPCRytjLpCjCXSa2ls5JQH0" />
18
19
  <meta name="theme-color" content="#faf8f4" media="(prefers-color-scheme: light)" />
19
20
  <meta name="theme-color" content="#14130f" media="(prefers-color-scheme: dark)" />
20
21
 
@@ -42,8 +43,11 @@
42
43
 
43
44
  <link rel="preload" href="fonts/PretendardVariable.woff2" as="font" type="font/woff2" crossorigin />
44
45
  <link rel="stylesheet" href="style.css" />
46
+ <!-- Privacy-friendly analytics for the marketing site only (never the local 127.0.0.1 app). -->
47
+ <script src="/analytics.js" defer></script>
45
48
  </head>
46
49
  <body>
50
+ <a class="skip-link" href="#top">본문으로 건너뛰기</a>
47
51
  <!-- ============================================================ nav -->
48
52
  <nav class="nav">
49
53
  <a class="nav__brand" href="#top">
@@ -67,65 +71,90 @@
67
71
  </div>
68
72
  </nav>
69
73
 
70
- <main id="top">
74
+ <main id="top" tabindex="-1">
71
75
 
72
76
  <!-- ========================================================== hero -->
73
77
  <header class="hero">
74
- <span class="pill-badge">
75
- <span class="pill-badge__dot" aria-hidden="true"></span>
76
- 로컬 전용 · 무료
77
- </span>
78
- <h1 class="hero__title">
79
- 내가 쓰던 <span class="accent">AI</span>를<br />
80
- 나만의 <span class="accent">커리어 비서</span>로
81
- </h1>
82
- <p class="hero__lede">
83
- 이력서·자기소개서·지원 현황을 <strong>이 컴퓨터 안에서</strong> 관리하세요.<br />
84
- 분석과 글쓰기는 평소 쓰던 AI가, 데이터 보관은 CareerMate가 맡습니다.
85
- </p>
86
-
87
- <div class="hero__cta">
88
- <a class="btn btn--primary" href="#install">
89
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M7 10l5 5 5-5"/><path d="M12 15V3"/></svg>
90
- 설치 방법 보기
91
- </a>
92
- <a class="btn btn--ghost" href="start.html">처음이세요? — 내게 맞는 길 찾기</a>
78
+ <div class="hero__main">
79
+ <span class="pill-badge">
80
+ <span class="pill-badge__dot" aria-hidden="true"></span>
81
+ 로컬 전용 · 무료
82
+ </span>
83
+ <h1 class="hero__title">
84
+ 내가 쓰던 AI를<br />
85
+ 나만의 커리어 비서로
86
+ </h1>
87
+ <p class="hero__lede">
88
+ <strong>공고 분석 · 맞춤 자기소개서 · 지원 현황 추적</strong>을 평소 쓰던 AI에게 말로 시키면, 결과가 이 컴퓨터 안에 쌓입니다. 한 번 정리한 경력은 대화마다 다시 붙여넣지 않아도 돼요.
89
+ </p>
90
+
91
+ <ul class="trust-strip" aria-label="안심 포인트">
92
+ <li>내 컴퓨터에만 저장</li>
93
+ <li>외부 업로드 없음</li>
94
+ <li>계정·API 키 불필요</li>
95
+ <li>언제든 삭제</li>
96
+ </ul>
97
+
98
+ <div class="hero__cta">
99
+ <a class="btn btn--primary" href="start.html">처음이세요? — 내게 맞는 설치법 찾기</a>
100
+ <a class="btn btn--ghost" href="#install">
101
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M7 10l5 5 5-5"/><path d="M12 15V3"/></svg>
102
+ 바로 설치 방법 보기
103
+ </a>
104
+ </div>
105
+
106
+ <p class="hero__note">
107
+ CareerMate는 <strong>내 컴퓨터에서 도는 로컬 도구</strong>예요. 그래서 <span class="nowrap">Claude&nbsp;Desktop · Claude&nbsp;Code · Codex</span>처럼 내 컴퓨터의 프로그램을 띄울 수 있는 AI에서 동작합니다. 설치를 마쳤다면 대시보드 <code>http://127.0.0.1:4319</code>에서 내 데이터를 확인하세요.
108
+ </p>
93
109
  </div>
94
110
 
95
- <p class="hero__note">
96
- CareerMate는 <strong>내 컴퓨터에서 도는 로컬 도구</strong>예요. 그래서 <span class="nowrap">Claude&nbsp;Desktop · Claude&nbsp;Code · Codex</span>처럼 내 컴퓨터의 프로그램을 띄울 수 있는 AI에서 동작합니다.<br />
97
- 설치를 마쳤다면 대시보드 <code>http://127.0.0.1:4319</code>에서 내 데이터를 확인하세요.
98
- </p>
111
+ <!-- signature element: CareerMate를 설치하는 방식 자체가 "내 AI에게 한 문장" — 그 순간을 보여준다.
112
+ 장식적 예시라 aria-hidden; 실제 복사 가능한 안내는 #install에 맥락과 함께 있다. -->
113
+ <aside class="hero__demo" aria-hidden="true">
114
+ <div class="chat">
115
+ <div class="chat__head">
116
+ <span class="chat__dot"></span>
117
+ 내 AI 앱
118
+ </div>
119
+ <div class="chat__msg chat__msg--me">
120
+ <span class="chat__who">나</span>
121
+ <p>careermate.life/llms-install.txt 읽고, 이 폴더에 CareerMate 설치하고 연결해줘.</p>
122
+ </div>
123
+ <div class="chat__msg chat__msg--ai">
124
+ <span class="chat__who">AI</span>
125
+ <p>설치했어요. 이제 이력서를 주거나 공고를 붙여넣어 보세요.</p>
126
+ <ul class="chat__steps">
127
+ <li>Node 확인 후 이 폴더에 설치</li>
128
+ <li>데이터는 <code>.cm/data</code>에만 저장</li>
129
+ <li>대시보드 <code>127.0.0.1:4319</code> 열림</li>
130
+ </ul>
131
+ </div>
132
+ </div>
133
+ <p class="hero__demo-note">내 AI 앱 대화창에 붙여넣으면 끝 — 터미널이 없어도 돼요.</p>
134
+ </aside>
99
135
  </header>
100
136
 
101
137
  <!-- ========================================================== how -->
102
138
  <section id="how" class="section">
103
139
  <div class="section__head">
104
- <span class="eyebrow">동작 방식</span>
105
- <h2>당신의 AI가 <span class="accent">두뇌</span>,<br />CareerMate는 <span class="accent">커리어 서랍장</span></h2>
106
- <p>CareerMate 안에는 AI가 들어 있지 않습니다.<br />생각하는 일은 당신의 AI가, 데이터 보관은 CareerMate가 맡습니다.</p>
140
+ <h2>당신의 AI가 두뇌, CareerMate는 커리어 서랍장</h2>
141
+ <p>CareerMate 안에는 AI가 들어 있지 않습니다. 생각하는 일은 당신의 AI가, 데이터 보관은 CareerMate가 맡습니다.</p>
107
142
  </div>
108
143
  <div class="cards-3">
109
144
  <article class="feature">
110
- <div class="feature__icon">
111
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5a3 3 0 1 0-5.99.13 3 3 0 0 0-1.5 5.5A3 3 0 0 0 6 16.5a3 3 0 0 0 6 .5V5z"/><path d="M12 5a3 3 0 1 1 5.99.13 3 3 0 0 1 1.5 5.5A3 3 0 0 1 18 16.5a3 3 0 0 1-6 .5V5z"/></svg>
112
- </div>
113
- <h3>1. 당신의 AI와 대화</h3>
114
- <p>평소 쓰던 Claude에게 말로 시키면 됩니다.<br />분석·글쓰기 같은 “생각”은 전부 당신의 AI가 합니다.</p>
145
+ <span class="feature__no">01</span>
146
+ <h3>당신의 AI와 대화</h3>
147
+ <p>평소 쓰던 Claude에게 말로 시키면 됩니다. 분석·글쓰기 같은 “생각”은 전부 당신의 AI가 합니다.</p>
115
148
  </article>
116
149
  <article class="feature">
117
- <div class="feature__icon">
118
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 12h6"/><path d="M9 12a3 3 0 0 1-3-3V7a3 3 0 0 1 3-3"/><path d="M15 12a3 3 0 0 0 3 3v2a3 3 0 0 1-3 3"/></svg>
119
- </div>
120
- <h3>2. MCP로 연결</h3>
121
- <p>표준 프로토콜 MCP가 AI와 CareerMate를 잇는 “USB-C” 역할을 합니다.<br />AI가 24개 도구로 당신의 커리어 DB를 읽고 씁니다.</p>
150
+ <span class="feature__no">02</span>
151
+ <h3>안전하게 연결</h3>
152
+ <p>표준 연결 방식(MCP)이 AI와 CareerMate를 잇는 다리가 됩니다. AI가 당신의 커리어 기록을 직접 읽고 정리해 저장합니다.</p>
122
153
  </article>
123
154
  <article class="feature">
124
- <div class="feature__icon">
125
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="6" rx="1.5"/><rect x="3" y="14" width="18" height="6" rx="1.5"/><path d="M7 7h.01M7 17h.01"/></svg>
126
- </div>
127
- <h3>3. 이 컴퓨터에 저장</h3>
128
- <p>모든 데이터는 로컬 SQLite(<code>~/.careermate</code>)에만 저장됩니다.<br />외부 서버로 전송하지 않습니다. 대시보드로 눈으로 확인하세요.</p>
155
+ <span class="feature__no">03</span>
156
+ <h3>이 컴퓨터에 저장</h3>
157
+ <p>모든 데이터는 로컬 SQLite(<code>~/.careermate</code>)에만 저장됩니다. 외부 서버로 전송하지 않고, 대시보드에서 눈으로 볼 수 있습니다.</p>
129
158
  </article>
130
159
  </div>
131
160
  </section>
@@ -133,8 +162,7 @@
133
162
  <!-- ====================================================== features -->
134
163
  <section id="features" class="section">
135
164
  <div class="section__head">
136
- <span class="eyebrow">할 수 있는 일</span>
137
- <h2>지원 한 건을 <span class="accent">끝까지</span> 함께 처리</h2>
165
+ <h2>지원 한 건을 끝까지 함께 처리</h2>
138
166
  <p>프로필 정리부터 면접 준비까지, AI와 대화만으로 이어집니다.</p>
139
167
  </div>
140
168
  <div class="checks">
@@ -149,10 +177,13 @@
149
177
 
150
178
  <!-- ======================================================= preview -->
151
179
  <section id="preview" class="section">
152
- <div class="section__head">
153
- <span class="eyebrow">대시보드</span>
154
- <h2>내 데이터를 <span class="accent">눈으로</span> 확인</h2>
155
- <p>모든 기록은 이 컴퓨터의 대시보드(<code>127.0.0.1:4319</code>)에 모입니다. 아래는 실제 화면입니다.</p>
180
+ <div class="section__head section__head--center">
181
+ <h2>내 데이터를 눈으로 확인</h2>
182
+ <p>모든 기록은 이 컴퓨터의 대시보드(<code>127.0.0.1:4319</code>)에 모입니다.<br>아래는 실제 화면 — 설치 없이 바로 둘러볼 수 있어요.</p>
183
+ <div class="section__cta">
184
+ <a class="btn btn--primary" href="demo/">데모 둘러보기 →</a>
185
+ <span class="section__cta-note">샘플 데이터로 클릭해 보는 미리보기 · 설치·로그인 불필요</span>
186
+ </div>
156
187
  </div>
157
188
 
158
189
  <div class="carousel" id="dash-carousel" role="group" aria-roledescription="캐러셀" aria-label="대시보드 화면 미리보기" tabindex="0">
@@ -256,9 +287,8 @@
256
287
 
257
288
  <!-- ======================================================= install -->
258
289
  <section id="install" class="section">
259
- <div class="section__head">
260
- <span class="eyebrow">설치</span>
261
- <h2>내 <span class="accent">AI 앱</span>에 연결하기</h2>
290
+ <div class="section__head section__head--center">
291
+ <h2>내 AI 앱에 연결하기</h2>
262
292
  <p>빈 폴더 잡고 <strong>한 문장이면 끝.</strong><br />
263
293
  터미널이 익숙하면 한 줄 설치, 싫으면 파일 하나로 — <strong>무료도 있습니다.</strong></p>
264
294
  </div>
@@ -276,19 +306,20 @@
276
306
  <details class="acc" open>
277
307
  <summary><span class="track__tag">추천 · 앱에서 한 문장</span> 내 AI 앱에서 빈 폴더 잡고 한 문장</summary>
278
308
  <div class="acc__body">
279
- <p class="track-desc"><strong>Claude 데스크톱의 “Code” 탭</strong>처럼 폴더를 잡고 작동하는 AI 앱이면 <strong>별도 터미널 없이</strong> 앱 안에서 끝납니다(Codex는 <strong>CLI</strong> 권장 — 데스크톱 앱은 등록이 사라지는 버그가 있어요). 작업 폴더 하나를 정하고 아래 문장만 주면, 에이전트가 공개 안내(<code>careermate.life/llms-install.txt</code>)를 읽고 <strong>그 폴더 안에</strong> 설치·연결합니다 — 데이터까지 그 폴더에 모여(<code>.cm/data</code>) 백업·이동이 폴더 하나로 끝.</p>
309
+ <p class="track-desc"><strong>Claude 데스크톱의 “Code” 탭</strong>처럼 폴더를 잡고 작동하는 AI 앱이면 <strong>별도 터미널 없이</strong> 앱 안에서 끝납니다(Codex는 <strong>CLI</strong> 권장 — 데스크톱 앱은 등록이 사라질 수 있습니다). 작업 폴더 하나를 정하고 아래 문장만 주면, 에이전트가 공개 안내(<code>careermate.life/llms-install.txt</code>)를 읽고 <strong>그 폴더 안에</strong> 설치·연결합니다. 데이터까지 그 폴더에 모여(<code>.cm/data</code>) 백업·이동이 폴더 하나로 끝.</p>
280
310
  <ol class="steps">
281
311
  <li class="step">
282
312
  <div class="step__no">1</div>
283
313
  <div class="step__body">
284
314
  <h3>앱 열고 빈 폴더 선택</h3>
285
- <p><strong>Claude 데스크톱</strong> → <strong>Code</strong> 탭에서 빈 폴더를 작업 폴더로 선택(<strong>Pro 이상</strong> 구독, <strong>Windows는 Git 먼저</strong>). <strong>Codex</strong>는 빈 폴더에서 <code>codex</code>(CLI, <strong>ChatGPT Plus 이상</strong>)로 실행하세요 — <strong>데스크톱 앱은 현재 등록이 사라지는 버그</strong>(openai/codex#24718)가 있어 권하지 않습니다.</p>
315
+ <p><strong>Claude 데스크톱</strong> → <strong>Code</strong> 탭에서 빈 폴더를 작업 폴더로 선택(<strong>Pro 이상</strong> 구독, <strong>Windows는 Git 먼저</strong>). <strong>Codex</strong>는 빈 폴더에서 <code>codex</code>(CLI, <strong>ChatGPT Plus 이상</strong>)로 실행하세요 — <strong>데스크톱 앱은 등록이 사라질 수 있어</strong> 권장하지 않습니다.</p>
286
316
  </div>
287
317
  </li>
288
318
  <li class="step">
289
319
  <div class="step__no">2</div>
290
320
  <div class="step__body">
291
321
  <h3>이 문장 붙여넣기</h3>
322
+ <p class="paste-note">💬 위에서 연 <strong>AI 앱의 대화창에</strong> 붙여넣으세요 — 터미널 명령이 아니에요. 이 문장은 공개 안내 페이지만 읽어 설치하며, <strong>당신의 이력서·개인정보는 외부로 올라가지 않아요.</strong></p>
292
323
  <div class="code">
293
324
  <button class="copy-btn" type="button" data-copy-target="code-prompt-agent">복사</button>
294
325
  <pre id="code-prompt-agent">https://careermate.life/llms-install.txt 를 읽고, 그 안내대로 CareerMate를 이 폴더에 설치하고 연결해줘. 데이터도 이 폴더의 .cm/data 안에 저장하고, Node 설치처럼 시스템에 바꿀 게 있으면 실행 전에 먼저 알려줘. 설치가 끝나면 앱 재시작 방법과 이력서를 주는 법, 대시보드 주소(http://127.0.0.1:4319)도 알려줘.</pre>
@@ -300,7 +331,7 @@
300
331
  <div class="step__no">3</div>
301
332
  <div class="step__body">
302
333
  <h3>재시작 후 확인</h3>
303
- <p>AI 앱을 완전히 종료했다 다시 켜고(처음 한 번 승인을 물으면 허용), <strong>“<code>get_onboarding_status</code> 호출해서 확인해줘”</strong>로 점검하세요. Codex는 <code>/mcp</code>로도 확인됩니다.</p>
334
+ <p>AI 앱을 완전히 종료했다 다시 켜고(처음 한 번 승인을 물으면 허용), <strong>“커리어메이트 연결됐는지 확인해줘”</strong>라고 말해 보세요. Codex는 <code>/mcp</code>로도 확인됩니다.</p>
304
335
  </div>
305
336
  </li>
306
337
  </ol>
@@ -329,7 +360,7 @@
329
360
  <div class="step__no">2</div>
330
361
  <div class="step__body">
331
362
  <h3>AI 앱 재시작 후 확인</h3>
332
- <p>연결한 AI 앱을 <strong>완전히 종료했다 다시 켜고</strong>, <strong>“<code>get_onboarding_status</code> 호출해서 연결됐는지 확인해줘”</strong>라고 말해 결과가 돌아오면 정상입니다.</p>
363
+ <p>연결한 AI 앱을 <strong>완전히 종료했다 다시 켜고</strong>, <strong>“커리어메이트 연결됐는지 확인해줘”</strong>라고 말해 결과가 돌아오면 정상입니다.</p>
333
364
  </div>
334
365
  </li>
335
366
  <li class="step">
@@ -347,7 +378,7 @@
347
378
  <details class="acc">
348
379
  <summary><span class="track__tag">Claude Desktop · <code>.mcpb</code></span> 설치 파일로 추가 (터미널 없이)</summary>
349
380
  <div class="acc__body">
350
- <p class="track-desc">터미널·Node 없이 파일 하나로 끝나지만, <strong>Claude Desktop 내장 Node 버전에 따라 불안정할 수 있어요.</strong> 잘 안 되면 위 <strong>에이전트가 직접 설치</strong> 또는 <strong>터미널 한 줄</strong>을 쓰세요.</p>
381
+ <p class="track-desc">터미널·Node 없이 파일 하나로 끝나지만, <strong>Claude Desktop 내장 Node 버전에 따라 불안정할 수 있습니다.</strong> 잘 안 되면 위 <strong>에이전트가 직접 설치</strong> 또는 <strong>터미널 한 줄</strong>을 쓰세요.</p>
351
382
  <ol class="steps">
352
383
  <li class="step">
353
384
  <div class="step__no">1</div>
@@ -372,7 +403,7 @@
372
403
  <div class="step__no">3</div>
373
404
  <div class="step__body">
374
405
  <h3>재시작하고 확인</h3>
375
- <p>Claude Desktop을 완전히 종료했다 다시 켜고, <strong>“<code>get_onboarding_status</code> 호출해서 확인해줘”</strong>로 점검하세요.</p>
406
+ <p>Claude Desktop을 완전히 종료했다 다시 켜고, <strong>“커리어메이트 연결됐는지 확인해줘”</strong>라고 말해 보세요.</p>
376
407
  </div>
377
408
  </li>
378
409
  </ol>
@@ -436,12 +467,15 @@
436
467
  </div>
437
468
  <div class="privacy-card__text">
438
469
  <h2>당신의 데이터는 당신 컴퓨터를 떠나지 않습니다</h2>
439
- <p>대시보드 서버는 <code>127.0.0.1</code>(이 컴퓨터)에만 바인딩되어 외부에서 접근할 수 없고,<br />MCP 서버는 네트워크 호출을 하지 않습니다. 분석·글쓰기는 당신이 직접 고른 AI가 수행합니다.</p>
470
+ <p>대시보드 서버는 <code>127.0.0.1</code>(이 컴퓨터)에만 바인딩되어 외부에서 접근할 수 없습니다.<br />MCP 서버가 외부로 내보내는 것은 (끌 수 있는) <strong>최신 버전 확인</strong>뿐이며, <strong>당신의 커리어 데이터는 전송하지 않습니다.</strong> 분석·글쓰기는 당신이 직접 고른 AI가 수행합니다.</p>
440
471
  <ul class="privacy-card__list">
441
472
  <li><strong>저장 위치</strong> <code>~/.careermate/careermate.sqlite</code></li>
442
473
  <li><strong>내보내기</strong> 자기소개서 등을 <code>~/.careermate/exports</code>로</li>
443
474
  <li><strong>삭제·관리</strong> 대시보드 <strong>설정(Settings)</strong>에서 직접</li>
444
475
  </ul>
476
+ <p class="privacy-card__note">
477
+ 위 내용은 <strong>설치하는 앱</strong> 이야기예요. 지금 보고 계신 <strong>이 소개 웹사이트</strong>는 방문 통계를 보려고 쿠키 없는 익명 분석(Vercel Analytics)만 사용하며, 여기엔 어떤 커리어 데이터도 포함되지 않습니다.
478
+ </p>
445
479
  </div>
446
480
  </div>
447
481
  </section>
@@ -460,42 +494,10 @@
460
494
 
461
495
  <div class="sr-only" role="status" aria-live="polite" id="copy-status"></div>
462
496
 
497
+ <script src="landing.js"></script>
463
498
  <script>
464
499
  (function () {
465
500
  'use strict';
466
- var status = document.getElementById('copy-status');
467
-
468
- // --- generic clipboard helper ------------------------------------
469
- function legacyCopy(text) {
470
- var ta = document.createElement('textarea');
471
- ta.value = text; ta.setAttribute('readonly', '');
472
- ta.style.position = 'absolute'; ta.style.left = '-9999px';
473
- document.body.appendChild(ta); ta.select();
474
- var ok = false;
475
- try { ok = document.execCommand('copy'); } catch (e) { ok = false; }
476
- document.body.removeChild(ta);
477
- return ok;
478
- }
479
- function copyText(text, onok) {
480
- if (navigator.clipboard && navigator.clipboard.writeText) {
481
- navigator.clipboard.writeText(text).then(onok, function () { if (legacyCopy(text)) onok(); });
482
- } else if (legacyCopy(text)) { onok(); }
483
- }
484
- function announce(msg) { if (status) status.textContent = msg; }
485
-
486
- // --- copy buttons (code blocks) ----------------------------------
487
- function flashSmall(btn) {
488
- var original = btn.textContent;
489
- btn.textContent = '복사됨'; btn.classList.add('copied');
490
- announce('클립보드에 복사되었습니다.');
491
- window.setTimeout(function () { btn.textContent = original; btn.classList.remove('copied'); }, 1600);
492
- }
493
- document.querySelectorAll('.copy-btn').forEach(function (btn) {
494
- btn.addEventListener('click', function () {
495
- var src = document.getElementById(btn.getAttribute('data-copy-target'));
496
- if (src) copyText(src.textContent || '', function () { flashSmall(btn); });
497
- });
498
- });
499
501
 
500
502
  // --- config tabs -------------------------------------------------
501
503
  var tablist = document.querySelector('.tabs[role="tablist"]');
@@ -540,26 +542,6 @@
540
542
  revealEls.forEach(function (el) { io.observe(el); });
541
543
  }
542
544
 
543
- // --- theme toggle (persist) --------------------------------------
544
- var root = document.documentElement;
545
- var toggle = document.getElementById('theme-toggle');
546
- var saved = null;
547
- try { saved = window.localStorage.getItem('cf-theme'); } catch (e) {}
548
- if (saved === 'dark' || saved === 'light') root.setAttribute('data-theme', saved);
549
- function isDark() {
550
- var t = root.getAttribute('data-theme');
551
- if (t === 'dark') return true;
552
- if (t === 'light') return false;
553
- return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
554
- }
555
- if (toggle) {
556
- toggle.addEventListener('click', function () {
557
- var next = isDark() ? 'light' : 'dark';
558
- root.setAttribute('data-theme', next);
559
- try { window.localStorage.setItem('cf-theme', next); } catch (e) {}
560
- });
561
- }
562
-
563
545
  // --- dashboard carousel (prev/next + dots + keyboard + swipe) -----
564
546
  var carousel = document.getElementById('dash-carousel');
565
547
  var track = document.getElementById('carousel-track');
@@ -0,0 +1,54 @@
1
+ // Shared behavior for the CareerMate landing pages (index.html, start.html).
2
+ // Loaded after the DOM is parsed; wires the clipboard "copy" buttons and the theme toggle.
3
+ // NOTE: the pre-paint theme resolution stays INLINE in each page's <head> to avoid a flash (FOUC) --
4
+ // do not move that into this file.
5
+ (function () {
6
+ 'use strict';
7
+ var status = document.getElementById('copy-status');
8
+
9
+ // --- clipboard ---------------------------------------------------
10
+ function legacyCopy(text) {
11
+ var ta = document.createElement('textarea');
12
+ ta.value = text; ta.setAttribute('readonly', '');
13
+ ta.style.position = 'absolute'; ta.style.left = '-9999px';
14
+ document.body.appendChild(ta); ta.select();
15
+ var ok = false;
16
+ try { ok = document.execCommand('copy'); } catch (e) { ok = false; }
17
+ document.body.removeChild(ta);
18
+ return ok;
19
+ }
20
+ function copyText(text, onok) {
21
+ if (navigator.clipboard && navigator.clipboard.writeText) {
22
+ navigator.clipboard.writeText(text).then(onok, function () { if (legacyCopy(text)) onok(); });
23
+ } else if (legacyCopy(text)) { onok(); }
24
+ }
25
+ document.querySelectorAll('.copy-btn').forEach(function (btn) {
26
+ btn.addEventListener('click', function () {
27
+ var src = document.getElementById(btn.getAttribute('data-copy-target'));
28
+ if (!src) return;
29
+ copyText(src.textContent || '', function () {
30
+ var original = btn.textContent;
31
+ btn.textContent = '복사됨'; btn.classList.add('copied');
32
+ if (status) status.textContent = '클립보드에 복사되었습니다.';
33
+ window.setTimeout(function () { btn.textContent = original; btn.classList.remove('copied'); }, 1600);
34
+ });
35
+ });
36
+ });
37
+
38
+ // --- theme toggle (persist) --------------------------------------
39
+ var root = document.documentElement;
40
+ var toggle = document.getElementById('theme-toggle');
41
+ function isDark() {
42
+ var t = root.getAttribute('data-theme');
43
+ if (t === 'dark') return true;
44
+ if (t === 'light') return false;
45
+ return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
46
+ }
47
+ if (toggle) {
48
+ toggle.addEventListener('click', function () {
49
+ var next = isDark() ? 'light' : 'dark';
50
+ root.setAttribute('data-theme', next);
51
+ try { window.localStorage.setItem('careermate-theme', next); window.localStorage.removeItem('cf-theme'); } catch (e) {}
52
+ });
53
+ }
54
+ })();
Binary file
@@ -0,0 +1,4 @@
1
+ User-agent: *
2
+ Allow: /
3
+
4
+ Sitemap: https://careermate.life/sitemap.xml
@@ -0,0 +1,21 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3
+ <url>
4
+ <loc>https://careermate.life/</loc>
5
+ <lastmod>2026-06-17</lastmod>
6
+ <changefreq>weekly</changefreq>
7
+ <priority>1.0</priority>
8
+ </url>
9
+ <url>
10
+ <loc>https://careermate.life/start</loc>
11
+ <lastmod>2026-06-17</lastmod>
12
+ <changefreq>monthly</changefreq>
13
+ <priority>0.8</priority>
14
+ </url>
15
+ <url>
16
+ <loc>https://careermate.life/demo</loc>
17
+ <lastmod>2026-06-17</lastmod>
18
+ <changefreq>monthly</changefreq>
19
+ <priority>0.6</priority>
20
+ </url>
21
+ </urlset>