@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.
- package/README.md +14 -0
- package/dist/editor.css +3 -4
- package/dist/editor.js +108 -61
- package/dist/lib/cli.js +101 -70
- package/dist/lib/cli.js.map +4 -4
- package/dist/lib/e8/agent.d.ts +35 -0
- package/dist/lib/e8/host.d.ts +22 -0
- package/dist/lib/e8/node.d.ts +66 -0
- package/dist/lib/e8/runtime.d.ts +10 -0
- package/dist/lib/e8/schema.d.ts +3 -0
- package/dist/lib/e8/types.d.ts +80 -0
- package/dist/lib/e8-agent.js +6781 -0
- package/dist/lib/e8-agent.js.map +7 -0
- package/dist/lib/e8-client.js +116 -0
- package/dist/lib/e8-host.js +6929 -0
- package/dist/lib/e8-host.js.map +7 -0
- package/dist/lib/e8-runtime.js +2024 -0
- package/dist/lib/e8-runtime.js.map +7 -0
- package/dist/lib/e8-schema.js +7 -0
- package/dist/lib/e8-schema.js.map +7 -0
- package/dist/lib/editor/api.d.ts +1 -0
- package/dist/lib/editor/embed.d.ts +9 -0
- package/dist/lib/editor/server.d.ts +23 -1
- package/dist/lib/editor/store.d.ts +2 -0
- package/dist/lib/editor/timeline.d.ts +15 -0
- package/dist/lib/editor-entry.d.ts +1 -1
- package/dist/lib/editor-entry.js +102 -70
- package/dist/lib/editor-entry.js.map +4 -4
- package/dist/lib/rig-import-layers.d.ts +2 -0
- package/dist/lib/rig-version.d.ts +12 -0
- package/dist/lib/tool-schema.d.ts +3 -0
- package/dist/lib/tools.d.ts +2 -0
- package/dist/lib/tools.js +49 -10
- package/dist/lib/tools.js.map +4 -4
- package/editor.html +2 -2
- package/package.json +38 -3
- package/skills/e8-golem/SKILL.md +71 -0
- package/skills/golem-symbol-animation/SKILL.md +87 -0
- package/skills/golem-symbol-animation/agents/openai.yaml +4 -0
- package/skills/golem-symbol-animation/references/facial-controls.md +48 -0
- package/skills/golem-symbol-animation/references/game-engine-integration.md +133 -0
- package/skills/golem-symbol-animation/references/golem-authoring.md +138 -0
- package/skills/golem-symbol-animation/references/interactive-editor.md +17 -0
- package/skills/golem-symbol-animation/references/motion-craft.md +113 -0
- package/skills/golem-symbol-animation/references/packed-delivery.md +88 -0
- package/skills/golem-symbol-animation/references/quantitative-qa.md +106 -0
- package/skills/golem-symbol-animation/references/spine-rive-study.md +87 -0
- package/skills/golem-symbol-animation/scripts/atlas_parts.py +87 -0
- package/skills/golem-symbol-animation/scripts/check_package.py +71 -0
- package/skills/golem-symbol-animation/scripts/image_gates.py +143 -0
- package/skills/golem-symbol-cutting/SKILL.md +70 -0
- package/skills/golem-symbol-cutting/agents/openai.yaml +4 -0
- package/skills/golem-symbol-cutting/assets/h1/h1.atlas.png +0 -0
- package/skills/golem-symbol-cutting/assets/h1/h1.png +0 -0
- package/skills/golem-symbol-cutting/references/cutting-workflow.md +92 -0
- package/skills/golem-symbol-cutting/references/facial-layers.md +44 -0
- package/skills/golem-symbol-cutting/references/generation-workflow.md +109 -0
- package/skills/golem-symbol-cutting/references/golem-handoff.md +62 -0
- package/skills/golem-symbol-cutting/references/h1-example.md +46 -0
- package/skills/golem-symbol-cutting/references/hybrid-workflow.md +119 -0
- package/skills/golem-symbol-cutting/references/registration-and-motion.md +89 -0
- package/skills/golem-symbol-cutting/references/spine-rive-construction.md +73 -0
- package/skills/golem-symbol-cutting/scripts/atlas_parts.py +87 -0
- package/skills/golem-symbol-cutting/scripts/hybrid_parts.py +279 -0
package/dist/lib/cli.js
CHANGED
|
@@ -3,9 +3,9 @@ import { resolve as resolve3 } from "node:path";
|
|
|
3
3
|
|
|
4
4
|
// src/editor/server.ts
|
|
5
5
|
import { createServer } from "node:http";
|
|
6
|
-
import { readFile as readFile10, stat as
|
|
6
|
+
import { readFile as readFile10, stat as stat4, readdir as readdir5, mkdir as mkdir7, open } from "node:fs/promises";
|
|
7
7
|
import { watch as fsWatch } from "node:fs";
|
|
8
|
-
import { resolve as resolve2, dirname as dirname9, basename as basename5, extname as extname2, sep, join as join10, relative as
|
|
8
|
+
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";
|
|
9
9
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
10
10
|
|
|
11
11
|
// src/rig-format.ts
|
|
@@ -571,7 +571,7 @@ function parse(input) {
|
|
|
571
571
|
// src/rig-api.ts
|
|
572
572
|
import { z as z3 } from "zod";
|
|
573
573
|
import { readFile as readFile9, writeFile as writeFile7, stat as stat2, mkdir as mkdir6, readdir as readdir4 } from "node:fs/promises";
|
|
574
|
-
import { dirname as dirname8, join as join9, relative as
|
|
574
|
+
import { dirname as dirname8, join as join9, relative as relative3, isAbsolute as isAbsolute2 } from "node:path";
|
|
575
575
|
|
|
576
576
|
// src/rig-io.ts
|
|
577
577
|
import { mkdir, rename, rm, writeFile } from "node:fs/promises";
|
|
@@ -594,8 +594,10 @@ var saveJson = (path, value) => saveBytes(path, JSON.stringify(value, null, 2));
|
|
|
594
594
|
// src/rig-queue.ts
|
|
595
595
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
596
596
|
import { resolve } from "node:path";
|
|
597
|
-
var pending
|
|
598
|
-
|
|
597
|
+
var { pending, active } = globalThis[Symbol.for("golem.rigQueue")] ??= {
|
|
598
|
+
pending: /* @__PURE__ */ new Map(),
|
|
599
|
+
active: new AsyncLocalStorage()
|
|
600
|
+
};
|
|
599
601
|
async function withRigQueue(path, op) {
|
|
600
602
|
const key = resolve(path);
|
|
601
603
|
const held = active.getStore();
|
|
@@ -2011,8 +2013,9 @@ function createDoc(name, width, height, notes) {
|
|
|
2011
2013
|
function createSymbol(name, asset, margin = 0.15, notes) {
|
|
2012
2014
|
const mx = Math.ceil(asset.width * margin), my = Math.ceil(asset.height * margin);
|
|
2013
2015
|
const width = asset.width + 2 * mx, height = asset.height + 2 * my;
|
|
2014
|
-
const
|
|
2015
|
-
const
|
|
2016
|
+
const stem = asset.src.replace(/^.*\//, "").replace(/\.[^.]+$/, "").toLowerCase().replace(/[^a-z0-9_]/g, "_");
|
|
2017
|
+
const fromFile = stem.replace(/^[^a-z]+/, "") || (stem ? `symbol_${stem}` : "");
|
|
2018
|
+
const id = asset.id ?? (ID_RE.test(fromFile) ? fromFile : ID_RE.test(name) ? name : "symbol");
|
|
2016
2019
|
let doc = createDoc(name, width, height, notes);
|
|
2017
2020
|
doc = { ...doc, bones: [{ ...doc.bones[0], transform: { ...doc.bones[0].transform, x: width / 2, y: height - my } }] };
|
|
2018
2021
|
doc = { ...doc, assets: [RigSchema.shape.assets.element.parse({ id, src: asset.src, width: asset.width, height: asset.height })] };
|
|
@@ -2768,7 +2771,7 @@ function setGoal(doc, bone, x, y, opts) {
|
|
|
2768
2771
|
|
|
2769
2772
|
// src/rig-import-layers.ts
|
|
2770
2773
|
import { readdir, readFile } from "node:fs/promises";
|
|
2771
|
-
import { join as join2, basename as basename2 } from "node:path";
|
|
2774
|
+
import { join as join2, basename as basename2, isAbsolute, relative, sep } from "node:path";
|
|
2772
2775
|
var DEFAULT_PARENT = {
|
|
2773
2776
|
head: "neck",
|
|
2774
2777
|
neck: "torso",
|
|
@@ -2794,6 +2797,10 @@ function pngSize(buf) {
|
|
|
2794
2797
|
if (buf.readUInt32BE(12) !== 1229472850) throw new Error("not a PNG (IHDR missing)");
|
|
2795
2798
|
return [buf.readUInt32BE(16), buf.readUInt32BE(20)];
|
|
2796
2799
|
}
|
|
2800
|
+
function outsideDir(dir, file) {
|
|
2801
|
+
const r = relative(dir, join2(dir, file));
|
|
2802
|
+
return isAbsolute(file) || r === ".." || r.startsWith(".." + sep);
|
|
2803
|
+
}
|
|
2797
2804
|
async function importLayers(doc, dir, relPrefix = "layers") {
|
|
2798
2805
|
let specs;
|
|
2799
2806
|
try {
|
|
@@ -2806,6 +2813,7 @@ async function importLayers(doc, dir, relPrefix = "layers") {
|
|
|
2806
2813
|
let next = doc;
|
|
2807
2814
|
const parsed = [];
|
|
2808
2815
|
for (const s2 of specs) {
|
|
2816
|
+
if (outsideDir(dir, s2.file)) throw new Error(`layers.json file "${s2.file}" is outside the layers folder (${dir})`);
|
|
2809
2817
|
const buf = await readFile(join2(dir, s2.file));
|
|
2810
2818
|
const [w, h] = pngSize(buf);
|
|
2811
2819
|
const m = basename2(s2.file, ".png").match(/^(?:(\d+)_)?(.+)$/);
|
|
@@ -4062,7 +4070,7 @@ async function buildAtlas(doc, assetsDir, outDir, opts = {}) {
|
|
|
4062
4070
|
|
|
4063
4071
|
// src/spine-import.ts
|
|
4064
4072
|
import { readFile as readFile7, stat, writeFile as writeFile5 } from "node:fs/promises";
|
|
4065
|
-
import { basename as basename4, dirname as dirname6, extname, join as join8, relative } from "node:path";
|
|
4073
|
+
import { basename as basename4, dirname as dirname6, extname, join as join8, relative as relative2 } from "node:path";
|
|
4066
4074
|
|
|
4067
4075
|
// src/ktx2.ts
|
|
4068
4076
|
import { readFile as readFile6 } from "node:fs/promises";
|
|
@@ -4211,13 +4219,13 @@ var Ids = class {
|
|
|
4211
4219
|
var spineToDoc = (x, y, originX, originY) => [x + originX, originY - y];
|
|
4212
4220
|
var r4 = (v) => Math.round(v * 1e6) / 1e6 + 0;
|
|
4213
4221
|
var hex8 = (c) => `#${(c ?? "ffffffff").toLowerCase().padEnd(8, "f").slice(0, 8)}`;
|
|
4214
|
-
function importSpine(
|
|
4222
|
+
function importSpine(json2, atlasText, opts = {}) {
|
|
4215
4223
|
const warnings = [];
|
|
4216
4224
|
const atlas = parseAtlas(atlasText);
|
|
4217
|
-
const version = String(
|
|
4218
|
-
if (
|
|
4225
|
+
const version = String(json2.skeleton?.spine ?? "4.0");
|
|
4226
|
+
if (json2.physics?.length) warnings.push(`${json2.physics.length} Spine physics constraints are not imported; bake their motion before delivery`);
|
|
4219
4227
|
const major = parseFloat(version);
|
|
4220
|
-
const bounds = { x:
|
|
4228
|
+
const bounds = { x: json2.skeleton?.x ?? 0, y: json2.skeleton?.y ?? 0, w: json2.skeleton?.width ?? 0, h: json2.skeleton?.height ?? 0 };
|
|
4221
4229
|
const margin = opts.margin ?? 0;
|
|
4222
4230
|
let originX = -bounds.x + margin, originY = bounds.y + bounds.h + margin;
|
|
4223
4231
|
const toDoc = (x, y) => spineToDoc(x, y, originX, originY);
|
|
@@ -4234,7 +4242,7 @@ function importSpine(json, atlasText, opts = {}) {
|
|
|
4234
4242
|
}
|
|
4235
4243
|
return id;
|
|
4236
4244
|
};
|
|
4237
|
-
const spineBones =
|
|
4245
|
+
const spineBones = json2.bones ?? [];
|
|
4238
4246
|
const boneIndexByName = new Map(spineBones.map((b, i) => [b.name, i]));
|
|
4239
4247
|
const bones = [{ id: ROOT, transform: { x: r4(originX), y: r4(originY) }, role: "root" }];
|
|
4240
4248
|
for (const b of spineBones) {
|
|
@@ -4247,7 +4255,7 @@ function importSpine(json, atlasText, opts = {}) {
|
|
|
4247
4255
|
...b.color ? { color: b.color } : {}
|
|
4248
4256
|
});
|
|
4249
4257
|
}
|
|
4250
|
-
const skins = Array.isArray(
|
|
4258
|
+
const skins = Array.isArray(json2.skins) ? json2.skins : Object.entries(json2.skins ?? {}).map(([name, attachments2]) => ({ name, attachments: attachments2 }));
|
|
4251
4259
|
const selectedSkin = opts.skin ? skins.find((s2) => s2.name === opts.skin) : skins.find((s2) => s2.name === "default") ?? skins[0];
|
|
4252
4260
|
if (opts.skin && !selectedSkin) throw new Error(`Spine skin "${opts.skin}" not found; available: ${skins.map((s2) => s2.name).join(", ")}`);
|
|
4253
4261
|
const lookupSkins = [selectedSkin, skins.find((s2) => s2.name === "default")].filter((s2, i, list) => s2 && list.indexOf(s2) === i);
|
|
@@ -4271,7 +4279,7 @@ function importSpine(json, atlasText, opts = {}) {
|
|
|
4271
4279
|
const resolved = skinnedName.get(`${slot} ${name}`);
|
|
4272
4280
|
return resolved === void 0 ? null : attachmentId(slot, resolved);
|
|
4273
4281
|
};
|
|
4274
|
-
const spineSlots =
|
|
4282
|
+
const spineSlots = json2.slots ?? [];
|
|
4275
4283
|
const slotIndexByName = new Map(spineSlots.map((s2, i) => [s2.name, i]));
|
|
4276
4284
|
const blendOf = (b) => b === "additive" ? "add" : b === "multiply" ? "multiply" : b === "screen" ? "screen" : "normal";
|
|
4277
4285
|
const slots = spineSlots.map((s2, i) => ({
|
|
@@ -4437,7 +4445,7 @@ function importSpine(json, atlasText, opts = {}) {
|
|
|
4437
4445
|
}
|
|
4438
4446
|
}
|
|
4439
4447
|
const activeConstraint = (kind, c) => !c.skin || lookupSkins.some((s2) => s2[kind]?.includes(c.name));
|
|
4440
|
-
const ik = (
|
|
4448
|
+
const ik = (json2.ik ?? []).filter((c) => activeConstraint("ik", c)).map((c) => ({
|
|
4441
4449
|
id: ikIds.get(c.name),
|
|
4442
4450
|
order: c.order ?? 0,
|
|
4443
4451
|
bones: c.bones.map((b) => boneIds.get(b)),
|
|
@@ -4450,7 +4458,7 @@ function importSpine(json, atlasText, opts = {}) {
|
|
|
4450
4458
|
stretch: !!c.stretch,
|
|
4451
4459
|
uniform: !!c.uniform
|
|
4452
4460
|
}));
|
|
4453
|
-
const transform = (
|
|
4461
|
+
const transform = (json2.transform ?? []).filter((c) => activeConstraint("transform", c)).map((c) => ({
|
|
4454
4462
|
id: tcIds.get(c.name),
|
|
4455
4463
|
order: c.order ?? 0,
|
|
4456
4464
|
bones: c.bones.map((b) => boneIds.get(b)),
|
|
@@ -4470,7 +4478,7 @@ function importSpine(json, atlasText, opts = {}) {
|
|
|
4470
4478
|
mixScaleY: c.mixScaleY ?? c.mixScaleX ?? c.scaleMix ?? 1,
|
|
4471
4479
|
mixShearY: c.mixShearY ?? c.shearMix ?? 1
|
|
4472
4480
|
}));
|
|
4473
|
-
const path = (
|
|
4481
|
+
const path = (json2.path ?? []).filter((c) => activeConstraint("path", c)).map((c) => ({
|
|
4474
4482
|
id: pcIds.get(c.name),
|
|
4475
4483
|
order: c.order ?? 0,
|
|
4476
4484
|
bones: c.bones.map((b) => boneIds.get(b)),
|
|
@@ -4524,7 +4532,7 @@ function importSpine(json, atlasText, opts = {}) {
|
|
|
4524
4532
|
return keys;
|
|
4525
4533
|
};
|
|
4526
4534
|
const animations = [];
|
|
4527
|
-
for (const [animName, a] of Object.entries(
|
|
4535
|
+
for (const [animName, a] of Object.entries(json2.animations ?? {})) {
|
|
4528
4536
|
const tracks = [], events = [];
|
|
4529
4537
|
let duration = 0;
|
|
4530
4538
|
const seen = (frames) => {
|
|
@@ -4805,7 +4813,7 @@ function importSpine(json, atlasText, opts = {}) {
|
|
|
4805
4813
|
const build = () => ({
|
|
4806
4814
|
format: "energy8.rig",
|
|
4807
4815
|
version: 2,
|
|
4808
|
-
meta: { name: opts.name ??
|
|
4816
|
+
meta: { name: opts.name ?? json2.skeleton?.hash ?? "spine", width, height, notes: opts.notes ?? `imported from Spine ${version}` },
|
|
4809
4817
|
assets: [...assets.values()],
|
|
4810
4818
|
bones,
|
|
4811
4819
|
slots,
|
|
@@ -4858,7 +4866,7 @@ async function firstExisting(candidates, what) {
|
|
|
4858
4866
|
throw new Error(`${what} not found, tried: ${candidates.join(", ")}`);
|
|
4859
4867
|
}
|
|
4860
4868
|
async function importSpineProject(opts) {
|
|
4861
|
-
const
|
|
4869
|
+
const json2 = JSON.parse(await readFile7(opts.json, "utf8"));
|
|
4862
4870
|
const stem = opts.json.replace(/\.json$/i, "");
|
|
4863
4871
|
const atlasPath = opts.atlas ?? await firstExisting([stem + ".atlas", stem + ".atlas.txt"], "atlas");
|
|
4864
4872
|
const atlasText = await readFile7(atlasPath, "utf8");
|
|
@@ -4868,6 +4876,7 @@ async function importSpineProject(opts) {
|
|
|
4868
4876
|
const rigDir = dirname6(opts.rigPath);
|
|
4869
4877
|
const pagePaths = /* @__PURE__ */ new Map();
|
|
4870
4878
|
for (const page2 of pages) {
|
|
4879
|
+
if (!opts.image && outsideDir(dirname6(atlasPath), page2.name)) throw new Error(`atlas page "${page2.name}" is outside the atlas folder (${dirname6(atlasPath)})`);
|
|
4871
4880
|
const pageStem = join8(dirname6(atlasPath), page2.name.replace(/\.[a-z0-9]+$/i, ""));
|
|
4872
4881
|
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}`);
|
|
4873
4882
|
let pngPath = imagePath;
|
|
@@ -4880,10 +4889,10 @@ async function importSpineProject(opts) {
|
|
|
4880
4889
|
}
|
|
4881
4890
|
pagePaths.set(page2.name, pngPath);
|
|
4882
4891
|
}
|
|
4883
|
-
const pageImage = (page2) =>
|
|
4892
|
+
const pageImage = (page2) => relative2(rigDir, pagePaths.get(page2)).split("\\").join("/");
|
|
4884
4893
|
const name = opts.name ?? basename4(stem);
|
|
4885
4894
|
const imagePaths = [...pagePaths.values()];
|
|
4886
|
-
return { ...importSpine(
|
|
4895
|
+
return { ...importSpine(json2, atlasText, { name, skin: opts.skin, pageImage, margin: opts.margin, notes: opts.notes }), imagePath: imagePaths[0], imagePaths };
|
|
4887
4896
|
}
|
|
4888
4897
|
|
|
4889
4898
|
// src/rig-templates.ts
|
|
@@ -5573,8 +5582,8 @@ async function load(path) {
|
|
|
5573
5582
|
}
|
|
5574
5583
|
var save = (path, doc) => saveJson(path, doc);
|
|
5575
5584
|
var exists2 = (p) => stat2(p).then(() => true, () => false);
|
|
5576
|
-
var resolveFrom = (rigPath, p) =>
|
|
5577
|
-
var srcFor = (rigPath, file) =>
|
|
5585
|
+
var resolveFrom = (rigPath, p) => isAbsolute2(p) ? p : join9(dirname8(rigPath), p);
|
|
5586
|
+
var srcFor = (rigPath, file) => relative3(dirname8(rigPath), file).split("\\").join("/");
|
|
5578
5587
|
var libraryFor = (rigPath, lib) => lib ? resolveFrom(rigPath, lib) : join9(dirname8(rigPath), "presets.json");
|
|
5579
5588
|
async function pngInfo(rigPath, file) {
|
|
5580
5589
|
const abs = resolveFrom(rigPath, file);
|
|
@@ -5825,9 +5834,9 @@ tool(
|
|
|
5825
5834
|
margin: z3.number().optional().describe("canvas margin around the skeleton bounds, px"),
|
|
5826
5835
|
notes: z3.string().optional()
|
|
5827
5836
|
},
|
|
5828
|
-
async ({ path, json, atlas, image, name, skin: skin2, margin, notes }) => {
|
|
5837
|
+
async ({ path, json: json2, atlas, image, name, skin: skin2, margin, notes }) => {
|
|
5829
5838
|
try {
|
|
5830
|
-
const r = await importSpineProject({ json: resolveFrom(path,
|
|
5839
|
+
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 });
|
|
5831
5840
|
const res = await create(path, parse(r.doc));
|
|
5832
5841
|
if (r.warnings.length) res.text += `
|
|
5833
5842
|
warnings:
|
|
@@ -6599,40 +6608,41 @@ tool(
|
|
|
6599
6608
|
}
|
|
6600
6609
|
);
|
|
6601
6610
|
|
|
6611
|
+
// src/rig-version.ts
|
|
6612
|
+
import { stat as stat3 } from "node:fs/promises";
|
|
6613
|
+
async function rigVersion(abs) {
|
|
6614
|
+
try {
|
|
6615
|
+
const s2 = await stat3(abs);
|
|
6616
|
+
return `${s2.mtimeMs}:${s2.size}`;
|
|
6617
|
+
} catch {
|
|
6618
|
+
return "missing";
|
|
6619
|
+
}
|
|
6620
|
+
}
|
|
6621
|
+
|
|
6602
6622
|
// src/editor/server.ts
|
|
6603
6623
|
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" };
|
|
6604
|
-
|
|
6624
|
+
var json = (res, status, body) => {
|
|
6625
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
6626
|
+
res.end(JSON.stringify(body));
|
|
6627
|
+
};
|
|
6628
|
+
function createEditorHandler(opts) {
|
|
6605
6629
|
const root3 = resolve2(opts.root);
|
|
6606
6630
|
const assets = resolve2(opts.assets ?? fileURLToPath3(new URL("../..", import.meta.url)));
|
|
6607
6631
|
const inside = (rel) => {
|
|
6608
6632
|
const abs = resolve2(root3, rel);
|
|
6609
|
-
return abs === root3 || abs.startsWith(root3 +
|
|
6610
|
-
};
|
|
6611
|
-
const version = async (abs) => {
|
|
6612
|
-
try {
|
|
6613
|
-
const s2 = await stat3(abs);
|
|
6614
|
-
return `${s2.mtimeMs}:${s2.size}`;
|
|
6615
|
-
} catch {
|
|
6616
|
-
return "missing";
|
|
6617
|
-
}
|
|
6633
|
+
return abs === root3 || abs.startsWith(root3 + sep2) ? abs : void 0;
|
|
6618
6634
|
};
|
|
6619
6635
|
const readDoc = async (abs) => RigDocument.parse(JSON.parse(await readFile10(abs, "utf8")));
|
|
6620
6636
|
const readDocAt = async (abs) => {
|
|
6621
|
-
let v = await
|
|
6637
|
+
let v = await rigVersion(abs);
|
|
6622
6638
|
for (let i = 0; i < 3; i++) {
|
|
6623
6639
|
const doc = await readDoc(abs);
|
|
6624
|
-
const after = await
|
|
6640
|
+
const after = await rigVersion(abs);
|
|
6625
6641
|
if (after === v) return { doc, version: v };
|
|
6626
6642
|
v = after;
|
|
6627
6643
|
}
|
|
6628
6644
|
return { doc: await readDoc(abs), version: v };
|
|
6629
6645
|
};
|
|
6630
|
-
const json = (res, status, body2) => {
|
|
6631
|
-
res.writeHead(status, { "content-type": "application/json" });
|
|
6632
|
-
res.end(JSON.stringify(body2));
|
|
6633
|
-
};
|
|
6634
|
-
let allowedHosts = /* @__PURE__ */ new Set();
|
|
6635
|
-
const hostOk = (req) => allowedHosts.has(String(req.headers.host ?? "").toLowerCase());
|
|
6636
6646
|
const clients = /* @__PURE__ */ new Map();
|
|
6637
6647
|
const watchers = /* @__PURE__ */ new Map();
|
|
6638
6648
|
function ensureWatch(abs) {
|
|
@@ -6644,7 +6654,7 @@ async function startEditorServer(opts) {
|
|
|
6644
6654
|
const schedule = () => {
|
|
6645
6655
|
clearTimeout(entry.timer);
|
|
6646
6656
|
entry.timer = setTimeout(async () => {
|
|
6647
|
-
const v = await
|
|
6657
|
+
const v = await rigVersion(abs);
|
|
6648
6658
|
if (v === entry.last) return;
|
|
6649
6659
|
entry.last = v;
|
|
6650
6660
|
for (const res of clients.get(abs) ?? []) res.write(`event: changed
|
|
@@ -6653,7 +6663,7 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6653
6663
|
`);
|
|
6654
6664
|
}, 50);
|
|
6655
6665
|
};
|
|
6656
|
-
|
|
6666
|
+
rigVersion(abs).then((v) => {
|
|
6657
6667
|
entry.last = v;
|
|
6658
6668
|
});
|
|
6659
6669
|
watchers.set(abs, entry);
|
|
@@ -6679,7 +6689,7 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6679
6689
|
name = String(JSON.parse(await readFile10(abs, "utf8")).meta?.name ?? "?");
|
|
6680
6690
|
} catch {
|
|
6681
6691
|
}
|
|
6682
|
-
out.push({ rig:
|
|
6692
|
+
out.push({ rig: relative4(root3, abs).split(sep2).join("/"), name, mtimeMs: (await stat4(abs)).mtimeMs });
|
|
6683
6693
|
}
|
|
6684
6694
|
};
|
|
6685
6695
|
await walk(root3);
|
|
@@ -6713,20 +6723,22 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6713
6723
|
}
|
|
6714
6724
|
return Buffer.concat(chunks);
|
|
6715
6725
|
}
|
|
6716
|
-
|
|
6726
|
+
async function handle(req, res, rawUrl = req.url ?? "/") {
|
|
6717
6727
|
try {
|
|
6718
|
-
|
|
6719
|
-
const url = new URL(req.url ?? "/", "http://x");
|
|
6728
|
+
const url = new URL(rawUrl, "http://x");
|
|
6720
6729
|
if (url.pathname === "/") {
|
|
6721
|
-
res.writeHead(302, { location: "
|
|
6722
|
-
return res.end();
|
|
6730
|
+
res.writeHead(302, { location: "editor.html" + url.search });
|
|
6731
|
+
return void res.end();
|
|
6732
|
+
}
|
|
6733
|
+
if (url.pathname.startsWith("/api/")) {
|
|
6734
|
+
await api(url, req, res);
|
|
6735
|
+
return;
|
|
6723
6736
|
}
|
|
6724
|
-
if (url.pathname.startsWith("/api/")) return await api(url, req, res);
|
|
6725
6737
|
const STATIC = /* @__PURE__ */ new Set(["/editor.html", "/dist/editor.js", "/dist/editor.js.map", "/dist/editor.css", "/dist/editor.css.map"]);
|
|
6726
6738
|
const abs = url.pathname.startsWith("/rig/") ? inside(decodeURIComponent(url.pathname.slice(5))) : STATIC.has(url.pathname) ? join10(assets, url.pathname) : void 0;
|
|
6727
6739
|
if (!abs) {
|
|
6728
6740
|
res.writeHead(403);
|
|
6729
|
-
return res.end("forbidden");
|
|
6741
|
+
return void res.end("forbidden");
|
|
6730
6742
|
}
|
|
6731
6743
|
try {
|
|
6732
6744
|
const data = await readFile10(abs);
|
|
@@ -6744,7 +6756,7 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6744
6756
|
}
|
|
6745
6757
|
json(res, 500, { isError: true, text: `ERROR: ${e.message}` });
|
|
6746
6758
|
}
|
|
6747
|
-
}
|
|
6759
|
+
}
|
|
6748
6760
|
async function api(url, req, res) {
|
|
6749
6761
|
const rigOf = (rel) => {
|
|
6750
6762
|
if (!rel) return void 0;
|
|
@@ -6761,7 +6773,7 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6761
6773
|
res.writeHead(403);
|
|
6762
6774
|
return res.end();
|
|
6763
6775
|
}
|
|
6764
|
-
if (!await
|
|
6776
|
+
if (!await stat4(abs).then(() => true, () => false)) return json(res, 404, { isError: true, text: `ERROR: ${url.searchParams.get("rig")} does not exist under ${root3}` });
|
|
6765
6777
|
ensureWatch(abs);
|
|
6766
6778
|
res.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", connection: "keep-alive" });
|
|
6767
6779
|
res.write(`: connected
|
|
@@ -6783,7 +6795,7 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6783
6795
|
if (!abs) return json(res, 403, { isError: true, text: "rig outside root" });
|
|
6784
6796
|
ensureWatch(abs);
|
|
6785
6797
|
return await withRigQueue(abs, async () => {
|
|
6786
|
-
if (baseVersion !== void 0 && await
|
|
6798
|
+
if (baseVersion !== void 0 && await rigVersion(abs) !== baseVersion) {
|
|
6787
6799
|
const at = await readDocAt(abs);
|
|
6788
6800
|
return json(res, 409, {
|
|
6789
6801
|
isError: true,
|
|
@@ -6792,8 +6804,15 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6792
6804
|
text: `ERROR: the rig changed on disk (expected ${baseVersion}, found ${at.version}); nothing was written`
|
|
6793
6805
|
});
|
|
6794
6806
|
}
|
|
6795
|
-
const
|
|
6796
|
-
|
|
6807
|
+
const tool2 = url.pathname.slice(10);
|
|
6808
|
+
let given = { ...args2 ?? {}, path: abs };
|
|
6809
|
+
try {
|
|
6810
|
+
if (opts.args) given = opts.args(tool2, given);
|
|
6811
|
+
} catch (e) {
|
|
6812
|
+
return json(res, 400, { isError: true, text: `ERROR: ${e instanceof Error ? e.message : String(e)}` });
|
|
6813
|
+
}
|
|
6814
|
+
const r = await callTool(tool2, given);
|
|
6815
|
+
return json(res, 200, { text: r.text, doc: r.doc, image: r.image?.toString("base64"), isError: r.isError, version: await rigVersion(abs) });
|
|
6797
6816
|
});
|
|
6798
6817
|
}
|
|
6799
6818
|
if (url.pathname === "/api/rigs" && req.method === "GET") return json(res, 200, await listRigs());
|
|
@@ -6804,11 +6823,11 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6804
6823
|
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" });
|
|
6805
6824
|
const abs = inside(rig);
|
|
6806
6825
|
if (!abs) return json(res, 403, { isError: true, text: "rig outside root" });
|
|
6807
|
-
if (await
|
|
6826
|
+
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` });
|
|
6808
6827
|
await mkdir7(dirname9(abs), { recursive: true });
|
|
6809
6828
|
const r = await callTool("rig_create", { path: abs, name: name ?? basename5(dirname9(abs)), width: width ?? 640, height: height ?? 700 });
|
|
6810
6829
|
ensureWatch(abs);
|
|
6811
|
-
return json(res, r.isError ? 400 : 200, { text: r.text, doc: r.doc, isError: r.isError, version: await
|
|
6830
|
+
return json(res, r.isError ? 400 : 200, { text: r.text, doc: r.doc, isError: r.isError, version: await rigVersion(abs) });
|
|
6812
6831
|
}
|
|
6813
6832
|
if (url.pathname === "/api/files" && req.method === "GET") {
|
|
6814
6833
|
const abs = rigOf(url.searchParams.get("rig"));
|
|
@@ -6822,8 +6841,8 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6822
6841
|
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` });
|
|
6823
6842
|
if (!String(req.headers["content-type"] ?? "").toLowerCase().startsWith("image/png")) return json(res, 415, { isError: true, text: "ERROR: upload needs content-type: image/png" });
|
|
6824
6843
|
const target = join10(dirname9(abs), name);
|
|
6825
|
-
if (!inside(
|
|
6826
|
-
if (await
|
|
6844
|
+
if (!inside(relative4(root3, target))) return json(res, 403, { isError: true, text: "file outside root" });
|
|
6845
|
+
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` });
|
|
6827
6846
|
const data = await bytes(req, UPLOAD_MAX);
|
|
6828
6847
|
if (!data) return json(res, 413, { isError: true, text: `ERROR: ${name} is larger than ${UPLOAD_MAX / 1048576} MB` });
|
|
6829
6848
|
let size;
|
|
@@ -6837,6 +6856,22 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6837
6856
|
}
|
|
6838
6857
|
json(res, 404, { isError: true, text: "unknown api route" });
|
|
6839
6858
|
}
|
|
6859
|
+
return { handle, close() {
|
|
6860
|
+
for (const w of watchers.values()) {
|
|
6861
|
+
clearTimeout(w.timer);
|
|
6862
|
+
w.w.close();
|
|
6863
|
+
}
|
|
6864
|
+
for (const set of clients.values()) for (const c of set) c.end();
|
|
6865
|
+
} };
|
|
6866
|
+
}
|
|
6867
|
+
async function startEditorServer(opts) {
|
|
6868
|
+
const editor = createEditorHandler(opts);
|
|
6869
|
+
let allowedHosts = /* @__PURE__ */ new Set();
|
|
6870
|
+
const server = createServer((req, res) => {
|
|
6871
|
+
if (!allowedHosts.has(String(req.headers.host ?? "").toLowerCase()))
|
|
6872
|
+
return json(res, 403, { isError: true, text: `ERROR: forbidden Host "${req.headers.host ?? ""}" \u2014 this server answers only on 127.0.0.1 / localhost` });
|
|
6873
|
+
void editor.handle(req, res);
|
|
6874
|
+
});
|
|
6840
6875
|
await new Promise((ok, fail2) => {
|
|
6841
6876
|
server.once("error", fail2);
|
|
6842
6877
|
server.listen(opts.port ?? 8765, "127.0.0.1", () => {
|
|
@@ -6847,11 +6882,7 @@ data: ${JSON.stringify({ version: v })}
|
|
|
6847
6882
|
const port2 = server.address().port;
|
|
6848
6883
|
allowedHosts = /* @__PURE__ */ new Set([`127.0.0.1:${port2}`, `localhost:${port2}`, `[::1]:${port2}`]);
|
|
6849
6884
|
return { port: port2, url: `http://127.0.0.1:${port2}`, close: async () => {
|
|
6850
|
-
|
|
6851
|
-
clearTimeout(w.timer);
|
|
6852
|
-
w.w.close();
|
|
6853
|
-
}
|
|
6854
|
-
for (const set of clients.values()) for (const c of set) c.end();
|
|
6885
|
+
editor.close();
|
|
6855
6886
|
await new Promise((r) => server.close(() => r()));
|
|
6856
6887
|
} };
|
|
6857
6888
|
}
|