careermate 0.0.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.
- package/AGENTS.md +33 -0
- package/INSTALL.md +230 -0
- package/LICENSE +21 -0
- package/README.md +532 -0
- package/THIRD_PARTY_NOTICES.md +57 -0
- package/bin/careermate.mjs +77 -0
- package/dist/career-os/README.md +78 -0
- package/dist/career-os/eop/cover-letter.md +46 -0
- package/dist/career-os/eop/fit-analysis.md +48 -0
- package/dist/career-os/eop/interview-prep.md +39 -0
- package/dist/career-os/eop/job-analysis.md +42 -0
- package/dist/career-os/eop/profile-extraction.md +37 -0
- package/dist/career-os/knowledge/ats.md +131 -0
- package/dist/career-os/knowledge/company-research.md +137 -0
- package/dist/career-os/knowledge/cover-letter.md +135 -0
- package/dist/career-os/knowledge/fit-matching.md +121 -0
- package/dist/career-os/knowledge/human-writing.md +118 -0
- package/dist/career-os/knowledge/interview-behavioral.md +132 -0
- package/dist/career-os/knowledge/interview-technical.md +126 -0
- package/dist/career-os/knowledge/linkedin-profile.md +152 -0
- package/dist/career-os/knowledge/networking-referrals.md +166 -0
- package/dist/career-os/knowledge/offer-evaluation-decision.md +121 -0
- package/dist/career-os/knowledge/onboarding-first-90-days.md +126 -0
- package/dist/career-os/knowledge/portfolio.md +121 -0
- package/dist/career-os/knowledge/recruiter-screen.md +105 -0
- package/dist/career-os/knowledge/rejection-triage-iteration.md +115 -0
- package/dist/career-os/knowledge/resume.md +123 -0
- package/dist/career-os/knowledge/salary-negotiation.md +123 -0
- package/dist/career-os/knowledge/verifiers/ats-compat.md +64 -0
- package/dist/career-os/knowledge/verifiers/consistency.md +61 -0
- package/dist/career-os/knowledge/verifiers/human-voice.md +82 -0
- package/dist/career-os/knowledge/verifiers/recency-staleness.md +72 -0
- package/dist/career-os/knowledge/verifiers/responsiveness-on-target.md +82 -0
- package/dist/career-os/knowledge/verifiers/truthfulness.md +67 -0
- package/dist/doctor.mjs +79094 -0
- package/dist/mcp.mjs +96108 -0
- package/dist/migrate.mjs +4735 -0
- package/dist/pdf.worker.min.mjs +28 -0
- package/dist/public/app.js +150 -0
- package/dist/public/fonts/PretendardVariable.woff2 +0 -0
- package/dist/public/icon-180.png +0 -0
- package/dist/public/index.html +52 -0
- package/dist/public/lib.js +394 -0
- package/dist/public/pages/applications.js +107 -0
- package/dist/public/pages/documents.js +467 -0
- package/dist/public/pages/home.js +261 -0
- package/dist/public/pages/interview.js +227 -0
- package/dist/public/pages/jobs.js +493 -0
- package/dist/public/pages/profile.js +556 -0
- package/dist/public/pages/settings.js +240 -0
- package/dist/public/styles.css +498 -0
- package/dist/site/analytics.js +97 -0
- package/dist/site/favicon.svg +1 -0
- package/dist/site/fonts/PretendardVariable.woff2 +0 -0
- package/dist/site/icon-180.png +0 -0
- package/dist/site/icon-512.png +0 -0
- package/dist/site/index.html +598 -0
- package/dist/site/landing.js +54 -0
- package/dist/site/llms-install.txt +217 -0
- package/dist/site/robots.txt +4 -0
- package/dist/site/sitemap.xml +21 -0
- package/dist/site/start.html +326 -0
- package/dist/site/style.css +550 -0
- package/dist/site/vercel.json +20 -0
- package/dist/web.mjs +46852 -0
- package/docs/FAQ.md +101 -0
- package/docs/MCP_TOOLS.md +311 -0
- package/docs/README.md +28 -0
- package/docs/START_WORKFLOW.md +137 -0
- package/docs/SUPPORTED_AI_APPS.md +112 -0
- package/docs/WORKFLOWS.md +202 -0
- package/docs/career-os/README.md +78 -0
- package/docs/career-os/eop/cover-letter.md +46 -0
- package/docs/career-os/eop/fit-analysis.md +48 -0
- package/docs/career-os/eop/interview-prep.md +39 -0
- package/docs/career-os/eop/job-analysis.md +42 -0
- package/docs/career-os/eop/profile-extraction.md +37 -0
- package/docs/career-os/knowledge/ats.md +131 -0
- package/docs/career-os/knowledge/company-research.md +137 -0
- package/docs/career-os/knowledge/cover-letter.md +135 -0
- package/docs/career-os/knowledge/fit-matching.md +121 -0
- package/docs/career-os/knowledge/human-writing.md +118 -0
- package/docs/career-os/knowledge/interview-behavioral.md +132 -0
- package/docs/career-os/knowledge/interview-technical.md +126 -0
- package/docs/career-os/knowledge/linkedin-profile.md +152 -0
- package/docs/career-os/knowledge/networking-referrals.md +166 -0
- package/docs/career-os/knowledge/offer-evaluation-decision.md +121 -0
- package/docs/career-os/knowledge/onboarding-first-90-days.md +126 -0
- package/docs/career-os/knowledge/portfolio.md +121 -0
- package/docs/career-os/knowledge/recruiter-screen.md +105 -0
- package/docs/career-os/knowledge/rejection-triage-iteration.md +115 -0
- package/docs/career-os/knowledge/resume.md +123 -0
- package/docs/career-os/knowledge/salary-negotiation.md +123 -0
- package/docs/career-os/knowledge/verifiers/ats-compat.md +64 -0
- package/docs/career-os/knowledge/verifiers/consistency.md +61 -0
- package/docs/career-os/knowledge/verifiers/human-voice.md +82 -0
- package/docs/career-os/knowledge/verifiers/recency-staleness.md +72 -0
- package/docs/career-os/knowledge/verifiers/responsiveness-on-target.md +82 -0
- package/docs/career-os/knowledge/verifiers/truthfulness.md +67 -0
- package/package.json +89 -0
- package/packages/core/src/context.ts +74 -0
- package/packages/core/src/index.ts +8 -0
- package/packages/core/src/onboarding.ts +81 -0
- package/packages/core/src/services.ts +187 -0
- package/packages/core/src/summary.ts +104 -0
- package/packages/db/src/connection.ts +76 -0
- package/packages/db/src/index.ts +23 -0
- package/packages/db/src/paths.ts +52 -0
- package/packages/db/src/repositories.ts +891 -0
- package/packages/db/src/runtime.ts +58 -0
- package/packages/db/src/schema.ts +307 -0
- package/packages/exporters/src/html.ts +110 -0
- package/packages/exporters/src/index.ts +364 -0
- package/packages/exporters/src/markdown.ts +178 -0
- package/packages/knowledge/src/index.ts +168 -0
- package/packages/mcp-tools/src/bridge.ts +106 -0
- package/packages/mcp-tools/src/index.ts +17 -0
- package/packages/mcp-tools/src/result.ts +49 -0
- package/packages/mcp-tools/src/tools.ts +747 -0
- package/packages/mcp-tools/src/update.ts +330 -0
- package/packages/parsers/src/documents.ts +533 -0
- package/packages/parsers/src/html.ts +86 -0
- package/packages/parsers/src/index.ts +231 -0
- package/packages/parsers/src/keywords.ts +151 -0
- package/packages/prompts/src/humanize.ts +59 -0
- package/packages/prompts/src/index.ts +82 -0
- package/packages/prompts/src/install.ts +43 -0
- package/packages/prompts/src/onboarding.ts +35 -0
- package/packages/prompts/src/system.ts +57 -0
- package/packages/shared/src/build-info.ts +22 -0
- package/packages/shared/src/enums.ts +142 -0
- package/packages/shared/src/index.ts +19 -0
- package/packages/shared/src/schemas.ts +453 -0
- package/packages/workflows/src/definitions.ts +109 -0
- package/packages/workflows/src/index.ts +39 -0
- package/scripts/init.ts +371 -0
- package/tsconfig.json +32 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// Settings — data location, connection, theme, backup/export, and danger zone.
|
|
2
|
+
// Everything stays local; this page surfaces where data lives and how to manage it.
|
|
3
|
+
import {
|
|
4
|
+
el, get, post, icon, navigate, Card, Btn, IconBtn, Input,
|
|
5
|
+
openModal, copyText, downloadUrl, fmtRelative, toastOk, toastError, mount,
|
|
6
|
+
} from '/lib.js';
|
|
7
|
+
|
|
8
|
+
const COUNT_LABELS = {
|
|
9
|
+
profile: '프로필', experiences: '경력', projects: '프로젝트', skills: '기술',
|
|
10
|
+
documents: '문서', cover_letters: '자기소개서', jobs: '채용공고',
|
|
11
|
+
applications: '지원', interview_preps: '면접 준비',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export async function render(ctx) {
|
|
15
|
+
ctx.setActions([]);
|
|
16
|
+
const { info, backups } = await get('/api/settings');
|
|
17
|
+
|
|
18
|
+
const wrap = el('div', { class: 'stack-4' },
|
|
19
|
+
DataLocation(info),
|
|
20
|
+
Connection(info),
|
|
21
|
+
Theme(),
|
|
22
|
+
MyData(info.counts),
|
|
23
|
+
Backup(backups, () => render(ctx)),
|
|
24
|
+
DangerZone(ctx),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
mount(ctx.view, wrap);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* ----------------------------------------------------- 데이터 저장 위치 */
|
|
31
|
+
function kvRow(label, value, copyable) {
|
|
32
|
+
return [
|
|
33
|
+
el('dt', {}, label),
|
|
34
|
+
el('dd', {},
|
|
35
|
+
el('div', { class: 'flex between gap-3' },
|
|
36
|
+
el('span', { class: 'truncate tnum', style: { fontFamily: 'var(--mono, ui-monospace, monospace)' }, title: value }, value || '—'),
|
|
37
|
+
copyable && value
|
|
38
|
+
? IconBtn('copy', { title: '복사', onClick: () => copyText(value) })
|
|
39
|
+
: null)),
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function DataLocation(info) {
|
|
44
|
+
const kv = el('dl', { class: 'kv' },
|
|
45
|
+
...kvRow('데이터 폴더', info.data_dir, true),
|
|
46
|
+
...kvRow('데이터베이스', info.db_path, true),
|
|
47
|
+
...kvRow('앱 버전', info.version, false),
|
|
48
|
+
...kvRow('Node 버전', info.node_version, false),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const privacy = el('div', { class: 'callout callout--privacy' },
|
|
52
|
+
icon('lock'),
|
|
53
|
+
el('div', {},
|
|
54
|
+
el('div', { class: 'callout__title' }, '내 컴퓨터에만 저장됩니다'),
|
|
55
|
+
el('div', { class: 'callout__body' },
|
|
56
|
+
'모든 데이터는 이 컴퓨터에만 저장됩니다. 외부로 전송되지 않습니다. AI에는 사용자가 직접 입력하거나 AI가 MCP로 조회한 정보만 전달됩니다.')));
|
|
57
|
+
|
|
58
|
+
return Card({
|
|
59
|
+
title: '데이터 저장 위치',
|
|
60
|
+
body: el('div', { class: 'stack-3' }, kv, privacy),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* ---------------------------------------------------------- 연결 상태 */
|
|
65
|
+
function Connection(info) {
|
|
66
|
+
const dashRow = el('div', { class: 'flex between gap-3', style: { padding: '4px 0' } },
|
|
67
|
+
el('div', { class: 'flex gap-3', style: { alignItems: 'center' } },
|
|
68
|
+
el('span', { class: 'badge badge--final_passed' }, el('span', { class: 'dot' }), '실행 중'),
|
|
69
|
+
el('div', {},
|
|
70
|
+
el('div', { class: 'strong' }, '대시보드'),
|
|
71
|
+
el('div', { class: 'muted text-sm tnum' }, info.url || '—'))),
|
|
72
|
+
info.url
|
|
73
|
+
? Btn('새 탭에서 열기', { icon: 'external', sm: true, variant: 'ghost', onClick: () => window.open(info.url, '_blank') })
|
|
74
|
+
: null);
|
|
75
|
+
|
|
76
|
+
const mcpRow = el('div', { class: 'flex between gap-3', style: { padding: '4px 0', alignItems: 'flex-start' } },
|
|
77
|
+
el('div', { class: 'flex gap-3', style: { alignItems: 'flex-start' } },
|
|
78
|
+
el('div', { class: 'empty__icon', style: { width: '30px', height: '30px', margin: 0, borderRadius: '8px' } }, icon('link')),
|
|
79
|
+
el('div', {},
|
|
80
|
+
el('div', { class: 'strong' }, 'MCP 서버'),
|
|
81
|
+
el('div', { class: 'muted text-sm', style: { maxWidth: '440px', lineHeight: '1.6' } },
|
|
82
|
+
'MCP 서버는 사용하시는 AI 클라이언트(Claude·ChatGPT·Cursor)가 직접 실행하며, 이 대시보드와 같은 데이터베이스를 공유합니다.'))),
|
|
83
|
+
Btn('설치·연결 안내 열기', { icon: 'external', sm: true, variant: 'ghost', onClick: () => window.open('/install', '_blank') }));
|
|
84
|
+
|
|
85
|
+
return Card({
|
|
86
|
+
title: '연결 상태',
|
|
87
|
+
body: el('div', { class: 'stack-2' }, dashRow, el('div', { class: 'divider' }), mcpRow),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* ------------------------------------------------------------- 테마 */
|
|
92
|
+
function currentTheme() {
|
|
93
|
+
// New key with legacy 'cf-theme' fallback so a saved theme survives the rename.
|
|
94
|
+
const saved = localStorage.getItem('careermate-theme') || localStorage.getItem('cf-theme');
|
|
95
|
+
return saved === 'light' || saved === 'dark' ? saved : 'system';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function Theme() {
|
|
99
|
+
const options = [
|
|
100
|
+
{ value: 'light', label: '라이트' },
|
|
101
|
+
{ value: 'dark', label: '다크' },
|
|
102
|
+
{ value: 'system', label: '시스템' },
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
const row = el('div', { class: 'flex gap-2 wrap' });
|
|
106
|
+
|
|
107
|
+
function applyTheme(value) {
|
|
108
|
+
if (value === 'system') {
|
|
109
|
+
delete document.documentElement.dataset.theme;
|
|
110
|
+
localStorage.removeItem('careermate-theme');
|
|
111
|
+
localStorage.removeItem('cf-theme'); // also clear legacy key
|
|
112
|
+
} else {
|
|
113
|
+
document.documentElement.dataset.theme = value;
|
|
114
|
+
localStorage.setItem('careermate-theme', value);
|
|
115
|
+
localStorage.removeItem('cf-theme'); // migrate off legacy key
|
|
116
|
+
}
|
|
117
|
+
paint();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function paint() {
|
|
121
|
+
const active = currentTheme();
|
|
122
|
+
mount(row, ...options.map((o) => {
|
|
123
|
+
const b = Btn(o.label, { variant: 'ghost', onClick: () => applyTheme(o.value) });
|
|
124
|
+
b.setAttribute('aria-pressed', String(o.value === active));
|
|
125
|
+
if (o.value === active) b.classList.add('is-selected');
|
|
126
|
+
return b;
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
paint();
|
|
130
|
+
|
|
131
|
+
return Card({
|
|
132
|
+
title: '테마',
|
|
133
|
+
sub: '시스템 설정을 따르거나 직접 고를 수 있어요',
|
|
134
|
+
body: row,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* ---------------------------------------------------------- 내 데이터 */
|
|
139
|
+
function MyData(counts = {}) {
|
|
140
|
+
const items = Object.entries(COUNT_LABELS).map(([key, label]) =>
|
|
141
|
+
el('div', { class: 'countstrip__item' },
|
|
142
|
+
el('div', { class: 'countstrip__num tnum' }, String(counts[key] ?? 0)),
|
|
143
|
+
el('div', { class: 'countstrip__label' }, label)));
|
|
144
|
+
return Card({
|
|
145
|
+
title: '내 데이터',
|
|
146
|
+
sub: '이 컴퓨터에 저장된 항목 수',
|
|
147
|
+
body: el('div', { class: 'countstrip' }, ...items),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* ------------------------------------------------- 백업 / 내보내기 */
|
|
152
|
+
function Backup(backups = [], rerender) {
|
|
153
|
+
async function createBackup() {
|
|
154
|
+
try {
|
|
155
|
+
const res = await post('/api/settings/backup');
|
|
156
|
+
toastOk('백업을 생성했어요.');
|
|
157
|
+
await rerender();
|
|
158
|
+
} catch (e) {
|
|
159
|
+
toastError(e);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const list = backups.length
|
|
164
|
+
? el('table', { class: 'table' },
|
|
165
|
+
el('thead', {}, el('tr', {},
|
|
166
|
+
el('th', {}, '파일'), el('th', {}, '생성'), el('th', { style: { textAlign: 'right' } }, '크기'))),
|
|
167
|
+
el('tbody', {}, ...backups.map((b) => el('tr', {},
|
|
168
|
+
el('td', {}, el('span', { class: 'truncate', title: b.path || b.filename }, b.filename)),
|
|
169
|
+
el('td', { class: 'muted text-sm' }, fmtRelative(b.created_at)),
|
|
170
|
+
el('td', { class: 'muted text-sm tnum', style: { textAlign: 'right' } }, `${Math.round((b.size || 0) / 1024)} KB`)))))
|
|
171
|
+
: el('p', { class: 'muted', style: { margin: 0 } }, '아직 백업이 없습니다. "백업 생성"으로 현재 데이터를 안전하게 보관하세요.');
|
|
172
|
+
|
|
173
|
+
return Card({
|
|
174
|
+
title: '백업 / 내보내기',
|
|
175
|
+
actions: [
|
|
176
|
+
Btn('백업 생성', { icon: 'copy', sm: true, variant: 'primary', onClick: createBackup }),
|
|
177
|
+
Btn('전체 데이터 내보내기(JSON)', { icon: 'download', sm: true, variant: 'ghost', onClick: () => downloadUrl('/api/settings/export-all') }),
|
|
178
|
+
],
|
|
179
|
+
body: list,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/* -------------------------------------------------- 위험 구역 (Danger zone) */
|
|
184
|
+
function DangerZone(ctx) {
|
|
185
|
+
function openResetModal() {
|
|
186
|
+
let confirmBtn;
|
|
187
|
+
// Gate on the Korean word so there's no IME switch and it matches the '초기화' button.
|
|
188
|
+
// (The API still expects the fixed 'DELETE' token; we send that on the wire below.)
|
|
189
|
+
const input = Input({
|
|
190
|
+
placeholder: '초기화',
|
|
191
|
+
onInput: (e) => { if (confirmBtn) confirmBtn.disabled = e.target.value.trim() !== '초기화'; },
|
|
192
|
+
onKeydown: (e) => { if (e.key === 'Enter' && e.target.value.trim() === '초기화' && confirmBtn && !confirmBtn.disabled) confirmBtn.click(); },
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
openModal({
|
|
196
|
+
title: '모든 데이터 초기화',
|
|
197
|
+
body: el('div', { class: 'stack-3' },
|
|
198
|
+
el('div', { class: 'callout' },
|
|
199
|
+
icon('info'),
|
|
200
|
+
el('div', {},
|
|
201
|
+
el('div', { class: 'callout__title' }, '되돌릴 수 없습니다'),
|
|
202
|
+
el('div', { class: 'callout__body' },
|
|
203
|
+
'프로필·공고·지원·문서·면접 준비를 포함한 모든 데이터가 영구적으로 삭제됩니다. 안전을 위해 초기화 직전에 자동으로 백업이 한 번 생성됩니다.'))),
|
|
204
|
+
el('p', { class: 'text-secondary', style: { margin: 0, lineHeight: '1.6' } },
|
|
205
|
+
'계속하려면 아래 입력란에 ', el('span', { class: 'strong' }, '초기화'), ' 를 입력하세요.'),
|
|
206
|
+
input),
|
|
207
|
+
footer: (close) => {
|
|
208
|
+
confirmBtn = Btn('모든 데이터 초기화', {
|
|
209
|
+
variant: 'danger',
|
|
210
|
+
disabled: true,
|
|
211
|
+
onClick: async () => {
|
|
212
|
+
confirmBtn.disabled = true;
|
|
213
|
+
try {
|
|
214
|
+
await post('/api/settings/reset', { confirm: 'DELETE' });
|
|
215
|
+
close();
|
|
216
|
+
toastOk('모든 데이터를 초기화했어요.');
|
|
217
|
+
navigate('/');
|
|
218
|
+
await ctx.refreshNav();
|
|
219
|
+
} catch (e) {
|
|
220
|
+
toastError(e);
|
|
221
|
+
confirmBtn.disabled = input.value.trim() !== 'DELETE';
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
return [Btn('취소', { onClick: close }), confirmBtn];
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return Card({
|
|
231
|
+
title: '위험 구역',
|
|
232
|
+
sub: '이 작업은 되돌릴 수 없습니다',
|
|
233
|
+
body: el('div', { class: 'flex between gap-3 wrap' },
|
|
234
|
+
el('div', { style: { maxWidth: '440px' } },
|
|
235
|
+
el('div', { class: 'strong' }, '모든 데이터 초기화'),
|
|
236
|
+
el('div', { class: 'muted text-sm', style: { lineHeight: '1.6' } },
|
|
237
|
+
'이 컴퓨터에 저장된 모든 CareerMate 데이터를 삭제합니다. 초기화 전에 자동으로 백업이 생성됩니다.')),
|
|
238
|
+
Btn('모든 데이터 초기화', { icon: 'trash', variant: 'danger', onClick: openResetModal })),
|
|
239
|
+
});
|
|
240
|
+
}
|