knowme-careerforge-skill 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/LICENSE +21 -0
- package/README.md +227 -0
- package/README.zh-CN.md +231 -0
- package/SKILL.md +179 -0
- package/agents/claude/knowme-careerforge.md +16 -0
- package/agents/codex/knowme-careerforge.yaml +15 -0
- package/agents/cursor/knowme-careerforge.mdc +12 -0
- package/agents/gemini/knowme-careerforge.json +6 -0
- package/agents/opencode/skill.yaml +11 -0
- package/agents/windsurf/knowme-careerforge.rules +10 -0
- package/bin/knowme.js +32 -0
- package/dist/cli/src/commands/init.d.ts +10 -0
- package/dist/cli/src/commands/init.js +130 -0
- package/dist/cli/src/commands/list.d.ts +5 -0
- package/dist/cli/src/commands/list.js +92 -0
- package/dist/cli/src/index.d.ts +6 -0
- package/dist/cli/src/index.js +149 -0
- package/dist/scripts/render-pdf.d.ts +11 -0
- package/dist/scripts/render-pdf.js +105 -0
- package/dist/scripts/validate-ats.d.ts +23 -0
- package/dist/scripts/validate-ats.js +169 -0
- package/dist/scripts/validate-layout.d.ts +24 -0
- package/dist/scripts/validate-layout.js +161 -0
- package/package.json +58 -0
- package/scripts/analyze-jd.py +108 -0
- package/scripts/build-gallery.py +233 -0
- package/scripts/build-knowledge.py +81 -0
- package/scripts/instantiate-resume.py +83 -0
- package/scripts/release.py +101 -0
- package/scripts/render-pdf.ts +86 -0
- package/scripts/run-all-tests.py +41 -0
- package/scripts/search-template.py +147 -0
- package/scripts/sync-version.py +57 -0
- package/scripts/validate-ats.ts +173 -0
- package/scripts/validate-layout.ts +161 -0
- package/scripts/validate-resume.py +139 -0
- package/skill.json +47 -0
- package/src/knowledge/ats-rules.json +37 -0
- package/src/knowledge/career/seniority-signals.json +24 -0
- package/src/knowledge/index.json +734 -0
- package/src/knowledge/industries/enterprise-saas.json +21 -0
- package/src/knowledge/industries/fintech.json +21 -0
- package/src/knowledge/industries/tech-ai.json +21 -0
- package/src/knowledge/layouts.json +59 -0
- package/src/knowledge/resume/action-verbs.json +20 -0
- package/src/knowledge/resume/fab-model.json +22 -0
- package/src/knowledge/roles/ai-agent-engineer.json +52 -0
- package/src/knowledge/roles/android-engineer.json +45 -0
- package/src/knowledge/roles/architect.json +48 -0
- package/src/knowledge/roles/cpp-systems.json +47 -0
- package/src/knowledge/roles/frontend.json +49 -0
- package/src/knowledge/roles/ios-engineer.json +45 -0
- package/src/knowledge/roles/java-backend.json +49 -0
- package/src/knowledge/roles/node-fullstack.json +49 -0
- package/src/knowledge/roles/product-manager.json +48 -0
- package/src/knowledge/skills/skill-matrix.json +29 -0
- package/src/knowledge/styles.json +73 -0
- package/src/knowledge/templates.json +255 -0
- package/src/references/ats.md +17 -0
- package/src/references/evidence-first.md +22 -0
- package/src/references/pdf.md +40 -0
- package/src/references/resume-writing.md +25 -0
- package/src/references/template-design.md +57 -0
- package/src/templates/classic/README.md +12 -0
- package/src/templates/classic/metadata.json +34 -0
- package/src/templates/classic/style.css +164 -0
- package/src/templates/classic/template.html +133 -0
- package/src/templates/common/resume-contract.md +69 -0
- package/src/templates/executive/README.md +12 -0
- package/src/templates/executive/metadata.json +37 -0
- package/src/templates/executive/style.css +235 -0
- package/src/templates/executive/template.html +101 -0
- package/src/templates/minimal/README.md +12 -0
- package/src/templates/minimal/metadata.json +37 -0
- package/src/templates/minimal/style.css +219 -0
- package/src/templates/minimal/template.html +114 -0
- package/src/templates/modern/README.md +12 -0
- package/src/templates/modern/metadata.json +38 -0
- package/src/templates/modern/style.css +277 -0
- package/src/templates/modern/template.html +139 -0
- package/src/workflows/analyze-job.md +16 -0
- package/src/workflows/define-goal.md +16 -0
- package/src/workflows/forge-resume.md +25 -0
- package/src/workflows/know-me.md +21 -0
- package/src/workflows/position.md +19 -0
- package/src/workflows/review.md +31 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
KnowMe CareerForge — Template Gallery Builder
|
|
4
|
+
扫描验证 src/templates/ 核心模板,生成 output/templates_gallery/ 静态可视化画廊。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os, sys, json, re
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
def build_gallery():
|
|
11
|
+
base_dir = Path(__file__).resolve().parent.parent
|
|
12
|
+
templates_dir = base_dir / "src" / "templates"
|
|
13
|
+
gallery_dir = base_dir / "output" / "templates_gallery"
|
|
14
|
+
gallery_dir.mkdir(parents=True, exist_ok=True)
|
|
15
|
+
|
|
16
|
+
gallery_items = []
|
|
17
|
+
|
|
18
|
+
print("==============================================================")
|
|
19
|
+
print(" KnowMe CareerForge — Template Gallery Builder")
|
|
20
|
+
print("==============================================================")
|
|
21
|
+
|
|
22
|
+
for t_dir in sorted(templates_dir.iterdir()):
|
|
23
|
+
if not t_dir.is_dir() or t_dir.name == "common":
|
|
24
|
+
continue
|
|
25
|
+
|
|
26
|
+
html_file = t_dir / "template.html"
|
|
27
|
+
css_file = t_dir / "style.css"
|
|
28
|
+
meta_file = t_dir / "metadata.json"
|
|
29
|
+
|
|
30
|
+
if not html_file.exists() or not css_file.exists() or not meta_file.exists():
|
|
31
|
+
print(f"[!] Skipping incomplete template: {t_dir.name}")
|
|
32
|
+
continue
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
meta = json.loads(meta_file.read_text(encoding="utf-8"))
|
|
36
|
+
html_raw = html_file.read_text(encoding="utf-8")
|
|
37
|
+
css_raw = css_file.read_text(encoding="utf-8")
|
|
38
|
+
|
|
39
|
+
# 生成单文件自包含预览页面
|
|
40
|
+
inlined_preview = html_raw.replace(
|
|
41
|
+
'<link rel="stylesheet" href="style.css">',
|
|
42
|
+
f'<style>\n{css_raw}\n </style>'
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
preview_path = gallery_dir / f"{meta['id']}.html"
|
|
46
|
+
preview_path.write_text(inlined_preview, encoding="utf-8")
|
|
47
|
+
|
|
48
|
+
gallery_items.append({
|
|
49
|
+
"id": meta["id"],
|
|
50
|
+
"name": meta.get("name", meta["id"]),
|
|
51
|
+
"style": meta.get("style", "custom"),
|
|
52
|
+
"category": meta.get("roleCategory", "engineering-ai"),
|
|
53
|
+
"accent": meta.get("visualStyle", {}).get("accentColor", "#2563eb"),
|
|
54
|
+
"targetPages": meta.get("layout", {}).get("targetPages", 1),
|
|
55
|
+
"density": meta.get("layout", {}).get("density", "balanced"),
|
|
56
|
+
"supported": meta.get("supportedRoles", []),
|
|
57
|
+
"previewUrl": f"{meta['id']}.html"
|
|
58
|
+
})
|
|
59
|
+
print(f"[✓] Generated Gallery Preview -> {preview_path.name}")
|
|
60
|
+
except Exception as e:
|
|
61
|
+
print(f"[✗] Error processing {t_dir.name}: {e}")
|
|
62
|
+
|
|
63
|
+
# 生成主索引 index.html
|
|
64
|
+
gallery_html = f"""<!DOCTYPE html>
|
|
65
|
+
<html lang="zh-CN">
|
|
66
|
+
<head>
|
|
67
|
+
<meta charset="UTF-8">
|
|
68
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
69
|
+
<title>KnowMe CareerForge — 核心基准模板画廊</title>
|
|
70
|
+
<style>
|
|
71
|
+
:root {{
|
|
72
|
+
--bg: #0f172a;
|
|
73
|
+
--card-bg: #1e293b;
|
|
74
|
+
--border: #334155;
|
|
75
|
+
--accent: #38bdf8;
|
|
76
|
+
--text: #f8fafc;
|
|
77
|
+
--muted: #94a3b8;
|
|
78
|
+
}}
|
|
79
|
+
* {{ box-sizing: border-box; margin: 0; padding: 0; }}
|
|
80
|
+
body {{
|
|
81
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", sans-serif;
|
|
82
|
+
background: var(--bg);
|
|
83
|
+
color: var(--text);
|
|
84
|
+
padding: 32px 20px;
|
|
85
|
+
}}
|
|
86
|
+
.container {{
|
|
87
|
+
max-width: 1360px;
|
|
88
|
+
margin: 0 auto;
|
|
89
|
+
}}
|
|
90
|
+
.header {{
|
|
91
|
+
text-align: center;
|
|
92
|
+
margin-bottom: 32px;
|
|
93
|
+
}}
|
|
94
|
+
h1 {{ font-size: 26px; color: var(--accent); margin-bottom: 8px; font-weight: 800; }}
|
|
95
|
+
p.subtitle {{ color: var(--muted); font-size: 14px; max-width: 680px; margin: 0 auto; line-height: 1.5; }}
|
|
96
|
+
.grid {{
|
|
97
|
+
display: grid;
|
|
98
|
+
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
|
99
|
+
gap: 24px;
|
|
100
|
+
}}
|
|
101
|
+
.card {{
|
|
102
|
+
background: var(--card-bg);
|
|
103
|
+
border: 1px solid var(--border);
|
|
104
|
+
border-radius: 12px;
|
|
105
|
+
padding: 20px;
|
|
106
|
+
display: flex;
|
|
107
|
+
flex-direction: column;
|
|
108
|
+
gap: 16px;
|
|
109
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2);
|
|
110
|
+
}}
|
|
111
|
+
.card-header {{
|
|
112
|
+
display: flex;
|
|
113
|
+
justify-content: space-between;
|
|
114
|
+
align-items: flex-start;
|
|
115
|
+
border-bottom: 1px solid var(--border);
|
|
116
|
+
padding-bottom: 12px;
|
|
117
|
+
}}
|
|
118
|
+
.card-title {{
|
|
119
|
+
font-size: 18px;
|
|
120
|
+
font-weight: 700;
|
|
121
|
+
color: var(--text);
|
|
122
|
+
}}
|
|
123
|
+
.badge {{
|
|
124
|
+
font-size: 11px;
|
|
125
|
+
padding: 3px 8px;
|
|
126
|
+
border-radius: 12px;
|
|
127
|
+
background: rgba(56, 189, 248, 0.15);
|
|
128
|
+
color: var(--accent);
|
|
129
|
+
border: 1px solid rgba(56, 189, 248, 0.3);
|
|
130
|
+
}}
|
|
131
|
+
.meta-list {{
|
|
132
|
+
display: flex;
|
|
133
|
+
flex-direction: column;
|
|
134
|
+
gap: 6px;
|
|
135
|
+
font-size: 13px;
|
|
136
|
+
color: var(--muted);
|
|
137
|
+
}}
|
|
138
|
+
.meta-item strong {{
|
|
139
|
+
color: var(--text);
|
|
140
|
+
}}
|
|
141
|
+
.tags {{
|
|
142
|
+
display: flex;
|
|
143
|
+
flex-wrap: wrap;
|
|
144
|
+
gap: 6px;
|
|
145
|
+
margin-top: 4px;
|
|
146
|
+
}}
|
|
147
|
+
.tag {{
|
|
148
|
+
background: #334155;
|
|
149
|
+
font-size: 11px;
|
|
150
|
+
padding: 2px 6px;
|
|
151
|
+
border-radius: 4px;
|
|
152
|
+
color: #e2e8f0;
|
|
153
|
+
}}
|
|
154
|
+
.frame-wrapper {{
|
|
155
|
+
width: 100%;
|
|
156
|
+
height: 480px;
|
|
157
|
+
border: 1px solid var(--border);
|
|
158
|
+
border-radius: 8px;
|
|
159
|
+
overflow: hidden;
|
|
160
|
+
background: #ffffff;
|
|
161
|
+
position: relative;
|
|
162
|
+
}}
|
|
163
|
+
iframe {{
|
|
164
|
+
width: 210mm;
|
|
165
|
+
height: 297mm;
|
|
166
|
+
border: none;
|
|
167
|
+
transform: scale(0.48);
|
|
168
|
+
transform-origin: top left;
|
|
169
|
+
position: absolute;
|
|
170
|
+
top: 0;
|
|
171
|
+
left: 0;
|
|
172
|
+
pointer-events: none;
|
|
173
|
+
}}
|
|
174
|
+
.btn {{
|
|
175
|
+
display: inline-block;
|
|
176
|
+
text-align: center;
|
|
177
|
+
background: var(--accent);
|
|
178
|
+
color: #0f172a;
|
|
179
|
+
padding: 10px 16px;
|
|
180
|
+
border-radius: 6px;
|
|
181
|
+
font-size: 13px;
|
|
182
|
+
font-weight: 700;
|
|
183
|
+
text-decoration: none;
|
|
184
|
+
transition: opacity 0.2s;
|
|
185
|
+
}}
|
|
186
|
+
.btn:hover {{ opacity: 0.9; }}
|
|
187
|
+
</style>
|
|
188
|
+
</head>
|
|
189
|
+
<body>
|
|
190
|
+
<div class="container">
|
|
191
|
+
<div class="header">
|
|
192
|
+
<h1>KnowMe CareerForge — 核心基准模板画廊</h1>
|
|
193
|
+
<p class="subtitle">面向 AI Agent 的确定性 HTML 简历设计工作场。所有模板均遵循 A4 绝对印刷几何与 CSS Design Tokens 规范。</p>
|
|
194
|
+
</div>
|
|
195
|
+
<div class="grid">
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
for item in gallery_items:
|
|
199
|
+
tags_html = "".join([f'<span class="tag">{r}</span>' for r in item['supported'][:5]])
|
|
200
|
+
gallery_html += f"""
|
|
201
|
+
<div class="card">
|
|
202
|
+
<div class="card-header">
|
|
203
|
+
<div>
|
|
204
|
+
<div class="card-title">{item['name']}</div>
|
|
205
|
+
<div style="font-size: 12px; color: var(--muted); margin-top: 2px;">ID: <code>{item['id']}</code></div>
|
|
206
|
+
</div>
|
|
207
|
+
<span class="badge">{item['style']}</span>
|
|
208
|
+
</div>
|
|
209
|
+
<div class="meta-list">
|
|
210
|
+
<div class="meta-item"><strong>类别:</strong>{item['category']} | <strong>页数:</strong>{item['targetPages']} 页 | <strong>密度:</strong>{item['density']}</div>
|
|
211
|
+
<div class="meta-item"><strong>支持岗位:</strong></div>
|
|
212
|
+
<div class="tags">{tags_html}</div>
|
|
213
|
+
</div>
|
|
214
|
+
<div class="frame-wrapper">
|
|
215
|
+
<iframe src="{item['previewUrl']}"></iframe>
|
|
216
|
+
</div>
|
|
217
|
+
<a href="{item['previewUrl']}" target="_blank" class="btn">打开独立预览 (A4 全尺寸)</a>
|
|
218
|
+
</div>
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
gallery_html += """
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
</body>
|
|
225
|
+
</html>
|
|
226
|
+
"""
|
|
227
|
+
gallery_index = gallery_dir / "index.html"
|
|
228
|
+
gallery_index.write_text(gallery_html, encoding="utf-8")
|
|
229
|
+
print(f"\n[✓] Generated Gallery Master Index -> {gallery_index}")
|
|
230
|
+
print("==============================================================")
|
|
231
|
+
|
|
232
|
+
if __name__ == "__main__":
|
|
233
|
+
build_gallery()
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
KnowMe CareerForge — Knowledge & Template Index Compiler
|
|
4
|
+
扫描 src/knowledge/ 与 src/templates/,生成全量注册表与知识库索引。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys, os, json, re
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
def build_knowledge_index():
|
|
11
|
+
base_dir = Path(__file__).resolve().parent.parent
|
|
12
|
+
knowledge_dir = base_dir / "src" / "knowledge"
|
|
13
|
+
templates_dir = base_dir / "src" / "templates"
|
|
14
|
+
|
|
15
|
+
# 1. 扫描岗位画像
|
|
16
|
+
roles_dir = knowledge_dir / "roles"
|
|
17
|
+
roles = []
|
|
18
|
+
if roles_dir.exists():
|
|
19
|
+
for f in sorted(roles_dir.glob("*.json")):
|
|
20
|
+
try:
|
|
21
|
+
data = json.loads(f.read_text(encoding="utf-8"))
|
|
22
|
+
roles.append(data)
|
|
23
|
+
except Exception as e:
|
|
24
|
+
print(f"[!] Error parsing role {f.name}: {e}")
|
|
25
|
+
|
|
26
|
+
# 2. 扫描模板
|
|
27
|
+
templates = []
|
|
28
|
+
if templates_dir.exists():
|
|
29
|
+
for t_dir in sorted(templates_dir.iterdir()):
|
|
30
|
+
if not t_dir.is_dir() or t_dir.name == "common":
|
|
31
|
+
continue
|
|
32
|
+
meta_file = t_dir / "metadata.json"
|
|
33
|
+
if meta_file.exists():
|
|
34
|
+
try:
|
|
35
|
+
data = json.loads(meta_file.read_text(encoding="utf-8"))
|
|
36
|
+
data["directory"] = f"src/templates/{t_dir.name}"
|
|
37
|
+
|
|
38
|
+
# 侦测 CSS 变量
|
|
39
|
+
css_file = t_dir / "style.css"
|
|
40
|
+
if css_file.exists():
|
|
41
|
+
css_text = css_file.read_text(encoding="utf-8")
|
|
42
|
+
vars_found = sorted(list(set(re.findall(r'--resume-[\w-]+', css_text))))
|
|
43
|
+
data["detectedTokens"] = vars_found
|
|
44
|
+
|
|
45
|
+
templates.append(data)
|
|
46
|
+
except Exception as e:
|
|
47
|
+
print(f"[!] Error parsing template {t_dir.name}: {e}")
|
|
48
|
+
|
|
49
|
+
# 3. 读取主版本号
|
|
50
|
+
pkg_json_file = base_dir / "package.json"
|
|
51
|
+
version = "0.0.1"
|
|
52
|
+
if pkg_json_file.exists():
|
|
53
|
+
try:
|
|
54
|
+
version = json.loads(pkg_json_file.read_text(encoding="utf-8")).get("version", "0.0.1")
|
|
55
|
+
except:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
# 4. 聚合输出
|
|
59
|
+
index_data = {
|
|
60
|
+
"version": version,
|
|
61
|
+
"totalRoles": len(roles),
|
|
62
|
+
"totalTemplates": len(templates),
|
|
63
|
+
"roles": roles,
|
|
64
|
+
"templates": templates
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
out_index = knowledge_dir / "index.json"
|
|
68
|
+
out_index.write_text(json.dumps(index_data, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
69
|
+
|
|
70
|
+
out_templates = knowledge_dir / "templates.json"
|
|
71
|
+
out_templates.write_text(json.dumps({"templates": templates}, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
72
|
+
|
|
73
|
+
print("=" * 60)
|
|
74
|
+
print(" KnowMe CareerForge — Knowledge Index Built Successfully!")
|
|
75
|
+
print(f" Indexed Roles : {len(roles)}")
|
|
76
|
+
print(f" Indexed Templates : {len(templates)}")
|
|
77
|
+
print(f" Output Registry : {out_index.resolve()}")
|
|
78
|
+
print("=" * 60)
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
build_knowledge_index()
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
KnowMe CareerForge — Resume Workspace Instantiator
|
|
4
|
+
将指定模板与关键词高亮组装为自包含的单文件 HTML 工作区修改场 (Intermediate Working Canvas)。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys, os, json, argparse, re
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
def highlight_keywords(text, keywords):
|
|
11
|
+
if not keywords:
|
|
12
|
+
return text
|
|
13
|
+
for kw in keywords:
|
|
14
|
+
kw_clean = kw.strip()
|
|
15
|
+
if kw_clean:
|
|
16
|
+
# 避免对已有标签属性进行错误替换
|
|
17
|
+
pattern = re.compile(rf'(?<![<\w/])({re.escape(kw_clean)})(?![>\w])', re.IGNORECASE)
|
|
18
|
+
text = pattern.sub(lambda m: f"<strong>{m.group(0)}</strong>", text)
|
|
19
|
+
return text
|
|
20
|
+
|
|
21
|
+
def instantiate_workspace(template_id, keywords=None, output_path="workspace/resume.html"):
|
|
22
|
+
script_dir = Path(__file__).resolve().parent
|
|
23
|
+
base_dir = script_dir.parent
|
|
24
|
+
|
|
25
|
+
template_dir = base_dir / "src" / "templates" / template_id
|
|
26
|
+
|
|
27
|
+
if not template_dir.exists():
|
|
28
|
+
# 尝试通过 ID 查找
|
|
29
|
+
for sub in (base_dir / "src" / "templates").iterdir():
|
|
30
|
+
if sub.is_dir() and (sub / "metadata.json").exists():
|
|
31
|
+
try:
|
|
32
|
+
meta = json.loads((sub / "metadata.json").read_text(encoding="utf-8"))
|
|
33
|
+
if meta.get("id") == template_id:
|
|
34
|
+
template_dir = sub
|
|
35
|
+
break
|
|
36
|
+
except:
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
if not template_dir.exists():
|
|
40
|
+
raise ValueError(f"Template '{template_id}' not found in src/templates/")
|
|
41
|
+
|
|
42
|
+
html_file = template_dir / "template.html"
|
|
43
|
+
css_file = template_dir / "style.css"
|
|
44
|
+
|
|
45
|
+
if not html_file.exists() or not css_file.exists():
|
|
46
|
+
raise FileNotFoundError(f"template.html or style.css missing in {template_dir}")
|
|
47
|
+
|
|
48
|
+
html_content = html_file.read_text(encoding="utf-8")
|
|
49
|
+
css_content = css_file.read_text(encoding="utf-8")
|
|
50
|
+
|
|
51
|
+
# 将外部 style.css 内联进 HTML,形成零依赖单文件工作场
|
|
52
|
+
inlined_html = html_content.replace(
|
|
53
|
+
'<link rel="stylesheet" href="style.css">',
|
|
54
|
+
f'<style>\n{css_content}\n </style>'
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# 注入关键词高亮
|
|
58
|
+
if keywords:
|
|
59
|
+
kw_list = [k.strip() for k in keywords.split(",") if k.strip()]
|
|
60
|
+
inlined_html = highlight_keywords(inlined_html, kw_list)
|
|
61
|
+
|
|
62
|
+
out_file = Path(output_path)
|
|
63
|
+
out_file.parent.mkdir(parents=True, exist_ok=True)
|
|
64
|
+
out_file.write_text(inlined_html, encoding="utf-8")
|
|
65
|
+
|
|
66
|
+
print("=" * 70)
|
|
67
|
+
print(f" KnowMe CareerForge — Intermediate Canvas Instantiated!")
|
|
68
|
+
print(f" Selected Template : {template_id} ({template_dir.name})")
|
|
69
|
+
print(f" Working Canvas : {out_file.resolve()}")
|
|
70
|
+
print("=" * 70)
|
|
71
|
+
return out_file
|
|
72
|
+
|
|
73
|
+
def main():
|
|
74
|
+
parser = argparse.ArgumentParser(description="KnowMe CareerForge — Workspace Instantiator")
|
|
75
|
+
parser.add_argument("--template", "-t", required=True, help="Template ID (minimal, modern, executive, classic)")
|
|
76
|
+
parser.add_argument("--keywords", "-k", default=None, help="Comma-separated keywords to highlight (e.g. 'Python,LLM,RAG')")
|
|
77
|
+
parser.add_argument("--output", "-o", default="workspace/resume.html", help="Output canvas path (default: workspace/resume.html)")
|
|
78
|
+
|
|
79
|
+
args = parser.parse_args()
|
|
80
|
+
instantiate_workspace(args.template, keywords=args.keywords, output_path=args.output)
|
|
81
|
+
|
|
82
|
+
if __name__ == "__main__":
|
|
83
|
+
main()
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
KnowMe CareerForge — Release & NPM Publishing Automation Script
|
|
4
|
+
自动化执行版本同步、知识库编译、画廊刷新、TypeScript 构建、测试验证与 NPM 打包检查。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import sys, os, subprocess, json, re, argparse
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
def run_command(cmd, cwd, desc):
|
|
11
|
+
print(f"\n[+] {desc}...")
|
|
12
|
+
proc = subprocess.run(cmd, shell=True, text=True, cwd=cwd, capture_output=True)
|
|
13
|
+
if proc.returncode != 0:
|
|
14
|
+
print(f"[✗] Failed at: {desc}")
|
|
15
|
+
print(f"Error Output:\n{proc.stderr}\n{proc.stdout}")
|
|
16
|
+
sys.exit(1)
|
|
17
|
+
print(f"[✓] {desc} succeeded.")
|
|
18
|
+
return proc.stdout
|
|
19
|
+
|
|
20
|
+
def main():
|
|
21
|
+
parser = argparse.ArgumentParser(description="KnowMe CareerForge — Release & NPM Publish Helper")
|
|
22
|
+
parser.add_argument("version", nargs="?", help="New semantic version to release (e.g. 1.0.1, 1.1.0)")
|
|
23
|
+
parser.add_argument("--dry-run", action="store_true", help="Perform build and test verification without publishing")
|
|
24
|
+
parser.add_argument("--publish", action="store_true", help="Directly trigger npm publish --access public after verification")
|
|
25
|
+
|
|
26
|
+
args = parser.parse_args()
|
|
27
|
+
base_dir = Path(__file__).resolve().parent.parent
|
|
28
|
+
|
|
29
|
+
# 1. 确定版本号
|
|
30
|
+
pkg_file = base_dir / "package.json"
|
|
31
|
+
current_version = "1.0.0"
|
|
32
|
+
if pkg_file.exists():
|
|
33
|
+
current_version = json.loads(pkg_file.read_text(encoding="utf-8")).get("version", "1.0.0")
|
|
34
|
+
|
|
35
|
+
target_version = args.version or current_version
|
|
36
|
+
|
|
37
|
+
if not re.match(r'^\d+\.\d+\.\d+(?:-[\w.]+)?$', target_version):
|
|
38
|
+
print(f"[!] Invalid semantic version format: {target_version}. Expected format: X.Y.Z (e.g. 1.0.1)")
|
|
39
|
+
sys.exit(1)
|
|
40
|
+
|
|
41
|
+
print("==============================================================")
|
|
42
|
+
print(" KnowMe CareerForge — NPM Build & Release Automation")
|
|
43
|
+
print(f" Current Version : {current_version}")
|
|
44
|
+
print(f" Target Version : {target_version}")
|
|
45
|
+
print(f" Working Root : {base_dir}")
|
|
46
|
+
print("==============================================================")
|
|
47
|
+
|
|
48
|
+
# 2. 同步多文件版本号
|
|
49
|
+
if target_version != current_version or args.version:
|
|
50
|
+
sync_script = base_dir / "scripts" / "sync-version.py"
|
|
51
|
+
run_command(f"python3 {sync_script} {target_version}", base_dir, f"Synchronizing version to v{target_version}")
|
|
52
|
+
|
|
53
|
+
# 3. 重新编译知识库索引
|
|
54
|
+
build_kb = base_dir / "scripts" / "build-knowledge.py"
|
|
55
|
+
run_command(f"python3 {build_kb}", base_dir, "Compiling Knowledge Base Index")
|
|
56
|
+
|
|
57
|
+
# 4. 重新构建模板画廊
|
|
58
|
+
build_gal = base_dir / "scripts" / "build-gallery.py"
|
|
59
|
+
run_command(f"python3 {build_gal}", base_dir, "Building HTML Template Gallery")
|
|
60
|
+
|
|
61
|
+
# 5. 编译 TypeScript CLI 产物
|
|
62
|
+
run_command("npm run build", base_dir, "Compiling TypeScript CLI (tsc -> dist/)")
|
|
63
|
+
|
|
64
|
+
# 6. 运行全链路自动化测试套件
|
|
65
|
+
test_script = base_dir / "scripts" / "run-all-tests.py"
|
|
66
|
+
run_command(f"python3 {test_script}", base_dir, "Running Full-Chain Test Suites (21/21)")
|
|
67
|
+
|
|
68
|
+
# 7. NPM 打包预检 (npm pack --dry-run)
|
|
69
|
+
pack_output = run_command("npm pack --dry-run --json", base_dir, "Inspecting NPM Package Files")
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
pack_data = json.loads(pack_output)
|
|
73
|
+
if isinstance(pack_data, list) and len(pack_data) > 0:
|
|
74
|
+
files_count = pack_data[0].get("entryCount", "N/A")
|
|
75
|
+
unpacked_size = pack_data[0].get("unpackedSize", "N/A")
|
|
76
|
+
print(f"\n[📦 NPM Package Stats]: {files_count} files included, Unpacked Size: ~{round(unpacked_size / 1024, 1)} KB")
|
|
77
|
+
except:
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
# 8. 发布执行或指引
|
|
81
|
+
print("\n" + "=" * 62)
|
|
82
|
+
print(f" [✓] Release Preparation for v{target_version} Completed!")
|
|
83
|
+
print("=" * 62)
|
|
84
|
+
|
|
85
|
+
if args.publish:
|
|
86
|
+
print("\n[🚀 Triggering NPM Publish...]")
|
|
87
|
+
run_command("npm publish --access public", base_dir, f"Publishing v{target_version} to NPM Registry")
|
|
88
|
+
print(f"\n🎉 Successfully published knowme-careerforge-skill@v{target_version} to NPM!")
|
|
89
|
+
else:
|
|
90
|
+
print("\nNext steps to publish your package to NPM:")
|
|
91
|
+
print(" 1. Review Git status and commit changes:")
|
|
92
|
+
print(f" git add . && git commit -m \"chore(release): bump version to v{target_version}\"")
|
|
93
|
+
print(f" git tag v{target_version} && git push origin main --tags")
|
|
94
|
+
print("\n 2. Publish to NPM registry:")
|
|
95
|
+
print(" npm publish --access public")
|
|
96
|
+
print("\n 3. Verify on NPM:")
|
|
97
|
+
print(f" npm view knowme-careerforge-skill@v{target_version}")
|
|
98
|
+
print(f" npx knowme-careerforge-skill@{target_version} list\n")
|
|
99
|
+
|
|
100
|
+
if __name__ == "__main__":
|
|
101
|
+
main()
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
/**
|
|
3
|
+
* KnowMe CareerForge — Playwright Deterministic PDF Renderer
|
|
4
|
+
* 加载自包含 HTML 中间工作区,等待字体及样式准备就绪,输出标准像素级 A4 PDF。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { chromium } from 'playwright';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
|
|
11
|
+
interface RenderOptions {
|
|
12
|
+
inputHtml: string;
|
|
13
|
+
outputPdf: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function renderResumePdf(options: RenderOptions): Promise<void> {
|
|
17
|
+
const absoluteHtml = path.resolve(options.inputHtml);
|
|
18
|
+
const absolutePdf = path.resolve(options.outputPdf);
|
|
19
|
+
|
|
20
|
+
if (!fs.existsSync(absoluteHtml)) {
|
|
21
|
+
throw new Error(`Input HTML file does not exist: ${absoluteHtml}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 确保输出目录存在
|
|
25
|
+
const outputDir = path.dirname(absolutePdf);
|
|
26
|
+
if (!fs.existsSync(outputDir)) {
|
|
27
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log('======================================================');
|
|
31
|
+
console.log(' KnowMe CareerForge — Playwright PDF Renderer');
|
|
32
|
+
console.log(` Source Canvas : ${absoluteHtml}`);
|
|
33
|
+
console.log(` Target PDF : ${absolutePdf}`);
|
|
34
|
+
console.log('======================================================');
|
|
35
|
+
|
|
36
|
+
const browser = await chromium.launch({ headless: true });
|
|
37
|
+
const context = await browser.newContext({
|
|
38
|
+
viewport: { width: 794, height: 1123 }, // A4 at 96 DPI
|
|
39
|
+
deviceScaleFactor: 2
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const page = await context.newPage();
|
|
43
|
+
await page.goto(`file://${absoluteHtml}`, { waitUntil: 'networkidle' });
|
|
44
|
+
|
|
45
|
+
// 确保所有 Web 字体准备就绪
|
|
46
|
+
await page.evaluateHandle('document.fonts.ready');
|
|
47
|
+
|
|
48
|
+
await page.pdf({
|
|
49
|
+
path: absolutePdf,
|
|
50
|
+
format: 'A4',
|
|
51
|
+
printBackground: true,
|
|
52
|
+
preferCSSPageSize: true,
|
|
53
|
+
margin: { top: '0px', right: '0px', bottom: '0px', left: '0px' }
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await browser.close();
|
|
57
|
+
console.log(`[✓] Deterministic PDF Rendered Successfully -> ${absolutePdf}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function main() {
|
|
61
|
+
const args = process.argv.slice(2);
|
|
62
|
+
let inputHtml = 'workspace/resume.html';
|
|
63
|
+
let outputPdf = 'workspace/resume.pdf';
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < args.length; i++) {
|
|
66
|
+
if (args[i] === '--input' || args[i] === '-i') {
|
|
67
|
+
inputHtml = args[i + 1];
|
|
68
|
+
i++;
|
|
69
|
+
} else if (args[i] === '--output' || args[i] === '-o') {
|
|
70
|
+
outputPdf = args[i + 1];
|
|
71
|
+
i++;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
await renderResumePdf({ inputHtml, outputPdf });
|
|
77
|
+
} catch (err: unknown) {
|
|
78
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
79
|
+
console.error(`[✗] PDF Rendering Failed: ${errorMsg}`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (require.main === module) {
|
|
85
|
+
main();
|
|
86
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
KnowMe CareerForge — Unified Full-Chain Test Runner
|
|
4
|
+
执行所有自动化测试套件(Templates, ATS, Workflows, End-to-End Pipeline),输出格式化质检报告。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os, sys, unittest, time
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
def run_all_test_suites():
|
|
11
|
+
start_time = time.time()
|
|
12
|
+
base_dir = Path(__file__).resolve().parent.parent
|
|
13
|
+
tests_dir = base_dir / "tests"
|
|
14
|
+
|
|
15
|
+
print("==============================================================")
|
|
16
|
+
print(" KnowMe CareerForge — Full-Chain Automated Test Suite")
|
|
17
|
+
print(f" Test Root : {tests_dir}")
|
|
18
|
+
print("==============================================================")
|
|
19
|
+
|
|
20
|
+
loader = unittest.TestLoader()
|
|
21
|
+
suite = loader.discover(start_dir=str(tests_dir), pattern="test_*.py")
|
|
22
|
+
|
|
23
|
+
runner = unittest.TextTestRunner(verbosity=2)
|
|
24
|
+
result = runner.run(suite)
|
|
25
|
+
|
|
26
|
+
elapsed = time.time() - start_time
|
|
27
|
+
|
|
28
|
+
print("\n" + "=" * 62)
|
|
29
|
+
print(" Test Execution Summary")
|
|
30
|
+
print(f" Total Tests Run : {result.testsRun}")
|
|
31
|
+
print(f" Failures : {len(result.failures)}")
|
|
32
|
+
print(f" Errors : {len(result.errors)}")
|
|
33
|
+
print(f" Skipped : {len(result.skipped)}")
|
|
34
|
+
print(f" Time Elapsed : {elapsed:.2f}s")
|
|
35
|
+
print(f" Overall Status : {'[✓] ALL TESTS PASSED' if result.wasSuccessful() else '[✗] TEST FAILURES DETECTED'}")
|
|
36
|
+
print("==============================================================")
|
|
37
|
+
|
|
38
|
+
return 0 if result.wasSuccessful() else 1
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
sys.exit(run_all_test_suites())
|