chatroom-cli 1.136.0 → 1.137.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/dist/index.js +501 -153
- package/dist/index.js.map +26 -25
- 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"
|
|
2339
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"
|
|
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: {
|
|
@@ -107472,9 +107785,11 @@ function getEntryPointToArchitectHandoffTemplate(query) {
|
|
|
107472
107785
|
const entryPoint = query.fromRole;
|
|
107473
107786
|
return `The automatically injected \`<user-message>\` is the authoritative request. Add only concise context that the Architect needs to design within the existing repository and team scope.
|
|
107474
107787
|
|
|
107788
|
+
Use this before any backend contract, API, or schema/persistence change.
|
|
107789
|
+
|
|
107475
107790
|
Provide the request, relevant constraints, and the files or product surfaces that need inspection. Ask for one evidence-backed design focused on ${getArchitectFocus()}.
|
|
107476
107791
|
|
|
107477
|
-
After completing the design, hand it back to \`${entryPoint}\` with the normal handoff command
|
|
107792
|
+
After completing the design, the \`architect\` must hand it back to \`${entryPoint}\` with the normal handoff command, then stop; it must not implement the design or delegate further work.
|
|
107478
107793
|
|
|
107479
107794
|
\`\`\`bash
|
|
107480
107795
|
${handoffCommand({
|
|
@@ -107914,7 +108229,7 @@ function formatProofOfPrinciples() {
|
|
|
107914
108229
|
var GAME_ASSET_RULES;
|
|
107915
108230
|
var init_game_asset_designer_workflow = __esm(() => {
|
|
107916
108231
|
GAME_ASSET_RULES = [
|
|
107917
|
-
"Generate only with `chatroom generate-image --platform=fal
|
|
108232
|
+
"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
108233
|
"Process images only with `chatroom slice-image` and `chatroom remove-image-background`; no other tool, script, or remote service.",
|
|
107919
108234
|
"A sheet is at most the brief's `Sheet grid` (default 2x2); a larger grid needs a user request quoted in the brief.",
|
|
107920
108235
|
"Names come from looking at each tile, never from cell order or prompt order.",
|
|
@@ -107928,14 +108243,15 @@ var init_game_asset_designer_workflow = __esm(() => {
|
|
|
107928
108243
|
"Never switch generators.",
|
|
107929
108244
|
"Describe generated output as production-ready only when every checklist item for it passes with `measured` or `inspected` evidence.",
|
|
107930
108245
|
"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."
|
|
108246
|
+
"Generate only when the brief names an output location and allowed tools.",
|
|
108247
|
+
"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
108248
|
];
|
|
107933
108249
|
});
|
|
107934
108250
|
|
|
107935
108251
|
// ../../services/backend/prompts/teams/game-asset-designer-handoff-templates.ts
|
|
107936
108252
|
function getEntryPointToGameAssetDesignerHandoffTemplate(query) {
|
|
107937
108253
|
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 (
|
|
108254
|
+
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
108255
|
|
|
107940
108256
|
## Asset request
|
|
107941
108257
|
<one to three sentences: what the assets are for and the user-visible goal>
|
|
@@ -107952,14 +108268,17 @@ function getEntryPointToGameAssetDesignerHandoffTemplate(query) {
|
|
|
107952
108268
|
## Style reference
|
|
107953
108269
|
<repository paths, URLs, or a written style sheet; or \`Not specified.\`>
|
|
107954
108270
|
|
|
108271
|
+
## Perspective
|
|
108272
|
+
<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.\`>
|
|
108273
|
+
|
|
107955
108274
|
## Model
|
|
107956
|
-
|
|
108275
|
+
<\`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
108276
|
|
|
107958
108277
|
## Model parameters
|
|
107959
|
-
<a JSON object as text passed to \`generate-image --input\`; \`Not specified.\` if none>
|
|
108278
|
+
<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
108279
|
|
|
107961
108280
|
## Spec consumer
|
|
107962
|
-
<who integrates these assets from the spec: \`builder\` (application
|
|
108281
|
+
<who integrates these assets from the spec: \`builder\` (application logic outside the allowed edit paths) or \`user\`; asset files themselves are generated only by \`game-asset-designer\`>
|
|
107963
108282
|
|
|
107964
108283
|
## Existing assets
|
|
107965
108284
|
<repository paths the designer must reuse or must not overwrite; or \`Not specified.\`>
|
|
@@ -107968,7 +108287,7 @@ function getEntryPointToGameAssetDesignerHandoffTemplate(query) {
|
|
|
107968
108287
|
<\`spec and generate\` (default when you name an output location and allowed tools) or \`spec only\`>
|
|
107969
108288
|
|
|
107970
108289
|
## Allowed tools
|
|
107971
|
-
\`chatroom generate-image --platform=fal (
|
|
108290
|
+
\`chatroom generate-image --platform=fal --model <Model> (dry run first), chatroom slice-image, chatroom remove-image-background\`
|
|
107972
108291
|
|
|
107973
108292
|
## Allowed edit paths
|
|
107974
108293
|
<repository-relative paths the designer may edit; \`None.\` for none>
|
|
@@ -107997,7 +108316,7 @@ Instructions after the body (not part of the message): the planner may append ex
|
|
|
107997
108316
|
- assets must meet engine import conventions or budgets before a builder integrates them;
|
|
107998
108317
|
- the user asks for an asset spec, style sheet, or asset audit.
|
|
107999
108318
|
|
|
108000
|
-
Skip it for code-only work
|
|
108319
|
+
Skip it for code-only work. Non-game UI icons and illustrations also go to this role; they are not sent to \`uiux-engineer\`.
|
|
108001
108320
|
|
|
108002
108321
|
\`\`\`bash
|
|
108003
108322
|
${handoffCommand({
|
|
@@ -108028,7 +108347,7 @@ ${PROOF_OF_PRINCIPLES_REPORT}
|
|
|
108028
108347
|
<each assumption you made and why; each deviation from the planner's target or budgets; or \`None.\`>
|
|
108029
108348
|
|
|
108030
108349
|
## 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 \`--
|
|
108350
|
+
<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
108351
|
|
|
108033
108352
|
## Quality checklist results
|
|
108034
108353
|
<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>
|
|
@@ -108058,7 +108377,7 @@ Write exactly \`None.\` if no sheet was used.
|
|
|
108058
108377
|
<every property you could not measure or inspect; write exactly \`None.\` if none>
|
|
108059
108378
|
|
|
108060
108379
|
## Recommended next step
|
|
108061
|
-
<exactly one target: \`game-asset-designer\` (re-run with the listed asset IDs), \`builder\` (
|
|
108380
|
+
<exactly one target: \`game-asset-designer\` (re-run with the listed asset IDs), \`builder\` (application logic outside the allowed edit paths), \`uiux-engineer\` (layout, component, or styling changes only, never asset files), or \`planner\` (decision needed on an open question); then the affected asset IDs>
|
|
108062
108381
|
|
|
108063
108382
|
Instructions after the body (not part of the message): run the normal handoff command to \`${entryPoint}\` with the complete body above, then stop.
|
|
108064
108383
|
|
|
@@ -108129,11 +108448,11 @@ var init_planner_architect_chat = __esm(() => {
|
|
|
108129
108448
|
function getPlannerBuilderCodeBody() {
|
|
108130
108449
|
return `${getDelegationBriefIntro()}
|
|
108131
108450
|
|
|
108132
|
-
**Before you delegate:** if this slice introduces anything not yet grounded in the repository (a new library, API, external platform behavior, or an unresolved
|
|
108451
|
+
**Before you delegate:** if this slice introduces anything not yet grounded in the repository (a new library, API, external platform behavior, or an unresolved choice that depends on missing facts), hand off to \`researcher\` first and delegate only after its brief returns. Skip this when the \`researcher\` template's skip condition applies. The \`researcher\` handoff template defines the criteria.
|
|
108133
108452
|
|
|
108134
108453
|
**Bug fixes:** if this slice fixes a reported bug, delegate only after a \`triage\` report for that bug has returned, and build the brief from its root cause and proposed fix. The \`triage\` handoff template defines the investigation.
|
|
108135
108454
|
|
|
108136
|
-
**Division of labor:** You (planner) own
|
|
108455
|
+
**Division of labor:** For backend contract, API, or schema/persistence changes, the architect designs first and you delegate only after its design returns. You (planner) own slicing and sequencing of builder work. The UI/UX engineer owns UI/UX design. Include its target markup, classes, styles, and screen and flow drawings in UI implementation briefs; the builder implements the components.
|
|
108137
108456
|
|
|
108138
108457
|
**Detail bar:** Specify down to **every file** the builder will create or modify (full repo paths). Include code snippets — types, signatures, stubs, or target implementations — until a competent builder **cannot misinterpret** what to write. Vague layers ("update the backend", "fix the component") are not acceptable.
|
|
108139
108458
|
|
|
@@ -108236,7 +108555,7 @@ function getEntryPointToResearcherHandoffTemplate(query) {
|
|
|
108236
108555
|
Research before delegating when the slice introduces something not already grounded in this repository:
|
|
108237
108556
|
- a new library, API, external service, platform behavior, or version-sensitive dependency;
|
|
108238
108557
|
- an unfamiliar subsystem;
|
|
108239
|
-
- a
|
|
108558
|
+
- a choice between several plausible approaches that depends on facts not yet in the repository (the \`architect\` still owns backend contract, API, and schema/persistence design);
|
|
108240
108559
|
- any ambiguity the user has not resolved.
|
|
108241
108560
|
|
|
108242
108561
|
Skip research when the change is a small, well-understood edit in code you have already inspected.
|
|
@@ -108323,7 +108642,7 @@ Review before delivery for code-producing work:
|
|
|
108323
108642
|
- the user asks for a code review;
|
|
108324
108643
|
- a change is large, risky, or touches security-sensitive or shared code.
|
|
108325
108644
|
|
|
108326
|
-
Skip review for a
|
|
108645
|
+
Skip review only for an edit of a few lines in one file that the builder already verified with a test or command.
|
|
108327
108646
|
|
|
108328
108647
|
After completing the review, the reviewer must return its issue report to \`${entryPoint}\` with the normal handoff command.
|
|
108329
108648
|
|
|
@@ -108385,6 +108704,8 @@ function getEntryPointToTriageHandoffTemplate(query) {
|
|
|
108385
108704
|
const entryPoint = query.fromRole;
|
|
108386
108705
|
return `The automatically injected \`<user-message>\` is the authoritative request. Pass the reported bug, observed and expected behavior, reproduction details and commands, relevant constraints, and the files or symbols already suspected. Ask triage to investigate and report only. Triage may inspect code and attempt a focused automated regression test as evidence, but must not edit implementation files or implement a fix.
|
|
108387
108706
|
|
|
108707
|
+
Use this for every reported bug (something broken or behaving differently than expected) before any fix is implemented.
|
|
108708
|
+
|
|
108388
108709
|
After completing the investigation, triage must return the evidence-backed report to \`${entryPoint}\` with the normal handoff command.
|
|
108389
108710
|
|
|
108390
108711
|
\`\`\`bash
|
|
@@ -108451,11 +108772,12 @@ var init_planner_triage_chat = __esm(() => {
|
|
|
108451
108772
|
|
|
108452
108773
|
// ../../services/backend/prompts/teams/uiux-engineer-handoff-templates.ts
|
|
108453
108774
|
function getUiuxEngineerFocus() {
|
|
108454
|
-
return "complete user flows, loading/empty/error/success states, component ownership, theme tokens and layout, responsive details, and UI tests";
|
|
108775
|
+
return "complete user flows, screen and flow drawings, loading/empty/error/success states, component ownership, theme tokens and layout, responsive details, and UI tests";
|
|
108455
108776
|
}
|
|
108456
108777
|
function getUiuxEngineerRequirements() {
|
|
108457
108778
|
return `
|
|
108458
108779
|
- Specify complete user flows with explicit loading, empty, error, success, disabled, and retry states where applicable.
|
|
108780
|
+
- For any user-facing workflow, draw each affected screen as an ASCII wireframe and each flow as a Mermaid flowchart, using the "Screens, Flows, and Layouts" section. Each screen drawing labels every button, input, and link with its exact text and the repository component it uses; each flow drawing shows the loading, empty, error, success, disabled, and retry transitions that apply.
|
|
108459
108781
|
- Define component hierarchy and ownership, props/state/events, data boundaries, and interaction behavior.
|
|
108460
108782
|
- Choose concrete existing ShadCN components with the Base UI backend and lucide-react/react-icons conventions; do not write "use an appropriate component."
|
|
108461
108783
|
- Specify Tailwind utility classes or semantic theme-token classes for layout, spacing, sizing, typography, states, responsive behavior, and dark mode.
|
|
@@ -108470,7 +108792,7 @@ function getEntryPointToUiuxEngineerHandoffTemplate(query) {
|
|
|
108470
108792
|
|
|
108471
108793
|
Provide the request, relevant constraints, and the files or product surfaces that need inspection. Ask for one evidence-backed design focused on ${getUiuxEngineerFocus()}.
|
|
108472
108794
|
|
|
108473
|
-
After completing the design, hand it back to \`${entryPoint}\` with the normal handoff command
|
|
108795
|
+
After completing the design, the \`uiux-engineer\` must hand it back to \`${entryPoint}\` with the normal handoff command, then stop; it must not implement the design or delegate further work.
|
|
108474
108796
|
|
|
108475
108797
|
\`\`\`bash
|
|
108476
108798
|
${handoffCommand({
|
|
@@ -108498,6 +108820,24 @@ function getUiuxEngineerToEntryPointHandoffTemplate(query) {
|
|
|
108498
108820
|
- Name concrete repository paths, existing components/tokens, props/state/events, data boundaries, acceptance criteria, risks, and verification steps.
|
|
108499
108821
|
${getUiuxEngineerRequirements()}
|
|
108500
108822
|
|
|
108823
|
+
## Screens, Flows, and Layouts
|
|
108824
|
+
<required for a user-facing workflow; write exactly \`Not Applicable.\` for a non-visual change>
|
|
108825
|
+
|
|
108826
|
+
### Flow: <flow name>
|
|
108827
|
+
\`\`\`mermaid
|
|
108828
|
+
flowchart TD
|
|
108829
|
+
<one node per screen or state; each edge labelled with the user action or system event that triggers it; include the loading, empty, error, success, disabled, and retry states that apply>
|
|
108830
|
+
\`\`\`
|
|
108831
|
+
|
|
108832
|
+
### Screen: <screen name> (\`<route or entry point>\`)
|
|
108833
|
+
\`\`\`text
|
|
108834
|
+
<ASCII wireframe: the layout regions, headings, and every button, input, and link labelled with its exact text, with the repository component in brackets, for example [Button variant="destructive"]. Show the empty, loading, and error variants as separate drawings if they change the layout.>
|
|
108835
|
+
\`\`\`
|
|
108836
|
+
- States: <loading, empty, error, success, disabled, retry — which apply and how each changes the layout>
|
|
108837
|
+
- Responsive: <breakpoint behavior, one line per breakpoint that changes the layout>
|
|
108838
|
+
|
|
108839
|
+
<repeat one ### Flow block per flow and one ### Screen block per screen; do not collapse several screens into one drawing>
|
|
108840
|
+
|
|
108501
108841
|
## Force Multipliers
|
|
108502
108842
|
<name the existing components, patterns, tokens, utilities, and platform conventions to reuse, and state why each reduces implementation risk or duplication>
|
|
108503
108843
|
- Keep the design within the repository's established source-of-truth boundaries and avoid introducing a new registry or abstraction without evidence.
|
|
@@ -109928,7 +110268,7 @@ var init_architect3 = __esm(() => {
|
|
|
109928
110268
|
init_sections();
|
|
109929
110269
|
ARCHITECT_GUIDANCE = `## Architect Operating Model
|
|
109930
110270
|
|
|
109931
|
-
You are the architect responsible for
|
|
110271
|
+
You are the architect responsible for system design and code-level implementation plans. You are advisory only: do not implement code, edit files, or act as the builder.
|
|
109932
110272
|
|
|
109933
110273
|
${"{SESSION_CONTINUITY}"}
|
|
109934
110274
|
|
|
@@ -109962,7 +110302,7 @@ var init_game_asset_designer2 = __esm(() => {
|
|
|
109962
110302
|
init_sections();
|
|
109963
110303
|
GAME_ASSET_DESIGNER_GUIDANCE = `## Game Asset Designer Operating Model
|
|
109964
110304
|
|
|
109965
|
-
You specify production-quality game assets
|
|
110305
|
+
You specify and generate production-quality game and UI assets (generation is the default unless the brief says \`spec only\`), using \`chatroom generate-image --platform=fal\`. You are not the builder or planner; do not implement application logic outside the asset-integration paths the brief allows. Your delivery report uses Proof of Principles and Proof of Completion.
|
|
109966
110306
|
|
|
109967
110307
|
${"{SESSION_CONTINUITY}"}
|
|
109968
110308
|
|
|
@@ -109980,7 +110320,7 @@ P1 Recover the brief. IF engine, tier, style, or budgets are missing and the rep
|
|
|
109980
110320
|
P2 Inspect assets, names, folders, engine config; cite paths.
|
|
109981
110321
|
P3 Classify each asset; one manifest entry per ID (\`A01\`…).
|
|
109982
110322
|
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\`.
|
|
110323
|
+
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
110324
|
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
110325
|
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
110326
|
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 +110359,20 @@ P11 Hand off to \`{ENTRY_POINT_ROLE}\`, then stop.
|
|
|
110019
110359
|
- Animation: uniform frame size per clip, frame count, frame rate, loop flag, and pivot per clip.
|
|
110020
110360
|
- 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
110361
|
- Export PNG (lossless with alpha); name states size and frame count where relevant.
|
|
110362
|
+
- 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
110363
|
|
|
110023
110364
|
**Audio**
|
|
110024
110365
|
- 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
110366
|
- 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
110367
|
|
|
110368
|
+
### Perspective consistency (generated 2D sets)
|
|
110369
|
+
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.
|
|
110370
|
+
- 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.
|
|
110371
|
+
- 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.
|
|
110372
|
+
- 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.
|
|
110373
|
+
- 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.
|
|
110374
|
+
- 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.
|
|
110375
|
+
|
|
110027
110376
|
### Scope boundary
|
|
110028
110377
|
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
110378
|
|
|
@@ -110128,17 +110477,18 @@ ${getInterRoleHandoffRule()}
|
|
|
110128
110477
|
|
|
110129
110478
|
1. Recover the authoritative user request and relevant chatroom history before designing. Inspect existing UI patterns, components, tokens, interaction conventions, and nearby tests that constrain the experience.
|
|
110130
110479
|
2. Treat the injected \`<handoff-templates>\` UI/UX Engineer Design Handoff Brief as authoritative. Complete every section in its exact order, using \`Not Applicable.\` only for a genuinely inapplicable section and never as filler.
|
|
110131
|
-
3. Produce exactly one recommended, evidence-backed interface and experience design. Do not present alternatives or generic UX advice. Include concrete repository paths, complete user flows, and explicit loading, empty, error,
|
|
110132
|
-
4.
|
|
110133
|
-
5.
|
|
110134
|
-
6.
|
|
110135
|
-
7.
|
|
110480
|
+
3. Produce exactly one recommended, evidence-backed interface and experience design. Do not present alternatives or generic UX advice. Include concrete repository paths, complete user flows, and explicit loading, empty, error, and success states, plus disabled and retry states where applicable.
|
|
110481
|
+
4. For any user-facing workflow, draw each affected screen as an ASCII wireframe (every button and state labelled) and each flow as a Mermaid flowchart in the handback. Write \`Not Applicable.\` for the drawings section only when the change is non-visual.
|
|
110482
|
+
5. Specify component hierarchy and ownership, props/state/events, data boundaries, and interaction behavior. Do not propose or require web accessibility implementation work unless the user explicitly requests it.
|
|
110483
|
+
6. Choose concrete ShadCN components using the Base UI backend and existing icon conventions. Specify exact Tailwind utility or semantic theme-token classes for layout, spacing, sizing, typography, states, responsive behavior, and dark mode; keep the styling theme-aware and make state ownership explicit. Also specify responsive breakpoints, visual feedback, destructive-action safeguards, and the UI/integration test plan.
|
|
110484
|
+
7. Keep the proposal within the user's request and the repository's established patterns. Do not expand scope, spawn subagents, implement the design, edit files, or act as the builder. You do not generate asset files: any icon or illustration is specified by \`game-asset-designer\` and generated by \`game-asset-designer\`.
|
|
110485
|
+
8. Hand the complete design brief to \`{ENTRY_POINT_ROLE}\` with the normal command, then stop:
|
|
110136
110486
|
|
|
110137
110487
|
\`\`\`bash
|
|
110138
110488
|
chatroom handoff --chatroom-id="<chatroom-id>" --role="uiux-engineer" --next-role="{ENTRY_POINT_ROLE}"
|
|
110139
110489
|
\`\`\`
|
|
110140
110490
|
|
|
110141
|
-
The handoff must contain every required brief section, repository evidence, chosen flow, exact component/prop/state details, explicit states,
|
|
110491
|
+
The handoff must contain every required brief section, repository evidence, chosen flow, exact component/prop/state details, explicit states, ownership decisions, concrete classes/tokens, risks, acceptance criteria, tests, and implementation sequence \`{ENTRY_POINT_ROLE}\` needs to coordinate implementation. It must not contain implementation edits or alternative designs.`;
|
|
110142
110492
|
});
|
|
110143
110493
|
|
|
110144
110494
|
// ../../services/backend/prompts/cli/roles/verifier.ts
|
|
@@ -110304,7 +110654,7 @@ var init_templates = __esm(() => {
|
|
|
110304
110654
|
"game-asset-designer": {
|
|
110305
110655
|
role: "game-asset-designer",
|
|
110306
110656
|
title: "Game Asset Designer",
|
|
110307
|
-
description: "You are the game asset designer responsible for specifying production-quality game assets (
|
|
110657
|
+
description: "You are the game asset designer responsible for specifying and generating production-quality game and UI assets (generation is the default unless the brief says `spec only`); you do not implement application code.",
|
|
110308
110658
|
responsibilities: [
|
|
110309
110659
|
"Recover the target, asset request, style reference, and budgets from the planner brief",
|
|
110310
110660
|
"Inspect the repository for existing assets, naming and folder conventions, and engine import configuration",
|
|
@@ -110317,13 +110667,13 @@ var init_templates = __esm(() => {
|
|
|
110317
110667
|
verifier: {
|
|
110318
110668
|
role: "verifier",
|
|
110319
110669
|
title: "Verifier",
|
|
110320
|
-
description: "You are the verifier responsible for judging, from chatroom logs only, whether the user's last message was achieved; you do not implement,
|
|
110670
|
+
description: "You are the verifier responsible for judging, from chatroom logs only, whether the user's last message was achieved; you do not implement, build, or edit code, and you run only the tests the user explicitly asked for.",
|
|
110321
110671
|
responsibilities: [
|
|
110322
110672
|
"Recover the user's last message and the pinned context from the chatroom logs",
|
|
110323
110673
|
"Break the objective into numbered requirements",
|
|
110324
110674
|
"Check each requirement against concrete evidence in the logs, treating the planner claim as unverified",
|
|
110325
110675
|
"Return one verdict (ACHIEVED, NOT_ACHIEVED, or INCONCLUSIVE) with evidence and the missing items",
|
|
110326
|
-
"Execute nothing: no
|
|
110676
|
+
"Execute nothing except the tests the user explicitly asked for: no builds, lints, or edits"
|
|
110327
110677
|
],
|
|
110328
110678
|
defaultHandoffTarget: "planner"
|
|
110329
110679
|
},
|
|
@@ -118959,7 +119309,7 @@ function parseYaml(text) {
|
|
|
118959
119309
|
}
|
|
118960
119310
|
}
|
|
118961
119311
|
function validateFields(data, body) {
|
|
118962
|
-
if (!
|
|
119312
|
+
if (!isPlainObject4(data))
|
|
118963
119313
|
return fail6("invalid-frontmatter", "frontmatter is not a mapping");
|
|
118964
119314
|
const name = data.name;
|
|
118965
119315
|
if (!isValidName(name))
|
|
@@ -118969,7 +119319,7 @@ function validateFields(data, body) {
|
|
|
118969
119319
|
return fail6("invalid-description");
|
|
118970
119320
|
return { ok: true, name, description, body };
|
|
118971
119321
|
}
|
|
118972
|
-
function
|
|
119322
|
+
function isPlainObject4(value2) {
|
|
118973
119323
|
return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
|
|
118974
119324
|
}
|
|
118975
119325
|
function isValidName(value2) {
|
|
@@ -118989,7 +119339,7 @@ var init_parse_skill_file = __esm(() => {
|
|
|
118989
119339
|
});
|
|
118990
119340
|
|
|
118991
119341
|
// src/infrastructure/external-skills/discover-external-skills.ts
|
|
118992
|
-
import { lstat as lstat2, readdir as readdir2, readFile as
|
|
119342
|
+
import { lstat as lstat2, readdir as readdir2, readFile as readFile9, realpath, stat as stat6 } from "node:fs/promises";
|
|
118993
119343
|
import os2 from "node:os";
|
|
118994
119344
|
import { join as join20 } from "node:path";
|
|
118995
119345
|
function errorCode(err) {
|
|
@@ -119097,7 +119447,7 @@ async function isSymlink2(path3) {
|
|
|
119097
119447
|
}
|
|
119098
119448
|
async function readSkillFile(sourcePath, issues) {
|
|
119099
119449
|
try {
|
|
119100
|
-
return await
|
|
119450
|
+
return await readFile9(join20(sourcePath, SKILL_FILE_NAME), "utf8");
|
|
119101
119451
|
} catch (err) {
|
|
119102
119452
|
issues.push(issue2(sourcePath, "unreadable", errorCode(err)));
|
|
119103
119453
|
return;
|
|
@@ -121588,7 +121938,7 @@ var init_orphan_tracker = __esm(() => {
|
|
|
121588
121938
|
});
|
|
121589
121939
|
|
|
121590
121940
|
// src/daemon/entry/handlers/process/output-store.ts
|
|
121591
|
-
import { appendFile as appendFile2, mkdir as mkdir10, readFile as
|
|
121941
|
+
import { appendFile as appendFile2, mkdir as mkdir10, readFile as readFile10, rm as rm2 } from "node:fs/promises";
|
|
121592
121942
|
import { tmpdir } from "node:os";
|
|
121593
121943
|
import { join as join23 } from "node:path";
|
|
121594
121944
|
|
|
@@ -121621,7 +121971,7 @@ class TempFileOutputStore {
|
|
|
121621
121971
|
async getLastNLines(maxLines) {
|
|
121622
121972
|
let source;
|
|
121623
121973
|
try {
|
|
121624
|
-
source = await
|
|
121974
|
+
source = await readFile10(this.state.filePath, "utf-8");
|
|
121625
121975
|
} catch {
|
|
121626
121976
|
source = this.state.inMemory;
|
|
121627
121977
|
}
|
|
@@ -121638,7 +121988,7 @@ class TempFileOutputStore {
|
|
|
121638
121988
|
}
|
|
121639
121989
|
async getFullOutput() {
|
|
121640
121990
|
try {
|
|
121641
|
-
return await
|
|
121991
|
+
return await readFile10(this.state.filePath, "utf-8");
|
|
121642
121992
|
} catch {
|
|
121643
121993
|
return this.state.inMemory;
|
|
121644
121994
|
}
|
|
@@ -123268,7 +123618,7 @@ var init_workspace_visibility_policy = __esm(() => {
|
|
|
123268
123618
|
|
|
123269
123619
|
// src/infrastructure/services/workspace/workspace-ignore.ts
|
|
123270
123620
|
import { promises as fsPromises } from "node:fs";
|
|
123271
|
-
import { readFile as
|
|
123621
|
+
import { readFile as readFile11 } from "node:fs/promises";
|
|
123272
123622
|
import { homedir as homedir11 } from "node:os";
|
|
123273
123623
|
import path3 from "node:path";
|
|
123274
123624
|
function normalizeRelativePath(relativePath) {
|
|
@@ -123333,7 +123683,7 @@ async function loadGitExtraExcludeRuleSets(rootDir) {
|
|
|
123333
123683
|
}
|
|
123334
123684
|
async function readIgnoreFile(filePath) {
|
|
123335
123685
|
try {
|
|
123336
|
-
return await
|
|
123686
|
+
return await readFile11(filePath, "utf-8");
|
|
123337
123687
|
} catch {
|
|
123338
123688
|
return null;
|
|
123339
123689
|
}
|
|
@@ -127928,7 +128278,7 @@ function parseMakefileTargets(content) {
|
|
|
127928
128278
|
var MAX_NAME_LENGTH2 = 256;
|
|
127929
128279
|
|
|
127930
128280
|
// src/infrastructure/services/workspace/workspace-resolver.ts
|
|
127931
|
-
import { readFile as
|
|
128281
|
+
import { readFile as readFile13, readdir as readdir5, stat as stat10 } from "node:fs/promises";
|
|
127932
128282
|
import { join as join28, basename as basename7, resolve as resolve10 } from "node:path";
|
|
127933
128283
|
async function resolveGlobPatternStar(rootDir, cleaned) {
|
|
127934
128284
|
const parentDir2 = join28(rootDir, cleaned.slice(0, -2));
|
|
@@ -127969,7 +128319,7 @@ async function resolveGlobPattern(rootDir, pattern) {
|
|
|
127969
128319
|
}
|
|
127970
128320
|
async function readPackageJson(dir) {
|
|
127971
128321
|
try {
|
|
127972
|
-
const content = await
|
|
128322
|
+
const content = await readFile13(join28(dir, "package.json"), "utf-8");
|
|
127973
128323
|
const pkg = JSON.parse(content);
|
|
127974
128324
|
return {
|
|
127975
128325
|
name: pkg.name || basename7(dir),
|
|
@@ -127981,7 +128331,7 @@ async function readPackageJson(dir) {
|
|
|
127981
128331
|
}
|
|
127982
128332
|
async function readPnpmWorkspacePatterns(rootDir) {
|
|
127983
128333
|
try {
|
|
127984
|
-
const content = await
|
|
128334
|
+
const content = await readFile13(join28(rootDir, "pnpm-workspace.yaml"), "utf-8");
|
|
127985
128335
|
const patterns = [];
|
|
127986
128336
|
let inPackages = false;
|
|
127987
128337
|
for (const line of content.split(`
|
|
@@ -128008,7 +128358,7 @@ async function readPnpmWorkspacePatterns(rootDir) {
|
|
|
128008
128358
|
}
|
|
128009
128359
|
async function readPackageJsonWorkspacePatterns(rootDir) {
|
|
128010
128360
|
try {
|
|
128011
|
-
const content = await
|
|
128361
|
+
const content = await readFile13(join28(rootDir, "package.json"), "utf-8");
|
|
128012
128362
|
const pkg = JSON.parse(content);
|
|
128013
128363
|
if (!pkg.workspaces)
|
|
128014
128364
|
return [];
|
|
@@ -128056,7 +128406,7 @@ async function resolveSubWorkspaces(rootDir, pm) {
|
|
|
128056
128406
|
var init_workspace_resolver = () => {};
|
|
128057
128407
|
|
|
128058
128408
|
// src/infrastructure/services/workspace/command-discovery.ts
|
|
128059
|
-
import { access as access2, readFile as
|
|
128409
|
+
import { access as access2, readFile as readFile14 } from "node:fs/promises";
|
|
128060
128410
|
import { join as join29, relative as relative3, basename as basename8 } from "node:path";
|
|
128061
128411
|
async function detectPackageManager(workingDir) {
|
|
128062
128412
|
for (const { file: file2, manager } of LOCKFILE_MAP) {
|
|
@@ -128105,7 +128455,7 @@ function isValidScriptEntry(name, script) {
|
|
|
128105
128455
|
}
|
|
128106
128456
|
async function readJsonFile(filePath, label) {
|
|
128107
128457
|
try {
|
|
128108
|
-
const content = await
|
|
128458
|
+
const content = await readFile14(filePath, "utf-8");
|
|
128109
128459
|
return parseJsonc2(content);
|
|
128110
128460
|
} catch (error51) {
|
|
128111
128461
|
if (error51.code !== "ENOENT") {
|
|
@@ -128182,7 +128532,7 @@ async function readMakefile(workingDir) {
|
|
|
128182
128532
|
const commands = [];
|
|
128183
128533
|
for (const fileName of ["Makefile", "makefile"]) {
|
|
128184
128534
|
try {
|
|
128185
|
-
const content = await
|
|
128535
|
+
const content = await readFile14(join29(workingDir, fileName), "utf-8");
|
|
128186
128536
|
const targets = parseMakefileTargets(content);
|
|
128187
128537
|
if (targets.length === 0)
|
|
128188
128538
|
continue;
|
|
@@ -134302,7 +134652,7 @@ function gunzipBase64Payload(base643, maxBytes) {
|
|
|
134302
134652
|
var init_workspace_path_security = () => {};
|
|
134303
134653
|
|
|
134304
134654
|
// src/daemon/entry/files/file-content-fulfillment.ts
|
|
134305
|
-
import { readFile as
|
|
134655
|
+
import { readFile as readFile15 } from "node:fs/promises";
|
|
134306
134656
|
import { gzipSync as gzipSync3 } from "node:zlib";
|
|
134307
134657
|
function getErrorCause(error51) {
|
|
134308
134658
|
if (typeof error51 === "object" && error51 !== null && "cause" in error51 && error51.cause !== undefined) {
|
|
@@ -134377,7 +134727,7 @@ var init_file_content_fulfillment = __esm(() => {
|
|
|
134377
134727
|
continue;
|
|
134378
134728
|
}
|
|
134379
134729
|
let fileNotFound = false;
|
|
134380
|
-
const buffer = yield* exports_Effect.catch(exports_Effect.tryPromise(() =>
|
|
134730
|
+
const buffer = yield* exports_Effect.catch(exports_Effect.tryPromise(() => readFile15(absolutePath)), (error51) => {
|
|
134381
134731
|
if (isENOENT(error51)) {
|
|
134382
134732
|
fileNotFound = true;
|
|
134383
134733
|
return exports_Effect.succeed(Buffer.alloc(0));
|
|
@@ -146086,9 +146436,10 @@ var {
|
|
|
146086
146436
|
} = import__.default;
|
|
146087
146437
|
|
|
146088
146438
|
// src/commands/generate/index.ts
|
|
146089
|
-
import { readFile, stat as stat2 } from "node:fs/promises";
|
|
146439
|
+
import { readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
146090
146440
|
init_image();
|
|
146091
146441
|
init_local_image_common();
|
|
146442
|
+
init_model_capabilities();
|
|
146092
146443
|
var OUTPUT_FORMATS = ["png", "webp"];
|
|
146093
146444
|
function parsePlatform(value) {
|
|
146094
146445
|
if (!GENERATE_IMAGE_PLATFORMS.includes(value)) {
|
|
@@ -146114,7 +146465,7 @@ async function readInputOption(value) {
|
|
|
146114
146465
|
if (stats.size > MAX_SIDECAR_BYTES) {
|
|
146115
146466
|
throw new Error(`--input file is ${stats.size} bytes; the maximum is ${MAX_SIDECAR_BYTES} bytes (1 MiB).`);
|
|
146116
146467
|
}
|
|
146117
|
-
return parseInputJson(await
|
|
146468
|
+
return parseInputJson(await readFile2(path, "utf8"));
|
|
146118
146469
|
}
|
|
146119
146470
|
function parseSeed(value) {
|
|
146120
146471
|
const seed = Number(value);
|
|
@@ -146125,38 +146476,49 @@ function parseSeed(value) {
|
|
|
146125
146476
|
}
|
|
146126
146477
|
var HELP_AFTER = `
|
|
146127
146478
|
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
|
|
146479
|
+
chatroom generate-image --platform=fal --model ${FLUX_2_FLASH_MODEL} --prompt "a hero" --out hero.png
|
|
146480
|
+
chatroom generate-image --platform=fal --model ${FLUX_2_FLASH_MODEL} --prompt 'pixel-art potion, transparent-friendly flat background' --output-format png --out potion.png
|
|
146481
|
+
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
146482
|
`;
|
|
146131
|
-
function
|
|
146132
|
-
|
|
146133
|
-
|
|
146134
|
-
|
|
146135
|
-
|
|
146136
|
-
|
|
146137
|
-
|
|
146138
|
-
|
|
146139
|
-
|
|
146140
|
-
|
|
146141
|
-
|
|
146142
|
-
|
|
146143
|
-
|
|
146144
|
-
|
|
146145
|
-
|
|
146146
|
-
|
|
146147
|
-
|
|
146148
|
-
|
|
146149
|
-
|
|
146150
|
-
console.
|
|
146151
|
-
|
|
146483
|
+
async function runGenerateImage(options) {
|
|
146484
|
+
try {
|
|
146485
|
+
const input = options.input === undefined ? undefined : await readInputOption(options.input);
|
|
146486
|
+
const { createDefaultGenerateImageDeps: createDefaultGenerateImageDeps2 } = await Promise.resolve().then(() => (init_deps(), exports_deps));
|
|
146487
|
+
const deps = createDefaultGenerateImageDeps2();
|
|
146488
|
+
const generateOptions = {
|
|
146489
|
+
prompt: options.prompt,
|
|
146490
|
+
out: options.out,
|
|
146491
|
+
platform: options.platform,
|
|
146492
|
+
model: options.model,
|
|
146493
|
+
aspectRatio: options.aspectRatio,
|
|
146494
|
+
outputFormat: options.outputFormat,
|
|
146495
|
+
seed: options.seed,
|
|
146496
|
+
input,
|
|
146497
|
+
inputImage: options.inputImage,
|
|
146498
|
+
force: options.force
|
|
146499
|
+
};
|
|
146500
|
+
if (options.dryRun === true) {
|
|
146501
|
+
const { formatGenerateImagePlan: formatGenerateImagePlan2, planGenerateImage: planGenerateImage2 } = await Promise.resolve().then(() => (init_image(), exports_image));
|
|
146502
|
+
console.log(formatGenerateImagePlan2(await planGenerateImage2(generateOptions, deps)));
|
|
146503
|
+
return;
|
|
146152
146504
|
}
|
|
146153
|
-
|
|
146505
|
+
const result = await generateImage(generateOptions, deps);
|
|
146506
|
+
console.log(`Generated ${result.outPath}`);
|
|
146507
|
+
console.log(`Manifest ${result.manifestPath}`);
|
|
146508
|
+
} catch (err) {
|
|
146509
|
+
console.error(`❌ ${err instanceof Error ? err.message : String(err)}`);
|
|
146510
|
+
process.exitCode = 1;
|
|
146511
|
+
}
|
|
146512
|
+
}
|
|
146513
|
+
function registerGenerateImageCommand(program2) {
|
|
146514
|
+
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
146515
|
}
|
|
146155
146516
|
|
|
146156
146517
|
// src/commands/generate/remove-background.ts
|
|
146157
146518
|
import { basename, dirname as dirname3, extname as extname2, join as join2, resolve as resolve2 } from "node:path";
|
|
146158
146519
|
|
|
146159
146520
|
// src/commands/generate/background-key.ts
|
|
146521
|
+
init_local_image_common();
|
|
146160
146522
|
var DEFAULT_TOLERANCE = 24;
|
|
146161
146523
|
var DEFAULT_MIN_AREA_PERCENT = 0.5;
|
|
146162
146524
|
var BORDER_NOISE_REFUSAL_RATIO = 0.02;
|
|
@@ -146174,13 +146536,6 @@ function borderCoordinates(width, height) {
|
|
|
146174
146536
|
}
|
|
146175
146537
|
return coordinates;
|
|
146176
146538
|
}
|
|
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
146539
|
function forEachNeighbour(index, width, height, visit) {
|
|
146185
146540
|
const x = index % width;
|
|
146186
146541
|
const y = Math.floor(index / width);
|
|
@@ -147313,14 +147668,7 @@ function mostFrequentBorderColor(image) {
|
|
|
147313
147668
|
}
|
|
147314
147669
|
if (red.length === 0)
|
|
147315
147670
|
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)];
|
|
147671
|
+
return [median(red), median(green), median(blue)];
|
|
147324
147672
|
}
|
|
147325
147673
|
function countCellEdgeForeground(image, cell, background) {
|
|
147326
147674
|
const { width, height, data } = image;
|
|
@@ -147408,7 +147756,7 @@ async function sliceImage(opts, deps) {
|
|
|
147408
147756
|
throw new SliceImageError(`--in is ${bytes.byteLength} bytes; the maximum is ${MAX_SOURCE_BYTES} bytes (64 MiB).`);
|
|
147409
147757
|
}
|
|
147410
147758
|
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'
|
|
147759
|
+
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
147760
|
}
|
|
147413
147761
|
const dimensions = readPngDimensions(bytes);
|
|
147414
147762
|
if (dimensions === null) {
|
|
@@ -148369,4 +148717,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
148369
148717
|
});
|
|
148370
148718
|
program2.parse();
|
|
148371
148719
|
|
|
148372
|
-
//# debugId=
|
|
148720
|
+
//# debugId=E840D0ACF405C0D664756E2164756E21
|