careermate 0.0.8 → 0.0.10
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 +2 -2
- package/LICENSE +1 -1
- package/README.md +78 -81
- package/bin/careermate.mjs +11 -2
- package/dist/career-os/eop/cover-letter.md +10 -4
- package/dist/career-os/eop/interview-prep.md +1 -0
- package/dist/career-os/knowledge/cover-letter.md +4 -0
- package/dist/career-os/knowledge/verifiers/human-voice.md +7 -2
- package/dist/doctor.mjs +18138 -6866
- package/dist/mcp.mjs +14820 -4124
- package/dist/migrate.mjs +14436 -3712
- package/dist/pdf.worker.min.mjs +4 -4
- package/dist/public/app.js +10 -19
- package/dist/public/i18n/en/applications.js +19 -0
- package/dist/public/i18n/en/common.js +6 -0
- package/dist/public/i18n/en/documents.js +1 -0
- package/dist/public/i18n/en/home.js +8 -1
- package/dist/public/i18n/en/jobs.js +19 -0
- package/dist/public/i18n/en/nav.js +1 -0
- package/dist/public/i18n/en/settings.js +5 -2
- package/dist/public/i18n/ko/applications.js +19 -0
- package/dist/public/i18n/ko/common.js +5 -0
- package/dist/public/i18n/ko/documents.js +1 -0
- package/dist/public/i18n/ko/home.js +8 -1
- package/dist/public/i18n/ko/jobs.js +19 -0
- package/dist/public/i18n/ko/nav.js +1 -0
- package/dist/public/i18n/ko/settings.js +5 -2
- package/dist/public/lib.js +29 -0
- package/dist/public/pages/applications.js +86 -10
- package/dist/public/pages/documents.js +25 -12
- package/dist/public/pages/home.js +24 -4
- package/dist/public/pages/insights.js +156 -0
- package/dist/public/pages/interview.js +11 -4
- package/dist/public/pages/jobs.js +92 -4
- package/dist/public/pages/settings.js +2 -2
- package/dist/public/styles.css +16 -0
- package/dist/web.mjs +17079 -5651
- package/docs/FAQ.md +13 -3
- package/docs/LOCAL_FIRST.md +32 -0
- package/docs/MCP_TOOLS.md +22 -9
- package/docs/REVIEW_IMPLEMENTATION_PLAN.md +22 -0
- package/docs/career-os/eop/cover-letter.md +10 -4
- package/docs/career-os/eop/interview-prep.md +1 -0
- package/docs/career-os/knowledge/cover-letter.md +4 -0
- package/docs/career-os/knowledge/verifiers/human-voice.md +7 -2
- package/package.json +9 -5
- package/packages/core/src/data-health.ts +161 -0
- package/packages/core/src/fit-explain.ts +63 -0
- package/packages/core/src/index.ts +4 -0
- package/packages/core/src/jobsearch.ts +99 -54
- package/packages/core/src/offers.ts +41 -0
- package/packages/core/src/reuse-insights.ts +126 -0
- package/packages/core/src/services.ts +5 -1
- package/packages/core/src/summary.ts +245 -19
- package/packages/db/src/connection.ts +102 -14
- package/packages/db/src/index.ts +3 -0
- package/packages/db/src/interview-retrospectives.ts +149 -0
- package/packages/db/src/provenance.ts +125 -0
- package/packages/db/src/repositories.ts +140 -8
- package/packages/db/src/schema.ts +140 -8
- package/packages/mcp-tools/src/result.ts +9 -0
- package/packages/mcp-tools/src/tools.ts +143 -20
- package/packages/parsers/src/documents.ts +7 -1
- package/packages/prompts/src/humanize.ts +4 -0
- package/packages/prompts/src/system.ts +21 -64
- package/packages/shared/src/schemas.ts +134 -0
- package/scripts/init.ts +2 -0
- package/tsconfig.json +9 -10
|
@@ -380,6 +380,49 @@ export type CoverLetterRecord = z.infer<typeof CoverLetterRecordSchema>;
|
|
|
380
380
|
|
|
381
381
|
/* --------------------------------------------------------------------- Job */
|
|
382
382
|
|
|
383
|
+
/**
|
|
384
|
+
* One company-specified cover-letter question (한국 문항형 자소서). Stored on the job so
|
|
385
|
+
* the AI writes each answer to its own prompt and checks length against that question's
|
|
386
|
+
* limit. CareerMate counts chars deterministically (checkCharLimit) — it never generates.
|
|
387
|
+
*/
|
|
388
|
+
export const JobQuestionSchema = z.object({
|
|
389
|
+
no: z.number().int().positive().optional().describe('문항 번호(있으면)'),
|
|
390
|
+
question: z.string().max(MAX_BODY).describe('문항 텍스트(회사가 제시한 자소서 항목)'),
|
|
391
|
+
min_chars: z.number().int().positive().optional().describe('글자수 하한(있으면)'),
|
|
392
|
+
max_chars: z.number().int().positive().optional().describe('글자수 상한(있으면)'),
|
|
393
|
+
count_mode: z
|
|
394
|
+
.enum(['with_space', 'no_space', 'byte'])
|
|
395
|
+
.optional()
|
|
396
|
+
.describe('글자수 기준: with_space=공백포함(기본)·no_space=공백제외·byte=UTF-8 바이트'),
|
|
397
|
+
note: optLine.describe('부가 메모(예: "필수", "경험 중심")'),
|
|
398
|
+
});
|
|
399
|
+
export type JobQuestion = z.infer<typeof JobQuestionSchema>;
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* One recruitment-schedule entry (전형 일정 — 면접·인적성·발표 등). Kept on the job so
|
|
403
|
+
* save_job_posting carries it without a new tool, and the server computes D-day/conflicts
|
|
404
|
+
* deterministically (getActionDigest). This is NOT the document deadline — that stays in
|
|
405
|
+
* `deadline`; these are the downstream stage dates the AI extracts from an interview invite
|
|
406
|
+
* or announcement the user pastes in. Dates are strict YYYY-MM-DD so the server can compute
|
|
407
|
+
* D-day and same-day collisions without guessing formats.
|
|
408
|
+
*/
|
|
409
|
+
const SCHEDULE_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
410
|
+
const SCHEDULE_TIME_RE = /^([01]\d|2[0-3]):[0-5]\d$/;
|
|
411
|
+
export const JobScheduleItemSchema = z.object({
|
|
412
|
+
label: reqLine.describe('일정 이름 (예: 1차 면접·AI역검·서류 발표)'),
|
|
413
|
+
date: z
|
|
414
|
+
.string()
|
|
415
|
+
.regex(SCHEDULE_DATE_RE, 'YYYY-MM-DD 형식이어야 합니다')
|
|
416
|
+
.describe('일정 날짜 YYYY-MM-DD'),
|
|
417
|
+
time: z
|
|
418
|
+
.string()
|
|
419
|
+
.regex(SCHEDULE_TIME_RE, 'HH:MM(24시간) 형식이어야 합니다')
|
|
420
|
+
.optional()
|
|
421
|
+
.describe('시각 HH:MM(24시간, 선택)'),
|
|
422
|
+
note: optLine.describe('부가 메모(예: 장소·준비물, 선택)'),
|
|
423
|
+
});
|
|
424
|
+
export type JobScheduleItem = z.infer<typeof JobScheduleItemSchema>;
|
|
425
|
+
|
|
383
426
|
export const JobInputSchema = z.object({
|
|
384
427
|
company: reqLine,
|
|
385
428
|
position: reqLine.describe('직무/포지션명'),
|
|
@@ -394,6 +437,17 @@ export const JobInputSchema = z.object({
|
|
|
394
437
|
company_overview: optBody.describe('회사 사업개요·미션·주요 제품·최근 활동 (AI가 리서치해 채움)'),
|
|
395
438
|
talent_profile: optBody.describe('회사가 강조하는 인재상 (한국 자소서·면접에서 정렬 기준)'),
|
|
396
439
|
core_values: strList.optional().describe('회사 핵심가치 (키워드 배열)'),
|
|
440
|
+
reputation: optBody.describe('회사 평판 요약 — 평점·워라밸·문화 리스크 등 (AI가 company-research 플레이북으로 리서치해 채움; 출처·최근성 표기)'),
|
|
441
|
+
questions: z
|
|
442
|
+
.array(JobQuestionSchema)
|
|
443
|
+
.max(MAX_ITEMS)
|
|
444
|
+
.optional()
|
|
445
|
+
.describe('회사 지정 자소서 문항·글자수 — 사용자가 문항을 주면 구조화 저장'),
|
|
446
|
+
schedule: z
|
|
447
|
+
.array(JobScheduleItemSchema)
|
|
448
|
+
.max(MAX_ITEMS)
|
|
449
|
+
.optional()
|
|
450
|
+
.describe('전형 일정 — 면접초대 메일·발표 공지를 받으면 구조화 저장(label·date 필수, time 선택). 서류 마감은 deadline 필드를 쓰세요.'),
|
|
397
451
|
});
|
|
398
452
|
export type JobInput = z.infer<typeof JobInputSchema>;
|
|
399
453
|
|
|
@@ -412,9 +466,65 @@ export const JobRecordSchema = z.object({
|
|
|
412
466
|
company_overview: z.string().nullable(),
|
|
413
467
|
talent_profile: z.string().nullable(),
|
|
414
468
|
core_values: z.array(z.string()),
|
|
469
|
+
reputation: z.string().nullable(),
|
|
470
|
+
questions: z.array(JobQuestionSchema),
|
|
471
|
+
schedule: z.array(JobScheduleItemSchema),
|
|
415
472
|
});
|
|
416
473
|
export type JobRecord = z.infer<typeof JobRecordSchema>;
|
|
417
474
|
|
|
475
|
+
/* ------------------------------------------------------------------- Offer */
|
|
476
|
+
|
|
477
|
+
/** Offer lifecycle — MY response to the company's proposal. Orthogonal to the
|
|
478
|
+
* application's 8-stage status (which tracks the COMPANY's process): together
|
|
479
|
+
* they can express e.g. "최종 합격했지만 내가 거절" (final_passed + declined). */
|
|
480
|
+
export const OFFER_STATUSES = ['open', 'accepted', 'declined'] as const;
|
|
481
|
+
export type OfferStatus = (typeof OFFER_STATUSES)[number];
|
|
482
|
+
|
|
483
|
+
/** Job offer for comparison. Cash pieces are 만원(numeric) so the server can sum an
|
|
484
|
+
* annual estimate deterministically; equity/other stay free-text. ai_score/verdict
|
|
485
|
+
* are produced by the connected AI at save time (CareerMate has no LLM). */
|
|
486
|
+
export const OfferInputSchema = z.object({
|
|
487
|
+
job_id: reqLine,
|
|
488
|
+
offer_status: z
|
|
489
|
+
.enum(OFFER_STATUSES)
|
|
490
|
+
.optional()
|
|
491
|
+
.describe('오퍼 상태 — open(진행 중)/accepted(수락)/declined(거절). 생략 시 기존 값 유지(신규 저장은 open)'),
|
|
492
|
+
base_salary: z.number().nonnegative().optional().describe('기본급/연봉 (만원)'),
|
|
493
|
+
bonus_amount: z.number().nonnegative().optional().describe('연 성과급·상여 기대액 (만원)'),
|
|
494
|
+
welfare_amount: z.number().nonnegative().optional().describe('복지포인트 등 연환산 (만원)'),
|
|
495
|
+
signing_amount: z.number().nonnegative().optional().describe('사이닝보너스 일시금 (만원)'),
|
|
496
|
+
equity_note: optNote.describe('주식/스톡옵션 (정성 서술 — 가치평가가 모호하므로 텍스트)'),
|
|
497
|
+
comp_note: optBody.describe('기타 보상 자유 서술'),
|
|
498
|
+
work_arrangement: optLine.describe('근무형태 (재택/하이브리드/출근)'),
|
|
499
|
+
contract_type: optLine.describe('계약형태 (정규/계약 등)'),
|
|
500
|
+
accept_deadline: optLine.describe('수락 마감일 YYYY-MM-DD'),
|
|
501
|
+
ai_score: z.number().min(0).max(100).optional().describe('AI가 매긴 종합 점수 0~100 (저장 시 AI가 채움)'),
|
|
502
|
+
verdict: optBody.describe('최종평 — 어떤 상황이면 어디가 좋은지 등 (AI가 저장 시 작성)'),
|
|
503
|
+
notes: optNote,
|
|
504
|
+
});
|
|
505
|
+
export type OfferInput = z.infer<typeof OfferInputSchema>;
|
|
506
|
+
|
|
507
|
+
export const OfferRecordSchema = z.object({
|
|
508
|
+
...baseRecord,
|
|
509
|
+
job_id: z.string(),
|
|
510
|
+
offer_status: z.enum(OFFER_STATUSES),
|
|
511
|
+
base_salary: z.number().nullable(),
|
|
512
|
+
bonus_amount: z.number().nullable(),
|
|
513
|
+
welfare_amount: z.number().nullable(),
|
|
514
|
+
signing_amount: z.number().nullable(),
|
|
515
|
+
equity_note: z.string().nullable(),
|
|
516
|
+
comp_note: z.string().nullable(),
|
|
517
|
+
work_arrangement: z.string().nullable(),
|
|
518
|
+
contract_type: z.string().nullable(),
|
|
519
|
+
accept_deadline: z.string().nullable(),
|
|
520
|
+
ai_score: z.number().nullable(),
|
|
521
|
+
verdict: z.string().nullable(),
|
|
522
|
+
notes: z.string().nullable(),
|
|
523
|
+
/** Server-computed annual cash estimate = base + bonus + welfare (signing is one-time, excluded). Null if no numeric pieces. */
|
|
524
|
+
total_comp_annual_est: z.number().nullable(),
|
|
525
|
+
});
|
|
526
|
+
export type OfferRecord = z.infer<typeof OfferRecordSchema>;
|
|
527
|
+
|
|
418
528
|
/* ------------------------------------------------------------- FitAnalysis */
|
|
419
529
|
|
|
420
530
|
export const FitAnalysisInputSchema = z.object({
|
|
@@ -444,6 +554,23 @@ export type FitAnalysisRecord = z.infer<typeof FitAnalysisRecordSchema>;
|
|
|
444
554
|
|
|
445
555
|
/* ------------------------------------------------------------- Application */
|
|
446
556
|
|
|
557
|
+
/**
|
|
558
|
+
* 탈락 회고 — 어느 전형 단계에서 떨어졌는지. 탈락 사유를 알려주는 기업은 6%뿐이고 지원자
|
|
559
|
+
* 83%가 알고 싶어하므로, 이 자가 기록이 "서류는 붙는데 면접에서 떨어진다" 같은 패턴을 로컬
|
|
560
|
+
* 데이터로 드러내는 유일한 신호다. status='rejected'일 때만 의미가 있다.
|
|
561
|
+
*/
|
|
562
|
+
export const REJECTED_STAGES = ['document', 'assessment', 'interview', 'final', 'other'] as const;
|
|
563
|
+
export type RejectedStage = (typeof REJECTED_STAGES)[number];
|
|
564
|
+
|
|
565
|
+
/** Korean labels for the rejection stage (dashboard localizes separately via i18n). */
|
|
566
|
+
export const REJECTED_STAGE_LABELS: Record<RejectedStage, string> = {
|
|
567
|
+
document: '서류',
|
|
568
|
+
assessment: '인적성·AI역검',
|
|
569
|
+
interview: '면접',
|
|
570
|
+
final: '최종·처우',
|
|
571
|
+
other: '기타',
|
|
572
|
+
};
|
|
573
|
+
|
|
447
574
|
export const ApplicationInputSchema = z.object({
|
|
448
575
|
job_id: reqLine,
|
|
449
576
|
status: z.enum(APPLICATION_STATUSES).optional(),
|
|
@@ -469,6 +596,12 @@ export const ApplicationStatusUpdateSchema = z.object({
|
|
|
469
596
|
status: z.enum(APPLICATION_STATUSES),
|
|
470
597
|
note: optNote.describe('상태 변경 사유/메모'),
|
|
471
598
|
submission: ApplicationSubmissionSchema.optional().describe('지원 완료 기록에 남길 제출 자료 스냅샷'),
|
|
599
|
+
rejected_stage: z
|
|
600
|
+
.enum(REJECTED_STAGES)
|
|
601
|
+
.optional()
|
|
602
|
+
.describe(
|
|
603
|
+
"탈락 회고 — 'rejected'로 바꿀 때 어느 단계에서 떨어졌는지: 서류=document, 인적성·AI역검=assessment, 면접=interview, 최종·처우=final, 기타=other. rejected가 아닌 상태에는 저장되지 않습니다.",
|
|
604
|
+
),
|
|
472
605
|
});
|
|
473
606
|
export type ApplicationStatusUpdate = z.infer<typeof ApplicationStatusUpdateSchema>;
|
|
474
607
|
|
|
@@ -480,6 +613,7 @@ export const ApplicationRecordSchema = z.object({
|
|
|
480
613
|
cover_letter_id: z.string().nullable(),
|
|
481
614
|
applied_at: z.string().nullable(),
|
|
482
615
|
notes: z.string().nullable(),
|
|
616
|
+
rejected_stage: z.enum(REJECTED_STAGES).nullable(),
|
|
483
617
|
});
|
|
484
618
|
export type ApplicationRecord = z.infer<typeof ApplicationRecordSchema>;
|
|
485
619
|
|
package/scripts/init.ts
CHANGED
|
@@ -399,6 +399,8 @@ const SAFE_TOOLS: readonly string[] = [
|
|
|
399
399
|
// search_jobs: 공개 잡보드에서 공고를 가져오기만(보내는 건 검색어뿐, 사용자 데이터 미전송).
|
|
400
400
|
// check_for_update와 같은 "네트워크 read" 부류라 사전허용 안전(우회·쓰기 없음).
|
|
401
401
|
'search_jobs',
|
|
402
|
+
// 오퍼(제안) — 로컬 저장/조회(save_fit_analysis 등과 동급), 외부 전송 없음.
|
|
403
|
+
'save_offer', 'compare_offers', 'get_offer',
|
|
402
404
|
// careermate 자신의 인박스 폴더·대시보드(127.0.0.1)만 여는 로컬 동작 + 인박스 내부 문서 읽기 → 사전허용 안전.
|
|
403
405
|
'read_inbox', 'open_inbox', 'open_dashboard', 'open_application',
|
|
404
406
|
];
|
package/tsconfig.json
CHANGED
|
@@ -13,17 +13,16 @@
|
|
|
13
13
|
"forceConsistentCasingInFileNames": true,
|
|
14
14
|
"allowImportingTsExtensions": true,
|
|
15
15
|
"noEmit": true,
|
|
16
|
-
"baseUrl": ".",
|
|
17
16
|
"paths": {
|
|
18
|
-
"@careermate/shared": ["packages/shared/src/index.ts"],
|
|
19
|
-
"@careermate/db": ["packages/db/src/index.ts"],
|
|
20
|
-
"@careermate/core": ["packages/core/src/index.ts"],
|
|
21
|
-
"@careermate/mcp-tools": ["packages/mcp-tools/src/index.ts"],
|
|
22
|
-
"@careermate/exporters": ["packages/exporters/src/index.ts"],
|
|
23
|
-
"@careermate/parsers": ["packages/parsers/src/index.ts"],
|
|
24
|
-
"@careermate/prompts": ["packages/prompts/src/index.ts"],
|
|
25
|
-
"@careermate/workflows": ["packages/workflows/src/index.ts"],
|
|
26
|
-
"@careermate/knowledge": ["packages/knowledge/src/index.ts"]
|
|
17
|
+
"@careermate/shared": ["./packages/shared/src/index.ts"],
|
|
18
|
+
"@careermate/db": ["./packages/db/src/index.ts"],
|
|
19
|
+
"@careermate/core": ["./packages/core/src/index.ts"],
|
|
20
|
+
"@careermate/mcp-tools": ["./packages/mcp-tools/src/index.ts"],
|
|
21
|
+
"@careermate/exporters": ["./packages/exporters/src/index.ts"],
|
|
22
|
+
"@careermate/parsers": ["./packages/parsers/src/index.ts"],
|
|
23
|
+
"@careermate/prompts": ["./packages/prompts/src/index.ts"],
|
|
24
|
+
"@careermate/workflows": ["./packages/workflows/src/index.ts"],
|
|
25
|
+
"@careermate/knowledge": ["./packages/knowledge/src/index.ts"]
|
|
27
26
|
}
|
|
28
27
|
},
|
|
29
28
|
"include": ["packages/**/*.ts", "apps/**/*.ts", "scripts/**/*.ts"],
|