@higgsfield/video-editor-mcp 1.1.1-dev.0 → 2.0.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 (59) hide show
  1. package/README.md +63 -26
  2. package/dist/cli.mjs +284 -112
  3. package/dist/guide.md +2 -3
  4. package/dist/index.html +1 -1
  5. package/dist/launcher.mjs +34 -14
  6. package/dist/server.mjs +209 -265
  7. package/node_modules/@higgsfield/higgsedit/dist/api.d.ts +481 -0
  8. package/node_modules/@higgsfield/higgsedit/dist/{chunk-PFQYUJQL.mjs → chunk-3XVMJSV2.mjs} +1 -1
  9. package/node_modules/@higgsfield/higgsedit/dist/{chunk-F4CMIG5G.mjs → chunk-4VE54E4U.mjs} +3 -1
  10. package/node_modules/@higgsfield/higgsedit/dist/{chunk-2JYLVSVV.mjs → chunk-55JARHLA.mjs} +205 -65
  11. package/node_modules/@higgsfield/higgsedit/dist/{chunk-YLVXQIPX.mjs → chunk-E4N76T2R.mjs} +1 -1
  12. package/node_modules/@higgsfield/higgsedit/dist/{chunk-OCIZ36XD.mjs → chunk-ECRSOIKI.mjs} +1 -1
  13. package/node_modules/@higgsfield/higgsedit/dist/{chunk-IEPPEDEU.mjs → chunk-ENALCTAD.mjs} +1 -1
  14. package/node_modules/@higgsfield/higgsedit/dist/chunk-HY2E7IWX.mjs +43 -0
  15. package/node_modules/@higgsfield/higgsedit/dist/{chunk-NARTAWRN.mjs → chunk-HYCUOCUQ.mjs} +9 -4
  16. package/node_modules/@higgsfield/higgsedit/dist/{chunk-C3GSB3XZ.mjs → chunk-NLEIBCAK.mjs} +1 -7
  17. package/node_modules/@higgsfield/higgsedit/dist/{chunk-2WKNH7OL.mjs → chunk-O3AVYZ6U.mjs} +1 -1
  18. package/node_modules/@higgsfield/higgsedit/dist/chunk-UXO22E3P.mjs +22 -0
  19. package/node_modules/@higgsfield/higgsedit/dist/{chunk-BH2QF635.mjs → chunk-V5XTCFYK.mjs} +4 -100
  20. package/node_modules/@higgsfield/higgsedit/dist/chunk-W2TANYEH.mjs +118 -0
  21. package/node_modules/@higgsfield/higgsedit/dist/{chunk-DVN4LUXV.mjs → chunk-XY5CCUOZ.mjs} +2 -6
  22. package/node_modules/@higgsfield/higgsedit/dist/{chunk-KIWGVAPJ.mjs → chunk-Y7SAX2P4.mjs} +8 -12
  23. package/node_modules/@higgsfield/higgsedit/dist/chunk-YNAIRXOP.mjs +22 -0
  24. package/node_modules/@higgsfield/higgsedit/dist/{chunk-WENFTPXQ.mjs → chunk-ZO3NDQD3.mjs} +1 -1
  25. package/node_modules/@higgsfield/higgsedit/dist/higgsedit.mjs +13 -13
  26. package/node_modules/@higgsfield/higgsedit/package.json +4 -1
  27. package/package.json +4 -4
  28. package/plugin/.codex-plugin/plugin.json +1 -1
  29. package/plugin/skills/video-editing/SKILL.md +109 -95
  30. package/plugin/mcp.json +0 -10
  31. package/plugin/plugin.json +0 -41
  32. package/plugin/skills/motion-craft/SKILL.md +0 -87
  33. package/plugin/skills/motion-craft/agents/openai.yaml +0 -6
  34. package/plugin/skills/motion-craft/references/causality.md +0 -25
  35. package/plugin/skills/motion-craft/references/composition.md +0 -20
  36. package/plugin/skills/motion-craft/references/easing.md +0 -19
  37. package/plugin/skills/motion-craft/references/qc.md +0 -33
  38. package/plugin/skills/motion-craft/references/recipes.md +0 -31
  39. package/plugin/skills/motion-craft/references/stepped.md +0 -43
  40. package/plugin/skills/motion-craft/references/timing.md +0 -60
  41. package/plugin/skills/motion-craft/references/tsx.md +0 -60
  42. package/plugin/skills/motion-craft/references/typography.md +0 -22
  43. package/plugin/skills/video-editing/agents/openai.yaml +0 -6
  44. package/plugin/skills/video-editing/references/animation-contract.md +0 -45
  45. package/plugin/skills/video-editing/references/assembly.md +0 -58
  46. package/plugin/skills/video-editing/references/caption-systems.md +0 -29
  47. package/plugin/skills/video-editing/references/caption-titling.md +0 -41
  48. package/plugin/skills/video-editing/references/clip-geometry.md +0 -37
  49. package/plugin/skills/video-editing/references/compose.md +0 -283
  50. package/plugin/skills/video-editing/references/editor-measured.md +0 -37
  51. package/plugin/skills/video-editing/references/failure-modes.md +0 -37
  52. package/plugin/skills/video-editing/references/local-setup.md +0 -25
  53. package/plugin/skills/video-editing/references/motion-language.md +0 -27
  54. package/plugin/skills/video-editing/references/provenance.md +0 -7
  55. package/plugin/skills/video-editing/references/sandbox.md +0 -31
  56. package/plugin/skills/video-editing/references/shot-blueprints.md +0 -24
  57. package/plugin/skills/video-editing/references/title-animation.md +0 -29
  58. package/plugin/skills/video-editing/references/tsx-projects.md +0 -187
  59. package/plugin/skills/video-editing/references/workflows.md +0 -41
