image-skill 0.1.28 → 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.28";
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,6 +1483,7 @@ 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,
@@ -1344,6 +1495,7 @@ async function createGuide(args) {
1344
1495
  requestedProviderId,
1345
1496
  requestedIntent,
1346
1497
  budgetGuard,
1498
+ aspectRatio: selectedAspectRatio,
1347
1499
  apiBaseUrl: explicitApiBaseUrl(args),
1348
1500
  commandPrefix: PUBLIC_NPX_COMMAND_PREFIX,
1349
1501
  });
@@ -1353,6 +1505,28 @@ async function createGuide(args) {
1353
1505
  });
1354
1506
  const noSpendNextCommand =
1355
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
+ );
1356
1530
  const afterNext =
1357
1531
  stage === "auth_required" || stage === "quota_required"
1358
1532
  ? renderGuideCommand(
@@ -1367,7 +1541,18 @@ async function createGuide(args) {
1367
1541
  afterNext,
1368
1542
  authConfigWrite,
1369
1543
  });
1370
- 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, {
1371
1556
  schema: "image-skill.create-guide.v1",
1372
1557
  ready: stage === "ready_to_create",
1373
1558
  stage,
@@ -1379,8 +1564,8 @@ async function createGuide(args) {
1379
1564
  error_code: health.envelope.error?.code ?? null,
1380
1565
  },
1381
1566
  auth: {
1382
- source: token.source === "anonymous" ? "none" : token.source,
1383
- authenticated: quota?.envelope.data?.authenticated === true,
1567
+ source: publicTokenSource,
1568
+ authenticated,
1384
1569
  claim_state: quota?.envelope.data?.claim_state ?? null,
1385
1570
  token_status: quota?.envelope.data?.token_status ?? null,
1386
1571
  saved_config_path: configPath(),
@@ -1419,14 +1604,24 @@ async function createGuide(args) {
1419
1604
  selected === null
1420
1605
  ? null
1421
1606
  : {
1422
- operation: "create",
1607
+ operation: createGuideSelectedModelRequiresInputImage(selected)
1608
+ ? "edit"
1609
+ : "create",
1423
1610
  model_id: selected.id,
1424
1611
  model_status: selected.status,
1425
1612
  model_execution_status: selected.execution.model_execution_status,
1613
+ modality: selected.modality ?? null,
1614
+ suggested_aspect_ratio: selectedAspectRatio,
1426
1615
  reason:
1427
1616
  requestedModelId === null
1428
- ? "default executable create model for first image"
1429
- : "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",
1430
1625
  },
1431
1626
  cost: {
1432
1627
  estimated_credits: estimatedCredits,
@@ -1437,13 +1632,20 @@ async function createGuide(args) {
1437
1632
  pricing_confidence: pricing?.pricing_confidence ?? null,
1438
1633
  },
1439
1634
  blocker,
1635
+ guide_warning: guideWarning,
1636
+ auth_ready: authReady,
1440
1637
  next_command: nextCommand,
1441
1638
  next_command_effect: nextCommandEffect,
1442
1639
  no_spend_next_command: noSpendNextCommand,
1443
- no_spend_next_command_label:
1444
- noSpendNextCommand === null
1445
- ? null
1446
- : "dry_run_plan_no_provider_call_no_credit_debit_no_media_write",
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,
1447
1649
  after_next: afterNext,
1448
1650
  auth_handoff: authHandoff,
1449
1651
  escape_hatches: escapeHatches,
@@ -1458,31 +1660,126 @@ async function createGuide(args) {
1458
1660
  });
1459
1661
  }
1460
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
+
1461
1669
  function selectCreateGuideModel(
1462
1670
  models,
1463
1671
  requestedModelId,
1464
- { maxEstimatedUsdPerImage = null } = {},
1672
+ { prompt = "", intent = undefined, maxEstimatedUsdPerImage = null } = {},
1465
1673
  ) {
1466
1674
  const isExecutableCreate = (model) =>
1467
1675
  model?.status === "available" &&
1468
1676
  model?.execution?.model_execution_status === "executable" &&
1469
1677
  Array.isArray(model?.supports) &&
1470
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);
1471
1688
  if (requestedModelId !== null) {
1472
1689
  const requested = models.find((model) => model.id === requestedModelId);
1473
- return requested !== undefined && isExecutableCreate(requested)
1690
+ return requested !== undefined && isExecutableGuideModel(requested)
1474
1691
  ? requested
1475
1692
  : null;
1476
1693
  }
1477
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
+ }) {
1478
1735
  if (maxEstimatedUsdPerImage === null) {
1479
- return candidates[0] ?? null;
1736
+ return candidates;
1480
1737
  }
1481
1738
  const capped = candidates.filter((model) => {
1482
1739
  const estimatedUsd = guideBudgetUsdForModel(model);
1483
1740
  return estimatedUsd === null || estimatedUsd <= maxEstimatedUsdPerImage;
1484
1741
  });
1485
- 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
+ ];
1486
1783
  }
