u1s1-cli 1.15.2 → 1.16.1
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 +5 -11
- package/dist/api.js +1 -1
- package/dist/brand.d.ts +4 -2
- package/dist/brand.js +29 -73
- package/dist/index.js +39 -66
- package/dist/launcher.d.ts +2 -4
- package/dist/launcher.js +8 -26
- package/dist/login.js +6 -14
- package/dist/model.js +4 -3
- package/dist/usage.d.ts +44 -1
- package/dist/usage.js +80 -35
- package/package.json +1 -1
package/dist/agents/pi.js
CHANGED
|
@@ -364,17 +364,11 @@ export async function runAgent(cfg, args) {
|
|
|
364
364
|
const site = siteOf(cfg);
|
|
365
365
|
const text = [
|
|
366
366
|
theme.bold(theme.fg("text", tr("u1s1 常用操作", "u1s1 basics"))),
|
|
367
|
-
theme.fg("text", tr("
|
|
368
|
-
theme.fg("text", tr("/model 切换模型
|
|
369
|
-
theme.fg("text", tr("/
|
|
370
|
-
|
|
371
|
-
theme.fg("dim", tr(
|
|
372
|
-
// 国际站没有网页托管,不提 u1s1 deploy
|
|
373
|
-
theme.fg("dim", site.id === "intl"
|
|
374
|
-
? tr("退出后在终端:u1s1 update 升级", "Back in your terminal: u1s1 update to upgrade")
|
|
375
|
-
: tr("退出后在终端:u1s1 deploy 发布网页 · u1s1 update 升级", "Back in your terminal: u1s1 deploy to publish a web page · u1s1 update to upgrade")),
|
|
376
|
-
theme.fg("dim", tr("匿名使用统计只记会话开始/结束等节点,不含任何内容;U1S1_TELEMETRY=0 可关闭", "Anonymous usage stats only record events like session start/end, never content; U1S1_TELEMETRY=0 turns them off")),
|
|
377
|
-
theme.fg("dim", tr(`新手教程 → ${siteLinks(site).guides}`, `Guides → ${siteLinks(site).guides}`)),
|
|
367
|
+
theme.fg("text", tr("回车发送 · Shift+Enter 换行 · Ctrl+V 贴图 · Esc 中断", "Enter to send · Shift+Enter newline · Ctrl+V paste image · Esc stop")),
|
|
368
|
+
theme.fg("text", tr("/model 切换模型 · /clear 新会话 · /resume 历史会话 · /usage 额度", "/model switch models · /clear new session · /resume past sessions · /usage balance")),
|
|
369
|
+
theme.fg("text", tr("/feedback 反馈问题 · /context 上下文占用 · /compact 压缩上下文 · /exit 退出", "/feedback report a problem · /context context usage · /compact compact context · /exit exit")),
|
|
370
|
+
// 终端命令、匿名统计说明都在 u1s1 --help 里,这里不重复
|
|
371
|
+
theme.fg("dim", tr(`/hotkeys 快捷键 · 终端命令 u1s1 --help · 教程 → ${siteLinks(site).guides}`, `/hotkeys shortcuts · terminal: u1s1 --help · Guides → ${siteLinks(site).guides}`)),
|
|
378
372
|
].join("\n");
|
|
379
373
|
return new Text(text, 1, 0);
|
|
380
374
|
});
|
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/brand.d.ts
CHANGED
|
@@ -21,7 +21,8 @@ export declare function cwdAdvice(cwd: string, dirs?: {
|
|
|
21
21
|
export declare const HERO_ART: string[];
|
|
22
22
|
/**
|
|
23
23
|
* Startup hero, responsive to terminal width:
|
|
24
|
-
* wide → wordmark with info column beside it;
|
|
24
|
+
* wide → wordmark with a short info column beside it; narrow → three compact lines.
|
|
25
|
+
* 只放每次都要看的:版本(+升级状态)、目录与账号、/help 入口;其余收进 /help。
|
|
25
26
|
*/
|
|
26
27
|
export declare function renderBrandHeader(theme: Theme, input: {
|
|
27
28
|
version: string;
|
|
@@ -32,11 +33,12 @@ export declare function renderBrandHeader(theme: Theme, input: {
|
|
|
32
33
|
text: string;
|
|
33
34
|
url?: string;
|
|
34
35
|
};
|
|
35
|
-
/**
|
|
36
|
+
/** 当前目录为空时给一句可照抄的开场 prompt(见 starterLine)。 */
|
|
36
37
|
starterTips?: boolean;
|
|
37
38
|
/** 启动目录不合适时的提醒(见 cwdAdvice)。 */
|
|
38
39
|
cwdWarning?: string;
|
|
39
40
|
/** 登录账号(config.email),跟在 cwd 后面;多账号/换机时一眼看到登的是谁。 */
|
|
40
41
|
account?: string;
|
|
41
42
|
}): string[];
|
|
43
|
+
/** `u1s1 --help`、登录前的一行抬头(大字模只在对话界面的启动横幅里出现)。 */
|
|
42
44
|
export declare function printConsoleBanner(version: string): void;
|
package/dist/brand.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join, resolve, sep } from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import { isEnglish, tr } from "./i18n.js";
|
|
3
|
+
import { tr } from "./i18n.js";
|
|
5
4
|
import { siteLinks } from "./site.js";
|
|
6
5
|
export const BRAND_NAME = "u1s1";
|
|
7
6
|
export const BRAND_CN = "有一说一";
|
|
@@ -9,10 +8,6 @@ export const BRAND_CN = "有一说一";
|
|
|
9
8
|
export function brandTagline() {
|
|
10
9
|
return tr("说人话的 AI 编程搭子", "the plain-spoken AI coding assistant");
|
|
11
10
|
}
|
|
12
|
-
/** 横幅里品牌名后面那一行:中国站「有一说一 · 标语」,国际站只有英文标语。 */
|
|
13
|
-
function brandSubtitle() {
|
|
14
|
-
return isEnglish() ? "Plain-spoken AI coding assistant" : `${BRAND_CN} · ${brandTagline()}`;
|
|
15
|
-
}
|
|
16
11
|
/**
|
|
17
12
|
* 中国站仪表盘。只为 embed 导出保留(Desktop App 现在只有中国站包,P3 出 intl 包时
|
|
18
13
|
* 改用自带的站点配置);CLI 内部一律按当前站点取 siteLinks(activeSite()).dashboard。
|
|
@@ -39,10 +34,10 @@ export function cwdAdvice(cwd, dirs = {}) {
|
|
|
39
34
|
const here = resolve(cwd);
|
|
40
35
|
const within = (dir) => here === dir || here.startsWith(dir + sep);
|
|
41
36
|
if (within(configDir)) {
|
|
42
|
-
return tr("当前在 u1s1
|
|
37
|
+
return tr("当前在 u1s1 配置目录(存着登录凭据),模型能直接读到;建议 /exit 后 cd 到你的项目文件夹再运行", "You're in u1s1's config folder (it holds your credentials), which the model can read. /exit and cd to your project folder first.");
|
|
43
38
|
}
|
|
44
39
|
if (here === home) {
|
|
45
|
-
return tr("
|
|
40
|
+
return tr("当前在用户主目录,模型找文件会翻遍整个主目录;建议 /exit 后 cd 到项目文件夹再运行", "You're in your home folder, so the model will search all of it. /exit and cd to a project folder first.");
|
|
46
41
|
}
|
|
47
42
|
return undefined;
|
|
48
43
|
}
|
|
@@ -79,87 +74,48 @@ function announcementLine(theme, a) {
|
|
|
79
74
|
const link = a.url ? ` ${theme.fg("dim", a.url)}` : "";
|
|
80
75
|
return `${theme.fg("accent", "📢")} ${theme.fg("accent", a.text)}${link}`;
|
|
81
76
|
}
|
|
82
|
-
/**
|
|
83
|
-
function
|
|
84
|
-
|
|
85
|
-
return [
|
|
86
|
-
` ${theme.fg("muted", "Empty folder? Just describe what you want to build, for example:")}`,
|
|
87
|
-
` ${theme.fg("text", "\"Build a personal landing page with a short bio\"")}`,
|
|
88
|
-
` ${theme.fg("text", "\"Make a birthday card page for a friend with a little animation\"")}`,
|
|
89
|
-
` ${theme.fg("text", "\"Write a small tool that renames the photos in this folder by date\"")}`,
|
|
90
|
-
];
|
|
91
|
-
}
|
|
92
|
-
const lines = [
|
|
93
|
-
` ${theme.fg("muted", "这个文件夹还是空的?把想做的直接说出来就行,比如:")}`,
|
|
94
|
-
` ${theme.fg("text", "「做一个自我介绍网页,做完帮我发布出去」")}`,
|
|
95
|
-
` ${theme.fg("text", "「做一个给朋友的生日祝福页面,要有点小动画」")}`,
|
|
96
|
-
` ${theme.fg("text", "「写一个把文件夹里照片按日期重命名的小工具」")}`,
|
|
97
|
-
];
|
|
98
|
-
// 国际站没有网页托管(u1s1 deploy),不提发布
|
|
99
|
-
if (activeSite().id !== "intl") {
|
|
100
|
-
lines.push(` ${theme.fg("muted", "网页做好后,输入 /exit 回到终端,运行 u1s1 deploy --public 一键上线,拿到可分享的链接发给朋友")}`);
|
|
101
|
-
}
|
|
102
|
-
return lines;
|
|
77
|
+
/** 空目录首跑引导:给一句能照抄的开场白,不让新手对着空白输入框想 prompt。 */
|
|
78
|
+
function starterLine(theme) {
|
|
79
|
+
return ` ${theme.fg("muted", tr("空文件夹?直接说想做什么,比如:", "Empty folder? Just say what to build, e.g. "))}${theme.fg("text", tr("「做一个自我介绍网页」", "\"a personal landing page with a short bio\""))}`;
|
|
103
80
|
}
|
|
81
|
+
/** 信息列(字模旁)最宽的常规行需要的列数;再窄就退成不带字模的紧凑版。 */
|
|
82
|
+
const INFO_WIDTH = 46;
|
|
104
83
|
/**
|
|
105
84
|
* Startup hero, responsive to terminal width:
|
|
106
|
-
* wide → wordmark with info column beside it;
|
|
85
|
+
* wide → wordmark with a short info column beside it; narrow → three compact lines.
|
|
86
|
+
* 只放每次都要看的:版本(+升级状态)、目录与账号、/help 入口;其余收进 /help。
|
|
107
87
|
*/
|
|
108
88
|
export function renderBrandHeader(theme, input) {
|
|
109
89
|
const name = theme.bold(theme.fg("text", `${BRAND_NAME} v${input.version}`)) +
|
|
110
90
|
(input.notice ? ` ${theme.fg("accent", input.notice)}` : "");
|
|
111
|
-
const brand = theme.fg("muted", brandSubtitle());
|
|
112
91
|
const dir = theme.fg("dim", `cwd: ${formatHomePath(input.cwd)}${input.account ? tr(` · 账号 ${input.account}`, ` · account ${input.account}`) : ""}`);
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
if (input.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
];
|
|
92
|
+
// Ctrl+V 贴图必须留在横幅上:macOS 上 Cmd+V 贴不了图,不写出来很多人找不到
|
|
93
|
+
const hints = theme.fg("dim", tr("/help 查看命令 · Ctrl+V 贴图 · Esc 中断", "/help for commands · Ctrl+V image · Esc stop"));
|
|
94
|
+
const extra = [];
|
|
95
|
+
if (input.cwdWarning)
|
|
96
|
+
extra.push(` ${theme.fg("warning", "⚠")} ${theme.fg("warning", input.cwdWarning)}`);
|
|
97
|
+
if (input.starterTips)
|
|
98
|
+
extra.push(starterLine(theme));
|
|
99
|
+
const tail = extra.length ? ["", ...extra] : [];
|
|
100
|
+
if (input.width < ART_WIDTH + 3 + INFO_WIDTH) {
|
|
101
|
+
const lines = ["", ` ${theme.fg("accent", "✻")} ${name}`, ` ${dir}`, ` ${hints}`];
|
|
123
102
|
if (input.announcement)
|
|
124
103
|
lines.push(` ${announcementLine(theme, input.announcement)}`);
|
|
125
|
-
|
|
126
|
-
lines.push("", ...warning);
|
|
127
|
-
if (starter.length)
|
|
128
|
-
lines.push("", ...starter);
|
|
129
|
-
return [...lines, ""];
|
|
104
|
+
return [...lines, ...tail, ""];
|
|
130
105
|
}
|
|
131
|
-
const
|
|
132
|
-
// widest info row (hints) needs 46 cols beside the 28-col wordmark (English hints are wider: 56)
|
|
133
|
-
if (input.width < ART_WIDTH + (isEnglish() ? 56 : 46)) {
|
|
134
|
-
const lines = ["", ...art, "", ` ${name} ${brand}`, ` ${dir}`, ` ${hints}`];
|
|
135
|
-
if (input.announcement)
|
|
136
|
-
lines.push(` ${announcementLine(theme, input.announcement)}`);
|
|
137
|
-
if (warning.length)
|
|
138
|
-
lines.push("", ...warning);
|
|
139
|
-
if (starter.length)
|
|
140
|
-
lines.push("", ...starter);
|
|
141
|
-
return [...lines, ""];
|
|
142
|
-
}
|
|
143
|
-
const rows = [...art];
|
|
106
|
+
const rows = HERO_ART.map((line) => ` ${paintArt(theme, line)}`);
|
|
144
107
|
const gap = " ";
|
|
145
108
|
rows[1] += `${gap}${name}`;
|
|
146
|
-
rows[2] += `${gap}${
|
|
147
|
-
rows[3] += `${gap}${
|
|
148
|
-
|
|
149
|
-
// 公告放信息列末尾(最后一行字模旁),够醒目又不挤掉常规信息
|
|
109
|
+
rows[2] += `${gap}${dir}`;
|
|
110
|
+
rows[3] += `${gap}${hints}`;
|
|
111
|
+
// 公告放信息列末尾(字模旁),够醒目又不挤掉常规信息
|
|
150
112
|
if (input.announcement)
|
|
151
|
-
rows[
|
|
152
|
-
|
|
153
|
-
rows.push("", ...warning);
|
|
154
|
-
if (starter.length)
|
|
155
|
-
rows.push("", ...starter);
|
|
156
|
-
return ["", ...rows, ""];
|
|
113
|
+
rows[4] += `${gap}${announcementLine(theme, input.announcement)}`;
|
|
114
|
+
return ["", ...rows, ...tail, ""];
|
|
157
115
|
}
|
|
116
|
+
/** `u1s1 --help`、登录前的一行抬头(大字模只在对话界面的启动横幅里出现)。 */
|
|
158
117
|
export function printConsoleBanner(version) {
|
|
159
118
|
console.log("");
|
|
160
|
-
|
|
161
|
-
console.log(` ${line}`);
|
|
162
|
-
console.log("");
|
|
163
|
-
console.log(tr(` ${BRAND_NAME} v${version} — ${BRAND_CN},${brandTagline()}`, ` ${BRAND_NAME} v${version} — ${brandTagline()}`));
|
|
119
|
+
console.log(tr(` ${BRAND_NAME} v${version} — ${BRAND_CN},${brandTagline()}`, ` ${BRAND_NAME} v${version} — ${brandTagline()}`));
|
|
164
120
|
console.log("");
|
|
165
121
|
}
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { printConsoleBanner } from "./brand.js";
|
|
|
3
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
|
-
import { isLauncherWord,
|
|
6
|
+
import { isLauncherWord, launcherHelpRow, runLauncher } from "./launcher.js";
|
|
7
7
|
/**
|
|
8
8
|
* Configure the same undici dispatcher used by the embedded kernel before any
|
|
9
9
|
* CLI startup request runs. The kernel keeps this helper in an internal file,
|
|
@@ -17,61 +17,40 @@ async function configureStartupHttp() {
|
|
|
17
17
|
dispatcher.applyHttpProxySettings(readSettings().httpProxy);
|
|
18
18
|
dispatcher.configureHttpDispatcher();
|
|
19
19
|
}
|
|
20
|
+
/** 终端显示宽度:中文等宽字符占 2 列,命令列才能中英文都对齐。 */
|
|
21
|
+
function displayWidth(text) {
|
|
22
|
+
let w = 0;
|
|
23
|
+
for (const ch of text)
|
|
24
|
+
w += /[\u1100-\u115f\u2e80-\ua4cf\uac00-\ud7a3\uf900-\ufaff\ufe30-\ufe4f\uff00-\uff60\uffe0-\uffe6]/.test(ch) ? 2 : 1;
|
|
25
|
+
return w;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* `u1s1 --help`:只列常用命令,一屏看完;低频命令只报名字,各自 `--help` 看用法。
|
|
29
|
+
* 会话内命令在对话里 /help 看,这里不重复。
|
|
30
|
+
*/
|
|
20
31
|
function printHelp() {
|
|
21
32
|
printConsoleBanner(VERSION);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
console.log(line);
|
|
31
|
-
console.log(" u1s1 login / logout 登录 / 退出登录");
|
|
32
|
-
console.log(" u1s1 model 查看或切换默认模型");
|
|
33
|
-
console.log(" u1s1 usage 查看剩余额度");
|
|
34
|
-
console.log(" u1s1 whoami 查看当前登录的账号");
|
|
35
|
-
console.log(" u1s1 update 升级到最新版");
|
|
33
|
+
const rows = [
|
|
34
|
+
[tr("u1s1 -p \"一句话\"", "u1s1 -p \"prompt\""), tr("不进界面直接回答(脚本里用)", "Answer once without the UI (for scripts)")],
|
|
35
|
+
launcherHelpRow(),
|
|
36
|
+
["u1s1 login / logout", tr("登录 / 退出", "Sign in / sign out")],
|
|
37
|
+
["u1s1 usage", tr("查看剩余额度", "Show your remaining balance")],
|
|
38
|
+
["u1s1 model", tr("查看或切换默认模型", "Show or change the default model")],
|
|
39
|
+
["u1s1 update", tr("升级到最新版", "Update to the latest version")],
|
|
40
|
+
];
|
|
36
41
|
// 国际站没有网页托管(网关拒绝),不列 deploy
|
|
37
|
-
if (activeSite().id !== "intl")
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
console.log(" u1s1 feedback \"一句话\" 反馈问题或建议(自动建工单,--bug/--question…)");
|
|
43
|
-
console.log(" u1s1 import 导入历史会话(import skills 导入技能)");
|
|
44
|
-
console.log(" u1s1 mcp 配置第三方 MCP 服务(add/list/test/remove/import)");
|
|
45
|
-
console.log(" u1s1 bench 模型编码能力评测");
|
|
46
|
-
console.log(" u1s1 --version 查看版本");
|
|
42
|
+
if (activeSite().id !== "intl")
|
|
43
|
+
rows.push(["u1s1 deploy", tr("发布网页(list / remove 管理已发布的)", "Publish a web page (list / remove to manage)")]);
|
|
44
|
+
rows.push([tr("u1s1 feedback \"一句话\"", "u1s1 feedback \"text\""), tr("反馈问题或建议", "Send feedback or report a bug")]);
|
|
45
|
+
console.log(tr(" 用法: u1s1 [命令] 不带命令直接进入对话", " Usage: u1s1 [command] no command starts a session"));
|
|
47
46
|
console.log("");
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
console.log(` 更多帮助 → ${siteLinks(activeSite()).guides}`);
|
|
51
|
-
}
|
|
52
|
-
/** 英文帮助:命令列按 ASCII 宽度对齐(中文版靠 CJK 双宽,两边不能共用一套空格)。 */
|
|
53
|
-
function printHelpEn() {
|
|
54
|
-
const commands = [
|
|
55
|
-
["u1s1", "Start an interactive session (most common, no arguments)"],
|
|
56
|
-
["u1s1 -p \"prompt\"", "Answer once without the UI, handy in scripts"],
|
|
57
|
-
...launcherHelpRows(),
|
|
58
|
-
["u1s1 login / logout", "Sign in / sign out"],
|
|
59
|
-
["u1s1 model", "Show or change the default model"],
|
|
60
|
-
["u1s1 usage", "Show your remaining balance"],
|
|
61
|
-
["u1s1 whoami", "Show the signed-in account"],
|
|
62
|
-
["u1s1 update", "Update to the latest version"],
|
|
63
|
-
];
|
|
64
|
-
if (activeSite().id !== "intl") {
|
|
65
|
-
commands.push(["u1s1 deploy", "Publish a web page (--public / --private)"], ["u1s1 deploy list", "List published sites"], ["u1s1 deploy remove", "Remove a published site"]);
|
|
66
|
-
}
|
|
67
|
-
commands.push(["u1s1 feedback \"text\"", "Send feedback or report a bug (opens a support ticket; --bug/--question…)"], ["u1s1 import", "Import past sessions (import skills to import skills)"], ["u1s1 mcp", "Configure third-party MCP servers (add/list/test/remove/import)"], ["u1s1 bench", "Benchmark model coding ability"], ["u1s1 --version", "Show the version"]);
|
|
68
|
-
console.log(" Commands:");
|
|
69
|
-
for (const [cmd, desc] of commands)
|
|
70
|
-
console.log(` ${cmd.padEnd(22)} ${desc}`);
|
|
47
|
+
for (const [cmd, desc] of rows)
|
|
48
|
+
console.log(` ${cmd}${" ".repeat(Math.max(1, 24 - displayWidth(cmd)))}${desc}`);
|
|
71
49
|
console.log("");
|
|
72
|
-
console.log("
|
|
73
|
-
console.log("
|
|
74
|
-
console.log(
|
|
50
|
+
console.log(tr(" 其他命令: use · agents · config · whoami · import · mcp · bench · --version", " More: use · agents · config · whoami · import · mcp · bench · --version"));
|
|
51
|
+
console.log(tr(" 命令后加 --help 看用法;对话里输入 /help 看会话内命令", " Add --help to any command for details; in a session, type /help"));
|
|
52
|
+
console.log(tr(" 匿名使用统计不含对话内容,设 U1S1_TELEMETRY=0 关闭", " Anonymous usage stats never include content; set U1S1_TELEMETRY=0 to turn them off"));
|
|
53
|
+
console.log(tr(` 教程 → ${siteLinks(activeSite()).guides}`, ` Guides → ${siteLinks(activeSite()).guides}`));
|
|
75
54
|
}
|
|
76
55
|
function wantsHelp(rest) {
|
|
77
56
|
return rest.includes("--help") || rest.includes("-h");
|
|
@@ -110,15 +89,8 @@ async function whoamiCommand() {
|
|
|
110
89
|
console.error(e.message);
|
|
111
90
|
process.exit(1);
|
|
112
91
|
});
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
console.log(` Server ${cfg.baseUrl}`);
|
|
116
|
-
console.log(" Switch accounts: u1s1 logout, then u1s1 login. Check your balance: u1s1 usage");
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
console.log(me.email ?? "(未绑定邮箱)");
|
|
120
|
-
console.log(` 服务器 ${cfg.baseUrl}`);
|
|
121
|
-
console.log(" 换账号:u1s1 logout 再 u1s1 login;看额度:u1s1 usage");
|
|
92
|
+
// 换账号/看额度的指引在 whoami --help 里,这里只答「登的是谁、连的哪台服务器」
|
|
93
|
+
console.log(`${me.email ?? tr("(未绑定邮箱)", "(no email linked)")} ${new URL(cfg.baseUrl).host}`);
|
|
122
94
|
}
|
|
123
95
|
async function logoutCommand() {
|
|
124
96
|
const { saveConfig } = await import("./config.js");
|
|
@@ -165,9 +137,9 @@ const COMMANDS = {
|
|
|
165
137
|
await login(parsed.key, { site: parsed.site });
|
|
166
138
|
},
|
|
167
139
|
logout: logoutCommand,
|
|
168
|
-
usage: async () => {
|
|
140
|
+
usage: async (rest) => {
|
|
169
141
|
const { usage } = await import("./usage.js");
|
|
170
|
-
await usage();
|
|
142
|
+
await usage(rest);
|
|
171
143
|
},
|
|
172
144
|
whoami: whoamiCommand,
|
|
173
145
|
feedback: async (rest) => {
|
|
@@ -223,8 +195,7 @@ function rejectMistypedCommand(cmd, argCount) {
|
|
|
223
195
|
process.exitCode = 1;
|
|
224
196
|
return true;
|
|
225
197
|
}
|
|
226
|
-
console.error(tr(`不认识的命令「${cmd}
|
|
227
|
-
console.error(tr("查看全部命令:u1s1 --help;直接开始对话:运行 u1s1(不带参数)", "See all commands: u1s1 --help. To start a session, run u1s1 with no arguments."));
|
|
198
|
+
console.error(tr(`不认识的命令「${cmd}」,是想运行 u1s1 ${near} 吗?(全部命令:u1s1 --help)`, `Unknown command "${cmd}". Did you mean u1s1 ${near}? (All commands: u1s1 --help)`));
|
|
228
199
|
process.exitCode = 1;
|
|
229
200
|
return true;
|
|
230
201
|
}
|
|
@@ -284,8 +255,9 @@ const SIMPLE_COMMAND_HELP = {
|
|
|
284
255
|
" 退出本机登录,并注销这台设备在服务端的授权(连不上服务器时只清本机)。",
|
|
285
256
|
],
|
|
286
257
|
usage: [
|
|
287
|
-
"用法: u1s1 usage",
|
|
258
|
+
"用法: u1s1 usage [--json]",
|
|
288
259
|
" 查看剩余额度:免费包、已购用量包与余额。会话内也可输入 /usage。",
|
|
260
|
+
" --json 输出 JSON,给别的程序读(字段见 usage.ts 的 UsageJson)",
|
|
289
261
|
],
|
|
290
262
|
whoami: [
|
|
291
263
|
"用法: u1s1 whoami",
|
|
@@ -309,8 +281,9 @@ const SIMPLE_COMMAND_HELP_EN = {
|
|
|
309
281
|
" Sign out on this machine and revoke this device's authorization on the server (only clears local data if the server is unreachable).",
|
|
310
282
|
],
|
|
311
283
|
usage: [
|
|
312
|
-
"Usage: u1s1 usage",
|
|
284
|
+
"Usage: u1s1 usage [--json]",
|
|
313
285
|
" Show your remaining balance. In a session you can also type /usage.",
|
|
286
|
+
" --json Print JSON for other programs to read",
|
|
314
287
|
],
|
|
315
288
|
whoami: [
|
|
316
289
|
"Usage: u1s1 whoami",
|
package/dist/launcher.d.ts
CHANGED
|
@@ -42,7 +42,5 @@ export declare function resolveDispatch(args: readonly string[], ctx: DispatchCo
|
|
|
42
42
|
export declare function isExistingUser(raw: Record<string, unknown> | undefined, agentSettingsExists: boolean): boolean;
|
|
43
43
|
export declare function runLauncher(args: string[]): Promise<void>;
|
|
44
44
|
export declare function parseSelection(answer: string): AgentChoice | undefined;
|
|
45
|
-
/** `u1s1 --help`
|
|
46
|
-
export declare function
|
|
47
|
-
/** 英文版:与 index.ts 的命令表一起按 ASCII 宽度对齐。 */
|
|
48
|
-
export declare function launcherHelpRows(): [string, string][];
|
|
45
|
+
/** `u1s1 --help` 里启动器那一行;use / agents / config 只在「其他命令」里报名字,各自 --help 看用法。 */
|
|
46
|
+
export declare function launcherHelpRow(): [string, string];
|
package/dist/launcher.js
CHANGED
|
@@ -82,7 +82,7 @@ export async function runLauncher(args) {
|
|
|
82
82
|
case "select": {
|
|
83
83
|
const agent = await selectAgent();
|
|
84
84
|
saveDefaultAgent(agent);
|
|
85
|
-
console.log(tr(`
|
|
85
|
+
console.log(tr(` 已把 ${agent} 设为默认(以后换:u1s1 use <客户端>)`, ` ${agent} is now the default (change it with u1s1 use <client>)`));
|
|
86
86
|
await launch(agent, dispatch.args);
|
|
87
87
|
return;
|
|
88
88
|
}
|
|
@@ -110,15 +110,15 @@ async function launch(agent, args) {
|
|
|
110
110
|
const OPTIONS = [
|
|
111
111
|
{
|
|
112
112
|
agent: "pi",
|
|
113
|
-
line: () => tr("pi
|
|
113
|
+
line: () => tr("pi 内置,开箱即用(推荐)", "pi Built in, ready to go (recommended)"),
|
|
114
114
|
},
|
|
115
115
|
{
|
|
116
116
|
agent: "claude",
|
|
117
|
-
line: () => tr("claude
|
|
117
|
+
line: () => tr("claude 官方 Claude Code,首次使用时下载", "claude Official Claude Code, downloaded on first use"),
|
|
118
118
|
},
|
|
119
119
|
{
|
|
120
120
|
agent: "codex",
|
|
121
|
-
line: () => tr("codex
|
|
121
|
+
line: () => tr("codex 官方 Codex CLI,首次使用时下载", "codex Official Codex CLI, downloaded on first use"),
|
|
122
122
|
},
|
|
123
123
|
];
|
|
124
124
|
export function parseSelection(answer) {
|
|
@@ -132,7 +132,7 @@ export function parseSelection(answer) {
|
|
|
132
132
|
}
|
|
133
133
|
async function selectAgent() {
|
|
134
134
|
console.log("");
|
|
135
|
-
console.log(tr("
|
|
135
|
+
console.log(tr(" 选择客户端:", " Choose a client:"));
|
|
136
136
|
OPTIONS.forEach((o, i) => console.log(` ${i + 1}) ${o.line()}`));
|
|
137
137
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
138
138
|
try {
|
|
@@ -211,25 +211,7 @@ function agentsHelpLines() {
|
|
|
211
211
|
];
|
|
212
212
|
}
|
|
213
213
|
// ---------------------------------------------------------------- help
|
|
214
|
-
/** `u1s1 --help`
|
|
215
|
-
export function
|
|
216
|
-
return [
|
|
217
|
-
" u1s1 pi 进入内置的 Pi",
|
|
218
|
-
" u1s1 claude [参数] 启动官方 Claude Code(首次使用会下载)",
|
|
219
|
-
" u1s1 codex [参数] 启动官方 Codex CLI(首次使用会下载)",
|
|
220
|
-
" u1s1 use <客户端> 设置默认客户端(pi / claude / codex)",
|
|
221
|
-
" u1s1 agents 查看各客户端的安装状态、版本与更新",
|
|
222
|
-
" u1s1 config 设置默认客户端、各客户端的默认模型、自动更新",
|
|
223
|
-
];
|
|
224
|
-
}
|
|
225
|
-
/** 英文版:与 index.ts 的命令表一起按 ASCII 宽度对齐。 */
|
|
226
|
-
export function launcherHelpRows() {
|
|
227
|
-
return [
|
|
228
|
-
["u1s1 pi", "Open the built-in Pi"],
|
|
229
|
-
["u1s1 claude [args]", "Launch the official Claude Code (downloaded on first use)"],
|
|
230
|
-
["u1s1 codex [args]", "Launch the official Codex CLI (downloaded on first use)"],
|
|
231
|
-
["u1s1 use <client>", "Set the default client (pi / claude / codex)"],
|
|
232
|
-
["u1s1 agents", "Show each client's install status, version and updates"],
|
|
233
|
-
["u1s1 config", "Set the default client, each client's default model and auto-update"],
|
|
234
|
-
];
|
|
214
|
+
/** `u1s1 --help` 里启动器那一行;use / agents / config 只在「其他命令」里报名字,各自 --help 看用法。 */
|
|
215
|
+
export function launcherHelpRow() {
|
|
216
|
+
return ["u1s1 pi|claude|codex", tr("用指定客户端启动,后面的参数原样传入", "Start a specific client; extra args are passed through")];
|
|
235
217
|
}
|
package/dist/login.js
CHANGED
|
@@ -211,24 +211,18 @@ export async function login(keyArg, opts = {}) {
|
|
|
211
211
|
console.error(tr(" 稍等几分钟再运行 u1s1 login 重试即可。", " Wait a few minutes, then run u1s1 login again."));
|
|
212
212
|
}
|
|
213
213
|
else {
|
|
214
|
-
console.error(tr(" 连不上 u1s1
|
|
215
|
-
console.error(tr(" 如果网络正常,可能是 u1s1 版本太旧,运行 u1s1 update 升级后再试。", " If your network is fine, your u1s1 may be too old; run u1s1 update and try again."));
|
|
214
|
+
console.error(tr(" 连不上 u1s1 服务器,检查网络后重试;网络正常的话,运行 u1s1 update 升级后再试。", " Can't reach the u1s1 server. Check your network and try again; if it's fine, run u1s1 update and retry."));
|
|
216
215
|
}
|
|
217
216
|
process.exit(1);
|
|
218
217
|
}
|
|
219
|
-
console.log(tr("
|
|
220
|
-
console.log("");
|
|
218
|
+
console.log(tr(" 在浏览器里批准这台设备(没有自动打开的话,复制下面的网址):", " Approve this device in your browser (if it doesn't open, copy this URL):"));
|
|
221
219
|
console.log(` ${start.verify_url}`);
|
|
222
|
-
console.log("");
|
|
223
|
-
console.log(tr(" 正在尝试打开浏览器;如果没有自动打开,把上面这行网址复制到浏览器打开。", " Opening your browser; if it doesn't open, copy the URL above into your browser."));
|
|
224
|
-
console.log(tr(" 批准后,本机会用设备私钥为每次 API 请求签名。", " Once approved, this machine signs every API request with its device key."));
|
|
225
220
|
tryOpenBrowser(start.verify_url);
|
|
226
|
-
console.log("");
|
|
227
|
-
console.log(tr(" 等待浏览器里完成批准……(按 Ctrl+C 取消)", " Waiting for approval in the browser… (Ctrl+C to cancel)"));
|
|
221
|
+
console.log(tr(" 等待批准…(Ctrl+C 取消)", " Waiting for approval… (Ctrl+C to cancel)"));
|
|
228
222
|
const waitStartedAt = Date.now();
|
|
229
223
|
const waitTimer = setInterval(() => {
|
|
230
224
|
const mins = Math.round((Date.now() - waitStartedAt) / 60_000);
|
|
231
|
-
console.log(tr(` 仍在等待批准(已等 ${mins} 分钟)
|
|
225
|
+
console.log(tr(` 仍在等待批准(已等 ${mins} 分钟)…`, ` Still waiting for approval (${mins} min so far)…`));
|
|
232
226
|
}, 120_000);
|
|
233
227
|
const outcome = {};
|
|
234
228
|
try {
|
|
@@ -239,8 +233,7 @@ export async function login(keyArg, opts = {}) {
|
|
|
239
233
|
}
|
|
240
234
|
if (!credential) {
|
|
241
235
|
if (outcome.reason === "expired" || outcome.reason === "timeout") {
|
|
242
|
-
console.error(tr("
|
|
243
|
-
console.error(tr(" 重新运行 u1s1 会生成新的登录链接;网页打不开的话检查一下网络。", " Run u1s1 again for a new sign-in link; if the page won't load, check your network."));
|
|
236
|
+
console.error(tr(" 登录链接已过期,重新运行 u1s1 获取新链接(网页打不开的话检查一下网络)。", " The sign-in link expired. Run u1s1 again for a new one (if the page won't load, check your network)."));
|
|
244
237
|
}
|
|
245
238
|
else {
|
|
246
239
|
console.error(tr(" 没能取得设备凭证,请确认网络正常后重试。", " Couldn't get a device credential. Check your network and try again."));
|
|
@@ -252,8 +245,7 @@ export async function login(keyArg, opts = {}) {
|
|
|
252
245
|
saveConfig(next);
|
|
253
246
|
const me = await fetchMe(next).catch((e) => {
|
|
254
247
|
console.log(tr(" ✓ 设备已批准,登录凭证已保存。", " ✓ Device approved and credentials saved."));
|
|
255
|
-
console.error(tr(`
|
|
256
|
-
console.error(tr(" 稍后直接运行 u1s1 即可,不需要重新登录。", " Just run u1s1 later; no need to sign in again."));
|
|
248
|
+
console.error(tr(` 暂时没能确认账号信息(${e.message}),稍后直接运行 u1s1 即可,不用重新登录。`, ` Couldn't confirm your account details just now (${e.message}). Just run u1s1 later; no need to sign in again.`));
|
|
257
249
|
process.exit(0);
|
|
258
250
|
});
|
|
259
251
|
const tpu = me.tokens_per_usd ?? 0;
|
package/dist/model.js
CHANGED
|
@@ -16,8 +16,9 @@ export async function refreshOfficialModels(cfg, fetcher = fetchModels) {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
export async function modelCommand(nameOrAlias) {
|
|
19
|
-
// `u1s1 model --help` 不该被当成模型名报错;直接展示列表(自带切换说明)
|
|
20
|
-
|
|
19
|
+
// `u1s1 model --help` 不该被当成模型名报错;直接展示列表(自带切换说明),另加低频的自定义端点说明
|
|
20
|
+
const wantsHelp = nameOrAlias === "--help" || nameOrAlias === "-h";
|
|
21
|
+
if (wantsHelp)
|
|
21
22
|
nameOrAlias = undefined;
|
|
22
23
|
const cfg = loadConfig();
|
|
23
24
|
// Fetch both live catalogues before resolving the persisted default.
|
|
@@ -53,7 +54,7 @@ export async function modelCommand(nameOrAlias) {
|
|
|
53
54
|
if (CUSTOM_ENDPOINTS.length) {
|
|
54
55
|
console.log(tr(" 端点模型重名时可加限定:u1s1 model 端点名:模型id", " If endpoint model names clash, qualify them: u1s1 model <endpoint>:<model-id>"));
|
|
55
56
|
}
|
|
56
|
-
else {
|
|
57
|
+
else if (wantsHelp) {
|
|
57
58
|
console.log(tr(` 想接自己的 API(含本机 Ollama)?去 ${siteLinks(siteOf(cfg)).dashboard} 配「自定义模型端点」`, ` Want to use your own API (including local Ollama)? Add a custom model endpoint at ${siteLinks(siteOf(cfg)).dashboard}`));
|
|
58
59
|
}
|
|
59
60
|
console.log("");
|
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";
|
|
@@ -70,43 +70,33 @@ function groupPackages(packages) {
|
|
|
70
70
|
}
|
|
71
71
|
return [...groups.values()];
|
|
72
72
|
}
|
|
73
|
-
function packageScopeNote(pkg) {
|
|
74
|
-
if (pkg.kind === "login_checkin") {
|
|
75
|
-
return tr("全模型可用", "All models");
|
|
76
|
-
}
|
|
77
|
-
if (pkg.scope === "free") {
|
|
78
|
-
// 只有按日发放的免费包(首月/年度包的每日额度)才 0 点恢复;一次性免费包用完就没了。
|
|
79
|
-
return pkg.daily_tokens != null
|
|
80
|
-
? tr("免费包适用模型 · 每日额度北京时间 0 点恢复", "Free-tier models · daily quota resets at 00:00 Beijing time (UTC+8)")
|
|
81
|
-
: tr("免费包适用模型", "Free-tier models");
|
|
82
|
-
}
|
|
83
|
-
return tr("免费包适用模型", "Free-tier models");
|
|
84
|
-
}
|
|
85
73
|
function packageLabel(pkg) {
|
|
86
74
|
const count = pkg.count > 1 ? ` ×${pkg.count}` : "";
|
|
87
75
|
const label = `${packageLabels()[pkg.kind] ?? pkg.kind}${count}`;
|
|
88
76
|
// 中文靠全角空格对齐到第 13 列;英文标签长短不一,直接跟两个空格
|
|
89
77
|
return isEnglish() ? `${label} ` : label.padEnd(5, " ");
|
|
90
78
|
}
|
|
91
|
-
|
|
79
|
+
/**
|
|
80
|
+
* 每个包一行:标签、剩余/总量、进度条、到期日。适用范围只在打卡包(全模型可用)上标;
|
|
81
|
+
* 免费包的适用模型和「0 点恢复」在报告尾部说一次。
|
|
82
|
+
*/
|
|
83
|
+
function packageLine(pkg) {
|
|
92
84
|
const isDaily = pkg.daily_tokens != null;
|
|
93
85
|
const total = isDaily ? (pkg.daily_tokens ?? 0) : (pkg.total_tokens ?? 0);
|
|
94
86
|
const ratio = total > 0 ? pkg.remaining / total : 0;
|
|
95
87
|
const expiry = pkg.expires_at
|
|
96
88
|
? tr(`${pkg.expires_at.slice(0, 10)} 到期`, `expires ${pkg.expires_at.slice(0, 10)}`)
|
|
97
89
|
: tr("永不过期", "never expires");
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
` ${packageScopeNote(pkg)} · ${expiry}`,
|
|
101
|
-
];
|
|
90
|
+
const scope = pkg.kind === "login_checkin" ? tr(" · 全模型", " · all models") : "";
|
|
91
|
+
return tr(` ${packageLabel(pkg)} 还剩 ${fmtTokensCn(pkg.remaining)} / ${fmtTokensCn(total)}${isDaily ? "/天" : ""} ${bar(ratio)} ${expiry}${scope}`, ` ${packageLabel(pkg)} ${fmtTokensEn(pkg.remaining)} / ${fmtTokensEn(total)}${isDaily ? "/day" : ""} left ${bar(ratio)} ${expiry}${scope}`);
|
|
102
92
|
}
|
|
103
93
|
function packageUsageLines(me, tokensPerUsd, site) {
|
|
104
|
-
const packages = me.packages ?? [];
|
|
94
|
+
const packages = groupPackages(me.packages ?? []);
|
|
105
95
|
const lines = [];
|
|
106
96
|
if (packages.length === 0)
|
|
107
97
|
lines.push(tr(" 用量包 (无生效中的用量包)", " Packs (no active usage packs)"));
|
|
108
|
-
for (const pkg of
|
|
109
|
-
lines.push(
|
|
98
|
+
for (const pkg of packages)
|
|
99
|
+
lines.push(packageLine(pkg));
|
|
110
100
|
if (me.bonus_balance_usd > 0) {
|
|
111
101
|
const balance = tokensPerUsd > 0
|
|
112
102
|
? `${fmtTokens(me.bonus_balance_usd * tokensPerUsd)} Token`
|
|
@@ -118,10 +108,10 @@ function packageUsageLines(me, tokensPerUsd, site) {
|
|
|
118
108
|
: `$${me.mtd_usd.toFixed(2)}`;
|
|
119
109
|
lines.push(tr(` 本月已用 ${monthlyUsage}`, ` This month ${monthlyUsage}`));
|
|
120
110
|
lines.push("");
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
lines.push(tr("
|
|
124
|
-
lines.push("");
|
|
111
|
+
// 只有按日发放的免费包(首月/年度包的每日额度)才 0 点恢复;一次性免费包用完就没了,不写恢复
|
|
112
|
+
const hasDaily = packages.some((pkg) => pkg.scope === "free" && pkg.daily_tokens != null);
|
|
113
|
+
lines.push(tr(" 免费包只覆盖 DeepSeek Flash、GLM-5.3 Flash、Qwen3.8 Flash 和联网搜索", " Free packs cover DeepSeek Flash, GLM-5.3 Flash, Qwen3.8 Flash and web search"));
|
|
114
|
+
lines.push(tr(` ${hasDaily ? "每日额度北京时间 0 点恢复;" : ""}其他模型用余额或全模型包;Token 数为约数`, ` ${hasDaily ? "Daily quotas reset at 00:00 UTC+8; o" : "O"}ther models use your balance or an all-model pack; token counts are estimates`));
|
|
125
115
|
lines.push(...usageCtaLines(me, site));
|
|
126
116
|
return lines;
|
|
127
117
|
}
|
|
@@ -156,15 +146,8 @@ function legacyUsageLines(me, tokensPerUsd, site) {
|
|
|
156
146
|
*/
|
|
157
147
|
function intlUsageLines(me, site) {
|
|
158
148
|
const lines = [];
|
|
159
|
-
for (const pkg of groupPackages(me.packages ?? []))
|
|
160
|
-
|
|
161
|
-
const total = isDaily ? (pkg.daily_tokens ?? 0) : (pkg.total_tokens ?? 0);
|
|
162
|
-
const ratio = total > 0 ? pkg.remaining / total : 0;
|
|
163
|
-
const expiry = pkg.expires_at
|
|
164
|
-
? tr(`${pkg.expires_at.slice(0, 10)} 到期`, `expires ${pkg.expires_at.slice(0, 10)}`)
|
|
165
|
-
: tr("永不过期", "never expires");
|
|
166
|
-
lines.push(tr(` ${packageLabel(pkg)} 还剩 ${fmtTokensCn(pkg.remaining)} / ${fmtTokensCn(total)}${isDaily ? "/天" : ""} ${bar(ratio)} · ${expiry}`, ` ${packageLabel(pkg)} ${fmtTokensEn(pkg.remaining)} / ${fmtTokensEn(total)}${isDaily ? "/day" : ""} left ${bar(ratio)} · ${expiry}`));
|
|
167
|
-
}
|
|
149
|
+
for (const pkg of groupPackages(me.packages ?? []))
|
|
150
|
+
lines.push(packageLine(pkg));
|
|
168
151
|
lines.push(tr(` 余额 $${me.bonus_balance_usd.toFixed(2)}`, ` Balance $${me.bonus_balance_usd.toFixed(2)}`));
|
|
169
152
|
lines.push(tr(` 本月已用 $${me.mtd_usd.toFixed(2)}`, ` This month $${me.mtd_usd.toFixed(2)}`));
|
|
170
153
|
lines.push("");
|
|
@@ -221,8 +204,70 @@ export async function usageEntryData() {
|
|
|
221
204
|
return { error: e instanceof Error ? e.message : String(e) };
|
|
222
205
|
}
|
|
223
206
|
}
|
|
224
|
-
export
|
|
207
|
+
export function usageJson(me, site) {
|
|
208
|
+
const { dashboard, topup } = siteLinks(site);
|
|
209
|
+
return {
|
|
210
|
+
v: 1,
|
|
211
|
+
site: site.id,
|
|
212
|
+
email: me.email ?? null,
|
|
213
|
+
balance_usd: me.packages ? me.bonus_balance_usd : me.remaining_usd,
|
|
214
|
+
month_usd: me.mtd_usd,
|
|
215
|
+
tokens_per_usd: me.tokens_per_usd && me.tokens_per_usd > 0 ? me.tokens_per_usd : null,
|
|
216
|
+
packages: me.packages
|
|
217
|
+
? groupPackages(me.packages).map((pkg) => {
|
|
218
|
+
const daily = pkg.daily_tokens != null;
|
|
219
|
+
return {
|
|
220
|
+
kind: pkg.kind,
|
|
221
|
+
label: packageLabels()[pkg.kind] ?? pkg.kind,
|
|
222
|
+
scope: pkg.scope,
|
|
223
|
+
daily,
|
|
224
|
+
total_tokens: (daily ? pkg.daily_tokens : pkg.total_tokens) ?? 0,
|
|
225
|
+
remaining_tokens: pkg.remaining,
|
|
226
|
+
expires_at: pkg.expires_at,
|
|
227
|
+
count: pkg.count,
|
|
228
|
+
};
|
|
229
|
+
})
|
|
230
|
+
: null,
|
|
231
|
+
dashboard,
|
|
232
|
+
topup: me.pay_enabled === false ? null : topup,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
export function parseUsageArgs(rest) {
|
|
236
|
+
let json = false;
|
|
237
|
+
for (const a of rest) {
|
|
238
|
+
if (a === "--json")
|
|
239
|
+
json = true;
|
|
240
|
+
else
|
|
241
|
+
return { json, error: tr(`不认识的参数 ${a};用法: u1s1 usage [--json]`, `Unknown option ${a}. Usage: u1s1 usage [--json]`) };
|
|
242
|
+
}
|
|
243
|
+
return { json };
|
|
244
|
+
}
|
|
245
|
+
export async function usage(rest = []) {
|
|
246
|
+
const args = parseUsageArgs(rest);
|
|
247
|
+
if (args.error) {
|
|
248
|
+
console.error(args.error);
|
|
249
|
+
process.exitCode = 1;
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
225
252
|
const cfg = loadConfig();
|
|
253
|
+
if (args.json) {
|
|
254
|
+
const fail = (error, message) => {
|
|
255
|
+
console.log(JSON.stringify({ v: 1, error, message }));
|
|
256
|
+
process.exitCode = 1;
|
|
257
|
+
};
|
|
258
|
+
if (!hasDeviceCredential(cfg))
|
|
259
|
+
return fail("login", tr("还没登录,先跑 u1s1 login", "Not signed in. Run u1s1 login first."));
|
|
260
|
+
let me;
|
|
261
|
+
try {
|
|
262
|
+
me = await fetchMe(cfg);
|
|
263
|
+
}
|
|
264
|
+
catch (e) {
|
|
265
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
266
|
+
return fail(e instanceof AuthError ? "login" : "failed", message);
|
|
267
|
+
}
|
|
268
|
+
console.log(JSON.stringify(usageJson(me, siteOf(cfg))));
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
226
271
|
if (!hasDeviceCredential(cfg)) {
|
|
227
272
|
console.error(tr("还没登录,先跑 u1s1 login", "Not signed in. Run u1s1 login first."));
|
|
228
273
|
process.exit(1);
|