u1s1-cli 1.15.1 → 1.16.0
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/dist/agents/pi.js +3 -5
- package/dist/api.js +1 -1
- package/dist/config.d.ts +20 -4
- package/dist/config.js +41 -8
- package/dist/index.js +9 -5
- package/dist/update.d.ts +15 -1
- package/dist/update.js +41 -7
- package/dist/usage.d.ts +44 -1
- package/dist/usage.js +64 -2
- package/package.json +4 -2
package/dist/agents/pi.js
CHANGED
|
@@ -20,7 +20,7 @@ import { deployHintText, markNudge, shouldShowDeployHint } from "../nudges.js";
|
|
|
20
20
|
import { createGatewayTelemetry } from "../telemetry.js";
|
|
21
21
|
import { installCommand, siteLinks, siteOf } from "../site.js";
|
|
22
22
|
import { lang, tr } from "../i18n.js";
|
|
23
|
-
|
|
23
|
+
import { globalUpdateCommand, latestVersionUrl } from "../update.js";
|
|
24
24
|
/** 启动时检测到的可自动安装的新版;TUI 退出后才装(见 installPendingUpdate)。 */
|
|
25
25
|
let pendingUpdate;
|
|
26
26
|
/**
|
|
@@ -36,7 +36,7 @@ let pendingUpdate;
|
|
|
36
36
|
async function checkForUpdate() {
|
|
37
37
|
let latest;
|
|
38
38
|
try {
|
|
39
|
-
const url =
|
|
39
|
+
const url = latestVersionUrl();
|
|
40
40
|
const res = await fetch(url, {
|
|
41
41
|
headers: { Accept: "application/json" },
|
|
42
42
|
signal: AbortSignal.timeout(5_000),
|
|
@@ -82,9 +82,7 @@ async function checkForUpdate() {
|
|
|
82
82
|
: userAgent.startsWith("yarn") ? "yarn"
|
|
83
83
|
: userAgent.startsWith("bun") ? "bun"
|
|
84
84
|
: "npm";
|
|
85
|
-
const installCmd = pm
|
|
86
|
-
? `npm install -g --loglevel=error ${PACKAGE_NAME}@latest`
|
|
87
|
-
: `${pm} add -g ${PACKAGE_NAME}@latest`;
|
|
85
|
+
const installCmd = globalUpdateCommand(pm);
|
|
88
86
|
pendingUpdate = { latest, installCmd };
|
|
89
87
|
setUpdateNotice(tr(`⬆ 发现新版 v${latest},退出后自动更新`, `⬆ v${latest} available, will update on exit`));
|
|
90
88
|
}
|
package/dist/api.js
CHANGED
|
@@ -356,7 +356,7 @@ export async function fetchMe(cfg) {
|
|
|
356
356
|
throw new Error(unreachableMessage(cfg.baseUrl));
|
|
357
357
|
}
|
|
358
358
|
if (resp.status === 401)
|
|
359
|
-
throw new
|
|
359
|
+
throw new AuthError(sessionExpiredMessage());
|
|
360
360
|
if (!resp.ok)
|
|
361
361
|
throw new Error(await errorMessageFromResponse(resp, tr(`服务端返回 ${resp.status},稍后再试`, `Server returned ${resp.status}. Please try again later.`)));
|
|
362
362
|
const body = await readJsonResponseCapped(resp);
|
package/dist/config.d.ts
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import { type webcrypto } from "node:crypto";
|
|
2
|
-
import { type Site } from "./site.js";
|
|
2
|
+
import { type Site, type SiteId } from "./site.js";
|
|
3
3
|
export declare const VERSION: string;
|
|
4
|
+
/**
|
|
5
|
+
* npm 上同一份构建发两个包(scripts/make-cli-npm-packages.mjs):u1s1-cli 默认国际站,
|
|
6
|
+
* u1s1-cli-cn 默认中国站,只差 package.json 的 name 和 u1s1.defaultSite。仓库、便携包、
|
|
7
|
+
* 桌面版内嵌的 package.json 不带标记,按中国站(老行为)。升级/查新版都用自己的包名。
|
|
8
|
+
*/
|
|
9
|
+
export declare const PACKAGE_NAME: string;
|
|
10
|
+
export declare const PACKAGE_DEFAULT_SITE: SiteId;
|
|
4
11
|
/**
|
|
5
12
|
* 便携包安装(install.sh / install.ps1):包根旁边带自己的 node 运行时,
|
|
6
13
|
* npm 更新碰不到这份拷贝,升级只能整包重装。npm 全局安装的包在 node_modules
|
|
7
14
|
* 下,按父目录名先排除,避免撞上恰好叫 node 的目录误判。
|
|
8
15
|
*/
|
|
9
16
|
export declare function isPortableInstall(): boolean;
|
|
10
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* 本机还没有 ~/.u1s1/config.json 时连的站(包标记决定)。配置文件已存在但没写 baseUrl 的
|
|
19
|
+
* 一律是中国站:中国站历来不写 baseUrl,翻默认站不能把存量用户和他们的凭证带去国际站。
|
|
20
|
+
*/
|
|
11
21
|
export declare const DEFAULT_BASE_URL: string;
|
|
12
22
|
export declare const PROVIDER_ID = "u1s1";
|
|
13
23
|
export declare const THINKING_LEVELS: readonly ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
@@ -225,12 +235,18 @@ export declare function resolvePreferredModel(cfg: Pick<CliConfig, "model" | "mo
|
|
|
225
235
|
export declare function loadConfig(): CliConfig;
|
|
226
236
|
/** 只看配置文件、不叠加环境变量覆盖的配置(判断「文件里存的是哪个站」用)。 */
|
|
227
237
|
export declare function loadFileConfig(): CliConfig;
|
|
238
|
+
/**
|
|
239
|
+
* 配置文件在、却没写 baseUrl(老中国站用户)→ 显式写成中国站固定下来,以后换装
|
|
240
|
+
* 默认国际站的包也不会把人带走。只补这一个字段,其它原样保留;读不懂的文件不碰。
|
|
241
|
+
*/
|
|
242
|
+
export declare function pinLegacySiteBaseUrl(): void;
|
|
228
243
|
/**
|
|
229
244
|
* 配置文件内容 + 环境变量 → 生效配置。环境变量优先于文件;文件里的历代中国站
|
|
230
245
|
* API 根换成当前中国站 apiBase(migrateStoredBaseUrl),下次 saveConfig 时落盘。
|
|
231
|
-
*
|
|
246
|
+
* 都没有 baseUrl 时用 fallback 站(见 fallbackSite)。
|
|
247
|
+
* env / currentCnApiBase / fallback 可注入,供测试模拟 P1 换主域后的迁移和不同包的默认站。
|
|
232
248
|
*/
|
|
233
|
-
export declare function configFromFile(file: Partial<CliConfig>, env?: NodeJS.ProcessEnv, currentCnApiBase?: string): CliConfig;
|
|
249
|
+
export declare function configFromFile(file: Partial<CliConfig>, env?: NodeJS.ProcessEnv, currentCnApiBase?: string, fallback?: SiteId): CliConfig;
|
|
234
250
|
/**
|
|
235
251
|
* 当前生效站点(按 loadConfig().baseUrl 判定,含 U1S1_BASE_URL 覆盖)。手里没有 cfg 的
|
|
236
252
|
* 文案/链接(模块级提示、生成的扩展、错误人话化)用它;有 cfg 的地方直接 siteOf(cfg)。
|
package/dist/config.js
CHANGED
|
@@ -7,7 +7,15 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import { resetLang, setLangResolver, tr } from "./i18n.js";
|
|
8
8
|
import { migrateStoredBaseUrl, SITES, siteOf } from "./site.js";
|
|
9
9
|
const require = createRequire(import.meta.url);
|
|
10
|
-
|
|
10
|
+
const pkg = require("../package.json");
|
|
11
|
+
export const VERSION = pkg.version;
|
|
12
|
+
/**
|
|
13
|
+
* npm 上同一份构建发两个包(scripts/make-cli-npm-packages.mjs):u1s1-cli 默认国际站,
|
|
14
|
+
* u1s1-cli-cn 默认中国站,只差 package.json 的 name 和 u1s1.defaultSite。仓库、便携包、
|
|
15
|
+
* 桌面版内嵌的 package.json 不带标记,按中国站(老行为)。升级/查新版都用自己的包名。
|
|
16
|
+
*/
|
|
17
|
+
export const PACKAGE_NAME = typeof pkg.name === "string" && /^[a-z0-9][a-z0-9._-]*$/.test(pkg.name) ? pkg.name : "u1s1-cli";
|
|
18
|
+
export const PACKAGE_DEFAULT_SITE = pkg.u1s1?.defaultSite === "intl" ? "intl" : "cn";
|
|
11
19
|
/**
|
|
12
20
|
* 便携包安装(install.sh / install.ps1):包根旁边带自己的 node 运行时,
|
|
13
21
|
* npm 更新碰不到这份拷贝,升级只能整包重装。npm 全局安装的包在 node_modules
|
|
@@ -22,8 +30,11 @@ export function isPortableInstall() {
|
|
|
22
30
|
: join(pkgRoot, "..", "node", "bin", "node");
|
|
23
31
|
return existsSync(portableNode);
|
|
24
32
|
}
|
|
25
|
-
/**
|
|
26
|
-
|
|
33
|
+
/**
|
|
34
|
+
* 本机还没有 ~/.u1s1/config.json 时连的站(包标记决定)。配置文件已存在但没写 baseUrl 的
|
|
35
|
+
* 一律是中国站:中国站历来不写 baseUrl,翻默认站不能把存量用户和他们的凭证带去国际站。
|
|
36
|
+
*/
|
|
37
|
+
export const DEFAULT_BASE_URL = SITES[PACKAGE_DEFAULT_SITE].apiBase;
|
|
27
38
|
export const PROVIDER_ID = "u1s1";
|
|
28
39
|
/**
|
|
29
40
|
* Make a short alias from a model id by stripping common prefixes:厂商前缀
|
|
@@ -336,19 +347,39 @@ export function resolvePreferredModel(cfg) {
|
|
|
336
347
|
}
|
|
337
348
|
return { provider: PROVIDER_ID, id: defaultModelId() };
|
|
338
349
|
}
|
|
350
|
+
/** 配置里没写 baseUrl 时按哪个站:没有配置文件 → 包的默认站;有文件(老中国站用户)→ 中国站。 */
|
|
351
|
+
function fallbackSite() {
|
|
352
|
+
return existsSync(configFile) ? "cn" : PACKAGE_DEFAULT_SITE;
|
|
353
|
+
}
|
|
339
354
|
export function loadConfig() {
|
|
340
|
-
return configFromFile((readJsonFile(configFile) ?? {}));
|
|
355
|
+
return configFromFile((readJsonFile(configFile) ?? {}), process.env, SITES.cn.apiBase, fallbackSite());
|
|
341
356
|
}
|
|
342
357
|
/** 只看配置文件、不叠加环境变量覆盖的配置(判断「文件里存的是哪个站」用)。 */
|
|
343
358
|
export function loadFileConfig() {
|
|
344
|
-
return configFromFile((readJsonFile(configFile) ?? {}), {});
|
|
359
|
+
return configFromFile((readJsonFile(configFile) ?? {}), {}, SITES.cn.apiBase, fallbackSite());
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* 配置文件在、却没写 baseUrl(老中国站用户)→ 显式写成中国站固定下来,以后换装
|
|
363
|
+
* 默认国际站的包也不会把人带走。只补这一个字段,其它原样保留;读不懂的文件不碰。
|
|
364
|
+
*/
|
|
365
|
+
export function pinLegacySiteBaseUrl() {
|
|
366
|
+
const file = readJsonFile(configFile);
|
|
367
|
+
if (!file || Array.isArray(file) || file.baseUrl)
|
|
368
|
+
return;
|
|
369
|
+
try {
|
|
370
|
+
writePrivateFile(configFile, JSON.stringify({ ...file, baseUrl: SITES.cn.apiBase }, null, 2) + "\n");
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
// 写不进去也照样按中国站判定(fallbackSite),下次启动再试
|
|
374
|
+
}
|
|
345
375
|
}
|
|
346
376
|
/**
|
|
347
377
|
* 配置文件内容 + 环境变量 → 生效配置。环境变量优先于文件;文件里的历代中国站
|
|
348
378
|
* API 根换成当前中国站 apiBase(migrateStoredBaseUrl),下次 saveConfig 时落盘。
|
|
349
|
-
*
|
|
379
|
+
* 都没有 baseUrl 时用 fallback 站(见 fallbackSite)。
|
|
380
|
+
* env / currentCnApiBase / fallback 可注入,供测试模拟 P1 换主域后的迁移和不同包的默认站。
|
|
350
381
|
*/
|
|
351
|
-
export function configFromFile(file, env = process.env, currentCnApiBase = SITES.cn.apiBase) {
|
|
382
|
+
export function configFromFile(file, env = process.env, currentCnApiBase = SITES.cn.apiBase, fallback = "cn") {
|
|
352
383
|
const envJwk = (name) => {
|
|
353
384
|
const raw = env[name];
|
|
354
385
|
if (!raw)
|
|
@@ -366,7 +397,9 @@ export function configFromFile(file, env = process.env, currentCnApiBase = SITES
|
|
|
366
397
|
deviceId: file.deviceId,
|
|
367
398
|
devicePrivateJwk: envJwk("U1S1_DEVICE_PRIVATE_JWK") || file.devicePrivateJwk,
|
|
368
399
|
devicePublicJwk: envJwk("U1S1_DEVICE_PUBLIC_JWK") || file.devicePublicJwk,
|
|
369
|
-
baseUrl: env["U1S1_BASE_URL"]
|
|
400
|
+
baseUrl: env["U1S1_BASE_URL"]
|
|
401
|
+
|| migrateStoredBaseUrl(file.baseUrl, currentCnApiBase)
|
|
402
|
+
|| (fallback === "intl" ? SITES.intl.apiBase : currentCnApiBase),
|
|
370
403
|
// 有效性不在这里裁决:端点列表可能还没拉回来,交给 resolvePreferredModel
|
|
371
404
|
model: file.model,
|
|
372
405
|
modelProvider: typeof file.modelProvider === "string" ? file.modelProvider : undefined,
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { printConsoleBanner } from "./brand.js";
|
|
3
|
-
import { activeSite, loadConfig, readSettings, VERSION } from "./config.js";
|
|
3
|
+
import { activeSite, loadConfig, pinLegacySiteBaseUrl, readSettings, VERSION } from "./config.js";
|
|
4
4
|
import { siteLinks, siteOf } from "./site.js";
|
|
5
5
|
import { isEnglish, tr } from "./i18n.js";
|
|
6
6
|
import { isLauncherWord, launcherHelpLines, launcherHelpRows, runLauncher } from "./launcher.js";
|
|
@@ -165,9 +165,9 @@ const COMMANDS = {
|
|
|
165
165
|
await login(parsed.key, { site: parsed.site });
|
|
166
166
|
},
|
|
167
167
|
logout: logoutCommand,
|
|
168
|
-
usage: async () => {
|
|
168
|
+
usage: async (rest) => {
|
|
169
169
|
const { usage } = await import("./usage.js");
|
|
170
|
-
await usage();
|
|
170
|
+
await usage(rest);
|
|
171
171
|
},
|
|
172
172
|
whoami: whoamiCommand,
|
|
173
173
|
feedback: async (rest) => {
|
|
@@ -229,6 +229,8 @@ function rejectMistypedCommand(cmd, argCount) {
|
|
|
229
229
|
return true;
|
|
230
230
|
}
|
|
231
231
|
async function run() {
|
|
232
|
+
// 老中国站配置(有文件没 baseUrl)先钉成中国站,再做任何按站点的判断
|
|
233
|
+
pinLegacySiteBaseUrl();
|
|
232
234
|
const args = process.argv.slice(2);
|
|
233
235
|
const cmd = args[0];
|
|
234
236
|
const rest = args.slice(1);
|
|
@@ -282,8 +284,9 @@ const SIMPLE_COMMAND_HELP = {
|
|
|
282
284
|
" 退出本机登录,并注销这台设备在服务端的授权(连不上服务器时只清本机)。",
|
|
283
285
|
],
|
|
284
286
|
usage: [
|
|
285
|
-
"用法: u1s1 usage",
|
|
287
|
+
"用法: u1s1 usage [--json]",
|
|
286
288
|
" 查看剩余额度:免费包、已购用量包与余额。会话内也可输入 /usage。",
|
|
289
|
+
" --json 输出 JSON,给别的程序读(字段见 usage.ts 的 UsageJson)",
|
|
287
290
|
],
|
|
288
291
|
whoami: [
|
|
289
292
|
"用法: u1s1 whoami",
|
|
@@ -307,8 +310,9 @@ const SIMPLE_COMMAND_HELP_EN = {
|
|
|
307
310
|
" Sign out on this machine and revoke this device's authorization on the server (only clears local data if the server is unreachable).",
|
|
308
311
|
],
|
|
309
312
|
usage: [
|
|
310
|
-
"Usage: u1s1 usage",
|
|
313
|
+
"Usage: u1s1 usage [--json]",
|
|
311
314
|
" Show your remaining balance. In a session you can also type /usage.",
|
|
315
|
+
" --json Print JSON for other programs to read",
|
|
312
316
|
],
|
|
313
317
|
whoami: [
|
|
314
318
|
"Usage: u1s1 whoami",
|
package/dist/update.d.ts
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
|
+
import { PACKAGE_NAME } from "./config.js";
|
|
1
2
|
export declare const VERSION: string;
|
|
2
|
-
export
|
|
3
|
+
export { PACKAGE_NAME };
|
|
4
|
+
/** 中国站的 npm 包名;u1s1-cli 自 1.15 起是国际站的包(见 config.ts PACKAGE_NAME)。 */
|
|
5
|
+
export declare const CN_PACKAGE_NAME = "u1s1-cli-cn";
|
|
3
6
|
/** Detect the package manager that installed u1s1. */
|
|
4
7
|
export declare function detectPackageManager(): string;
|
|
8
|
+
/** npm registry 上本包 latest 的元数据地址(两个包各查各的)。 */
|
|
9
|
+
export declare function latestVersionUrl(packageName?: string): string;
|
|
10
|
+
/** 全局升级本包的包管理器参数(execFileSync 用,不经 shell)。 */
|
|
11
|
+
export declare function globalUpdateArgs(pm: string, packageName?: string): string[];
|
|
12
|
+
/** 同上,拼成一行命令(退出后自动更新、给用户看的提示用)。 */
|
|
13
|
+
export declare function globalUpdateCommand(pm: string, packageName?: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* 装的是默认国际站的 u1s1-cli、连的却是中国站(老用户):提示改装 u1s1-cli-cn,只提示不自动换包。
|
|
16
|
+
* 两个包都提供 u1s1 命令,要先卸再装,否则 npm 报 bin 冲突。返回要打印的行;不需要提示时为空。
|
|
17
|
+
*/
|
|
18
|
+
export declare function cnPackageHint(pm: string, siteId: string, packageName?: string, defaultSite?: string): string[];
|
|
5
19
|
/** Fetch the latest published version from npm registry. */
|
|
6
20
|
export declare function getLatestVersion(): Promise<string | undefined>;
|
|
7
21
|
export declare function compareVersions(a: string, b: string): number;
|
package/dist/update.js
CHANGED
|
@@ -5,14 +5,16 @@ import { createRequire } from "node:module";
|
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
-
import { activeSite, isPortableInstall } from "./config.js";
|
|
8
|
+
import { activeSite, isPortableInstall, PACKAGE_DEFAULT_SITE, PACKAGE_NAME } from "./config.js";
|
|
9
9
|
import { tr } from "./i18n.js";
|
|
10
10
|
import { installCommand } from "./site.js";
|
|
11
11
|
import { readJsonResponseCapped, readResponseTextCapped } from "./api.js";
|
|
12
12
|
const require = createRequire(import.meta.url);
|
|
13
13
|
const pkg = require("../package.json");
|
|
14
14
|
export const VERSION = pkg.version;
|
|
15
|
-
export
|
|
15
|
+
export { PACKAGE_NAME };
|
|
16
|
+
/** 中国站的 npm 包名;u1s1-cli 自 1.15 起是国际站的包(见 config.ts PACKAGE_NAME)。 */
|
|
17
|
+
export const CN_PACKAGE_NAME = "u1s1-cli-cn";
|
|
16
18
|
const MAX_NPM_METADATA_BYTES = 64 * 1024;
|
|
17
19
|
const MAX_INSTALL_SCRIPT_BYTES = 1024 * 1024;
|
|
18
20
|
function publishedVersion(value) {
|
|
@@ -39,9 +41,39 @@ export function detectPackageManager() {
|
|
|
39
41
|
return "bun";
|
|
40
42
|
return "npm";
|
|
41
43
|
}
|
|
44
|
+
/** npm registry 上本包 latest 的元数据地址(两个包各查各的)。 */
|
|
45
|
+
export function latestVersionUrl(packageName = PACKAGE_NAME) {
|
|
46
|
+
return `https://registry.npmjs.org/${packageName}/latest`;
|
|
47
|
+
}
|
|
48
|
+
/** 全局升级本包的包管理器参数(execFileSync 用,不经 shell)。 */
|
|
49
|
+
export function globalUpdateArgs(pm, packageName = PACKAGE_NAME) {
|
|
50
|
+
return pm === "npm"
|
|
51
|
+
? ["install", "-g", "--loglevel=error", `${packageName}@latest`]
|
|
52
|
+
: ["add", "-g", `${packageName}@latest`];
|
|
53
|
+
}
|
|
54
|
+
/** 同上,拼成一行命令(退出后自动更新、给用户看的提示用)。 */
|
|
55
|
+
export function globalUpdateCommand(pm, packageName = PACKAGE_NAME) {
|
|
56
|
+
return [pm, ...globalUpdateArgs(pm, packageName)].join(" ");
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 装的是默认国际站的 u1s1-cli、连的却是中国站(老用户):提示改装 u1s1-cli-cn,只提示不自动换包。
|
|
60
|
+
* 两个包都提供 u1s1 命令,要先卸再装,否则 npm 报 bin 冲突。返回要打印的行;不需要提示时为空。
|
|
61
|
+
*/
|
|
62
|
+
export function cnPackageHint(pm, siteId, packageName = PACKAGE_NAME, defaultSite = PACKAGE_DEFAULT_SITE) {
|
|
63
|
+
if (siteId !== "cn" || defaultSite !== "intl" || packageName === CN_PACKAGE_NAME)
|
|
64
|
+
return [];
|
|
65
|
+
const remove = pm === "npm" ? `npm uninstall -g ${packageName}` : `${pm} remove -g ${packageName}`;
|
|
66
|
+
const add = pm === "npm" ? `npm install -g ${CN_PACKAGE_NAME}` : `${pm} add -g ${CN_PACKAGE_NAME}`;
|
|
67
|
+
return [
|
|
68
|
+
tr(`提示:你连的是中国站。npm 包 ${packageName} 现在默认国际站,中国站专用包是 ${CN_PACKAGE_NAME},建议改装(登录状态和设置都保留):`, `Note: you're on the China site. The npm package ${packageName} now defaults to the international site; the China site package is ${CN_PACKAGE_NAME}. We suggest switching (your sign-in and settings are kept):`),
|
|
69
|
+
` ${remove}`,
|
|
70
|
+
` ${add}`,
|
|
71
|
+
tr(" 不改装也能照常用,当前仍连中国站。", " You can keep using this package; it still connects to the China site."),
|
|
72
|
+
];
|
|
73
|
+
}
|
|
42
74
|
/** Fetch the latest published version from npm registry. */
|
|
43
75
|
export async function getLatestVersion() {
|
|
44
|
-
const url =
|
|
76
|
+
const url = latestVersionUrl();
|
|
45
77
|
try {
|
|
46
78
|
const res = await fetch(url, {
|
|
47
79
|
headers: { Accept: "application/json" },
|
|
@@ -211,6 +243,11 @@ async function portableSelfUpdate(latest) {
|
|
|
211
243
|
}
|
|
212
244
|
}
|
|
213
245
|
export async function update() {
|
|
246
|
+
if (!isPortableInstall()) {
|
|
247
|
+
const hint = cnPackageHint(detectPackageManager(), activeSite().id);
|
|
248
|
+
if (hint.length)
|
|
249
|
+
console.log(hint.join("\n") + "\n");
|
|
250
|
+
}
|
|
214
251
|
console.log(tr("正在检查更新…", "Checking for updates…"));
|
|
215
252
|
const latest = await getLatestVersion();
|
|
216
253
|
if (!latest) {
|
|
@@ -246,10 +283,7 @@ export async function update() {
|
|
|
246
283
|
console.log(tr(`正在用 ${pm} 更新 ${PACKAGE_NAME}…`, `Updating ${PACKAGE_NAME} with ${pm}…`));
|
|
247
284
|
try {
|
|
248
285
|
// npm 压掉 EBADENGINE 等警告墙,对新手只有噪音;出错时 error 仍会显示
|
|
249
|
-
|
|
250
|
-
? ["install", "-g", "--loglevel=error", `${PACKAGE_NAME}@latest`]
|
|
251
|
-
: ["add", "-g", `${PACKAGE_NAME}@latest`];
|
|
252
|
-
execFileSync(pm, args, { stdio: "inherit" });
|
|
286
|
+
execFileSync(pm, globalUpdateArgs(pm), { stdio: "inherit" });
|
|
253
287
|
console.log(tr(`\n✅ 已更新到 v${latest},重启 u1s1 后生效。`, `\n✅ Updated to v${latest}. Restart u1s1 to use it.`));
|
|
254
288
|
}
|
|
255
289
|
catch {
|
package/dist/usage.d.ts
CHANGED
|
@@ -18,4 +18,47 @@ export declare function usageEntryData(): Promise<{
|
|
|
18
18
|
lines?: string[];
|
|
19
19
|
error?: string;
|
|
20
20
|
}>;
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* `u1s1 usage --json` 的输出,给别的程序读(比如 Hearth 在界面上显示 u1s1 用量)。
|
|
23
|
+
* 字段只增不改;要改含义就升 v。金额是美元,Token 是网关记的个数(不是按默认模型折算的约数)。
|
|
24
|
+
* 不含凭证;email 是登录账号,方便调用方标明是哪个号。
|
|
25
|
+
*/
|
|
26
|
+
export interface UsageJson {
|
|
27
|
+
v: 1;
|
|
28
|
+
site: "cn" | "intl";
|
|
29
|
+
email: string | null;
|
|
30
|
+
/** 可用余额(美元):用量包网关是按量余额 bonus_balance_usd,老网关是 remaining_usd */
|
|
31
|
+
balance_usd: number;
|
|
32
|
+
/** 本月已用(美元) */
|
|
33
|
+
month_usd: number;
|
|
34
|
+
/** 美元折默认模型 Token 的比率;老网关没有时为 null */
|
|
35
|
+
tokens_per_usd: number | null;
|
|
36
|
+
/** 生效中的用量包,同类同到期日的合成一项;老网关为 null */
|
|
37
|
+
packages: {
|
|
38
|
+
kind: string;
|
|
39
|
+
label: string;
|
|
40
|
+
/** free:只能用于免费包适用模型;其余为全模型 */
|
|
41
|
+
scope: string;
|
|
42
|
+
/** true:每日额度(total 是每天的量,0 点恢复);false:一次性总量 */
|
|
43
|
+
daily: boolean;
|
|
44
|
+
total_tokens: number;
|
|
45
|
+
remaining_tokens: number;
|
|
46
|
+
expires_at: string | null;
|
|
47
|
+
count: number;
|
|
48
|
+
}[] | null;
|
|
49
|
+
/** 仪表盘、充值入口;支付通道没开放时 topup 为 null */
|
|
50
|
+
dashboard: string;
|
|
51
|
+
topup: string | null;
|
|
52
|
+
}
|
|
53
|
+
/** 出错时的 --json 输出:login 没登录 / 登录失效,其余是 failed,message 是给人看的原因 */
|
|
54
|
+
export interface UsageJsonError {
|
|
55
|
+
v: 1;
|
|
56
|
+
error: "login" | "failed";
|
|
57
|
+
message: string;
|
|
58
|
+
}
|
|
59
|
+
export declare function usageJson(me: MeResponse, site: Site): UsageJson;
|
|
60
|
+
export declare function parseUsageArgs(rest: string[]): {
|
|
61
|
+
json: boolean;
|
|
62
|
+
error?: string;
|
|
63
|
+
};
|
|
64
|
+
export declare function usage(rest?: string[]): Promise<void>;
|
package/dist/usage.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fetchMe } from "./api.js";
|
|
1
|
+
import { AuthError, fetchMe } from "./api.js";
|
|
2
2
|
import { activeSite, loadConfig } from "./config.js";
|
|
3
3
|
import { hasDeviceCredential } from "./device-auth.js";
|
|
4
4
|
import { isEnglish, tr } from "./i18n.js";
|
|
@@ -221,8 +221,70 @@ export async function usageEntryData() {
|
|
|
221
221
|
return { error: e instanceof Error ? e.message : String(e) };
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
|
-
export
|
|
224
|
+
export function usageJson(me, site) {
|
|
225
|
+
const { dashboard, topup } = siteLinks(site);
|
|
226
|
+
return {
|
|
227
|
+
v: 1,
|
|
228
|
+
site: site.id,
|
|
229
|
+
email: me.email ?? null,
|
|
230
|
+
balance_usd: me.packages ? me.bonus_balance_usd : me.remaining_usd,
|
|
231
|
+
month_usd: me.mtd_usd,
|
|
232
|
+
tokens_per_usd: me.tokens_per_usd && me.tokens_per_usd > 0 ? me.tokens_per_usd : null,
|
|
233
|
+
packages: me.packages
|
|
234
|
+
? groupPackages(me.packages).map((pkg) => {
|
|
235
|
+
const daily = pkg.daily_tokens != null;
|
|
236
|
+
return {
|
|
237
|
+
kind: pkg.kind,
|
|
238
|
+
label: packageLabels()[pkg.kind] ?? pkg.kind,
|
|
239
|
+
scope: pkg.scope,
|
|
240
|
+
daily,
|
|
241
|
+
total_tokens: (daily ? pkg.daily_tokens : pkg.total_tokens) ?? 0,
|
|
242
|
+
remaining_tokens: pkg.remaining,
|
|
243
|
+
expires_at: pkg.expires_at,
|
|
244
|
+
count: pkg.count,
|
|
245
|
+
};
|
|
246
|
+
})
|
|
247
|
+
: null,
|
|
248
|
+
dashboard,
|
|
249
|
+
topup: me.pay_enabled === false ? null : topup,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
export function parseUsageArgs(rest) {
|
|
253
|
+
let json = false;
|
|
254
|
+
for (const a of rest) {
|
|
255
|
+
if (a === "--json")
|
|
256
|
+
json = true;
|
|
257
|
+
else
|
|
258
|
+
return { json, error: tr(`不认识的参数 ${a};用法: u1s1 usage [--json]`, `Unknown option ${a}. Usage: u1s1 usage [--json]`) };
|
|
259
|
+
}
|
|
260
|
+
return { json };
|
|
261
|
+
}
|
|
262
|
+
export async function usage(rest = []) {
|
|
263
|
+
const args = parseUsageArgs(rest);
|
|
264
|
+
if (args.error) {
|
|
265
|
+
console.error(args.error);
|
|
266
|
+
process.exitCode = 1;
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
225
269
|
const cfg = loadConfig();
|
|
270
|
+
if (args.json) {
|
|
271
|
+
const fail = (error, message) => {
|
|
272
|
+
console.log(JSON.stringify({ v: 1, error, message }));
|
|
273
|
+
process.exitCode = 1;
|
|
274
|
+
};
|
|
275
|
+
if (!hasDeviceCredential(cfg))
|
|
276
|
+
return fail("login", tr("还没登录,先跑 u1s1 login", "Not signed in. Run u1s1 login first."));
|
|
277
|
+
let me;
|
|
278
|
+
try {
|
|
279
|
+
me = await fetchMe(cfg);
|
|
280
|
+
}
|
|
281
|
+
catch (e) {
|
|
282
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
283
|
+
return fail(e instanceof AuthError ? "login" : "failed", message);
|
|
284
|
+
}
|
|
285
|
+
console.log(JSON.stringify(usageJson(me, siteOf(cfg))));
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
226
288
|
if (!hasDeviceCredential(cfg)) {
|
|
227
289
|
console.error(tr("还没登录,先跑 u1s1 login", "Not signed in. Run u1s1 login first."));
|
|
228
290
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "u1s1-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"description": "u1s1 — 有一说一,最省心的 AI 编程搭子。终端里用中文说需求,AI 帮你读文件、改代码、跑命令。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
"build": "tsc",
|
|
26
26
|
"dev": "tsx src/index.ts",
|
|
27
27
|
"typecheck": "tsc --noEmit",
|
|
28
|
-
"prepublishOnly": "npm run build && npm test",
|
|
29
28
|
"test": "U1S1_LANG=zh tsx --test test/*.test.ts && U1S1_LANG=zh node scripts/render-regression.mjs",
|
|
30
29
|
"postinstall": "node scripts/patch-pi.js",
|
|
31
30
|
"package": "bash ../../scripts/package.sh",
|
|
@@ -56,5 +55,8 @@
|
|
|
56
55
|
"@types/node": "^22.20.1",
|
|
57
56
|
"tsx": "^4.23.12",
|
|
58
57
|
"typescript": "^5.9.3"
|
|
58
|
+
},
|
|
59
|
+
"u1s1": {
|
|
60
|
+
"defaultSite": "cn"
|
|
59
61
|
}
|
|
60
62
|
}
|