image-skill 0.1.43 → 0.1.45

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.
@@ -15,7 +15,7 @@ import { Readable } from "node:stream";
15
15
  import { pipeline } from "node:stream/promises";
16
16
  import os from "node:os";
17
17
 
18
- const VERSION = "0.1.43";
18
+ const VERSION = "0.1.45";
19
19
  const PACKAGE_NAME = "image-skill";
20
20
  const DEFAULT_API_BASE_URL = "https://api.image-skill.com";
21
21
  const DEFAULT_DOCS_BASE_URL = "https://image-skill.com";
@@ -49,6 +49,9 @@ const HOSTED_SIGNUP_TOKEN_RETURNED_WARNING =
49
49
  const PUBLIC_NPX_COMMAND_PREFIX =
50
50
  "npm_config_update_notifier=false npx -y image-skill@latest";
51
51
  const CREDIT_UNIT_USD = 0.01;
52
+ const TARGET_GROSS_MARGIN = 0.4;
53
+ const PAYMENT_BACKED_CREDIT_PAYMENT_FEE_RATE = 0.015;
54
+ const PAYMENT_BACKED_CREDIT_PAYMENT_FEE_MODEL = "stripe_stablecoin_usd_percent";
52
55
  const MODALITY_COMMAND_ALIASES = new Map([
53
56
  ["image", { command: "create", intent: null }],
54
57
  ["video", { command: "create", intent: "video" }],
@@ -529,6 +532,7 @@ function commandHelpByKey(key) {
529
532
  docs_url: "https://image-skill.com/cli.md#image-skill-edit",
530
533
  required_flags: ["--input"],
531
534
  optional_flags: [
535
+ "--guide",
532
536
  "--dry-run",
533
537
  "--prompt",
534
538
  "--model",
@@ -1607,17 +1611,19 @@ async function capabilities(argv) {
1607
1611
  });
1608
1612
  }
1609
1613
 
1610
- async function createGuide(args) {
1614
+ async function createGuide(args, options = {}) {
1615
+ const guideOperation = options.guideOperation ?? "create";
1616
+ const command = `image-skill ${guideOperation} --guide`;
1611
1617
  if (flagBool(args, "dry-run")) {
1612
1618
  return invalid(
1613
- "image-skill create --guide",
1614
- "create --guide cannot be combined with --dry-run; the guide returns the dry-run escape hatch separately",
1619
+ command,
1620
+ `${guideOperation} --guide cannot be combined with --dry-run; the guide returns the dry-run escape hatch separately`,
1615
1621
  );
1616
1622
  }
1617
1623
  if (hasReferenceFlags(args)) {
1618
1624
  return invalid(
1619
- "image-skill create --guide",
1620
- "create --guide does not upload or resolve reference images; inspect the model with models show, then run create --dry-run before live referenced creates",
1625
+ command,
1626
+ `${guideOperation} --guide does not upload or resolve reference images; inspect the model with models show, then run ${guideOperation} --dry-run before live referenced media calls`,
1621
1627
  );
1622
1628
  }
1623
1629
  const modelParameters = jsonObjectFlag(args, "model-parameters-json");
@@ -1632,6 +1638,10 @@ async function createGuide(args) {
1632
1638
  const requestedProviderId = flagString(args, "provider");
1633
1639
  const requestedIntentFlag = flagString(args, "intent");
1634
1640
  const requestedIntent = requestedIntentFlag ?? "explore";
1641
+ const requestedModelParametersJson =
1642
+ modelParameters.value === null
1643
+ ? null
1644
+ : JSON.stringify(modelParameters.value);
1635
1645
  const maxEstimatedUsdPerImage = flagNumber(
1636
1646
  args,
1637
1647
  "max-estimated-usd-per-image",
@@ -1662,17 +1672,58 @@ async function createGuide(args) {
1662
1672
  const selected =
1663
1673
  models.envelope.ok && models.envelope.data?.models
1664
1674
  ? selectCreateGuideModel(models.envelope.data.models, requestedModelId, {
1675
+ operation: guideOperation,
1665
1676
  prompt: trimmedPrompt,
1666
1677
  intent: requestedIntent,
1667
1678
  maxEstimatedUsdPerImage,
1668
1679
  })
1669
1680
  : null;
1670
1681
  const selectedAspectRatio = createGuideSuggestedAspectRatio(selected);
1671
- const pricing = createGuideModelCreditPricing(selected);
1682
+ const pricingContext = {
1683
+ aspectRatio: selectedAspectRatio ?? "1:1",
1684
+ outputCount: 1,
1685
+ };
1686
+ const defaultedModelParameters =
1687
+ selected === null || createGuideSelectedModelRequiresInputImage(selected)
1688
+ ? {
1689
+ modelParameters: modelParameters.value ?? {},
1690
+ defaultsApplied: [],
1691
+ }
1692
+ : createGuideDefaultModelParameters({
1693
+ model: selected,
1694
+ aspectRatio: pricingContext.aspectRatio,
1695
+ intent: requestedIntent,
1696
+ modelParameters: modelParameters.value ?? {},
1697
+ maxEstimatedUsdPerImage,
1698
+ });
1699
+ const shouldPriceModelParameters =
1700
+ selected !== null &&
1701
+ !createGuideSelectedModelRequiresInputImage(selected) &&
1702
+ createGuideCanPriceModelParameters(selected) &&
1703
+ (defaultedModelParameters.defaultsApplied.length > 0 ||
1704
+ Object.keys(modelParameters.value ?? {}).length > 0);
1705
+ const pricing =
1706
+ selected === null
1707
+ ? null
1708
+ : shouldPriceModelParameters
1709
+ ? createGuidePricingForModel(
1710
+ selected,
1711
+ defaultedModelParameters.modelParameters,
1712
+ pricingContext,
1713
+ )
1714
+ : createGuideModelCreditPricing(selected);
1715
+ const providerCostEstimate =
1716
+ selected === null || !shouldPriceModelParameters
1717
+ ? null
1718
+ : createGuideProviderCostEstimateForModel(
1719
+ selected,
1720
+ defaultedModelParameters.modelParameters,
1721
+ pricingContext,
1722
+ );
1672
1723
  const estimatedCredits = pricing?.credits_required ?? null;
1673
1724
  const estimatedProviderUsdPerImage =
1725
+ providerCostEstimate?.estimated_provider_cost_usd ??
1674
1726
  selected?.economics?.estimated_usd_per_image ??
1675
- pricing?.estimated_provider_cost_usd ??
1676
1727
  pricing?.fallback_provider_cost_usd ??
1677
1728
  (typeof selected?.estimated_usd_per_image === "number"
1678
1729
  ? selected.estimated_usd_per_image
@@ -1699,6 +1750,9 @@ async function createGuide(args) {
1699
1750
  token.source === "anonymous" ? "none" : token.source;
1700
1751
  const stage = createGuideStage({
1701
1752
  prompt: trimmedPrompt,
1753
+ promptRequired:
1754
+ trimmedPrompt.length === 0 &&
1755
+ (selected === null || !PROMPTLESS_EDIT_MODEL_IDS.has(selected.id)),
1702
1756
  health,
1703
1757
  models,
1704
1758
  selected,
@@ -1730,9 +1784,12 @@ async function createGuide(args) {
1730
1784
  requestedIntent,
1731
1785
  requestedIntentFlag,
1732
1786
  requestedModelId,
1787
+ guideOperation,
1788
+ inputReference: options.inputReference,
1733
1789
  maxEstimatedUsdPerImage,
1734
1790
  budgetGuard,
1735
1791
  aspectRatio: selectedAspectRatio,
1792
+ modelParametersJson: requestedModelParametersJson,
1736
1793
  apiBaseUrl: explicitApiBaseUrl(args),
1737
1794
  paymentSummary,
1738
1795
  commandPrefix: guideCommandPrefix,
@@ -1748,9 +1805,12 @@ async function createGuide(args) {
1748
1805
  requestedIntent,
1749
1806
  requestedIntentFlag,
1750
1807
  requestedModelId,
1808
+ guideOperation,
1809
+ inputReference: options.inputReference,
1751
1810
  maxEstimatedUsdPerImage,
1752
1811
  budgetGuard,
1753
1812
  aspectRatio: selectedAspectRatio,
1813
+ modelParametersJson: requestedModelParametersJson,
1754
1814
  apiBaseUrl: explicitApiBaseUrl(args),
1755
1815
  commandPrefix: guideCommandPrefix,
1756
1816
  });
@@ -1792,10 +1852,13 @@ async function createGuide(args) {
1792
1852
  explicitApiBaseUrl(args),
1793
1853
  guideCommandPrefix,
1794
1854
  {
1855
+ operation: guideOperation,
1856
+ inputReference: options.inputReference,
1795
1857
  modelId: requestedModelId,
1796
1858
  providerId: requestedProviderId,
1797
1859
  intent: requestedIntentFlag,
1798
1860
  maxEstimatedUsdPerImage,
1861
+ modelParametersJson: requestedModelParametersJson,
1799
1862
  },
1800
1863
  )
1801
1864
  : null;
@@ -1818,8 +1881,19 @@ async function createGuide(args) {
1818
1881
  tokenSource: publicTokenSource,
1819
1882
  commandPrefix: guideCommandPrefix,
1820
1883
  });
1821
- return createGuideSuccess(quota?.envelope.actor ?? null, {
1822
- schema: "image-skill.create-guide.v1",
1884
+ const guideRecovery = createGuideRecovery(stage, {
1885
+ blocker,
1886
+ nextCommand,
1887
+ noSpendNextCommand,
1888
+ afterNext,
1889
+ escapeHatches,
1890
+ selfFundNextCommand,
1891
+ });
1892
+ return createGuideSuccess(command, quota?.envelope.actor ?? null, {
1893
+ schema:
1894
+ guideOperation === "edit"
1895
+ ? "image-skill.edit-guide.v1"
1896
+ : "image-skill.create-guide.v1",
1823
1897
  ready: stage === "ready_to_create",
1824
1898
  stage,
1825
1899
  checks: {
@@ -1884,6 +1958,7 @@ async function createGuide(args) {
1884
1958
  selected,
1885
1959
  trimmedPrompt,
1886
1960
  requestedIntent,
1961
+ guideOperation,
1887
1962
  )
1888
1963
  : createGuideSelectedModelRequiresInputImage(selected)
1889
1964
  ? selected.modality === "3d"
@@ -1898,6 +1973,9 @@ async function createGuide(args) {
1898
1973
  estimated_provider_usd_per_image: estimatedProviderUsdPerImage,
1899
1974
  credit_unit_usd: pricing?.credit_unit_usd ?? CREDIT_UNIT_USD,
1900
1975
  pricing_confidence: pricing?.pricing_confidence ?? null,
1976
+ pricing_source: pricing?.pricing_source ?? null,
1977
+ model_parameter_defaults_applied:
1978
+ defaultedModelParameters.defaultsApplied,
1901
1979
  },
1902
1980
  blocker,
1903
1981
  guide_warning: guideWarning,
@@ -1910,6 +1988,7 @@ async function createGuide(args) {
1910
1988
  no_spend_next_command_label: noSpendNextCommandLabel,
1911
1989
  no_spend_next_command_effect: noSpendNextCommandEffect,
1912
1990
  no_spend_evaluation: noSpendEvaluation,
1991
+ guide_recovery: guideRecovery,
1913
1992
  recommended_no_spend_command: noSpendNextCommand,
1914
1993
  recommended_no_spend_command_label: noSpendNextCommandLabel,
1915
1994
  recommended_no_spend_command_effect: noSpendNextCommandEffect,
@@ -1930,8 +2009,8 @@ async function createGuide(args) {
1930
2009
  });
1931
2010
  }
1932
2011
 
1933
- function createGuideSuccess(actor, data) {
1934
- const result = success("image-skill create --guide", data);
2012
+ function createGuideSuccess(command, actor, data) {
2013
+ const result = success(command, data);
1935
2014
  result.envelope.actor = actor;
1936
2015
  return result;
1937
2016
  }
@@ -1939,7 +2018,12 @@ function createGuideSuccess(actor, data) {
1939
2018
  function selectCreateGuideModel(
1940
2019
  models,
1941
2020
  requestedModelId,
1942
- { prompt = "", intent = undefined, maxEstimatedUsdPerImage = null } = {},
2021
+ {
2022
+ operation = "create",
2023
+ prompt = "",
2024
+ intent = undefined,
2025
+ maxEstimatedUsdPerImage = null,
2026
+ } = {},
1943
2027
  ) {
1944
2028
  const isExecutableCreate = (model) =>
1945
2029
  model?.status === "available" &&
@@ -1953,14 +2037,19 @@ function selectCreateGuideModel(
1953
2037
  (model.supports.includes("edit") || model.supports.includes("variation")) &&
1954
2038
  createGuideSelectedModelRequiresInputImage(model);
1955
2039
  const isExecutableGuideModel = (model) =>
1956
- isExecutableCreate(model) || isExecutableInputImageEdit(model);
2040
+ operation === "edit"
2041
+ ? isExecutableInputImageEdit(model)
2042
+ : isExecutableCreate(model) || isExecutableInputImageEdit(model);
1957
2043
  if (requestedModelId !== null) {
1958
2044
  const requested = models.find((model) => model.id === requestedModelId);
1959
2045
  return requested !== undefined && isExecutableGuideModel(requested)
1960
2046
  ? requested
1961
2047
  : null;
1962
2048
  }
1963
- const candidates = models.filter(isExecutableCreate);
2049
+ const candidates =
2050
+ operation === "edit"
2051
+ ? models.filter(isExecutableInputImageEdit)
2052
+ : models.filter(isExecutableCreate);
1964
2053
  if (createGuideImplies3d({ prompt, intent })) {
1965
2054
  const eligible3d = guideCandidatesWithinBudget({
1966
2055
  candidates: models.filter(
@@ -2068,6 +2157,221 @@ function guideBudgetUsdForModel(model) {
2068
2157
  );
2069
2158
  }
2070
2159
 
2160
+ function createGuideDefaultModelParameters(input) {
2161
+ const modelParameters = { ...(input.modelParameters ?? {}) };
2162
+ const defaultsApplied = [];
2163
+
2164
+ if (
2165
+ input.model?.id === "xai.grok-imagine-image-quality" &&
2166
+ modelParameters.resolution === undefined
2167
+ ) {
2168
+ const twoKEstimate = createGuideProviderCostEstimateForModel(
2169
+ input.model,
2170
+ { resolution: "2k" },
2171
+ { aspectRatio: input.aspectRatio },
2172
+ ).estimated_provider_cost_usd;
2173
+ const twoKAllowedByBudget =
2174
+ input.maxEstimatedUsdPerImage === null ||
2175
+ twoKEstimate === null ||
2176
+ twoKEstimate <= input.maxEstimatedUsdPerImage;
2177
+ const intentClass = createGuideIntentClass(input.intent);
2178
+ const resolution =
2179
+ intentClass !== "budget_draft" && twoKAllowedByBudget ? "2k" : "1k";
2180
+ modelParameters.resolution = resolution;
2181
+ defaultsApplied.push(`resolution=${resolution}`);
2182
+ }
2183
+
2184
+ if (
2185
+ input.model?.id === "fal.flux-dev" &&
2186
+ modelParameters.image_size === undefined
2187
+ ) {
2188
+ const imageSize = falDefaultImageSize(input.aspectRatio);
2189
+ if (imageSize !== null) {
2190
+ modelParameters.image_size = imageSize;
2191
+ defaultsApplied.push(`image_size=${imageSize}`);
2192
+ }
2193
+ }
2194
+
2195
+ if (
2196
+ input.model?.id === "openai.gpt-image-2" &&
2197
+ modelParameters.quality === undefined
2198
+ ) {
2199
+ const mediumEstimate = createGuideProviderCostEstimateForModel(
2200
+ input.model,
2201
+ { ...modelParameters, quality: "medium" },
2202
+ { aspectRatio: input.aspectRatio },
2203
+ ).estimated_provider_cost_usd;
2204
+ const mediumAllowedByBudget =
2205
+ input.maxEstimatedUsdPerImage === null ||
2206
+ mediumEstimate === null ||
2207
+ mediumEstimate <= input.maxEstimatedUsdPerImage;
2208
+ if (mediumAllowedByBudget) {
2209
+ modelParameters.quality = "medium";
2210
+ defaultsApplied.push("quality=medium");
2211
+ }
2212
+ }
2213
+
2214
+ return { modelParameters, defaultsApplied };
2215
+ }
2216
+
2217
+ function createGuidePricingForModel(model, modelParameters, context = {}) {
2218
+ const estimate = createGuideProviderCostEstimateForModel(
2219
+ model,
2220
+ modelParameters,
2221
+ context,
2222
+ );
2223
+ if (estimate.estimated_provider_cost_usd === null) {
2224
+ return createGuideModelCreditPricing(model);
2225
+ }
2226
+ return createGuideCreditPricingForProviderCost({
2227
+ providerCostUsd: estimate.estimated_provider_cost_usd,
2228
+ pricingConfidence: estimate.pricing_confidence,
2229
+ pricingSource: estimate.pricing_source,
2230
+ });
2231
+ }
2232
+
2233
+ function createGuideCanPriceModelParameters(model) {
2234
+ return String(model?.id ?? "").startsWith("xai.grok-imagine-image");
2235
+ }
2236
+
2237
+ function createGuideProviderCostEstimateForModel(
2238
+ model,
2239
+ modelParameters = {},
2240
+ context = {},
2241
+ ) {
2242
+ if (String(model?.id ?? "").startsWith("xai.grok-imagine-image")) {
2243
+ return createGuideXaiImageCostEstimate(model, modelParameters, context);
2244
+ }
2245
+ return {
2246
+ estimated_provider_cost_usd:
2247
+ typeof model?.economics?.estimated_usd_per_image === "number"
2248
+ ? model.economics.estimated_usd_per_image
2249
+ : (createGuideModelCreditPricing(model)?.estimated_provider_cost_usd ??
2250
+ null),
2251
+ pricing_source: "model_registry",
2252
+ pricing_confidence: "known",
2253
+ };
2254
+ }
2255
+
2256
+ function createGuideXaiImageCostEstimate(model, modelParameters, context) {
2257
+ const modelId = String(model?.id ?? "");
2258
+ const quality = modelId.includes("-quality");
2259
+ const edit = modelId.endsWith("-edit");
2260
+ const resolution = modelParameters?.resolution === "2k" ? "2k" : "1k";
2261
+ const outputImageCount =
2262
+ Number.isInteger(context?.outputCount) && context.outputCount > 0
2263
+ ? context.outputCount
2264
+ : 1;
2265
+ const referenceAssetCount =
2266
+ Number.isInteger(context?.referenceAssetCount) &&
2267
+ context.referenceAssetCount > 0
2268
+ ? context.referenceAssetCount
2269
+ : 0;
2270
+ const sourceImageCount = edit ? 1 + referenceAssetCount : 0;
2271
+ const inputUsdPerImage = quality ? 0.01 : 0.002;
2272
+ const outputUsdPerImage =
2273
+ quality && resolution === "2k" ? 0.07 : quality ? 0.05 : 0.02;
2274
+ const defaultResolution =
2275
+ modelParameters?.resolution === undefined ||
2276
+ modelParameters?.resolution === null ||
2277
+ modelParameters?.resolution === "1k";
2278
+ const defaultShape =
2279
+ defaultResolution &&
2280
+ outputImageCount === 1 &&
2281
+ sourceImageCount === (edit ? 1 : 0);
2282
+ return {
2283
+ estimated_provider_cost_usd: roundUsdMicro(
2284
+ inputUsdPerImage * sourceImageCount +
2285
+ outputUsdPerImage * outputImageCount,
2286
+ ),
2287
+ pricing_source: defaultShape ? "model_registry" : "model_parameters",
2288
+ pricing_confidence: "known",
2289
+ };
2290
+ }
2291
+
2292
+ function createGuideCreditPricingForProviderCost(input) {
2293
+ const providerCostUsd = roundUsdMicro(input.providerCostUsd);
2294
+ const creditsRequired = Math.max(
2295
+ 1,
2296
+ Math.ceil(
2297
+ roundUsdMicro(
2298
+ providerCostUsd / (1 - TARGET_GROSS_MARGIN) / CREDIT_UNIT_USD,
2299
+ ),
2300
+ ),
2301
+ );
2302
+ const estimatedRevenueUsd = roundUsd(creditsRequired * CREDIT_UNIT_USD);
2303
+ const estimatedPaymentFeeUsd = roundUsdMicro(
2304
+ estimatedRevenueUsd * PAYMENT_BACKED_CREDIT_PAYMENT_FEE_RATE,
2305
+ );
2306
+ const estimatedNetRevenueUsd = roundUsdMicro(
2307
+ estimatedRevenueUsd - estimatedPaymentFeeUsd,
2308
+ );
2309
+ const estimatedGrossMargin =
2310
+ estimatedRevenueUsd > 0
2311
+ ? roundRatio(
2312
+ (estimatedRevenueUsd - providerCostUsd) / estimatedRevenueUsd,
2313
+ )
2314
+ : null;
2315
+ const estimatedFeeAdjustedMargin =
2316
+ estimatedRevenueUsd > 0
2317
+ ? roundRatio(
2318
+ (estimatedNetRevenueUsd - providerCostUsd) / estimatedRevenueUsd,
2319
+ )
2320
+ : null;
2321
+ const selfFundBlockReason =
2322
+ estimatedNetRevenueUsd + 1e-9 < providerCostUsd
2323
+ ? "payment_fee_margin_negative"
2324
+ : null;
2325
+ return {
2326
+ credits_required: creditsRequired,
2327
+ credit_unit_usd: CREDIT_UNIT_USD,
2328
+ estimated_provider_cost_usd: providerCostUsd,
2329
+ fallback_provider_cost_usd: null,
2330
+ estimated_revenue_usd: estimatedRevenueUsd,
2331
+ estimated_gross_margin: estimatedGrossMargin,
2332
+ payment_fee_rate: PAYMENT_BACKED_CREDIT_PAYMENT_FEE_RATE,
2333
+ payment_fee_model: PAYMENT_BACKED_CREDIT_PAYMENT_FEE_MODEL,
2334
+ estimated_payment_fee_usd: estimatedPaymentFeeUsd,
2335
+ estimated_net_revenue_usd: estimatedNetRevenueUsd,
2336
+ estimated_fee_adjusted_margin: estimatedFeeAdjustedMargin,
2337
+ self_fundable: selfFundBlockReason === null,
2338
+ self_fund_block_reason: selfFundBlockReason,
2339
+ target_gross_margin: TARGET_GROSS_MARGIN,
2340
+ pricing_confidence: input.pricingConfidence,
2341
+ pricing_source: input.pricingSource,
2342
+ margin_model: "provider_cost_plus_margin",
2343
+ };
2344
+ }
2345
+
2346
+ function falDefaultImageSize(aspectRatio) {
2347
+ switch (aspectRatio) {
2348
+ case "1:1":
2349
+ return "square_hd";
2350
+ case "4:3":
2351
+ return "landscape_4_3";
2352
+ case "3:4":
2353
+ return "portrait_4_3";
2354
+ case "16:9":
2355
+ return "landscape_16_9";
2356
+ case "9:16":
2357
+ return "portrait_16_9";
2358
+ default:
2359
+ return null;
2360
+ }
2361
+ }
2362
+
2363
+ function roundUsd(value) {
2364
+ return Math.round(value * 100) / 100;
2365
+ }
2366
+
2367
+ function roundRatio(value) {
2368
+ return Math.round(value * 1000) / 1000;
2369
+ }
2370
+
2371
+ function roundUsdMicro(value) {
2372
+ return Math.round(value * 1_000_000) / 1_000_000;
2373
+ }
2374
+
2071
2375
  function guideModelExecutionStatus(model) {
2072
2376
  if (
2073
2377
  isRecord(model?.execution) &&
@@ -2155,7 +2459,12 @@ function createGuideSelectedModelRequiresInputImage(model) {
2155
2459
  );
2156
2460
  }
2157
2461
 
2158
- function createGuideSelectionReason(model, prompt, intent) {
2462
+ function createGuideSelectionReason(
2463
+ model,
2464
+ prompt,
2465
+ intent,
2466
+ operation = "create",
2467
+ ) {
2159
2468
  if (
2160
2469
  createGuideSelectedModelRequiresInputImage(model) &&
2161
2470
  createGuideImplies3d({ prompt, intent })
@@ -2183,7 +2492,9 @@ function createGuideSelectionReason(model, prompt, intent) {
2183
2492
  ? "guide selected a draft/budget create model with high-definition defaults"
2184
2493
  : "guide selected the strongest currently available quality-first create model for this intent";
2185
2494
  }
2186
- return "guide selected the first available executable create model";
2495
+ return operation === "edit"
2496
+ ? "guide selected the first available executable input-image edit model"
2497
+ : "guide selected the first available executable create model";
2187
2498
  }
2188
2499
 
2189
2500
  function createGuidePaymentSummary(data, commandPrefix) {
@@ -2398,7 +2709,7 @@ function renderCopyRunnablePaymentCommand(commandPrefix, command) {
2398
2709
  }
2399
2710
 
2400
2711
  function createGuideStage(input) {
2401
- if (input.prompt.length === 0) {
2712
+ if (input.promptRequired) {
2402
2713
  return "prompt_required";
2403
2714
  }
2404
2715
  if (!input.health.envelope.ok || !input.models.envelope.ok) {
@@ -2805,6 +3116,64 @@ function createGuideNoSpendEvaluation(stage, input) {
2805
3116
  };
2806
3117
  }
2807
3118
 
3119
+ function createGuideRecovery(stage, input) {
3120
+ let noSpendCommand = null;
3121
+ let noSpendCommandField = null;
3122
+ if (stage === "ready_to_create" && input.noSpendNextCommand !== null) {
3123
+ noSpendCommand = input.noSpendNextCommand;
3124
+ noSpendCommandField = "recommended_no_spend_command";
3125
+ } else if (stage === "quota_required") {
3126
+ noSpendCommand = input.escapeHatches.quota;
3127
+ noSpendCommandField = "escape_hatches.quota";
3128
+ } else if (
3129
+ stage === "no_executable_model" ||
3130
+ stage === "service_unreachable"
3131
+ ) {
3132
+ noSpendCommand = input.nextCommand;
3133
+ noSpendCommandField = "next_command";
3134
+ } else if (stage === "auth_required" || stage === "prompt_required") {
3135
+ noSpendCommand = input.nextCommand;
3136
+ noSpendCommandField = "next_command";
3137
+ }
3138
+ const noSpendMissingInputs =
3139
+ noSpendCommand === null
3140
+ ? []
3141
+ : createGuideNextCommandMissingInputs(noSpendCommand);
3142
+ const liveCreateCommandField =
3143
+ stage === "ready_to_create" ? "next_command" : null;
3144
+ const livePaymentCommandField =
3145
+ stage === "quota_required" && input.selfFundNextCommand !== null
3146
+ ? "self_fund_next_command"
3147
+ : null;
3148
+ const doubleSpendGuardRequired =
3149
+ liveCreateCommandField !== null || livePaymentCommandField !== null;
3150
+ return {
3151
+ schema: "image-skill.guide-recovery.v1",
3152
+ stage,
3153
+ precondition_code: input.blocker?.code ?? null,
3154
+ precondition_message: input.blocker?.message ?? null,
3155
+ no_spend_command: noSpendCommand,
3156
+ no_spend_command_field: noSpendCommandField,
3157
+ no_spend_command_copy_runnable:
3158
+ noSpendCommand === null ? null : noSpendMissingInputs.length === 0,
3159
+ no_spend_command_missing_inputs: noSpendMissingInputs,
3160
+ after_success_command: input.afterNext,
3161
+ after_success_command_field: input.afterNext === null ? null : "after_next",
3162
+ live_create_command_field: liveCreateCommandField,
3163
+ live_payment_command_field: livePaymentCommandField,
3164
+ double_spend_guard: {
3165
+ required: doubleSpendGuardRequired,
3166
+ safe_rerun_command_field: noSpendCommandField,
3167
+ warning:
3168
+ liveCreateCommandField !== null
3169
+ ? "Do not blindly rerun data.next_command after a partial or unknown create/edit failure; use data.guide_recovery.no_spend_command, jobs/activity, or error.recovery before any live retry."
3170
+ : livePaymentCommandField !== null
3171
+ ? "Do not blindly rerun live payment commands with fresh identifiers after a partial or unknown payment failure; use data.guide_recovery.no_spend_command and payment status recovery before any new buy."
3172
+ : "No live payment or live media command is exposed for this stage; follow the no-spend command and rerun the guide after the precondition is satisfied.",
3173
+ },
3174
+ };
3175
+ }
3176
+
2808
3177
  function createGuideWarning(stage, input) {
2809
3178
  const effect = input.nextCommandEffect;
2810
3179
  const base = {
@@ -2925,10 +3294,13 @@ function escapeRegExp(value) {
2925
3294
  function createGuideNextCommand(stage, input) {
2926
3295
  if (stage === "prompt_required") {
2927
3296
  return renderGuideCommand("PROMPT", input.apiBaseUrl, input.commandPrefix, {
3297
+ operation: input.guideOperation,
3298
+ inputReference: input.inputReference,
2928
3299
  modelId: input.requestedModelId,
2929
3300
  providerId: input.requestedProviderId,
2930
3301
  intent: input.requestedIntentFlag,
2931
3302
  maxEstimatedUsdPerImage: input.maxEstimatedUsdPerImage,
3303
+ modelParametersJson: input.modelParametersJson,
2932
3304
  });
2933
3305
  }
2934
3306
  if (stage === "no_executable_model" || stage === "service_unreachable") {
@@ -2950,7 +3322,9 @@ function createGuideNextCommand(stage, input) {
2950
3322
  return renderInputImageGuideCommand({
2951
3323
  modelId: input.selected.id,
2952
3324
  prompt: input.prompt,
3325
+ inputReference: input.inputReference,
2953
3326
  budgetGuard: input.budgetGuard,
3327
+ modelParametersJson: input.modelParametersJson,
2954
3328
  dryRun: false,
2955
3329
  idempotencyKey: `edit-guide-${Date.now()}-${randomBytes(4).toString("hex")}`,
2956
3330
  apiBaseUrl: input.apiBaseUrl,
@@ -2964,6 +3338,7 @@ function createGuideNextCommand(stage, input) {
2964
3338
  intent: input.requestedIntent,
2965
3339
  budgetGuard: input.budgetGuard,
2966
3340
  aspectRatio: input.aspectRatio,
3341
+ modelParametersJson: input.modelParametersJson,
2967
3342
  dryRun: false,
2968
3343
  // Retry-safe by default (#1228): bake a stable idempotency key into the
2969
3344
  // advertised create command so an agent that copies it and retries after a
@@ -2993,16 +3368,22 @@ function createGuideEscapeHatches(input) {
2993
3368
  "usage quota --json",
2994
3369
  ),
2995
3370
  dry_run:
2996
- input.selected === null || input.prompt.length === 0
3371
+ input.selected === null ||
3372
+ (input.prompt.length === 0 &&
3373
+ !PROMPTLESS_EDIT_MODEL_IDS.has(input.selected.id))
2997
3374
  ? renderGuidePrefixedCommand(
2998
3375
  input.commandPrefix,
2999
- "create --dry-run --prompt PROMPT --json",
3376
+ input.guideOperation === "edit"
3377
+ ? "edit --dry-run --input image_... --prompt PROMPT --json"
3378
+ : "create --dry-run --prompt PROMPT --json",
3000
3379
  )
3001
3380
  : createGuideSelectedModelRequiresInputImage(input.selected)
3002
3381
  ? renderInputImageGuideCommand({
3003
3382
  modelId: input.selected.id,
3004
3383
  prompt: input.prompt,
3384
+ inputReference: input.inputReference,
3005
3385
  budgetGuard: input.budgetGuard,
3386
+ modelParametersJson: input.modelParametersJson,
3006
3387
  dryRun: true,
3007
3388
  apiBaseUrl: input.apiBaseUrl,
3008
3389
  commandPrefix: input.commandPrefix,
@@ -3014,6 +3395,7 @@ function createGuideEscapeHatches(input) {
3014
3395
  intent: input.requestedIntent,
3015
3396
  budgetGuard: input.budgetGuard,
3016
3397
  aspectRatio: input.aspectRatio,
3398
+ modelParametersJson: input.modelParametersJson,
3017
3399
  dryRun: true,
3018
3400
  apiBaseUrl: input.apiBaseUrl,
3019
3401
  commandPrefix: input.commandPrefix,
@@ -3027,10 +3409,16 @@ function renderGuideCommand(
3027
3409
  commandPrefix = "image-skill",
3028
3410
  options = {},
3029
3411
  ) {
3412
+ const operation = options.operation ?? "create";
3030
3413
  return [
3031
3414
  commandPrefix,
3032
- "create --guide --prompt",
3415
+ `${operation} --guide --prompt`,
3033
3416
  shellQuote(prompt),
3417
+ ...(operation === "edit" &&
3418
+ typeof options.inputReference === "string" &&
3419
+ options.inputReference.trim().length > 0
3420
+ ? ["--input", shellQuote(options.inputReference.trim())]
3421
+ : []),
3034
3422
  ...(options.modelId === null ||
3035
3423
  options.modelId === undefined ||
3036
3424
  options.modelId === ""
@@ -3053,6 +3441,10 @@ function renderGuideCommand(
3053
3441
  "--max-estimated-usd-per-image",
3054
3442
  shellQuote(formatUsd(options.maxEstimatedUsdPerImage)),
3055
3443
  ]),
3444
+ ...(options.modelParametersJson === null ||
3445
+ options.modelParametersJson === undefined
3446
+ ? []
3447
+ : ["--model-parameters-json", shellQuote(options.modelParametersJson)]),
3056
3448
  ...(apiBaseUrl === null ? [] : ["--api-base-url", shellQuote(apiBaseUrl)]),
3057
3449
  "--json",
3058
3450
  ].join(" ");
@@ -3107,12 +3499,18 @@ function renderInputImageGuideCommand(input) {
3107
3499
  "edit",
3108
3500
  ...(input.dryRun ? ["--dry-run"] : []),
3109
3501
  "--input",
3110
- "image_...",
3502
+ input.inputReference?.trim()
3503
+ ? shellQuote(input.inputReference.trim())
3504
+ : "image_...",
3111
3505
  "--model",
3112
3506
  shellQuote(input.modelId),
3113
3507
  ...(promptless ? [] : ["--prompt", shellQuote(input.prompt)]),
3114
3508
  "--max-estimated-usd-per-image",
3115
3509
  shellQuote(formatUsd(input.budgetGuard)),
3510
+ ...(input.modelParametersJson === null ||
3511
+ input.modelParametersJson === undefined
3512
+ ? []
3513
+ : ["--model-parameters-json", shellQuote(input.modelParametersJson)]),
3116
3514
  ...(input.idempotencyKey === undefined || input.idempotencyKey === null
3117
3515
  ? []
3118
3516
  : ["--idempotency-key", shellQuote(input.idempotencyKey)]),
@@ -3142,6 +3540,10 @@ function renderCreateCommand(input) {
3142
3540
  : ["--aspect-ratio", shellQuote(input.aspectRatio)]),
3143
3541
  "--max-estimated-usd-per-image",
3144
3542
  shellQuote(formatUsd(input.budgetGuard)),
3543
+ ...(input.modelParametersJson === null ||
3544
+ input.modelParametersJson === undefined
3545
+ ? []
3546
+ : ["--model-parameters-json", shellQuote(input.modelParametersJson)]),
3145
3547
  ...(input.idempotencyKey === undefined || input.idempotencyKey === null
3146
3548
  ? []
3147
3549
  : ["--idempotency-key", shellQuote(input.idempotencyKey)]),
@@ -3377,6 +3779,12 @@ async function edit(argv) {
3377
3779
  const args = parseArgs(argv);
3378
3780
  const input = flagString(args, "input") ?? args.positionals[0];
3379
3781
  const modelId = flagString(args, "model");
3782
+ if (flagBool(args, "guide")) {
3783
+ return createGuide(args, {
3784
+ guideOperation: "edit",
3785
+ inputReference: input,
3786
+ });
3787
+ }
3380
3788
  if (input === undefined) {
3381
3789
  return invalid(
3382
3790
  "image-skill edit",