1487
1784
 
1488
1785
  function guideBudgetUsdForModel(model) {
@@ -1496,6 +1793,81 @@ function guideBudgetUsdForModel(model) {
1496
1793
  );
1497
1794
  }
1498
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
+
1499
1871
  function createGuidePaymentSummary(data) {
1500
1872
  const methods = Array.isArray(data?.methods)
1501
1873
  ? data.methods.filter((method) => method.live_money)
@@ -1544,6 +1916,10 @@ function createGuidePaymentSummary(data) {
1544
1916
  (method) => method.method_id,
1545
1917
  ),
1546
1918
  preferred_method: preferredMethod?.method_id ?? null,
1919
+ preferred_method_summary:
1920
+ preferredMethod === undefined
1921
+ ? null
1922
+ : createGuidePreferredPaymentSummary(preferredMethod),
1547
1923
  buyer_modes: [
1548
1924
  ...new Set(methods.flatMap((method) => method.buyer_modes ?? [])),
1549
1925
  ],
@@ -1554,6 +1930,64 @@ function createGuidePaymentSummary(data) {
1554
1930
  };
1555
1931
  }
1556
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
+
1557
1991
  function createGuidePaymentCommands(preferredMethod, fallbackMethods) {
1558
1992
  const commands = [
1559
1993
  "image-skill credits methods --json",
@@ -1658,6 +2092,10 @@ function createGuideBlocker(stage, input) {
1658
2092
  function createGuideAuthHandoff(stage, input) {
1659
2093
  if (stage === "auth_required") {
1660
2094
  const authConfigWritable = input.authConfigWrite?.ok ?? true;
2095
+ const recovery =
2096
+ input.authConfigWrite?.ok === false
2097
+ ? configWriteRecovery("image-skill create --guide")
2098
+ : null;
1661
2099
  return {
1662
2100
  required: true,
1663
2101
  token_source: "none",
@@ -1665,16 +2103,21 @@ function createGuideAuthHandoff(stage, input) {
1665
2103
  accepted_methods: ["IMAGE_SKILL_TOKEN", "--token-stdin", "config"],
1666
2104
  signup: {
1667
2105
  returns_token_once: true,
1668
- public_cli_saves_config: authConfigWritable,
2106
+ public_cli_saves_config: true,
1669
2107
  store_token_in: authConfigWritable
1670
2108
  ? "public_cli_config_by_default"
1671
- : "agent_runtime_secret_store",
2109
+ : "public_cli_config_after_setting_IMAGE_SKILL_CONFIG_PATH",
1672
2110
  config_path: configPath(),
1673
2111
  config_writable: authConfigWritable,
1674
- recovery:
1675
- input.authConfigWrite?.ok === false
1676
- ? configWriteRecovery("image-skill create --guide")
1677
- : 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,
1678
2121
  },
1679
2122
  rerun_guide:
1680
2123
  input.afterNext === null
@@ -1686,7 +2129,7 @@ function createGuideAuthHandoff(stage, input) {
1686
2129
  next_command: null,
1687
2130
  };
1688
2131
  }
1689
- if (stage === "ready_to_create") {
2132
+ if (stage === "quota_required" || stage === "ready_to_create") {
1690
2133
  return {
1691
2134
  required: true,
1692
2135
  token_source: input.tokenSource,
@@ -1705,6 +2148,152 @@ function createGuideAuthHandoff(stage, input) {
1705
2148
  return null;
1706
2149
  }
1707
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
+
1708
2297
  function createGuideNextCommandEffect(stage, input) {
1709
2298
  const base = {
1710
2299
  label: "read_only_or_no_media_setup",
@@ -1751,7 +2340,7 @@ function createGuideNextCommandEffect(stage, input) {
1751
2340
  estimated_credits: input.estimatedCredits,
1752
2341
  estimated_debit_usd_per_image: input.estimatedDebitUsdPerImage,
1753
2342
  warning:
1754
- "data.next_command creates hosted media and can debit credits. For no-spend verification, run data.no_spend_next_command instead.",
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.",
1755
2344
  };
1756
2345
  }
1757
2346
  if (stage === "prompt_required") {
@@ -1763,6 +2352,140 @@ function createGuideNextCommandEffect(stage, input) {
1763
2352
  return base;
1764
2353
  }
1765
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
+
1766
2489
  function createGuideNextCommand(stage, input) {
1767
2490
  if (stage === "prompt_required") {
1768
2491
  return renderGuideCommand("PROMPT", input.apiBaseUrl, input.commandPrefix);
@@ -1774,11 +2497,7 @@ function createGuideNextCommand(stage, input) {
1774
2497
  );
1775
2498
  }
1776
2499
  if (stage === "auth_required") {
1777
- const signupCommand =
1778
- input.authConfigWritable === false
1779
- ? "signup --agent --agent-contact AGENT_OR_OPERATOR_INBOX --agent-name AGENT_NAME --runtime RUNTIME_NAME --show-token --no-save --json"
1780
- : "signup --agent --agent-contact AGENT_OR_OPERATOR_INBOX --agent-name AGENT_NAME --runtime RUNTIME_NAME --json";
1781
- return renderGuidePrefixedCommand(input.commandPrefix, signupCommand);
2500
+ return renderGuideSignupCommand(input);
1782
2501
  }
1783
2502
  if (stage === "quota_required") {
1784
2503
  return renderGuidePrefixedCommand(
@@ -1788,12 +2507,23 @@ function createGuideNextCommand(stage, input) {
1788
2507
  ),
1789
2508
  );
1790
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
+ }
1791
2520
  return renderCreateCommand({
1792
2521
  prompt: input.prompt,
1793
2522
  modelId: input.selected.id,
1794
2523
  providerId: input.requestedProviderId,
1795
2524
  intent: input.requestedIntent,
1796
2525
  budgetGuard: input.budgetGuard,
2526
+ aspectRatio: input.aspectRatio,
1797
2527
  dryRun: false,
1798
2528
  // Retry-safe by default (#1228): bake a stable idempotency key into the
1799
2529
  // advertised create command so an agent that copies it and retries after a
@@ -1828,16 +2558,25 @@ function createGuideEscapeHatches(input) {
1828
2558
  input.commandPrefix,
1829
2559
  "create --dry-run --prompt PROMPT --json",
1830
2560
  )
1831
- : renderCreateCommand({
1832
- prompt: input.prompt,
1833
- modelId: input.selected.id,
1834
- providerId: input.requestedProviderId,
1835
- intent: input.requestedIntent,
1836
- budgetGuard: input.budgetGuard,
1837
- dryRun: true,
1838
- apiBaseUrl: input.apiBaseUrl,
1839
- commandPrefix: input.commandPrefix,
1840
- }),
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
+ }),
1841
2580
  };
1842
2581
  }
1843
2582
 
@@ -1851,6 +2590,23 @@ function renderGuideCommand(prompt, apiBaseUrl, commandPrefix = "image-skill") {
1851
2590
  ].join(" ");
1852
2591
  }
1853
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
+
1854
2610
  function renderTokenStdinCommand(command) {
1855
2611
  return `printf '%s\\n' "$IMAGE_SKILL_TOKEN" | ${command} --token-stdin`;
1856
2612
  }
@@ -1865,6 +2621,37 @@ function firstPaymentActionCommand(commands) {
1865
2621
  );
1866
2622
  }
1867
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
+
1868
2655
  function renderCreateCommand(input) {
1869
2656
  return [
1870
2657
  input.commandPrefix ?? "image-skill",
@@ -1879,6 +2666,9 @@ function renderCreateCommand(input) {
1879
2666
  shellQuote(input.prompt),
1880
2667
  "--intent",
1881
2668
  shellQuote(input.intent),
2669
+ ...(input.aspectRatio === null || input.aspectRatio === undefined
2670
+ ? []
2671
+ : ["--aspect-ratio", shellQuote(input.aspectRatio)]),
1882
2672
  "--max-estimated-usd-per-image",
1883
2673
  shellQuote(formatUsd(input.budgetGuard)),
1884
2674
  ...(input.idempotencyKey === undefined || input.idempotencyKey === null
@@ -1895,6 +2685,10 @@ function renderGuidePrefixedCommand(commandPrefix, command) {
1895
2685
  return `${commandPrefix} ${stripImageSkillCommandPrefix(command)}`;
1896
2686
  }
1897
2687
 
2688
+ function renderWritableConfigCommand(command) {
2689
+ return `IMAGE_SKILL_CONFIG_PATH="${LOCAL_WRITABLE_CONFIG_PATH}" ${command}`;
2690
+ }
2691
+
1898
2692
  function stripImageSkillCommandPrefix(command) {
1899
2693
  return String(command ?? "").replace(/^image-skill\s+/, "");
1900
2694
  }
@@ -2120,6 +2914,7 @@ async function edit(argv) {
2120
2914
  ...(modelParameters.value === null
2121
2915
  ? {}
2122
2916
  : { model_parameters: modelParameters.value }),
2917
+ ...(flagBool(args, "dry-run") ? { dry_run: true } : {}),
2123
2918
  // Retry-safe dedupe (#1228): see create — same key dedupes a retry that
2124
2919
  // follows a transient 502 which already debited a credit.
2125
2920
  ...(flagString(args, "idempotency-key") === null
@@ -3028,20 +3823,19 @@ function trustSafeCommands() {
3028
3823
  return [
3029
3824
  {
3030
3825
  purpose: "trust_packet",
3031
- command: "npx -y image-skill@latest trust --json",
3826
+ command: `${PUBLIC_NPX_COMMAND_PREFIX} trust --json`,
3032
3827
  mutation: false,
3033
3828
  spend: false,
3034
3829
  },
3035
3830
  {
3036
3831
  purpose: "first_image_guide",
3037
- command:
3038
- '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`,
3039
3833
  mutation: false,
3040
3834
  spend: false,
3041
3835
  },
3042
3836
  {
3043
3837
  purpose: "model_inspection",
3044
- command: "npx -y image-skill@latest models list --json",
3838
+ command: `${PUBLIC_NPX_COMMAND_PREFIX} models list --json`,
3045
3839
  mutation: false,
3046
3840
  spend: false,
3047
3841
  },
@@ -3659,19 +4453,22 @@ function configWriteErrorMessage(error) {
3659
4453
  }
3660
4454
 
3661
4455
  function configWriteRecovery(command) {
3662
- const safeConfigPath = "$PWD/.image-skill/config.json";
3663
- const baseSignupCommand = `IMAGE_SKILL_CONFIG_PATH="${safeConfigPath}" ${SIGNUP_SUGGESTED_COMMAND}`;
4456
+ const baseSignupCommand = renderWritableConfigCommand(
4457
+ SIGNUP_SUGGESTED_COMMAND,
4458
+ );
3664
4459
  if (command === "image-skill auth save") {
3665
4460
  return {
3666
4461
  config_path_env: "IMAGE_SKILL_CONFIG_PATH",
3667
- suggested_config_path: safeConfigPath,
3668
- 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
+ ),
3669
4466
  docs_url: "https://image-skill.com/cli.md#local-config-and-install",
3670
4467
  };
3671
4468
  }
3672
4469
  return {
3673
4470
  config_path_env: "IMAGE_SKILL_CONFIG_PATH",
3674
- suggested_config_path: safeConfigPath,
4471
+ suggested_config_path: LOCAL_WRITABLE_CONFIG_PATH,
3675
4472
  suggested_command: baseSignupCommand,
3676
4473
  fallback_command: `${SIGNUP_SUGGESTED_COMMAND} --show-token --no-save`,
3677
4474
  fallback_auth_method: "--token-stdin",