chatroom-cli 1.137.1 → 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 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("No upload or fal call was made.");
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: createHash("sha256").update(bytes).digest("hex"),
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 { outPath: plan.outPath, manifestPath: plan.manifestPath, sha256: saved.sha256 };
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();
@@ -146479,6 +146848,9 @@ Examples:
146479
146848
  chatroom generate-image --platform=fal --model ${FLUX_2_FLASH_MODEL} --prompt "a hero" --out hero.png
146480
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
146481
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.
146482
146854
  `;
146483
146855
  async function runGenerateImage(options) {
146484
146856
  try {
@@ -146505,13 +146877,15 @@ async function runGenerateImage(options) {
146505
146877
  const result = await generateImage(generateOptions, deps);
146506
146878
  console.log(`Generated ${result.outPath}`);
146507
146879
  console.log(`Manifest ${result.manifestPath}`);
146880
+ for (const line of result.costLines)
146881
+ console.log(line);
146508
146882
  } catch (err) {
146509
146883
  console.error(`❌ ${err instanceof Error ? err.message : String(err)}`);
146510
146884
  process.exitCode = 1;
146511
146885
  }
146512
146886
  }
146513
146887
  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);
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);
146515
146889
  }
146516
146890
 
146517
146891
  // src/commands/generate/remove-background.ts
@@ -147444,8 +147818,10 @@ Model:
147444
147818
  a PNG with alpha.
147445
147819
 
147446
147820
  Billing:
147447
- bria is billed per call (about $0.018 at the time of writing). birefnet is
147448
- billed per compute-second.
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.
147449
147825
 
147450
147826
  Refusals:
147451
147827
  A missing FAL_API_KEY, a missing or non-PNG input, an input over 8 MiB, an
@@ -148717,4 +149093,4 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
148717
149093
  });
148718
149094
  program2.parse();
148719
149095
 
148720
- //# debugId=E840D0ACF405C0D664756E2164756E21
149096
+ //# debugId=F8A3E4AE84EB0AE364756E2164756E21