@higgsfield/video-editor-mcp 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.html +147 -138
- package/dist/server.mjs +139 -24
- package/node_modules/@higgsfield/higgsedit/dist/chunk-C6SASQHE.mjs +10 -76
- package/node_modules/@higgsfield/higgsedit/dist/chunk-QLPCYN6K.mjs +22 -5
- package/node_modules/@higgsfield/higgsedit/dist/chunk-VI6IV6JP.mjs +35 -19
- package/node_modules/@higgsfield/higgsedit/dist/higgsedit.mjs +14 -24
- package/node_modules/@higgsfield/higgsedit/dist/history.mjs +295 -0
- package/package.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/plugin.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
212
|
+
exitAfterOutput(1);
|
|
223
213
|
}
|
|
224
214
|
);
|
|
225
215
|
var CLI_ROOT = resolve(import.meta.dirname, "..");
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { closeSync, constants, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const MAX_BYTES = 16 * 1024 * 1024;
|
|
5
|
+
const MAX_ENTRIES = 100;
|
|
6
|
+
const STALE = "edit history changed in another editor; reopen the project before editing or undoing";
|
|
7
|
+
const message = (error) => error instanceof Error ? error.message : String(error);
|
|
8
|
+
const missing = (error) => error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
9
|
+
const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10
|
+
const safePath = (path) => !/[\\\0]/.test(path) && path.split("/").every((part) => part !== "" && part !== "." && part !== ".." && part !== ".higgsedit-cache") && !/^[A-Za-z]:/.test(path);
|
|
11
|
+
const identity = (stat) => `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
|
|
12
|
+
function encode(undo, redo, revision, text = (entry) => JSON.stringify(entryValue(entry))) {
|
|
13
|
+
return `{"version":1,"revision":${JSON.stringify(revision)},"undo":[${undo.map(text).join(",")}],"redo":[${redo.map(text).join(",")}]}`;
|
|
14
|
+
}
|
|
15
|
+
const entryValue = ({ name, files }) => ({ name, files: [...files].map(([path, texts]) => ({ path, ...texts })) });
|
|
16
|
+
function decode(text) {
|
|
17
|
+
let value;
|
|
18
|
+
try {
|
|
19
|
+
value = JSON.parse(text);
|
|
20
|
+
} catch {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (!record(value) || value.version !== 1 || typeof value.revision !== "string" || !Array.isArray(value.undo) || !Array.isArray(value.redo) || value.undo.length + value.redo.length > MAX_ENTRIES) return;
|
|
24
|
+
const entries = (stack) => {
|
|
25
|
+
const result = [];
|
|
26
|
+
for (const entry of stack) {
|
|
27
|
+
if (!record(entry) || typeof entry.name !== "string" || !Array.isArray(entry.files) || !entry.files.length) return;
|
|
28
|
+
const files = /* @__PURE__ */ new Map();
|
|
29
|
+
for (const file of entry.files) {
|
|
30
|
+
if (!record(file) || typeof file.path !== "string" || !safePath(file.path) || typeof file.before !== "string" || typeof file.after !== "string" || files.has(file.path)) return;
|
|
31
|
+
files.set(file.path, { before: file.before, after: file.after });
|
|
32
|
+
}
|
|
33
|
+
result.push({ name: entry.name, files });
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
const undo = entries(value.undo), redo = entries(value.redo);
|
|
38
|
+
return undo && redo ? { undo, redo } : void 0;
|
|
39
|
+
}
|
|
40
|
+
class EditHistory {
|
|
41
|
+
constructor(dir, onWrite, limits = {}) {
|
|
42
|
+
this.dir = dir;
|
|
43
|
+
this.onWrite = onWrite;
|
|
44
|
+
this.maxBytes = Math.min(MAX_BYTES, limits.maxBytes ?? MAX_BYTES);
|
|
45
|
+
this.maxEntries = Math.min(MAX_ENTRIES, limits.maxEntries ?? MAX_ENTRIES);
|
|
46
|
+
if (!Number.isSafeInteger(this.maxBytes) || this.maxBytes < 100 || !Number.isSafeInteger(this.maxEntries) || this.maxEntries < 1) throw new Error("invalid edit history limits");
|
|
47
|
+
try {
|
|
48
|
+
const saved = this.readJournal();
|
|
49
|
+
this.revision = saved.revision;
|
|
50
|
+
const parsed = saved.text === void 0 ? void 0 : decode(saved.text);
|
|
51
|
+
if (parsed) {
|
|
52
|
+
const bounded = this.bound(parsed.undo, parsed.redo);
|
|
53
|
+
this.undoStack = bounded.undo;
|
|
54
|
+
this.redoStack = bounded.redo;
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
dir;
|
|
60
|
+
onWrite;
|
|
61
|
+
undoStack = [];
|
|
62
|
+
redoStack = [];
|
|
63
|
+
revision = "missing";
|
|
64
|
+
maxBytes;
|
|
65
|
+
maxEntries;
|
|
66
|
+
// Committed entries never change. Weak keys release evicted history and abandoned transactions.
|
|
67
|
+
encoded = /* @__PURE__ */ new WeakMap();
|
|
68
|
+
begin(name) {
|
|
69
|
+
if (typeof name !== "string") throw new Error("edit history name must be a string");
|
|
70
|
+
this.assertCurrent();
|
|
71
|
+
const entry = { name, files: /* @__PURE__ */ new Map() };
|
|
72
|
+
const previousWrites = /* @__PURE__ */ new Map();
|
|
73
|
+
const fileBytes = /* @__PURE__ */ new Map();
|
|
74
|
+
let entryBytes = Buffer.byteLength(encode([entry], [], randomUUID()));
|
|
75
|
+
let finished = false;
|
|
76
|
+
return {
|
|
77
|
+
onWrite: (path, text) => {
|
|
78
|
+
if (finished) throw new Error("edit history transaction already finished");
|
|
79
|
+
const prior = entry.files.get(path);
|
|
80
|
+
const current = this.readSource(path);
|
|
81
|
+
if (prior && current !== prior.after) throw new Error(`${path} changed outside the editor since ${entry.name}`);
|
|
82
|
+
const texts = { before: prior?.before ?? current, after: text };
|
|
83
|
+
const bytes = Buffer.byteLength(JSON.stringify({ path, ...texts }));
|
|
84
|
+
const nextBytes = entryBytes - (fileBytes.get(path) ?? 0) + bytes + (!prior && entry.files.size ? 1 : 0);
|
|
85
|
+
if (nextBytes > this.maxBytes) throw new Error(`edit history size limit exceeded (${this.maxBytes} bytes); split the edit or reduce the affected source files`);
|
|
86
|
+
entryBytes = nextBytes;
|
|
87
|
+
fileBytes.set(path, bytes);
|
|
88
|
+
previousWrites.set(path, current);
|
|
89
|
+
entry.files.set(path, texts);
|
|
90
|
+
this.encoded.delete(entry);
|
|
91
|
+
this.onWrite?.(path, text);
|
|
92
|
+
},
|
|
93
|
+
commit: () => {
|
|
94
|
+
if (finished) throw new Error("edit history transaction already finished");
|
|
95
|
+
if (entry.files.size) {
|
|
96
|
+
this.checkSources(entry, "after");
|
|
97
|
+
this.publish([...this.undoStack, entry], []);
|
|
98
|
+
}
|
|
99
|
+
finished = true;
|
|
100
|
+
},
|
|
101
|
+
rollback: () => {
|
|
102
|
+
if (finished) return;
|
|
103
|
+
this.restore(entry, "after", "before", previousWrites);
|
|
104
|
+
finished = true;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
state() {
|
|
109
|
+
return {
|
|
110
|
+
canUndo: this.undoStack.length > 0,
|
|
111
|
+
canRedo: this.redoStack.length > 0,
|
|
112
|
+
undoName: this.undoStack.at(-1)?.name ?? null,
|
|
113
|
+
redoName: this.redoStack.at(-1)?.name ?? null
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
undo() {
|
|
117
|
+
return this.step(this.undoStack, this.redoStack, "after", "before");
|
|
118
|
+
}
|
|
119
|
+
redo() {
|
|
120
|
+
return this.step(this.redoStack, this.undoStack, "before", "after");
|
|
121
|
+
}
|
|
122
|
+
step(from, to, expect, write) {
|
|
123
|
+
const entry = from.at(-1);
|
|
124
|
+
if (!entry) return { ok: false, reason: `nothing to ${write === "before" ? "undo" : "redo"}` };
|
|
125
|
+
try {
|
|
126
|
+
this.assertCurrent();
|
|
127
|
+
this.checkSources(entry, expect);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
return { ok: false, reason: message(error) };
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
for (const [path, texts] of entry.files) this.write(path, texts[write]);
|
|
133
|
+
const remaining = from.slice(0, -1), moved = [...to, entry];
|
|
134
|
+
this.publish(write === "before" ? remaining : moved, write === "before" ? moved : remaining);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
try {
|
|
137
|
+
this.restore(entry, write, expect);
|
|
138
|
+
} catch (rollbackError) {
|
|
139
|
+
return { ok: false, reason: `${message(error)}; rollback failed: ${message(rollbackError)}` };
|
|
140
|
+
}
|
|
141
|
+
return { ok: false, reason: message(error) };
|
|
142
|
+
}
|
|
143
|
+
return { ok: true, written: [...entry.files.keys()] };
|
|
144
|
+
}
|
|
145
|
+
checkSources(entry, expect) {
|
|
146
|
+
for (const [path, texts] of entry.files) {
|
|
147
|
+
if (this.readSource(path) !== texts[expect]) throw new Error(`${path} changed outside the editor since ${entry.name}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/** A failed write may still contain the original text. Never roll back someone else's version. */
|
|
151
|
+
restore(entry, expect, restore, previousWrites) {
|
|
152
|
+
const pending = [];
|
|
153
|
+
for (const [path, texts] of entry.files) {
|
|
154
|
+
const current = this.readSource(path);
|
|
155
|
+
if (current === texts[restore]) continue;
|
|
156
|
+
if (current !== texts[expect] && current !== previousWrites?.get(path)) throw new Error(`${path} changed outside the editor since ${entry.name}`);
|
|
157
|
+
pending.push([path, texts[restore]]);
|
|
158
|
+
}
|
|
159
|
+
for (const [path, text] of pending) this.write(path, text);
|
|
160
|
+
}
|
|
161
|
+
readSource(path) {
|
|
162
|
+
if (!safePath(path)) throw new Error("unsafe edit history source path");
|
|
163
|
+
let target = this.dir;
|
|
164
|
+
for (const part of path.split("/")) {
|
|
165
|
+
target = join(target, part);
|
|
166
|
+
try {
|
|
167
|
+
if (lstatSync(target).isSymbolicLink()) throw new Error(`${path} is a symlink; cannot restore edit history`);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (missing(error)) throw new Error(`${path} no longer exists`, { cause: error });
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return readFileSync(target, "utf8");
|
|
174
|
+
}
|
|
175
|
+
write(path, text) {
|
|
176
|
+
const target = join(this.dir, path);
|
|
177
|
+
this.onWrite?.(path, text);
|
|
178
|
+
this.atomicWrite(target, text, lstatSync(target).mode & 511);
|
|
179
|
+
}
|
|
180
|
+
cacheDir() {
|
|
181
|
+
const cache = join(this.dir, ".higgsedit-cache");
|
|
182
|
+
try {
|
|
183
|
+
const stat = lstatSync(cache);
|
|
184
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error("unsafe edit history cache directory");
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (!missing(error)) throw error;
|
|
187
|
+
}
|
|
188
|
+
return cache;
|
|
189
|
+
}
|
|
190
|
+
readJournal() {
|
|
191
|
+
const file = join(this.cacheDir(), "history.json");
|
|
192
|
+
let fd;
|
|
193
|
+
try {
|
|
194
|
+
if (!lstatSync(file).isFile()) throw new Error("edit history journal is not a regular file");
|
|
195
|
+
fd = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
196
|
+
} catch (error) {
|
|
197
|
+
if (missing(error)) return { revision: "missing" };
|
|
198
|
+
throw new Error(`cannot read edit history: ${message(error)}`, { cause: error });
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
const stat = fstatSync(fd, { bigint: true });
|
|
202
|
+
if (!stat.isFile()) throw new Error("edit history journal is not a regular file");
|
|
203
|
+
const revision = identity(stat);
|
|
204
|
+
if (stat.size > BigInt(this.maxBytes)) return { revision };
|
|
205
|
+
const bytes = Buffer.alloc(Number(stat.size) + 1);
|
|
206
|
+
let length = 0, count = 0;
|
|
207
|
+
do {
|
|
208
|
+
count = readSync(fd, bytes, length, bytes.length - length, null);
|
|
209
|
+
length += count;
|
|
210
|
+
} while (count && length < bytes.length);
|
|
211
|
+
if (BigInt(length) !== stat.size || identity(fstatSync(fd, { bigint: true })) !== revision) throw new Error("edit history changed while reading");
|
|
212
|
+
const data = bytes.subarray(0, length);
|
|
213
|
+
return { revision, text: data.toString("utf8") };
|
|
214
|
+
} finally {
|
|
215
|
+
closeSync(fd);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
assertCurrent() {
|
|
219
|
+
let revision;
|
|
220
|
+
try {
|
|
221
|
+
const stat = lstatSync(join(this.cacheDir(), "history.json"), { bigint: true });
|
|
222
|
+
if (!stat.isFile()) throw new Error("edit history journal is not a regular file");
|
|
223
|
+
revision = identity(stat);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
if (!missing(error)) throw error;
|
|
226
|
+
revision = "missing";
|
|
227
|
+
}
|
|
228
|
+
if (revision !== this.revision) throw new Error(STALE);
|
|
229
|
+
}
|
|
230
|
+
serialized(entry) {
|
|
231
|
+
let saved = this.encoded.get(entry);
|
|
232
|
+
if (!saved) {
|
|
233
|
+
const text = JSON.stringify(entryValue(entry));
|
|
234
|
+
saved = { text, bytes: Buffer.byteLength(text) };
|
|
235
|
+
this.encoded.set(entry, saved);
|
|
236
|
+
}
|
|
237
|
+
return saved;
|
|
238
|
+
}
|
|
239
|
+
bound(undo, redo) {
|
|
240
|
+
const revision = randomUUID();
|
|
241
|
+
const sized = (stack) => stack.map((entry) => ({ entry, bytes: this.serialized(entry).bytes }));
|
|
242
|
+
const keepUndo = sized(undo), keepRedo = sized(redo);
|
|
243
|
+
let bytes = Buffer.byteLength(encode([], [], revision)) + [...keepUndo, ...keepRedo].reduce((sum, entry) => sum + entry.bytes, 0);
|
|
244
|
+
const separators = () => Math.max(0, keepUndo.length - 1) + Math.max(0, keepRedo.length - 1);
|
|
245
|
+
while (keepUndo.length + keepRedo.length > this.maxEntries || bytes + separators() > this.maxBytes) {
|
|
246
|
+
const removed = keepUndo.length > 1 || !keepRedo.length ? keepUndo.shift() : keepRedo.shift();
|
|
247
|
+
if (!removed) throw new Error("edit history size limit exceeded");
|
|
248
|
+
bytes -= removed.bytes;
|
|
249
|
+
}
|
|
250
|
+
undo = keepUndo.map(({ entry }) => entry);
|
|
251
|
+
redo = keepRedo.map(({ entry }) => entry);
|
|
252
|
+
return { undo, redo, text: encode(undo, redo, revision, (entry) => this.serialized(entry).text) };
|
|
253
|
+
}
|
|
254
|
+
publish(undo, redo) {
|
|
255
|
+
const next = this.bound(undo, redo);
|
|
256
|
+
this.assertCurrent();
|
|
257
|
+
const cache = this.cacheDir();
|
|
258
|
+
mkdirSync(cache, { recursive: true, mode: 448 });
|
|
259
|
+
const target = join(cache, "history.json");
|
|
260
|
+
try {
|
|
261
|
+
this.revision = this.atomicWrite(target, next.text, 384);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
throw new Error(`cannot save edit history: ${message(error)}`, { cause: error });
|
|
264
|
+
}
|
|
265
|
+
this.undoStack = next.undo;
|
|
266
|
+
this.redoStack = next.redo;
|
|
267
|
+
}
|
|
268
|
+
atomicWrite(target, text, mode) {
|
|
269
|
+
const tmp = `${target}.higgsedit-${process.pid}-${randomUUID()}.tmp`;
|
|
270
|
+
let published = false;
|
|
271
|
+
let fd;
|
|
272
|
+
try {
|
|
273
|
+
fd = openSync(tmp, "wx", mode);
|
|
274
|
+
writeFileSync(fd, text);
|
|
275
|
+
fsyncSync(fd);
|
|
276
|
+
renameSync(tmp, target);
|
|
277
|
+
published = true;
|
|
278
|
+
return identity(fstatSync(fd, { bigint: true }));
|
|
279
|
+
} finally {
|
|
280
|
+
try {
|
|
281
|
+
if (fd !== void 0) closeSync(fd);
|
|
282
|
+
} finally {
|
|
283
|
+
if (!published) {
|
|
284
|
+
try {
|
|
285
|
+
unlinkSync(tmp);
|
|
286
|
+
} catch {
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
export {
|
|
294
|
+
EditHistory
|
|
295
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@higgsfield/video-editor-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Higgsfield Video Editor as a local MCP server over stdio: edits TSX video projects on this machine with the higgsedit CLI and serves the editor panel.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "higgsfield-video-editor-local",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Edit and render video projects on your Mac with Higgsfield. Arrange clips, add text, audio and motion graphics, preview your timeline in the interactive editor, and render finished videos. Generate images and videos in the editor by connecting your Higgsfield account.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Higgsfield AI, Inc."
|
package/plugin/plugin.json
CHANGED