@taloswork/ose-shared 0.1.15 → 0.1.17
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/package.json +3 -3
- package/skills/document/SKILL.md +20 -15
- package/skills/document/assets/fonts/fonts.json +1 -1
- package/skills/document/bin/deck-build.cjs +2 -1
- package/skills/document/bin/deck-chart.cjs +2 -1
- package/skills/document/bin/deck-qa.cjs +58 -43
- package/skills/document/bin/doc-convert.cjs +53971 -14710
- package/skills/document/bin/doc-fill.cjs +7 -5
- package/skills/document/bin/doc-gen.cjs +66172 -27127
- package/skills/document/bin/doc-scan.cjs +13 -9
- package/skills/document/bin/doc-text.cjs +13 -9
- package/skills/document/bin/doc-validate.cjs +15 -12
- package/skills/document/bin/html2pptx.cjs +84 -40
- package/skills/document/bin/ocr.cjs +14 -4
- package/skills/document/bin/pdf-form.cjs +31 -14
- package/skills/document/bin/pdf-images.cjs +22 -4
- package/skills/document/bin/pdf-ops.cjs +31 -14
- package/skills/document/bin/pdf-render.cjs +21 -3
- package/skills/document/bin/pdf-tables.cjs +7 -1
- package/skills/document/bin/pptx-clone.cjs +9 -16
- package/skills/document/bin/pptx-slide.cjs +9 -16
- package/skills/document/bin/template-skin.cjs +7 -1
- package/skills/document/bin/xlsx-recalc.cjs +9 -2
- package/skills/document/bin-reference.md +5 -5
- package/skills/document/content-pipeline.md +18 -21
- package/skills/document/deck-design.md +2 -2
- package/skills/document/rewrite.md +21 -16
- package/skills/document/vision.md +1 -1
- package/skills/expert-create/LICENSE.txt +7 -0
- package/skills/expert-create/SKILL.md +106 -0
- package/skills/expert-create/bin/expert-create.cjs +96 -0
- package/skills/expert-create/references/role-guide.md +24 -0
- package/skills/image-layout/SKILL.md +3 -3
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
// apps/admin/data/skills.lib/document/bin-src/pdf-render.mjs
|
|
5
5
|
var import_promises = require("node:fs/promises");
|
|
6
|
-
var
|
|
6
|
+
var import_node_path3 = require("node:path");
|
|
7
7
|
|
|
8
8
|
// packages/core/dist/document/pdf-extract.js
|
|
9
9
|
var import_node_path = require("node:path");
|
|
@@ -33,9 +33,15 @@ function loadMupdf() {
|
|
|
33
33
|
}
|
|
34
34
|
return _mupdf;
|
|
35
35
|
}
|
|
36
|
+
function openPdf(mupdf, bytes) {
|
|
37
|
+
const doc = mupdf.Document.openDocument(bytes, "application/pdf").asPDF();
|
|
38
|
+
if (!doc)
|
|
39
|
+
throw new Error("not a PDF document");
|
|
40
|
+
return doc;
|
|
41
|
+
}
|
|
36
42
|
async function openDoc(bytes) {
|
|
37
43
|
const mupdf = await loadMupdf();
|
|
38
|
-
const doc = mupdf
|
|
44
|
+
const doc = openPdf(mupdf, bytes);
|
|
39
45
|
if (doc.needsPassword())
|
|
40
46
|
throw new PdfEncryptedError();
|
|
41
47
|
if (doc.countPages() === 0) {
|
|
@@ -61,6 +67,17 @@ async function renderPdfPages(bytes, pages, scale = 2, maxEdge) {
|
|
|
61
67
|
return out;
|
|
62
68
|
}
|
|
63
69
|
|
|
70
|
+
// apps/admin/data/skills.lib/document/bin-src/lib/derived-path.mjs
|
|
71
|
+
var import_node_path2 = require("node:path");
|
|
72
|
+
var SCRATCH_ENV = "TALOS_SCRATCH_DIR";
|
|
73
|
+
function derivedDir(source, env = process.env) {
|
|
74
|
+
const scratch = env[SCRATCH_ENV];
|
|
75
|
+
return scratch ? (0, import_node_path2.join)(scratch, "derived") : (0, import_node_path2.dirname)((0, import_node_path2.resolve)(source));
|
|
76
|
+
}
|
|
77
|
+
function derivedPath(source, name, env = process.env) {
|
|
78
|
+
return (0, import_node_path2.join)(derivedDir(source, env), name);
|
|
79
|
+
}
|
|
80
|
+
|
|
64
81
|
// apps/admin/data/skills.lib/document/bin-src/pdf-render.mjs
|
|
65
82
|
function die(msg, code = 1) {
|
|
66
83
|
process.stderr.write(`pdf-render: ${msg}
|
|
@@ -104,8 +121,9 @@ async function main() {
|
|
|
104
121
|
}
|
|
105
122
|
if (!rendered.length) die("no pages rendered (range out of bounds?)", 64);
|
|
106
123
|
const out = [];
|
|
124
|
+
await (0, import_promises.mkdir)(derivedDir(file), { recursive: true });
|
|
107
125
|
for (const r of rendered) {
|
|
108
|
-
const dest = (
|
|
126
|
+
const dest = derivedPath(file, `${(0, import_node_path3.basename)(file).replace(/\.[^.]+$/, "")}_p${r.page}.png`);
|
|
109
127
|
await (0, import_promises.writeFile)(dest, Buffer.from(r.png));
|
|
110
128
|
out.push({ page: r.page, path: dest });
|
|
111
129
|
}
|
|
@@ -33,9 +33,15 @@ function loadMupdf() {
|
|
|
33
33
|
}
|
|
34
34
|
return _mupdf;
|
|
35
35
|
}
|
|
36
|
+
function openPdf(mupdf, bytes) {
|
|
37
|
+
const doc = mupdf.Document.openDocument(bytes, "application/pdf").asPDF();
|
|
38
|
+
if (!doc)
|
|
39
|
+
throw new Error("not a PDF document");
|
|
40
|
+
return doc;
|
|
41
|
+
}
|
|
36
42
|
async function openDoc(bytes) {
|
|
37
43
|
const mupdf = await loadMupdf();
|
|
38
|
-
const doc = mupdf
|
|
44
|
+
const doc = openPdf(mupdf, bytes);
|
|
39
45
|
if (doc.needsPassword())
|
|
40
46
|
throw new PdfEncryptedError();
|
|
41
47
|
if (doc.countPages() === 0) {
|
|
@@ -10000,10 +10000,7 @@ function capacityWarning(slideXml, token, value) {
|
|
|
10000
10000
|
return void 0;
|
|
10001
10001
|
return `${token}: replacement is ~${Math.ceil(width)} width-units, slot fits ~${Math.floor(capacity)} (${Math.round(widthPt)}\xD7${Math.round(heightPt)}pt box @ ${fontPt}pt) \u2014 likely overflow. Shorten the wording; do NOT shrink the font to make it fit.`;
|
|
10002
10002
|
}
|
|
10003
|
-
async function
|
|
10004
|
-
if (!Number.isInteger(slideNumber) || slideNumber < 1) {
|
|
10005
|
-
throw new Error(`slide number must be a positive integer, got ${slideNumber}`);
|
|
10006
|
-
}
|
|
10003
|
+
async function openDeckAtSlide(bytes, slideNumber) {
|
|
10007
10004
|
let zip;
|
|
10008
10005
|
try {
|
|
10009
10006
|
zip = await import_jszip.default.loadAsync(Buffer.from(bytes));
|
|
@@ -10016,6 +10013,13 @@ async function clonePptxSlide(bytes, slideNumber, replacements = {}) {
|
|
|
10016
10013
|
if (!slideNums.includes(slideNumber)) {
|
|
10017
10014
|
throw new Error(`slide ${slideNumber} does not exist (deck has slides ${Math.min(...slideNums)}\u2013${Math.max(...slideNums)})`);
|
|
10018
10015
|
}
|
|
10016
|
+
return { zip, slideNums };
|
|
10017
|
+
}
|
|
10018
|
+
async function clonePptxSlide(bytes, slideNumber, replacements = {}) {
|
|
10019
|
+
if (!Number.isInteger(slideNumber) || slideNumber < 1) {
|
|
10020
|
+
throw new Error(`slide number must be a positive integer, got ${slideNumber}`);
|
|
10021
|
+
}
|
|
10022
|
+
const { zip, slideNums } = await openDeckAtSlide(bytes, slideNumber);
|
|
10019
10023
|
const newNum = Math.max(...slideNums) + 1;
|
|
10020
10024
|
const srcXmlPath = `ppt/slides/slide${slideNumber}.xml`;
|
|
10021
10025
|
const newXmlPath = `ppt/slides/slide${newNum}.xml`;
|
|
@@ -10055,18 +10059,7 @@ async function removePptxSlide(bytes, slideNumber) {
|
|
|
10055
10059
|
if (!Number.isInteger(slideNumber) || slideNumber < 1) {
|
|
10056
10060
|
throw new Error(`slide number must be a positive integer, got ${slideNumber}`);
|
|
10057
10061
|
}
|
|
10058
|
-
|
|
10059
|
-
try {
|
|
10060
|
-
zip = await import_jszip.default.loadAsync(Buffer.from(bytes));
|
|
10061
|
-
} catch (e) {
|
|
10062
|
-
throw new Error(`not a readable .pptx: ${e.message}`);
|
|
10063
|
-
}
|
|
10064
|
-
const slideNums = Object.keys(zip.files).map((p) => /^ppt\/slides\/slide(\d+)\.xml$/.exec(p)?.[1]).filter((n) => Boolean(n)).map(Number);
|
|
10065
|
-
if (!slideNums.length)
|
|
10066
|
-
throw new Error("not a PowerPoint file (no slides found)");
|
|
10067
|
-
if (!slideNums.includes(slideNumber)) {
|
|
10068
|
-
throw new Error(`slide ${slideNumber} does not exist (deck has slides ${Math.min(...slideNums)}\u2013${Math.max(...slideNums)})`);
|
|
10069
|
-
}
|
|
10062
|
+
const { zip, slideNums } = await openDeckAtSlide(bytes, slideNumber);
|
|
10070
10063
|
if (slideNums.length === 1)
|
|
10071
10064
|
throw new Error("cannot remove the only slide in the deck");
|
|
10072
10065
|
const xmlPath = `ppt/slides/slide${slideNumber}.xml`;
|
|
@@ -10084,10 +10084,7 @@ function capacityWarning(slideXml, token, value) {
|
|
|
10084
10084
|
return void 0;
|
|
10085
10085
|
return `${token}: replacement is ~${Math.ceil(width)} width-units, slot fits ~${Math.floor(capacity)} (${Math.round(widthPt)}\xD7${Math.round(heightPt)}pt box @ ${fontPt}pt) \u2014 likely overflow. Shorten the wording; do NOT shrink the font to make it fit.`;
|
|
10086
10086
|
}
|
|
10087
|
-
async function
|
|
10088
|
-
if (!Number.isInteger(slideNumber) || slideNumber < 1) {
|
|
10089
|
-
throw new Error(`slide number must be a positive integer, got ${slideNumber}`);
|
|
10090
|
-
}
|
|
10087
|
+
async function openDeckAtSlide(bytes, slideNumber) {
|
|
10091
10088
|
let zip;
|
|
10092
10089
|
try {
|
|
10093
10090
|
zip = await import_jszip2.default.loadAsync(Buffer.from(bytes));
|
|
@@ -10100,6 +10097,13 @@ async function clonePptxSlide(bytes, slideNumber, replacements = {}) {
|
|
|
10100
10097
|
if (!slideNums.includes(slideNumber)) {
|
|
10101
10098
|
throw new Error(`slide ${slideNumber} does not exist (deck has slides ${Math.min(...slideNums)}\u2013${Math.max(...slideNums)})`);
|
|
10102
10099
|
}
|
|
10100
|
+
return { zip, slideNums };
|
|
10101
|
+
}
|
|
10102
|
+
async function clonePptxSlide(bytes, slideNumber, replacements = {}) {
|
|
10103
|
+
if (!Number.isInteger(slideNumber) || slideNumber < 1) {
|
|
10104
|
+
throw new Error(`slide number must be a positive integer, got ${slideNumber}`);
|
|
10105
|
+
}
|
|
10106
|
+
const { zip, slideNums } = await openDeckAtSlide(bytes, slideNumber);
|
|
10103
10107
|
const newNum = Math.max(...slideNums) + 1;
|
|
10104
10108
|
const srcXmlPath = `ppt/slides/slide${slideNumber}.xml`;
|
|
10105
10109
|
const newXmlPath = `ppt/slides/slide${newNum}.xml`;
|
|
@@ -10139,18 +10143,7 @@ async function removePptxSlide(bytes, slideNumber) {
|
|
|
10139
10143
|
if (!Number.isInteger(slideNumber) || slideNumber < 1) {
|
|
10140
10144
|
throw new Error(`slide number must be a positive integer, got ${slideNumber}`);
|
|
10141
10145
|
}
|
|
10142
|
-
|
|
10143
|
-
try {
|
|
10144
|
-
zip = await import_jszip2.default.loadAsync(Buffer.from(bytes));
|
|
10145
|
-
} catch (e) {
|
|
10146
|
-
throw new Error(`not a readable .pptx: ${e.message}`);
|
|
10147
|
-
}
|
|
10148
|
-
const slideNums = Object.keys(zip.files).map((p) => /^ppt\/slides\/slide(\d+)\.xml$/.exec(p)?.[1]).filter((n) => Boolean(n)).map(Number);
|
|
10149
|
-
if (!slideNums.length)
|
|
10150
|
-
throw new Error("not a PowerPoint file (no slides found)");
|
|
10151
|
-
if (!slideNums.includes(slideNumber)) {
|
|
10152
|
-
throw new Error(`slide ${slideNumber} does not exist (deck has slides ${Math.min(...slideNums)}\u2013${Math.max(...slideNums)})`);
|
|
10153
|
-
}
|
|
10146
|
+
const { zip, slideNums } = await openDeckAtSlide(bytes, slideNumber);
|
|
10154
10147
|
if (slideNums.length === 1)
|
|
10155
10148
|
throw new Error("cannot remove the only slide in the deck");
|
|
10156
10149
|
const xmlPath = `ppt/slides/slide${slideNumber}.xml`;
|
|
@@ -10642,9 +10642,15 @@ function loadMupdf() {
|
|
|
10642
10642
|
}
|
|
10643
10643
|
return _mupdf;
|
|
10644
10644
|
}
|
|
10645
|
+
function openPdf(mupdf, bytes) {
|
|
10646
|
+
const doc = mupdf.Document.openDocument(bytes, "application/pdf").asPDF();
|
|
10647
|
+
if (!doc)
|
|
10648
|
+
throw new Error("not a PDF document");
|
|
10649
|
+
return doc;
|
|
10650
|
+
}
|
|
10645
10651
|
async function openDoc(bytes) {
|
|
10646
10652
|
const mupdf = await loadMupdf();
|
|
10647
|
-
const doc = mupdf
|
|
10653
|
+
const doc = openPdf(mupdf, bytes);
|
|
10648
10654
|
if (doc.needsPassword())
|
|
10649
10655
|
throw new PdfEncryptedError();
|
|
10650
10656
|
if (doc.countPages() === 0) {
|
|
@@ -111707,6 +111707,13 @@ for (const pluginName of Object.getOwnPropertyNames(plugin_exports)) {
|
|
|
111707
111707
|
}
|
|
111708
111708
|
|
|
111709
111709
|
// packages/core/dist/document/xlsx-recalc.js
|
|
111710
|
+
var EXCEL_ERROR_CODES = ["#N/A", "#REF!", "#NAME?", "#DIV/0!", "#NULL!", "#VALUE!", "#NUM!"];
|
|
111711
|
+
function isExcelErrorCode(code) {
|
|
111712
|
+
return EXCEL_ERROR_CODES.includes(code);
|
|
111713
|
+
}
|
|
111714
|
+
function excelErrorValue(code) {
|
|
111715
|
+
return { error: isExcelErrorCode(code) ? code : "#VALUE!" };
|
|
111716
|
+
}
|
|
111710
111717
|
function toPrimitive(v) {
|
|
111711
111718
|
if (v == null)
|
|
111712
111719
|
return { value: null };
|
|
@@ -111720,7 +111727,7 @@ function toPrimitive(v) {
|
|
|
111720
111727
|
async function recalcXlsx(bytes) {
|
|
111721
111728
|
const wb = new import_exceljs.default.Workbook();
|
|
111722
111729
|
try {
|
|
111723
|
-
await wb.xlsx.load(
|
|
111730
|
+
await wb.xlsx.load(new Uint8Array(bytes).buffer);
|
|
111724
111731
|
} catch (e) {
|
|
111725
111732
|
throw new Error(`not a readable xlsx: ${e.message}`);
|
|
111726
111733
|
}
|
|
@@ -111755,7 +111762,7 @@ async function recalcXlsx(bytes) {
|
|
|
111755
111762
|
const raw = hf.getCellValue({ sheet: sheetId, row: fc.row - 1, col: fc.col - 1 });
|
|
111756
111763
|
const { value, error } = toPrimitive(raw);
|
|
111757
111764
|
const cell = wb.getWorksheet(fc.sheet).getCell(fc.row, fc.col);
|
|
111758
|
-
cell.value = { formula: fc.formula, result: error ?
|
|
111765
|
+
cell.value = { formula: fc.formula, result: error ? excelErrorValue(error) : value ?? void 0 };
|
|
111759
111766
|
if (error)
|
|
111760
111767
|
errors.push({ sheet: fc.sheet, cell: cell.address, error });
|
|
111761
111768
|
else if (sample.length < 20)
|
|
@@ -15,15 +15,15 @@ unreadable file · `69` a required engine is unavailable · `75` transient, retr
|
|
|
15
15
|
|
|
16
16
|
- `bin/doc-scan.cjs <file.pdf>` — per-page signals: `{file, pages:[{page, textChars, imageCoverage, mathHint, tableHint, scanned, …}]}`. PDF only. Every PDF reader here (doc-scan, doc-text, pdf-render, pdf-images, pdf-tables) exits 1 on a password-protected PDF — see pdf.md.
|
|
17
17
|
- `bin/doc-text.cjs <file.pdf>` — the PDF's text layer, `[page N]`-delimited. A .docx/.pptx/.xlsx exits 64 (read the sidecar / use xlsx-query).
|
|
18
|
-
- `bin/pdf-render.cjs <file.pdf> <pages|all> [scale]` — render pages to PNG for vision, written
|
|
19
|
-
- `bin/pdf-images.cjs <file.pdf> [pages]` — EMBEDDED images at native resolution, written
|
|
18
|
+
- `bin/pdf-render.cjs <file.pdf> <pages|all> [scale]` — render pages to PNG for vision, written as `<stem>_p<N>.png` to the scratch dir (prints `{rendered:[{page, path}]}`); `pages` = `3` | `2-5` | `3,7-8` | `all`. `scale` 0.5–4 (default `2` ≈ 144dpi) trades resolution for vision tokens: `1` (≈72dpi, ¼ the tokens) for layout QA; keep `2` when fine print must stay legible (formulas/tables).
|
|
19
|
+
- `bin/pdf-images.cjs <file.pdf> [pages]` — EMBEDDED images at native resolution, written as `<stem>_p<page>_i<index>.png` to the scratch dir, plus a manifest `{images:[{page, index, bbox, width, height, path}], skipped}` (`pages` = `N` | `N-M` | `all`, default all). Use this — not a full-page `pdf-render` — when you need the original figures as their own files (e.g. rebuilding a deck). Vector/text-only pages yield nothing → `pdf-render`.
|
|
20
20
|
- `bin/pdf-tables.cjs <file.pdf> [page] [minCols]` — text-layer tables as `{tables:[{page, rows}]}` (`minCols` default 2; rows may carry empty spacer cells). Nothing found, or a ruled/scanned table → render + vision.
|
|
21
21
|
- `bin/ocr.cjs <image> [lang]` — text off a png/jpg/webp/bmp image → `{text, confidence}`; for a scanned PDF `pdf-render` the page first. `lang` is a tesseract code, default `eng`; `chi_sim` = simplified Chinese, `chi_tra` traditional, `+` combines (`chi_sim+eng` for mixed text). `eng` and `chi_sim` ship with the skill and work offline; others download once into `$TALOS_TESSDATA` (no network → exit 1 `OCR failed: …`); `$TALOS_TESSDATA_LANGPATH` names a dir holding every `<lang>.traineddata.gz`. **Prefer vision** when a vision model is available; OCR is the text-only fallback.
|
|
22
22
|
- `bin/xlsx-query.cjs <file> [--summary | --agg <sheet> <col> <op> | --sample <sheet> [n]] [--header-row N] [--include-totals]` — exact spreadsheet compute; no mode = `--summary` (sheet names, row/column counts, `headerRow`, headers). `<op>` = `sum` | `avg` | `min` | `max` | `count` | `count-nonempty` (anything else exits 64 listing these); `<col>` is a header label or a 1-based index. The header row is detected (a title row is skipped; `--header-row N` overrides). `--agg` excludes total rows (合计/总计/小计/Total/Subtotal/Sum, or a SUM over the column) and reports them in `excludedRows` (`--include-totals` keeps them), and lists non-numeric cells it skipped in `skippedCells`. `15%` text is 0.15; `¥`/`元` amounts parse. An unknown sheet exits 1 naming the sheets; a column it cannot find returns `value: null, colIdx: -1` — run `--summary` for the exact names. `--sample` prints up to `n` (default 20) rows keyed by header. Caveats: xlsx.md.
|
|
23
23
|
|
|
24
24
|
## Generate
|
|
25
25
|
|
|
26
|
-
- `bin/doc-gen.cjs <docx|xlsx|pdf> <out> <spec.json> [--theme <theme.json|name>] [--topic <subject> | --palette <id>] [--accent <#rrggbb> | --style <id>] [--no-preview]` — generate documents from scratch; spec shapes in rewrite.md "Generate". DOCX/PDF body fields are exactly `paragraphs` and/or `blocks` (never `sections`); in a PDF, any http(s) URL written in body text, list items or table cells becomes a clickable link and wraps inside the page on its own — never split a URL by hand (there is no other link syntax). Unknown fields and empty bodies exit 65 with `DOC_SPEC_INVALID` before any output is written (`allowCoverOnly:true` is the explicit title-only opt-in): correct the reported JSON path and rerun. Prints `wrote <out>` (a PDF adds its engine) then one JSON line — the self-check in SKILL.md "After writing a file"; PDF previews land
|
|
26
|
+
- `bin/doc-gen.cjs <docx|xlsx|pdf> <out> <spec.json> [--theme <theme.json|name>] [--topic <subject> | --palette <id>] [--accent <#rrggbb> | --style <id>] [--no-preview]` — generate documents from scratch; spec shapes in rewrite.md "Generate". DOCX/PDF body fields are exactly `paragraphs` and/or `blocks` (never `sections`); in a PDF, any http(s) URL written in body text, list items or table cells becomes a clickable link and wraps inside the page on its own — never split a URL by hand (there is no other link syntax). Unknown fields and empty bodies exit 65 with `DOC_SPEC_INVALID` before any output is written (`allowCoverOnly:true` is the explicit title-only opt-in): correct the reported JSON path and rerun. Prints `wrote <out>` (a PDF adds its engine) then one JSON line — the self-check in SKILL.md "After writing a file"; PDF previews land in the scratch dir as `<stem>_p<N>.png` (`--no-preview` skips them). A PDF prints in Chromium — the app's service, else a headless Chromium; with neither it exits 69. `doc-gen pptx` exits 64: decks are built with html2pptx. **Look flags** (docx/pdf): `--theme` = a theme file (built-ins `<skill>/themes/<id>.json`) or the name of one the user saved in `$TALOS_DOC_STYLES_DIR/themes`; `--topic <subject>` picks a topic-adaptive palette, `--palette <id>` forces one (`doc-gen --list-palettes`); `--accent <#rrggbb>` / `--style <id>` keep white paper and near-black text with ONE brand colour on H1/H2 + a faint table-header tint — `--style` takes it from a catalog skin or pack (the deck picker's ids; the deck's full palette is NOT carried onto paper); an accent that is not 6-digit hex falls back to black-on-white with a warning. Precedence: `--theme` > spec `"theme"` > `--accent`/`--style` > `--palette`/`--topic` > default.
|
|
27
27
|
- `bin/doc-run.cjs <script.js> [args…]` — run a custom Node script with the engine's libraries bundled in (`require("pptxgenjs"|"docx"|"xlsx"|"exceljs"|"pdf-lib"|"jszip"|"pizzip"|"hyperformula")` guaranteed on any machine; `--list` prints the registry; nothing else is installed, and an unbundled `require` fails naming the bundled alternative). For a **formatted workbook** (fills, font colors, borders, merged cells) use `exceljs` — `xlsx` (SheetJS community) writes no cell styles at all and drops them without an error; `doc-gen xlsx` covers only a bold header, column widths, number formats and formulas. The long-tail escape hatch when no verb fits — see rewrite.md "Custom scripts"; ALWAYS `doc-validate --strict` the output.
|
|
28
28
|
- `bin/xlsx-recalc.cjs <in.xlsx> <out.xlsx>` — re-evaluate every formula (HyperFormula) and cache the results, keeping the formulas (`out` may equal `in`). Prints `{ok, out, recomputed, errors:[{sheet, cell, error}], sample}`; it exits 0 even with formula errors, so `errors` must be read — rewrite.md "Formulas".
|
|
29
29
|
|
|
@@ -47,7 +47,7 @@ unreadable file · `69` a required engine is unavailable · `75` transient, retr
|
|
|
47
47
|
|
|
48
48
|
Flags and outputs only; the workflow is deck-design.md.
|
|
49
49
|
|
|
50
|
-
- `bin/deck-scenario.cjs "<scenario>"` — the ONE matrix row the
|
|
50
|
+
- `bin/deck-scenario.cjs "<scenario>"` — the ONE matrix row the brief picked: `{ok, scenario:{id, tone, density, pages, narrative, flows, sections:[{label, key, content}], researchFocus}}`. `content` = what that section must contain (elements, evidence; `;宜` a recommended shape) — it is in no md table. Matches the id, else the longest alias in the text; no match exits 1 with the ids (map to the nearest and rerun).
|
|
51
51
|
- `bin/deck-styles.cjs --scenario "<scenario>"` — the style picker's first round: `{scenario, source, options}`; pass `options` to the picker as printed (SKILL.md "STYLE PICKER"). `source` `mixed`/`fallback` = the scenario claimed under six styles and the default group topped it up.
|
|
52
52
|
- `bin/deck-plan.cjs --layouts --style <id>` — before plan.json: the layouts this style renders and the plan fields each takes (`stats×N` = exactly N, `unit?` optional, `slots.<name>` = pass it, `source ✓` = footer).
|
|
53
53
|
- `bin/deck-plan.cjs <plan.json> [spec.json]` — **content-first layout selection, run BEFORE deck-build.** The plan is the deck spec's top-level fields (`style`, `themeVersion`, `layoutPack`, `scenario`, `density` (read · talk; default from scenario), `footer`, `chartRender`, …) plus per slide its CONTENT: `role` (cover · toc · section · content · ending; default content), optional `intent` (layouts/profiles.json) / `shape` / `layout` (pin), and `title` · `subtitle` (口径 line) · `takeaway` (启示 line) · `eyebrow` · `meta` · `index` · `points` (`[{title, body, date?, label?}]` or strings) · `stats` (`[{value, unit, label, note}]`) · `chart` · `quote` (`{text, source}` or a string) · `table` (`{columns, rows}`) · `image: true` · `source` (one short source line, "IDC,2026" → the footer; dropped with a warning on a layout without one) · `notes` (speaker notes) · `narrative` (outline only) · `file` (a bespoke slide: one `<section class="slide">` in that file, taken as is, `pick:"bespoke"`) · `slots` / `rows` (explicit values, override the draft). A `\n` in any text is a line break. `--outline <slide-outline.md>` also writes the readable outline (one block per slide + its pick) — the approval gate's document, rendered from the plan, never hand-written. Prints `{ok, slides:[{slide, pick, warnings, blanks, alternatives, rejected:[{layout, why}]}], next}`. Fit = the layout's item count (a 3-card layout takes exactly 3 points; rows layouts take 1..their measured ceiling, at least the count the README density column names) + every drafted string within the layout's capacity measured under your `style` (`layouts/capacity.json`; an unknown style uses the tightest skin). Picks under P1/P2/P3 (layouts/README.md). With `spec.json` it writes the drafted deck-build spec (chart slides get `chartRender:"svg"`) — only when EVERY slide got a pick; otherwise exit 1, nothing written: fix the content the `why` names and re-plan. `blanks` = slots the content had nothing for (left empty, not demo text) — fill them or accept. A layout rejected for leaving slots empty names where each one is filled from (`kpi1_desc ← each stat's note`).
|
|
@@ -60,4 +60,4 @@ Flags and outputs only; the workflow is deck-design.md.
|
|
|
60
60
|
## Validate and convert
|
|
61
61
|
|
|
62
62
|
- `bin/doc-validate.cjs <file.docx|pptx|xlsx|pdf> [--strict] [--design-lint]` — structural validation gate: OOXML zip/parts/rels/Content_Types/XML well-formedness, plus pptx `notes-keynote` (→ `pptx-notes <in> {} <out>`) and docx `docx-default-style` (→ `docx-defaults`); a PDF must parse, open without a password (`pdf-encrypted` error: validate before `pdf-ops encrypt`) and hold at least one page (reports `pages` and a `pdf-parse-only` warning — how it looks is render-and-look's job). Exit 1 = broken, fix before delivering. `--strict` (OOXML only; on a PDF it just adds a `strict-not-applicable` warning) adds full ECMA-376 schema (XSD) validation of every recognised part — REQUIRED after custom-code (doc-run) or raw-XML output; a strict failure means the file is non-standard even if Office opens it. `--design-lint` (pptx only) adds deck-qa's lint as advisory warnings (never changes the exit code; codes under deck-qa in Decks) — real design bugs, fix them.
|
|
63
|
-
- `bin/doc-convert.cjs <in.docx|pptx|xlsx> <out.pdf> [--cache-dir <dir>] [--require-office]` — Office → PDF. **PPTX requires Office**: local `soffice`, then `TALOS_OFFICE_URL` sidecar. Missing Office exits 69; failed Office exits 75. Never rebuild a deck as browser HTML. Spreadsheets also require Office. DOCX can fall back to reconstructed HTML via the app's Chromium or headless Chromium (content retained, layout lost); `--require-office` blocks this fallback. Report the `[pdf (engine)]` result and any caveat. `--cache-dir` (or `TALOS_OFFICE_CACHE_DIR`) reuses the local Office result for identical source bytes; after any PPTX edit, convert again. Fonts come from the Office environment, which must have the required faces for faithful typography.
|
|
63
|
+
- `bin/doc-convert.cjs <in.docx|pptx|xlsx> <out.pdf> [--cache-dir <dir>] [--require-office]` — Office → PDF. **PPTX requires Office**: local `soffice`, then Keynote (Mac) / PowerPoint (Win) for .pptx, then `TALOS_OFFICE_URL` sidecar. Missing Office exits 69; failed Office exits 75. Never rebuild a deck as browser HTML. Spreadsheets also require Office. DOCX can fall back to reconstructed HTML via the app's Chromium or headless Chromium (content retained, layout lost); `--require-office` blocks this fallback. Report the `[pdf (engine)]` result and any caveat. `--cache-dir` (or `TALOS_OFFICE_CACHE_DIR`) reuses the local Office result for identical source bytes; after any PPTX edit, convert again. Fonts come from the Office environment, which must have the required faces for faithful typography.
|
|
@@ -29,33 +29,29 @@ reorganize THEIR content, never invent facts.
|
|
|
29
29
|
|
|
30
30
|
---
|
|
31
31
|
|
|
32
|
-
## Phase 0 —
|
|
33
|
-
|
|
34
|
-
The
|
|
35
|
-
|
|
36
|
-
call
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
1. **Content source** — 提供材料 / 联网调研 / 自由创作 (offline / no web tool → say so;
|
|
43
|
-
only the first and last). Provided → request the materials now; when you ask for a FILE, set
|
|
44
|
-
`filePick: "file"` on that question (a native file button; the picked path is the
|
|
32
|
+
## Phase 0 — Brief & style
|
|
33
|
+
|
|
34
|
+
The brief and the style picker are defined once, in SKILL.md: source, scenario and
|
|
35
|
+
slide count are inferred from the request, never asked on their own; the style picker
|
|
36
|
+
is the one call before the outline. What each value means:
|
|
37
|
+
|
|
38
|
+
1. **Content source** — 提供材料 / 联网调研 / 自由创作 (no web tool → not 联网调研;
|
|
39
|
+
say so). 提供材料 without the material in hand → ask for it in the picker call,
|
|
40
|
+
with `filePick: "file"` on that question (a native file button; the picked path is the
|
|
45
41
|
answer). **Material given as CONTENT is for its TEXT/DATA only** — extract and
|
|
46
42
|
re-author it in the chosen style; never reuse its look. A `.pptx` handed in as
|
|
47
43
|
content → read its text (`.txt` sidecar or `bin/doc-text`); do **NOT**
|
|
48
44
|
`pptx-tokenize`/`pptx-theme` it. Reusing a deck's look happens only when the user
|
|
49
45
|
picks it as their **style** ("复用这份 PPT 的风格"), never because the content
|
|
50
46
|
arrived as a `.pptx`.
|
|
51
|
-
2. **Scenario** —
|
|
52
|
-
matrix
|
|
47
|
+
2. **Scenario** — inferred; when it cannot be, the picker call's second question offers
|
|
48
|
+
the 3–4 matrix rows that best fit the topic (the user may type another); an unlisted
|
|
53
49
|
answer maps to the nearest row. Record it as a short phrase. It locks **five** things:
|
|
54
50
|
tone · density (feeds Phase 4) · **required roles** (the default checklist for Phase 2) ·
|
|
55
51
|
narrative shape (Phase 2) · page range (the slide-count default). Then run
|
|
56
52
|
`bin/deck-scenario.cjs "<scenario>"` once: it prints that row with each section's
|
|
57
53
|
`content` — what the section must contain (elements, evidence, a recommended shape).
|
|
58
|
-
3. **Slide count** —
|
|
54
|
+
3. **Slide count** — as stated, else the row's **默认页数**; clamp 3–30, double-check outside.
|
|
59
55
|
|
|
60
56
|
**Scenario matrix** — source of truth `scenarios.json`; the tables below are generated
|
|
61
57
|
from it. The required roles (必备章节) are the scenario's **default**: every one appears
|
|
@@ -104,9 +100,8 @@ Tone also biases the catalog style ranking: 张力/情绪(pitch·发布会·大
|
|
|
104
100
|
**Persist `deck-brief.md`**: Topic · Scenario · Source · Style (entry id + 2–3 sentences
|
|
105
101
|
on palette/type/motif) · Template style · Slide Count · Overrides (if any) · (assumptions) —
|
|
106
102
|
≤10 lines of DECISIONS. Research findings, numbers, chart data and citations do not go
|
|
107
|
-
here: they are written once, in `plan.json`.
|
|
108
|
-
|
|
109
|
-
material assumption that changes scope, show only that assumption and pause.
|
|
103
|
+
here: they are written once, in `plan.json`. Persisting it is not an approval round
|
|
104
|
+
(Execution discipline 1).
|
|
110
105
|
|
|
111
106
|
---
|
|
112
107
|
|
|
@@ -199,7 +194,8 @@ node <skill>/bin/deck-plan.cjs --layouts --style <style id>
|
|
|
199
194
|
One line per layout this style renders, with the plan fields it takes
|
|
200
195
|
(bin-reference.md). Write each slide's content to the layout you mean it for.
|
|
201
196
|
|
|
202
|
-
Write the outline ONCE, as `plan.json`
|
|
197
|
+
Write the outline ONCE, as `plan.json` in the task's scratch folder with every other
|
|
198
|
+
working file (SKILL.md "Where files go") — `deck-plan`'s input and the single source of
|
|
203
199
|
truth for Phase 4–5 (do not re-decide count or roles later). Per slide: `role` · `title`
|
|
204
200
|
(the takeaway, stated as a CONCLUSION) · `shape` / `intent` · what goes ON the canvas,
|
|
205
201
|
(`subtitle` · `points` · `stats` · `quote` · `table` · `chart` · `image` · `takeaway`) ·
|
|
@@ -234,7 +230,8 @@ Count them within the total; mark purpose and image/text mode in `narrative`, re
|
|
|
234
230
|
Then **GATE**, in this order — never ask before the outline exists:
|
|
235
231
|
1. `plan.json` written and `slide-outline.md` rendered by the command above;
|
|
236
232
|
2. `read` `slide-outline.md` — the outline shows in the timeline;
|
|
237
|
-
3. AskUserQuestion, ONE sentence ("是否按这份 12 页大纲制作?
|
|
233
|
+
3. AskUserQuestion, ONE sentence naming the inferred brief ("是否按这份 12 页大纲制作?
|
|
234
|
+
(培训 · 自由创作)"), `confirmation` with
|
|
238
235
|
`options: ["按此制作", "调整大纲"]` — never the outline in the question.
|
|
239
236
|
|
|
240
237
|
Advance only on approval. A revision edits `plan.json` and re-runs the same command — the outline and the
|
|
@@ -183,7 +183,7 @@ Spend the boldness in ONE place (layer 5).
|
|
|
183
183
|
## Motion — presets, entrance animation, transitions
|
|
184
184
|
|
|
185
185
|
**Every catalog entry ships a motion preset (`motion/<id>.json`); the scenario decides
|
|
186
|
-
whether `deck-build` applies it.** Put the
|
|
186
|
+
whether `deck-build` applies it.** Put the brief's scenario in the spec as
|
|
187
187
|
`"scenario"`: a **presented** deck (pitch / 路演 / 发布会 / keynote / 大会 / 讲座 / 销售)
|
|
188
188
|
gets the preset — `data-anim*` / `data-transition*` injected per slide by role; a
|
|
189
189
|
**read** deck (内审 / 评审 / 报告 / 汇报 / 讲义 / 培训 / 述职) gets none; no or an unknown
|
|
@@ -374,7 +374,7 @@ Do not launch LibreOffice after every edit.
|
|
|
374
374
|
chart, which only Office draws) and `previews` (≈72dpi images of those).
|
|
375
375
|
**Read only `previews`.** For hybrid PPTX QA, other slides already passed the HTML
|
|
376
376
|
preview; their visuals come from that render. PDF delivery needs the final check below.
|
|
377
|
-
- **Read every slide instead** (`pdf-render.cjs <deck
|
|
377
|
+
- **Read every slide instead** (`pdf-render.cjs <deck-qa's office.pdf> all 1`) when
|
|
378
378
|
html2pptx reported a `layout (fallback)` engine, or you changed the PPTX after
|
|
379
379
|
building it (`doc-xml`, `pptx-slide`, a script).
|
|
380
380
|
- `office.unavailable`: PPTX has no Office layout check; disclose this. A running
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
**Read only the sections your task needs** (Read with the offset/limit of the line range):
|
|
4
4
|
|
|
5
|
-
- New .docx / .pdf / .xlsx → **Generate** (lines 14–
|
|
6
|
-
- Fill a `{{placeholder}}` .docx / .pptx / .xlsx template → **Fill a template** (lines
|
|
7
|
-
- Change an existing .docx — text, structure, comments, tracked changes → **Edit an existing .docx** (lines
|
|
8
|
-
- New content in an existing deck's look, or restructure a .pptx → **Template decks** (lines
|
|
9
|
-
- Nothing above fits → **Custom scripts** (lines
|
|
5
|
+
- New .docx / .pdf / .xlsx → **Generate** (lines 14–59); a formula in a workbook → also **Formulas** (lines 61–79); a particular look, theme or brand → also **Themes** (lines 81–131)
|
|
6
|
+
- Fill a `{{placeholder}}` .docx / .pptx / .xlsx template → **Fill a template** (lines 133–152)
|
|
7
|
+
- Change an existing .docx — text, structure, comments, tracked changes → **Edit an existing .docx** (lines 154–205)
|
|
8
|
+
- New content in an existing deck's look, or restructure a .pptx → **Template decks** (lines 207–287)
|
|
9
|
+
- Nothing above fits → **Custom scripts** (lines 289–313)
|
|
10
10
|
|
|
11
11
|
A PDF's forms, page ops and stamps are in pdf.md; a deck from scratch is the deck route
|
|
12
12
|
(SKILL.md) — `doc-gen` makes no decks. `<skill>` below is this skill's directory.
|
|
@@ -31,9 +31,13 @@ than delivering the previous output.
|
|
|
31
31
|
(DOCX widths in twips; default: full page width, equal columns)
|
|
32
32
|
- `{ "type": "image", "data": "<base64>", "imageType"?: "png", "width"?, "height"? }` (px, default 300×200)
|
|
33
33
|
- **xlsx**: `{ "sheets": { "Name": [["h1","h2"], ["a", 1]] } }` (row 1 = bold header), or per
|
|
34
|
-
sheet `{ "rows": [...], "header"?, "columnWidths"?: [n…], "numFmt"?: ["#,##0.00"…]
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
sheet `{ "rows": [...], "header"?, "columnWidths"?: [n…], "numFmt"?: ["#,##0.00"…],
|
|
35
|
+
"merges"?: ["A1:D1"], "freeze"?: { "rows": 1 }, "styles"?: [{ "range": "A1:D1", "bold"?,
|
|
36
|
+
"italic"?, "fontSize"?, "color"?: "#1F4E79", "fill"?: "#DDEBF7", "border"?: "thin"|"medium",
|
|
37
|
+
"align"?: "left"|"center"|"right", "wrap"?, "numFmt"? }] }` — styles apply in order. A cell
|
|
38
|
+
string starting with `=` is a **live formula**; `"2026-10-21"` is written as a date and
|
|
39
|
+
keeps a date format. Each sheet prints one page wide (landscape when wide). A quote,
|
|
40
|
+
invoice, tracker or report sheet is a spec, not a script.
|
|
37
41
|
- The spec may also carry `"topic"`, `"palette"` or `"theme"` (same as the flags).
|
|
38
42
|
|
|
39
43
|
**A length limit** (正文 1000 字以内 / under 800 words) is kept in the spec, before
|
|
@@ -51,18 +55,20 @@ page size and margin. Inside Talos it uses the app's service (`[pdf (chromium)]`
|
|
|
51
55
|
elsewhere a headless Chromium (`[pdf (headless chromium)]`); with neither it exits 69 and
|
|
52
56
|
writes nothing — tell the user (a .docx can be offered; never swap formats silently).
|
|
53
57
|
|
|
54
|
-
Anything past these shapes — an exotic layout, a
|
|
58
|
+
Anything past these shapes — an exotic layout, a chart or image in a workbook — is a doc-run script
|
|
55
59
|
(**Custom scripts**), not an edit of the bundled bin.
|
|
56
60
|
|
|
57
61
|
## Formulas — the xlsx repair loop (mandatory)
|
|
58
62
|
|
|
59
63
|
Write live formulas (`"=SUM(A2:A9)"`), never precomputed values — the file must
|
|
60
|
-
recalculate when the user edits data. doc-gen and
|
|
61
|
-
|
|
64
|
+
recalculate when the user edits data. `doc-gen xlsx` evaluates and caches them as it
|
|
65
|
+
writes: its JSON line reports `recomputed` and `formulaErrors`, and exits 1 when that
|
|
66
|
+
is not empty. A workbook from an exceljs script or an edit has no cached results:
|
|
62
67
|
|
|
63
68
|
1. `node <skill>/bin/xlsx-recalc.cjs out.xlsx out.xlsx` — evaluates every formula and
|
|
64
69
|
caches the results.
|
|
65
|
-
2. Its `errors` array must be **empty** —
|
|
70
|
+
2. Its `errors` array (doc-gen: `formulaErrors`) must be **empty** — xlsx-recalc exits 0
|
|
71
|
+
either way, so read it. A
|
|
66
72
|
`#DIV/0!`, `#REF!` or `#N/A` → fix the formula (guard divisors with IF, correct the
|
|
67
73
|
range) and recalc again. **Never deliver a spreadsheet with formula errors**, and never
|
|
68
74
|
"fix" one by replacing the formula with a hardcoded value.
|
|
@@ -282,8 +288,7 @@ TEXT goes through `doc-fill` (templated) or `doc-xml` (raw).
|
|
|
282
288
|
|
|
283
289
|
## Custom scripts — `bin/doc-run` (the long-tail escape hatch)
|
|
284
290
|
|
|
285
|
-
When no verb or spec block expresses it (an exotic layout, a bulk transformation, a
|
|
286
|
-
styled workbook), write a CommonJS Node script and run it with `doc-run` — the engine's
|
|
291
|
+
When no verb or spec block expresses it (an exotic layout, a bulk transformation), write a CommonJS Node script and run it with `doc-run` — the engine's
|
|
287
292
|
pure-JS libraries are bundled inside it, so it works on any machine:
|
|
288
293
|
|
|
289
294
|
```
|
|
@@ -291,8 +296,8 @@ node <skill>/bin/doc-run.cjs myscript.js [args…]
|
|
|
291
296
|
node <skill>/bin/doc-run.cjs --list # the guaranteed module registry
|
|
292
297
|
```
|
|
293
298
|
|
|
294
|
-
`require()` of these is guaranteed: `docx` (Word) · `exceljs` (
|
|
295
|
-
|
|
299
|
+
`require()` of these is guaranteed: `docx` (Word) · `exceljs` (what a doc-gen xlsx spec
|
|
300
|
+
cannot say: charts, images, conditional formats — then **Formulas**) · `xlsx` (SheetJS: reading
|
|
296
301
|
and plain-data workbooks only — it writes no cell styles) · `pdf-lib` (PDF assembly) ·
|
|
297
302
|
`jszip` / `pizzip` (OOXML zip surgery) · `hyperformula` (formula evaluation) ·
|
|
298
303
|
`pptxgenjs` (non-deck pptx only — every deck goes through html2pptx). Nothing else: no
|
|
@@ -7,7 +7,7 @@ loses.
|
|
|
7
7
|
## Steps
|
|
8
8
|
|
|
9
9
|
1. `bin/pdf-render.cjs file.pdf <pages> [scale]` — `<pages>` is `4`, `2-6`, `3,7-8`
|
|
10
|
-
or `all`; PNGs
|
|
10
|
+
or `all`; PNGs (`<stem>_p<N>.png`) land in the scratch directory and their paths are printed.
|
|
11
11
|
Keep the default scale `2` for formulas and fine print; `1` is enough for layout.
|
|
12
12
|
2. Look at each PNG. For:
|
|
13
13
|
- **Formulas** → transcribe to LaTeX from the image.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
© 2026 Nexora AI Limited. All rights reserved.
|
|
2
|
+
|
|
3
|
+
LICENSE: This Skill (including all prompts, reference documents, and other
|
|
4
|
+
components) is proprietary to Nexora AI Limited and provided solely for use
|
|
5
|
+
within the Talos application under your agreement with Nexora AI Limited.
|
|
6
|
+
Redistribution, extraction, or use outside Talos is not permitted without
|
|
7
|
+
prior written consent.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: expert-create
|
|
3
|
+
description: "Use when the user wants to create, edit or delete their own expert (an OSE: a named assistant persona with its own role prompt, shown in the expert switcher). Triggers: create an expert / make a new expert / build an assistant for X / edit my expert / change the expert's role / delete my expert, 新建专家 / 创建一个专家 / 做一个XX专家 / 修改专家 / 改一下专家的角色 / 删除专家. Not for installing skills (install_skill) or for switching experts."
|
|
4
|
+
license: Proprietary. LICENSE.txt has complete terms
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Create, edit and delete local experts
|
|
8
|
+
|
|
9
|
+
An expert is a persona the user switches to: a name, a one-line description,
|
|
10
|
+
an optional English label, up to four starter prompts, and a role prompt
|
|
11
|
+
(`role.md`) that becomes the opening of every conversation with it. Experts the
|
|
12
|
+
user makes here are **local**: only on this machine, editable and deletable.
|
|
13
|
+
Published experts cannot be changed here.
|
|
14
|
+
|
|
15
|
+
Everything goes through one command, where `<skill>` is this skill's
|
|
16
|
+
directory. The Talos app validates, writes and refreshes the expert list; never
|
|
17
|
+
create or edit expert files yourself, and never state where they live.
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
node <skill>/bin/expert-create.cjs list
|
|
21
|
+
node <skill>/bin/expert-create.cjs show <id>
|
|
22
|
+
node <skill>/bin/expert-create.cjs create <draft.json>
|
|
23
|
+
node <skill>/bin/expert-create.cjs update <id> <patch.json>
|
|
24
|
+
node <skill>/bin/expert-create.cjs delete <id>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`create`, `update` and `delete` each wait for the user to confirm in a Talos
|
|
28
|
+
dialog that shows what will change; agreeing in the chat does not replace it.
|
|
29
|
+
The dialog stays open for up to 5 minutes (300 seconds), so run these three
|
|
30
|
+
without a `timeout`, or with one of at least 330 seconds. A shorter timeout
|
|
31
|
+
kills the command while the user is still reading the dialog: the dialog
|
|
32
|
+
closes, nothing is written, and running it again opens a second one.
|
|
33
|
+
|
|
34
|
+
Exit codes:
|
|
35
|
+
|
|
36
|
+
- 0 done (JSON on stdout).
|
|
37
|
+
- 1 the app refused or failed (read stderr; if it names a field, fix it and
|
|
38
|
+
retry).
|
|
39
|
+
- 3 the user said no in the dialog; nothing was written.
|
|
40
|
+
- 4 the dialog went unanswered and closed after 5 minutes; nothing was written.
|
|
41
|
+
- 5 `update` would change nothing: the patch matches the expert as it is. No
|
|
42
|
+
dialog was shown and nothing was written; tell the user it already reads
|
|
43
|
+
that way.
|
|
44
|
+
- 64 usage; 66/65 the JSON file could not be read or parsed.
|
|
45
|
+
- 69 the Talos app is not reachable or not ready — not running inside Talos,
|
|
46
|
+
this is a scheduled task, a sub-agent or a channel session (which cannot
|
|
47
|
+
change experts), or Talos was starting or switched profile. Nothing was
|
|
48
|
+
written. Do not retry; tell the user, and do not work around it.
|
|
49
|
+
|
|
50
|
+
## Draft JSON
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"name": "Sales Coach",
|
|
55
|
+
"desc": "Coaches sales calls",
|
|
56
|
+
"role": "…the full role.md…",
|
|
57
|
+
"en": { "name": "Sales Coach", "desc": "Coaches sales calls" },
|
|
58
|
+
"demos": [{ "label": "Review a call", "output": "PDF", "icon": "search", "seed": "…" }]
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Limits the app enforces: `name`, `desc`, `en.name`, `en.desc` and each demo
|
|
63
|
+
`label` are one line (no line breaks); `name` ≤ 24 characters; `desc` ≤ 20
|
|
64
|
+
characters when it contains Chinese/Japanese/Korean text or full-width
|
|
65
|
+
punctuation, otherwise ≤ 38; `en.name` ≤ 40, `en.desc` ≤ 38; `role` up to 32 KiB; at most 4 `demos`, each with a
|
|
66
|
+
`label` and a `seed` (≤ 420 characters, a prompt that stands alone), `icon` one
|
|
67
|
+
of search, file, image, clock, code, chart, table, bug. A patch for `update`
|
|
68
|
+
holds only the fields that change.
|
|
69
|
+
|
|
70
|
+
## Creating
|
|
71
|
+
|
|
72
|
+
1. Ask, in one message, what the expert is for: who uses it, the work it does,
|
|
73
|
+
the output it should produce, and anything it must or must not do. Do not
|
|
74
|
+
ask one question at a time.
|
|
75
|
+
2. Draft every field. Write `role` following `references/role-guide.md`.
|
|
76
|
+
Write the name, description and demos in the language the user speaks; add
|
|
77
|
+
`en` when that language is not English.
|
|
78
|
+
3. Show the user the name, description, demos and the full role, and revise
|
|
79
|
+
until they confirm. Do not create before they confirm.
|
|
80
|
+
4. Write the draft to a JSON file in the working directory and run `create`;
|
|
81
|
+
Talos asks the user to confirm. Exit 3 means they said no — ask what to
|
|
82
|
+
change. On success, report the expert's name. Offer to switch to it — switching is the user's
|
|
83
|
+
action in the expert switcher.
|
|
84
|
+
5. The new expert starts with the shared skills only. If the work needs more,
|
|
85
|
+
say which skills would help; installing them is a separate step.
|
|
86
|
+
|
|
87
|
+
## Editing
|
|
88
|
+
|
|
89
|
+
Run `show <id>` (get the id from `list`), change only what the user asked for,
|
|
90
|
+
show the before and after of each changed field, and after confirmation run
|
|
91
|
+
`update` with a patch of just those fields. Talos shows the user the change in
|
|
92
|
+
its own dialog and keeps the version it replaces; exit 3 means they said no.
|
|
93
|
+
An edit to the expert in use takes effect from the next message.
|
|
94
|
+
|
|
95
|
+
## Deleting
|
|
96
|
+
|
|
97
|
+
Say what goes: the expert itself. Its conversations stay on this machine but will no longer appear in Talos. Deleting the expert currently in use
|
|
98
|
+
switches the user back to the default expert once this reply ends. Deleting
|
|
99
|
+
the expert this conversation is with ends the conversation: finish your reply,
|
|
100
|
+
and tell the user to continue with another expert, because Talos refuses any
|
|
101
|
+
further message here.
|
|
102
|
+
Then run `delete <id>`; Talos asks the user to confirm in its own dialog. Exit 3
|
|
103
|
+
means they said no — leave it there.
|
|
104
|
+
|
|
105
|
+
For any of the three, exit 4 means the dialog went unanswered and closed after
|
|
106
|
+
5 minutes; nothing was written — ask the user before trying again.
|