Files
pftom a98096a042 Add initial project structure with essential files
- Created .gitignore to exclude build artifacts and dependencies.
- Added index.html as the main entry point for the application.
- Included LICENSE file with Apache 2.0 terms.
- Initialized package.json and package-lock.json for project dependencies.
- Added pnpm-lock.yaml for package management.
- Created QUICKSTART.md for setup instructions.
- Added README.md and README.zh-CN.md for project documentation in English and Chinese.
2026-04-28 12:25:59 +08:00

137 lines
3.7 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Minimal YAML front-matter parser. Handles the subset used by SKILL.md in
// our examples: scalar strings/numbers/booleans, block-literal (|) strings,
// and flat arrays ("- foo"). Keeps the daemon dep-free. If you need real
// YAML (nested objects, flow-style, anchors), swap for `yaml` or `js-yaml`.
export function parseFrontmatter(src) {
const text = src.replace(/^/, '');
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/.exec(text);
if (!match) return { data: {}, body: text };
const [, yaml, body] = match;
return { data: parseYamlSubset(yaml), body };
}
function parseYamlSubset(src) {
const lines = src.split(/\r?\n/);
const root = {};
const stack = [{ indent: -1, container: root, key: null }];
let i = 0;
while (i < lines.length) {
const raw = lines[i];
if (/^\s*(#.*)?$/.test(raw)) {
i++;
continue;
}
const indent = raw.match(/^\s*/)[0].length;
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
stack.pop();
}
const top = stack[stack.length - 1];
const line = raw.slice(indent);
// Array item
if (line.startsWith('- ')) {
const value = line.slice(2).trim();
let container = top.container;
if (!Array.isArray(container)) {
// Convert the pending key's value to an array on first `-`.
const parent = stack[stack.length - 2];
if (parent && top.key) {
parent.container[top.key] = [];
container = parent.container[top.key];
top.container = container;
} else {
i++;
continue;
}
}
if (value.includes(':')) {
const obj = {};
const colonIdx = value.indexOf(':');
const key = value.slice(0, colonIdx).trim();
const valRaw = value.slice(colonIdx + 1).trim();
if (valRaw) obj[key] = coerce(valRaw);
container.push(obj);
stack.push({ indent, container: obj, key: null });
} else {
container.push(coerce(value));
}
i++;
continue;
}
// key: value or key: |
const kv = /^([^:]+):\s*(.*)$/.exec(line);
if (!kv) {
i++;
continue;
}
const key = kv[1].trim();
const val = kv[2];
if (val === '' || val === undefined) {
top.container[key] = {};
stack.push({ indent, container: top.container[key], key });
i++;
continue;
}
if (val === '|' || val === '|-' || val === '>' || val === '>-') {
const collected = [];
const childIndent = indent + 2;
i++;
while (i < lines.length) {
const next = lines[i];
if (/^\s*$/.test(next)) {
collected.push('');
i++;
continue;
}
const nIndent = next.match(/^\s*/)[0].length;
if (nIndent < childIndent) break;
collected.push(next.slice(childIndent));
i++;
}
top.container[key] = collected.join('\n').trimEnd();
continue;
}
if (val === '[]') {
top.container[key] = [];
i++;
continue;
}
if (val.startsWith('[') && val.endsWith(']')) {
top.container[key] = val
.slice(1, -1)
.split(',')
.map((s) => coerce(s.trim()))
.filter((v) => v !== '');
i++;
continue;
}
top.container[key] = coerce(val);
i++;
}
return root;
}
function coerce(raw) {
if (raw === undefined) return '';
let v = raw.trim();
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
return v.slice(1, -1);
}
if (v === 'true') return true;
if (v === 'false') return false;
if (v === 'null' || v === '~') return null;
if (/^-?\d+$/.test(v)) return Number(v);
if (/^-?\d*\.\d+$/.test(v)) return Number(v);
return v;
}