image-skill 0.1.6 → 0.1.8
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 +53 -0
- package/PROVENANCE.md +78 -0
- package/README.md +66 -11
- package/bin/image-skill.mjs +647 -36
- package/cli.md +378 -44
- package/llms.txt +45 -24
- package/package.json +22 -4
- package/skill.md +254 -25
- package/skills/image-skill/SKILL.md +562 -0
- package/skills/image-skill/references/cli.md +1263 -0
- package/skills/image-skill/references/llms.txt +248 -0
package/bin/image-skill.mjs
CHANGED
|
@@ -7,8 +7,13 @@ 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.8";
|
|
11
11
|
const DEFAULT_API_BASE_URL = "https://api.image-skill.com";
|
|
12
|
+
const PROMPTLESS_EDIT_MODEL_IDS = new Set([
|
|
13
|
+
"fal.flux-dev-redux",
|
|
14
|
+
"fal.flux-krea-redux",
|
|
15
|
+
"fal.flux-schnell-redux",
|
|
16
|
+
]);
|
|
12
17
|
const DEFAULT_CONFIG_PATH = join(
|
|
13
18
|
process.env.XDG_CONFIG_HOME ?? join(os.homedir(), ".config"),
|
|
14
19
|
"image-skill",
|
|
@@ -79,37 +84,37 @@ async function main(rawArgv) {
|
|
|
79
84
|
try {
|
|
80
85
|
switch (command) {
|
|
81
86
|
case "doctor":
|
|
82
|
-
return doctor(rest);
|
|
87
|
+
return await doctor(rest);
|
|
83
88
|
case "signup":
|
|
84
|
-
return signup(rest);
|
|
89
|
+
return await signup(rest);
|
|
85
90
|
case "auth":
|
|
86
|
-
return auth(rest);
|
|
91
|
+
return await auth(rest);
|
|
87
92
|
case "whoami":
|
|
88
|
-
return whoami(rest);
|
|
93
|
+
return await whoami(rest);
|
|
89
94
|
case "usage":
|
|
90
|
-
return usage(rest);
|
|
95
|
+
return await usage(rest);
|
|
91
96
|
case "quota":
|
|
92
|
-
return quota(rest);
|
|
97
|
+
return await quota(rest);
|
|
93
98
|
case "credits":
|
|
94
|
-
return credits(rest);
|
|
99
|
+
return await credits(rest);
|
|
95
100
|
case "models":
|
|
96
|
-
return models(rest);
|
|
101
|
+
return await models(rest);
|
|
97
102
|
case "capabilities":
|
|
98
|
-
return capabilities(rest);
|
|
103
|
+
return await capabilities(rest);
|
|
99
104
|
case "create":
|
|
100
|
-
return create(rest);
|
|
105
|
+
return await create(rest);
|
|
101
106
|
case "upload":
|
|
102
|
-
return upload(rest);
|
|
107
|
+
return await upload(rest);
|
|
103
108
|
case "edit":
|
|
104
|
-
return edit(rest);
|
|
109
|
+
return await edit(rest);
|
|
105
110
|
case "assets":
|
|
106
|
-
return assets(rest);
|
|
111
|
+
return await assets(rest);
|
|
107
112
|
case "jobs":
|
|
108
|
-
return jobs(rest);
|
|
113
|
+
return await jobs(rest);
|
|
109
114
|
case "activity":
|
|
110
|
-
return activity(rest);
|
|
115
|
+
return await activity(rest);
|
|
111
116
|
case "feedback":
|
|
112
|
-
return feedback(rest);
|
|
117
|
+
return await feedback(rest);
|
|
113
118
|
default:
|
|
114
119
|
return failure(
|
|
115
120
|
`image-skill ${command}`,
|
|
@@ -209,6 +214,12 @@ async function signup(argv) {
|
|
|
209
214
|
}
|
|
210
215
|
const save = flagBool(args, "save");
|
|
211
216
|
const showToken = flagBool(args, "show-token");
|
|
217
|
+
if (save) {
|
|
218
|
+
const configReady = await assertConfigWritable("image-skill signup");
|
|
219
|
+
if (!configReady.ok) {
|
|
220
|
+
return configReady.result;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
212
223
|
const result = await apiRequest({
|
|
213
224
|
command: "image-skill signup",
|
|
214
225
|
method: "POST",
|
|
@@ -238,12 +249,16 @@ async function signup(argv) {
|
|
|
238
249
|
},
|
|
239
250
|
);
|
|
240
251
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
252
|
+
try {
|
|
253
|
+
await saveConfig({
|
|
254
|
+
api_base_url: apiBase(args),
|
|
255
|
+
token,
|
|
256
|
+
saved_at: new Date().toISOString(),
|
|
257
|
+
actor: result.envelope.actor ?? result.envelope.data?.actor ?? null,
|
|
258
|
+
});
|
|
259
|
+
} catch (error) {
|
|
260
|
+
return configWriteFailure("image-skill signup", error);
|
|
261
|
+
}
|
|
247
262
|
warnings.push(`saved hosted token to ${configPath()}`);
|
|
248
263
|
}
|
|
249
264
|
|
|
@@ -302,12 +317,20 @@ async function auth(argv) {
|
|
|
302
317
|
if (!token.ok) {
|
|
303
318
|
return token.result;
|
|
304
319
|
}
|
|
305
|
-
await
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
320
|
+
const configReady = await assertConfigWritable("image-skill auth save");
|
|
321
|
+
if (!configReady.ok) {
|
|
322
|
+
return configReady.result;
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
await saveConfig({
|
|
326
|
+
api_base_url: apiBase(args),
|
|
327
|
+
token: token.token,
|
|
328
|
+
saved_at: new Date().toISOString(),
|
|
329
|
+
actor: null,
|
|
330
|
+
});
|
|
331
|
+
} catch (error) {
|
|
332
|
+
return configWriteFailure("image-skill auth save", error);
|
|
333
|
+
}
|
|
311
334
|
return success("image-skill auth save", {
|
|
312
335
|
saved: true,
|
|
313
336
|
config_path: configPath(),
|
|
@@ -481,7 +504,7 @@ async function credits(argv) {
|
|
|
481
504
|
idempotency_key: idempotency.value,
|
|
482
505
|
},
|
|
483
506
|
});
|
|
484
|
-
return result;
|
|
507
|
+
return withStripeCheckoutCopyFallback(result);
|
|
485
508
|
}
|
|
486
509
|
if (subcommand === "fake-purchase") {
|
|
487
510
|
const args = parseArgs(rest);
|
|
@@ -528,13 +551,14 @@ async function credits(argv) {
|
|
|
528
551
|
addQueryFlag(query, args, "payment-attempt-id", "payment_attempt_id");
|
|
529
552
|
addQueryFlag(query, args, "checkout-session-id", "checkout_session_id");
|
|
530
553
|
addQueryFlag(query, args, "receipt-id", "receipt_id");
|
|
531
|
-
|
|
554
|
+
const result = await apiRequest({
|
|
532
555
|
command: "image-skill credits status",
|
|
533
556
|
method: "GET",
|
|
534
557
|
apiBaseUrl: apiBase(args),
|
|
535
558
|
path: `/v1/credit-purchases/status?${query.toString()}`,
|
|
536
559
|
token: token.token,
|
|
537
560
|
});
|
|
561
|
+
return withStripeCheckoutCopyFallback(result);
|
|
538
562
|
}
|
|
539
563
|
return invalid(
|
|
540
564
|
"image-skill credits",
|
|
@@ -629,10 +653,28 @@ async function create(argv) {
|
|
|
629
653
|
if (!token.ok) {
|
|
630
654
|
return token.result;
|
|
631
655
|
}
|
|
656
|
+
const referencePlan = parseReferencePlan(args, "image-skill create");
|
|
657
|
+
if (!referencePlan.ok) {
|
|
658
|
+
return referencePlan.result;
|
|
659
|
+
}
|
|
632
660
|
const modelParameters = jsonObjectFlag(args, "model-parameters-json");
|
|
633
661
|
if (!modelParameters.ok) {
|
|
634
662
|
return modelParameters.result;
|
|
635
663
|
}
|
|
664
|
+
const outputCount = positiveIntegerFlag(args, "output-count", {
|
|
665
|
+
command: "image-skill create",
|
|
666
|
+
});
|
|
667
|
+
if (!outputCount.ok) {
|
|
668
|
+
return outputCount.result;
|
|
669
|
+
}
|
|
670
|
+
const references = await resolveReferences(
|
|
671
|
+
referencePlan.referencePlans,
|
|
672
|
+
args,
|
|
673
|
+
token.token,
|
|
674
|
+
);
|
|
675
|
+
if (!references.ok) {
|
|
676
|
+
return references.result;
|
|
677
|
+
}
|
|
636
678
|
return apiRequest({
|
|
637
679
|
command: "image-skill create",
|
|
638
680
|
method: "POST",
|
|
@@ -651,6 +693,12 @@ async function create(argv) {
|
|
|
651
693
|
? {}
|
|
652
694
|
: { intent: flagString(args, "intent") }),
|
|
653
695
|
aspect_ratio: flagString(args, "aspect-ratio") ?? "1:1",
|
|
696
|
+
...(references.references.length === 0
|
|
697
|
+
? {}
|
|
698
|
+
: { references: references.references }),
|
|
699
|
+
...(outputCount.value === null
|
|
700
|
+
? {}
|
|
701
|
+
: { output_count: outputCount.value }),
|
|
654
702
|
...(flagNumber(args, "max-estimated-usd-per-image") === null
|
|
655
703
|
? {}
|
|
656
704
|
: {
|
|
@@ -695,13 +743,14 @@ async function upload(argv) {
|
|
|
695
743
|
async function edit(argv) {
|
|
696
744
|
const args = parseArgs(argv);
|
|
697
745
|
const input = flagString(args, "input") ?? args.positionals[0];
|
|
746
|
+
const modelId = flagString(args, "model");
|
|
698
747
|
if (input === undefined) {
|
|
699
748
|
return invalid(
|
|
700
749
|
"image-skill edit",
|
|
701
750
|
"edit requires --input ASSET_ID_OR_PATH_OR_URL",
|
|
702
751
|
);
|
|
703
752
|
}
|
|
704
|
-
const prompt = await
|
|
753
|
+
const prompt = await editPromptValue(args, modelId);
|
|
705
754
|
if (!prompt.ok) {
|
|
706
755
|
return prompt.result;
|
|
707
756
|
}
|
|
@@ -709,10 +758,28 @@ async function edit(argv) {
|
|
|
709
758
|
if (!token.ok) {
|
|
710
759
|
return token.result;
|
|
711
760
|
}
|
|
761
|
+
const referencePlan = parseReferencePlan(args, "image-skill edit");
|
|
762
|
+
if (!referencePlan.ok) {
|
|
763
|
+
return referencePlan.result;
|
|
764
|
+
}
|
|
712
765
|
const assetId = await resolveInputAssetId(input, args, token.token);
|
|
713
766
|
if (!assetId.ok) {
|
|
714
767
|
return assetId.result;
|
|
715
768
|
}
|
|
769
|
+
const mask = flagString(args, "mask");
|
|
770
|
+
const maskAssetId =
|
|
771
|
+
mask === null ? null : await resolveInputAssetId(mask, args, token.token);
|
|
772
|
+
if (maskAssetId !== null && !maskAssetId.ok) {
|
|
773
|
+
return maskAssetId.result;
|
|
774
|
+
}
|
|
775
|
+
const references = await resolveReferences(
|
|
776
|
+
referencePlan.referencePlans,
|
|
777
|
+
args,
|
|
778
|
+
token.token,
|
|
779
|
+
);
|
|
780
|
+
if (!references.ok) {
|
|
781
|
+
return references.result;
|
|
782
|
+
}
|
|
716
783
|
const modelParameters = jsonObjectFlag(args, "model-parameters-json");
|
|
717
784
|
if (!modelParameters.ok) {
|
|
718
785
|
return modelParameters.result;
|
|
@@ -725,13 +792,15 @@ async function edit(argv) {
|
|
|
725
792
|
token: token.token,
|
|
726
793
|
body: {
|
|
727
794
|
input_asset_id: assetId.assetId,
|
|
728
|
-
|
|
795
|
+
...(maskAssetId === null ? {} : { mask_asset_id: maskAssetId.assetId }),
|
|
796
|
+
...(references.references.length === 0
|
|
797
|
+
? {}
|
|
798
|
+
: { references: references.references }),
|
|
799
|
+
...(prompt.value.length === 0 ? {} : { prompt: prompt.value }),
|
|
729
800
|
...(flagString(args, "provider") === null
|
|
730
801
|
? {}
|
|
731
802
|
: { provider: flagString(args, "provider") }),
|
|
732
|
-
...(
|
|
733
|
-
? {}
|
|
734
|
-
: { model: flagString(args, "model") }),
|
|
803
|
+
...(modelId === null ? {} : { model: modelId }),
|
|
735
804
|
...(flagString(args, "intent") === null
|
|
736
805
|
? {}
|
|
737
806
|
: { intent: flagString(args, "intent") }),
|
|
@@ -1022,6 +1091,308 @@ async function resolveInputAssetId(input, args, token) {
|
|
|
1022
1091
|
return { ok: true, assetId };
|
|
1023
1092
|
}
|
|
1024
1093
|
|
|
1094
|
+
function parseReferencePlan(args, command) {
|
|
1095
|
+
for (const flag of [
|
|
1096
|
+
"element-frontal",
|
|
1097
|
+
"element-reference",
|
|
1098
|
+
"reference-image",
|
|
1099
|
+
]) {
|
|
1100
|
+
if (
|
|
1101
|
+
args.flags.has(flag) &&
|
|
1102
|
+
args.flags.get(flag)?.some((value) => typeof value !== "string")
|
|
1103
|
+
) {
|
|
1104
|
+
return {
|
|
1105
|
+
ok: false,
|
|
1106
|
+
result: invalid(command, `--${flag} requires an image`),
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
const referencePlans = [];
|
|
1111
|
+
for (const value of flagStrings(args, "element-frontal")) {
|
|
1112
|
+
const parsed = parseElementReferenceFlag(value, {
|
|
1113
|
+
flag: "--element-frontal",
|
|
1114
|
+
allowReferenceIndex: false,
|
|
1115
|
+
command,
|
|
1116
|
+
});
|
|
1117
|
+
if (!parsed.ok) {
|
|
1118
|
+
return parsed;
|
|
1119
|
+
}
|
|
1120
|
+
referencePlans.push({
|
|
1121
|
+
input: parsed.input,
|
|
1122
|
+
role: "element_frontal",
|
|
1123
|
+
index: parsed.index,
|
|
1124
|
+
referenceIndex: null,
|
|
1125
|
+
referenceTask: null,
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
for (const value of flagStrings(args, "element-reference")) {
|
|
1129
|
+
const parsed = parseElementReferenceFlag(value, {
|
|
1130
|
+
flag: "--element-reference",
|
|
1131
|
+
allowReferenceIndex: true,
|
|
1132
|
+
command,
|
|
1133
|
+
});
|
|
1134
|
+
if (!parsed.ok) {
|
|
1135
|
+
return parsed;
|
|
1136
|
+
}
|
|
1137
|
+
referencePlans.push({
|
|
1138
|
+
input: parsed.input,
|
|
1139
|
+
role: "element_reference",
|
|
1140
|
+
index: parsed.index,
|
|
1141
|
+
referenceIndex: parsed.referenceIndex,
|
|
1142
|
+
referenceTask: null,
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
for (const value of flagStrings(args, "reference-image")) {
|
|
1146
|
+
const parsed = parseReferenceImageFlag(value, {
|
|
1147
|
+
flag: "--reference-image",
|
|
1148
|
+
command,
|
|
1149
|
+
});
|
|
1150
|
+
if (!parsed.ok) {
|
|
1151
|
+
return parsed;
|
|
1152
|
+
}
|
|
1153
|
+
referencePlans.push({
|
|
1154
|
+
input: parsed.input,
|
|
1155
|
+
role: "reference_image",
|
|
1156
|
+
index: parsed.index,
|
|
1157
|
+
referenceIndex: null,
|
|
1158
|
+
referenceTask: parsed.referenceTask,
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
const planValidation = validateElementReferencePlan(referencePlans, command);
|
|
1162
|
+
if (!planValidation.ok) {
|
|
1163
|
+
return planValidation;
|
|
1164
|
+
}
|
|
1165
|
+
return { ok: true, referencePlans };
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
async function resolveReferences(referencePlans, args, token) {
|
|
1169
|
+
const references = [];
|
|
1170
|
+
for (const plan of referencePlans) {
|
|
1171
|
+
const assetId = await resolveInputAssetId(plan.input, args, token);
|
|
1172
|
+
if (!assetId.ok) {
|
|
1173
|
+
return assetId;
|
|
1174
|
+
}
|
|
1175
|
+
if (plan.role === "element_frontal") {
|
|
1176
|
+
references.push({
|
|
1177
|
+
asset_id: assetId.assetId,
|
|
1178
|
+
role: "element_frontal",
|
|
1179
|
+
index: plan.index,
|
|
1180
|
+
});
|
|
1181
|
+
continue;
|
|
1182
|
+
}
|
|
1183
|
+
if (plan.role === "reference_image") {
|
|
1184
|
+
references.push({
|
|
1185
|
+
asset_id: assetId.assetId,
|
|
1186
|
+
role: "reference_image",
|
|
1187
|
+
index: plan.index,
|
|
1188
|
+
...(plan.referenceTask === null
|
|
1189
|
+
? {}
|
|
1190
|
+
: { reference_task: plan.referenceTask }),
|
|
1191
|
+
});
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
references.push({
|
|
1195
|
+
asset_id: assetId.assetId,
|
|
1196
|
+
role: "element_reference",
|
|
1197
|
+
index: plan.index,
|
|
1198
|
+
...(plan.referenceIndex === null
|
|
1199
|
+
? {}
|
|
1200
|
+
: { reference_index: plan.referenceIndex }),
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
return { ok: true, references };
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
function validateElementReferencePlan(referencePlans, command) {
|
|
1207
|
+
if (referencePlans.length === 0) {
|
|
1208
|
+
return { ok: true };
|
|
1209
|
+
}
|
|
1210
|
+
const frontals = new Set();
|
|
1211
|
+
const referencesByElement = new Map();
|
|
1212
|
+
const elementIndexes = new Set();
|
|
1213
|
+
for (const plan of referencePlans) {
|
|
1214
|
+
if (plan.role === "reference_image") {
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
elementIndexes.add(plan.index);
|
|
1218
|
+
if (plan.role === "element_frontal") {
|
|
1219
|
+
if (frontals.has(plan.index)) {
|
|
1220
|
+
return {
|
|
1221
|
+
ok: false,
|
|
1222
|
+
result: invalid(
|
|
1223
|
+
command,
|
|
1224
|
+
`only one --element-frontal is allowed for element ${plan.index}`,
|
|
1225
|
+
),
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
frontals.add(plan.index);
|
|
1229
|
+
} else {
|
|
1230
|
+
const count = referencesByElement.get(plan.index) ?? 0;
|
|
1231
|
+
referencesByElement.set(plan.index, count + 1);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
const sortedIndexes = [...elementIndexes].sort((left, right) => left - right);
|
|
1236
|
+
for (let expected = 0; expected < sortedIndexes.length; expected += 1) {
|
|
1237
|
+
if (sortedIndexes[expected] !== expected) {
|
|
1238
|
+
return {
|
|
1239
|
+
ok: false,
|
|
1240
|
+
result: invalid(
|
|
1241
|
+
command,
|
|
1242
|
+
"element indexes must be contiguous starting at 0",
|
|
1243
|
+
),
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
for (const [index, count] of referencesByElement.entries()) {
|
|
1248
|
+
if (!frontals.has(index)) {
|
|
1249
|
+
return {
|
|
1250
|
+
ok: false,
|
|
1251
|
+
result: invalid(
|
|
1252
|
+
command,
|
|
1253
|
+
`--element-reference for element ${index} requires --element-frontal for the same element`,
|
|
1254
|
+
),
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
if (count > 3) {
|
|
1258
|
+
return {
|
|
1259
|
+
ok: false,
|
|
1260
|
+
result: invalid(
|
|
1261
|
+
command,
|
|
1262
|
+
`element ${index} accepts at most 3 --element-reference images`,
|
|
1263
|
+
),
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
const referenceImageIndexes = new Set();
|
|
1268
|
+
for (const plan of referencePlans) {
|
|
1269
|
+
if (plan.role !== "reference_image") {
|
|
1270
|
+
continue;
|
|
1271
|
+
}
|
|
1272
|
+
if (referenceImageIndexes.has(plan.index)) {
|
|
1273
|
+
return {
|
|
1274
|
+
ok: false,
|
|
1275
|
+
result: invalid(
|
|
1276
|
+
command,
|
|
1277
|
+
`only one --reference-image is allowed for index ${plan.index}`,
|
|
1278
|
+
),
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
referenceImageIndexes.add(plan.index);
|
|
1282
|
+
}
|
|
1283
|
+
const sortedReferenceImageIndexes = [...referenceImageIndexes].sort(
|
|
1284
|
+
(left, right) => left - right,
|
|
1285
|
+
);
|
|
1286
|
+
for (
|
|
1287
|
+
let expected = 0;
|
|
1288
|
+
expected < sortedReferenceImageIndexes.length;
|
|
1289
|
+
expected += 1
|
|
1290
|
+
) {
|
|
1291
|
+
if (sortedReferenceImageIndexes[expected] !== expected) {
|
|
1292
|
+
return {
|
|
1293
|
+
ok: false,
|
|
1294
|
+
result: invalid(
|
|
1295
|
+
command,
|
|
1296
|
+
"reference image indexes must be contiguous starting at 0",
|
|
1297
|
+
),
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
return { ok: true };
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
function parseReferenceImageFlag(value, options) {
|
|
1305
|
+
const parsed = parseReferenceImageSuffix(value);
|
|
1306
|
+
if (parsed.input.length === 0) {
|
|
1307
|
+
return {
|
|
1308
|
+
ok: false,
|
|
1309
|
+
result: invalid(options.command, `${options.flag} requires an image`),
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
if (parsed.index > 9) {
|
|
1313
|
+
return {
|
|
1314
|
+
ok: false,
|
|
1315
|
+
result: invalid(
|
|
1316
|
+
options.command,
|
|
1317
|
+
`${options.flag} index must be between 0 and 9`,
|
|
1318
|
+
),
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
return { ok: true, ...parsed };
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
function parseReferenceImageSuffix(value) {
|
|
1325
|
+
const atIndex = value.lastIndexOf("@");
|
|
1326
|
+
if (atIndex === -1) {
|
|
1327
|
+
return { input: value, index: 0, referenceTask: null };
|
|
1328
|
+
}
|
|
1329
|
+
const suffix = value.slice(atIndex + 1);
|
|
1330
|
+
if (!/^\d+(?::(?:ip|id|style))?$/.test(suffix)) {
|
|
1331
|
+
return { input: value, index: 0, referenceTask: null };
|
|
1332
|
+
}
|
|
1333
|
+
const [index, referenceTask] = suffix.split(":");
|
|
1334
|
+
return {
|
|
1335
|
+
input: value.slice(0, atIndex),
|
|
1336
|
+
index: Number(index),
|
|
1337
|
+
referenceTask: referenceTask ?? null,
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
function parseElementReferenceFlag(value, options) {
|
|
1342
|
+
const parsed = parseElementReferenceSuffix(value);
|
|
1343
|
+
if (parsed.input.length === 0) {
|
|
1344
|
+
return {
|
|
1345
|
+
ok: false,
|
|
1346
|
+
result: invalid(options.command, `${options.flag} requires an image`),
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
if (!options.allowReferenceIndex && parsed.referenceIndex !== null) {
|
|
1350
|
+
return {
|
|
1351
|
+
ok: false,
|
|
1352
|
+
result: invalid(
|
|
1353
|
+
options.command,
|
|
1354
|
+
`${options.flag} accepts IMAGE[@ELEMENT_INDEX], not a reference index`,
|
|
1355
|
+
),
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
if (parsed.index > 9) {
|
|
1359
|
+
return {
|
|
1360
|
+
ok: false,
|
|
1361
|
+
result: invalid(
|
|
1362
|
+
options.command,
|
|
1363
|
+
`${options.flag} element index must be between 0 and 9`,
|
|
1364
|
+
),
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
if (parsed.referenceIndex !== null && parsed.referenceIndex > 2) {
|
|
1368
|
+
return {
|
|
1369
|
+
ok: false,
|
|
1370
|
+
result: invalid(
|
|
1371
|
+
options.command,
|
|
1372
|
+
`${options.flag} reference index must be between 0 and 2`,
|
|
1373
|
+
),
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
return { ok: true, ...parsed };
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
function parseElementReferenceSuffix(value) {
|
|
1380
|
+
const atIndex = value.lastIndexOf("@");
|
|
1381
|
+
if (atIndex === -1) {
|
|
1382
|
+
return { input: value, index: 0, referenceIndex: null };
|
|
1383
|
+
}
|
|
1384
|
+
const suffix = value.slice(atIndex + 1);
|
|
1385
|
+
if (!/^\d+(?::\d+)?$/.test(suffix)) {
|
|
1386
|
+
return { input: value, index: 0, referenceIndex: null };
|
|
1387
|
+
}
|
|
1388
|
+
const [index, referenceIndex] = suffix.split(":").map((part) => Number(part));
|
|
1389
|
+
return {
|
|
1390
|
+
input: value.slice(0, atIndex),
|
|
1391
|
+
index,
|
|
1392
|
+
referenceIndex: referenceIndex ?? null,
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1025
1396
|
async function uploadPayload(input) {
|
|
1026
1397
|
const isRemote = /^https?:\/\//i.test(input);
|
|
1027
1398
|
let bytes;
|
|
@@ -1150,6 +1521,125 @@ function parseEnvelope(text, command, statusCode) {
|
|
|
1150
1521
|
};
|
|
1151
1522
|
}
|
|
1152
1523
|
|
|
1524
|
+
function withStripeCheckoutCopyFallback(result) {
|
|
1525
|
+
const data = result.envelope.data;
|
|
1526
|
+
if (!isRecord(data)) {
|
|
1527
|
+
return result;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
const updated = stripeCheckoutCopyFallbackData(data);
|
|
1531
|
+
if (updated === data) {
|
|
1532
|
+
return result;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
return {
|
|
1536
|
+
...result,
|
|
1537
|
+
envelope: {
|
|
1538
|
+
...result.envelope,
|
|
1539
|
+
data: updated,
|
|
1540
|
+
},
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
function stripeCheckoutCopyFallbackData(data) {
|
|
1545
|
+
let changed = false;
|
|
1546
|
+
const updated = { ...data };
|
|
1547
|
+
|
|
1548
|
+
if (addCheckoutCompactUrl(updated)) {
|
|
1549
|
+
changed = true;
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
if (isRecord(updated.next)) {
|
|
1553
|
+
const next = { ...updated.next };
|
|
1554
|
+
let nextChanged = addCheckoutCompactUrl(next);
|
|
1555
|
+
if (
|
|
1556
|
+
typeof updated.checkout_compact_url === "string" &&
|
|
1557
|
+
typeof next.checkout_compact_url !== "string"
|
|
1558
|
+
) {
|
|
1559
|
+
next.checkout_compact_url = updated.checkout_compact_url;
|
|
1560
|
+
nextChanged = true;
|
|
1561
|
+
}
|
|
1562
|
+
if (nextChanged) {
|
|
1563
|
+
updated.next = next;
|
|
1564
|
+
changed = true;
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
if (isRecord(updated.payment_attempt)) {
|
|
1569
|
+
const paymentAttempt = { ...updated.payment_attempt };
|
|
1570
|
+
if (addCheckoutCompactUrl(paymentAttempt)) {
|
|
1571
|
+
updated.payment_attempt = paymentAttempt;
|
|
1572
|
+
changed = true;
|
|
1573
|
+
}
|
|
1574
|
+
if (isRecord(updated.next)) {
|
|
1575
|
+
const next = { ...updated.next };
|
|
1576
|
+
if (
|
|
1577
|
+
next.human_action === "open_checkout_url" &&
|
|
1578
|
+
typeof paymentAttempt.checkout_compact_url === "string" &&
|
|
1579
|
+
typeof next.checkout_compact_url !== "string"
|
|
1580
|
+
) {
|
|
1581
|
+
next.checkout_compact_url = paymentAttempt.checkout_compact_url;
|
|
1582
|
+
updated.next = next;
|
|
1583
|
+
changed = true;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
return changed ? updated : data;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
function addCheckoutCompactUrl(record) {
|
|
1592
|
+
const raw =
|
|
1593
|
+
typeof record.checkout_url === "string"
|
|
1594
|
+
? record.checkout_url
|
|
1595
|
+
: typeof record.fallback_checkout_url === "string"
|
|
1596
|
+
? record.fallback_checkout_url
|
|
1597
|
+
: null;
|
|
1598
|
+
if (raw === null || raw.length === 0) {
|
|
1599
|
+
return false;
|
|
1600
|
+
}
|
|
1601
|
+
const compact = stripeCheckoutCompactUrl(raw);
|
|
1602
|
+
let changed = false;
|
|
1603
|
+
if (record.checkout_compact_url !== compact) {
|
|
1604
|
+
record.checkout_compact_url = compact;
|
|
1605
|
+
changed = true;
|
|
1606
|
+
}
|
|
1607
|
+
if (
|
|
1608
|
+
typeof record.checkout_url === "string" &&
|
|
1609
|
+
record.checkout_url !== compact
|
|
1610
|
+
) {
|
|
1611
|
+
record.checkout_url = compact;
|
|
1612
|
+
changed = true;
|
|
1613
|
+
}
|
|
1614
|
+
if (
|
|
1615
|
+
typeof record.fallback_checkout_url === "string" &&
|
|
1616
|
+
record.fallback_checkout_url !== compact
|
|
1617
|
+
) {
|
|
1618
|
+
record.fallback_checkout_url = compact;
|
|
1619
|
+
changed = true;
|
|
1620
|
+
}
|
|
1621
|
+
return changed;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
function stripeCheckoutCompactUrl(checkoutUrl) {
|
|
1625
|
+
const trimmed = checkoutUrl.trim();
|
|
1626
|
+
if (trimmed.length === 0) {
|
|
1627
|
+
return checkoutUrl;
|
|
1628
|
+
}
|
|
1629
|
+
try {
|
|
1630
|
+
const parsed = new URL(trimmed);
|
|
1631
|
+
parsed.hash = "";
|
|
1632
|
+
return parsed.toString();
|
|
1633
|
+
} catch {
|
|
1634
|
+
const hashIndex = trimmed.indexOf("#");
|
|
1635
|
+
return hashIndex === -1 ? trimmed : trimmed.slice(0, hashIndex);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
function isRecord(value) {
|
|
1640
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1153
1643
|
async function promptValue(args) {
|
|
1154
1644
|
const prompt = flagString(args, "prompt");
|
|
1155
1645
|
const promptFile = flagString(args, "prompt-file");
|
|
@@ -1174,6 +1664,69 @@ async function promptValue(args) {
|
|
|
1174
1664
|
};
|
|
1175
1665
|
}
|
|
1176
1666
|
|
|
1667
|
+
async function editPromptValue(args, modelId) {
|
|
1668
|
+
if (args.flags.has("prompt") && flagString(args, "prompt") === null) {
|
|
1669
|
+
return {
|
|
1670
|
+
ok: false,
|
|
1671
|
+
result: invalid("image-skill edit", "--prompt requires a value"),
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
if (
|
|
1675
|
+
args.flags.has("prompt-file") &&
|
|
1676
|
+
flagString(args, "prompt-file") === null
|
|
1677
|
+
) {
|
|
1678
|
+
return {
|
|
1679
|
+
ok: false,
|
|
1680
|
+
result: invalid("image-skill edit", "--prompt-file requires a value"),
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1683
|
+
const prompt = flagString(args, "prompt");
|
|
1684
|
+
const promptFile = flagString(args, "prompt-file");
|
|
1685
|
+
if (prompt !== null && promptFile !== null) {
|
|
1686
|
+
return {
|
|
1687
|
+
ok: false,
|
|
1688
|
+
result: invalid(
|
|
1689
|
+
"image-skill edit",
|
|
1690
|
+
"provide either --prompt or --prompt-file, not both",
|
|
1691
|
+
),
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
const isPromptlessModel =
|
|
1695
|
+
modelId !== null && PROMPTLESS_EDIT_MODEL_IDS.has(modelId);
|
|
1696
|
+
let value = null;
|
|
1697
|
+
if (prompt !== null) {
|
|
1698
|
+
value = prompt;
|
|
1699
|
+
} else if (promptFile !== null) {
|
|
1700
|
+
value = await readFile(promptFile, "utf8");
|
|
1701
|
+
}
|
|
1702
|
+
const trimmed = value?.trim() ?? "";
|
|
1703
|
+
if (isPromptlessModel) {
|
|
1704
|
+
if (trimmed.length > 0) {
|
|
1705
|
+
return {
|
|
1706
|
+
ok: false,
|
|
1707
|
+
result: invalid(
|
|
1708
|
+
"image-skill edit",
|
|
1709
|
+
`model ${modelId} does not accept --prompt`,
|
|
1710
|
+
),
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
return { ok: true, value: "" };
|
|
1714
|
+
}
|
|
1715
|
+
if (value === null) {
|
|
1716
|
+
return {
|
|
1717
|
+
ok: false,
|
|
1718
|
+
result: invalid("image-skill edit", "edit requires --prompt"),
|
|
1719
|
+
};
|
|
1720
|
+
}
|
|
1721
|
+
if (trimmed.length === 0) {
|
|
1722
|
+
return {
|
|
1723
|
+
ok: false,
|
|
1724
|
+
result: invalid("image-skill edit", "edit prompt cannot be empty"),
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
return { ok: true, value: trimmed };
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1177
1730
|
async function resolveToken(args, options = {}) {
|
|
1178
1731
|
if (flagBool(args, "token-stdin")) {
|
|
1179
1732
|
if (process.stdin.isTTY) {
|
|
@@ -1256,6 +1809,43 @@ async function saveConfig(value) {
|
|
|
1256
1809
|
await chmod(path, 0o600);
|
|
1257
1810
|
}
|
|
1258
1811
|
|
|
1812
|
+
async function assertConfigWritable(command) {
|
|
1813
|
+
const path = configPath();
|
|
1814
|
+
const probePath = `${path}.write-test-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
1815
|
+
try {
|
|
1816
|
+
await mkdir(dirname(path), { recursive: true });
|
|
1817
|
+
await writeFile(probePath, "", { mode: 0o600 });
|
|
1818
|
+
await chmod(probePath, 0o600);
|
|
1819
|
+
await rm(probePath, { force: true });
|
|
1820
|
+
return { ok: true };
|
|
1821
|
+
} catch (error) {
|
|
1822
|
+
await rm(probePath, { force: true }).catch(() => {});
|
|
1823
|
+
return {
|
|
1824
|
+
ok: false,
|
|
1825
|
+
result: configWriteFailure(command, error),
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
function configWriteFailure(command, error) {
|
|
1831
|
+
const message =
|
|
1832
|
+
error instanceof Error
|
|
1833
|
+
? error.message
|
|
1834
|
+
: "public CLI could not write its local auth config";
|
|
1835
|
+
return failure(
|
|
1836
|
+
command,
|
|
1837
|
+
9,
|
|
1838
|
+
"PUBLIC_CLI_CONFIG_WRITE_FAILED",
|
|
1839
|
+
`public CLI could not write auth config at ${configPath()}: ${message}`,
|
|
1840
|
+
true,
|
|
1841
|
+
{
|
|
1842
|
+
suggested_command:
|
|
1843
|
+
'IMAGE_SKILL_CONFIG_PATH="$PWD/.image-skill/config.json" image-skill signup --agent --agent-contact CONTACT_OR_SPONSOR_INBOX --agent-name NAME --runtime RUNTIME --save --json',
|
|
1844
|
+
docs_url: "https://image-skill.com/cli.md#local-config-and-install",
|
|
1845
|
+
},
|
|
1846
|
+
);
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1259
1849
|
function parseArgs(argv) {
|
|
1260
1850
|
const flags = new Map();
|
|
1261
1851
|
const positionals = [];
|
|
@@ -1293,6 +1883,12 @@ function flagString(args, name) {
|
|
|
1293
1883
|
return typeof value === "string" ? value : null;
|
|
1294
1884
|
}
|
|
1295
1885
|
|
|
1886
|
+
function flagStrings(args, name) {
|
|
1887
|
+
return (args.flags.get(name) ?? []).filter(
|
|
1888
|
+
(value) => typeof value === "string",
|
|
1889
|
+
);
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1296
1892
|
function flagBool(args, name) {
|
|
1297
1893
|
return args.flags.has(name) && args.flags.get(name)?.at(-1) !== "false";
|
|
1298
1894
|
}
|
|
@@ -1349,6 +1945,21 @@ function flagNumber(args, name) {
|
|
|
1349
1945
|
return Number.isFinite(number) ? number : null;
|
|
1350
1946
|
}
|
|
1351
1947
|
|
|
1948
|
+
function positiveIntegerFlag(args, name, input) {
|
|
1949
|
+
const value = flagString(args, name);
|
|
1950
|
+
if (value === null) {
|
|
1951
|
+
return { ok: true, value: null };
|
|
1952
|
+
}
|
|
1953
|
+
const number = Number(value);
|
|
1954
|
+
if (!Number.isInteger(number) || number <= 0) {
|
|
1955
|
+
return {
|
|
1956
|
+
ok: false,
|
|
1957
|
+
result: invalid(input.command, `${name} must be a positive integer`),
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
return { ok: true, value: number };
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1352
1963
|
function jsonObjectFlag(args, name) {
|
|
1353
1964
|
const raw = flagString(args, name);
|
|
1354
1965
|
if (raw === null) {
|