#!/usr/bin/env zx import 'zx/globals'; import { readFileSync, existsSync, mkdirSync, rmSync, cpSync, writeFileSync, readdirSync, statSync } from 'node:fs'; import { join, dirname, basename } from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT = join(__dirname, '..'); const MANIFEST_PATH = join(ROOT, 'resources', 'skills', 'preinstalled-manifest.json'); const OUTPUT_ROOT = join(ROOT, 'build', 'preinstalled-skills'); const TMP_ROOT = join(ROOT, 'build', '.tmp-preinstalled-skills'); function loadManifest() { if (!existsSync(MANIFEST_PATH)) { throw new Error(`Missing manifest: ${MANIFEST_PATH}`); } const raw = readFileSync(MANIFEST_PATH, 'utf8'); const parsed = JSON.parse(raw); if (!parsed || !Array.isArray(parsed.skills)) { throw new Error('Invalid preinstalled-skills manifest format'); } for (const item of parsed.skills) { const hasRemoteSource = Boolean(item.repo && item.repoPath); const hasLocalSource = Boolean(item.localPath); if (!item.slug || (hasRemoteSource === hasLocalSource)) { throw new Error(`Invalid manifest entry: ${JSON.stringify(item)}`); } } return parsed.skills; } function groupByRepoRef(entries) { const grouped = new Map(); for (const entry of entries) { if (entry.localPath) continue; const ref = entry.ref || 'main'; const key = `${entry.repo}#${ref}`; if (!grouped.has(key)) grouped.set(key, { repo: entry.repo, ref, entries: [] }); grouped.get(key).entries.push(entry); } return [...grouped.values()]; } function createRepoDirName(repo, ref) { return `${repo.replace(/[\\/]/g, '__')}__${ref.replace(/[^a-zA-Z0-9._-]/g, '_')}`; } function toGitPath(inputPath) { if (process.platform !== 'win32') return inputPath; // Git on Windows accepts forward slashes and avoids backslash escape quirks. return inputPath.replace(/\\/g, '/'); } function normalizeRepoPath(repoPath) { return repoPath.replace(/\\/g, '/').replace(/^\/+/, '').replace(/\/+$/, ''); } function shouldCopySkillFile(srcPath) { const base = basename(srcPath); if (base === '.git') return false; if (base === '.subset.tar') return false; return true; } function toYamlString(value) { if (/^[a-zA-Z0-9_-]+$/.test(value)) return value; return JSON.stringify(value); } function rewriteSkillName(skillManifest, name) { const raw = readFileSync(skillManifest, 'utf8'); const frontmatterMatch = raw.match(/^---\n([\s\S]*?)\n---/); if (!frontmatterMatch) return; const body = frontmatterMatch[1]; const nextBody = /^name:\s*.*$/m.test(body) ? body.replace(/^name:\s*.*$/m, `name: ${toYamlString(name)}`) : `name: ${toYamlString(name)}\n${body}`; const next = raw.replace(/^---\n[\s\S]*?\n---/, `---\n${nextBody}\n---`); if (next !== raw) writeFileSync(skillManifest, next, 'utf8'); } const runtimePolicies = new Map([ ['docx', `## Zhinian Runtime Policy - Do not ask the user to install any desktop office suite or GUI converter. - For normal .docx creation/editing, use the OpenXML/.NET workflow in this skill. - If the .NET runtime is unavailable, run the skill setup/repair path silently where possible; report a clear environment limitation only if document generation is blocked. - If optional .doc conversion or visual preview requires an unavailable desktop converter, skip that optional step and continue. Mention the limitation only when it directly blocks the requested output. `], ['xlsx', `## Zhinian Runtime Policy - Do not ask the user to install any desktop office suite or GUI converter. - Use the XML/static validation workflow as the default path. - When running Python scripts, prefer \`uv run python\`. If a Python dependency is missing, use \`uv pip install\` instead of asking the user to install it manually. - Do not perform desktop-engine formula recalculation in the Zhinian bundled runtime; use static validation with \`formula_check.py\`. `], ['pptx', `## Zhinian Runtime Policy - Do not ask the user to install any desktop office suite or GUI converter. - Use the PptxGenJS/XML workflows in this skill as the default path. - Use the project-bundled Node/npm modules when available. Do not ask the user to run global npm installs for \`pptxgenjs\`, \`react-icons\`, \`react\`, \`react-dom\`, or \`sharp\`. - If optional preview/conversion tools are unavailable, skip those optional steps and continue. Mention the limitation only when it directly blocks the requested output. `], ['pdf', `## Zhinian Runtime Policy - Do not ask the user to install desktop office suites or unrelated external GUI tools. - Use the Python/HTML/PDF workflows in this skill and keep outputs in project-accessible local paths. - When running Python scripts, prefer \`uv run python\`. If a Python dependency is missing, use \`uv pip install\` instead of asking the user to install it manually. - If an optional browser renderer is unavailable, choose another supported path or report the limitation only when it directly blocks the requested output. - Never run task-time Playwright or Chromium installers inside a user task. Chromium is prepared by the app/runtime layer; task-time browser downloads can collide with other runs. `], ['html-slides', `## Zhinian Runtime Policy - Use the project/app-bundled browser runtime for optional preview checks. - Never run \`npx playwright install chromium\`, \`playwright install chromium\`, or \`npm install -g playwright\` inside a user task. - If browser preview is unavailable, validate the deck by static file checks and report that browser preview was skipped. Do not block deck delivery on a task-time Chromium download. `], ]); function insertAfterFrontmatter(raw, content) { const frontmatterMatch = raw.match(/^---\n[\s\S]*?\n---\n?/); if (!frontmatterMatch) { return `${content}\n${raw}`; } const index = frontmatterMatch[0].length; return `${raw.slice(0, index)}${content}\n${raw.slice(index)}`; } function applyRuntimePolicy(skillManifest, slug) { const policy = runtimePolicies.get(slug); if (!policy) return; const raw = readFileSync(skillManifest, 'utf8'); if (raw.includes('## Zhinian Runtime Policy')) return; writeFileSync(skillManifest, insertAfterFrontmatter(raw, policy), 'utf8'); } function replaceFileText(filePath, replacements) { if (!existsSync(filePath)) return; let next = readFileSync(filePath, 'utf8'); const original = next; for (const [pattern, replacement] of replacements) { next = next.replace(pattern, replacement); } if (next !== original) writeFileSync(filePath, next, 'utf8'); } function listTextFiles(rootDir) { if (!existsSync(rootDir)) return []; const files = []; const visit = (dir) => { for (const entry of readdirSync(dir)) { const fullPath = join(dir, entry); const stats = statSync(fullPath); if (stats.isDirectory()) { visit(fullPath); } else if (stats.isFile()) { files.push(fullPath); } } }; visit(rootDir); return files; } function findMacAppLikeDirectories(rootDir) { if (!existsSync(rootDir)) return []; const hits = []; const visit = (dir) => { for (const entry of readdirSync(dir)) { const fullPath = join(dir, entry); const stats = statSync(fullPath); if (!stats.isDirectory()) continue; if (entry.endsWith('.app')) { hits.push(fullPath); } visit(fullPath); } }; visit(rootDir); return hits; } function assertNoMacAppLikeDirectories(rootDir) { const hits = findMacAppLikeDirectories(rootDir); if (!hits.length) return; const formatted = hits .map((hit) => ` - ${hit.replace(`${ROOT}/`, '')}`) .join('\n'); throw new Error(`Preinstalled skills must not contain .app directories because macOS codesign treats them as app bundles:\n${formatted}`); } function sanitizeDesktopOfficeEngineTerms(targetDir, slug) { if (!['docx', 'xlsx', 'pptx', 'pdf'].includes(slug)) return; if (slug === 'xlsx') { rmSync(join(targetDir, 'scripts', 'libreoffice_recalc.py'), { force: true }); rmSync(join(targetDir, 'recalc.py'), { force: true }); mkdirSync(join(targetDir, 'references'), { recursive: true }); writeFileSync(join(targetDir, 'references', 'validate.md'), `# XLSX Formula Validation Use static validation only in the Zhinian bundled runtime. ## Standard Path 1. Run \`formula_check.py\` on the workbook. 2. Fix malformed formulas, missing references, circular references, or packaging errors reported by the script. 3. Repack the workbook when XML edits are needed. 4. Run \`formula_check.py\` again and deliver only after it exits successfully. Do not ask the user to install desktop office suites or GUI converters. Do not run desktop-engine recalculation in the bundled runtime. `, 'utf8'); writeFileSync(join(targetDir, 'formula_check.py'), `#!/usr/bin/env python3 import json import sys from pathlib import Path from openpyxl import load_workbook EXCEL_ERRORS = ["#VALUE!", "#DIV/0!", "#REF!", "#NAME?", "#NULL!", "#NUM!", "#N/A"] def scan_workbook(filename): path = Path(filename) if not path.exists(): return {"error": f"File {filename} does not exist"} result = { "status": "success", "total_errors": 0, "total_formulas": 0, "error_summary": {}, } wb_formulas = load_workbook(path, data_only=False) try: for sheet_name in wb_formulas.sheetnames: sheet = wb_formulas[sheet_name] for row in sheet.iter_rows(): for cell in row: value = cell.value if isinstance(value, str) and value.startswith("="): result["total_formulas"] += 1 for error in EXCEL_ERRORS: if error in value: result["error_summary"].setdefault(error, {"count": 0, "locations": []}) result["error_summary"][error]["count"] += 1 result["error_summary"][error]["locations"].append(f"{sheet_name}!{cell.coordinate}") result["total_errors"] += 1 break finally: wb_formulas.close() wb_values = load_workbook(path, data_only=True) try: for sheet_name in wb_values.sheetnames: sheet = wb_values[sheet_name] for row in sheet.iter_rows(): for cell in row: value = cell.value if not isinstance(value, str): continue for error in EXCEL_ERRORS: if error in value: result["error_summary"].setdefault(error, {"count": 0, "locations": []}) result["error_summary"][error]["count"] += 1 result["error_summary"][error]["locations"].append(f"{sheet_name}!{cell.coordinate}") result["total_errors"] += 1 break finally: wb_values.close() if result["total_errors"] > 0: result["status"] = "errors_found" return result def main(): if len(sys.argv) < 2: print("Usage: python formula_check.py ") sys.exit(1) print(json.dumps(scan_workbook(sys.argv[1]), ensure_ascii=False, indent=2)) if __name__ == "__main__": main() `, 'utf8'); } if (slug === 'docx') { rmSync(join(targetDir, 'scripts', 'doc_to_docx.sh'), { force: true }); } for (const filePath of listTextFiles(targetDir)) { let raw; try { raw = readFileSync(filePath, 'utf8'); } catch { continue; } if (raw.includes('\u0000')) continue; let next = raw .replace(/libreoffice_recalc\.py/g, 'formula_check.py') .replace(/\brecalc\.py\b/g, 'formula_check.py') .replace(/scripts\/doc_to_docx\.sh\s+input\.doc\s+output_dir\/?/g, 'legacy .doc conversion is not bundled; request or create a .docx file instead') .replace(/Convert `?\.doc`?\s*→\s*`?\.docx`? if needed:[^\n]*/g, 'Legacy .doc conversion is not bundled. Ask for a .docx source or create a new .docx output.') .replace(/Excel\/LibreOffice/g, 'Excel-compatible') .replace(/Word\/LibreOffice/g, 'Word-compatible') .replace(/LibreOffice/g, 'desktop office engine') .replace(/libreoffice/g, 'desktop-office-engine') .replace(/\bsoffice\b/g, 'desktop office converter') .replace(/\bLibra\b/g, 'desktop office engine') .replace(/\blibra\b/g, 'desktop-office-engine') .replace(/desktop office engine/g, 'compatible viewer') .replace(/desktop-office-engine/g, 'compatible-viewer') .replace(/desktop office converter/g, 'legacy document converter') .replace(/dynamic recalculation/g, 'runtime formula execution') .replace(/headless recalculation/g, 'static validation') .replace(/`sudo apt-get install pandoc`/g, 'use bundled text extraction when available') .replace(/`sudo apt-get install poppler-utils`/g, 'use bundled PDF/image tooling when available') .replace(/`sudo apt-get install compatible-viewer`/g, 'skip optional PDF conversion if unavailable') .replace(/`npm install -g docx`/g, 'use the project-bundled `docx` module when available') .replace(/`npm install -g pptxgenjs`/g, 'use the project-bundled `pptxgenjs` module') .replace(/`npm install -g playwright`/g, 'use the project-bundled Playwright runtime when available') .replace(/`npx playwright install chromium`/g, 'use the project-bundled Chromium browser when available') .replace(/`playwright install chromium`/g, 'use the project-bundled Chromium browser when available') .replace(/\bnpx\s+playwright\s+install\s+chromium\b/g, 'use the project-bundled Chromium browser when available') .replace(/\bplaywright\s+install\s+chromium\b/g, 'use the project-bundled Chromium browser when available') .replace(/`npm install -g react-icons react react-dom`/g, 'use project-bundled `react-icons`, `react`, and `react-dom`') .replace(/`npm install -g sharp`/g, 'use the project-bundled `sharp` module when available') .replace(/`pip install defusedxml`/g, '`uv pip install defusedxml`') .replace(/`pip install "markitdown\[pptx\]"`/g, '`uv pip install "markitdown[pptx]"`') .replace(/# Requires: pip install ([^\n]+)/g, '# Requires: uv pip install $1') .replace(/Assumes docx is already installed globally\s*If not installed: use the project-bundled `docx` module when available/g, 'Use the project-bundled `docx` module when available') .replace(/Required dependencies \(install if not available\):/g, 'Runtime dependencies are managed by Zhinian when available:') .replace(/Required dependencies \(should already be installed\):/g, 'Runtime dependencies are managed by Zhinian when available:'); if (slug === 'xlsx') { next = next .replace(/\*\*compatible viewer Required for Formula Recalculation\*\*:[^\n]*/g, '**Static formula validation is available in the bundled runtime**: Use `formula_check.py` to scan formulas and cached error values. Do not ask the user to install external office software.') .replace(/Recalculating formulas/g, 'Static formula validation') .replace(/Recalculate formulas/g, 'Validate formulas') .replace(/Recalculates all formulas/g, 'Checks formulas') .replace(/recalculate formulas/g, 'validate formulas') .replace(/recalculation/g, 'static validation') .replace(/recalculate again/g, 'validate again') .replace(/Use `formula_check\.py` for dynamic recalculation when available\./g, 'Use `formula_check.py` for static validation.') .replace(/Dynamic Validation \(desktop office engine Headless\)/g, 'Static Formula Validation') .replace(/Tier 2 dynamic validation/g, 'Optional dynamic validation') .replace(/Tier 2 — Dynamic Validation/g, 'Optional Dynamic Validation') .replace(/Run `formula_check\.py` for static validation\. Use `formula_check\.py` for dynamic recalculation when available\./g, 'Run `formula_check.py` for static formula validation. Do not run desktop-engine recalculation in the bundled runtime.'); } if (slug === 'docx') { next = next .replace(/install_soffice/g, 'skip_optional_doc_converter') .replace(/soffice_ver/g, 'converter_ver') .replace(/soffice_found/g, 'converter_found') .replace(/\s*"\$SCRIPT_DIR\/doc_to_docx\.sh"\n/g, '') .replace(/^\s*echo " desktop office converter:[^\n]*\n/gm, ' echo " legacy .doc conversion: not bundled (use .docx)"\n') .replace( /# --- Optional: desktop office engine ---[\s\S]*?(?=\n# --- Optional: zip\/unzip ---)/, `# --- Legacy DOC Conversion --- printf "[WARN] %-14s not bundled — use .docx files\\n" "legacy .doc conversion" WARNINGS=$((WARNINGS + 1)) `, ); } if (next !== raw) writeFileSync(filePath, next, 'utf8'); } } function applyNoExternalOfficePrompts(targetDir, slug) { if (slug === 'docx') { replaceFileText(join(targetDir, 'scripts', 'setup.sh'), [ [ /# --- LibreOffice Installation \(Optional\) ---[\s\S]*?\n# --- zip\/unzip ---/, `# --- Legacy DOC Conversion (Optional) --- skip_optional_doc_converter() { step "Checking legacy .doc conversion support" warn "Legacy .doc conversion is not bundled; use .docx files in Zhinian runtime" } # --- zip/unzip ---`, ], [/echo " --minimal Only install critical dependencies \(skip pandoc, soffice, fonts\)"/g, 'echo " --minimal Only install critical dependencies (skip optional tools)"'], ]); replaceFileText(join(targetDir, 'scripts', 'setup.ps1'), [ [ /# --- LibreOffice \(Optional\) ---[\s\S]*?\n# --- NuGet Configuration ---/, `# --- Legacy DOC Conversion (Optional) --- if (-not $Minimal) { Step "Checking legacy .doc conversion support" Warn "Legacy .doc conversion is not bundled; use .docx files in Zhinian runtime" } # --- NuGet Configuration ---`, ], [/-Minimal Only install critical dependencies \(skip pandoc, soffice, fonts\)/g, '-Minimal Only install critical dependencies (skip optional tools)'], ]); replaceFileText(join(targetDir, 'scripts', 'env_check.sh'), [ [/echo " Install: brew install --cask libreoffice"/g, 'echo " Optional .doc conversion skipped in Zhinian runtime"'], [/echo " Install: sudo apt-get install libreoffice-core"/g, 'echo " Optional .doc conversion skipped in Zhinian runtime"'], [/echo " Install: winget install TheDocumentFoundation\.LibreOffice"/g, 'echo " Optional .doc conversion skipped in Zhinian runtime"'], ]); replaceFileText(join(targetDir, 'scripts', 'doc_to_docx.sh'), [ [/echo "Install LibreOffice: brew install --cask libreoffice"/g, 'echo "Optional .doc conversion unavailable in Zhinian runtime; please provide a .docx file."'], ]); } if (slug === 'xlsx') { replaceFileText(join(targetDir, 'scripts', 'xlsx_reader.py'), [ [/Run: pip install pandas openpyxl/g, 'Run: uv pip install pandas openpyxl'], ]); replaceFileText(join(targetDir, 'references', 'validate.md'), [ [ /### Install LibreOffice \(if permitted in the environment\)[\s\S]*?\n### Run headless recalculation/, `### Optional dynamic recalculation Do not ask the user to install LibreOffice. If LibreOffice is not already available, record "Tier 2: SKIPPED — LibreOffice not available" and continue with Tier 1 static validation. ### Run headless recalculation`, ], [ /\*\*If LibreOffice is not installed:\*\*[\s\S]*?\n\*\*If the script times out/, `**If LibreOffice is not installed:** Record "Tier 2: SKIPPED — LibreOffice not available" and continue. Do not provide installation commands to the user. **If the script times out`, ], ]); replaceFileText(join(targetDir, 'scripts', 'libreoffice_recalc.py'), [ [ /"LibreOffice not found\. Tier 2 validation is unavailable in this environment\. "\s*\n\s*"Install LibreOffice to enable dynamic formula recalculation\.\\n"\s*\n\s*" macOS: brew install --cask libreoffice\\n"\s*\n\s*" Linux: sudo apt-get install -y libreoffice"/, `"LibreOffice not found. Tier 2 validation is skipped in Zhinian runtime. "`, ], [/print\(" macOS: brew install --cask libreoffice"\)\n\s*print\(" Linux: sudo apt-get install -y libreoffice"\)/g, 'print("Tier 2 dynamic validation skipped; continue with static validation.")'], ]); } if (slug === 'pptx') { replaceFileText(join(targetDir, 'SKILL.md'), [ [/`pip install "markitdown\[pptx\]"` — text extraction/g, '`uv pip install "markitdown[pptx]"` — text extraction when needed'], [/`npm install -g pptxgenjs` — creating from scratch/g, 'Use the project-bundled `pptxgenjs` module for creating from scratch'], [/`npm install -g react-icons react react-dom sharp` — icons \(optional\)/g, 'Use project-bundled `react-icons`, `react`, `react-dom`, and `sharp` when icons are needed'], ]); replaceFileText(join(targetDir, 'references', 'pptxgenjs.md'), [ [/Install: `npm install -g react-icons react react-dom sharp`/g, 'Use project-bundled `react-icons`, `react`, `react-dom`, and `sharp`; do not ask the user to install them globally.'], ]); } if (slug === 'pdf') { replaceFileText(join(targetDir, 'SKILL.md'), [ [/`pip install reportlab`/g, '`uv pip install reportlab`'], [/`pip install pypdf`/g, '`uv pip install pypdf`'], [/`npm install -g playwright && npx playwright install chromium`/g, 'use the project-bundled Playwright runtime when available'], ]); replaceFileText(join(targetDir, 'README.md'), [ [/`pip install reportlab`/g, '`uv pip install reportlab`'], [/`pip install pypdf`/g, '`uv pip install pypdf`'], [/`npm install -g playwright && npx playwright install chromium`/g, 'optional; use project-bundled Playwright runtime when available'], ]); replaceFileText(join(targetDir, 'scripts', 'make.sh'), [ [/pip install failed — try: pip install reportlab pypdf matplotlib/g, 'dependency install failed — try: uv pip install reportlab pypdf matplotlib'], [/python3 -m pip install/g, 'uv pip install'], [` # Playwright if node -e "require('playwright')" 2>/dev/null || \\ node -e "require(require('child_process').execSync('npm root -g').toString().trim()+'/playwright')" 2>/dev/null; then green " ✓ playwright" else yellow " ⚠ playwright not found (run: make.sh fix)" ok=false fi`, ` # Playwright is optional in Zhinian runtime. if node -e "require('playwright')" 2>/dev/null || \\ node -e "require('playwright-core')" 2>/dev/null; then green " ✓ playwright" else yellow " ⚠ playwright not found (optional cover rendering skipped)" fi`], [` # Playwright if command -v npm &>/dev/null; then npm install -g playwright --silent 2>/dev/null && \\ npx playwright install chromium --silent 2>/dev/null && \\ green " ✓ Playwright + Chromium installed" || \\ { yellow " playwright install failed — try manually"; rc=3; } else yellow " npm not found — cannot install Playwright automatically" rc=2 fi`, ` # Playwright is optional in Zhinian runtime. Do not install global npm packages. yellow " Playwright cover rendering is optional; skipping global install"`], ]); replaceFileText(join(targetDir, 'scripts', 'render_cover.js'), [ [ /\/\/ ── Playwright loader \(tolerates global npm installs\) ─────────────────────────[\s\S]*?\/\/ ── Main ───────────────────────────────────────────────────────────────────────/, `// ── Playwright loader (Zhinian runtime) ───────────────────────────────────── function loadPlaywright() { try { return require("playwright"); } catch (_) {} try { return require("playwright-core"); } catch (_) {} console.error(JSON.stringify({ status: "error", error: "playwright runtime not available", hint: "Playwright cover rendering is optional in Zhinian runtime. Continue without the HTML cover or contact an administrator." })); process.exit(2); } // ── Main ───────────────────────────────────────────────────────────────────────`, ], [ / let browser;\n try {\n browser = await chromium\.launch\(\);\n } catch \(e\) {[\s\S]*? browser = await chromium\.launch\(\);\n }\n/, ` let browser; try { browser = await chromium.launch(); } catch (e) { console.error(JSON.stringify({ status: "error", error: "Chromium is not available for Playwright cover rendering", hint: "Cover rendering is optional in Zhinian runtime. Continue without the HTML cover or contact an administrator." })); process.exit(2); } `, ], ]); } } async function extractArchive(archiveFileName, cwd) { const prevCwd = $.cwd; $.cwd = cwd; try { try { await $`tar -xf ${archiveFileName}`; return; } catch (tarError) { if (process.platform === 'win32') { // Some Windows images expose bsdtar instead of tar. await $`bsdtar -xf ${archiveFileName}`; return; } throw tarError; } } finally { $.cwd = prevCwd; } } async function fetchSparseRepo(repo, ref, paths, checkoutDir) { const remote = `https://github.com/${repo}.git`; mkdirSync(checkoutDir, { recursive: true }); const gitCheckoutDir = toGitPath(checkoutDir); const archiveFileName = '.subset.tar'; const archivePath = join(checkoutDir, archiveFileName); const archivePaths = [...new Set(paths.map(normalizeRepoPath))]; await $`git init ${gitCheckoutDir}`; await $`git -C ${gitCheckoutDir} remote add origin ${remote}`; await $`git -C ${gitCheckoutDir} fetch --depth 1 origin ${ref}`; // Do not checkout working tree on Windows: upstream repos may contain // Windows-invalid paths. Export only requested directories via git archive. await $`git -C ${gitCheckoutDir} archive --format=tar --output ${archiveFileName} FETCH_HEAD ${archivePaths}`; await extractArchive(archiveFileName, checkoutDir); rmSync(archivePath, { force: true }); const commit = (await $`git -C ${gitCheckoutDir} rev-parse FETCH_HEAD`).stdout.trim(); return commit; } echo`Bundling preinstalled skills...`; if (process.env.SKIP_PREINSTALLED_SKILLS === '1') { echo`⏭ SKIP_PREINSTALLED_SKILLS=1 set, skipping skills fetch.`; process.exit(0); } const manifestSkills = loadManifest(); rmSync(OUTPUT_ROOT, { recursive: true, force: true }); mkdirSync(OUTPUT_ROOT, { recursive: true }); rmSync(TMP_ROOT, { recursive: true, force: true }); mkdirSync(TMP_ROOT, { recursive: true }); const lock = { generatedAt: new Date().toISOString(), skills: [], }; const groups = groupByRepoRef(manifestSkills); for (const group of groups) { const repoDir = join(TMP_ROOT, createRepoDirName(group.repo, group.ref)); const sparsePaths = [...new Set(group.entries.map((entry) => entry.repoPath))]; echo`Fetching ${group.repo} @ ${group.ref}`; const commit = await fetchSparseRepo(group.repo, group.ref, sparsePaths, repoDir); echo` commit ${commit}`; for (const entry of group.entries) { const sourceDir = join(repoDir, entry.repoPath); const targetDir = join(OUTPUT_ROOT, entry.slug); if (!existsSync(sourceDir)) { throw new Error(`Missing source path in repo checkout: ${entry.repoPath}`); } rmSync(targetDir, { recursive: true, force: true }); cpSync(sourceDir, targetDir, { recursive: true, dereference: true, filter: shouldCopySkillFile }); const skillManifest = join(targetDir, 'SKILL.md'); if (!existsSync(skillManifest)) { throw new Error(`Skill ${entry.slug} is missing SKILL.md after copy`); } rewriteSkillName(skillManifest, entry.skillName || entry.slug); applyRuntimePolicy(skillManifest, entry.slug); applyNoExternalOfficePrompts(targetDir, entry.slug); sanitizeDesktopOfficeEngineTerms(targetDir, entry.slug); const requestedVersion = (entry.version || '').trim(); const resolvedVersion = !requestedVersion || requestedVersion === 'main' ? commit : requestedVersion; lock.skills.push({ slug: entry.slug, version: resolvedVersion, repo: entry.repo, repoPath: entry.repoPath, ref: group.ref, commit, }); echo` OK ${entry.slug}`; } } for (const entry of manifestSkills.filter((item) => item.localPath)) { const sourceDir = join(ROOT, entry.localPath); const targetDir = join(OUTPUT_ROOT, entry.slug); if (!existsSync(sourceDir)) { throw new Error(`Missing local skill source: ${entry.localPath}`); } if (!existsSync(join(sourceDir, 'SKILL.md'))) { throw new Error(`Local skill ${entry.slug} is missing SKILL.md: ${entry.localPath}`); } rmSync(targetDir, { recursive: true, force: true }); cpSync(sourceDir, targetDir, { recursive: true, dereference: true, filter: shouldCopySkillFile }); const skillManifest = join(targetDir, 'SKILL.md'); rewriteSkillName(skillManifest, entry.skillName || entry.slug); applyRuntimePolicy(skillManifest, entry.slug); applyNoExternalOfficePrompts(targetDir, entry.slug); sanitizeDesktopOfficeEngineTerms(targetDir, entry.slug); const resolvedVersion = (entry.version || 'local').trim() || 'local'; lock.skills.push({ slug: entry.slug, version: resolvedVersion, localPath: entry.localPath, ref: 'local', commit: resolvedVersion, }); echo` OK ${entry.slug} (local)`; } writeFileSync(join(OUTPUT_ROOT, '.preinstalled-lock.json'), `${JSON.stringify(lock, null, 2)}\n`, 'utf8'); assertNoMacAppLikeDirectories(OUTPUT_ROOT); rmSync(TMP_ROOT, { recursive: true, force: true }); echo`Preinstalled skills ready: ${OUTPUT_ROOT}`;