@energy8platform/golem 0.6.0 → 0.7.0

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 (64) hide show
  1. package/README.md +14 -0
  2. package/dist/editor.css +3 -4
  3. package/dist/editor.js +108 -61
  4. package/dist/lib/cli.js +101 -70
  5. package/dist/lib/cli.js.map +4 -4
  6. package/dist/lib/e8/agent.d.ts +35 -0
  7. package/dist/lib/e8/host.d.ts +22 -0
  8. package/dist/lib/e8/node.d.ts +66 -0
  9. package/dist/lib/e8/runtime.d.ts +10 -0
  10. package/dist/lib/e8/schema.d.ts +3 -0
  11. package/dist/lib/e8/types.d.ts +80 -0
  12. package/dist/lib/e8-agent.js +6781 -0
  13. package/dist/lib/e8-agent.js.map +7 -0
  14. package/dist/lib/e8-client.js +116 -0
  15. package/dist/lib/e8-host.js +6929 -0
  16. package/dist/lib/e8-host.js.map +7 -0
  17. package/dist/lib/e8-runtime.js +2024 -0
  18. package/dist/lib/e8-runtime.js.map +7 -0
  19. package/dist/lib/e8-schema.js +7 -0
  20. package/dist/lib/e8-schema.js.map +7 -0
  21. package/dist/lib/editor/api.d.ts +1 -0
  22. package/dist/lib/editor/embed.d.ts +9 -0
  23. package/dist/lib/editor/server.d.ts +23 -1
  24. package/dist/lib/editor/store.d.ts +2 -0
  25. package/dist/lib/editor/timeline.d.ts +15 -0
  26. package/dist/lib/editor-entry.d.ts +1 -1
  27. package/dist/lib/editor-entry.js +102 -70
  28. package/dist/lib/editor-entry.js.map +4 -4
  29. package/dist/lib/rig-import-layers.d.ts +2 -0
  30. package/dist/lib/rig-version.d.ts +12 -0
  31. package/dist/lib/tool-schema.d.ts +3 -0
  32. package/dist/lib/tools.d.ts +2 -0
  33. package/dist/lib/tools.js +49 -10
  34. package/dist/lib/tools.js.map +4 -4
  35. package/editor.html +2 -2
  36. package/package.json +38 -3
  37. package/skills/e8-golem/SKILL.md +71 -0
  38. package/skills/golem-symbol-animation/SKILL.md +87 -0
  39. package/skills/golem-symbol-animation/agents/openai.yaml +4 -0
  40. package/skills/golem-symbol-animation/references/facial-controls.md +48 -0
  41. package/skills/golem-symbol-animation/references/game-engine-integration.md +133 -0
  42. package/skills/golem-symbol-animation/references/golem-authoring.md +138 -0
  43. package/skills/golem-symbol-animation/references/interactive-editor.md +17 -0
  44. package/skills/golem-symbol-animation/references/motion-craft.md +113 -0
  45. package/skills/golem-symbol-animation/references/packed-delivery.md +88 -0
  46. package/skills/golem-symbol-animation/references/quantitative-qa.md +106 -0
  47. package/skills/golem-symbol-animation/references/spine-rive-study.md +87 -0
  48. package/skills/golem-symbol-animation/scripts/atlas_parts.py +87 -0
  49. package/skills/golem-symbol-animation/scripts/check_package.py +71 -0
  50. package/skills/golem-symbol-animation/scripts/image_gates.py +143 -0
  51. package/skills/golem-symbol-cutting/SKILL.md +70 -0
  52. package/skills/golem-symbol-cutting/agents/openai.yaml +4 -0
  53. package/skills/golem-symbol-cutting/assets/h1/h1.atlas.png +0 -0
  54. package/skills/golem-symbol-cutting/assets/h1/h1.png +0 -0
  55. package/skills/golem-symbol-cutting/references/cutting-workflow.md +92 -0
  56. package/skills/golem-symbol-cutting/references/facial-layers.md +44 -0
  57. package/skills/golem-symbol-cutting/references/generation-workflow.md +109 -0
  58. package/skills/golem-symbol-cutting/references/golem-handoff.md +62 -0
  59. package/skills/golem-symbol-cutting/references/h1-example.md +46 -0
  60. package/skills/golem-symbol-cutting/references/hybrid-workflow.md +119 -0
  61. package/skills/golem-symbol-cutting/references/registration-and-motion.md +89 -0
  62. package/skills/golem-symbol-cutting/references/spine-rive-construction.md +73 -0
  63. package/skills/golem-symbol-cutting/scripts/atlas_parts.py +87 -0
  64. package/skills/golem-symbol-cutting/scripts/hybrid_parts.py +279 -0
