chatroom-cli 1.136.0 → 1.137.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.
- package/dist/index.js +456 -132
- package/dist/index.js.map +13 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2140,7 +2140,156 @@ var require_commander = __commonJS((exports) => {
|
|
|
2140
2140
|
exports.InvalidOptionArgumentError = InvalidArgumentError;
|
|
2141
2141
|
});
|
|
2142
2142
|
|
|
2143
|
+
// src/commands/generate/model-capabilities.ts
|
|
2144
|
+
function getModelCapability(id) {
|
|
2145
|
+
return Object.prototype.hasOwnProperty.call(MODEL_CAPABILITIES, id) ? MODEL_CAPABILITIES[id] : undefined;
|
|
2146
|
+
}
|
|
2147
|
+
function describeKey(capability, key, source) {
|
|
2148
|
+
const support = capability.params[key];
|
|
2149
|
+
if (support === undefined) {
|
|
2150
|
+
return [
|
|
2151
|
+
{
|
|
2152
|
+
kind: "unknown",
|
|
2153
|
+
key,
|
|
2154
|
+
message: `not in the CLI's capability data for ${capability.id}; not checked.`
|
|
2155
|
+
}
|
|
2156
|
+
];
|
|
2157
|
+
}
|
|
2158
|
+
if (support.support === "unsupported") {
|
|
2159
|
+
return [{ kind: "unsupported", key, source, message: support.reason }];
|
|
2160
|
+
}
|
|
2161
|
+
return [];
|
|
2162
|
+
}
|
|
2163
|
+
function describeInputKeys(capability, input) {
|
|
2164
|
+
const disclosures = [];
|
|
2165
|
+
for (const key of Object.keys(input)) {
|
|
2166
|
+
if (key === "aspect_ratio")
|
|
2167
|
+
continue;
|
|
2168
|
+
disclosures.push(...describeKey(capability, key, "input"));
|
|
2169
|
+
}
|
|
2170
|
+
return disclosures;
|
|
2171
|
+
}
|
|
2172
|
+
function describeFlagKeys(capability, flags) {
|
|
2173
|
+
return flags.aspectRatio === undefined ? [] : describeKey(capability, "aspect_ratio", "flag");
|
|
2174
|
+
}
|
|
2175
|
+
function describeDefaults(capability, input) {
|
|
2176
|
+
return Object.entries(capability.params).flatMap(([key, support]) => {
|
|
2177
|
+
if (support.support !== "supported" || support.default === undefined)
|
|
2178
|
+
return [];
|
|
2179
|
+
if (input[key] !== undefined)
|
|
2180
|
+
return [];
|
|
2181
|
+
return [
|
|
2182
|
+
{
|
|
2183
|
+
kind: "default",
|
|
2184
|
+
key,
|
|
2185
|
+
message: `${key} is not set; the model default ${support.default} applies. Set one explicit value for every call of a set.`
|
|
2186
|
+
}
|
|
2187
|
+
];
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
function describeNotes(capability) {
|
|
2191
|
+
return capability.notes.map((message) => ({ kind: "note", message }));
|
|
2192
|
+
}
|
|
2193
|
+
function describeConstraints(model, input, flags) {
|
|
2194
|
+
const capability = getModelCapability(model);
|
|
2195
|
+
if (capability === undefined) {
|
|
2196
|
+
return [
|
|
2197
|
+
{
|
|
2198
|
+
kind: "note",
|
|
2199
|
+
message: `The CLI has no capability data for ${model}; no constraints were checked.`
|
|
2200
|
+
}
|
|
2201
|
+
];
|
|
2202
|
+
}
|
|
2203
|
+
return [
|
|
2204
|
+
...describeInputKeys(capability, input),
|
|
2205
|
+
...describeFlagKeys(capability, flags),
|
|
2206
|
+
...describeDefaults(capability, input),
|
|
2207
|
+
...describeNotes(capability)
|
|
2208
|
+
];
|
|
2209
|
+
}
|
|
2210
|
+
var FLUX_2_FLASH_MODEL = "fal-ai/flux-2/flash", FLUX_2_FLASH_EDIT_MODEL = "fal-ai/flux-2/flash/edit", FLASH_SOURCE = "https://fal.ai/api/openapi/queue/openapi.json?endpoint_id=fal-ai/flux-2/flash (read 2026-10-24); no negative prompt: https://help.bfl.ai/articles/7734566352-does-flux-2-support-negative-prompting (read 2026-10-24)", EDIT_SOURCE = "https://fal.ai/api/openapi/queue/openapi.json?endpoint_id=fal-ai/flux-2/flash/edit (read 2026-10-24); no negative prompt: https://help.bfl.ai/articles/7734566352-does-flux-2-support-negative-prompting (read 2026-10-24)", MODEL_CAPABILITIES;
|
|
2211
|
+
var init_model_capabilities = __esm(() => {
|
|
2212
|
+
MODEL_CAPABILITIES = {
|
|
2213
|
+
[FLUX_2_FLASH_MODEL]: {
|
|
2214
|
+
id: FLUX_2_FLASH_MODEL,
|
|
2215
|
+
name: "FLUX 2 Flash",
|
|
2216
|
+
task: "text-to-image",
|
|
2217
|
+
params: {
|
|
2218
|
+
prompt: { support: "supported" },
|
|
2219
|
+
seed: { support: "supported" },
|
|
2220
|
+
output_format: { support: "supported" },
|
|
2221
|
+
image_size: {
|
|
2222
|
+
support: "supported",
|
|
2223
|
+
default: "landscape_4_3",
|
|
2224
|
+
note: "set one explicit value for every call of a set"
|
|
2225
|
+
},
|
|
2226
|
+
aspect_ratio: {
|
|
2227
|
+
support: "unsupported",
|
|
2228
|
+
reason: `FLUX 2 Flash sizes output with image_size. Pass --input '{"image_size":"landscape_4_3"}' instead.`
|
|
2229
|
+
},
|
|
2230
|
+
negative_prompt: {
|
|
2231
|
+
support: "unsupported",
|
|
2232
|
+
reason: "FLUX 2 Flash has no negative prompt; describe what you want in --prompt instead."
|
|
2233
|
+
},
|
|
2234
|
+
image_urls: {
|
|
2235
|
+
support: "unsupported",
|
|
2236
|
+
reason: "Text-to-image only; use --model fal-ai/flux-2/flash/edit with --input-image."
|
|
2237
|
+
}
|
|
2238
|
+
},
|
|
2239
|
+
notes: [
|
|
2240
|
+
"FLUX 2 Flash has no camera or perspective parameter; perspective comes only from the prompt.",
|
|
2241
|
+
"Same-seed reproducibility is not documented by fal.",
|
|
2242
|
+
"Returns PNG without an alpha channel by default."
|
|
2243
|
+
],
|
|
2244
|
+
source: FLASH_SOURCE
|
|
2245
|
+
},
|
|
2246
|
+
[FLUX_2_FLASH_EDIT_MODEL]: {
|
|
2247
|
+
id: FLUX_2_FLASH_EDIT_MODEL,
|
|
2248
|
+
name: "FLUX 2 Flash (edit)",
|
|
2249
|
+
task: "image-edit",
|
|
2250
|
+
params: {
|
|
2251
|
+
prompt: { support: "supported" },
|
|
2252
|
+
seed: { support: "supported" },
|
|
2253
|
+
image_urls: {
|
|
2254
|
+
support: "supported",
|
|
2255
|
+
note: "set by --input-image; the CLI sends one image"
|
|
2256
|
+
},
|
|
2257
|
+
image_size: {
|
|
2258
|
+
support: "supported",
|
|
2259
|
+
default: "square_hd",
|
|
2260
|
+
note: "set one explicit value for every call of a set"
|
|
2261
|
+
},
|
|
2262
|
+
aspect_ratio: {
|
|
2263
|
+
support: "unsupported",
|
|
2264
|
+
reason: `FLUX 2 Flash (edit) sizes output with image_size. Pass --input '{"image_size":"square_hd"}' instead.`
|
|
2265
|
+
},
|
|
2266
|
+
negative_prompt: {
|
|
2267
|
+
support: "unsupported",
|
|
2268
|
+
reason: "FLUX 2 Flash has no negative prompt; describe what you want in --prompt instead."
|
|
2269
|
+
}
|
|
2270
|
+
},
|
|
2271
|
+
notes: [
|
|
2272
|
+
"FLUX 2 Flash has no camera or perspective parameter; perspective comes only from the prompt.",
|
|
2273
|
+
"Not documented to carry a reference image's perspective over to new content; it may return a modified copy of the input.",
|
|
2274
|
+
"Same-seed reproducibility is not documented by fal."
|
|
2275
|
+
],
|
|
2276
|
+
source: EDIT_SOURCE
|
|
2277
|
+
}
|
|
2278
|
+
};
|
|
2279
|
+
});
|
|
2280
|
+
|
|
2143
2281
|
// src/commands/generate/image.ts
|
|
2282
|
+
var exports_image = {};
|
|
2283
|
+
__export(exports_image, {
|
|
2284
|
+
planGenerateImage: () => planGenerateImage,
|
|
2285
|
+
parseInputJson: () => parseInputJson,
|
|
2286
|
+
generateImage: () => generateImage,
|
|
2287
|
+
formatGenerateImagePlan: () => formatGenerateImagePlan,
|
|
2288
|
+
GenerateImageError: () => GenerateImageError,
|
|
2289
|
+
GENERATE_IMAGE_PLATFORMS: () => GENERATE_IMAGE_PLATFORMS,
|
|
2290
|
+
FAL_API_KEY_MISSING_MESSAGE: () => FAL_API_KEY_MISSING_MESSAGE,
|
|
2291
|
+
DEFAULT_GENERATE_IMAGE_PLATFORM: () => DEFAULT_GENERATE_IMAGE_PLATFORM
|
|
2292
|
+
});
|
|
2144
2293
|
import { createHash } from "node:crypto";
|
|
2145
2294
|
import { extname, resolve } from "node:path";
|
|
2146
2295
|
function resolvePlatform(platform) {
|
|
@@ -2163,6 +2312,14 @@ function requireNonEmpty(value, message) {
|
|
|
2163
2312
|
}
|
|
2164
2313
|
return trimmed;
|
|
2165
2314
|
}
|
|
2315
|
+
function inputImageContentType(path) {
|
|
2316
|
+
const extension = extname(path).toLowerCase();
|
|
2317
|
+
const contentType = INPUT_IMAGE_CONTENT_TYPES[extension];
|
|
2318
|
+
if (contentType === undefined) {
|
|
2319
|
+
throw new GenerateImageError(`Unsupported --input-image extension ${extension || "(none)"}. Use one of: ${Object.keys(INPUT_IMAGE_CONTENT_TYPES).join(", ")}.`);
|
|
2320
|
+
}
|
|
2321
|
+
return contentType;
|
|
2322
|
+
}
|
|
2166
2323
|
function validateInputObject(input) {
|
|
2167
2324
|
if ("sync_mode" in input && input.sync_mode !== undefined) {
|
|
2168
2325
|
throw new GenerateImageError('--input must not contain "sync_mode": it returns data URIs that this command cannot download. Omit it.');
|
|
@@ -2176,6 +2333,9 @@ function validateInputObject(input) {
|
|
|
2176
2333
|
}
|
|
2177
2334
|
}
|
|
2178
2335
|
}
|
|
2336
|
+
function isPlainObject(value) {
|
|
2337
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2338
|
+
}
|
|
2179
2339
|
function parseInputJson(text) {
|
|
2180
2340
|
let parsed;
|
|
2181
2341
|
try {
|
|
@@ -2183,28 +2343,30 @@ function parseInputJson(text) {
|
|
|
2183
2343
|
} catch {
|
|
2184
2344
|
throw new GenerateImageError("--input must be valid JSON.");
|
|
2185
2345
|
}
|
|
2186
|
-
if (
|
|
2346
|
+
if (!isPlainObject(parsed)) {
|
|
2187
2347
|
throw new GenerateImageError("--input must be a JSON object.");
|
|
2188
2348
|
}
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
return input;
|
|
2349
|
+
validateInputObject(parsed);
|
|
2350
|
+
return parsed;
|
|
2192
2351
|
}
|
|
2193
|
-
function buildInput(prompt, opts) {
|
|
2194
|
-
if (opts.input !== undefined) {
|
|
2195
|
-
if (typeof opts.input !== "object" || opts.input === null || Array.isArray(opts.input)) {
|
|
2196
|
-
throw new GenerateImageError("--input must be a JSON object.");
|
|
2197
|
-
}
|
|
2198
|
-
validateInputObject(opts.input);
|
|
2199
|
-
}
|
|
2352
|
+
function buildInput(prompt, opts, imageUrl) {
|
|
2200
2353
|
return {
|
|
2201
2354
|
prompt,
|
|
2202
2355
|
...opts.input ?? {},
|
|
2203
2356
|
...opts.aspectRatio !== undefined ? { aspect_ratio: opts.aspectRatio } : {},
|
|
2204
2357
|
...opts.outputFormat !== undefined ? { output_format: opts.outputFormat } : {},
|
|
2205
|
-
...opts.seed !== undefined ? { seed: opts.seed } : {}
|
|
2358
|
+
...opts.seed !== undefined ? { seed: opts.seed } : {},
|
|
2359
|
+
...imageUrl !== undefined ? { image_urls: [imageUrl] } : {}
|
|
2206
2360
|
};
|
|
2207
2361
|
}
|
|
2362
|
+
function validateInputOption(opts) {
|
|
2363
|
+
if (opts.input === undefined)
|
|
2364
|
+
return;
|
|
2365
|
+
if (!isPlainObject(opts.input)) {
|
|
2366
|
+
throw new GenerateImageError("--input must be a JSON object.");
|
|
2367
|
+
}
|
|
2368
|
+
validateInputObject(opts.input);
|
|
2369
|
+
}
|
|
2208
2370
|
function requireImage(images, model) {
|
|
2209
2371
|
const image = images[0];
|
|
2210
2372
|
if (!image?.url) {
|
|
@@ -2266,6 +2428,7 @@ function buildManifest(args) {
|
|
|
2266
2428
|
sdk: `@fal-ai/client@${args.sdkVersion}`,
|
|
2267
2429
|
model: args.model,
|
|
2268
2430
|
input: args.input,
|
|
2431
|
+
inputImage: args.inputImage,
|
|
2269
2432
|
requestId: args.requestId ?? null,
|
|
2270
2433
|
seed: resolveSeed(args.resultSeed, args.requestedSeed),
|
|
2271
2434
|
output: {
|
|
@@ -2278,21 +2441,118 @@ function buildManifest(args) {
|
|
|
2278
2441
|
licensing: LICENSING
|
|
2279
2442
|
};
|
|
2280
2443
|
}
|
|
2281
|
-
|
|
2444
|
+
function requireTaskPairing(capability, model, inputImage) {
|
|
2445
|
+
if (capability.task === "image-edit" && inputImage === undefined) {
|
|
2446
|
+
throw new GenerateImageError(`${model} edits an image; pass --input-image <path-or-url>.`);
|
|
2447
|
+
}
|
|
2448
|
+
if (capability.task === "text-to-image" && inputImage !== undefined) {
|
|
2449
|
+
throw new GenerateImageError(`${model} does not take an input image; use --model ${FLUX_2_FLASH_EDIT_MODEL} with --input-image.`);
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
function requireAspectRatioSupport(capability, model, aspectRatio) {
|
|
2453
|
+
if (aspectRatio === undefined)
|
|
2454
|
+
return;
|
|
2455
|
+
const support = capability.params.aspect_ratio;
|
|
2456
|
+
if (support?.support !== "unsupported")
|
|
2457
|
+
return;
|
|
2458
|
+
throw new GenerateImageError(`--aspect-ratio is not supported by ${model}. ${support.reason}`);
|
|
2459
|
+
}
|
|
2460
|
+
function requireModelPairing(model, inputImage, aspectRatio) {
|
|
2461
|
+
const capability = getModelCapability(model);
|
|
2462
|
+
if (capability === undefined)
|
|
2463
|
+
return;
|
|
2464
|
+
requireTaskPairing(capability, model, inputImage);
|
|
2465
|
+
requireAspectRatioSupport(capability, model, aspectRatio);
|
|
2466
|
+
}
|
|
2467
|
+
function resolveInputImageOption(opts) {
|
|
2468
|
+
return opts.inputImage === undefined ? undefined : requireNonEmpty(opts.inputImage, "--input-image must not be empty.");
|
|
2469
|
+
}
|
|
2470
|
+
async function validateInputImage(deps, inputImage) {
|
|
2471
|
+
if (inputImage === undefined)
|
|
2472
|
+
return;
|
|
2473
|
+
if (HTTP_URL_PATTERN.test(inputImage))
|
|
2474
|
+
return { kind: "url", url: inputImage };
|
|
2475
|
+
const localPath = resolve(inputImage);
|
|
2476
|
+
const contentType = inputImageContentType(localPath);
|
|
2477
|
+
const status = await deps.pathStatus(localPath);
|
|
2478
|
+
if (status !== "file") {
|
|
2479
|
+
throw new GenerateImageError(`--input-image is not a file: ${localPath}`);
|
|
2480
|
+
}
|
|
2481
|
+
return { kind: "file", path: localPath, contentType };
|
|
2482
|
+
}
|
|
2483
|
+
function planInputImageUrl(target) {
|
|
2484
|
+
if (target === undefined)
|
|
2485
|
+
return;
|
|
2486
|
+
return target.kind === "url" ? target.url : `<fal storage URL for ${target.path}>`;
|
|
2487
|
+
}
|
|
2488
|
+
async function uploadInputImage(deps, target) {
|
|
2489
|
+
if (target === undefined)
|
|
2490
|
+
return { url: undefined, record: null };
|
|
2491
|
+
if (target.kind === "url") {
|
|
2492
|
+
return { url: target.url, record: { source: target.url, url: target.url } };
|
|
2493
|
+
}
|
|
2494
|
+
const url = await deps.uploadFile(target.path, target.contentType);
|
|
2495
|
+
return { url, record: { source: target.path, url } };
|
|
2496
|
+
}
|
|
2497
|
+
async function planGenerateImage(opts, deps) {
|
|
2282
2498
|
const platform = resolvePlatform(opts.platform);
|
|
2283
2499
|
requireSupportedPlatform(platform);
|
|
2284
|
-
|
|
2500
|
+
const model = requireNonEmpty(opts.model, "--model must not be empty.");
|
|
2285
2501
|
const prompt = requireNonEmpty(opts.prompt, "--prompt must not be empty.");
|
|
2286
2502
|
const outPath = resolve(requireNonEmpty(opts.out, "--out must not be empty."));
|
|
2287
2503
|
requireSupportedExtension(outPath);
|
|
2288
2504
|
if (opts.outputFormat !== undefined) {
|
|
2289
2505
|
requireFormatMatchesExtension(outPath, opts.outputFormat);
|
|
2290
2506
|
}
|
|
2507
|
+
const inputImage = resolveInputImageOption(opts);
|
|
2508
|
+
requireModelPairing(model, inputImage, opts.aspectRatio);
|
|
2509
|
+
validateInputOption(opts);
|
|
2510
|
+
const inputTarget = await validateInputImage(deps, inputImage);
|
|
2291
2511
|
const manifestPath = `${outPath}.json`;
|
|
2292
2512
|
await requireWritableOutput(deps, outPath, manifestPath, opts.force === true);
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2513
|
+
const input = buildInput(prompt, opts, planInputImageUrl(inputTarget));
|
|
2514
|
+
return {
|
|
2515
|
+
platform,
|
|
2516
|
+
model,
|
|
2517
|
+
outPath,
|
|
2518
|
+
manifestPath,
|
|
2519
|
+
input,
|
|
2520
|
+
inputTarget,
|
|
2521
|
+
disclosures: describeConstraints(model, input, { aspectRatio: opts.aspectRatio })
|
|
2522
|
+
};
|
|
2523
|
+
}
|
|
2524
|
+
function disclosureFlag(key) {
|
|
2525
|
+
return key === "aspect_ratio" ? "--aspect-ratio" : "--input";
|
|
2526
|
+
}
|
|
2527
|
+
function formatDisclosure(disclosure) {
|
|
2528
|
+
if (disclosure.kind === "unsupported" || disclosure.kind === "unknown") {
|
|
2529
|
+
return `- ${disclosure.kind}: ${disclosure.key} (${disclosureFlag(disclosure.key)}) — ${disclosure.message}`;
|
|
2530
|
+
}
|
|
2531
|
+
return `- ${disclosure.kind}: ${disclosure.message}`;
|
|
2532
|
+
}
|
|
2533
|
+
function formatInputImageLine(target) {
|
|
2534
|
+
if (target === undefined)
|
|
2535
|
+
return;
|
|
2536
|
+
return target.kind === "url" ? `Input image ${target.url} (passed through)` : `Input image ${target.path} (would be uploaded to fal storage, 1-day lifetime)`;
|
|
2537
|
+
}
|
|
2538
|
+
function formatGenerateImagePlan(plan) {
|
|
2539
|
+
const capability = getModelCapability(plan.model);
|
|
2540
|
+
const lines = [
|
|
2541
|
+
`Would generate ${plan.outPath}`,
|
|
2542
|
+
`Manifest ${plan.manifestPath}`,
|
|
2543
|
+
capability === undefined ? `Model ${plan.model} (no capability data)` : `Model ${plan.model} (${capability.name}, ${capability.task})`
|
|
2544
|
+
];
|
|
2545
|
+
const inputLine = formatInputImageLine(plan.inputTarget);
|
|
2546
|
+
if (inputLine !== undefined)
|
|
2547
|
+
lines.push(inputLine);
|
|
2548
|
+
lines.push("Request:", JSON.stringify(plan.input, null, 2), "Constraints:");
|
|
2549
|
+
for (const disclosure of plan.disclosures)
|
|
2550
|
+
lines.push(formatDisclosure(disclosure));
|
|
2551
|
+
lines.push("No upload or fal call was made.");
|
|
2552
|
+
return lines.join(`
|
|
2553
|
+
`);
|
|
2554
|
+
}
|
|
2555
|
+
async function generateAndSave(deps, model, input, outPath) {
|
|
2296
2556
|
const result = await deps.falSubscribe(model, input);
|
|
2297
2557
|
if (result.data.images.length > 1) {
|
|
2298
2558
|
deps.warn(`⚠️ fal returned ${result.data.images.length} images; only the first is saved.`);
|
|
@@ -2300,25 +2560,46 @@ async function generateImage(opts, deps) {
|
|
|
2300
2560
|
const image = requireImage(result.data.images, model);
|
|
2301
2561
|
const bytes = await downloadImage(deps, result.requestId, image.url);
|
|
2302
2562
|
await saveImage(deps, outPath, bytes, result.requestId, image.url);
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
input,
|
|
2563
|
+
return {
|
|
2564
|
+
image,
|
|
2565
|
+
bytes,
|
|
2566
|
+
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
2308
2567
|
requestId: result.requestId,
|
|
2309
|
-
|
|
2568
|
+
seed: result.data.seed
|
|
2569
|
+
};
|
|
2570
|
+
}
|
|
2571
|
+
async function generateImage(opts, deps) {
|
|
2572
|
+
requireFalKey(deps);
|
|
2573
|
+
const plan = await planGenerateImage(opts, deps);
|
|
2574
|
+
for (const disclosure of plan.disclosures) {
|
|
2575
|
+
if (disclosure.kind === "unsupported") {
|
|
2576
|
+
deps.warn(`⚠️ ${disclosure.key}: ${disclosure.message}`);
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
await deps.ensureParentDir(plan.outPath);
|
|
2580
|
+
const preparedInput = await uploadInputImage(deps, plan.inputTarget);
|
|
2581
|
+
const input = preparedInput.url === undefined ? plan.input : { ...plan.input, image_urls: [preparedInput.url] };
|
|
2582
|
+
const saved = await generateAndSave(deps, plan.model, input, plan.outPath);
|
|
2583
|
+
await deps.writeJson(plan.manifestPath, buildManifest({
|
|
2584
|
+
platform: plan.platform,
|
|
2585
|
+
model: plan.model,
|
|
2586
|
+
input,
|
|
2587
|
+
inputImage: preparedInput.record,
|
|
2588
|
+
requestId: saved.requestId,
|
|
2589
|
+
resultSeed: saved.seed,
|
|
2310
2590
|
requestedSeed: opts.seed,
|
|
2311
|
-
outPath,
|
|
2312
|
-
sha256,
|
|
2313
|
-
contentType: image.content_type,
|
|
2314
|
-
byteLength: bytes.byteLength,
|
|
2591
|
+
outPath: plan.outPath,
|
|
2592
|
+
sha256: saved.sha256,
|
|
2593
|
+
contentType: saved.image.content_type,
|
|
2594
|
+
byteLength: saved.bytes.byteLength,
|
|
2315
2595
|
generatedAt: deps.now().toISOString(),
|
|
2316
2596
|
sdkVersion: deps.sdkVersion
|
|
2317
2597
|
}));
|
|
2318
|
-
return { outPath, manifestPath, sha256 };
|
|
2598
|
+
return { outPath: plan.outPath, manifestPath: plan.manifestPath, sha256: saved.sha256 };
|
|
2319
2599
|
}
|
|
2320
|
-
var
|
|
2600
|
+
var GENERATE_IMAGE_PLATFORMS, DEFAULT_GENERATE_IMAGE_PLATFORM = "fal", GenerateImageError, LICENSING = "unverified", FAL_API_KEY_MISSING_MESSAGE = "FAL_API_KEY is not set. Set FAL_API_KEY in your environment to generate images with fal. (FAL_KEY_ID/FAL_KEY_SECRET are not supported by this command.)", EXTENSION_FORMATS, UNSUPPORTED_OUTPUT_EXTENSIONS, INPUT_DEDICATED_FLAGS, INPUT_IMAGE_CONTENT_TYPES, HTTP_URL_PATTERN;
|
|
2321
2601
|
var init_image = __esm(() => {
|
|
2602
|
+
init_model_capabilities();
|
|
2322
2603
|
GENERATE_IMAGE_PLATFORMS = ["fal"];
|
|
2323
2604
|
GenerateImageError = class GenerateImageError extends Error {
|
|
2324
2605
|
constructor(message) {
|
|
@@ -2335,8 +2616,17 @@ var init_image = __esm(() => {
|
|
|
2335
2616
|
prompt: "--prompt",
|
|
2336
2617
|
seed: "--seed",
|
|
2337
2618
|
output_format: "--output-format",
|
|
2338
|
-
aspect_ratio: "--aspect-ratio"
|
|
2619
|
+
aspect_ratio: "--aspect-ratio",
|
|
2620
|
+
image_urls: "--input-image"
|
|
2621
|
+
};
|
|
2622
|
+
INPUT_IMAGE_CONTENT_TYPES = {
|
|
2623
|
+
".png": "image/png",
|
|
2624
|
+
".jpg": "image/jpeg",
|
|
2625
|
+
".jpeg": "image/jpeg",
|
|
2626
|
+
".webp": "image/webp",
|
|
2627
|
+
".gif": "image/gif"
|
|
2339
2628
|
};
|
|
2629
|
+
HTTP_URL_PATTERN = /^https?:\/\//i;
|
|
2340
2630
|
});
|
|
2341
2631
|
|
|
2342
2632
|
// src/commands/generate/local-image-common.ts
|
|
@@ -2350,6 +2640,13 @@ function sha256(bytes) {
|
|
|
2350
2640
|
function errorMessage(err) {
|
|
2351
2641
|
return err instanceof Error ? err.message : String(err);
|
|
2352
2642
|
}
|
|
2643
|
+
function median(values) {
|
|
2644
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
2645
|
+
const middle = Math.floor(sorted.length / 2);
|
|
2646
|
+
if (sorted.length % 2 === 1)
|
|
2647
|
+
return sorted[middle] ?? 0;
|
|
2648
|
+
return Math.round(((sorted[middle - 1] ?? 0) + (sorted[middle] ?? 0)) / 2);
|
|
2649
|
+
}
|
|
2353
2650
|
function hasPngMagic(bytes) {
|
|
2354
2651
|
if (bytes.length < PNG_MAGIC.length)
|
|
2355
2652
|
return false;
|
|
@@ -2668,7 +2965,7 @@ var require_utils = __commonJS((exports) => {
|
|
|
2668
2965
|
exports.isValidUrl = isValidUrl;
|
|
2669
2966
|
exports.throttle = throttle;
|
|
2670
2967
|
exports.isReact = isReact;
|
|
2671
|
-
exports.isPlainObject =
|
|
2968
|
+
exports.isPlainObject = isPlainObject2;
|
|
2672
2969
|
exports.sleep = sleep;
|
|
2673
2970
|
function ensureEndpointIdFormat(id) {
|
|
2674
2971
|
const parts = id.split("/");
|
|
@@ -2744,7 +3041,7 @@ var require_utils = __commonJS((exports) => {
|
|
|
2744
3041
|
}
|
|
2745
3042
|
return isRunningInReact;
|
|
2746
3043
|
}
|
|
2747
|
-
function
|
|
3044
|
+
function isPlainObject2(value) {
|
|
2748
3045
|
return !!value && Object.getPrototypeOf(value) === Object.prototype;
|
|
2749
3046
|
}
|
|
2750
3047
|
function sleep(ms) {
|
|
@@ -6495,7 +6792,7 @@ __export(exports_deps, {
|
|
|
6495
6792
|
createFalRawSubscribe: () => createFalRawSubscribe,
|
|
6496
6793
|
createDefaultGenerateImageDeps: () => createDefaultGenerateImageDeps
|
|
6497
6794
|
});
|
|
6498
|
-
import { mkdir } from "node:fs/promises";
|
|
6795
|
+
import { mkdir, readFile } from "node:fs/promises";
|
|
6499
6796
|
import { dirname as dirname2 } from "node:path";
|
|
6500
6797
|
function isRecord2(value) {
|
|
6501
6798
|
return typeof value === "object" && value !== null;
|
|
@@ -6598,13 +6895,16 @@ function describeFalError(err) {
|
|
|
6598
6895
|
return formatApiError(err);
|
|
6599
6896
|
return err instanceof Error ? err.message : String(err);
|
|
6600
6897
|
}
|
|
6898
|
+
function configureFal(env) {
|
|
6899
|
+
const credentials = env.FAL_API_KEY;
|
|
6900
|
+
if (!credentials) {
|
|
6901
|
+
throw new GenerateImageError(FAL_API_KEY_MISSING_MESSAGE);
|
|
6902
|
+
}
|
|
6903
|
+
import_client.fal.config({ credentials });
|
|
6904
|
+
}
|
|
6601
6905
|
function createFalRawSubscribe(env) {
|
|
6602
6906
|
return async (model, input) => {
|
|
6603
|
-
|
|
6604
|
-
if (!credentials) {
|
|
6605
|
-
throw new GenerateImageError(FAL_API_KEY_MISSING_MESSAGE);
|
|
6606
|
-
}
|
|
6607
|
-
import_client.fal.config({ credentials });
|
|
6907
|
+
configureFal(env);
|
|
6608
6908
|
try {
|
|
6609
6909
|
return await import_client.fal.subscribe(model, {
|
|
6610
6910
|
input,
|
|
@@ -6620,6 +6920,18 @@ function createFalSubscribe(env) {
|
|
|
6620
6920
|
const subscribe = createFalRawSubscribe(env);
|
|
6621
6921
|
return async (model, input) => validateFalResult(await subscribe(model, input));
|
|
6622
6922
|
}
|
|
6923
|
+
function createFalUploadFile(env) {
|
|
6924
|
+
return async (path, contentType) => {
|
|
6925
|
+
configureFal(env);
|
|
6926
|
+
try {
|
|
6927
|
+
const bytes = await readFile(path);
|
|
6928
|
+
const blob = new Blob([new Uint8Array(bytes)], { type: contentType });
|
|
6929
|
+
return await import_client.fal.storage.upload(blob, { lifecycle: { expiresIn: "1d" } });
|
|
6930
|
+
} catch (err) {
|
|
6931
|
+
throw new GenerateImageError(`Failed to upload input image ${path}: ${describeFalError(err)}`);
|
|
6932
|
+
}
|
|
6933
|
+
};
|
|
6934
|
+
}
|
|
6623
6935
|
async function ensureParentDir(path) {
|
|
6624
6936
|
await mkdir(dirname2(path), { recursive: true });
|
|
6625
6937
|
}
|
|
@@ -6642,6 +6954,7 @@ function createDefaultGenerateImageDeps() {
|
|
|
6642
6954
|
return {
|
|
6643
6955
|
env,
|
|
6644
6956
|
falSubscribe: createFalSubscribe(env),
|
|
6957
|
+
uploadFile: createFalUploadFile(env),
|
|
6645
6958
|
fetchBytes,
|
|
6646
6959
|
writeFile: writeFileBytes,
|
|
6647
6960
|
writeJson: writeJsonFile,
|
|
@@ -8552,7 +8865,7 @@ var exports_slice_deps = {};
|
|
|
8552
8865
|
__export(exports_slice_deps, {
|
|
8553
8866
|
createDefaultSliceImageDeps: () => createDefaultSliceImageDeps
|
|
8554
8867
|
});
|
|
8555
|
-
import { lstat, mkdir as mkdir2, readFile as
|
|
8868
|
+
import { lstat, mkdir as mkdir2, readFile as readFile3, stat as stat3 } from "node:fs/promises";
|
|
8556
8869
|
function decodePng(bytes) {
|
|
8557
8870
|
const png = PNG.sync.read(Buffer.from(bytes));
|
|
8558
8871
|
return { width: png.width, height: png.height, data: png.data };
|
|
@@ -8582,7 +8895,7 @@ async function statInput(path) {
|
|
|
8582
8895
|
async function readJsonIfExists(path) {
|
|
8583
8896
|
let text;
|
|
8584
8897
|
try {
|
|
8585
|
-
text = await
|
|
8898
|
+
text = await readFile3(path, "utf8");
|
|
8586
8899
|
} catch (err) {
|
|
8587
8900
|
if (err.code === "ENOENT") {
|
|
8588
8901
|
return;
|
|
@@ -8619,7 +8932,7 @@ function createDefaultSliceImageDeps() {
|
|
|
8619
8932
|
return {
|
|
8620
8933
|
pathStatus,
|
|
8621
8934
|
statInput,
|
|
8622
|
-
readFile:
|
|
8935
|
+
readFile: readFile3,
|
|
8623
8936
|
readJsonIfExists,
|
|
8624
8937
|
decodePng,
|
|
8625
8938
|
encodePng,
|
|
@@ -27379,7 +27692,7 @@ __export(exports_util, {
|
|
|
27379
27692
|
jsonStringifyReplacer: () => jsonStringifyReplacer,
|
|
27380
27693
|
joinValues: () => joinValues,
|
|
27381
27694
|
issue: () => issue,
|
|
27382
|
-
isPlainObject: () =>
|
|
27695
|
+
isPlainObject: () => isPlainObject2,
|
|
27383
27696
|
isObject: () => isObject,
|
|
27384
27697
|
hexToUint8Array: () => hexToUint8Array,
|
|
27385
27698
|
getSizableOrigin: () => getSizableOrigin,
|
|
@@ -27544,7 +27857,7 @@ function slugify(input) {
|
|
|
27544
27857
|
function isObject(data) {
|
|
27545
27858
|
return typeof data === "object" && data !== null && !Array.isArray(data);
|
|
27546
27859
|
}
|
|
27547
|
-
function
|
|
27860
|
+
function isPlainObject2(o) {
|
|
27548
27861
|
if (isObject(o) === false)
|
|
27549
27862
|
return false;
|
|
27550
27863
|
const ctor = o.constructor;
|
|
@@ -27561,7 +27874,7 @@ function isPlainObject(o) {
|
|
|
27561
27874
|
return true;
|
|
27562
27875
|
}
|
|
27563
27876
|
function shallowClone(o) {
|
|
27564
|
-
if (
|
|
27877
|
+
if (isPlainObject2(o))
|
|
27565
27878
|
return { ...o };
|
|
27566
27879
|
if (Array.isArray(o))
|
|
27567
27880
|
return [...o];
|
|
@@ -27701,7 +28014,7 @@ function omit2(schema, mask) {
|
|
|
27701
28014
|
return clone(schema, def);
|
|
27702
28015
|
}
|
|
27703
28016
|
function extend(schema, shape) {
|
|
27704
|
-
if (!
|
|
28017
|
+
if (!isPlainObject2(shape)) {
|
|
27705
28018
|
throw new Error("Invalid input to extend: expected a plain object");
|
|
27706
28019
|
}
|
|
27707
28020
|
const checks = schema._zod.def.checks;
|
|
@@ -27724,7 +28037,7 @@ function extend(schema, shape) {
|
|
|
27724
28037
|
return clone(schema, def);
|
|
27725
28038
|
}
|
|
27726
28039
|
function safeExtend(schema, shape) {
|
|
27727
|
-
if (!
|
|
28040
|
+
if (!isPlainObject2(shape)) {
|
|
27728
28041
|
throw new Error("Invalid input to safeExtend: expected a plain object");
|
|
27729
28042
|
}
|
|
27730
28043
|
const def = mergeDefs(schema._zod.def, {
|
|
@@ -29207,7 +29520,7 @@ function mergeValues(a, b) {
|
|
|
29207
29520
|
if (a instanceof Date && b instanceof Date && +a === +b) {
|
|
29208
29521
|
return { valid: true, data: a };
|
|
29209
29522
|
}
|
|
29210
|
-
if (
|
|
29523
|
+
if (isPlainObject2(a) && isPlainObject2(b)) {
|
|
29211
29524
|
const bKeys = Object.keys(b);
|
|
29212
29525
|
const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
|
|
29213
29526
|
const newObj = { ...a, ...b };
|
|
@@ -30464,7 +30777,7 @@ var init_schemas = __esm(() => {
|
|
|
30464
30777
|
$ZodType.init(inst, def);
|
|
30465
30778
|
inst._zod.parse = (payload, ctx) => {
|
|
30466
30779
|
const input = payload.value;
|
|
30467
|
-
if (!
|
|
30780
|
+
if (!isPlainObject2(input)) {
|
|
30468
30781
|
payload.issues.push({
|
|
30469
30782
|
expected: "record",
|
|
30470
30783
|
code: "invalid_type",
|
|
@@ -71424,7 +71737,7 @@ var require_filesystem = __commonJS((exports, module) => {
|
|
|
71424
71737
|
fs6.close(fd, () => {});
|
|
71425
71738
|
return buffer.subarray(0, bytesRead);
|
|
71426
71739
|
};
|
|
71427
|
-
var
|
|
71740
|
+
var readFile7 = (path2) => new Promise((resolve6, reject) => {
|
|
71428
71741
|
fs6.open(path2, "r", (err, fd) => {
|
|
71429
71742
|
if (err) {
|
|
71430
71743
|
reject(err);
|
|
@@ -71441,7 +71754,7 @@ var require_filesystem = __commonJS((exports, module) => {
|
|
|
71441
71754
|
LDD_PATH,
|
|
71442
71755
|
SELF_PATH,
|
|
71443
71756
|
readFileSync: readFileSync12,
|
|
71444
|
-
readFile:
|
|
71757
|
+
readFile: readFile7
|
|
71445
71758
|
};
|
|
71446
71759
|
});
|
|
71447
71760
|
|
|
@@ -71483,7 +71796,7 @@ var require_elf = __commonJS((exports, module) => {
|
|
|
71483
71796
|
var require_detect_libc = __commonJS((exports, module) => {
|
|
71484
71797
|
var childProcess = __require("child_process");
|
|
71485
71798
|
var { isLinux, getReport } = require_process();
|
|
71486
|
-
var { LDD_PATH, SELF_PATH, readFile:
|
|
71799
|
+
var { LDD_PATH, SELF_PATH, readFile: readFile7, readFileSync: readFileSync12 } = require_filesystem();
|
|
71487
71800
|
var { interpreterPath } = require_elf();
|
|
71488
71801
|
var cachedFamilyInterpreter;
|
|
71489
71802
|
var cachedFamilyFilesystem;
|
|
@@ -71563,7 +71876,7 @@ var require_detect_libc = __commonJS((exports, module) => {
|
|
|
71563
71876
|
}
|
|
71564
71877
|
cachedFamilyFilesystem = null;
|
|
71565
71878
|
try {
|
|
71566
|
-
const lddContent = await
|
|
71879
|
+
const lddContent = await readFile7(LDD_PATH);
|
|
71567
71880
|
cachedFamilyFilesystem = getFamilyFromLddContent(lddContent);
|
|
71568
71881
|
} catch (e) {}
|
|
71569
71882
|
return cachedFamilyFilesystem;
|
|
@@ -71585,7 +71898,7 @@ var require_detect_libc = __commonJS((exports, module) => {
|
|
|
71585
71898
|
}
|
|
71586
71899
|
cachedFamilyInterpreter = null;
|
|
71587
71900
|
try {
|
|
71588
|
-
const selfContent = await
|
|
71901
|
+
const selfContent = await readFile7(SELF_PATH);
|
|
71589
71902
|
const path2 = interpreterPath(selfContent);
|
|
71590
71903
|
cachedFamilyInterpreter = familyFromInterpreterPath(path2);
|
|
71591
71904
|
} catch (e) {}
|
|
@@ -71645,7 +71958,7 @@ var require_detect_libc = __commonJS((exports, module) => {
|
|
|
71645
71958
|
}
|
|
71646
71959
|
cachedVersionFilesystem = null;
|
|
71647
71960
|
try {
|
|
71648
|
-
const lddContent = await
|
|
71961
|
+
const lddContent = await readFile7(LDD_PATH);
|
|
71649
71962
|
const versionMatch = lddContent.match(RE_GLIBC_VERSION);
|
|
71650
71963
|
if (versionMatch) {
|
|
71651
71964
|
cachedVersionFilesystem = versionMatch[1];
|
|
@@ -82140,7 +82453,7 @@ var require_loadImage = __commonJS((exports, module) => {
|
|
|
82140
82453
|
var fs8 = __require("fs");
|
|
82141
82454
|
var fetch2 = global.fetch || require_lib3();
|
|
82142
82455
|
var isURL = require_is_url();
|
|
82143
|
-
var
|
|
82456
|
+
var readFile7 = util.promisify(fs8.readFile);
|
|
82144
82457
|
module.exports = async (image) => {
|
|
82145
82458
|
let data = image;
|
|
82146
82459
|
if (typeof image === "undefined") {
|
|
@@ -82153,7 +82466,7 @@ var require_loadImage = __commonJS((exports, module) => {
|
|
|
82153
82466
|
} else if (/data:image\/([a-zA-Z]*);base64,([^"]*)/.test(image)) {
|
|
82154
82467
|
data = Buffer.from(image.split(",")[1], "base64");
|
|
82155
82468
|
} else {
|
|
82156
|
-
data = await
|
|
82469
|
+
data = await readFile7(image);
|
|
82157
82470
|
}
|
|
82158
82471
|
} else if (Buffer.isBuffer(image)) {
|
|
82159
82472
|
data = image;
|
|
@@ -82753,9 +83066,9 @@ function merge5(...objs) {
|
|
|
82753
83066
|
}
|
|
82754
83067
|
const targetKey = caseless && typeof key === "string" && findKey(result3, key) || key;
|
|
82755
83068
|
const existing = hasOwnProperty(result3, targetKey) ? result3[targetKey] : undefined;
|
|
82756
|
-
if (
|
|
83069
|
+
if (isPlainObject3(existing) && isPlainObject3(val)) {
|
|
82757
83070
|
result3[targetKey] = merge5(existing, val);
|
|
82758
|
-
} else if (
|
|
83071
|
+
} else if (isPlainObject3(val)) {
|
|
82759
83072
|
result3[targetKey] = merge5({}, val);
|
|
82760
83073
|
} else if (isArray2(val)) {
|
|
82761
83074
|
result3[targetKey] = val.slice();
|
|
@@ -82858,7 +83171,7 @@ var toString2, getPrototypeOf, iterator, toStringTag, hasOwnProperty, isUnsafeOb
|
|
|
82858
83171
|
}, kindOf, kindOfTest = (type) => {
|
|
82859
83172
|
type = type.toLowerCase();
|
|
82860
83173
|
return (thing) => kindOf(thing) === type;
|
|
82861
|
-
}, typeOfTest = (type) => (thing) => typeof thing === type, isArray2, isUndefined2, isArrayBuffer, isString2, isFunction2, isNumber2, isObject3 = (thing) => thing !== null && typeof thing === "object", isBoolean = (thing) => thing === true || thing === false,
|
|
83174
|
+
}, typeOfTest = (type) => (thing) => typeof thing === type, isArray2, isUndefined2, isArrayBuffer, isString2, isFunction2, isNumber2, isObject3 = (thing) => thing !== null && typeof thing === "object", isBoolean = (thing) => thing === true || thing === false, isPlainObject3 = (val) => {
|
|
82862
83175
|
if (!isObject3(val)) {
|
|
82863
83176
|
return false;
|
|
82864
83177
|
}
|
|
@@ -83134,7 +83447,7 @@ var init_utils = __esm(() => {
|
|
|
83134
83447
|
isNumber: isNumber2,
|
|
83135
83448
|
isBoolean,
|
|
83136
83449
|
isObject: isObject3,
|
|
83137
|
-
isPlainObject:
|
|
83450
|
+
isPlainObject: isPlainObject3,
|
|
83138
83451
|
isEmptyObject,
|
|
83139
83452
|
isReadableStream,
|
|
83140
83453
|
isRequest,
|
|
@@ -106274,7 +106587,7 @@ var exports_parse_pdf = {};
|
|
|
106274
106587
|
__export(exports_parse_pdf, {
|
|
106275
106588
|
parsePdf: () => parsePdf
|
|
106276
106589
|
});
|
|
106277
|
-
import { access, readFile as
|
|
106590
|
+
import { access, readFile as readFile7, writeFile as writeFile7, mkdir as mkdir7, appendFile } from "node:fs/promises";
|
|
106278
106591
|
import { resolve as resolve6 } from "node:path";
|
|
106279
106592
|
function isUrl(input) {
|
|
106280
106593
|
return input.startsWith("http://") || input.startsWith("https://");
|
|
@@ -106284,9 +106597,9 @@ function createDefaultDeps6() {
|
|
|
106284
106597
|
fs: {
|
|
106285
106598
|
access: (p) => access(p),
|
|
106286
106599
|
mkdir: (p, opts) => mkdir7(p, opts).then(() => {}),
|
|
106287
|
-
readFile: (p, enc) =>
|
|
106600
|
+
readFile: (p, enc) => readFile7(p, { encoding: enc }).then((b) => b.toString()),
|
|
106288
106601
|
appendFile: (p, content) => appendFile(p, content),
|
|
106289
|
-
readFileAsBuffer: (p) =>
|
|
106602
|
+
readFileAsBuffer: (p) => readFile7(p),
|
|
106290
106603
|
writeFile: (p, content, enc) => writeFile7(p, content, enc)
|
|
106291
106604
|
},
|
|
106292
106605
|
parser: {
|
|
@@ -107914,7 +108227,7 @@ function formatProofOfPrinciples() {
|
|
|
107914
108227
|
var GAME_ASSET_RULES;
|
|
107915
108228
|
var init_game_asset_designer_workflow = __esm(() => {
|
|
107916
108229
|
GAME_ASSET_RULES = [
|
|
107917
|
-
"Generate only with `chatroom generate-image --platform=fal
|
|
108230
|
+
"Generate only with `chatroom generate-image --platform=fal --model <Model>`, where <Model> is the brief's Model: `fal-ai/flux-2/flash` (FLUX 2 Flash), or `fal-ai/flux-2/flash/edit` with --input-image for reference edits.",
|
|
107918
108231
|
"Process images only with `chatroom slice-image` and `chatroom remove-image-background`; no other tool, script, or remote service.",
|
|
107919
108232
|
"A sheet is at most the brief's `Sheet grid` (default 2x2); a larger grid needs a user request quoted in the brief.",
|
|
107920
108233
|
"Names come from looking at each tile, never from cell order or prompt order.",
|
|
@@ -107928,14 +108241,15 @@ var init_game_asset_designer_workflow = __esm(() => {
|
|
|
107928
108241
|
"Never switch generators.",
|
|
107929
108242
|
"Describe generated output as production-ready only when every checklist item for it passes with `measured` or `inspected` evidence.",
|
|
107930
108243
|
"Commit only when the brief names the paths and the commit message.",
|
|
107931
|
-
"Generate only when the brief names an output location and allowed tools."
|
|
108244
|
+
"Generate only when the brief names an output location and allowed tools.",
|
|
108245
|
+
"Before the first real call of each command shape (sheet, per-asset, or reference edit), run the same command with --dry-run and apply every constraint it prints; take model-specific constraints only from the dry run, never from memory."
|
|
107932
108246
|
];
|
|
107933
108247
|
});
|
|
107934
108248
|
|
|
107935
108249
|
// ../../services/backend/prompts/teams/game-asset-designer-handoff-templates.ts
|
|
107936
108250
|
function getEntryPointToGameAssetDesignerHandoffTemplate(query) {
|
|
107937
108251
|
const entryPoint = query.fromRole;
|
|
107938
|
-
return `Instructions (not part of the message): Pass the asset request as a brief the designer can act on without guessing. Provide every field below; write \`Not specified.\` for a field you cannot fill and the designer will ask or apply a stated default. Give each asset a stable ID (\`A01\`, \`A02\`…). Use \`spec only\` only when you want a specification with no files; the default is \`spec and generate\`. The \`## Allowed tools\` value is fixed: \`chatroom generate-image --platform=fal (
|
|
108252
|
+
return `Instructions (not part of the message): Pass the asset request as a brief the designer can act on without guessing. Provide every field below; write \`Not specified.\` for a field you cannot fill and the designer will ask or apply a stated default. Give each asset a stable ID (\`A01\`, \`A02\`…). Use \`spec only\` only when you want a specification with no files; the default is \`spec and generate\`. The \`## Allowed tools\` value is fixed: \`chatroom generate-image --platform=fal --model <Model> (dry run first), chatroom slice-image, chatroom remove-image-background\`. Do not name any other generator.
|
|
107939
108253
|
|
|
107940
108254
|
## Asset request
|
|
107941
108255
|
<one to three sentences: what the assets are for and the user-visible goal>
|
|
@@ -107952,11 +108266,14 @@ function getEntryPointToGameAssetDesignerHandoffTemplate(query) {
|
|
|
107952
108266
|
## Style reference
|
|
107953
108267
|
<repository paths, URLs, or a written style sheet; or \`Not specified.\`>
|
|
107954
108268
|
|
|
108269
|
+
## Perspective
|
|
108270
|
+
<one camera perspective shared by every asset in the set, for example \`orthographic front view\`, \`orthographic isometric\`, \`dimetric 2:1\`, or \`side view\`; optionally \`reference: <repository path>\` of an existing asset to match (requires \`## Model\` = \`fal-ai/flux-2/flash/edit\`); a reference makes every asset an individual edit, with no sheets; \`Not applicable.\` for 3D models and audio; or \`Not specified.\`>
|
|
108271
|
+
|
|
107955
108272
|
## Model
|
|
107956
|
-
|
|
108273
|
+
<\`fal-ai/flux-2/flash\` (FLUX 2 Flash), passed as --model; \`fal-ai/flux-2/flash/edit\` only when \`## Perspective\` names a reference asset>
|
|
107957
108274
|
|
|
107958
108275
|
## Model parameters
|
|
107959
|
-
<a JSON object as text passed to \`generate-image --input\`; \`Not specified.\` if none>
|
|
108276
|
+
<a JSON object as text passed to \`generate-image --input\`; one object for every call of a set; its keys must pass the model's --dry-run check; \`Not specified.\` if none>
|
|
107960
108277
|
|
|
107961
108278
|
## Spec consumer
|
|
107962
108279
|
<who integrates these assets from the spec: \`builder\` (application code) or \`user\`; asset files themselves are generated only by \`game-asset-designer\`>
|
|
@@ -107968,7 +108285,7 @@ function getEntryPointToGameAssetDesignerHandoffTemplate(query) {
|
|
|
107968
108285
|
<\`spec and generate\` (default when you name an output location and allowed tools) or \`spec only\`>
|
|
107969
108286
|
|
|
107970
108287
|
## Allowed tools
|
|
107971
|
-
\`chatroom generate-image --platform=fal (
|
|
108288
|
+
\`chatroom generate-image --platform=fal --model <Model> (dry run first), chatroom slice-image, chatroom remove-image-background\`
|
|
107972
108289
|
|
|
107973
108290
|
## Allowed edit paths
|
|
107974
108291
|
<repository-relative paths the designer may edit; \`None.\` for none>
|
|
@@ -108028,7 +108345,7 @@ ${PROOF_OF_PRINCIPLES_REPORT}
|
|
|
108028
108345
|
<each assumption you made and why; each deviation from the planner's target or budgets; or \`None.\`>
|
|
108029
108346
|
|
|
108030
108347
|
## Asset manifest
|
|
108031
|
-
<one entry per asset, keyed by the planner's stable ID (\`A01\`…): name, category, file format, path (or \`Specified only.\`), dimensions/units/scale, pivot, naming, LOD list with triangle budgets, texture set with resolution, color space and channel packing, atlas/pixel grid/frame data, audio format and loudness target, import settings, dependencies and variants; for each budget, the acceptance check (exact command and expected value); for each generated file, the generator command line (\`chatroom generate-image\` with its \`--platform\`, \`--model\`, and \`--
|
|
108348
|
+
<one entry per asset, keyed by the planner's stable ID (\`A01\`…): name, category, file format, path (or \`Specified only.\`), dimensions/units/scale, pivot, naming, LOD list with triangle budgets, texture set with resolution, color space and channel packing, atlas/pixel grid/frame data, audio format and loudness target, import settings, dependencies and variants; for each budget, the acceptance check (exact command and expected value); for each generated file, the generator command line (\`chatroom generate-image\` with its \`--platform\`, \`--model\`, \`--seed\`, \`--input\`, and \`--input-image\` values), the sheet and tile manifests, and the isolated asset manifest path>
|
|
108032
108349
|
|
|
108033
108350
|
## Quality checklist results
|
|
108034
108351
|
<per asset: each applicable checklist item as \`pass\`, \`fail\`, \`n/a\`, or \`not checked\`, with its evidence label and, for \`measured\`, the command and value; write exactly \`None failed.\` after the list if no item failed>
|
|
@@ -109980,7 +110297,7 @@ P1 Recover the brief. IF engine, tier, style, or budgets are missing and the rep
|
|
|
109980
110297
|
P2 Inspect assets, names, folders, engine config; cite paths.
|
|
109981
110298
|
P3 Classify each asset; one manifest entry per ID (\`A01\`…).
|
|
109982
110299
|
P4 IF \`Deliverable mode\` = \`spec only\` OR \`Output location\`/\`Allowed tools\` missing/ambiguous THEN no files, deliver \`Specified only.\`, GOTO P10.
|
|
109983
|
-
P5 Count N. IF N=1 or styles differ THEN per-asset \`generate-image\`, GOTO P8. ELSE use ceil(N / (C×R)) sheets of ≤(C×R) items per \`Sheet grid\` (2x2 default). Prompt: one item per cell, CxR grid, no lines/borders/frames/duplicates. Generate \`generate-image --platform=fal --prompt "<prompt>" --out <Output location>/_work/<sheet>.png\`.
|
|
110300
|
+
P5 Count N. IF N=1 or styles differ THEN per-asset \`generate-image\`, GOTO P8. ELSE use ceil(N / (C×R)) sheets of ≤(C×R) items per \`Sheet grid\` (2x2 default). Prompt: one item per cell, CxR grid, no lines/borders/frames/duplicates. Generate \`generate-image --platform=fal --model <Model> --prompt "<prompt>" --out <Output location>/_work/<sheet>.png\`.
|
|
109984
110301
|
P6 \`chatroom slice-image --in <sheet> --out-dir <Output location>/_work/tiles --cols C --rows R\`. IF refused regenerate once; IF still refused one \`generate-image\` per asset (P5).
|
|
109985
110302
|
P7 View each tile. IF any tile has 0/2+ items, a cut item, or count differs from the brief's requested count (extra cells OK) THEN regenerate once; IF still bad THEN one \`generate-image\` per asset (P5). Name from sight.
|
|
109986
110303
|
P8 IF \`remove-image-background --help\` succeeds THEN run \`remove-image-background --in-dir <tiles> --out-dir <Output location> --map <stem>=<name>,... --trim\` (single: \`--in <image> --out <Output location>/<name>.png --trim\`); view results. IF warnings/halo THEN rerun with \`--tolerance\`/\`--force\` max twice, ELSE accept. ELSE rerun \`slice-image --names\`; record trimming not delivered as a failed item/open question.
|
|
@@ -110019,11 +110336,20 @@ P11 Hand off to \`{ENTRY_POINT_ROLE}\`, then stop.
|
|
|
110019
110336
|
- Animation: uniform frame size per clip, frame count, frame rate, loop flag, and pivot per clip.
|
|
110020
110337
|
- Atlas: padding of at least the engine default (Unity default 4 px), atlas size within the platform max, filter mode Point for pixel art and no mip maps.
|
|
110021
110338
|
- Export PNG (lossless with alpha); name states size and frame count where relevant.
|
|
110339
|
+
- Camera perspective matches the perspective chosen for the set (the brief's \`Perspective\`, or the one recorded under assumptions) and every other asset in the set (compare them side by side); \`n/a\` for a single asset.
|
|
110022
110340
|
|
|
110023
110341
|
**Audio**
|
|
110024
110342
|
- Loudness measured against the platform target stated in the request (for example console -24 LUFS, mobile -18 LUFS, true peak at or below -1 dBTP); no clipping. Measure with a tool: \`ffmpeg -i <file> -af ebur128=peak=true -f null -\` for integrated loudness and true peak; \`ffprobe <file>\` for sample rate, bit depth and channels; for glTF, \`gltf-transform inspect <file>\` (quote the command's actual output). Cite the platform source. If the request does not state the target, mark the loudness target \`unverified\` and name the source. Mobile targets vary (-18 vs -16 LUFS).
|
|
110025
110343
|
- Sample rate, bit depth, channel count, loop points, and format stated; short noisy SFX use PCM or ADPCM, long audio uses compressed or streamed formats, per the engine's guidance.
|
|
110026
110344
|
|
|
110345
|
+
### Perspective consistency (generated 2D sets)
|
|
110346
|
+
Assets used together must share one camera perspective. The model's constraints (unsupported parameters, size defaults, what it does not guarantee) come only from \`generate-image --dry-run\` (R16); apply them, and never add a parameter the dry run reports as unsupported.
|
|
110347
|
+
- Repeat the perspective term from the brief's \`Perspective\` word for word in every prompt of the set (never the \`reference:\` path), and add the same flattening terms to every prompt: \`flat 2D game asset, orthographic, centered, plain background, even lighting\`. Suggested perspective terms (\`unverified\`): side view, front view, orthographic top-down, orthographic isometric, dimetric 2:1, three-quarter view. If the field names only a reference, describe that reference's view in words and record the description under assumptions. If \`Perspective\` is \`Not specified.\`, pick one, record it under assumptions, and use it for the whole set. If the \`Style reference\` asks for a lighting style these terms contradict (for example dramatic lighting), keep the perspective term and drop only the conflicting flattening term.
|
|
110348
|
+
- Use one \`--input\` JSON (from \`Model parameters\`) on every call of the set; if the dry run reports a size parameter as defaulted, set it once in that JSON and reuse it. Add \`--input '<JSON>'\` and the seed to every P5 command.
|
|
110349
|
+
- Use one shared \`--seed\` for the first call of each item or sheet. For every regeneration (P6, P7, or a perspective mismatch), use a new seed and record it (P9). A shared seed does not keep perspective across different prompts.
|
|
110350
|
+
- Pass \`--input-image <path>\` with \`--model fal-ai/flux-2/flash/edit\` only when the brief's \`Model\` is \`fal-ai/flux-2/flash/edit\` and \`Perspective\` names a reference path. In this mode generate every asset individually (the P5 per-asset path, one \`--input-image\` per asset); never use a sheet.
|
|
110351
|
+
- Inspect the set side by side. A mismatched item is regenerated individually (the P5 per-asset path, with a new seed), at most twice per item, separately from the sheet regenerations of P6 and P7. If it still differs, mark the perspective checklist item \`fail\` with \`inspected\` evidence.
|
|
110352
|
+
|
|
110027
110353
|
### Scope boundary
|
|
110028
110354
|
You may specify, name, organize, generate, validate, and integrate assets within the paths the brief allows. You must not claim fidelity you did not establish. If you cannot open or measure a file, write \`unverified\`. Do not present alternative art directions beyond what the request needs; recommend one.
|
|
110029
110355
|
|
|
@@ -118959,7 +119285,7 @@ function parseYaml(text) {
|
|
|
118959
119285
|
}
|
|
118960
119286
|
}
|
|
118961
119287
|
function validateFields(data, body) {
|
|
118962
|
-
if (!
|
|
119288
|
+
if (!isPlainObject4(data))
|
|
118963
119289
|
return fail6("invalid-frontmatter", "frontmatter is not a mapping");
|
|
118964
119290
|
const name = data.name;
|
|
118965
119291
|
if (!isValidName(name))
|
|
@@ -118969,7 +119295,7 @@ function validateFields(data, body) {
|
|
|
118969
119295
|
return fail6("invalid-description");
|
|
118970
119296
|
return { ok: true, name, description, body };
|
|
118971
119297
|
}
|
|
118972
|
-
function
|
|
119298
|
+
function isPlainObject4(value2) {
|
|
118973
119299
|
return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
|
|
118974
119300
|
}
|
|
118975
119301
|
function isValidName(value2) {
|
|
@@ -118989,7 +119315,7 @@ var init_parse_skill_file = __esm(() => {
|
|
|
118989
119315
|
});
|
|
118990
119316
|
|
|
118991
119317
|
// src/infrastructure/external-skills/discover-external-skills.ts
|
|
118992
|
-
import { lstat as lstat2, readdir as readdir2, readFile as
|
|
119318
|
+
import { lstat as lstat2, readdir as readdir2, readFile as readFile9, realpath, stat as stat6 } from "node:fs/promises";
|
|
118993
119319
|
import os2 from "node:os";
|
|
118994
119320
|
import { join as join20 } from "node:path";
|
|
118995
119321
|
function errorCode(err) {
|
|
@@ -119097,7 +119423,7 @@ async function isSymlink2(path3) {
|
|
|
119097
119423
|
}
|
|
119098
119424
|
async function readSkillFile(sourcePath, issues) {
|
|
119099
119425
|
try {
|
|
119100
|
-
return await
|
|
119426
|
+
return await readFile9(join20(sourcePath, SKILL_FILE_NAME), "utf8");
|
|
119101
119427
|
} catch (err) {
|
|
119102
119428
|
issues.push(issue2(sourcePath, "unreadable", errorCode(err)));
|
|
119103
119429
|
return;
|
|
@@ -121588,7 +121914,7 @@ var init_orphan_tracker = __esm(() => {
|
|
|
121588
121914
|
});
|
|
121589
121915
|
|
|
121590
121916
|
// src/daemon/entry/handlers/process/output-store.ts
|
|
121591
|
-
import { appendFile as appendFile2, mkdir as mkdir10, readFile as
|
|
121917
|
+
import { appendFile as appendFile2, mkdir as mkdir10, readFile as readFile10, rm as rm2 } from "node:fs/promises";
|
|
121592
121918
|
import { tmpdir } from "node:os";
|
|
121593
121919
|
import { join as join23 } from "node:path";
|
|
121594
121920
|
|
|
@@ -121621,7 +121947,7 @@ class TempFileOutputStore {
|
|
|
121621
121947
|
async getLastNLines(maxLines) {
|
|
121622
121948
|
let source;
|
|
121623
121949
|
try {
|
|
121624
|
-
source = await
|
|
121950
|
+
source = await readFile10(this.state.filePath, "utf-8");
|
|
121625
121951
|
} catch {
|
|
121626
121952
|
source = this.state.inMemory;
|
|
121627
121953
|
}
|
|
@@ -121638,7 +121964,7 @@ class TempFileOutputStore {
|
|
|
121638
121964
|
}
|
|
121639
121965
|
async getFullOutput() {
|
|
121640
121966
|
try {
|
|
121641
|
-
return await
|
|
121967
|
+
return await readFile10(this.state.filePath, "utf-8");
|
|
121642
121968
|
} catch {
|
|
121643
121969
|
return this.state.inMemory;
|
|
121644
121970
|
}
|
|
@@ -123268,7 +123594,7 @@ var init_workspace_visibility_policy = __esm(() => {
|
|
|
123268
123594
|
|
|
123269
123595
|
// src/infrastructure/services/workspace/workspace-ignore.ts
|
|
123270
123596
|
import { promises as fsPromises } from "node:fs";
|
|
123271
|
-
import { readFile as
|
|
123597
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
123272
123598
|
import { homedir as homedir11 } from "node:os";
|
|
123273
123599
|
import path3 from "node:path";
|
|
123274
123600
|
function normalizeRelativePath(relativePath) {
|
|
@@ -123333,7 +123659,7 @@ async function loadGitExtraExcludeRuleSets(rootDir) {
|
|
|
123333
123659
|
}
|
|
123334
123660
|
async function readIgnoreFile(filePath) {
|
|
123335
123661
|
try {
|
|
123336
|
-
return await
|
|
123662
|
+
return await readFile11(filePath, "utf-8");
|
|
123337
123663
|
} catch {
|
|
123338
123664
|
return null;
|
|
123339
123665
|
}
|
|
@@ -127928,7 +128254,7 @@ function parseMakefileTargets(content) {
|
|
|
127928
128254
|
var MAX_NAME_LENGTH2 = 256;
|
|
127929
128255
|
|
|
127930
128256
|
// src/infrastructure/services/workspace/workspace-resolver.ts
|
|
127931
|
-
import { readFile as
|
|
128257
|
+
import { readFile as readFile13, readdir as readdir5, stat as stat10 } from "node:fs/promises";
|
|
127932
128258
|
import { join as join28, basename as basename7, resolve as resolve10 } from "node:path";
|
|
127933
128259
|
async function resolveGlobPatternStar(rootDir, cleaned) {
|
|
127934
128260
|
const parentDir2 = join28(rootDir, cleaned.slice(0, -2));
|
|
@@ -127969,7 +128295,7 @@ async function resolveGlobPattern(rootDir, pattern) {
|
|
|
127969
128295
|
}
|
|
127970
128296
|
async function readPackageJson(dir) {
|
|
127971
128297
|
try {
|
|
127972
|
-
const content = await
|
|
128298
|
+
const content = await readFile13(join28(dir, "package.json"), "utf-8");
|
|
127973
128299
|
const pkg = JSON.parse(content);
|
|
127974
128300
|
return {
|
|
127975
128301
|
name: pkg.name || basename7(dir),
|
|
@@ -127981,7 +128307,7 @@ async function readPackageJson(dir) {
|
|
|
127981
128307
|
}
|
|
127982
128308
|
async function readPnpmWorkspacePatterns(rootDir) {
|
|
127983
128309
|
try {
|
|
127984
|
-
const content = await
|
|
128310
|
+
const content = await readFile13(join28(rootDir, "pnpm-workspace.yaml"), "utf-8");
|
|
127985
128311
|
const patterns = [];
|
|
127986
128312
|
let inPackages = false;
|
|
127987
128313
|
for (const line of content.split(`
|
|
@@ -128008,7 +128334,7 @@ async function readPnpmWorkspacePatterns(rootDir) {
|
|
|
128008
128334
|
}
|
|
128009
128335
|
async function readPackageJsonWorkspacePatterns(rootDir) {
|
|
128010
128336
|
try {
|
|
128011
|
-
const content = await
|
|
128337
|
+
const content = await readFile13(join28(rootDir, "package.json"), "utf-8");
|
|
128012
128338
|
const pkg = JSON.parse(content);
|
|
128013
128339
|
if (!pkg.workspaces)
|
|
128014
128340
|
return [];
|
|
@@ -128056,7 +128382,7 @@ async function resolveSubWorkspaces(rootDir, pm) {
|
|
|
128056
128382
|
var init_workspace_resolver = () => {};
|
|
128057
128383
|
|
|
128058
128384
|
// src/infrastructure/services/workspace/command-discovery.ts
|
|
128059
|
-
import { access as access2, readFile as
|
|
128385
|
+
import { access as access2, readFile as readFile14 } from "node:fs/promises";
|
|
128060
128386
|
import { join as join29, relative as relative3, basename as basename8 } from "node:path";
|
|
128061
128387
|
async function detectPackageManager(workingDir) {
|
|
128062
128388
|
for (const { file: file2, manager } of LOCKFILE_MAP) {
|
|
@@ -128105,7 +128431,7 @@ function isValidScriptEntry(name, script) {
|
|
|
128105
128431
|
}
|
|
128106
128432
|
async function readJsonFile(filePath, label) {
|
|
128107
128433
|
try {
|
|
128108
|
-
const content = await
|
|
128434
|
+
const content = await readFile14(filePath, "utf-8");
|
|
128109
128435
|
return parseJsonc2(content);
|
|
128110
128436
|
} catch (error51) {
|
|
128111
128437
|
if (error51.code !== "ENOENT") {
|
|
@@ -128182,7 +128508,7 @@ async function readMakefile(workingDir) {
|
|
|
128182
128508
|
const commands = [];
|
|
128183
128509
|
for (const fileName of ["Makefile", "makefile"]) {
|
|
128184
128510
|
try {
|
|
128185
|
-
const content = await
|
|
128511
|
+
const content = await readFile14(join29(workingDir, fileName), "utf-8");
|
|
128186
128512
|
const targets = parseMakefileTargets(content);
|
|
128187
128513
|
if (targets.length === 0)
|
|
128188
128514
|
continue;
|
|
@@ -134302,7 +134628,7 @@ function gunzipBase64Payload(base643, maxBytes) {
|
|
|
134302
134628
|
var init_workspace_path_security = () => {};
|
|
134303
134629
|
|
|
134304
134630
|
// src/daemon/entry/files/file-content-fulfillment.ts
|
|
134305
|
-
import { readFile as
|
|
134631
|
+
import { readFile as readFile15 } from "node:fs/promises";
|
|
134306
134632
|
import { gzipSync as gzipSync3 } from "node:zlib";
|
|
134307
134633
|
function getErrorCause(error51) {
|
|
134308
134634
|
if (typeof error51 === "object" && error51 !== null && "cause" in error51 && error51.cause !== undefined) {
|
|
@@ -134377,7 +134703,7 @@ var init_file_content_fulfillment = __esm(() => {
|
|
|
134377
134703
|
continue;
|
|
134378
134704
|
}
|
|
134379
134705
|
let fileNotFound = false;
|
|
134380
|
-
const buffer = yield* exports_Effect.catch(exports_Effect.tryPromise(() =>
|
|
134706
|
+
const buffer = yield* exports_Effect.catch(exports_Effect.tryPromise(() => readFile15(absolutePath)), (error51) => {
|
|
134381
134707
|
if (isENOENT(error51)) {
|
|
134382
134708
|
fileNotFound = true;
|
|
134383
134709
|
return exports_Effect.succeed(Buffer.alloc(0));
|
|
@@ -146086,9 +146412,10 @@ var {
|
|
|
146086
146412
|
} = import__.default;
|
|
146087
146413
|
|
|
146088
146414
|
// src/commands/generate/index.ts
|
|
146089
|
-
import { readFile, stat as stat2 } from "node:fs/promises";
|
|
146415
|
+
import { readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
146090
146416
|
init_image();
|
|
146091
146417
|
init_local_image_common();
|
|
146418
|
+
init_model_capabilities();
|
|
146092
146419
|
var OUTPUT_FORMATS = ["png", "webp"];
|
|
146093
146420
|
function parsePlatform(value) {
|
|
146094
146421
|
if (!GENERATE_IMAGE_PLATFORMS.includes(value)) {
|
|
@@ -146114,7 +146441,7 @@ async function readInputOption(value) {
|
|
|
146114
146441
|
if (stats.size > MAX_SIDECAR_BYTES) {
|
|
146115
146442
|
throw new Error(`--input file is ${stats.size} bytes; the maximum is ${MAX_SIDECAR_BYTES} bytes (1 MiB).`);
|
|
146116
146443
|
}
|
|
146117
|
-
return parseInputJson(await
|
|
146444
|
+
return parseInputJson(await readFile2(path, "utf8"));
|
|
146118
146445
|
}
|
|
146119
146446
|
function parseSeed(value) {
|
|
146120
146447
|
const seed = Number(value);
|
|
@@ -146125,38 +146452,49 @@ function parseSeed(value) {
|
|
|
146125
146452
|
}
|
|
146126
146453
|
var HELP_AFTER = `
|
|
146127
146454
|
Examples:
|
|
146128
|
-
chatroom generate-image --platform=fal --prompt "a hero" --out hero.png
|
|
146129
|
-
chatroom generate-image --platform=fal --prompt 'pixel-art potion, transparent-friendly flat background' --output-format png --out potion.png
|
|
146455
|
+
chatroom generate-image --platform=fal --model ${FLUX_2_FLASH_MODEL} --prompt "a hero" --out hero.png
|
|
146456
|
+
chatroom generate-image --platform=fal --model ${FLUX_2_FLASH_MODEL} --prompt 'pixel-art potion, transparent-friendly flat background' --output-format png --out potion.png
|
|
146457
|
+
chatroom generate-image --platform=fal --model ${FLUX_2_FLASH_EDIT_MODEL} --prompt "make the sky stormy" --input-image ./hero.png --out hero-stormy.png
|
|
146130
146458
|
`;
|
|
146131
|
-
function
|
|
146132
|
-
|
|
146133
|
-
|
|
146134
|
-
|
|
146135
|
-
|
|
146136
|
-
|
|
146137
|
-
|
|
146138
|
-
|
|
146139
|
-
|
|
146140
|
-
|
|
146141
|
-
|
|
146142
|
-
|
|
146143
|
-
|
|
146144
|
-
|
|
146145
|
-
|
|
146146
|
-
|
|
146147
|
-
|
|
146148
|
-
|
|
146149
|
-
|
|
146150
|
-
console.
|
|
146151
|
-
|
|
146459
|
+
async function runGenerateImage(options) {
|
|
146460
|
+
try {
|
|
146461
|
+
const input = options.input === undefined ? undefined : await readInputOption(options.input);
|
|
146462
|
+
const { createDefaultGenerateImageDeps: createDefaultGenerateImageDeps2 } = await Promise.resolve().then(() => (init_deps(), exports_deps));
|
|
146463
|
+
const deps = createDefaultGenerateImageDeps2();
|
|
146464
|
+
const generateOptions = {
|
|
146465
|
+
prompt: options.prompt,
|
|
146466
|
+
out: options.out,
|
|
146467
|
+
platform: options.platform,
|
|
146468
|
+
model: options.model,
|
|
146469
|
+
aspectRatio: options.aspectRatio,
|
|
146470
|
+
outputFormat: options.outputFormat,
|
|
146471
|
+
seed: options.seed,
|
|
146472
|
+
input,
|
|
146473
|
+
inputImage: options.inputImage,
|
|
146474
|
+
force: options.force
|
|
146475
|
+
};
|
|
146476
|
+
if (options.dryRun === true) {
|
|
146477
|
+
const { formatGenerateImagePlan: formatGenerateImagePlan2, planGenerateImage: planGenerateImage2 } = await Promise.resolve().then(() => (init_image(), exports_image));
|
|
146478
|
+
console.log(formatGenerateImagePlan2(await planGenerateImage2(generateOptions, deps)));
|
|
146479
|
+
return;
|
|
146152
146480
|
}
|
|
146153
|
-
|
|
146481
|
+
const result = await generateImage(generateOptions, deps);
|
|
146482
|
+
console.log(`Generated ${result.outPath}`);
|
|
146483
|
+
console.log(`Manifest ${result.manifestPath}`);
|
|
146484
|
+
} catch (err) {
|
|
146485
|
+
console.error(`❌ ${err instanceof Error ? err.message : String(err)}`);
|
|
146486
|
+
process.exitCode = 1;
|
|
146487
|
+
}
|
|
146488
|
+
}
|
|
146489
|
+
function registerGenerateImageCommand(program2) {
|
|
146490
|
+
program2.command("generate-image").description("Generate one image with a fal model and write a JSON manifest next to it").requiredOption("--prompt <text>", "Text prompt describing the image").requiredOption("--out <path>", "Output image path (manifest is written to <path>.json)").option("--platform <name>", "Generation platform: fal", parsePlatform, DEFAULT_GENERATE_IMAGE_PLATFORM).requiredOption("--model <id>", `fal model id (required), for example ${FLUX_2_FLASH_MODEL}, or ${FLUX_2_FLASH_EDIT_MODEL} with --input-image`).option("--aspect-ratio <ratio>", "Aspect ratio for models that take one, e.g. 1:1, 16:9 (omitted = model default). Rejected for models the CLI knows do not support it").option("--output-format <format>", "Output format: png or webp (omitted = model default)", parseOutputFormat).option("--seed <n>", "Seed for the random number generator", parseSeed).option("--input <json|@file>", "Model-specific JSON input object merged into the fal request; @file reads JSON from a file").option("--input-image <path-or-url>", `Image to edit: a local .png, .jpg, .jpeg, .webp, or .gif (uploaded to fal) or an http(s) URL. Requires an edit model such as ${FLUX_2_FLASH_EDIT_MODEL}`).option("--force", "Overwrite existing output and manifest files").option("--dry-run", "Validate and print the request and the model constraints the CLI knows, without uploading or calling fal (FAL_API_KEY not needed)").addHelpText("after", HELP_AFTER).action(runGenerateImage);
|
|
146154
146491
|
}
|
|
146155
146492
|
|
|
146156
146493
|
// src/commands/generate/remove-background.ts
|
|
146157
146494
|
import { basename, dirname as dirname3, extname as extname2, join as join2, resolve as resolve2 } from "node:path";
|
|
146158
146495
|
|
|
146159
146496
|
// src/commands/generate/background-key.ts
|
|
146497
|
+
init_local_image_common();
|
|
146160
146498
|
var DEFAULT_TOLERANCE = 24;
|
|
146161
146499
|
var DEFAULT_MIN_AREA_PERCENT = 0.5;
|
|
146162
146500
|
var BORDER_NOISE_REFUSAL_RATIO = 0.02;
|
|
@@ -146174,13 +146512,6 @@ function borderCoordinates(width, height) {
|
|
|
146174
146512
|
}
|
|
146175
146513
|
return coordinates;
|
|
146176
146514
|
}
|
|
146177
|
-
function median(values) {
|
|
146178
|
-
const sorted = [...values].sort((a, b) => a - b);
|
|
146179
|
-
const middle = Math.floor(sorted.length / 2);
|
|
146180
|
-
if (sorted.length % 2 === 1)
|
|
146181
|
-
return sorted[middle] ?? 0;
|
|
146182
|
-
return Math.round(((sorted[middle - 1] ?? 0) + (sorted[middle] ?? 0)) / 2);
|
|
146183
|
-
}
|
|
146184
146515
|
function forEachNeighbour(index, width, height, visit) {
|
|
146185
146516
|
const x = index % width;
|
|
146186
146517
|
const y = Math.floor(index / width);
|
|
@@ -147313,14 +147644,7 @@ function mostFrequentBorderColor(image) {
|
|
|
147313
147644
|
}
|
|
147314
147645
|
if (red.length === 0)
|
|
147315
147646
|
return null;
|
|
147316
|
-
|
|
147317
|
-
const sorted = [...values].sort((a, b) => a - b);
|
|
147318
|
-
const middle = Math.floor(sorted.length / 2);
|
|
147319
|
-
if (sorted.length % 2 === 1)
|
|
147320
|
-
return sorted[middle] ?? 0;
|
|
147321
|
-
return Math.round(((sorted[middle - 1] ?? 0) + (sorted[middle] ?? 0)) / 2);
|
|
147322
|
-
};
|
|
147323
|
-
return [median2(red), median2(green), median2(blue)];
|
|
147647
|
+
return [median(red), median(green), median(blue)];
|
|
147324
147648
|
}
|
|
147325
147649
|
function countCellEdgeForeground(image, cell, background) {
|
|
147326
147650
|
const { width, height, data } = image;
|
|
@@ -147408,7 +147732,7 @@ async function sliceImage(opts, deps) {
|
|
|
147408
147732
|
throw new SliceImageError(`--in is ${bytes.byteLength} bytes; the maximum is ${MAX_SOURCE_BYTES} bytes (64 MiB).`);
|
|
147409
147733
|
}
|
|
147410
147734
|
if (!hasPngMagic(bytes)) {
|
|
147411
|
-
throw new SliceImageError("--in is not a PNG. slice-image only reads PNG sheets; regenerate with 'chatroom generate-image --output-format png'
|
|
147735
|
+
throw new SliceImageError("--in is not a PNG. slice-image only reads PNG sheets; regenerate with 'chatroom generate-image --model <id> --output-format png'.");
|
|
147412
147736
|
}
|
|
147413
147737
|
const dimensions = readPngDimensions(bytes);
|
|
147414
147738
|
if (dimensions === null) {
|
|
@@ -148369,4 +148693,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
148369
148693
|
});
|
|
148370
148694
|
program2.parse();
|
|
148371
148695
|
|
|
148372
|
-
//# debugId=
|
|
148696
|
+
//# debugId=0C6C6A8595C191FE64756E2164756E21
|