@higgsfield/video-editor-mcp 1.0.5 → 1.0.7

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/dist/server.mjs CHANGED
@@ -41605,7 +41605,7 @@ function registerHostedProxyTools(server, url2, session = new LocalBrowserSessio
41605
41605
  const previous = connected?.client;
41606
41606
  const next = new Client({
41607
41607
  name: "higgsfield-video-editor-local",
41608
- version: "1.0.5"
41608
+ version: "1.0.7"
41609
41609
  });
41610
41610
  const transport = new StreamableHTTPClientTransport(new URL(url2), {
41611
41611
  requestInit: { headers: { Authorization: `Bearer ${token}` } }
@@ -41875,12 +41875,15 @@ function parseCliJson(result) {
41875
41875
  throw new Error(
41876
41876
  (result.stderr || result.stdout).trim().slice(-4e3) || `higgsedit exited ${result.code}`
41877
41877
  );
41878
- const text = result.stdout.trim();
41878
+ let value;
41879
41879
  try {
41880
- return JSON.parse(text);
41880
+ value = JSON.parse(result.stdout);
41881
41881
  } catch {
41882
- return text.slice(-2e4);
41882
+ throw new Error("higgsedit returned invalid or incomplete JSON.");
41883
41883
  }
41884
+ if (value === null || typeof value !== "object" || Array.isArray(value))
41885
+ throw new Error("higgsedit returned an unexpected JSON response; expected an object.");
41886
+ return value;
41884
41887
  }
41885
41888
  var cliJson = async (args, cwd, timeoutMs) => parseCliJson(await runCli(args, cwd, timeoutMs));
41886
41889
 
@@ -41893,8 +41896,8 @@ var directCspOrigins = () => cspOrigins;
41893
41896
  var CORS = {
41894
41897
  "access-control-allow-origin": "*",
41895
41898
  "access-control-allow-methods": "GET, HEAD, POST, PUT, DELETE, OPTIONS",
41896
- "access-control-allow-headers": "range, content-type",
41897
- "access-control-expose-headers": "content-range, content-length, accept-ranges, content-type",
41899
+ "access-control-allow-headers": "range, content-type, if-none-match, if-modified-since, if-range",
41900
+ "access-control-expose-headers": "content-range, content-length, accept-ranges, content-type, etag, last-modified, cache-control",
41898
41901
  // An https page reaching loopback (Private Network Access / Local Network Access preflights).
41899
41902
  "access-control-allow-private-network": "true",
41900
41903
  "cross-origin-resource-policy": "cross-origin",
@@ -41906,7 +41909,8 @@ var FORWARDED = [
41906
41909
  "content-length",
41907
41910
  "accept",
41908
41911
  "if-none-match",
41909
- "if-modified-since"
41912
+ "if-modified-since",
41913
+ "if-range"
41910
41914
  ];
41911
41915
  var DirectServer = class {
41912
41916
  constructor(origin, ports = DIRECT_PORTS) {
@@ -42245,7 +42249,7 @@ var restyleStyleSchema = external_exports.object({
42245
42249
  });
42246
42250
 
42247
42251
  // src/tools/video-editor-local/standalone-resource.ts
42248
- var packageVersion = "1.0.5";
42252
+ var packageVersion = "1.0.7";
42249
42253
  var VIDEO_EDITOR_RESOURCE_URI = packageVersion ? `ui://higgsfield/video-editor-v3-${encodeURIComponent(packageVersion)}.html` : "ui://higgsfield/video-editor-v3.html";
42250
42254
  function panelHtmlPath(env = process.env) {
42251
42255
  return env.VIDEO_EDITOR_LOCAL_HTML?.trim() || join5(import.meta.dirname, "index.html");
@@ -42296,10 +42300,48 @@ import { mkdtemp, rm as rm3, writeFile as writeFile4 } from "node:fs/promises";
42296
42300
  import { tmpdir } from "node:os";
42297
42301
  import { join as join6 } from "node:path";
42298
42302
  var MAX_CHUNK = 2 << 20;
42299
- var KEPT_HEADERS = ["content-type", "content-range", "content-length", "accept-ranges"];
42303
+ var KEPT_HEADERS = [
42304
+ "content-type",
42305
+ "content-range",
42306
+ "content-length",
42307
+ "accept-ranges",
42308
+ "etag",
42309
+ "last-modified",
42310
+ "cache-control"
42311
+ ];
42312
+ var CONDITIONAL_HEADERS = ["if-none-match", "if-modified-since", "if-range"];
42313
+ async function readReply(res, method) {
42314
+ if (method === "HEAD" || res.status === 304 || res.status === 204) return Buffer.alloc(0);
42315
+ if (Number(res.headers.get("content-length")) > MAX_CHUNK) {
42316
+ await res.body?.cancel();
42317
+ throw new Error("response exceeds the tool chunk limit; ask for a range");
42318
+ }
42319
+ const reader = res.body?.getReader();
42320
+ if (!reader) return Buffer.alloc(0);
42321
+ const chunks = [];
42322
+ let size = 0;
42323
+ try {
42324
+ for (; ; ) {
42325
+ const { done, value } = await reader.read();
42326
+ if (done) return Buffer.concat(chunks, size);
42327
+ size += value.byteLength;
42328
+ if (size > MAX_CHUNK) {
42329
+ await reader.cancel();
42330
+ throw new Error("response exceeds the tool chunk limit; ask for a range");
42331
+ }
42332
+ chunks.push(value);
42333
+ }
42334
+ } finally {
42335
+ reader.releaseLock();
42336
+ }
42337
+ }
42300
42338
  async function httpTool(origin, call) {
42301
42339
  checkPath(call.path);
42302
42340
  const headers = new Headers();
42341
+ for (const name of CONDITIONAL_HEADERS) {
42342
+ const value = call.headers?.[name];
42343
+ if (value !== void 0) headers.set(name, value);
42344
+ }
42303
42345
  if (call.rangeStart !== void 0) {
42304
42346
  const end = Math.min(call.rangeEnd ?? Infinity, call.rangeStart + MAX_CHUNK - 1);
42305
42347
  headers.set("range", `bytes=${call.rangeStart}-${end}`);
@@ -42322,9 +42364,7 @@ async function send(origin, method, path2, headers, body, signal) {
42322
42364
  await res.body?.cancel();
42323
42365
  throw new Error(STREAMING_IMPORT_REQUIRED);
42324
42366
  }
42325
- const bytes = Buffer.from(await res.arrayBuffer());
42326
- if (bytes.length > MAX_CHUNK && res.status !== 206)
42327
- throw new Error(`${path2}: ${bytes.length} bytes without a range; ask for a range`);
42367
+ const bytes = await readReply(res, method);
42328
42368
  const kept = {};
42329
42369
  for (const name of KEPT_HEADERS) {
42330
42370
  const value = res.headers.get(name);
@@ -42445,7 +42485,6 @@ var reload = (undo) => ({
42445
42485
  detectedAt: Date.now(),
42446
42486
  undo
42447
42487
  });
42448
- var NO_UNDO = { canUndo: false, canRedo: false, undoName: null, redoName: null };
42449
42488
  var EventFeed = class {
42450
42489
  constructor(origin) {
42451
42490
  this.origin = origin;
@@ -42494,14 +42533,14 @@ var EventFeed = class {
42494
42533
  holds(after) {
42495
42534
  return after >= (this.log[0]?.seq ?? this.seq + 1) - 1 && after <= this.seq;
42496
42535
  }
42497
- /** Events after `after`; waits up to `waitMs` for one. `after < 0` returns the current position at once. */
42536
+ /** Events after `after`; a new reader gets one full resync with current undo, not historical edits. */
42498
42537
  async next(after, waitMs) {
42499
- if (after >= 0 && !this.holds(after)) {
42500
- const undo = await fetch(`${this.origin}/api/history`).then(
42501
- (r) => r.json(),
42502
- () => NO_UNDO
42503
- );
42504
- return { seq: this.seq, events: [reload(undo)] };
42538
+ if (after < 0 || !this.holds(after)) {
42539
+ const seq = this.seq;
42540
+ const response = await fetch(`${this.origin}/api/history`, { signal: this.abort.signal });
42541
+ if (!response.ok) throw new Error(`history: ${response.status}`);
42542
+ const undo = await response.json();
42543
+ return { seq, events: [reload(undo)] };
42505
42544
  }
42506
42545
  if (after >= 0 && after >= this.seq) {
42507
42546
  await new Promise((resolve3) => {
@@ -43075,7 +43114,7 @@ async function initProject(path2, roots, size = "1080x1920", fps = 30) {
43075
43114
  if (await hasProjectFile(dir)) return dir;
43076
43115
  cliPath();
43077
43116
  await mkdir5(dir, { recursive: true });
43078
- await cliJson(["init", dir, `--size=${size}`, `--fps=${fps}`], dir);
43117
+ await cliJson(["init", dir, `--size=${size}`, `--fps=${fps}`, "--json"], dir);
43079
43118
  return dir;
43080
43119
  }
43081
43120
  async function newFile(dir, name, place) {
@@ -43288,7 +43327,7 @@ ${LOCAL_PROJECT_GUIDE}`);
43288
43327
  annotations: { readOnlyHint: true, openWorldHint: false }
43289
43328
  },
43290
43329
  async ({ project }) => scoped(project, async (session) => {
43291
- const outline = await cliJson(["outline", ".", "--json"], session.root);
43330
+ const outline = external_exports.looseObject({ text: external_exports.string().optional() }).parse(await cliJson(["outline", ".", "--json"], session.root));
43292
43331
  const { text, ...rest } = outline;
43293
43332
  return reply({ ...describe3(session), ...rest }, text ?? JSON.stringify(rest));
43294
43333
  })
@@ -43444,6 +43483,7 @@ ${LOCAL_PROJECT_GUIDE}`);
43444
43483
  [
43445
43484
  "sheet",
43446
43485
  ".",
43486
+ "--json",
43447
43487
  `--times=${times.join(",")}`,
43448
43488
  "-o",
43449
43489
  out,
@@ -43792,6 +43832,11 @@ function registerPanelRelay(server, store, stateDirectory, direct) {
43792
43832
  path: external_exports.string().min(1).max(4096),
43793
43833
  rangeStart: external_exports.number().int().nonnegative().optional(),
43794
43834
  rangeEnd: external_exports.number().int().nonnegative().optional(),
43835
+ headers: external_exports.object({
43836
+ "if-none-match": external_exports.string().max(8192).optional(),
43837
+ "if-modified-since": external_exports.string().max(128).optional(),
43838
+ "if-range": external_exports.string().max(128).optional()
43839
+ }).optional(),
43795
43840
  body: external_exports.string().max(64 * 1024 * 1024).optional(),
43796
43841
  project: sessionArg
43797
43842
  },
@@ -44585,7 +44630,7 @@ function createEditorUpdates(options, start = (done) => new Promise((resolve3, r
44585
44630
  }
44586
44631
  }
44587
44632
  return {
44588
- runningVersion: options?.version ?? "1.0.5" ?? "Source checkout",
44633
+ runningVersion: options?.version ?? "1.0.7" ?? "Source checkout",
44589
44634
  readyVersion: options && selectedDist(options) !== options.dist ? state.active : null,
44590
44635
  channel: options?.channel ?? null,
44591
44636
  supported: options !== null,
@@ -44649,7 +44694,7 @@ var handle = serveStdio(
44649
44694
  const server = new McpServer(
44650
44695
  {
44651
44696
  name: "Higgsfield Video Editor (local)",
44652
- version: "1.0.5"
44697
+ version: "1.0.7"
44653
44698
  },
44654
44699
  { instructions: VIDEO_EDITOR_LOCAL_INSTRUCTIONS }
44655
44700
  );
@@ -17,7 +17,7 @@ import {
17
17
  } from "./chunk-QLPCYN6K.mjs";
18
18
  import {
19
19
  analyze,
20
- applyEdit
20
+ applyEdits
21
21
  } from "./chunk-VI6IV6JP.mjs";
22
22
  import {
23
23
  inside,
@@ -94,73 +94,7 @@ var WIDGET_DIST = join(EDITOR, "dist-mcp");
94
94
  var WIDGET = join(WIDGET_DIST, "mcp-app.js");
95
95
 
96
96
  // src/history.ts
97
- import { readFileSync as readFileSync2, renameSync, writeFileSync } from "node:fs";
98
- import { join as join2 } from "node:path";
99
- var EditHistory = class {
100
- constructor(dir, onWrite) {
101
- this.dir = dir;
102
- this.onWrite = onWrite;
103
- }
104
- dir;
105
- onWrite;
106
- undoStack = [];
107
- redoStack = [];
108
- begin(name) {
109
- const entry = { name, files: /* @__PURE__ */ new Map() };
110
- return {
111
- onWrite: (path, text) => {
112
- const prior = entry.files.get(path);
113
- entry.files.set(path, { before: prior?.before ?? readFileSync2(join2(this.dir, path), "utf8"), after: text });
114
- this.onWrite?.(path, text);
115
- },
116
- commit: () => {
117
- if (!entry.files.size) return;
118
- this.undoStack.push(entry);
119
- this.redoStack.length = 0;
120
- },
121
- rollback: () => {
122
- for (const [path, texts] of entry.files) this.write(path, texts.before);
123
- }
124
- };
125
- }
126
- state() {
127
- return {
128
- canUndo: this.undoStack.length > 0,
129
- canRedo: this.redoStack.length > 0,
130
- undoName: this.undoStack.at(-1)?.name ?? null,
131
- redoName: this.redoStack.at(-1)?.name ?? null
132
- };
133
- }
134
- undo() {
135
- return this.step(this.undoStack, this.redoStack, "after", "before");
136
- }
137
- redo() {
138
- return this.step(this.redoStack, this.undoStack, "before", "after");
139
- }
140
- step(from, to, expect, write) {
141
- const entry = from.at(-1);
142
- if (!entry) return { ok: false, reason: `nothing to ${write === "before" ? "undo" : "redo"}` };
143
- for (const [path, texts] of entry.files) {
144
- let current;
145
- try {
146
- current = readFileSync2(join2(this.dir, path), "utf8");
147
- } catch {
148
- return { ok: false, reason: `${path} no longer exists` };
149
- }
150
- if (current !== texts[expect]) return { ok: false, reason: `${path} changed outside the editor since ${entry.name}` };
151
- }
152
- for (const [path, texts] of entry.files) this.write(path, texts[write]);
153
- from.pop();
154
- to.push(entry);
155
- return { ok: true, written: [...entry.files.keys()] };
156
- }
157
- write(path, text) {
158
- const target = join2(this.dir, path);
159
- this.onWrite?.(path, text);
160
- writeFileSync(`${target}.higgsedit-${process.pid}.tmp`, text);
161
- renameSync(`${target}.higgsedit-${process.pid}.tmp`, target);
162
- }
163
- };
97
+ import { EditHistory } from "./history.mjs";
164
98
 
165
99
  import { Proxies } from "./proxy.mjs";
166
100
 
@@ -300,15 +234,15 @@ async function startDev(dir, port = 0, options = {}) {
300
234
  if (url.pathname !== "/api/edit" || req.method !== "POST") return false;
301
235
  const body = JSON.parse((await readBody(req)).toString());
302
236
  const tx = history.begin(body.name ?? "Edit");
303
- const results = [];
304
- for (const { key, edit } of body.edits) {
305
- const r = applyEdit(dir, key, edit, { onWrite: tx.onWrite, ...body.dryRun ? { dryRun: true } : {} });
306
- results.push(r);
307
- if (!r.ok) break;
237
+ try {
238
+ const results = applyEdits(dir, body.edits, { onWrite: tx.onWrite, ...body.dryRun ? { dryRun: true } : {} });
239
+ if (results.every((r) => r.ok)) tx.commit();
240
+ else tx.rollback();
241
+ json(res, 200, { ok: results.every((r) => r.ok), results, undo: history.state() });
242
+ } catch (error) {
243
+ tx.rollback();
244
+ throw error;
308
245
  }
309
- if (results.every((r) => r.ok)) tx.commit();
310
- else tx.rollback();
311
- json(res, 200, { ok: results.every((r) => r.ok), results, undo: history.state() });
312
246
  return true;
313
247
  },
314
248
  (req, res, url) => {
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { createHash } from "node:crypto";
2
3
  import { createRequire as __higgseditRequire } from "node:module";
3
4
  import { fileURLToPath as __higgseditPath } from "node:url";
4
5
  const require = __higgseditRequire(import.meta.url);
@@ -131,10 +132,14 @@ var TYPES = {
131
132
  };
132
133
  function sendFile(req, res, file) {
133
134
  let size;
135
+ let etag;
136
+ let modified;
134
137
  try {
135
- const st = statSync(file);
138
+ const st = statSync(file, { bigint: true });
136
139
  if (!st.isFile()) throw new Error("not a file");
137
- size = st.size;
140
+ size = Number(st.size);
141
+ etag = `"${createHash("sha256").update(`${st.dev}:${st.ino}:${st.size}:${st.mtimeNs}:${st.ctimeNs}`).digest("hex")}"`;
142
+ modified = Math.floor(Number(st.mtimeNs > st.ctimeNs ? st.mtimeNs : st.ctimeNs) / 1e9) * 1e3;
138
143
  } catch {
139
144
  res.statusCode = 404;
140
145
  res.end("not found");
@@ -143,11 +148,23 @@ function sendFile(req, res, file) {
143
148
  res.setHeader("content-type", TYPES[extname(file).toLowerCase()] ?? "application/octet-stream");
144
149
  res.setHeader("accept-ranges", "bytes");
145
150
  res.setHeader("cache-control", "no-cache");
146
- const range = /^bytes=(\d*)-(\d*)$/.exec(req.headers.range ?? "");
151
+ res.setHeader("etag", etag);
152
+ res.setHeader("last-modified", new Date(modified).toUTCString());
153
+ const readable = req.method === "GET" || req.method === "HEAD";
154
+ const noneMatch = req.headers["if-none-match"];
155
+ const unmodified = noneMatch !== void 0 ? noneMatch.split(",").some((value) => value.trim() === "*" || value.trim().replace(/^W\//, "") === etag) : Date.parse(req.headers["if-modified-since"] ?? "") >= modified;
156
+ if (readable && unmodified) {
157
+ res.statusCode = 304;
158
+ res.end();
159
+ return;
160
+ }
161
+ const ifRange = req.headers["if-range"];
162
+ const sameRange = !ifRange || ifRange === etag || modified <= Date.now() - 6e4 && typeof ifRange === "string" && Date.parse(ifRange) === modified;
163
+ const range = req.method === "GET" && sameRange ? /^bytes=(\d*)-(\d*)$/.exec(req.headers.range ?? "") : null;
147
164
  if (range) {
148
- const start = range[1] ? Number(range[1]) : size - Number(range[2]);
165
+ const start = range[1] ? Number(range[1]) : Math.max(0, size - Number(range[2]));
149
166
  const end = range[1] && range[2] ? Math.min(Number(range[2]), size - 1) : size - 1;
150
- if (start > end || start >= size) {
167
+ if (!range[1] && !Number(range[2]) || !Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start > end || start >= size) {
151
168
  res.statusCode = 416;
152
169
  res.setHeader("content-range", `bytes */${size}`);
153
170
  res.end();
@@ -242191,7 +242191,7 @@ function literalAt(root, path, at) {
242191
242191
  }
242192
242192
  return node;
242193
242193
  }
242194
- function setProp(project, landed, scope, name, value) {
242194
+ function setProp(project, landed, scope, name, value, validateOnly = false) {
242195
242195
  const { tag } = landed;
242196
242196
  const at = line(tag);
242197
242197
  const [prop, ...path] = name.split(".");
@@ -242210,18 +242210,18 @@ function setProp(project, landed, scope, name, value) {
242210
242210
  if (ctx.component && ctx.component.callSites.length > 1) {
242211
242211
  refuse("shared-component", `<${tagName(tag)}> is inside <${ctx.component.name}>, used at ${ctx.component.callSites.length} call sites; pass ${prop} as a prop to edit one instance`, tag);
242212
242212
  }
242213
- addAttribute(tag, prop, initializerText(value));
242213
+ if (!validateOnly) addAttribute(tag, prop, initializerText(value));
242214
242214
  return at;
242215
242215
  }
242216
242216
  const init = attr.getInitializer();
242217
242217
  if (!init) {
242218
242218
  if (path.length) refuse("invalid", `${prop} is a bare boolean`, tag);
242219
242219
  if (remove) {
242220
- attr.remove();
242220
+ if (!validateOnly) attr.remove();
242221
242221
  return at;
242222
242222
  }
242223
242223
  if (value === true) return at;
242224
- attr.setInitializer(initializerText(value));
242224
+ if (!validateOnly) attr.setInitializer(initializerText(value));
242225
242225
  return at;
242226
242226
  }
242227
242227
  const expr = init.isKind(import_ts_morph3.SyntaxKind.JsxExpression) ? init.getExpression() : init;
@@ -242230,12 +242230,13 @@ function setProp(project, landed, scope, name, value) {
242230
242230
  if (ctx.component && ctx.component.callSites.length > 1) {
242231
242231
  refuse("shared-component", `${prop} is a literal inside <${ctx.component.name}>, which has ${ctx.component.callSites.length} call sites; pass it as a prop to edit one instance`, attr, attr.getText());
242232
242232
  }
242233
- if (remove) attr.remove();
242234
- else if (path.length) {
242233
+ if (remove) {
242234
+ if (!validateOnly) attr.remove();
242235
+ } else if (path.length) {
242235
242236
  const node = literalAt(expr, path, attr);
242236
- node.replaceWithText(literalText(value));
242237
+ if (!validateOnly) node.replaceWithText(literalText(value));
242237
242238
  } else {
242238
- attr.setInitializer(initializerText(value));
242239
+ if (!validateOnly) attr.setInitializer(initializerText(value));
242239
242240
  }
242240
242241
  return at;
242241
242242
  }
@@ -242244,7 +242245,7 @@ function setProp(project, landed, scope, name, value) {
242244
242245
  const forwarded = forwardedProp(expr, ctx);
242245
242246
  if (forwarded) {
242246
242247
  const site = callSiteFor(ctx, scope, attr);
242247
- return setProp(project, site, site.scope, [forwarded, ...path].join("."), value);
242248
+ return setProp(project, site, site.scope, [forwarded, ...path].join("."), value, validateOnly);
242248
242249
  }
242249
242250
  return refuse("expression", `code-driven: ${prop} is bound to an expression`, attr, attr.getText());
242250
242251
  }
@@ -242262,13 +242263,14 @@ function saidRange(child) {
242262
242263
  return [child.getPos() + lead, child.getEnd() - trail];
242263
242264
  }
242264
242265
  var jsxText = (text) => /^[^\s{}<>&][^\n{}<>&]*[^\s{}<>&]$|^[^\s{}<>&]$/.test(text) ? text : `{${JSON.stringify(text)}}`;
242265
- function setText(project, landed, scope, text) {
242266
+ function setText(project, landed, scope, text, validateOnly = false) {
242266
242267
  const { tag } = landed;
242267
242268
  const at = line(tag);
242268
242269
  const element = elementOf(tag);
242269
242270
  const sf = tag.getSourceFile();
242270
242271
  if (element.isKind(import_ts_morph3.SyntaxKind.JsxSelfClosingElement)) {
242271
242272
  if (tagName(tag) !== "text") refuse("invalid", `<${tagName(tag)}> has no text`, tag);
242273
+ if (validateOnly) return at;
242272
242274
  const opening = element.getText().replace(/\s*\/>$/, ">");
242273
242275
  sf.replaceText([element.getStart(), element.getEnd()], `${opening}${jsxText(text)}</${tagName(tag)}>`);
242274
242276
  return at;
@@ -242279,7 +242281,7 @@ function setText(project, landed, scope, text) {
242279
242281
  if (expressions.length) {
242280
242282
  const e = expressions[0].asKindOrThrow(import_ts_morph3.SyntaxKind.JsxExpression).getExpressionOrThrow();
242281
242283
  if (isLiteral(e) && said.length === 1) {
242282
- e.replaceWithText(literalText(text));
242284
+ if (!validateOnly) e.replaceWithText(literalText(text));
242283
242285
  return at;
242284
242286
  }
242285
242287
  const loopVar = ctx.inLoop ? mentions(e, ctx.loopNames) : void 0;
@@ -242287,14 +242289,15 @@ function setText(project, landed, scope, text) {
242287
242289
  const forwarded = said.length === 1 ? forwardedProp(e, ctx) : void 0;
242288
242290
  if (forwarded) {
242289
242291
  const site = callSiteFor(ctx, scope, e);
242290
- if (forwarded === "children") return setText(project, site, site.scope, text);
242291
- return setProp(project, site, site.scope, forwarded, text);
242292
+ if (forwarded === "children") return setText(project, site, site.scope, text, validateOnly);
242293
+ return setProp(project, site, site.scope, forwarded, text, validateOnly);
242292
242294
  }
242293
242295
  refuse("expression", `code-driven: the text is computed by {${e.getText()}}`, e, e.getText());
242294
242296
  }
242295
242297
  if (ctx.component && ctx.component.callSites.length > 1) {
242296
242298
  refuse("shared-component", `this text is inside <${ctx.component.name}>, used at ${ctx.component.callSites.length} call sites`, tag);
242297
242299
  }
242300
+ if (validateOnly) return at;
242298
242301
  if (!said.length) {
242299
242302
  sf.insertText(element.getOpeningElement().getEnd(), jsxText(text));
242300
242303
  return at;
@@ -242407,7 +242410,22 @@ function finish(project, target, newId, options) {
242407
242410
  return { ok: true, written, target: target(), ...newId ? { id: newId } : {} };
242408
242411
  }
242409
242412
  function applyEdit(dir, key, edit, options = {}) {
242413
+ return applyProjectEdit(new SourceProject(dir), key, edit, options);
242414
+ }
242415
+ function applyEdits(dir, edits, options = {}) {
242410
242416
  const project = new SourceProject(dir);
242417
+ const results = [];
242418
+ for (const { key, edit } of edits) {
242419
+ const before = new Map([...project.files].map(([path, file]) => [path, file.sourceFile.getFullText()]));
242420
+ const result = applyProjectEdit(project, key, edit, { dryRun: true });
242421
+ if (result.ok) result.written = [...project.files].filter(([path, file]) => file.sourceFile.getFullText() !== before.get(path)).map(([path]) => path);
242422
+ results.push(result);
242423
+ if (!result.ok) return results;
242424
+ }
242425
+ if (!options.dryRun) project.save(options.onWrite);
242426
+ return results;
242427
+ }
242428
+ function applyProjectEdit(project, key, edit, options) {
242411
242429
  try {
242412
242430
  if (edit.op === "set-const") {
242413
242431
  const landed2 = setConst(project, edit);
@@ -242521,11 +242539,9 @@ function describeProp(project, found, scope, name) {
242521
242539
  const init = attr?.getInitializer();
242522
242540
  const expr = init?.isKind(import_ts_morph3.SyntaxKind.JsxExpression) ? init.getExpression() : init;
242523
242541
  const source = attr?.getText();
242524
- const probe = new SourceProject(project.dir, [...project.files.keys()]);
242525
242542
  try {
242526
- const target = findById(probe, idOf(found.tag));
242527
- const origin = line(target.tag);
242528
- const landed = setProp(probe, target, scope, name, 0);
242543
+ const origin = line(found.tag);
242544
+ const landed = setProp(project, found, scope, name, 0, true);
242529
242545
  const followed = landed.file !== origin.file || landed.line !== origin.line;
242530
242546
  if (!attr) return { name, kind: "absent", editable: true };
242531
242547
  if (followed && landed.id) {
@@ -242549,12 +242565,11 @@ var forwardedName = (expr) => {
242549
242565
  return void 0;
242550
242566
  };
242551
242567
  function describeText(project, found, scope) {
242552
- const probe = new SourceProject(project.dir, [...project.files.keys()]);
242553
242568
  const element = elementOf(found.tag);
242554
242569
  const said = element.isKind(import_ts_morph3.SyntaxKind.JsxElement) ? element.getJsxChildren().filter(isSaid) : [];
242555
242570
  const source = said.map((c) => c.getText()).join("").trim();
242556
242571
  try {
242557
- setText(probe, findById(probe, idOf(found.tag)), scope, "x");
242572
+ setText(project, found, scope, "x", true);
242558
242573
  const literal = said.length === 1 && said[0].isKind(import_ts_morph3.SyntaxKind.JsxText);
242559
242574
  return { name: "text", kind: literal || !said.length ? "literal" : "call-site", editable: true, ...source ? { source } : {} };
242560
242575
  } catch (error) {
@@ -242573,6 +242588,7 @@ export {
242573
242588
  stampProject,
242574
242589
  parseKey,
242575
242590
  applyEdit,
242591
+ applyEdits,
242576
242592
  analyze,
242577
242593
  sourceFacts
242578
242594
  };
@@ -4,13 +4,6 @@ import { fileURLToPath as __higgseditPath } from "node:url";
4
4
  const require = __higgseditRequire(import.meta.url);
5
5
  const __filename = __higgseditPath(import.meta.url);
6
6
  const __dirname = __filename.slice(0, __filename.lastIndexOf("/"));
7
- import {
8
- frame,
9
- render,
10
- sheet,
11
- verify
12
- } from "./chunk-4D6AXU4B.mjs";
13
- import "./chunk-QLPCYN6K.mjs";
14
7
  import {
15
8
  flag,
16
9
  parseArgs
@@ -18,21 +11,6 @@ import {
18
11
  import {
19
12
  CLI_MANIFEST
20
13
  } from "./chunk-FZ5U36SQ.mjs";
21
- import {
22
- check
23
- } from "./chunk-MK6PL7N4.mjs";
24
- import {
25
- stampProject
26
- } from "./chunk-VI6IV6JP.mjs";
27
- import {
28
- assertProject
29
- } from "./chunk-SIMO6I3D.mjs";
30
- import "./chunk-6BETMESC.mjs";
31
- import "./chunk-WZR5PZM7.mjs";
32
- import "./chunk-F7YUKEI5.mjs";
33
- import "./chunk-EKFC4S4M.mjs";
34
- import "./chunk-2VKQHJMM.mjs";
35
- import "./chunk-75WO2NOF.mjs";
36
14
 
37
15
  // src/main.ts
38
16
  import { resolve } from "node:path";
@@ -114,11 +92,13 @@ async function main(argv) {
114
92
  print(r, () => [r.written.length ? `created ${target}` : `${target}: nothing to create`, ...r.written.map((f) => ` ${f}`), ...r.skipped.map((f) => ` skipped (exists): ${f}`), `next: ${cd}npm install && npm run dev`].join("\n"));
115
93
  return 0;
116
94
  }
95
+ const { assertProject } = await import("./chunk-SIMO6I3D.mjs");
117
96
  const dir = assertProject(dirArg);
118
97
  const depth = flag(args, "depth") === "10" ? 10 : 8;
119
98
  const allowFontFallback = args.flags["allow-font-fallback"] === true;
120
99
  switch (command) {
121
100
  case "check": {
101
+ const { check } = await import("./chunk-MK6PL7N4.mjs");
122
102
  const r = await check(dir, { allowFontFallback });
123
103
  print(
124
104
  r,
@@ -137,6 +117,7 @@ async function main(argv) {
137
117
  return r.ok ? 0 : 1;
138
118
  }
139
119
  case "stamp": {
120
+ const { stampProject } = await import("./chunk-VI6IV6JP.mjs");
140
121
  const r = stampProject(dir);
141
122
  print(r, () => [`stamped ${r.stamped.length} element(s) in ${r.written.length} file(s)`, ...r.stamped.map((s) => ` ${s.file}: <${s.tag}> id="${s.id}"`), ...r.skipped.map((s) => ` skipped ${s.file}: ${s.reason}`)].join("\n"));
142
123
  return r.skipped.length ? 1 : 0;
@@ -145,6 +126,7 @@ async function main(argv) {
145
126
  const at = args.positional[1];
146
127
  const out = flag(args, "o", "out");
147
128
  if (!at || !out) throw new Error("usage: higgsedit frame <dir> <t> -o x.png");
129
+ const { frame } = await import("./chunk-4D6AXU4B.mjs");
148
130
  const r = await frame(dir, at, out, depth, allowFontFallback);
149
131
  print(r, () => `wrote ${r.out} (frame ${r.frame}, ${r.ms.toFixed(0)} ms)`);
150
132
  return 0;
@@ -153,6 +135,7 @@ async function main(argv) {
153
135
  const times = flag(args, "times")?.split(",").map((s) => s.trim()).filter(Boolean);
154
136
  const out = flag(args, "o", "out");
155
137
  if (!times?.length || !out) throw new Error("usage: higgsedit sheet <dir> --times 0,1,2 -o sheet.png");
138
+ const { sheet } = await import("./chunk-4D6AXU4B.mjs");
156
139
  const r = await sheet(dir, times, out, Number(flag(args, "columns") ?? Math.min(4, times.length)), Number(flag(args, "width") ?? 480), allowFontFallback);
157
140
  print(r, () => `wrote ${r.out} (frames ${r.frames.join(", ")})`);
158
141
  return 0;
@@ -163,6 +146,7 @@ async function main(argv) {
163
146
  const encoder = flag(args, "encoder");
164
147
  if (encoder && !ENCODERS.includes(encoder)) throw new Error(`unknown --encoder ${encoder}; use ${ENCODERS.join(", ")}`);
165
148
  const crf = flag(args, "crf");
149
+ const { render } = await import("./chunk-4D6AXU4B.mjs");
166
150
  const r = await render(dir, {
167
151
  out,
168
152
  depth,
@@ -188,6 +172,7 @@ async function main(argv) {
188
172
  return 0;
189
173
  }
190
174
  case "verify": {
175
+ const { verify } = await import("./chunk-4D6AXU4B.mjs");
191
176
  const r = await verify(dir, Number(flag(args, "count") ?? 12), depth, allowFontFallback);
192
177
  print(r, () => [`${r.identical ? "identical" : "DIFFERENT"}: ${r.frames.length} frames, ${r.depth}-bit, shuffled order ${r.shuffledOrder.join(",")}`, ...r.frames.map((f, i) => ` ${String(f).padStart(6)} ${r.forward[i]} ${r.shuffled[i] === r.forward[i] ? "=" : "\u2260"}`)].join("\n"));
193
178
  return r.identical ? 0 : 1;
@@ -213,13 +198,18 @@ async function main(argv) {
213
198
  return 1;
214
199
  }
215
200
  }
201
+ function exitAfterOutput(code) {
202
+ let pending = 2;
203
+ const flushed = () => { if (--pending === 0) process.exit(code); };
204
+ for (const stream of [process.stdout, process.stderr]) stream.write("", flushed);
205
+ }
216
206
  patientConnects();
217
207
  main(process.argv.slice(2)).then(
218
- (code) => process.exit(code),
208
+ exitAfterOutput,
219
209
  (error) => {
220
210
  const hint = error instanceof Error && error.name === "FontLoadError" ? " Pass --allow-font-fallback to render anyway." : "";
221
211
  console.error(`higgsedit: ${error instanceof Error ? error.message : String(error)}${hint}`);
222
- process.exit(1);
212
+ exitAfterOutput(1);
223
213
  }
224
214
  );
225
215
  var CLI_ROOT = resolve(import.meta.dirname, "..");