@chalky/chalky 0.1.5 → 0.1.6

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.
Files changed (52) hide show
  1. package/README.md +8 -2
  2. package/agent/SYSTEM.md +2 -1
  3. package/agent/extensions/app/README.md +15 -11
  4. package/agent/extensions/app/skills/test-your-app/SKILL.md +23 -0
  5. package/agent/extensions/board/browser.js +5 -9
  6. package/agent/extensions/card/index.ts +18 -18
  7. package/agent/extensions/file/browser.js +46 -7
  8. package/agent/extensions/file/index.ts +3 -2
  9. package/agent/extensions/frame/README.md +2 -2
  10. package/agent/extensions/frame/index.ts +16 -7
  11. package/agent/extensions/history/index.ts +3 -1
  12. package/agent/extensions/note/README.md +3 -1
  13. package/agent/extensions/note/browser.js +33 -7
  14. package/agent/extensions/note/guide.md +2 -1
  15. package/agent/extensions/note/index.ts +36 -13
  16. package/agent/extensions/page/browser.js +453 -0
  17. package/agent/extensions/page/index.ts +70 -1
  18. package/agent/extensions/page/page.html +47 -0
  19. package/agent/extensions/page/skills/interact-with-page/SKILL.md +59 -0
  20. package/agent/extensions/screen/browser.js +6 -19
  21. package/agent/extensions/version/browser.js +9 -0
  22. package/agent/extensions/version/index.ts +73 -21
  23. package/agent/skills/design/SKILL.md +1 -1
  24. package/agent/skills/show-me/SKILL.md +66 -0
  25. package/package.json +2 -1
  26. package/server.ts +169 -7
  27. package/node_modules/xlsx/CHANGELOG.md +0 -361
  28. package/node_modules/xlsx/LICENSE +0 -201
  29. package/node_modules/xlsx/README.md +0 -41
  30. package/node_modules/xlsx/bin/xlsx.njs +0 -310
  31. package/node_modules/xlsx/bower.json +0 -22
  32. package/node_modules/xlsx/dist/LICENSE +0 -201
  33. package/node_modules/xlsx/dist/cpexcel.d.ts +0 -39
  34. package/node_modules/xlsx/dist/cpexcel.full.mjs +0 -1502
  35. package/node_modules/xlsx/dist/cpexcel.js +0 -1506
  36. package/node_modules/xlsx/dist/shim.min.js +0 -2
  37. package/node_modules/xlsx/dist/xlsx.core.min.js +0 -18
  38. package/node_modules/xlsx/dist/xlsx.core.min.map +0 -1
  39. package/node_modules/xlsx/dist/xlsx.extendscript.js +0 -28259
  40. package/node_modules/xlsx/dist/xlsx.full.min.js +0 -24
  41. package/node_modules/xlsx/dist/xlsx.full.min.map +0 -1
  42. package/node_modules/xlsx/dist/xlsx.mini.min.js +0 -10
  43. package/node_modules/xlsx/dist/xlsx.mini.min.map +0 -1
  44. package/node_modules/xlsx/dist/xlsx.zahl.js +0 -4
  45. package/node_modules/xlsx/dist/xlsx.zahl.mjs +0 -2
  46. package/node_modules/xlsx/dist/zahl.d.ts +0 -4
  47. package/node_modules/xlsx/package.json +0 -183
  48. package/node_modules/xlsx/types/index.d.ts +0 -1065
  49. package/node_modules/xlsx/types/tsconfig.json +0 -15
  50. package/node_modules/xlsx/xlsx.js +0 -28103
  51. package/node_modules/xlsx/xlsx.mjs +0 -28228
  52. package/node_modules/xlsx/xlsxworker.js +0 -14
package/server.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  // - keeps one pi AgentSession per agent (session name = agent name; saved under data/sessions/<agent>);
6
6
  // - keeps the rule for the version history (host.history) and serves /recover, going back to a working version
7
7
  // when an agent broke the page (the safety net);
8
+ // - brings a new release of this package into the home's agent/, merged with what the agents changed (host.update);
8
9
  // - hands each agent's pi events to the extensions that asked (host.eventHandlers), and tells them when a page
9
10
  // opens (host.connectHandlers). The page extension turns pi's messages into the page's and replays them.
10
11
  // Extensions reach the host through globalThis.chalky (pi loads them with jiti, so they don't share our imports).
@@ -17,6 +18,7 @@ import path from "node:path";
17
18
  import { createAgentSession, createCodemodeExtension, createMcpExtension, createToolSearchExtension, DefaultResourceLoader, SessionManager,
18
19
  SettingsManager } from "@earendil-works/pi-coding-agent";
