Skip to content
300 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Every agent starts with g1t's foundational skills, playbooks for documents, research, data, code, communication, and files and media, each using only tools the agent already has and saying plainly what isn't possible yet: agents make PDFs, Word documents, spreadsheets, CSV and Markdown files with make_file, kept with a doc in Artifacts and served from g1tusercontent.com; an agent's Skills tab shows what each skill can do and with which tools, and owners turn a skill off per agent; the agent skills guide says how.1/**
2 * Word documents and spreadsheets for `make_file` (files.ts), as Office
3 * Open XML: a .docx from Markdown and an .xlsx from rows, each a zip of a
4 * few XML parts. Pure and small: the zip stores its parts uncompressed,
5 * which every reader takes.
6 */
7import { type Block, type Span, parseBlocks } from "./file-markdown.ts";
8
9// ── Zip ───────────────────────────────────────────────────────────────────
10
11const CRC_TABLE = (() => {
12 const table = new Uint32Array(256);
13 for (let i = 0; i < 256; i++) {
14 let c = i;
15 for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
16 table[i] = c >>> 0;
17 }
18 return table;
19})();
20
21export function crc32(bytes: Uint8Array): number {
22 let crc = 0xffffffff;
23 for (const byte of bytes) crc = CRC_TABLE[(crc ^ byte) & 0xff]! ^ (crc >>> 8);
24 return (crc ^ 0xffffffff) >>> 0;
25}
26
27/** A zip of `files` (path to bytes or text), stored without compression. */
28export function zip(files: [string, Uint8Array | string][], at = new Date()): Uint8Array {
29 const encoder = new TextEncoder();
30 const time = (at.getUTCHours() << 11) | (at.getUTCMinutes() << 5) | Math.floor(at.getUTCSeconds() / 2);
31 const date = ((Math.max(1980, at.getUTCFullYear()) - 1980) << 9) | ((at.getUTCMonth() + 1) << 5) | at.getUTCDate();
32 const locals: Uint8Array[] = [];
33 const centrals: Uint8Array[] = [];
34 let offset = 0;
35 for (const [path, content] of files) {
36 const name = encoder.encode(path);
37 const data = typeof content === "string" ? encoder.encode(content) : content;
38 const crc = crc32(data);
39 const local = new Uint8Array(30 + name.length + data.length);
40 const lv = new DataView(local.buffer);
41 lv.setUint32(0, 0x04034b50, true);
42 lv.setUint16(4, 20, true);
43 lv.setUint16(6, 0x0800, true);
44 lv.setUint16(8, 0, true);
45 lv.setUint16(10, time, true);
46 lv.setUint16(12, date, true);
47 lv.setUint32(14, crc, true);
48 lv.setUint32(18, data.length, true);
49 lv.setUint32(22, data.length, true);
50 lv.setUint16(26, name.length, true);
51 lv.setUint16(28, 0, true);
52 local.set(name, 30);
53 local.set(data, 30 + name.length);
54 const central = new Uint8Array(46 + name.length);
55 const cv = new DataView(central.buffer);
56 cv.setUint32(0, 0x02014b50, true);
57 cv.setUint16(4, 20, true);
58 cv.setUint16(6, 20, true);
59 cv.setUint16(8, 0x0800, true);
60 cv.setUint16(10, 0, true);
61 cv.setUint16(12, time, true);
62 cv.setUint16(14, date, true);
63 cv.setUint32(16, crc, true);
64 cv.setUint32(20, data.length, true);
65 cv.setUint32(24, data.length, true);
66 cv.setUint16(28, name.length, true);
67 cv.setUint32(42, offset, true);
68 central.set(name, 46);
69 locals.push(local);
70 centrals.push(central);
71 offset += local.length;
72 }
73 const centralSize = centrals.reduce((sum, c) => sum + c.length, 0);
74 const end = new Uint8Array(22);
75 const ev = new DataView(end.buffer);
76 ev.setUint32(0, 0x06054b50, true);
77 ev.setUint16(8, files.length, true);
78 ev.setUint16(10, files.length, true);
79 ev.setUint32(12, centralSize, true);
80 ev.setUint32(16, offset, true);
81 const out = new Uint8Array(offset + centralSize + end.length);
82 let at2 = 0;
83 for (const part of [...locals, ...centrals, end]) {
84 out.set(part, at2);
85 at2 += part.length;
86 }
87 return out;
88}
89
90/** Text for XML: escaped, without the control characters XML forbids. */
91export function xml(text: string): string {
92 return String(text)
93 // eslint-disable-next-line no-control-regex
94 .replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f￾￿]/g, "")
95 .replace(/&/g, "&amp;")
96 .replace(/</g, "&lt;")
97 .replace(/>/g, "&gt;")
98 .replace(/"/g, "&quot;");
99}
100
101const XML_HEAD = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n';
102
103function coreProps(title: string, at: Date): string {
104 const when = at.toISOString().replace(/\.\d{3}Z$/, "Z");
105 return `${XML_HEAD}<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><dc:title>${xml(title)}</dc:title><dc:creator>g1t</dc:creator><dcterms:created xsi:type="dcterms:W3CDTF">${when}</dcterms:created><dcterms:modified xsi:type="dcterms:W3CDTF">${when}</dcterms:modified></cp:coreProperties>`;
106}
107
108// ── Word ──────────────────────────────────────────────────────────────────
109
110const W = 'xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"';
111
112const DOCX_STYLES = `${XML_HEAD}<w:styles ${W}>
113<w:docDefaults><w:rPrDefault><w:rPr><w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:eastAsia="Calibri" w:cs="Calibri"/><w:sz w:val="22"/><w:szCs w:val="22"/><w:lang w:val="en-US"/></w:rPr></w:rPrDefault><w:pPrDefault><w:pPr><w:spacing w:after="140" w:line="276" w:lineRule="auto"/></w:pPr></w:pPrDefault></w:docDefaults>
114<w:style w:type="paragraph" w:default="1" w:styleId="Normal"><w:name w:val="Normal"/><w:qFormat/></w:style>
115<w:style w:type="paragraph" w:styleId="Heading1"><w:name w:val="heading 1"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:qFormat/><w:pPr><w:keepNext/><w:spacing w:before="360" w:after="160"/><w:outlineLvl w:val="0"/></w:pPr><w:rPr><w:b/><w:sz w:val="36"/><w:szCs w:val="36"/></w:rPr></w:style>
116<w:style w:type="paragraph" w:styleId="Heading2"><w:name w:val="heading 2"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:qFormat/><w:pPr><w:keepNext/><w:spacing w:before="280" w:after="120"/><w:outlineLvl w:val="1"/></w:pPr><w:rPr><w:b/><w:sz w:val="28"/><w:szCs w:val="28"/></w:rPr></w:style>
117<w:style w:type="paragraph" w:styleId="Heading3"><w:name w:val="heading 3"/><w:basedOn w:val="Normal"/><w:next w:val="Normal"/><w:qFormat/><w:pPr><w:keepNext/><w:spacing w:before="200" w:after="80"/><w:outlineLvl w:val="2"/></w:pPr><w:rPr><w:b/><w:sz w:val="24"/><w:szCs w:val="24"/></w:rPr></w:style>
118<w:style w:type="paragraph" w:styleId="Quote"><w:name w:val="Quote"/><w:basedOn w:val="Normal"/><w:qFormat/><w:pPr><w:pBdr><w:left w:val="single" w:sz="18" w:space="8" w:color="BFBFBF"/></w:pBdr><w:ind w:left="240"/></w:pPr><w:rPr><w:color w:val="595959"/></w:rPr></w:style>
119<w:style w:type="paragraph" w:styleId="Code"><w:name w:val="Code"/><w:basedOn w:val="Normal"/><w:pPr><w:shd w:val="clear" w:color="auto" w:fill="F2F2F2"/><w:spacing w:after="140" w:line="240" w:lineRule="auto"/></w:pPr><w:rPr><w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/><w:sz w:val="19"/><w:szCs w:val="19"/></w:rPr></w:style>
120<w:style w:type="paragraph" w:styleId="ListParagraph"><w:name w:val="List Paragraph"/><w:basedOn w:val="Normal"/><w:qFormat/><w:pPr><w:spacing w:after="60"/></w:pPr></w:style>
121<w:style w:type="character" w:styleId="Hyperlink"><w:name w:val="Hyperlink"/><w:rPr><w:color w:val="2F5FD0"/><w:u w:val="single"/></w:rPr></w:style>
122<w:style w:type="table" w:styleId="Table"><w:name w:val="Table"/><w:tblPr><w:tblBorders><w:top w:val="single" w:sz="4" w:color="BFBFBF"/><w:left w:val="single" w:sz="4" w:color="BFBFBF"/><w:bottom w:val="single" w:sz="4" w:color="BFBFBF"/><w:right w:val="single" w:sz="4" w:color="BFBFBF"/><w:insideH w:val="single" w:sz="4" w:color="BFBFBF"/><w:insideV w:val="single" w:sz="4" w:color="BFBFBF"/></w:tblBorders><w:tblCellMar><w:left w:w="100" w:type="dxa"/><w:right w:w="100" w:type="dxa"/></w:tblCellMar></w:tblPr></w:style>
123</w:styles>`;
124
125function runs(spans: Span[], links: string[], base: { bold?: boolean } = {}): string {
126 return spans
127 .map((span) => {
128 const props = [
129 span.href ? '<w:rStyle w:val="Hyperlink"/>' : "",
130 span.code ? '<w:rFonts w:ascii="Consolas" w:hAnsi="Consolas" w:cs="Consolas"/>' : "",
131 span.bold || base.bold ? "<w:b/>" : "",
132 span.italic ? "<w:i/>" : "",
133 span.code ? '<w:shd w:val="clear" w:color="auto" w:fill="F2F2F2"/>' : "",
134 ].join("");
135 const run = `<w:r>${props ? `<w:rPr>${props}</w:rPr>` : ""}<w:t xml:space="preserve">${xml(span.text)}</w:t></w:r>`;
136 if (!span.href || !/^(https?:|mailto:)/i.test(span.href)) return run;
137 links.push(span.href);
138 return `<w:hyperlink r:id="rIdLink${links.length}">${run}</w:hyperlink>`;
139 })
140 .join("");
141}
142
143function paragraph(style: string | null, body: string, extra = ""): string {
144 const props = `${style ? `<w:pStyle w:val="${style}"/>` : ""}${extra}`;
145 return `<w:p>${props ? `<w:pPr>${props}</w:pPr>` : ""}${body}</w:p>`;
146}
147
148function wordBlock(block: Block, links: string[]): string {
149 switch (block.kind) {
150 case "heading":
151 return paragraph(`Heading${block.level}`, runs(block.spans, links));
152 case "paragraph":
153 return paragraph(null, runs(block.spans, links));
154 case "item": {
155 const left = 360 + block.depth * 360;
156 const marker = block.ordered ? `${block.number}.` : block.depth % 2 ? "–" : "•";
157 return paragraph("ListParagraph", `<w:r><w:t xml:space="preserve">${xml(marker)}</w:t></w:r><w:r><w:tab/></w:r>${runs(block.spans, links)}`, `<w:tabs><w:tab w:val="left" w:pos="${left}"/></w:tabs><w:ind w:left="${left}" w:hanging="300"/>`);
158 }
159 case "quote":
160 return paragraph("Quote", runs(block.spans, links));
161 case "code":
162 return paragraph(
163 "Code",
164 block.text
165 .split("\n")
166 .map((line, i) => `${i ? "<w:r><w:br/></w:r>" : ""}<w:r><w:t xml:space="preserve">${xml(line)}</w:t></w:r>`)
167 .join(""),
168 );
169 case "rule":
170 return paragraph(null, "", '<w:pBdr><w:bottom w:val="single" w:sz="6" w:space="1" w:color="BFBFBF"/></w:pBdr>');
171 case "table": {
172 const columns = Math.max(block.header.length, ...block.rows.map((row) => row.length), 1);
173 const row = (cells: Span[][], header: boolean) =>
174 `<w:tr>${header ? "<w:trPr><w:tblHeader/></w:trPr>" : ""}${Array.from({ length: columns }, (_, c) => `<w:tc><w:tcPr>${header ? '<w:shd w:val="clear" w:color="auto" w:fill="EFEFEF"/>' : ""}</w:tcPr>${paragraph(null, runs(cells[c] ?? [], links, { bold: header }), '<w:spacing w:after="0"/>')}</w:tc>`).join("")}</w:tr>`;
175 return `<w:tbl><w:tblPr><w:tblStyle w:val="Table"/><w:tblW w:w="5000" w:type="pct"/></w:tblPr><w:tblGrid>${Array.from({ length: columns }, () => `<w:gridCol w:w="${Math.floor(9720 / columns)}"/>`).join("")}</w:tblGrid>${block.header.length ? row(block.header, true) : ""}${block.rows.map((r) => row(r, false)).join("")}</w:tbl>${paragraph(null, "")}`;
176 }
177 }
178}
179
180/** A Word document of `markdown`, titled `title` (as its first heading when the Markdown starts without one). */
181export function markdownDocx(title: string, markdown: string, at = new Date()): Uint8Array {
182 let blocks = parseBlocks(markdown);
183 if (title.trim() && !(blocks[0]?.kind === "heading" && blocks[0].level === 1)) blocks = [{ kind: "heading", level: 1, spans: [{ text: title.trim() }] }, ...blocks];
184 const links: string[] = [];
185 const body = blocks.map((block) => wordBlock(block, links)).join("\n");
186 const document = `${XML_HEAD}<w:document ${W}><w:body>${body}<w:sectPr><w:pgSz w:w="12240" w:h="15840"/><w:pgMar w:top="1260" w:right="1260" w:bottom="1260" w:left="1260" w:header="708" w:footer="708" w:gutter="0"/></w:sectPr></w:body></w:document>`;
187 const rels = `${XML_HEAD}<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rIdStyles" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>${links
188 .map((href, i) => `<Relationship Id="rIdLink${i + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="${xml(href)}" TargetMode="External"/>`)
189 .join("")}</Relationships>`;
190 return zip(
191 [
192 [
193 "[Content_Types].xml",
194 `${XML_HEAD}<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/><Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/><Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/></Types>`,
195 ],
196 [
197 "_rels/.rels",
198 `${XML_HEAD}<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/></Relationships>`,
199 ],
200 ["docProps/core.xml", coreProps(title, at)],
201 ["word/document.xml", document],
202 ["word/_rels/document.xml.rels", rels],
203 ["word/styles.xml", DOCX_STYLES],
204 ],
205 at,
206 );
207}
208
209// ── Excel ─────────────────────────────────────────────────────────────────
210
211export type Cell = string | number | boolean | null;
212export type Sheet = { name: string; rows: Cell[][] };
213
214/** A column's letters: 0 is A, 26 is AA. */
215export function columnName(index: number): string {
216 let name = "";
217 for (let i = index + 1; i > 0; i = Math.floor((i - 1) / 26)) name = String.fromCharCode(65 + ((i - 1) % 26)) + name;
218 return name;
219}
220
221/** A cell's value as Excel keeps it: numbers (and numeric text) as numbers, the rest as text. */
222export function cellValue(value: Cell): { kind: "number"; value: number } | { kind: "text"; value: string } | null {
223 if (value === null || value === undefined || value === "") return null;
224 if (typeof value === "number") return Number.isFinite(value) ? { kind: "number", value } : { kind: "text", value: String(value) };
225 if (typeof value === "boolean") return { kind: "text", value: value ? "TRUE" : "FALSE" };
226 const text = String(value);
227 // Numeric text is a number, unless a leading zero says it's a code (a ZIP, an id).
228 if (/^-?(0|[1-9]\d{0,14})(\.\d+)?$/.test(text.trim())) return { kind: "number", value: Number(text.trim()) };
229 return { kind: "text", value: text };
230}
231
232/** A sheet name Excel takes: at most 31 characters, none of []:*?/\, unique in the workbook. */
233export function sheetNames(names: string[]): string[] {
234 const used = new Set<string>();
235 return names.map((raw, i) => {
236 const base = (String(raw ?? "").replace(/[[\]:*?/\\]/g, " ").replace(/\s+/g, " ").trim() || `Sheet${i + 1}`).replace(/^'|'$/g, "").slice(0, 31) || `Sheet${i + 1}`;
237 let name = base;
238 for (let k = 2; used.has(name.toLowerCase()); k++) name = `${base.slice(0, 31 - String(k).length - 1)} ${k}`;
239 used.add(name.toLowerCase());
240 return name;
241 });
242}
243
244function worksheet(sheet: Sheet): string {
245 const columns = Math.max(1, ...sheet.rows.map((row) => row.length));
246 const widths = Array.from({ length: columns }, (_, c) => Math.min(60, Math.max(8, ...sheet.rows.slice(0, 200).map((row) => String(row[c] ?? "").length + 2))));
247 const rows = sheet.rows
248 .map((row, r) => {
249 const cells = row
250 .map((raw, c) => {
251 const value = cellValue(raw);
252 if (!value) return "";
253 const ref = `${columnName(c)}${r + 1}`;
254 const style = r === 0 ? ' s="1"' : "";
255 return value.kind === "number" ? `<c r="${ref}"${style}><v>${value.value}</v></c>` : `<c r="${ref}"${style} t="inlineStr"><is><t xml:space="preserve">${xml(value.value)}</t></is></c>`;
256 })
257 .join("");
258 return `<row r="${r + 1}">${cells}</row>`;
259 })
260 .join("");
261 const frozen = sheet.rows.length > 1 ? '<pane ySplit="1" topLeftCell="A2" activePane="bottomLeft" state="frozen"/>' : "";
262 return `${XML_HEAD}<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetViews><sheetView workbookViewId="0">${frozen}</sheetView></sheetViews><cols>${widths.map((w, c) => `<col min="${c + 1}" max="${c + 1}" width="${w}" customWidth="1"/>`).join("")}</cols><sheetData>${rows}</sheetData></worksheet>`;
263}
264
265const XLSX_STYLES = `${XML_HEAD}<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fonts count="2"><font><sz val="11"/><name val="Calibri"/></font><font><b/><sz val="11"/><name val="Calibri"/></font></fonts><fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills><borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders><cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs><cellXfs count="2"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/><xf numFmtId="0" fontId="1" fillId="0" borderId="0" xfId="0" applyFont="1"/></cellXfs><cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles></styleSheet>`;
266
267/** An Excel workbook of `sheets`, each one's first row its header (in bold, frozen). */
268export function workbook(title: string, sheets: Sheet[], at = new Date()): Uint8Array {
269 const names = sheetNames(sheets.map((sheet) => sheet.name));
270 return zip(
271 [
272 [
273 "[Content_Types].xml",
274 `${XML_HEAD}<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/><Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>${sheets
275 .map((_, i) => `<Override PartName="/xl/worksheets/sheet${i + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`)
276 .join("")}<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/></Types>`,
277 ],
278 [
279 "_rels/.rels",
280 `${XML_HEAD}<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/><Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/></Relationships>`,
281 ],
282 ["docProps/core.xml", coreProps(title, at)],
283 [
284 "xl/workbook.xml",
285 `${XML_HEAD}<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets>${names
286 .map((name, i) => `<sheet name="${xml(name)}" sheetId="${i + 1}" r:id="rId${i + 1}"/>`)
287 .join("")}</sheets></workbook>`,
288 ],
289 [
290 "xl/_rels/workbook.xml.rels",
291 `${XML_HEAD}<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">${sheets
292 .map((_, i) => `<Relationship Id="rId${i + 1}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet${i + 1}.xml"/>`)
293 .join("")}<Relationship Id="rIdStyles" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/></Relationships>`,
294 ],
295 ["xl/styles.xml", XLSX_STYLES],
296 ...sheets.map((sheet, i): [string, string] => [`xl/worksheets/sheet${i + 1}.xml`, worksheet(sheet)]),
297 ],
298 at,
299 );
300}