careermate 0.0.13 → 0.0.15
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 +1 -1
- package/README.md +8 -6
- package/dist/doctor.mjs +33 -10
- package/dist/mcp.mjs +33 -10
- package/dist/public/app.js +1 -0
- package/dist/public/i18n/en/nav.js +1 -0
- package/dist/public/i18n/en/search.js +43 -0
- package/dist/public/i18n/en.js +2 -1
- package/dist/public/i18n/ko/nav.js +1 -0
- package/dist/public/i18n/ko/search.js +43 -0
- package/dist/public/i18n/ko.js +2 -1
- package/dist/public/pages/search.js +208 -0
- package/dist/web.mjs +316 -25
- package/docs/FAQ.md +24 -0
- package/docs/MCP_TOOLS.md +1 -1
- package/docs/START_WORKFLOW.md +30 -0
- package/docs/WORKFLOWS.md +24 -0
- package/package.json +1 -1
- package/packages/core/src/job-categories.ts +16 -5
- package/packages/core/src/jobsearch.ts +26 -3
- package/packages/mcp-tools/src/tools.ts +10 -2
|
@@ -50,7 +50,7 @@ export interface JobSearchResponse {
|
|
|
50
50
|
query: string;
|
|
51
51
|
results: JobSearchResult[];
|
|
52
52
|
providers: ProviderOutcome[];
|
|
53
|
-
/** Results dropped by `postedSince`. */
|
|
53
|
+
/** Results dropped by `postedSince` and `years`. */
|
|
54
54
|
filtered_out?: number;
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -64,6 +64,8 @@ export interface JobSearchOptions {
|
|
|
64
64
|
saraminCategories?: string[];
|
|
65
65
|
/** Keep only postings dated on/after this day (YYYY-MM-DD): posted_at, else first_seen_at. */
|
|
66
66
|
postedSince?: string;
|
|
67
|
+
/** User's years of experience: keep postings whose range covers it (unknown ranges stay). */
|
|
68
|
+
years?: number;
|
|
67
69
|
/** Record first-seen dates in the local DB (default true; tests turn it off). */
|
|
68
70
|
trackSeen?: boolean;
|
|
69
71
|
/** "Today" for first-seen/postedSince, injectable for tests. */
|
|
@@ -450,8 +452,13 @@ export async function searchJobs(query: string, opts: JobSearchOptions = {}): Pr
|
|
|
450
452
|
}
|
|
451
453
|
results = dedupeAcrossBoards(results);
|
|
452
454
|
let filtered_out: number | undefined;
|
|
453
|
-
|
|
454
|
-
|
|
455
|
+
const years = opts.years;
|
|
456
|
+
if (opts.postedSince || years != null) {
|
|
457
|
+
const kept = results.filter((r) => {
|
|
458
|
+
if (opts.postedSince && (r.posted_at ?? r.first_seen_at ?? today) < opts.postedSince) return false;
|
|
459
|
+
const range = years != null ? experienceRange(r.experience) : null;
|
|
460
|
+
return !range || (range[0] <= years! && years! <= range[1]);
|
|
461
|
+
});
|
|
455
462
|
filtered_out = results.length - kept.length;
|
|
456
463
|
results = kept;
|
|
457
464
|
}
|
|
@@ -464,6 +471,22 @@ export async function searchJobs(query: string, opts: JobSearchOptions = {}): Pr
|
|
|
464
471
|
};
|
|
465
472
|
}
|
|
466
473
|
|
|
474
|
+
/**
|
|
475
|
+
* Years a posting accepts, from each board's wording: "3~10년", "경력 3~10년", "5년 이상",
|
|
476
|
+
* "경력5년↑", "~3년", "신입", "신입·경력", "경력무관". Unreadable text gives null.
|
|
477
|
+
*/
|
|
478
|
+
export function experienceRange(text: string | undefined): [number, number] | null {
|
|
479
|
+
const t = (text ?? '').replace(/\s/g, '');
|
|
480
|
+
if (!t) return null;
|
|
481
|
+
if (/무관/.test(t)) return [0, Infinity];
|
|
482
|
+
const m = t.match(/(\d+)?~(\d+)년/);
|
|
483
|
+
if (m) return [m[1] ? +m[1] : 0, +m[2]];
|
|
484
|
+
const min = t.match(/(\d+)년(이상|↑)/);
|
|
485
|
+
if (min) return [/신입/.test(t) ? 0 : +min[1], Infinity];
|
|
486
|
+
if (/신입/.test(t)) return /경력/.test(t) ? [0, Infinity] : [0, 0];
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
|
|
467
490
|
// Any "(...)" goes: "(주)" and brand notes like "힐링페이퍼(강남언니)" vs "(주)힐링페이퍼".
|
|
468
491
|
const normCompany = (s: string) =>
|
|
469
492
|
s.replace(/\([^)]*\)|㈜|주식회사/g, '').toLowerCase().replace(/[^\p{L}\p{N}]/gu, '');
|
|
@@ -1110,6 +1110,13 @@ export const TOOLS: ToolDef[] = [
|
|
|
1110
1110
|
.refine((s) => s === 'today' || isYmd(s), '"today" 또는 실제 날짜(YYYY-MM-DD)')
|
|
1111
1111
|
.optional()
|
|
1112
1112
|
.describe('"today" 또는 YYYY-MM-DD. 그날 이후 공고만'),
|
|
1113
|
+
years: z
|
|
1114
|
+
.number()
|
|
1115
|
+
.int()
|
|
1116
|
+
.min(0)
|
|
1117
|
+
.max(40)
|
|
1118
|
+
.optional()
|
|
1119
|
+
.describe('사용자 경력 연차(신입 0). 공고의 경력 범위에 이 연차가 들어가는 것만 남김. "5년차"면 5. 사용자가 말하지 않았으면 프로필 경력으로 계산해 넣되 확인. 경력 표기를 읽을 수 없는 공고는 남김'),
|
|
1113
1120
|
},
|
|
1114
1121
|
// 공고를 가져오기만 한다. 처음 본 날짜 기록은 사용자 데이터가 아닌 로컬 캐시라 readOnly로 둔다.
|
|
1115
1122
|
readOnly: true,
|
|
@@ -1135,18 +1142,19 @@ export const TOOLS: ToolDef[] = [
|
|
|
1135
1142
|
rememberCategories,
|
|
1136
1143
|
saraminCategories,
|
|
1137
1144
|
postedSince: args.posted_since === 'today' ? today : args.posted_since,
|
|
1145
|
+
years: args.years,
|
|
1138
1146
|
today,
|
|
1139
1147
|
});
|
|
1140
1148
|
const failed = res.providers.filter((p) => !p.ok);
|
|
1141
1149
|
const okCount = res.results.length;
|
|
1142
1150
|
const parts = [`'${res.query || '직무 조건'}' 검색 결과 ${okCount}건${usedSaved ? '(저장된 검색 조건 사용)' : ''}`];
|
|
1143
|
-
if (res.filtered_out) parts.push(`(
|
|
1151
|
+
if (res.filtered_out) parts.push(`(게시일·경력 조건으로 ${res.filtered_out}건 제외)`);
|
|
1144
1152
|
if (failed.length) parts.push(`(일부 사이트 응답 실패: ${failed.map((f) => `${f.source}: ${f.error ?? f.status}`).join('; ')})`);
|
|
1145
1153
|
if (okCount === 0) parts.push('- 키워드나 직무 조건을 바꾸거나, 공고 URL/본문을 주시면 직접 저장해 드릴게요.');
|
|
1146
1154
|
return ok(parts.join(' '), {
|
|
1147
1155
|
...res,
|
|
1148
1156
|
used_saved_preferences: usedSaved,
|
|
1149
|
-
conditions: { keyword, remember_categories: rememberCategories, saramin_categories: saraminCategories },
|
|
1157
|
+
conditions: { keyword, remember_categories: rememberCategories, saramin_categories: saraminCategories, years: args.years },
|
|
1150
1158
|
});
|
|
1151
1159
|
},
|
|
1152
1160
|
},
|