image-skill 0.1.27 → 0.1.29

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.
@@ -7,7 +7,7 @@ import { Readable } from "node:stream";
7
7
  import { pipeline } from "node:stream/promises";
8
8
  import os from "node:os";
9
9
 
10
- const VERSION = "0.1.27";
10
+ const VERSION = "0.1.29";
11
11
  const PACKAGE_NAME = "image-skill";
12
12
  const DEFAULT_API_BASE_URL = "https://api.image-skill.com";
13
13
  const DEFAULT_DOCS_BASE_URL = "https://image-skill.com";
@@ -27,13 +27,15 @@ const DEFAULT_CONFIG_PATH = join(
27
27
  "image-skill",
28
28
  "config.json",
29
29
  );
30
+ const LOCAL_WRITABLE_CONFIG_PATH = "$PWD/.image-skill/config.json";
30
31
  const SIGNUP_SUGGESTED_COMMAND =
31
32
  "image-skill signup --agent --agent-contact AGENT_OR_OPERATOR_INBOX --agent-name NAME --runtime RUNTIME --json";
32
33
  const SIGNUP_CONTACT_GUIDANCE =
33
34
  "Preview signup currently requires an email-shaped durable contact inbox, not an individual human email. Use an agent-owned inbox when available; otherwise use an operator, team, or sponsor inbox that can receive future claim, billing, or abuse notices. Do not block waiting for a person, invent a person, or use a throwaway inbox. --human-email remains a compatibility alias.";
34
35
  const HOSTED_SIGNUP_TOKEN_RETURNED_WARNING =
35
36
  "hosted restricted token is returned once; store it in the agent runtime secret store and never paste it into prompts, logs, issues, or product feedback";
36
- const PUBLIC_NPX_COMMAND_PREFIX = "npx -y image-skill@latest";
37
+ const PUBLIC_NPX_COMMAND_PREFIX =
38
+ "npm_config_update_notifier=false npx -y image-skill@latest";
37
39
  const CREDIT_UNIT_USD = 0.01;
