knowme-careerforge-skill 0.0.4 → 0.0.7
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/AGENT.md +276 -0
- package/ARCHITECTURE.md +220 -0
- package/README.md +46 -63
- package/README.zh-CN.md +47 -66
- package/SKILL.md +38 -135
- package/agents/claude/knowme-careerforge.md +2 -17
- package/agents/codex/knowme-careerforge.yaml +2 -20
- package/agents/cursor/knowme-careerforge.mdc +4 -13
- package/agents/gemini/knowme-careerforge.json +1 -1
- package/agents/opencode/skill.yaml +1 -1
- package/agents/windsurf/knowme-careerforge.rules +2 -12
- package/dist/cli/src/commands/init.js +51 -32
- package/dist/cli/src/index.js +25 -21
- package/dist/scripts/rendering/engine-loader.d.ts +2 -0
- package/dist/scripts/rendering/engine-loader.js +46 -0
- package/dist/scripts/rendering/render-pdf.d.ts +3 -8
- package/dist/scripts/rendering/render-pdf.js +7 -103
- package/dist/scripts/rendering/runtime.d.ts +9 -0
- package/dist/scripts/rendering/runtime.js +63 -0
- package/dist/scripts/validation/validate-ats.d.ts +2 -22
- package/dist/scripts/validation/validate-ats.js +6 -168
- package/dist/scripts/validation/validate-layout.d.ts +2 -23
- package/dist/scripts/validation/validate-layout.js +5 -159
- package/package.json +15 -4
- package/references/01-evidence-mining.md +35 -0
- package/references/02-career-goal.md +40 -0
- package/references/03-jd-analysis.md +32 -0
- package/references/04-template-selection.md +30 -0
- package/references/05-html-canvas-tokens.md +12 -0
- package/references/06-qa-and-rendering.md +27 -0
- package/references/07-artifact-contract.md +83 -0
- package/scripts/Agent.md +9 -136
- package/scripts/build/build-gallery.py +66 -56
- package/scripts/build/run-all-tests.py +1 -0
- package/scripts/build/sync-version.py +27 -9
- package/scripts/contracts/profile.py +101 -0
- package/scripts/evidence/extract-evidence.py +109 -62
- package/scripts/pipeline/forge.py +276 -136
- package/scripts/rendering/README.md +65 -0
- package/scripts/rendering/browser-engine.d.ts +15 -0
- package/scripts/rendering/browser-engine.js +408 -0
- package/scripts/rendering/browser_engine.py +39 -0
- package/scripts/rendering/engine-loader.ts +11 -0
- package/scripts/rendering/render-pdf.py +26 -151
- package/scripts/rendering/render-pdf.ts +7 -83
- package/scripts/template/instantiate-resume.py +266 -173
- package/scripts/template/search-template.py +89 -3
- package/scripts/validation/measure-dom.js +5 -0
- package/scripts/validation/validate-ats.ts +6 -174
- package/scripts/validation/validate-layout.ts +5 -159
- package/scripts/validation/validate-resume.py +23 -134
- package/skill.json +7 -6
- package/src/knowledge/index.json +391 -3
- package/src/knowledge/resume-schema.json +377 -71
- package/src/knowledge/templates.json +389 -1
- package/src/references/01-evidence-mining.md +24 -30
- package/src/references/02-career-goal.md +4 -2
- package/src/references/03-jd-analysis.md +5 -17
- package/src/references/04-template-selection.md +20 -33
- package/src/references/05-html-canvas-tokens.md +6 -45
- package/src/references/06-qa-and-rendering.md +16 -43
- package/src/references/07-artifact-contract.md +83 -0
- package/src/templates/Agent.md +11 -0
- package/src/templates/academic-research/README.md +25 -0
- package/src/templates/academic-research/canvas.html +21 -0
- package/src/templates/academic-research/metadata.json +38 -0
- package/src/templates/academic-research/sample-profile.json +86 -0
- package/src/templates/academic-research/style.css +213 -0
- package/src/templates/classic/README.md +9 -0
- package/src/templates/classic/canvas.html +21 -0
- package/src/templates/classic/metadata.json +3 -0
- package/src/templates/classic/sample-profile.json +86 -0
- package/src/templates/classic/style.css +14 -4
- package/src/templates/common/base.css +32 -0
- package/src/templates/common/canvas-bindings.css +41 -0
- package/src/templates/common/resume-contract.md +8 -0
- package/src/templates/compact-dense/README.md +25 -0
- package/src/templates/compact-dense/canvas.html +14 -0
- package/src/templates/compact-dense/metadata.json +38 -0
- package/src/templates/compact-dense/sample-profile.json +86 -0
- package/src/templates/compact-dense/style.css +218 -0
- package/src/templates/creative-tech/README.md +26 -0
- package/src/templates/creative-tech/canvas.html +15 -0
- package/src/templates/creative-tech/metadata.json +39 -0
- package/src/templates/creative-tech/sample-profile.json +86 -0
- package/src/templates/creative-tech/style.css +257 -0
- package/src/templates/data-analyst/README.md +26 -0
- package/src/templates/data-analyst/canvas.html +15 -0
- package/src/templates/data-analyst/metadata.json +39 -0
- package/src/templates/data-analyst/sample-profile.json +86 -0
- package/src/templates/data-analyst/style.css +285 -0
- package/src/templates/executive/README.md +9 -0
- package/src/templates/executive/canvas.html +15 -0
- package/src/templates/executive/sample-profile.json +86 -0
- package/src/templates/executive/style.css +58 -6
- package/src/templates/international-flow/README.md +26 -0
- package/src/templates/international-flow/canvas.html +21 -0
- package/src/templates/international-flow/metadata.json +37 -0
- package/src/templates/international-flow/sample-profile.json +86 -0
- package/src/templates/international-flow/style.css +208 -0
- package/src/templates/minimal/README.md +12 -1
- package/src/templates/minimal/canvas.html +21 -0
- package/src/templates/minimal/sample-profile.json +86 -0
- package/src/templates/minimal/style.css +24 -0
- package/src/templates/modern/README.md +9 -0
- package/src/templates/modern/canvas.html +15 -0
- package/src/templates/modern/sample-profile.json +86 -0
- package/src/templates/modern/style.css +48 -1
- package/src/templates/startup-generalist/README.md +25 -0
- package/src/templates/startup-generalist/canvas.html +21 -0
- package/src/templates/startup-generalist/metadata.json +37 -0
- package/src/templates/startup-generalist/sample-profile.json +86 -0
- package/src/templates/startup-generalist/style.css +237 -0
- package/src/templates/classic/template.html +0 -133
- package/src/templates/executive/template.html +0 -101
- package/src/templates/minimal/template.html +0 -114
- package/src/templates/modern/template.html +0 -139
|
@@ -1,162 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import sys, os, subprocess, platform, shutil, argparse
|
|
2
|
+
"""Render and verify a fresh PDF through browser-engine.js before publication."""
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
8
6
|
from pathlib import Path
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def get_project_root() -> Path:
|
|
12
|
-
curr = Path(__file__).resolve().parent
|
|
13
|
-
for _ in range(5):
|
|
14
|
-
if (curr / "SKILL.md").exists() or (curr / "package.json").exists():
|
|
15
|
-
return curr
|
|
16
|
-
curr = curr.parent
|
|
17
|
-
return Path.cwd()
|
|
18
|
-
|
|
19
|
-
def find_system_browser() -> Optional[str]:
|
|
20
|
-
system = platform.system()
|
|
21
|
-
|
|
22
|
-
custom_bin = os.environ.get("CHROME_BIN") or os.environ.get("BROWSER_PATH")
|
|
23
|
-
if custom_bin and Path(custom_bin).exists():
|
|
24
|
-
return custom_bin
|
|
25
|
-
|
|
26
|
-
candidates: List[str] = []
|
|
27
|
-
|
|
28
|
-
if system == "Darwin": # macOS
|
|
29
|
-
candidates = [
|
|
30
|
-
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
31
|
-
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
32
|
-
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
|
33
|
-
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
34
|
-
str(Path.home() / "Applications/Google Chrome.app/Contents/MacOS/Google Chrome"),
|
|
35
|
-
str(Path.home() / "Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"),
|
|
36
|
-
]
|
|
37
|
-
elif system == "Linux":
|
|
38
|
-
candidates = [
|
|
39
|
-
"google-chrome", "google-chrome-stable", "chromium", "chromium-browser",
|
|
40
|
-
"/usr/bin/google-chrome", "/usr/bin/google-chrome-stable",
|
|
41
|
-
"/usr/bin/chromium", "/usr/bin/chromium-browser",
|
|
42
|
-
"/snap/bin/chromium"
|
|
43
|
-
]
|
|
44
|
-
elif system == "Windows":
|
|
45
|
-
prog_files = os.environ.get("PROGRAMFILES", r"C:\Program Files")
|
|
46
|
-
prog_files_x86 = os.environ.get("PROGRAMFILES(X86)", r"C:\Program Files (x86)")
|
|
47
|
-
local_app = os.environ.get("LOCALAPPDATA", "")
|
|
48
|
-
|
|
49
|
-
candidates = [
|
|
50
|
-
os.path.join(prog_files, r"Google\Chrome\Application\chrome.exe"),
|
|
51
|
-
os.path.join(prog_files_x86, r"Google\Chrome\Application\chrome.exe"),
|
|
52
|
-
os.path.join(prog_files_x86, r"Microsoft\Edge\Application\msedge.exe"),
|
|
53
|
-
os.path.join(prog_files, r"Microsoft\Edge\Application\msedge.exe"),
|
|
54
|
-
os.path.join(local_app, r"Google\Chrome\Application\chrome.exe"),
|
|
55
|
-
os.path.join(local_app, r"Microsoft\Edge\Application\msedge.exe"),
|
|
56
|
-
os.path.join(local_app, r"BraveSoftware\Brave-Browser\Application\brave.exe"),
|
|
57
|
-
]
|
|
58
|
-
|
|
59
|
-
for candidate in candidates:
|
|
60
|
-
if candidate.startswith("/") or ":" in candidate:
|
|
61
|
-
if Path(candidate).exists():
|
|
62
|
-
return candidate
|
|
63
|
-
else:
|
|
64
|
-
resolved = shutil.which(candidate)
|
|
65
|
-
if resolved:
|
|
66
|
-
return resolved
|
|
67
|
-
|
|
68
|
-
return None
|
|
69
|
-
|
|
70
|
-
def render_via_playwright(html_path: Path, pdf_path: Path) -> bool:
|
|
71
|
-
root = get_project_root()
|
|
72
|
-
script_ts = root / "scripts" / "rendering" / "render-pdf.ts"
|
|
73
|
-
if not script_ts.exists():
|
|
74
|
-
script_ts = root / "scripts" / "render-pdf.ts"
|
|
7
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
8
|
+
from browser_engine import run, exit_code
|
|
75
9
|
|
|
76
|
-
if script_ts.exists() and shutil.which("npx"):
|
|
77
|
-
try:
|
|
78
|
-
cmd = ["npx", "ts-node", str(script_ts), "--input", str(html_path), "--output", str(pdf_path)]
|
|
79
|
-
res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=30)
|
|
80
|
-
if res.returncode == 0 and pdf_path.exists():
|
|
81
|
-
return True
|
|
82
|
-
except Exception:
|
|
83
|
-
pass
|
|
84
|
-
return False
|
|
85
10
|
|
|
86
|
-
def
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
"--no-first-run",
|
|
93
|
-
"--no-default-browser-check",
|
|
94
|
-
"--disable-background-networking",
|
|
95
|
-
"--no-pdf-header-footer",
|
|
96
|
-
"--run-all-compositor-stages-before-draw",
|
|
97
|
-
f"--print-to-pdf={pdf_path.resolve()}",
|
|
98
|
-
html_url
|
|
99
|
-
]
|
|
100
|
-
try:
|
|
101
|
-
res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=25)
|
|
102
|
-
return pdf_path.exists() and pdf_path.stat().st_size > 0
|
|
103
|
-
except Exception as e:
|
|
104
|
-
print(f"[!] Browser render failed: {e}")
|
|
105
|
-
return False
|
|
11
|
+
def render_pdf(html_path_str, pdf_path_str, quiet=False, expected_pages=1, auto_heal=False):
|
|
12
|
+
result = run(html_path_str, expected_pages=expected_pages,
|
|
13
|
+
auto_heal=auto_heal, output_pdf=pdf_path_str)
|
|
14
|
+
if result["status"] != "PASS":
|
|
15
|
+
raise RuntimeError(json.dumps(result, ensure_ascii=False))
|
|
16
|
+
return Path(pdf_path_str).resolve()
|
|
106
17
|
|
|
107
|
-
def render_pdf(html_path_str: str, pdf_path_str: str, quiet: bool = False) -> Path:
|
|
108
|
-
html_path = Path(html_path_str).resolve()
|
|
109
|
-
pdf_path = Path(pdf_path_str).resolve()
|
|
110
|
-
|
|
111
|
-
if not html_path.exists():
|
|
112
|
-
raise FileNotFoundError(f"Input HTML file does not exist: {html_path}")
|
|
113
|
-
|
|
114
|
-
pdf_path.parent.mkdir(parents=True, exist_ok=True)
|
|
115
|
-
|
|
116
|
-
if not quiet:
|
|
117
|
-
print("==============================================================")
|
|
118
|
-
print(" KnowMe CareerForge — Multi-Strategy PDF Renderer")
|
|
119
|
-
print(f" Source Canvas : {html_path}")
|
|
120
|
-
print(f" Target PDF : {pdf_path}")
|
|
121
|
-
print("==============================================================")
|
|
122
|
-
|
|
123
|
-
# Strategy 1: Playwright Headless
|
|
124
|
-
if not quiet:
|
|
125
|
-
print("[*] Strategy 1: Trying Playwright Headless Engine...")
|
|
126
|
-
if render_via_playwright(html_path, pdf_path):
|
|
127
|
-
if not quiet:
|
|
128
|
-
print(f"[✓] Render Succeeded via Playwright -> {pdf_path}")
|
|
129
|
-
return pdf_path
|
|
130
|
-
|
|
131
|
-
# Strategy 2: System Installed Chromium/Chrome/Edge/Brave
|
|
132
|
-
browser_bin = find_system_browser()
|
|
133
|
-
if browser_bin:
|
|
134
|
-
if not quiet:
|
|
135
|
-
print(f"[*] Strategy 2: Found system browser at: {browser_bin}")
|
|
136
|
-
if render_via_browser(browser_bin, html_path, pdf_path):
|
|
137
|
-
if not quiet:
|
|
138
|
-
print(f"[✓] Render Succeeded via System Browser -> {pdf_path}")
|
|
139
|
-
return pdf_path
|
|
140
|
-
|
|
141
|
-
raise RuntimeError("No suitable PDF rendering engine found. Please install Playwright or Google Chrome / Edge.")
|
|
142
18
|
|
|
143
19
|
def main():
|
|
144
|
-
parser = argparse.ArgumentParser(description=
|
|
145
|
-
parser.add_argument("html", nargs="?", default="workspace/resume.html"
|
|
146
|
-
parser.add_argument("pdf", nargs="?", default="workspace/resume.pdf"
|
|
147
|
-
parser.add_argument("--input", "-i"
|
|
148
|
-
parser.add_argument("--output", "-o"
|
|
149
|
-
parser.add_argument("--
|
|
150
|
-
|
|
20
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
21
|
+
parser.add_argument("html", nargs="?", default="workspace/resume.html")
|
|
22
|
+
parser.add_argument("pdf", nargs="?", default="workspace/resume.pdf")
|
|
23
|
+
parser.add_argument("--input", "-i")
|
|
24
|
+
parser.add_argument("--output", "-o")
|
|
25
|
+
parser.add_argument("--expected-pages", "-p", type=int, default=1)
|
|
26
|
+
parser.add_argument("--auto-heal", action="store_true")
|
|
27
|
+
parser.add_argument("--quiet", "-q", action="store_true")
|
|
28
|
+
parser.add_argument("--json", action="store_true")
|
|
151
29
|
args = parser.parse_args()
|
|
152
|
-
|
|
153
|
-
|
|
30
|
+
result = run(args.input or args.html, expected_pages=args.expected_pages,
|
|
31
|
+
auto_heal=args.auto_heal, output_pdf=args.output or args.pdf)
|
|
32
|
+
print(json.dumps(result, ensure_ascii=False))
|
|
33
|
+
return exit_code(result)
|
|
154
34
|
|
|
155
|
-
try:
|
|
156
|
-
render_pdf(input_html, output_pdf, quiet=args.quiet)
|
|
157
|
-
except Exception as e:
|
|
158
|
-
print(f"[✗] PDF Generation Failed: {e}", file=sys.stderr)
|
|
159
|
-
sys.exit(1)
|
|
160
35
|
|
|
161
36
|
if __name__ == "__main__":
|
|
162
|
-
main()
|
|
37
|
+
raise SystemExit(main())
|
|
@@ -1,86 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env ts-node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* 加载自包含 HTML 中间工作区,等待字体及样式准备就绪,输出标准像素级 A4 PDF。
|
|
5
|
-
*/
|
|
2
|
+
import engine = require('./engine-loader');
|
|
3
|
+
import type { EngineOptions, EngineResult } from './browser-engine';
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
const outputDir = path.dirname(absolutePdf);
|
|
25
|
-
if (!fs.existsSync(outputDir)) {
|
|
26
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
console.log('======================================================');
|
|
30
|
-
console.log(' KnowMe CareerForge — Playwright PDF Renderer');
|
|
31
|
-
console.log(` Source Canvas : ${absoluteHtml}`);
|
|
32
|
-
console.log(` Target PDF : ${absolutePdf}`);
|
|
33
|
-
console.log('======================================================');
|
|
34
|
-
|
|
35
|
-
const browser = await chromium.launch({ headless: true });
|
|
36
|
-
const context = await browser.newContext({
|
|
37
|
-
viewport: { width: 794, height: 1123 }, // A4 at 96 DPI
|
|
38
|
-
deviceScaleFactor: 2
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const page = await context.newPage();
|
|
42
|
-
await page.goto(`file://${absoluteHtml}`, { waitUntil: 'networkidle' });
|
|
43
|
-
await page.evaluateHandle('document.fonts.ready');
|
|
44
|
-
|
|
45
|
-
await page.pdf({
|
|
46
|
-
path: absolutePdf,
|
|
47
|
-
format: 'A4',
|
|
48
|
-
printBackground: true,
|
|
49
|
-
preferCSSPageSize: true,
|
|
50
|
-
margin: { top: '0px', right: '0px', bottom: '0px', left: '0px' }
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
await browser.close();
|
|
54
|
-
console.log(`[✓] Deterministic PDF Rendered Successfully -> ${absolutePdf}`);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async function main() {
|
|
58
|
-
const args = process.argv.slice(2);
|
|
59
|
-
let inputHtml = 'workspace/resume.html';
|
|
60
|
-
let outputPdf = 'workspace/resume.pdf';
|
|
61
|
-
|
|
62
|
-
for (let i = 0; i < args.length; i++) {
|
|
63
|
-
if (args[i] === '--input' || args[i] === '-i') {
|
|
64
|
-
inputHtml = args[i + 1];
|
|
65
|
-
i++;
|
|
66
|
-
} else if (args[i] === '--output' || args[i] === '-o') {
|
|
67
|
-
outputPdf = args[i + 1];
|
|
68
|
-
i++;
|
|
69
|
-
} else if (!args[i].startsWith('-')) {
|
|
70
|
-
if (i === 0) inputHtml = args[i];
|
|
71
|
-
else if (i === 1) outputPdf = args[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();
|
|
5
|
+
export async function renderResumePdf(options: EngineOptions & { inputHtml: string; outputPdf: string }): Promise<EngineResult> {
|
|
6
|
+
const result = await engine.run(options.inputHtml, options);
|
|
7
|
+
if (result.status !== 'PASS') throw new Error(JSON.stringify(result));
|
|
8
|
+
return result;
|
|
86
9
|
}
|
|
10
|
+
if (require.main === module) engine.cli(process.argv.slice(2), { outputPdf: 'workspace/resume.pdf' });
|