image-skill 0.1.42 → 0.1.43
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 +12 -0
- package/bin/image-skill.mjs +182 -3
- package/cli.md +30 -0
- package/commands.json +2 -1
- package/package.json +1 -1
- package/skills/image-skill/references/cli.md +30 -0
- package/skills/image-skill/references/commands.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ 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.43 - 2026-06-12
|
|
8
|
+
|
|
9
|
+
- Feature (recovery): `doctor --json` now reports `data.in_flight` with
|
|
10
|
+
outstanding live-spend breadcrumbs, idempotency keys, TTL state, sweep
|
|
11
|
+
eligibility, and copy-runnable recovery commands.
|
|
12
|
+
- Feature (recovery): `doctor --sweep-in-flight --json` explicitly removes
|
|
13
|
+
only sweep-eligible stale breadcrumbs after the long grace window; plain
|
|
14
|
+
`doctor` remains inspect-only.
|
|
15
|
+
- Docs (recovery): the CLI contract now documents the stderr `in_flight` JSON
|
|
16
|
+
diagnostic emitted by live create/edit before the blocking request, including
|
|
17
|
+
the `2>&1` parsing caveat for combined-stream consumers.
|
|
18
|
+
|
|
7
19
|
## 0.1.42 - 2026-06-12
|
|
8
20
|
|
|
9
21
|
- Feature (distribution): the public repo/package now ships a root `SKILL.md`
|
package/bin/image-skill.mjs
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
3
|
import { createWriteStream } from "node:fs";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
chmod,
|
|
6
|
+
mkdir,
|
|
7
|
+
readdir,
|
|
8
|
+
readFile,
|
|
9
|
+
rm,
|
|
10
|
+
stat,
|
|
11
|
+
writeFile,
|
|
12
|
+
} from "node:fs/promises";
|
|
5
13
|
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
6
14
|
import { Readable } from "node:stream";
|
|
7
15
|
import { pipeline } from "node:stream/promises";
|
|
8
16
|
import os from "node:os";
|
|
9
17
|
|
|
10
|
-
const VERSION = "0.1.
|
|
18
|
+
const VERSION = "0.1.43";
|
|
11
19
|
const PACKAGE_NAME = "image-skill";
|
|
12
20
|
const DEFAULT_API_BASE_URL = "https://api.image-skill.com";
|
|
13
21
|
const DEFAULT_DOCS_BASE_URL = "https://image-skill.com";
|
|
14
22
|
const DEFAULT_NPM_REGISTRY_BASE_URL = "https://registry.npmjs.org";
|
|
15
23
|
const PUBLIC_REPO_URL = "https://github.com/danielgwilson/image-skill-cli";
|
|
24
|
+
const IN_FLIGHT_RESERVATION_TTL_MS = 15 * 60 * 1000;
|
|
25
|
+
const IN_FLIGHT_SWEEP_AFTER_MS = 24 * 60 * 60 * 1000;
|
|
16
26
|
const PROMPTLESS_EDIT_MODEL_IDS = new Set([
|
|
17
27
|
"fal.flux-dev-redux",
|
|
18
28
|
"fal.flux-krea-redux",
|
|
@@ -327,7 +337,8 @@ function commandHelpByKey(key) {
|
|
|
327
337
|
usage: "image-skill doctor --json",
|
|
328
338
|
docs_url: "https://image-skill.com/cli.md#image-skill-doctor",
|
|
329
339
|
description:
|
|
330
|
-
"Check hosted API reachability, CLI version, auth state, and
|
|
340
|
+
"Check hosted API reachability, CLI version, auth state, health, and live-spend recovery breadcrumbs.",
|
|
341
|
+
optional_flags: ["--sweep-in-flight"],
|
|
331
342
|
},
|
|
332
343
|
trust: {
|
|
333
344
|
command: "image-skill trust help",
|
|
@@ -606,6 +617,10 @@ async function doctor(argv) {
|
|
|
606
617
|
const args = parseArgs(argv);
|
|
607
618
|
const apiBaseUrl = apiBase(args);
|
|
608
619
|
const config = await readConfig(configPath());
|
|
620
|
+
const inFlight = await inFlightSpendDoctorReport({
|
|
621
|
+
sweep: flagBool(args, "sweep-in-flight"),
|
|
622
|
+
now: new Date(),
|
|
623
|
+
});
|
|
609
624
|
const health = await apiRequest({
|
|
610
625
|
command: "image-skill doctor",
|
|
611
626
|
method: "GET",
|
|
@@ -628,6 +643,7 @@ async function doctor(argv) {
|
|
|
628
643
|
saved_token: config.tokenPresent,
|
|
629
644
|
env_token: hasEnvToken(),
|
|
630
645
|
},
|
|
646
|
+
in_flight: inFlight,
|
|
631
647
|
docs: {
|
|
632
648
|
skill: "https://image-skill.com/skill.md",
|
|
633
649
|
llms: "https://image-skill.com/llms.txt",
|
|
@@ -4575,6 +4591,169 @@ function inFlightSpendFileName(idempotencyKey) {
|
|
|
4575
4591
|
return `${trimmed.length === 0 ? "key" : trimmed}.json`;
|
|
4576
4592
|
}
|
|
4577
4593
|
|
|
4594
|
+
async function inFlightSpendDoctorReport(input) {
|
|
4595
|
+
const dir = inFlightSpendDir();
|
|
4596
|
+
const now = input.now ?? new Date();
|
|
4597
|
+
const files = await readdir(dir).catch((error) => {
|
|
4598
|
+
if (error?.code === "ENOENT") {
|
|
4599
|
+
return [];
|
|
4600
|
+
}
|
|
4601
|
+
return null;
|
|
4602
|
+
});
|
|
4603
|
+
if (files === null) {
|
|
4604
|
+
return {
|
|
4605
|
+
schema: "image-skill.in-flight-spend-report.v1",
|
|
4606
|
+
directory: dir,
|
|
4607
|
+
count: null,
|
|
4608
|
+
recoverable_count: null,
|
|
4609
|
+
ttl_elapsed_count: null,
|
|
4610
|
+
sweep_eligible_count: null,
|
|
4611
|
+
invalid_count: null,
|
|
4612
|
+
entries: [],
|
|
4613
|
+
error: "in-flight directory could not be read",
|
|
4614
|
+
reservation_ttl_ms: IN_FLIGHT_RESERVATION_TTL_MS,
|
|
4615
|
+
sweep_after_ms: IN_FLIGHT_SWEEP_AFTER_MS,
|
|
4616
|
+
swept_count: 0,
|
|
4617
|
+
sweep_requested: input.sweep === true,
|
|
4618
|
+
};
|
|
4619
|
+
}
|
|
4620
|
+
|
|
4621
|
+
const entries = [];
|
|
4622
|
+
let invalidCount = 0;
|
|
4623
|
+
let sweptCount = 0;
|
|
4624
|
+
for (const file of files.sort()) {
|
|
4625
|
+
if (!file.endsWith(".json")) {
|
|
4626
|
+
continue;
|
|
4627
|
+
}
|
|
4628
|
+
const path = join(dir, file);
|
|
4629
|
+
const entry = await readInFlightSpendEntry({ path, file, now });
|
|
4630
|
+
if (entry === null) {
|
|
4631
|
+
invalidCount += 1;
|
|
4632
|
+
continue;
|
|
4633
|
+
}
|
|
4634
|
+
if (input.sweep === true && entry.sweep_eligible === true) {
|
|
4635
|
+
await rm(path, { force: true }).catch(() => {});
|
|
4636
|
+
sweptCount += 1;
|
|
4637
|
+
continue;
|
|
4638
|
+
}
|
|
4639
|
+
entries.push(entry);
|
|
4640
|
+
}
|
|
4641
|
+
|
|
4642
|
+
return {
|
|
4643
|
+
schema: "image-skill.in-flight-spend-report.v1",
|
|
4644
|
+
directory: dir,
|
|
4645
|
+
count: entries.length,
|
|
4646
|
+
recoverable_count: entries.filter((entry) => entry.state === "recoverable")
|
|
4647
|
+
.length,
|
|
4648
|
+
ttl_elapsed_count: entries.filter((entry) => entry.state === "ttl_elapsed")
|
|
4649
|
+
.length,
|
|
4650
|
+
sweep_eligible_count: entries.filter((entry) => entry.sweep_eligible)
|
|
4651
|
+
.length,
|
|
4652
|
+
invalid_count: invalidCount,
|
|
4653
|
+
swept_count: sweptCount,
|
|
4654
|
+
reservation_ttl_ms: IN_FLIGHT_RESERVATION_TTL_MS,
|
|
4655
|
+
sweep_after_ms: IN_FLIGHT_SWEEP_AFTER_MS,
|
|
4656
|
+
sweep_requested: input.sweep === true,
|
|
4657
|
+
entries,
|
|
4658
|
+
note:
|
|
4659
|
+
entries.length === 0
|
|
4660
|
+
? "no in-flight live spend breadcrumbs found"
|
|
4661
|
+
: "rerun an entry's recover_command to settle or inspect a maybe-reserved spend before sweeping it",
|
|
4662
|
+
};
|
|
4663
|
+
}
|
|
4664
|
+
|
|
4665
|
+
async function readInFlightSpendEntry({ path, file, now }) {
|
|
4666
|
+
let parsed;
|
|
4667
|
+
let fileStat;
|
|
4668
|
+
try {
|
|
4669
|
+
parsed = JSON.parse(await readFile(path, "utf8"));
|
|
4670
|
+
fileStat = await stat(path);
|
|
4671
|
+
} catch {
|
|
4672
|
+
return null;
|
|
4673
|
+
}
|
|
4674
|
+
if (
|
|
4675
|
+
parsed?.schema !== "image-skill.in-flight-spend.v1" ||
|
|
4676
|
+
typeof parsed.idempotency_key !== "string" ||
|
|
4677
|
+
typeof parsed.operation !== "string"
|
|
4678
|
+
) {
|
|
4679
|
+
return null;
|
|
4680
|
+
}
|
|
4681
|
+
|
|
4682
|
+
const startedAt =
|
|
4683
|
+
typeof parsed.started_at === "string" ? parsed.started_at : null;
|
|
4684
|
+
const startedTime =
|
|
4685
|
+
startedAt === null ? Number.NaN : new Date(startedAt).getTime();
|
|
4686
|
+
const fallbackTime = fileStat.mtime.getTime();
|
|
4687
|
+
const basisTime = Number.isFinite(startedTime) ? startedTime : fallbackTime;
|
|
4688
|
+
const ageMs = Math.max(0, now.getTime() - basisTime);
|
|
4689
|
+
const state =
|
|
4690
|
+
ageMs >= IN_FLIGHT_RESERVATION_TTL_MS ? "ttl_elapsed" : "recoverable";
|
|
4691
|
+
const sweepEligible = ageMs >= IN_FLIGHT_SWEEP_AFTER_MS;
|
|
4692
|
+
const argv = Array.isArray(parsed.argv)
|
|
4693
|
+
? parsed.argv.filter((value) => typeof value === "string")
|
|
4694
|
+
: [];
|
|
4695
|
+
const recoverCommand = renderRecoverCommand({
|
|
4696
|
+
operation: parsed.operation,
|
|
4697
|
+
argv,
|
|
4698
|
+
idempotencyKey: parsed.idempotency_key,
|
|
4699
|
+
fallback: parsed.recover_command,
|
|
4700
|
+
});
|
|
4701
|
+
|
|
4702
|
+
return {
|
|
4703
|
+
file,
|
|
4704
|
+
path,
|
|
4705
|
+
operation: parsed.operation,
|
|
4706
|
+
command:
|
|
4707
|
+
typeof parsed.command === "string"
|
|
4708
|
+
? parsed.command
|
|
4709
|
+
: `image-skill ${parsed.operation}`,
|
|
4710
|
+
idempotency_key: parsed.idempotency_key,
|
|
4711
|
+
started_at: startedAt,
|
|
4712
|
+
age_ms: ageMs,
|
|
4713
|
+
state,
|
|
4714
|
+
sweep_eligible: sweepEligible,
|
|
4715
|
+
recover_command: recoverCommand,
|
|
4716
|
+
original_recover_command:
|
|
4717
|
+
typeof parsed.recover_command === "string"
|
|
4718
|
+
? parsed.recover_command
|
|
4719
|
+
: null,
|
|
4720
|
+
warning:
|
|
4721
|
+
state === "recoverable"
|
|
4722
|
+
? "the hosted reservation TTL has not elapsed; recover before cleanup"
|
|
4723
|
+
: sweepEligible
|
|
4724
|
+
? "reservation TTL has long elapsed; recover first if the original result still matters, or run doctor --sweep-in-flight to remove this breadcrumb"
|
|
4725
|
+
: "reservation TTL has elapsed; recover if you need the result, otherwise leave it until it becomes sweep-eligible",
|
|
4726
|
+
};
|
|
4727
|
+
}
|
|
4728
|
+
|
|
4729
|
+
function renderRecoverCommand(input) {
|
|
4730
|
+
const argv = withRecoveryArgs(input.argv, input.idempotencyKey);
|
|
4731
|
+
if (argv.length === 0 && typeof input.fallback === "string") {
|
|
4732
|
+
return input.fallback;
|
|
4733
|
+
}
|
|
4734
|
+
return renderImageSkillCommand(input.operation, argv);
|
|
4735
|
+
}
|
|
4736
|
+
|
|
4737
|
+
function withRecoveryArgs(argv, idempotencyKey) {
|
|
4738
|
+
const args = [...argv];
|
|
4739
|
+
const hasIdempotency = args.some(
|
|
4740
|
+
(arg) =>
|
|
4741
|
+
arg === "--idempotency-key" || arg.startsWith("--idempotency-key="),
|
|
4742
|
+
);
|
|
4743
|
+
if (!hasIdempotency) {
|
|
4744
|
+
args.push("--idempotency-key", idempotencyKey);
|
|
4745
|
+
}
|
|
4746
|
+
const hasJson = args.some((arg) => arg === "--json");
|
|
4747
|
+
if (!hasJson) {
|
|
4748
|
+
args.push("--json");
|
|
4749
|
+
}
|
|
4750
|
+
return args;
|
|
4751
|
+
}
|
|
4752
|
+
|
|
4753
|
+
function renderImageSkillCommand(operation, argv) {
|
|
4754
|
+
return ["image-skill", operation, ...argv.map(shellQuote)].join(" ");
|
|
4755
|
+
}
|
|
4756
|
+
|
|
4578
4757
|
async function recordInFlightSpend(input) {
|
|
4579
4758
|
const { command, operation, idempotencyKey, argv } = input;
|
|
4580
4759
|
const recoverCommand = recoverCommandFor(operation, idempotencyKey);
|
package/cli.md
CHANGED
|
@@ -52,6 +52,16 @@ Checks thin CLI/client health, hosted service reachability, auth state, local ou
|
|
|
52
52
|
image-skill doctor --json
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
`doctor` also reports `data.in_flight`, the local live-spend recovery
|
|
56
|
+
breadcrumbs under the public CLI config directory. Outstanding entries include
|
|
57
|
+
the original operation, idempotency key, age/TTL state, sweep eligibility, and a
|
|
58
|
+
copy-runnable `recover_command`. Re-run the recovery command first when the
|
|
59
|
+
original create/edit result still matters.
|
|
60
|
+
|
|
61
|
+
Use `image-skill doctor --sweep-in-flight --json` to remove only
|
|
62
|
+
sweep-eligible stale breadcrumbs after the long grace window. Plain `doctor`
|
|
63
|
+
never deletes recovery breadcrumbs.
|
|
64
|
+
|
|
55
65
|
### `image-skill trust`
|
|
56
66
|
|
|
57
67
|
Returns a no-auth, no-spend evidence packet for tool selection and package
|
|
@@ -1229,6 +1239,24 @@ generated `--idempotency-key` into its advertised create `next_command`, and a
|
|
|
1229
1239
|
retryable create error returns an `error.recovery.idempotency_key` plus an
|
|
1230
1240
|
`error.recovery.suggested_command` that re-runs the same create with that key.
|
|
1231
1241
|
|
|
1242
|
+
Live non-dry-run create/edit emits one JSON diagnostic line to stderr before
|
|
1243
|
+
the blocking hosted request:
|
|
1244
|
+
|
|
1245
|
+
```json
|
|
1246
|
+
{
|
|
1247
|
+
"in_flight": {
|
|
1248
|
+
"command": "image-skill create",
|
|
1249
|
+
"idempotency_key": "create-...",
|
|
1250
|
+
"recover_command": "image-skill create --idempotency-key create-... <same arguments> --json"
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
```
|
|
1254
|
+
|
|
1255
|
+
stdout remains the command JSON envelope. If an agent combines streams with
|
|
1256
|
+
`2>&1`, split stderr diagnostics from the stdout envelope before parsing. The
|
|
1257
|
+
same recovery breadcrumb is stored under `<config-dir>/in-flight/` and appears
|
|
1258
|
+
in `image-skill doctor --json` at `data.in_flight`.
|
|
1259
|
+
|
|
1232
1260
|
```bash
|
|
1233
1261
|
image-skill create \
|
|
1234
1262
|
--prompt "A compact field camera on a stainless workbench" \
|
|
@@ -1470,6 +1498,8 @@ reuses the same key does not create a second credit reservation, so a transient
|
|
|
1470
1498
|
`502`/`PROVIDER_FAILURE` after a reservation cannot double-charge; a retryable
|
|
1471
1499
|
edit error returns an `error.recovery.idempotency_key` and an
|
|
1472
1500
|
`error.recovery.suggested_command` that re-runs the same edit with that key.
|
|
1501
|
+
Live non-dry-run edit emits the same stderr `in_flight` diagnostic and local
|
|
1502
|
+
doctor-visible recovery breadcrumb as create.
|
|
1473
1503
|
|
|
1474
1504
|
### `image-skill assets show`
|
|
1475
1505
|
|
package/commands.json
CHANGED
|
@@ -195,7 +195,8 @@
|
|
|
195
195
|
"command": "image-skill doctor help",
|
|
196
196
|
"usage": "image-skill doctor --json",
|
|
197
197
|
"docs_url": "https://image-skill.com/cli.md#image-skill-doctor",
|
|
198
|
-
"description": "Check hosted API reachability, CLI version, auth state, and
|
|
198
|
+
"description": "Check hosted API reachability, CLI version, auth state, health, and live-spend recovery breadcrumbs.",
|
|
199
|
+
"optional_flags": ["--sweep-in-flight"]
|
|
199
200
|
}
|
|
200
201
|
},
|
|
201
202
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "image-skill",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.43",
|
|
4
4
|
"description": "Zero-setup durable creative-media CLI for agents (image + video + audio + 3D): guide-first creation, model and cost inspection, owned URLs, JSON recovery, payments, reusable assets, and feedback.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -52,6 +52,16 @@ Checks thin CLI/client health, hosted service reachability, auth state, local ou
|
|
|
52
52
|
image-skill doctor --json
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
`doctor` also reports `data.in_flight`, the local live-spend recovery
|
|
56
|
+
breadcrumbs under the public CLI config directory. Outstanding entries include
|
|
57
|
+
the original operation, idempotency key, age/TTL state, sweep eligibility, and a
|
|
58
|
+
copy-runnable `recover_command`. Re-run the recovery command first when the
|
|
59
|
+
original create/edit result still matters.
|
|
60
|
+
|
|
61
|
+
Use `image-skill doctor --sweep-in-flight --json` to remove only
|
|
62
|
+
sweep-eligible stale breadcrumbs after the long grace window. Plain `doctor`
|
|
63
|
+
never deletes recovery breadcrumbs.
|
|
64
|
+
|
|
55
65
|
### `image-skill trust`
|
|
56
66
|
|
|
57
67
|
Returns a no-auth, no-spend evidence packet for tool selection and package
|
|
@@ -1229,6 +1239,24 @@ generated `--idempotency-key` into its advertised create `next_command`, and a
|
|
|
1229
1239
|
retryable create error returns an `error.recovery.idempotency_key` plus an
|
|
1230
1240
|
`error.recovery.suggested_command` that re-runs the same create with that key.
|
|
1231
1241
|
|
|
1242
|
+
Live non-dry-run create/edit emits one JSON diagnostic line to stderr before
|
|
1243
|
+
the blocking hosted request:
|
|
1244
|
+
|
|
1245
|
+
```json
|
|
1246
|
+
{
|
|
1247
|
+
"in_flight": {
|
|
1248
|
+
"command": "image-skill create",
|
|
1249
|
+
"idempotency_key": "create-...",
|
|
1250
|
+
"recover_command": "image-skill create --idempotency-key create-... <same arguments> --json"
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
```
|
|
1254
|
+
|
|
1255
|
+
stdout remains the command JSON envelope. If an agent combines streams with
|
|
1256
|
+
`2>&1`, split stderr diagnostics from the stdout envelope before parsing. The
|
|
1257
|
+
same recovery breadcrumb is stored under `<config-dir>/in-flight/` and appears
|
|
1258
|
+
in `image-skill doctor --json` at `data.in_flight`.
|
|
1259
|
+
|
|
1232
1260
|
```bash
|
|
1233
1261
|
image-skill create \
|
|
1234
1262
|
--prompt "A compact field camera on a stainless workbench" \
|
|
@@ -1470,6 +1498,8 @@ reuses the same key does not create a second credit reservation, so a transient
|
|
|
1470
1498
|
`502`/`PROVIDER_FAILURE` after a reservation cannot double-charge; a retryable
|
|
1471
1499
|
edit error returns an `error.recovery.idempotency_key` and an
|
|
1472
1500
|
`error.recovery.suggested_command` that re-runs the same edit with that key.
|
|
1501
|
+
Live non-dry-run edit emits the same stderr `in_flight` diagnostic and local
|
|
1502
|
+
doctor-visible recovery breadcrumb as create.
|
|
1473
1503
|
|
|
1474
1504
|
### `image-skill assets show`
|
|
1475
1505
|
|
|
@@ -195,7 +195,8 @@
|
|
|
195
195
|
"command": "image-skill doctor help",
|
|
196
196
|
"usage": "image-skill doctor --json",
|
|
197
197
|
"docs_url": "https://image-skill.com/cli.md#image-skill-doctor",
|
|
198
|
-
"description": "Check hosted API reachability, CLI version, auth state, and
|
|
198
|
+
"description": "Check hosted API reachability, CLI version, auth state, health, and live-spend recovery breadcrumbs.",
|
|
199
|
+
"optional_flags": ["--sweep-in-flight"]
|
|
199
200
|
}
|
|
200
201
|
},
|
|
201
202
|
{
|