chatroom-cli 1.137.0 → 1.137.2
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 +516 -116
- package/dist/index.js.map +26 -25
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2140,6 +2140,240 @@ var require_commander = __commonJS((exports) => {
|
|
|
2140
2140
|
exports.InvalidOptionArgumentError = InvalidArgumentError;
|
|
2141
2141
|
});
|
|
2142
2142
|
|
|
2143
|
+
// src/commands/generate/local-image-common.ts
|
|
2144
|
+
import { createHash } from "node:crypto";
|
|
2145
|
+
function isRecord(value) {
|
|
2146
|
+
return typeof value === "object" && value !== null;
|
|
2147
|
+
}
|
|
2148
|
+
function sha256(bytes) {
|
|
2149
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
2150
|
+
}
|
|
2151
|
+
function errorMessage(err) {
|
|
2152
|
+
return err instanceof Error ? err.message : String(err);
|
|
2153
|
+
}
|
|
2154
|
+
function median(values) {
|
|
2155
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
2156
|
+
const middle = Math.floor(sorted.length / 2);
|
|
2157
|
+
if (sorted.length % 2 === 1)
|
|
2158
|
+
return sorted[middle] ?? 0;
|
|
2159
|
+
return Math.round(((sorted[middle - 1] ?? 0) + (sorted[middle] ?? 0)) / 2);
|
|
2160
|
+
}
|
|
2161
|
+
function hasPngMagic(bytes) {
|
|
2162
|
+
if (bytes.length < PNG_MAGIC.length)
|
|
2163
|
+
return false;
|
|
2164
|
+
return PNG_MAGIC.every((byte, index) => bytes[index] === byte);
|
|
2165
|
+
}
|
|
2166
|
+
function readPngDimensions(bytes) {
|
|
2167
|
+
if (bytes.length < 24)
|
|
2168
|
+
return null;
|
|
2169
|
+
if (bytes[12] !== 73 || bytes[13] !== 72 || bytes[14] !== 68 || bytes[15] !== 82) {
|
|
2170
|
+
return null;
|
|
2171
|
+
}
|
|
2172
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
2173
|
+
return { width: view.getUint32(16), height: view.getUint32(20) };
|
|
2174
|
+
}
|
|
2175
|
+
function formatConflicts(conflicts) {
|
|
2176
|
+
const shown = conflicts.slice(0, 10);
|
|
2177
|
+
const lines = shown.map((conflict) => `- ${conflict.path}: ${conflict.reason}`);
|
|
2178
|
+
if (conflicts.length > shown.length) {
|
|
2179
|
+
lines.push(`- and ${conflicts.length - shown.length} more`);
|
|
2180
|
+
}
|
|
2181
|
+
return `Refusing to write ${conflicts.length} output file(s):
|
|
2182
|
+
${lines.join(`
|
|
2183
|
+
`)}
|
|
2184
|
+
Pass --force to overwrite.`;
|
|
2185
|
+
}
|
|
2186
|
+
async function findOutputConflicts(deps, paths, sourcePaths, sourceIdentities, force) {
|
|
2187
|
+
const matchesSourceIdentity = (identity) => sourceIdentities.some((source) => source !== null && source.dev === identity.dev && source.ino === identity.ino);
|
|
2188
|
+
const conflicts = [];
|
|
2189
|
+
for (const path of paths) {
|
|
2190
|
+
if (sourcePaths.has(path)) {
|
|
2191
|
+
conflicts.push({ path, reason: "this is the source sheet or its manifest" });
|
|
2192
|
+
continue;
|
|
2193
|
+
}
|
|
2194
|
+
if (await deps.isSymlink(path)) {
|
|
2195
|
+
conflicts.push({ path, reason: "this is a symbolic link" });
|
|
2196
|
+
continue;
|
|
2197
|
+
}
|
|
2198
|
+
const status = await deps.pathStatus(path);
|
|
2199
|
+
if (status === "directory") {
|
|
2200
|
+
conflicts.push({ path, reason: "a directory is in the way" });
|
|
2201
|
+
continue;
|
|
2202
|
+
}
|
|
2203
|
+
const identity = await deps.fileIdentity(path);
|
|
2204
|
+
if (identity !== null && matchesSourceIdentity(identity)) {
|
|
2205
|
+
conflicts.push({ path, reason: "this resolves to the source sheet or its manifest" });
|
|
2206
|
+
continue;
|
|
2207
|
+
}
|
|
2208
|
+
if (status === "file" && !force) {
|
|
2209
|
+
conflicts.push({ path, reason: "already exists" });
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
return conflicts;
|
|
2213
|
+
}
|
|
2214
|
+
var SAFE_NAME, MAX_SOURCE_PIXELS = 1e8, MAX_SOURCE_BYTES, MAX_SIDECAR_BYTES, PNG_MAGIC;
|
|
2215
|
+
var init_local_image_common = __esm(() => {
|
|
2216
|
+
SAFE_NAME = /^[A-Za-z0-9_][A-Za-z0-9_-]{0,63}$/;
|
|
2217
|
+
MAX_SOURCE_BYTES = 64 * 1024 * 1024;
|
|
2218
|
+
MAX_SIDECAR_BYTES = 1024 * 1024;
|
|
2219
|
+
PNG_MAGIC = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
2220
|
+
});
|
|
2221
|
+
|
|
2222
|
+
// src/commands/generate/fal-pricing.ts
|
|
2223
|
+
function listUnitPrices(endpointIds) {
|
|
2224
|
+
const prices = new Map;
|
|
2225
|
+
for (const endpointId of endpointIds) {
|
|
2226
|
+
const fallback = LIST_PRICES[endpointId];
|
|
2227
|
+
if (fallback !== undefined) {
|
|
2228
|
+
prices.set(endpointId, { endpointId, ...fallback, rateSource: "list" });
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
return prices;
|
|
2232
|
+
}
|
|
2233
|
+
function parseLivePrice(entry) {
|
|
2234
|
+
if (!isRecord(entry))
|
|
2235
|
+
return;
|
|
2236
|
+
const { endpoint_id, unit_price, unit, currency } = entry;
|
|
2237
|
+
if (typeof endpoint_id !== "string" || endpoint_id.length === 0)
|
|
2238
|
+
return;
|
|
2239
|
+
if (typeof unit_price !== "number" || !Number.isFinite(unit_price) || unit_price < 0) {
|
|
2240
|
+
return;
|
|
2241
|
+
}
|
|
2242
|
+
if (typeof unit !== "string" || unit.length === 0)
|
|
2243
|
+
return;
|
|
2244
|
+
if (currency !== undefined && currency !== "USD")
|
|
2245
|
+
return;
|
|
2246
|
+
return { endpointId: endpoint_id, unitPrice: unit_price, unit, rateSource: "live" };
|
|
2247
|
+
}
|
|
2248
|
+
function livePricesByEndpoint(body) {
|
|
2249
|
+
const prices = new Map;
|
|
2250
|
+
if (!isRecord(body) || !Array.isArray(body.prices))
|
|
2251
|
+
return prices;
|
|
2252
|
+
for (const entry of body.prices) {
|
|
2253
|
+
const price = parseLivePrice(entry);
|
|
2254
|
+
if (price !== undefined)
|
|
2255
|
+
prices.set(price.endpointId, price);
|
|
2256
|
+
}
|
|
2257
|
+
return prices;
|
|
2258
|
+
}
|
|
2259
|
+
async function lookupFalUnitPrices(endpointIds, deps) {
|
|
2260
|
+
const ids = [...new Set(endpointIds)];
|
|
2261
|
+
const key = deps.env.FAL_API_KEY?.trim();
|
|
2262
|
+
if (!key || ids.length === 0)
|
|
2263
|
+
return listUnitPrices(ids);
|
|
2264
|
+
try {
|
|
2265
|
+
const url = new URL(PRICING_URL);
|
|
2266
|
+
for (const endpointId of ids)
|
|
2267
|
+
url.searchParams.append("endpoint_id", endpointId);
|
|
2268
|
+
const body = await deps.getJson(url.toString(), { Authorization: `Key ${key}` }, PRICING_TIMEOUT_MS);
|
|
2269
|
+
const live = livePricesByEndpoint(body);
|
|
2270
|
+
const list = listUnitPrices(ids);
|
|
2271
|
+
const prices = new Map;
|
|
2272
|
+
for (const endpointId of ids) {
|
|
2273
|
+
const price = live.get(endpointId) ?? list.get(endpointId);
|
|
2274
|
+
if (price !== undefined)
|
|
2275
|
+
prices.set(endpointId, price);
|
|
2276
|
+
}
|
|
2277
|
+
return prices;
|
|
2278
|
+
} catch {
|
|
2279
|
+
return listUnitPrices(ids);
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
async function lookupUnitPrice(endpointId, lookupPrices) {
|
|
2283
|
+
try {
|
|
2284
|
+
const price = (await lookupPrices([endpointId])).get(endpointId);
|
|
2285
|
+
if (price !== undefined)
|
|
2286
|
+
return price;
|
|
2287
|
+
} catch {}
|
|
2288
|
+
return listUnitPrices([endpointId]).get(endpointId);
|
|
2289
|
+
}
|
|
2290
|
+
function estimateUsd(price, quantity) {
|
|
2291
|
+
return quantity * price.unitPrice;
|
|
2292
|
+
}
|
|
2293
|
+
function formatUsd(value) {
|
|
2294
|
+
if (!Number.isFinite(value))
|
|
2295
|
+
return "unknown";
|
|
2296
|
+
if (value === 0)
|
|
2297
|
+
return "$0";
|
|
2298
|
+
const magnitude = Math.floor(Math.log10(Math.abs(value)));
|
|
2299
|
+
const decimals = Math.min(12, Math.max(4, 3 - magnitude));
|
|
2300
|
+
return `$${value.toFixed(decimals)}`;
|
|
2301
|
+
}
|
|
2302
|
+
function imageSizeMegapixels(imageSize) {
|
|
2303
|
+
if (typeof imageSize === "string") {
|
|
2304
|
+
const preset = IMAGE_SIZE_PRESETS[imageSize];
|
|
2305
|
+
if (preset === undefined)
|
|
2306
|
+
return;
|
|
2307
|
+
return preset.width * preset.height / 1e6;
|
|
2308
|
+
}
|
|
2309
|
+
if (!Number.isFinite(imageSize.width) || !Number.isFinite(imageSize.height) || imageSize.width <= 0 || imageSize.height <= 0) {
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
2312
|
+
return imageSize.width * imageSize.height / 1e6;
|
|
2313
|
+
}
|
|
2314
|
+
function displayUnit(unit) {
|
|
2315
|
+
return DISPLAY_UNITS[unit] ?? unit;
|
|
2316
|
+
}
|
|
2317
|
+
function formatRate(price) {
|
|
2318
|
+
return `${formatUsd(price.unitPrice)} per ${displayUnit(price.unit)}`;
|
|
2319
|
+
}
|
|
2320
|
+
function formatRateSource(price) {
|
|
2321
|
+
return price.rateSource === "live" ? "live rate for this account" : `list price as of ${LIST_PRICES_AS_OF}; live lookup unavailable`;
|
|
2322
|
+
}
|
|
2323
|
+
function formatCostEstimate(price, quantity, quantityLabel) {
|
|
2324
|
+
return `${formatRate(price)} (${formatRateSource(price)}) × ${quantityLabel} ≈ ${formatUsd(estimateUsd(price, quantity))}`;
|
|
2325
|
+
}
|
|
2326
|
+
function formatCostDisclosure(disclosure, label = "Cost") {
|
|
2327
|
+
if (disclosure.kind === "unknown") {
|
|
2328
|
+
return `${label}: unknown for ${disclosure.model}; no rate data.`;
|
|
2329
|
+
}
|
|
2330
|
+
if (disclosure.kind === "rate-only") {
|
|
2331
|
+
return `${label} (rate only): ${formatRate(disclosure.price)} (${formatRateSource(disclosure.price)}). ${disclosure.reason}`;
|
|
2332
|
+
}
|
|
2333
|
+
const estimateLabel = label === "Estimated cost" ? label : `${label} (estimate)`;
|
|
2334
|
+
return `${estimateLabel}: ${formatCostEstimate(disclosure.price, disclosure.quantity, disclosure.quantityLabel)}`;
|
|
2335
|
+
}
|
|
2336
|
+
function createDefaultFalPricingDeps(env = process.env) {
|
|
2337
|
+
return {
|
|
2338
|
+
env,
|
|
2339
|
+
getJson: async (url, headers, timeoutMs) => {
|
|
2340
|
+
const response = await fetch(url, {
|
|
2341
|
+
headers: { Accept: "application/json", ...headers },
|
|
2342
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
2343
|
+
redirect: "error"
|
|
2344
|
+
});
|
|
2345
|
+
if (!response.ok) {
|
|
2346
|
+
throw new Error(`fal pricing lookup failed (HTTP ${response.status})`);
|
|
2347
|
+
}
|
|
2348
|
+
return await response.json();
|
|
2349
|
+
}
|
|
2350
|
+
};
|
|
2351
|
+
}
|
|
2352
|
+
var LIST_PRICES_AS_OF = "2026-10-25", LIST_PRICES, IMAGE_SIZE_PRESETS, PRICING_TIMEOUT_MS = 3000, PRICING_URL = "https://api.fal.ai/v1/models/pricing", DISPLAY_UNITS;
|
|
2353
|
+
var init_fal_pricing = __esm(() => {
|
|
2354
|
+
init_local_image_common();
|
|
2355
|
+
LIST_PRICES = {
|
|
2356
|
+
"fal-ai/flux-2/flash": { unitPrice: 0.005, unit: "megapixels" },
|
|
2357
|
+
"fal-ai/flux-2/flash/edit": { unitPrice: 0.005, unit: "processed megapixels" },
|
|
2358
|
+
"fal-ai/bria/background/remove": { unitPrice: 0.018, unit: "generations" },
|
|
2359
|
+
"fal-ai/birefnet/v2": { unitPrice: 0.000575, unit: "compute seconds" }
|
|
2360
|
+
};
|
|
2361
|
+
IMAGE_SIZE_PRESETS = {
|
|
2362
|
+
square_hd: { width: 1024, height: 1024 },
|
|
2363
|
+
square: { width: 512, height: 512 },
|
|
2364
|
+
portrait_4_3: { width: 768, height: 1024 },
|
|
2365
|
+
portrait_16_9: { width: 576, height: 1024 },
|
|
2366
|
+
landscape_4_3: { width: 1024, height: 768 },
|
|
2367
|
+
landscape_16_9: { width: 1024, height: 576 }
|
|
2368
|
+
};
|
|
2369
|
+
DISPLAY_UNITS = {
|
|
2370
|
+
megapixels: "megapixel",
|
|
2371
|
+
"processed megapixels": "processed megapixel",
|
|
2372
|
+
generations: "generation",
|
|
2373
|
+
"compute seconds": "compute second"
|
|
2374
|
+
};
|
|
2375
|
+
});
|
|
2376
|
+
|
|
2143
2377
|
// src/commands/generate/model-capabilities.ts
|
|
2144
2378
|
function getModelCapability(id) {
|
|
2145
2379
|
return Object.prototype.hasOwnProperty.call(MODEL_CAPABILITIES, id) ? MODEL_CAPABILITIES[id] : undefined;
|
|
@@ -2290,7 +2524,7 @@ __export(exports_image, {
|
|
|
2290
2524
|
FAL_API_KEY_MISSING_MESSAGE: () => FAL_API_KEY_MISSING_MESSAGE,
|
|
2291
2525
|
DEFAULT_GENERATE_IMAGE_PLATFORM: () => DEFAULT_GENERATE_IMAGE_PLATFORM
|
|
2292
2526
|
});
|
|
2293
|
-
import { createHash } from "node:crypto";
|
|
2527
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2294
2528
|
import { extname, resolve } from "node:path";
|
|
2295
2529
|
function resolvePlatform(platform) {
|
|
2296
2530
|
return platform ?? DEFAULT_GENERATE_IMAGE_PLATFORM;
|
|
@@ -2494,6 +2728,155 @@ async function uploadInputImage(deps, target) {
|
|
|
2494
2728
|
const url = await deps.uploadFile(target.path, target.contentType);
|
|
2495
2729
|
return { url, record: { source: target.path, url } };
|
|
2496
2730
|
}
|
|
2731
|
+
function formatMegapixels(megapixels) {
|
|
2732
|
+
return `${megapixels.toFixed(2)} MP`;
|
|
2733
|
+
}
|
|
2734
|
+
function readImageSizeValue(value) {
|
|
2735
|
+
if (typeof value === "string")
|
|
2736
|
+
return value;
|
|
2737
|
+
if (isPlainObject(value) && typeof value.width === "number" && typeof value.height === "number") {
|
|
2738
|
+
return { width: value.width, height: value.height };
|
|
2739
|
+
}
|
|
2740
|
+
return;
|
|
2741
|
+
}
|
|
2742
|
+
function plannedOutputSize(model, input) {
|
|
2743
|
+
const capability = getModelCapability(model);
|
|
2744
|
+
const support = capability?.params.image_size;
|
|
2745
|
+
const value = input.image_size !== undefined ? input.image_size : support?.support === "supported" ? support.default : undefined;
|
|
2746
|
+
if (value === undefined) {
|
|
2747
|
+
return {
|
|
2748
|
+
megapixels: undefined,
|
|
2749
|
+
reason: "The image_size is unset and the model has no known default; output size not estimated."
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
const size = readImageSizeValue(value);
|
|
2753
|
+
if (size === undefined) {
|
|
2754
|
+
return {
|
|
2755
|
+
megapixels: undefined,
|
|
2756
|
+
reason: `The image_size ${JSON.stringify(value)} is not a preset name or {width,height}; output size not estimated.`
|
|
2757
|
+
};
|
|
2758
|
+
}
|
|
2759
|
+
const megapixels = imageSizeMegapixels(size);
|
|
2760
|
+
if (megapixels === undefined) {
|
|
2761
|
+
return {
|
|
2762
|
+
megapixels: undefined,
|
|
2763
|
+
reason: `The image_size ${JSON.stringify(value)} is not a known preset; output size not estimated.`
|
|
2764
|
+
};
|
|
2765
|
+
}
|
|
2766
|
+
return { megapixels, reason: undefined };
|
|
2767
|
+
}
|
|
2768
|
+
async function inputSizeNote(deps, target) {
|
|
2769
|
+
if (target === undefined)
|
|
2770
|
+
return EDIT_INPUT_NOTE;
|
|
2771
|
+
if (target.kind === "url") {
|
|
2772
|
+
return "Input is a URL; fal resizes it to 1 MP, so the estimate counts 1 MP of input.";
|
|
2773
|
+
}
|
|
2774
|
+
if (target.contentType !== "image/png") {
|
|
2775
|
+
return "Input is not a PNG; fal resizes it to 1 MP, so the estimate counts 1 MP of input.";
|
|
2776
|
+
}
|
|
2777
|
+
try {
|
|
2778
|
+
const dimensions = readPngDimensions(await deps.readFileBytes(target.path));
|
|
2779
|
+
if (dimensions === null || dimensions.width === 0 || dimensions.height === 0) {
|
|
2780
|
+
return EDIT_INPUT_NOTE;
|
|
2781
|
+
}
|
|
2782
|
+
const megapixels = dimensions.width * dimensions.height / 1e6;
|
|
2783
|
+
return `Input is ${dimensions.width}x${dimensions.height} (${formatMegapixels(megapixels)}); fal resizes it to 1 MP, so the estimate counts 1 MP of input.`;
|
|
2784
|
+
} catch {
|
|
2785
|
+
return EDIT_INPUT_NOTE;
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
async function lookupPrice(deps, model) {
|
|
2789
|
+
return lookupUnitPrice(model, (ids) => deps.lookupPrices(ids));
|
|
2790
|
+
}
|
|
2791
|
+
async function buildGenerateImageCost(args) {
|
|
2792
|
+
const price = await lookupPrice(args.deps, args.model);
|
|
2793
|
+
const output = plannedOutputSize(args.model, args.input);
|
|
2794
|
+
const capability = getModelCapability(args.model);
|
|
2795
|
+
if (capability?.task === "image-edit") {
|
|
2796
|
+
const notes = [
|
|
2797
|
+
"Formula (unverified): (1 MP input + output MP) × rate.",
|
|
2798
|
+
await inputSizeNote(args.deps, args.inputTarget)
|
|
2799
|
+
];
|
|
2800
|
+
if (output.megapixels === undefined) {
|
|
2801
|
+
return {
|
|
2802
|
+
price,
|
|
2803
|
+
quantity: undefined,
|
|
2804
|
+
quantityLabel: undefined,
|
|
2805
|
+
inputMegapixels: EDIT_INPUT_MEGAPIXELS,
|
|
2806
|
+
reason: output.reason,
|
|
2807
|
+
notes
|
|
2808
|
+
};
|
|
2809
|
+
}
|
|
2810
|
+
const quantity = EDIT_INPUT_MEGAPIXELS + output.megapixels;
|
|
2811
|
+
return {
|
|
2812
|
+
price,
|
|
2813
|
+
quantity,
|
|
2814
|
+
quantityLabel: `${formatMegapixels(quantity)} (${formatMegapixels(EDIT_INPUT_MEGAPIXELS)} input + ${formatMegapixels(output.megapixels)} output)`,
|
|
2815
|
+
inputMegapixels: EDIT_INPUT_MEGAPIXELS,
|
|
2816
|
+
reason: undefined,
|
|
2817
|
+
notes
|
|
2818
|
+
};
|
|
2819
|
+
}
|
|
2820
|
+
if (output.megapixels === undefined) {
|
|
2821
|
+
return {
|
|
2822
|
+
price,
|
|
2823
|
+
quantity: undefined,
|
|
2824
|
+
quantityLabel: undefined,
|
|
2825
|
+
inputMegapixels: undefined,
|
|
2826
|
+
reason: output.reason,
|
|
2827
|
+
notes: []
|
|
2828
|
+
};
|
|
2829
|
+
}
|
|
2830
|
+
return {
|
|
2831
|
+
price,
|
|
2832
|
+
quantity: output.megapixels,
|
|
2833
|
+
quantityLabel: `${formatMegapixels(output.megapixels)} (planned output)`,
|
|
2834
|
+
inputMegapixels: undefined,
|
|
2835
|
+
reason: undefined,
|
|
2836
|
+
notes: []
|
|
2837
|
+
};
|
|
2838
|
+
}
|
|
2839
|
+
function formatGenerateImageCost(cost, model, label = "Cost") {
|
|
2840
|
+
const disclosure = cost.price === undefined ? { kind: "unknown", model } : cost.quantity === undefined ? {
|
|
2841
|
+
kind: "rate-only",
|
|
2842
|
+
model,
|
|
2843
|
+
price: cost.price,
|
|
2844
|
+
reason: cost.reason ?? "Quantity not estimated."
|
|
2845
|
+
} : {
|
|
2846
|
+
kind: "estimate",
|
|
2847
|
+
model,
|
|
2848
|
+
price: cost.price,
|
|
2849
|
+
quantity: cost.quantity,
|
|
2850
|
+
quantityLabel: cost.quantityLabel ?? formatMegapixels(cost.quantity)
|
|
2851
|
+
};
|
|
2852
|
+
const note = cost.price === undefined ? [] : [cost.quantity === undefined ? ACTUAL_COST_NOTE : PLANNED_COST_NOTE];
|
|
2853
|
+
return [formatCostDisclosure(disclosure, label), ...note, ...cost.notes];
|
|
2854
|
+
}
|
|
2855
|
+
function actualCostLines(plan, image) {
|
|
2856
|
+
const { price, inputMegapixels } = plan.cost;
|
|
2857
|
+
if (price === undefined) {
|
|
2858
|
+
return formatGenerateImageCost(plan.cost, plan.model, "Estimated cost");
|
|
2859
|
+
}
|
|
2860
|
+
const width = image.width;
|
|
2861
|
+
const height = image.height;
|
|
2862
|
+
const hasDimensions = width !== undefined && height !== undefined && Number.isFinite(width) && Number.isFinite(height) && width > 0 && height > 0;
|
|
2863
|
+
if (!hasDimensions) {
|
|
2864
|
+
if (plan.cost.quantity === undefined) {
|
|
2865
|
+
return formatGenerateImageCost(plan.cost, plan.model, "Estimated cost");
|
|
2866
|
+
}
|
|
2867
|
+
return [
|
|
2868
|
+
`Estimated cost (planned size): ${formatCostEstimate(price, plan.cost.quantity, plan.cost.quantityLabel ?? formatMegapixels(plan.cost.quantity))}`,
|
|
2869
|
+
ACTUAL_COST_NOTE
|
|
2870
|
+
];
|
|
2871
|
+
}
|
|
2872
|
+
const outputMegapixels = width * height / 1e6;
|
|
2873
|
+
const quantity = inputMegapixels === undefined ? outputMegapixels : inputMegapixels + outputMegapixels;
|
|
2874
|
+
const quantityLabel = inputMegapixels === undefined ? `${formatMegapixels(outputMegapixels)} (actual ${width}x${height})` : `${formatMegapixels(quantity)} (actual ${width}x${height}; ${formatMegapixels(inputMegapixels)} input + ${formatMegapixels(outputMegapixels)} output)`;
|
|
2875
|
+
return [
|
|
2876
|
+
`Estimated cost: ${formatCostEstimate(price, quantity, quantityLabel)}`,
|
|
2877
|
+
ACTUAL_COST_NOTE
|
|
2878
|
+
];
|
|
2879
|
+
}
|
|
2497
2880
|
async function planGenerateImage(opts, deps) {
|
|
2498
2881
|
const platform = resolvePlatform(opts.platform);
|
|
2499
2882
|
requireSupportedPlatform(platform);
|
|
@@ -2511,6 +2894,7 @@ async function planGenerateImage(opts, deps) {
|
|
|
2511
2894
|
const manifestPath = `${outPath}.json`;
|
|
2512
2895
|
await requireWritableOutput(deps, outPath, manifestPath, opts.force === true);
|
|
2513
2896
|
const input = buildInput(prompt, opts, planInputImageUrl(inputTarget));
|
|
2897
|
+
const cost = await buildGenerateImageCost({ model, input, inputTarget, deps });
|
|
2514
2898
|
return {
|
|
2515
2899
|
platform,
|
|
2516
2900
|
model,
|
|
@@ -2518,7 +2902,8 @@ async function planGenerateImage(opts, deps) {
|
|
|
2518
2902
|
manifestPath,
|
|
2519
2903
|
input,
|
|
2520
2904
|
inputTarget,
|
|
2521
|
-
disclosures: describeConstraints(model, input, { aspectRatio: opts.aspectRatio })
|
|
2905
|
+
disclosures: describeConstraints(model, input, { aspectRatio: opts.aspectRatio }),
|
|
2906
|
+
cost
|
|
2522
2907
|
};
|
|
2523
2908
|
}
|
|
2524
2909
|
function disclosureFlag(key) {
|
|
@@ -2548,7 +2933,8 @@ function formatGenerateImagePlan(plan) {
|
|
|
2548
2933
|
lines.push("Request:", JSON.stringify(plan.input, null, 2), "Constraints:");
|
|
2549
2934
|
for (const disclosure of plan.disclosures)
|
|
2550
2935
|
lines.push(formatDisclosure(disclosure));
|
|
2551
|
-
lines.push(
|
|
2936
|
+
lines.push(...formatGenerateImageCost(plan.cost, plan.model));
|
|
2937
|
+
lines.push("No upload or fal generation call was made.");
|
|
2552
2938
|
return lines.join(`
|
|
2553
2939
|
`);
|
|
2554
2940
|
}
|
|
@@ -2563,7 +2949,7 @@ async function generateAndSave(deps, model, input, outPath) {
|
|
|
2563
2949
|
return {
|
|
2564
2950
|
image,
|
|
2565
2951
|
bytes,
|
|
2566
|
-
sha256:
|
|
2952
|
+
sha256: createHash2("sha256").update(bytes).digest("hex"),
|
|
2567
2953
|
requestId: result.requestId,
|
|
2568
2954
|
seed: result.data.seed
|
|
2569
2955
|
};
|
|
@@ -2576,10 +2962,14 @@ async function generateImage(opts, deps) {
|
|
|
2576
2962
|
deps.warn(`⚠️ ${disclosure.key}: ${disclosure.message}`);
|
|
2577
2963
|
}
|
|
2578
2964
|
}
|
|
2965
|
+
for (const line of formatGenerateImageCost(plan.cost, plan.model)) {
|
|
2966
|
+
deps.log(line);
|
|
2967
|
+
}
|
|
2579
2968
|
await deps.ensureParentDir(plan.outPath);
|
|
2580
2969
|
const preparedInput = await uploadInputImage(deps, plan.inputTarget);
|
|
2581
2970
|
const input = preparedInput.url === undefined ? plan.input : { ...plan.input, image_urls: [preparedInput.url] };
|
|
2582
2971
|
const saved = await generateAndSave(deps, plan.model, input, plan.outPath);
|
|
2972
|
+
const costLines = actualCostLines(plan, saved.image);
|
|
2583
2973
|
await deps.writeJson(plan.manifestPath, buildManifest({
|
|
2584
2974
|
platform: plan.platform,
|
|
2585
2975
|
model: plan.model,
|
|
@@ -2595,10 +2985,17 @@ async function generateImage(opts, deps) {
|
|
|
2595
2985
|
generatedAt: deps.now().toISOString(),
|
|
2596
2986
|
sdkVersion: deps.sdkVersion
|
|
2597
2987
|
}));
|
|
2598
|
-
return {
|
|
2988
|
+
return {
|
|
2989
|
+
outPath: plan.outPath,
|
|
2990
|
+
manifestPath: plan.manifestPath,
|
|
2991
|
+
sha256: saved.sha256,
|
|
2992
|
+
costLines
|
|
2993
|
+
};
|
|
2599
2994
|
}
|
|
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;
|
|
2995
|
+
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, PLANNED_COST_NOTE = "fal returns no billed cost; this is computed from the planned image size.", ACTUAL_COST_NOTE = "fal returns no billed cost; check the fal dashboard for the actual bill.", EDIT_INPUT_MEGAPIXELS = 1, EDIT_INPUT_NOTE = "fal resizes the input image to 1 MP; the estimate counts 1 MP of input regardless of its dimensions.";
|
|
2601
2996
|
var init_image = __esm(() => {
|
|
2997
|
+
init_fal_pricing();
|
|
2998
|
+
init_local_image_common();
|
|
2602
2999
|
init_model_capabilities();
|
|
2603
3000
|
GENERATE_IMAGE_PLATFORMS = ["fal"];
|
|
2604
3001
|
GenerateImageError = class GenerateImageError extends Error {
|
|
@@ -2629,85 +3026,6 @@ var init_image = __esm(() => {
|
|
|
2629
3026
|
HTTP_URL_PATTERN = /^https?:\/\//i;
|
|
2630
3027
|
});
|
|
2631
3028
|
|
|
2632
|
-
// src/commands/generate/local-image-common.ts
|
|
2633
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
2634
|
-
function isRecord(value) {
|
|
2635
|
-
return typeof value === "object" && value !== null;
|
|
2636
|
-
}
|
|
2637
|
-
function sha256(bytes) {
|
|
2638
|
-
return createHash2("sha256").update(bytes).digest("hex");
|
|
2639
|
-
}
|
|
2640
|
-
function errorMessage(err) {
|
|
2641
|
-
return err instanceof Error ? err.message : String(err);
|
|
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
|
-
}
|
|
2650
|
-
function hasPngMagic(bytes) {
|
|
2651
|
-
if (bytes.length < PNG_MAGIC.length)
|
|
2652
|
-
return false;
|
|
2653
|
-
return PNG_MAGIC.every((byte, index) => bytes[index] === byte);
|
|
2654
|
-
}
|
|
2655
|
-
function readPngDimensions(bytes) {
|
|
2656
|
-
if (bytes.length < 24)
|
|
2657
|
-
return null;
|
|
2658
|
-
if (bytes[12] !== 73 || bytes[13] !== 72 || bytes[14] !== 68 || bytes[15] !== 82) {
|
|
2659
|
-
return null;
|
|
2660
|
-
}
|
|
2661
|
-
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
2662
|
-
return { width: view.getUint32(16), height: view.getUint32(20) };
|
|
2663
|
-
}
|
|
2664
|
-
function formatConflicts(conflicts) {
|
|
2665
|
-
const shown = conflicts.slice(0, 10);
|
|
2666
|
-
const lines = shown.map((conflict) => `- ${conflict.path}: ${conflict.reason}`);
|
|
2667
|
-
if (conflicts.length > shown.length) {
|
|
2668
|
-
lines.push(`- and ${conflicts.length - shown.length} more`);
|
|
2669
|
-
}
|
|
2670
|
-
return `Refusing to write ${conflicts.length} output file(s):
|
|
2671
|
-
${lines.join(`
|
|
2672
|
-
`)}
|
|
2673
|
-
Pass --force to overwrite.`;
|
|
2674
|
-
}
|
|
2675
|
-
async function findOutputConflicts(deps, paths, sourcePaths, sourceIdentities, force) {
|
|
2676
|
-
const matchesSourceIdentity = (identity) => sourceIdentities.some((source) => source !== null && source.dev === identity.dev && source.ino === identity.ino);
|
|
2677
|
-
const conflicts = [];
|
|
2678
|
-
for (const path of paths) {
|
|
2679
|
-
if (sourcePaths.has(path)) {
|
|
2680
|
-
conflicts.push({ path, reason: "this is the source sheet or its manifest" });
|
|
2681
|
-
continue;
|
|
2682
|
-
}
|
|
2683
|
-
if (await deps.isSymlink(path)) {
|
|
2684
|
-
conflicts.push({ path, reason: "this is a symbolic link" });
|
|
2685
|
-
continue;
|
|
2686
|
-
}
|
|
2687
|
-
const status = await deps.pathStatus(path);
|
|
2688
|
-
if (status === "directory") {
|
|
2689
|
-
conflicts.push({ path, reason: "a directory is in the way" });
|
|
2690
|
-
continue;
|
|
2691
|
-
}
|
|
2692
|
-
const identity = await deps.fileIdentity(path);
|
|
2693
|
-
if (identity !== null && matchesSourceIdentity(identity)) {
|
|
2694
|
-
conflicts.push({ path, reason: "this resolves to the source sheet or its manifest" });
|
|
2695
|
-
continue;
|
|
2696
|
-
}
|
|
2697
|
-
if (status === "file" && !force) {
|
|
2698
|
-
conflicts.push({ path, reason: "already exists" });
|
|
2699
|
-
}
|
|
2700
|
-
}
|
|
2701
|
-
return conflicts;
|
|
2702
|
-
}
|
|
2703
|
-
var SAFE_NAME, MAX_SOURCE_PIXELS = 1e8, MAX_SOURCE_BYTES, MAX_SIDECAR_BYTES, PNG_MAGIC;
|
|
2704
|
-
var init_local_image_common = __esm(() => {
|
|
2705
|
-
SAFE_NAME = /^[A-Za-z0-9_][A-Za-z0-9_-]{0,63}$/;
|
|
2706
|
-
MAX_SOURCE_BYTES = 64 * 1024 * 1024;
|
|
2707
|
-
MAX_SIDECAR_BYTES = 1024 * 1024;
|
|
2708
|
-
PNG_MAGIC = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
2709
|
-
});
|
|
2710
|
-
|
|
2711
3029
|
// ../../node_modules/.pnpm/@fal-ai+client@1.10.1/node_modules/@fal-ai/client/src/middleware.js
|
|
2712
3030
|
var require_middleware = __commonJS((exports) => {
|
|
2713
3031
|
var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
|
|
@@ -6788,7 +7106,6 @@ var init_package_version = () => {};
|
|
|
6788
7106
|
// src/commands/generate/deps.ts
|
|
6789
7107
|
var exports_deps = {};
|
|
6790
7108
|
__export(exports_deps, {
|
|
6791
|
-
fetchBytes: () => fetchBytes,
|
|
6792
7109
|
createFalRawSubscribe: () => createFalRawSubscribe,
|
|
6793
7110
|
createDefaultGenerateImageDeps: () => createDefaultGenerateImageDeps
|
|
6794
7111
|
});
|
|
@@ -6828,16 +7145,23 @@ function requireSeed(value) {
|
|
|
6828
7145
|
}
|
|
6829
7146
|
return value;
|
|
6830
7147
|
}
|
|
7148
|
+
function optionalDimension(value) {
|
|
7149
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
|
|
7150
|
+
}
|
|
6831
7151
|
function validateImageFile(value) {
|
|
6832
7152
|
if (!isRecord2(value)) {
|
|
6833
7153
|
throw invalidResult("image entry is not an object");
|
|
6834
7154
|
}
|
|
6835
7155
|
const content_type = optionalString(value.content_type, "image content_type");
|
|
6836
7156
|
const file_name = optionalString(value.file_name, "image file_name");
|
|
7157
|
+
const width = optionalDimension(value.width);
|
|
7158
|
+
const height = optionalDimension(value.height);
|
|
6837
7159
|
return {
|
|
6838
7160
|
url: requireImageUrl(value.url),
|
|
6839
7161
|
...content_type !== undefined ? { content_type } : {},
|
|
6840
|
-
...file_name !== undefined ? { file_name } : {}
|
|
7162
|
+
...file_name !== undefined ? { file_name } : {},
|
|
7163
|
+
...width !== undefined ? { width } : {},
|
|
7164
|
+
...height !== undefined ? { height } : {}
|
|
6841
7165
|
};
|
|
6842
7166
|
}
|
|
6843
7167
|
function requireData(value) {
|
|
@@ -6951,22 +7275,27 @@ async function fetchBytes(url) {
|
|
|
6951
7275
|
}
|
|
6952
7276
|
function createDefaultGenerateImageDeps() {
|
|
6953
7277
|
const env = process.env;
|
|
7278
|
+
const pricingDeps = createDefaultFalPricingDeps(env);
|
|
6954
7279
|
return {
|
|
6955
7280
|
env,
|
|
6956
7281
|
falSubscribe: createFalSubscribe(env),
|
|
6957
7282
|
uploadFile: createFalUploadFile(env),
|
|
6958
7283
|
fetchBytes,
|
|
7284
|
+
readFileBytes: async (path) => new Uint8Array(await readFile(path)),
|
|
7285
|
+
lookupPrices: (ids) => lookupFalUnitPrices(ids, pricingDeps),
|
|
6959
7286
|
writeFile: writeFileBytes,
|
|
6960
7287
|
writeJson: writeJsonFile,
|
|
6961
7288
|
pathStatus,
|
|
6962
7289
|
ensureParentDir,
|
|
6963
7290
|
now: () => new Date,
|
|
6964
7291
|
sdkVersion: readInstalledPackageVersion("@fal-ai/client"),
|
|
6965
|
-
warn: (message) => console.error(message)
|
|
7292
|
+
warn: (message) => console.error(message),
|
|
7293
|
+
log: (message) => console.log(message)
|
|
6966
7294
|
};
|
|
6967
7295
|
}
|
|
6968
7296
|
var import_client, SUBSCRIBE_TIMEOUT_SECONDS = 300;
|
|
6969
7297
|
var init_deps = __esm(() => {
|
|
7298
|
+
init_fal_pricing();
|
|
6970
7299
|
init_fs_helpers();
|
|
6971
7300
|
init_image();
|
|
6972
7301
|
init_package_version();
|
|
@@ -9046,6 +9375,32 @@ function recoveryMessage2(action, err, requestId, url) {
|
|
|
9046
9375
|
const detail = err instanceof Error ? err.message : String(err);
|
|
9047
9376
|
return `Failed to ${action} image for request ${requestId} (${url}): ${detail}. The result can still be recovered from that URL.`;
|
|
9048
9377
|
}
|
|
9378
|
+
async function findBackgroundPrice(deps, model) {
|
|
9379
|
+
return lookupUnitPrice(model, (ids) => deps.lookupPrices(ids));
|
|
9380
|
+
}
|
|
9381
|
+
function backgroundCostDisclosure(cost) {
|
|
9382
|
+
if (cost.price === undefined)
|
|
9383
|
+
return { kind: "unknown", model: cost.model };
|
|
9384
|
+
if (cost.engine === "birefnet") {
|
|
9385
|
+
return {
|
|
9386
|
+
kind: "rate-only",
|
|
9387
|
+
model: cost.model,
|
|
9388
|
+
price: cost.price,
|
|
9389
|
+
reason: BIREFNET_RATE_REASON
|
|
9390
|
+
};
|
|
9391
|
+
}
|
|
9392
|
+
return {
|
|
9393
|
+
kind: "estimate",
|
|
9394
|
+
model: cost.model,
|
|
9395
|
+
price: cost.price,
|
|
9396
|
+
quantity: 1,
|
|
9397
|
+
quantityLabel: "1 generation"
|
|
9398
|
+
};
|
|
9399
|
+
}
|
|
9400
|
+
function formatBackgroundCost(cost, label) {
|
|
9401
|
+
const line = formatCostDisclosure(backgroundCostDisclosure(cost), label);
|
|
9402
|
+
return cost.price === undefined ? [line] : [line, FAL_BILL_NOTE];
|
|
9403
|
+
}
|
|
9049
9404
|
async function removeBackgroundFal(options, deps) {
|
|
9050
9405
|
if (!deps.env.FAL_API_KEY) {
|
|
9051
9406
|
throw new RemoveBackgroundFalError(FAL_API_KEY_MISSING_MESSAGE);
|
|
@@ -9060,6 +9415,13 @@ async function removeBackgroundFal(options, deps) {
|
|
|
9060
9415
|
}
|
|
9061
9416
|
const source = await requireSource(inPath, deps);
|
|
9062
9417
|
await requireWritableOutputs(outPath, manifestPath, options.force === true, deps);
|
|
9418
|
+
const cost = {
|
|
9419
|
+
model: spec.model,
|
|
9420
|
+
engine,
|
|
9421
|
+
price: await findBackgroundPrice(deps, spec.model)
|
|
9422
|
+
};
|
|
9423
|
+
for (const line of formatBackgroundCost(cost, "Cost"))
|
|
9424
|
+
deps.log(line);
|
|
9063
9425
|
const dataUri = `data:image/png;base64,${Buffer.from(source.bytes).toString("base64")}`;
|
|
9064
9426
|
const raw = await deps.falSubscribe(spec.model, spec.buildInput(dataUri));
|
|
9065
9427
|
const result = validateBackgroundResult(raw, spec.model);
|
|
@@ -9121,6 +9483,8 @@ async function removeBackgroundFal(options, deps) {
|
|
|
9121
9483
|
} catch (err) {
|
|
9122
9484
|
throw new RemoveBackgroundFalError(manifestSaveMessage(err, result.requestId, outPath, manifestPath));
|
|
9123
9485
|
}
|
|
9486
|
+
for (const line of formatBackgroundCost(cost, "Estimated cost"))
|
|
9487
|
+
deps.log(line);
|
|
9124
9488
|
return {
|
|
9125
9489
|
outPath,
|
|
9126
9490
|
manifestPath,
|
|
@@ -9129,8 +9493,9 @@ async function removeBackgroundFal(options, deps) {
|
|
|
9129
9493
|
sha256: outputSha256
|
|
9130
9494
|
};
|
|
9131
9495
|
}
|
|
9132
|
-
var DEFAULT_BACKGROUND_ENGINE = "bria", BACKGROUND_ENGINES, MAX_FAL_INPUT_BYTES, MAX_FAL_RESULT_BYTES, TOOL2 = "chatroom remove-image-background-fal", LICENSING2 = "unverified", RemoveBackgroundFalError;
|
|
9496
|
+
var DEFAULT_BACKGROUND_ENGINE = "bria", BACKGROUND_ENGINES, MAX_FAL_INPUT_BYTES, MAX_FAL_RESULT_BYTES, TOOL2 = "chatroom remove-image-background-fal", LICENSING2 = "unverified", RemoveBackgroundFalError, FAL_BILL_NOTE = "fal returns no billed cost; check the fal dashboard for the actual bill.", BIREFNET_RATE_REASON = "Total depends on runtime; not estimated.";
|
|
9133
9497
|
var init_remove_background_fal = __esm(() => {
|
|
9498
|
+
init_fal_pricing();
|
|
9134
9499
|
init_image();
|
|
9135
9500
|
init_local_image_common();
|
|
9136
9501
|
BACKGROUND_ENGINES = {
|
|
@@ -9187,9 +9552,11 @@ async function fetchCappedBytes(url, maxBytes) {
|
|
|
9187
9552
|
}
|
|
9188
9553
|
function createDefaultRemoveBackgroundFalDeps() {
|
|
9189
9554
|
const slice = createDefaultSliceImageDeps();
|
|
9555
|
+
const pricingDeps = createDefaultFalPricingDeps(process.env);
|
|
9190
9556
|
return {
|
|
9191
9557
|
env: process.env,
|
|
9192
9558
|
falSubscribe: createFalRawSubscribe(process.env),
|
|
9559
|
+
lookupPrices: (ids) => lookupFalUnitPrices(ids, pricingDeps),
|
|
9193
9560
|
fetchBytes: (url) => fetchCappedBytes(url, MAX_FAL_RESULT_BYTES),
|
|
9194
9561
|
readInput: slice.readFile,
|
|
9195
9562
|
statInput: slice.statInput,
|
|
@@ -9198,11 +9565,13 @@ function createDefaultRemoveBackgroundFalDeps() {
|
|
|
9198
9565
|
writeJson: slice.writeJson,
|
|
9199
9566
|
ensureParentDir: (path) => slice.ensureDir(dirname4(path)),
|
|
9200
9567
|
now: () => new Date,
|
|
9201
|
-
sdkVersion: readInstalledPackageVersion("@fal-ai/client")
|
|
9568
|
+
sdkVersion: readInstalledPackageVersion("@fal-ai/client"),
|
|
9569
|
+
log: (message) => console.log(message)
|
|
9202
9570
|
};
|
|
9203
9571
|
}
|
|
9204
9572
|
var init_remove_background_fal_deps = __esm(() => {
|
|
9205
9573
|
init_deps();
|
|
9574
|
+
init_fal_pricing();
|
|
9206
9575
|
init_package_version();
|
|
9207
9576
|
init_remove_background_fal();
|
|
9208
9577
|
init_slice_deps();
|
|
@@ -107785,9 +108154,11 @@ function getEntryPointToArchitectHandoffTemplate(query) {
|
|
|
107785
108154
|
const entryPoint = query.fromRole;
|
|
107786
108155
|
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.
|
|
107787
108156
|
|
|
108157
|
+
Use this before any backend contract, API, or schema/persistence change.
|
|
108158
|
+
|
|
107788
108159
|
Provide the request, relevant constraints, and the files or product surfaces that need inspection. Ask for one evidence-backed design focused on ${getArchitectFocus()}.
|
|
107789
108160
|
|
|
107790
|
-
After completing the design, hand it back to \`${entryPoint}\` with the normal handoff command
|
|
108161
|
+
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.
|
|
107791
108162
|
|
|
107792
108163
|
\`\`\`bash
|
|
107793
108164
|
${handoffCommand({
|
|
@@ -108267,7 +108638,7 @@ function getEntryPointToGameAssetDesignerHandoffTemplate(query) {
|
|
|
108267
108638
|
<repository paths, URLs, or a written style sheet; or \`Not specified.\`>
|
|
108268
108639
|
|
|
108269
108640
|
## 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
|
|
108641
|
+
<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
108642
|
|
|
108272
108643
|
## Model
|
|
108273
108644
|
<\`fal-ai/flux-2/flash\` (FLUX 2 Flash), passed as --model; \`fal-ai/flux-2/flash/edit\` only when \`## Perspective\` names a reference asset>
|
|
@@ -108276,7 +108647,7 @@ function getEntryPointToGameAssetDesignerHandoffTemplate(query) {
|
|
|
108276
108647
|
<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>
|
|
108277
108648
|
|
|
108278
108649
|
## Spec consumer
|
|
108279
|
-
<who integrates these assets from the spec: \`builder\` (application
|
|
108650
|
+
<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\`>
|
|
108280
108651
|
|
|
108281
108652
|
## Existing assets
|
|
108282
108653
|
<repository paths the designer must reuse or must not overwrite; or \`Not specified.\`>
|
|
@@ -108314,7 +108685,7 @@ Instructions after the body (not part of the message): the planner may append ex
|
|
|
108314
108685
|
- assets must meet engine import conventions or budgets before a builder integrates them;
|
|
108315
108686
|
- the user asks for an asset spec, style sheet, or asset audit.
|
|
108316
108687
|
|
|
108317
|
-
Skip it for code-only work
|
|
108688
|
+
Skip it for code-only work. Non-game UI icons and illustrations also go to this role; they are not sent to \`uiux-engineer\`.
|
|
108318
108689
|
|
|
108319
108690
|
\`\`\`bash
|
|
108320
108691
|
${handoffCommand({
|
|
@@ -108375,7 +108746,7 @@ Write exactly \`None.\` if no sheet was used.
|
|
|
108375
108746
|
<every property you could not measure or inspect; write exactly \`None.\` if none>
|
|
108376
108747
|
|
|
108377
108748
|
## Recommended next step
|
|
108378
|
-
<exactly one target: \`game-asset-designer\` (re-run with the listed asset IDs), \`builder\` (
|
|
108749
|
+
<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>
|
|
108379
108750
|
|
|
108380
108751
|
Instructions after the body (not part of the message): run the normal handoff command to \`${entryPoint}\` with the complete body above, then stop.
|
|
108381
108752
|
|
|
@@ -108446,11 +108817,11 @@ var init_planner_architect_chat = __esm(() => {
|
|
|
108446
108817
|
function getPlannerBuilderCodeBody() {
|
|
108447
108818
|
return `${getDelegationBriefIntro()}
|
|
108448
108819
|
|
|
108449
|
-
**Before you delegate:** if this slice introduces anything not yet grounded in the repository (a new library, API, external platform behavior, or an unresolved
|
|
108820
|
+
**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.
|
|
108450
108821
|
|
|
108451
108822
|
**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.
|
|
108452
108823
|
|
|
108453
|
-
**Division of labor:** You (planner) own
|
|
108824
|
+
**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.
|
|
108454
108825
|
|
|
108455
108826
|
**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.
|
|
108456
108827
|
|
|
@@ -108553,7 +108924,7 @@ function getEntryPointToResearcherHandoffTemplate(query) {
|
|
|
108553
108924
|
Research before delegating when the slice introduces something not already grounded in this repository:
|
|
108554
108925
|
- a new library, API, external service, platform behavior, or version-sensitive dependency;
|
|
108555
108926
|
- an unfamiliar subsystem;
|
|
108556
|
-
- a
|
|
108927
|
+
- 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);
|
|
108557
108928
|
- any ambiguity the user has not resolved.
|
|
108558
108929
|
|
|
108559
108930
|
Skip research when the change is a small, well-understood edit in code you have already inspected.
|
|
@@ -108640,7 +109011,7 @@ Review before delivery for code-producing work:
|
|
|
108640
109011
|
- the user asks for a code review;
|
|
108641
109012
|
- a change is large, risky, or touches security-sensitive or shared code.
|
|
108642
109013
|
|
|
108643
|
-
Skip review for a
|
|
109014
|
+
Skip review only for an edit of a few lines in one file that the builder already verified with a test or command.
|
|
108644
109015
|
|
|
108645
109016
|
After completing the review, the reviewer must return its issue report to \`${entryPoint}\` with the normal handoff command.
|
|
108646
109017
|
|
|
@@ -108702,6 +109073,8 @@ function getEntryPointToTriageHandoffTemplate(query) {
|
|
|
108702
109073
|
const entryPoint = query.fromRole;
|
|
108703
109074
|
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.
|
|
108704
109075
|
|
|
109076
|
+
Use this for every reported bug (something broken or behaving differently than expected) before any fix is implemented.
|
|
109077
|
+
|
|
108705
109078
|
After completing the investigation, triage must return the evidence-backed report to \`${entryPoint}\` with the normal handoff command.
|
|
108706
109079
|
|
|
108707
109080
|
\`\`\`bash
|
|
@@ -108768,11 +109141,12 @@ var init_planner_triage_chat = __esm(() => {
|
|
|
108768
109141
|
|
|
108769
109142
|
// ../../services/backend/prompts/teams/uiux-engineer-handoff-templates.ts
|
|
108770
109143
|
function getUiuxEngineerFocus() {
|
|
108771
|
-
return "complete user flows, loading/empty/error/success states, component ownership, theme tokens and layout, responsive details, and UI tests";
|
|
109144
|
+
return "complete user flows, screen and flow drawings, loading/empty/error/success states, component ownership, theme tokens and layout, responsive details, and UI tests";
|
|
108772
109145
|
}
|
|
108773
109146
|
function getUiuxEngineerRequirements() {
|
|
108774
109147
|
return `
|
|
108775
109148
|
- Specify complete user flows with explicit loading, empty, error, success, disabled, and retry states where applicable.
|
|
109149
|
+
- 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.
|
|
108776
109150
|
- Define component hierarchy and ownership, props/state/events, data boundaries, and interaction behavior.
|
|
108777
109151
|
- Choose concrete existing ShadCN components with the Base UI backend and lucide-react/react-icons conventions; do not write "use an appropriate component."
|
|
108778
109152
|
- Specify Tailwind utility classes or semantic theme-token classes for layout, spacing, sizing, typography, states, responsive behavior, and dark mode.
|
|
@@ -108787,7 +109161,7 @@ function getEntryPointToUiuxEngineerHandoffTemplate(query) {
|
|
|
108787
109161
|
|
|
108788
109162
|
Provide the request, relevant constraints, and the files or product surfaces that need inspection. Ask for one evidence-backed design focused on ${getUiuxEngineerFocus()}.
|
|
108789
109163
|
|
|
108790
|
-
After completing the design, hand it back to \`${entryPoint}\` with the normal handoff command
|
|
109164
|
+
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.
|
|
108791
109165
|
|
|
108792
109166
|
\`\`\`bash
|
|
108793
109167
|
${handoffCommand({
|
|
@@ -108815,6 +109189,24 @@ function getUiuxEngineerToEntryPointHandoffTemplate(query) {
|
|
|
108815
109189
|
- Name concrete repository paths, existing components/tokens, props/state/events, data boundaries, acceptance criteria, risks, and verification steps.
|
|
108816
109190
|
${getUiuxEngineerRequirements()}
|
|
108817
109191
|
|
|
109192
|
+
## Screens, Flows, and Layouts
|
|
109193
|
+
<required for a user-facing workflow; write exactly \`Not Applicable.\` for a non-visual change>
|
|
109194
|
+
|
|
109195
|
+
### Flow: <flow name>
|
|
109196
|
+
\`\`\`mermaid
|
|
109197
|
+
flowchart TD
|
|
109198
|
+
<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>
|
|
109199
|
+
\`\`\`
|
|
109200
|
+
|
|
109201
|
+
### Screen: <screen name> (\`<route or entry point>\`)
|
|
109202
|
+
\`\`\`text
|
|
109203
|
+
<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.>
|
|
109204
|
+
\`\`\`
|
|
109205
|
+
- States: <loading, empty, error, success, disabled, retry — which apply and how each changes the layout>
|
|
109206
|
+
- Responsive: <breakpoint behavior, one line per breakpoint that changes the layout>
|
|
109207
|
+
|
|
109208
|
+
<repeat one ### Flow block per flow and one ### Screen block per screen; do not collapse several screens into one drawing>
|
|
109209
|
+
|
|
108818
109210
|
## Force Multipliers
|
|
108819
109211
|
<name the existing components, patterns, tokens, utilities, and platform conventions to reuse, and state why each reduces implementation risk or duplication>
|
|
108820
109212
|
- Keep the design within the repository's established source-of-truth boundaries and avoid introducing a new registry or abstraction without evidence.
|
|
@@ -110245,7 +110637,7 @@ var init_architect3 = __esm(() => {
|
|
|
110245
110637
|
init_sections();
|
|
110246
110638
|
ARCHITECT_GUIDANCE = `## Architect Operating Model
|
|
110247
110639
|
|
|
110248
|
-
You are the architect responsible for
|
|
110640
|
+
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.
|
|
110249
110641
|
|
|
110250
110642
|
${"{SESSION_CONTINUITY}"}
|
|
110251
110643
|
|
|
@@ -110279,7 +110671,7 @@ var init_game_asset_designer2 = __esm(() => {
|
|
|
110279
110671
|
init_sections();
|
|
110280
110672
|
GAME_ASSET_DESIGNER_GUIDANCE = `## Game Asset Designer Operating Model
|
|
110281
110673
|
|
|
110282
|
-
You specify production-quality game assets
|
|
110674
|
+
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.
|
|
110283
110675
|
|
|
110284
110676
|
${"{SESSION_CONTINUITY}"}
|
|
110285
110677
|
|
|
@@ -110454,17 +110846,18 @@ ${getInterRoleHandoffRule()}
|
|
|
110454
110846
|
|
|
110455
110847
|
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.
|
|
110456
110848
|
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.
|
|
110457
|
-
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,
|
|
110458
|
-
4.
|
|
110459
|
-
5.
|
|
110460
|
-
6.
|
|
110461
|
-
7.
|
|
110849
|
+
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.
|
|
110850
|
+
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.
|
|
110851
|
+
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.
|
|
110852
|
+
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.
|
|
110853
|
+
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\`.
|
|
110854
|
+
8. Hand the complete design brief to \`{ENTRY_POINT_ROLE}\` with the normal command, then stop:
|
|
110462
110855
|
|
|
110463
110856
|
\`\`\`bash
|
|
110464
110857
|
chatroom handoff --chatroom-id="<chatroom-id>" --role="uiux-engineer" --next-role="{ENTRY_POINT_ROLE}"
|
|
110465
110858
|
\`\`\`
|
|
110466
110859
|
|
|
110467
|
-
The handoff must contain every required brief section, repository evidence, chosen flow, exact component/prop/state details, explicit states,
|
|
110860
|
+
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.`;
|
|
110468
110861
|
});
|
|
110469
110862
|
|
|
110470
110863
|
// ../../services/backend/prompts/cli/roles/verifier.ts
|
|
@@ -110630,7 +111023,7 @@ var init_templates = __esm(() => {
|
|
|
110630
111023
|
"game-asset-designer": {
|
|
110631
111024
|
role: "game-asset-designer",
|
|
110632
111025
|
title: "Game Asset Designer",
|
|
110633
|
-
description: "You are the game asset designer responsible for specifying production-quality game assets (
|
|
111026
|
+
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.",
|
|
110634
111027
|
responsibilities: [
|
|
110635
111028
|
"Recover the target, asset request, style reference, and budgets from the planner brief",
|
|
110636
111029
|
"Inspect the repository for existing assets, naming and folder conventions, and engine import configuration",
|
|
@@ -110643,13 +111036,13 @@ var init_templates = __esm(() => {
|
|
|
110643
111036
|
verifier: {
|
|
110644
111037
|
role: "verifier",
|
|
110645
111038
|
title: "Verifier",
|
|
110646
|
-
description: "You are the verifier responsible for judging, from chatroom logs only, whether the user's last message was achieved; you do not implement,
|
|
111039
|
+
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.",
|
|
110647
111040
|
responsibilities: [
|
|
110648
111041
|
"Recover the user's last message and the pinned context from the chatroom logs",
|
|
110649
111042
|
"Break the objective into numbered requirements",
|
|
110650
111043
|
"Check each requirement against concrete evidence in the logs, treating the planner claim as unverified",
|
|
110651
111044
|
"Return one verdict (ACHIEVED, NOT_ACHIEVED, or INCONCLUSIVE) with evidence and the missing items",
|
|
110652
|
-
"Execute nothing: no
|
|
111045
|
+
"Execute nothing except the tests the user explicitly asked for: no builds, lints, or edits"
|
|
110653
111046
|
],
|
|
110654
111047
|
defaultHandoffTarget: "planner"
|
|
110655
111048
|
},
|
|
@@ -146455,6 +146848,9 @@ Examples:
|
|
|
146455
146848
|
chatroom generate-image --platform=fal --model ${FLUX_2_FLASH_MODEL} --prompt "a hero" --out hero.png
|
|
146456
146849
|
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
146850
|
chatroom generate-image --platform=fal --model ${FLUX_2_FLASH_EDIT_MODEL} --prompt "make the sky stormy" --input-image ./hero.png --out hero-stormy.png
|
|
146851
|
+
|
|
146852
|
+
Cost lines are estimates: fal returns no billed cost. The rate is live when
|
|
146853
|
+
available, otherwise the list price; check the fal dashboard for the actual bill.
|
|
146458
146854
|
`;
|
|
146459
146855
|
async function runGenerateImage(options) {
|
|
146460
146856
|
try {
|
|
@@ -146481,13 +146877,15 @@ async function runGenerateImage(options) {
|
|
|
146481
146877
|
const result = await generateImage(generateOptions, deps);
|
|
146482
146878
|
console.log(`Generated ${result.outPath}`);
|
|
146483
146879
|
console.log(`Manifest ${result.manifestPath}`);
|
|
146880
|
+
for (const line of result.costLines)
|
|
146881
|
+
console.log(line);
|
|
146484
146882
|
} catch (err) {
|
|
146485
146883
|
console.error(`❌ ${err instanceof Error ? err.message : String(err)}`);
|
|
146486
146884
|
process.exitCode = 1;
|
|
146487
146885
|
}
|
|
146488
146886
|
}
|
|
146489
146887
|
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
|
|
146888
|
+
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, the model constraints the CLI knows, and the estimated fal cost. No upload or fal generation call is made; one read-only pricing request may be made when FAL_API_KEY is set (the key is not required)").addHelpText("after", HELP_AFTER).action(runGenerateImage);
|
|
146491
146889
|
}
|
|
146492
146890
|
|
|
146493
146891
|
// src/commands/generate/remove-background.ts
|
|
@@ -147420,8 +147818,10 @@ Model:
|
|
|
147420
147818
|
a PNG with alpha.
|
|
147421
147819
|
|
|
147422
147820
|
Billing:
|
|
147423
|
-
bria is
|
|
147424
|
-
|
|
147821
|
+
bria is estimated per generation at the live rate, or at the list price of
|
|
147822
|
+
$0.018 (as of 2026-10-25) when the live lookup is unavailable. birefnet is
|
|
147823
|
+
billed per compute second; the rate is shown but the total is not estimated.
|
|
147824
|
+
fal returns no billed cost; check the fal dashboard for the actual bill.
|
|
147425
147825
|
|
|
147426
147826
|
Refusals:
|
|
147427
147827
|
A missing FAL_API_KEY, a missing or non-PNG input, an input over 8 MiB, an
|
|
@@ -148693,4 +149093,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
148693
149093
|
});
|
|
148694
149094
|
program2.parse();
|
|
148695
149095
|
|
|
148696
|
-
//# debugId=
|
|
149096
|
+
//# debugId=F8A3E4AE84EB0AE364756E2164756E21
|