38
40
  const PAYMENT_CREDENTIAL_FLAGS = new Set([
39
41
  "payment-token",
@@ -301,8 +303,8 @@ function commandHelpByKey(key) {
301
303
  usage:
302
304
  "image-skill credits quote --pack PACK_ID --payment-method stripe_x402.exact.usdc --json",
303
305
  docs_url: "https://image-skill.com/cli.md#image-skill-credits",
304
- required_flags: ["--pack or --credits"],
305
- optional_flags: ["--payment-method", "--idempotency-key"],
306
+ required_flags: ["--pack or --credits", "--payment-method"],
307
+ optional_flags: ["--idempotency-key"],
306
308
  },
307
309
  "credits buy": {
308
310
  command: "image-skill credits buy help",
@@ -378,6 +380,7 @@ function commandHelpByKey(key) {
378
380
  docs_url: "https://image-skill.com/cli.md#image-skill-edit",
379
381
  required_flags: ["--input"],
380
382
  optional_flags: [
383
+ "--dry-run",
381
384
  "--prompt",
382
385
  "--model",
383
386
  "--mask",
@@ -496,7 +499,7 @@ async function doctor(argv) {
496
499
  async function trust(argv) {
497
500
  const args = parseArgs(argv);
498
501
  const unsupportedFlags = [...args.flags.keys()].filter(
499
- (flag) => !["json", "api-base-url"].includes(flag),
502
+ (flag) => !["json", "api-base-url", "token", "token-stdin"].includes(flag),
500
503
  );
501
504
  if (args.positionals.length > 0 || unsupportedFlags.length > 0) {
502
505
  return invalid(
@@ -506,6 +509,13 @@ async function trust(argv) {
506
509
  : "trust does not accept positional arguments",
507
510
  );
508
511
  }
512
+ const tokenHandoff = await acceptNoAuthTokenHandoff(
513
+ args,
514
+ "image-skill trust",
515
+ );
516
+ if (tokenHandoff !== null) {
517
+ return tokenHandoff;
518
+ }
509
519
 
510
520
  const checkedAt = new Date().toISOString();
511
521
  const apiBaseUrl = apiBase(args);
@@ -972,12 +982,17 @@ async function credits(argv) {
972
982
  );
973
983
  }
974
984
  const idempotency = optionalIdempotencyKey(args, "quote");
975
- const paymentMethod =
976
- flagString(args, "payment-method") ?? "stripe_checkout";
985
+ const paymentMethod = flagString(args, "payment-method");
977
986
  const PUBLIC_QUOTE_PAYMENT_METHODS = [
978
987
  "stripe_checkout",
979
988
  "stripe_x402.exact.usdc",
980
989
  ];
990
+ if (paymentMethod === null) {
991
+ return invalid(
992
+ "image-skill credits quote",
993
+ "credits quote requires --payment-method from credits methods --json; use stripe_x402.exact.usdc for an agent-settleable browserless rail or stripe_checkout for a human Checkout handoff",
994
+ );
995
+ }
981
996
  if (!PUBLIC_QUOTE_PAYMENT_METHODS.includes(paymentMethod)) {
982
997
  return invalid(
983
998
  "image-skill credits quote",
@@ -1144,13 +1159,14 @@ async function models(argv) {
1144
1159
  query.message,
1145
1160
  );
1146
1161
  }
1147
- return apiRequest({
1162
+ const result = await apiRequest({
1148
1163
  command:
1149
1164
  subcommand === "list" ? "image-skill models list" : "image-skill models",
1150
1165
  method: "GET",
1151
1166
  apiBaseUrl: apiBase(args),
1152
1167
  path: query.path,
1153
1168
  });
1169
+ return flagBool(args, "summary") ? withModelSummary(result) : result;
1154
1170
  }
1155
1171
 
1156
1172
  function modelListQuery(args) {
@@ -1175,6 +1191,7 @@ function modelListQuery(args) {
1175
1191
  params.set("catalog_only", "true");
1176
1192
  }
1177
1193
  addQueryValue(params, "operation", flagString(args, "operation"));
1194
+ addQueryValue(params, "modality", flagString(args, "modality"));
1178
1195
  addQueryValue(params, "provider", flagString(args, "provider"));
1179
1196
  const query = params.toString();
1180
1197
  return {
@@ -1183,6 +1200,133 @@ function modelListQuery(args) {
1183
1200
  };
1184
1201
  }
1185
1202
 
1203
+ function withModelSummary(result) {
1204
+ const data = result.envelope.data;
1205
+ if (!isRecord(data) || !Array.isArray(data.models)) {
1206
+ return result;
1207
+ }
1208
+ return {
1209
+ ...result,
1210
+ envelope: {
1211
+ ...result.envelope,
1212
+ data: {
1213
+ ...data,
1214
+ summary: {
1215
+ ...(isRecord(data.summary) ? data.summary : {}),
1216
+ result_shape: "compact_model_summary",
1217
+ full_list_command: "image-skill models list --json",
1218
+ },
1219
+ models: data.models.map(modelSummaryRow),
1220
+ },
1221
+ },
1222
+ };
1223
+ }
1224
+
1225
+ function modelSummaryRow(model) {
1226
+ return {
1227
+ id: model.id,
1228
+ display_name: model.display_name,
1229
+ provider_id: model.provider_id,
1230
+ mode: model.mode,
1231
+ status: model.status,
1232
+ availability_reason: model.availability_reason ?? null,
1233
+ supports: Array.isArray(model.supports) ? [...model.supports] : [],
1234
+ operations: Array.isArray(model.operations) ? [...model.operations] : [],
1235
+ task_tags: modelSummaryTaskTags(model),
1236
+ estimated_usd_per_image: model.economics?.estimated_usd_per_image ?? null,
1237
+ credits_required: model.economics?.credit_pricing?.credits_required ?? null,
1238
+ pricing_confidence:
1239
+ model.economics?.credit_pricing?.pricing_confidence ?? null,
1240
+ cost_known: model.economics?.cost_known ?? false,
1241
+ budget_required_for_live:
1242
+ model.economics?.budget_required_for_live ?? false,
1243
+ max_outputs_per_request:
1244
+ model.media?.output?.max_outputs_per_request ?? null,
1245
+ max_resolution: model.media?.output?.max_resolution ?? null,
1246
+ artifact_storage: model.execution?.artifact_storage ?? null,
1247
+ model_execution_status: model.execution?.model_execution_status ?? null,
1248
+ grants_required: Array.isArray(model.capability?.grants_required)
1249
+ ? [...model.capability.grants_required]
1250
+ : [],
1251
+ show_command:
1252
+ typeof model.id === "string"
1253
+ ? `image-skill models show ${model.id} --json`
1254
+ : "image-skill models show MODEL_ID --json",
1255
+ };
1256
+ }
1257
+
1258
+ function modelSummaryTaskTags(model) {
1259
+ const tags = [];
1260
+ const seen = new Set();
1261
+ const add = (tag) => {
1262
+ if (!seen.has(tag)) {
1263
+ seen.add(tag);
1264
+ tags.push(tag);
1265
+ }
1266
+ };
1267
+ for (const support of Array.isArray(model.supports) ? model.supports : []) {
1268
+ add(support);
1269
+ }
1270
+ for (const operation of Array.isArray(model.operations)
1271
+ ? model.operations
1272
+ : []) {
1273
+ add(operationTag(operation));
1274
+ }
1275
+ for (const intent of Array.isArray(model.capability?.intents)
1276
+ ? model.capability.intents
1277
+ : []) {
1278
+ add(intent);
1279
+ }
1280
+ if (
1281
+ model.operations?.includes("image.generate") &&
1282
+ model.media?.input?.images?.required !== true &&
1283
+ model.media?.input?.references?.required !== true
1284
+ ) {
1285
+ add("text-to-image");
1286
+ }
1287
+ if (model.media?.input?.images?.required === true) {
1288
+ add("input-image");
1289
+ add("image-to-image");
1290
+ }
1291
+ if (model.media?.input?.mask?.supported === true) {
1292
+ add(model.media?.input?.mask?.required === true ? "mask-required" : "mask");
1293
+ }
1294
+ if (model.media?.input?.references?.supported === true) {
1295
+ add("reference-image");
1296
+ if ((model.media?.input?.references?.max ?? 0) > 1) {
1297
+ add("multi-reference");
1298
+ }
1299
+ }
1300
+ if (model.media?.output?.transparent_background === true) {
1301
+ add("transparent-background");
1302
+ }
1303
+ if ((model.media?.output?.max_outputs_per_request ?? 0) > 1) {
1304
+ add("multi-output");
1305
+ }
1306
+ if (
1307
+ String(model.id ?? "").includes("video") ||
1308
+ String(model.display_name ?? "")
1309
+ .toLowerCase()
1310
+ .includes("video")
1311
+ ) {
1312
+ add("video");
1313
+ }
1314
+ if (model.execution?.artifact_storage === "image_skill_owned") {
1315
+ add("downloadable");
1316
+ }
1317
+ return tags;
1318
+ }
1319
+
1320
+ function operationTag(operation) {
1321
+ if (operation === "image.generate") return "generate";
1322
+ if (operation === "image.edit") return "edit";
1323
+ if (operation === "image.variation") return "variation";
1324
+ if (operation === "image.upscale") return "upscale";
1325
+ if (operation === "image.utility") return "utility";
1326
+ if (operation === "image.vision") return "vision";
1327
+ return "inspect";
1328
+ }
1329
+
1186
1330
  function addQueryValue(params, name, value) {
1187
1331
  if (typeof value === "string" && value.trim().length > 0) {
1188
1332
  params.set(name, value.trim());
@@ -1284,9 +1428,12 @@ async function createGuide(args) {
1284
1428
  const selected =
1285
1429
  models.envelope.ok && models.envelope.data?.models
1286
1430
  ? selectCreateGuideModel(models.envelope.data.models, requestedModelId, {
1431
+ prompt: trimmedPrompt,
1432
+ intent: requestedIntent,
1287
1433
  maxEstimatedUsdPerImage,
1288
1434
  })
1289
1435
  : null;
1436
+ const selectedAspectRatio = createGuideSuggestedAspectRatio(selected);
1290
1437
  const pricing = selected?.economics?.credit_pricing ?? null;
1291
1438
  const estimatedCredits = pricing?.credits_required ?? null;
1292
1439
  const estimatedProviderUsdPerImage =
@@ -1310,6 +1457,9 @@ async function createGuide(args) {
1310
1457
  path: "/v1/quota",
1311
1458
  token: token.token,
1312
1459
  });
1460
+ const authenticated = quota?.envelope.data?.authenticated === true;
1461
+ const publicTokenSource =
1462
+ token.source === "anonymous" ? "none" : token.source;
1313
1463
  const paymentSummary = createGuidePaymentSummary(payments.envelope.data);
1314
1464
  const stage = createGuideStage({
1315
1465
  prompt: trimmedPrompt,
@@ -1333,11 +1483,50 @@ async function createGuide(args) {
1333
1483
  requestedProviderId,
1334
1484
  requestedIntent,
1335
1485
  budgetGuard,
1486
+ aspectRatio: selectedAspectRatio,
1336
1487
  apiBaseUrl: explicitApiBaseUrl(args),
1337
1488
  paymentSummary,
1338
1489
  commandPrefix: PUBLIC_NPX_COMMAND_PREFIX,
1339
1490
  authConfigWritable: authConfigWrite?.ok ?? true,
1340
1491
  });
1492
+ const escapeHatches = createGuideEscapeHatches({
1493
+ prompt: trimmedPrompt,
1494
+ selected,
1495
+ requestedProviderId,
1496
+ requestedIntent,
1497
+ budgetGuard,
1498
+ aspectRatio: selectedAspectRatio,
1499
+ apiBaseUrl: explicitApiBaseUrl(args),
1500
+ commandPrefix: PUBLIC_NPX_COMMAND_PREFIX,
1501
+ });
1502
+ const nextCommandEffect = createGuideNextCommandEffect(stage, {
1503
+ estimatedCredits,
1504
+ estimatedDebitUsdPerImage,
1505
+ });
1506
+ const noSpendNextCommand =
1507
+ stage === "ready_to_create" ? escapeHatches.dry_run : null;
1508
+ const noSpendNextCommandLabel =
1509
+ noSpendNextCommand === null
1510
+ ? null
1511
+ : "dry_run_plan_no_provider_call_no_credit_debit_no_media_write";
1512
+ const noSpendNextCommandEffect = createGuideNoSpendNextCommandEffect(stage, {
1513
+ estimatedCredits,
1514
+ estimatedDebitUsdPerImage,
1515
+ });
1516
+ const noSpendEvaluation = createGuideNoSpendEvaluation(stage, {
1517
+ noSpendNextCommand,
1518
+ noSpendNextCommandLabel,
1519
+ noSpendNextCommandEffect,
1520
+ });
1521
+ const guideWarning = createGuideWarning(stage, {
1522
+ nextCommandEffect,
1523
+ paymentSummary,
1524
+ });
1525
+ const selfFundNextCommand = stage === "quota_required" ? nextCommand : null;
1526
+ const selfFundNextCommandLabel = createGuideSelfFundNextCommandLabel(
1527
+ stage,
1528
+ paymentSummary,
1529
+ );
1341
1530
  const afterNext =
1342
1531
  stage === "auth_required" || stage === "quota_required"
1343
1532
  ? renderGuideCommand(
@@ -1352,7 +1541,18 @@ async function createGuide(args) {
1352
1541
  afterNext,
1353
1542
  authConfigWrite,
1354
1543
  });
1355
- return success("image-skill create --guide", {
1544
+ const authReady = createGuideAuthReady(stage, {
1545
+ authenticated,
1546
+ tokenSource: publicTokenSource,
1547
+ savedConfigPath: configPath(),
1548
+ });
1549
+ const selfFundHandoff = createGuideSelfFundHandoff(stage, {
1550
+ paymentSummary,
1551
+ nextCommand,
1552
+ afterNext,
1553
+ tokenSource: publicTokenSource,
1554
+ });
1555
+ return createGuideSuccess(quota?.envelope.actor ?? null, {
1356
1556
  schema: "image-skill.create-guide.v1",
1357
1557
  ready: stage === "ready_to_create",
1358
1558
  stage,
@@ -1364,8 +1564,8 @@ async function createGuide(args) {
1364
1564
  error_code: health.envelope.error?.code ?? null,
1365
1565
  },
1366
1566
  auth: {
1367
- source: token.source === "anonymous" ? "none" : token.source,
1368
- authenticated: quota?.envelope.data?.authenticated === true,
1567
+ source: publicTokenSource,
1568
+ authenticated,
1369
1569
  claim_state: quota?.envelope.data?.claim_state ?? null,
1370
1570
  token_status: quota?.envelope.data?.token_status ?? null,
1371
1571
  saved_config_path: configPath(),
@@ -1404,14 +1604,24 @@ async function createGuide(args) {
1404
1604
  selected === null
1405
1605
  ? null
1406
1606
  : {
1407
- operation: "create",
1607
+ operation: createGuideSelectedModelRequiresInputImage(selected)
1608
+ ? "edit"
1609
+ : "create",
1408
1610
  model_id: selected.id,
1409
1611
  model_status: selected.status,
1410
1612
  model_execution_status: selected.execution.model_execution_status,
1613
+ modality: selected.modality ?? null,
1614
+ suggested_aspect_ratio: selectedAspectRatio,
1411
1615
  reason:
1412
1616
  requestedModelId === null
1413
- ? "default executable create model for first image"
1414
- : "requested executable create model",
1617
+ ? createGuideSelectionReason(
1618
+ selected,
1619
+ trimmedPrompt,
1620
+ requestedIntent,
1621
+ )
1622
+ : createGuideSelectedModelRequiresInputImage(selected)
1623
+ ? "requested executable image-to-3D model"
1624
+ : "requested executable create model",
1415
1625
  },
1416
1626
  cost: {
1417
1627
  estimated_credits: estimatedCredits,
@@ -1422,49 +1632,23 @@ async function createGuide(args) {
1422
1632
  pricing_confidence: pricing?.pricing_confidence ?? null,
1423
1633
  },
1424
1634
  blocker,
1635
+ guide_warning: guideWarning,
1636
+ auth_ready: authReady,
1425
1637
  next_command: nextCommand,
1638
+ next_command_effect: nextCommandEffect,
1639
+ no_spend_next_command: noSpendNextCommand,
1640
+ no_spend_next_command_label: noSpendNextCommandLabel,
1641
+ no_spend_next_command_effect: noSpendNextCommandEffect,
1642
+ no_spend_evaluation: noSpendEvaluation,
1643
+ recommended_no_spend_command: noSpendNextCommand,
1644
+ recommended_no_spend_command_label: noSpendNextCommandLabel,
1645
+ recommended_no_spend_command_effect: noSpendNextCommandEffect,
1646
+ self_fund_next_command: selfFundNextCommand,
1647
+ self_fund_next_command_label: selfFundNextCommandLabel,
1648
+ self_fund_handoff: selfFundHandoff,
1426
1649
  after_next: afterNext,
1427
1650
  auth_handoff: authHandoff,
1428
- escape_hatches: {
1429
- doctor: renderGuidePrefixedCommand(
1430
- PUBLIC_NPX_COMMAND_PREFIX,
1431
- "doctor --json",
1432
- ),
1433
- model_inspection:
1434
- selected === null
1435
- ? renderGuidePrefixedCommand(
1436
- PUBLIC_NPX_COMMAND_PREFIX,
1437
- "models list --json",
1438
- )
1439
- : renderGuidePrefixedCommand(
1440
- PUBLIC_NPX_COMMAND_PREFIX,
1441
- `models show ${shellQuote(selected.id)} --json`,
1442
- ),
1443
- payment_methods: renderGuidePrefixedCommand(
1444
- PUBLIC_NPX_COMMAND_PREFIX,
1445
- "credits methods --json",
1446
- ),
1447
- quota: renderGuidePrefixedCommand(
1448
- PUBLIC_NPX_COMMAND_PREFIX,
1449
- "usage quota --json",
1450
- ),
1451
- dry_run:
1452
- selected === null || trimmedPrompt.length === 0
1453
- ? renderGuidePrefixedCommand(
1454
- PUBLIC_NPX_COMMAND_PREFIX,
1455
- "create --dry-run --prompt PROMPT --json",
1456
- )
1457
- : renderCreateCommand({
1458
- prompt: trimmedPrompt,
1459
- modelId: selected.id,
1460
- providerId: requestedProviderId,
1461
- intent: requestedIntent,
1462
- budgetGuard,
1463
- dryRun: true,
1464
- apiBaseUrl: explicitApiBaseUrl(args),
1465
- commandPrefix: PUBLIC_NPX_COMMAND_PREFIX,
1466
- }),
1467
- },
1651
+ escape_hatches: escapeHatches,
1468
1652
  mutation: {
1469
1653
  provider_call: false,
1470
1654
  hosted_create: false,
@@ -1476,31 +1660,126 @@ async function createGuide(args) {
1476
1660
  });
1477
1661
  }
1478
1662
 
1663
+ function createGuideSuccess(actor, data) {
1664
+ const result = success("image-skill create --guide", data);
1665
+ result.envelope.actor = actor;
1666
+ return result;
1667
+ }
1668
+
1479
1669
  function selectCreateGuideModel(
1480
1670
  models,
1481
1671
  requestedModelId,
1482
- { maxEstimatedUsdPerImage = null } = {},
1672
+ { prompt = "", intent = undefined, maxEstimatedUsdPerImage = null } = {},
1483
1673
  ) {
1484
1674
  const isExecutableCreate = (model) =>
1485
1675
  model?.status === "available" &&
1486
1676
  model?.execution?.model_execution_status === "executable" &&
1487
1677
  Array.isArray(model?.supports) &&
1488
1678
  model.supports.includes("create");
1679
+ const isExecutableImageTo3d = (model) =>
1680
+ model?.status === "available" &&
1681
+ model?.execution?.model_execution_status === "executable" &&
1682
+ model?.modality === "3d" &&
1683
+ Array.isArray(model?.supports) &&
1684
+ model.supports.includes("variation") &&
1685
+ createGuideSelectedModelRequiresInputImage(model);
1686
+ const isExecutableGuideModel = (model) =>
1687
+ isExecutableCreate(model) || isExecutableImageTo3d(model);
1489
1688
  if (requestedModelId !== null) {
1490
1689
  const requested = models.find((model) => model.id === requestedModelId);
1491
- return requested !== undefined && isExecutableCreate(requested)
1690
+ return requested !== undefined && isExecutableGuideModel(requested)
1492
1691
  ? requested
1493
1692
  : null;
1494
1693
  }
1495
1694
  const candidates = models.filter(isExecutableCreate);
1695
+ if (createGuideImplies3d({ prompt, intent })) {
1696
+ const eligible3d = guideCandidatesWithinBudget({
1697
+ candidates: models.filter(isExecutableImageTo3d),
1698
+ maxEstimatedUsdPerImage,
1699
+ });
1700
+ const threeDimensional = eligible3d[0];
1701
+ if (threeDimensional !== undefined) {
1702
+ return threeDimensional;
1703
+ }
1704
+ }
1705
+ const eligible = guideCandidatesWithinBudget({
1706
+ candidates,
1707
+ maxEstimatedUsdPerImage,
1708
+ });
1709
+ if (createGuideImpliesAudio({ prompt, intent })) {
1710
+ const audio = eligible.find((model) => model?.modality === "audio");
1711
+ if (audio !== undefined) {
1712
+ return audio;
1713
+ }
1714
+ }
1715
+ if (createGuideImpliesVideo({ prompt, intent })) {
1716
+ const video = eligible.find((model) => model?.modality === "video");
1717
+ if (video !== undefined) {
1718
+ return video;
1719
+ }
1720
+ }
1721
+ const intentClass = createGuideIntentClass(intent);
1722
+ for (const modelId of preferredCreateGuideModelIds(intentClass)) {
1723
+ const preferred = eligible.find((model) => model?.id === modelId);
1724
+ if (preferred !== undefined) {
1725
+ return preferred;
1726
+ }
1727
+ }
1728
+ return eligible[0] ?? null;
1729
+ }
1730
+
1731
+ function guideCandidatesWithinBudget({
1732
+ candidates,
1733
+ maxEstimatedUsdPerImage = null,
1734
+ }) {
1496
1735
  if (maxEstimatedUsdPerImage === null) {
1497
- return candidates[0] ?? null;
1736
+ return candidates;
1498
1737
  }
1499
1738
  const capped = candidates.filter((model) => {
1500
1739
  const estimatedUsd = guideBudgetUsdForModel(model);
1501
1740
  return estimatedUsd === null || estimatedUsd <= maxEstimatedUsdPerImage;
1502
1741
  });
1503
- return (capped.length === 0 ? candidates : capped)[0] ?? null;
1742
+ return capped.length === 0 ? candidates : capped;
1743
+ }
1744
+
1745
+ function createGuideIntentClass(intent) {
1746
+ const normalized = String(intent ?? "")
1747
+ .trim()
1748
+ .toLowerCase();
1749
+ if (["cheap", "budget", "draft", "test"].includes(normalized)) {
1750
+ return "budget_draft";
1751
+ }
1752
+ if (
1753
+ [
1754
+ "final",
1755
+ "finalize",
1756
+ "hero",
1757
+ "product",
1758
+ "product-shot",
1759
+ "campaign",
1760
+ "publication",
1761
+ "deliverable",
1762
+ ].includes(normalized)
1763
+ ) {
1764
+ return "final";
1765
+ }
1766
+ return "general";
1767
+ }
1768
+
1769
+ function preferredCreateGuideModelIds(intentClass) {
1770
+ return intentClass === "budget_draft"
1771
+ ? [
1772
+ "fal.flux-dev",
1773
+ "xai.grok-imagine-image-quality",
1774
+ "xai.grok-imagine-image",
1775
+ "openai.gpt-image-2",
1776
+ ]
1777
+ : [
1778
+ "xai.grok-imagine-image-quality",
1779
+ "fal.flux-dev",
1780
+ "xai.grok-imagine-image",
1781
+ "openai.gpt-image-2",
1782
+ ];
1504
1783
  }
1505
1784
 
1506
1785
  function guideBudgetUsdForModel(model) {
@@ -1514,6 +1793,81 @@ function guideBudgetUsdForModel(model) {
1514
1793
  );
1515
1794
  }
1516
1795
 
1796
+ function createGuideImplies3d(input) {
1797
+ const searchable =
1798
+ `${input?.intent ?? ""} ${input?.prompt ?? ""}`.toLowerCase();
1799
+ return /\b(?:glb|gltf|mesh|model\s+asset|asset\s+model|textured\s+model|image-to-3d|(?:3d|three-d)\s+(?:\w+\s+){0,3}(?:model|asset|mesh|object)|(?:model|asset|mesh|object)\s+(?:in|as)\s+(?:3d|three-d))\b/.test(
1800
+ searchable,
1801
+ );
1802
+ }
1803
+
1804
+ function createGuideImpliesAudio(input) {
1805
+ const searchable =
1806
+ `${input?.intent ?? ""} ${input?.prompt ?? ""}`.toLowerCase();
1807
+ if (/\bmusic\s+video\b/.test(searchable)) {
1808
+ return false;
1809
+ }
1810
+ return /\b(?:audio|sound|music|soundtrack|wav|text-to-audio)\b/.test(
1811
+ searchable,
1812
+ );
1813
+ }
1814
+
1815
+ function createGuideImpliesVideo(input) {
1816
+ const searchable =
1817
+ `${input?.intent ?? ""} ${input?.prompt ?? ""}`.toLowerCase();
1818
+ return /\b(?:video|clip|footage|animation|animated|mp4|b-roll|timelapse|time-lapse)\b/.test(
1819
+ searchable,
1820
+ );
1821
+ }
1822
+
1823
+ function createGuideSuggestedAspectRatio(model) {
1824
+ if (model?.modality !== "video") {
1825
+ return null;
1826
+ }
1827
+ const values = model?.media?.input?.aspect_ratios?.values;
1828
+ if (!Array.isArray(values)) {
1829
+ return null;
1830
+ }
1831
+ return values.includes("16:9") ? "16:9" : (values[0] ?? null);
1832
+ }
1833
+
1834
+ function createGuideSelectedModelRequiresInputImage(model) {
1835
+ return (
1836
+ model?.modality === "3d" && model?.media?.input?.images?.required === true
1837
+ );
1838
+ }
1839
+
1840
+ function createGuideSelectionReason(model, prompt, intent) {
1841
+ if (
1842
+ createGuideSelectedModelRequiresInputImage(model) &&
1843
+ createGuideImplies3d({ prompt, intent })
1844
+ ) {
1845
+ return "3D intent matched executable image-to-3D model; provide one Image Skill-owned image_... input asset";
1846
+ }
1847
+ if (
1848
+ model?.modality === "audio" &&
1849
+ createGuideImpliesAudio({ prompt, intent })
1850
+ ) {
1851
+ return "audio intent matched executable audio create model";
1852
+ }
1853
+ if (
1854
+ model?.modality === "video" &&
1855
+ createGuideImpliesVideo({ prompt, intent })
1856
+ ) {
1857
+ return "video intent matched executable video create model";
1858
+ }
1859
+ if (
1860
+ preferredCreateGuideModelIds(createGuideIntentClass(intent)).includes(
1861
+ model?.id,
1862
+ )
1863
+ ) {
1864
+ return createGuideIntentClass(intent) === "budget_draft"
1865
+ ? "guide selected a draft/budget create model with high-definition defaults"
1866
+ : "guide selected the strongest currently available quality-first create model for this intent";
1867
+ }
1868
+ return "guide selected the first available executable create model";
1869
+ }
1870
+
1517
1871
  function createGuidePaymentSummary(data) {
1518
1872
  const methods = Array.isArray(data?.methods)
1519
1873
  ? data.methods.filter((method) => method.live_money)
@@ -1562,6 +1916,10 @@ function createGuidePaymentSummary(data) {
1562
1916
  (method) => method.method_id,
1563
1917
  ),
1564
1918
  preferred_method: preferredMethod?.method_id ?? null,
1919
+ preferred_method_summary:
1920
+ preferredMethod === undefined
1921
+ ? null
1922
+ : createGuidePreferredPaymentSummary(preferredMethod),
1565
1923
  buyer_modes: [
1566
1924
  ...new Set(methods.flatMap((method) => method.buyer_modes ?? [])),
1567
1925
  ],
@@ -1572,6 +1930,64 @@ function createGuidePaymentSummary(data) {
1572
1930
  };
1573
1931
  }
1574
1932
 
1933
+ function createGuidePreferredPaymentSummary(method) {
1934
+ const buyerModes = Array.isArray(method.buyer_modes)
1935
+ ? method.buyer_modes.filter((mode) => typeof mode === "string")
1936
+ : [];
1937
+ const liveMoney = method.live_money !== false;
1938
+ const browserless = method.requires_browser === false;
1939
+ const requiresBrowser = method.requires_browser === true;
1940
+ const agentInitiated = method.agent_initiated === true;
1941
+ const agentSettleable = method.agent_settleable === true;
1942
+ const humanHandoffRequired =
1943
+ requiresBrowser || buyerModes.includes("human_only");
1944
+ const topUpPath =
1945
+ agentSettleable && browserless
1946
+ ? "browserless_agent_self_fund"
1947
+ : humanHandoffRequired
1948
+ ? "human_payment_handoff"
1949
+ : "payment_method_inspection";
1950
+ return {
1951
+ method_id: method.method_id,
1952
+ live_money: liveMoney,
1953
+ requires_browser: requiresBrowser,
1954
+ browserless,
1955
+ agent_initiated: agentInitiated,
1956
+ agent_settleable: agentSettleable,
1957
+ human_handoff_required: humanHandoffRequired,
1958
+ buyer_modes: buyerModes,
1959
+ settlement_blocker:
1960
+ typeof method.settlement_blocker === "string"
1961
+ ? method.settlement_blocker
1962
+ : null,
1963
+ default_pack_id:
1964
+ typeof method.default_pack_id === "string"
1965
+ ? method.default_pack_id
1966
+ : null,
1967
+ min_amount_cents:
1968
+ typeof method.limits?.min_amount_cents === "number"
1969
+ ? method.limits.min_amount_cents
1970
+ : null,
1971
+ max_amount_cents:
1972
+ typeof method.limits?.max_amount_cents === "number"
1973
+ ? method.limits.max_amount_cents
1974
+ : null,
1975
+ top_up_path: topUpPath,
1976
+ next_step:
1977
+ topUpPath === "browserless_agent_self_fund"
1978
+ ? "quote_buy_status_then_rerun_after_next"
1979
+ : topUpPath === "human_payment_handoff"
1980
+ ? "quote_buy_open_checkout_status_then_rerun_after_next"
1981
+ : "inspect_credits_methods",
1982
+ warning:
1983
+ topUpPath === "browserless_agent_self_fund"
1984
+ ? "Preferred rail is browserless live money that a wallet-equipped agent can initiate and settle inside delegated caps."
1985
+ : topUpPath === "human_payment_handoff"
1986
+ ? "Preferred rail starts a live-money payment handoff and requires human or browser completion before credits are granted."
1987
+ : "Preferred payment rail needs inspection before an agent can choose the next top-up action.",
1988
+ };
1989
+ }
1990
+
1575
1991
  function createGuidePaymentCommands(preferredMethod, fallbackMethods) {
1576
1992
  const commands = [
1577
1993
  "image-skill credits methods --json",
@@ -1676,6 +2092,10 @@ function createGuideBlocker(stage, input) {
1676
2092
  function createGuideAuthHandoff(stage, input) {
1677
2093
  if (stage === "auth_required") {
1678
2094
  const authConfigWritable = input.authConfigWrite?.ok ?? true;
2095
+ const recovery =
2096
+ input.authConfigWrite?.ok === false
2097
+ ? configWriteRecovery("image-skill create --guide")
2098
+ : null;
1679
2099
  return {
1680
2100
  required: true,
1681
2101
  token_source: "none",
@@ -1683,16 +2103,21 @@ function createGuideAuthHandoff(stage, input) {
1683
2103
  accepted_methods: ["IMAGE_SKILL_TOKEN", "--token-stdin", "config"],
1684
2104
  signup: {
1685
2105
  returns_token_once: true,
1686
- public_cli_saves_config: authConfigWritable,
2106
+ public_cli_saves_config: true,
1687
2107
  store_token_in: authConfigWritable
1688
2108
  ? "public_cli_config_by_default"
1689
- : "agent_runtime_secret_store",
2109
+ : "public_cli_config_after_setting_IMAGE_SKILL_CONFIG_PATH",
1690
2110
  config_path: configPath(),
1691
2111
  config_writable: authConfigWritable,
1692
- recovery:
1693
- input.authConfigWrite?.ok === false
1694
- ? configWriteRecovery("image-skill create --guide")
1695
- : null,
2112
+ preferred_save_config:
2113
+ recovery === null
2114
+ ? null
2115
+ : {
2116
+ config_path_env: recovery.config_path_env,
2117
+ config_path: recovery.suggested_config_path,
2118
+ command: input.nextCommand,
2119
+ },
2120
+ recovery,
1696
2121
  },
1697
2122
  rerun_guide:
1698
2123
  input.afterNext === null
@@ -1704,7 +2129,7 @@ function createGuideAuthHandoff(stage, input) {
1704
2129
  next_command: null,
1705
2130
  };
1706
2131
  }
1707
- if (stage === "ready_to_create") {
2132
+ if (stage === "quota_required" || stage === "ready_to_create") {
1708
2133
  return {
1709
2134
  required: true,
1710
2135
  token_source: input.tokenSource,
@@ -1723,6 +2148,344 @@ function createGuideAuthHandoff(stage, input) {
1723
2148
  return null;
1724
2149
  }
1725
2150
 
2151
+ function createGuideAuthReady(stage, input) {
2152
+ const nextCommandRequiresAuth =
2153
+ stage === "quota_required" || stage === "ready_to_create";
2154
+ const ready = input.authenticated;
2155
+ return {
2156
+ ready,
2157
+ authenticated: input.authenticated,
2158
+ source: input.tokenSource,
2159
+ saved_config_path: input.savedConfigPath,
2160
+ next_command_requires_auth: nextCommandRequiresAuth,
2161
+ next_command_auth_ready: nextCommandRequiresAuth ? ready : null,
2162
+ secret_value_included: false,
2163
+ accepted_methods: ["config", "IMAGE_SKILL_TOKEN", "--token-stdin"],
2164
+ warning: ready
2165
+ ? "Current hosted auth is ready; data.next_command can reuse this auth context without exposing a raw token."
2166
+ : stage === "auth_required"
2167
+ ? "Auth is not ready yet; run data.next_command to create a restricted agent identity, then rerun the guide."
2168
+ : null,
2169
+ };
2170
+ }
2171
+
2172
+ function createGuideSelfFundNextCommandLabel(stage, paymentSummary) {
2173
+ if (stage !== "quota_required") {
2174
+ return null;
2175
+ }
2176
+ const preferredMethod = paymentSummary.preferred_method;
2177
+ if (
2178
+ preferredMethod !== null &&
2179
+ paymentSummary.browserless_methods.includes(preferredMethod) &&
2180
+ paymentSummary.agent_settleable_methods.includes(preferredMethod)
2181
+ ) {
2182
+ return "browserless_agent_payable_quote";
2183
+ }
2184
+ if (
2185
+ preferredMethod !== null &&
2186
+ paymentSummary.human_handoff_methods.includes(preferredMethod)
2187
+ ) {
2188
+ return "human_handoff_payment_quote";
2189
+ }
2190
+ return "payment_or_quota_action";
2191
+ }
2192
+
2193
+ function createGuideSelfFundHandoff(stage, input) {
2194
+ if (stage !== "quota_required") {
2195
+ return null;
2196
+ }
2197
+ const preferredMethod = input.paymentSummary.preferred_method;
2198
+ const browserless =
2199
+ preferredMethod !== null &&
2200
+ input.paymentSummary.browserless_methods.includes(preferredMethod);
2201
+ const agentInitiated =
2202
+ preferredMethod !== null &&
2203
+ input.paymentSummary.agent_initiated_methods.includes(preferredMethod);
2204
+ const agentSettleable =
2205
+ preferredMethod !== null &&
2206
+ input.paymentSummary.agent_settleable_methods.includes(preferredMethod);
2207
+ const humanHandoffRequired =
2208
+ preferredMethod !== null &&
2209
+ input.paymentSummary.human_handoff_methods.includes(preferredMethod);
2210
+ const statusCommand = guidePaymentCommandByKind(
2211
+ input.paymentSummary.suggested_commands,
2212
+ "status",
2213
+ );
2214
+
2215
+ return {
2216
+ required: true,
2217
+ preferred_method: preferredMethod,
2218
+ live_money:
2219
+ preferredMethod !== null &&
2220
+ input.paymentSummary.live_money_methods.includes(preferredMethod),
2221
+ browserless,
2222
+ agent_initiated: agentInitiated,
2223
+ agent_settleable: agentSettleable,
2224
+ human_handoff_required: humanHandoffRequired,
2225
+ payment_commands: {
2226
+ quote: guidePaymentCommandByKind(
2227
+ input.paymentSummary.suggested_commands,
2228
+ "quote",
2229
+ ),
2230
+ buy: guidePaymentCommandByKind(
2231
+ input.paymentSummary.suggested_commands,
2232
+ "buy",
2233
+ ),
2234
+ status: statusCommand,
2235
+ },
2236
+ wallet_settlement: createGuideWalletSettlementHandoff({
2237
+ preferredMethod,
2238
+ browserless,
2239
+ agentSettleable,
2240
+ statusCommand,
2241
+ }),
2242
+ after_next: input.afterNext,
2243
+ auth: {
2244
+ token_source: input.tokenSource,
2245
+ secret_value_included: false,
2246
+ accepted_methods: ["IMAGE_SKILL_TOKEN", "--token-stdin", "config"],
2247
+ next_command: {
2248
+ requires_auth: true,
2249
+ reuse_current_auth_context: input.tokenSource,
2250
+ with_env: `IMAGE_SKILL_TOKEN="$IMAGE_SKILL_TOKEN" ${input.nextCommand}`,
2251
+ with_stdin: renderTokenStdinCommand(input.nextCommand),
2252
+ },
2253
+ },
2254
+ warning: agentSettleable
2255
+ ? "data.self_fund_next_command starts a browserless live-money quote. Preserve auth with data.self_fund_handoff.auth.next_command, then follow payment_commands.buy, pay exactly what wallet_settlement points to, run payment_commands.status, and rerun after_next."
2256
+ : "data.self_fund_next_command starts a live-money payment handoff. Preserve auth with data.self_fund_handoff.auth.next_command, complete the payment, then rerun after_next.",
2257
+ };
2258
+ }
2259
+
2260
+ function createGuideWalletSettlementHandoff({
2261
+ preferredMethod,
2262
+ browserless,
2263
+ agentSettleable,
2264
+ statusCommand,
2265
+ }) {
2266
+ if (
2267
+ preferredMethod !== "stripe_x402.exact.usdc" ||
2268
+ !browserless ||
2269
+ !agentSettleable
2270
+ ) {
2271
+ return null;
2272
+ }
2273
+ return {
2274
+ method_id: "stripe_x402.exact.usdc",
2275
+ wallet_required: true,
2276
+ browser_required: false,
2277
+ network: "base",
2278
+ token_currency: "usdc",
2279
+ exact_amount_required: true,
2280
+ secret_value_included: false,
2281
+ payable_instructions_fields: {
2282
+ buy_response: "data.stripe_x402.payable_instructions",
2283
+ status_response: "data.payment_attempt.stripe_x402.payable_instructions",
2284
+ },
2285
+ amount_atomic_field: "token_amount_atomic",
2286
+ destination_field: "deposit_address",
2287
+ status_command_after_payment: statusCommand,
2288
+ next_step:
2289
+ "Run payment_commands.buy, pay payable_instructions.token_amount_atomic USDC atomic units to payable_instructions.deposit_address on Base from a delegated wallet, then run status_command_after_payment until credits are granted before rerunning after_next.",
2290
+ credential_boundary:
2291
+ "Never send wallet private keys, seed phrases, x402 authorization payloads, Stripe secrets, client secrets, card data, provider receipts, or raw wallet credentials to Image Skill.",
2292
+ warning:
2293
+ "This is live money. Pay exactly the returned Base/USDC amount to the returned deposit address and stay within the delegated cap.",
2294
+ };
2295
+ }
2296
+
2297
+ function createGuideNextCommandEffect(stage, input) {
2298
+ const base = {
2299
+ label: "read_only_or_no_media_setup",
2300
+ no_spend: true,
2301
+ provider_call: false,
2302
+ hosted_create: false,
2303
+ hosted_signup: false,
2304
+ payment_object: false,
2305
+ credit_debit: false,
2306
+ media_write: false,
2307
+ estimated_credits: null,
2308
+ estimated_debit_usd_per_image: null,
2309
+ warning: null,
2310
+ };
2311
+ if (stage === "auth_required") {
2312
+ return {
2313
+ ...base,
2314
+ label: "hosted_signup_restricted_agent_identity",
2315
+ hosted_signup: true,
2316
+ warning:
2317
+ "This signs up a restricted Image Skill agent identity but does not create media, call a provider, open payment, or debit credits.",
2318
+ };
2319
+ }
2320
+ if (stage === "quota_required") {
2321
+ return {
2322
+ ...base,
2323
+ label: "payment_or_quota_action",
2324
+ no_spend: false,
2325
+ payment_object: true,
2326
+ warning:
2327
+ "This may create or inspect a payment quote/attempt. Stay within the delegated cap, or use escape_hatches for read-only checks.",
2328
+ };
2329
+ }
2330
+ if (stage === "ready_to_create") {
2331
+ return {
2332
+ label: "live_media_create_credit_debit",
2333
+ no_spend: false,
2334
+ provider_call: true,
2335
+ hosted_create: true,
2336
+ hosted_signup: false,
2337
+ payment_object: false,
2338
+ credit_debit: true,
2339
+ media_write: true,
2340
+ estimated_credits: input.estimatedCredits,
2341
+ estimated_debit_usd_per_image: input.estimatedDebitUsdPerImage,
2342
+ warning:
2343
+ "data.next_command creates hosted media and can debit credits. For no-spend verification, run data.recommended_no_spend_command (same value as data.no_spend_next_command) instead.",
2344
+ };
2345
+ }
2346
+ if (stage === "prompt_required") {
2347
+ return {
2348
+ ...base,
2349
+ label: "rerun_guide_with_prompt",
2350
+ };
2351
+ }
2352
+ return base;
2353
+ }
2354
+
2355
+ function createGuideNoSpendNextCommandEffect(stage, input) {
2356
+ if (stage !== "ready_to_create") {
2357
+ return null;
2358
+ }
2359
+ return {
2360
+ label:
2361
+ "dry_run_planned_job_no_provider_call_no_credit_debit_no_media_write",
2362
+ no_spend: true,
2363
+ provider_call: false,
2364
+ hosted_create: false,
2365
+ hosted_create_dry_run: true,
2366
+ hosted_signup: false,
2367
+ payment_object: false,
2368
+ credit_debit: false,
2369
+ media_write: false,
2370
+ planned_job: true,
2371
+ plan_receipt: true,
2372
+ activity_event: "job.planned",
2373
+ estimated_credits: input.estimatedCredits,
2374
+ estimated_debit_usd_per_image: input.estimatedDebitUsdPerImage,
2375
+ warning:
2376
+ "data.no_spend_next_command may create a recoverable planned job/activity receipt (job.planned), but it does not call a provider, debit credits, or create downloadable media.",
2377
+ };
2378
+ }
2379
+
2380
+ function createGuideNoSpendEvaluation(stage, input) {
2381
+ if (stage !== "ready_to_create") {
2382
+ return {
2383
+ stop_here: false,
2384
+ stop_stage: "ready_to_create",
2385
+ stop_reason: null,
2386
+ next_command_is_live_create: false,
2387
+ live_create_command_field: null,
2388
+ live_create_allowed_when: null,
2389
+ recommended_command_field: null,
2390
+ recommended_command: null,
2391
+ recommended_command_label: null,
2392
+ recommended_command_effect: null,
2393
+ warning: null,
2394
+ };
2395
+ }
2396
+ return {
2397
+ stop_here: true,
2398
+ stop_stage: "ready_to_create",
2399
+ stop_reason:
2400
+ "ready_to_create means data.next_command is a live media create; no-spend evaluators should stop before it unless media spend is allowed.",
2401
+ next_command_is_live_create: true,
2402
+ live_create_command_field: "next_command",
2403
+ live_create_allowed_when: "media_spend_allowed",
2404
+ recommended_command_field: "recommended_no_spend_command",
2405
+ recommended_command: input.noSpendNextCommand,
2406
+ recommended_command_label: input.noSpendNextCommandLabel,
2407
+ recommended_command_effect: input.noSpendNextCommandEffect,
2408
+ warning:
2409
+ "For no-spend verification at ready_to_create, run data.recommended_no_spend_command instead of data.next_command.",
2410
+ };
2411
+ }
2412
+
2413
+ function createGuideWarning(stage, input) {
2414
+ const effect = input.nextCommandEffect;
2415
+ const base = {
2416
+ stage,
2417
+ no_spend_safe:
2418
+ effect.no_spend &&
2419
+ !effect.provider_call &&
2420
+ !effect.payment_object &&
2421
+ !effect.credit_debit &&
2422
+ !effect.media_write,
2423
+ live_money_action: false,
2424
+ spend_required: false,
2425
+ provider_call: effect.provider_call,
2426
+ payment_object: effect.payment_object,
2427
+ credit_debit: effect.credit_debit,
2428
+ media_write: effect.media_write,
2429
+ payment_top_up_path: null,
2430
+ };
2431
+
2432
+ if (stage === "prompt_required") {
2433
+ return {
2434
+ ...base,
2435
+ next_command_safety: "rerun_guide_no_spend",
2436
+ recommended_command_field: "next_command",
2437
+ warning:
2438
+ "data.next_command reruns the free guide with a real prompt; it does not call a provider, open payment, debit credits, or create media.",
2439
+ };
2440
+ }
2441
+ if (stage === "no_executable_model" || stage === "service_unreachable") {
2442
+ return {
2443
+ ...base,
2444
+ next_command_safety: "read_only_inspection_no_spend",
2445
+ recommended_command_field: "next_command",
2446
+ warning:
2447
+ "data.next_command is read-only inspection/recovery; it does not call a provider, open payment, debit credits, or create media.",
2448
+ };
2449
+ }
2450
+ if (stage === "auth_required") {
2451
+ return {
2452
+ ...base,
2453
+ next_command_safety: "hosted_signup_no_spend_setup",
2454
+ recommended_command_field: "next_command",
2455
+ warning:
2456
+ "data.next_command is no-spend hosted signup/setup; it creates a restricted agent identity but does not call a provider, open payment, debit credits, or create media.",
2457
+ };
2458
+ }
2459
+ if (stage === "quota_required") {
2460
+ const paymentTopUpPath =
2461
+ input.paymentSummary.preferred_method_summary?.top_up_path ?? null;
2462
+ return {
2463
+ ...base,
2464
+ next_command_safety: "live_money_payment_action",
2465
+ no_spend_safe: false,
2466
+ live_money_action: true,
2467
+ spend_required: true,
2468
+ recommended_command_field: "escape_hatches",
2469
+ payment_top_up_path: paymentTopUpPath,
2470
+ warning:
2471
+ paymentTopUpPath === "browserless_agent_self_fund"
2472
+ ? "data.next_command starts the browserless live-money top-up path; stay within the delegated cap, or use data.escape_hatches.payment_methods for read-only payment inspection."
2473
+ : paymentTopUpPath === "human_payment_handoff"
2474
+ ? "data.next_command starts a live-money payment handoff that needs human or browser completion; stay within the delegated cap, or use data.escape_hatches.payment_methods for read-only inspection."
2475
+ : "data.next_command starts payment or quota recovery; inspect data.checks.payments before attempting live money, or use data.escape_hatches.payment_methods for read-only inspection.",
2476
+ };
2477
+ }
2478
+ return {
2479
+ ...base,
2480
+ next_command_safety: "live_media_create_credit_debit",
2481
+ no_spend_safe: false,
2482
+ spend_required: true,
2483
+ recommended_command_field: "recommended_no_spend_command",
2484
+ warning:
2485
+ "data.next_command is a live media create that can call a provider, debit credits, and create media. Run it only when media spend is allowed; otherwise run data.recommended_no_spend_command.",
2486
+ };
2487
+ }
2488
+
1726
2489
  function createGuideNextCommand(stage, input) {
1727
2490
  if (stage === "prompt_required") {
1728
2491
  return renderGuideCommand("PROMPT", input.apiBaseUrl, input.commandPrefix);
@@ -1734,11 +2497,7 @@ function createGuideNextCommand(stage, input) {
1734
2497
  );
1735
2498
  }
1736
2499
  if (stage === "auth_required") {
1737
- const signupCommand =
1738
- input.authConfigWritable === false
1739
- ? "signup --agent --agent-contact AGENT_OR_OPERATOR_INBOX --agent-name AGENT_NAME --runtime RUNTIME_NAME --show-token --no-save --json"
1740
- : "signup --agent --agent-contact AGENT_OR_OPERATOR_INBOX --agent-name AGENT_NAME --runtime RUNTIME_NAME --json";
1741
- return renderGuidePrefixedCommand(input.commandPrefix, signupCommand);
2500
+ return renderGuideSignupCommand(input);
1742
2501
  }
1743
2502
  if (stage === "quota_required") {
1744
2503
  return renderGuidePrefixedCommand(
@@ -1748,12 +2507,23 @@ function createGuideNextCommand(stage, input) {
1748
2507
  ),
1749
2508
  );
1750
2509
  }
2510
+ if (createGuideSelectedModelRequiresInputImage(input.selected)) {
2511
+ return renderImageTo3dGuideCommand({
2512
+ modelId: input.selected.id,
2513
+ budgetGuard: input.budgetGuard,
2514
+ dryRun: false,
2515
+ idempotencyKey: `edit-guide-${Date.now()}-${randomBytes(4).toString("hex")}`,
2516
+ apiBaseUrl: input.apiBaseUrl,
2517
+ commandPrefix: input.commandPrefix,
2518
+ });
2519
+ }
1751
2520
  return renderCreateCommand({
1752
2521
  prompt: input.prompt,
1753
2522
  modelId: input.selected.id,
1754
2523
  providerId: input.requestedProviderId,
1755
2524
  intent: input.requestedIntent,
1756
2525
  budgetGuard: input.budgetGuard,
2526
+ aspectRatio: input.aspectRatio,
1757
2527
  dryRun: false,
1758
2528
  // Retry-safe by default (#1228): bake a stable idempotency key into the
1759
2529
  // advertised create command so an agent that copies it and retries after a
@@ -1764,6 +2534,52 @@ function createGuideNextCommand(stage, input) {
1764
2534
  });
1765
2535
  }
1766
2536
 
2537
+ function createGuideEscapeHatches(input) {
2538
+ return {
2539
+ doctor: renderGuidePrefixedCommand(input.commandPrefix, "doctor --json"),
2540
+ model_inspection:
2541
+ input.selected === null
2542
+ ? renderGuidePrefixedCommand(input.commandPrefix, "models list --json")
2543
+ : renderGuidePrefixedCommand(
2544
+ input.commandPrefix,
2545
+ `models show ${shellQuote(input.selected.id)} --json`,
2546
+ ),
2547
+ payment_methods: renderGuidePrefixedCommand(
2548
+ input.commandPrefix,
2549
+ "credits methods --json",
2550
+ ),
2551
+ quota: renderGuidePrefixedCommand(
2552
+ input.commandPrefix,
2553
+ "usage quota --json",
2554
+ ),
2555
+ dry_run:
2556
+ input.selected === null || input.prompt.length === 0
2557
+ ? renderGuidePrefixedCommand(
2558
+ input.commandPrefix,
2559
+ "create --dry-run --prompt PROMPT --json",
2560
+ )
2561
+ : createGuideSelectedModelRequiresInputImage(input.selected)
2562
+ ? renderImageTo3dGuideCommand({
2563
+ modelId: input.selected.id,
2564
+ budgetGuard: input.budgetGuard,
2565
+ dryRun: true,
2566
+ apiBaseUrl: input.apiBaseUrl,
2567
+ commandPrefix: input.commandPrefix,
2568
+ })
2569
+ : renderCreateCommand({
2570
+ prompt: input.prompt,
2571
+ modelId: input.selected.id,
2572
+ providerId: input.requestedProviderId,
2573
+ intent: input.requestedIntent,
2574
+ budgetGuard: input.budgetGuard,
2575
+ aspectRatio: input.aspectRatio,
2576
+ dryRun: true,
2577
+ apiBaseUrl: input.apiBaseUrl,
2578
+ commandPrefix: input.commandPrefix,
2579
+ }),
2580
+ };
2581
+ }
2582
+
1767
2583
  function renderGuideCommand(prompt, apiBaseUrl, commandPrefix = "image-skill") {
1768
2584
  return [
1769
2585
  commandPrefix,
@@ -1774,6 +2590,23 @@ function renderGuideCommand(prompt, apiBaseUrl, commandPrefix = "image-skill") {
1774
2590
  ].join(" ");
1775
2591
  }
1776
2592
 
2593
+ function renderGuideSignupCommand(input) {
2594
+ const signupCommand = [
2595
+ "signup --agent --agent-contact AGENT_OR_OPERATOR_INBOX --agent-name AGENT_NAME --runtime RUNTIME_NAME",
2596
+ ...(input.apiBaseUrl === null
2597
+ ? []
2598
+ : ["--api-base-url", shellQuote(input.apiBaseUrl)]),
2599
+ "--json",
2600
+ ].join(" ");
2601
+ const command = renderGuidePrefixedCommand(
2602
+ input.commandPrefix,
2603
+ signupCommand,
2604
+ );
2605
+ return input.authConfigWritable === false
2606
+ ? renderWritableConfigCommand(command)
2607
+ : command;
2608
+ }
2609
+
1777
2610
  function renderTokenStdinCommand(command) {
1778
2611
  return `printf '%s\\n' "$IMAGE_SKILL_TOKEN" | ${command} --token-stdin`;
1779
2612
  }
@@ -1788,6 +2621,37 @@ function firstPaymentActionCommand(commands) {
1788
2621
  );
1789
2622
  }
1790
2623
 
2624
+ function guidePaymentCommandByKind(commands, kind) {
2625
+ const pattern =
2626
+ kind === "quote"
2627
+ ? /\bcredits\s+quote\b/
2628
+ : kind === "buy"
2629
+ ? /\bcredits\s+buy\b/
2630
+ : /\bcredits\s+status\b/;
2631
+ return commands.find((command) => pattern.test(command)) ?? null;
2632
+ }
2633
+
2634
+ function renderImageTo3dGuideCommand(input) {
2635
+ return [
2636
+ input.commandPrefix ?? "image-skill",
2637
+ "edit",
2638
+ ...(input.dryRun ? ["--dry-run"] : []),
2639
+ "--input",
2640
+ "image_...",
2641
+ "--model",
2642
+ shellQuote(input.modelId),
2643
+ "--max-estimated-usd-per-image",
2644
+ shellQuote(formatUsd(input.budgetGuard)),
2645
+ ...(input.idempotencyKey === undefined || input.idempotencyKey === null
2646
+ ? []
2647
+ : ["--idempotency-key", shellQuote(input.idempotencyKey)]),
2648
+ ...(input.apiBaseUrl === null
2649
+ ? []
2650
+ : ["--api-base-url", shellQuote(input.apiBaseUrl)]),
2651
+ "--json",
2652
+ ].join(" ");
2653
+ }
2654
+
1791
2655
  function renderCreateCommand(input) {
1792
2656
  return [
1793
2657
  input.commandPrefix ?? "image-skill",
@@ -1802,6 +2666,9 @@ function renderCreateCommand(input) {
1802
2666
  shellQuote(input.prompt),
1803
2667
  "--intent",
1804
2668
  shellQuote(input.intent),
2669
+ ...(input.aspectRatio === null || input.aspectRatio === undefined
2670
+ ? []
2671
+ : ["--aspect-ratio", shellQuote(input.aspectRatio)]),
1805
2672
  "--max-estimated-usd-per-image",
1806
2673
  shellQuote(formatUsd(input.budgetGuard)),
1807
2674
  ...(input.idempotencyKey === undefined || input.idempotencyKey === null
@@ -1818,6 +2685,10 @@ function renderGuidePrefixedCommand(commandPrefix, command) {
1818
2685
  return `${commandPrefix} ${stripImageSkillCommandPrefix(command)}`;
1819
2686
  }
1820
2687
 
2688
+ function renderWritableConfigCommand(command) {
2689
+ return `IMAGE_SKILL_CONFIG_PATH="${LOCAL_WRITABLE_CONFIG_PATH}" ${command}`;
2690
+ }
2691
+
1821
2692
  function stripImageSkillCommandPrefix(command) {
1822
2693
  return String(command ?? "").replace(/^image-skill\s+/, "");
1823
2694
  }
@@ -2043,6 +2914,7 @@ async function edit(argv) {
2043
2914
  ...(modelParameters.value === null
2044
2915
  ? {}
2045
2916
  : { model_parameters: modelParameters.value }),
2917
+ ...(flagBool(args, "dry-run") ? { dry_run: true } : {}),
2046
2918
  // Retry-safe dedupe (#1228): see create — same key dedupes a retry that
2047
2919
  // follows a transient 502 which already debited a credit.
2048
2920
  ...(flagString(args, "idempotency-key") === null
@@ -2951,20 +3823,19 @@ function trustSafeCommands() {
2951
3823
  return [
2952
3824
  {
2953
3825
  purpose: "trust_packet",
2954
- command: "npx -y image-skill@latest trust --json",
3826
+ command: `${PUBLIC_NPX_COMMAND_PREFIX} trust --json`,
2955
3827
  mutation: false,
2956
3828
  spend: false,
2957
3829
  },
2958
3830
  {
2959
3831
  purpose: "first_image_guide",
2960
- command:
2961
- 'npx -y image-skill@latest create --guide --prompt "a compact field camera on a stainless workbench" --json',
3832
+ command: `${PUBLIC_NPX_COMMAND_PREFIX} create --guide --prompt "a compact field camera on a stainless workbench" --json`,
2962
3833
  mutation: false,
2963
3834
  spend: false,
2964
3835
  },
2965
3836
  {
2966
3837
  purpose: "model_inspection",
2967
- command: "npx -y image-skill@latest models list --json",
3838
+ command: `${PUBLIC_NPX_COMMAND_PREFIX} models list --json`,
2968
3839
  mutation: false,
2969
3840
  spend: false,
2970
3841
  },
@@ -3582,19 +4453,22 @@ function configWriteErrorMessage(error) {
3582
4453
  }
3583
4454
 
3584
4455
  function configWriteRecovery(command) {
3585
- const safeConfigPath = "$PWD/.image-skill/config.json";
3586
- const baseSignupCommand = `IMAGE_SKILL_CONFIG_PATH="${safeConfigPath}" ${SIGNUP_SUGGESTED_COMMAND}`;
4456
+ const baseSignupCommand = renderWritableConfigCommand(
4457
+ SIGNUP_SUGGESTED_COMMAND,
4458
+ );
3587
4459
  if (command === "image-skill auth save") {
3588
4460
  return {
3589
4461
  config_path_env: "IMAGE_SKILL_CONFIG_PATH",
3590
- suggested_config_path: safeConfigPath,
3591
- suggested_command: `IMAGE_SKILL_CONFIG_PATH="${safeConfigPath}" image-skill auth save --json`,
4462
+ suggested_config_path: LOCAL_WRITABLE_CONFIG_PATH,
4463
+ suggested_command: renderWritableConfigCommand(
4464
+ "image-skill auth save --json",
4465
+ ),
3592
4466
  docs_url: "https://image-skill.com/cli.md#local-config-and-install",
3593
4467
  };
3594
4468
  }
3595
4469
  return {
3596
4470
  config_path_env: "IMAGE_SKILL_CONFIG_PATH",
3597
- suggested_config_path: safeConfigPath,
4471
+ suggested_config_path: LOCAL_WRITABLE_CONFIG_PATH,
3598
4472
  suggested_command: baseSignupCommand,
3599
4473
  fallback_command: `${SIGNUP_SUGGESTED_COMMAND} --show-token --no-save`,
3600
4474
  fallback_auth_method: "--token-stdin",