@@ -1,8 +1,8 @@
1
1
  // src/editor/server.ts
2
2
  import { createServer } from "node:http";
3
- import { readFile as readFile10, stat as stat3, readdir as readdir5, mkdir as mkdir7, open } from "node:fs/promises";
3
+ import { readFile as readFile10, stat as stat4, readdir as readdir5, mkdir as mkdir7, open } from "node:fs/promises";
4
4
  import { watch as fsWatch } from "node:fs";
5
- import { resolve as resolve2, dirname as dirname9, basename as basename5, extname as extname2, sep, join as join10, relative as relative3 } from "node:path";
5
+ import { resolve as resolve2, dirname as dirname9, basename as basename5, extname as extname2, sep as sep2, join as join10, relative as relative4 } from "node:path";
6
6
  import { fileURLToPath as fileURLToPath3 } from "node:url";
7
7
 
8
8
  // src/rig-format.ts
@@ -568,7 +568,7 @@ function parse(input) {
568
568
  // src/rig-api.ts
569
569
  import { z as z3 } from "zod";
570
570
  import { readFile as readFile9, writeFile as writeFile7, stat as stat2, mkdir as mkdir6, readdir as readdir4 } from "node:fs/promises";
571
- import { dirname as dirname8, join as join9, relative as relative2, isAbsolute } from "node:path";
571
+ import { dirname as dirname8, join as join9, relative as relative3, isAbsolute as isAbsolute2 } from "node:path";
572
572
 
573
573
  // src/rig-io.ts
574
574
  import { mkdir, rename, rm, writeFile } from "node:fs/promises";
@@ -591,8 +591,10 @@ var saveJson = (path, value) => saveBytes(path, JSON.stringify(value, null, 2));
591
591
  // src/rig-queue.ts
592
592
  import { AsyncLocalStorage } from "node:async_hooks";
593
593
  import { resolve } from "node:path";
594
- var pending = /* @__PURE__ */ new Map();
595
- var active = new AsyncLocalStorage();
594
+ var { pending, active } = globalThis[Symbol.for("golem.rigQueue")] ??= {
595
+ pending: /* @__PURE__ */ new Map(),
596
+ active: new AsyncLocalStorage()
597
+ };
596
598
  async function withRigQueue(path, op) {
597
599
  const key = resolve(path);
598
600
  const held = active.getStore();
@@ -2008,8 +2010,9 @@ function createDoc(name, width, height, notes) {
2008
2010
  function createSymbol(name, asset, margin = 0.15, notes) {
2009
2011
  const mx = Math.ceil(asset.width * margin), my = Math.ceil(asset.height * margin);
2010
2012
  const width = asset.width + 2 * mx, height = asset.height + 2 * my;
2011
- const fromFile = asset.src.replace(/^.*\//, "").replace(/\.[^.]+$/, "").toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/^[^a-z]+/, "");
2012
- const id = asset.id ?? (fromFile || name);
2013
+ const stem = asset.src.replace(/^.*\//, "").replace(/\.[^.]+$/, "").toLowerCase().replace(/[^a-z0-9_]/g, "_");
2014
+ const fromFile = stem.replace(/^[^a-z]+/, "") || (stem ? `symbol_${stem}` : "");
2015
+ const id = asset.id ?? (ID_RE.test(fromFile) ? fromFile : ID_RE.test(name) ? name : "symbol");
2013
2016
  let doc = createDoc(name, width, height, notes);
2014
2017
  doc = { ...doc, bones: [{ ...doc.bones[0], transform: { ...doc.bones[0].transform, x: width / 2, y: height - my } }] };
2015
2018
  doc = { ...doc, assets: [RigSchema.shape.assets.element.parse({ id, src: asset.src, width: asset.width, height: asset.height })] };
@@ -2765,7 +2768,7 @@ function setGoal(doc, bone, x, y, opts) {
2765
2768
 
2766
2769
  // src/rig-import-layers.ts
2767
2770
  import { readdir, readFile } from "node:fs/promises";
2768
- import { join as join2, basename as basename2 } from "node:path";
2771
+ import { join as join2, basename as basename2, isAbsolute, relative, sep } from "node:path";
2769
2772
  var DEFAULT_PARENT = {
2770
2773
  head: "neck",
2771
2774
  neck: "torso",
@@ -2791,6 +2794,10 @@ function pngSize(buf) {
2791
2794
  if (buf.readUInt32BE(12) !== 1229472850) throw new Error("not a PNG (IHDR missing)");
2792
2795
  return [buf.readUInt32BE(16), buf.readUInt32BE(20)];
2793
2796
  }
2797
+ function outsideDir(dir, file) {
2798
+ const r = relative(dir, join2(dir, file));
2799
+ return isAbsolute(file) || r === ".." || r.startsWith(".." + sep);
2800
+ }
2794
2801
  async function importLayers(doc, dir, relPrefix = "layers") {
2795
2802
  let specs;
2796
2803
  try {
@@ -2803,6 +2810,7 @@ async function importLayers(doc, dir, relPrefix = "layers") {
2803
2810
  let next = doc;
2804
2811
  const parsed = [];
2805
2812
  for (const s of specs) {
2813
+ if (outsideDir(dir, s.file)) throw new Error(`layers.json file "${s.file}" is outside the layers folder (${dir})`);
2806
2814
  const buf = await readFile(join2(dir, s.file));
2807
2815
  const [w, h] = pngSize(buf);
2808
2816
  const m = basename2(s.file, ".png").match(/^(?:(\d+)_)?(.+)$/);
@@ -4052,7 +4060,7 @@ async function buildAtlas(doc, assetsDir, outDir, opts = {}) {
4052
4060
 
4053
4061
  // src/spine-import.ts
4054
4062
  import { readFile as readFile7, stat, writeFile as writeFile5 } from "node:fs/promises";
4055
- import { basename as basename4, dirname as dirname6, extname, join as join8, relative } from "node:path";
4063
+ import { basename as basename4, dirname as dirname6, extname, join as join8, relative as relative2 } from "node:path";
4056
4064
 
4057
4065
  // src/ktx2.ts
4058
4066
  import { readFile as readFile6 } from "node:fs/promises";
@@ -4201,13 +4209,13 @@ var Ids = class {
4201
4209
  var spineToDoc = (x, y, originX, originY) => [x + originX, originY - y];
4202
4210
  var r4 = (v) => Math.round(v * 1e6) / 1e6 + 0;
4203
4211
  var hex8 = (c) => `#${(c ?? "ffffffff").toLowerCase().padEnd(8, "f").slice(0, 8)}`;
4204
- function importSpine(json, atlasText, opts = {}) {
4212
+ function importSpine(json2, atlasText, opts = {}) {
4205
4213
  const warnings = [];
4206
4214
  const atlas = parseAtlas(atlasText);
4207
- const version = String(json.skeleton?.spine ?? "4.0");
4208
- if (json.physics?.length) warnings.push(`${json.physics.length} Spine physics constraints are not imported; bake their motion before delivery`);
4215
+ const version = String(json2.skeleton?.spine ?? "4.0");
4216
+ if (json2.physics?.length) warnings.push(`${json2.physics.length} Spine physics constraints are not imported; bake their motion before delivery`);
4209
4217
  const major = parseFloat(version);
4210
- const bounds = { x: json.skeleton?.x ?? 0, y: json.skeleton?.y ?? 0, w: json.skeleton?.width ?? 0, h: json.skeleton?.height ?? 0 };
4218
+ const bounds = { x: json2.skeleton?.x ?? 0, y: json2.skeleton?.y ?? 0, w: json2.skeleton?.width ?? 0, h: json2.skeleton?.height ?? 0 };
4211
4219
  const margin = opts.margin ?? 0;
4212
4220
  let originX = -bounds.x + margin, originY = bounds.y + bounds.h + margin;
4213
4221
  const toDoc = (x, y) => spineToDoc(x, y, originX, originY);
@@ -4224,7 +4232,7 @@ function importSpine(json, atlasText, opts = {}) {
4224
4232
  }
4225
4233
  return id;
4226
4234
  };
4227
- const spineBones = json.bones ?? [];
4235
+ const spineBones = json2.bones ?? [];
4228
4236
  const boneIndexByName = new Map(spineBones.map((b, i) => [b.name, i]));
4229
4237
  const bones = [{ id: ROOT, transform: { x: r4(originX), y: r4(originY) }, role: "root" }];
4230
4238
  for (const b of spineBones) {
@@ -4237,7 +4245,7 @@ function importSpine(json, atlasText, opts = {}) {
4237
4245
  ...b.color ? { color: b.color } : {}
4238
4246
  });
4239
4247
  }
4240
- const skins = Array.isArray(json.skins) ? json.skins : Object.entries(json.skins ?? {}).map(([name, attachments2]) => ({ name, attachments: attachments2 }));
4248
+ const skins = Array.isArray(json2.skins) ? json2.skins : Object.entries(json2.skins ?? {}).map(([name, attachments2]) => ({ name, attachments: attachments2 }));
4241
4249
  const selectedSkin = opts.skin ? skins.find((s) => s.name === opts.skin) : skins.find((s) => s.name === "default") ?? skins[0];
4242
4250
  if (opts.skin && !selectedSkin) throw new Error(`Spine skin "${opts.skin}" not found; available: ${skins.map((s) => s.name).join(", ")}`);
4243
4251
  const lookupSkins = [selectedSkin, skins.find((s) => s.name === "default")].filter((s, i, list) => s && list.indexOf(s) === i);
@@ -4261,7 +4269,7 @@ function importSpine(json, atlasText, opts = {}) {
4261
4269
  const resolved = skinnedName.get(`${slot} ${name}`);
4262
4270
  return resolved === void 0 ? null : attachmentId(slot, resolved);
4263
4271
  };
4264
- const spineSlots = json.slots ?? [];
4272
+ const spineSlots = json2.slots ?? [];
4265
4273
  const slotIndexByName = new Map(spineSlots.map((s, i) => [s.name, i]));
4266
4274
  const blendOf = (b) => b === "additive" ? "add" : b === "multiply" ? "multiply" : b === "screen" ? "screen" : "normal";
4267
4275
  const slots = spineSlots.map((s, i) => ({
@@ -4427,7 +4435,7 @@ function importSpine(json, atlasText, opts = {}) {
4427
4435
  }
4428
4436
  }
4429
4437
  const activeConstraint = (kind, c) => !c.skin || lookupSkins.some((s) => s[kind]?.includes(c.name));
4430
- const ik = (json.ik ?? []).filter((c) => activeConstraint("ik", c)).map((c) => ({
4438
+ const ik = (json2.ik ?? []).filter((c) => activeConstraint("ik", c)).map((c) => ({
4431
4439
  id: ikIds.get(c.name),
4432
4440
  order: c.order ?? 0,
4433
4441
  bones: c.bones.map((b) => boneIds.get(b)),
@@ -4440,7 +4448,7 @@ function importSpine(json, atlasText, opts = {}) {
4440
4448
  stretch: !!c.stretch,
4441
4449
  uniform: !!c.uniform
4442
4450
  }));
4443
- const transform = (json.transform ?? []).filter((c) => activeConstraint("transform", c)).map((c) => ({
4451
+ const transform = (json2.transform ?? []).filter((c) => activeConstraint("transform", c)).map((c) => ({
4444
4452
  id: tcIds.get(c.name),
4445
4453
  order: c.order ?? 0,
4446
4454
  bones: c.bones.map((b) => boneIds.get(b)),
@@ -4460,7 +4468,7 @@ function importSpine(json, atlasText, opts = {}) {
4460
4468
  mixScaleY: c.mixScaleY ?? c.mixScaleX ?? c.scaleMix ?? 1,
4461
4469
  mixShearY: c.mixShearY ?? c.shearMix ?? 1
4462
4470
  }));
4463
- const path = (json.path ?? []).filter((c) => activeConstraint("path", c)).map((c) => ({
4471
+ const path = (json2.path ?? []).filter((c) => activeConstraint("path", c)).map((c) => ({
4464
4472
  id: pcIds.get(c.name),
4465
4473
  order: c.order ?? 0,
4466
4474
  bones: c.bones.map((b) => boneIds.get(b)),
@@ -4514,7 +4522,7 @@ function importSpine(json, atlasText, opts = {}) {
4514
4522
  return keys;
4515
4523
  };
4516
4524
  const animations = [];
4517
- for (const [animName, a] of Object.entries(json.animations ?? {})) {
4525
+ for (const [animName, a] of Object.entries(json2.animations ?? {})) {
4518
4526
  const tracks = [], events = [];
4519
4527
  let duration = 0;
4520
4528
  const seen = (frames) => {
@@ -4795,7 +4803,7 @@ function importSpine(json, atlasText, opts = {}) {
4795
4803
  const build = () => ({
4796
4804
  format: "energy8.rig",
4797
4805
  version: 2,
4798
- meta: { name: opts.name ?? json.skeleton?.hash ?? "spine", width, height, notes: opts.notes ?? `imported from Spine ${version}` },
4806
+ meta: { name: opts.name ?? json2.skeleton?.hash ?? "spine", width, height, notes: opts.notes ?? `imported from Spine ${version}` },
4799
4807
  assets: [...assets.values()],
4800
4808
  bones,
4801
4809
  slots,
@@ -4848,7 +4856,7 @@ async function firstExisting(candidates, what) {
4848
4856
  throw new Error(`${what} not found, tried: ${candidates.join(", ")}`);
4849
4857
  }
4850
4858
  async function importSpineProject(opts) {
4851
- const json = JSON.parse(await readFile7(opts.json, "utf8"));
4859
+ const json2 = JSON.parse(await readFile7(opts.json, "utf8"));
4852
4860
  const stem = opts.json.replace(/\.json$/i, "");
4853
4861
  const atlasPath = opts.atlas ?? await firstExisting([stem + ".atlas", stem + ".atlas.txt"], "atlas");
4854
4862
  const atlasText = await readFile7(atlasPath, "utf8");
@@ -4858,6 +4866,7 @@ async function importSpineProject(opts) {
4858
4866
  const rigDir = dirname6(opts.rigPath);
4859
4867
  const pagePaths = /* @__PURE__ */ new Map();
4860
4868
  for (const page2 of pages) {
4869
+ if (!opts.image && outsideDir(dirname6(atlasPath), page2.name)) throw new Error(`atlas page "${page2.name}" is outside the atlas folder (${dirname6(atlasPath)})`);
4861
4870
  const pageStem = join8(dirname6(atlasPath), page2.name.replace(/\.[a-z0-9]+$/i, ""));
4862
4871
  const imagePath = opts.image ?? await firstExisting([join8(dirname6(atlasPath), page2.name), pageStem + ".png", pageStem + ".ktx2", ...pages.length === 1 ? [stem + ".png", stem + ".ktx2"] : []], `atlas page image ${page2.name}`);
4863
4872
  let pngPath = imagePath;
@@ -4870,10 +4879,10 @@ async function importSpineProject(opts) {
4870
4879
  }
4871
4880
  pagePaths.set(page2.name, pngPath);
4872
4881
  }
4873
- const pageImage = (page2) => relative(rigDir, pagePaths.get(page2)).split("\\").join("/");
4882
+ const pageImage = (page2) => relative2(rigDir, pagePaths.get(page2)).split("\\").join("/");
4874
4883
  const name = opts.name ?? basename4(stem);
4875
4884
  const imagePaths = [...pagePaths.values()];
4876
- return { ...importSpine(json, atlasText, { name, skin: opts.skin, pageImage, margin: opts.margin, notes: opts.notes }), imagePath: imagePaths[0], imagePaths };
4885
+ return { ...importSpine(json2, atlasText, { name, skin: opts.skin, pageImage, margin: opts.margin, notes: opts.notes }), imagePath: imagePaths[0], imagePaths };
4877
4886
  }
4878
4887
 
4879
4888
  // src/rig-templates.ts
@@ -5562,8 +5571,8 @@ async function load(path) {
5562
5571
  }
5563
5572
  var save = (path, doc) => saveJson(path, doc);
5564
5573
  var exists2 = (p) => stat2(p).then(() => true, () => false);
5565
- var resolveFrom = (rigPath, p) => isAbsolute(p) ? p : join9(dirname8(rigPath), p);
5566
- var srcFor = (rigPath, file) => relative2(dirname8(rigPath), file).split("\\").join("/");
5574
+ var resolveFrom = (rigPath, p) => isAbsolute2(p) ? p : join9(dirname8(rigPath), p);
5575
+ var srcFor = (rigPath, file) => relative3(dirname8(rigPath), file).split("\\").join("/");
5567
5576
  var libraryFor = (rigPath, lib) => lib ? resolveFrom(rigPath, lib) : join9(dirname8(rigPath), "presets.json");
5568
5577
  async function pngInfo(rigPath, file) {
5569
5578
  const abs = resolveFrom(rigPath, file);
@@ -5814,9 +5823,9 @@ tool(
5814
5823
  margin: z3.number().optional().describe("canvas margin around the skeleton bounds, px"),
5815
5824
  notes: z3.string().optional()
5816
5825
  },
5817
- async ({ path, json, atlas, image, name, skin: skin2, margin, notes }) => {
5826
+ async ({ path, json: json2, atlas, image, name, skin: skin2, margin, notes }) => {
5818
5827
  try {
5819
- const r = await importSpineProject({ json: resolveFrom(path, json), atlas: atlas && resolveFrom(path, atlas), image: image && resolveFrom(path, image), rigPath: path, name, skin: skin2, margin, notes });
5828
+ const r = await importSpineProject({ json: resolveFrom(path, json2), atlas: atlas && resolveFrom(path, atlas), image: image && resolveFrom(path, image), rigPath: path, name, skin: skin2, margin, notes });
5820
5829
  const res = await create(path, parse(r.doc));
5821
5830
  if (r.warnings.length) res.text += `
5822
5831
  warnings:
@@ -6588,40 +6597,41 @@ tool(
6588
6597
  }
6589
6598
  );
6590
6599
 
6600
+ // src/rig-version.ts
6601
+ import { stat as stat3 } from "node:fs/promises";
6602
+ async function rigVersion(abs) {
6603
+ try {
6604
+ const s = await stat3(abs);
6605
+ return `${s.mtimeMs}:${s.size}`;
6606
+ } catch {
6607
+ return "missing";
6608
+ }
6609
+ }
6610
+
6591
6611
  // src/editor/server.ts
6592
6612
  var TYPES = { ".html": "text/html; charset=utf-8", ".js": "text/javascript", ".css": "text/css", ".map": "application/json", ".json": "application/json", ".png": "image/png", ".jpg": "image/jpeg", ".webp": "image/webp" };
6593
- async function startEditorServer(opts) {
6613
+ var json = (res, status, body) => {
6614
+ res.writeHead(status, { "content-type": "application/json" });
6615
+ res.end(JSON.stringify(body));
6616
+ };
6617
+ function createEditorHandler(opts) {
6594
6618
  const root2 = resolve2(opts.root);
6595
6619
  const assets = resolve2(opts.assets ?? fileURLToPath3(new URL("../..", import.meta.url)));
6596
6620
  const inside = (rel) => {
6597
6621
  const abs = resolve2(root2, rel);
6598
- return abs === root2 || abs.startsWith(root2 + sep) ? abs : void 0;
6599
- };
6600
- const version = async (abs) => {
6601
- try {
6602
- const s = await stat3(abs);
6603
- return `${s.mtimeMs}:${s.size}`;
6604
- } catch {
6605
- return "missing";
6606
- }
6622
+ return abs === root2 || abs.startsWith(root2 + sep2) ? abs : void 0;
6607
6623
  };
6608
6624
  const readDoc = async (abs) => RigDocument.parse(JSON.parse(await readFile10(abs, "utf8")));
6609
6625
  const readDocAt = async (abs) => {
6610
- let v = await version(abs);
6626
+ let v = await rigVersion(abs);
6611
6627
  for (let i = 0; i < 3; i++) {
6612
6628
  const doc = await readDoc(abs);
6613
- const after = await version(abs);
6629
+ const after = await rigVersion(abs);
6614
6630
  if (after === v) return { doc, version: v };
6615
6631
  v = after;
6616
6632
  }
6617
6633
  return { doc: await readDoc(abs), version: v };
6618
6634
  };
6619
- const json = (res, status, body2) => {
6620
- res.writeHead(status, { "content-type": "application/json" });
6621
- res.end(JSON.stringify(body2));
6622
- };
6623
- let allowedHosts = /* @__PURE__ */ new Set();
6624
- const hostOk = (req) => allowedHosts.has(String(req.headers.host ?? "").toLowerCase());
6625
6635
  const clients = /* @__PURE__ */ new Map();
6626
6636
  const watchers = /* @__PURE__ */ new Map();
6627
6637
  function ensureWatch(abs) {
@@ -6633,7 +6643,7 @@ async function startEditorServer(opts) {
6633
6643
  const schedule = () => {
6634
6644
  clearTimeout(entry.timer);
6635
6645
  entry.timer = setTimeout(async () => {
6636
- const v = await version(abs);
6646
+ const v = await rigVersion(abs);
6637
6647
  if (v === entry.last) return;
6638
6648
  entry.last = v;
6639
6649
  for (const res of clients.get(abs) ?? []) res.write(`event: changed
@@ -6642,7 +6652,7 @@ data: ${JSON.stringify({ version: v })}
6642
6652
  `);
6643
6653
  }, 50);
6644
6654
  };
6645
- version(abs).then((v) => {
6655
+ rigVersion(abs).then((v) => {
6646
6656
  entry.last = v;
6647
6657
  });
6648
6658
  watchers.set(abs, entry);
@@ -6668,7 +6678,7 @@ data: ${JSON.stringify({ version: v })}
6668
6678
  name = String(JSON.parse(await readFile10(abs, "utf8")).meta?.name ?? "?");
6669
6679
  } catch {
6670
6680
  }
6671
- out.push({ rig: relative3(root2, abs).split(sep).join("/"), name, mtimeMs: (await stat3(abs)).mtimeMs });
6681
+ out.push({ rig: relative4(root2, abs).split(sep2).join("/"), name, mtimeMs: (await stat4(abs)).mtimeMs });
6672
6682
  }
6673
6683
  };
6674
6684
  await walk(root2);
@@ -6702,20 +6712,22 @@ data: ${JSON.stringify({ version: v })}
6702
6712
  }
6703
6713
  return Buffer.concat(chunks);
6704
6714
  }
6705
- const server = createServer(async (req, res) => {
6715
+ async function handle(req, res, rawUrl = req.url ?? "/") {
6706
6716
  try {
6707
- if (!hostOk(req)) return json(res, 403, { isError: true, text: `ERROR: forbidden Host "${req.headers.host ?? ""}" \u2014 this server answers only on 127.0.0.1 / localhost` });
6708
- const url = new URL(req.url ?? "/", "http://x");
6717
+ const url = new URL(rawUrl, "http://x");
6709
6718
  if (url.pathname === "/") {
6710
- res.writeHead(302, { location: "/editor.html" + url.search });
6711
- return res.end();
6719
+ res.writeHead(302, { location: "editor.html" + url.search });
6720
+ return void res.end();
6721
+ }
6722
+ if (url.pathname.startsWith("/api/")) {
6723
+ await api(url, req, res);
6724
+ return;
6712
6725
  }
6713
- if (url.pathname.startsWith("/api/")) return await api(url, req, res);
6714
6726
  const STATIC = /* @__PURE__ */ new Set(["/editor.html", "/dist/editor.js", "/dist/editor.js.map", "/dist/editor.css", "/dist/editor.css.map"]);
6715
6727
  const abs = url.pathname.startsWith("/rig/") ? inside(decodeURIComponent(url.pathname.slice(5))) : STATIC.has(url.pathname) ? join10(assets, url.pathname) : void 0;
6716
6728
  if (!abs) {
6717
6729
  res.writeHead(403);
6718
- return res.end("forbidden");
6730
+ return void res.end("forbidden");
6719
6731
  }
6720
6732
  try {
6721
6733
  const data = await readFile10(abs);
@@ -6733,7 +6745,7 @@ data: ${JSON.stringify({ version: v })}
6733
6745
  }
6734
6746
  json(res, 500, { isError: true, text: `ERROR: ${e.message}` });
6735
6747
  }
6736
- });
6748
+ }
6737
6749
  async function api(url, req, res) {
6738
6750
  const rigOf = (rel) => {
6739
6751
  if (!rel) return void 0;
@@ -6750,7 +6762,7 @@ data: ${JSON.stringify({ version: v })}
6750
6762
  res.writeHead(403);
6751
6763
  return res.end();
6752
6764
  }
6753
- if (!await stat3(abs).then(() => true, () => false)) return json(res, 404, { isError: true, text: `ERROR: ${url.searchParams.get("rig")} does not exist under ${root2}` });
6765
+ if (!await stat4(abs).then(() => true, () => false)) return json(res, 404, { isError: true, text: `ERROR: ${url.searchParams.get("rig")} does not exist under ${root2}` });
6754
6766
  ensureWatch(abs);
6755
6767
  res.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", connection: "keep-alive" });
6756
6768
  res.write(`: connected
@@ -6772,7 +6784,7 @@ data: ${JSON.stringify({ version: v })}
6772
6784
  if (!abs) return json(res, 403, { isError: true, text: "rig outside root" });
6773
6785
  ensureWatch(abs);
6774
6786
  return await withRigQueue(abs, async () => {
6775
- if (baseVersion !== void 0 && await version(abs) !== baseVersion) {
6787
+ if (baseVersion !== void 0 && await rigVersion(abs) !== baseVersion) {
6776
6788
  const at = await readDocAt(abs);
6777
6789
  return json(res, 409, {
6778
6790
  isError: true,
@@ -6781,8 +6793,15 @@ data: ${JSON.stringify({ version: v })}
6781
6793
  text: `ERROR: the rig changed on disk (expected ${baseVersion}, found ${at.version}); nothing was written`
6782
6794
  });
6783
6795
  }
6784
- const r = await callTool(url.pathname.slice(10), { ...args ?? {}, path: abs });
6785
- return json(res, 200, { text: r.text, doc: r.doc, image: r.image?.toString("base64"), isError: r.isError, version: await version(abs) });
6796
+ const tool2 = url.pathname.slice(10);
6797
+ let given = { ...args ?? {}, path: abs };
6798
+ try {
6799
+ if (opts.args) given = opts.args(tool2, given);
6800
+ } catch (e) {
6801
+ return json(res, 400, { isError: true, text: `ERROR: ${e instanceof Error ? e.message : String(e)}` });
6802
+ }
6803
+ const r = await callTool(tool2, given);
6804
+ return json(res, 200, { text: r.text, doc: r.doc, image: r.image?.toString("base64"), isError: r.isError, version: await rigVersion(abs) });
6786
6805
  });
6787
6806
  }
6788
6807
  if (url.pathname === "/api/rigs" && req.method === "GET") return json(res, 200, await listRigs());
@@ -6793,11 +6812,11 @@ data: ${JSON.stringify({ version: v })}
6793
6812
  if (typeof rig !== "string" || !/(^|\/)rig\.json$/.test(rig)) return json(res, 400, { isError: true, text: "ERROR: a project is a directory with a rig.json \u2014 the path must end with /rig.json" });
6794
6813
  const abs = inside(rig);
6795
6814
  if (!abs) return json(res, 403, { isError: true, text: "rig outside root" });
6796
- if (await stat3(abs).then(() => true, () => false)) return json(res, 409, { isError: true, text: `ERROR: ${rig} already exists \u2014 open it, or pick another directory` });
6815
+ if (await stat4(abs).then(() => true, () => false)) return json(res, 409, { isError: true, text: `ERROR: ${rig} already exists \u2014 open it, or pick another directory` });
6797
6816
  await mkdir7(dirname9(abs), { recursive: true });
6798
6817
  const r = await callTool("rig_create", { path: abs, name: name ?? basename5(dirname9(abs)), width: width ?? 640, height: height ?? 700 });
6799
6818
  ensureWatch(abs);
6800
- return json(res, r.isError ? 400 : 200, { text: r.text, doc: r.doc, isError: r.isError, version: await version(abs) });
6819
+ return json(res, r.isError ? 400 : 200, { text: r.text, doc: r.doc, isError: r.isError, version: await rigVersion(abs) });
6801
6820
  }
6802
6821
  if (url.pathname === "/api/files" && req.method === "GET") {
6803
6822
  const abs = rigOf(url.searchParams.get("rig"));
@@ -6811,8 +6830,8 @@ data: ${JSON.stringify({ version: v })}
6811
6830
  if (!/^[A-Za-z0-9._-]+\.png$/i.test(name) || name.startsWith(".")) return json(res, 400, { isError: true, text: `ERROR: bad file name "${name}" \u2014 letters, digits, . _ - and a .png extension` });
6812
6831
  if (!String(req.headers["content-type"] ?? "").toLowerCase().startsWith("image/png")) return json(res, 415, { isError: true, text: "ERROR: upload needs content-type: image/png" });
6813
6832
  const target = join10(dirname9(abs), name);
6814
- if (!inside(relative3(root2, target))) return json(res, 403, { isError: true, text: "file outside root" });
6815
- if (await stat3(target).then(() => true, () => false)) return json(res, 409, { isError: true, text: `ERROR: ${name} already exists next to the rig \u2014 rename the file` });
6833
+ if (!inside(relative4(root2, target))) return json(res, 403, { isError: true, text: "file outside root" });
6834
+ if (await stat4(target).then(() => true, () => false)) return json(res, 409, { isError: true, text: `ERROR: ${name} already exists next to the rig \u2014 rename the file` });
6816
6835
  const data = await bytes(req, UPLOAD_MAX);
6817
6836
  if (!data) return json(res, 413, { isError: true, text: `ERROR: ${name} is larger than ${UPLOAD_MAX / 1048576} MB` });
6818
6837
  let size;
@@ -6826,6 +6845,22 @@ data: ${JSON.stringify({ version: v })}
6826
6845
  }
6827
6846
  json(res, 404, { isError: true, text: "unknown api route" });
6828
6847
  }
6848
+ return { handle, close() {
6849
+ for (const w of watchers.values()) {
6850
+ clearTimeout(w.timer);
6851
+ w.w.close();
6852
+ }
6853
+ for (const set of clients.values()) for (const c of set) c.end();
6854
+ } };
6855
+ }
6856
+ async function startEditorServer(opts) {
6857
+ const editor = createEditorHandler(opts);
6858
+ let allowedHosts = /* @__PURE__ */ new Set();
6859
+ const server = createServer((req, res) => {
6860
+ if (!allowedHosts.has(String(req.headers.host ?? "").toLowerCase()))
6861
+ return json(res, 403, { isError: true, text: `ERROR: forbidden Host "${req.headers.host ?? ""}" \u2014 this server answers only on 127.0.0.1 / localhost` });
6862
+ void editor.handle(req, res);
6863
+ });
6829
6864
  await new Promise((ok, fail2) => {
6830
6865
  server.once("error", fail2);
6831
6866
  server.listen(opts.port ?? 8765, "127.0.0.1", () => {
@@ -6836,15 +6871,12 @@ data: ${JSON.stringify({ version: v })}
6836
6871
  const port = server.address().port;
6837
6872
  allowedHosts = /* @__PURE__ */ new Set([`127.0.0.1:${port}`, `localhost:${port}`, `[::1]:${port}`]);
6838
6873
  return { port, url: `http://127.0.0.1:${port}`, close: async () => {
6839
- for (const w of watchers.values()) {
6840
- clearTimeout(w.timer);
6841
- w.w.close();
6842
- }
6843
- for (const set of clients.values()) for (const c of set) c.end();
6874
+ editor.close();
6844
6875
  await new Promise((r) => server.close(() => r()));
6845
6876
  } };
6846
6877
  }
6847
6878
  export {
6879
+ createEditorHandler,
6848
6880
  startEditorServer
6849
6881
  };
6850
6882
  //# sourceMappingURL=editor-entry.js.map