package/dist/server.mjs CHANGED
@@ -4274,7 +4274,7 @@ var require_core2 = __commonJS({
4274
4274
  var util_1 = require_util2();
4275
4275
  var $dataRefSchema = require_data2();
4276
4276
  var uri_1 = require_uri2();
4277
- var defaultRegExp = (str, flags2) => new RegExp(str, flags2);
4277
+ var defaultRegExp = (str, flags) => new RegExp(str, flags);
4278
4278
  defaultRegExp.code = "new RegExp";
4279
4279
  var META_IGNORE_OPTIONS = ["removeAdditional", "useDefaults", "coerceTypes"];
4280
4280
  var EXT_SCOPE_NAMES = /* @__PURE__ */ new Set([
@@ -30739,7 +30739,7 @@ var require_core$3 = /* @__PURE__ */ __commonJSMin(((exports) => {
30739
30739
  const util_1 = require_util();
30740
30740
  const $dataRefSchema = require_data();
30741
30741
  const uri_1 = require_uri();
30742
- const defaultRegExp = (str, flags2) => new RegExp(str, flags2);
30742
+ const defaultRegExp = (str, flags) => new RegExp(str, flags);
30743
30743
  defaultRegExp.code = "new RegExp";
30744
30744
  const META_IGNORE_OPTIONS = [
30745
30745
  "removeAdditional",
@@ -41164,13 +41164,13 @@ var StreamableHTTPClientTransport = class {
41164
41164
  this.onerror?.(new Error(`Maximum reconnection attempts (${maxRetries}) exceeded.`));
41165
41165
  return;
41166
41166
  }
41167
- const delay2 = this._getNextReconnectionDelay(attemptCount);
41167
+ const delay3 = this._getNextReconnectionDelay(attemptCount);
41168
41168
  this._reconnectionTimeout = setTimeout(() => {
41169
41169
  this._startOrAuthSse(options).catch((error51) => {
41170
41170
  this.onerror?.(new Error(`Failed to reconnect SSE stream: ${error51 instanceof Error ? error51.message : String(error51)}`));
41171
41171
  this._scheduleReconnection(options, attemptCount + 1);
41172
41172
  });
41173
- }, delay2);
41173
+ }, delay3);
41174
41174
  }
41175
41175
  _handleSseStream(stream, options, isReconnectable) {
41176
41176
  if (!stream) {
@@ -41915,7 +41915,7 @@ var DirectServer = class {
41915
41915
  };
41916
41916
 
41917
41917
  // src/video-editor-local/standalone-resource.ts
41918
- var packageVersion = "1.1.1-dev.0";
41918
+ var packageVersion = "2.0.0";
41919
41919
  var VIDEO_EDITOR_RESOURCE_URI = packageVersion ? `ui://higgsfield/video-editor-v3-${encodeURIComponent(packageVersion)}.html` : "ui://higgsfield/video-editor-v3.html";
41920
41920
  function panelHtmlPath(env = process.env) {
41921
41921
  return env.VIDEO_EDITOR_LOCAL_HTML?.trim() || join2(import.meta.dirname, "index.html");
@@ -42049,7 +42049,7 @@ function registerHostedProxyTools(server, url2, session = new LocalBrowserSessio
42049
42049
  const previous = connected?.client;
42050
42050
  const next = new Client({
42051
42051
  name: "higgsfield-video-editor-local",
42052
- version: "1.1.1-dev.0"
42052
+ version: "2.0.0"
42053
42053
  });
42054
42054
  const transport = new StreamableHTTPClientTransport(new URL(url2), {
42055
42055
  requestInit: { headers: { Authorization: `Bearer ${token}` } }
@@ -42128,9 +42128,57 @@ function registerHostedProxyTools(server, url2, session = new LocalBrowserSessio
42128
42128
 
42129
42129
  // src/video-editor-local/index.ts
42130
42130
  import { readFileSync as readFileSync3 } from "node:fs";
42131
- import { mkdir as mkdir7, mkdtemp as mkdtemp2, readFile as readFile7, rm as rm6 } from "node:fs/promises";
42132
- import { tmpdir as tmpdir3 } from "node:os";
42133
- import { join as join11 } from "node:path";
42131
+ import { mkdir as mkdir6 } from "node:fs/promises";
42132
+ import { tmpdir as tmpdir2 } from "node:os";
42133
+ import { join as join10 } from "node:path";
42134
+
42135
+ // src/video-editor-local/dev-api.ts
42136
+ import { setTimeout as delay } from "node:timers/promises";
42137
+ var DevApiError = class extends Error {
42138
+ constructor(status, message) {
42139
+ super(message);
42140
+ this.status = status;
42141
+ }
42142
+ status;
42143
+ };
42144
+ async function send(origin, method, path2, body) {
42145
+ const res = await fetch(`${origin}${path2}`, {
42146
+ method,
42147
+ ...body === void 0 ? {} : { headers: { "content-type": "application/json" }, body: JSON.stringify(body) }
42148
+ });
42149
+ if (res.ok) return res;
42150
+ let message = `${path2}: ${res.status}`;
42151
+ try {
42152
+ const error51 = await res.json();
42153
+ if (typeof error51.error === "string") message = error51.error;
42154
+ } catch {
42155
+ }
42156
+ throw new DevApiError(res.status, message);
42157
+ }
42158
+ var POLL_MS = 1e3;
42159
+ function devApi(origin) {
42160
+ const get = async (path2) => (await send(origin, "GET", path2)).json();
42161
+ const post = async (path2, body) => (await send(origin, "POST", path2, body)).json();
42162
+ return {
42163
+ get,
42164
+ post,
42165
+ /** A route that answers bytes (the contact sheet). */
42166
+ bytes: async (path2, body) => Buffer.from(await (await send(origin, "POST", path2, body)).arrayBuffer()),
42167
+ /** Starts a job and waits for its result. */
42168
+ async job(path2, body, timeoutMs) {
42169
+ const { id: id2 } = await post(path2, body);
42170
+ const deadline = Date.now() + timeoutMs;
42171
+ for (; ; ) {
42172
+ const job = await get(`/api/jobs/${id2}`);
42173
+ if (job.status === "done") return job.result;
42174
+ if (job.status === "failed") throw new Error(job.error);
42175
+ if (Date.now() >= deadline)
42176
+ throw new Error(`${path2} did not finish within ${Math.round(timeoutMs / 1e3)} s`);
42177
+ await delay(POLL_MS);
42178
+ }
42179
+ }
42180
+ };
42181
+ }
42134
42182
 
42135
42183
  // src/video-editor-local/higgsedit.ts
42136
42184
  import { execFile as execFile2 } from "node:child_process";
@@ -42180,22 +42228,14 @@ function runCli(args, cwd, timeoutMs = 12e4) {
42180
42228
  );
42181
42229
  });
42182
42230
  }
42183
- function parseCliJson(result) {
42231
+ async function cli(args, cwd, timeoutMs) {
42232
+ const result = await runCli(args, cwd, timeoutMs);
42184
42233
  if (result.code !== 0)
42185
42234
  throw new Error(
42186
42235
  (result.stderr || result.stdout).trim().slice(-4e3) || `higgsedit exited ${result.code}`
42187
42236
  );
42188
- let value;
42189
- try {
42190
- value = JSON.parse(result.stdout);
42191
- } catch {
42192
- throw new Error("higgsedit returned invalid or incomplete JSON.");
42193
- }
42194
- if (value === null || typeof value !== "object" || Array.isArray(value))
42195
- throw new Error("higgsedit returned an unexpected JSON response; expected an object.");
42196
- return value;
42237
+ return result;
42197
42238
  }
42198
- var cliJson = async (args, cwd, timeoutMs) => parseCliJson(await runCli(args, cwd, timeoutMs));
42199
42239
 
42200
42240
  // ../../packages/mcp-app/src/shared/video-editor-media-import.ts
42201
42241
  var STREAMING_MEDIA_PATH = "api/media/stream";
@@ -42341,9 +42381,8 @@ async function importMedia(origin, url2, fileName, signal, options = {}) {
42341
42381
  }
42342
42382
 
42343
42383
  // src/video-editor-local/media-tools.ts
42344
- import { mkdir as mkdir2, readFile as readFile3, stat as stat2 } from "node:fs/promises";
42345
- import { tmpdir } from "node:os";
42346
- import { basename as basename2, extname, isAbsolute as isAbsolute2, join as join5, relative as relative2 } from "node:path";
42384
+ import { readFile as readFile3, stat as stat2 } from "node:fs/promises";
42385
+ import { relative as relative2 } from "node:path";
42347
42386
 
42348
42387
  // src/video-editor-local/roots.ts
42349
42388
  import { realpath, stat } from "node:fs/promises";
@@ -42444,13 +42483,11 @@ var TIME = external_exports.union([external_exports.number(), external_exports.s
42444
42483
  var PROJECT = external_exports.string().min(1).max(4096).optional().describe(
42445
42484
  "The project to act on: its session id (or folder path) from video_editing_open_project. Default the last opened project; pass it whenever more than one project is open."
42446
42485
  );
42447
- var flags = (values) => Object.entries(values).flatMap(
42448
- ([name, value]) => value === void 0 || value === false ? [] : [value === true ? `--${name}` : `--${name}=${value}`]
42449
- );
42450
42486
 
42451
42487
  // src/video-editor-local/media-tools.ts
42452
42488
  var INLINE_MAX_IMAGES = 4;
42453
42489
  var INLINE_MAX_BYTES = 1 << 20;
42490
+ var JOB_MS = 18e5;
42454
42491
  async function withImages(result, paths) {
42455
42492
  const content = [{ type: "text", text: JSON.stringify(result) }];
42456
42493
  const sizes = await Promise.all(paths.map(async (p) => (await stat2(p)).size));
@@ -42478,20 +42515,8 @@ var SCALE = external_exports.number().min(0.25).max(4).optional().describe(
42478
42515
  var OUTPUT = external_exports.string().max(4096).optional().describe(
42479
42516
  "Where to save: a file path, or a folder to create a new file in; default a new file in the system temp dir"
42480
42517
  );
42481
- function registerMediaTools(server, mediaScoped) {
42482
- const mediaPath = (path2, root) => {
42483
- const expanded = expandHome(path2);
42484
- if (isAbsolute2(expanded)) return expanded;
42485
- if (!root)
42486
- throw new Error(`${path2} is relative but no project is open; pass an absolute path.`);
42487
- return join5(root, expanded);
42488
- };
42489
- const media2 = async (root, command, path2, args, timeoutMs = 3e5) => await cliJson(
42490
- ["media", command, mediaPath(path2, root), ...args, "--json"],
42491
- root ?? tmpdir(),
42492
- timeoutMs
42493
- );
42494
- const output = (path2, root) => path2 ? ["-o", mediaPath(path2, root)] : [];
42518
+ var defined = (values) => Object.fromEntries(Object.entries(values).filter(([, value]) => value !== void 0));
42519
+ function registerMediaTools(server, scoped) {
42495
42520
  const read = { readOnlyHint: true, openWorldHint: false };
42496
42521
  server.registerTool(
42497
42522
  "video_editing_media_probe",
@@ -42501,7 +42526,9 @@ function registerMediaTools(server, mediaScoped) {
42501
42526
  inputSchema: external_exports.object({ path: MEDIA_PATH, project: PROJECT }),
42502
42527
  annotations: read
42503
42528
  },
42504
- async ({ path: path2, project }) => mediaScoped(project, (root) => media2(root, "probe", path2, []))
42529
+ async ({ path: path2, project }) => scoped(project, async ({ origin }) => ({
42530
+ ...await devApi(origin).post("/api/media/probe", { path: path2 })
42531
+ }))
42505
42532
  );
42506
42533
  server.registerTool(
42507
42534
  "video_editing_media_grab",
@@ -42528,34 +42555,13 @@ function registerMediaTools(server, mediaScoped) {
42528
42555
  }),
42529
42556
  annotations: read
42530
42557
  },
42531
- async ({
42532
- path: path2,
42533
- times,
42534
- count,
42535
- start,
42536
- end,
42537
- quality,
42538
- separate,
42539
- per_sheet,
42540
- uncapped,
42541
- output: out,
42542
- project
42543
- }) => mediaScoped(project, async (root) => {
42544
- const r = await media2(root, "grab", path2, [
42545
- ...flags({
42546
- times: times?.join(","),
42547
- count,
42548
- start,
42549
- end,
42550
- quality,
42551
- separate,
42552
- "per-sheet": per_sheet,
42553
- uncapped
42554
- }),
42555
- ...output(out, root)
42556
- ]);
42558
+ async ({ path: path2, times, count, start, end, quality, separate, per_sheet, uncapped, output, project }) => scoped(project, async ({ origin }) => {
42559
+ const r = await devApi(origin).post(
42560
+ "/api/media/grab",
42561
+ defined({ path: path2, times, count, start, end, quality, separate, perSheet: per_sheet, uncapped, output })
42562
+ );
42557
42563
  return withImages(
42558
- r,
42564
+ { ...r },
42559
42565
  r.images.map((image) => image.path)
42560
42566
  );
42561
42567
  })
@@ -42574,12 +42580,12 @@ function registerMediaTools(server, mediaScoped) {
42574
42580
  }),
42575
42581
  annotations: read
42576
42582
  },
42577
- async ({ path: path2, start, end, scale, output: out, project }) => mediaScoped(project, async (root) => {
42578
- const r = await media2(root, "filmstrip", path2, [
42579
- ...flags({ start, end, scale }),
42580
- ...output(out, root)
42581
- ]);
42582
- return withImages(r, [r.path]);
42583
+ async ({ path: path2, start, end, scale, output, project }) => scoped(project, async ({ origin }) => {
42584
+ const r = await devApi(origin).post(
42585
+ "/api/media/filmstrip",
42586
+ defined({ path: path2, start, end, scale, output })
42587
+ );
42588
+ return withImages({ ...r }, [r.path]);
42583
42589
  })
42584
42590
  );
42585
42591
  server.registerTool(
@@ -42598,12 +42604,12 @@ function registerMediaTools(server, mediaScoped) {
42598
42604
  }),
42599
42605
  annotations: read
42600
42606
  },
42601
- async ({ path: path2, start, end, scale, output: out, silence_db, min_silence, project }) => mediaScoped(project, async (root) => {
42602
- const r = await media2(root, "waveform", path2, [
42603
- ...flags({ start, end, scale, "silence-db": silence_db, "min-silence": min_silence }),
42604
- ...output(out, root)
42605
- ]);
42606
- return withImages(r, [r.path]);
42607
+ async ({ path: path2, start, end, scale, output, silence_db, min_silence, project }) => scoped(project, async ({ origin }) => {
42608
+ const r = await devApi(origin).post(
42609
+ "/api/media/waveform",
42610
+ defined({ path: path2, start, end, scale, output, silenceDb: silence_db, minSilence: min_silence })
42611
+ );
42612
+ return withImages({ ...r }, [r.path]);
42607
42613
  })
42608
42614
  );
42609
42615
  server.registerTool(
@@ -42622,16 +42628,13 @@ function registerMediaTools(server, mediaScoped) {
42622
42628
  }),
42623
42629
  annotations: read
42624
42630
  },
42625
- async ({ path: path2, output: out, language, model, project }) => mediaScoped(
42626
- project,
42627
- (root) => media2(
42628
- root,
42629
- "transcribe",
42630
- path2,
42631
- [...flags({ language, model }), ...output(out, root)],
42632
- 18e5
42631
+ async ({ path: path2, output, language, model, project }) => scoped(project, async ({ origin }) => ({
42632
+ ...await devApi(origin).job(
42633
+ "/api/media/transcribe",
42634
+ defined({ path: path2, output, language, model }),
42635
+ JOB_MS
42633
42636
  )
42634
- )
42637
+ }))
42635
42638
  );
42636
42639
  const POINT = external_exports.object({ x: external_exports.number().min(0).max(1), y: external_exports.number().min(0).max(1) });
42637
42640
  server.registerTool(
@@ -42649,41 +42652,26 @@ function registerMediaTools(server, mediaScoped) {
42649
42652
  box: external_exports.tuple([external_exports.number(), external_exports.number(), external_exports.number(), external_exports.number()]).optional().describe("[x0, y0, x1, y1] in 0..1"),
42650
42653
  ...WINDOW,
42651
42654
  model: external_exports.enum(["tiny", "small", "base-plus", "large"]).optional().describe("SAM 2.1 size; tiny (default) is fastest"),
42652
- output: external_exports.string().max(4096).optional().describe(
42653
- "Where the .mask goes; default masks/<video name>.mask in the open project, else a temp file"
42654
- ),
42655
+ output: external_exports.string().max(4096).optional().describe("Where the .mask goes; default masks/<video name>.mask in the open project"),
42655
42656
  project: PROJECT
42656
42657
  }),
42657
42658
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true }
42658
42659
  },
42659
- async ({ path: path2, time: time3, points, exclude, box, start, end, model, output: out, project }) => mediaScoped(project, async (root) => {
42660
- const file2 = mediaPath(path2, root);
42661
- const stem = basename2(file2, extname(file2));
42662
- const mask = out ? mediaPath(out, root) : root ? join5(root, "masks", `${stem}.mask`) : join5(tmpdir(), `video-editing-${stem}-${process.pid}.mask`);
42663
- await mkdir2(join5(mask, ".."), { recursive: true });
42664
- const r = await cliJson(
42665
- [
42666
- "segment",
42667
- file2,
42668
- "-o",
42669
- mask,
42670
- ...(points ?? []).map((p) => `--point=${p.x},${p.y}`),
42671
- ...(exclude ?? []).map((p) => `--exclude=${p.x},${p.y}`),
42672
- ...flags({ box: box?.join(","), at: time3, start, end, model, json: true })
42673
- ],
42674
- root ?? tmpdir(),
42675
- 18e5
42660
+ async ({ path: path2, time: time3, points, exclude, box, start, end, model, output, project }) => scoped(project, async ({ origin, root }) => {
42661
+ const r = await devApi(origin).job(
42662
+ "/api/media/segment",
42663
+ defined({ path: path2, at: time3, points, exclude, box, start, end, model, output }),
42664
+ JOB_MS
42676
42665
  );
42677
- const src = root && isInside(root, mask) ? relative2(root, mask) : void 0;
42678
- return { ...r, ...src ? { src } : {} };
42666
+ return { ...r, ...isInside(root, r.path) ? { src: relative2(root, r.path) } : {} };
42679
42667
  })
42680
42668
  );
42681
42669
  }
42682
42670
 
42683
42671
  // src/video-editor-local/panel-relay.ts
42684
42672
  import { execFile as execFile3 } from "node:child_process";
42685
- import { appendFile, link as link2, mkdir as mkdir5, rm as rm4, stat as stat3, writeFile as writeFile5 } from "node:fs/promises";
42686
- import { basename as basename3, extname as extname2, join as join9, resolve as resolve2 } from "node:path";
42673
+ import { appendFile, link as link2, mkdir as mkdir4, rm as rm4, stat as stat3, writeFile as writeFile5 } from "node:fs/promises";
42674
+ import { basename as basename2, extname, join as join8, resolve as resolve2 } from "node:path";
42687
42675
  import { promisify } from "node:util";
42688
42676
 
42689
42677
  // ../../packages/mcp-app/src/shared/video-editor-notes.ts
@@ -42743,11 +42731,11 @@ function projectNameFromPath(path2) {
42743
42731
 
42744
42732
  // src/video-editor-local/notes.ts
42745
42733
  import { randomUUID as randomUUID2, createHash as createHash2 } from "node:crypto";
42746
- import { link, mkdir as mkdir3, readFile as readFile4, readdir, rm, writeFile as writeFile2 } from "node:fs/promises";
42747
- import { join as join6 } from "node:path";
42734
+ import { link, mkdir as mkdir2, readFile as readFile4, readdir, rm, writeFile as writeFile2 } from "node:fs/promises";
42735
+ import { join as join5 } from "node:path";
42748
42736
  async function projectNotes(directory, root, update) {
42749
42737
  const key = createHash2("sha256").update(root).digest("hex");
42750
- const folder = join6(directory, "notes", key);
42738
+ const folder = join5(directory, "notes", key);
42751
42739
  const revisions = async () => {
42752
42740
  try {
42753
42741
  return (await readdir(folder)).filter((name) => /^[1-9]\d*\.json$/.test(name)).map((name) => Number.parseInt(name)).sort((a, b) => b - a);
@@ -42762,7 +42750,7 @@ async function projectNotes(directory, root, update) {
42762
42750
  if (!latest) return { revision: 0, notes: [] };
42763
42751
  try {
42764
42752
  return editorNotesSchema.parse(
42765
- JSON.parse(await readFile4(join6(folder, `${latest}.json`), "utf8"))
42753
+ JSON.parse(await readFile4(join5(folder, `${latest}.json`), "utf8"))
42766
42754
  );
42767
42755
  } catch (error51) {
42768
42756
  if (error51.code !== "ENOENT") throw error51;
@@ -42775,12 +42763,12 @@ async function projectNotes(directory, root, update) {
42775
42763
  const current = await read();
42776
42764
  if (current.revision !== next.revision)
42777
42765
  throw new Error("Notes changed in another window. Reload notes before saving again.");
42778
- await mkdir3(folder, { recursive: true, mode: 448 });
42779
- const temporary = join6(folder, `${randomUUID2()}.tmp`);
42766
+ await mkdir2(folder, { recursive: true, mode: 448 });
42767
+ const temporary = join5(folder, `${randomUUID2()}.tmp`);
42780
42768
  const saved = { revision: current.revision + 1, notes: next.notes };
42781
42769
  try {
42782
42770
  await writeFile2(temporary, JSON.stringify(saved), { mode: 384, flag: "wx" });
42783
- await link(temporary, join6(folder, `${saved.revision}.json`));
42771
+ await link(temporary, join5(folder, `${saved.revision}.json`));
42784
42772
  } catch (error51) {
42785
42773
  if (error51.code === "EEXIST")
42786
42774
  throw new Error("Notes changed in another window. Reload notes before saving again.");
@@ -42791,19 +42779,19 @@ async function projectNotes(directory, root, update) {
42791
42779
  if ((await read()).revision !== saved.revision)
42792
42780
  throw new Error("Notes changed in another window. Reload notes before saving again.");
42793
42781
  for (const revision of await revisions()) {
42794
- if (revision < saved.revision - 2) await rm(join6(folder, `${revision}.json`), { force: true });
42782
+ if (revision < saved.revision - 2) await rm(join5(folder, `${revision}.json`), { force: true });
42795
42783
  }
42796
42784
  return saved;
42797
42785
  }
42798
42786
 
42799
42787
  // src/video-editor-local/project-name.ts
42800
42788
  import { createHash as createHash3, randomUUID as randomUUID3 } from "node:crypto";
42801
- import { mkdir as mkdir4, readFile as readFile5, rename as rename2, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
42802
- import { join as join7 } from "node:path";
42789
+ import { mkdir as mkdir3, readFile as readFile5, rename as rename2, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
42790
+ import { join as join6 } from "node:path";
42803
42791
  async function projectName(directory, root, update) {
42804
- const folder = join7(directory, "project-names");
42792
+ const folder = join6(directory, "project-names");
42805
42793
  const key = createHash3("sha256").update(root).digest("hex");
42806
- const file2 = join7(folder, `${key}.json`);
42794
+ const file2 = join6(folder, `${key}.json`);
42807
42795
  if (update === void 0) {
42808
42796
  try {
42809
42797
  return projectNameSchema.parse(JSON.parse(await readFile5(file2, "utf8")));
@@ -42813,8 +42801,8 @@ async function projectName(directory, root, update) {
42813
42801
  }
42814
42802
  }
42815
42803
  const name = projectNameSchema.parse(update);
42816
- await mkdir4(folder, { recursive: true, mode: 448 });
42817
- const temp = join7(folder, `${randomUUID3()}.tmp`);
42804
+ await mkdir3(folder, { recursive: true, mode: 448 });
42805
+ const temp = join6(folder, `${randomUUID3()}.tmp`);
42818
42806
  try {
42819
42807
  await writeFile3(temp, JSON.stringify(name), { mode: 384, flag: "wx" });
42820
42808
  await rename2(temp, file2);
@@ -42827,8 +42815,8 @@ async function projectName(directory, root, update) {
42827
42815
  // src/video-editor-local/relay.ts
42828
42816
  import { openAsBlob } from "node:fs";
42829
42817
  import { mkdtemp, rm as rm3, writeFile as writeFile4 } from "node:fs/promises";
42830
- import { tmpdir as tmpdir2 } from "node:os";
42831
- import { join as join8 } from "node:path";
42818
+ import { tmpdir } from "node:os";
42819
+ import { join as join7 } from "node:path";
42832
42820
  var MAX_CHUNK = 2 << 20;
42833
42821
  var KEPT_HEADERS = [
42834
42822
  "content-type",
@@ -42878,12 +42866,12 @@ async function httpTool(origin, call) {
42878
42866
  }
42879
42867
  const method = call.method.toUpperCase();
42880
42868
  const body = call.body !== void 0 && method !== "GET" && method !== "HEAD" ? Buffer.from(call.body, "base64") : void 0;
42881
- return send(origin, method, call.path, headers, body);
42869
+ return send2(origin, method, call.path, headers, body);
42882
42870
  }
42883
42871
  function checkPath(path2) {
42884
42872
  if (!path2.startsWith("/") || path2.startsWith("//")) throw new Error(`bad path ${path2}`);
42885
42873
  }
42886
- async function send(origin, method, path2, headers, body, signal) {
42874
+ async function send2(origin, method, path2, headers, body, signal) {
42887
42875
  const res = await fetch(`${origin}${path2}`, {
42888
42876
  method,
42889
42877
  headers,
@@ -42971,9 +42959,9 @@ var UploadParts = class {
42971
42959
  if (current.size + bytes.length > this.maxBytes)
42972
42960
  throw new Error(`Upload exceeds ${this.maxBytes} bytes`);
42973
42961
  current.abort.signal.throwIfAborted();
42974
- current.directory ??= await mkdtemp(join8(tmpdir2(), "video-editor-upload-"));
42962
+ current.directory ??= await mkdtemp(join7(tmpdir(), "video-editor-upload-"));
42975
42963
  current.abort.signal.throwIfAborted();
42976
- const file2 = join8(current.directory, "body");
42964
+ const file2 = join7(current.directory, "body");
42977
42965
  await writeFile4(file2, bytes, { flag: "a", mode: 384, signal: current.abort.signal });
42978
42966
  current.size += bytes.length;
42979
42967
  current.idle.refresh();
@@ -42983,7 +42971,7 @@ var UploadParts = class {
42983
42971
  }
42984
42972
  const endpoint = new URL(current.path, origin);
42985
42973
  if (endpoint.pathname === "/api/media") endpoint.pathname = `/${STREAMING_MEDIA_PATH}`;
42986
- const result = await send(
42974
+ const result = await send2(
42987
42975
  origin,
42988
42976
  "POST",
42989
42977
  endpoint.pathname + endpoint.search,
@@ -43094,12 +43082,12 @@ var EventFeed = class {
43094
43082
 
43095
43083
  // src/video-editor-local/panel-relay.ts
43096
43084
  async function newFile(dir, name, place) {
43097
- const ext = extname2(name);
43098
- const stem = basename3(name, ext);
43085
+ const ext = extname(name);
43086
+ const stem = basename2(name, ext);
43099
43087
  for (let n = 1; ; n++) {
43100
43088
  const file2 = n === 1 ? name : `${stem}-${n}${ext}`;
43101
43089
  try {
43102
- await place(join9(dir, file2));
43090
+ await place(join8(dir, file2));
43103
43091
  return file2;
43104
43092
  } catch (error51) {
43105
43093
  if (error51.code !== "EEXIST") throw error51;
@@ -43109,7 +43097,7 @@ async function newFile(dir, name, place) {
43109
43097
  var MEDIA_NAME = /^[^./\\\0][^/\\\0]{0,199}\.(mp4|webm|mov|m4a|weba|wav|mp3|png|jpe?g|webp|gif)$/i;
43110
43098
  var execFileAsync = promisify(execFile3);
43111
43099
  function registerPanelRelay(server, store, stateDirectory, direct) {
43112
- void mkdir5(stateDirectory, { recursive: true });
43100
+ void mkdir4(stateDirectory, { recursive: true });
43113
43101
  const sessionArg = external_exports.string().regex(/^[0-9a-f]{16}$/).optional();
43114
43102
  const feeds = /* @__PURE__ */ new Map();
43115
43103
  const uploads = new UploadParts();
@@ -43181,7 +43169,7 @@ function registerPanelRelay(server, store, stateDirectory, direct) {
43181
43169
  },
43182
43170
  async ({ lines }) => guarded(async () => {
43183
43171
  await appendFile(
43184
- join9(stateDirectory, "panel.log"),
43172
+ join8(stateDirectory, "panel.log"),
43185
43173
  lines.map((line) => `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
43186
43174
  `).join("")
43187
43175
  );
@@ -43278,9 +43266,9 @@ function registerPanelRelay(server, store, stateDirectory, direct) {
43278
43266
  },
43279
43267
  async ({ id: id2, name, offset, part, last, project }) => guarded(async () => {
43280
43268
  const { root } = await store.session(project);
43281
- const dir = join9(root, "renders");
43282
- await mkdir5(dir, { recursive: true });
43283
- const temp = join9(dir, `.${id2}.part`);
43269
+ const dir = join8(root, "renders");
43270
+ await mkdir4(dir, { recursive: true });
43271
+ const temp = join8(dir, `.${id2}.part`);
43284
43272
  const size = offset === 0 ? 0 : await stat3(temp).then(
43285
43273
  (s) => s.size,
43286
43274
  () => 0
@@ -43291,7 +43279,7 @@ function registerPanelRelay(server, store, stateDirectory, direct) {
43291
43279
  if (!last) return { received: offset + bytes.length };
43292
43280
  const file2 = await newFile(dir, name, (path2) => link2(temp, path2));
43293
43281
  await rm4(temp, { force: true });
43294
- return { file: `renders/${file2}`, path: join9(dir, file2) };
43282
+ return { file: `renders/${file2}`, path: join8(dir, file2) };
43295
43283
  })
43296
43284
  );
43297
43285
  registerAppTool(
@@ -43310,7 +43298,7 @@ function registerPanelRelay(server, store, stateDirectory, direct) {
43310
43298
  const { root } = await store.session(project);
43311
43299
  const target = resolve2(root, path2 ?? ".");
43312
43300
  if (!isInside(root, target)) throw new Error(`${path2} is outside the project folder.`);
43313
- if (how === "open" && target !== root && !MEDIA_NAME.test(basename3(target)))
43301
+ if (how === "open" && target !== root && !MEDIA_NAME.test(basename2(target)))
43314
43302
  throw new Error("Only the project folder and its media can be opened.");
43315
43303
  if (process.platform !== "darwin") throw new Error("Showing files needs macOS.");
43316
43304
  await execFileAsync("open", how === "reveal" ? ["-R", target] : [target]);
@@ -43324,7 +43312,7 @@ import { spawn } from "node:child_process";
43324
43312
  import { createHash as createHash4, randomUUID as randomUUID4 } from "node:crypto";
43325
43313
  import { readFileSync as readFileSync2 } from "node:fs";
43326
43314
  import {
43327
- mkdir as mkdir6,
43315
+ mkdir as mkdir5,
43328
43316
  readdir as readdir2,
43329
43317
  readFile as readFile6,
43330
43318
  rename as rename3,
@@ -43335,10 +43323,10 @@ import {
43335
43323
  } from "node:fs/promises";
43336
43324
  import { createServer as createServer3 } from "node:net";
43337
43325
  import { homedir as homedir3 } from "node:os";
43338
- import { join as join10 } from "node:path";
43326
+ import { join as join9 } from "node:path";
43339
43327
 
43340
43328
  // src/video-editor-local/session-health.ts
43341
- import { setTimeout as delay } from "node:timers/promises";
43329
+ import { setTimeout as delay2 } from "node:timers/promises";
43342
43330
  var PROBE_MS = 2e3;
43343
43331
  async function request2(origin, path2, timeoutMs, read, method = "GET") {
43344
43332
  const controller = new AbortController();
@@ -43365,7 +43353,7 @@ async function waitForDevReady(origin, exited, timeoutMs) {
43365
43353
  if (exited()) return false;
43366
43354
  const budget = deadline - Date.now();
43367
43355
  if (budget <= 0) return false;
43368
- await delay(Math.min(backoff, budget));
43356
+ await delay2(Math.min(backoff, budget));
43369
43357
  if (budget <= backoff) return false;
43370
43358
  backoff = Math.min(backoff * 2, 1e3);
43371
43359
  }
@@ -43397,7 +43385,7 @@ async function requestIdleStop(origin) {
43397
43385
 
43398
43386
  // src/video-editor-local/session.ts
43399
43387
  function localStateDirectory(env = process.env) {
43400
- return env.VIDEO_EDITOR_LOCAL_STATE?.trim() || join10(homedir3(), ".higgsfield", "video-editor-local");
43388
+ return env.VIDEO_EDITOR_LOCAL_STATE?.trim() || join9(homedir3(), ".higgsfield", "video-editor-local");
43401
43389
  }
43402
43390
  var IDLE_MS = 60 * 60 * 1e3;
43403
43391
  var sessionIdOf = (root) => createHash4("sha256").update(root).digest("hex").slice(0, 16);
@@ -43463,16 +43451,16 @@ var parseRecord = (text) => {
43463
43451
  };
43464
43452
  function createSessionStore(options = {}) {
43465
43453
  const directory = options.directory ?? localStateDirectory();
43466
- const projects = join10(directory, "projects");
43454
+ const projects = join9(directory, "projects");
43467
43455
  const roots = options.roots ?? (() => configuredRoots());
43468
43456
  const start = options.startDev ?? startDev;
43469
43457
  const currentCli = options.cli ?? (() => cliIdentity());
43470
43458
  const idleMs = options.idleMs ?? IDLE_MS;
43471
43459
  const now = options.now ?? Date.now;
43472
- const file2 = (id2, kind) => join10(projects, `${id2}.${kind}`);
43460
+ const file2 = (id2, kind) => join9(projects, `${id2}.${kind}`);
43473
43461
  const read = async (id2) => parseRecord(await readFile6(file2(id2, "json"), "utf8").catch(() => ""));
43474
43462
  const write = async (id2, record2) => {
43475
- await mkdir6(projects, { recursive: true, mode: 448 });
43463
+ await mkdir5(projects, { recursive: true, mode: 448 });
43476
43464
  const temp = `${file2(id2, "json")}.${randomUUID4()}.tmp`;
43477
43465
  await writeFile6(temp, JSON.stringify(record2), { mode: 384 });
43478
43466
  await rename3(temp, file2(id2, "json"));
@@ -43484,7 +43472,7 @@ function createSessionStore(options = {}) {
43484
43472
  return (await Promise.all(ids.map(read))).filter((record2) => record2 !== null).sort((a, b) => b.openedAt - a.openedAt);
43485
43473
  };
43486
43474
  const locked = async (id2, run) => {
43487
- await mkdir6(projects, { recursive: true, mode: 448 });
43475
+ await mkdir5(projects, { recursive: true, mode: 448 });
43488
43476
  const lock = file2(id2, "lock");
43489
43477
  for (; ; ) {
43490
43478
  const body = JSON.stringify({ pid: process.pid, at: Date.now() });
@@ -43532,7 +43520,7 @@ function createSessionStore(options = {}) {
43532
43520
  const written = (await stat4(file2(id2, "used")).catch(() => null))?.mtimeMs ?? record2.openedAt;
43533
43521
  return Math.max(written, touched.get(id2) ?? -Infinity);
43534
43522
  };
43535
- const activityDirectory = (id2) => join10(projects, `${id2}.activity`);
43523
+ const activityDirectory = (id2) => join9(projects, `${id2}.activity`);
43536
43524
  const hasActivity = async (id2) => {
43537
43525
  const names = await readdir2(activityDirectory(id2)).catch((error51) => {
43538
43526
  if (error51.code === "ENOENT") return [];
@@ -43541,7 +43529,7 @@ function createSessionStore(options = {}) {
43541
43529
  for (const name of names) {
43542
43530
  const owner = /^([1-9]\d*)-[0-9a-f-]{36}$/.exec(name);
43543
43531
  if (!owner || alive(Number(owner[1]))) return true;
43544
- await rm5(join10(activityDirectory(id2), name), { force: true });
43532
+ await rm5(join9(activityDirectory(id2), name), { force: true });
43545
43533
  }
43546
43534
  return false;
43547
43535
  };
@@ -43612,9 +43600,9 @@ function createSessionStore(options = {}) {
43612
43600
  record2 = { ...current, openedAt };
43613
43601
  if (opened) await write(id2, record2);
43614
43602
  } else {
43615
- const cli = currentCli();
43603
+ const cli2 = currentCli();
43616
43604
  if (opened) await write(id2, { root, openedAt });
43617
- record2 = { root, ...await start(root), cli, openedAt };
43605
+ record2 = { root, ...await start(root), cli: cli2, openedAt };
43618
43606
  await write(id2, record2);
43619
43607
  }
43620
43608
  await touch(id2, true);
@@ -43669,8 +43657,8 @@ function createSessionStore(options = {}) {
43669
43657
  "The local project server changed before this operation. Retry the operation."
43670
43658
  );
43671
43659
  const directory2 = activityDirectory(current.id);
43672
- await mkdir6(directory2, { recursive: true, mode: 448 });
43673
- const path2 = join10(directory2, `${process.pid}-${randomUUID4()}`);
43660
+ await mkdir5(directory2, { recursive: true, mode: 448 });
43661
+ const path2 = join9(directory2, `${process.pid}-${randomUUID4()}`);
43674
43662
  await writeFile6(path2, "", { flag: "wx", mode: 384 });
43675
43663
  return path2;
43676
43664
  });
@@ -43733,13 +43721,12 @@ function createSessionStore(options = {}) {
43733
43721
 
43734
43722
  // src/video-editor-local/index.ts
43735
43723
  var LOCAL_PROJECT_GUIDE = readFileSync3(new URL("./guide.md", import.meta.url), "utf8");
43736
- var VIDEO_EDITOR_LOCAL_INSTRUCTIONS = "Local video editor (this Mac only): for any request to edit, cut or assemble a video in the editor, use the video_editing_* tools instead of building with sandbox_exec. Start a new project with video_editing_create_project {path} (ask for a folder name when none is given) or open one with video_editing_open_project {path}. A project is a folder (index.tsx + media/; allowed roots: HIGGSEDIT_PROJECT_ROOTS, default ~/Downloads, ~/Movies, ~/Documents) shown in the Video Editor panel; the TSX source is the document and the panel hot-reloads it. Call video_editing_outline for element ids, then video_editing_edit to change props, text, timing or structure by id (all-or-nothing, one undo step shared with the panel). Look at footage with video_editing_media_* (probe, grab, filmstrip, waveform, transcribe, segment), check the result with video_editing_preview_frames, and render an mp4 under renders/ with video_editing_render (poll video_editing_render_status). The open and create results carry the project guide. This local higgsedit is video-editor-next, not the sandbox CLI of the video-editing skill; never mix their project formats. Several projects can be open at once, each in its own panel: pass project (the session from the open or create result) to every video_editing_* call once more than one is open. Without it a tool acts on the last opened project; every result names the project it acted on.";
43724
+ var VIDEO_EDITOR_LOCAL_INSTRUCTIONS = "Local video editor (this Mac only): for any request to edit, cut or assemble a video in the editor, use the video_editing_* tools. Start a new project with video_editing_create_project {path} (ask for a folder name when none is given) or open one with video_editing_open_project {path}. A project is a folder (index.tsx + media/; allowed roots: HIGGSEDIT_PROJECT_ROOTS, default ~/Downloads, ~/Movies, ~/Documents) shown in the Video Editor panel; the TSX source is the document and the panel hot-reloads it. Call video_editing_outline for element ids, then video_editing_edit to change props, text, timing or structure by id (all-or-nothing, one undo step shared with the panel). Look at footage with video_editing_media_* (probe, grab, filmstrip, waveform, transcribe, segment), check the result with video_editing_preview_frames, and render an mp4 under renders/ with video_editing_render (poll video_editing_render_status). The open and create results carry the project guide. Several projects can be open at once, each in its own panel: pass project (the session from the open or create result) to every video_editing_* call once more than one is open. Without it a tool acts on the last opened project; every result names the project it acted on.";
43737
43725
  async function initProject(path2, roots, size = "1080x1920", fps = 30) {
43738
43726
  const dir = await newProjectDir(path2, roots);
43739
43727
  if (await hasProjectFile(dir)) return dir;
43740
- cliPath();
43741
- await mkdir7(dir, { recursive: true });
43742
- await cliJson(["init", dir, `--size=${size}`, `--fps=${fps}`, "--json"], dir);
43728
+ await mkdir6(dir, { recursive: true });
43729
+ await cli(["init", dir, `--size=${size}`, `--fps=${fps}`], dir);
43743
43730
  return dir;
43744
43731
  }
43745
43732
  var ID = external_exports.string().min(1).max(200).describe(
@@ -43794,8 +43781,6 @@ var keyed = (e) => {
43794
43781
  const { id: id2, ...edit } = e;
43795
43782
  return { key: id2, edit };
43796
43783
  };
43797
- var decode3 = (r) => JSON.parse(Buffer.from(r.body, "base64").toString());
43798
- var encode3 = (value) => Buffer.from(JSON.stringify(value)).toString("base64");
43799
43784
  function registerVideoEditorLocalTools(server, options = {}) {
43800
43785
  const stateDirectory = options.stateDirectory ?? localStateDirectory();
43801
43786
  const store = options.store ?? createSessionStore({ directory: stateDirectory });
@@ -43803,16 +43788,7 @@ function registerVideoEditorLocalTools(server, options = {}) {
43803
43788
  const sweep = setInterval(() => void store.stopIdle().catch(() => void 0), 5 * 60 * 1e3);
43804
43789
  sweep.unref();
43805
43790
  }
43806
- const api = async (session, path2, body, method = "POST") => {
43807
- const r = await httpTool(session.origin, {
43808
- method,
43809
- path: path2,
43810
- ...body === void 0 ? {} : { body: encode3(body) }
43811
- });
43812
- if (r.status >= 400)
43813
- throw new Error(decode3(r).error ?? `${path2}: ${r.status}`);
43814
- return decode3(r);
43815
- };
43791
+ const dev = (session) => devApi(session.origin);
43816
43792
  const othersOpen = async (session) => (await store.live()).filter((other) => other.id !== session.id).map((other) => `${other.name} (project: "${other.id}")`).join(", ");
43817
43793
  const naming = async (session, project) => {
43818
43794
  const others = project ? "" : await othersOpen(session);
@@ -43830,11 +43806,6 @@ function registerVideoEditorLocalTools(server, options = {}) {
43830
43806
  await naming(session, project)
43831
43807
  );
43832
43808
  });
43833
- const mediaScoped = (project, run) => guarded(async () => {
43834
- const session = project ? await store.session(project) : await store.find().catch(() => null);
43835
- const result = session ? await store.withActivity(session, () => run(session.root)) : await run(void 0);
43836
- return session ? withProject(result, await naming(session, project)) : result;
43837
- });
43838
43809
  const opened = async (dir) => {
43839
43810
  const session = await store.open(dir);
43840
43811
  const others = await othersOpen(session);
@@ -43898,9 +43869,8 @@ ${LOCAL_PROJECT_GUIDE}`);
43898
43869
  annotations: { readOnlyHint: true, openWorldHint: false }
43899
43870
  },
43900
43871
  async ({ project }) => scoped(project, async (session) => {
43901
- const outline = external_exports.looseObject({ text: external_exports.string().optional() }).parse(await cliJson(["outline", ".", "--json"], session.root));
43902
- const { text, ...rest } = outline;
43903
- return reply({ ...describe3(session), ...rest }, text ?? JSON.stringify(rest));
43872
+ const { text, ...rest } = await dev(session).get("/api/outline");
43873
+ return reply({ ...describe3(session), ...rest }, text);
43904
43874
  })
43905
43875
  );
43906
43876
  server.registerTool(
@@ -43917,7 +43887,7 @@ ${LOCAL_PROJECT_GUIDE}`);
43917
43887
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
43918
43888
  },
43919
43889
  async ({ name, edits, dry_run, project }) => scoped(project, async (session) => {
43920
- const r = await api(session, "/api/edit", {
43890
+ const r = await dev(session).post("/api/edit", {
43921
43891
  name: name ?? "Agent edit",
43922
43892
  edits: edits.map(keyed),
43923
43893
  ...dry_run ? { dryRun: true } : {}
@@ -43949,12 +43919,9 @@ ${LOCAL_PROJECT_GUIDE}`);
43949
43919
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
43950
43920
  },
43951
43921
  async ({ project }) => scoped(project, async (session) => {
43952
- const r = await api(
43953
- session,
43954
- path2
43955
- );
43956
- if (!r.ok) throw new Error(r.reason ?? `${title} failed`);
43957
- return r;
43922
+ const r = await dev(session).post(path2, {});
43923
+ if (!r.ok) throw new Error(r.reason || `${title} failed`);
43924
+ return { ...r };
43958
43925
  })
43959
43926
  );
43960
43927
  }
@@ -43973,7 +43940,7 @@ ${LOCAL_PROJECT_GUIDE}`);
43973
43940
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
43974
43941
  },
43975
43942
  async ({ file_name, from, to, depth, project }) => scoped(project, async (session) => {
43976
- const { id: id2 } = await api(session, "/api/render", {
43943
+ const { id: id2 } = await dev(session).post("/api/render", {
43977
43944
  ...file_name ? { fileName: file_name } : {},
43978
43945
  ...from ? { from } : {},
43979
43946
  ...to ? { to } : {},
@@ -43994,16 +43961,10 @@ ${LOCAL_PROJECT_GUIDE}`);
43994
43961
  annotations: { readOnlyHint: true, openWorldHint: false }
43995
43962
  },
43996
43963
  async ({ job_id, project }) => scoped(project, async (session) => {
43997
- const job = await api(
43998
- session,
43999
- `/api/render?id=${job_id}`,
44000
- void 0,
44001
- "GET"
44002
- );
44003
- if (job.state !== "done") return job;
44004
- const { root } = session;
44005
- const path2 = join11(root, job.out);
44006
- return { ...job, path: path2, media: await cliJson(["media", "probe", path2, "--json"], root) };
43964
+ const job = await dev(session).get(`/api/render?id=${job_id}`);
43965
+ if (job.state !== "done" || !job.out) return { ...job };
43966
+ const path2 = join10(session.root, job.out);
43967
+ return { ...job, path: path2, media: await dev(session).post("/api/media/probe", { path: path2 }) };
44007
43968
  })
44008
43969
  );
44009
43970
  const read = { readOnlyHint: true, openWorldHint: false };
@@ -44033,39 +43994,20 @@ ${LOCAL_PROJECT_GUIDE}`);
44033
43994
  }),
44034
43995
  annotations: read
44035
43996
  },
44036
- async ({ times, width, project }) => scoped(project, async ({ root }) => {
44037
- const dir = await mkdtemp2(join11(tmpdir3(), "video-editing-sheet-"));
44038
- try {
44039
- const out = join11(dir, "sheet.png");
44040
- await cliJson(
44041
- [
44042
- "sheet",
44043
- ".",
44044
- "--json",
44045
- `--times=${times.join(",")}`,
44046
- "-o",
44047
- out,
44048
- ...flags({ columns: Math.min(6, times.length), width: width ?? 360 })
44049
- ],
44050
- root
44051
- );
44052
- return {
44053
- content: [
44054
- {
44055
- type: "image",
44056
- data: (await readFile7(out)).toString("base64"),
44057
- mimeType: "image/png"
44058
- }
44059
- ],
44060
- // guarded passes a result through as is only when it has structuredContent.
44061
- structuredContent: { times }
44062
- };
44063
- } finally {
44064
- await rm6(dir, { recursive: true, force: true });
44065
- }
43997
+ async ({ times, width, project }) => scoped(project, async (session) => {
43998
+ const png = await dev(session).bytes("/api/sheet", {
43999
+ times,
44000
+ columns: Math.min(6, times.length),
44001
+ width: width ?? 360
44002
+ });
44003
+ return {
44004
+ content: [{ type: "image", data: png.toString("base64"), mimeType: "image/png" }],
44005
+ // guarded passes a result through as is only when it has structuredContent.
44006
+ structuredContent: { times }
44007
+ };
44066
44008
  })
44067
44009
  );
44068
- registerMediaTools(server, mediaScoped);
44010
+ registerMediaTools(server, scoped);
44069
44011
  server.registerTool(
44070
44012
  "video_editing_fonts",
44071
44013
  {
@@ -44075,20 +44017,19 @@ ${LOCAL_PROJECT_GUIDE}`);
44075
44017
  family: external_exports.string().max(100).optional().describe("Substring of the family name, any case"),
44076
44018
  category: external_exports.enum(["sans-serif", "serif", "display", "handwriting", "monospace"]).optional(),
44077
44019
  cached: external_exports.boolean().optional().describe("Only families with variants already downloaded"),
44078
- limit: external_exports.number().int().min(1).max(2e3).optional()
44020
+ limit: external_exports.number().int().min(1).max(2e3).optional(),
44021
+ project: PROJECT
44079
44022
  }),
44080
44023
  annotations: { readOnlyHint: true, openWorldHint: true }
44081
44024
  },
44082
- async ({ family, category, cached: cached2, limit }) => guarded(
44083
- async () => await cliJson(
44084
- [
44085
- "fonts",
44086
- ...family ? [family] : [],
44087
- ...flags({ category, cached: cached2, limit, json: true })
44088
- ],
44089
- tmpdir3()
44090
- )
44091
- )
44025
+ async ({ family, category, cached: cached2, limit, project }) => scoped(project, async (session) => {
44026
+ const query = new URLSearchParams();
44027
+ if (family) query.set("family", family);
44028
+ if (category) query.set("category", category);
44029
+ if (cached2) query.set("cached", "1");
44030
+ if (limit) query.set("limit", String(limit));
44031
+ return { ...await dev(session).get(`/api/fonts?${query}`) };
44032
+ })
44092
44033
  );
44093
44034
  server.registerTool(
44094
44035
  "video_editing_diagnostics",
@@ -44099,7 +44040,7 @@ ${LOCAL_PROJECT_GUIDE}`);
44099
44040
  annotations: read
44100
44041
  },
44101
44042
  async ({ project: ref }) => guarded(async () => {
44102
- const r = await runCli(["doctor", "--json"], tmpdir3());
44043
+ const r = await runCli(["doctor", "--json"], tmpdir2());
44103
44044
  let doctor;
44104
44045
  try {
44105
44046
  doctor = JSON.parse(r.stdout);
@@ -44319,8 +44260,8 @@ import { EventEmitter } from "node:events";
44319
44260
 
44320
44261
  // src/video-editor-timeline/local-store.ts
44321
44262
  import { chmodSync, mkdirSync } from "node:fs";
44322
- import { dirname as dirname3, join as join12 } from "node:path";
44323
- function createLocalTimelineStore(path2 = join12(localStateDirectory(), "timeline", "sessions.sqlite")) {
44263
+ import { dirname as dirname3, join as join11 } from "node:path";
44264
+ function createLocalTimelineStore(path2 = join11(localStateDirectory(), "timeline", "sessions.sqlite")) {
44324
44265
  if (path2 !== ":memory:") mkdirSync(dirname3(path2), { recursive: true, mode: 448 });
44325
44266
  const { DatabaseSync } = process.getBuiltinModule("node:sqlite");
44326
44267
  const db = new DatabaseSync(path2, { timeout: 100 });
@@ -44452,7 +44393,7 @@ function createTimelineBroker({
44452
44393
  if (!local()) return (await client()).get(sessionKey);
44453
44394
  return (await storage()).read(sessionKey, now());
44454
44395
  }
44455
- function decode4(value) {
44396
+ function decode3(value) {
44456
44397
  if (!value)
44457
44398
  throw new Error(
44458
44399
  "This editor connection expired or belongs to a different account or workspace. Use the current editor context; do not edit another project."
@@ -44468,7 +44409,7 @@ function createTimelineBroker({
44468
44409
  async function update(sessionKey, change) {
44469
44410
  for (let attempt = 0; attempt < 12; attempt++) {
44470
44411
  const before = await raw(sessionKey);
44471
- const state = decode4(before);
44412
+ const state = decode3(before);
44472
44413
  change(state);
44473
44414
  const next = JSON.stringify(state);
44474
44415
  if (Buffer.byteLength(next) > MAX_BYTES)
@@ -44581,7 +44522,7 @@ function createTimelineBroker({
44581
44522
  const deadline = now() + 15e3;
44582
44523
  while (!state.commands.length && now() < deadline) {
44583
44524
  const version2 = versions.get(sessionKey);
44584
- state = decode4(await raw(sessionKey));
44525
+ state = decode3(await raw(sessionKey));
44585
44526
  writer(state, token, connectionId);
44586
44527
  if (!state.commands.length)
44587
44528
  await changed(sessionKey, Math.min(pollInterval(), deadline - now()), signal, version2);
@@ -44608,7 +44549,7 @@ function createTimelineBroker({
44608
44549
  while (now() < command.expiresAt) {
44609
44550
  signal?.throwIfAborted();
44610
44551
  const version2 = versions.get(sessionKey);
44611
- const state = decode4(await raw(sessionKey));
44552
+ const state = decode3(await raw(sessionKey));
44612
44553
  if (state.results[command.id]) return state.results[command.id];
44613
44554
  await changed(
44614
44555
  sessionKey,
@@ -44633,7 +44574,7 @@ function createTimelineBroker({
44633
44574
  for (let attempt = 0; attempt < 12; attempt++) {
44634
44575
  const before = await raw(sessionKey);
44635
44576
  if (!before) return;
44636
- writer(decode4(before), token, connectionId);
44577
+ writer(decode3(before), token, connectionId);
44637
44578
  if (local()) {
44638
44579
  if (!(await storage()).remove(sessionKey, before)) continue;
44639
44580
  } else if (await (await client()).eval(REMOVE, 2, sessionKey, `video-editor:{${hash2(scope)}}:sessions`, before) !== 1)
@@ -44657,7 +44598,7 @@ function createTimelineBroker({
44657
44598
  // src/video-editor-local/update-tools.ts
44658
44599
  import { spawn as spawn2 } from "node:child_process";
44659
44600
  import { existsSync as existsSync3, statSync as statSync3 } from "node:fs";
44660
- import { dirname as dirname5, join as join14 } from "node:path";
44601
+ import { dirname as dirname5, join as join13 } from "node:path";
44661
44602
 
44662
44603
  // src/video-editor-local/updates.ts
44663
44604
  import {
@@ -44670,7 +44611,7 @@ import {
44670
44611
  writeFileSync
44671
44612
  } from "node:fs";
44672
44613
  import { homedir as homedir4 } from "node:os";
44673
- import { dirname as dirname4, join as join13 } from "node:path";
44614
+ import { dirname as dirname4, join as join12 } from "node:path";
44674
44615
 
44675
44616
  // src/adapters/codex-plugin.ts
44676
44617
  var PLUGIN_ROLLBACK_ERROR = "Plugin refresh failed and rollback requires attention.";
@@ -44679,6 +44620,9 @@ var identity = external_exports.object({
44679
44620
  version: external_exports.string()
44680
44621
  });
44681
44622
  var registration = external_exports.object({ name: identity.shape.name, codex: external_exports.string().min(1) });
44623
+ var marketplaceList = external_exports.object({
44624
+ marketplaces: external_exports.array(external_exports.object({ name: external_exports.string(), root: external_exports.string() }))
44625
+ });
44682
44626
 
44683
44627
  // src/video-editor-local/updates.ts
44684
44628
  var UPDATE_PACKAGE = "@higgsfield/video-editor-mcp";
@@ -44690,14 +44634,14 @@ var stateSchema = external_exports.object({
44690
44634
  error: external_exports.enum(["Update failed; keeping the installed editor.", PLUGIN_ROLLBACK_ERROR]).optional()
44691
44635
  });
44692
44636
  function updateOptions(dist) {
44693
- const manifest = JSON.parse(readFileSync4(join13(dirname4(dist), "package.json"), "utf8"));
44637
+ const manifest = JSON.parse(readFileSync4(join12(dirname4(dist), "package.json"), "utf8"));
44694
44638
  const version2 = versionSchema.parse(manifest.version);
44695
44639
  const channel = external_exports.enum(["next", "latest"]).parse(process.env.VIDEO_EDITOR_UPDATE_CHANNEL ?? (version2.includes("-") ? "next" : "latest"));
44696
44640
  return {
44697
44641
  dist,
44698
44642
  version: version2,
44699
44643
  channel,
44700
- root: join13(
44644
+ root: join12(
44701
44645
  homedir4(),
44702
44646
  ".higgsfield/video-editor-mcp",
44703
44647
  UPDATE_PACKAGE === "@higgsfield/video-editor-mcp" ? "public-updates" : "updates",
@@ -44707,7 +44651,7 @@ function updateOptions(dist) {
44707
44651
  }
44708
44652
  function readUpdateState(root) {
44709
44653
  try {
44710
- return stateSchema.parse(JSON.parse(readFileSync4(join13(root, "state.json"), "utf8")));
44654
+ return stateSchema.parse(JSON.parse(readFileSync4(join12(root, "state.json"), "utf8")));
44711
44655
  } catch {
44712
44656
  return { checkedAt: 0 };
44713
44657
  }
@@ -44742,13 +44686,13 @@ function newerVersion(candidate, current) {
44742
44686
  return false;
44743
44687
  }
44744
44688
  function packageDist(prefix) {
44745
- return join13(prefix, "node_modules", UPDATE_PACKAGE, "dist");
44689
+ return join12(prefix, "node_modules", UPDATE_PACKAGE, "dist");
44746
44690
  }
44747
44691
  function validPackage(dist, version2) {
44748
44692
  try {
44749
- const pkg = JSON.parse(readFileSync4(join13(dirname4(dist), "package.json"), "utf8"));
44693
+ const pkg = JSON.parse(readFileSync4(join12(dirname4(dist), "package.json"), "utf8"));
44750
44694
  return pkg.name === UPDATE_PACKAGE && pkg.version === version2 && ["server.mjs", "cli.mjs", "launcher.mjs", "index.html", "guide.md"].every(
44751
- (file2) => existsSync2(join13(dist, file2))
44695
+ (file2) => existsSync2(join12(dist, file2))
44752
44696
  );
44753
44697
  } catch {
44754
44698
  return false;
@@ -44757,7 +44701,7 @@ function validPackage(dist, version2) {
44757
44701
  function selectedDist(options) {
44758
44702
  const { active } = readUpdateState(options.root);
44759
44703
  if (!active || !newerVersion(active, options.version)) return options.dist;
44760
- const dist = packageDist(join13(options.root, active));
44704
+ const dist = packageDist(join12(options.root, active));
44761
44705
  return validPackage(dist, active) ? dist : options.dist;
44762
44706
  }
44763
44707
 
@@ -44766,7 +44710,7 @@ var EDITOR_UPDATES_TOOL = "video_editing_updates";
44766
44710
  function createEditorUpdates(options, start = (done) => new Promise((resolve3, reject) => {
44767
44711
  const child = spawn2(
44768
44712
  process.execPath,
44769
- [join14(options.dist, "launcher.mjs"), "--update", "--force"],
44713
+ [join13(options.dist, "launcher.mjs"), "--update", "--force"],
44770
44714
  {
44771
44715
  detached: true,
44772
44716
  stdio: "ignore"
@@ -44789,12 +44733,12 @@ function createEditorUpdates(options, start = (done) => new Promise((resolve3, r
44789
44733
  let locked = false;
44790
44734
  if (options) {
44791
44735
  try {
44792
- locked = Date.now() - statSync3(join14(options.root, "update.lock")).mtimeMs <= 6e5;
44736
+ locked = Date.now() - statSync3(join13(options.root, "update.lock")).mtimeMs <= 6e5;
44793
44737
  } catch {
44794
44738
  }
44795
44739
  }
44796
44740
  return {
44797
- runningVersion: options?.version ?? "1.1.1-dev.0" ?? "Source checkout",
44741
+ runningVersion: options?.version ?? "2.0.0" ?? "Source checkout",
44798
44742
  readyVersion: options && selectedDist(options) !== options.dist ? state.active : null,
44799
44743
  channel: options?.channel ?? null,
44800
44744
  supported: options !== null,
@@ -44826,7 +44770,7 @@ function createEditorUpdates(options, start = (done) => new Promise((resolve3, r
44826
44770
  }
44827
44771
  function registerEditorUpdateTools(server) {
44828
44772
  const dist = dirname5(import.meta.filename);
44829
- const options = process.env.VIDEO_EDITOR_UPDATE_LAUNCHER === "1" && existsSync3(join14(dist, "launcher.mjs")) ? updateOptions(dist) : null;
44773
+ const options = process.env.VIDEO_EDITOR_UPDATE_LAUNCHER === "1" && existsSync3(join13(dist, "launcher.mjs")) ? updateOptions(dist) : null;
44830
44774
  const updates = createEditorUpdates(options);
44831
44775
  registerAppTool(
44832
44776
  server,
@@ -44858,7 +44802,7 @@ var handle = serveStdio(
44858
44802
  const server = new McpServer(
44859
44803
  {
44860
44804
  name: "Higgsfield Video Editor (local)",
44861
- version: "1.1.1-dev.0"
44805
+ version: "2.0.0"
44862
44806
  },
44863
44807
  { instructions: VIDEO_EDITOR_LOCAL_INSTRUCTIONS }
44864
44808
  );