@blockrun/clawrouter 0.12.31 → 0.12.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -12
- package/dist/cli.js +279 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.js +289 -14
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/imagegen/SKILL.md +7 -7
- package/skills/x-api/SKILL.md +5 -5
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ One wallet, 41+ models, zero API keys.
|
|
|
48
48
|
| [vs OpenRouter](#-vs-openrouter) | Why ClawRouter wins |
|
|
49
49
|
| [Support](#-support) | Telegram, X, founders |
|
|
50
50
|
|
|
51
|
-
**API Docs:** [Image Generation](docs/image-generation.md) · [Architecture](docs/architecture.md) · [Configuration](docs/configuration.md)
|
|
51
|
+
**API Docs:** [Image Generation & Editing](docs/image-generation.md) · [Architecture](docs/architecture.md) · [Configuration](docs/configuration.md)
|
|
52
52
|
|
|
53
53
|
---
|
|
54
54
|
|
|
@@ -102,6 +102,27 @@ Generate images directly from chat with `/imagegen`:
|
|
|
102
102
|
|
|
103
103
|
Default model: `nano-banana`. Images are returned as hosted URLs for compatibility with Telegram, Discord, and other clients.
|
|
104
104
|
|
|
105
|
+
## ✏️ Image Editing (img2img)
|
|
106
|
+
|
|
107
|
+
Edit existing images with `/img2img` — pass a local file and describe what to change:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
/img2img --image ~/photo.png change the background to a starry sky
|
|
111
|
+
/img2img --image ./cat.jpg --mask ./mask.png remove the background
|
|
112
|
+
/img2img --image /tmp/portrait.png --size 1536x1024 add a hat
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
| Option | Required | Description |
|
|
116
|
+
| ----------------- | -------- | ------------------------------------- |
|
|
117
|
+
| `--image <path>` | Yes | Local image file path (supports `~/`) |
|
|
118
|
+
| `--mask <path>` | No | Mask image (white = area to edit) |
|
|
119
|
+
| `--model <model>` | No | Model to use (default: `gpt-image-1`) |
|
|
120
|
+
| `--size <WxH>` | No | Output size (default: `1024x1024`) |
|
|
121
|
+
|
|
122
|
+
Supported model: `gpt-image-1` (OpenAI GPT Image 1, $0.02/image). ClawRouter reads the local file, converts it to base64, and sends it to BlockRun's `/v1/images/image2image` endpoint with automatic x402 payment.
|
|
123
|
+
|
|
124
|
+
**API endpoint:** `POST http://localhost:8402/v1/images/image2image` is also available for programmatic use — see [Image Generation docs](docs/image-generation.md#post-v1imagesimage2image) for API reference and code examples.
|
|
125
|
+
|
|
105
126
|
---
|
|
106
127
|
|
|
107
128
|
## ⚡ How It Works
|
|
@@ -330,17 +351,17 @@ npm test
|
|
|
330
351
|
|
|
331
352
|
## 📚 More Resources
|
|
332
353
|
|
|
333
|
-
| Resource
|
|
334
|
-
|
|
|
335
|
-
| [Documentation](https://blockrun.ai/docs)
|
|
336
|
-
| [Model Pricing](https://blockrun.ai/models)
|
|
337
|
-
| [Image Generation](docs/image-generation.md) | API examples, 5 models |
|
|
338
|
-
| [Routing Profiles](docs/routing-profiles.md)
|
|
339
|
-
| [Architecture](docs/architecture.md)
|
|
340
|
-
| [Configuration](docs/configuration.md)
|
|
341
|
-
| [vs OpenRouter](docs/vs-openrouter.md)
|
|
342
|
-
| [Features](docs/features.md)
|
|
343
|
-
| [Troubleshooting](docs/troubleshooting.md)
|
|
354
|
+
| Resource | Description |
|
|
355
|
+
| ------------------------------------------------------ | ------------------------ |
|
|
356
|
+
| [Documentation](https://blockrun.ai/docs) | Full docs |
|
|
357
|
+
| [Model Pricing](https://blockrun.ai/models) | All models & prices |
|
|
358
|
+
| [Image Generation & Editing](docs/image-generation.md) | API examples, 5 models |
|
|
359
|
+
| [Routing Profiles](docs/routing-profiles.md) | ECO/AUTO/PREMIUM details |
|
|
360
|
+
| [Architecture](docs/architecture.md) | Technical deep dive |
|
|
361
|
+
| [Configuration](docs/configuration.md) | Environment variables |
|
|
362
|
+
| [vs OpenRouter](docs/vs-openrouter.md) | Why ClawRouter wins |
|
|
363
|
+
| [Features](docs/features.md) | All features |
|
|
364
|
+
| [Troubleshooting](docs/troubleshooting.md) | Common issues |
|
|
344
365
|
|
|
345
366
|
---
|
|
346
367
|
|
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import { finished } from "stream";
|
|
|
6
6
|
import { homedir as homedir4 } from "os";
|
|
7
7
|
import { join as join5 } from "path";
|
|
8
8
|
import { mkdir as mkdir3, writeFile as writeFile2, readFile, stat as fsStat } from "fs/promises";
|
|
9
|
+
import { readFileSync, existsSync } from "fs";
|
|
9
10
|
import { createPublicClient as createPublicClient2, http as http2 } from "viem";
|
|
10
11
|
import { base as base2 } from "viem/chains";
|
|
11
12
|
import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
|
|
@@ -5614,6 +5615,22 @@ async function proxyPartnerRequest(req, res, apiBase, payFetch) {
|
|
|
5614
5615
|
}).catch(() => {
|
|
5615
5616
|
});
|
|
5616
5617
|
}
|
|
5618
|
+
function readImageFileAsDataUri(filePath) {
|
|
5619
|
+
const resolved = filePath.startsWith("~/") ? join5(homedir4(), filePath.slice(2)) : filePath;
|
|
5620
|
+
if (!existsSync(resolved)) {
|
|
5621
|
+
throw new Error(`Image file not found: ${resolved}`);
|
|
5622
|
+
}
|
|
5623
|
+
const ext = resolved.split(".").pop()?.toLowerCase() ?? "png";
|
|
5624
|
+
const mimeMap = {
|
|
5625
|
+
png: "image/png",
|
|
5626
|
+
jpg: "image/jpeg",
|
|
5627
|
+
jpeg: "image/jpeg",
|
|
5628
|
+
webp: "image/webp"
|
|
5629
|
+
};
|
|
5630
|
+
const mime = mimeMap[ext] ?? "image/png";
|
|
5631
|
+
const data = readFileSync(resolved);
|
|
5632
|
+
return `data:${mime};base64,${data.toString("base64")}`;
|
|
5633
|
+
}
|
|
5617
5634
|
async function uploadDataUriToHost(dataUri) {
|
|
5618
5635
|
const match = dataUri.match(/^data:(image\/\w+);base64,(.+)$/);
|
|
5619
5636
|
if (!match) throw new Error("Invalid data URI format");
|
|
@@ -5887,14 +5904,31 @@ async function startProxy(options) {
|
|
|
5887
5904
|
await mkdir3(IMAGE_DIR, { recursive: true });
|
|
5888
5905
|
const port2 = server.address()?.port ?? 8402;
|
|
5889
5906
|
for (const img of result.data) {
|
|
5890
|
-
const
|
|
5891
|
-
if (
|
|
5892
|
-
const [, mimeType, b64] =
|
|
5907
|
+
const dataUriMatch = img.url?.match(/^data:(image\/\w+);base64,(.+)$/);
|
|
5908
|
+
if (dataUriMatch) {
|
|
5909
|
+
const [, mimeType, b64] = dataUriMatch;
|
|
5893
5910
|
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
5894
5911
|
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
5895
5912
|
await writeFile2(join5(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
5896
5913
|
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
5897
5914
|
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
5915
|
+
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
5916
|
+
try {
|
|
5917
|
+
const imgResp = await fetch(img.url);
|
|
5918
|
+
if (imgResp.ok) {
|
|
5919
|
+
const contentType = imgResp.headers.get("content-type") ?? "image/png";
|
|
5920
|
+
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
5921
|
+
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
5922
|
+
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
5923
|
+
await writeFile2(join5(IMAGE_DIR, filename), buf);
|
|
5924
|
+
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
5925
|
+
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
5926
|
+
}
|
|
5927
|
+
} catch (downloadErr) {
|
|
5928
|
+
console.warn(
|
|
5929
|
+
`[ClawRouter] Failed to download image, using original URL: ${downloadErr instanceof Error ? downloadErr.message : String(downloadErr)}`
|
|
5930
|
+
);
|
|
5931
|
+
}
|
|
5898
5932
|
}
|
|
5899
5933
|
}
|
|
5900
5934
|
}
|
|
@@ -5910,6 +5944,76 @@ async function startProxy(options) {
|
|
|
5910
5944
|
}
|
|
5911
5945
|
return;
|
|
5912
5946
|
}
|
|
5947
|
+
if (req.url === "/v1/images/image2image" && req.method === "POST") {
|
|
5948
|
+
const chunks = [];
|
|
5949
|
+
for await (const chunk of req) {
|
|
5950
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
5951
|
+
}
|
|
5952
|
+
const reqBody = Buffer.concat(chunks);
|
|
5953
|
+
try {
|
|
5954
|
+
const upstream = await payFetch(`${apiBase}/v1/images/image2image`, {
|
|
5955
|
+
method: "POST",
|
|
5956
|
+
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
5957
|
+
body: reqBody
|
|
5958
|
+
});
|
|
5959
|
+
const text = await upstream.text();
|
|
5960
|
+
if (!upstream.ok) {
|
|
5961
|
+
res.writeHead(upstream.status, { "Content-Type": "application/json" });
|
|
5962
|
+
res.end(text);
|
|
5963
|
+
return;
|
|
5964
|
+
}
|
|
5965
|
+
let result;
|
|
5966
|
+
try {
|
|
5967
|
+
result = JSON.parse(text);
|
|
5968
|
+
} catch {
|
|
5969
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
5970
|
+
res.end(text);
|
|
5971
|
+
return;
|
|
5972
|
+
}
|
|
5973
|
+
if (result.data?.length) {
|
|
5974
|
+
await mkdir3(IMAGE_DIR, { recursive: true });
|
|
5975
|
+
const port2 = server.address()?.port ?? 8402;
|
|
5976
|
+
for (const img of result.data) {
|
|
5977
|
+
const dataUriMatch = img.url?.match(/^data:(image\/\w+);base64,(.+)$/);
|
|
5978
|
+
if (dataUriMatch) {
|
|
5979
|
+
const [, mimeType, b64] = dataUriMatch;
|
|
5980
|
+
const ext = mimeType === "image/jpeg" ? "jpg" : mimeType.split("/")[1] ?? "png";
|
|
5981
|
+
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
5982
|
+
await writeFile2(join5(IMAGE_DIR, filename), Buffer.from(b64, "base64"));
|
|
5983
|
+
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
5984
|
+
console.log(`[ClawRouter] Image saved \u2192 ${img.url}`);
|
|
5985
|
+
} else if (img.url?.startsWith("https://") || img.url?.startsWith("http://")) {
|
|
5986
|
+
try {
|
|
5987
|
+
const imgResp = await fetch(img.url);
|
|
5988
|
+
if (imgResp.ok) {
|
|
5989
|
+
const contentType = imgResp.headers.get("content-type") ?? "image/png";
|
|
5990
|
+
const ext = contentType.includes("jpeg") || contentType.includes("jpg") ? "jpg" : contentType.includes("webp") ? "webp" : "png";
|
|
5991
|
+
const filename = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}.${ext}`;
|
|
5992
|
+
const buf = Buffer.from(await imgResp.arrayBuffer());
|
|
5993
|
+
await writeFile2(join5(IMAGE_DIR, filename), buf);
|
|
5994
|
+
img.url = `http://localhost:${port2}/images/${filename}`;
|
|
5995
|
+
console.log(`[ClawRouter] Image downloaded & saved \u2192 ${img.url}`);
|
|
5996
|
+
}
|
|
5997
|
+
} catch (downloadErr) {
|
|
5998
|
+
console.warn(
|
|
5999
|
+
`[ClawRouter] Failed to download image, using original URL: ${downloadErr instanceof Error ? downloadErr.message : String(downloadErr)}`
|
|
6000
|
+
);
|
|
6001
|
+
}
|
|
6002
|
+
}
|
|
6003
|
+
}
|
|
6004
|
+
}
|
|
6005
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6006
|
+
res.end(JSON.stringify(result));
|
|
6007
|
+
} catch (err) {
|
|
6008
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6009
|
+
console.error(`[ClawRouter] Image editing error: ${msg}`);
|
|
6010
|
+
if (!res.headersSent) {
|
|
6011
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
6012
|
+
res.end(JSON.stringify({ error: "Image editing failed", details: msg }));
|
|
6013
|
+
}
|
|
6014
|
+
}
|
|
6015
|
+
return;
|
|
6016
|
+
}
|
|
5913
6017
|
if (req.url?.match(/^\/v1\/(?:x|partner)\//)) {
|
|
5914
6018
|
try {
|
|
5915
6019
|
await proxyPartnerRequest(req, res, apiBase, payFetch);
|
|
@@ -6524,6 +6628,177 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
6524
6628
|
}
|
|
6525
6629
|
return;
|
|
6526
6630
|
}
|
|
6631
|
+
if (lastContent.startsWith("/img2img")) {
|
|
6632
|
+
const imgArgs = lastContent.slice("/img2img".length).trim();
|
|
6633
|
+
let img2imgModel = "openai/gpt-image-1";
|
|
6634
|
+
let img2imgSize = "1024x1024";
|
|
6635
|
+
let imagePath = null;
|
|
6636
|
+
let maskPath = null;
|
|
6637
|
+
let img2imgPrompt = imgArgs;
|
|
6638
|
+
const imageMatch = imgArgs.match(/--image\s+(\S+)/);
|
|
6639
|
+
if (imageMatch) {
|
|
6640
|
+
imagePath = imageMatch[1];
|
|
6641
|
+
img2imgPrompt = img2imgPrompt.replace(/--image\s+\S+/, "").trim();
|
|
6642
|
+
}
|
|
6643
|
+
const maskMatch = imgArgs.match(/--mask\s+(\S+)/);
|
|
6644
|
+
if (maskMatch) {
|
|
6645
|
+
maskPath = maskMatch[1];
|
|
6646
|
+
img2imgPrompt = img2imgPrompt.replace(/--mask\s+\S+/, "").trim();
|
|
6647
|
+
}
|
|
6648
|
+
const img2imgSizeMatch = imgArgs.match(/--size\s+(\d+x\d+)/);
|
|
6649
|
+
if (img2imgSizeMatch) {
|
|
6650
|
+
img2imgSize = img2imgSizeMatch[1];
|
|
6651
|
+
img2imgPrompt = img2imgPrompt.replace(/--size\s+\d+x\d+/, "").trim();
|
|
6652
|
+
}
|
|
6653
|
+
const img2imgModelMatch = imgArgs.match(/--model\s+(\S+)/);
|
|
6654
|
+
if (img2imgModelMatch) {
|
|
6655
|
+
const raw = img2imgModelMatch[1];
|
|
6656
|
+
const IMG2IMG_ALIASES = {
|
|
6657
|
+
"gpt-image": "openai/gpt-image-1",
|
|
6658
|
+
"gpt-image-1": "openai/gpt-image-1"
|
|
6659
|
+
};
|
|
6660
|
+
img2imgModel = IMG2IMG_ALIASES[raw] ?? raw;
|
|
6661
|
+
img2imgPrompt = img2imgPrompt.replace(/--model\s+\S+/, "").trim();
|
|
6662
|
+
}
|
|
6663
|
+
const usageText = [
|
|
6664
|
+
"Usage: /img2img --image <path> <prompt>",
|
|
6665
|
+
"",
|
|
6666
|
+
"Options:",
|
|
6667
|
+
" --image <path> Source image path (required)",
|
|
6668
|
+
" --mask <path> Mask image path (optional, white = area to edit)",
|
|
6669
|
+
" --model <model> Model (default: gpt-image-1)",
|
|
6670
|
+
" --size <WxH> Output size (default: 1024x1024)",
|
|
6671
|
+
"",
|
|
6672
|
+
"Models:",
|
|
6673
|
+
" gpt-image-1 OpenAI GPT Image 1 \u2014 $0.02/image",
|
|
6674
|
+
"",
|
|
6675
|
+
"Examples:",
|
|
6676
|
+
" /img2img --image ~/photo.png change background to starry sky",
|
|
6677
|
+
" /img2img --image ./cat.jpg --mask ./mask.png remove the background",
|
|
6678
|
+
" /img2img --image /tmp/portrait.png --size 1536x1024 add a hat"
|
|
6679
|
+
].join("\n");
|
|
6680
|
+
const sendImg2ImgText = (text) => {
|
|
6681
|
+
const completionId = `chatcmpl-img2img-${Date.now()}`;
|
|
6682
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
6683
|
+
if (isStreaming) {
|
|
6684
|
+
res.writeHead(200, {
|
|
6685
|
+
"Content-Type": "text/event-stream",
|
|
6686
|
+
"Cache-Control": "no-cache",
|
|
6687
|
+
Connection: "keep-alive"
|
|
6688
|
+
});
|
|
6689
|
+
res.write(
|
|
6690
|
+
`data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/img2img", choices: [{ index: 0, delta: { role: "assistant", content: text }, finish_reason: null }] })}
|
|
6691
|
+
|
|
6692
|
+
`
|
|
6693
|
+
);
|
|
6694
|
+
res.write(
|
|
6695
|
+
`data: ${JSON.stringify({ id: completionId, object: "chat.completion.chunk", created: timestamp, model: "clawrouter/img2img", choices: [{ index: 0, delta: {}, finish_reason: "stop" }] })}
|
|
6696
|
+
|
|
6697
|
+
`
|
|
6698
|
+
);
|
|
6699
|
+
res.write("data: [DONE]\n\n");
|
|
6700
|
+
res.end();
|
|
6701
|
+
} else {
|
|
6702
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6703
|
+
res.end(
|
|
6704
|
+
JSON.stringify({
|
|
6705
|
+
id: completionId,
|
|
6706
|
+
object: "chat.completion",
|
|
6707
|
+
created: timestamp,
|
|
6708
|
+
model: "clawrouter/img2img",
|
|
6709
|
+
choices: [
|
|
6710
|
+
{
|
|
6711
|
+
index: 0,
|
|
6712
|
+
message: { role: "assistant", content: text },
|
|
6713
|
+
finish_reason: "stop"
|
|
6714
|
+
}
|
|
6715
|
+
],
|
|
6716
|
+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
6717
|
+
})
|
|
6718
|
+
);
|
|
6719
|
+
}
|
|
6720
|
+
};
|
|
6721
|
+
if (!imagePath || !img2imgPrompt) {
|
|
6722
|
+
sendImg2ImgText(usageText);
|
|
6723
|
+
return;
|
|
6724
|
+
}
|
|
6725
|
+
let imageDataUri;
|
|
6726
|
+
let maskDataUri;
|
|
6727
|
+
try {
|
|
6728
|
+
imageDataUri = readImageFileAsDataUri(imagePath);
|
|
6729
|
+
if (maskPath) maskDataUri = readImageFileAsDataUri(maskPath);
|
|
6730
|
+
} catch (fileErr) {
|
|
6731
|
+
const fileErrMsg = fileErr instanceof Error ? fileErr.message : String(fileErr);
|
|
6732
|
+
sendImg2ImgText(`Failed to read image file: ${fileErrMsg}`);
|
|
6733
|
+
return;
|
|
6734
|
+
}
|
|
6735
|
+
console.log(
|
|
6736
|
+
`[ClawRouter] /img2img \u2192 ${img2imgModel} (${img2imgSize}): ${img2imgPrompt.slice(0, 80)}`
|
|
6737
|
+
);
|
|
6738
|
+
try {
|
|
6739
|
+
const img2imgBody = JSON.stringify({
|
|
6740
|
+
model: img2imgModel,
|
|
6741
|
+
prompt: img2imgPrompt,
|
|
6742
|
+
image: imageDataUri,
|
|
6743
|
+
...maskDataUri ? { mask: maskDataUri } : {},
|
|
6744
|
+
size: img2imgSize,
|
|
6745
|
+
n: 1
|
|
6746
|
+
});
|
|
6747
|
+
const img2imgResponse = await payFetch(`${apiBase}/v1/images/image2image`, {
|
|
6748
|
+
method: "POST",
|
|
6749
|
+
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
6750
|
+
body: img2imgBody
|
|
6751
|
+
});
|
|
6752
|
+
const img2imgResult = await img2imgResponse.json();
|
|
6753
|
+
let responseText;
|
|
6754
|
+
if (!img2imgResponse.ok || img2imgResult.error) {
|
|
6755
|
+
const errMsg = typeof img2imgResult.error === "string" ? img2imgResult.error : img2imgResult.error?.message ?? `HTTP ${img2imgResponse.status}`;
|
|
6756
|
+
responseText = `Image editing failed: ${errMsg}`;
|
|
6757
|
+
console.log(`[ClawRouter] /img2img error: ${errMsg}`);
|
|
6758
|
+
} else {
|
|
6759
|
+
const images = img2imgResult.data ?? [];
|
|
6760
|
+
if (images.length === 0) {
|
|
6761
|
+
responseText = "Image editing returned no results.";
|
|
6762
|
+
} else {
|
|
6763
|
+
const lines = [];
|
|
6764
|
+
for (const img of images) {
|
|
6765
|
+
if (img.url) {
|
|
6766
|
+
if (img.url.startsWith("data:")) {
|
|
6767
|
+
try {
|
|
6768
|
+
const hostedUrl = await uploadDataUriToHost(img.url);
|
|
6769
|
+
lines.push(hostedUrl);
|
|
6770
|
+
} catch (uploadErr) {
|
|
6771
|
+
console.error(
|
|
6772
|
+
`[ClawRouter] /img2img: failed to upload data URI: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
|
|
6773
|
+
);
|
|
6774
|
+
lines.push("Image edited but upload failed. Try again.");
|
|
6775
|
+
}
|
|
6776
|
+
} else {
|
|
6777
|
+
lines.push(img.url);
|
|
6778
|
+
}
|
|
6779
|
+
}
|
|
6780
|
+
if (img.revised_prompt) lines.push(`Revised prompt: ${img.revised_prompt}`);
|
|
6781
|
+
}
|
|
6782
|
+
lines.push("", `Model: ${img2imgModel} | Size: ${img2imgSize}`);
|
|
6783
|
+
responseText = lines.join("\n");
|
|
6784
|
+
}
|
|
6785
|
+
console.log(`[ClawRouter] /img2img success: ${images.length} image(s)`);
|
|
6786
|
+
}
|
|
6787
|
+
sendImg2ImgText(responseText);
|
|
6788
|
+
} catch (err) {
|
|
6789
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
6790
|
+
console.error(`[ClawRouter] /img2img error: ${errMsg}`);
|
|
6791
|
+
if (!res.headersSent) {
|
|
6792
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
6793
|
+
res.end(
|
|
6794
|
+
JSON.stringify({
|
|
6795
|
+
error: { message: `Image editing failed: ${errMsg}`, type: "img2img_error" }
|
|
6796
|
+
})
|
|
6797
|
+
);
|
|
6798
|
+
}
|
|
6799
|
+
}
|
|
6800
|
+
return;
|
|
6801
|
+
}
|
|
6527
6802
|
if (parsed.stream === true) {
|
|
6528
6803
|
parsed.stream = false;
|
|
6529
6804
|
bodyModified = true;
|
|
@@ -7899,7 +8174,7 @@ ClawRouter Partner APIs (v${VERSION})
|
|
|
7899
8174
|
wallet,
|
|
7900
8175
|
port: args.port,
|
|
7901
8176
|
onReady: (port) => {
|
|
7902
|
-
console.log(`[ClawRouter] Proxy listening on http://127.0.0.1:${port}`);
|
|
8177
|
+
console.log(`[ClawRouter] v${VERSION} | Proxy listening on http://127.0.0.1:${port}`);
|
|
7903
8178
|
console.log(`[ClawRouter] Health check: http://127.0.0.1:${port}/health`);
|
|
7904
8179
|
},
|
|
7905
8180
|
onError: (error) => {
|