careermate 0.1.0 → 0.1.1

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 (93) hide show
  1. package/README.md +10 -7
  2. package/apps/mcp/src/index.ts +3 -1
  3. package/apps/web/DESIGN_GUIDE.md +2 -2
  4. package/apps/web/public/icon-180.png +0 -0
  5. package/apps/web/public/index.html +4 -1
  6. package/apps/web/public/lib.js +1 -1
  7. package/apps/web/public/pages/applications.js +17 -1
  8. package/apps/web/public/pages/settings.js +6 -4
  9. package/apps/web/public/styles.css +49 -36
  10. package/apps/web/src/info.ts +9 -22
  11. package/apps/web/src/routes.ts +17 -13
  12. package/apps/web/src/server.ts +33 -25
  13. package/apps/web/src/settings.ts +35 -17
  14. package/bin/careermate.mjs +8 -5
  15. package/dist/{mcpb-stage/dist/web.mjs → doctor.mjs} +1122 -1450
  16. package/dist/install-page/favicon.svg +1 -0
  17. package/dist/install-page/icon-180.png +0 -0
  18. package/dist/install-page/icon-512.png +0 -0
  19. package/dist/install-page/index.html +309 -167
  20. package/dist/install-page/llms-install.txt +202 -0
  21. package/dist/install-page/og.png +0 -0
  22. package/dist/install-page/shots/applications-dark.png +0 -0
  23. package/dist/install-page/shots/applications-light.png +0 -0
  24. package/dist/install-page/shots/documents-dark.png +0 -0
  25. package/dist/install-page/shots/documents-light.png +0 -0
  26. package/dist/install-page/shots/home-dark.png +0 -0
  27. package/dist/install-page/shots/home-light.png +0 -0
  28. package/dist/install-page/shots/interview-dark.png +0 -0
  29. package/dist/install-page/shots/interview-light.png +0 -0
  30. package/dist/install-page/shots/jobs-dark.png +0 -0
  31. package/dist/install-page/shots/jobs-light.png +0 -0
  32. package/dist/install-page/shots/settings-dark.png +0 -0
  33. package/dist/install-page/shots/settings-light.png +0 -0
  34. package/dist/install-page/start.html +346 -0
  35. package/dist/install-page/style.css +94 -3
  36. package/dist/mcp.mjs +326 -68
  37. package/dist/migrate.mjs +4715 -0
  38. package/dist/public/icon-180.png +0 -0
  39. package/dist/public/index.html +4 -1
  40. package/dist/public/lib.js +1 -1
  41. package/dist/public/pages/applications.js +17 -1
  42. package/dist/public/pages/settings.js +6 -4
  43. package/dist/public/styles.css +49 -36
  44. package/dist/web.mjs +323 -129
  45. package/docs/DEMO.md +3 -3
  46. package/docs/MCP_TOOLS.md +21 -2
  47. package/docs/ROADMAP.md +3 -3
  48. package/docs/RUNTIME_DISTRIBUTION_RESEARCH.md +135 -0
  49. package/docs/START_WORKFLOW.md +6 -0
  50. package/docs/SUPPORTED_AI_APPS.md +85 -33
  51. package/docs/WORKFLOWS.md +1 -1
  52. package/package.json +3 -2
  53. package/packages/core/src/services.ts +42 -1
  54. package/packages/db/src/connection.ts +30 -0
  55. package/packages/db/src/index.ts +1 -0
  56. package/packages/db/src/repositories.ts +109 -63
  57. package/packages/db/src/schema.ts +118 -0
  58. package/packages/exporters/src/html.ts +9 -12
  59. package/packages/mcp-tools/src/tools.ts +81 -0
  60. package/packages/shared/src/enums.ts +39 -0
  61. package/scripts/build-dist.mjs +27 -8
  62. package/scripts/build-mcpb.mjs +60 -4
  63. package/scripts/gen-og.mjs +86 -0
  64. package/scripts/init.ts +32 -3
  65. package/scripts/seed.ts +5 -5
  66. package/scripts/shots-seed.ts +192 -0
  67. package/scripts/shots.ts +89 -0
  68. package/tsconfig.json +3 -1
  69. package/dist/careermate.mcpb +0 -0
  70. package/dist/mcp-smoke.err +0 -3
  71. package/dist/mcpb-stage/README.md +0 -219
  72. package/dist/mcpb-stage/dist/install-page/index.html +0 -434
  73. package/dist/mcpb-stage/dist/install-page/style.css +0 -407
  74. package/dist/mcpb-stage/dist/install-page/vercel.json +0 -20
  75. package/dist/mcpb-stage/dist/mcp.mjs +0 -23704
  76. package/dist/mcpb-stage/dist/public/app.js +0 -118
  77. package/dist/mcpb-stage/dist/public/index.html +0 -41
  78. package/dist/mcpb-stage/dist/public/lib.js +0 -282
  79. package/dist/mcpb-stage/dist/public/pages/applications.js +0 -98
  80. package/dist/mcpb-stage/dist/public/pages/documents.js +0 -446
  81. package/dist/mcpb-stage/dist/public/pages/home.js +0 -263
  82. package/dist/mcpb-stage/dist/public/pages/interview.js +0 -230
  83. package/dist/mcpb-stage/dist/public/pages/jobs.js +0 -494
  84. package/dist/mcpb-stage/dist/public/pages/profile.js +0 -576
  85. package/dist/mcpb-stage/dist/public/pages/settings.js +0 -233
  86. package/dist/mcpb-stage/dist/public/styles.css +0 -420
  87. package/dist/mcpb-stage/manifest.json +0 -40
  88. package/docs/INSTALL.md +0 -148
  89. package/docs/INSTALL_AND_USAGE.md +0 -99
  90. package/install-page/index.html +0 -474
  91. package/install-page/style.css +0 -391
  92. package/install-page/vercel.json +0 -20
  93. /package/dist/{mcpb-stage/dist/public → install-page}/fonts/PretendardVariable.woff2 +0 -0
