imgx-mcp 1.3.0 → 1.4.1
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/CHANGELOG.md +22 -0
- package/README.md +12 -1
- package/dist/cli.bundle.js +262 -218
- package/dist/mcp.bundle.js +117 -54
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.4.1 (2026-03-04)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **MCP roots detection race condition** — `listRoots()` was called immediately after `server.connect()`, before the MCP initialization handshake completed. The request failed silently, causing `setProjectRoot()` to never execute and images to save to the fallback `~/Pictures/imgx/` instead of `<project-root>/.imgx/`. Now uses `oninitialized` callback to wait for handshake completion and checks `roots` capability before requesting.
|
|
8
|
+
|
|
9
|
+
## 1.4.0 (2026-03-04)
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Sequential file naming for edit chains** — `edit_last` now generates sequential filenames based on the origin file: `cover.png` → `cover-1.png` → `cover-2.png`. Default UUID-based names follow the same pattern: `imgx-a1b2c3d4.png` → `imgx-a1b2c3d4-1.png`
|
|
14
|
+
- **File deletion on undo + re-edit** — When editing after undo, spliced (abandoned) entries' files are deleted from disk, preventing orphaned images
|
|
15
|
+
- **Session base info tracking** — `Session` now stores `baseName`, `baseExt`, `baseDir` to maintain file identity across the edit chain
|
|
16
|
+
- **`getSessionChainNumber()` API** — returns the next sequential number for chained edits
|
|
17
|
+
- **`getSessionBaseInfo()` / `setSessionBaseInfo()` API** — read/write the origin file identity on the active session
|
|
18
|
+
- 15 new tests (total: 81 tests)
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **`saveImage()` signature** — new optional `isChained` parameter for sequential naming (backward compatible, defaults to falsy)
|
|
23
|
+
- Legacy sessions without `baseName` fall back to UUID naming when `isChained` is true
|
|
24
|
+
|
|
3
25
|
## 1.3.0 (2026-03-04)
|
|
4
26
|
|
|
5
27
|
### Added
|
package/README.md
CHANGED
|
@@ -129,7 +129,18 @@ generate → edit_last → edit_last → edit_last
|
|
|
129
129
|
↑ current
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
-
After undo, calling `edit_last` branches from the current position (
|
|
132
|
+
After undo, calling `edit_last` branches from the current position (abandoned entries and their files are deleted from disk).
|
|
133
|
+
|
|
134
|
+
**File naming** — `edit_last` generates sequential filenames based on the origin file:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
generate_image → cover.png
|
|
138
|
+
edit_last → cover-1.png
|
|
139
|
+
edit_last → cover-2.png
|
|
140
|
+
|
|
141
|
+
generate_image (no output) → imgx-a1b2c3d4.png
|
|
142
|
+
edit_last → imgx-a1b2c3d4-1.png
|
|
143
|
+
```
|
|
133
144
|
|
|
134
145
|
**Session switching** — Use `edit_history` to see all sessions, then `switch_session` to resume a previous session. The `edit_last` tool will use the current position in the switched session.
|
|
135
146
|
|
package/dist/cli.bundle.js
CHANGED
|
@@ -39226,10 +39226,239 @@ function getApiKeyFromEnv() {
|
|
|
39226
39226
|
}
|
|
39227
39227
|
|
|
39228
39228
|
// build/core/storage.js
|
|
39229
|
-
import { readFileSync as
|
|
39230
|
-
import { dirname as
|
|
39229
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "node:fs";
|
|
39230
|
+
import { dirname as dirname3, join as join3, resolve as resolve3 } from "node:path";
|
|
39231
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
39232
|
+
import { homedir as homedir3 } from "node:os";
|
|
39233
|
+
|
|
39234
|
+
// build/core/history.js
|
|
39235
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2, rmSync } from "node:fs";
|
|
39236
|
+
import { basename as basename3, dirname as dirname2, extname, join as join2, resolve as resolve2, sep } from "node:path";
|
|
39237
|
+
import { homedir as homedir2, platform as platform2 } from "node:os";
|
|
39231
39238
|
import { randomUUID } from "node:crypto";
|
|
39232
|
-
|
|
39239
|
+
var HISTORY_FILE = "output-history.json";
|
|
39240
|
+
function globalHistoryDir() {
|
|
39241
|
+
if (platform2() === "win32") {
|
|
39242
|
+
return join2(process.env.APPDATA || join2(homedir2(), "AppData", "Roaming"), "imgx");
|
|
39243
|
+
}
|
|
39244
|
+
return join2(process.env.XDG_CONFIG_HOME || join2(homedir2(), ".config"), "imgx");
|
|
39245
|
+
}
|
|
39246
|
+
function historyDir() {
|
|
39247
|
+
if (process.env.IMGX_TEST_CONFIG_DIR)
|
|
39248
|
+
return process.env.IMGX_TEST_CONFIG_DIR;
|
|
39249
|
+
const projectRoot = findProjectRoot();
|
|
39250
|
+
if (projectRoot)
|
|
39251
|
+
return join2(projectRoot, ".imgx");
|
|
39252
|
+
return globalHistoryDir();
|
|
39253
|
+
}
|
|
39254
|
+
function historyPath() {
|
|
39255
|
+
return join2(historyDir(), HISTORY_FILE);
|
|
39256
|
+
}
|
|
39257
|
+
function emptyHistory() {
|
|
39258
|
+
return {
|
|
39259
|
+
sessions: [],
|
|
39260
|
+
activeSessionId: null,
|
|
39261
|
+
maxEntriesPerSession: 10
|
|
39262
|
+
};
|
|
39263
|
+
}
|
|
39264
|
+
function loadHistory() {
|
|
39265
|
+
try {
|
|
39266
|
+
const raw = readFileSync2(historyPath(), "utf-8");
|
|
39267
|
+
return JSON.parse(raw);
|
|
39268
|
+
} catch {
|
|
39269
|
+
return emptyHistory();
|
|
39270
|
+
}
|
|
39271
|
+
}
|
|
39272
|
+
function saveHistory(history) {
|
|
39273
|
+
const dir = historyDir();
|
|
39274
|
+
mkdirSync2(dir, { recursive: true });
|
|
39275
|
+
writeFileSync2(historyPath(), JSON.stringify(history, null, 2) + "\n", "utf-8");
|
|
39276
|
+
}
|
|
39277
|
+
function createSession() {
|
|
39278
|
+
return {
|
|
39279
|
+
id: `s-${randomUUID().slice(0, 8)}`,
|
|
39280
|
+
entries: [],
|
|
39281
|
+
cursor: 0
|
|
39282
|
+
};
|
|
39283
|
+
}
|
|
39284
|
+
function pushHistory(entry, opts) {
|
|
39285
|
+
const history = loadHistory();
|
|
39286
|
+
if (opts.newSession) {
|
|
39287
|
+
const session2 = createSession();
|
|
39288
|
+
if (opts.sessionId)
|
|
39289
|
+
session2.id = opts.sessionId;
|
|
39290
|
+
if (opts.outputDir)
|
|
39291
|
+
session2.outputDir = opts.outputDir;
|
|
39292
|
+
if (entry.filePaths.length > 0) {
|
|
39293
|
+
const fp = entry.filePaths[0];
|
|
39294
|
+
const ext = extname(fp);
|
|
39295
|
+
session2.baseName = basename3(fp, ext);
|
|
39296
|
+
session2.baseExt = ext;
|
|
39297
|
+
session2.baseDir = dirname2(fp);
|
|
39298
|
+
}
|
|
39299
|
+
session2.entries.push(entry);
|
|
39300
|
+
history.sessions.push(session2);
|
|
39301
|
+
history.activeSessionId = session2.id;
|
|
39302
|
+
saveHistory(history);
|
|
39303
|
+
return session2.id;
|
|
39304
|
+
}
|
|
39305
|
+
const session = history.sessions.find((s2) => s2.id === history.activeSessionId);
|
|
39306
|
+
if (!session)
|
|
39307
|
+
throw new Error("No active session. Run generate first.");
|
|
39308
|
+
if (session.cursor > 0) {
|
|
39309
|
+
const removed = session.entries.splice(0, session.cursor);
|
|
39310
|
+
for (const entry2 of removed) {
|
|
39311
|
+
for (const fp of entry2.filePaths) {
|
|
39312
|
+
try {
|
|
39313
|
+
if (existsSync2(fp))
|
|
39314
|
+
rmSync(fp);
|
|
39315
|
+
} catch {
|
|
39316
|
+
}
|
|
39317
|
+
}
|
|
39318
|
+
}
|
|
39319
|
+
session.cursor = 0;
|
|
39320
|
+
}
|
|
39321
|
+
session.entries.unshift(entry);
|
|
39322
|
+
while (session.entries.length > history.maxEntriesPerSession) {
|
|
39323
|
+
session.entries.splice(session.entries.length - 2, 1);
|
|
39324
|
+
}
|
|
39325
|
+
saveHistory(history);
|
|
39326
|
+
return session.id;
|
|
39327
|
+
}
|
|
39328
|
+
function getActiveEntry() {
|
|
39329
|
+
const history = loadHistory();
|
|
39330
|
+
if (!history.activeSessionId)
|
|
39331
|
+
return void 0;
|
|
39332
|
+
const session = history.sessions.find((s2) => s2.id === history.activeSessionId);
|
|
39333
|
+
if (!session || session.entries.length === 0)
|
|
39334
|
+
return void 0;
|
|
39335
|
+
return session.entries[session.cursor];
|
|
39336
|
+
}
|
|
39337
|
+
function getActiveSession() {
|
|
39338
|
+
const history = loadHistory();
|
|
39339
|
+
if (!history.activeSessionId) {
|
|
39340
|
+
throw new Error("No active session. Run generate first.");
|
|
39341
|
+
}
|
|
39342
|
+
const session = history.sessions.find((s2) => s2.id === history.activeSessionId);
|
|
39343
|
+
if (!session) {
|
|
39344
|
+
throw new Error("No active session. Run generate first.");
|
|
39345
|
+
}
|
|
39346
|
+
return { history, session };
|
|
39347
|
+
}
|
|
39348
|
+
function undoHistory() {
|
|
39349
|
+
const { history, session } = getActiveSession();
|
|
39350
|
+
const maxCursor = session.entries.length - 1;
|
|
39351
|
+
if (session.cursor >= maxCursor) {
|
|
39352
|
+
throw new Error("Already at the oldest entry in this session");
|
|
39353
|
+
}
|
|
39354
|
+
session.cursor += 1;
|
|
39355
|
+
saveHistory(history);
|
|
39356
|
+
return {
|
|
39357
|
+
entry: session.entries[session.cursor],
|
|
39358
|
+
position: `${session.cursor + 1}/${session.entries.length}`
|
|
39359
|
+
};
|
|
39360
|
+
}
|
|
39361
|
+
function redoHistory() {
|
|
39362
|
+
const { history, session } = getActiveSession();
|
|
39363
|
+
if (session.cursor <= 0) {
|
|
39364
|
+
throw new Error("Already at the latest entry");
|
|
39365
|
+
}
|
|
39366
|
+
session.cursor -= 1;
|
|
39367
|
+
saveHistory(history);
|
|
39368
|
+
return {
|
|
39369
|
+
entry: session.entries[session.cursor],
|
|
39370
|
+
position: `${session.cursor + 1}/${session.entries.length}`
|
|
39371
|
+
};
|
|
39372
|
+
}
|
|
39373
|
+
function switchSession(sessionId) {
|
|
39374
|
+
const history = loadHistory();
|
|
39375
|
+
const session = history.sessions.find((s2) => s2.id === sessionId);
|
|
39376
|
+
if (!session) {
|
|
39377
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
39378
|
+
}
|
|
39379
|
+
history.activeSessionId = sessionId;
|
|
39380
|
+
saveHistory(history);
|
|
39381
|
+
}
|
|
39382
|
+
function getHistory() {
|
|
39383
|
+
return loadHistory();
|
|
39384
|
+
}
|
|
39385
|
+
function clearHistory() {
|
|
39386
|
+
const history = loadHistory();
|
|
39387
|
+
const filePaths = [];
|
|
39388
|
+
for (const session of history.sessions) {
|
|
39389
|
+
for (const entry of session.entries) {
|
|
39390
|
+
filePaths.push(...entry.filePaths);
|
|
39391
|
+
}
|
|
39392
|
+
}
|
|
39393
|
+
history.sessions = [];
|
|
39394
|
+
history.activeSessionId = null;
|
|
39395
|
+
saveHistory(history);
|
|
39396
|
+
return { filePaths };
|
|
39397
|
+
}
|
|
39398
|
+
function clearSession(sessionId) {
|
|
39399
|
+
const history = loadHistory();
|
|
39400
|
+
const idx = history.sessions.findIndex((s2) => s2.id === sessionId);
|
|
39401
|
+
if (idx === -1)
|
|
39402
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
39403
|
+
const [removed] = history.sessions.splice(idx, 1);
|
|
39404
|
+
const filePaths = [];
|
|
39405
|
+
for (const entry of removed.entries)
|
|
39406
|
+
filePaths.push(...entry.filePaths);
|
|
39407
|
+
if (history.activeSessionId === sessionId)
|
|
39408
|
+
history.activeSessionId = null;
|
|
39409
|
+
saveHistory(history);
|
|
39410
|
+
return { filePaths };
|
|
39411
|
+
}
|
|
39412
|
+
function clearGlobalHistory() {
|
|
39413
|
+
const globalPath = join2(globalHistoryDir(), HISTORY_FILE);
|
|
39414
|
+
try {
|
|
39415
|
+
const raw = readFileSync2(globalPath, "utf-8");
|
|
39416
|
+
const history = JSON.parse(raw);
|
|
39417
|
+
const filePaths = [];
|
|
39418
|
+
for (const session of history.sessions) {
|
|
39419
|
+
for (const entry of session.entries)
|
|
39420
|
+
filePaths.push(...entry.filePaths);
|
|
39421
|
+
}
|
|
39422
|
+
const dir = globalHistoryDir();
|
|
39423
|
+
mkdirSync2(dir, { recursive: true });
|
|
39424
|
+
writeFileSync2(globalPath, JSON.stringify(emptyHistory(), null, 2) + "\n", "utf-8");
|
|
39425
|
+
return { filePaths };
|
|
39426
|
+
} catch {
|
|
39427
|
+
return { filePaths: [] };
|
|
39428
|
+
}
|
|
39429
|
+
}
|
|
39430
|
+
function updateHistoryPaths(oldBase, newBase) {
|
|
39431
|
+
const history = loadHistory();
|
|
39432
|
+
for (const session of history.sessions) {
|
|
39433
|
+
for (const entry of session.entries) {
|
|
39434
|
+
entry.filePaths = entry.filePaths.map((p) => p.startsWith(oldBase) ? newBase + p.slice(oldBase.length) : p);
|
|
39435
|
+
if (entry.inputImage && entry.inputImage.startsWith(oldBase)) {
|
|
39436
|
+
entry.inputImage = newBase + entry.inputImage.slice(oldBase.length);
|
|
39437
|
+
}
|
|
39438
|
+
}
|
|
39439
|
+
}
|
|
39440
|
+
saveHistory(history);
|
|
39441
|
+
}
|
|
39442
|
+
function getSessionChainNumber() {
|
|
39443
|
+
const history = loadHistory();
|
|
39444
|
+
if (!history.activeSessionId)
|
|
39445
|
+
return null;
|
|
39446
|
+
const session = history.sessions.find((s2) => s2.id === history.activeSessionId);
|
|
39447
|
+
if (!session || session.entries.length === 0)
|
|
39448
|
+
return null;
|
|
39449
|
+
return session.entries.length;
|
|
39450
|
+
}
|
|
39451
|
+
function getSessionBaseInfo() {
|
|
39452
|
+
const history = loadHistory();
|
|
39453
|
+
if (!history.activeSessionId)
|
|
39454
|
+
return null;
|
|
39455
|
+
const session = history.sessions.find((s2) => s2.id === history.activeSessionId);
|
|
39456
|
+
if (!session?.baseName)
|
|
39457
|
+
return null;
|
|
39458
|
+
return { baseName: session.baseName, baseExt: session.baseExt, baseDir: session.baseDir };
|
|
39459
|
+
}
|
|
39460
|
+
|
|
39461
|
+
// build/core/storage.js
|
|
39233
39462
|
var MIME_TO_EXT = {
|
|
39234
39463
|
"image/png": ".png",
|
|
39235
39464
|
"image/jpeg": ".jpg",
|
|
@@ -39237,7 +39466,7 @@ var MIME_TO_EXT = {
|
|
|
39237
39466
|
};
|
|
39238
39467
|
function readImageAsBase64(filePath) {
|
|
39239
39468
|
const absPath = resolveProjectPath(filePath);
|
|
39240
|
-
const buffer =
|
|
39469
|
+
const buffer = readFileSync3(absPath);
|
|
39241
39470
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
39242
39471
|
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png";
|
|
39243
39472
|
return { data: buffer.toString("base64"), mimeType };
|
|
@@ -39250,21 +39479,29 @@ function fallbackOutputDir(outputDir) {
|
|
|
39250
39479
|
return resolveProjectPath(configured);
|
|
39251
39480
|
const projectRoot = findProjectRoot();
|
|
39252
39481
|
if (projectRoot)
|
|
39253
|
-
return
|
|
39254
|
-
return
|
|
39482
|
+
return join3(projectRoot, ".imgx");
|
|
39483
|
+
return join3(homedir3(), "Pictures", "imgx");
|
|
39255
39484
|
}
|
|
39256
|
-
function saveImage(image, outputPath, outputDir, sessionId) {
|
|
39485
|
+
function saveImage(image, outputPath, outputDir, sessionId, isChained) {
|
|
39257
39486
|
const ext = MIME_TO_EXT[image.mimeType] || ".png";
|
|
39258
39487
|
let filePath;
|
|
39259
|
-
if (
|
|
39488
|
+
if (isChained) {
|
|
39489
|
+
const baseInfo = getSessionBaseInfo();
|
|
39490
|
+
const chainNum = getSessionChainNumber();
|
|
39491
|
+
if (baseInfo && chainNum !== null) {
|
|
39492
|
+
filePath = resolve3(baseInfo.baseDir, `${baseInfo.baseName}-${chainNum}${baseInfo.baseExt}`);
|
|
39493
|
+
} else {
|
|
39494
|
+
filePath = resolve3(fallbackOutputDir(outputDir), sessionId || "", `imgx-${randomUUID2().slice(0, 8)}${ext}`);
|
|
39495
|
+
}
|
|
39496
|
+
} else if (outputPath) {
|
|
39260
39497
|
filePath = resolveProjectPath(outputPath);
|
|
39261
39498
|
} else {
|
|
39262
39499
|
const baseDir = fallbackOutputDir(outputDir);
|
|
39263
|
-
const targetDir = sessionId ?
|
|
39264
|
-
filePath =
|
|
39500
|
+
const targetDir = sessionId ? join3(baseDir, sessionId) : baseDir;
|
|
39501
|
+
filePath = resolve3(targetDir, `imgx-${randomUUID2().slice(0, 8)}${ext}`);
|
|
39265
39502
|
}
|
|
39266
|
-
|
|
39267
|
-
|
|
39503
|
+
mkdirSync3(dirname3(filePath), { recursive: true });
|
|
39504
|
+
writeFileSync3(filePath, image.data);
|
|
39268
39505
|
return filePath;
|
|
39269
39506
|
}
|
|
39270
39507
|
|
|
@@ -39399,7 +39636,7 @@ function initGemini() {
|
|
|
39399
39636
|
}
|
|
39400
39637
|
|
|
39401
39638
|
// build/providers/openai/client.js
|
|
39402
|
-
import { readFileSync as
|
|
39639
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
39403
39640
|
|
|
39404
39641
|
// build/providers/openai/capabilities.js
|
|
39405
39642
|
var OPENAI_PROVIDER_INFO = {
|
|
@@ -39513,7 +39750,7 @@ var OpenAIProvider = class {
|
|
|
39513
39750
|
async edit(input, model) {
|
|
39514
39751
|
const modelName = model || this.info.defaultModel;
|
|
39515
39752
|
const absPath = resolveProjectPath(input.inputImage);
|
|
39516
|
-
const imageBuffer =
|
|
39753
|
+
const imageBuffer = readFileSync4(absPath);
|
|
39517
39754
|
const ext = absPath.split(".").pop()?.toLowerCase();
|
|
39518
39755
|
const contentType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png";
|
|
39519
39756
|
const fields = {
|
|
@@ -39584,201 +39821,8 @@ function initOpenAI() {
|
|
|
39584
39821
|
registerProvider(new OpenAIProvider(apiKey));
|
|
39585
39822
|
}
|
|
39586
39823
|
|
|
39587
|
-
// build/core/history.js
|
|
39588
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "node:fs";
|
|
39589
|
-
import { join as join3, resolve as resolve3, sep } from "node:path";
|
|
39590
|
-
import { homedir as homedir3, platform as platform2 } from "node:os";
|
|
39591
|
-
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
39592
|
-
var HISTORY_FILE = "output-history.json";
|
|
39593
|
-
function globalHistoryDir() {
|
|
39594
|
-
if (platform2() === "win32") {
|
|
39595
|
-
return join3(process.env.APPDATA || join3(homedir3(), "AppData", "Roaming"), "imgx");
|
|
39596
|
-
}
|
|
39597
|
-
return join3(process.env.XDG_CONFIG_HOME || join3(homedir3(), ".config"), "imgx");
|
|
39598
|
-
}
|
|
39599
|
-
function historyDir() {
|
|
39600
|
-
if (process.env.IMGX_TEST_CONFIG_DIR)
|
|
39601
|
-
return process.env.IMGX_TEST_CONFIG_DIR;
|
|
39602
|
-
const projectRoot = findProjectRoot();
|
|
39603
|
-
if (projectRoot)
|
|
39604
|
-
return join3(projectRoot, ".imgx");
|
|
39605
|
-
return globalHistoryDir();
|
|
39606
|
-
}
|
|
39607
|
-
function historyPath() {
|
|
39608
|
-
return join3(historyDir(), HISTORY_FILE);
|
|
39609
|
-
}
|
|
39610
|
-
function emptyHistory() {
|
|
39611
|
-
return {
|
|
39612
|
-
sessions: [],
|
|
39613
|
-
activeSessionId: null,
|
|
39614
|
-
maxEntriesPerSession: 10
|
|
39615
|
-
};
|
|
39616
|
-
}
|
|
39617
|
-
function loadHistory() {
|
|
39618
|
-
try {
|
|
39619
|
-
const raw = readFileSync4(historyPath(), "utf-8");
|
|
39620
|
-
return JSON.parse(raw);
|
|
39621
|
-
} catch {
|
|
39622
|
-
return emptyHistory();
|
|
39623
|
-
}
|
|
39624
|
-
}
|
|
39625
|
-
function saveHistory(history) {
|
|
39626
|
-
const dir = historyDir();
|
|
39627
|
-
mkdirSync3(dir, { recursive: true });
|
|
39628
|
-
writeFileSync3(historyPath(), JSON.stringify(history, null, 2) + "\n", "utf-8");
|
|
39629
|
-
}
|
|
39630
|
-
function createSession() {
|
|
39631
|
-
return {
|
|
39632
|
-
id: `s-${randomUUID2().slice(0, 8)}`,
|
|
39633
|
-
entries: [],
|
|
39634
|
-
cursor: 0
|
|
39635
|
-
};
|
|
39636
|
-
}
|
|
39637
|
-
function pushHistory(entry, opts) {
|
|
39638
|
-
const history = loadHistory();
|
|
39639
|
-
if (opts.newSession) {
|
|
39640
|
-
const session2 = createSession();
|
|
39641
|
-
if (opts.sessionId)
|
|
39642
|
-
session2.id = opts.sessionId;
|
|
39643
|
-
if (opts.outputDir)
|
|
39644
|
-
session2.outputDir = opts.outputDir;
|
|
39645
|
-
session2.entries.push(entry);
|
|
39646
|
-
history.sessions.push(session2);
|
|
39647
|
-
history.activeSessionId = session2.id;
|
|
39648
|
-
saveHistory(history);
|
|
39649
|
-
return session2.id;
|
|
39650
|
-
}
|
|
39651
|
-
const session = history.sessions.find((s2) => s2.id === history.activeSessionId);
|
|
39652
|
-
if (!session)
|
|
39653
|
-
throw new Error("No active session. Run generate first.");
|
|
39654
|
-
if (session.cursor > 0) {
|
|
39655
|
-
session.entries.splice(0, session.cursor);
|
|
39656
|
-
session.cursor = 0;
|
|
39657
|
-
}
|
|
39658
|
-
session.entries.unshift(entry);
|
|
39659
|
-
while (session.entries.length > history.maxEntriesPerSession) {
|
|
39660
|
-
session.entries.splice(session.entries.length - 2, 1);
|
|
39661
|
-
}
|
|
39662
|
-
saveHistory(history);
|
|
39663
|
-
return session.id;
|
|
39664
|
-
}
|
|
39665
|
-
function getActiveEntry() {
|
|
39666
|
-
const history = loadHistory();
|
|
39667
|
-
if (!history.activeSessionId)
|
|
39668
|
-
return void 0;
|
|
39669
|
-
const session = history.sessions.find((s2) => s2.id === history.activeSessionId);
|
|
39670
|
-
if (!session || session.entries.length === 0)
|
|
39671
|
-
return void 0;
|
|
39672
|
-
return session.entries[session.cursor];
|
|
39673
|
-
}
|
|
39674
|
-
function getActiveSession() {
|
|
39675
|
-
const history = loadHistory();
|
|
39676
|
-
if (!history.activeSessionId) {
|
|
39677
|
-
throw new Error("No active session. Run generate first.");
|
|
39678
|
-
}
|
|
39679
|
-
const session = history.sessions.find((s2) => s2.id === history.activeSessionId);
|
|
39680
|
-
if (!session) {
|
|
39681
|
-
throw new Error("No active session. Run generate first.");
|
|
39682
|
-
}
|
|
39683
|
-
return { history, session };
|
|
39684
|
-
}
|
|
39685
|
-
function undoHistory() {
|
|
39686
|
-
const { history, session } = getActiveSession();
|
|
39687
|
-
const maxCursor = session.entries.length - 1;
|
|
39688
|
-
if (session.cursor >= maxCursor) {
|
|
39689
|
-
throw new Error("Already at the oldest entry in this session");
|
|
39690
|
-
}
|
|
39691
|
-
session.cursor += 1;
|
|
39692
|
-
saveHistory(history);
|
|
39693
|
-
return {
|
|
39694
|
-
entry: session.entries[session.cursor],
|
|
39695
|
-
position: `${session.cursor + 1}/${session.entries.length}`
|
|
39696
|
-
};
|
|
39697
|
-
}
|
|
39698
|
-
function redoHistory() {
|
|
39699
|
-
const { history, session } = getActiveSession();
|
|
39700
|
-
if (session.cursor <= 0) {
|
|
39701
|
-
throw new Error("Already at the latest entry");
|
|
39702
|
-
}
|
|
39703
|
-
session.cursor -= 1;
|
|
39704
|
-
saveHistory(history);
|
|
39705
|
-
return {
|
|
39706
|
-
entry: session.entries[session.cursor],
|
|
39707
|
-
position: `${session.cursor + 1}/${session.entries.length}`
|
|
39708
|
-
};
|
|
39709
|
-
}
|
|
39710
|
-
function switchSession(sessionId) {
|
|
39711
|
-
const history = loadHistory();
|
|
39712
|
-
const session = history.sessions.find((s2) => s2.id === sessionId);
|
|
39713
|
-
if (!session) {
|
|
39714
|
-
throw new Error(`Session not found: ${sessionId}`);
|
|
39715
|
-
}
|
|
39716
|
-
history.activeSessionId = sessionId;
|
|
39717
|
-
saveHistory(history);
|
|
39718
|
-
}
|
|
39719
|
-
function getHistory() {
|
|
39720
|
-
return loadHistory();
|
|
39721
|
-
}
|
|
39722
|
-
function clearHistory() {
|
|
39723
|
-
const history = loadHistory();
|
|
39724
|
-
const filePaths = [];
|
|
39725
|
-
for (const session of history.sessions) {
|
|
39726
|
-
for (const entry of session.entries) {
|
|
39727
|
-
filePaths.push(...entry.filePaths);
|
|
39728
|
-
}
|
|
39729
|
-
}
|
|
39730
|
-
history.sessions = [];
|
|
39731
|
-
history.activeSessionId = null;
|
|
39732
|
-
saveHistory(history);
|
|
39733
|
-
return { filePaths };
|
|
39734
|
-
}
|
|
39735
|
-
function clearSession(sessionId) {
|
|
39736
|
-
const history = loadHistory();
|
|
39737
|
-
const idx = history.sessions.findIndex((s2) => s2.id === sessionId);
|
|
39738
|
-
if (idx === -1)
|
|
39739
|
-
throw new Error(`Session not found: ${sessionId}`);
|
|
39740
|
-
const [removed] = history.sessions.splice(idx, 1);
|
|
39741
|
-
const filePaths = [];
|
|
39742
|
-
for (const entry of removed.entries)
|
|
39743
|
-
filePaths.push(...entry.filePaths);
|
|
39744
|
-
if (history.activeSessionId === sessionId)
|
|
39745
|
-
history.activeSessionId = null;
|
|
39746
|
-
saveHistory(history);
|
|
39747
|
-
return { filePaths };
|
|
39748
|
-
}
|
|
39749
|
-
function clearGlobalHistory() {
|
|
39750
|
-
const globalPath = join3(globalHistoryDir(), HISTORY_FILE);
|
|
39751
|
-
try {
|
|
39752
|
-
const raw = readFileSync4(globalPath, "utf-8");
|
|
39753
|
-
const history = JSON.parse(raw);
|
|
39754
|
-
const filePaths = [];
|
|
39755
|
-
for (const session of history.sessions) {
|
|
39756
|
-
for (const entry of session.entries)
|
|
39757
|
-
filePaths.push(...entry.filePaths);
|
|
39758
|
-
}
|
|
39759
|
-
const dir = globalHistoryDir();
|
|
39760
|
-
mkdirSync3(dir, { recursive: true });
|
|
39761
|
-
writeFileSync3(globalPath, JSON.stringify(emptyHistory(), null, 2) + "\n", "utf-8");
|
|
39762
|
-
return { filePaths };
|
|
39763
|
-
} catch {
|
|
39764
|
-
return { filePaths: [] };
|
|
39765
|
-
}
|
|
39766
|
-
}
|
|
39767
|
-
function updateHistoryPaths(oldBase, newBase) {
|
|
39768
|
-
const history = loadHistory();
|
|
39769
|
-
for (const session of history.sessions) {
|
|
39770
|
-
for (const entry of session.entries) {
|
|
39771
|
-
entry.filePaths = entry.filePaths.map((p) => p.startsWith(oldBase) ? newBase + p.slice(oldBase.length) : p);
|
|
39772
|
-
if (entry.inputImage && entry.inputImage.startsWith(oldBase)) {
|
|
39773
|
-
entry.inputImage = newBase + entry.inputImage.slice(oldBase.length);
|
|
39774
|
-
}
|
|
39775
|
-
}
|
|
39776
|
-
}
|
|
39777
|
-
saveHistory(history);
|
|
39778
|
-
}
|
|
39779
|
-
|
|
39780
39824
|
// build/cli/commands/init.js
|
|
39781
|
-
import { existsSync as
|
|
39825
|
+
import { existsSync as existsSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
39782
39826
|
import { resolve as resolve4 } from "node:path";
|
|
39783
39827
|
|
|
39784
39828
|
// build/cli/output.js
|
|
@@ -39801,7 +39845,7 @@ var TEMPLATE = {
|
|
|
39801
39845
|
};
|
|
39802
39846
|
function runInit() {
|
|
39803
39847
|
const filePath = resolve4(".imgxrc");
|
|
39804
|
-
if (
|
|
39848
|
+
if (existsSync3(filePath)) {
|
|
39805
39849
|
fail(".imgxrc already exists in current directory");
|
|
39806
39850
|
}
|
|
39807
39851
|
writeFileSync4(filePath, JSON.stringify(TEMPLATE, null, 2) + "\n", "utf-8");
|
|
@@ -39884,7 +39928,7 @@ async function runEdit(provider, args) {
|
|
|
39884
39928
|
|
|
39885
39929
|
// build/cli/commands/config.js
|
|
39886
39930
|
import { createInterface } from "node:readline";
|
|
39887
|
-
import { existsSync as
|
|
39931
|
+
import { existsSync as existsSync4, readdirSync, cpSync, rmSync as rmSync2, mkdirSync as mkdirSync4 } from "node:fs";
|
|
39888
39932
|
import { resolve as resolve5 } from "node:path";
|
|
39889
39933
|
import { homedir as homedir4 } from "node:os";
|
|
39890
39934
|
function extractProvider(args) {
|
|
@@ -39997,7 +40041,7 @@ function setOutputDir(newDir, skipConfirm, noMove) {
|
|
|
39997
40041
|
success({ key: "output-dir", status: "saved", outputDir: resolvedNew });
|
|
39998
40042
|
return;
|
|
39999
40043
|
}
|
|
40000
|
-
if (!
|
|
40044
|
+
if (!existsSync4(oldDir)) {
|
|
40001
40045
|
success({ key: "output-dir", status: "saved", outputDir: resolvedNew });
|
|
40002
40046
|
return;
|
|
40003
40047
|
}
|
|
@@ -40039,7 +40083,7 @@ function setOutputDir(newDir, skipConfirm, noMove) {
|
|
|
40039
40083
|
function moveFiles(oldDir, newDir) {
|
|
40040
40084
|
mkdirSync4(newDir, { recursive: true });
|
|
40041
40085
|
cpSync(oldDir, newDir, { recursive: true });
|
|
40042
|
-
|
|
40086
|
+
rmSync2(oldDir, { recursive: true, force: true });
|
|
40043
40087
|
updateHistoryPaths(oldDir, newDir);
|
|
40044
40088
|
}
|
|
40045
40089
|
function getKey(key, provider) {
|
|
@@ -40088,8 +40132,8 @@ function showAll() {
|
|
|
40088
40132
|
|
|
40089
40133
|
// build/cli/commands/history.js
|
|
40090
40134
|
import { createInterface as createInterface2 } from "node:readline";
|
|
40091
|
-
import { rmSync as
|
|
40092
|
-
import { dirname as
|
|
40135
|
+
import { rmSync as rmSync3, rmdirSync, existsSync as existsSync5 } from "node:fs";
|
|
40136
|
+
import { dirname as dirname4 } from "node:path";
|
|
40093
40137
|
function runHistory(args) {
|
|
40094
40138
|
const sub = args[0];
|
|
40095
40139
|
if (!sub) {
|
|
@@ -40191,10 +40235,10 @@ function deleteSessionFiles(filePaths) {
|
|
|
40191
40235
|
const dirs = /* @__PURE__ */ new Set();
|
|
40192
40236
|
for (const fp of filePaths) {
|
|
40193
40237
|
try {
|
|
40194
|
-
if (
|
|
40195
|
-
|
|
40238
|
+
if (existsSync5(fp)) {
|
|
40239
|
+
rmSync3(fp);
|
|
40196
40240
|
count++;
|
|
40197
|
-
dirs.add(
|
|
40241
|
+
dirs.add(dirname4(fp));
|
|
40198
40242
|
}
|
|
40199
40243
|
} catch {
|
|
40200
40244
|
}
|
|
@@ -40237,7 +40281,7 @@ function runRedo() {
|
|
|
40237
40281
|
}
|
|
40238
40282
|
|
|
40239
40283
|
// build/cli/index.js
|
|
40240
|
-
var VERSION2 = "1.
|
|
40284
|
+
var VERSION2 = "1.4.1";
|
|
40241
40285
|
var HELP = `imgx v${VERSION2} \u2014 AI image generation and editing for MCP-compatible AI agents
|
|
40242
40286
|
|
|
40243
40287
|
Commands:
|
package/dist/mcp.bundle.js
CHANGED
|
@@ -28935,6 +28935,8 @@ __export(history_exports, {
|
|
|
28935
28935
|
getActiveEntry: () => getActiveEntry,
|
|
28936
28936
|
getActiveSessionOutputDir: () => getActiveSessionOutputDir,
|
|
28937
28937
|
getHistory: () => getHistory,
|
|
28938
|
+
getSessionBaseInfo: () => getSessionBaseInfo,
|
|
28939
|
+
getSessionChainNumber: () => getSessionChainNumber,
|
|
28938
28940
|
globalHistoryDir: () => globalHistoryDir,
|
|
28939
28941
|
historyDir: () => historyDir,
|
|
28940
28942
|
isManagedPath: () => isManagedPath,
|
|
@@ -28942,30 +28944,31 @@ __export(history_exports, {
|
|
|
28942
28944
|
pushHistory: () => pushHistory,
|
|
28943
28945
|
redoHistory: () => redoHistory,
|
|
28944
28946
|
saveHistory: () => saveHistory,
|
|
28947
|
+
setSessionBaseInfo: () => setSessionBaseInfo,
|
|
28945
28948
|
switchSession: () => switchSession,
|
|
28946
28949
|
undoHistory: () => undoHistory,
|
|
28947
28950
|
updateHistoryPaths: () => updateHistoryPaths
|
|
28948
28951
|
});
|
|
28949
|
-
import { readFileSync as
|
|
28950
|
-
import { join as
|
|
28951
|
-
import { homedir as
|
|
28952
|
-
import { randomUUID
|
|
28952
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2, rmSync } from "node:fs";
|
|
28953
|
+
import { basename as basename3, dirname as dirname2, extname, join as join2, resolve as resolve2, sep } from "node:path";
|
|
28954
|
+
import { homedir as homedir2, platform as platform2 } from "node:os";
|
|
28955
|
+
import { randomUUID } from "node:crypto";
|
|
28953
28956
|
function globalHistoryDir() {
|
|
28954
28957
|
if (platform2() === "win32") {
|
|
28955
|
-
return
|
|
28958
|
+
return join2(process.env.APPDATA || join2(homedir2(), "AppData", "Roaming"), "imgx");
|
|
28956
28959
|
}
|
|
28957
|
-
return
|
|
28960
|
+
return join2(process.env.XDG_CONFIG_HOME || join2(homedir2(), ".config"), "imgx");
|
|
28958
28961
|
}
|
|
28959
28962
|
function historyDir() {
|
|
28960
28963
|
if (process.env.IMGX_TEST_CONFIG_DIR)
|
|
28961
28964
|
return process.env.IMGX_TEST_CONFIG_DIR;
|
|
28962
28965
|
const projectRoot = findProjectRoot();
|
|
28963
28966
|
if (projectRoot)
|
|
28964
|
-
return
|
|
28967
|
+
return join2(projectRoot, ".imgx");
|
|
28965
28968
|
return globalHistoryDir();
|
|
28966
28969
|
}
|
|
28967
28970
|
function historyPath() {
|
|
28968
|
-
return
|
|
28971
|
+
return join2(historyDir(), HISTORY_FILE);
|
|
28969
28972
|
}
|
|
28970
28973
|
function emptyHistory() {
|
|
28971
28974
|
return {
|
|
@@ -28976,7 +28979,7 @@ function emptyHistory() {
|
|
|
28976
28979
|
}
|
|
28977
28980
|
function loadHistory() {
|
|
28978
28981
|
try {
|
|
28979
|
-
const raw =
|
|
28982
|
+
const raw = readFileSync2(historyPath(), "utf-8");
|
|
28980
28983
|
return JSON.parse(raw);
|
|
28981
28984
|
} catch {
|
|
28982
28985
|
return emptyHistory();
|
|
@@ -28984,12 +28987,12 @@ function loadHistory() {
|
|
|
28984
28987
|
}
|
|
28985
28988
|
function saveHistory(history) {
|
|
28986
28989
|
const dir = historyDir();
|
|
28987
|
-
|
|
28988
|
-
|
|
28990
|
+
mkdirSync2(dir, { recursive: true });
|
|
28991
|
+
writeFileSync2(historyPath(), JSON.stringify(history, null, 2) + "\n", "utf-8");
|
|
28989
28992
|
}
|
|
28990
28993
|
function createSession() {
|
|
28991
28994
|
return {
|
|
28992
|
-
id: `s-${
|
|
28995
|
+
id: `s-${randomUUID().slice(0, 8)}`,
|
|
28993
28996
|
entries: [],
|
|
28994
28997
|
cursor: 0
|
|
28995
28998
|
};
|
|
@@ -29002,6 +29005,13 @@ function pushHistory(entry, opts) {
|
|
|
29002
29005
|
session2.id = opts.sessionId;
|
|
29003
29006
|
if (opts.outputDir)
|
|
29004
29007
|
session2.outputDir = opts.outputDir;
|
|
29008
|
+
if (entry.filePaths.length > 0) {
|
|
29009
|
+
const fp = entry.filePaths[0];
|
|
29010
|
+
const ext = extname(fp);
|
|
29011
|
+
session2.baseName = basename3(fp, ext);
|
|
29012
|
+
session2.baseExt = ext;
|
|
29013
|
+
session2.baseDir = dirname2(fp);
|
|
29014
|
+
}
|
|
29005
29015
|
session2.entries.push(entry);
|
|
29006
29016
|
history.sessions.push(session2);
|
|
29007
29017
|
history.activeSessionId = session2.id;
|
|
@@ -29012,7 +29022,16 @@ function pushHistory(entry, opts) {
|
|
|
29012
29022
|
if (!session)
|
|
29013
29023
|
throw new Error("No active session. Run generate first.");
|
|
29014
29024
|
if (session.cursor > 0) {
|
|
29015
|
-
session.entries.splice(0, session.cursor);
|
|
29025
|
+
const removed = session.entries.splice(0, session.cursor);
|
|
29026
|
+
for (const entry2 of removed) {
|
|
29027
|
+
for (const fp of entry2.filePaths) {
|
|
29028
|
+
try {
|
|
29029
|
+
if (existsSync2(fp))
|
|
29030
|
+
rmSync(fp);
|
|
29031
|
+
} catch {
|
|
29032
|
+
}
|
|
29033
|
+
}
|
|
29034
|
+
}
|
|
29016
29035
|
session.cursor = 0;
|
|
29017
29036
|
}
|
|
29018
29037
|
session.entries.unshift(entry);
|
|
@@ -29114,15 +29133,15 @@ function clearSession(sessionId) {
|
|
|
29114
29133
|
return { filePaths };
|
|
29115
29134
|
}
|
|
29116
29135
|
function isManagedPath(filePath) {
|
|
29117
|
-
const normalized =
|
|
29118
|
-
const projectManaged =
|
|
29119
|
-
const globalManaged =
|
|
29136
|
+
const normalized = resolve2(filePath);
|
|
29137
|
+
const projectManaged = resolve2(historyDir());
|
|
29138
|
+
const globalManaged = resolve2(join2(homedir2(), "Pictures", "imgx"));
|
|
29120
29139
|
return normalized.startsWith(projectManaged + sep) || normalized === projectManaged || normalized.startsWith(globalManaged + sep) || normalized === globalManaged;
|
|
29121
29140
|
}
|
|
29122
29141
|
function clearGlobalHistory() {
|
|
29123
|
-
const globalPath =
|
|
29142
|
+
const globalPath = join2(globalHistoryDir(), HISTORY_FILE);
|
|
29124
29143
|
try {
|
|
29125
|
-
const raw =
|
|
29144
|
+
const raw = readFileSync2(globalPath, "utf-8");
|
|
29126
29145
|
const history = JSON.parse(raw);
|
|
29127
29146
|
const filePaths = [];
|
|
29128
29147
|
for (const session of history.sessions) {
|
|
@@ -29130,8 +29149,8 @@ function clearGlobalHistory() {
|
|
|
29130
29149
|
filePaths.push(...entry.filePaths);
|
|
29131
29150
|
}
|
|
29132
29151
|
const dir = globalHistoryDir();
|
|
29133
|
-
|
|
29134
|
-
|
|
29152
|
+
mkdirSync2(dir, { recursive: true });
|
|
29153
|
+
writeFileSync2(globalPath, JSON.stringify(emptyHistory(), null, 2) + "\n", "utf-8");
|
|
29135
29154
|
return { filePaths };
|
|
29136
29155
|
} catch {
|
|
29137
29156
|
return { filePaths: [] };
|
|
@@ -29149,6 +29168,36 @@ function updateHistoryPaths(oldBase, newBase) {
|
|
|
29149
29168
|
}
|
|
29150
29169
|
saveHistory(history);
|
|
29151
29170
|
}
|
|
29171
|
+
function getSessionChainNumber() {
|
|
29172
|
+
const history = loadHistory();
|
|
29173
|
+
if (!history.activeSessionId)
|
|
29174
|
+
return null;
|
|
29175
|
+
const session = history.sessions.find((s2) => s2.id === history.activeSessionId);
|
|
29176
|
+
if (!session || session.entries.length === 0)
|
|
29177
|
+
return null;
|
|
29178
|
+
return session.entries.length;
|
|
29179
|
+
}
|
|
29180
|
+
function getSessionBaseInfo() {
|
|
29181
|
+
const history = loadHistory();
|
|
29182
|
+
if (!history.activeSessionId)
|
|
29183
|
+
return null;
|
|
29184
|
+
const session = history.sessions.find((s2) => s2.id === history.activeSessionId);
|
|
29185
|
+
if (!session?.baseName)
|
|
29186
|
+
return null;
|
|
29187
|
+
return { baseName: session.baseName, baseExt: session.baseExt, baseDir: session.baseDir };
|
|
29188
|
+
}
|
|
29189
|
+
function setSessionBaseInfo(baseName, baseExt, baseDir) {
|
|
29190
|
+
const history = loadHistory();
|
|
29191
|
+
if (!history.activeSessionId)
|
|
29192
|
+
return;
|
|
29193
|
+
const session = history.sessions.find((s2) => s2.id === history.activeSessionId);
|
|
29194
|
+
if (!session)
|
|
29195
|
+
return;
|
|
29196
|
+
session.baseName = baseName;
|
|
29197
|
+
session.baseExt = baseExt;
|
|
29198
|
+
session.baseDir = baseDir;
|
|
29199
|
+
saveHistory(history);
|
|
29200
|
+
}
|
|
29152
29201
|
var HISTORY_FILE;
|
|
29153
29202
|
var init_history = __esm({
|
|
29154
29203
|
"build/core/history.js"() {
|
|
@@ -69542,10 +69591,11 @@ function getApiKeyFromEnv() {
|
|
|
69542
69591
|
|
|
69543
69592
|
// build/core/storage.js
|
|
69544
69593
|
init_config();
|
|
69545
|
-
|
|
69546
|
-
import {
|
|
69547
|
-
import {
|
|
69548
|
-
import {
|
|
69594
|
+
init_history();
|
|
69595
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "node:fs";
|
|
69596
|
+
import { dirname as dirname3, join as join3, resolve as resolve3 } from "node:path";
|
|
69597
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
69598
|
+
import { homedir as homedir3 } from "node:os";
|
|
69549
69599
|
var MIME_TO_EXT = {
|
|
69550
69600
|
"image/png": ".png",
|
|
69551
69601
|
"image/jpeg": ".jpg",
|
|
@@ -69553,7 +69603,7 @@ var MIME_TO_EXT = {
|
|
|
69553
69603
|
};
|
|
69554
69604
|
function readImageAsBase64(filePath) {
|
|
69555
69605
|
const absPath = resolveProjectPath(filePath);
|
|
69556
|
-
const buffer =
|
|
69606
|
+
const buffer = readFileSync3(absPath);
|
|
69557
69607
|
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
69558
69608
|
const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png";
|
|
69559
69609
|
return { data: buffer.toString("base64"), mimeType };
|
|
@@ -69566,21 +69616,29 @@ function fallbackOutputDir(outputDir) {
|
|
|
69566
69616
|
return resolveProjectPath(configured);
|
|
69567
69617
|
const projectRoot = findProjectRoot();
|
|
69568
69618
|
if (projectRoot)
|
|
69569
|
-
return
|
|
69570
|
-
return
|
|
69619
|
+
return join3(projectRoot, ".imgx");
|
|
69620
|
+
return join3(homedir3(), "Pictures", "imgx");
|
|
69571
69621
|
}
|
|
69572
|
-
function saveImage(image, outputPath, outputDir, sessionId) {
|
|
69622
|
+
function saveImage(image, outputPath, outputDir, sessionId, isChained) {
|
|
69573
69623
|
const ext = MIME_TO_EXT[image.mimeType] || ".png";
|
|
69574
69624
|
let filePath;
|
|
69575
|
-
if (
|
|
69625
|
+
if (isChained) {
|
|
69626
|
+
const baseInfo = getSessionBaseInfo();
|
|
69627
|
+
const chainNum = getSessionChainNumber();
|
|
69628
|
+
if (baseInfo && chainNum !== null) {
|
|
69629
|
+
filePath = resolve3(baseInfo.baseDir, `${baseInfo.baseName}-${chainNum}${baseInfo.baseExt}`);
|
|
69630
|
+
} else {
|
|
69631
|
+
filePath = resolve3(fallbackOutputDir(outputDir), sessionId || "", `imgx-${randomUUID2().slice(0, 8)}${ext}`);
|
|
69632
|
+
}
|
|
69633
|
+
} else if (outputPath) {
|
|
69576
69634
|
filePath = resolveProjectPath(outputPath);
|
|
69577
69635
|
} else {
|
|
69578
69636
|
const baseDir = fallbackOutputDir(outputDir);
|
|
69579
|
-
const targetDir = sessionId ?
|
|
69580
|
-
filePath =
|
|
69637
|
+
const targetDir = sessionId ? join3(baseDir, sessionId) : baseDir;
|
|
69638
|
+
filePath = resolve3(targetDir, `imgx-${randomUUID2().slice(0, 8)}${ext}`);
|
|
69581
69639
|
}
|
|
69582
|
-
|
|
69583
|
-
|
|
69640
|
+
mkdirSync3(dirname3(filePath), { recursive: true });
|
|
69641
|
+
writeFileSync3(filePath, image.data);
|
|
69584
69642
|
return filePath;
|
|
69585
69643
|
}
|
|
69586
69644
|
|
|
@@ -69719,7 +69777,7 @@ init_config();
|
|
|
69719
69777
|
|
|
69720
69778
|
// build/providers/openai/client.js
|
|
69721
69779
|
init_config();
|
|
69722
|
-
import { readFileSync as
|
|
69780
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
69723
69781
|
|
|
69724
69782
|
// build/providers/openai/capabilities.js
|
|
69725
69783
|
var OPENAI_PROVIDER_INFO = {
|
|
@@ -69833,7 +69891,7 @@ var OpenAIProvider = class {
|
|
|
69833
69891
|
async edit(input, model) {
|
|
69834
69892
|
const modelName = model || this.info.defaultModel;
|
|
69835
69893
|
const absPath = resolveProjectPath(input.inputImage);
|
|
69836
|
-
const imageBuffer =
|
|
69894
|
+
const imageBuffer = readFileSync4(absPath);
|
|
69837
69895
|
const ext = absPath.split(".").pop()?.toLowerCase();
|
|
69838
69896
|
const contentType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png";
|
|
69839
69897
|
const fields = {
|
|
@@ -69919,7 +69977,7 @@ function buildImageContent(images, paths, extra) {
|
|
|
69919
69977
|
}
|
|
69920
69978
|
var server = new McpServer({
|
|
69921
69979
|
name: "imgx",
|
|
69922
|
-
version: "1.
|
|
69980
|
+
version: "1.4.1"
|
|
69923
69981
|
});
|
|
69924
69982
|
initGemini();
|
|
69925
69983
|
initOpenAI();
|
|
@@ -70060,7 +70118,7 @@ server.tool("edit_last", "Edit the last generated/edited image with new text ins
|
|
|
70060
70118
|
}
|
|
70061
70119
|
const sessionId = loadHistory().activeSessionId;
|
|
70062
70120
|
const sessionOutputDir = args.output_dir || getActiveSessionOutputDir();
|
|
70063
|
-
const saved = saveImage(result.images[0], args.output, sessionOutputDir, sessionId || void 0);
|
|
70121
|
+
const saved = saveImage(result.images[0], args.output, sessionOutputDir, sessionId || void 0, true);
|
|
70064
70122
|
pushHistory({
|
|
70065
70123
|
filePaths: [saved],
|
|
70066
70124
|
prompt: args.prompt,
|
|
@@ -70142,8 +70200,8 @@ server.tool("clear_history", "Clear edit history for the current project. Files
|
|
|
70142
70200
|
let filesDeleted = 0;
|
|
70143
70201
|
let skippedFiles = 0;
|
|
70144
70202
|
if (args.delete_files) {
|
|
70145
|
-
const { existsSync:
|
|
70146
|
-
const { dirname:
|
|
70203
|
+
const { existsSync: existsSync3, rmSync: rmSync2, rmdirSync } = await import("node:fs");
|
|
70204
|
+
const { dirname: dirname4 } = await import("node:path");
|
|
70147
70205
|
const dirs = /* @__PURE__ */ new Set();
|
|
70148
70206
|
for (const fp of result.filePaths) {
|
|
70149
70207
|
if (!isManagedPath(fp)) {
|
|
@@ -70151,10 +70209,10 @@ server.tool("clear_history", "Clear edit history for the current project. Files
|
|
|
70151
70209
|
continue;
|
|
70152
70210
|
}
|
|
70153
70211
|
try {
|
|
70154
|
-
if (
|
|
70155
|
-
|
|
70212
|
+
if (existsSync3(fp)) {
|
|
70213
|
+
rmSync2(fp);
|
|
70156
70214
|
filesDeleted++;
|
|
70157
|
-
dirs.add(
|
|
70215
|
+
dirs.add(dirname4(fp));
|
|
70158
70216
|
}
|
|
70159
70217
|
} catch {
|
|
70160
70218
|
}
|
|
@@ -70187,11 +70245,11 @@ server.tool("set_output_dir", "Change the default output directory for generated
|
|
|
70187
70245
|
config2.defaults.outputDir = args.path;
|
|
70188
70246
|
saveConfig2(config2);
|
|
70189
70247
|
if (args.move_files && oldDir !== args.path) {
|
|
70190
|
-
const { mkdirSync: mkdirSync4, cpSync, rmSync, existsSync:
|
|
70191
|
-
if (
|
|
70248
|
+
const { mkdirSync: mkdirSync4, cpSync, rmSync: rmSync2, existsSync: existsSync3 } = await import("node:fs");
|
|
70249
|
+
if (existsSync3(oldDir)) {
|
|
70192
70250
|
mkdirSync4(args.path, { recursive: true });
|
|
70193
70251
|
cpSync(oldDir, args.path, { recursive: true });
|
|
70194
|
-
|
|
70252
|
+
rmSync2(oldDir, { recursive: true, force: true });
|
|
70195
70253
|
updateHistoryPaths2(oldDir, args.path);
|
|
70196
70254
|
}
|
|
70197
70255
|
}
|
|
@@ -70229,17 +70287,22 @@ function fileUriToPath(uri) {
|
|
|
70229
70287
|
}
|
|
70230
70288
|
async function main() {
|
|
70231
70289
|
const transport = new StdioServerTransport();
|
|
70232
|
-
|
|
70233
|
-
|
|
70234
|
-
|
|
70235
|
-
|
|
70236
|
-
|
|
70237
|
-
|
|
70238
|
-
|
|
70290
|
+
server.server.oninitialized = async () => {
|
|
70291
|
+
try {
|
|
70292
|
+
const caps = server.server.getClientCapabilities();
|
|
70293
|
+
if (!caps?.roots)
|
|
70294
|
+
return;
|
|
70295
|
+
const result = await server.server.listRoots();
|
|
70296
|
+
if (result.roots.length > 0) {
|
|
70297
|
+
const rootUri = result.roots[0].uri;
|
|
70298
|
+
if (rootUri.startsWith("file://")) {
|
|
70299
|
+
setProjectRoot(fileUriToPath(rootUri));
|
|
70300
|
+
}
|
|
70239
70301
|
}
|
|
70302
|
+
} catch {
|
|
70240
70303
|
}
|
|
70241
|
-
}
|
|
70242
|
-
|
|
70304
|
+
};
|
|
70305
|
+
await server.connect(transport);
|
|
70243
70306
|
}
|
|
70244
70307
|
main().catch((err) => {
|
|
70245
70308
|
console.error("MCP server error:", err);
|
package/package.json
CHANGED