@yswgaicx/yswg-img-cli 0.1.5 → 0.1.7
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/README.md +35 -3
- package/package.json +1 -1
- package/src/cli.js +29 -9
- package/src/config.js +3 -2
- package/src/model-cache.js +162 -0
package/README.md
CHANGED
|
@@ -43,6 +43,18 @@ Use these rules when an AI agent calls the CLI:
|
|
|
43
43
|
yswg-img generate --prompt "一只可爱的白色兔子,粉色背景" --json
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
+
- Read the local model cache without calling the backend:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
yswg-img models --json
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- If model validation fails after backend permissions change, refresh the cache:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
yswg-img models refresh --json
|
|
56
|
+
```
|
|
57
|
+
|
|
46
58
|
- For up to 4 parallel images in one backend request, call:
|
|
47
59
|
|
|
48
60
|
```bash
|
|
@@ -204,18 +216,37 @@ The command calls `/prod-api/web/amazon/asin/{asin}` and returns the backend
|
|
|
204
216
|
|
|
205
217
|
## Configuration
|
|
206
218
|
|
|
207
|
-
|
|
219
|
+
Runtime flags override environment variables, which override the saved config.
|
|
220
|
+
The app ID is the exception: it is fixed to the `npm` app and cannot be
|
|
221
|
+
overridden.
|
|
208
222
|
|
|
209
223
|
- `YSWG_TOKEN`
|
|
210
224
|
- `YSWG_REFRESH_TOKEN`
|
|
211
|
-
- `YSWG_APP_ID`
|
|
212
225
|
- `YSWG_BASE_URL`
|
|
213
226
|
- `YSWG_WS_PATH`
|
|
214
227
|
|
|
215
|
-
Default app
|
|
228
|
+
Default app name: `npm`.
|
|
229
|
+
Default app ID: `2066371323654864898`.
|
|
230
|
+
The app ID is fixed. `--app-id` and `YSWG_APP_ID` are intentionally rejected.
|
|
216
231
|
Default ratio: `1:1`.
|
|
217
232
|
Default generation model: `gemini-3.1-flash-image-preview` (`groupId=6`, 三代).
|
|
218
233
|
|
|
234
|
+
## Model Cache
|
|
235
|
+
|
|
236
|
+
`yswg-img models --json` reads local metadata from
|
|
237
|
+
`~/.yswg-img-cli/model-cache.json`. If that file does not exist, the CLI falls
|
|
238
|
+
back to built-in `npm` app defaults. This keeps generation validation fast and
|
|
239
|
+
stable for agents.
|
|
240
|
+
|
|
241
|
+
Run this only when backend model permissions or model metadata change:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
yswg-img models refresh --json
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
If generation reports a model/group validation mismatch, refresh the cache
|
|
248
|
+
before submitting the same task again.
|
|
249
|
+
|
|
219
250
|
## Code Structure
|
|
220
251
|
|
|
221
252
|
- `bin/yswg-img.js`: executable entrypoint only.
|
|
@@ -223,5 +254,6 @@ Default generation model: `gemini-3.1-flash-image-preview` (`groupId=6`, 三代)
|
|
|
223
254
|
- `src/generate.js`: generation payloads, validation, history recovery, WebSocket wait, uploads, downloads.
|
|
224
255
|
- `src/api.js`: YSWG HTTP API client.
|
|
225
256
|
- `src/config.js`: config loading, env/flag precedence, email normalization.
|
|
257
|
+
- `src/model-cache.js`: built-in model metadata plus local cache refresh/read helpers.
|
|
226
258
|
- `src/args.js`: minimal CLI argument parsing.
|
|
227
259
|
- `src/image-compress.js`: frontend-compatible reference image compression.
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseArgs, readFlag, splitCsv } from "./args.js";
|
|
2
|
-
import { loadConfig, normalizeEmail, resolveConfig, saveConfig, DEFAULT_CONFIG_PATH } from "./config.js";
|
|
2
|
+
import { loadConfig, normalizeEmail, resolveConfig, saveConfig, DEFAULT_APP_ID, DEFAULT_APP_NAME, DEFAULT_CONFIG_PATH } from "./config.js";
|
|
3
|
+
import { loadModelCache, refreshModelCache } from "./model-cache.js";
|
|
3
4
|
import { YswgApi } from "./api.js";
|
|
4
5
|
import {
|
|
5
6
|
buildGeneratePayload,
|
|
@@ -25,8 +26,8 @@ export function buildHelpText() {
|
|
|
25
26
|
Commands:
|
|
26
27
|
auth send-code --email <name|email>
|
|
27
28
|
auth login --email <name|email> --code <6 digits>
|
|
28
|
-
models [--
|
|
29
|
-
templates [--
|
|
29
|
+
models [refresh] [--json]
|
|
30
|
+
templates [--json]
|
|
30
31
|
amazon asin <asin> [--max 7] [--json]
|
|
31
32
|
tasks search [--size 10] [--keyword <text>] [--tab all|expiring1d|expiring2d|nightQueue] [--json]
|
|
32
33
|
tasks get --id <record-id> [--json]
|
|
@@ -37,6 +38,9 @@ Commands:
|
|
|
37
38
|
|
|
38
39
|
Agent usage:
|
|
39
40
|
Always prefer --json for machine-readable output.
|
|
41
|
+
Default app: ${DEFAULT_APP_NAME} (${DEFAULT_APP_ID}).
|
|
42
|
+
The app ID is fixed; --app-id and YSWG_APP_ID are not supported.
|
|
43
|
+
Model metadata is read from local cache. Use "models refresh --json" after backend model changes.
|
|
40
44
|
Defaults: group-id=6, model=gemini-3.1-flash-image-preview, ratio=1:1, count=1.
|
|
41
45
|
One generate request supports count 1-4. For larger batches, split calls.
|
|
42
46
|
For long tasks, use --no-wait first, then tasks recover --task-id <id>.
|
|
@@ -44,7 +48,7 @@ Agent usage:
|
|
|
44
48
|
Amazon ASIN gallery lookup: amazon asin <asin> --max 7 --json returns asin, title, images.
|
|
45
49
|
|
|
46
50
|
Environment:
|
|
47
|
-
YSWG_TOKEN, YSWG_REFRESH_TOKEN,
|
|
51
|
+
YSWG_TOKEN, YSWG_REFRESH_TOKEN, YSWG_BASE_URL, YSWG_WS_PATH
|
|
48
52
|
|
|
49
53
|
Config:
|
|
50
54
|
${DEFAULT_CONFIG_PATH}
|
|
@@ -58,6 +62,11 @@ function writeOutput(write, data, json = false) {
|
|
|
58
62
|
write(`${text}\n`);
|
|
59
63
|
}
|
|
60
64
|
|
|
65
|
+
function rejectAppIdOverride(flags) {
|
|
66
|
+
if (flags.appId !== undefined) throw new Error("不允许传入 --app-id;CLI 固定使用 npm app。");
|
|
67
|
+
if (process.env.YSWG_APP_ID) throw new Error("不允许设置 YSWG_APP_ID;CLI 固定使用 npm app。");
|
|
68
|
+
}
|
|
69
|
+
|
|
61
70
|
function assertAuthenticated(resolved) {
|
|
62
71
|
if (!resolved.token) throw new Error("missing token; run auth login or set YSWG_TOKEN");
|
|
63
72
|
}
|
|
@@ -109,7 +118,7 @@ async function handleGenerate({ api, config, flags, resolved, json, write }) {
|
|
|
109
118
|
});
|
|
110
119
|
|
|
111
120
|
if (!flags.skipValidate) {
|
|
112
|
-
const modelGroups = await
|
|
121
|
+
const modelGroups = await loadModelCache();
|
|
113
122
|
validateGenerateOptions({
|
|
114
123
|
model: findModelByGroupId(modelGroups, groupId),
|
|
115
124
|
ratio,
|
|
@@ -180,7 +189,7 @@ async function handleTaskCommand({ api, config, flags, resolved, subcommand, jso
|
|
|
180
189
|
tab: String(readFlag(flags, ["tab"], "all")),
|
|
181
190
|
keyword: String(readFlag(flags, ["keyword", "q"], "")),
|
|
182
191
|
userId: getUserId(config),
|
|
183
|
-
appId:
|
|
192
|
+
appId: resolved.appId,
|
|
184
193
|
size: readFlag(flags, ["size"], 10),
|
|
185
194
|
}));
|
|
186
195
|
writeOutput(write, result, json);
|
|
@@ -210,7 +219,7 @@ async function handleTaskCommand({ api, config, flags, resolved, subcommand, jso
|
|
|
210
219
|
if (!taskIds.length) throw new Error("missing --task-id");
|
|
211
220
|
const records = await findRecordsByInvokeTaskIds(api, {
|
|
212
221
|
taskIds,
|
|
213
|
-
appId:
|
|
222
|
+
appId: resolved.appId,
|
|
214
223
|
userId: getUserId(config),
|
|
215
224
|
pageSize: normalizeHistoryLimit(readFlag(flags, ["size"], 10)),
|
|
216
225
|
maxPages: 1,
|
|
@@ -247,6 +256,8 @@ async function handleAmazonCommand({ api, flags, resolved, subcommand, positiona
|
|
|
247
256
|
export async function runCli(argv, {
|
|
248
257
|
loadConfigFn = loadConfig,
|
|
249
258
|
saveConfigFn = saveConfig,
|
|
259
|
+
loadModelCacheFn = loadModelCache,
|
|
260
|
+
refreshModelCacheFn = refreshModelCache,
|
|
250
261
|
createApi = (resolved) => new YswgApi(resolved),
|
|
251
262
|
write = (text) => process.stdout.write(text),
|
|
252
263
|
} = {}) {
|
|
@@ -257,6 +268,7 @@ export async function runCli(argv, {
|
|
|
257
268
|
write(buildHelpText());
|
|
258
269
|
return;
|
|
259
270
|
}
|
|
271
|
+
rejectAppIdOverride(flags);
|
|
260
272
|
|
|
261
273
|
const config = await loadConfigFn();
|
|
262
274
|
const resolved = resolveConfig(config, flags);
|
|
@@ -291,13 +303,21 @@ export async function runCli(argv, {
|
|
|
291
303
|
}
|
|
292
304
|
|
|
293
305
|
if (command === "models") {
|
|
294
|
-
|
|
306
|
+
if (subcommand === "refresh") {
|
|
307
|
+
assertAuthenticated(resolved);
|
|
308
|
+
const result = await refreshModelCacheFn({
|
|
309
|
+
fetchModels: () => api.models(resolved.appId),
|
|
310
|
+
});
|
|
311
|
+
writeOutput(write, { ok: true, ...result }, json);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
writeOutput(write, await loadModelCacheFn(), json);
|
|
295
315
|
return;
|
|
296
316
|
}
|
|
297
317
|
|
|
298
318
|
if (command === "templates") {
|
|
299
319
|
assertAuthenticated(resolved);
|
|
300
|
-
writeOutput(write, await api.templates(
|
|
320
|
+
writeOutput(write, await api.templates(resolved.appId), json);
|
|
301
321
|
return;
|
|
302
322
|
}
|
|
303
323
|
|
package/src/config.js
CHANGED
|
@@ -4,7 +4,8 @@ import { homedir } from "node:os";
|
|
|
4
4
|
|
|
5
5
|
export const DEFAULT_CONFIG_PATH = join(homedir(), ".yswg-img-cli", "config.json");
|
|
6
6
|
export const DEFAULT_BASE_URL = "https://www.yswg.love";
|
|
7
|
-
export const
|
|
7
|
+
export const DEFAULT_APP_NAME = "npm";
|
|
8
|
+
export const DEFAULT_APP_ID = "2066371323654864898";
|
|
8
9
|
export const DEFAULT_WS_PATH = "/websocket";
|
|
9
10
|
|
|
10
11
|
export async function loadConfig(path = DEFAULT_CONFIG_PATH) {
|
|
@@ -25,7 +26,7 @@ export async function saveConfig(config, path = DEFAULT_CONFIG_PATH) {
|
|
|
25
26
|
export function resolveConfig(config, flags = {}) {
|
|
26
27
|
return {
|
|
27
28
|
baseUrl: flags.baseUrl || process.env.YSWG_BASE_URL || config.baseUrl || DEFAULT_BASE_URL,
|
|
28
|
-
appId:
|
|
29
|
+
appId: DEFAULT_APP_ID,
|
|
29
30
|
token: flags.token || process.env.YSWG_TOKEN || config.token || "",
|
|
30
31
|
refreshToken: flags.refreshToken || process.env.YSWG_REFRESH_TOKEN || config.refreshToken || "",
|
|
31
32
|
wsPath: flags.wsPath || process.env.YSWG_WS_PATH || config.wsPath || DEFAULT_WS_PATH,
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_MODEL_CACHE_PATH = join(homedir(), ".yswg-img-cli", "model-cache.json");
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_MODEL_GROUPS = [
|
|
8
|
+
{
|
|
9
|
+
typeId: "2066371840108666882",
|
|
10
|
+
typeName: "npm",
|
|
11
|
+
typeCode: "npm",
|
|
12
|
+
sort: 0,
|
|
13
|
+
enabled: true,
|
|
14
|
+
bindingType: 1,
|
|
15
|
+
templates: [],
|
|
16
|
+
models: [
|
|
17
|
+
{
|
|
18
|
+
groupId: "2",
|
|
19
|
+
modelName: "gemini-3-pro-image-preview",
|
|
20
|
+
size: "1k",
|
|
21
|
+
label: "天才猴子二代1k",
|
|
22
|
+
points: 8,
|
|
23
|
+
imageGenOptions: { imageCountEnabled: true, imageRadioEnabled: true },
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
groupId: "3",
|
|
27
|
+
modelName: "gemini-3-pro-image-preview",
|
|
28
|
+
size: "2k",
|
|
29
|
+
label: "天才猴子二代2k",
|
|
30
|
+
points: 8,
|
|
31
|
+
imageGenOptions: { imageCountEnabled: true, imageRadioEnabled: true },
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
groupId: "5",
|
|
35
|
+
modelName: "gemini-3-pro-image-preview",
|
|
36
|
+
size: "4k",
|
|
37
|
+
label: "天才猴子二代4k",
|
|
38
|
+
points: 8,
|
|
39
|
+
imageGenOptions: { imageCountEnabled: true, imageRadioEnabled: true },
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
groupId: "1",
|
|
43
|
+
modelName: "gemini-2.5-flash-image",
|
|
44
|
+
size: "1k",
|
|
45
|
+
label: "天才猴子一代",
|
|
46
|
+
points: 1,
|
|
47
|
+
imageGenOptions: { imageCountEnabled: true, imageRadioEnabled: true },
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
groupId: "4",
|
|
51
|
+
modelName: "gemini-3.1-flash-image-preview",
|
|
52
|
+
size: "2k",
|
|
53
|
+
label: "天才猴子三代2k",
|
|
54
|
+
points: 2,
|
|
55
|
+
imageGenOptions: { imageCountEnabled: true, imageRadioEnabled: true },
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
groupId: "6",
|
|
59
|
+
modelName: "gemini-3.1-flash-image-preview",
|
|
60
|
+
size: "4k",
|
|
61
|
+
label: "天才猴子三代4k",
|
|
62
|
+
points: 2,
|
|
63
|
+
imageGenOptions: { imageCountEnabled: true, imageRadioEnabled: true },
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
groupId: "2046043157249933314",
|
|
67
|
+
modelName: "图灵二代",
|
|
68
|
+
size: "1k",
|
|
69
|
+
label: "图灵二代",
|
|
70
|
+
points: 2,
|
|
71
|
+
imageGenOptions: { imageCountEnabled: true, imageRadioEnabled: true },
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
groupId: "2046853405565095939",
|
|
75
|
+
modelName: "图灵二代(高清)",
|
|
76
|
+
size: "2k",
|
|
77
|
+
label: "图灵二代高清",
|
|
78
|
+
points: 2,
|
|
79
|
+
imageGenOptions: { imageCountEnabled: true, imageRadioEnabled: true },
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
groupId: "2046853405565095940",
|
|
83
|
+
modelName: "图灵二代(超清)",
|
|
84
|
+
size: "4k",
|
|
85
|
+
label: "图灵二代超清",
|
|
86
|
+
points: 2,
|
|
87
|
+
imageGenOptions: { imageCountEnabled: true, imageRadioEnabled: true },
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
groupId: "12",
|
|
91
|
+
modelName: "gemini-2.5-flash-lite",
|
|
92
|
+
size: "",
|
|
93
|
+
label: "一代文本",
|
|
94
|
+
points: 1,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
groupId: "13",
|
|
98
|
+
modelName: "gemini-3-flash-preview",
|
|
99
|
+
size: "",
|
|
100
|
+
label: "二代文本",
|
|
101
|
+
points: 1,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
groupId: "930000000000000301",
|
|
105
|
+
modelName: "seedance-2-0",
|
|
106
|
+
size: "720p",
|
|
107
|
+
label: "Seedance 2.0",
|
|
108
|
+
points: 6,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
groupId: "910000000000000304",
|
|
112
|
+
modelName: "kling-3.0-omni",
|
|
113
|
+
size: "720p",
|
|
114
|
+
label: "kling 3.0",
|
|
115
|
+
points: 5,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
groupId: "21",
|
|
119
|
+
modelName: "grok_video",
|
|
120
|
+
size: "720P",
|
|
121
|
+
label: "Grok视频",
|
|
122
|
+
points: 15,
|
|
123
|
+
skipAspectRatio: true,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
groupId: "17",
|
|
127
|
+
modelName: "veo_fast",
|
|
128
|
+
size: "720x1280",
|
|
129
|
+
label: "VEO",
|
|
130
|
+
points: 15,
|
|
131
|
+
skipAspectRatio: true,
|
|
132
|
+
imageGenOptions: { imageCountEnabled: true, imageRadioEnabled: true },
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
export async function loadModelCache(cachePath = DEFAULT_MODEL_CACHE_PATH) {
|
|
139
|
+
try {
|
|
140
|
+
const text = await readFile(cachePath, "utf8");
|
|
141
|
+
const parsed = JSON.parse(text);
|
|
142
|
+
return Array.isArray(parsed?.models) ? parsed.models : parsed;
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (error.code === "ENOENT") return DEFAULT_MODEL_GROUPS;
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function refreshModelCache({
|
|
150
|
+
cachePath = DEFAULT_MODEL_CACHE_PATH,
|
|
151
|
+
fetchModels,
|
|
152
|
+
} = {}) {
|
|
153
|
+
if (typeof fetchModels !== "function") throw new Error("missing fetchModels");
|
|
154
|
+
const models = await fetchModels();
|
|
155
|
+
const payload = {
|
|
156
|
+
refreshedAt: new Date().toISOString(),
|
|
157
|
+
models,
|
|
158
|
+
};
|
|
159
|
+
await mkdir(dirname(cachePath), { recursive: true });
|
|
160
|
+
await writeFile(cachePath, `${JSON.stringify(payload, null, 2)}\n`, { mode: 0o600 });
|
|
161
|
+
return { cachePath, models };
|
|
162
|
+
}
|