@yswgaicx/yswg-img-cli 0.1.7 → 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/README.md +52 -0
- package/package.json +1 -1
- package/src/cli.js +47 -3
- package/src/version-check.js +97 -0
package/README.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
`yswg-img` wraps the YSWG Monkey Genius image generation page as a CLI.
|
|
4
4
|
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Install from npm on any machine with Node.js 22 or newer:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @yswgaicx/yswg-img-cli@latest
|
|
11
|
+
yswg-img --help
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Upgrade an existing global install:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g @yswgaicx/yswg-img-cli@latest
|
|
18
|
+
yswg-img version --json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`yswg-img` checks npm for newer versions at most once every 12 hours and writes
|
|
22
|
+
upgrade hints to stderr, so `--json` stdout stays parseable. Disable this check
|
|
23
|
+
with `--no-update-check` or `YSWG_IMG_CLI_NO_UPDATE_CHECK=1`.
|
|
24
|
+
|
|
5
25
|
## Install Locally
|
|
6
26
|
|
|
7
27
|
```bash
|
|
@@ -55,6 +75,12 @@ yswg-img models --json
|
|
|
55
75
|
yswg-img models refresh --json
|
|
56
76
|
```
|
|
57
77
|
|
|
78
|
+
- Check whether the globally installed CLI is current:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
yswg-img version --json
|
|
82
|
+
```
|
|
83
|
+
|
|
58
84
|
- For up to 4 parallel images in one backend request, call:
|
|
59
85
|
|
|
60
86
|
```bash
|
|
@@ -224,6 +250,7 @@ overridden.
|
|
|
224
250
|
- `YSWG_REFRESH_TOKEN`
|
|
225
251
|
- `YSWG_BASE_URL`
|
|
226
252
|
- `YSWG_WS_PATH`
|
|
253
|
+
- `YSWG_IMG_CLI_NO_UPDATE_CHECK`
|
|
227
254
|
|
|
228
255
|
Default app name: `npm`.
|
|
229
256
|
Default app ID: `2066371323654864898`.
|
|
@@ -247,6 +274,30 @@ yswg-img models refresh --json
|
|
|
247
274
|
If generation reports a model/group validation mismatch, refresh the cache
|
|
248
275
|
before submitting the same task again.
|
|
249
276
|
|
|
277
|
+
## Version Updates
|
|
278
|
+
|
|
279
|
+
Use `yswg-img version --json` for a machine-readable version check:
|
|
280
|
+
|
|
281
|
+
```json
|
|
282
|
+
{
|
|
283
|
+
"currentVersion": "0.1.7",
|
|
284
|
+
"latestVersion": "0.1.8",
|
|
285
|
+
"updateAvailable": true,
|
|
286
|
+
"upgradeCommand": "npm install -g @yswgaicx/yswg-img-cli@latest"
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Use `yswg-img upgrade --json` to print the canonical upgrade command without
|
|
291
|
+
touching npm:
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
yswg-img upgrade --json
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
The CLI caches npm latest metadata in `~/.yswg-img-cli/update-check.json`.
|
|
298
|
+
Regular commands emit update hints to stderr only, preserving stdout for JSON
|
|
299
|
+
parsers and agents.
|
|
300
|
+
|
|
250
301
|
## Code Structure
|
|
251
302
|
|
|
252
303
|
- `bin/yswg-img.js`: executable entrypoint only.
|
|
@@ -255,5 +306,6 @@ before submitting the same task again.
|
|
|
255
306
|
- `src/api.js`: YSWG HTTP API client.
|
|
256
307
|
- `src/config.js`: config loading, env/flag precedence, email normalization.
|
|
257
308
|
- `src/model-cache.js`: built-in model metadata plus local cache refresh/read helpers.
|
|
309
|
+
- `src/version-check.js`: npm latest lookup, update cache, version comparison, upgrade command.
|
|
258
310
|
- `src/args.js`: minimal CLI argument parsing.
|
|
259
311
|
- `src/image-compress.js`: frontend-compatible reference image compression.
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseArgs, readFlag, splitCsv } from "./args.js";
|
|
2
2
|
import { loadConfig, normalizeEmail, resolveConfig, saveConfig, DEFAULT_APP_ID, DEFAULT_APP_NAME, DEFAULT_CONFIG_PATH } from "./config.js";
|
|
3
3
|
import { loadModelCache, refreshModelCache } from "./model-cache.js";
|
|
4
|
+
import { buildUpgradeCommand, checkForUpdate, PACKAGE_NAME } from "./version-check.js";
|
|
4
5
|
import { YswgApi } from "./api.js";
|
|
5
6
|
import {
|
|
6
7
|
buildGeneratePayload,
|
|
@@ -26,6 +27,8 @@ export function buildHelpText() {
|
|
|
26
27
|
Commands:
|
|
27
28
|
auth send-code --email <name|email>
|
|
28
29
|
auth login --email <name|email> --code <6 digits>
|
|
30
|
+
version [--json]
|
|
31
|
+
upgrade [--json]
|
|
29
32
|
models [refresh] [--json]
|
|
30
33
|
templates [--json]
|
|
31
34
|
amazon asin <asin> [--max 7] [--json]
|
|
@@ -46,9 +49,10 @@ Agent usage:
|
|
|
46
49
|
For long tasks, use --no-wait first, then tasks recover --task-id <id>.
|
|
47
50
|
Keep generated image file paths and short summaries in agent context; do not paste image bytes or base64.
|
|
48
51
|
Amazon ASIN gallery lookup: amazon asin <asin> --max 7 --json returns asin, title, images.
|
|
52
|
+
Upgrade globally with: ${buildUpgradeCommand()}
|
|
49
53
|
|
|
50
54
|
Environment:
|
|
51
|
-
YSWG_TOKEN, YSWG_REFRESH_TOKEN, YSWG_BASE_URL, YSWG_WS_PATH
|
|
55
|
+
YSWG_TOKEN, YSWG_REFRESH_TOKEN, YSWG_BASE_URL, YSWG_WS_PATH, YSWG_IMG_CLI_NO_UPDATE_CHECK
|
|
52
56
|
|
|
53
57
|
Config:
|
|
54
58
|
${DEFAULT_CONFIG_PATH}
|
|
@@ -71,6 +75,17 @@ function assertAuthenticated(resolved) {
|
|
|
71
75
|
if (!resolved.token) throw new Error("missing token; run auth login or set YSWG_TOKEN");
|
|
72
76
|
}
|
|
73
77
|
|
|
78
|
+
function shouldSkipUpdateCheck(flags) {
|
|
79
|
+
return Boolean(flags.noUpdateCheck || process.env.YSWG_IMG_CLI_NO_UPDATE_CHECK);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function emitUpdateHint({ flags, checkForUpdateFn, writeErr }) {
|
|
83
|
+
if (shouldSkipUpdateCheck(flags)) return;
|
|
84
|
+
const result = await checkForUpdateFn();
|
|
85
|
+
if (!result?.updateAvailable) return;
|
|
86
|
+
writeErr(`yswg-img: 发现新版本 ${result.latestVersion},当前版本 ${result.currentVersion}。升级:${result.upgradeCommand}\n`);
|
|
87
|
+
}
|
|
88
|
+
|
|
74
89
|
function getUserId(config) {
|
|
75
90
|
return config.user?.userId || config.user?.id;
|
|
76
91
|
}
|
|
@@ -258,8 +273,10 @@ export async function runCli(argv, {
|
|
|
258
273
|
saveConfigFn = saveConfig,
|
|
259
274
|
loadModelCacheFn = loadModelCache,
|
|
260
275
|
refreshModelCacheFn = refreshModelCache,
|
|
276
|
+
checkForUpdateFn = checkForUpdate,
|
|
261
277
|
createApi = (resolved) => new YswgApi(resolved),
|
|
262
278
|
write = (text) => process.stdout.write(text),
|
|
279
|
+
writeErr = (text) => process.stderr.write(text),
|
|
263
280
|
} = {}) {
|
|
264
281
|
const parsed = parseArgs(argv);
|
|
265
282
|
const { command, subcommand, flags, positionals } = parsed;
|
|
@@ -268,6 +285,21 @@ export async function runCli(argv, {
|
|
|
268
285
|
write(buildHelpText());
|
|
269
286
|
return;
|
|
270
287
|
}
|
|
288
|
+
|
|
289
|
+
if (command === "version") {
|
|
290
|
+
writeOutput(write, await checkForUpdateFn({ force: true }), json);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (command === "upgrade") {
|
|
295
|
+
writeOutput(write, {
|
|
296
|
+
packageName: PACKAGE_NAME,
|
|
297
|
+
upgradeCommand: buildUpgradeCommand(),
|
|
298
|
+
checkCommand: "yswg-img version --json",
|
|
299
|
+
}, json);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
271
303
|
rejectAppIdOverride(flags);
|
|
272
304
|
|
|
273
305
|
const config = await loadConfigFn();
|
|
@@ -280,6 +312,7 @@ export async function runCli(argv, {
|
|
|
280
312
|
if (check?.registered === false) throw new Error(check.message || "email is not registered");
|
|
281
313
|
await api.sendCode(email);
|
|
282
314
|
writeOutput(write, { ok: true, email, message: "验证码已发送,请在企业微信中查收" }, json);
|
|
315
|
+
await emitUpdateHint({ flags, checkForUpdateFn, writeErr });
|
|
283
316
|
return;
|
|
284
317
|
}
|
|
285
318
|
|
|
@@ -299,6 +332,7 @@ export async function runCli(argv, {
|
|
|
299
332
|
email,
|
|
300
333
|
});
|
|
301
334
|
writeOutput(write, { ok: true, email, configPath: DEFAULT_CONFIG_PATH }, json);
|
|
335
|
+
await emitUpdateHint({ flags, checkForUpdateFn, writeErr });
|
|
302
336
|
return;
|
|
303
337
|
}
|
|
304
338
|
|
|
@@ -309,22 +343,32 @@ export async function runCli(argv, {
|
|
|
309
343
|
fetchModels: () => api.models(resolved.appId),
|
|
310
344
|
});
|
|
311
345
|
writeOutput(write, { ok: true, ...result }, json);
|
|
346
|
+
await emitUpdateHint({ flags, checkForUpdateFn, writeErr });
|
|
312
347
|
return;
|
|
313
348
|
}
|
|
314
349
|
writeOutput(write, await loadModelCacheFn(), json);
|
|
350
|
+
await emitUpdateHint({ flags, checkForUpdateFn, writeErr });
|
|
315
351
|
return;
|
|
316
352
|
}
|
|
317
353
|
|
|
318
354
|
if (command === "templates") {
|
|
319
355
|
assertAuthenticated(resolved);
|
|
320
356
|
writeOutput(write, await api.templates(resolved.appId), json);
|
|
357
|
+
await emitUpdateHint({ flags, checkForUpdateFn, writeErr });
|
|
321
358
|
return;
|
|
322
359
|
}
|
|
323
360
|
|
|
324
|
-
if (command === "amazon" && await handleAmazonCommand({ api, flags, resolved, subcommand, positionals, json, write }))
|
|
325
|
-
|
|
361
|
+
if (command === "amazon" && await handleAmazonCommand({ api, flags, resolved, subcommand, positionals, json, write })) {
|
|
362
|
+
await emitUpdateHint({ flags, checkForUpdateFn, writeErr });
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (command === "tasks" && await handleTaskCommand({ api, config, flags, resolved, subcommand, json, write })) {
|
|
366
|
+
await emitUpdateHint({ flags, checkForUpdateFn, writeErr });
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
326
369
|
if (command === "generate") {
|
|
327
370
|
await handleGenerate({ api, config, flags, resolved, json, write });
|
|
371
|
+
await emitUpdateHint({ flags, checkForUpdateFn, writeErr });
|
|
328
372
|
return;
|
|
329
373
|
}
|
|
330
374
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
|
|
6
|
+
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
7
|
+
|
|
8
|
+
export const PACKAGE_NAME = packageJson.name;
|
|
9
|
+
export const CURRENT_VERSION = packageJson.version;
|
|
10
|
+
export const DEFAULT_UPDATE_CACHE_PATH = join(homedir(), ".yswg-img-cli", "update-check.json");
|
|
11
|
+
export const UPDATE_CHECK_TTL_MS = 12 * 60 * 60 * 1000;
|
|
12
|
+
|
|
13
|
+
function parseVersion(version) {
|
|
14
|
+
return String(version || "")
|
|
15
|
+
.replace(/^v/i, "")
|
|
16
|
+
.split("-")[0]
|
|
17
|
+
.split(".")
|
|
18
|
+
.map((part) => Number.parseInt(part, 10) || 0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function compareVersions(left, right) {
|
|
22
|
+
const a = parseVersion(left);
|
|
23
|
+
const b = parseVersion(right);
|
|
24
|
+
const length = Math.max(a.length, b.length, 3);
|
|
25
|
+
for (let index = 0; index < length; index += 1) {
|
|
26
|
+
const diff = (a[index] || 0) - (b[index] || 0);
|
|
27
|
+
if (diff > 0) return 1;
|
|
28
|
+
if (diff < 0) return -1;
|
|
29
|
+
}
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function buildUpgradeCommand() {
|
|
34
|
+
return `npm install -g ${PACKAGE_NAME}@latest`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function fetchNpmLatestVersion(packageName = PACKAGE_NAME) {
|
|
38
|
+
const encodedName = encodeURIComponent(packageName);
|
|
39
|
+
const response = await fetch(`https://registry.npmjs.org/${encodedName}/latest`, {
|
|
40
|
+
headers: { accept: "application/json" },
|
|
41
|
+
});
|
|
42
|
+
if (!response.ok) throw new Error(`npm registry returned ${response.status}`);
|
|
43
|
+
const data = await response.json();
|
|
44
|
+
if (!data?.version) throw new Error("npm registry response missing version");
|
|
45
|
+
return data.version;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function readFreshCache(cachePath, now, ttlMs) {
|
|
49
|
+
try {
|
|
50
|
+
const cache = JSON.parse(await readFile(cachePath, "utf8"));
|
|
51
|
+
const checkedAt = new Date(cache.checkedAt).getTime();
|
|
52
|
+
if (!Number.isFinite(checkedAt) || now.getTime() - checkedAt > ttlMs) return null;
|
|
53
|
+
return cache.latestVersion ? cache : null;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (error.code === "ENOENT") return null;
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function writeCache(cachePath, payload) {
|
|
61
|
+
await mkdir(dirname(cachePath), { recursive: true });
|
|
62
|
+
await writeFile(cachePath, `${JSON.stringify(payload, null, 2)}\n`, { mode: 0o600 });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function checkForUpdate({
|
|
66
|
+
cachePath = DEFAULT_UPDATE_CACHE_PATH,
|
|
67
|
+
currentVersion = CURRENT_VERSION,
|
|
68
|
+
fetchLatestVersion = fetchNpmLatestVersion,
|
|
69
|
+
now = new Date(),
|
|
70
|
+
ttlMs = UPDATE_CHECK_TTL_MS,
|
|
71
|
+
force = false,
|
|
72
|
+
} = {}) {
|
|
73
|
+
try {
|
|
74
|
+
const cached = force ? null : await readFreshCache(cachePath, now, ttlMs);
|
|
75
|
+
const latestVersion = cached?.latestVersion || await fetchLatestVersion(PACKAGE_NAME);
|
|
76
|
+
if (!cached) {
|
|
77
|
+
await writeCache(cachePath, {
|
|
78
|
+
checkedAt: now.toISOString(),
|
|
79
|
+
latestVersion,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
currentVersion,
|
|
84
|
+
latestVersion,
|
|
85
|
+
updateAvailable: compareVersions(latestVersion, currentVersion) > 0,
|
|
86
|
+
upgradeCommand: buildUpgradeCommand(),
|
|
87
|
+
};
|
|
88
|
+
} catch (error) {
|
|
89
|
+
return {
|
|
90
|
+
currentVersion,
|
|
91
|
+
latestVersion: currentVersion,
|
|
92
|
+
updateAvailable: false,
|
|
93
|
+
upgradeCommand: buildUpgradeCommand(),
|
|
94
|
+
error: error.message,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|