19
20
  import git from "isomorphic-git";
21
+ import diff3Merge from "diff3";
20
22
  import { WebSocketServer } from "ws";
21
23
 
22
24
  const ROOT = import.meta.dirname; // this package: server.ts and the starting agent/
@@ -97,9 +99,9 @@ function migrate() {
97
99
  .replaceAll("/ui/agents.js", "/app/agents.js").replaceAll("/ui/voice.js", "/voice/voice.js")
98
100
  .replaceAll("/ui-api/app-data/", "/app/data/").replaceAll("/ui/apps/", "/built/apps/"));
99
101
  }
100
- migrate();
101
- if (!fs.existsSync(AGENT)) startAgent(); // first run: the starting agent
102
+ migrate(); // (a new home's agent/ comes from the update below: the first release, merged into nothing)
102
103
  if (HOME !== ROOT) { // HOME/agent's extensions import our dependencies: Node looks for a node_modules above them
104
+ fs.mkdirSync(HOME, { recursive: true }); // a new home: nothing made it yet
103
105
  const link = path.join(HOME, "node_modules");
104
106
  if (fs.lstatSync(link, { throwIfNoEntry: false })?.isSymbolicLink()) fs.unlinkSync(link); // again each start: the package may have moved
105
107
  if (fs.existsSync(link)) throw new Error(`${link} is in the way: Chalky links it to ${MODULES}`);
@@ -266,19 +268,174 @@ function file(res: http.ServerResponse, p: string) {
266
268
  // The version extension commits and rolls back with this; /recover too, so it works when that extension is broken.
267
269
  // repo isomorphic-git's {fs, dir: HOME, gitdir}
268
270
  // stage() git add -A for what inHistory keeps (.gitignore files play no part); returns the changed paths
271
+ // restore(oid) that version's agent/ and built/ into the folders and the index, removing files it doesn't have
272
+ // (the branch stays where it is; what the history doesn't keep, logins and endpoints, isn't touched)
273
+ // versions() the user's versions, newest first, as git.log gives them ({oid, commit}): main's own line, without
274
+ // the releases an update merged in (see "updates" below)
269
275
  const REPO = { fs, dir: HOME, gitdir: path.join(DATA, "version", "git") };
270
276
  const KEPT = { filepaths: ["agent", "built"], ignored: true, filter: inHistory }; // statusMatrix's walk: only these
277
+ // isomorphic-git 1.44.2 takes a file whose size and mtime (whole seconds) match the index as unchanged: a same-size
278
+ // change in the second after it was staged would be missed (and an update would write over it). Real git checks
279
+ // those "racy" files; here every file that looks unchanged is checked by its content. Its status also misses a change
280
+ // of the x bit alone: checked too.
271
281
  async function stage() {
272
- const changed = [];
282
+ const changed = [], staged = new Map<string, string>(); // the index: path -> "oid mode"
283
+ await git.walk({ ...REPO, trees: [git.STAGE()], map: async (f, [e]) => {
284
+ if (e && f !== "." && (await e.type()) === "blob") staged.set(f, `${await e.oid()} ${await e.mode()}`);
285
+ return true;
286
+ } });
287
+ const now = async (f: string) => { const p = path.join(HOME, f); // the file as git would stage it
288
+ return `${(await git.hashBlob({ object: fs.readFileSync(p) })).oid} ${fs.statSync(p).mode & 0o111 ? 0o100755 : 0o100644}`; };
273
289
  for (const [filepath, head, workdir, index] of await git.statusMatrix({ ...REPO, ...KEPT })) {
274
- if (head === 1 && workdir === 1 && index === 1) continue; // unchanged
290
+ if (head === 1 && workdir === 1 && index === 1 && await now(filepath) === staged.get(filepath)) continue; // unchanged
275
291
  if (workdir === 0) { if (index !== 0) await git.remove({ ...REPO, filepath }); }
276
292
  else await git.add({ ...REPO, filepath, force: true });
277
293
  changed.push(filepath);
278
294
  }
279
295
  return changed;
280
296
  }
281
- Object.assign(host, { history: { repo: REPO, kept: KEPT, stage } });
297
+ // Only the folders that either version has files in: for a folder neither has (built/ before anything was made, still
298
+ // on disk as built/apps/), isomorphic-git 1.44.2's checkout takes it for a file to delete and throws EISDIR.
299
+ async function restore(oid: string) {
300
+ const has = async (ref: string, dir: string) => (await git.listFiles({ ...REPO, ref })).some((f) => f.startsWith(`${dir}/`));
301
+ const head = await git.resolveRef({ ...REPO, ref: "HEAD" });
302
+ const filepaths = [];
303
+ for (const dir of KEPT.filepaths) if (await has(oid, dir) || await has(head, dir)) filepaths.push(dir);
304
+ if (filepaths.length) await git.checkout({ ...REPO, ref: oid, noUpdateHead: true, force: true, filepaths });
305
+ }
306
+ Object.assign(host, { history: { repo: REPO, kept: KEPT, stage, restore } });
307
+
308
+ // ---- updates: each release of this package is a commit on the history's "package" branch, merged into main ----
309
+ // The package's agent/ is a release; the home's agent/ is what runs, and the agents change it. At start, a release
310
+ // not merged yet is committed on "package" (its agent/ only) and merged into main, file by file. The base is the
311
+ // release merged last (git's merge base), so for each file:
312
+ // only the release changed it -> the release's only the home changed it -> the home's
313
+ // both, in different lines -> both changes (diff3)
314
+ // both, in the same lines, or one side deleted it -> the home's stays; the release's goes next to it as
315
+ // <file>.new, and agent1 is asked to merge them (host.update.conflicts)
316
+ // First a "Before update" commit of unsaved changes, then one commit with both parents: the version panel undoes an
317
+ // update like any change. A new home is the first release merged into nothing; a home from before this has no
318
+ // "package" yet: its base is its "original" version (the release it started from). If the update fails, the home
319
+ // runs as it was and the page says why (host.update.error). Not when the home is this folder (node server.ts here).
320
+ // agent/mcp.json is the user's own list of MCP servers: the package never ships it, so no release brings or removes it.
321
+ const RELEASE = `Chalky ${JSON.parse(fs.readFileSync(path.join(ROOT, "package.json"), "utf8")).version}`;
322
+ const CHALKY = { name: "chalky", email: "chalky@chalky" };
323
+ const OWN = ["agent/mcp.json"];
324
+ const ref = (r: string) => git.resolveRef({ ...REPO, ref: r }).catch(() => undefined);
325
+ const blob = async (oid: string) => Buffer.from((await git.readBlob({ ...REPO, oid })).blob);
326
+
327
+ // The user's versions: from HEAD by first parents only (an update's first parent is the version before it; its
328
+ // second, the release), stopping at a release (a new home's first version comes after one). Not git.log: it walks
329
+ // both parents, so the releases would be listed as versions (going back to one would take the agents' changes and
330
+ // built/ away), the first release twice, and versions made in the same second out of order.
331
+ async function versions() {
332
+ const releases = new Set<string>(), original = await ref("refs/tags/original"); // a home from before: its "original" isn't one
333
+ for (let oid = await ref("package"); oid && oid !== original; oid = (await git.readCommit({ ...REPO, oid })).commit.parent[0])
334
+ releases.add(oid);
335
+ const out = [];
336
+ for (let oid = await ref("HEAD"); oid && !releases.has(oid); ) {
337
+ const c = await git.readCommit({ ...REPO, oid });
338
+ out.push(c);
339
+ oid = c.commit.parent[0];
340
+ }
341
+ return out;
342
+ }
343
+ Object.assign(host.history, { versions });
344
+
345
+ async function releaseTree(dir: string): Promise<string | null> { // the package's agent/ (what startAgent copies) as a git tree
346
+ const tree = [];
347
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
348
+ const p = path.join(dir, e.name);
349
+ const rel = path.relative(path.join(ROOT, "agent"), p);
350
+ if (!isAgentPart(rel) || OWN.includes(`agent/${rel}`)) continue;
351
+ if (e.isFile()) tree.push({ mode: fs.statSync(p).mode & 0o111 ? "100755" : "100644", path: e.name, type: "blob" as const,
352
+ oid: await git.writeBlob({ ...REPO, blob: fs.readFileSync(p) }) });
353
+ else if (e.isDirectory()) { const oid = await releaseTree(p); if (oid) tree.push({ mode: "040000", path: e.name, type: "tree" as const, oid }); }
354
+ }
355
+ return tree.length ? git.writeTree({ ...REPO, tree }) : null;
356
+ }
357
+
358
+ async function filesOf(commit?: string) { // agent/'s files in a commit: path -> {oid, mode}
359
+ const files = new Map<string, { oid: string; mode: number }>();
360
+ if (commit) await git.walk({ ...REPO, trees: [git.TREE({ ref: commit })], map: async (p, [e]) => {
361
+ if (p !== "." && p !== "agent" && !p.startsWith("agent/")) return null; // built/: not the release's
362
+ if (e && (await e.type()) === "blob") files.set(p, { oid: await e.oid(), mode: await e.mode() });
363
+ return true;
364
+ } });
365
+ return files;
366
+ }
367
+
368
+ function merge3(base: Buffer, ours: Buffer, theirs: Buffer) { // both changes, or null if they touch the same lines
369
+ if ([base, ours, theirs].some((b) => b.includes(0))) return null; // not text
370
+ const lines = (b: Buffer) => b.toString("utf8").match(/^.*(\r?\n|$)/gm) ?? [];
371
+ const parts = diff3Merge(lines(ours), lines(base), lines(theirs));
372
+ return parts.some((p: any) => p.conflict) ? null : parts.flatMap((p: any) => p.ok).join("");
373
+ }
374
+
375
+ async function update() {
376
+ let main = await ref("main"), pkg = await ref("package");
377
+ const last = pkg && (await git.readCommit({ ...REPO, oid: pkg })).commit.message.trim();
378
+ if (last === RELEASE && main && (await git.findMergeBase({ ...REPO, oids: [main, pkg!] }))[0] === pkg) return; // merged
379
+ await git.init({ ...REPO, defaultBranch: "main" }); // (does nothing if there is one)
380
+ if ((await stage()).length) main = await git.commit({ ...REPO, message: `Before update to ${RELEASE}`, author: CHALKY });
381
+ if (last !== RELEASE) {
382
+ const agent = await releaseTree(path.join(ROOT, "agent"));
383
+ const tree = await git.writeTree({ ...REPO, tree: agent ? [{ mode: "040000", path: "agent", type: "tree", oid: agent }] : [] });
384
+ const parent = pkg ?? (main && await ref("refs/tags/original")); // a home from before this: the release it started from
385
+ pkg = await git.commit({ ...REPO, ref: "refs/heads/package", tree, parent: parent ? [parent] : [], message: RELEASE, author: CHALKY });
386
+ }
387
+ const [base] = main ? await git.findMergeBase({ ...REPO, oids: [main, pkg!] }) : [];
388
+ const [B, O, T] = await Promise.all([base, main, pkg].map(filesOf));
389
+ const writes: [string, Buffer | null, number?][] = [], conflicts: string[] = []; // [path in HOME, content (null: delete), mode]
390
+ for (const f of new Set([...O.keys(), ...T.keys()])) {
391
+ if (OWN.includes(f)) continue;
392
+ const [b, o, t] = [B.get(f), O.get(f), T.get(f)], same = (x?: typeof b, y?: typeof b) => x?.oid === y?.oid && x?.mode === y?.mode;
393
+ if (same(o, t) || same(b, t)) continue; // the same, or the release didn't change it: the home's
394
+ if (same(b, o)) { writes.push([f, t ? await blob(t.oid) : null, t?.mode]); continue; } // only the release changed it
395
+ const merged = b && o && t && merge3(await blob(b.oid), await blob(o.oid), await blob(t.oid)); // (and the x bit: whose changed)
396
+ if (merged != null) { writes.push([f, Buffer.from(merged), b!.mode === t!.mode ? o!.mode : t!.mode]); continue; }
397
+ if (t) writes.push([`${f}.new`, await blob(t.oid), t.mode]);
398
+ conflicts.push(!o ? `${f}: you had deleted it; this release changed it (its version: ${f}.new)`
399
+ : !t ? `${f}: this release removed it; you had changed it (yours is kept)`
400
+ : `${f}: you and this release changed the same lines (its version: ${f}.new)`);
401
+ }
402
+ try { // (then saved: main moves only when every file is written)
403
+ for (const [f, content, mode] of writes) {
404
+ const p = path.join(HOME, f);
405
+ if (!content) { // and the folders that leaves empty
406
+ fs.rmSync(p, { force: true });
407
+ for (let d = path.dirname(p); d !== AGENT && fs.existsSync(d) && !fs.readdirSync(d).length; d = path.dirname(d)) fs.rmdirSync(d);
408
+ continue;
409
+ }
410
+ fs.mkdirSync(path.dirname(p), { recursive: true });
411
+ fs.writeFileSync(p, content);
412
+ if (mode) fs.chmodSync(p, mode & 0o777);
413
+ }
414
+ await stage();
415
+ await git.commit({ ...REPO, message: main ? `Updated to ${RELEASE}` : `Started with ${RELEASE}`, author: CHALKY,
416
+ parent: main ? [main, pkg!] : [pkg!] });
417
+ } catch (e) { // each file written goes back as main has it (contents and x bit), or away (.new files too)
418
+ for (const [f] of writes) {
419
+ const p = path.join(HOME, f), o = O.get(f);
420
+ try {
421
+ if (!o) { fs.rmSync(p, { force: true }); continue; }
422
+ fs.mkdirSync(path.dirname(p), { recursive: true });
423
+ fs.writeFileSync(p, await blob(o.oid));
424
+ fs.chmodSync(p, o.mode & 0o777);
425
+ } catch (e2) { console.error(`could not put ${f} back:`, e2); }
426
+ }
427
+ throw e;
428
+ }
429
+ const files = writes.length - writes.filter(([f]) => f.endsWith(".new")).length;
430
+ if (main) return { release: RELEASE, files, conflicts }; // (a new home: nothing to tell)
431
+ }
432
+
433
+ if (HOME !== ROOT) try { Object.assign(host, { update: await update() }); } catch (e: any) { // run as it was: main didn't move
434
+ console.error(`update to ${RELEASE} failed:`, e);
435
+ const main = await ref("main");
436
+ if (main) await restore(main).catch((e) => console.error("could not put agent/ and built/ back as main has them:", e)); // and the index
437
+ Object.assign(host, { update: { release: RELEASE, error: String(e?.message ?? e) } });
438
+ }
282
439
 
283
440
  // ---- recovery: the safety net for when an agent breaks the page (or the version extension, so its panel) ----
284
441
  // Here, not in an extension, so agents can't break it too. Plain HTML forms, no script: it works whatever agent/ holds.
@@ -292,7 +449,7 @@ const esc = (s: string) => s.replace(/[&<>"]/g, (c) => `&#${c.charCodeAt(0)};`);
292
449
 
293
450
  async function recoverPage(res: http.ServerResponse) {
294
451
  // the history's folder comes before its first commit: right after a start there may be no versions yet
295
- const log = fs.existsSync(REPO.gitdir) ? await git.log(REPO).catch((e) => e.code === "NotFoundError" ? [] : Promise.reject(e)) : [];
452
+ const log = await versions(); // (none yet: [])
296
453
  const rows = log.map(({ oid, commit: c }, i) => `<tr><td>${new Date(c.author.timestamp * 1000).toLocaleString()}</td>
297
454
  <td>${esc(c.author.name)}</td><td>${esc(c.message.split("\n")[0])}</td>
298
455
  <td>${fs.existsSync(path.join(DATA, "version", "shots", `${oid}.png`)) ? "the page worked" : ""}</td>
@@ -315,7 +472,7 @@ async function recover(sha: string, res: http.ServerResponse) {
315
472
  const subject = `${sha.slice(0, 7)} ${(await git.readCommit({ ...REPO, oid: sha })).commit.message.split("\n")[0]}`;
316
473
  await stage();
317
474
  try { await git.commit({ ...REPO, message: "uncommitted changes before recovery", author, disallowEmpty: true }); } catch {}
318
- await git.checkout({ ...REPO, ref: sha, noUpdateHead: true, force: true, filepaths: KEPT.filepaths });
475
+ await restore(sha);
319
476
  const files = await stage();
320
477
  for (const f of files) for (let d = path.dirname(f); !["agent", "built", "."].includes(d); d = path.dirname(d)) { // folders it emptied
321
478
  try { fs.rmdirSync(path.join(HOME, d)); } catch { break; }
@@ -366,6 +523,11 @@ new WebSocketServer({ server, path: "/ws" }).on("connection", (sock) => {
366
523
 
367
524
  // agent1, then every agent saved by an earlier run (their conversations come back from their session files)
368
525
  await host.createAgent("agent1", "the user's main agent");
526
+ const conflicts: string[] = (host as any).update?.conflicts ?? []; // the update's files to merge: agent1's task
527
+ if (conflicts.length) host.send("chalky", "agent1", `Chalky was updated to ${(host as any).update.release}. These files `
528
+ + `were changed both by you (or another agent) and by the update, so yours were kept:\n${conflicts.map((c) => `- ${c}`).join("\n")}\n`
529
+ + "For each, read both versions and make one that keeps what the user had and adds what the update brings, then "
530
+ + "delete the .new file. If you can't tell what the user would want, keep theirs and tell them what the update had.");
369
531
  for (const name of fs.readdirSync(path.join(DATA, "sessions")).filter((n) => n !== "agent1")) {
370
532
  const { role, app } = JSON.parse(fs.readFileSync(path.join(DATA, "sessions", name, "role.json"), "utf8"));
371
533
  await host.createAgent(name, role, app);
@@ -1,361 +0,0 @@
1
- # CHANGELOG
2
-
3
- This log is intended to keep track of backwards-incompatible changes, including
4
- but not limited to API changes and file location changes. Minor behavioral
5
- changes may not be included if they are not expected to break existing code.
6
-
7
- ## v0.20.3
8
-
9
- * Correct parsing of NUMBERS and ODS merge cells (h/t @s-ashwin)
10
- * More precise treatment of infinite and NaN values
11
- * XLML Streaming Write
12
- * Parse `Int8Array` objects (for compatibility with JS engines in Java)
13
- * CSV Export only quote leading ID (h/t @lako12)
14
-
15
- ## v0.20.2
16
-
17
- * Reworked parsing methods to avoid slow regexes (CVE-2024-22363)
18
- * HTML properly encode data-v attribute
19
- * SYLK read and write error cells
20
-
21
- ## v0.20.1
22
-
23
- * `init` use packaged test files to work around GitHub breaking changes
24
- * SSF date code rounding to 15 decimal digits (h/t @davidtamaki)
25
- * `sheet_to_json` force UTC interpretation for formatted strings (h/t @Blanay)
26
- * QPW extract result of string formula
27
- * XLSX parse non-compliant merge cell expressions
28
- * NUMBERS correctly handle rows omitted from official exports
29
- * DBF parse empty logical field (h/t @Roman91)
30
- * `dense` option added to types
31
- * package.json add mini and core scripts to export map (h/t @stof)
32
-
33
- ## v0.20.0
34
-
35
- * Use UTC interpretation of Date objects for date cells (potentially breaking)
36
- * API functions support UTC and local time value interpretations
37
- * Export `NaN` values to `#NUM!` and infinite values to `#DIV/0!`
38
-
39
- ## v0.19.3
40
-
41
- * XLSX Ensure comment address is valid (h/t @slonser)
42
- * Enforce Excel worksheet name restrictions
43
- * Fixed "Prototype Pollution" vulnerability (CVE-2023-30533)
44
-
45
- ## v0.19.2
46
-
47
- * XLSX proper decoding of hyperlinks (h/t @tw-yaxu)
48
- * XLSX ignore unexpected attributes in rich text (h/t @colin4)
49
- * `sheet_to_json` type fix (h/t @chsdwn)
50
-
51
- ## v0.19.1
52
-
53
- * Fixed types issue in strict mode (h/t @younes-io)
54
- * Numbers 12.2 parsing skip ActivityStream.iwa
55
-
56
- ## v0.19.0
57
-
58
- * XLSX export hyperlinks compatible with google sheets (h/t Evan Bovie)
59
- * NUMBERS export multiple sheets, full worksheet range
60
- * formalized `dense` mode
61
-
62
- ## v0.18.12
63
-
64
- * `package.json` added types in `exports` structure
65
- * uncapped NUMBERS single-sheet single-table export
66
- * DBF export records using supported codepages
67
-
68
- ## v0.18.11
69
-
70
- * Base64 input ignore data URI wrapper
71
- * Parse ZIP files that use ZIP64 extended information field
72
- * More precise handling of time-only values
73
- * Threaded Comment fallback text for older Excel
74
-
75
- ## v0.18.10
76
-
77
- * `exports` field in package.json to satiate ViteJS and newer tooling
78
- * JSC (Safari / Bun) perf, see <https://bugs.webkit.org/show_bug.cgi?id=243148>
79
- * workbook `bookType` property to denote the origin format when parsed from file
80
- * XLSX force export of stub cells with number formats when `sheetStubs` is set
81
-
82
- ## v0.18.9
83
-
84
- * XLSX / ODS write defined names
85
- * sync defined names to AutoFilter setting on export
86
- * 1904 date system setting properly roundtripped
87
- * ODS read/write number formats
88
-
89
- ## v0.18.8
90
-
91
- * Plaintext parsing of dateless meridien time values (`1:23:45 PM`)
92
- * Legacy format (SYLK / WK# / Multiplan) minutiae
93
-
94
- ## v0.18.7
95
-
96
- * Normalized handling of `\r` and `\n` newline characters
97
-
98
- ## v0.18.6
99
-
100
- * Removed all npm dependencies
101
- * Auto-correct bad Google Sheets format `d.m`
102
- * NUMBERS write merge cells, cells up to column "ALL"
103
-
104
- ## v0.18.5
105
-
106
- * Enabled `sideEffects: false` in package.json
107
- * Basic NUMBERS write support
108
-
109
- ## v0.18.4
110
-
111
- * CSV output omits trailing record separator
112
- * Properly terminate NodeJS Streams
113
- * DBF preserve column types on import and use when applicable on export
114
-
115
- ## v0.18.3
116
-
117
- * Removed references to `require` and `process` in browser builds
118
-
119
- ## v0.18.2
120
-
121
- * Hotfix for unicode processing of XLSX exports
122
-
123
- ## v0.18.1
124
-
125
- * Removed Node ESM build script and folded into standard ESM build
126
- * Removed undocumented aliases including `make_formulae` and `get_formulae`
127
-
128
- ## v0.18.0
129
-
130
- * Browser scripts only expose `XLSX` variable
131
- * Module no longer ships with `dist/jszip.js` browser script
132
-
133
- ## v0.17.4
134
-
135
- * CLI script moved to `xlsx-cli` package
136
-
137
- ## v0.17.3
138
-
139
- * `window.XLSX` explicit assignment to satiate LWC
140
- * CSV Proper formatting of errors
141
- * HTML emit data-\* attributes
142
-
143
- ## v0.17.2
144
-
145
- * Browser and Node optional ESM support
146
- * DSV correct handling of bare quotes (h/t @bgamrat)
147
-
148
- ## v0.17.1
149
-
150
- * `XLSB` writer uses short cell form when viable
151
-
152
- ## 0.17.0:
153
-
154
- * mini build includes ODS parse/write support
155
- * DBF explicitly cap worksheet to 1<<20 rows
156
- * XLS throw errors on truncated records
157
-
158
- ## v0.16.2
159
-
160
- * Disabled `PRN` parsing by default (better support for CSV without delimeters)
161
-
162
- ## v0.16.1
163
-
164
- * skip empty custom property tags if data is absent (fixes DocSecurity issue)
165
- * HTML output add raw value, type, number format
166
- * DOM parse look for `v` / `t` / `z` attributes when determining value
167
- * double quotes in properties escaped using `_x0022_`
168
- * changed AMD structure for NetSuite and other RequireJS implementations
169
- - `encode_cell` and `decode_cell` do not rely on `encode_col` / `decode_col`
170
-
171
- ## v0.16.0
172
-
173
- * Date handling changed
174
- * XLML certain tag tests are now case insensitive
175
- * Fixed potentially vulnerable regular expressions
176
-
177
- ## v0.15.6
178
-
179
- * CFB prevent infinite loop
180
- * ODS empty cells marked as stub (type "z")
181
- * `cellStyles` option implies `sheetStubs`
182
-
183
- ## v0.15.5
184
-
185
- * `sheets` parse option to specify which sheets to parse
186
-
187
- ## v0.15.4
188
-
189
- * AOA utilities properly preserve number formats
190
- * Number formats captured in stub cells
191
-
192
- ## v0.15.3
193
-
194
- * Properties and Custom Properties properly XML-encoded
195
-
196
- ## v0.15.2
197
-
198
- - `sheet_get_cell` utility function
199
- - `sheet_to_json` explicitly support `null` as alias for default behavior
200
- - `encode_col` throw on negative column index
201
- - HTML properly handle whitespace around tags in a run
202
- - HTML use `id` option on write
203
- - Files starting with `0x09` followed by a display character are now TSV files
204
- - XLS parse references col/row indices mod by the correct number for BIFF ver
205
- - XLSX comments moved to avoid overlapping cell
206
- - XLSB outline level
207
- - AutoFilter update `_FilterDatabase` defined name on write
208
- - XLML skip CDATA blocks
209
-
210
- ## v0.15.1 (2019-08-14)
211
-
212
- * XLSX ignore XML artifacts
213
- * HTML capture and persist merges
214
-
215
- ## v0.15.0
216
-
217
- * `dist/xlsx.mini.min.js` mini build with XLSX read/write and some utilities
218
- * Removed legacy conversion utility functions
219
-
220
- ## v0.14.5
221
-
222
- * XLS PtgNameX lookup
223
- * XLS always create stub cells for blank cells with comments
224
-
225
-
226
- ## v0.14.4
227
-
228
- * Better treatment of `skipHidden` in CSV output
229
- * Ignore CLSID in XLS
230
- * SYLK 7-bit character encoding
231
- * SYLK and DBF codepage support
232
-
233
- ## v0.14.3
234
-
235
- * Proper shifting of addresses in Shared Formulae
236
-
237
- ## v0.14.2
238
-
239
- * Proper XML encoding of comments
240
-
241
- ## v0.14.1
242
-
243
- * raw cell objects can be passed to `sheet_add_aoa`
244
- * `_FilterDatabase` fix for AutoFilter-related crashes
245
- * `stream.to_json` doesn't end up accidentally scanning to max row
246
-
247
- ## 0.14.0 (2018-09-06)
248
-
249
- * `sheet_to_json` default flipped to `raw: true`
250
-
251
- ## 0.13.5 (2018-08-25)
252
-
253
- * HTML output generates `<br/>` instead of encoded newline character
254
-
255
- ## 0.13.2 (2018-07-08)
256
-
257
- * Buffer.from shim replaced, will not be defined in node `<=0.12`
258
-
259
- ## 0.13.0 (2018-06-01)
260
-
261
- * Library reshaped to support AMD out of the box
262
-
263
- ## 0.12.11 (2018-04-27)
264
-
265
- * XLS/XLSX/XLSB range truncation (errors in `WTF` mode)
266
-
267
- ## 0.12.4 (2018-03-04)
268
-
269
- * `JSZip` renamed to `JSZipSync`
270
-
271
- ## 0.12.0 (2018-02-08)
272
-
273
- * Extendscript target script in NPM package
274
-
275
- ## 0.11.19 (2018-02-03)
276
-
277
- * Error on empty workbook
278
-
279
- ## 0.11.16 (2017-12-30)
280
-
281
- * XLS ANSI/CP separation
282
- * 'array' write type and ArrayBuffer processing
283
-
284
- ## 0.11.6 (2017-10-16)
285
-
286
- * Semicolon-delimited files are detected
287
-
288
- ## 0.11.5 (2017-09-30)
289
-
290
- * Bower main script shifted to full version
291
- * 'binary' / 'string' encoding
292
-
293
- ## 0.11.3 (2017-08-19)
294
-
295
- * XLS cell ixfe/XF removed
296
-
297
- ## 0.11.0 (2017-07-31)
298
-
299
- * Strip `require` statements from minified version
300
- * minifier mangler enabled
301
-
302
- ## 0.10.9 (2017-07-28)
303
-
304
- * XLML/HTML resolution logic looks further into the data stream to decide type
305
- * Errors thrown on suspected RTF files
306
-
307
- ## 0.10.5 (2017-06-09)
308
-
309
- * HTML Table output header/footer should not include `<table>` tag
310
-
311
- ## 0.10.2 (2017-05-16)
312
-
313
- * Dates are converted to numbers by default (set `cellDates:true` to emit Dates)
314
- * Module does not export CFB
315
-
316
- ## 0.9.10 (2017-04-08)
317
-
318
- * `--perf` renamed to `--read-only`
319
-
320
- ## 0.9.9 (2017-04-03)
321
-
322
- * default output format changed to XLSB
323
- * comment text line endings are now normalized
324
- * errors thrown on write when worksheets have invalid names
325
-
326
- ## 0.9.7 (2017-03-28)
327
-
328
- * XLS legacy `!range` field removed
329
- * Hyperlink tooltip is stored in the `Tooltip` field
330
-
331
- ## 0.9.6 (2017-03-25)
332
-
333
- * `sheet_to_json` now passes `null` values when `raw` is set to `true`
334
- * `sheet_to_json` treats `null` stub cells as values in conjunction with `raw`
335
-
336
- ## 0.9.5 (2017-03-22)
337
-
338
- * `cellDates` affects parsing in non-XLSX formats
339
-
340
- ## 0.9.3 (2017-03-15)
341
-
342
- * XLML property names are more closely mapped to the XLSX equivalent
343
- * Stub cells are now cell type `z`
344
-
345
- ## 0.9.2 (2017-03-13)
346
-
347
- * Removed stale TypeScript definition files. Flowtype comments are used in the
348
- `xlsx.flow.js` source and stripped to produce `xlsx.js`.
349
- * sed usage reworked to support GNU sed in-place form. BSD sed seems to work,
350
- but the build script has not been tested on other sed variants:
351
-
352
- ```bash
353
- $ sed -i.ext [...] # GNU
354
- $ sed -i .ext [...] # bsd
355
- ```
356
-
357
- ## 0.9.0 (2017-03-09)
358
-
359
- * Removed ods.js source. The xlsx.js source absorbed the ODS logic and exposes
360
- the ODS variable, so projects should remove references to ods.js
361
-