Binary file
@@ -5,7 +5,10 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>CareerMate — 내 커리어 흐름 관리</title>
7
7
  <meta name="description" content="ChatGPT/Claude와 함께 쓰는 로컬 커리어 관리 도구" />
8
- <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><rect width='24' height='24' rx='6' fill='%234f46e5'/><text x='12' y='17' font-size='14' text-anchor='middle' fill='white' font-family='sans-serif' font-weight='bold'>C</text></svg>" />
8
+ <meta name="theme-color" content="#faf8f4" media="(prefers-color-scheme: light)" />
9
+ <meta name="theme-color" content="#14130f" media="(prefers-color-scheme: dark)" />
10
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><rect width='24' height='24' rx='6' fill='%230f7256'/><text x='12' y='17' font-size='14' text-anchor='middle' fill='white' font-family='sans-serif' font-weight='bold'>C</text></svg>" />
11
+ <link rel="apple-touch-icon" href="/icon-180.png" />
9
12
  <link rel="preload" href="/fonts/PretendardVariable.woff2" as="font" type="font/woff2" crossorigin />
10
13
  <link rel="stylesheet" href="/styles.css" />
11
14
  </head>
@@ -49,7 +49,7 @@ export function mount(node, ...children) { clear(node); appendChildren(node, chi
49
49
  const ICONS = {
50
50
  home: '<path d="M3 10.5 12 3l9 7.5"/><path d="M5 9.5V21h14V9.5"/>',
51
51
  user: '<circle cx="12" cy="8" r="4"/><path d="M4 21c0-4 4-6 8-6s8 2 8 6"/>',
52
- briefcase: '<rect x="3" y="7" width="18" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M3 12h18"/>',
52
+ briefcase: '<rect x="3" y="8" width="18" height="11" rx="2.5"/><path d="M8.5 8V6.5a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2V8"/><path d="M3 13h18"/>',
53
53
  layers: '<path d="m12 3 9 5-9 5-9-5 9-5Z"/><path d="m3 13 9 5 9-5"/>',
54
54
  file: '<path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8Z"/><path d="M14 3v5h5"/>',
55
55
  mic: '<rect x="9" y="3" width="6" height="11" rx="3"/><path d="M5 11a7 7 0 0 0 14 0"/><path d="M12 18v3"/>',
@@ -12,6 +12,19 @@ const STATUS_COLOR = {
12
12
  final_passed: 'var(--green)', on_hold: 'var(--slate)', rejected: 'var(--red)',
13
13
  };
14
14
 
15
+ // deadline(YYYY-MM-DD) → "마감 D-7" / "마감 D-day" / "마감 지남" (null if absent/invalid)
16
+ function ddayLabel(deadline) {
17
+ if (!deadline) return null;
18
+ const d = new Date(`${deadline}T00:00:00`);
19
+ if (isNaN(d.getTime())) return null;
20
+ const today = new Date();
21
+ today.setHours(0, 0, 0, 0);
22
+ const days = Math.round((d.getTime() - today.getTime()) / 86400000);
23
+ if (days > 0) return `마감 D-${days}`;
24
+ if (days === 0) return '마감 D-day';
25
+ return '마감 지남';
26
+ }
27
+
15
28
  export async function render(ctx) {
16
29
  ctx.setActions([]);
17
30
  const data = await get('/api/applications');
@@ -58,6 +71,9 @@ export async function render(ctx) {
58
71
 
59
72
  function AppRow(app) {
60
73
  const job = app.job || {};
74
+ const subBits = [job.position, job.location].filter(Boolean);
75
+ const dday = ddayLabel(job.deadline);
76
+ if (dday) subBits.push(dday);
61
77
  const sel = Select(
62
78
  statusOptions.map((o) => ({ ...o, selected: o.value === app.status })),
63
79
  {
@@ -69,7 +85,7 @@ export async function render(ctx) {
69
85
  );
70
86
  return ListRow({
71
87
  title: job.company || '—',
72
- sub: job.position || '',
88
+ sub: subBits.join(' · '),
73
89
  trailing: [
74
90
  app.fit_score != null
75
91
  ? el('span', { class: `strong tnum ${scoreClass(app.fit_score)}` }, `${app.fit_score}점`)
@@ -1,7 +1,7 @@
1
1
  // Settings — data location, connection, theme, backup/export, and danger zone.
2
2
  // Everything stays local; this page surfaces where data lives and how to manage it.
3
3
  import {
4
- el, get, post, icon, navigate, Card, Stat, Btn, IconBtn, Input,
4
+ el, get, post, icon, navigate, Card, Btn, IconBtn, Input,
5
5
  openModal, copyText, downloadUrl, fmtRelative, toastOk, toastError, mount,
6
6
  } from '/lib.js';
7
7
 
@@ -134,12 +134,14 @@ function Theme() {
134
134
 
135
135
  /* ---------------------------------------------------------- 내 데이터 */
136
136
  function MyData(counts = {}) {
137
- const entries = Object.entries(COUNT_LABELS)
138
- .map(([key, label]) => Stat({ label, value: counts[key] ?? 0 }));
137
+ const items = Object.entries(COUNT_LABELS).map(([key, label]) =>
138
+ el('div', { class: 'countstrip__item' },
139
+ el('div', { class: 'countstrip__num tnum' }, String(counts[key] ?? 0)),
140
+ el('div', { class: 'countstrip__label' }, label)));
139
141
  return Card({
140
142
  title: '내 데이터',
141
143
  sub: '이 컴퓨터에 저장된 항목 수',
142
- body: el('div', { class: 'grid grid--3' }, ...entries),
144
+ body: el('div', { class: 'countstrip' }, ...items),
143
145
  });
144
146
  }
145
147
 
@@ -19,24 +19,24 @@
19
19
  }
20
20
 
21
21
  :root {
22
- /* Neutral palette (light) */
23
- --bg: #ffffff;
24
- --bg-subtle: #f7f7f8;
25
- --bg-sunken: #f1f1f3;
22
+ /* Warm paper neutrals (light) — unified with the public landing page */
23
+ --bg: #faf8f4;
24
+ --bg-subtle: #f2efe8;
25
+ --bg-sunken: #ece8df;
26
26
  --surface: #ffffff;
27
- --surface-hover: #f7f7f8;
28
- --border: #e6e6e9;
29
- --border-strong: #d4d4d8;
30
- --text: #1c1c1f;
31
- --text-secondary: #5c5c66;
32
- --text-tertiary: #8a8a94;
27
+ --surface-hover: #f5f2ec;
28
+ --border: #e6e1d6;
29
+ --border-strong: #d6d0c2;
30
+ --text: #1c1a16;
31
+ --text-secondary: #595449;
32
+ --text-tertiary: #8b867a;
33
33
  --text-on-accent: #ffffff;
34
34
 
35
- /* Accent — calm indigo */
36
- --accent: #4f46e5;
37
- --accent-hover: #4338ca;
38
- --accent-soft: #eef2ff;
39
- --accent-border: #c7d2fe;
35
+ /* Accent — deep emerald (single flat accent, no gradient) */
36
+ --accent: #0f7256;
37
+ --accent-hover: #0b5b44;
38
+ --accent-soft: #e7f1ec;
39
+ --accent-border: #bcdccd;
40
40
 
41
41
  /* Semantic */
42
42
  --green: #15803d; --green-soft: #ecfdf3; --green-border: #bbf7d0;
@@ -67,31 +67,38 @@
67
67
 
68
68
  @media (prefers-color-scheme: dark) {
69
69
  :root:not([data-theme='light']) {
70
- --bg: #161618;
71
- --bg-subtle: #1d1d20;
72
- --bg-sunken: #0f0f11;
73
- --surface: #1d1d20;
74
- --surface-hover: #26262a;
75
- --border: #2c2c31;
76
- --border-strong: #3a3a41;
77
- --text: #ededf0;
78
- --text-secondary: #a5a5b0;
79
- --text-tertiary: #74747e;
80
- --accent: #818cf8;
81
- --accent-hover: #a5b4fc;
82
- --accent-soft: #1e1b3a;
83
- --accent-border: #3730a3;
70
+ --bg: #14130f;
71
+ --bg-subtle: #201d17;
72
+ --bg-sunken: #100f0b;
73
+ --surface: #1b1915;
74
+ --surface-hover: #262219;
75
+ --border: #2e2a22;
76
+ --border-strong: #3e3930;
77
+ --text: #f2eee5;
78
+ --text-secondary: #aaa599;
79
+ --text-tertiary: #79746a;
80
+ --accent: #34d399;
81
+ --accent-hover: #6ee7b7;
82
+ --accent-soft: #11281d;
83
+ --accent-border: #1f4f3c;
84
+ --text-on-accent: #05241a;
84
85
  --green-soft:#0e2a1a; --teal-soft:#0c2826; --blue-soft:#11203f; --violet-soft:#1e1638;
85
86
  --amber-soft:#2a2008; --red-soft:#2c1414; --slate-soft:#1f242b;
87
+ /* lighter semantic foregrounds so status badges stay legible on dark soft bg */
88
+ --green:#4ade80; --teal:#2dd4bf; --blue:#60a5fa; --violet:#a78bfa; --amber:#fbbf24; --red:#ef4444; --slate:#94a3b8;
89
+ --green-border:#1f4f3c; --teal-border:#155e57; --blue-border:#1e3a6b; --violet-border:#3b2f6b; --amber-border:#5c4410; --red-border:#5c2020; --slate-border:#3a4250;
86
90
  --shadow-lg: 0 12px 40px rgba(0,0,0,.5);
87
91
  }
88
92
  }
89
93
  :root[data-theme='dark'] {
90
- --bg:#161618; --bg-subtle:#1d1d20; --bg-sunken:#0f0f11; --surface:#1d1d20; --surface-hover:#26262a;
91
- --border:#2c2c31; --border-strong:#3a3a41; --text:#ededf0; --text-secondary:#a5a5b0; --text-tertiary:#74747e;
92
- --accent:#818cf8; --accent-hover:#a5b4fc; --accent-soft:#1e1b3a; --accent-border:#3730a3;
94
+ --bg:#14130f; --bg-subtle:#201d17; --bg-sunken:#100f0b; --surface:#1b1915; --surface-hover:#262219;
95
+ --border:#2e2a22; --border-strong:#3e3930; --text:#f2eee5; --text-secondary:#aaa599; --text-tertiary:#79746a;
96
+ --accent:#34d399; --accent-hover:#6ee7b7; --accent-soft:#11281d; --accent-border:#1f4f3c; --text-on-accent:#05241a;
93
97
  --green-soft:#0e2a1a; --teal-soft:#0c2826; --blue-soft:#11203f; --violet-soft:#1e1638;
94
- --amber-soft:#2a2008; --red-soft:#2c1414; --slate-soft:#1f242b; --shadow-lg:0 12px 40px rgba(0,0,0,.5);
98
+ --amber-soft:#2a2008; --red-soft:#2c1414; --slate-soft:#1f242b;
99
+ --green:#4ade80; --teal:#2dd4bf; --blue:#60a5fa; --violet:#a78bfa; --amber:#fbbf24; --red:#ef4444; --slate:#94a3b8;
100
+ --green-border:#1f4f3c; --teal-border:#155e57; --blue-border:#1e3a6b; --violet-border:#3b2f6b; --amber-border:#5c4410; --red-border:#5c2020; --slate-border:#3a4250;
101
+ --shadow-lg:0 12px 40px rgba(0,0,0,.5);
95
102
  }
96
103
 
97
104
  * { box-sizing: border-box; }
@@ -120,7 +127,7 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
120
127
  .sidebar__logo {
121
128
  width: 30px; height: 30px; border-radius: 8px; flex: none;
122
129
  background: var(--accent);
123
- display: grid; place-items: center; color: #fff; font-weight: 800; font-size: 15px;
130
+ display: grid; place-items: center; color: var(--text-on-accent); font-weight: 800; font-size: 15px;
124
131
  }
125
132
  .sidebar__title { font-weight: 700; font-size: 15px; letter-spacing: -.01em; }
126
133
  .sidebar__subtitle { font-size: 12px; color: var(--text-tertiary); margin-top: 1px; }
@@ -135,7 +142,7 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
135
142
  .nav__item svg { width: 17px; height: 17px; flex: none; }
136
143
  .nav__badge { margin-left: auto; font-size: 11.5px; background: var(--bg-sunken); color: var(--text-secondary);
137
144
  padding: 1px 7px; border-radius: 999px; font-weight: 600; }
138
- .nav__item.is-active .nav__badge { background: var(--accent); color: #fff; }
145
+ .nav__item.is-active .nav__badge { background: var(--accent); color: var(--text-on-accent); }
139
146
  .sidebar__foot { padding: 12px 16px; border-top: 1px solid var(--border); font-size: 12px; color: var(--text-tertiary); }
140
147
  .sidebar__foot .dot { display:inline-block; width:7px; height:7px; border-radius:999px; background: var(--green); margin-right:6px; }
141
148
 
@@ -184,6 +191,12 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
184
191
  .stat__label svg { width: 15px; height: 15px; color: var(--text-tertiary); }
185
192
  .stat__value { font-size: 26px; font-weight: 700; letter-spacing: -.02em; margin-top: 6px; }
186
193
  .stat__hint { font-size: 12.5px; color: var(--text-tertiary); margin-top: 2px; }
194
+ /* borderless count strip (settings 내 데이터) — flat counts, never a card-in-card */
195
+ .countstrip { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px 24px; }
196
+ .countstrip__item { display: flex; flex-direction: column; gap: 2px; }
197
+ .countstrip__num { font-size: 24px; font-weight: 700; letter-spacing: -.02em; }
198
+ .countstrip__label { font-size: 13px; color: var(--text-secondary); }
199
+ @media (max-width: 560px){ .countstrip { grid-template-columns: repeat(2, 1fr); } }
187
200
 
188
201
  /* ----------------------------------------------------------------- buttons */
189
202
  .btn {
@@ -195,7 +208,7 @@ h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
195
208
  .btn:hover { background: var(--surface-hover); }
196
209
  .btn:disabled { opacity: .5; cursor: not-allowed; }
197
210
  .btn svg { width: 15px; height: 15px; }
198
- .btn--primary { background: var(--accent); border-color: var(--accent); color: #fff; }
211
+ .btn--primary { background: var(--accent); border-color: var(--accent); color: var(--text-on-accent); }
199
212
  .btn--primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
200
213
  .btn--ghost { border-color: transparent; background: transparent; }
201
214
  .btn--ghost:hover { background: var(--surface-hover); }