image-skill 0.1.16 → 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/bin/image-skill.mjs +65 -5
- package/cli.md +21 -0
- package/package.json +1 -1
- package/skills/image-skill/references/cli.md +21 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,17 @@ This changelog tracks the public `image-skill` CLI package and public skill
|
|
|
4
4
|
mirror. The npm package metadata remains the authority for tarball integrity and
|
|
5
5
|
provenance; this file is the human- and agent-readable release map.
|
|
6
6
|
|
|
7
|
+
## 0.1.17 - 2026-06-01
|
|
8
|
+
|
|
9
|
+
- Money integrity: `create` and `edit` now send `--idempotency-key` to the
|
|
10
|
+
server so a retry of a transiently-failed generation REPLAYS the original
|
|
11
|
+
job instead of charging again. `create --guide` bakes a generated key into
|
|
12
|
+
its suggested command, and a proxy-killed 502 (`HOSTED_API_NON_JSON_RESPONSE`)
|
|
13
|
+
now returns a recovery block with the request's idempotency key so the
|
|
14
|
+
advertised retry is charge-safe. (0.1.16 parsed the flag but did not send it
|
|
15
|
+
on create, so same-key retries still double-charged against the live server's
|
|
16
|
+
dedup; this build closes that end-to-end.)
|
|
17
|
+
|
|
7
18
|
## 0.1.16 - 2026-06-01
|
|
8
19
|
|
|
9
20
|
- `credits buy` now accepts `--provider stripe_x402` to execute the agent-native
|
package/bin/image-skill.mjs
CHANGED
|
@@ -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.
|
|
10
|
+
const VERSION = "0.1.17";
|
|
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";
|
|
@@ -1197,6 +1197,10 @@ function createGuideNextCommand(stage, input) {
|
|
|
1197
1197
|
intent: input.requestedIntent,
|
|
1198
1198
|
budgetGuard: input.budgetGuard,
|
|
1199
1199
|
dryRun: false,
|
|
1200
|
+
// Retry-safe by default (#1228): bake a stable idempotency key into the
|
|
1201
|
+
// advertised create command so an agent that copies it and retries after a
|
|
1202
|
+
// transient 502 does not double-charge.
|
|
1203
|
+
idempotencyKey: `create-guide-${Date.now()}-${randomBytes(4).toString("hex")}`,
|
|
1200
1204
|
apiBaseUrl: input.apiBaseUrl,
|
|
1201
1205
|
commandPrefix: input.commandPrefix,
|
|
1202
1206
|
});
|
|
@@ -1228,6 +1232,9 @@ function renderCreateCommand(input) {
|
|
|
1228
1232
|
shellQuote(input.intent),
|
|
1229
1233
|
"--max-estimated-usd-per-image",
|
|
1230
1234
|
shellQuote(formatUsd(input.budgetGuard)),
|
|
1235
|
+
...(input.idempotencyKey === undefined || input.idempotencyKey === null
|
|
1236
|
+
? []
|
|
1237
|
+
: ["--idempotency-key", shellQuote(input.idempotencyKey)]),
|
|
1231
1238
|
...(input.apiBaseUrl === null
|
|
1232
1239
|
? []
|
|
1233
1240
|
: ["--api-base-url", shellQuote(input.apiBaseUrl)]),
|
|
@@ -1353,6 +1360,11 @@ async function create(argv) {
|
|
|
1353
1360
|
...(modelParameters.value === null
|
|
1354
1361
|
? {}
|
|
1355
1362
|
: { model_parameters: modelParameters.value }),
|
|
1363
|
+
// Retry-safe dedupe (#1228): when provided, a retry with the same key does
|
|
1364
|
+
// not double-charge after a transient 502 that already debited a credit.
|
|
1365
|
+
...(flagString(args, "idempotency-key") === null
|
|
1366
|
+
? {}
|
|
1367
|
+
: { idempotency_key: flagString(args, "idempotency-key") }),
|
|
1356
1368
|
dry_run: flagBool(args, "dry-run"),
|
|
1357
1369
|
accept_unknown_cost: flagBool(args, "accept-unknown-cost"),
|
|
1358
1370
|
},
|
|
@@ -1459,6 +1471,11 @@ async function edit(argv) {
|
|
|
1459
1471
|
...(modelParameters.value === null
|
|
1460
1472
|
? {}
|
|
1461
1473
|
: { model_parameters: modelParameters.value }),
|
|
1474
|
+
// Retry-safe dedupe (#1228): see create — same key dedupes a retry that
|
|
1475
|
+
// follows a transient 502 which already debited a credit.
|
|
1476
|
+
...(flagString(args, "idempotency-key") === null
|
|
1477
|
+
? {}
|
|
1478
|
+
: { idempotency_key: flagString(args, "idempotency-key") }),
|
|
1462
1479
|
accept_unknown_cost: flagBool(args, "accept-unknown-cost"),
|
|
1463
1480
|
},
|
|
1464
1481
|
});
|
|
@@ -2556,7 +2573,9 @@ async function apiRequest(input) {
|
|
|
2556
2573
|
body: input.body === undefined ? undefined : JSON.stringify(input.body),
|
|
2557
2574
|
});
|
|
2558
2575
|
const text = await response.text();
|
|
2559
|
-
const envelope = parseEnvelope(text, input.command, response.status
|
|
2576
|
+
const envelope = parseEnvelope(text, input.command, response.status, {
|
|
2577
|
+
requestBody: input.body,
|
|
2578
|
+
});
|
|
2560
2579
|
const exitCodeHeader = response.headers.get("x-image-skill-exit-code");
|
|
2561
2580
|
return {
|
|
2562
2581
|
exitCode:
|
|
@@ -2583,7 +2602,7 @@ async function apiRequest(input) {
|
|
|
2583
2602
|
}
|
|
2584
2603
|
}
|
|
2585
2604
|
|
|
2586
|
-
function parseEnvelope(text, command, statusCode) {
|
|
2605
|
+
function parseEnvelope(text, command, statusCode, options = {}) {
|
|
2587
2606
|
try {
|
|
2588
2607
|
const parsed = JSON.parse(text);
|
|
2589
2608
|
if (parsed && typeof parsed === "object" && "ok" in parsed) {
|
|
@@ -2592,21 +2611,62 @@ function parseEnvelope(text, command, statusCode) {
|
|
|
2592
2611
|
} catch {
|
|
2593
2612
|
// Fall through to normalized public error.
|
|
2594
2613
|
}
|
|
2614
|
+
const retryable = statusCode >= 500;
|
|
2615
|
+
// Money integrity (#1228): a proxy-killed 502 returns a non-JSON body, so the
|
|
2616
|
+
// server's own recovery guidance never reaches the agent. For a retryable
|
|
2617
|
+
// create/edit (which may already have debited a credit) synthesize an
|
|
2618
|
+
// idempotency-keyed retry command so the advertised retry dedupes to one
|
|
2619
|
+
// charge instead of double-charging. Echo the request's key when present;
|
|
2620
|
+
// otherwise mint a stable key so the NEXT retry is safe.
|
|
2621
|
+
const recovery =
|
|
2622
|
+
retryable && isCreateOrEditCommand(command)
|
|
2623
|
+
? nonJsonRetryRecovery(command, options.requestBody)
|
|
2624
|
+
: undefined;
|
|
2595
2625
|
return {
|
|
2596
2626
|
ok: false,
|
|
2597
2627
|
command,
|
|
2598
2628
|
trace_id: traceId(),
|
|
2599
2629
|
actor: null,
|
|
2600
2630
|
data: null,
|
|
2601
|
-
warnings:
|
|
2631
|
+
warnings: retryable
|
|
2632
|
+
? [
|
|
2633
|
+
"the hosted API may have already reserved a credit; retry with the returned idempotency_key so the retry is not double-charged",
|
|
2634
|
+
]
|
|
2635
|
+
: [],
|
|
2602
2636
|
error: {
|
|
2603
2637
|
code: "HOSTED_API_NON_JSON_RESPONSE",
|
|
2604
2638
|
message: `hosted API returned HTTP ${statusCode} without a JSON envelope`,
|
|
2605
|
-
retryable
|
|
2639
|
+
retryable,
|
|
2640
|
+
...(recovery === undefined ? {} : { recovery }),
|
|
2606
2641
|
},
|
|
2607
2642
|
};
|
|
2608
2643
|
}
|
|
2609
2644
|
|
|
2645
|
+
function isCreateOrEditCommand(command) {
|
|
2646
|
+
return command === "image-skill create" || command === "image-skill edit";
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
function nonJsonRetryRecovery(command, requestBody) {
|
|
2650
|
+
const operation = command === "image-skill edit" ? "edit" : "create";
|
|
2651
|
+
const existingKey =
|
|
2652
|
+
requestBody &&
|
|
2653
|
+
typeof requestBody === "object" &&
|
|
2654
|
+
typeof requestBody.idempotency_key === "string"
|
|
2655
|
+
? requestBody.idempotency_key
|
|
2656
|
+
: null;
|
|
2657
|
+
const idempotencyKey =
|
|
2658
|
+
existingKey ??
|
|
2659
|
+
`${operation}-retry-${Date.now()}-${randomBytes(4).toString("hex")}`;
|
|
2660
|
+
const anchor =
|
|
2661
|
+
operation === "edit" ? "image-skill-edit" : "image-skill-create";
|
|
2662
|
+
return {
|
|
2663
|
+
suggested_command: `${command} --idempotency-key ${idempotencyKey} --json`,
|
|
2664
|
+
idempotency_key: idempotencyKey,
|
|
2665
|
+
docs_url: `https://image-skill.com/cli.md#${anchor}`,
|
|
2666
|
+
retry_after_seconds: 5,
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2610
2670
|
function withStripeCheckoutCopyFallback(result) {
|
|
2611
2671
|
const data = result.envelope.data;
|
|
2612
2672
|
if (!isRecord(data)) {
|
package/cli.md
CHANGED
|
@@ -876,6 +876,21 @@ If provider generation succeeds but artifact storage fails, the command returns
|
|
|
876
876
|
should not retry the whole create blindly, because that may duplicate paid
|
|
877
877
|
provider spend.
|
|
878
878
|
|
|
879
|
+
For retry-safe create automation, pass an explicit non-secret
|
|
880
|
+
`--idempotency-key`. A retry that reuses the same key does not create a second
|
|
881
|
+
credit reservation, so a transient `502`/`PROVIDER_FAILURE` that already
|
|
882
|
+
reserved a credit cannot double-charge on retry. `create --guide` bakes a
|
|
883
|
+
generated `--idempotency-key` into its advertised create `next_command`, and a
|
|
884
|
+
retryable create error returns an `error.recovery.idempotency_key` plus an
|
|
885
|
+
`error.recovery.suggested_command` that re-runs the same create with that key.
|
|
886
|
+
|
|
887
|
+
```bash
|
|
888
|
+
image-skill create \
|
|
889
|
+
--prompt "A compact field camera on a stainless workbench" \
|
|
890
|
+
--idempotency-key create-run-001 \
|
|
891
|
+
--json
|
|
892
|
+
```
|
|
893
|
+
|
|
879
894
|
Hosted free-preview API equivalent:
|
|
880
895
|
|
|
881
896
|
```bash
|
|
@@ -1074,6 +1089,12 @@ public UX. The public selection surface should be Image Skill capabilities and
|
|
|
1074
1089
|
model-parameter schemas; provider/model details belong in explicit
|
|
1075
1090
|
provenance/debug output.
|
|
1076
1091
|
|
|
1092
|
+
Edit accepts the same retry-safe `--idempotency-key` as create. A retry that
|
|
1093
|
+
reuses the same key does not create a second credit reservation, so a transient
|
|
1094
|
+
`502`/`PROVIDER_FAILURE` after a reservation cannot double-charge; a retryable
|
|
1095
|
+
edit error returns an `error.recovery.idempotency_key` and an
|
|
1096
|
+
`error.recovery.suggested_command` that re-runs the same edit with that key.
|
|
1097
|
+
|
|
1077
1098
|
### `image-skill assets show`
|
|
1078
1099
|
|
|
1079
1100
|
Inspects an Image Skill-owned asset URL or hosted asset id.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "image-skill",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"description": "Zero-setup durable creative-media CLI for agents (image + video): guide-first creation, model and cost inspection, owned URLs, JSON recovery, payments, reusable assets, and feedback.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -876,6 +876,21 @@ If provider generation succeeds but artifact storage fails, the command returns
|
|
|
876
876
|
should not retry the whole create blindly, because that may duplicate paid
|
|
877
877
|
provider spend.
|
|
878
878
|
|
|
879
|
+
For retry-safe create automation, pass an explicit non-secret
|
|
880
|
+
`--idempotency-key`. A retry that reuses the same key does not create a second
|
|
881
|
+
credit reservation, so a transient `502`/`PROVIDER_FAILURE` that already
|
|
882
|
+
reserved a credit cannot double-charge on retry. `create --guide` bakes a
|
|
883
|
+
generated `--idempotency-key` into its advertised create `next_command`, and a
|
|
884
|
+
retryable create error returns an `error.recovery.idempotency_key` plus an
|
|
885
|
+
`error.recovery.suggested_command` that re-runs the same create with that key.
|
|
886
|
+
|
|
887
|
+
```bash
|
|
888
|
+
image-skill create \
|
|
889
|
+
--prompt "A compact field camera on a stainless workbench" \
|
|
890
|
+
--idempotency-key create-run-001 \
|
|
891
|
+
--json
|
|
892
|
+
```
|
|
893
|
+
|
|
879
894
|
Hosted free-preview API equivalent:
|
|
880
895
|
|
|
881
896
|
```bash
|
|
@@ -1074,6 +1089,12 @@ public UX. The public selection surface should be Image Skill capabilities and
|
|
|
1074
1089
|
model-parameter schemas; provider/model details belong in explicit
|
|
1075
1090
|
provenance/debug output.
|
|
1076
1091
|
|
|
1092
|
+
Edit accepts the same retry-safe `--idempotency-key` as create. A retry that
|
|
1093
|
+
reuses the same key does not create a second credit reservation, so a transient
|
|
1094
|
+
`502`/`PROVIDER_FAILURE` after a reservation cannot double-charge; a retryable
|
|
1095
|
+
edit error returns an `error.recovery.idempotency_key` and an
|
|
1096
|
+
`error.recovery.suggested_command` that re-runs the same edit with that key.
|
|
1097
|
+
|
|
1077
1098
|
### `image-skill assets show`
|
|
1078
1099
|
|
|
1079
1100
|
Inspects an Image Skill-owned asset URL or hosted asset id.
|